From 11f56a79af721fff2aab6f1472f6bf8297d5b605 Mon Sep 17 00:00:00 2001 From: Konstantin Belousov Date: Fri, 27 Oct 2023 06:53:49 +0300 Subject: [PATCH 01/73] freebsd: fix the 'df' command df, and perhaps other commands, get the list of the mounted filesystems with the call to getmntinfo(3). Since Rust still use FreeBSD 11.x ABI for filesystem metadata call, it should use matching versioned symbol for getmntinfo from libc. --- src/uucore/src/lib/features/fsext.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/uucore/src/lib/features/fsext.rs b/src/uucore/src/lib/features/fsext.rs index 52c079e2e..65f5a13b9 100644 --- a/src/uucore/src/lib/features/fsext.rs +++ b/src/uucore/src/lib/features/fsext.rs @@ -362,13 +362,19 @@ extern "C" { fn get_mount_info(mount_buffer_p: *mut *mut StatFs, flags: c_int) -> c_int; #[cfg(any( - target_os = "freebsd", target_os = "netbsd", target_os = "openbsd", all(target_vendor = "apple", target_arch = "aarch64") ))] #[link_name = "getmntinfo"] // spell-checker:disable-line fn get_mount_info(mount_buffer_p: *mut *mut StatFs, flags: c_int) -> c_int; + + // Rust on FreeBSD uses 11.x ABI for filesystem metadata syscalls. + // Call the right version of the symbol for getmntinfo() result to + // match libc StatFS layout. + #[cfg(target_os = "freebsd")] + #[link_name = "getmntinfo@FBSD_1.0"] // spell-checker:disable-line + fn get_mount_info(mount_buffer_p: *mut *mut StatFs, flags: c_int) -> c_int; } #[cfg(any(target_os = "linux", target_os = "android"))] From 9f63ae6645c21d7674397c0ece79e3ec6810c154 Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Fri, 27 Oct 2023 14:25:41 +0200 Subject: [PATCH 02/73] fsext: add getmntinfo to spell-checker:ignore --- src/uucore/src/lib/features/fsext.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/uucore/src/lib/features/fsext.rs b/src/uucore/src/lib/features/fsext.rs index 65f5a13b9..5c2121d69 100644 --- a/src/uucore/src/lib/features/fsext.rs +++ b/src/uucore/src/lib/features/fsext.rs @@ -5,7 +5,7 @@ //! Set of functions to manage file systems -// spell-checker:ignore DATETIME subsecond (arch) bitrig ; (fs) cifs smbfs +// spell-checker:ignore DATETIME getmntinfo subsecond (arch) bitrig ; (fs) cifs smbfs use time::macros::format_description; use time::UtcOffset; From e887944ef1eab809d262dd5e8896f558f24a60ff Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Sat, 28 Oct 2023 16:53:09 +0200 Subject: [PATCH 03/73] Remove "last synced with" comments --- src/uu/cat/src/cat.rs | 2 -- src/uu/env/src/env.rs | 2 -- src/uu/logname/src/logname.rs | 2 -- src/uu/printenv/src/printenv.rs | 2 -- src/uu/uname/src/uname.rs | 2 -- src/uu/unlink/src/unlink.rs | 2 -- src/uu/whoami/src/whoami.rs | 2 -- src/uu/yes/src/yes.rs | 2 -- 8 files changed, 16 deletions(-) diff --git a/src/uu/cat/src/cat.rs b/src/uu/cat/src/cat.rs index d49f4aa07..10e5d9ce1 100644 --- a/src/uu/cat/src/cat.rs +++ b/src/uu/cat/src/cat.rs @@ -4,8 +4,6 @@ // file that was distributed with this source code. // spell-checker:ignore (ToDO) nonprint nonblank nonprinting - -// last synced with: cat (GNU coreutils) 8.13 use clap::{crate_version, Arg, ArgAction, Command}; use std::fs::{metadata, File}; use std::io::{self, IsTerminal, Read, Write}; diff --git a/src/uu/env/src/env.rs b/src/uu/env/src/env.rs index d7c9687de..608357f50 100644 --- a/src/uu/env/src/env.rs +++ b/src/uu/env/src/env.rs @@ -3,8 +3,6 @@ // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. -/* last synced with: env (GNU coreutils) 8.13 */ - // spell-checker:ignore (ToDO) chdir execvp progname subcommand subcommands unsets setenv putenv spawnp SIGSEGV SIGBUS sigaction use clap::{crate_name, crate_version, Arg, ArgAction, Command}; diff --git a/src/uu/logname/src/logname.rs b/src/uu/logname/src/logname.rs index 52505d98d..55d4fec75 100644 --- a/src/uu/logname/src/logname.rs +++ b/src/uu/logname/src/logname.rs @@ -3,8 +3,6 @@ // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. -/* last synced with: logname (GNU coreutils) 8.22 */ - // spell-checker:ignore (ToDO) getlogin userlogin use clap::{crate_version, Command}; diff --git a/src/uu/printenv/src/printenv.rs b/src/uu/printenv/src/printenv.rs index cab24336f..47bd7c259 100644 --- a/src/uu/printenv/src/printenv.rs +++ b/src/uu/printenv/src/printenv.rs @@ -3,8 +3,6 @@ // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. -/* last synced with: printenv (GNU coreutils) 8.13 */ - use clap::{crate_version, Arg, ArgAction, Command}; use std::env; use uucore::{error::UResult, format_usage, help_about, help_usage}; diff --git a/src/uu/uname/src/uname.rs b/src/uu/uname/src/uname.rs index 73ab07a63..e6d5c3a0a 100644 --- a/src/uu/uname/src/uname.rs +++ b/src/uu/uname/src/uname.rs @@ -3,8 +3,6 @@ // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. -// last synced with: uname (GNU coreutils) 8.21 - // spell-checker:ignore (API) nodename osname sysname (options) mnrsv mnrsvo use clap::{crate_version, Arg, ArgAction, Command}; diff --git a/src/uu/unlink/src/unlink.rs b/src/uu/unlink/src/unlink.rs index 85e1ab4f5..4c9f2d829 100644 --- a/src/uu/unlink/src/unlink.rs +++ b/src/uu/unlink/src/unlink.rs @@ -3,8 +3,6 @@ // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. -/* last synced with: unlink (GNU coreutils) 8.21 */ - use std::ffi::OsString; use std::fs::remove_file; use std::path::Path; diff --git a/src/uu/whoami/src/whoami.rs b/src/uu/whoami/src/whoami.rs index 738f7509a..294c91328 100644 --- a/src/uu/whoami/src/whoami.rs +++ b/src/uu/whoami/src/whoami.rs @@ -3,8 +3,6 @@ // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. -/* last synced with: whoami (GNU coreutils) 8.21 */ - use std::ffi::OsString; use clap::{crate_version, Command}; diff --git a/src/uu/yes/src/yes.rs b/src/uu/yes/src/yes.rs index a58b73404..b1d8f9f49 100644 --- a/src/uu/yes/src/yes.rs +++ b/src/uu/yes/src/yes.rs @@ -3,8 +3,6 @@ // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. -/* last synced with: yes (GNU coreutils) 8.13 */ - // cSpell:ignore strs use clap::{builder::ValueParser, crate_version, Arg, ArgAction, Command}; From c24a51403a8e48af7fc3417a46bd6795f28bcedc Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Sat, 28 Oct 2023 22:29:24 +0200 Subject: [PATCH 04/73] cat: return the same error message as GNU with loop symlink (#5466) * cat: return the same error message as GNU with loop symlink Should fix tests/du/long-sloop.sh because it is using cat as a ref for error messages Co-authored-by: Daniel Hofstetter --- src/uu/cat/src/cat.rs | 22 ++++++++++++++++++++-- tests/by-util/test_cat.rs | 12 ++++++++++++ 2 files changed, 32 insertions(+), 2 deletions(-) diff --git a/src/uu/cat/src/cat.rs b/src/uu/cat/src/cat.rs index 10e5d9ce1..da47485cc 100644 --- a/src/uu/cat/src/cat.rs +++ b/src/uu/cat/src/cat.rs @@ -3,7 +3,7 @@ // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. -// spell-checker:ignore (ToDO) nonprint nonblank nonprinting +// spell-checker:ignore (ToDO) nonprint nonblank nonprinting ELOOP use clap::{crate_version, Arg, ArgAction, Command}; use std::fs::{metadata, File}; use std::io::{self, IsTerminal, Read, Write}; @@ -50,6 +50,8 @@ enum CatError { IsDirectory, #[error("input file is output file")] OutputIsInput, + #[error("Too many levels of symbolic links")] + TooManySymlinks, } type CatResult = Result; @@ -401,7 +403,23 @@ fn get_input_type(path: &str) -> CatResult { return Ok(InputType::StdIn); } - let ft = metadata(path)?.file_type(); + let ft = match metadata(path) { + Ok(md) => md.file_type(), + Err(e) => { + if let Some(raw_error) = e.raw_os_error() { + // On Unix-like systems, the error code for "Too many levels of symbolic links" is 40 (ELOOP). + // we want to provide a proper error message in this case. + #[cfg(not(target_os = "macos"))] + let too_many_symlink_code = 40; + #[cfg(target_os = "macos")] + let too_many_symlink_code = 62; + if raw_error == too_many_symlink_code { + return Err(CatError::TooManySymlinks); + } + } + return Err(CatError::Io(e)); + } + }; match ft { #[cfg(unix)] ft if ft.is_block_device() => Ok(InputType::BlockDevice), diff --git a/tests/by-util/test_cat.rs b/tests/by-util/test_cat.rs index 27f40356d..aa86ab066 100644 --- a/tests/by-util/test_cat.rs +++ b/tests/by-util/test_cat.rs @@ -540,3 +540,15 @@ fn test_write_to_self() { "first_file_content.second_file_content." ); } + +#[test] +#[cfg(unix)] +fn test_error_loop() { + let (at, mut ucmd) = at_and_ucmd!(); + at.symlink_file("2", "1"); + at.symlink_file("3", "2"); + at.symlink_file("1", "3"); + ucmd.arg("1") + .fails() + .stderr_is("cat: 1: Too many levels of symbolic links\n"); +} From 6a468e928d3ff0bb294b0e59d2afe300e4ce19c0 Mon Sep 17 00:00:00 2001 From: Zhuoxun Yang Date: Sun, 29 Oct 2023 18:54:40 +0800 Subject: [PATCH 05/73] fuzz: use parse_size_u64 --- fuzz/fuzz_targets/fuzz_parse_size.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/fuzz/fuzz_targets/fuzz_parse_size.rs b/fuzz/fuzz_targets/fuzz_parse_size.rs index 23b3b5ea4..d032adf06 100644 --- a/fuzz/fuzz_targets/fuzz_parse_size.rs +++ b/fuzz/fuzz_targets/fuzz_parse_size.rs @@ -1,10 +1,10 @@ #![no_main] use libfuzzer_sys::fuzz_target; -use uucore::parse_size::parse_size; +use uucore::parse_size::parse_size_u64; fuzz_target!(|data: &[u8]| { if let Ok(s) = std::str::from_utf8(data) { - _ = parse_size(s); + _ = parse_size_u64(s); } }); From a7cc3b6dcaee8f06cc7607e6c4e598c982b064ce Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Sun, 29 Oct 2023 14:37:47 +0100 Subject: [PATCH 06/73] cp: restrict two test functions to linux/mac/win --- tests/by-util/test_cp.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/by-util/test_cp.rs b/tests/by-util/test_cp.rs index c79367afb..07b52523a 100644 --- a/tests/by-util/test_cp.rs +++ b/tests/by-util/test_cp.rs @@ -3216,6 +3216,7 @@ fn test_cp_archive_on_directory_ending_dot() { } #[test] +#[cfg(any(target_os = "linux", target_os = "windows", target_os = "macos"))] fn test_cp_debug_default() { let ts = TestScenario::new(util_name!()); let at = &ts.fixtures; @@ -3243,6 +3244,7 @@ fn test_cp_debug_default() { } #[test] +#[cfg(any(target_os = "linux", target_os = "windows", target_os = "macos"))] fn test_cp_debug_multiple_default() { let ts = TestScenario::new(util_name!()); let at = &ts.fixtures; From d899787ba6f45044b0e29222bc10d6f09317053e Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sun, 29 Oct 2023 17:04:13 +0000 Subject: [PATCH 07/73] chore(deps): update rust crate procfs to 0.16 --- Cargo.lock | 40 +++++++++++++++------------------------- Cargo.toml | 2 +- 2 files changed, 16 insertions(+), 26 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 164d6d6b7..048b00263 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1207,12 +1207,6 @@ version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f7012b1bbb0719e1097c47611d3898568c546d597c2e74d66f6087edd5233ff4" -[[package]] -name = "linux-raw-sys" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f051f77a7c8e6957c0696eac88f26b0117e54f52d3fc682ab19397a8812846a4" - [[package]] name = "linux-raw-sys" version = "0.3.8" @@ -1598,15 +1592,25 @@ dependencies = [ [[package]] name = "procfs" -version = "0.15.1" +version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "943ca7f9f29bab5844ecd8fdb3992c5969b6622bb9609b9502fef9b4310e3f1f" +checksum = "731e0d9356b0c25f16f33b5be79b1c57b562f141ebfcdb0ad8ac2c13a24293b4" dependencies = [ - "bitflags 1.3.2", - "byteorder", + "bitflags 2.4.0", "hex", "lazy_static", - "rustix 0.36.16", + "procfs-core", + "rustix 0.38.21", +] + +[[package]] +name = "procfs-core" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d3554923a69f4ce04c4a754260c338f505ce22642d3830e049a399fc2059a29" +dependencies = [ + "bitflags 2.4.0", + "hex", ] [[package]] @@ -1822,20 +1826,6 @@ dependencies = [ "semver", ] -[[package]] -name = "rustix" -version = "0.36.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6da3636faa25820d8648e0e31c5d519bbb01f72fdf57131f0f5f7da5fed36eab" -dependencies = [ - "bitflags 1.3.2", - "errno", - "io-lifetimes", - "libc", - "linux-raw-sys 0.1.4", - "windows-sys 0.45.0", -] - [[package]] name = "rustix" version = "0.37.26" diff --git a/Cargo.toml b/Cargo.toml index afddd4985..a565370a4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -492,7 +492,7 @@ hex-literal = "0.4.1" rstest = { workspace = true } [target.'cfg(any(target_os = "linux", target_os = "android"))'.dev-dependencies] -procfs = { version = "0.15", default-features = false } +procfs = { version = "0.16", default-features = false } rlimit = "0.10.1" [target.'cfg(unix)'.dev-dependencies] From f39ab620a6f421acc9a21dd6c2526efc639a2759 Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Mon, 30 Oct 2023 07:14:26 +0100 Subject: [PATCH 08/73] cat: use error code 62 for ELOOP on FreeBSD --- src/uu/cat/src/cat.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/uu/cat/src/cat.rs b/src/uu/cat/src/cat.rs index da47485cc..a7a4c5f40 100644 --- a/src/uu/cat/src/cat.rs +++ b/src/uu/cat/src/cat.rs @@ -409,9 +409,9 @@ fn get_input_type(path: &str) -> CatResult { if let Some(raw_error) = e.raw_os_error() { // On Unix-like systems, the error code for "Too many levels of symbolic links" is 40 (ELOOP). // we want to provide a proper error message in this case. - #[cfg(not(target_os = "macos"))] + #[cfg(not(any(target_os = "macos", target_os = "freebsd")))] let too_many_symlink_code = 40; - #[cfg(target_os = "macos")] + #[cfg(any(target_os = "macos", target_os = "freebsd"))] let too_many_symlink_code = 62; if raw_error == too_many_symlink_code { return Err(CatError::TooManySymlinks); From 6e114fe2034f106400b835afdc49efdb5c9a6126 Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Mon, 30 Oct 2023 09:26:47 +0100 Subject: [PATCH 09/73] deny.toml: remove two entries from skip list rustix & linux-raw-sys --- deny.toml | 3 --- 1 file changed, 3 deletions(-) diff --git a/deny.toml b/deny.toml index fa8f77c01..03301ad7c 100644 --- a/deny.toml +++ b/deny.toml @@ -58,10 +58,7 @@ highlight = "all" # introduces it. # spell-checker: disable skip = [ - # procfs - { name = "rustix", version = "0.36.16" }, # rustix - { name = "linux-raw-sys", version = "0.1.4" }, { name = "linux-raw-sys", version = "0.3.8" }, # terminal_size { name = "rustix", version = "0.37.26" }, From a4775d288bc63b01142f1eeaa0d7b716c35fecc6 Mon Sep 17 00:00:00 2001 From: tommady Date: Tue, 31 Oct 2023 00:55:03 +0800 Subject: [PATCH 10/73] cp: fix cp -rT dir dir2 leads to different result than with GNU cp (#5467) * add a test case test_cp_treat_dest_as_a_normal_file * fix 5457 * cp: fix comment --------- Co-authored-by: Daniel Hofstetter --- src/uu/cp/src/copydir.rs | 18 +++++++++++++++--- tests/by-util/test_cp.rs | 16 ++++++++++++++++ 2 files changed, 31 insertions(+), 3 deletions(-) diff --git a/src/uu/cp/src/copydir.rs b/src/uu/cp/src/copydir.rs index a8b941364..763d66c0b 100644 --- a/src/uu/cp/src/copydir.rs +++ b/src/uu/cp/src/copydir.rs @@ -87,6 +87,9 @@ struct Context<'a> { /// The target path to which the directory will be copied. target: &'a Path, + + /// The source path from which the directory will be copied. + root: &'a Path, } impl<'a> Context<'a> { @@ -102,6 +105,7 @@ impl<'a> Context<'a> { current_dir, root_parent, target, + root, }) } } @@ -156,11 +160,19 @@ struct Entry { } impl Entry { - fn new(context: &Context, direntry: &DirEntry) -> Result { + fn new( + context: &Context, + direntry: &DirEntry, + no_target_dir: bool, + ) -> Result { let source_relative = direntry.path().to_path_buf(); let source_absolute = context.current_dir.join(&source_relative); - let descendant = + let mut descendant = get_local_to_root_parent(&source_absolute, context.root_parent.as_deref())?; + if no_target_dir { + descendant = descendant.strip_prefix(context.root)?.to_path_buf(); + } + let local_to_target = context.target.join(descendant); let target_is_file = context.target.is_file(); Ok(Self { @@ -389,7 +401,7 @@ pub(crate) fn copy_directory( { match direntry_result { Ok(direntry) => { - let entry = Entry::new(&context, &direntry)?; + let entry = Entry::new(&context, &direntry, options.no_target_dir)?; copy_direntry( progress_bar, entry, diff --git a/tests/by-util/test_cp.rs b/tests/by-util/test_cp.rs index c79367afb..565279a62 100644 --- a/tests/by-util/test_cp.rs +++ b/tests/by-util/test_cp.rs @@ -230,6 +230,22 @@ fn test_cp_arg_no_target_directory() { .stderr_contains("cannot overwrite directory"); } +#[test] +fn test_cp_arg_no_target_directory_with_recursive() { + let (at, mut ucmd) = at_and_ucmd!(); + + at.mkdir("dir"); + at.mkdir("dir2"); + at.touch("dir/a"); + at.touch("dir/b"); + + ucmd.arg("-rT").arg("dir").arg("dir2").succeeds(); + + assert!(at.plus("dir2").join("a").exists()); + assert!(at.plus("dir2").join("b").exists()); + assert!(!at.plus("dir2").join("dir").exists()); +} + #[test] fn test_cp_target_directory_is_file() { new_ucmd!() From 615b562b64f2d392975a697fa36748f5c2759f08 Mon Sep 17 00:00:00 2001 From: tommady Date: Tue, 31 Oct 2023 16:08:40 +0800 Subject: [PATCH 11/73] github action: split the run of the fuzzers (#5444) * fix-5443 by using strategy --- .github/workflows/fuzzing.yml | 74 ++++++++++++++++++++--------------- 1 file changed, 42 insertions(+), 32 deletions(-) diff --git a/.github/workflows/fuzzing.yml b/.github/workflows/fuzzing.yml index 311d6a0d7..e7a9cb1e3 100644 --- a/.github/workflows/fuzzing.yml +++ b/.github/workflows/fuzzing.yml @@ -13,52 +13,62 @@ concurrency: cancel-in-progress: ${{ github.ref != 'refs/heads/main' }} jobs: - fuzz: - name: Run the fuzzers + fuzz-build: + name: Build the fuzzers runs-on: ubuntu-latest - env: - RUN_FOR: 60 steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@nightly - name: Install `cargo-fuzz` run: cargo install cargo-fuzz - uses: Swatinem/rust-cache@v2 + with: + shared-key: "cargo-fuzz-cache-key" + cache-directories: "fuzz/target" + - name: Run `cargo-fuzz build` + run: cargo +nightly fuzz build + + fuzz-run: + needs: fuzz-build + name: Run the fuzzers + runs-on: ubuntu-latest + env: + RUN_FOR: 60 + strategy: + matrix: + test-target: + [ + fuzz_date, + fuzz_test, + fuzz_expr, + fuzz_parse_glob, + fuzz_parse_size, + fuzz_parse_time, + # adding more fuzz tests here. + # e.g. fuzz_test_a, + ] + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@nightly + - name: Install `cargo-fuzz` + run: cargo install cargo-fuzz + - uses: Swatinem/rust-cache@v2 + with: + shared-key: "cargo-fuzz-cache-key" + cache-directories: "fuzz/target" - name: Restore Cached Corpus uses: actions/cache/restore@v3 with: - key: corpus-cache + key: corpus-cache-${{ matrix.test-target }} path: | - fuzz/corpus - - name: Run fuzz_date for XX seconds - continue-on-error: true + fuzz/corpus/${{ matrix.test-target }} + - name: Run ${{ matrix.test-target }} for XX seconds shell: bash run: | - cargo +nightly fuzz run fuzz_date -- -max_total_time=${{ env.RUN_FOR }} -detect_leaks=0 - - name: Run fuzz_test for XX seconds - shell: bash - run: | - cargo +nightly fuzz run fuzz_test -- -max_total_time=${{ env.RUN_FOR }} -detect_leaks=0 - - name: Run fuzz_expr for XX seconds - continue-on-error: true - shell: bash - run: | - cargo +nightly fuzz run fuzz_expr -- -max_total_time=${{ env.RUN_FOR }} -detect_leaks=0 - - name: Run fuzz_parse_glob for XX seconds - shell: bash - run: | - cargo +nightly fuzz run fuzz_parse_glob -- -max_total_time=${{ env.RUN_FOR }} -detect_leaks=0 - - name: Run fuzz_parse_size for XX seconds - shell: bash - run: | - cargo +nightly fuzz run fuzz_parse_size -- -max_total_time=${{ env.RUN_FOR }} -detect_leaks=0 - - name: Run fuzz_parse_time for XX seconds - shell: bash - run: | - cargo +nightly fuzz run fuzz_parse_time -- -max_total_time=${{ env.RUN_FOR }} -detect_leaks=0 + cargo +nightly fuzz run ${{ matrix.test-target }} -- -max_total_time=${{ env.RUN_FOR }} -detect_leaks=0 - name: Save Corpus Cache uses: actions/cache/save@v3 with: - key: corpus-cache + key: corpus-cache-${{ matrix.test-target }} path: | - fuzz/corpus + fuzz/corpus/${{ matrix.test-target }} From a6522e011407bc098381cb7f601a69fdda15d71e Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Thu, 2 Nov 2023 16:15:18 +0100 Subject: [PATCH 12/73] `cp`: remove `crash!` call It seems to be unnecessary since we have already made the path relative using `construct_dest_path`. --- src/uu/cp/src/cp.rs | 38 +++++++++++++------------------------- 1 file changed, 13 insertions(+), 25 deletions(-) diff --git a/src/uu/cp/src/cp.rs b/src/uu/cp/src/cp.rs index dce35a8b9..7265e89f1 100644 --- a/src/uu/cp/src/cp.rs +++ b/src/uu/cp/src/cp.rs @@ -7,7 +7,6 @@ #![allow(clippy::extra_unused_lifetimes)] use quick_error::quick_error; -use std::borrow::Cow; use std::cmp::Ordering; use std::collections::{HashMap, HashSet}; use std::env; @@ -41,8 +40,8 @@ use uucore::{backup_control, update_control}; // requires these enum. pub use uucore::{backup_control::BackupMode, update_control::UpdateMode}; use uucore::{ - crash, format_usage, help_about, help_section, help_usage, prompt_yes, show_error, - show_warning, util_name, + format_usage, help_about, help_section, help_usage, prompt_yes, show_error, show_warning, + util_name, }; use crate::copydir::copy_directory; @@ -144,6 +143,7 @@ pub enum SparseMode { } /// The expected file type of copy target +#[derive(Copy, Clone)] pub enum TargetType { Directory, File, @@ -1195,7 +1195,7 @@ pub fn copy(sources: &[PathBuf], target: &Path, options: &Options) -> CopyResult &progress_bar, source, target, - &target_type, + target_type, options, &mut symlinked_files, &mut copied_files, @@ -1220,7 +1220,7 @@ pub fn copy(sources: &[PathBuf], target: &Path, options: &Options) -> CopyResult fn construct_dest_path( source_path: &Path, target: &Path, - target_type: &TargetType, + target_type: TargetType, options: &Options, ) -> CopyResult { if options.no_target_dir && target.is_dir() { @@ -1235,7 +1235,7 @@ fn construct_dest_path( return Err("with --parents, the destination must be a directory".into()); } - Ok(match *target_type { + Ok(match target_type { TargetType::Directory => { let root = if options.parents { Path::new("") @@ -1252,7 +1252,7 @@ fn copy_source( progress_bar: &Option, source: &Path, target: &Path, - target_type: &TargetType, + target_type: TargetType, options: &Options, symlinked_files: &mut HashSet, copied_files: &mut HashMap, @@ -1995,24 +1995,12 @@ fn copy_link( ) -> CopyResult<()> { // Here, we will copy the symlink itself (actually, just recreate it) let link = fs::read_link(source)?; - let dest: Cow<'_, Path> = if dest.is_dir() { - match source.file_name() { - Some(name) => dest.join(name).into(), - None => crash!( - EXIT_ERR, - "cannot stat {}: No such file or directory", - source.quote() - ), - } - } else { - // we always need to remove the file to be able to create a symlink, - // even if it is writeable. - if dest.is_symlink() || dest.is_file() { - fs::remove_file(dest)?; - } - dest.into() - }; - symlink_file(&link, &dest, &context_for(&link, &dest), symlinked_files) + // we always need to remove the file to be able to create a symlink, + // even if it is writeable. + if dest.is_symlink() || dest.is_file() { + fs::remove_file(dest)?; + } + symlink_file(&link, dest, &context_for(&link, dest), symlinked_files) } /// Generate an error message if `target` is not the correct `target_type` From 733359d48bd44b38af6f0fa3f24263f4bdd0e2d7 Mon Sep 17 00:00:00 2001 From: zhitkoff Date: Mon, 23 Oct 2023 17:44:47 -0400 Subject: [PATCH 13/73] split: refactor suffix auto-widening and auto-width --- src/uu/split/src/split.rs | 131 ++++++++++++++++++++++---------------- 1 file changed, 75 insertions(+), 56 deletions(-) diff --git a/src/uu/split/src/split.rs b/src/uu/split/src/split.rs index fff1ccb65..2d1701e60 100644 --- a/src/uu/split/src/split.rs +++ b/src/uu/split/src/split.rs @@ -668,17 +668,38 @@ impl Strategy { } } -/// Parse the suffix type, start and length from the command-line arguments. -/// Determine if the output file names suffix is allowed to auto-widen, -/// i.e. go beyond suffix_length, when more output files need to be written into. -/// Suffix auto-widening rules are: -/// - OFF when suffix length N is specified -/// `-a N` or `--suffix-length=N` -/// - OFF when suffix start number N is specified using long option with value +/// Parse the suffix type, start and length from the command-line arguments +/// as well suffix length auto-widening and auto-width scenarios +/// +/// Suffix auto-widening: Determine if the output file names suffix is allowed to dynamically auto-widen, +/// i.e. change (increase) suffix length dynamically as more files need to be written into. +/// Suffix length auto-widening rules are (in the order they are applied): +/// - ON by default +/// - OFF when suffix start N is specified via long option with a value /// `--numeric-suffixes=N` or `--hex-suffixes=N` -/// - Exception to the above: ON with `-n`/`--number` option (N, K/N, l/N, l/K/N, r/N, r/K/N) +/// - OFF when suffix length N is specified, except for N=0 (see edge cases below) +/// `-a N` or `--suffix-length=N` +/// - OFF if suffix length is auto pre-calculated (auto-width) +/// +/// Suffix auto-width: Determine if the the output file names suffix length should be automatically pre-calculated +/// based on number of files that need to written into, having number of files known upfront +/// Suffix length auto pre-calculation rules: +/// - Pre-calculate new suffix length when `-n`/`--number` option (N, K/N, l/N, l/K/N, r/N, r/K/N) +/// is used, where N is number of chunks = number of files to write into /// and suffix start < N number of files -/// - ON when suffix start number is NOT specified +/// as in `split --numeric-suffixes=1 --number=r/100 file` +/// - Do NOT pre-calculate new suffix length otherwise, i.e. when +/// suffix start >= N number of files +/// as in `split --numeric-suffixes=100 --number=r/100 file` +/// OR when suffix length N is specified, except for N=0 (see edge cases below) +/// `-a N` or `--suffix-length=N` +/// +/// Edge case: +/// - If suffix length is specified as 0 AND `-n`/`--number` option used specifying number of files: +/// set auto widening OFF AND auto pre-calculate required suffix length based on number of files needed +/// - If suffix length is specified as 0 in any other situation +/// keep auto widening ON and suffix length to default value +/// fn suffix_from( matches: &ArgMatches, strategy: &Strategy, @@ -701,18 +722,22 @@ fn suffix_from( ) { (true, _, _, _) => { suffix_type = SuffixType::Decimal; - let suffix_opt = matches.get_one::(OPT_NUMERIC_SUFFIXES); // if option was specified, but without value - this will return None as there is no default value - if suffix_opt.is_some() { - (suffix_start, suffix_auto_widening) = - handle_long_suffix_opt(suffix_opt.unwrap(), strategy, false)?; + let opt = matches.get_one::(OPT_NUMERIC_SUFFIXES); // if option was specified, but without value - this will return None as there is no default value + if opt.is_some() { + suffix_start = opt + .unwrap() + .parse::() + .map_err(|_| SettingsError::SuffixNotParsable(opt.unwrap().to_string()))?; + suffix_auto_widening = false; } } (_, true, _, _) => { suffix_type = SuffixType::Hexadecimal; - let suffix_opt = matches.get_one::(OPT_HEX_SUFFIXES); // if option was specified, but without value - this will return None as there is no default value - if suffix_opt.is_some() { - (suffix_start, suffix_auto_widening) = - handle_long_suffix_opt(suffix_opt.unwrap(), strategy, true)?; + let opt = matches.get_one::(OPT_HEX_SUFFIXES); // if option was specified, but without value - this will return None as there is no default value + if opt.is_some() { + suffix_start = usize::from_str_radix(opt.unwrap(), 16) + .map_err(|_| SettingsError::SuffixNotParsable(opt.unwrap().to_string()))?; + suffix_auto_widening = false; } } (_, _, true, _) => suffix_type = SuffixType::Decimal, // short numeric suffix '-d' @@ -720,17 +745,46 @@ fn suffix_from( _ => suffix_type = SuffixType::Alphabetic, // no numeric/hex suffix, using default alphabetic } + // Get suffix length (could be coming from command line of default value) let suffix_length_str = matches.get_one::(OPT_SUFFIX_LENGTH).unwrap(); // safe to unwrap here as there is default value for this option - let suffix_length: usize = suffix_length_str + let mut suffix_length: usize = suffix_length_str .parse() .map_err(|_| SettingsError::SuffixNotParsable(suffix_length_str.to_string()))?; - // Override suffix_auto_widening if suffix length value came from command line - // and not from default value - if matches.value_source(OPT_SUFFIX_LENGTH) == Some(ValueSource::CommandLine) { + // Disable dynamic auto-widening if suffix length was specified in command line with value > 0 + if matches.value_source(OPT_SUFFIX_LENGTH) == Some(ValueSource::CommandLine) + && suffix_length > 0 + { suffix_auto_widening = false; } + // Auto pre-calculate new suffix length (auto-width) if necessary + if let Strategy::Number(ref number_type) = strategy { + let chunks = number_type.num_chunks(); + let required_suffix_length = ((suffix_start as u64 + chunks) as f64) + .log(suffix_type.radix() as f64) + .ceil() as usize; + + if (suffix_start as u64) < chunks + && !(matches.value_source(OPT_SUFFIX_LENGTH) == Some(ValueSource::CommandLine) + && suffix_length > 0) + { + suffix_auto_widening = false; + suffix_length = required_suffix_length; + } + + if suffix_length < required_suffix_length { + return Err(SettingsError::SuffixTooSmall(required_suffix_length)); + } + } + + // Check suffix length == 0 edge case + // If it is still 0 at this point, then auto-width pre-calculation did not apply + // So, set it to default value and keep auto-widening ON + if suffix_length == 0 { + suffix_length = OPT_DEFAULT_SUFFIX_LENGTH.parse().unwrap(); + } + Ok(( suffix_type, suffix_start, @@ -739,30 +793,6 @@ fn suffix_from( )) } -/// Helper function to [`suffix_from`] function -fn handle_long_suffix_opt( - suffix_opt: &String, - strategy: &Strategy, - is_hex: bool, -) -> Result<(usize, bool), SettingsError> { - let suffix_start = if is_hex { - usize::from_str_radix(suffix_opt, 16) - .map_err(|_| SettingsError::SuffixNotParsable(suffix_opt.to_string()))? - } else { - suffix_opt - .parse::() - .map_err(|_| SettingsError::SuffixNotParsable(suffix_opt.to_string()))? - }; - - let suffix_auto_widening = if let Strategy::Number(ref number_type) = strategy { - let chunks = number_type.num_chunks(); - (suffix_start as u64) < chunks - } else { - false - }; - Ok((suffix_start, suffix_auto_widening)) -} - /// Parameters that control how a file gets split. /// /// You can convert an [`ArgMatches`] instance into a [`Settings`] @@ -877,17 +907,6 @@ impl Settings { let (suffix_type, suffix_start, suffix_auto_widening, suffix_length) = suffix_from(matches, &strategy)?; - if let Strategy::Number(ref number_type) = strategy { - let chunks = number_type.num_chunks(); - if !suffix_auto_widening { - 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)); - } - } - } - // Make sure that separator is only one UTF8 character (if specified) // defaults to '\n' - newline character // If the same separator (the same value) was used multiple times - `split` should NOT fail From fbb454a08014a2ac41e3eefccd19f897f6ecf9c4 Mon Sep 17 00:00:00 2001 From: zhitkoff Date: Mon, 23 Oct 2023 19:44:16 -0400 Subject: [PATCH 14/73] split: suffix auto-widening and auto-width tests --- src/uu/split/src/split.rs | 7 +++- tests/by-util/test_split.rs | 73 +++++++++++++++++++++++++++++++++++++ 2 files changed, 79 insertions(+), 1 deletion(-) diff --git a/src/uu/split/src/split.rs b/src/uu/split/src/split.rs index 2d1701e60..4282f1433 100644 --- a/src/uu/split/src/split.rs +++ b/src/uu/split/src/split.rs @@ -769,8 +769,13 @@ fn suffix_from( && !(matches.value_source(OPT_SUFFIX_LENGTH) == Some(ValueSource::CommandLine) && suffix_length > 0) { + // with auto-width ON the auto-widening is OFF suffix_auto_widening = false; - suffix_length = required_suffix_length; + + // do not reduce suffix length with auto-width + if suffix_length < required_suffix_length { + suffix_length = required_suffix_length; + } } if suffix_length < required_suffix_length { diff --git a/tests/by-util/test_split.rs b/tests/by-util/test_split.rs index 5760be560..3ebadde4d 100644 --- a/tests/by-util/test_split.rs +++ b/tests/by-util/test_split.rs @@ -824,6 +824,79 @@ fn test_hex_dynamic_suffix_length() { assert_eq!(file_read(&at, "xf000"), "a"); } +/// Test for dynamic suffix length (auto-widening) disabled when suffix start number is specified +#[test] +fn test_dynamic_suffix_length_off_with_suffix_start() { + new_ucmd!() + .args(&["-b", "1", "--numeric-suffixes=89", "ninetyonebytes.txt"]) + .fails() + .stderr_only("split: output file suffixes exhausted\n"); +} + +/// Test for dynamic suffix length (auto-widening) enabled when suffix start number is NOT specified +#[test] +fn test_dynamic_suffix_length_on_with_suffix_start_no_value() { + let (at, mut ucmd) = at_and_ucmd!(); + ucmd.args(&["-b", "1", "--numeric-suffixes", "ninetyonebytes.txt"]) + .succeeds(); + assert_eq!(file_read(&at, "x9000"), "a"); +} + +/// Test for suffix auto-width with --number strategy and suffix start number +#[test] +fn test_suffix_auto_width_with_number() { + let (at, mut ucmd) = at_and_ucmd!(); + ucmd.args(&["--numeric-suffixes=1", "--number=r/100", "fivelines.txt"]) + .succeeds(); + let glob = Glob::new(&at, ".", r"x\d\d\d$"); + assert_eq!(glob.count(), 100); + assert_eq!(glob.collate(), at.read_bytes("fivelines.txt")); + assert_eq!(file_read(&at, "x001"), "1\n"); + assert_eq!(file_read(&at, "x100"), ""); + + new_ucmd!() + .args(&["--numeric-suffixes=100", "--number=r/100", "fivelines.txt"]) + .fails(); +} + +/// Test for edge case of specifying 0 for suffix length +#[test] +fn test_suffix_length_zero() { + let (at, mut ucmd) = at_and_ucmd!(); + ucmd.args(&[ + "--numeric-suffixes=1", + "--number=r/100", + "-a", + "0", + "fivelines.txt", + ]) + .succeeds(); + let glob = Glob::new(&at, ".", r"x\d\d\d$"); + assert_eq!(glob.count(), 100); + + new_ucmd!() + .args(&[ + "--numeric-suffixes=100", + "--number=r/100", + "-a", + "0", + "fivelines.txt", + ]) + .fails(); + + new_ucmd!() + .args(&[ + "-b", + "1", + "--numeric-suffixes=89", + "-a", + "0", + "ninetyonebytes.txt", + ]) + .fails() + .stderr_only("split: output file suffixes exhausted\n"); +} + #[test] fn test_suffixes_exhausted() { new_ucmd!() From 8372a3d2ccda7e845fe4b886f20f53b9888c6993 Mon Sep 17 00:00:00 2001 From: zhitkoff Date: Wed, 25 Oct 2023 20:35:35 -0400 Subject: [PATCH 15/73] split: refactor filename suffix --- src/uu/split/src/filenames.rs | 374 +++++++++++++++++---- src/uu/split/src/number.rs | 2 +- src/uu/split/src/split.rs | 614 ++-------------------------------- src/uu/split/src/strategy.rs | 379 +++++++++++++++++++++ 4 files changed, 715 insertions(+), 654 deletions(-) create mode 100644 src/uu/split/src/strategy.rs diff --git a/src/uu/split/src/filenames.rs b/src/uu/split/src/filenames.rs index e6a9f19b2..e776b274b 100644 --- a/src/uu/split/src/filenames.rs +++ b/src/uu/split/src/filenames.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 zaaa zaab +// spell-checker:ignore zaaa zaab stype //! Compute filenames from a given index. //! //! The [`FilenameIterator`] yields filenames for use with ``split``. @@ -16,18 +16,31 @@ //! use crate::filenames::SuffixType; //! //! let prefix = "chunk_".to_string(); -//! let suffix = ".txt".to_string(); -//! let width = 2; -//! let suffix_type = SuffixType::Alphabetic; -//! let it = FilenameIterator::new(prefix, suffix, width, suffix_type); +//! let suffix = Suffix { +//! stype: SuffixType::Alphabetic, +//! length: 2, +//! start: 0, +//! auto_widening: true, +//! additional: ".txt".to_string(), +//! }; +//! let it = FilenameIterator::new(prefix, suffix); //! //! assert_eq!(it.next().unwrap(), "chunk_aa.txt"); //! assert_eq!(it.next().unwrap(), "chunk_ab.txt"); //! assert_eq!(it.next().unwrap(), "chunk_ac.txt"); //! ``` + use crate::number::DynamicWidthNumber; use crate::number::FixedWidthNumber; use crate::number::Number; +use crate::strategy::Strategy; +use crate::{ + OPT_ADDITIONAL_SUFFIX, OPT_HEX_SUFFIXES, OPT_HEX_SUFFIXES_SHORT, OPT_NUMERIC_SUFFIXES, + OPT_NUMERIC_SUFFIXES_SHORT, OPT_SUFFIX_LENGTH, +}; +use clap::ArgMatches; +use std::fmt; +use uucore::display::Quotable; use uucore::error::{UResult, USimpleError}; /// The format to use for suffixes in the filename for each output chunk. @@ -54,21 +67,200 @@ impl SuffixType { } } +/// Filename suffix parameters +#[derive(Clone)] +pub struct Suffix { + stype: SuffixType, + length: usize, + start: usize, + auto_widening: bool, + additional: String, +} + +/// An error when parsing suffix parameters from command-line arguments. +pub enum SuffixError { + /// Invalid suffix length parameter. + NotParsable(String), + + /// Suffix contains a directory separator, which is not allowed. + ContainsSeparator(String), + + /// Suffix is not large enough to split into specified chunks + TooSmall(usize), +} + +impl fmt::Display for SuffixError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + Self::NotParsable(s) => write!(f, "invalid suffix length: {}", s.quote()), + Self::TooSmall(i) => write!(f, "the suffix length needs to be at least {i}"), + Self::ContainsSeparator(s) => write!( + f, + "invalid suffix {}, contains directory separator", + s.quote() + ), + } + } +} + +impl Suffix { + /// Parse the suffix type, start, length and additional suffix from the command-line arguments + /// as well process suffix length auto-widening and auto-width scenarios + /// + /// Suffix auto-widening: Determine if the output file names suffix is allowed to dynamically auto-widen, + /// i.e. change (increase) suffix length dynamically as more files need to be written into. + /// Suffix length auto-widening rules are (in the order they are applied): + /// - ON by default + /// - OFF when suffix start N is specified via long option with a value + /// `--numeric-suffixes=N` or `--hex-suffixes=N` + /// - OFF when suffix length N is specified, except for N=0 (see edge cases below) + /// `-a N` or `--suffix-length=N` + /// - OFF if suffix length is auto pre-calculated (auto-width) + /// + /// Suffix auto-width: Determine if the the output file names suffix length should be automatically pre-calculated + /// based on number of files that need to written into, having number of files known upfront + /// Suffix length auto pre-calculation rules: + /// - Pre-calculate new suffix length when `-n`/`--number` option (N, K/N, l/N, l/K/N, r/N, r/K/N) + /// is used, where N is number of chunks = number of files to write into + /// and suffix start < N number of files + /// as in `split --numeric-suffixes=1 --number=r/100 file` + /// - Do NOT pre-calculate new suffix length otherwise, i.e. when + /// suffix start >= N number of files + /// as in `split --numeric-suffixes=100 --number=r/100 file` + /// OR when suffix length N is specified, except for N=0 (see edge cases below) + /// `-a N` or `--suffix-length=N` + /// + /// Edge case: + /// - If suffix length is specified as 0 in a command line, + /// first apply auto-width calculations and if still 0 + /// set it to default value. + /// Do NOT change auto-widening value + /// + pub fn from(matches: &ArgMatches, strategy: &Strategy) -> Result { + let stype: SuffixType; + + // Defaults + let mut start = 0; + let mut auto_widening = true; + let default_length: usize = 2; + + // Check if the user is specifying one or more than one suffix + // Any combination of suffixes is allowed + // Since all suffixes are setup with 'overrides_with_all()' against themselves and each other, + // last one wins, all others are ignored + match ( + matches.contains_id(OPT_NUMERIC_SUFFIXES), + matches.contains_id(OPT_HEX_SUFFIXES), + matches.get_flag(OPT_NUMERIC_SUFFIXES_SHORT), + matches.get_flag(OPT_HEX_SUFFIXES_SHORT), + ) { + (true, _, _, _) => { + stype = SuffixType::Decimal; + // if option was specified, but without value - this will return None as there is no default value + if let Some(opt) = matches.get_one::(OPT_NUMERIC_SUFFIXES) { + start = opt + .parse::() + .map_err(|_| SuffixError::NotParsable(opt.to_string()))?; + auto_widening = false; + } + } + (_, true, _, _) => { + stype = SuffixType::Hexadecimal; + // if option was specified, but without value - this will return None as there is no default value + if let Some(opt) = matches.get_one::(OPT_HEX_SUFFIXES) { + start = usize::from_str_radix(opt, 16) + .map_err(|_| SuffixError::NotParsable(opt.to_string()))?; + auto_widening = false; + } + } + (_, _, true, _) => stype = SuffixType::Decimal, // short numeric suffix '-d' + (_, _, _, true) => stype = SuffixType::Hexadecimal, // short hex suffix '-x' + _ => stype = SuffixType::Alphabetic, // no numeric/hex suffix, using default alphabetic + } + + // Get suffix length and a flag to indicate if it was specified with command line option + let (mut length, is_length_cmd_opt) = + if let Some(v) = matches.get_one::(OPT_SUFFIX_LENGTH) { + // suffix length was specified in command line + ( + v.parse::() + .map_err(|_| SuffixError::NotParsable(v.to_string()))?, + true, + ) + } else { + // no suffix length option was specified in command line + // set to default value + (default_length, false) + }; + + // Disable dynamic auto-widening if suffix length was specified in command line with value > 0 + if is_length_cmd_opt && length > 0 { + auto_widening = false; + } + + // Auto pre-calculate new suffix length (auto-width) if necessary + if let Strategy::Number(ref number_type) = strategy { + let chunks = number_type.num_chunks(); + let required_length = ((start as u64 + chunks) as f64) + .log(stype.radix() as f64) + .ceil() as usize; + + if (start as u64) < chunks && !(is_length_cmd_opt && length > 0) { + // with auto-width ON the auto-widening is OFF + auto_widening = false; + + // do not reduce suffix length with auto-width + if length < required_length { + length = required_length; + } + } + + if length < required_length { + return Err(SuffixError::TooSmall(required_length)); + } + } + + // Check edge case when suffix length == 0 was specified in command line + // Set it to default value + if is_length_cmd_opt && length == 0 { + length = default_length; + } + + let additional = matches + .get_one::(OPT_ADDITIONAL_SUFFIX) + .unwrap() + .to_string(); + if additional.contains('/') { + return Err(SuffixError::ContainsSeparator(additional)); + } + + let result = Self { + stype, + length, + start, + auto_widening, + additional, + }; + + Ok(result) + } +} + /// Compute filenames from a given index. /// /// This iterator yields filenames for use with ``split``. /// /// The `prefix` is prepended to each filename and the -/// `additional_suffix1` is appended to each filename. +/// `suffix.additional` is appended to each filename. /// -/// If `suffix_length` is 0, then the variable portion of the filename +/// If `suffix.auto_widening` is true, then the variable portion of the filename /// that identifies the current chunk will have a dynamically -/// increasing width. If `suffix_length` is greater than zero, then -/// the variable portion of the filename will always be exactly that +/// increasing width. If `suffix.auto_widening` is false, then +/// the variable portion of the filename will always be exactly `suffix.length` /// width in characters. In that case, after the iterator yields each /// string of that width, the iterator is exhausted. /// -/// Finally, `suffix_type` controls which type of suffix to produce, +/// Finally, `suffix.stype` controls which type of suffix to produce, /// alphabetic or numeric. /// /// # Examples @@ -81,10 +273,14 @@ impl SuffixType { /// use crate::filenames::SuffixType; /// /// let prefix = "chunk_".to_string(); -/// let suffix = ".txt".to_string(); -/// let width = 2; -/// let suffix_type = SuffixType::Alphabetic; -/// let it = FilenameIterator::new(prefix, suffix, width, suffix_type); +/// let suffix = Suffix { +/// stype: SuffixType::Alphabetic, +/// length: 2, +/// start: 0, +/// auto_widening: true, +/// additional: ".txt".to_string(), +/// }; +/// let it = FilenameIterator::new(prefix, suffix); /// /// assert_eq!(it.next().unwrap(), "chunk_aa.txt"); /// assert_eq!(it.next().unwrap(), "chunk_ab.txt"); @@ -98,37 +294,34 @@ impl SuffixType { /// use crate::filenames::SuffixType; /// /// let prefix = "chunk_".to_string(); -/// let suffix = ".txt".to_string(); -/// let width = 2; -/// let suffix_type = SuffixType::Decimal; -/// let it = FilenameIterator::new(prefix, suffix, width, suffix_type); +/// let suffix = Suffix { +/// stype: SuffixType::Decimal, +/// length: 2, +/// start: 0, +/// auto_widening: true, +/// additional: ".txt".to_string(), +/// }; +/// let it = FilenameIterator::new(prefix, suffix); /// /// assert_eq!(it.next().unwrap(), "chunk_00.txt"); /// assert_eq!(it.next().unwrap(), "chunk_01.txt"); /// assert_eq!(it.next().unwrap(), "chunk_02.txt"); /// ``` pub struct FilenameIterator<'a> { - additional_suffix: &'a str, prefix: &'a str, + additional_suffix: &'a str, number: Number, first_iteration: bool, } impl<'a> FilenameIterator<'a> { - pub fn new( - prefix: &'a str, - additional_suffix: &'a str, - suffix_length: usize, - suffix_type: SuffixType, - suffix_start: usize, - suffix_auto_widening: bool, - ) -> UResult> { - let radix = suffix_type.radix(); - let number = if suffix_auto_widening { - Number::DynamicWidth(DynamicWidthNumber::new(radix, suffix_start)) + pub fn new(prefix: &'a str, suffix: &'a Suffix) -> UResult> { + let radix = suffix.stype.radix(); + let number = if suffix.auto_widening { + Number::DynamicWidth(DynamicWidthNumber::new(radix, suffix.start)) } else { Number::FixedWidth( - FixedWidthNumber::new(radix, suffix_length, suffix_start).map_err(|_| { + FixedWidthNumber::new(radix, suffix.length, suffix.start).map_err(|_| { USimpleError::new( 1, "numerical suffix start value is too large for the suffix length", @@ -136,6 +329,7 @@ impl<'a> FilenameIterator<'a> { })?, ) }; + let additional_suffix = suffix.additional.as_str(); Ok(FilenameIterator { prefix, @@ -168,46 +362,62 @@ impl<'a> Iterator for FilenameIterator<'a> { mod tests { use crate::filenames::FilenameIterator; + use crate::filenames::Suffix; use crate::filenames::SuffixType; #[test] fn test_filename_iterator_alphabetic_fixed_width() { - let mut it = - FilenameIterator::new("chunk_", ".txt", 2, SuffixType::Alphabetic, 0, false).unwrap(); + let suffix = Suffix { + stype: SuffixType::Alphabetic, + length: 2, + start: 0, + auto_widening: false, + additional: ".txt".to_string(), + }; + let mut it = FilenameIterator::new("chunk_", &suffix).unwrap(); 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, SuffixType::Alphabetic, 0, false).unwrap(); + let mut it = FilenameIterator::new("chunk_", &suffix).unwrap(); 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, SuffixType::Decimal, 0, false).unwrap(); + let suffix = Suffix { + stype: SuffixType::Decimal, + length: 2, + start: 0, + auto_widening: false, + additional: ".txt".to_string(), + }; + let mut it = FilenameIterator::new("chunk_", &suffix).unwrap(); 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::Decimal, 0, false).unwrap(); + let mut it = FilenameIterator::new("chunk_", &suffix).unwrap(); 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", 2, SuffixType::Alphabetic, 0, true).unwrap(); + let suffix = Suffix { + stype: SuffixType::Alphabetic, + length: 2, + start: 0, + auto_widening: true, + additional: ".txt".to_string(), + }; + let mut it = FilenameIterator::new("chunk_", &suffix).unwrap(); 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, SuffixType::Alphabetic, 0, true).unwrap(); + let mut it = FilenameIterator::new("chunk_", &suffix).unwrap(); 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"); @@ -215,54 +425,96 @@ mod tests { #[test] fn test_filename_iterator_numeric_dynamic_width() { - let mut it = - FilenameIterator::new("chunk_", ".txt", 2, SuffixType::Decimal, 0, true).unwrap(); + let suffix = Suffix { + stype: SuffixType::Decimal, + length: 2, + start: 0, + auto_widening: true, + additional: ".txt".to_string(), + }; + let mut it = FilenameIterator::new("chunk_", &suffix).unwrap(); 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::Decimal, 0, true).unwrap(); + let mut it = FilenameIterator::new("chunk_", &suffix).unwrap(); 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"); } #[test] - fn test_filename_iterator_numeric_suffix_decimal() { - let mut it = - FilenameIterator::new("chunk_", ".txt", 2, SuffixType::Decimal, 5, true).unwrap(); + fn test_filename_iterator_numeric_decimal() { + let suffix = Suffix { + stype: SuffixType::Decimal, + length: 2, + start: 5, + auto_widening: true, + additional: ".txt".to_string(), + }; + let mut it = FilenameIterator::new("chunk_", &suffix).unwrap(); assert_eq!(it.next().unwrap(), "chunk_05.txt"); assert_eq!(it.next().unwrap(), "chunk_06.txt"); assert_eq!(it.next().unwrap(), "chunk_07.txt"); } #[test] - fn test_filename_iterator_numeric_suffix_hex() { - let mut it = - FilenameIterator::new("chunk_", ".txt", 2, SuffixType::Hexadecimal, 9, true).unwrap(); + fn test_filename_iterator_numeric_hex() { + let suffix = Suffix { + stype: SuffixType::Hexadecimal, + length: 2, + start: 9, + auto_widening: true, + additional: ".txt".to_string(), + }; + let mut it = FilenameIterator::new("chunk_", &suffix).unwrap(); assert_eq!(it.next().unwrap(), "chunk_09.txt"); assert_eq!(it.next().unwrap(), "chunk_0a.txt"); assert_eq!(it.next().unwrap(), "chunk_0b.txt"); } #[test] - fn test_filename_iterator_numeric_suffix_err() { - let mut it = - FilenameIterator::new("chunk_", ".txt", 3, SuffixType::Decimal, 999, false).unwrap(); + fn test_filename_iterator_numeric_err() { + let suffix = Suffix { + stype: SuffixType::Decimal, + length: 3, + start: 999, + auto_widening: false, + additional: ".txt".to_string(), + }; + let mut it = FilenameIterator::new("chunk_", &suffix).unwrap(); assert_eq!(it.next().unwrap(), "chunk_999.txt"); assert!(it.next().is_none()); - let it = FilenameIterator::new("chunk_", ".txt", 3, SuffixType::Decimal, 1000, false); + let suffix = Suffix { + stype: SuffixType::Decimal, + length: 3, + start: 1000, + auto_widening: false, + additional: ".txt".to_string(), + }; + let it = FilenameIterator::new("chunk_", &suffix); assert!(it.is_err()); - let mut it = - FilenameIterator::new("chunk_", ".txt", 3, SuffixType::Hexadecimal, 0xfff, false) - .unwrap(); + let suffix = Suffix { + stype: SuffixType::Hexadecimal, + length: 3, + start: 0xfff, + auto_widening: false, + additional: ".txt".to_string(), + }; + let mut it = FilenameIterator::new("chunk_", &suffix).unwrap(); assert_eq!(it.next().unwrap(), "chunk_fff.txt"); assert!(it.next().is_none()); - let it = FilenameIterator::new("chunk_", ".txt", 3, SuffixType::Hexadecimal, 0x1000, false); + let suffix = Suffix { + stype: SuffixType::Hexadecimal, + length: 3, + start: 0x1000, + auto_widening: false, + additional: ".txt".to_string(), + }; + let it = FilenameIterator::new("chunk_", &suffix); assert!(it.is_err()); } } diff --git a/src/uu/split/src/number.rs b/src/uu/split/src/number.rs index a01701c80..6312d0a3f 100644 --- a/src/uu/split/src/number.rs +++ b/src/uu/split/src/number.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 zaaa zaab +// spell-checker:ignore zaaa zaab feff //! A number in arbitrary radix expressed in a positional notation. //! //! Use the [`Number`] enum to represent an arbitrary number in an diff --git a/src/uu/split/src/split.rs b/src/uu/split/src/split.rs index 4282f1433..4b5cd9207 100644 --- a/src/uu/split/src/split.rs +++ b/src/uu/split/src/split.rs @@ -8,9 +8,10 @@ mod filenames; mod number; mod platform; +mod strategy; -use crate::filenames::FilenameIterator; -use crate::filenames::SuffixType; +use crate::filenames::{FilenameIterator, Suffix, SuffixError}; +use crate::strategy::{NumberType, Strategy, StrategyError}; use clap::{crate_version, parser::ValueSource, Arg, ArgAction, ArgMatches, Command, ValueHint}; use std::env; use std::ffi::OsString; @@ -22,7 +23,7 @@ use std::path::Path; use std::u64; use uucore::display::Quotable; use uucore::error::{FromIo, UIoError, UResult, USimpleError, UUsageError}; -use uucore::parse_size::{parse_size_u64, parse_size_u64_max, ParseSizeError}; + use uucore::uio_error; use uucore::{format_usage, help_about, help_section, help_usage}; @@ -37,8 +38,6 @@ static OPT_NUMERIC_SUFFIXES_SHORT: &str = "-d"; static OPT_HEX_SUFFIXES: &str = "hex-suffixes"; static OPT_HEX_SUFFIXES_SHORT: &str = "-x"; static OPT_SUFFIX_LENGTH: &str = "suffix-length"; -// If no suffix length is specified, default to "2" characters following GNU split behavior -static OPT_DEFAULT_SUFFIX_LENGTH: &str = "2"; static OPT_VERBOSE: &str = "verbose"; static OPT_SEPARATOR: &str = "separator"; //The ---io and ---io-blksize parameters are consumed and ignored. @@ -357,7 +356,6 @@ pub fn uu_app() -> Command { .long(OPT_SUFFIX_LENGTH) .allow_hyphen_values(true) .value_name("N") - .default_value(OPT_DEFAULT_SUFFIX_LENGTH) .help("generate suffixes of length N (default 2)"), ) .arg( @@ -398,418 +396,13 @@ pub fn uu_app() -> Command { ) } -/// 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 byte - /// but output only the *k*th chunk. - KthBytes(u64, 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::KthBytes(_, 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 K/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 - /// or if `K` is 0 - /// or if `K` is greater than `N`: - /// - /// ```ignore - /// -n K/N - /// -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" - /// "K/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 `K` is 0, - /// or if `N` is not a positive integer, - /// or if `K` is greater than `N` - /// then this function returns [`NumberTypeError`]. - fn from(s: &str) -> Result { - fn is_invalid_chunk(chunk_number: u64, num_chunks: u64) -> bool { - chunk_number > num_chunks || chunk_number == 0 - } - let parts: Vec<&str> = s.split('/').collect(); - match &parts[..] { - [n_str] => { - let num_chunks = parse_size_u64(n_str) - .map_err(|_| NumberTypeError::NumberOfChunks(n_str.to_string()))?; - if num_chunks > 0 { - Ok(Self::Bytes(num_chunks)) - } else { - Err(NumberTypeError::NumberOfChunks(s.to_string())) - } - } - [k_str, n_str] if !k_str.starts_with('l') && !k_str.starts_with('r') => { - let num_chunks = parse_size_u64(n_str) - .map_err(|_| NumberTypeError::NumberOfChunks(n_str.to_string()))?; - let chunk_number = parse_size_u64(k_str) - .map_err(|_| NumberTypeError::ChunkNumber(k_str.to_string()))?; - if is_invalid_chunk(chunk_number, num_chunks) { - return Err(NumberTypeError::ChunkNumber(k_str.to_string())); - } - Ok(Self::KthBytes(chunk_number, num_chunks)) - } - ["l", n_str] => { - let num_chunks = parse_size_u64(n_str) - .map_err(|_| NumberTypeError::NumberOfChunks(n_str.to_string()))?; - Ok(Self::Lines(num_chunks)) - } - ["l", k_str, n_str] => { - let num_chunks = parse_size_u64(n_str) - .map_err(|_| NumberTypeError::NumberOfChunks(n_str.to_string()))?; - let chunk_number = parse_size_u64(k_str) - .map_err(|_| NumberTypeError::ChunkNumber(k_str.to_string()))?; - if is_invalid_chunk(chunk_number, num_chunks) { - return Err(NumberTypeError::ChunkNumber(k_str.to_string())); - } - Ok(Self::KthLines(chunk_number, num_chunks)) - } - ["r", n_str] => { - let num_chunks = parse_size_u64(n_str) - .map_err(|_| NumberTypeError::NumberOfChunks(n_str.to_string()))?; - Ok(Self::RoundRobin(num_chunks)) - } - ["r", k_str, n_str] => { - let num_chunks = parse_size_u64(n_str) - .map_err(|_| NumberTypeError::NumberOfChunks(n_str.to_string()))?; - let chunk_number = parse_size_u64(k_str) - .map_err(|_| NumberTypeError::ChunkNumber(k_str.to_string()))?; - if is_invalid_chunk(chunk_number, num_chunks) { - return 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. - Lines(u64), - - /// Each chunk has the specified number of bytes. - Bytes(u64), - - /// Each chunk has as many lines as possible without exceeding the - /// specified number of bytes. - LineBytes(u64), - - /// Split the file into this many chunks. - /// - /// There are several sub-strategies available, as defined by - /// [`NumberType`]. - Number(NumberType), -} - -/// An error when parsing a chunking strategy from command-line arguments. -enum StrategyError { - /// Invalid number of lines. - Lines(ParseSizeError), - - /// Invalid number of bytes. - Bytes(ParseSizeError), - - /// Invalid number type. - NumberType(NumberTypeError), - - /// Multiple chunking strategies were specified (but only one should be). - MultipleWays, -} - -impl fmt::Display for StrategyError { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { - Self::Lines(e) => write!(f, "invalid number of lines: {e}"), - Self::Bytes(e) => write!(f, "invalid number of bytes: {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"), - } - } -} - -impl Strategy { - /// Parse a strategy from the command-line arguments. - fn from(matches: &ArgMatches, obs_lines: &Option) -> Result { - fn get_and_parse( - matches: &ArgMatches, - option: &str, - strategy: fn(u64) -> Strategy, - error: fn(ParseSizeError) -> StrategyError, - ) -> Result { - let s = matches.get_one::(option).unwrap(); - let n = parse_size_u64_max(s).map_err(error)?; - if n > 0 { - Ok(strategy(n)) - } else { - Err(error(ParseSizeError::ParseFailure(s.to_string()))) - } - } - // Check that the user is not specifying more than one strategy. - // - // Note: right now, this exact behavior cannot be handled by - // overrides_with_all() due to obsolete lines value option - match ( - obs_lines, - matches.value_source(OPT_LINES) == Some(ValueSource::CommandLine), - matches.value_source(OPT_BYTES) == Some(ValueSource::CommandLine), - matches.value_source(OPT_LINE_BYTES) == Some(ValueSource::CommandLine), - matches.value_source(OPT_NUMBER) == Some(ValueSource::CommandLine), - ) { - (Some(v), false, false, false, false) => { - let v = parse_size_u64_max(v).map_err(|_| { - StrategyError::Lines(ParseSizeError::ParseFailure(v.to_string())) - })?; - if v > 0 { - Ok(Self::Lines(v)) - } else { - Err(StrategyError::Lines(ParseSizeError::ParseFailure( - v.to_string(), - ))) - } - } - (None, false, false, false, false) => Ok(Self::Lines(1000)), - (None, true, false, false, false) => { - get_and_parse(matches, OPT_LINES, Self::Lines, StrategyError::Lines) - } - (None, false, true, false, false) => { - get_and_parse(matches, OPT_BYTES, Self::Bytes, StrategyError::Bytes) - } - (None, false, false, true, false) => get_and_parse( - matches, - OPT_LINE_BYTES, - Self::LineBytes, - StrategyError::Bytes, - ), - (None, false, false, false, true) => { - let s = matches.get_one::(OPT_NUMBER).unwrap(); - let number_type = NumberType::from(s).map_err(StrategyError::NumberType)?; - Ok(Self::Number(number_type)) - } - _ => Err(StrategyError::MultipleWays), - } - } -} - -/// Parse the suffix type, start and length from the command-line arguments -/// as well suffix length auto-widening and auto-width scenarios -/// -/// Suffix auto-widening: Determine if the output file names suffix is allowed to dynamically auto-widen, -/// i.e. change (increase) suffix length dynamically as more files need to be written into. -/// Suffix length auto-widening rules are (in the order they are applied): -/// - ON by default -/// - OFF when suffix start N is specified via long option with a value -/// `--numeric-suffixes=N` or `--hex-suffixes=N` -/// - OFF when suffix length N is specified, except for N=0 (see edge cases below) -/// `-a N` or `--suffix-length=N` -/// - OFF if suffix length is auto pre-calculated (auto-width) -/// -/// Suffix auto-width: Determine if the the output file names suffix length should be automatically pre-calculated -/// based on number of files that need to written into, having number of files known upfront -/// Suffix length auto pre-calculation rules: -/// - Pre-calculate new suffix length when `-n`/`--number` option (N, K/N, l/N, l/K/N, r/N, r/K/N) -/// is used, where N is number of chunks = number of files to write into -/// and suffix start < N number of files -/// as in `split --numeric-suffixes=1 --number=r/100 file` -/// - Do NOT pre-calculate new suffix length otherwise, i.e. when -/// suffix start >= N number of files -/// as in `split --numeric-suffixes=100 --number=r/100 file` -/// OR when suffix length N is specified, except for N=0 (see edge cases below) -/// `-a N` or `--suffix-length=N` -/// -/// Edge case: -/// - If suffix length is specified as 0 AND `-n`/`--number` option used specifying number of files: -/// set auto widening OFF AND auto pre-calculate required suffix length based on number of files needed -/// - If suffix length is specified as 0 in any other situation -/// keep auto widening ON and suffix length to default value -/// -fn suffix_from( - matches: &ArgMatches, - strategy: &Strategy, -) -> Result<(SuffixType, usize, bool, usize), SettingsError> { - let suffix_type: SuffixType; - - // Defaults - let mut suffix_start = 0; - let mut suffix_auto_widening = true; - - // Check if the user is specifying one or more than one suffix - // Any combination of suffixes is allowed - // Since all suffixes are setup with 'overrides_with_all()' against themselves and each other, - // last one wins, all others are ignored - match ( - matches.contains_id(OPT_NUMERIC_SUFFIXES), - matches.contains_id(OPT_HEX_SUFFIXES), - matches.get_flag(OPT_NUMERIC_SUFFIXES_SHORT), - matches.get_flag(OPT_HEX_SUFFIXES_SHORT), - ) { - (true, _, _, _) => { - suffix_type = SuffixType::Decimal; - let opt = matches.get_one::(OPT_NUMERIC_SUFFIXES); // if option was specified, but without value - this will return None as there is no default value - if opt.is_some() { - suffix_start = opt - .unwrap() - .parse::() - .map_err(|_| SettingsError::SuffixNotParsable(opt.unwrap().to_string()))?; - suffix_auto_widening = false; - } - } - (_, true, _, _) => { - suffix_type = SuffixType::Hexadecimal; - let opt = matches.get_one::(OPT_HEX_SUFFIXES); // if option was specified, but without value - this will return None as there is no default value - if opt.is_some() { - suffix_start = usize::from_str_radix(opt.unwrap(), 16) - .map_err(|_| SettingsError::SuffixNotParsable(opt.unwrap().to_string()))?; - suffix_auto_widening = false; - } - } - (_, _, true, _) => suffix_type = SuffixType::Decimal, // short numeric suffix '-d' - (_, _, _, true) => suffix_type = SuffixType::Hexadecimal, // short hex suffix '-x' - _ => suffix_type = SuffixType::Alphabetic, // no numeric/hex suffix, using default alphabetic - } - - // Get suffix length (could be coming from command line of default value) - let suffix_length_str = matches.get_one::(OPT_SUFFIX_LENGTH).unwrap(); // safe to unwrap here as there is default value for this option - let mut suffix_length: usize = suffix_length_str - .parse() - .map_err(|_| SettingsError::SuffixNotParsable(suffix_length_str.to_string()))?; - - // Disable dynamic auto-widening if suffix length was specified in command line with value > 0 - if matches.value_source(OPT_SUFFIX_LENGTH) == Some(ValueSource::CommandLine) - && suffix_length > 0 - { - suffix_auto_widening = false; - } - - // Auto pre-calculate new suffix length (auto-width) if necessary - if let Strategy::Number(ref number_type) = strategy { - let chunks = number_type.num_chunks(); - let required_suffix_length = ((suffix_start as u64 + chunks) as f64) - .log(suffix_type.radix() as f64) - .ceil() as usize; - - if (suffix_start as u64) < chunks - && !(matches.value_source(OPT_SUFFIX_LENGTH) == Some(ValueSource::CommandLine) - && suffix_length > 0) - { - // with auto-width ON the auto-widening is OFF - suffix_auto_widening = false; - - // do not reduce suffix length with auto-width - if suffix_length < required_suffix_length { - suffix_length = required_suffix_length; - } - } - - if suffix_length < required_suffix_length { - return Err(SettingsError::SuffixTooSmall(required_suffix_length)); - } - } - - // Check suffix length == 0 edge case - // If it is still 0 at this point, then auto-width pre-calculation did not apply - // So, set it to default value and keep auto-widening ON - if suffix_length == 0 { - suffix_length = OPT_DEFAULT_SUFFIX_LENGTH.parse().unwrap(); - } - - Ok(( - suffix_type, - suffix_start, - suffix_auto_widening, - suffix_length, - )) -} - /// 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, - suffix_type: SuffixType, - suffix_length: usize, - suffix_start: usize, - /// Whether or not suffix length should automatically widen - suffix_auto_widening: bool, - additional_suffix: String, + suffix: Suffix, input: String, /// When supplied, a shell command to output to instead of xaa, xab โ€ฆ filter: Option, @@ -834,13 +427,7 @@ enum SettingsError { Strategy(StrategyError), /// Invalid suffix length parameter. - 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), + Suffix(SuffixError), /// Multi-character (Invalid) separator MultiCharacterSeparator(String), @@ -864,7 +451,8 @@ impl SettingsError { fn requires_usage(&self) -> bool { matches!( self, - Self::Strategy(StrategyError::MultipleWays) | Self::SuffixContainsSeparator(_) + Self::Strategy(StrategyError::MultipleWays) + | Self::Suffix(SuffixError::ContainsSeparator(_)) ) } } @@ -873,19 +461,13 @@ impl fmt::Display for SettingsError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { Self::Strategy(e) => e.fmt(f), - 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::Suffix(e) => e.fmt(f), Self::MultiCharacterSeparator(s) => { write!(f, "multi-character separator {}", s.quote()) } Self::MultipleSeparatorCharacters => { write!(f, "multiple separator characters specified") } - Self::SuffixContainsSeparator(s) => write!( - f, - "invalid suffix {}, contains directory separator", - s.quote() - ), Self::FilterWithKthChunkNumber => { write!(f, "--filter does not process a chunk extracted to stdout") } @@ -901,16 +483,8 @@ impl fmt::Display for SettingsError { impl Settings { /// Parse a strategy from the command-line arguments. fn from(matches: &ArgMatches, obs_lines: &Option) -> Result { - let additional_suffix = matches - .get_one::(OPT_ADDITIONAL_SUFFIX) - .unwrap() - .to_string(); - if additional_suffix.contains('/') { - return Err(SettingsError::SuffixContainsSeparator(additional_suffix)); - } let strategy = Strategy::from(matches, obs_lines).map_err(SettingsError::Strategy)?; - let (suffix_type, suffix_start, suffix_auto_widening, suffix_length) = - suffix_from(matches, &strategy)?; + let suffix = Suffix::from(matches, &strategy).map_err(SettingsError::Suffix)?; // Make sure that separator is only one UTF8 character (if specified) // defaults to '\n' - newline character @@ -932,17 +506,13 @@ impl Settings { }; let result = Self { - suffix_length, - suffix_type, - suffix_start, - suffix_auto_widening, - additional_suffix, + prefix: matches.get_one::(ARG_PREFIX).unwrap().to_owned(), + suffix, + input: matches.get_one::(ARG_INPUT).unwrap().to_owned(), + filter: matches.get_one::(OPT_FILTER).map(|s| s.to_owned()), + strategy, verbose: matches.value_source(OPT_VERBOSE) == Some(ValueSource::CommandLine), separator, - strategy, - input: matches.get_one::(ARG_INPUT).unwrap().to_owned(), - prefix: matches.get_one::(ARG_PREFIX).unwrap().to_owned(), - filter: matches.get_one::(OPT_FILTER).map(|s| s.to_owned()), elide_empty_files: matches.get_flag(OPT_ELIDE_EMPTY_FILES), }; @@ -1059,14 +629,7 @@ struct ByteChunkWriter<'a> { impl<'a> ByteChunkWriter<'a> { fn new(chunk_size: u64, settings: &'a Settings) -> UResult> { - let mut filename_iterator = FilenameIterator::new( - &settings.prefix, - &settings.additional_suffix, - settings.suffix_length, - settings.suffix_type, - settings.suffix_start, - settings.suffix_auto_widening, - )?; + let mut filename_iterator = FilenameIterator::new(&settings.prefix, &settings.suffix)?; let filename = filename_iterator .next() .ok_or_else(|| USimpleError::new(1, "output file suffixes exhausted"))?; @@ -1190,14 +753,7 @@ struct LineChunkWriter<'a> { impl<'a> LineChunkWriter<'a> { fn new(chunk_size: u64, settings: &'a Settings) -> UResult> { - let mut filename_iterator = FilenameIterator::new( - &settings.prefix, - &settings.additional_suffix, - settings.suffix_length, - settings.suffix_type, - settings.suffix_start, - settings.suffix_auto_widening, - )?; + let mut filename_iterator = FilenameIterator::new(&settings.prefix, &settings.suffix)?; let filename = filename_iterator .next() .ok_or_else(|| USimpleError::new(1, "output file suffixes exhausted"))?; @@ -1304,14 +860,7 @@ struct LineBytesChunkWriter<'a> { impl<'a> LineBytesChunkWriter<'a> { fn new(chunk_size: u64, settings: &'a Settings) -> UResult> { - let mut filename_iterator = FilenameIterator::new( - &settings.prefix, - &settings.additional_suffix, - settings.suffix_length, - settings.suffix_type, - settings.suffix_start, - settings.suffix_auto_widening, - )?; + let mut filename_iterator = FilenameIterator::new(&settings.prefix, &settings.suffix)?; let filename = filename_iterator .next() .ok_or_else(|| USimpleError::new(1, "output file suffixes exhausted"))?; @@ -1528,14 +1077,7 @@ where .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, - &settings.additional_suffix, - settings.suffix_length, - settings.suffix_type, - settings.suffix_start, - settings.suffix_auto_widening, - )?; + let mut filename_iterator = FilenameIterator::new(&settings.prefix, &settings.suffix)?; // Create one writer for each chunk. This will create each // of the underlying files (if not in `--filter` mode). @@ -1700,14 +1242,7 @@ where let chunk_size = (num_bytes / num_chunks) 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, - settings.suffix_start, - settings.suffix_auto_widening, - )?; + let mut filename_iterator = FilenameIterator::new(&settings.prefix, &settings.suffix)?; // Create one writer for each chunk. This will create each // of the underlying files (if not in `--filter` mode). @@ -1842,15 +1377,8 @@ where R: BufRead, { // 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, - settings.suffix_start, - settings.suffix_auto_widening, - ) - .map_err(|e| io::Error::new(ErrorKind::Other, format!("{e}")))?; + let mut filename_iterator = FilenameIterator::new(&settings.prefix, &settings.suffix) + .map_err(|e| io::Error::new(ErrorKind::Other, format!("{e}")))?; // Create one writer for each chunk. This will create each // of the underlying files (if not in `--filter` mode). @@ -2027,101 +1555,3 @@ 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] - #[allow(clippy::cognitive_complexity)] - 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()) - ); - assert_eq!( - NumberType::from("l/456/123").unwrap_err(), - NumberTypeError::ChunkNumber("456".to_string()) - ); - assert_eq!( - NumberType::from("r/456/123").unwrap_err(), - NumberTypeError::ChunkNumber("456".to_string()) - ); - assert_eq!( - NumberType::from("456/123").unwrap_err(), - NumberTypeError::ChunkNumber("456".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("123/456").unwrap().num_chunks(), 456); - 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); - } -} diff --git a/src/uu/split/src/strategy.rs b/src/uu/split/src/strategy.rs new file mode 100644 index 000000000..e85abcee5 --- /dev/null +++ b/src/uu/split/src/strategy.rs @@ -0,0 +1,379 @@ +// 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. +//! Determine the strategy for breaking up the input (file or stdin) into chunks +//! based on the command line options + +use crate::{OPT_BYTES, OPT_LINES, OPT_LINE_BYTES, OPT_NUMBER}; +use clap::{parser::ValueSource, ArgMatches}; +use std::fmt; +use uucore::parse_size::{parse_size_u64, parse_size_u64_max, ParseSizeError}; + +/// Sub-strategy of the [`Strategy::Number`] +/// Splitting a file into a specific number of chunks. +#[derive(Debug, PartialEq)] +pub enum NumberType { + /// Split into a specific number of chunks by byte. + Bytes(u64), + + /// Split into a specific number of chunks by byte + /// but output only the *k*th chunk. + KthBytes(u64, 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. + pub fn num_chunks(&self) -> u64 { + match self { + Self::Bytes(n) => *n, + Self::KthBytes(_, 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)] +pub 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 K/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 + /// or if `K` is 0 + /// or if `K` is greater than `N`: + /// + /// ```ignore + /// -n K/N + /// -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" + /// "K/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 `K` is 0, + /// or if `N` is not a positive integer, + /// or if `K` is greater than `N` + /// then this function returns [`NumberTypeError`]. + fn from(s: &str) -> Result { + fn is_invalid_chunk(chunk_number: u64, num_chunks: u64) -> bool { + chunk_number > num_chunks || chunk_number == 0 + } + let parts: Vec<&str> = s.split('/').collect(); + match &parts[..] { + [n_str] => { + let num_chunks = parse_size_u64(n_str) + .map_err(|_| NumberTypeError::NumberOfChunks(n_str.to_string()))?; + if num_chunks > 0 { + Ok(Self::Bytes(num_chunks)) + } else { + Err(NumberTypeError::NumberOfChunks(s.to_string())) + } + } + [k_str, n_str] if !k_str.starts_with('l') && !k_str.starts_with('r') => { + let num_chunks = parse_size_u64(n_str) + .map_err(|_| NumberTypeError::NumberOfChunks(n_str.to_string()))?; + let chunk_number = parse_size_u64(k_str) + .map_err(|_| NumberTypeError::ChunkNumber(k_str.to_string()))?; + if is_invalid_chunk(chunk_number, num_chunks) { + return Err(NumberTypeError::ChunkNumber(k_str.to_string())); + } + Ok(Self::KthBytes(chunk_number, num_chunks)) + } + ["l", n_str] => { + let num_chunks = parse_size_u64(n_str) + .map_err(|_| NumberTypeError::NumberOfChunks(n_str.to_string()))?; + Ok(Self::Lines(num_chunks)) + } + ["l", k_str, n_str] => { + let num_chunks = parse_size_u64(n_str) + .map_err(|_| NumberTypeError::NumberOfChunks(n_str.to_string()))?; + let chunk_number = parse_size_u64(k_str) + .map_err(|_| NumberTypeError::ChunkNumber(k_str.to_string()))?; + if is_invalid_chunk(chunk_number, num_chunks) { + return Err(NumberTypeError::ChunkNumber(k_str.to_string())); + } + Ok(Self::KthLines(chunk_number, num_chunks)) + } + ["r", n_str] => { + let num_chunks = parse_size_u64(n_str) + .map_err(|_| NumberTypeError::NumberOfChunks(n_str.to_string()))?; + Ok(Self::RoundRobin(num_chunks)) + } + ["r", k_str, n_str] => { + let num_chunks = parse_size_u64(n_str) + .map_err(|_| NumberTypeError::NumberOfChunks(n_str.to_string()))?; + let chunk_number = parse_size_u64(k_str) + .map_err(|_| NumberTypeError::ChunkNumber(k_str.to_string()))?; + if is_invalid_chunk(chunk_number, num_chunks) { + return 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. +pub enum Strategy { + /// Each chunk has the specified number of lines. + Lines(u64), + + /// Each chunk has the specified number of bytes. + Bytes(u64), + + /// Each chunk has as many lines as possible without exceeding the + /// specified number of bytes. + LineBytes(u64), + + /// Split the file into this many chunks. + /// + /// There are several sub-strategies available, as defined by + /// [`NumberType`]. + Number(NumberType), +} + +/// An error when parsing a chunking strategy from command-line arguments. +pub enum StrategyError { + /// Invalid number of lines. + Lines(ParseSizeError), + + /// Invalid number of bytes. + Bytes(ParseSizeError), + + /// Invalid number type. + NumberType(NumberTypeError), + + /// Multiple chunking strategies were specified (but only one should be). + MultipleWays, +} + +impl fmt::Display for StrategyError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + Self::Lines(e) => write!(f, "invalid number of lines: {e}"), + Self::Bytes(e) => write!(f, "invalid number of bytes: {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"), + } + } +} + +impl Strategy { + /// Parse a strategy from the command-line arguments. + pub fn from(matches: &ArgMatches, obs_lines: &Option) -> Result { + fn get_and_parse( + matches: &ArgMatches, + option: &str, + strategy: fn(u64) -> Strategy, + error: fn(ParseSizeError) -> StrategyError, + ) -> Result { + let s = matches.get_one::(option).unwrap(); + let n = parse_size_u64_max(s).map_err(error)?; + if n > 0 { + Ok(strategy(n)) + } else { + Err(error(ParseSizeError::ParseFailure(s.to_string()))) + } + } + // Check that the user is not specifying more than one strategy. + // + // Note: right now, this exact behavior cannot be handled by + // overrides_with_all() due to obsolete lines value option + match ( + obs_lines, + matches.value_source(OPT_LINES) == Some(ValueSource::CommandLine), + matches.value_source(OPT_BYTES) == Some(ValueSource::CommandLine), + matches.value_source(OPT_LINE_BYTES) == Some(ValueSource::CommandLine), + matches.value_source(OPT_NUMBER) == Some(ValueSource::CommandLine), + ) { + (Some(v), false, false, false, false) => { + let v = parse_size_u64_max(v).map_err(|_| { + StrategyError::Lines(ParseSizeError::ParseFailure(v.to_string())) + })?; + if v > 0 { + Ok(Self::Lines(v)) + } else { + Err(StrategyError::Lines(ParseSizeError::ParseFailure( + v.to_string(), + ))) + } + } + (None, false, false, false, false) => Ok(Self::Lines(1000)), + (None, true, false, false, false) => { + get_and_parse(matches, OPT_LINES, Self::Lines, StrategyError::Lines) + } + (None, false, true, false, false) => { + get_and_parse(matches, OPT_BYTES, Self::Bytes, StrategyError::Bytes) + } + (None, false, false, true, false) => get_and_parse( + matches, + OPT_LINE_BYTES, + Self::LineBytes, + StrategyError::Bytes, + ), + (None, false, false, false, true) => { + let s = matches.get_one::(OPT_NUMBER).unwrap(); + let number_type = NumberType::from(s).map_err(StrategyError::NumberType)?; + Ok(Self::Number(number_type)) + } + _ => Err(StrategyError::MultipleWays), + } + } +} + +#[cfg(test)] +mod tests { + + use crate::{strategy::NumberType, strategy::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] + #[allow(clippy::cognitive_complexity)] + 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()) + ); + assert_eq!( + NumberType::from("l/456/123").unwrap_err(), + NumberTypeError::ChunkNumber("456".to_string()) + ); + assert_eq!( + NumberType::from("r/456/123").unwrap_err(), + NumberTypeError::ChunkNumber("456".to_string()) + ); + assert_eq!( + NumberType::from("456/123").unwrap_err(), + NumberTypeError::ChunkNumber("456".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("123/456").unwrap().num_chunks(), 456); + 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 f05474a33ad4888708ff36ae4cd149763eb2e462 Mon Sep 17 00:00:00 2001 From: zhitkoff Date: Mon, 30 Oct 2023 15:39:29 -0400 Subject: [PATCH 16/73] split: slash separator --- src/uu/split/src/filenames.rs | 2 +- tests/by-util/test_split.rs | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/uu/split/src/filenames.rs b/src/uu/split/src/filenames.rs index e776b274b..80243c2bc 100644 --- a/src/uu/split/src/filenames.rs +++ b/src/uu/split/src/filenames.rs @@ -230,7 +230,7 @@ impl Suffix { .get_one::(OPT_ADDITIONAL_SUFFIX) .unwrap() .to_string(); - if additional.contains('/') { + if additional.contains('/') || additional.contains('\\') { return Err(SuffixError::ContainsSeparator(additional)); } diff --git a/tests/by-util/test_split.rs b/tests/by-util/test_split.rs index 3ebadde4d..e0e85be48 100644 --- a/tests/by-util/test_split.rs +++ b/tests/by-util/test_split.rs @@ -252,6 +252,10 @@ fn test_additional_suffix_no_slash() { .args(&["--additional-suffix", "a/b"]) .fails() .usage_error("invalid suffix 'a/b', contains directory separator"); + new_ucmd!() + .args(&["--additional-suffix", "a\\b"]) + .fails() + .usage_error("invalid suffix 'a\\b', contains directory separator"); } #[test] From 62887c7a58b0f5e4045a418aa2df09e9b2a54654 Mon Sep 17 00:00:00 2001 From: zhitkoff Date: Thu, 2 Nov 2023 10:36:15 -0400 Subject: [PATCH 17/73] split: directory separator in additional suffix --- src/uu/split/src/filenames.rs | 3 ++- tests/by-util/test_split.rs | 5 ++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/uu/split/src/filenames.rs b/src/uu/split/src/filenames.rs index 80243c2bc..843e11ea0 100644 --- a/src/uu/split/src/filenames.rs +++ b/src/uu/split/src/filenames.rs @@ -40,6 +40,7 @@ use crate::{ }; use clap::ArgMatches; use std::fmt; +use std::path::is_separator; use uucore::display::Quotable; use uucore::error::{UResult, USimpleError}; @@ -230,7 +231,7 @@ impl Suffix { .get_one::(OPT_ADDITIONAL_SUFFIX) .unwrap() .to_string(); - if additional.contains('/') || additional.contains('\\') { + if additional.chars().any(is_separator) { return Err(SuffixError::ContainsSeparator(additional)); } diff --git a/tests/by-util/test_split.rs b/tests/by-util/test_split.rs index e0e85be48..aec6f0594 100644 --- a/tests/by-util/test_split.rs +++ b/tests/by-util/test_split.rs @@ -247,11 +247,14 @@ fn test_split_additional_suffix() { } #[test] -fn test_additional_suffix_no_slash() { +fn test_additional_suffix_dir_separator() { + #[cfg(unix)] new_ucmd!() .args(&["--additional-suffix", "a/b"]) .fails() .usage_error("invalid suffix 'a/b', contains directory separator"); + + #[cfg(windows)] new_ucmd!() .args(&["--additional-suffix", "a\\b"]) .fails() From 3d0c8ae6e72ac53b50e8de583e7acdf1b59b037c Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Thu, 2 Nov 2023 18:19:56 +0100 Subject: [PATCH 18/73] Expand CONTRIBUTING.md (WIP) --- .../workspace.wordlist.txt | 1 + CONTRIBUTING.md | 263 ++++++++++++++---- 2 files changed, 209 insertions(+), 55 deletions(-) diff --git a/.vscode/cspell.dictionaries/workspace.wordlist.txt b/.vscode/cspell.dictionaries/workspace.wordlist.txt index 6d6533bcf..c3c854a4c 100644 --- a/.vscode/cspell.dictionaries/workspace.wordlist.txt +++ b/.vscode/cspell.dictionaries/workspace.wordlist.txt @@ -59,6 +59,7 @@ clippy rustc rustfmt rustup +rustdoc # bitor # BitOr trait function bitxor # BitXor trait function diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 695e5ad18..d9ee528f8 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,52 +1,202 @@ - + # Contributing to coreutils -Contributions are very welcome via Pull Requests. If you don't know where to -start, take a look at the -[`good-first-issues`](https://github.com/uutils/coreutils/issues?q=is%3Aopen+is%3Aissue+label%3A%22good+first+issue%22). -If you have any questions, feel free to ask them in the issues or on -[Discord](https://discord.gg/wQVJbvJ). +Hi! Welcome to uutils/coreutils! -## Best practices +Thanks for wanting to contribute to this project! This document explains +everything you need to know to contribute. Before you start make sure to also +check out these documents: -1. Follow what GNU is doing in terms of options and behavior. It is recommended - to look at the GNU Coreutils manual ([on the - web](https://www.gnu.org/software/coreutils/manual/html_node/index.html), or - locally using `info `). It is more in depth than the man pages and - provides a good description of available features and their implementation - details. -1. If possible, look at the GNU test suite execution in the CI and make the test - work if failing. -1. Use clap for argument management. -1. Make sure that the code coverage is covering all of the cases, including - errors. -1. The code must be clippy-warning-free and rustfmt-compliant. -1. Don't hesitate to move common functions into uucore if they can be reused by - other binaries. -1. Unsafe code should be documented with Safety comments. -1. uutils is original code. It cannot contain code from existing GNU or Unix-like - utilities, nor should it link to or reference GNU libraries. +- Our community's [CODE_OF_CONDUCT.md](./CODE_OF_CONDUCT.md). +- [DEVELOPMENT.md](./DEVELOPMENT.md) for setting up your development + environment. + +Now follows a very important warning: + +> [!WARNING] +> uutils is original code and cannot contain any code from GNU or +> other implementations. This means that **we cannot accept any changes based on +> the GNU source code**. To make sure that cannot happen, **you cannot link to +> the GNU source code** either. + +Finally, feel free to join our [Discord](https://discord.gg/wQVJbvJ)! + +## Getting Oriented + +uutils is a big project consisting of many parts. Here are the most important +parts for getting started: + +- [`src/uu`](./src/uu/): The code for all utilities +- [`src/uucore`](./src/uucore/): Crate containing all the shared code between + the utilities. +- [`tests/by-util`](./tests/by-util/): The tests for all utilities. +- [`src/bin/coreutils.rs`](./src/bin/coreutils.rs): Code for the multicall + binary. +- [`docs`](./docs/src): the documentation for the website + +Each utility is defined as a separate crate. The structure of each of these +crates is as follows: + +- `Cargo.toml` +- `src/main.rs`: contains only a single macro call +- `src/.rs`: the actual code for the utility +- `.md`: the documentation for the utility + +We have separated repositories for crates that we maintain but also publish for +use by others: + +- [uutils-term-grid](https://github.com/uutils/uutils-term-grid) +- [parse_datetime](https://github.com/uutils/parse_datetime) + +## Design Goals + +todo + +## How to Help + +There are several ways to help and writing code is just one them. Reporting +issues and writing documentation are just as important as writing code. + +### Reporting Issues + +We can't fix bugs we don't know about, so good issues are super helpful! Here +are some tips for writing good issues: + +- If you find a bug, make sure it's still a problem on the `main` branch. +- Search through the existing issues to see whether it has already been + reported. +- Make sure to include all relevant information, such as: + - Which version of uutils did you check? + - Which version of GNU coreutils are you comparing with? + - What platform are you on? +- Provide a way to reliably reproduce the issue. +- Be as specific as possible! + +### Writing Documentation + +There's never enough documentation. If you come across any documentation that +could be improved, feel free to submit a PR for it! + +### Writing Code + +If you want to submit a PR, make sure that you've discussed the solution with +the maintainers beforehand. We want to avoid situations where you put a lot of +work into a fix that we can't merge! If there's no issue for what you're trying +to fix yet, make one _before_ you start working on the PR. + +Generally, we try to follow what GNU is doing in terms of options and behavior. +It is recommended to look at the GNU Coreutils manual +([on the web](https://www.gnu.org/software/coreutils/manual/html_node/index.html), +or locally using `info `). It is more in depth than the man pages and +provides a good description of available features and their implementation +details. But remember, you cannot look at the GNU source code! + +Also remember that we can only merge PRs which pass our test suite, follow +rustfmt, and do not have any warnings from clippy. See +[DEVELOPMENT.md](./DEVELOPMENT.md) for more information. Be sure to also read +about our [Rust style](#our-rust-style). + +## Our Rust Style + +We want uutils to be written in idiomatic Rust, so here are some guidelines to +follow. Some of these are aspirational, meaning that we don't do them correctly +everywhere in the code. If you find violations of the advice below, feel free to +submit a patch! + +### Don't `panic!` + +The coreutils should be very reliable. This means that we should never `panic!`. +Therefore, you should avoid using `.unwrap()` and `panic!`. Sometimes the use of +`unreachable!` can be justified with a comment explaining why that code is +unreachable. + +### Don't `exit` + +We want uutils to be embeddable in other programs. This means that no function +in uutils should exit the program. Doing so would also lead to code with more +confusing control flow. Avoid therefore `std::process::exit` and similar +functions which exit the program early. + +### `unsafe` + +uutils cannot be entirely safe, because we have to call out to `libc` and do +syscalls. However, we still want to limit our use of `unsafe`. We generally only +accept `unsafe` for FFI, with very few exceptions. Note that performance is very +rarely a valid argument for using `unsafe`. + +If you still need to write code with `unsafe`, make sure to read the +[Rustonomicon](https://doc.rust-lang.org/nomicon/intro.html) and annotate the +calls with `// SAFETY:` comments explaining why the use of `unsafe` is sound. + +### Macros + +Macros can be a great tool, but they are also usually hard to understand. They +should be used sparingly. Make sure to explore simpler options before you reach +for a solution involving macros. + +### `str`, `OsStr` & `Path` + +Rust has many string-like types, and sometimes it's hard to choose the right +one. It's tempting to use `str` (and `String`) for everything, but that is not +always the right choice for uutils, because we need to support invalid UTF-8, +just like the GNU coreutils. For example, paths on Linux might not be valid +UTF-8! Whenever we are dealing with paths, we should therefore stick with +`OsStr` and `Path`. Make sure that you only convert to `str`/`String` if you +know that something is always valid UTF-8. If you need more operations on +`OsStr`, you can use the [`bstr`](https://docs.rs/bstr/latest/bstr/) crate. + +### Doc-comments + +We use rustdoc for our documentation, so it's best to follow +[rustdoc's guidelines](https://doc.rust-lang.org/rustdoc/how-to-write-documentation.html#documenting-components). +Make sure that your documentation is not just repeating the name of the +function, but actually giving more useful information. Rustdoc recommends the +following structure: + +``` +[short sentence explaining what it is] + +[more detailed explanation] + +[at least one code example that users can copy/paste to try it] + +[even more advanced explanations if necessary] +``` + +### Other comments + +Comments should be written to _explain_ the code, not to _describe_ the code. +Try to focus on explaining _why_ the code is the way it is. If you feel like you +have to describe the code, that's usually a sign that you could improve the +naming of variables and functions. + +If you edit a piece of code, make sure to update any comments that need to +change as a result. The only thing worse than having no comments is having +outdated comments! + +## Git Etiquette + +todo ## Platforms We take pride in supporting many operating systems and architectures. Any code you contribute must at least compile without warnings for all platforms in the -CI. However, you can use `#[cfg(...)]` attributes to create platform dependent features. +CI. However, you can use `#[cfg(...)]` attributes to create platform dependent +features. **Tip:** For Windows, Microsoft provides some images (VMWare, Hyper-V, VirtualBox and Parallels) for development: -## Setting up your development environment - -To setup your local development environment for this project please follow [DEVELOPMENT.md guide](DEVELOPMENT.md) - -It covers [installation of necessary tools and prerequisites](DEVELOPMENT.md#tools) as well as using those tools to [test your code changes locally](DEVELOPMENT.md#testing) - ## Improving the GNU compatibility -Please make sure you have installed [GNU utils and prerequisites](DEVELOPMENT.md#gnu-utils-and-prerequisites) and can execute commands described in [Comparing with GNU](DEVELOPMENT.md#comparing-with-gnu) section of [DEVELOPMENT.md](DEVELOPMENT.md) +Please make sure you have installed +[GNU utils and prerequisites](DEVELOPMENT.md#gnu-utils-and-prerequisites) and +can execute commands described in +[Comparing with GNU](DEVELOPMENT.md#comparing-with-gnu) section of +[DEVELOPMENT.md](DEVELOPMENT.md) The Python script `./util/remaining-gnu-error.py` shows the list of failing tests in the CI. @@ -103,20 +253,20 @@ Further paragraphs come after blank lines. Furthermore, here are a few examples for a summary line: -* commit for a single utility +- commit for a single utility ``` nohup: cleanup and refactor ``` -* commit for a utility's tests +- commit for a utility's tests ``` tests/rm: test new feature ``` -Beyond changes to an individual utility or its tests, other summary -lines for non-utility modules include: +Beyond changes to an individual utility or its tests, other summary lines for +non-utility modules include: ``` README: add help @@ -136,23 +286,26 @@ gitignore: add temporary files ## Code coverage -To generate code coverage report locally please follow [Code coverage report](DEVELOPMENT.md#code-coverage-report) section of [DEVELOPMENT.md](DEVELOPMENT.md) +To generate code coverage report locally please follow +[Code coverage report](DEVELOPMENT.md#code-coverage-report) section of +[DEVELOPMENT.md](DEVELOPMENT.md) ## Other implementations -The Coreutils have different implementations, with different levels of completions: +The Coreutils have different implementations, with different levels of +completions: -* [GNU's](https://git.savannah.gnu.org/gitweb/?p=coreutils.git) -* [OpenBSD](https://github.com/openbsd/src/tree/master/bin) -* [Busybox](https://github.com/mirror/busybox/tree/master/coreutils) -* [Toybox (Android)](https://github.com/landley/toybox/tree/master/toys/posix) -* [V lang](https://github.com/vlang/coreutils) -* [SerenityOS](https://github.com/SerenityOS/serenity/tree/master/Userland/Utilities) -* [Initial Unix](https://github.com/dspinellis/unix-history-repo) -* [Perl Power Tools](https://metacpan.org/pod/PerlPowerTools) +- [GNU's](https://git.savannah.gnu.org/gitweb/?p=coreutils.git) +- [OpenBSD](https://github.com/openbsd/src/tree/master/bin) +- [Busybox](https://github.com/mirror/busybox/tree/master/coreutils) +- [Toybox (Android)](https://github.com/landley/toybox/tree/master/toys/posix) +- [V lang](https://github.com/vlang/coreutils) +- [SerenityOS](https://github.com/SerenityOS/serenity/tree/master/Userland/Utilities) +- [Initial Unix](https://github.com/dspinellis/unix-history-repo) +- [Perl Power Tools](https://metacpan.org/pod/PerlPowerTools) -However, when reimplementing the tools/options in Rust, don't read their source codes -when they are using reciprocal licenses (ex: GNU GPL, GNU LGPL, etc). +However, when reimplementing the tools/options in Rust, don't read their source +codes when they are using reciprocal licenses (ex: GNU GPL, GNU LGPL, etc). ## Licensing @@ -167,17 +320,17 @@ If you wish to add or change dependencies as part of a contribution to the project, a tool like `cargo-license` can be used to show their license details. The following types of license are acceptable: -* MIT License -* Dual- or tri-license with an MIT License option ("Apache-2.0 or MIT" is a +- MIT License +- Dual- or tri-license with an MIT License option ("Apache-2.0 or MIT" is a popular combination) -* "MIT equivalent" license (2-clause BSD, 3-clause BSD, ISC) -* License less restrictive than the MIT License (CC0 1.0 Universal) -* Apache License version 2.0 +- "MIT equivalent" license (2-clause BSD, 3-clause BSD, ISC) +- License less restrictive than the MIT License (CC0 1.0 Universal) +- Apache License version 2.0 Licenses we will not use: -* An ambiguous license, or no license -* Strongly reciprocal licenses (GNU GPL, GNU LGPL) +- An ambiguous license, or no license +- Strongly reciprocal licenses (GNU GPL, GNU LGPL) If you wish to add a reference but it doesn't meet these requirements, please raise an issue to describe the dependency. From d7b256be7bbbbc91ff95ef3e7805d5733fca7382 Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Fri, 3 Nov 2023 11:44:19 +0100 Subject: [PATCH 19/73] CONTRIBUTING.md: write Design Goals section --- CONTRIBUTING.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index d9ee528f8..f59d3b4f8 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -51,7 +51,13 @@ use by others: ## Design Goals -todo +We have the following goals with our development: + +- **Compatible**: The utilities should be a drop-in replacement for the GNU coreutils. +- **Cross-platform**: All utilities should run on as many of the supported platforms as possible. +- **Reliable**: The utilities should never unexpectedly fail. +- **Performant**: Our utilities should be written in fast idiomatic Rust. We aim to match or exceed the performance of the GNU utilities. +- **Well-tested**: We should have a lot of tests to be able to guarantee reliability and compatibility. ## How to Help From 29f6631554eb6417f4f302d113078e7de43b95b7 Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Fri, 3 Nov 2023 16:49:19 +0100 Subject: [PATCH 20/73] du: add -P/--no-dereference --- src/uu/du/src/du.rs | 16 +++++++++------- tests/by-util/test_du.rs | 36 ++++++++++++++++++++++++++++++++++++ 2 files changed, 45 insertions(+), 7 deletions(-) diff --git a/src/uu/du/src/du.rs b/src/uu/du/src/du.rs index ad5e87833..ae88ed13b 100644 --- a/src/uu/du/src/du.rs +++ b/src/uu/du/src/du.rs @@ -68,6 +68,7 @@ mod options { pub const ONE_FILE_SYSTEM: &str = "one-file-system"; pub const DEREFERENCE: &str = "dereference"; pub const DEREFERENCE_ARGS: &str = "dereference-args"; + pub const NO_DEREFERENCE: &str = "no-dereference"; pub const INODES: &str = "inodes"; pub const EXCLUDE: &str = "exclude"; pub const EXCLUDE_FROM: &str = "exclude-from"; @@ -824,13 +825,14 @@ pub fn uu_app() -> Command { .help("follow only symlinks that are listed on the command line") .action(ArgAction::SetTrue) ) - // .arg( - // Arg::new("no-dereference") - // .short('P') - // .long("no-dereference") - // .help("don't follow any symbolic links (this is the default)") - // .action(ArgAction::SetTrue), - // ) + .arg( + Arg::new(options::NO_DEREFERENCE) + .short('P') + .long(options::NO_DEREFERENCE) + .help("don't follow any symbolic links (this is the default)") + .overrides_with(options::DEREFERENCE) + .action(ArgAction::SetTrue), + ) .arg( Arg::new(options::BLOCK_SIZE_1M) .short('m') diff --git a/tests/by-util/test_du.rs b/tests/by-util/test_du.rs index 9a508da25..7e6bc4775 100644 --- a/tests/by-util/test_du.rs +++ b/tests/by-util/test_du.rs @@ -336,6 +336,42 @@ fn _du_dereference(s: &str) { } } +#[cfg(not(windows))] +#[test] +fn test_du_no_dereference() { + let ts = TestScenario::new(util_name!()); + let at = &ts.fixtures; + let dir = "a_dir"; + let symlink = "symlink"; + + at.mkdir(dir); + at.symlink_dir(dir, symlink); + + for arg in ["-P", "--no-dereference"] { + ts.ucmd() + .arg(arg) + .succeeds() + .stdout_contains(dir) + .stdout_does_not_contain(symlink); + + // ensure no-dereference "wins" + ts.ucmd() + .arg("--dereference") + .arg(arg) + .succeeds() + .stdout_contains(dir) + .stdout_does_not_contain(symlink); + + // ensure dereference "wins" + ts.ucmd() + .arg(arg) + .arg("--dereference") + .succeeds() + .stdout_contains(symlink) + .stdout_does_not_contain(dir); + } +} + #[test] fn test_du_inodes_basic() { let ts = TestScenario::new(util_name!()); From 44d105d01533e1a0dc255ec80718c739e4ad0b87 Mon Sep 17 00:00:00 2001 From: Brandon Elam Barker Date: Sat, 4 Nov 2023 07:31:11 -0400 Subject: [PATCH 21/73] Add support in uucore for illumos and solaris (#5489) * uucore support for illumos and solaris * use macro to consolidate illumos and solaris signals * fixing some CI issues * replaced macro with better cfg usage --- src/uucore/src/lib/features/fs.rs | 4 + src/uucore/src/lib/features/fsext.rs | 13 ++- src/uucore/src/lib/features/signals.rs | 116 ++++++++++++++++++++++++- 3 files changed, 129 insertions(+), 4 deletions(-) diff --git a/src/uucore/src/lib/features/fs.rs b/src/uucore/src/lib/features/fs.rs index 84ed006d9..f8593dfed 100644 --- a/src/uucore/src/lib/features/fs.rs +++ b/src/uucore/src/lib/features/fs.rs @@ -115,6 +115,8 @@ impl FileInformation { not(target_os = "android"), not(target_os = "freebsd"), not(target_os = "netbsd"), + not(target_os = "illumos"), + not(target_os = "solaris"), not(target_arch = "aarch64"), not(target_arch = "riscv64"), target_pointer_width = "64" @@ -127,6 +129,8 @@ impl FileInformation { target_os = "android", target_os = "freebsd", target_os = "netbsd", + target_os = "illumos", + target_os = "solaris", target_arch = "aarch64", target_arch = "riscv64", not(target_pointer_width = "64") diff --git a/src/uucore/src/lib/features/fsext.rs b/src/uucore/src/lib/features/fsext.rs index 5c2121d69..8b1c42de6 100644 --- a/src/uucore/src/lib/features/fsext.rs +++ b/src/uucore/src/lib/features/fsext.rs @@ -69,6 +69,8 @@ use std::convert::{AsRef, From}; target_os = "openbsd", target_os = "linux", target_os = "android", + target_os = "illumos", + target_os = "solaris", ))] use std::ffi::CStr; #[cfg(not(windows))] @@ -309,7 +311,7 @@ impl MountInfo { target_os = "freebsd", target_vendor = "apple", target_os = "netbsd", - target_os = "openbsd" + target_os = "openbsd", ))] impl From for MountInfo { fn from(statfs: StatFs) -> Self { @@ -615,6 +617,8 @@ impl FsMeta for StatFs { not(target_vendor = "apple"), not(target_os = "android"), not(target_os = "freebsd"), + not(target_os = "illumos"), + not(target_os = "solaris"), not(target_arch = "s390x"), target_pointer_width = "64" ))] @@ -630,7 +634,12 @@ impl FsMeta for StatFs { ) ))] return self.f_bsize.into(); - #[cfg(any(target_env = "musl", target_os = "freebsd"))] + #[cfg(any( + target_env = "musl", + target_os = "freebsd", + target_os = "illumos", + target_os = "solaris" + ))] return self.f_bsize.try_into().unwrap(); } fn total_blocks(&self) -> u64 { diff --git a/src/uucore/src/lib/features/signals.rs b/src/uucore/src/lib/features/signals.rs index 61482024d..2e8c26a45 100644 --- a/src/uucore/src/lib/features/signals.rs +++ b/src/uucore/src/lib/features/signals.rs @@ -3,14 +3,15 @@ // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. -// spell-checker:ignore (vars/api) fcntl setrlimit setitimer -// spell-checker:ignore (vars/signals) ABRT ALRM CHLD SEGV SIGABRT SIGALRM SIGBUS SIGCHLD SIGCONT SIGEMT SIGFPE SIGHUP SIGILL SIGINFO SIGINT SIGIO SIGIOT SIGKILL SIGPIPE SIGPROF SIGPWR SIGQUIT SIGSEGV SIGSTOP SIGSYS SIGTERM SIGTRAP SIGTSTP SIGTHR SIGTTIN SIGTTOU SIGURG SIGUSR SIGVTALRM SIGWINCH SIGXCPU SIGXFSZ STKFLT PWR THR TSTP TTIN TTOU VTALRM XCPU XFSZ +// spell-checker:ignore (vars/api) fcntl setrlimit setitimer rubout pollable occured sysconf +// spell-checker:ignore (vars/signals) ABRT ALRM CHLD SEGV SIGABRT SIGALRM SIGBUS SIGCHLD SIGCONT SIGEMT SIGFPE SIGHUP SIGILL SIGINFO SIGINT SIGIO SIGIOT SIGKILL SIGPIPE SIGPROF SIGPWR SIGQUIT SIGSEGV SIGSTOP SIGSYS SIGTERM SIGTRAP SIGTSTP SIGTHR SIGTTIN SIGTTOU SIGURG SIGUSR SIGVTALRM SIGWINCH SIGXCPU SIGXFSZ STKFLT PWR THR TSTP TTIN TTOU VTALRM XCPU XFSZ SIGCLD SIGPOLL SIGWAITING SIGAIOCANCEL SIGLWP SIGFREEZE SIGTHAW SIGCANCEL SIGLOST SIGXRES SIGJVM SIGRTMIN SIGRT SIGRTMAX AIOCANCEL XRES RTMIN RTMAX #[cfg(unix)] use nix::errno::Errno; #[cfg(unix)] use nix::sys::signal::{ signal, SigHandler::SigDfl, SigHandler::SigIgn, Signal::SIGINT, Signal::SIGPIPE, }; + pub static DEFAULT_SIGNAL: usize = 15; /* @@ -178,6 +179,117 @@ pub static ALL_SIGNALS: [&str; 33] = [ "XCPU", "XFSZ", "VTALRM", "PROF", "WINCH", "INFO", "USR1", "USR2", "THR", ]; +/* + The following signals are defined in Solaris and illumos; + (the signals for illumos are the same as Solaris, but illumos still has SIGLWP + as well as the alias for SIGLWP (SIGAIOCANCEL)): + + SIGHUP 1 hangup + SIGINT 2 interrupt (rubout) + SIGQUIT 3 quit (ASCII FS) + SIGILL 4 illegal instruction (not reset when caught) + SIGTRAP 5 trace trap (not reset when caught) + SIGIOT 6 IOT instruction + SIGABRT 6 used by abort, replace SIGIOT in the future + SIGEMT 7 EMT instruction + SIGFPE 8 floating point exception + SIGKILL 9 kill (cannot be caught or ignored) + SIGBUS 10 bus error + SIGSEGV 11 segmentation violation + SIGSYS 12 bad argument to system call + SIGPIPE 13 write on a pipe with no one to read it + SIGALRM 14 alarm clock + SIGTERM 15 software termination signal from kill + SIGUSR1 16 user defined signal 1 + SIGUSR2 17 user defined signal 2 + SIGCLD 18 child status change + SIGCHLD 18 child status change alias (POSIX) + SIGPWR 19 power-fail restart + SIGWINCH 20 window size change + SIGURG 21 urgent socket condition + SIGPOLL 22 pollable event occured + SIGIO SIGPOLL socket I/O possible (SIGPOLL alias) + SIGSTOP 23 stop (cannot be caught or ignored) + SIGTSTP 24 user stop requested from tty + SIGCONT 25 stopped process has been continued + SIGTTIN 26 background tty read attempted + SIGTTOU 27 background tty write attempted + SIGVTALRM 28 virtual timer expired + SIGPROF 29 profiling timer expired + SIGXCPU 30 exceeded cpu limit + SIGXFSZ 31 exceeded file size limit + SIGWAITING 32 reserved signal no longer used by threading code + SIGAIOCANCEL 33 reserved signal no longer used by threading code (formerly SIGLWP) + SIGFREEZE 34 special signal used by CPR + SIGTHAW 35 special signal used by CPR + SIGCANCEL 36 reserved signal for thread cancellation + SIGLOST 37 resource lost (eg, record-lock lost) + SIGXRES 38 resource control exceeded + SIGJVM1 39 reserved signal for Java Virtual Machine + SIGJVM2 40 reserved signal for Java Virtual Machine + SIGINFO 41 information request + SIGRTMIN ((int)_sysconf(_SC_SIGRT_MIN)) first realtime signal + SIGRTMAX ((int)_sysconf(_SC_SIGRT_MAX)) last realtime signal +*/ + +#[cfg(target_os = "solaris")] +const SIGNALS_SIZE: usize = 46; + +#[cfg(target_os = "illumos")] +const SIGNALS_SIZE: usize = 47; + +#[cfg(any(target_os = "solaris", target_os = "illumos"))] +static ALL_SIGNALS: [&str; SIGNALS_SIZE] = [ + "HUP", + "INT", + "QUIT", + "ILL", + "TRAP", + "IOT", + "ABRT", + "EMT", + "FPE", + "KILL", + "BUS", + "SEGV", + "SYS", + "PIPE", + "ALRM", + "TERM", + "USR1", + "USR2", + "CLD", + "CHLD", + "PWR", + "WINCH", + "URG", + "POLL", + "IO", + "STOP", + "TSTP", + "CONT", + "TTIN", + "TTOU", + "VTALRM", + "PROF", + "XCPU", + "XFSZ", + "WAITING", + "AIOCANCEL", + #[cfg(target_os = "illumos")] + "LWP", + "FREEZE", + "THAW", + "CANCEL", + "LOST", + "XRES", + "JVM1", + "JVM2", + "INFO", + "RTMIN", + "RTMAX", +]; + pub fn signal_by_name_or_value(signal_name_or_value: &str) -> Option { if let Ok(value) = signal_name_or_value.parse() { if is_signal(value) { From 6ac1af69537e6eb5baa94c7bb46bf001fa44196a Mon Sep 17 00:00:00 2001 From: Kostiantyn Hryshchuk Date: Sat, 4 Nov 2023 20:00:53 +0100 Subject: [PATCH 22/73] Fix clippy::implicit_clone --- .github/workflows/code-quality.yml | 2 +- src/uu/base32/src/base_common.rs | 2 +- src/uu/cat/src/cat.rs | 2 +- src/uu/csplit/src/csplit.rs | 12 +++--------- src/uu/cut/src/cut.rs | 4 ++-- src/uu/dirname/src/dirname.rs | 2 +- src/uu/du/src/du.rs | 2 +- src/uu/fold/src/fold.rs | 4 ++-- src/uu/head/src/head.rs | 2 +- src/uu/install/src/install.rs | 17 ++++++----------- src/uu/ls/src/ls.rs | 18 +++++------------- src/uu/mkfifo/src/mkfifo.rs | 2 +- src/uu/mv/src/mv.rs | 2 +- src/uu/nl/src/helper.rs | 4 ++-- src/uu/nl/src/nl.rs | 2 +- src/uu/numfmt/src/numfmt.rs | 4 +--- src/uu/paste/src/paste.rs | 2 +- src/uu/ptx/src/ptx.rs | 2 +- src/uu/shuf/src/shuf.rs | 2 +- src/uu/sort/src/numeric_str_cmp.rs | 4 ++-- src/uu/split/src/split.rs | 6 +++--- src/uu/sum/src/sum.rs | 2 +- src/uu/tail/src/follow/files.rs | 2 +- src/uu/tail/src/follow/watch.rs | 10 +++++----- src/uu/test/src/parser.rs | 2 +- 25 files changed, 46 insertions(+), 67 deletions(-) diff --git a/.github/workflows/code-quality.yml b/.github/workflows/code-quality.yml index 0a619e097..98691f34b 100644 --- a/.github/workflows/code-quality.yml +++ b/.github/workflows/code-quality.yml @@ -117,7 +117,7 @@ jobs: run: | ## `cargo clippy` lint testing unset fault - CLIPPY_FLAGS="-W clippy::default_trait_access -W clippy::manual_string_new -W clippy::cognitive_complexity" + CLIPPY_FLAGS="-W clippy::default_trait_access -W clippy::manual_string_new -W clippy::cognitive_complexity -W clippy::implicit_clone" fault_type="${{ steps.vars.outputs.FAULT_TYPE }}" fault_prefix=$(echo "$fault_type" | tr '[:lower:]' '[:upper:]') # * convert any warnings to GHA UI annotations; ref: diff --git a/src/uu/base32/src/base_common.rs b/src/uu/base32/src/base_common.rs index 4a30705af..74c3dc808 100644 --- a/src/uu/base32/src/base_common.rs +++ b/src/uu/base32/src/base_common.rs @@ -54,7 +54,7 @@ impl Config { format!("{}: No such file or directory", name.maybe_quote()), )); } - Some(name.to_owned()) + Some(name.clone()) } } None => None, diff --git a/src/uu/cat/src/cat.rs b/src/uu/cat/src/cat.rs index a7a4c5f40..34eb26512 100644 --- a/src/uu/cat/src/cat.rs +++ b/src/uu/cat/src/cat.rs @@ -213,7 +213,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { let squeeze_blank = matches.get_flag(options::SQUEEZE_BLANK); let files: Vec = match matches.get_many::(options::FILE) { - Some(v) => v.clone().map(|v| v.to_owned()).collect(), + Some(v) => v.cloned().collect(), None => vec!["-".to_owned()], }; diff --git a/src/uu/csplit/src/csplit.rs b/src/uu/csplit/src/csplit.rs index 6e03c2e5c..d33be1a5d 100644 --- a/src/uu/csplit/src/csplit.rs +++ b/src/uu/csplit/src/csplit.rs @@ -62,15 +62,9 @@ impl CsplitOptions { split_name: crash_if_err!( 1, SplitName::new( - matches - .get_one::(options::PREFIX) - .map(|s| s.to_owned()), - matches - .get_one::(options::SUFFIX_FORMAT) - .map(|s| s.to_owned()), - matches - .get_one::(options::DIGITS) - .map(|s| s.to_owned()) + matches.get_one::(options::PREFIX).cloned(), + matches.get_one::(options::SUFFIX_FORMAT).cloned(), + matches.get_one::(options::DIGITS).cloned() ) ), keep_files, diff --git a/src/uu/cut/src/cut.rs b/src/uu/cut/src/cut.rs index 4d3145c05..05e8bc6e4 100644 --- a/src/uu/cut/src/cut.rs +++ b/src/uu/cut/src/cut.rs @@ -400,7 +400,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { if s.is_empty() { Some("\0".to_owned()) } else { - Some(s.to_owned()) + Some(s.clone()) } } None => None, @@ -491,7 +491,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { let files: Vec = matches .get_many::(options::FILE) .unwrap_or_default() - .map(|s| s.to_owned()) + .cloned() .collect(); match mode_parse { diff --git a/src/uu/dirname/src/dirname.rs b/src/uu/dirname/src/dirname.rs index 9a56e9f5b..51935cb7f 100644 --- a/src/uu/dirname/src/dirname.rs +++ b/src/uu/dirname/src/dirname.rs @@ -30,7 +30,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { let dirnames: Vec = matches .get_many::(options::DIR) .unwrap_or_default() - .map(|s| s.to_owned()) + .cloned() .collect(); if dirnames.is_empty() { diff --git a/src/uu/du/src/du.rs b/src/uu/du/src/du.rs index ae88ed13b..9139c31c3 100644 --- a/src/uu/du/src/du.rs +++ b/src/uu/du/src/du.rs @@ -506,7 +506,7 @@ fn build_exclude_patterns(matches: &ArgMatches) -> UResult> { let excludes_iterator = matches .get_many::(options::EXCLUDE) .unwrap_or_default() - .map(|v| v.to_owned()); + .cloned(); let mut exclude_patterns = Vec::new(); for f in excludes_iterator.chain(exclude_from_iterator) { diff --git a/src/uu/fold/src/fold.rs b/src/uu/fold/src/fold.rs index 95b6d9a82..0223248be 100644 --- a/src/uu/fold/src/fold.rs +++ b/src/uu/fold/src/fold.rs @@ -35,7 +35,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { let bytes = matches.get_flag(options::BYTES); let spaces = matches.get_flag(options::SPACES); let poss_width = match matches.get_one::(options::WIDTH) { - Some(v) => Some(v.to_owned()), + Some(v) => Some(v.clone()), None => obs_width, }; @@ -50,7 +50,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { }; let files = match matches.get_many::(options::FILE) { - Some(v) => v.map(|v| v.to_owned()).collect(), + Some(v) => v.cloned().collect(), None => vec!["-".to_owned()], }; diff --git a/src/uu/head/src/head.rs b/src/uu/head/src/head.rs index c533f5a5d..5d0d3bedd 100644 --- a/src/uu/head/src/head.rs +++ b/src/uu/head/src/head.rs @@ -205,7 +205,7 @@ impl HeadOptions { options.mode = Mode::from(matches)?; options.files = match matches.get_many::(options::FILES_NAME) { - Some(v) => v.map(|s| s.to_owned()).collect(), + Some(v) => v.cloned().collect(), None => vec!["-".to_owned()], }; //println!("{:#?}", options); diff --git a/src/uu/install/src/install.rs b/src/uu/install/src/install.rs index 63ba52b1c..43925a7f8 100644 --- a/src/uu/install/src/install.rs +++ b/src/uu/install/src/install.rs @@ -375,9 +375,7 @@ fn behavior(matches: &ArgMatches) -> UResult { }; let backup_mode = backup_control::determine_backup_mode(matches)?; - let target_dir = matches - .get_one::(OPT_TARGET_DIRECTORY) - .map(|d| d.to_owned()); + let target_dir = matches.get_one::(OPT_TARGET_DIRECTORY).cloned(); let preserve_timestamps = matches.get_flag(OPT_PRESERVE_TIMESTAMPS); let compare = matches.get_flag(OPT_COMPARE); @@ -593,7 +591,7 @@ fn standard(mut paths: Vec, b: &Behavior) -> UResult<()> { let source = sources.first().unwrap(); if source.is_dir() { - return Err(InstallError::OmittingDirectory(source.to_path_buf()).into()); + return Err(InstallError::OmittingDirectory(source.clone()).into()); } if target.is_file() || is_new_file_path(&target) { @@ -628,7 +626,7 @@ fn copy_files_into_dir(files: &[PathBuf], target_dir: &Path, b: &Behavior) -> UR } if sourcepath.is_dir() { - let err = InstallError::OmittingDirectory(sourcepath.to_path_buf()); + let err = InstallError::OmittingDirectory(sourcepath.clone()); show!(err); continue; } @@ -701,12 +699,9 @@ fn perform_backup(to: &Path, b: &Behavior) -> UResult> { if let Some(ref backup_path) = backup_path { // TODO!! if let Err(err) = fs::rename(to, backup_path) { - return Err(InstallError::BackupFailed( - to.to_path_buf(), - backup_path.to_path_buf(), - err, - ) - .into()); + return Err( + InstallError::BackupFailed(to.to_path_buf(), backup_path.clone(), err).into(), + ); } } Ok(backup_path) diff --git a/src/uu/ls/src/ls.rs b/src/uu/ls/src/ls.rs index 327b914dd..88af56bb1 100644 --- a/src/uu/ls/src/ls.rs +++ b/src/uu/ls/src/ls.rs @@ -577,9 +577,7 @@ fn extract_color(options: &clap::ArgMatches) -> bool { /// /// A QuotingStyle variant representing the quoting style to use. fn extract_quoting_style(options: &clap::ArgMatches, show_control: bool) -> QuotingStyle { - let opt_quoting_style = options - .get_one::(options::QUOTING_STYLE) - .map(|cmd_line_qs| cmd_line_qs.to_owned()); + let opt_quoting_style = options.get_one::(options::QUOTING_STYLE).cloned(); if let Some(style) = opt_quoting_style { match style.as_str() { @@ -788,9 +786,7 @@ impl Config { match parse_size_u64(&raw_bs.to_string_lossy()) { Ok(size) => Some(size), Err(_) => { - show!(LsError::BlockSizeParseError( - cmd_line_bs.unwrap().to_owned() - )); + show!(LsError::BlockSizeParseError(cmd_line_bs.unwrap().clone())); None } } @@ -3056,7 +3052,7 @@ fn display_file_name( target_data.must_dereference, ) { Ok(md) => md, - Err(_) => path.md(out).unwrap().to_owned(), + Err(_) => path.md(out).unwrap().clone(), }; name.push_str(&color_name( @@ -3073,11 +3069,7 @@ fn display_file_name( } } Err(err) => { - show!(LsError::IOErrorContext( - err, - path.p_buf.to_path_buf(), - false - )); + show!(LsError::IOErrorContext(err, path.p_buf.clone(), false)); } } } @@ -3087,7 +3079,7 @@ fn display_file_name( if config.context { if let Some(pad_count) = prefix_context { let security_context = if matches!(config.format, Format::Commas) { - path.security_context.to_owned() + path.security_context.clone() } else { pad_left(&path.security_context, pad_count) }; diff --git a/src/uu/mkfifo/src/mkfifo.rs b/src/uu/mkfifo/src/mkfifo.rs index dc1f876fc..39d112739 100644 --- a/src/uu/mkfifo/src/mkfifo.rs +++ b/src/uu/mkfifo/src/mkfifo.rs @@ -42,7 +42,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { }; let fifos: Vec = match matches.get_many::(options::FIFO) { - Some(v) => v.clone().map(|s| s.to_owned()).collect(), + Some(v) => v.cloned().collect(), None => return Err(USimpleError::new(1, "missing operand")), }; diff --git a/src/uu/mv/src/mv.rs b/src/uu/mv/src/mv.rs index 0ceda8e75..036024f99 100644 --- a/src/uu/mv/src/mv.rs +++ b/src/uu/mv/src/mv.rs @@ -146,7 +146,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { let files: Vec = matches .get_many::(ARG_FILES) .unwrap_or_default() - .map(|v| v.to_os_string()) + .cloned() .collect(); let overwrite_mode = determine_overwrite_mode(&matches); diff --git a/src/uu/nl/src/helper.rs b/src/uu/nl/src/helper.rs index ae14a6d59..e617010c1 100644 --- a/src/uu/nl/src/helper.rs +++ b/src/uu/nl/src/helper.rs @@ -19,11 +19,11 @@ pub fn parse_options(settings: &mut crate::Settings, opts: &clap::ArgMatches) -> settings.section_delimiter = if delimiter.len() == 1 { format!("{delimiter}:") } else { - delimiter.to_owned() + delimiter.clone() }; } if let Some(val) = opts.get_one::(options::NUMBER_SEPARATOR) { - settings.number_separator = val.to_owned(); + settings.number_separator = val.clone(); } settings.number_format = opts .get_one::(options::NUMBER_FORMAT) diff --git a/src/uu/nl/src/nl.rs b/src/uu/nl/src/nl.rs index 61ca8406f..eaf27f3b6 100644 --- a/src/uu/nl/src/nl.rs +++ b/src/uu/nl/src/nl.rs @@ -195,7 +195,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { } let files: Vec = match matches.get_many::(options::FILE) { - Some(v) => v.clone().map(|v| v.to_owned()).collect(), + Some(v) => v.cloned().collect(), None => vec!["-".to_owned()], }; diff --git a/src/uu/numfmt/src/numfmt.rs b/src/uu/numfmt/src/numfmt.rs index 4afe56555..d1785209d 100644 --- a/src/uu/numfmt/src/numfmt.rs +++ b/src/uu/numfmt/src/numfmt.rs @@ -211,9 +211,7 @@ fn parse_options(args: &ArgMatches) -> Result { _ => unreachable!("Should be restricted by clap"), }; - let suffix = args - .get_one::(options::SUFFIX) - .map(|s| s.to_owned()); + let suffix = args.get_one::(options::SUFFIX).cloned(); let invalid = InvalidModes::from_str(args.get_one::(options::INVALID).unwrap()).unwrap(); diff --git a/src/uu/paste/src/paste.rs b/src/uu/paste/src/paste.rs index 89bba034c..118a66b81 100644 --- a/src/uu/paste/src/paste.rs +++ b/src/uu/paste/src/paste.rs @@ -44,7 +44,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { let files = matches .get_many::(options::FILE) .unwrap() - .map(|s| s.to_owned()) + .cloned() .collect(); let line_ending = LineEnding::from_zero_flag(matches.get_flag(options::ZERO_TERMINATED)); diff --git a/src/uu/ptx/src/ptx.rs b/src/uu/ptx/src/ptx.rs index 1e9532a3a..6dd2b2992 100644 --- a/src/uu/ptx/src/ptx.rs +++ b/src/uu/ptx/src/ptx.rs @@ -720,7 +720,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { let matches = uu_app().try_get_matches_from(args)?; let mut input_files: Vec = match &matches.get_many::(options::FILE) { - Some(v) => v.clone().map(|v| v.to_owned()).collect(), + Some(v) => v.clone().cloned().collect(), None => vec!["-".to_string()], }; diff --git a/src/uu/shuf/src/shuf.rs b/src/uu/shuf/src/shuf.rs index 1b21b9532..8c636f1cb 100644 --- a/src/uu/shuf/src/shuf.rs +++ b/src/uu/shuf/src/shuf.rs @@ -75,7 +75,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { let headcounts = matches .get_many::(options::HEAD_COUNT) .unwrap_or_default() - .map(|s| s.to_owned()) + .cloned() .collect(); match parse_head_count(headcounts) { Ok(val) => val, diff --git a/src/uu/sort/src/numeric_str_cmp.rs b/src/uu/sort/src/numeric_str_cmp.rs index 661f536a3..c6af856c2 100644 --- a/src/uu/sort/src/numeric_str_cmp.rs +++ b/src/uu/sort/src/numeric_str_cmp.rs @@ -394,8 +394,8 @@ mod tests { let (a_info, a_range) = NumInfo::parse(a, &NumInfoParseSettings::default()); let (b_info, b_range) = NumInfo::parse(b, &NumInfoParseSettings::default()); let ordering = numeric_str_cmp( - (&a[a_range.to_owned()], &a_info), - (&b[b_range.to_owned()], &b_info), + (&a[a_range.clone()], &a_info), + (&b[b_range.clone()], &b_info), ); assert_eq!(ordering, expected); let ordering = numeric_str_cmp((&b[b_range], &b_info), (&a[a_range], &a_info)); diff --git a/src/uu/split/src/split.rs b/src/uu/split/src/split.rs index 4b5cd9207..17a783d72 100644 --- a/src/uu/split/src/split.rs +++ b/src/uu/split/src/split.rs @@ -506,10 +506,10 @@ impl Settings { }; let result = Self { - prefix: matches.get_one::(ARG_PREFIX).unwrap().to_owned(), + prefix: matches.get_one::(ARG_PREFIX).unwrap().clone(), suffix, - input: matches.get_one::(ARG_INPUT).unwrap().to_owned(), - filter: matches.get_one::(OPT_FILTER).map(|s| s.to_owned()), + input: matches.get_one::(ARG_INPUT).unwrap().clone(), + filter: matches.get_one::(OPT_FILTER).cloned(), strategy, verbose: matches.value_source(OPT_VERBOSE) == Some(ValueSource::CommandLine), separator, diff --git a/src/uu/sum/src/sum.rs b/src/uu/sum/src/sum.rs index 4616274d0..38ad3964e 100644 --- a/src/uu/sum/src/sum.rs +++ b/src/uu/sum/src/sum.rs @@ -107,7 +107,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { let matches = uu_app().try_get_matches_from(args)?; let files: Vec = match matches.get_many::(options::FILE) { - Some(v) => v.clone().map(|v| v.to_owned()).collect(), + Some(v) => v.cloned().collect(), None => vec!["-".to_owned()], }; diff --git a/src/uu/tail/src/follow/files.rs b/src/uu/tail/src/follow/files.rs index e4f980267..d1aa0aed6 100644 --- a/src/uu/tail/src/follow/files.rs +++ b/src/uu/tail/src/follow/files.rs @@ -40,7 +40,7 @@ impl FileHandling { pub fn insert(&mut self, k: &Path, v: PathData, update_last: bool) { let k = Self::canonicalize_path(k); if update_last { - self.last = Some(k.to_owned()); + self.last = Some(k.clone()); } let _ = self.map.insert(k, v); } diff --git a/src/uu/tail/src/follow/watch.rs b/src/uu/tail/src/follow/watch.rs index fbda27aa0..1836a797a 100644 --- a/src/uu/tail/src/follow/watch.rs +++ b/src/uu/tail/src/follow/watch.rs @@ -279,7 +279,7 @@ impl Observer { if !path.is_file() { continue; } - let mut path = path.to_owned(); + let mut path = path.clone(); if path.is_relative() { path = std::env::current_dir()?.join(path); } @@ -345,7 +345,7 @@ impl Observer { show_error!("{}: file truncated", display_name); self.files.update_reader(event_path)?; } - paths.push(event_path.to_owned()); + paths.push(event_path.clone()); } else if !is_tailable && old_md.is_tailable() { if pd.reader.is_some() { self.files.reset_reader(event_path); @@ -359,7 +359,7 @@ impl Observer { } else if is_tailable { show_error!( "{} has appeared; following new file", display_name.quote()); self.files.update_reader(event_path)?; - paths.push(event_path.to_owned()); + paths.push(event_path.clone()); } else if settings.retry { if self.follow_descriptor() { show_error!( @@ -403,7 +403,7 @@ impl Observer { "{} cannot be used, reverting to polling", text::BACKEND ); - self.orphans.push(event_path.to_owned()); + self.orphans.push(event_path.clone()); let _ = self.watcher_rx.as_mut().unwrap().unwatch(event_path); } } else { @@ -451,7 +451,7 @@ impl Observer { if self.follow_descriptor() { let new_path = event.paths.last().unwrap(); - paths.push(new_path.to_owned()); + paths.push(new_path.clone()); let new_data = PathData::from_other_with_path(self.files.remove(event_path), new_path); self.files.insert( diff --git a/src/uu/test/src/parser.rs b/src/uu/test/src/parser.rs index 2b847fa15..23a2d7cf6 100644 --- a/src/uu/test/src/parser.rs +++ b/src/uu/test/src/parser.rs @@ -164,7 +164,7 @@ impl Parser { /// The stream is unchanged and will return the same Symbol on subsequent /// calls to `next()` or `peek()`. fn peek(&mut self) -> Symbol { - Symbol::new(self.tokens.peek().map(|s| s.to_os_string())) + Symbol::new(self.tokens.peek().cloned()) } /// Test if the next token in the stream is a BOOLOP (-a or -o), without From 91b19b7c56fa00d66f8cd8bdee1725daced19302 Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Sun, 5 Nov 2023 13:56:39 +0100 Subject: [PATCH 23/73] cp,tail: fix warnings in tests on Android --- tests/by-util/test_cp.rs | 4 ++-- tests/by-util/test_tail.rs | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/by-util/test_cp.rs b/tests/by-util/test_cp.rs index 8964665b0..311558d38 100644 --- a/tests/by-util/test_cp.rs +++ b/tests/by-util/test_cp.rs @@ -2120,7 +2120,7 @@ fn test_cp_reflink_insufficient_permission() { .stderr_only("cp: 'unreadable' -> 'existing_file.txt': Permission denied (os error 13)\n"); } -#[cfg(target_os = "linux")] +#[cfg(any(target_os = "linux", target_os = "android"))] #[test] fn test_closes_file_descriptors() { use procfs::process::Process; @@ -3436,7 +3436,7 @@ fn test_cp_debug_sparse_auto() { } #[test] -#[cfg(any(target_os = "linux", target_os = "android", target_os = "macos"))] +#[cfg(any(target_os = "linux", target_os = "macos"))] fn test_cp_debug_reflink_auto() { let ts = TestScenario::new(util_name!()); let at = &ts.fixtures; diff --git a/tests/by-util/test_tail.rs b/tests/by-util/test_tail.rs index bc89f56a0..c7ca09af8 100644 --- a/tests/by-util/test_tail.rs +++ b/tests/by-util/test_tail.rs @@ -23,6 +23,7 @@ use std::io::Write; use std::io::{Seek, SeekFrom}; #[cfg(all( not(target_vendor = "apple"), + not(target_os = "android"), not(target_os = "windows"), not(target_os = "freebsd") ))] @@ -31,6 +32,7 @@ use std::process::Stdio; use tail::chunks::BUFFER_SIZE as CHUNK_BUFFER_SIZE; #[cfg(all( not(target_vendor = "apple"), + not(target_os = "android"), not(target_os = "windows"), not(target_os = "freebsd") ))] From bbdde2890a8b3ca2e9ea55a2a817ec34c1c3bca3 Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Sun, 5 Nov 2023 14:19:43 +0100 Subject: [PATCH 24/73] du: ignore test under Android & FreeBSD --- tests/by-util/test_du.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/by-util/test_du.rs b/tests/by-util/test_du.rs index 7e6bc4775..fdb44ef53 100644 --- a/tests/by-util/test_du.rs +++ b/tests/by-util/test_du.rs @@ -336,7 +336,7 @@ fn _du_dereference(s: &str) { } } -#[cfg(not(windows))] +#[cfg(not(any(target_os = "windows", target_os = "android", target_os = "freebsd")))] #[test] fn test_du_no_dereference() { let ts = TestScenario::new(util_name!()); From 4f14a9d55275a8c327ad23a6a42ded8f8dc76ce3 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sun, 5 Nov 2023 13:50:12 +0000 Subject: [PATCH 25/73] chore(deps): update rust crate libc to 0.2.150 --- Cargo.lock | 4 ++-- Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 048b00263..3a3e4b2c4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1187,9 +1187,9 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" [[package]] name = "libc" -version = "0.2.149" +version = "0.2.150" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a08173bc88b7955d1b3145aa561539096c421ac8debde8cbc3612ec635fee29b" +checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c" [[package]] name = "libloading" diff --git a/Cargo.toml b/Cargo.toml index a565370a4..fac6f5e89 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -286,7 +286,7 @@ glob = "0.3.1" half = "2.3" indicatif = "0.17" itertools = "0.11.0" -libc = "0.2.149" +libc = "0.2.150" lscolors = { version = "0.15.0", default-features = false, features = [ "nu-ansi-term", ] } From e11878e7ba9f6344aa59b21722ba0c042176af6b Mon Sep 17 00:00:00 2001 From: Alexandre Hausen Date: Sun, 5 Nov 2023 20:58:04 -0300 Subject: [PATCH 26/73] du: remove crash! macro --- src/uu/du/src/du.rs | 49 ++++++++++++++++++++++++++------------------- 1 file changed, 28 insertions(+), 21 deletions(-) diff --git a/src/uu/du/src/du.rs b/src/uu/du/src/du.rs index ae88ed13b..ddfbc5cc4 100644 --- a/src/uu/du/src/du.rs +++ b/src/uu/du/src/du.rs @@ -31,13 +31,11 @@ use std::time::{Duration, UNIX_EPOCH}; use std::{error::Error, fmt::Display}; use uucore::display::{print_verbatim, Quotable}; use uucore::error::FromIo; -use uucore::error::{set_exit_code, UError, UResult}; +use uucore::error::{set_exit_code, UError, UResult, USimpleError}; use uucore::line_ending::LineEnding; use uucore::parse_glob; use uucore::parse_size::{parse_size_u64, ParseSizeError}; -use uucore::{ - crash, format_usage, help_about, help_section, help_usage, show, show_error, show_warning, -}; +use uucore::{format_usage, help_about, help_section, help_usage, show, show_error, show_warning}; #[cfg(windows)] use windows_sys::Win32::Foundation::HANDLE; #[cfg(windows)] @@ -255,22 +253,27 @@ fn get_file_info(path: &Path) -> Option { result } -fn read_block_size(s: Option<&str>) -> u64 { +fn read_block_size(s: Option<&str>) -> UResult { if let Some(s) = s { - parse_size_u64(s) - .unwrap_or_else(|e| crash!(1, "{}", format_error_message(&e, s, options::BLOCK_SIZE))) + match parse_size_u64(s) { + Ok(x) => Ok(x), + Err(e) => Err(USimpleError::new( + 1, + format_error_message(&e, s, options::BLOCK_SIZE), + )), + } } else { for env_var in ["DU_BLOCK_SIZE", "BLOCK_SIZE", "BLOCKSIZE"] { if let Ok(env_size) = env::var(env_var) { if let Ok(v) = parse_size_u64(&env_size) { - return v; + return Ok(v); } } } if env::var("POSIXLY_CORRECT").is_ok() { - 512 + Ok(512) } else { - 1024 + Ok(1024) } } } @@ -574,12 +577,20 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { matches .get_one::(options::BLOCK_SIZE) .map(|s| s.as_str()), - ); + )?; - let threshold = matches.get_one::(options::THRESHOLD).map(|s| { - Threshold::from_str(s) - .unwrap_or_else(|e| crash!(1, "{}", format_error_message(&e, s, options::THRESHOLD))) - }); + let threshold = match matches.get_one::(options::THRESHOLD) { + Some(s) => match Threshold::from_str(s) { + Ok(t) => Some(t), + Err(e) => { + return Err(USimpleError::new( + 1, + format_error_message(&e, s, options::THRESHOLD), + )) + } + }, + None => None, + }; let multiplier: u64 = if matches.get_flag(options::SI) { 1000 @@ -991,13 +1002,9 @@ mod test_du { #[test] fn test_read_block_size() { - let test_data = [ - (Some("1024".to_string()), 1024), - (Some("K".to_string()), 1024), - (None, 1024), - ]; + let test_data = [Some("1024".to_string()), Some("K".to_string()), None]; for it in &test_data { - assert_eq!(read_block_size(it.0.as_deref()), it.1); + assert!(matches!(read_block_size(it.as_deref()), Ok(1024))); } } } From 2571af8ededb52c53097508e4f2e848549f9075c Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Mon, 6 Nov 2023 10:15:47 +0100 Subject: [PATCH 27/73] du: use blocks to remove some cfgs --- src/uu/du/src/du.rs | 65 ++++++++++++++++++++++++--------------------- 1 file changed, 34 insertions(+), 31 deletions(-) diff --git a/src/uu/du/src/du.rs b/src/uu/du/src/du.rs index 9139c31c3..53577dcd5 100644 --- a/src/uu/du/src/du.rs +++ b/src/uu/du/src/du.rs @@ -137,39 +137,42 @@ impl Stat { }?; #[cfg(not(windows))] - let file_info = FileInfo { - file_id: metadata.ino() as u128, - dev_id: metadata.dev(), - }; - #[cfg(not(windows))] - return Ok(Self { - path: path.to_path_buf(), - is_dir: metadata.is_dir(), - size: if path.is_dir() { 0 } else { metadata.len() }, - blocks: metadata.blocks(), - inodes: 1, - inode: Some(file_info), - created: birth_u64(&metadata), - accessed: metadata.atime() as u64, - modified: metadata.mtime() as u64, - }); + { + let file_info = FileInfo { + file_id: metadata.ino() as u128, + dev_id: metadata.dev(), + }; + + return Ok(Self { + path: path.to_path_buf(), + is_dir: metadata.is_dir(), + size: if path.is_dir() { 0 } else { metadata.len() }, + blocks: metadata.blocks(), + inodes: 1, + inode: Some(file_info), + created: birth_u64(&metadata), + accessed: metadata.atime() as u64, + modified: metadata.mtime() as u64, + }); + } #[cfg(windows)] - let size_on_disk = get_size_on_disk(path); - #[cfg(windows)] - let file_info = get_file_info(path); - #[cfg(windows)] - Ok(Self { - path: path.to_path_buf(), - is_dir: metadata.is_dir(), - size: if path.is_dir() { 0 } else { metadata.len() }, - blocks: size_on_disk / 1024 * 2, - inode: file_info, - inodes: 1, - created: windows_creation_time_to_unix_time(metadata.creation_time()), - accessed: windows_time_to_unix_time(metadata.last_access_time()), - modified: windows_time_to_unix_time(metadata.last_write_time()), - }) + { + let size_on_disk = get_size_on_disk(path); + let file_info = get_file_info(path); + + Ok(Self { + path: path.to_path_buf(), + is_dir: metadata.is_dir(), + size: if path.is_dir() { 0 } else { metadata.len() }, + blocks: size_on_disk / 1024 * 2, + inodes: 1, + inode: file_info, + created: windows_creation_time_to_unix_time(metadata.creation_time()), + accessed: windows_time_to_unix_time(metadata.last_access_time()), + modified: windows_time_to_unix_time(metadata.last_write_time()), + }) + } } } From 993a995f8aa4ad04e46d79f4035dbf3eee02a953 Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Mon, 6 Nov 2023 10:21:24 +0100 Subject: [PATCH 28/73] du: remove unnecessary return --- src/uu/du/src/du.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/uu/du/src/du.rs b/src/uu/du/src/du.rs index 53577dcd5..0d5e34fde 100644 --- a/src/uu/du/src/du.rs +++ b/src/uu/du/src/du.rs @@ -143,7 +143,7 @@ impl Stat { dev_id: metadata.dev(), }; - return Ok(Self { + Ok(Self { path: path.to_path_buf(), is_dir: metadata.is_dir(), size: if path.is_dir() { 0 } else { metadata.len() }, @@ -153,7 +153,7 @@ impl Stat { created: birth_u64(&metadata), accessed: metadata.atime() as u64, modified: metadata.mtime() as u64, - }); + }) } #[cfg(windows)] From 7afb8461cbcccb54f8285617480c654b1ae160b8 Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Tue, 7 Nov 2023 10:30:54 +0100 Subject: [PATCH 29/73] du: add -H (alias for --dereference-args) --- src/uu/du/src/du.rs | 1 + tests/by-util/test_du.rs | 10 ++++++---- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/uu/du/src/du.rs b/src/uu/du/src/du.rs index 9139c31c3..407d90a54 100644 --- a/src/uu/du/src/du.rs +++ b/src/uu/du/src/du.rs @@ -821,6 +821,7 @@ pub fn uu_app() -> Command { .arg( Arg::new(options::DEREFERENCE_ARGS) .short('D') + .visible_short_alias('H') .long(options::DEREFERENCE_ARGS) .help("follow only symlinks that are listed on the command line") .action(ArgAction::SetTrue) diff --git a/tests/by-util/test_du.rs b/tests/by-util/test_du.rs index fdb44ef53..37594217d 100644 --- a/tests/by-util/test_du.rs +++ b/tests/by-util/test_du.rs @@ -299,11 +299,13 @@ fn test_du_dereference_args() { file2.write_all(b"amaz?ng").unwrap(); at.symlink_dir("subdir", "sublink"); - let result = ts.ucmd().arg("-D").arg("-s").arg("sublink").succeeds(); - let stdout = result.stdout_str(); + for arg in ["-D", "-H", "--dereference-args"] { + let result = ts.ucmd().arg(arg).arg("-s").arg("sublink").succeeds(); + let stdout = result.stdout_str(); - assert!(!stdout.starts_with('0')); - assert!(stdout.contains("sublink")); + assert!(!stdout.starts_with('0')); + assert!(stdout.contains("sublink")); + } // Without the option let result = ts.ucmd().arg("-s").arg("sublink").succeeds(); From 1a457c00aa99ecdf67c15673a93b70d56709f3aa Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Tue, 7 Nov 2023 11:37:17 +0100 Subject: [PATCH 30/73] CONTRIBUTING.md: start on git etiquette --- CONTRIBUTING.md | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index f59d3b4f8..ee912d786 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -183,7 +183,27 @@ outdated comments! ## Git Etiquette -todo +To ensure easy collaboration, we have guidelines for using Git and GitHub. + +### Commits + +- Make small and atomic commits. +- Keep a clean history of commits. +- Write informative commit messages. +- Annotate your commit message with the component you're editing. For example: `cp: do not overwrite on with -i` or `uucore: add support for FreeBSD`. +- Do not unnecessarily move items around in the code. This makes the changes much harder to review. If you do need to move things around, do that in a separate commit. + +### PRs + +- Make the titles of PRs descriptive. + - This means describing the problem you solve. For example, do not write `Fix #1234`, but `ls: fix version sort order`. + - You can prefix the title with the utility the PR concerns. +- Keep PRs small and self-contained. A set of small PRs is much more likely to get merged quickly than one large PR. +- Make sure the CI passes (up to intermittently failing tests). +- You know your code best, that's why it's best if you can solve merge conflicts on your branch yourself. + - It's up to you whether you want to use `git merge main` or `git rebase main`. + - Feel free to ask for help with merge conflicts. +- You do not need to ping maintainers to request a review, but it's fine to do so if you don't get a response within a few days. ## Platforms From 6678c17c52b6be1512d568e5b50c80c19018c346 Mon Sep 17 00:00:00 2001 From: Taylor <6225757+tskinn@users.noreply.github.com> Date: Tue, 7 Nov 2023 03:43:58 -0700 Subject: [PATCH 31/73] mktemp: add func to expose functionality (for use in nushell) (#5479) * mktemp: add func to expose functionality * mktemp: cleanup --- src/uu/mktemp/src/mktemp.rs | 58 +++++++++++++++++++++++++------------ 1 file changed, 40 insertions(+), 18 deletions(-) diff --git a/src/uu/mktemp/src/mktemp.rs b/src/uu/mktemp/src/mktemp.rs index 77ef5fcbe..d52351a89 100644 --- a/src/uu/mktemp/src/mktemp.rs +++ b/src/uu/mktemp/src/mktemp.rs @@ -115,29 +115,30 @@ impl Display for MkTempError { /// This provides a layer of indirection between the application logic /// and the argument parsing library `clap`, allowing each to vary /// independently. -struct Options { +#[derive(Clone)] +pub struct Options { /// Whether to create a temporary directory instead of a file. - directory: bool, + pub directory: bool, /// Whether to just print the name of a file that would have been created. - dry_run: bool, + pub dry_run: bool, /// Whether to suppress file creation error messages. - quiet: bool, + pub quiet: bool, /// The directory in which to create the temporary file. /// /// If `None`, the file will be created in the current directory. - tmpdir: Option, + pub tmpdir: Option, /// The suffix to append to the temporary file, if any. - suffix: Option, + pub suffix: Option, /// Whether to treat the template argument as a single file path component. - treat_as_template: bool, + pub treat_as_template: bool, /// The template to use for the name of the temporary file. - template: String, + pub template: String, } impl Options { @@ -192,7 +193,7 @@ impl Options { /// `num_rand_chars`. struct Params { /// The directory that will contain the temporary file. - directory: String, + directory: PathBuf, /// The (non-random) prefix of the temporary file. prefix: String, @@ -297,7 +298,7 @@ impl Params { let num_rand_chars = j - i; Ok(Self { - directory, + directory: directory.into(), prefix, num_rand_chars, suffix, @@ -357,12 +358,13 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { exec(&tmpdir, &prefix, rand, &suffix, make_dir) }; - if suppress_file_err { + let res = if suppress_file_err { // Mapping all UErrors to ExitCodes prevents the errors from being printed res.map_err(|e| e.code().into()) } else { res - } + }; + println_verbatim(res?).map_err_context(|| "failed to print directory name".to_owned()) } pub fn uu_app() -> Command { @@ -441,7 +443,7 @@ pub fn uu_app() -> Command { .arg(Arg::new(ARG_TEMPLATE).num_args(..=1)) } -pub fn dry_exec(tmpdir: &str, prefix: &str, rand: usize, suffix: &str) -> UResult<()> { +fn dry_exec(tmpdir: &Path, prefix: &str, rand: usize, suffix: &str) -> UResult { let len = prefix.len() + suffix.len() + rand; let mut buf = Vec::with_capacity(len); buf.extend(prefix.as_bytes()); @@ -462,7 +464,7 @@ pub fn dry_exec(tmpdir: &str, prefix: &str, rand: usize, suffix: &str) -> UResul // We guarantee utf8. let buf = String::from_utf8(buf).unwrap(); let tmpdir = Path::new(tmpdir).join(buf); - println_verbatim(tmpdir).map_err_context(|| "failed to print directory name".to_owned()) + Ok(tmpdir) } /// Create a temporary directory with the given parameters. @@ -476,7 +478,7 @@ pub fn dry_exec(tmpdir: &str, prefix: &str, rand: usize, suffix: &str) -> UResul /// /// If the temporary directory could not be written to disk or if the /// given directory `dir` does not exist. -fn make_temp_dir(dir: &str, prefix: &str, rand: usize, suffix: &str) -> UResult { +fn make_temp_dir(dir: &Path, prefix: &str, rand: usize, suffix: &str) -> UResult { let mut builder = Builder::new(); builder.prefix(prefix).rand_bytes(rand).suffix(suffix); match builder.tempdir_in(dir) { @@ -508,7 +510,7 @@ fn make_temp_dir(dir: &str, prefix: &str, rand: usize, suffix: &str) -> UResult< /// /// If the file could not be written to disk or if the directory does /// not exist. -fn make_temp_file(dir: &str, prefix: &str, rand: usize, suffix: &str) -> UResult { +fn make_temp_file(dir: &Path, prefix: &str, rand: usize, suffix: &str) -> UResult { let mut builder = Builder::new(); builder.prefix(prefix).rand_bytes(rand).suffix(suffix); match builder.tempfile_in(dir) { @@ -527,7 +529,7 @@ fn make_temp_file(dir: &str, prefix: &str, rand: usize, suffix: &str) -> UResult } } -fn exec(dir: &str, prefix: &str, rand: usize, suffix: &str, make_dir: bool) -> UResult<()> { +fn exec(dir: &Path, prefix: &str, rand: usize, suffix: &str, make_dir: bool) -> UResult { let path = if make_dir { make_temp_dir(dir, prefix, rand, suffix)? } else { @@ -546,7 +548,27 @@ fn exec(dir: &str, prefix: &str, rand: usize, suffix: &str, make_dir: bool) -> U // relative path. let path = Path::new(dir).join(filename); - println_verbatim(path).map_err_context(|| "failed to print directory name".to_owned()) + Ok(path) +} + +/// Create a temporary file or directory +/// +/// Behavior is determined by the `options` parameter, see [`Options`] for details. +pub fn mktemp(options: &Options) -> UResult { + // Parse file path parameters from the command-line options. + let Params { + directory: tmpdir, + prefix, + num_rand_chars: rand, + suffix, + } = Params::from(options.clone())?; + + // Create the temporary file or directory, or simulate creating it. + if options.dry_run { + dry_exec(&tmpdir, &prefix, rand, &suffix) + } else { + exec(&tmpdir, &prefix, rand, &suffix, options.directory) + } } #[cfg(test)] From f5709ded4743f96a852baa8134e09637b9619987 Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Tue, 7 Nov 2023 12:08:44 +0100 Subject: [PATCH 32/73] CONTRIBUTING.md: move and shorten commit message advice --- CONTRIBUTING.md | 135 ++++++++++++++++++++---------------------------- 1 file changed, 56 insertions(+), 79 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index ee912d786..aea215aec 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -14,8 +14,7 @@ check out these documents: Now follows a very important warning: -> [!WARNING] -> uutils is original code and cannot contain any code from GNU or +> [!WARNING] uutils is original code and cannot contain any code from GNU or > other implementations. This means that **we cannot accept any changes based on > the GNU source code**. To make sure that cannot happen, **you cannot link to > the GNU source code** either. @@ -53,15 +52,19 @@ use by others: We have the following goals with our development: -- **Compatible**: The utilities should be a drop-in replacement for the GNU coreutils. -- **Cross-platform**: All utilities should run on as many of the supported platforms as possible. +- **Compatible**: The utilities should be a drop-in replacement for the GNU + coreutils. +- **Cross-platform**: All utilities should run on as many of the supported + platforms as possible. - **Reliable**: The utilities should never unexpectedly fail. -- **Performant**: Our utilities should be written in fast idiomatic Rust. We aim to match or exceed the performance of the GNU utilities. -- **Well-tested**: We should have a lot of tests to be able to guarantee reliability and compatibility. +- **Performant**: Our utilities should be written in fast idiomatic Rust. We aim + to match or exceed the performance of the GNU utilities. +- **Well-tested**: We should have a lot of tests to be able to guarantee + reliability and compatibility. ## How to Help -There are several ways to help and writing code is just one them. Reporting +There are several ways to help and writing code is just one of them. Reporting issues and writing documentation are just as important as writing code. ### Reporting Issues @@ -92,7 +95,7 @@ work into a fix that we can't merge! If there's no issue for what you're trying to fix yet, make one _before_ you start working on the PR. Generally, we try to follow what GNU is doing in terms of options and behavior. -It is recommended to look at the GNU Coreutils manual +It is recommended to look at the GNU coreutils manual ([on the web](https://www.gnu.org/software/coreutils/manual/html_node/index.html), or locally using `info `). It is more in depth than the man pages and provides a good description of available features and their implementation @@ -190,20 +193,58 @@ To ensure easy collaboration, we have guidelines for using Git and GitHub. - Make small and atomic commits. - Keep a clean history of commits. - Write informative commit messages. -- Annotate your commit message with the component you're editing. For example: `cp: do not overwrite on with -i` or `uucore: add support for FreeBSD`. -- Do not unnecessarily move items around in the code. This makes the changes much harder to review. If you do need to move things around, do that in a separate commit. +- Annotate your commit message with the component you're editing. For example: + `cp: do not overwrite on with -i` or `uucore: add support for FreeBSD`. +- Do not unnecessarily move items around in the code. This makes the changes + much harder to review. If you do need to move things around, do that in a + separate commit. + +### Commit messages + +You can read this section in the Git book to learn how to write good commit +messages: https://git-scm.com/book/ch5-2.html. + +In addition, here are a few examples for a summary line when committing to +uutils: + +- commit for a single utility + +``` +nohup: cleanup and refactor +``` + +- commit for a utility's tests + +``` +tests/rm: test new feature +``` + +Beyond changes to an individual utility or its tests, other summary lines for +non-utility modules include: + +``` +README: add help +uucore: add new modules +uutils: add new utility +gitignore: add temporary files +``` ### PRs - Make the titles of PRs descriptive. - - This means describing the problem you solve. For example, do not write `Fix #1234`, but `ls: fix version sort order`. + - This means describing the problem you solve. For example, do not write + `Fix #1234`, but `ls: fix version sort order`. - You can prefix the title with the utility the PR concerns. -- Keep PRs small and self-contained. A set of small PRs is much more likely to get merged quickly than one large PR. +- Keep PRs small and self-contained. A set of small PRs is much more likely to + get merged quickly than one large PR. - Make sure the CI passes (up to intermittently failing tests). -- You know your code best, that's why it's best if you can solve merge conflicts on your branch yourself. - - It's up to you whether you want to use `git merge main` or `git rebase main`. +- You know your code best, that's why it's best if you can solve merge conflicts + on your branch yourself. + - It's up to you whether you want to use `git merge main` or + `git rebase main`. - Feel free to ask for help with merge conflicts. -- You do not need to ping maintainers to request a review, but it's fine to do so if you don't get a response within a few days. +- You do not need to ping maintainers to request a review, but it's fine to do + so if you don't get a response within a few days. ## Platforms @@ -246,70 +287,6 @@ To improve the GNU compatibility, the following process is recommended: 1. Start to modify the Rust implementation to match the expected behavior 1. Add a test to make sure that we don't regress (our test suite is super quick) -## Commit messages - -To help the project maintainers review pull requests from contributors across -numerous utilities, the team has settled on conventions for commit messages. - -From : - -``` -Capitalized, short (50 chars or less) summary - -More detailed explanatory text, if necessary. Wrap it to about 72 -characters or so. In some contexts, the first line is treated as the -subject of an email and the rest of the text as the body. The blank -line separating the summary from the body is critical (unless you omit -the body entirely); tools like rebase will confuse you if you run the -two together. - -Write your commit message in the imperative: "Fix bug" and not "Fixed bug" -or "Fixes bug." This convention matches up with commit messages generated -by commands like git merge and git revert. - -Further paragraphs come after blank lines. - - - Bullet points are okay, too - - - Typically a hyphen or asterisk is used for the bullet, followed by a - single space, with blank lines in between, but conventions vary here - - - Use a hanging indent -``` - -Furthermore, here are a few examples for a summary line: - -- commit for a single utility - -``` -nohup: cleanup and refactor -``` - -- commit for a utility's tests - -``` -tests/rm: test new feature -``` - -Beyond changes to an individual utility or its tests, other summary lines for -non-utility modules include: - -``` -README: add help -``` - -``` -uucore: add new modules -``` - -``` -uutils: add new utility -``` - -``` -gitignore: add temporary files -``` - ## Code coverage To generate code coverage report locally please follow From 46d4ebff4c32f135406aff9489c6859f9bec549c Mon Sep 17 00:00:00 2001 From: clint Date: Tue, 7 Nov 2023 18:02:13 -0500 Subject: [PATCH 33/73] expand: remove crash! macro --- src/uu/expand/src/expand.rs | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/src/uu/expand/src/expand.rs b/src/uu/expand/src/expand.rs index 9294d1a8f..71d03a49a 100644 --- a/src/uu/expand/src/expand.rs +++ b/src/uu/expand/src/expand.rs @@ -15,7 +15,7 @@ use std::str::from_utf8; use unicode_width::UnicodeWidthChar; use uucore::display::Quotable; use uucore::error::{FromIo, UError, UResult}; -use uucore::{crash, format_usage, help_about, help_usage}; +use uucore::{format_usage, help_about, help_usage}; const ABOUT: &str = help_about!("expand.md"); const USAGE: &str = help_usage!("expand.md"); @@ -308,16 +308,13 @@ pub fn uu_app() -> Command { ) } -fn open(path: &str) -> BufReader> { +fn open(path: &str) -> std::io::Result>> { let file_buf; if path == "-" { - BufReader::new(Box::new(stdin()) as Box) + Ok(BufReader::new(Box::new(stdin()) as Box)) } else { - file_buf = match File::open(path) { - Ok(a) => a, - Err(e) => crash!(1, "{}: {}\n", path.maybe_quote(), e), - }; - BufReader::new(Box::new(file_buf) as Box) + file_buf = File::open(path)?; + Ok(BufReader::new(Box::new(file_buf) as Box)) } } @@ -378,7 +375,7 @@ fn expand(options: &Options) -> std::io::Result<()> { let mut buf = Vec::new(); for file in &options.files { - let mut fh = open(file); + let mut fh = open(file)?; while match fh.read_until(b'\n', &mut buf) { Ok(s) => s > 0, From 3411c25112f31acb5b442c6bd39901b41104b7d6 Mon Sep 17 00:00:00 2001 From: clint Date: Tue, 7 Nov 2023 20:18:58 -0500 Subject: [PATCH 34/73] expand: make error output the same as it was --- src/uu/expand/src/expand.rs | 28 +++++++++++++++++++--------- 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/src/uu/expand/src/expand.rs b/src/uu/expand/src/expand.rs index 71d03a49a..5791fca9f 100644 --- a/src/uu/expand/src/expand.rs +++ b/src/uu/expand/src/expand.rs @@ -265,7 +265,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { let matches = uu_app().try_get_matches_from(expand_shortcuts(&args))?; - expand(&Options::new(&matches)?).map_err_context(|| "failed to write output".to_string()) + expand(&Options::new(&matches)?) } pub fn uu_app() -> Command { @@ -308,12 +308,12 @@ pub fn uu_app() -> Command { ) } -fn open(path: &str) -> std::io::Result>> { +fn open(path: &str) -> UResult>> { let file_buf; if path == "-" { Ok(BufReader::new(Box::new(stdin()) as Box)) } else { - file_buf = File::open(path)?; + file_buf = File::open(path).map_err_context(|| path.to_string())?; Ok(BufReader::new(Box::new(file_buf) as Box)) } } @@ -367,7 +367,7 @@ enum CharType { } #[allow(clippy::cognitive_complexity)] -fn expand(options: &Options) -> std::io::Result<()> { +fn expand(options: &Options) -> UResult<()> { use self::CharType::*; let mut output = BufWriter::new(stdout()); @@ -428,12 +428,18 @@ fn expand(options: &Options) -> std::io::Result<()> { // now dump out either spaces if we're expanding, or a literal tab if we're not if init || !options.iflag { if nts <= options.tspaces.len() { - output.write_all(options.tspaces[..nts].as_bytes())?; + output + .write_all(options.tspaces[..nts].as_bytes()) + .map_err_context(|| "failed to write output".to_string())?; } else { - output.write_all(" ".repeat(nts).as_bytes())?; + output + .write_all(" ".repeat(nts).as_bytes()) + .map_err_context(|| "failed to write output".to_string())?; }; } else { - output.write_all(&buf[byte..byte + nbytes])?; + output + .write_all(&buf[byte..byte + nbytes]) + .map_err_context(|| "failed to write output".to_string())?; } } _ => { @@ -451,14 +457,18 @@ fn expand(options: &Options) -> std::io::Result<()> { init = false; } - output.write_all(&buf[byte..byte + nbytes])?; + output + .write_all(&buf[byte..byte + nbytes]) + .map_err_context(|| "failed to write output".to_string())?; } } byte += nbytes; // advance the pointer } - output.flush()?; + output + .flush() + .map_err_context(|| "failed to write output".to_string())?; buf.truncate(0); // clear the buffer } } From 761213f1d2ad1784a00e9145817bd7c4919d92dc Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Tue, 7 Nov 2023 15:47:04 +0100 Subject: [PATCH 35/73] cp: make test_closes_file_descriptors Linux-only --- tests/by-util/test_cp.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/tests/by-util/test_cp.rs b/tests/by-util/test_cp.rs index 311558d38..c8761fab8 100644 --- a/tests/by-util/test_cp.rs +++ b/tests/by-util/test_cp.rs @@ -24,8 +24,6 @@ use std::path::PathBuf; #[cfg(any(target_os = "linux", target_os = "android"))] use filetime::FileTime; -#[cfg(any(target_os = "linux", target_os = "android"))] -use rlimit::Resource; #[cfg(target_os = "linux")] use std::ffi::OsString; #[cfg(any(target_os = "linux", target_os = "android"))] @@ -2120,10 +2118,11 @@ fn test_cp_reflink_insufficient_permission() { .stderr_only("cp: 'unreadable' -> 'existing_file.txt': Permission denied (os error 13)\n"); } -#[cfg(any(target_os = "linux", target_os = "android"))] +#[cfg(target_os = "linux")] #[test] fn test_closes_file_descriptors() { use procfs::process::Process; + use rlimit::Resource; let me = Process::myself().unwrap(); // The test suite runs in parallel, we have pipe, sockets @@ -2133,7 +2132,6 @@ fn test_closes_file_descriptors() { let limit_fd: u64 = number_file_already_opened + 9; // For debugging purposes: - #[cfg(not(target_os = "android"))] for f in me.fd().unwrap() { let fd = f.unwrap(); println!("{:?} {:?}", fd, fd.mode()); From a26e3db56e4778a4dd6058b14fad6912324a302b Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Wed, 8 Nov 2023 10:00:26 +0100 Subject: [PATCH 36/73] CONTRIBUTING.md: spell-checker:ignore "rustdoc's" --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index aea215aec..255ed2c53 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,4 +1,4 @@ - + # Contributing to coreutils From 4d40555cd58dbd82f908cbd20ea57d617fe6e7b3 Mon Sep 17 00:00:00 2001 From: Alexandre Hausen Date: Wed, 8 Nov 2023 20:32:03 -0300 Subject: [PATCH 37/73] fixup! du: remove crash! macro --- src/uu/du/src/du.rs | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/src/uu/du/src/du.rs b/src/uu/du/src/du.rs index ddfbc5cc4..3a8ac4b14 100644 --- a/src/uu/du/src/du.rs +++ b/src/uu/du/src/du.rs @@ -255,13 +255,8 @@ fn get_file_info(path: &Path) -> Option { fn read_block_size(s: Option<&str>) -> UResult { if let Some(s) = s { - match parse_size_u64(s) { - Ok(x) => Ok(x), - Err(e) => Err(USimpleError::new( - 1, - format_error_message(&e, s, options::BLOCK_SIZE), - )), - } + parse_size_u64(s) + .map_err(|e| USimpleError::new(1, format_error_message(&e, s, options::BLOCK_SIZE))) } else { for env_var in ["DU_BLOCK_SIZE", "BLOCK_SIZE", "BLOCKSIZE"] { if let Ok(env_size) = env::var(env_var) { From 7279bc1741c0ee7678a4a66d26392a1ec7dcfed9 Mon Sep 17 00:00:00 2001 From: Zhuoxun Yang Date: Thu, 9 Nov 2023 10:16:53 +0800 Subject: [PATCH 38/73] printf.md: support %q --- src/uu/printf/printf.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/uu/printf/printf.md b/src/uu/printf/printf.md index 60b50354c..9ce295770 100644 --- a/src/uu/printf/printf.md +++ b/src/uu/printf/printf.md @@ -78,6 +78,9 @@ Fields second parameter is min-width, integer output below that width is padded with leading zeroes +* `%q`: ARGUMENT is printed in a format that can be reused as shell input, escaping non-printable + characters with the proposed POSIX $'' syntax. + * `%f` or `%F`: decimal floating point value * `%e` or `%E`: scientific notation floating point value * `%g` or `%G`: shorter of specially interpreted decimal or SciNote floating point value. @@ -181,6 +184,11 @@ All string fields have a 'max width' parameter still be interpreted and not throw a warning, you will have problems if you use this for a literal whose code begins with zero, as it will be viewed as in `\\0NNN` form.) +* `%q`: escaped string - the string in a format that can be reused as input by most shells. + Non-printable characters are escaped with the POSIX proposed โ€˜$''โ€™ syntax, + and shell meta-characters are quoted appropriately. + This is an equivalent format to ls --quoting=shell-escape output. + #### CHAR SUBSTITUTIONS The character field does not have a secondary parameter. From e3ec12233b04f2846769a585aa0f73b64af08833 Mon Sep 17 00:00:00 2001 From: Zhuoxun Yang Date: Thu, 9 Nov 2023 10:17:44 +0800 Subject: [PATCH 39/73] printf: support %q --- src/uucore/src/lib/features/tokenize/sub.rs | 22 ++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/src/uucore/src/lib/features/tokenize/sub.rs b/src/uucore/src/lib/features/tokenize/sub.rs index c65a37a68..0ae966fc3 100644 --- a/src/uucore/src/lib/features/tokenize/sub.rs +++ b/src/uucore/src/lib/features/tokenize/sub.rs @@ -10,6 +10,7 @@ //! Subs which have numeric field chars make use of the num_format //! submodule use crate::error::{UError, UResult}; +use crate::quoting_style::{escape_name, QuotingStyle}; use itertools::{put_back_n, PutBackN}; use std::error::Error; use std::fmt::Display; @@ -91,7 +92,7 @@ impl Sub { // for more dry printing, field characters are grouped // in initialization of token. let field_type = match field_char { - 's' | 'b' => FieldType::Strf, + 's' | 'b' | 'q' => FieldType::Strf, 'd' | 'i' | 'u' | 'o' | 'x' | 'X' => FieldType::Intf, 'f' | 'F' => FieldType::Floatf, 'a' | 'A' => FieldType::CninetyNineHexFloatf, @@ -189,7 +190,7 @@ impl SubParser { let mut legal_fields = [ // 'a', 'A', //c99 hex float implementation not yet complete - 'b', 'c', 'd', 'e', 'E', 'f', 'F', 'g', 'G', 'i', 'o', 's', 'u', 'x', 'X', + 'b', 'c', 'd', 'e', 'E', 'f', 'F', 'g', 'G', 'i', 'o', 'q', 's', 'u', 'x', 'X', ]; let mut specifiers = ['h', 'j', 'l', 'L', 't', 'z']; legal_fields.sort_unstable(); @@ -260,7 +261,6 @@ impl SubParser { } x if legal_fields.binary_search(&x).is_ok() => { self.field_char = Some(ch); - self.text_so_far.push(ch); break; } x if specifiers.binary_search(&x).is_ok() => { @@ -331,7 +331,7 @@ impl SubParser { if (field_char == 's' && self.min_width_tmp == Some(String::from("0"))) || (field_char == 'c' && (self.min_width_tmp == Some(String::from("0")) || self.past_decimal)) - || (field_char == 'b' + || ((field_char == 'b' || field_char == 'q') && (self.min_width_tmp.is_some() || self.past_decimal || self.second_field_tmp.is_some())) @@ -391,6 +391,7 @@ impl Sub { // if %s just return arg // if %b use UnescapedText module's unescape-fn // if %c return first char of arg + // if %q return arg which non-printable characters are escaped FieldType::Strf | FieldType::Charf => { match pf_arg { Some(arg_string) => { @@ -404,11 +405,18 @@ impl Sub { UnescapedText::from_it_core(writer, &mut a_it, true); None } - // for 'c': get iter of string vals, + 'q' => Some(escape_name( + arg_string.as_ref(), + &QuotingStyle::Shell { + escape: true, + always_quote: false, + show_control: false, + }, + )), // get opt of first val // and map it to opt - /* 'c' | */ - _ => arg_string.chars().next().map(|x| x.to_string()), + 'c' => arg_string.chars().next().map(|x| x.to_string()), + _ => unreachable!(), } } None => None, From fb414ed9179a985de0e545182395a3bd6effbb02 Mon Sep 17 00:00:00 2001 From: Zhuoxun Yang Date: Thu, 9 Nov 2023 10:18:27 +0800 Subject: [PATCH 40/73] tests/printf: support %q --- tests/by-util/test_printf.rs | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/tests/by-util/test_printf.rs b/tests/by-util/test_printf.rs index d7ba5679e..a297dbf68 100644 --- a/tests/by-util/test_printf.rs +++ b/tests/by-util/test_printf.rs @@ -112,6 +112,15 @@ fn sub_b_string_handle_escapes() { .stdout_only("hello \tworld"); } +#[test] +fn sub_b_string_validate_field_params() { + new_ucmd!() + .args(&["hello %7b", "world"]) + .run() + .stdout_is("hello ") + .stderr_is("printf: %7b: invalid conversion specification\n"); +} + #[test] fn sub_b_string_ignore_subs() { new_ucmd!() @@ -120,6 +129,31 @@ fn sub_b_string_ignore_subs() { .stdout_only("hello world %% %i"); } +#[test] +fn sub_q_string_non_printable() { + new_ucmd!() + .args(&["non-printable: %q", "\"$test\""]) + .succeeds() + .stdout_only("non-printable: '\"$test\"'"); +} + +#[test] +fn sub_q_string_validate_field_params() { + new_ucmd!() + .args(&["hello %7q", "world"]) + .run() + .stdout_is("hello ") + .stderr_is("printf: %7q: invalid conversion specification\n"); +} + +#[test] +fn sub_q_string_special_non_printable() { + new_ucmd!() + .args(&["non-printable: %q", "test~"]) + .succeeds() + .stdout_only("non-printable: test~"); +} + #[test] fn sub_char() { new_ucmd!() From fc00b6bfc9c5649b428f2420a2d0e23209ee1ae1 Mon Sep 17 00:00:00 2001 From: clint Date: Wed, 8 Nov 2023 23:36:46 -0500 Subject: [PATCH 41/73] expand: move logic to expand a single line into its own function --- src/uu/expand/src/expand.rs | 181 ++++++++++++++++++------------------ 1 file changed, 91 insertions(+), 90 deletions(-) diff --git a/src/uu/expand/src/expand.rs b/src/uu/expand/src/expand.rs index 5791fca9f..4815c5f68 100644 --- a/src/uu/expand/src/expand.rs +++ b/src/uu/expand/src/expand.rs @@ -366,10 +366,98 @@ enum CharType { Other, } -#[allow(clippy::cognitive_complexity)] -fn expand(options: &Options) -> UResult<()> { +fn expand_line( + buf: &mut Vec, + output: &mut BufWriter, + ts: &[usize], + options: &Options, +) -> std::io::Result<()> { use self::CharType::*; + let mut col = 0; + let mut byte = 0; + let mut init = true; + + while byte < buf.len() { + let (ctype, cwidth, nbytes) = if options.uflag { + let nbytes = char::from(buf[byte]).len_utf8(); + + if byte + nbytes > buf.len() { + // don't overrun buffer because of invalid UTF-8 + (Other, 1, 1) + } else if let Ok(t) = from_utf8(&buf[byte..byte + nbytes]) { + match t.chars().next() { + Some('\t') => (Tab, 0, nbytes), + Some('\x08') => (Backspace, 0, nbytes), + Some(c) => (Other, UnicodeWidthChar::width(c).unwrap_or(0), nbytes), + None => { + // no valid char at start of t, so take 1 byte + (Other, 1, 1) + } + } + } else { + (Other, 1, 1) // implicit assumption: non-UTF-8 char is 1 col wide + } + } else { + ( + match buf[byte] { + // always take exactly 1 byte in strict ASCII mode + 0x09 => Tab, + 0x08 => Backspace, + _ => Other, + }, + 1, + 1, + ) + }; + + // figure out how many columns this char takes up + match ctype { + Tab => { + // figure out how many spaces to the next tabstop + let nts = next_tabstop(ts, col, &options.remaining_mode); + col += nts; + + // now dump out either spaces if we're expanding, or a literal tab if we're not + if init || !options.iflag { + if nts <= options.tspaces.len() { + output.write_all(options.tspaces[..nts].as_bytes())?; + } else { + output.write_all(" ".repeat(nts).as_bytes())?; + }; + } else { + output.write_all(&buf[byte..byte + nbytes])?; + } + } + _ => { + col = if ctype == Other { + col + cwidth + } else if col > 0 { + col - 1 + } else { + 0 + }; + + // if we're writing anything other than a space, then we're + // done with the line's leading spaces + if buf[byte] != 0x20 { + init = false; + } + + output.write_all(&buf[byte..byte + nbytes])?; + } + } + + byte += nbytes; // advance the pointer + } + + output.flush()?; + buf.truncate(0); // clear the buffer + + Ok(()) +} + +fn expand(options: &Options) -> UResult<()> { let mut output = BufWriter::new(stdout()); let ts = options.tabstops.as_ref(); let mut buf = Vec::new(); @@ -381,95 +469,8 @@ fn expand(options: &Options) -> UResult<()> { Ok(s) => s > 0, Err(_) => buf.is_empty(), } { - let mut col = 0; - let mut byte = 0; - let mut init = true; - - while byte < buf.len() { - let (ctype, cwidth, nbytes) = if options.uflag { - let nbytes = char::from(buf[byte]).len_utf8(); - - if byte + nbytes > buf.len() { - // don't overrun buffer because of invalid UTF-8 - (Other, 1, 1) - } else if let Ok(t) = from_utf8(&buf[byte..byte + nbytes]) { - match t.chars().next() { - Some('\t') => (Tab, 0, nbytes), - Some('\x08') => (Backspace, 0, nbytes), - Some(c) => (Other, UnicodeWidthChar::width(c).unwrap_or(0), nbytes), - None => { - // no valid char at start of t, so take 1 byte - (Other, 1, 1) - } - } - } else { - (Other, 1, 1) // implicit assumption: non-UTF-8 char is 1 col wide - } - } else { - ( - match buf[byte] { - // always take exactly 1 byte in strict ASCII mode - 0x09 => Tab, - 0x08 => Backspace, - _ => Other, - }, - 1, - 1, - ) - }; - - // figure out how many columns this char takes up - match ctype { - Tab => { - // figure out how many spaces to the next tabstop - let nts = next_tabstop(ts, col, &options.remaining_mode); - col += nts; - - // now dump out either spaces if we're expanding, or a literal tab if we're not - if init || !options.iflag { - if nts <= options.tspaces.len() { - output - .write_all(options.tspaces[..nts].as_bytes()) - .map_err_context(|| "failed to write output".to_string())?; - } else { - output - .write_all(" ".repeat(nts).as_bytes()) - .map_err_context(|| "failed to write output".to_string())?; - }; - } else { - output - .write_all(&buf[byte..byte + nbytes]) - .map_err_context(|| "failed to write output".to_string())?; - } - } - _ => { - col = if ctype == Other { - col + cwidth - } else if col > 0 { - col - 1 - } else { - 0 - }; - - // if we're writing anything other than a space, then we're - // done with the line's leading spaces - if buf[byte] != 0x20 { - init = false; - } - - output - .write_all(&buf[byte..byte + nbytes]) - .map_err_context(|| "failed to write output".to_string())?; - } - } - - byte += nbytes; // advance the pointer - } - - output - .flush() + expand_line(&mut buf, &mut output, ts, options) .map_err_context(|| "failed to write output".to_string())?; - buf.truncate(0); // clear the buffer } } Ok(()) From 2e0e88c3acf6b86965bb7ed60a23662d439d74f7 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Wed, 8 Nov 2023 12:46:24 +0100 Subject: [PATCH 42/73] fuzz: verify the various steps when opening/closing fd --- fuzz/fuzz_targets/fuzz_common.rs | 53 +++++++++++++++++++++++++------- 1 file changed, 42 insertions(+), 11 deletions(-) diff --git a/fuzz/fuzz_targets/fuzz_common.rs b/fuzz/fuzz_targets/fuzz_common.rs index a94963ef0..86b8e5618 100644 --- a/fuzz/fuzz_targets/fuzz_common.rs +++ b/fuzz/fuzz_targets/fuzz_common.rs @@ -3,9 +3,10 @@ // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. -use libc::{dup, dup2, STDOUT_FILENO}; +use libc::{close, dup, dup2, pipe, STDOUT_FILENO}; use std::ffi::OsString; use std::io; +use std::io::Write; use std::process::Command; use std::sync::atomic::Ordering; use std::sync::{atomic::AtomicBool, Once}; @@ -38,18 +39,43 @@ where { let uumain_exit_status; + // Duplicate the stdout file descriptor let original_stdout_fd = unsafe { dup(STDOUT_FILENO) }; - println!("Running test {:?}", &args[1..]); - let mut pipe_fds = [-1; 2]; - unsafe { libc::pipe(pipe_fds.as_mut_ptr()) }; - - { - unsafe { dup2(pipe_fds[1], STDOUT_FILENO) }; - uumain_exit_status = uumain_function(args.to_owned().into_iter()); - unsafe { dup2(original_stdout_fd, STDOUT_FILENO) }; - unsafe { libc::close(original_stdout_fd) }; + if original_stdout_fd == -1 { + return ("Failed to duplicate STDOUT_FILENO".to_string(), -1); } - unsafe { libc::close(pipe_fds[1]) }; + println!("Running test {:?}", &args[0..]); + let mut pipe_fds = [-1; 2]; + + if unsafe { pipe(pipe_fds.as_mut_ptr()) } == -1 { + return ("Failed to create a pipe".to_string(), -1); + } + + // Redirect stdout to the pipe + unsafe { + if dup2(pipe_fds[1], STDOUT_FILENO) == -1 { + close(pipe_fds[0]); + close(pipe_fds[1]); + return ( + "Failed to redirect STDOUT_FILENO to the pipe".to_string(), + -1, + ); + } + } + + uumain_exit_status = uumain_function(args.to_owned().into_iter()); + + // Restore the original stdout + unsafe { + if dup2(original_stdout_fd, STDOUT_FILENO) == -1 { + return ( + "Failed to restore the original STDOUT_FILENO".to_string(), + -1, + ); + } + close(original_stdout_fd); + } + unsafe { close(pipe_fds[1]) }; let mut captured_output = Vec::new(); let mut read_buffer = [0; 1024]; @@ -61,6 +87,11 @@ where read_buffer.len(), ) }; + + if bytes_read == -1 { + eprintln!("Failed to read from the pipe"); + break; + } if bytes_read <= 0 { break; } From 5fed5443e43a167d11a334163009e6538ec766f8 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Wed, 8 Nov 2023 12:46:46 +0100 Subject: [PATCH 43/73] fuzz: flush after calling uumain - was failing with printf --- fuzz/fuzz_targets/fuzz_common.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/fuzz/fuzz_targets/fuzz_common.rs b/fuzz/fuzz_targets/fuzz_common.rs index 86b8e5618..e30e24ddd 100644 --- a/fuzz/fuzz_targets/fuzz_common.rs +++ b/fuzz/fuzz_targets/fuzz_common.rs @@ -64,6 +64,7 @@ where } uumain_exit_status = uumain_function(args.to_owned().into_iter()); + io::stdout().flush().unwrap(); // Restore the original stdout unsafe { From 064ad7200c80a45a3a19e83a8becf73689af153a Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Wed, 8 Nov 2023 23:06:11 +0100 Subject: [PATCH 44/73] fuzzing function should also return stderr --- fuzz/fuzz_targets/fuzz_common.rs | 137 ++++++++++++++++++++----------- 1 file changed, 90 insertions(+), 47 deletions(-) diff --git a/fuzz/fuzz_targets/fuzz_common.rs b/fuzz/fuzz_targets/fuzz_common.rs index e30e24ddd..9afc2cc83 100644 --- a/fuzz/fuzz_targets/fuzz_common.rs +++ b/fuzz/fuzz_targets/fuzz_common.rs @@ -3,10 +3,11 @@ // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. -use libc::{close, dup, dup2, pipe, STDOUT_FILENO}; +use libc::{close, dup, dup2, pipe, STDERR_FILENO, STDOUT_FILENO}; use std::ffi::OsString; use std::io; use std::io::Write; +use std::os::fd::RawFd; use std::process::Command; use std::sync::atomic::Ordering; use std::sync::{atomic::AtomicBool, Once}; @@ -33,57 +34,90 @@ pub fn is_gnu_cmd(cmd_path: &str) -> Result<(), std::io::Error> { } } -pub fn generate_and_run_uumain(args: &[OsString], uumain_function: F) -> (String, i32) +pub fn generate_and_run_uumain(args: &[OsString], uumain_function: F) -> (String, String, i32) where F: FnOnce(std::vec::IntoIter) -> i32, { let uumain_exit_status; - // Duplicate the stdout file descriptor + // Duplicate the stdout and stderr file descriptors let original_stdout_fd = unsafe { dup(STDOUT_FILENO) }; - if original_stdout_fd == -1 { - return ("Failed to duplicate STDOUT_FILENO".to_string(), -1); + let original_stderr_fd = unsafe { dup(STDERR_FILENO) }; + if original_stdout_fd == -1 || original_stderr_fd == -1 { + return ( + "Failed to duplicate STDOUT_FILENO or STDERR_FILENO".to_string(), + "".to_string(), + -1, + ); } println!("Running test {:?}", &args[0..]); - let mut pipe_fds = [-1; 2]; + let mut pipe_stdout_fds = [-1; 2]; + let mut pipe_stderr_fds = [-1; 2]; - if unsafe { pipe(pipe_fds.as_mut_ptr()) } == -1 { - return ("Failed to create a pipe".to_string(), -1); + // Create pipes for stdout and stderr + if unsafe { pipe(pipe_stdout_fds.as_mut_ptr()) } == -1 + || unsafe { pipe(pipe_stderr_fds.as_mut_ptr()) } == -1 + { + return ("Failed to create pipes".to_string(), "".to_string(), -1); } - // Redirect stdout to the pipe - unsafe { - if dup2(pipe_fds[1], STDOUT_FILENO) == -1 { - close(pipe_fds[0]); - close(pipe_fds[1]); - return ( - "Failed to redirect STDOUT_FILENO to the pipe".to_string(), - -1, - ); + // Redirect stdout and stderr to their respective pipes + if unsafe { dup2(pipe_stdout_fds[1], STDOUT_FILENO) } == -1 + || unsafe { dup2(pipe_stderr_fds[1], STDERR_FILENO) } == -1 + { + unsafe { + close(pipe_stdout_fds[0]); + close(pipe_stdout_fds[1]); + close(pipe_stderr_fds[0]); + close(pipe_stderr_fds[1]); } + return ( + "Failed to redirect STDOUT_FILENO or STDERR_FILENO".to_string(), + "".to_string(), + -1, + ); } uumain_exit_status = uumain_function(args.to_owned().into_iter()); io::stdout().flush().unwrap(); + io::stderr().flush().unwrap(); - // Restore the original stdout - unsafe { - if dup2(original_stdout_fd, STDOUT_FILENO) == -1 { - return ( - "Failed to restore the original STDOUT_FILENO".to_string(), - -1, - ); - } - close(original_stdout_fd); + // Restore the original stdout and stderr + if unsafe { dup2(original_stdout_fd, STDOUT_FILENO) } == -1 + || unsafe { dup2(original_stderr_fd, STDERR_FILENO) } == -1 + { + return ( + "Failed to restore the original STDOUT_FILENO or STDERR_FILENO".to_string(), + "".to_string(), + -1, + ); } - unsafe { close(pipe_fds[1]) }; + unsafe { + close(original_stdout_fd); + close(original_stderr_fd); + } + unsafe { close(pipe_stdout_fds[1]) }; + unsafe { close(pipe_stderr_fds[1]) }; + let captured_stdout = read_from_fd(pipe_stdout_fds[0]).trim().to_string(); + let captured_stderr = read_from_fd(pipe_stderr_fds[0]).to_string(); + let captured_stderr = captured_stderr + .splitn(2, ':') + .nth(1) + .unwrap_or("") + .trim() + .to_string(); + + (captured_stdout, captured_stderr, uumain_exit_status) +} + +fn read_from_fd(fd: RawFd) -> String { let mut captured_output = Vec::new(); let mut read_buffer = [0; 1024]; loop { let bytes_read = unsafe { libc::read( - pipe_fds[0], + fd, read_buffer.as_mut_ptr() as *mut libc::c_void, read_buffer.len(), ) @@ -93,29 +127,30 @@ where eprintln!("Failed to read from the pipe"); break; } - if bytes_read <= 0 { + if bytes_read == 0 { break; } captured_output.extend_from_slice(&read_buffer[..bytes_read as usize]); } - unsafe { libc::close(pipe_fds[0]) }; + unsafe { libc::close(fd) }; - let my_output = String::from_utf8_lossy(&captured_output) - .to_string() - .trim() - .to_owned(); - - (my_output, uumain_exit_status) + String::from_utf8_lossy(&captured_output).into_owned() } pub fn run_gnu_cmd( cmd_path: &str, args: &[OsString], check_gnu: bool, -) -> Result<(String, i32), io::Error> { +) -> Result<(String, String, i32), (String, String, i32)> { if check_gnu { - is_gnu_cmd(cmd_path)?; // Check if it's a GNU implementation + match is_gnu_cmd(cmd_path) { + Ok(_) => {} // if the check passes, do nothing + Err(e) => { + // Convert the io::Error into the function's error type + return Err((String::new(), e.to_string(), -1)); + } + } } let mut command = Command::new(cmd_path); @@ -123,17 +158,25 @@ pub fn run_gnu_cmd( command.arg(arg); } - let output = command.output()?; + let output = match command.output() { + Ok(output) => output, + Err(e) => return Err((String::new(), e.to_string(), -1)), + }; let exit_code = output.status.code().unwrap_or(-1); + + // Here we get stdout and stderr as Strings + let stdout = String::from_utf8_lossy(&output.stdout).to_string(); + let stderr = String::from_utf8_lossy(&output.stderr).to_string(); + let stderr = stderr + .splitn(2, ':') + .nth(1) + .unwrap_or("") + .trim() + .to_string(); + if output.status.success() || !check_gnu { - Ok(( - String::from_utf8_lossy(&output.stdout).to_string(), - exit_code, - )) + Ok((stdout, stderr, exit_code)) } else { - Err(io::Error::new( - io::ErrorKind::Other, - format!("GNU command execution failed with exit code {}", exit_code), - )) + Err((stdout, stderr, exit_code)) } } From 8ab20c4673dec1bc54bd8bc9a20177f6fffde16a Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Wed, 8 Nov 2023 23:31:41 +0100 Subject: [PATCH 45/73] fuzz: adjust the fuzzers with the new arg --- fuzz/fuzz_targets/fuzz_expr.rs | 33 ++++++++++++++++++++++++--------- fuzz/fuzz_targets/fuzz_test.rs | 29 +++++++++++++++++++++-------- 2 files changed, 45 insertions(+), 17 deletions(-) diff --git a/fuzz/fuzz_targets/fuzz_expr.rs b/fuzz/fuzz_targets/fuzz_expr.rs index c2217c48a..ee65745bf 100644 --- a/fuzz/fuzz_targets/fuzz_expr.rs +++ b/fuzz/fuzz_targets/fuzz_expr.rs @@ -84,7 +84,7 @@ fuzz_target!(|_data: &[u8]| { let mut args = vec![OsString::from("expr")]; args.extend(expr.split_whitespace().map(OsString::from)); - let (rust_output, uumain_exit_code) = generate_and_run_uumain(&args, uumain); + let (rust_stdout, rust_stderr, uumain_exit_code) = generate_and_run_uumain(&args, uumain); // Use C locale to avoid false positives, like in https://github.com/uutils/coreutils/issues/5378, // because uutils expr doesn't support localization yet @@ -93,28 +93,43 @@ fuzz_target!(|_data: &[u8]| { // Run GNU expr with the provided arguments and compare the output match run_gnu_cmd(CMD_PATH, &args[1..], true) { - Ok((gnu_output, gnu_exit_code)) => { - let gnu_output = gnu_output.trim().to_owned(); + Ok((gnu_stdout, gnu_stderr, gnu_exit_code)) => { + let gnu_stdout = gnu_stdout.trim().to_owned(); if uumain_exit_code != gnu_exit_code { println!("Expression: {}", expr); + + println!("GNU stderr: {}", gnu_stderr); + println!("Rust stderr: {}", rust_stderr); + println!("Rust code: {}", uumain_exit_code); println!("GNU code: {}", gnu_exit_code); panic!("Different error codes"); } - if rust_output == gnu_output { + if rust_stdout == gnu_stdout { println!( "Outputs matched for expression: {} => Result: {}", - expr, rust_output + expr, rust_stdout ); } else { println!("Expression: {}", expr); - println!("Rust output: {}", rust_output); - println!("GNU output: {}", gnu_output); + println!("Rust output: {}", rust_stdout); + println!("GNU output: {}", gnu_stdout); panic!("Different output between Rust & GNU"); } } - Err(_) => { - println!("GNU expr execution failed for expression: {}", expr); + + Err((_gnu_stdout, gnu_stderr, _gnu_exit_code)) => { + if rust_stderr == gnu_stderr { + println!( + "GNU execution failed for input: {} stderr: {}", + expr, rust_stderr + ); + } else { + println!("Input: {}", expr); + println!("Rust stderr: {}", rust_stderr); + println!("GNU stderr: {}", gnu_stderr); + panic!("Different stderr between Rust & GNU"); + } } } }); diff --git a/fuzz/fuzz_targets/fuzz_test.rs b/fuzz/fuzz_targets/fuzz_test.rs index 4805a41af..4c4834bdb 100644 --- a/fuzz/fuzz_targets/fuzz_test.rs +++ b/fuzz/fuzz_targets/fuzz_test.rs @@ -204,19 +204,22 @@ fuzz_target!(|_data: &[u8]| { args.push(OsString::from(generate_test_arg())); } - let (rust_output, uumain_exit_status) = generate_and_run_uumain(&args, uumain); + let (rust_stdout, rust_stderr, uumain_exit_status) = generate_and_run_uumain(&args, uumain); // Run GNU test with the provided arguments and compare the output match run_gnu_cmd(CMD_PATH, &args[1..], false) { - Ok((gnu_output, gnu_exit_status)) => { - let gnu_output = gnu_output.trim().to_owned(); + Ok((gnu_stdout, gnu_stderr, gnu_exit_status)) => { + let gnu_stdout = gnu_stdout.trim().to_owned(); println!("gnu_exit_status {}", gnu_exit_status); println!("uumain_exit_status {}", uumain_exit_status); - if rust_output != gnu_output || uumain_exit_status != gnu_exit_status { + if rust_stdout != gnu_stdout || uumain_exit_status != gnu_exit_status { println!("Discrepancy detected!"); println!("Test: {:?}", &args[1..]); - println!("My output: {}", rust_output); - println!("GNU output: {}", gnu_output); + println!("Rust output: {}", rust_stdout); + println!("GNU output: {}", gnu_stdout); + + println!("Rust stderr: {}", rust_stderr); + println!("GNU stderr: {}", gnu_stderr); println!("My exit status: {}", uumain_exit_status); println!("GNU exit status: {}", gnu_exit_status); panic!(); @@ -227,8 +230,18 @@ fuzz_target!(|_data: &[u8]| { ); } } - Err(_) => { - println!("GNU test execution failed for expression {:?}", &args[1..]); + Err((_gnu_stdout, gnu_stderr, _gnu_exit_code)) => { + if rust_stderr == gnu_stderr { + println!( + "GNU execution failed for input: {:?} stderr: {}", + args, rust_stderr + ); + } else { + println!("Input: {:?}", args); + println!("Rust stderr: {}", rust_stderr); + println!("GNU stderr: {}", gnu_stderr); + panic!("Different stderr between Rust & GNU"); + } } } }); From 12e61d451ce130876a9871aea94819b7f2dfe333 Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Mon, 6 Nov 2023 16:34:52 +0100 Subject: [PATCH 46/73] du: make -l/--count-links work --- src/uu/du/src/du.rs | 5 +++++ tests/by-util/test_du.rs | 27 +++++++++++++++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/src/uu/du/src/du.rs b/src/uu/du/src/du.rs index 589524482..0efbd236d 100644 --- a/src/uu/du/src/du.rs +++ b/src/uu/du/src/du.rs @@ -88,6 +88,7 @@ struct Options { separate_dirs: bool, one_file_system: bool, dereference: Deref, + count_links: bool, inodes: bool, verbose: bool, } @@ -336,6 +337,9 @@ fn du( if let Some(inode) = this_stat.inode { if inodes.contains(&inode) { + if options.count_links { + my_stat.inodes += 1; + } continue; } inodes.insert(inode); @@ -561,6 +565,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { } else { Deref::None }, + count_links: matches.get_flag(options::COUNT_LINKS), inodes: matches.get_flag(options::INODES), verbose: matches.get_flag(options::VERBOSE), }; diff --git a/tests/by-util/test_du.rs b/tests/by-util/test_du.rs index 37594217d..c07de2851 100644 --- a/tests/by-util/test_du.rs +++ b/tests/by-util/test_du.rs @@ -441,6 +441,33 @@ fn test_du_inodes() { } } +#[test] +fn test_du_inodes_with_count_links() { + let ts = TestScenario::new(util_name!()); + let at = &ts.fixtures; + + at.mkdir("dir"); + at.touch("dir/file"); + at.hard_link("dir/file", "dir/hard_link_a"); + at.hard_link("dir/file", "dir/hard_link_b"); + + // ensure the hard links are not counted without --count-links + ts.ucmd() + .arg("--inodes") + .arg("dir") + .succeeds() + .stdout_is("2\tdir\n"); + + for arg in ["-l", "--count-links"] { + ts.ucmd() + .arg("--inodes") + .arg(arg) + .arg("dir") + .succeeds() + .stdout_is("4\tdir\n"); + } +} + #[test] fn test_du_h_flag_empty_file() { new_ucmd!() From 85777e6a4260f554b382c96431922e82bbac9fd5 Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Thu, 9 Nov 2023 16:11:56 +0100 Subject: [PATCH 47/73] du: rename inodes -> seen_inodes --- src/uu/du/src/du.rs | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/src/uu/du/src/du.rs b/src/uu/du/src/du.rs index 0efbd236d..148b197df 100644 --- a/src/uu/du/src/du.rs +++ b/src/uu/du/src/du.rs @@ -296,7 +296,7 @@ fn du( mut my_stat: Stat, options: &Options, depth: usize, - inodes: &mut HashSet, + seen_inodes: &mut HashSet, exclude: &[Pattern], ) -> Box> { let mut stats = vec![]; @@ -336,13 +336,13 @@ fn du( } if let Some(inode) = this_stat.inode { - if inodes.contains(&inode) { + if seen_inodes.contains(&inode) { if options.count_links { my_stat.inodes += 1; } continue; } - inodes.insert(inode); + seen_inodes.insert(inode); } if this_stat.is_dir { if options.one_file_system { @@ -354,7 +354,13 @@ fn du( } } } - futures.push(du(this_stat, options, depth + 1, inodes, exclude)); + futures.push(du( + this_stat, + options, + depth + 1, + seen_inodes, + exclude, + )); } else { my_stat.size += this_stat.size; my_stat.blocks += this_stat.blocks; @@ -637,11 +643,11 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { // Check existence of path provided in argument if let Ok(stat) = Stat::new(&path, &options) { // Kick off the computation of disk usage from the initial path - let mut inodes: HashSet = HashSet::new(); + let mut seen_inodes: HashSet = HashSet::new(); if let Some(inode) = stat.inode { - inodes.insert(inode); + seen_inodes.insert(inode); } - let iter = du(stat, &options, 0, &mut inodes, &excludes); + let iter = du(stat, &options, 0, &mut seen_inodes, &excludes); // Sum up all the returned `Stat`s and display results let (_, len) = iter.size_hint(); From 104e707b07057d81d7be33b09d72b5d3f618b1d2 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Thu, 9 Nov 2023 00:04:14 +0100 Subject: [PATCH 48/73] fuzzing: provide a better error management --- fuzz/fuzz_targets/fuzz_common.rs | 66 ++++++++++++++++++++++++++++---- fuzz/fuzz_targets/fuzz_expr.rs | 57 ++++++++------------------- fuzz/fuzz_targets/fuzz_test.rs | 54 ++++++++------------------ 3 files changed, 89 insertions(+), 88 deletions(-) diff --git a/fuzz/fuzz_targets/fuzz_common.rs b/fuzz/fuzz_targets/fuzz_common.rs index 9afc2cc83..4d7968666 100644 --- a/fuzz/fuzz_targets/fuzz_common.rs +++ b/fuzz/fuzz_targets/fuzz_common.rs @@ -38,8 +38,6 @@ pub fn generate_and_run_uumain(args: &[OsString], uumain_function: F) -> (Str where F: FnOnce(std::vec::IntoIter) -> i32, { - let uumain_exit_status; - // Duplicate the stdout and stderr file descriptors let original_stdout_fd = unsafe { dup(STDOUT_FILENO) }; let original_stderr_fd = unsafe { dup(STDERR_FILENO) }; @@ -78,7 +76,8 @@ where ); } - uumain_exit_status = uumain_function(args.to_owned().into_iter()); + let uumain_exit_status = uumain_function(args.to_owned().into_iter()); + io::stdout().flush().unwrap(); io::stderr().flush().unwrap(); @@ -102,8 +101,8 @@ where let captured_stdout = read_from_fd(pipe_stdout_fds[0]).trim().to_string(); let captured_stderr = read_from_fd(pipe_stderr_fds[0]).to_string(); let captured_stderr = captured_stderr - .splitn(2, ':') - .nth(1) + .split_once(':') + .map(|x| x.1) .unwrap_or("") .trim() .to_string(); @@ -168,8 +167,8 @@ pub fn run_gnu_cmd( let stdout = String::from_utf8_lossy(&output.stdout).to_string(); let stderr = String::from_utf8_lossy(&output.stderr).to_string(); let stderr = stderr - .splitn(2, ':') - .nth(1) + .split_once(':') + .map(|x| x.1) .unwrap_or("") .trim() .to_string(); @@ -180,3 +179,56 @@ pub fn run_gnu_cmd( Err((stdout, stderr, exit_code)) } } + +pub fn compare_result( + test_type: &str, + input: &str, + rust_stdout: &str, + gnu_stdout: &str, + rust_stderr: &str, + gnu_stderr: &str, + rust_exit_code: i32, + gnu_exit_code: i32, + fail_on_stderr_diff: bool, +) { + println!("Test Type: {}", test_type); + println!("Input: {}", input); + + let mut discrepancies = Vec::new(); + let mut should_panic = false; + + if rust_stdout.trim() != gnu_stdout.trim() { + discrepancies.push("stdout differs"); + println!("Rust stdout: {}", rust_stdout); + println!("GNU stdout: {}", gnu_stdout); + should_panic = true; + } + if rust_stderr.trim() != gnu_stderr.trim() { + discrepancies.push("stderr differs"); + println!("Rust stderr: {}", rust_stderr); + println!("GNU stderr: {}", gnu_stderr); + if fail_on_stderr_diff { + should_panic = true; + } + } + if rust_exit_code != gnu_exit_code { + discrepancies.push("exit code differs"); + println!("Rust exit code: {}", rust_exit_code); + println!("GNU exit code: {}", gnu_exit_code); + should_panic = true; + } + + if discrepancies.is_empty() { + println!("All outputs and exit codes matched."); + } else { + println!("Discrepancy detected: {}", discrepancies.join(", ")); + if should_panic { + panic!("Test failed for {}: {}", test_type, input); + } else { + println!( + "Test completed with discrepancies for {}: {}", + test_type, input + ); + } + } +} diff --git a/fuzz/fuzz_targets/fuzz_expr.rs b/fuzz/fuzz_targets/fuzz_expr.rs index ee65745bf..4f0ad3c4a 100644 --- a/fuzz/fuzz_targets/fuzz_expr.rs +++ b/fuzz/fuzz_targets/fuzz_expr.rs @@ -13,7 +13,7 @@ use rand::Rng; use std::{env, ffi::OsString}; mod fuzz_common; -use crate::fuzz_common::{generate_and_run_uumain, run_gnu_cmd}; +use crate::fuzz_common::{compare_result, generate_and_run_uumain, run_gnu_cmd}; static CMD_PATH: &str = "expr"; @@ -84,52 +84,25 @@ fuzz_target!(|_data: &[u8]| { let mut args = vec![OsString::from("expr")]; args.extend(expr.split_whitespace().map(OsString::from)); - let (rust_stdout, rust_stderr, uumain_exit_code) = generate_and_run_uumain(&args, uumain); - // Use C locale to avoid false positives, like in https://github.com/uutils/coreutils/issues/5378, // because uutils expr doesn't support localization yet // TODO remove once uutils expr supports localization env::set_var("LC_COLLATE", "C"); - // Run GNU expr with the provided arguments and compare the output - match run_gnu_cmd(CMD_PATH, &args[1..], true) { - Ok((gnu_stdout, gnu_stderr, gnu_exit_code)) => { - let gnu_stdout = gnu_stdout.trim().to_owned(); - if uumain_exit_code != gnu_exit_code { - println!("Expression: {}", expr); + let (rust_stdout, rust_stderr, uumain_exit_code) = generate_and_run_uumain(&args, uumain); - println!("GNU stderr: {}", gnu_stderr); - println!("Rust stderr: {}", rust_stderr); + let (gnu_stdout, gnu_stderr, gnu_exit_code) = + run_gnu_cmd(CMD_PATH, &args[1..], false).unwrap_or_else(|e| e); - println!("Rust code: {}", uumain_exit_code); - println!("GNU code: {}", gnu_exit_code); - panic!("Different error codes"); - } - if rust_stdout == gnu_stdout { - println!( - "Outputs matched for expression: {} => Result: {}", - expr, rust_stdout - ); - } else { - println!("Expression: {}", expr); - println!("Rust output: {}", rust_stdout); - println!("GNU output: {}", gnu_stdout); - panic!("Different output between Rust & GNU"); - } - } - - Err((_gnu_stdout, gnu_stderr, _gnu_exit_code)) => { - if rust_stderr == gnu_stderr { - println!( - "GNU execution failed for input: {} stderr: {}", - expr, rust_stderr - ); - } else { - println!("Input: {}", expr); - println!("Rust stderr: {}", rust_stderr); - println!("GNU stderr: {}", gnu_stderr); - panic!("Different stderr between Rust & GNU"); - } - } - } + compare_result( + "expr", + &format!("{:?}", &args[1..]), + &rust_stdout, + &gnu_stdout, + &rust_stderr, + &gnu_stderr, + uumain_exit_code, + gnu_exit_code, + false, // Set to true if you want to fail on stderr diff + ); }); diff --git a/fuzz/fuzz_targets/fuzz_test.rs b/fuzz/fuzz_targets/fuzz_test.rs index 4c4834bdb..d11212666 100644 --- a/fuzz/fuzz_targets/fuzz_test.rs +++ b/fuzz/fuzz_targets/fuzz_test.rs @@ -13,7 +13,7 @@ use rand::Rng; use std::ffi::OsString; mod fuzz_common; -use crate::fuzz_common::{generate_and_run_uumain, run_gnu_cmd}; +use crate::fuzz_common::{compare_result, generate_and_run_uumain, run_gnu_cmd}; #[derive(PartialEq, Debug, Clone)] enum ArgType { @@ -204,44 +204,20 @@ fuzz_target!(|_data: &[u8]| { args.push(OsString::from(generate_test_arg())); } - let (rust_stdout, rust_stderr, uumain_exit_status) = generate_and_run_uumain(&args, uumain); + let (rust_stdout, rust_stderr, uumain_exit_code) = generate_and_run_uumain(&args, uumain); - // Run GNU test with the provided arguments and compare the output - match run_gnu_cmd(CMD_PATH, &args[1..], false) { - Ok((gnu_stdout, gnu_stderr, gnu_exit_status)) => { - let gnu_stdout = gnu_stdout.trim().to_owned(); - println!("gnu_exit_status {}", gnu_exit_status); - println!("uumain_exit_status {}", uumain_exit_status); - if rust_stdout != gnu_stdout || uumain_exit_status != gnu_exit_status { - println!("Discrepancy detected!"); - println!("Test: {:?}", &args[1..]); - println!("Rust output: {}", rust_stdout); - println!("GNU output: {}", gnu_stdout); + let (gnu_stdout, gnu_stderr, gnu_exit_code) = + run_gnu_cmd(CMD_PATH, &args[1..], false).unwrap_or_else(|e| e); - println!("Rust stderr: {}", rust_stderr); - println!("GNU stderr: {}", gnu_stderr); - println!("My exit status: {}", uumain_exit_status); - println!("GNU exit status: {}", gnu_exit_status); - panic!(); - } else { - println!( - "Outputs and exit statuses matched for expression {:?}", - &args[1..] - ); - } - } - Err((_gnu_stdout, gnu_stderr, _gnu_exit_code)) => { - if rust_stderr == gnu_stderr { - println!( - "GNU execution failed for input: {:?} stderr: {}", - args, rust_stderr - ); - } else { - println!("Input: {:?}", args); - println!("Rust stderr: {}", rust_stderr); - println!("GNU stderr: {}", gnu_stderr); - panic!("Different stderr between Rust & GNU"); - } - } - } + compare_result( + "test", + &format!("{:?}", &args[1..]), + &rust_stdout, + &gnu_stdout, + &rust_stderr, + &gnu_stderr, + uumain_exit_code, + gnu_exit_code, + false, // Set to true if you want to fail on stderr diff + ); }); From 7a38284075889dad9781de70921dc5c694291220 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 9 Nov 2023 18:43:09 +0000 Subject: [PATCH 49/73] chore(deps): update rust crate bstr to 1.8 --- Cargo.lock | 4 ++-- Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3a3e4b2c4..677dbe89d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -188,9 +188,9 @@ dependencies = [ [[package]] name = "bstr" -version = "1.7.0" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c79ad7fb2dd38f3dabd76b09c6a5a20c038fc0213ef1e9afd30eb777f120f019" +checksum = "542f33a8835a0884b006a0c3df3dadd99c0c3f296ed26c2fdc8028e01ad6230c" dependencies = [ "memchr", "regex-automata", diff --git a/Cargo.toml b/Cargo.toml index fac6f5e89..7a96d9a4f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -259,7 +259,7 @@ test = ["uu_test"] [workspace.dependencies] bigdecimal = "0.4" binary-heap-plus = "0.5.0" -bstr = "1.7" +bstr = "1.8" bytecount = "0.6.7" byteorder = "1.5.0" chrono = { version = "^0.4.31", default-features = false, features = [ From a29ddea4e9ebf82b8c552bbdb79b6b0077900249 Mon Sep 17 00:00:00 2001 From: clint Date: Thu, 9 Nov 2023 15:58:12 -0500 Subject: [PATCH 50/73] expand: clarify tabstops argument name for `expand_line` --- src/uu/expand/src/expand.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/uu/expand/src/expand.rs b/src/uu/expand/src/expand.rs index 4815c5f68..99b9d6b81 100644 --- a/src/uu/expand/src/expand.rs +++ b/src/uu/expand/src/expand.rs @@ -366,10 +366,11 @@ enum CharType { Other, } +#[allow(clippy::cognitive_complexity)] fn expand_line( buf: &mut Vec, output: &mut BufWriter, - ts: &[usize], + tabstops: &[usize], options: &Options, ) -> std::io::Result<()> { use self::CharType::*; @@ -415,7 +416,7 @@ fn expand_line( match ctype { Tab => { // figure out how many spaces to the next tabstop - let nts = next_tabstop(ts, col, &options.remaining_mode); + let nts = next_tabstop(tabstops, col, &options.remaining_mode); col += nts; // now dump out either spaces if we're expanding, or a literal tab if we're not From 2746c199cf58cb8e4e9837fee299de5453396087 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Fri, 10 Nov 2023 16:01:38 +0100 Subject: [PATCH 51/73] use a command result structure --- fuzz/fuzz_targets/fuzz_common.rs | 90 +++++++++++++++++++++++--------- fuzz/fuzz_targets/fuzz_expr.rs | 32 ++++++++---- fuzz/fuzz_targets/fuzz_test.rs | 30 +++++++---- 3 files changed, 107 insertions(+), 45 deletions(-) diff --git a/fuzz/fuzz_targets/fuzz_common.rs b/fuzz/fuzz_targets/fuzz_common.rs index 4d7968666..0fe2306e7 100644 --- a/fuzz/fuzz_targets/fuzz_common.rs +++ b/fuzz/fuzz_targets/fuzz_common.rs @@ -12,6 +12,19 @@ use std::process::Command; use std::sync::atomic::Ordering; use std::sync::{atomic::AtomicBool, Once}; +/// Represents the result of running a command, including its standard output, +/// standard error, and exit code. +pub struct CommandResult { + /// The standard output (stdout) of the command as a string. + pub stdout: String, + + /// The standard error (stderr) of the command as a string. + pub stderr: String, + + /// The exit code of the command. + pub exit_code: i32, +} + static CHECK_GNU: Once = Once::new(); static IS_GNU: AtomicBool = AtomicBool::new(false); @@ -34,7 +47,7 @@ pub fn is_gnu_cmd(cmd_path: &str) -> Result<(), std::io::Error> { } } -pub fn generate_and_run_uumain(args: &[OsString], uumain_function: F) -> (String, String, i32) +pub fn generate_and_run_uumain(args: &[OsString], uumain_function: F) -> CommandResult where F: FnOnce(std::vec::IntoIter) -> i32, { @@ -42,11 +55,11 @@ where let original_stdout_fd = unsafe { dup(STDOUT_FILENO) }; let original_stderr_fd = unsafe { dup(STDERR_FILENO) }; if original_stdout_fd == -1 || original_stderr_fd == -1 { - return ( - "Failed to duplicate STDOUT_FILENO or STDERR_FILENO".to_string(), - "".to_string(), - -1, - ); + return CommandResult { + stdout: "Failed to duplicate STDOUT_FILENO or STDERR_FILENO".to_string(), + stderr: "".to_string(), + exit_code: -1, + }; } println!("Running test {:?}", &args[0..]); let mut pipe_stdout_fds = [-1; 2]; @@ -56,7 +69,11 @@ where if unsafe { pipe(pipe_stdout_fds.as_mut_ptr()) } == -1 || unsafe { pipe(pipe_stderr_fds.as_mut_ptr()) } == -1 { - return ("Failed to create pipes".to_string(), "".to_string(), -1); + return CommandResult { + stdout: "Failed to create pipes".to_string(), + stderr: "".to_string(), + exit_code: -1, + }; } // Redirect stdout and stderr to their respective pipes @@ -69,11 +86,11 @@ where close(pipe_stderr_fds[0]); close(pipe_stderr_fds[1]); } - return ( - "Failed to redirect STDOUT_FILENO or STDERR_FILENO".to_string(), - "".to_string(), - -1, - ); + return CommandResult { + stdout: "Failed to redirect STDOUT_FILENO or STDERR_FILENO".to_string(), + stderr: "".to_string(), + exit_code: -1, + }; } let uumain_exit_status = uumain_function(args.to_owned().into_iter()); @@ -85,18 +102,19 @@ where if unsafe { dup2(original_stdout_fd, STDOUT_FILENO) } == -1 || unsafe { dup2(original_stderr_fd, STDERR_FILENO) } == -1 { - return ( - "Failed to restore the original STDOUT_FILENO or STDERR_FILENO".to_string(), - "".to_string(), - -1, - ); + return CommandResult { + stdout: "Failed to restore the original STDOUT_FILENO or STDERR_FILENO".to_string(), + stderr: "".to_string(), + exit_code: -1, + }; } unsafe { close(original_stdout_fd); close(original_stderr_fd); + + close(pipe_stdout_fds[1]); + close(pipe_stderr_fds[1]); } - unsafe { close(pipe_stdout_fds[1]) }; - unsafe { close(pipe_stderr_fds[1]) }; let captured_stdout = read_from_fd(pipe_stdout_fds[0]).trim().to_string(); let captured_stderr = read_from_fd(pipe_stderr_fds[0]).to_string(); @@ -107,7 +125,11 @@ where .trim() .to_string(); - (captured_stdout, captured_stderr, uumain_exit_status) + CommandResult { + stdout: captured_stdout, + stderr: captured_stderr, + exit_code: uumain_exit_status, + } } fn read_from_fd(fd: RawFd) -> String { @@ -141,13 +163,17 @@ pub fn run_gnu_cmd( cmd_path: &str, args: &[OsString], check_gnu: bool, -) -> Result<(String, String, i32), (String, String, i32)> { +) -> Result { if check_gnu { match is_gnu_cmd(cmd_path) { Ok(_) => {} // if the check passes, do nothing Err(e) => { // Convert the io::Error into the function's error type - return Err((String::new(), e.to_string(), -1)); + return Err(CommandResult { + stdout: String::new(), + stderr: e.to_string(), + exit_code: -1, + }); } } } @@ -159,7 +185,13 @@ pub fn run_gnu_cmd( let output = match command.output() { Ok(output) => output, - Err(e) => return Err((String::new(), e.to_string(), -1)), + Err(e) => { + return Err(CommandResult { + stdout: String::new(), + stderr: e.to_string(), + exit_code: -1, + }); + } }; let exit_code = output.status.code().unwrap_or(-1); @@ -174,9 +206,17 @@ pub fn run_gnu_cmd( .to_string(); if output.status.success() || !check_gnu { - Ok((stdout, stderr, exit_code)) + Ok(CommandResult { + stdout, + stderr, + exit_code, + }) } else { - Err((stdout, stderr, exit_code)) + Err(CommandResult { + stdout, + stderr, + exit_code, + }) } } diff --git a/fuzz/fuzz_targets/fuzz_expr.rs b/fuzz/fuzz_targets/fuzz_expr.rs index 4f0ad3c4a..90daa107f 100644 --- a/fuzz/fuzz_targets/fuzz_expr.rs +++ b/fuzz/fuzz_targets/fuzz_expr.rs @@ -13,8 +13,8 @@ use rand::Rng; use std::{env, ffi::OsString}; mod fuzz_common; +use crate::fuzz_common::CommandResult; use crate::fuzz_common::{compare_result, generate_and_run_uumain, run_gnu_cmd}; - static CMD_PATH: &str = "expr"; fn generate_random_string(max_length: usize) -> String { @@ -88,21 +88,31 @@ fuzz_target!(|_data: &[u8]| { // because uutils expr doesn't support localization yet // TODO remove once uutils expr supports localization env::set_var("LC_COLLATE", "C"); + let rust_result = generate_and_run_uumain(&args, uumain); - let (rust_stdout, rust_stderr, uumain_exit_code) = generate_and_run_uumain(&args, uumain); - - let (gnu_stdout, gnu_stderr, gnu_exit_code) = - run_gnu_cmd(CMD_PATH, &args[1..], false).unwrap_or_else(|e| e); + let gnu_result = match run_gnu_cmd(CMD_PATH, &args[1..], false) { + Ok(result) => result, + Err(error_result) => { + eprintln!("Failed to run GNU command:"); + eprintln!("Stderr: {}", error_result.stderr); + eprintln!("Exit Code: {}", error_result.exit_code); + CommandResult { + stdout: String::new(), + stderr: error_result.stderr, + exit_code: error_result.exit_code, + } + } + }; compare_result( "expr", &format!("{:?}", &args[1..]), - &rust_stdout, - &gnu_stdout, - &rust_stderr, - &gnu_stderr, - uumain_exit_code, - gnu_exit_code, + &rust_result.stdout, + &gnu_result.stdout, + &rust_result.stderr, + &gnu_result.stderr, + rust_result.exit_code, + gnu_result.exit_code, false, // Set to true if you want to fail on stderr diff ); }); diff --git a/fuzz/fuzz_targets/fuzz_test.rs b/fuzz/fuzz_targets/fuzz_test.rs index d11212666..0f2328c55 100644 --- a/fuzz/fuzz_targets/fuzz_test.rs +++ b/fuzz/fuzz_targets/fuzz_test.rs @@ -13,6 +13,7 @@ use rand::Rng; use std::ffi::OsString; mod fuzz_common; +use crate::fuzz_common::CommandResult; use crate::fuzz_common::{compare_result, generate_and_run_uumain, run_gnu_cmd}; #[derive(PartialEq, Debug, Clone)] @@ -204,20 +205,31 @@ fuzz_target!(|_data: &[u8]| { args.push(OsString::from(generate_test_arg())); } - let (rust_stdout, rust_stderr, uumain_exit_code) = generate_and_run_uumain(&args, uumain); + let rust_result = generate_and_run_uumain(&args, uumain); - let (gnu_stdout, gnu_stderr, gnu_exit_code) = - run_gnu_cmd(CMD_PATH, &args[1..], false).unwrap_or_else(|e| e); + let gnu_result = match run_gnu_cmd(CMD_PATH, &args[1..], false) { + Ok(result) => result, + Err(error_result) => { + eprintln!("Failed to run GNU command:"); + eprintln!("Stderr: {}", error_result.stderr); + eprintln!("Exit Code: {}", error_result.exit_code); + CommandResult { + stdout: String::new(), + stderr: error_result.stderr, + exit_code: error_result.exit_code, + } + } + }; compare_result( "test", &format!("{:?}", &args[1..]), - &rust_stdout, - &gnu_stdout, - &rust_stderr, - &gnu_stderr, - uumain_exit_code, - gnu_exit_code, + &rust_result.stdout, + &gnu_result.stdout, + &rust_result.stderr, + &gnu_result.stderr, + rust_result.exit_code, + gnu_result.exit_code, false, // Set to true if you want to fail on stderr diff ); }); From 24d7f1fe5cbfce05b5a7f6ec0625f80a2a3eaf83 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 10 Nov 2023 16:59:08 +0000 Subject: [PATCH 52/73] chore(deps): update rust crate self_cell to 1.0.2 --- Cargo.lock | 4 ++-- Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 677dbe89d..2496bbb7b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1870,9 +1870,9 @@ checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" [[package]] name = "self_cell" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c309e515543e67811222dbc9e3dd7e1056279b782e1dacffe4242b718734fb6" +checksum = "e388332cd64eb80cd595a00941baf513caffae8dce9cfd0467fc9c66397dade6" [[package]] name = "selinux" diff --git a/Cargo.toml b/Cargo.toml index 7a96d9a4f..b4a0dc07c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -313,7 +313,7 @@ regex = "1.10.2" rstest = "0.18.2" rust-ini = "0.19.0" same-file = "1.0.6" -self_cell = "1.0.1" +self_cell = "1.0.2" selinux = "0.4" signal-hook = "0.3.17" smallvec = { version = "1.11", features = ["union"] } From 70c48fca78a7df31d752e6c7a9112e9c5da93bf7 Mon Sep 17 00:00:00 2001 From: David Zbarsky Date: Sat, 11 Nov 2023 12:53:15 -0500 Subject: [PATCH 53/73] Add darwin arm64 builds --- .github/workflows/CICD.yml | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/.github/workflows/CICD.yml b/.github/workflows/CICD.yml index 6583a0094..b158c9c4a 100644 --- a/.github/workflows/CICD.yml +++ b/.github/workflows/CICD.yml @@ -461,14 +461,15 @@ jobs: fail-fast: false matrix: job: - # { os , target , cargo-options , features , use-cross , toolchain } + # - { os , target , cargo-options , features , use-cross , toolchain, skip-tests } - { os: ubuntu-latest , target: arm-unknown-linux-gnueabihf, features: feat_os_unix_gnueabihf, use-cross: use-cross, } - { os: ubuntu-latest , target: aarch64-unknown-linux-gnu , features: feat_os_unix_gnueabihf , use-cross: use-cross } # - { os: ubuntu-latest , target: x86_64-unknown-linux-gnu , features: feat_selinux , use-cross: use-cross } - - { os: ubuntu-latest , target: i686-unknown-linux-gnu , features: feat_os_unix , use-cross: use-cross } - - { os: ubuntu-latest , target: i686-unknown-linux-musl , features: feat_os_unix_musl , use-cross: use-cross } - - { os: ubuntu-latest , target: x86_64-unknown-linux-gnu , features: feat_os_unix , use-cross: use-cross } - - { os: ubuntu-latest , target: x86_64-unknown-linux-musl , features: feat_os_unix_musl , use-cross: use-cross } + - { os: ubuntu-latest , target: i686-unknown-linux-gnu , features: feat_os_unix , use-cross: use-cross } + - { os: ubuntu-latest , target: i686-unknown-linux-musl , features: feat_os_unix_musl , use-cross: use-cross } + - { os: ubuntu-latest , target: x86_64-unknown-linux-gnu , features: feat_os_unix , use-cross: use-cross } + - { os: ubuntu-latest , target: x86_64-unknown-linux-musl , features: feat_os_unix_musl , use-cross: use-cross } + - { os: macos-latest , target: aarch64-apple-darwin , features: feat_os_macos , use-cross: use-cross, skip-tests: true} # Hopefully github provides free M1 runners soon... - { os: macos-latest , target: x86_64-apple-darwin , features: feat_os_macos } - { os: windows-latest , target: i686-pc-windows-msvc , features: feat_os_windows } - { os: windows-latest , target: x86_64-pc-windows-gnu , features: feat_os_windows } ## note: requires rust >= 1.43.0 to link correctly @@ -575,7 +576,7 @@ jobs: - uses: taiki-e/install-action@v2 if: steps.vars.outputs.CARGO_CMD == 'cross' with: - tool: cross@0.2.1 + tool: cross@0.2.5 - name: Create all needed build/work directories shell: bash run: | @@ -650,6 +651,7 @@ jobs: ${{ steps.vars.outputs.CARGO_CMD }} +${{ env.RUST_MIN_SRV }} build --release \ --target=${{ matrix.job.target }} ${{ matrix.job.cargo-options }} ${{ steps.vars.outputs.CARGO_FEATURES_OPTION }} - name: Test + if: matrix.job.skip-tests != true shell: bash run: | ## Test @@ -658,6 +660,7 @@ jobs: env: RUST_BACKTRACE: "1" - name: Test individual utilities + if: matrix.job.skip-tests != true shell: bash run: | ## Test individual utilities From fddf301a52e512ba4b20df4f2142a64c0560ba51 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Sun, 12 Nov 2023 10:25:55 +0100 Subject: [PATCH 54/73] fuzz: Move a duplicate function into fuzz_common --- fuzz/fuzz_targets/fuzz_common.rs | 25 +++++++++++++++++++++++++ fuzz/fuzz_targets/fuzz_expr.rs | 27 +++------------------------ fuzz/fuzz_targets/fuzz_test.rs | 27 +++------------------------ 3 files changed, 31 insertions(+), 48 deletions(-) diff --git a/fuzz/fuzz_targets/fuzz_common.rs b/fuzz/fuzz_targets/fuzz_common.rs index 0fe2306e7..2adbb3dd6 100644 --- a/fuzz/fuzz_targets/fuzz_common.rs +++ b/fuzz/fuzz_targets/fuzz_common.rs @@ -4,6 +4,8 @@ // file that was distributed with this source code. use libc::{close, dup, dup2, pipe, STDERR_FILENO, STDOUT_FILENO}; +use rand::prelude::SliceRandom; +use rand::Rng; use std::ffi::OsString; use std::io; use std::io::Write; @@ -272,3 +274,26 @@ pub fn compare_result( } } } + +pub fn generate_random_string(max_length: usize) -> String { + let mut rng = rand::thread_rng(); + let valid_utf8: Vec = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789" + .chars() + .collect(); + let invalid_utf8 = [0xC3, 0x28]; // Invalid UTF-8 sequence + let mut result = String::new(); + + for _ in 0..rng.gen_range(1..=max_length) { + if rng.gen_bool(0.9) { + let ch = valid_utf8.choose(&mut rng).unwrap(); + result.push(*ch); + } else { + let ch = invalid_utf8.choose(&mut rng).unwrap(); + if let Some(c) = char::from_u32(*ch as u32) { + result.push(c); + } + } + } + + result +} diff --git a/fuzz/fuzz_targets/fuzz_expr.rs b/fuzz/fuzz_targets/fuzz_expr.rs index 90daa107f..8d1848545 100644 --- a/fuzz/fuzz_targets/fuzz_expr.rs +++ b/fuzz/fuzz_targets/fuzz_expr.rs @@ -14,32 +14,11 @@ use std::{env, ffi::OsString}; mod fuzz_common; use crate::fuzz_common::CommandResult; -use crate::fuzz_common::{compare_result, generate_and_run_uumain, run_gnu_cmd}; +use crate::fuzz_common::{ + compare_result, generate_and_run_uumain, generate_random_string, run_gnu_cmd, +}; static CMD_PATH: &str = "expr"; -fn generate_random_string(max_length: usize) -> String { - let mut rng = rand::thread_rng(); - let valid_utf8: Vec = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789" - .chars() - .collect(); - let invalid_utf8 = [0xC3, 0x28]; // Invalid UTF-8 sequence - let mut result = String::new(); - - for _ in 0..rng.gen_range(1..=max_length) { - if rng.gen_bool(0.9) { - let ch = valid_utf8.choose(&mut rng).unwrap(); - result.push(*ch); - } else { - let ch = invalid_utf8.choose(&mut rng).unwrap(); - if let Some(c) = char::from_u32(*ch as u32) { - result.push(c); - } - } - } - - result -} - fn generate_expr(max_depth: u32) -> String { let mut rng = rand::thread_rng(); let ops = ["+", "-", "*", "/", "%", "<", ">", "=", "&", "|"]; diff --git a/fuzz/fuzz_targets/fuzz_test.rs b/fuzz/fuzz_targets/fuzz_test.rs index 0f2328c55..38cd691b3 100644 --- a/fuzz/fuzz_targets/fuzz_test.rs +++ b/fuzz/fuzz_targets/fuzz_test.rs @@ -14,7 +14,9 @@ use std::ffi::OsString; mod fuzz_common; use crate::fuzz_common::CommandResult; -use crate::fuzz_common::{compare_result, generate_and_run_uumain, run_gnu_cmd}; +use crate::fuzz_common::{ + compare_result, generate_and_run_uumain, generate_random_string, run_gnu_cmd, +}; #[derive(PartialEq, Debug, Clone)] enum ArgType { @@ -29,29 +31,6 @@ enum ArgType { static CMD_PATH: &str = "test"; -fn generate_random_string(max_length: usize) -> String { - let mut rng = rand::thread_rng(); - let valid_utf8: Vec = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789" - .chars() - .collect(); - let invalid_utf8 = [0xC3, 0x28]; // Invalid UTF-8 sequence - let mut result = String::new(); - - for _ in 0..rng.gen_range(1..=max_length) { - if rng.gen_bool(0.9) { - let ch = valid_utf8.choose(&mut rng).unwrap(); - result.push(*ch); - } else { - let ch = invalid_utf8.choose(&mut rng).unwrap(); - if let Some(c) = char::from_u32(*ch as u32) { - result.push(c); - } - } - } - - result -} - #[derive(Debug, Clone)] struct TestArg { arg: String, From cdbc7bb416ac74bbae65d509decf7de633ac4636 Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Sun, 12 Nov 2023 13:41:29 +0100 Subject: [PATCH 55/73] ci: remove outdated comment --- .github/workflows/CICD.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/CICD.yml b/.github/workflows/CICD.yml index b158c9c4a..66ee23168 100644 --- a/.github/workflows/CICD.yml +++ b/.github/workflows/CICD.yml @@ -472,7 +472,7 @@ jobs: - { os: macos-latest , target: aarch64-apple-darwin , features: feat_os_macos , use-cross: use-cross, skip-tests: true} # Hopefully github provides free M1 runners soon... - { os: macos-latest , target: x86_64-apple-darwin , features: feat_os_macos } - { os: windows-latest , target: i686-pc-windows-msvc , features: feat_os_windows } - - { os: windows-latest , target: x86_64-pc-windows-gnu , features: feat_os_windows } ## note: requires rust >= 1.43.0 to link correctly + - { os: windows-latest , target: x86_64-pc-windows-gnu , features: feat_os_windows } - { os: windows-latest , target: x86_64-pc-windows-msvc , features: feat_os_windows } steps: - uses: actions/checkout@v4 From 1f40cd69c80380192cde49bdfc6c4437f4a0e061 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 13 Nov 2023 18:55:11 +0000 Subject: [PATCH 56/73] chore(deps): update actions/github-script action to v7 --- .github/workflows/GnuComment.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/GnuComment.yml b/.github/workflows/GnuComment.yml index bb64232a9..36c54490c 100644 --- a/.github/workflows/GnuComment.yml +++ b/.github/workflows/GnuComment.yml @@ -18,7 +18,7 @@ jobs: github.event.workflow_run.event == 'pull_request' steps: - name: 'Download artifact' - uses: actions/github-script@v6 + uses: actions/github-script@v7 with: script: | // List all artifacts from GnuTests @@ -43,7 +43,7 @@ jobs: - run: unzip comment.zip - name: 'Comment on PR' - uses: actions/github-script@v6 + uses: actions/github-script@v7 with: github-token: ${{ secrets.GITHUB_TOKEN }} script: | From ff92bfb25be0e996f25b82e3d9e78884e573e655 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Mon, 13 Nov 2023 21:50:59 +0100 Subject: [PATCH 57/73] Prepare version 0.0.23 --- Cargo.lock | 214 ++++++++++++------------- Cargo.toml | 206 ++++++++++++------------ src/uu/arch/Cargo.toml | 2 +- src/uu/base32/Cargo.toml | 2 +- src/uu/base64/Cargo.toml | 2 +- src/uu/basename/Cargo.toml | 2 +- src/uu/basenc/Cargo.toml | 2 +- src/uu/cat/Cargo.toml | 2 +- src/uu/chcon/Cargo.toml | 2 +- src/uu/chgrp/Cargo.toml | 2 +- src/uu/chmod/Cargo.toml | 2 +- src/uu/chown/Cargo.toml | 2 +- src/uu/chroot/Cargo.toml | 2 +- src/uu/cksum/Cargo.toml | 2 +- src/uu/comm/Cargo.toml | 2 +- src/uu/cp/Cargo.toml | 2 +- src/uu/csplit/Cargo.toml | 2 +- src/uu/cut/Cargo.toml | 2 +- src/uu/date/Cargo.toml | 2 +- src/uu/dd/Cargo.toml | 2 +- src/uu/df/Cargo.toml | 2 +- src/uu/dir/Cargo.toml | 2 +- src/uu/dircolors/Cargo.toml | 2 +- src/uu/dirname/Cargo.toml | 2 +- src/uu/du/Cargo.toml | 2 +- src/uu/echo/Cargo.toml | 2 +- src/uu/env/Cargo.toml | 2 +- src/uu/expand/Cargo.toml | 2 +- src/uu/expr/Cargo.toml | 2 +- src/uu/factor/Cargo.toml | 2 +- src/uu/false/Cargo.toml | 2 +- src/uu/fmt/Cargo.toml | 2 +- src/uu/fold/Cargo.toml | 2 +- src/uu/groups/Cargo.toml | 2 +- src/uu/hashsum/Cargo.toml | 2 +- src/uu/head/Cargo.toml | 2 +- src/uu/hostid/Cargo.toml | 2 +- src/uu/hostname/Cargo.toml | 2 +- src/uu/id/Cargo.toml | 2 +- src/uu/install/Cargo.toml | 2 +- src/uu/join/Cargo.toml | 2 +- src/uu/kill/Cargo.toml | 2 +- src/uu/link/Cargo.toml | 2 +- src/uu/ln/Cargo.toml | 2 +- src/uu/logname/Cargo.toml | 2 +- src/uu/ls/Cargo.toml | 2 +- src/uu/mkdir/Cargo.toml | 2 +- src/uu/mkfifo/Cargo.toml | 2 +- src/uu/mknod/Cargo.toml | 2 +- src/uu/mktemp/Cargo.toml | 2 +- src/uu/more/Cargo.toml | 2 +- src/uu/mv/Cargo.toml | 2 +- src/uu/nice/Cargo.toml | 2 +- src/uu/nl/Cargo.toml | 2 +- src/uu/nohup/Cargo.toml | 2 +- src/uu/nproc/Cargo.toml | 2 +- src/uu/numfmt/Cargo.toml | 2 +- src/uu/od/Cargo.toml | 2 +- src/uu/paste/Cargo.toml | 2 +- src/uu/pathchk/Cargo.toml | 2 +- src/uu/pinky/Cargo.toml | 2 +- src/uu/pr/Cargo.toml | 2 +- src/uu/printenv/Cargo.toml | 2 +- src/uu/printf/Cargo.toml | 2 +- src/uu/ptx/Cargo.toml | 2 +- src/uu/pwd/Cargo.toml | 2 +- src/uu/readlink/Cargo.toml | 2 +- src/uu/realpath/Cargo.toml | 2 +- src/uu/rm/Cargo.toml | 2 +- src/uu/rmdir/Cargo.toml | 2 +- src/uu/runcon/Cargo.toml | 2 +- src/uu/seq/Cargo.toml | 2 +- src/uu/shred/Cargo.toml | 2 +- src/uu/shuf/Cargo.toml | 2 +- src/uu/sleep/Cargo.toml | 2 +- src/uu/sort/Cargo.toml | 2 +- src/uu/split/Cargo.toml | 2 +- src/uu/stat/Cargo.toml | 2 +- src/uu/stdbuf/Cargo.toml | 4 +- src/uu/stdbuf/src/libstdbuf/Cargo.toml | 2 +- src/uu/stty/Cargo.toml | 2 +- src/uu/sum/Cargo.toml | 2 +- src/uu/sync/Cargo.toml | 2 +- src/uu/tac/Cargo.toml | 2 +- src/uu/tail/Cargo.toml | 2 +- src/uu/tee/Cargo.toml | 2 +- src/uu/test/Cargo.toml | 2 +- src/uu/timeout/Cargo.toml | 2 +- src/uu/touch/Cargo.toml | 2 +- src/uu/tr/Cargo.toml | 2 +- src/uu/true/Cargo.toml | 2 +- src/uu/truncate/Cargo.toml | 2 +- src/uu/tsort/Cargo.toml | 2 +- src/uu/tty/Cargo.toml | 2 +- src/uu/uname/Cargo.toml | 2 +- src/uu/unexpand/Cargo.toml | 2 +- src/uu/uniq/Cargo.toml | 2 +- src/uu/unlink/Cargo.toml | 2 +- src/uu/uptime/Cargo.toml | 2 +- src/uu/users/Cargo.toml | 2 +- src/uu/vdir/Cargo.toml | 2 +- src/uu/wc/Cargo.toml | 2 +- src/uu/who/Cargo.toml | 2 +- src/uu/whoami/Cargo.toml | 2 +- src/uu/yes/Cargo.toml | 2 +- src/uucore/Cargo.toml | 2 +- src/uucore_procs/Cargo.toml | 4 +- src/uuhelp_parser/Cargo.toml | 2 +- util/update-version.sh | 4 +- 109 files changed, 320 insertions(+), 320 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2496bbb7b..d83adfee6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -374,7 +374,7 @@ checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" [[package]] name = "coreutils" -version = "0.0.22" +version = "0.0.23" dependencies = [ "chrono", "clap", @@ -2207,7 +2207,7 @@ checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" [[package]] name = "uu_arch" -version = "0.0.22" +version = "0.0.23" dependencies = [ "clap", "platform-info", @@ -2216,7 +2216,7 @@ dependencies = [ [[package]] name = "uu_base32" -version = "0.0.22" +version = "0.0.23" dependencies = [ "clap", "uucore", @@ -2224,7 +2224,7 @@ dependencies = [ [[package]] name = "uu_base64" -version = "0.0.22" +version = "0.0.23" dependencies = [ "uu_base32", "uucore", @@ -2232,7 +2232,7 @@ dependencies = [ [[package]] name = "uu_basename" -version = "0.0.22" +version = "0.0.23" dependencies = [ "clap", "uucore", @@ -2240,7 +2240,7 @@ dependencies = [ [[package]] name = "uu_basenc" -version = "0.0.22" +version = "0.0.23" dependencies = [ "clap", "uu_base32", @@ -2249,7 +2249,7 @@ dependencies = [ [[package]] name = "uu_cat" -version = "0.0.22" +version = "0.0.23" dependencies = [ "clap", "nix", @@ -2259,7 +2259,7 @@ dependencies = [ [[package]] name = "uu_chcon" -version = "0.0.22" +version = "0.0.23" dependencies = [ "clap", "fts-sys", @@ -2271,7 +2271,7 @@ dependencies = [ [[package]] name = "uu_chgrp" -version = "0.0.22" +version = "0.0.23" dependencies = [ "clap", "uucore", @@ -2279,7 +2279,7 @@ dependencies = [ [[package]] name = "uu_chmod" -version = "0.0.22" +version = "0.0.23" dependencies = [ "clap", "libc", @@ -2288,7 +2288,7 @@ dependencies = [ [[package]] name = "uu_chown" -version = "0.0.22" +version = "0.0.23" dependencies = [ "clap", "uucore", @@ -2296,7 +2296,7 @@ dependencies = [ [[package]] name = "uu_chroot" -version = "0.0.22" +version = "0.0.23" dependencies = [ "clap", "uucore", @@ -2304,7 +2304,7 @@ dependencies = [ [[package]] name = "uu_cksum" -version = "0.0.22" +version = "0.0.23" dependencies = [ "clap", "hex", @@ -2313,7 +2313,7 @@ dependencies = [ [[package]] name = "uu_comm" -version = "0.0.22" +version = "0.0.23" dependencies = [ "clap", "uucore", @@ -2321,7 +2321,7 @@ dependencies = [ [[package]] name = "uu_cp" -version = "0.0.22" +version = "0.0.23" dependencies = [ "clap", "exacl", @@ -2337,7 +2337,7 @@ dependencies = [ [[package]] name = "uu_csplit" -version = "0.0.22" +version = "0.0.23" dependencies = [ "clap", "regex", @@ -2347,7 +2347,7 @@ dependencies = [ [[package]] name = "uu_cut" -version = "0.0.22" +version = "0.0.23" dependencies = [ "bstr", "clap", @@ -2357,7 +2357,7 @@ dependencies = [ [[package]] name = "uu_date" -version = "0.0.22" +version = "0.0.23" dependencies = [ "chrono", "clap", @@ -2369,7 +2369,7 @@ dependencies = [ [[package]] name = "uu_dd" -version = "0.0.22" +version = "0.0.23" dependencies = [ "clap", "gcd", @@ -2381,7 +2381,7 @@ dependencies = [ [[package]] name = "uu_df" -version = "0.0.22" +version = "0.0.23" dependencies = [ "clap", "tempfile", @@ -2391,7 +2391,7 @@ dependencies = [ [[package]] name = "uu_dir" -version = "0.0.22" +version = "0.0.23" dependencies = [ "clap", "uu_ls", @@ -2400,7 +2400,7 @@ dependencies = [ [[package]] name = "uu_dircolors" -version = "0.0.22" +version = "0.0.23" dependencies = [ "clap", "uucore", @@ -2408,7 +2408,7 @@ dependencies = [ [[package]] name = "uu_dirname" -version = "0.0.22" +version = "0.0.23" dependencies = [ "clap", "uucore", @@ -2416,7 +2416,7 @@ dependencies = [ [[package]] name = "uu_du" -version = "0.0.22" +version = "0.0.23" dependencies = [ "chrono", "clap", @@ -2427,7 +2427,7 @@ dependencies = [ [[package]] name = "uu_echo" -version = "0.0.22" +version = "0.0.23" dependencies = [ "clap", "uucore", @@ -2435,7 +2435,7 @@ dependencies = [ [[package]] name = "uu_env" -version = "0.0.22" +version = "0.0.23" dependencies = [ "clap", "nix", @@ -2445,7 +2445,7 @@ dependencies = [ [[package]] name = "uu_expand" -version = "0.0.22" +version = "0.0.23" dependencies = [ "clap", "unicode-width", @@ -2454,7 +2454,7 @@ dependencies = [ [[package]] name = "uu_expr" -version = "0.0.22" +version = "0.0.23" dependencies = [ "clap", "num-bigint", @@ -2465,7 +2465,7 @@ dependencies = [ [[package]] name = "uu_factor" -version = "0.0.22" +version = "0.0.23" dependencies = [ "clap", "coz", @@ -2478,7 +2478,7 @@ dependencies = [ [[package]] name = "uu_false" -version = "0.0.22" +version = "0.0.23" dependencies = [ "clap", "uucore", @@ -2486,7 +2486,7 @@ dependencies = [ [[package]] name = "uu_fmt" -version = "0.0.22" +version = "0.0.23" dependencies = [ "clap", "unicode-width", @@ -2495,7 +2495,7 @@ dependencies = [ [[package]] name = "uu_fold" -version = "0.0.22" +version = "0.0.23" dependencies = [ "clap", "uucore", @@ -2503,7 +2503,7 @@ dependencies = [ [[package]] name = "uu_groups" -version = "0.0.22" +version = "0.0.23" dependencies = [ "clap", "uucore", @@ -2511,7 +2511,7 @@ dependencies = [ [[package]] name = "uu_hashsum" -version = "0.0.22" +version = "0.0.23" dependencies = [ "clap", "hex", @@ -2522,7 +2522,7 @@ dependencies = [ [[package]] name = "uu_head" -version = "0.0.22" +version = "0.0.23" dependencies = [ "clap", "memchr", @@ -2531,7 +2531,7 @@ dependencies = [ [[package]] name = "uu_hostid" -version = "0.0.22" +version = "0.0.23" dependencies = [ "clap", "libc", @@ -2540,7 +2540,7 @@ dependencies = [ [[package]] name = "uu_hostname" -version = "0.0.22" +version = "0.0.23" dependencies = [ "clap", "hostname", @@ -2550,7 +2550,7 @@ dependencies = [ [[package]] name = "uu_id" -version = "0.0.22" +version = "0.0.23" dependencies = [ "clap", "selinux", @@ -2559,7 +2559,7 @@ dependencies = [ [[package]] name = "uu_install" -version = "0.0.22" +version = "0.0.23" dependencies = [ "clap", "file_diff", @@ -2570,7 +2570,7 @@ dependencies = [ [[package]] name = "uu_join" -version = "0.0.22" +version = "0.0.23" dependencies = [ "clap", "memchr", @@ -2579,7 +2579,7 @@ dependencies = [ [[package]] name = "uu_kill" -version = "0.0.22" +version = "0.0.23" dependencies = [ "clap", "nix", @@ -2588,7 +2588,7 @@ dependencies = [ [[package]] name = "uu_link" -version = "0.0.22" +version = "0.0.23" dependencies = [ "clap", "uucore", @@ -2596,7 +2596,7 @@ dependencies = [ [[package]] name = "uu_ln" -version = "0.0.22" +version = "0.0.23" dependencies = [ "clap", "uucore", @@ -2604,7 +2604,7 @@ dependencies = [ [[package]] name = "uu_logname" -version = "0.0.22" +version = "0.0.23" dependencies = [ "clap", "libc", @@ -2613,7 +2613,7 @@ dependencies = [ [[package]] name = "uu_ls" -version = "0.0.22" +version = "0.0.23" dependencies = [ "chrono", "clap", @@ -2630,7 +2630,7 @@ dependencies = [ [[package]] name = "uu_mkdir" -version = "0.0.22" +version = "0.0.23" dependencies = [ "clap", "uucore", @@ -2638,7 +2638,7 @@ dependencies = [ [[package]] name = "uu_mkfifo" -version = "0.0.22" +version = "0.0.23" dependencies = [ "clap", "libc", @@ -2647,7 +2647,7 @@ dependencies = [ [[package]] name = "uu_mknod" -version = "0.0.22" +version = "0.0.23" dependencies = [ "clap", "libc", @@ -2656,7 +2656,7 @@ dependencies = [ [[package]] name = "uu_mktemp" -version = "0.0.22" +version = "0.0.23" dependencies = [ "clap", "rand", @@ -2666,7 +2666,7 @@ dependencies = [ [[package]] name = "uu_more" -version = "0.0.22" +version = "0.0.23" dependencies = [ "clap", "crossterm", @@ -2678,7 +2678,7 @@ dependencies = [ [[package]] name = "uu_mv" -version = "0.0.22" +version = "0.0.23" dependencies = [ "clap", "fs_extra", @@ -2688,7 +2688,7 @@ dependencies = [ [[package]] name = "uu_nice" -version = "0.0.22" +version = "0.0.23" dependencies = [ "clap", "libc", @@ -2698,7 +2698,7 @@ dependencies = [ [[package]] name = "uu_nl" -version = "0.0.22" +version = "0.0.23" dependencies = [ "clap", "regex", @@ -2707,7 +2707,7 @@ dependencies = [ [[package]] name = "uu_nohup" -version = "0.0.22" +version = "0.0.23" dependencies = [ "clap", "libc", @@ -2716,7 +2716,7 @@ dependencies = [ [[package]] name = "uu_nproc" -version = "0.0.22" +version = "0.0.23" dependencies = [ "clap", "libc", @@ -2725,7 +2725,7 @@ dependencies = [ [[package]] name = "uu_numfmt" -version = "0.0.22" +version = "0.0.23" dependencies = [ "clap", "uucore", @@ -2733,7 +2733,7 @@ dependencies = [ [[package]] name = "uu_od" -version = "0.0.22" +version = "0.0.23" dependencies = [ "byteorder", "clap", @@ -2743,7 +2743,7 @@ dependencies = [ [[package]] name = "uu_paste" -version = "0.0.22" +version = "0.0.23" dependencies = [ "clap", "uucore", @@ -2751,7 +2751,7 @@ dependencies = [ [[package]] name = "uu_pathchk" -version = "0.0.22" +version = "0.0.23" dependencies = [ "clap", "libc", @@ -2760,7 +2760,7 @@ dependencies = [ [[package]] name = "uu_pinky" -version = "0.0.22" +version = "0.0.23" dependencies = [ "clap", "uucore", @@ -2768,7 +2768,7 @@ dependencies = [ [[package]] name = "uu_pr" -version = "0.0.22" +version = "0.0.23" dependencies = [ "chrono", "clap", @@ -2780,7 +2780,7 @@ dependencies = [ [[package]] name = "uu_printenv" -version = "0.0.22" +version = "0.0.23" dependencies = [ "clap", "uucore", @@ -2788,7 +2788,7 @@ dependencies = [ [[package]] name = "uu_printf" -version = "0.0.22" +version = "0.0.23" dependencies = [ "clap", "uucore", @@ -2796,7 +2796,7 @@ dependencies = [ [[package]] name = "uu_ptx" -version = "0.0.22" +version = "0.0.23" dependencies = [ "clap", "regex", @@ -2805,7 +2805,7 @@ dependencies = [ [[package]] name = "uu_pwd" -version = "0.0.22" +version = "0.0.23" dependencies = [ "clap", "uucore", @@ -2813,7 +2813,7 @@ dependencies = [ [[package]] name = "uu_readlink" -version = "0.0.22" +version = "0.0.23" dependencies = [ "clap", "uucore", @@ -2821,7 +2821,7 @@ dependencies = [ [[package]] name = "uu_realpath" -version = "0.0.22" +version = "0.0.23" dependencies = [ "clap", "uucore", @@ -2829,7 +2829,7 @@ dependencies = [ [[package]] name = "uu_rm" -version = "0.0.22" +version = "0.0.23" dependencies = [ "clap", "libc", @@ -2840,7 +2840,7 @@ dependencies = [ [[package]] name = "uu_rmdir" -version = "0.0.22" +version = "0.0.23" dependencies = [ "clap", "libc", @@ -2849,7 +2849,7 @@ dependencies = [ [[package]] name = "uu_runcon" -version = "0.0.22" +version = "0.0.23" dependencies = [ "clap", "libc", @@ -2860,7 +2860,7 @@ dependencies = [ [[package]] name = "uu_seq" -version = "0.0.22" +version = "0.0.23" dependencies = [ "bigdecimal", "clap", @@ -2871,7 +2871,7 @@ dependencies = [ [[package]] name = "uu_shred" -version = "0.0.22" +version = "0.0.23" dependencies = [ "clap", "libc", @@ -2881,7 +2881,7 @@ dependencies = [ [[package]] name = "uu_shuf" -version = "0.0.22" +version = "0.0.23" dependencies = [ "clap", "memchr", @@ -2892,7 +2892,7 @@ dependencies = [ [[package]] name = "uu_sleep" -version = "0.0.22" +version = "0.0.23" dependencies = [ "clap", "fundu", @@ -2901,7 +2901,7 @@ dependencies = [ [[package]] name = "uu_sort" -version = "0.0.22" +version = "0.0.23" dependencies = [ "binary-heap-plus", "clap", @@ -2920,7 +2920,7 @@ dependencies = [ [[package]] name = "uu_split" -version = "0.0.22" +version = "0.0.23" dependencies = [ "clap", "memchr", @@ -2929,7 +2929,7 @@ dependencies = [ [[package]] name = "uu_stat" -version = "0.0.22" +version = "0.0.23" dependencies = [ "clap", "uucore", @@ -2937,7 +2937,7 @@ dependencies = [ [[package]] name = "uu_stdbuf" -version = "0.0.22" +version = "0.0.23" dependencies = [ "clap", "tempfile", @@ -2947,7 +2947,7 @@ dependencies = [ [[package]] name = "uu_stdbuf_libstdbuf" -version = "0.0.22" +version = "0.0.23" dependencies = [ "cpp", "cpp_build", @@ -2957,7 +2957,7 @@ dependencies = [ [[package]] name = "uu_stty" -version = "0.0.22" +version = "0.0.23" dependencies = [ "clap", "nix", @@ -2966,7 +2966,7 @@ dependencies = [ [[package]] name = "uu_sum" -version = "0.0.22" +version = "0.0.23" dependencies = [ "clap", "uucore", @@ -2974,7 +2974,7 @@ dependencies = [ [[package]] name = "uu_sync" -version = "0.0.22" +version = "0.0.23" dependencies = [ "clap", "libc", @@ -2985,7 +2985,7 @@ dependencies = [ [[package]] name = "uu_tac" -version = "0.0.22" +version = "0.0.23" dependencies = [ "clap", "memchr", @@ -2996,7 +2996,7 @@ dependencies = [ [[package]] name = "uu_tail" -version = "0.0.22" +version = "0.0.23" dependencies = [ "clap", "fundu", @@ -3012,7 +3012,7 @@ dependencies = [ [[package]] name = "uu_tee" -version = "0.0.22" +version = "0.0.23" dependencies = [ "clap", "libc", @@ -3021,7 +3021,7 @@ dependencies = [ [[package]] name = "uu_test" -version = "0.0.22" +version = "0.0.23" dependencies = [ "clap", "libc", @@ -3031,7 +3031,7 @@ dependencies = [ [[package]] name = "uu_timeout" -version = "0.0.22" +version = "0.0.23" dependencies = [ "clap", "libc", @@ -3041,7 +3041,7 @@ dependencies = [ [[package]] name = "uu_touch" -version = "0.0.22" +version = "0.0.23" dependencies = [ "chrono", "clap", @@ -3053,7 +3053,7 @@ dependencies = [ [[package]] name = "uu_tr" -version = "0.0.22" +version = "0.0.23" dependencies = [ "clap", "nom", @@ -3062,7 +3062,7 @@ dependencies = [ [[package]] name = "uu_true" -version = "0.0.22" +version = "0.0.23" dependencies = [ "clap", "uucore", @@ -3070,7 +3070,7 @@ dependencies = [ [[package]] name = "uu_truncate" -version = "0.0.22" +version = "0.0.23" dependencies = [ "clap", "uucore", @@ -3078,7 +3078,7 @@ dependencies = [ [[package]] name = "uu_tsort" -version = "0.0.22" +version = "0.0.23" dependencies = [ "clap", "uucore", @@ -3086,7 +3086,7 @@ dependencies = [ [[package]] name = "uu_tty" -version = "0.0.22" +version = "0.0.23" dependencies = [ "clap", "nix", @@ -3095,7 +3095,7 @@ dependencies = [ [[package]] name = "uu_uname" -version = "0.0.22" +version = "0.0.23" dependencies = [ "clap", "platform-info", @@ -3104,7 +3104,7 @@ dependencies = [ [[package]] name = "uu_unexpand" -version = "0.0.22" +version = "0.0.23" dependencies = [ "clap", "unicode-width", @@ -3113,7 +3113,7 @@ dependencies = [ [[package]] name = "uu_uniq" -version = "0.0.22" +version = "0.0.23" dependencies = [ "clap", "uucore", @@ -3121,7 +3121,7 @@ dependencies = [ [[package]] name = "uu_unlink" -version = "0.0.22" +version = "0.0.23" dependencies = [ "clap", "uucore", @@ -3129,7 +3129,7 @@ dependencies = [ [[package]] name = "uu_uptime" -version = "0.0.22" +version = "0.0.23" dependencies = [ "chrono", "clap", @@ -3138,7 +3138,7 @@ dependencies = [ [[package]] name = "uu_users" -version = "0.0.22" +version = "0.0.23" dependencies = [ "clap", "uucore", @@ -3146,7 +3146,7 @@ dependencies = [ [[package]] name = "uu_vdir" -version = "0.0.22" +version = "0.0.23" dependencies = [ "clap", "uu_ls", @@ -3155,7 +3155,7 @@ dependencies = [ [[package]] name = "uu_wc" -version = "0.0.22" +version = "0.0.23" dependencies = [ "bytecount", "clap", @@ -3168,7 +3168,7 @@ dependencies = [ [[package]] name = "uu_who" -version = "0.0.22" +version = "0.0.23" dependencies = [ "clap", "uucore", @@ -3176,7 +3176,7 @@ dependencies = [ [[package]] name = "uu_whoami" -version = "0.0.22" +version = "0.0.23" dependencies = [ "clap", "libc", @@ -3186,7 +3186,7 @@ dependencies = [ [[package]] name = "uu_yes" -version = "0.0.22" +version = "0.0.23" dependencies = [ "clap", "itertools", @@ -3196,7 +3196,7 @@ dependencies = [ [[package]] name = "uucore" -version = "0.0.22" +version = "0.0.23" dependencies = [ "blake2b_simd", "blake3", @@ -3232,7 +3232,7 @@ dependencies = [ [[package]] name = "uucore_procs" -version = "0.0.22" +version = "0.0.23" dependencies = [ "proc-macro2", "quote", @@ -3241,7 +3241,7 @@ dependencies = [ [[package]] name = "uuhelp_parser" -version = "0.0.22" +version = "0.0.23" [[package]] name = "uuid" diff --git a/Cargo.toml b/Cargo.toml index b4a0dc07c..f313e2b03 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,7 +5,7 @@ [package] name = "coreutils" -version = "0.0.22" +version = "0.0.23" authors = ["uutils developers"] license = "MIT" description = "coreutils ~ GNU coreutils (updated); implemented as universal (cross-platform) utils, written in Rust" @@ -361,109 +361,109 @@ zip = { workspace = true, optional = true } uuhelp_parser = { optional = true, version = ">=0.0.19", path = "src/uuhelp_parser" } # * uutils -uu_test = { optional = true, version = "0.0.22", package = "uu_test", path = "src/uu/test" } +uu_test = { optional = true, version = "0.0.23", package = "uu_test", path = "src/uu/test" } # -arch = { optional = true, version = "0.0.22", package = "uu_arch", path = "src/uu/arch" } -base32 = { optional = true, version = "0.0.22", package = "uu_base32", path = "src/uu/base32" } -base64 = { optional = true, version = "0.0.22", package = "uu_base64", path = "src/uu/base64" } -basename = { optional = true, version = "0.0.22", package = "uu_basename", path = "src/uu/basename" } -basenc = { optional = true, version = "0.0.22", package = "uu_basenc", path = "src/uu/basenc" } -cat = { optional = true, version = "0.0.22", package = "uu_cat", path = "src/uu/cat" } -chcon = { optional = true, version = "0.0.22", package = "uu_chcon", path = "src/uu/chcon" } -chgrp = { optional = true, version = "0.0.22", package = "uu_chgrp", path = "src/uu/chgrp" } -chmod = { optional = true, version = "0.0.22", package = "uu_chmod", path = "src/uu/chmod" } -chown = { optional = true, version = "0.0.22", package = "uu_chown", path = "src/uu/chown" } -chroot = { optional = true, version = "0.0.22", package = "uu_chroot", path = "src/uu/chroot" } -cksum = { optional = true, version = "0.0.22", package = "uu_cksum", path = "src/uu/cksum" } -comm = { optional = true, version = "0.0.22", package = "uu_comm", path = "src/uu/comm" } -cp = { optional = true, version = "0.0.22", package = "uu_cp", path = "src/uu/cp" } -csplit = { optional = true, version = "0.0.22", package = "uu_csplit", path = "src/uu/csplit" } -cut = { optional = true, version = "0.0.22", package = "uu_cut", path = "src/uu/cut" } -date = { optional = true, version = "0.0.22", package = "uu_date", path = "src/uu/date" } -dd = { optional = true, version = "0.0.22", package = "uu_dd", path = "src/uu/dd" } -df = { optional = true, version = "0.0.22", package = "uu_df", path = "src/uu/df" } -dir = { optional = true, version = "0.0.22", package = "uu_dir", path = "src/uu/dir" } -dircolors = { optional = true, version = "0.0.22", package = "uu_dircolors", path = "src/uu/dircolors" } -dirname = { optional = true, version = "0.0.22", package = "uu_dirname", path = "src/uu/dirname" } -du = { optional = true, version = "0.0.22", package = "uu_du", path = "src/uu/du" } -echo = { optional = true, version = "0.0.22", package = "uu_echo", path = "src/uu/echo" } -env = { optional = true, version = "0.0.22", package = "uu_env", path = "src/uu/env" } -expand = { optional = true, version = "0.0.22", package = "uu_expand", path = "src/uu/expand" } -expr = { optional = true, version = "0.0.22", package = "uu_expr", path = "src/uu/expr" } -factor = { optional = true, version = "0.0.22", package = "uu_factor", path = "src/uu/factor" } -false = { optional = true, version = "0.0.22", package = "uu_false", path = "src/uu/false" } -fmt = { optional = true, version = "0.0.22", package = "uu_fmt", path = "src/uu/fmt" } -fold = { optional = true, version = "0.0.22", package = "uu_fold", path = "src/uu/fold" } -groups = { optional = true, version = "0.0.22", package = "uu_groups", path = "src/uu/groups" } -hashsum = { optional = true, version = "0.0.22", package = "uu_hashsum", path = "src/uu/hashsum" } -head = { optional = true, version = "0.0.22", package = "uu_head", path = "src/uu/head" } -hostid = { optional = true, version = "0.0.22", package = "uu_hostid", path = "src/uu/hostid" } -hostname = { optional = true, version = "0.0.22", package = "uu_hostname", path = "src/uu/hostname" } -id = { optional = true, version = "0.0.22", package = "uu_id", path = "src/uu/id" } -install = { optional = true, version = "0.0.22", package = "uu_install", path = "src/uu/install" } -join = { optional = true, version = "0.0.22", package = "uu_join", path = "src/uu/join" } -kill = { optional = true, version = "0.0.22", package = "uu_kill", path = "src/uu/kill" } -link = { optional = true, version = "0.0.22", package = "uu_link", path = "src/uu/link" } -ln = { optional = true, version = "0.0.22", package = "uu_ln", path = "src/uu/ln" } -ls = { optional = true, version = "0.0.22", package = "uu_ls", path = "src/uu/ls" } -logname = { optional = true, version = "0.0.22", package = "uu_logname", path = "src/uu/logname" } -mkdir = { optional = true, version = "0.0.22", package = "uu_mkdir", path = "src/uu/mkdir" } -mkfifo = { optional = true, version = "0.0.22", package = "uu_mkfifo", path = "src/uu/mkfifo" } -mknod = { optional = true, version = "0.0.22", package = "uu_mknod", path = "src/uu/mknod" } -mktemp = { optional = true, version = "0.0.22", package = "uu_mktemp", path = "src/uu/mktemp" } -more = { optional = true, version = "0.0.22", package = "uu_more", path = "src/uu/more" } -mv = { optional = true, version = "0.0.22", package = "uu_mv", path = "src/uu/mv" } -nice = { optional = true, version = "0.0.22", package = "uu_nice", path = "src/uu/nice" } -nl = { optional = true, version = "0.0.22", package = "uu_nl", path = "src/uu/nl" } -nohup = { optional = true, version = "0.0.22", package = "uu_nohup", path = "src/uu/nohup" } -nproc = { optional = true, version = "0.0.22", package = "uu_nproc", path = "src/uu/nproc" } -numfmt = { optional = true, version = "0.0.22", package = "uu_numfmt", path = "src/uu/numfmt" } -od = { optional = true, version = "0.0.22", package = "uu_od", path = "src/uu/od" } -paste = { optional = true, version = "0.0.22", package = "uu_paste", path = "src/uu/paste" } -pathchk = { optional = true, version = "0.0.22", package = "uu_pathchk", path = "src/uu/pathchk" } -pinky = { optional = true, version = "0.0.22", package = "uu_pinky", path = "src/uu/pinky" } -pr = { optional = true, version = "0.0.22", package = "uu_pr", path = "src/uu/pr" } -printenv = { optional = true, version = "0.0.22", package = "uu_printenv", path = "src/uu/printenv" } -printf = { optional = true, version = "0.0.22", package = "uu_printf", path = "src/uu/printf" } -ptx = { optional = true, version = "0.0.22", package = "uu_ptx", path = "src/uu/ptx" } -pwd = { optional = true, version = "0.0.22", package = "uu_pwd", path = "src/uu/pwd" } -readlink = { optional = true, version = "0.0.22", package = "uu_readlink", path = "src/uu/readlink" } -realpath = { optional = true, version = "0.0.22", package = "uu_realpath", path = "src/uu/realpath" } -rm = { optional = true, version = "0.0.22", package = "uu_rm", path = "src/uu/rm" } -rmdir = { optional = true, version = "0.0.22", package = "uu_rmdir", path = "src/uu/rmdir" } -runcon = { optional = true, version = "0.0.22", package = "uu_runcon", path = "src/uu/runcon" } -seq = { optional = true, version = "0.0.22", package = "uu_seq", path = "src/uu/seq" } -shred = { optional = true, version = "0.0.22", package = "uu_shred", path = "src/uu/shred" } -shuf = { optional = true, version = "0.0.22", package = "uu_shuf", path = "src/uu/shuf" } -sleep = { optional = true, version = "0.0.22", package = "uu_sleep", path = "src/uu/sleep" } -sort = { optional = true, version = "0.0.22", package = "uu_sort", path = "src/uu/sort" } -split = { optional = true, version = "0.0.22", package = "uu_split", path = "src/uu/split" } -stat = { optional = true, version = "0.0.22", package = "uu_stat", path = "src/uu/stat" } -stdbuf = { optional = true, version = "0.0.22", package = "uu_stdbuf", path = "src/uu/stdbuf" } -stty = { optional = true, version = "0.0.22", package = "uu_stty", path = "src/uu/stty" } -sum = { optional = true, version = "0.0.22", package = "uu_sum", path = "src/uu/sum" } -sync = { optional = true, version = "0.0.22", package = "uu_sync", path = "src/uu/sync" } -tac = { optional = true, version = "0.0.22", package = "uu_tac", path = "src/uu/tac" } -tail = { optional = true, version = "0.0.22", package = "uu_tail", path = "src/uu/tail" } -tee = { optional = true, version = "0.0.22", package = "uu_tee", path = "src/uu/tee" } -timeout = { optional = true, version = "0.0.22", package = "uu_timeout", path = "src/uu/timeout" } -touch = { optional = true, version = "0.0.22", package = "uu_touch", path = "src/uu/touch" } -tr = { optional = true, version = "0.0.22", package = "uu_tr", path = "src/uu/tr" } -true = { optional = true, version = "0.0.22", package = "uu_true", path = "src/uu/true" } -truncate = { optional = true, version = "0.0.22", package = "uu_truncate", path = "src/uu/truncate" } -tsort = { optional = true, version = "0.0.22", package = "uu_tsort", path = "src/uu/tsort" } -tty = { optional = true, version = "0.0.22", package = "uu_tty", path = "src/uu/tty" } -uname = { optional = true, version = "0.0.22", package = "uu_uname", path = "src/uu/uname" } -unexpand = { optional = true, version = "0.0.22", package = "uu_unexpand", path = "src/uu/unexpand" } -uniq = { optional = true, version = "0.0.22", package = "uu_uniq", path = "src/uu/uniq" } -unlink = { optional = true, version = "0.0.22", package = "uu_unlink", path = "src/uu/unlink" } -uptime = { optional = true, version = "0.0.22", package = "uu_uptime", path = "src/uu/uptime" } -users = { optional = true, version = "0.0.22", package = "uu_users", path = "src/uu/users" } -vdir = { optional = true, version = "0.0.22", package = "uu_vdir", path = "src/uu/vdir" } -wc = { optional = true, version = "0.0.22", package = "uu_wc", path = "src/uu/wc" } -who = { optional = true, version = "0.0.22", package = "uu_who", path = "src/uu/who" } -whoami = { optional = true, version = "0.0.22", package = "uu_whoami", path = "src/uu/whoami" } -yes = { optional = true, version = "0.0.22", package = "uu_yes", path = "src/uu/yes" } +arch = { optional = true, version = "0.0.23", package = "uu_arch", path = "src/uu/arch" } +base32 = { optional = true, version = "0.0.23", package = "uu_base32", path = "src/uu/base32" } +base64 = { optional = true, version = "0.0.23", package = "uu_base64", path = "src/uu/base64" } +basename = { optional = true, version = "0.0.23", package = "uu_basename", path = "src/uu/basename" } +basenc = { optional = true, version = "0.0.23", package = "uu_basenc", path = "src/uu/basenc" } +cat = { optional = true, version = "0.0.23", package = "uu_cat", path = "src/uu/cat" } +chcon = { optional = true, version = "0.0.23", package = "uu_chcon", path = "src/uu/chcon" } +chgrp = { optional = true, version = "0.0.23", package = "uu_chgrp", path = "src/uu/chgrp" } +chmod = { optional = true, version = "0.0.23", package = "uu_chmod", path = "src/uu/chmod" } +chown = { optional = true, version = "0.0.23", package = "uu_chown", path = "src/uu/chown" } +chroot = { optional = true, version = "0.0.23", package = "uu_chroot", path = "src/uu/chroot" } +cksum = { optional = true, version = "0.0.23", package = "uu_cksum", path = "src/uu/cksum" } +comm = { optional = true, version = "0.0.23", package = "uu_comm", path = "src/uu/comm" } +cp = { optional = true, version = "0.0.23", package = "uu_cp", path = "src/uu/cp" } +csplit = { optional = true, version = "0.0.23", package = "uu_csplit", path = "src/uu/csplit" } +cut = { optional = true, version = "0.0.23", package = "uu_cut", path = "src/uu/cut" } +date = { optional = true, version = "0.0.23", package = "uu_date", path = "src/uu/date" } +dd = { optional = true, version = "0.0.23", package = "uu_dd", path = "src/uu/dd" } +df = { optional = true, version = "0.0.23", package = "uu_df", path = "src/uu/df" } +dir = { optional = true, version = "0.0.23", package = "uu_dir", path = "src/uu/dir" } +dircolors = { optional = true, version = "0.0.23", package = "uu_dircolors", path = "src/uu/dircolors" } +dirname = { optional = true, version = "0.0.23", package = "uu_dirname", path = "src/uu/dirname" } +du = { optional = true, version = "0.0.23", package = "uu_du", path = "src/uu/du" } +echo = { optional = true, version = "0.0.23", package = "uu_echo", path = "src/uu/echo" } +env = { optional = true, version = "0.0.23", package = "uu_env", path = "src/uu/env" } +expand = { optional = true, version = "0.0.23", package = "uu_expand", path = "src/uu/expand" } +expr = { optional = true, version = "0.0.23", package = "uu_expr", path = "src/uu/expr" } +factor = { optional = true, version = "0.0.23", package = "uu_factor", path = "src/uu/factor" } +false = { optional = true, version = "0.0.23", package = "uu_false", path = "src/uu/false" } +fmt = { optional = true, version = "0.0.23", package = "uu_fmt", path = "src/uu/fmt" } +fold = { optional = true, version = "0.0.23", package = "uu_fold", path = "src/uu/fold" } +groups = { optional = true, version = "0.0.23", package = "uu_groups", path = "src/uu/groups" } +hashsum = { optional = true, version = "0.0.23", package = "uu_hashsum", path = "src/uu/hashsum" } +head = { optional = true, version = "0.0.23", package = "uu_head", path = "src/uu/head" } +hostid = { optional = true, version = "0.0.23", package = "uu_hostid", path = "src/uu/hostid" } +hostname = { optional = true, version = "0.0.23", package = "uu_hostname", path = "src/uu/hostname" } +id = { optional = true, version = "0.0.23", package = "uu_id", path = "src/uu/id" } +install = { optional = true, version = "0.0.23", package = "uu_install", path = "src/uu/install" } +join = { optional = true, version = "0.0.23", package = "uu_join", path = "src/uu/join" } +kill = { optional = true, version = "0.0.23", package = "uu_kill", path = "src/uu/kill" } +link = { optional = true, version = "0.0.23", package = "uu_link", path = "src/uu/link" } +ln = { optional = true, version = "0.0.23", package = "uu_ln", path = "src/uu/ln" } +ls = { optional = true, version = "0.0.23", package = "uu_ls", path = "src/uu/ls" } +logname = { optional = true, version = "0.0.23", package = "uu_logname", path = "src/uu/logname" } +mkdir = { optional = true, version = "0.0.23", package = "uu_mkdir", path = "src/uu/mkdir" } +mkfifo = { optional = true, version = "0.0.23", package = "uu_mkfifo", path = "src/uu/mkfifo" } +mknod = { optional = true, version = "0.0.23", package = "uu_mknod", path = "src/uu/mknod" } +mktemp = { optional = true, version = "0.0.23", package = "uu_mktemp", path = "src/uu/mktemp" } +more = { optional = true, version = "0.0.23", package = "uu_more", path = "src/uu/more" } +mv = { optional = true, version = "0.0.23", package = "uu_mv", path = "src/uu/mv" } +nice = { optional = true, version = "0.0.23", package = "uu_nice", path = "src/uu/nice" } +nl = { optional = true, version = "0.0.23", package = "uu_nl", path = "src/uu/nl" } +nohup = { optional = true, version = "0.0.23", package = "uu_nohup", path = "src/uu/nohup" } +nproc = { optional = true, version = "0.0.23", package = "uu_nproc", path = "src/uu/nproc" } +numfmt = { optional = true, version = "0.0.23", package = "uu_numfmt", path = "src/uu/numfmt" } +od = { optional = true, version = "0.0.23", package = "uu_od", path = "src/uu/od" } +paste = { optional = true, version = "0.0.23", package = "uu_paste", path = "src/uu/paste" } +pathchk = { optional = true, version = "0.0.23", package = "uu_pathchk", path = "src/uu/pathchk" } +pinky = { optional = true, version = "0.0.23", package = "uu_pinky", path = "src/uu/pinky" } +pr = { optional = true, version = "0.0.23", package = "uu_pr", path = "src/uu/pr" } +printenv = { optional = true, version = "0.0.23", package = "uu_printenv", path = "src/uu/printenv" } +printf = { optional = true, version = "0.0.23", package = "uu_printf", path = "src/uu/printf" } +ptx = { optional = true, version = "0.0.23", package = "uu_ptx", path = "src/uu/ptx" } +pwd = { optional = true, version = "0.0.23", package = "uu_pwd", path = "src/uu/pwd" } +readlink = { optional = true, version = "0.0.23", package = "uu_readlink", path = "src/uu/readlink" } +realpath = { optional = true, version = "0.0.23", package = "uu_realpath", path = "src/uu/realpath" } +rm = { optional = true, version = "0.0.23", package = "uu_rm", path = "src/uu/rm" } +rmdir = { optional = true, version = "0.0.23", package = "uu_rmdir", path = "src/uu/rmdir" } +runcon = { optional = true, version = "0.0.23", package = "uu_runcon", path = "src/uu/runcon" } +seq = { optional = true, version = "0.0.23", package = "uu_seq", path = "src/uu/seq" } +shred = { optional = true, version = "0.0.23", package = "uu_shred", path = "src/uu/shred" } +shuf = { optional = true, version = "0.0.23", package = "uu_shuf", path = "src/uu/shuf" } +sleep = { optional = true, version = "0.0.23", package = "uu_sleep", path = "src/uu/sleep" } +sort = { optional = true, version = "0.0.23", package = "uu_sort", path = "src/uu/sort" } +split = { optional = true, version = "0.0.23", package = "uu_split", path = "src/uu/split" } +stat = { optional = true, version = "0.0.23", package = "uu_stat", path = "src/uu/stat" } +stdbuf = { optional = true, version = "0.0.23", package = "uu_stdbuf", path = "src/uu/stdbuf" } +stty = { optional = true, version = "0.0.23", package = "uu_stty", path = "src/uu/stty" } +sum = { optional = true, version = "0.0.23", package = "uu_sum", path = "src/uu/sum" } +sync = { optional = true, version = "0.0.23", package = "uu_sync", path = "src/uu/sync" } +tac = { optional = true, version = "0.0.23", package = "uu_tac", path = "src/uu/tac" } +tail = { optional = true, version = "0.0.23", package = "uu_tail", path = "src/uu/tail" } +tee = { optional = true, version = "0.0.23", package = "uu_tee", path = "src/uu/tee" } +timeout = { optional = true, version = "0.0.23", package = "uu_timeout", path = "src/uu/timeout" } +touch = { optional = true, version = "0.0.23", package = "uu_touch", path = "src/uu/touch" } +tr = { optional = true, version = "0.0.23", package = "uu_tr", path = "src/uu/tr" } +true = { optional = true, version = "0.0.23", package = "uu_true", path = "src/uu/true" } +truncate = { optional = true, version = "0.0.23", package = "uu_truncate", path = "src/uu/truncate" } +tsort = { optional = true, version = "0.0.23", package = "uu_tsort", path = "src/uu/tsort" } +tty = { optional = true, version = "0.0.23", package = "uu_tty", path = "src/uu/tty" } +uname = { optional = true, version = "0.0.23", package = "uu_uname", path = "src/uu/uname" } +unexpand = { optional = true, version = "0.0.23", package = "uu_unexpand", path = "src/uu/unexpand" } +uniq = { optional = true, version = "0.0.23", package = "uu_uniq", path = "src/uu/uniq" } +unlink = { optional = true, version = "0.0.23", package = "uu_unlink", path = "src/uu/unlink" } +uptime = { optional = true, version = "0.0.23", package = "uu_uptime", path = "src/uu/uptime" } +users = { optional = true, version = "0.0.23", package = "uu_users", path = "src/uu/users" } +vdir = { optional = true, version = "0.0.23", package = "uu_vdir", path = "src/uu/vdir" } +wc = { optional = true, version = "0.0.23", package = "uu_wc", path = "src/uu/wc" } +who = { optional = true, version = "0.0.23", package = "uu_who", path = "src/uu/who" } +whoami = { optional = true, version = "0.0.23", package = "uu_whoami", path = "src/uu/whoami" } +yes = { optional = true, version = "0.0.23", package = "uu_yes", path = "src/uu/yes" } # this breaks clippy linting with: "tests/by-util/test_factor_benches.rs: No such file or directory (os error 2)" # factor_benches = { optional = true, version = "0.0.0", package = "uu_factor_benches", path = "tests/benches/factor" } diff --git a/src/uu/arch/Cargo.toml b/src/uu/arch/Cargo.toml index 9686c3f0a..edb15f846 100644 --- a/src/uu/arch/Cargo.toml +++ b/src/uu/arch/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_arch" -version = "0.0.22" +version = "0.0.23" authors = ["uutils developers"] license = "MIT" description = "arch ~ (uutils) display machine architecture" diff --git a/src/uu/base32/Cargo.toml b/src/uu/base32/Cargo.toml index f3bca4a19..71fbe325f 100644 --- a/src/uu/base32/Cargo.toml +++ b/src/uu/base32/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_base32" -version = "0.0.22" +version = "0.0.23" authors = ["uutils developers"] license = "MIT" description = "base32 ~ (uutils) decode/encode input (base32-encoding)" diff --git a/src/uu/base64/Cargo.toml b/src/uu/base64/Cargo.toml index ddd669b89..e52665bb6 100644 --- a/src/uu/base64/Cargo.toml +++ b/src/uu/base64/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_base64" -version = "0.0.22" +version = "0.0.23" authors = ["uutils developers"] license = "MIT" description = "base64 ~ (uutils) decode/encode input (base64-encoding)" diff --git a/src/uu/basename/Cargo.toml b/src/uu/basename/Cargo.toml index e74ad7616..3de240e04 100644 --- a/src/uu/basename/Cargo.toml +++ b/src/uu/basename/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_basename" -version = "0.0.22" +version = "0.0.23" authors = ["uutils developers"] license = "MIT" description = "basename ~ (uutils) display PATHNAME with leading directory components removed" diff --git a/src/uu/basenc/Cargo.toml b/src/uu/basenc/Cargo.toml index 7f4188440..54a5a53a1 100644 --- a/src/uu/basenc/Cargo.toml +++ b/src/uu/basenc/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_basenc" -version = "0.0.22" +version = "0.0.23" authors = ["uutils developers"] license = "MIT" description = "basenc ~ (uutils) decode/encode input" diff --git a/src/uu/cat/Cargo.toml b/src/uu/cat/Cargo.toml index 33488cd07..14383895e 100644 --- a/src/uu/cat/Cargo.toml +++ b/src/uu/cat/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_cat" -version = "0.0.22" +version = "0.0.23" authors = ["uutils developers"] license = "MIT" description = "cat ~ (uutils) concatenate and display input" diff --git a/src/uu/chcon/Cargo.toml b/src/uu/chcon/Cargo.toml index 07bd73f4b..d21da4cf0 100644 --- a/src/uu/chcon/Cargo.toml +++ b/src/uu/chcon/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_chcon" -version = "0.0.22" +version = "0.0.23" authors = ["uutils developers"] license = "MIT" description = "chcon ~ (uutils) change file security context" diff --git a/src/uu/chgrp/Cargo.toml b/src/uu/chgrp/Cargo.toml index 9591e1365..79942033f 100644 --- a/src/uu/chgrp/Cargo.toml +++ b/src/uu/chgrp/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_chgrp" -version = "0.0.22" +version = "0.0.23" authors = ["uutils developers"] license = "MIT" description = "chgrp ~ (uutils) change the group ownership of FILE" diff --git a/src/uu/chmod/Cargo.toml b/src/uu/chmod/Cargo.toml index d10d0e08d..ebfe00fe1 100644 --- a/src/uu/chmod/Cargo.toml +++ b/src/uu/chmod/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_chmod" -version = "0.0.22" +version = "0.0.23" authors = ["uutils developers"] license = "MIT" description = "chmod ~ (uutils) change mode of FILE" diff --git a/src/uu/chown/Cargo.toml b/src/uu/chown/Cargo.toml index bbd75db03..aab8f20b6 100644 --- a/src/uu/chown/Cargo.toml +++ b/src/uu/chown/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_chown" -version = "0.0.22" +version = "0.0.23" authors = ["uutils developers"] license = "MIT" description = "chown ~ (uutils) change the ownership of FILE" diff --git a/src/uu/chroot/Cargo.toml b/src/uu/chroot/Cargo.toml index 847b18ef5..a78c89685 100644 --- a/src/uu/chroot/Cargo.toml +++ b/src/uu/chroot/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_chroot" -version = "0.0.22" +version = "0.0.23" authors = ["uutils developers"] license = "MIT" description = "chroot ~ (uutils) run COMMAND under a new root directory" diff --git a/src/uu/cksum/Cargo.toml b/src/uu/cksum/Cargo.toml index e0d54edb9..9a811fafd 100644 --- a/src/uu/cksum/Cargo.toml +++ b/src/uu/cksum/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_cksum" -version = "0.0.22" +version = "0.0.23" authors = ["uutils developers"] license = "MIT" description = "cksum ~ (uutils) display CRC and size of input" diff --git a/src/uu/comm/Cargo.toml b/src/uu/comm/Cargo.toml index 07430f9d9..71ed0ad71 100644 --- a/src/uu/comm/Cargo.toml +++ b/src/uu/comm/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_comm" -version = "0.0.22" +version = "0.0.23" authors = ["uutils developers"] license = "MIT" description = "comm ~ (uutils) compare sorted inputs" diff --git a/src/uu/cp/Cargo.toml b/src/uu/cp/Cargo.toml index 49ba75ca0..179b4668e 100644 --- a/src/uu/cp/Cargo.toml +++ b/src/uu/cp/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_cp" -version = "0.0.22" +version = "0.0.23" authors = [ "Jordy Dickinson ", "Joshua S. Miller ", diff --git a/src/uu/csplit/Cargo.toml b/src/uu/csplit/Cargo.toml index a30ebc4f6..e68b12582 100644 --- a/src/uu/csplit/Cargo.toml +++ b/src/uu/csplit/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_csplit" -version = "0.0.22" +version = "0.0.23" authors = ["uutils developers"] license = "MIT" description = "csplit ~ (uutils) Output pieces of FILE separated by PATTERN(s) to files 'xx00', 'xx01', ..., and output byte counts of each piece to standard output" diff --git a/src/uu/cut/Cargo.toml b/src/uu/cut/Cargo.toml index 695777a72..e572d987e 100644 --- a/src/uu/cut/Cargo.toml +++ b/src/uu/cut/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_cut" -version = "0.0.22" +version = "0.0.23" authors = ["uutils developers"] license = "MIT" description = "cut ~ (uutils) display byte/field columns of input lines" diff --git a/src/uu/date/Cargo.toml b/src/uu/date/Cargo.toml index cd860ce58..c5682f83e 100644 --- a/src/uu/date/Cargo.toml +++ b/src/uu/date/Cargo.toml @@ -1,7 +1,7 @@ # spell-checker:ignore datetime [package] name = "uu_date" -version = "0.0.22" +version = "0.0.23" authors = ["uutils developers"] license = "MIT" description = "date ~ (uutils) display or set the current time" diff --git a/src/uu/dd/Cargo.toml b/src/uu/dd/Cargo.toml index f56e46eaa..7db05b422 100644 --- a/src/uu/dd/Cargo.toml +++ b/src/uu/dd/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_dd" -version = "0.0.22" +version = "0.0.23" authors = ["uutils developers"] license = "MIT" description = "dd ~ (uutils) copy and convert files" diff --git a/src/uu/df/Cargo.toml b/src/uu/df/Cargo.toml index 2aae7bd55..d074e6be7 100644 --- a/src/uu/df/Cargo.toml +++ b/src/uu/df/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_df" -version = "0.0.22" +version = "0.0.23" authors = ["uutils developers"] license = "MIT" description = "df ~ (uutils) display file system information" diff --git a/src/uu/dir/Cargo.toml b/src/uu/dir/Cargo.toml index 0bca7bb9d..628c0fb8c 100644 --- a/src/uu/dir/Cargo.toml +++ b/src/uu/dir/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_dir" -version = "0.0.22" +version = "0.0.23" authors = ["uutils developers"] license = "MIT" description = "shortcut to ls -C -b" diff --git a/src/uu/dircolors/Cargo.toml b/src/uu/dircolors/Cargo.toml index a4a101326..6099b5a84 100644 --- a/src/uu/dircolors/Cargo.toml +++ b/src/uu/dircolors/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_dircolors" -version = "0.0.22" +version = "0.0.23" authors = ["uutils developers"] license = "MIT" description = "dircolors ~ (uutils) display commands to set LS_COLORS" diff --git a/src/uu/dirname/Cargo.toml b/src/uu/dirname/Cargo.toml index 67806986c..b093ce3c5 100644 --- a/src/uu/dirname/Cargo.toml +++ b/src/uu/dirname/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_dirname" -version = "0.0.22" +version = "0.0.23" authors = ["uutils developers"] license = "MIT" description = "dirname ~ (uutils) display parent directory of PATHNAME" diff --git a/src/uu/du/Cargo.toml b/src/uu/du/Cargo.toml index 158fa9786..5e87b2f43 100644 --- a/src/uu/du/Cargo.toml +++ b/src/uu/du/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_du" -version = "0.0.22" +version = "0.0.23" authors = ["uutils developers"] license = "MIT" description = "du ~ (uutils) display disk usage" diff --git a/src/uu/echo/Cargo.toml b/src/uu/echo/Cargo.toml index 92d5d4924..e63977e57 100644 --- a/src/uu/echo/Cargo.toml +++ b/src/uu/echo/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_echo" -version = "0.0.22" +version = "0.0.23" authors = ["uutils developers"] license = "MIT" description = "echo ~ (uutils) display TEXT" diff --git a/src/uu/env/Cargo.toml b/src/uu/env/Cargo.toml index 78b133733..a1df3563b 100644 --- a/src/uu/env/Cargo.toml +++ b/src/uu/env/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_env" -version = "0.0.22" +version = "0.0.23" authors = ["uutils developers"] license = "MIT" description = "env ~ (uutils) set each NAME to VALUE in the environment and run COMMAND" diff --git a/src/uu/expand/Cargo.toml b/src/uu/expand/Cargo.toml index c329e61b6..63425bc5e 100644 --- a/src/uu/expand/Cargo.toml +++ b/src/uu/expand/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_expand" -version = "0.0.22" +version = "0.0.23" authors = ["uutils developers"] license = "MIT" description = "expand ~ (uutils) convert input tabs to spaces" diff --git a/src/uu/expr/Cargo.toml b/src/uu/expr/Cargo.toml index 3f0b5ca76..a867c27c8 100644 --- a/src/uu/expr/Cargo.toml +++ b/src/uu/expr/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_expr" -version = "0.0.22" +version = "0.0.23" authors = ["uutils developers"] license = "MIT" description = "expr ~ (uutils) display the value of EXPRESSION" diff --git a/src/uu/factor/Cargo.toml b/src/uu/factor/Cargo.toml index 8ecee86c5..aee67eeea 100644 --- a/src/uu/factor/Cargo.toml +++ b/src/uu/factor/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_factor" -version = "0.0.22" +version = "0.0.23" authors = ["uutils developers"] license = "MIT" description = "factor ~ (uutils) display the prime factors of each NUMBER" diff --git a/src/uu/false/Cargo.toml b/src/uu/false/Cargo.toml index 3b1ca97f9..50b5bc027 100644 --- a/src/uu/false/Cargo.toml +++ b/src/uu/false/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_false" -version = "0.0.22" +version = "0.0.23" authors = ["uutils developers"] license = "MIT" description = "false ~ (uutils) do nothing and fail" diff --git a/src/uu/fmt/Cargo.toml b/src/uu/fmt/Cargo.toml index c49f52741..688525e3b 100644 --- a/src/uu/fmt/Cargo.toml +++ b/src/uu/fmt/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_fmt" -version = "0.0.22" +version = "0.0.23" authors = ["uutils developers"] license = "MIT" description = "fmt ~ (uutils) reformat each paragraph of input" diff --git a/src/uu/fold/Cargo.toml b/src/uu/fold/Cargo.toml index af36f0466..1887f1bbd 100644 --- a/src/uu/fold/Cargo.toml +++ b/src/uu/fold/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_fold" -version = "0.0.22" +version = "0.0.23" authors = ["uutils developers"] license = "MIT" description = "fold ~ (uutils) wrap each line of input" diff --git a/src/uu/groups/Cargo.toml b/src/uu/groups/Cargo.toml index 5f786acfd..9c5135175 100644 --- a/src/uu/groups/Cargo.toml +++ b/src/uu/groups/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_groups" -version = "0.0.22" +version = "0.0.23" authors = ["uutils developers"] license = "MIT" description = "groups ~ (uutils) display group memberships for USERNAME" diff --git a/src/uu/hashsum/Cargo.toml b/src/uu/hashsum/Cargo.toml index 23b60a701..1ecb5f840 100644 --- a/src/uu/hashsum/Cargo.toml +++ b/src/uu/hashsum/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_hashsum" -version = "0.0.22" +version = "0.0.23" authors = ["uutils developers"] license = "MIT" description = "hashsum ~ (uutils) display or check input digests" diff --git a/src/uu/head/Cargo.toml b/src/uu/head/Cargo.toml index 7c73f3941..12872c808 100644 --- a/src/uu/head/Cargo.toml +++ b/src/uu/head/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_head" -version = "0.0.22" +version = "0.0.23" authors = ["uutils developers"] license = "MIT" description = "head ~ (uutils) display the first lines of input" diff --git a/src/uu/hostid/Cargo.toml b/src/uu/hostid/Cargo.toml index 2e6dcc339..602e7ef37 100644 --- a/src/uu/hostid/Cargo.toml +++ b/src/uu/hostid/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_hostid" -version = "0.0.22" +version = "0.0.23" authors = ["uutils developers"] license = "MIT" description = "hostid ~ (uutils) display the numeric identifier of the current host" diff --git a/src/uu/hostname/Cargo.toml b/src/uu/hostname/Cargo.toml index 49cb14bf2..a9b033d12 100644 --- a/src/uu/hostname/Cargo.toml +++ b/src/uu/hostname/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_hostname" -version = "0.0.22" +version = "0.0.23" authors = ["uutils developers"] license = "MIT" description = "hostname ~ (uutils) display or set the host name of the current host" diff --git a/src/uu/id/Cargo.toml b/src/uu/id/Cargo.toml index bf1eaf44c..b62df1eeb 100644 --- a/src/uu/id/Cargo.toml +++ b/src/uu/id/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_id" -version = "0.0.22" +version = "0.0.23" authors = ["uutils developers"] license = "MIT" description = "id ~ (uutils) display user and group information for USER" diff --git a/src/uu/install/Cargo.toml b/src/uu/install/Cargo.toml index b5353eace..011038006 100644 --- a/src/uu/install/Cargo.toml +++ b/src/uu/install/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_install" -version = "0.0.22" +version = "0.0.23" authors = ["Ben Eills ", "uutils developers"] license = "MIT" description = "install ~ (uutils) copy files from SOURCE to DESTINATION (with specified attributes)" diff --git a/src/uu/join/Cargo.toml b/src/uu/join/Cargo.toml index c52f66de4..20853527d 100644 --- a/src/uu/join/Cargo.toml +++ b/src/uu/join/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_join" -version = "0.0.22" +version = "0.0.23" authors = ["uutils developers"] license = "MIT" description = "join ~ (uutils) merge lines from inputs with matching join fields" diff --git a/src/uu/kill/Cargo.toml b/src/uu/kill/Cargo.toml index a606c81e7..6cb8cee6e 100644 --- a/src/uu/kill/Cargo.toml +++ b/src/uu/kill/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_kill" -version = "0.0.22" +version = "0.0.23" authors = ["uutils developers"] license = "MIT" description = "kill ~ (uutils) send a signal to a process" diff --git a/src/uu/link/Cargo.toml b/src/uu/link/Cargo.toml index c79ca6041..cba12c95c 100644 --- a/src/uu/link/Cargo.toml +++ b/src/uu/link/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_link" -version = "0.0.22" +version = "0.0.23" authors = ["uutils developers"] license = "MIT" description = "link ~ (uutils) create a hard (file system) link to FILE" diff --git a/src/uu/ln/Cargo.toml b/src/uu/ln/Cargo.toml index e9f0e6eb7..4243075ea 100644 --- a/src/uu/ln/Cargo.toml +++ b/src/uu/ln/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_ln" -version = "0.0.22" +version = "0.0.23" authors = ["uutils developers"] license = "MIT" description = "ln ~ (uutils) create a (file system) link to TARGET" diff --git a/src/uu/logname/Cargo.toml b/src/uu/logname/Cargo.toml index 41cbcf73b..2e8553f12 100644 --- a/src/uu/logname/Cargo.toml +++ b/src/uu/logname/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_logname" -version = "0.0.22" +version = "0.0.23" authors = ["uutils developers"] license = "MIT" description = "logname ~ (uutils) display the login name of the current user" diff --git a/src/uu/ls/Cargo.toml b/src/uu/ls/Cargo.toml index 64d1cf136..96cf7df1a 100644 --- a/src/uu/ls/Cargo.toml +++ b/src/uu/ls/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_ls" -version = "0.0.22" +version = "0.0.23" authors = ["uutils developers"] license = "MIT" description = "ls ~ (uutils) display directory contents" diff --git a/src/uu/mkdir/Cargo.toml b/src/uu/mkdir/Cargo.toml index c0d60dc9d..4f6ddb3cc 100644 --- a/src/uu/mkdir/Cargo.toml +++ b/src/uu/mkdir/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_mkdir" -version = "0.0.22" +version = "0.0.23" authors = ["uutils developers"] license = "MIT" description = "mkdir ~ (uutils) create DIRECTORY" diff --git a/src/uu/mkfifo/Cargo.toml b/src/uu/mkfifo/Cargo.toml index 285fc2b34..d7f787511 100644 --- a/src/uu/mkfifo/Cargo.toml +++ b/src/uu/mkfifo/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_mkfifo" -version = "0.0.22" +version = "0.0.23" authors = ["uutils developers"] license = "MIT" description = "mkfifo ~ (uutils) create FIFOs (named pipes)" diff --git a/src/uu/mknod/Cargo.toml b/src/uu/mknod/Cargo.toml index 2e6601714..58caa5d9d 100644 --- a/src/uu/mknod/Cargo.toml +++ b/src/uu/mknod/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_mknod" -version = "0.0.22" +version = "0.0.23" authors = ["uutils developers"] license = "MIT" description = "mknod ~ (uutils) create special file NAME of TYPE" diff --git a/src/uu/mktemp/Cargo.toml b/src/uu/mktemp/Cargo.toml index 8d8a885b1..2913e4b6e 100644 --- a/src/uu/mktemp/Cargo.toml +++ b/src/uu/mktemp/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_mktemp" -version = "0.0.22" +version = "0.0.23" authors = ["uutils developers"] license = "MIT" description = "mktemp ~ (uutils) create and display a temporary file or directory from TEMPLATE" diff --git a/src/uu/more/Cargo.toml b/src/uu/more/Cargo.toml index 5614a0495..9f0bc9d3d 100644 --- a/src/uu/more/Cargo.toml +++ b/src/uu/more/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_more" -version = "0.0.22" +version = "0.0.23" authors = ["uutils developers"] license = "MIT" description = "more ~ (uutils) input perusal filter" diff --git a/src/uu/mv/Cargo.toml b/src/uu/mv/Cargo.toml index 0be31ad72..83a10ef6b 100644 --- a/src/uu/mv/Cargo.toml +++ b/src/uu/mv/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_mv" -version = "0.0.22" +version = "0.0.23" authors = ["uutils developers"] license = "MIT" description = "mv ~ (uutils) move (rename) SOURCE to DESTINATION" diff --git a/src/uu/nice/Cargo.toml b/src/uu/nice/Cargo.toml index 465b7b765..6fab5cefc 100644 --- a/src/uu/nice/Cargo.toml +++ b/src/uu/nice/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_nice" -version = "0.0.22" +version = "0.0.23" authors = ["uutils developers"] license = "MIT" description = "nice ~ (uutils) run PROGRAM with modified scheduling priority" diff --git a/src/uu/nl/Cargo.toml b/src/uu/nl/Cargo.toml index 003e17913..56cb8d8e9 100644 --- a/src/uu/nl/Cargo.toml +++ b/src/uu/nl/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_nl" -version = "0.0.22" +version = "0.0.23" authors = ["uutils developers"] license = "MIT" description = "nl ~ (uutils) display input with added line numbers" diff --git a/src/uu/nohup/Cargo.toml b/src/uu/nohup/Cargo.toml index 41c98982a..24d8f659c 100644 --- a/src/uu/nohup/Cargo.toml +++ b/src/uu/nohup/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_nohup" -version = "0.0.22" +version = "0.0.23" authors = ["uutils developers"] license = "MIT" description = "nohup ~ (uutils) run COMMAND, ignoring hangup signals" diff --git a/src/uu/nproc/Cargo.toml b/src/uu/nproc/Cargo.toml index 0acc0e951..54bb1d83c 100644 --- a/src/uu/nproc/Cargo.toml +++ b/src/uu/nproc/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_nproc" -version = "0.0.22" +version = "0.0.23" authors = ["uutils developers"] license = "MIT" description = "nproc ~ (uutils) display the number of processing units available" diff --git a/src/uu/numfmt/Cargo.toml b/src/uu/numfmt/Cargo.toml index cf2f08646..79e6d5401 100644 --- a/src/uu/numfmt/Cargo.toml +++ b/src/uu/numfmt/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_numfmt" -version = "0.0.22" +version = "0.0.23" authors = ["uutils developers"] license = "MIT" description = "numfmt ~ (uutils) reformat NUMBER" diff --git a/src/uu/od/Cargo.toml b/src/uu/od/Cargo.toml index b7799e052..84c89ee86 100644 --- a/src/uu/od/Cargo.toml +++ b/src/uu/od/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_od" -version = "0.0.22" +version = "0.0.23" authors = ["uutils developers"] license = "MIT" description = "od ~ (uutils) display formatted representation of input" diff --git a/src/uu/paste/Cargo.toml b/src/uu/paste/Cargo.toml index dd030478c..68255c82d 100644 --- a/src/uu/paste/Cargo.toml +++ b/src/uu/paste/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_paste" -version = "0.0.22" +version = "0.0.23" authors = ["uutils developers"] license = "MIT" description = "paste ~ (uutils) merge lines from inputs" diff --git a/src/uu/pathchk/Cargo.toml b/src/uu/pathchk/Cargo.toml index 708ae2afb..dee85deae 100644 --- a/src/uu/pathchk/Cargo.toml +++ b/src/uu/pathchk/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_pathchk" -version = "0.0.22" +version = "0.0.23" authors = ["uutils developers"] license = "MIT" description = "pathchk ~ (uutils) diagnose invalid or non-portable PATHNAME" diff --git a/src/uu/pinky/Cargo.toml b/src/uu/pinky/Cargo.toml index c4739e7fc..f64cb7585 100644 --- a/src/uu/pinky/Cargo.toml +++ b/src/uu/pinky/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_pinky" -version = "0.0.22" +version = "0.0.23" authors = ["uutils developers"] license = "MIT" description = "pinky ~ (uutils) display user information" diff --git a/src/uu/pr/Cargo.toml b/src/uu/pr/Cargo.toml index 9a12e10f5..ab5459383 100644 --- a/src/uu/pr/Cargo.toml +++ b/src/uu/pr/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_pr" -version = "0.0.22" +version = "0.0.23" authors = ["uutils developers"] license = "MIT" description = "pr ~ (uutils) convert text files for printing" diff --git a/src/uu/printenv/Cargo.toml b/src/uu/printenv/Cargo.toml index 64435127f..a6d7c91d0 100644 --- a/src/uu/printenv/Cargo.toml +++ b/src/uu/printenv/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_printenv" -version = "0.0.22" +version = "0.0.23" authors = ["uutils developers"] license = "MIT" description = "printenv ~ (uutils) display value of environment VAR" diff --git a/src/uu/printf/Cargo.toml b/src/uu/printf/Cargo.toml index a4fd773da..1cb05ec09 100644 --- a/src/uu/printf/Cargo.toml +++ b/src/uu/printf/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_printf" -version = "0.0.22" +version = "0.0.23" authors = ["Nathan Ross", "uutils developers"] license = "MIT" description = "printf ~ (uutils) FORMAT and display ARGUMENTS" diff --git a/src/uu/ptx/Cargo.toml b/src/uu/ptx/Cargo.toml index ea241600b..4ea395dc5 100644 --- a/src/uu/ptx/Cargo.toml +++ b/src/uu/ptx/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_ptx" -version = "0.0.22" +version = "0.0.23" authors = ["uutils developers"] license = "MIT" description = "ptx ~ (uutils) display a permuted index of input" diff --git a/src/uu/pwd/Cargo.toml b/src/uu/pwd/Cargo.toml index 5f15245b5..8bd0ccece 100644 --- a/src/uu/pwd/Cargo.toml +++ b/src/uu/pwd/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_pwd" -version = "0.0.22" +version = "0.0.23" authors = ["uutils developers"] license = "MIT" description = "pwd ~ (uutils) display current working directory" diff --git a/src/uu/readlink/Cargo.toml b/src/uu/readlink/Cargo.toml index a696abdfa..4d1574c50 100644 --- a/src/uu/readlink/Cargo.toml +++ b/src/uu/readlink/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_readlink" -version = "0.0.22" +version = "0.0.23" authors = ["uutils developers"] license = "MIT" description = "readlink ~ (uutils) display resolved path of PATHNAME" diff --git a/src/uu/realpath/Cargo.toml b/src/uu/realpath/Cargo.toml index 6d2072753..8b21d6c7f 100644 --- a/src/uu/realpath/Cargo.toml +++ b/src/uu/realpath/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_realpath" -version = "0.0.22" +version = "0.0.23" authors = ["uutils developers"] license = "MIT" description = "realpath ~ (uutils) display resolved absolute path of PATHNAME" diff --git a/src/uu/rm/Cargo.toml b/src/uu/rm/Cargo.toml index a1b3999d4..34ada60e8 100644 --- a/src/uu/rm/Cargo.toml +++ b/src/uu/rm/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_rm" -version = "0.0.22" +version = "0.0.23" authors = ["uutils developers"] license = "MIT" description = "rm ~ (uutils) remove PATHNAME" diff --git a/src/uu/rmdir/Cargo.toml b/src/uu/rmdir/Cargo.toml index 8288309f2..f17db50b0 100644 --- a/src/uu/rmdir/Cargo.toml +++ b/src/uu/rmdir/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_rmdir" -version = "0.0.22" +version = "0.0.23" authors = ["uutils developers"] license = "MIT" description = "rmdir ~ (uutils) remove empty DIRECTORY" diff --git a/src/uu/runcon/Cargo.toml b/src/uu/runcon/Cargo.toml index 04d3aae71..72237ece9 100644 --- a/src/uu/runcon/Cargo.toml +++ b/src/uu/runcon/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_runcon" -version = "0.0.22" +version = "0.0.23" authors = ["uutils developers"] license = "MIT" description = "runcon ~ (uutils) run command with specified security context" diff --git a/src/uu/seq/Cargo.toml b/src/uu/seq/Cargo.toml index b8a5c084c..95f761696 100644 --- a/src/uu/seq/Cargo.toml +++ b/src/uu/seq/Cargo.toml @@ -1,7 +1,7 @@ # spell-checker:ignore bigdecimal [package] name = "uu_seq" -version = "0.0.22" +version = "0.0.23" authors = ["uutils developers"] license = "MIT" description = "seq ~ (uutils) display a sequence of numbers" diff --git a/src/uu/shred/Cargo.toml b/src/uu/shred/Cargo.toml index 0e7ad2ad3..8721cd624 100644 --- a/src/uu/shred/Cargo.toml +++ b/src/uu/shred/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_shred" -version = "0.0.22" +version = "0.0.23" authors = ["uutils developers"] license = "MIT" description = "shred ~ (uutils) hide former FILE contents with repeated overwrites" diff --git a/src/uu/shuf/Cargo.toml b/src/uu/shuf/Cargo.toml index 6a86c6d98..2bbf2af14 100644 --- a/src/uu/shuf/Cargo.toml +++ b/src/uu/shuf/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_shuf" -version = "0.0.22" +version = "0.0.23" authors = ["uutils developers"] license = "MIT" description = "shuf ~ (uutils) display random permutations of input lines" diff --git a/src/uu/sleep/Cargo.toml b/src/uu/sleep/Cargo.toml index 6ca686c78..d3d1fe61e 100644 --- a/src/uu/sleep/Cargo.toml +++ b/src/uu/sleep/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_sleep" -version = "0.0.22" +version = "0.0.23" authors = ["uutils developers"] license = "MIT" description = "sleep ~ (uutils) pause for DURATION" diff --git a/src/uu/sort/Cargo.toml b/src/uu/sort/Cargo.toml index 0cbafc5cf..8c1c03dc6 100644 --- a/src/uu/sort/Cargo.toml +++ b/src/uu/sort/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_sort" -version = "0.0.22" +version = "0.0.23" authors = ["uutils developers"] license = "MIT" description = "sort ~ (uutils) sort input lines" diff --git a/src/uu/split/Cargo.toml b/src/uu/split/Cargo.toml index c5a348473..6253dbf2b 100644 --- a/src/uu/split/Cargo.toml +++ b/src/uu/split/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_split" -version = "0.0.22" +version = "0.0.23" authors = ["uutils developers"] license = "MIT" description = "split ~ (uutils) split input into output files" diff --git a/src/uu/stat/Cargo.toml b/src/uu/stat/Cargo.toml index 2974562f3..5ed91a88c 100644 --- a/src/uu/stat/Cargo.toml +++ b/src/uu/stat/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_stat" -version = "0.0.22" +version = "0.0.23" authors = ["uutils developers"] license = "MIT" description = "stat ~ (uutils) display FILE status" diff --git a/src/uu/stdbuf/Cargo.toml b/src/uu/stdbuf/Cargo.toml index 8168733af..ed7a0e2e1 100644 --- a/src/uu/stdbuf/Cargo.toml +++ b/src/uu/stdbuf/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_stdbuf" -version = "0.0.22" +version = "0.0.23" authors = ["uutils developers"] license = "MIT" description = "stdbuf ~ (uutils) run COMMAND with modified standard stream buffering" @@ -20,7 +20,7 @@ tempfile = { workspace = true } uucore = { workspace = true } [build-dependencies] -libstdbuf = { version = "0.0.22", package = "uu_stdbuf_libstdbuf", path = "src/libstdbuf" } +libstdbuf = { version = "0.0.23", package = "uu_stdbuf_libstdbuf", path = "src/libstdbuf" } [[bin]] name = "stdbuf" diff --git a/src/uu/stdbuf/src/libstdbuf/Cargo.toml b/src/uu/stdbuf/src/libstdbuf/Cargo.toml index 5fdfa27e0..be97c47ae 100644 --- a/src/uu/stdbuf/src/libstdbuf/Cargo.toml +++ b/src/uu/stdbuf/src/libstdbuf/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_stdbuf_libstdbuf" -version = "0.0.22" +version = "0.0.23" authors = ["uutils developers"] license = "MIT" description = "stdbuf/libstdbuf ~ (uutils); dynamic library required for stdbuf" diff --git a/src/uu/stty/Cargo.toml b/src/uu/stty/Cargo.toml index 965de5cd8..a353c6603 100644 --- a/src/uu/stty/Cargo.toml +++ b/src/uu/stty/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_stty" -version = "0.0.22" +version = "0.0.23" authors = ["uutils developers"] license = "MIT" description = "stty ~ (uutils) print or change terminal characteristics" diff --git a/src/uu/sum/Cargo.toml b/src/uu/sum/Cargo.toml index 58fa7a08d..04be0fe72 100644 --- a/src/uu/sum/Cargo.toml +++ b/src/uu/sum/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_sum" -version = "0.0.22" +version = "0.0.23" authors = ["uutils developers"] license = "MIT" description = "sum ~ (uutils) display checksum and block counts for input" diff --git a/src/uu/sync/Cargo.toml b/src/uu/sync/Cargo.toml index 2367561f8..404811524 100644 --- a/src/uu/sync/Cargo.toml +++ b/src/uu/sync/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_sync" -version = "0.0.22" +version = "0.0.23" authors = ["uutils developers"] license = "MIT" description = "sync ~ (uutils) synchronize cache writes to storage" diff --git a/src/uu/tac/Cargo.toml b/src/uu/tac/Cargo.toml index 1716a6d03..164972c5f 100644 --- a/src/uu/tac/Cargo.toml +++ b/src/uu/tac/Cargo.toml @@ -2,7 +2,7 @@ [package] name = "uu_tac" -version = "0.0.22" +version = "0.0.23" authors = ["uutils developers"] license = "MIT" description = "tac ~ (uutils) concatenate and display input lines in reverse order" diff --git a/src/uu/tail/Cargo.toml b/src/uu/tail/Cargo.toml index 66775c8d9..636480e08 100644 --- a/src/uu/tail/Cargo.toml +++ b/src/uu/tail/Cargo.toml @@ -1,7 +1,7 @@ # spell-checker:ignore (libs) kqueue fundu [package] name = "uu_tail" -version = "0.0.22" +version = "0.0.23" authors = ["uutils developers"] license = "MIT" description = "tail ~ (uutils) display the last lines of input" diff --git a/src/uu/tee/Cargo.toml b/src/uu/tee/Cargo.toml index 787c0cb32..441b891a4 100644 --- a/src/uu/tee/Cargo.toml +++ b/src/uu/tee/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_tee" -version = "0.0.22" +version = "0.0.23" authors = ["uutils developers"] license = "MIT" description = "tee ~ (uutils) display input and copy to FILE" diff --git a/src/uu/test/Cargo.toml b/src/uu/test/Cargo.toml index 696fa3662..bc2751186 100644 --- a/src/uu/test/Cargo.toml +++ b/src/uu/test/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_test" -version = "0.0.22" +version = "0.0.23" authors = ["uutils developers"] license = "MIT" description = "test ~ (uutils) evaluate comparison and file type expressions" diff --git a/src/uu/timeout/Cargo.toml b/src/uu/timeout/Cargo.toml index b6cb700a4..4f6c062bb 100644 --- a/src/uu/timeout/Cargo.toml +++ b/src/uu/timeout/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_timeout" -version = "0.0.22" +version = "0.0.23" authors = ["uutils developers"] license = "MIT" description = "timeout ~ (uutils) run COMMAND with a DURATION time limit" diff --git a/src/uu/touch/Cargo.toml b/src/uu/touch/Cargo.toml index b44e8cf69..d67cd1f8a 100644 --- a/src/uu/touch/Cargo.toml +++ b/src/uu/touch/Cargo.toml @@ -1,7 +1,7 @@ # spell-checker:ignore datetime [package] name = "uu_touch" -version = "0.0.22" +version = "0.0.23" authors = ["uutils developers"] license = "MIT" description = "touch ~ (uutils) change FILE timestamps" diff --git a/src/uu/tr/Cargo.toml b/src/uu/tr/Cargo.toml index c7e7bd606..a07679dd1 100644 --- a/src/uu/tr/Cargo.toml +++ b/src/uu/tr/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_tr" -version = "0.0.22" +version = "0.0.23" authors = ["uutils developers"] license = "MIT" description = "tr ~ (uutils) translate characters within input and display" diff --git a/src/uu/true/Cargo.toml b/src/uu/true/Cargo.toml index ec6c5d2f9..8c4080724 100644 --- a/src/uu/true/Cargo.toml +++ b/src/uu/true/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_true" -version = "0.0.22" +version = "0.0.23" authors = ["uutils developers"] license = "MIT" description = "true ~ (uutils) do nothing and succeed" diff --git a/src/uu/truncate/Cargo.toml b/src/uu/truncate/Cargo.toml index a9cc179a6..254a004e7 100644 --- a/src/uu/truncate/Cargo.toml +++ b/src/uu/truncate/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_truncate" -version = "0.0.22" +version = "0.0.23" authors = ["uutils developers"] license = "MIT" description = "truncate ~ (uutils) truncate (or extend) FILE to SIZE" diff --git a/src/uu/tsort/Cargo.toml b/src/uu/tsort/Cargo.toml index f72aeeda0..567d41e0b 100644 --- a/src/uu/tsort/Cargo.toml +++ b/src/uu/tsort/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_tsort" -version = "0.0.22" +version = "0.0.23" authors = ["uutils developers"] license = "MIT" description = "tsort ~ (uutils) topologically sort input (partially ordered) pairs" diff --git a/src/uu/tty/Cargo.toml b/src/uu/tty/Cargo.toml index 66d4b42c7..95734e6c9 100644 --- a/src/uu/tty/Cargo.toml +++ b/src/uu/tty/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_tty" -version = "0.0.22" +version = "0.0.23" authors = ["uutils developers"] license = "MIT" description = "tty ~ (uutils) display the name of the terminal connected to standard input" diff --git a/src/uu/uname/Cargo.toml b/src/uu/uname/Cargo.toml index 10363b63c..cfd459c5b 100644 --- a/src/uu/uname/Cargo.toml +++ b/src/uu/uname/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_uname" -version = "0.0.22" +version = "0.0.23" authors = ["uutils developers"] license = "MIT" description = "uname ~ (uutils) display system information" diff --git a/src/uu/unexpand/Cargo.toml b/src/uu/unexpand/Cargo.toml index bab5b4e91..459a67b90 100644 --- a/src/uu/unexpand/Cargo.toml +++ b/src/uu/unexpand/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_unexpand" -version = "0.0.22" +version = "0.0.23" authors = ["uutils developers"] license = "MIT" description = "unexpand ~ (uutils) convert input spaces to tabs" diff --git a/src/uu/uniq/Cargo.toml b/src/uu/uniq/Cargo.toml index 2fb0552ce..efe5ca8ee 100644 --- a/src/uu/uniq/Cargo.toml +++ b/src/uu/uniq/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_uniq" -version = "0.0.22" +version = "0.0.23" authors = ["uutils developers"] license = "MIT" description = "uniq ~ (uutils) filter identical adjacent lines from input" diff --git a/src/uu/unlink/Cargo.toml b/src/uu/unlink/Cargo.toml index aae56bf0b..c6bfcd66b 100644 --- a/src/uu/unlink/Cargo.toml +++ b/src/uu/unlink/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_unlink" -version = "0.0.22" +version = "0.0.23" authors = ["uutils developers"] license = "MIT" description = "unlink ~ (uutils) remove a (file system) link to FILE" diff --git a/src/uu/uptime/Cargo.toml b/src/uu/uptime/Cargo.toml index f5fa083f9..899a30dfa 100644 --- a/src/uu/uptime/Cargo.toml +++ b/src/uu/uptime/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_uptime" -version = "0.0.22" +version = "0.0.23" authors = ["uutils developers"] license = "MIT" description = "uptime ~ (uutils) display dynamic system information" diff --git a/src/uu/users/Cargo.toml b/src/uu/users/Cargo.toml index d894ff4ba..030c7d625 100644 --- a/src/uu/users/Cargo.toml +++ b/src/uu/users/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_users" -version = "0.0.22" +version = "0.0.23" authors = ["uutils developers"] license = "MIT" description = "users ~ (uutils) display names of currently logged-in users" diff --git a/src/uu/vdir/Cargo.toml b/src/uu/vdir/Cargo.toml index b63f8c9ea..d14e10d95 100644 --- a/src/uu/vdir/Cargo.toml +++ b/src/uu/vdir/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_vdir" -version = "0.0.22" +version = "0.0.23" authors = ["uutils developers"] license = "MIT" description = "shortcut to ls -l -b" diff --git a/src/uu/wc/Cargo.toml b/src/uu/wc/Cargo.toml index 6a233c1ad..8471472f8 100644 --- a/src/uu/wc/Cargo.toml +++ b/src/uu/wc/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_wc" -version = "0.0.22" +version = "0.0.23" authors = ["uutils developers"] license = "MIT" description = "wc ~ (uutils) display newline, word, and byte counts for input" diff --git a/src/uu/who/Cargo.toml b/src/uu/who/Cargo.toml index fdfc05897..249d5a8ee 100644 --- a/src/uu/who/Cargo.toml +++ b/src/uu/who/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_who" -version = "0.0.22" +version = "0.0.23" authors = ["uutils developers"] license = "MIT" description = "who ~ (uutils) display information about currently logged-in users" diff --git a/src/uu/whoami/Cargo.toml b/src/uu/whoami/Cargo.toml index 59d011209..533656833 100644 --- a/src/uu/whoami/Cargo.toml +++ b/src/uu/whoami/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_whoami" -version = "0.0.22" +version = "0.0.23" authors = ["uutils developers"] license = "MIT" description = "whoami ~ (uutils) display user name of current effective user ID" diff --git a/src/uu/yes/Cargo.toml b/src/uu/yes/Cargo.toml index c36c2b189..dd562cab6 100644 --- a/src/uu/yes/Cargo.toml +++ b/src/uu/yes/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_yes" -version = "0.0.22" +version = "0.0.23" authors = ["uutils developers"] license = "MIT" description = "yes ~ (uutils) repeatedly display a line with STRING (or 'y')" diff --git a/src/uucore/Cargo.toml b/src/uucore/Cargo.toml index fb0b20338..370c8a386 100644 --- a/src/uucore/Cargo.toml +++ b/src/uucore/Cargo.toml @@ -2,7 +2,7 @@ [package] name = "uucore" -version = "0.0.22" +version = "0.0.23" authors = ["uutils developers"] license = "MIT" description = "uutils ~ 'core' uutils code library (cross-platform)" diff --git a/src/uucore_procs/Cargo.toml b/src/uucore_procs/Cargo.toml index 55d880961..f6b92fe63 100644 --- a/src/uucore_procs/Cargo.toml +++ b/src/uucore_procs/Cargo.toml @@ -1,7 +1,7 @@ # spell-checker:ignore uuhelp [package] name = "uucore_procs" -version = "0.0.22" +version = "0.0.23" authors = ["Roy Ivy III "] license = "MIT" description = "uutils ~ 'uucore' proc-macros" @@ -19,4 +19,4 @@ proc-macro = true [dependencies] proc-macro2 = "1.0" quote = "1.0" -uuhelp_parser = { path = "../uuhelp_parser", version = "0.0.22" } +uuhelp_parser = { path = "../uuhelp_parser", version = "0.0.23" } diff --git a/src/uuhelp_parser/Cargo.toml b/src/uuhelp_parser/Cargo.toml index 007f1ebb2..c07bce631 100644 --- a/src/uuhelp_parser/Cargo.toml +++ b/src/uuhelp_parser/Cargo.toml @@ -1,7 +1,7 @@ # spell-checker:ignore uuhelp [package] name = "uuhelp_parser" -version = "0.0.22" +version = "0.0.23" edition = "2021" license = "MIT" description = "A collection of functions to parse the markdown code of help files" diff --git a/util/update-version.sh b/util/update-version.sh index bab1c4e0a..244a7a7b2 100755 --- a/util/update-version.sh +++ b/util/update-version.sh @@ -17,8 +17,8 @@ # 10) Create the release on github https://github.com/uutils/coreutils/releases/new # 11) Make sure we have good release notes -FROM="0.0.21" -TO="0.0.22" +FROM="0.0.22" +TO="0.0.23" PROGS=$(ls -1d src/uu/*/Cargo.toml src/uu/stdbuf/src/libstdbuf/Cargo.toml src/uucore/Cargo.toml Cargo.toml) From c4580df2a4cbb4f1cb4e3124779bde43d7b027f8 Mon Sep 17 00:00:00 2001 From: cswn Date: Tue, 14 Nov 2023 18:07:35 +0100 Subject: [PATCH 58/73] split: remove crash macro --- src/uu/split/src/platform/unix.rs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/uu/split/src/platform/unix.rs b/src/uu/split/src/platform/unix.rs index f4adb8188..c2bf7216b 100644 --- a/src/uu/split/src/platform/unix.rs +++ b/src/uu/split/src/platform/unix.rs @@ -7,9 +7,10 @@ use std::io::Write; use std::io::{BufWriter, Error, ErrorKind, Result}; use std::path::Path; use std::process::{Child, Command, Stdio}; -use uucore::crash; +use uucore::error::USimpleError; use uucore::fs; use uucore::fs::FileInformation; +use uucore::show; /// A writer that writes to a shell_process' stdin /// @@ -101,10 +102,13 @@ impl Drop for FilterWriter { .expect("Couldn't wait for child process"); if let Some(return_code) = exit_status.code() { if return_code != 0 { - crash!(1, "Shell process returned {}", return_code); + show!(USimpleError::new( + 1, + format!("Shell process returned {}", return_code) + )); } } else { - crash!(1, "Shell process terminated by signal") + show!(USimpleError::new(1, "Shell process terminated by signal")); } } } From e2e5ec60cde2a407a2c8a467c1d0057f312e2276 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 14 Nov 2023 20:05:36 +0000 Subject: [PATCH 59/73] chore(deps): update rust crate itertools to 0.12.0 --- Cargo.lock | 4 ++-- Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d83adfee6..c89c87df4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1122,9 +1122,9 @@ dependencies = [ [[package]] name = "itertools" -version = "0.11.0" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" +checksum = "25db6b064527c5d482d0423354fcd07a89a2dfe07b67892e62411946db7f07b0" dependencies = [ "either", ] diff --git a/Cargo.toml b/Cargo.toml index f313e2b03..2f3af2c83 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -285,7 +285,7 @@ gcd = "2.3" glob = "0.3.1" half = "2.3" indicatif = "0.17" -itertools = "0.11.0" +itertools = "0.12.0" libc = "0.2.150" lscolors = { version = "0.15.0", default-features = false, features = [ "nu-ansi-term", From 6446ef294c1ec0dc9ab9450d659e51dc4b30526f Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Tue, 14 Nov 2023 20:44:19 +0100 Subject: [PATCH 60/73] publishing: check if the current version is already there or not This can happen when a publishing step failed --- util/publish.sh | 32 ++++++++++++++++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/util/publish.sh b/util/publish.sh index 71830f1f9..7207ba7fb 100755 --- a/util/publish.sh +++ b/util/publish.sh @@ -5,6 +5,22 @@ if test "$1" != "--do-it"; then ARG="--dry-run --allow-dirty" fi +# Function to check if the crate is already published +is_already_published() { + local crate_name=$1 + local crate_version=$2 + + # Use the crates.io API to get the latest version of the crate + local latest_published_version + latest_published_version=$(curl -s https://crates.io/api/v1/crates/$crate_name | jq -r '.crate.max_version') + + if [ "$latest_published_version" = "$crate_version" ]; then + return 0 + else + return 1 + fi +} + # Figure out any dependencies between the util via Cargo.toml # We store this as edges in a graph with each line: # [dependent] [dependency] @@ -35,12 +51,19 @@ TOTAL_ORDER=$(echo -e $PARTIAL_ORDER | tsort | tac) # Remove the ROOT node from the start TOTAL_ORDER=${TOTAL_ORDER#ROOT} +CRATE_VERSION=$(grep '^version' Cargo.toml | head -n1 | cut -d '"' -f2) + set -e for dir in src/uuhelp_parser/ src/uucore_procs/ src/uucore/ src/uu/stdbuf/src/libstdbuf/; do ( cd "$dir" + CRATE_NAME=$(grep '^name =' "Cargo.toml" | head -n1 | cut -d '"' -f2) #shellcheck disable=SC2086 - cargo publish $ARG + if ! is_already_published "$CRATE_NAME" "$CRATE_VERSION"; then + cargo publish $ARG + else + echo "Skip: $CRATE_NAME $CRATE_VERSION already published" + fi ) sleep 2s done @@ -48,8 +71,13 @@ done for p in $TOTAL_ORDER; do ( cd "src/uu/$p" + CRATE_NAME=$(grep '^name =' "Cargo.toml" | head -n1 | cut -d '"' -f2) #shellcheck disable=SC2086 - cargo publish $ARG + if ! is_already_published "$CRATE_NAME" "$CRATE_VERSION"; then + cargo publish $ARG + else + echo "Skip: $CRATE_NAME $CRATE_VERSION already published" + fi ) done From 3f86bc59de6f8c6dbb73faaa81d3ad3fa40c01e7 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Wed, 15 Nov 2023 08:31:47 +0100 Subject: [PATCH 61/73] add missing features to uucore --- src/uu/dd/Cargo.toml | 2 +- src/uu/printf/Cargo.toml | 2 +- src/uu/seq/Cargo.toml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/uu/dd/Cargo.toml b/src/uu/dd/Cargo.toml index 7db05b422..d654d8297 100644 --- a/src/uu/dd/Cargo.toml +++ b/src/uu/dd/Cargo.toml @@ -18,7 +18,7 @@ path = "src/dd.rs" clap = { workspace = true } gcd = { workspace = true } libc = { workspace = true } -uucore = { workspace = true, features = ["memo"] } +uucore = { workspace = true, features = ["memo", "quoting-style"] } [target.'cfg(any(target_os = "linux"))'.dependencies] nix = { workspace = true, features = ["fs"] } diff --git a/src/uu/printf/Cargo.toml b/src/uu/printf/Cargo.toml index 1cb05ec09..f36eff35e 100644 --- a/src/uu/printf/Cargo.toml +++ b/src/uu/printf/Cargo.toml @@ -16,7 +16,7 @@ path = "src/printf.rs" [dependencies] clap = { workspace = true } -uucore = { workspace = true, features = ["memo"] } +uucore = { workspace = true, features = ["memo", "quoting-style"] } [[bin]] name = "printf" diff --git a/src/uu/seq/Cargo.toml b/src/uu/seq/Cargo.toml index 95f761696..d4e7cd316 100644 --- a/src/uu/seq/Cargo.toml +++ b/src/uu/seq/Cargo.toml @@ -20,7 +20,7 @@ bigdecimal = { workspace = true } clap = { workspace = true } num-bigint = { workspace = true } num-traits = { workspace = true } -uucore = { workspace = true, features = ["memo"] } +uucore = { workspace = true, features = ["memo", "quoting-style"] } [[bin]] name = "seq" From 3f177ef97fc4e6f7a17f7565761732d89847fe72 Mon Sep 17 00:00:00 2001 From: "Y.D.X" <73375426+YDX-2147483647@users.noreply.github.com> Date: Wed, 15 Nov 2023 19:09:46 +0800 Subject: [PATCH 62/73] doc: Fix the markdown highlighting syntax MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit There should be a new line after `[!WARNING]`, according to [community ยท Discussion #16925](https://github.com/orgs/community/discussions/16925). --- CONTRIBUTING.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 255ed2c53..b10d3d114 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -14,7 +14,8 @@ check out these documents: Now follows a very important warning: -> [!WARNING] uutils is original code and cannot contain any code from GNU or +> [!WARNING] +> uutils is original code and cannot contain any code from GNU or > other implementations. This means that **we cannot accept any changes based on > the GNU source code**. To make sure that cannot happen, **you cannot link to > the GNU source code** either. From 94f6702ba52a875d251e869eba55583e61da9ad1 Mon Sep 17 00:00:00 2001 From: cswn Date: Wed, 15 Nov 2023 13:20:22 +0100 Subject: [PATCH 63/73] join: remove crash! macro --- src/uu/join/src/join.rs | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/src/uu/join/src/join.rs b/src/uu/join/src/join.rs index a48ba3657..3f2172da3 100644 --- a/src/uu/join/src/join.rs +++ b/src/uu/join/src/join.rs @@ -21,7 +21,7 @@ use std::os::unix::ffi::OsStrExt; use uucore::display::Quotable; use uucore::error::{set_exit_code, UError, UResult, USimpleError}; use uucore::line_ending::LineEnding; -use uucore::{crash, crash_if_err, format_usage, help_about, help_usage}; +use uucore::{crash_if_err, format_usage, help_about, help_usage}; const ABOUT: &str = help_about!("join.md"); const USAGE: &str = help_usage!("join.md"); @@ -334,17 +334,23 @@ impl<'a> State<'a> { key: usize, line_ending: LineEnding, print_unpaired: bool, - ) -> State<'a> { + ) -> Result, JoinError> { let f = if name == "-" { Box::new(stdin.lock()) as Box } else { match File::open(name) { Ok(file) => Box::new(BufReader::new(file)) as Box, - Err(err) => crash!(1, "{}: {}", name.maybe_quote(), err), + Err(err) => { + return Err(JoinError::UnorderedInput(format!( + "{}: {}", + name.maybe_quote(), + err + ))); + } } }; - State { + Ok(State { key, file_name: name, file_num, @@ -355,7 +361,7 @@ impl<'a> State<'a> { line_num: 0, has_failed: false, has_unpaired: false, - } + }) } /// Skip the current unpaired line. @@ -847,7 +853,7 @@ fn exec(file1: &str, file2: &str, settings: Settings) -> Result<(), JoinError> { settings.key1, settings.line_ending, settings.print_unpaired1, - ); + )?; let mut state2 = State::new( FileNum::File2, @@ -856,7 +862,7 @@ fn exec(file1: &str, file2: &str, settings: Settings) -> Result<(), JoinError> { settings.key2, settings.line_ending, settings.print_unpaired2, - ); + )?; let input = Input::new( settings.separator, From 5dff5f2f736c44c576adce2dbcbe8275d52ebbd2 Mon Sep 17 00:00:00 2001 From: cswn Date: Wed, 15 Nov 2023 13:52:01 +0100 Subject: [PATCH 64/73] join: rename f variable to file_buf --- src/uu/join/src/join.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/uu/join/src/join.rs b/src/uu/join/src/join.rs index 3f2172da3..c8008c91c 100644 --- a/src/uu/join/src/join.rs +++ b/src/uu/join/src/join.rs @@ -335,7 +335,7 @@ impl<'a> State<'a> { line_ending: LineEnding, print_unpaired: bool, ) -> Result, JoinError> { - let f = if name == "-" { + let file_buf = if name == "-" { Box::new(stdin.lock()) as Box } else { match File::open(name) { @@ -355,7 +355,7 @@ impl<'a> State<'a> { file_name: name, file_num, print_unpaired, - lines: f.split(line_ending as u8), + lines: file_buf.split(line_ending as u8), max_len: 1, seq: Vec::new(), line_num: 0, From b3eae16faddf03e5ce83047f244a510932970565 Mon Sep 17 00:00:00 2001 From: ALXD Date: Wed, 15 Nov 2023 17:40:54 +0100 Subject: [PATCH 65/73] printf: intf: change warning and exit code --- .../features/tokenize/num_format/formatters/intf.rs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/uucore/src/lib/features/tokenize/num_format/formatters/intf.rs b/src/uucore/src/lib/features/tokenize/num_format/formatters/intf.rs index 11070113c..767c0c4bb 100644 --- a/src/uucore/src/lib/features/tokenize/num_format/formatters/intf.rs +++ b/src/uucore/src/lib/features/tokenize/num_format/formatters/intf.rs @@ -8,10 +8,11 @@ //! formatter for unsigned and signed int subs //! unsigned int: %X %x (hex u64) %o (octal u64) %u (base ten u64) //! signed int: %i %d (both base ten i64) +use crate::error::set_exit_code; +use crate::features::tokenize::num_format::num_format::warn_expected_numeric; + use super::super::format_field::FormatField; -use super::super::formatter::{ - get_it_at, warn_incomplete_conv, Base, FormatPrimitive, Formatter, InitialPrefix, -}; +use super::super::formatter::{get_it_at, Base, FormatPrimitive, Formatter, InitialPrefix}; use std::i64; use std::u64; @@ -112,7 +113,8 @@ impl Intf { } } _ => { - warn_incomplete_conv(str_in); + warn_expected_numeric(str_in); + set_exit_code(1); break; } } From 3cdb0966ae1b86724be8f7b6db2b6f41472169fc Mon Sep 17 00:00:00 2001 From: ALXD Date: Wed, 15 Nov 2023 17:46:58 +0100 Subject: [PATCH 66/73] printf: add a test for %x with invalid value --- tests/by-util/test_printf.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tests/by-util/test_printf.rs b/tests/by-util/test_printf.rs index a297dbf68..ab3505a32 100644 --- a/tests/by-util/test_printf.rs +++ b/tests/by-util/test_printf.rs @@ -258,6 +258,14 @@ fn sub_num_hex_upper() { .stdout_only("thirty in hex is 1E"); } +#[test] +fn sub_num_hex_non_numerical() { + new_ucmd!() + .args(&["parameters need to be numbers %X", "%194"]) + .fails() + .code_is(1); +} + #[test] fn sub_num_float() { new_ucmd!() From 212991cd53cba8be58e877ab5b17ea86eb46f20e Mon Sep 17 00:00:00 2001 From: "Y.D.X." <73375426+YDX-2147483647@users.noreply.github.com> Date: Thu, 16 Nov 2023 12:26:40 +0800 Subject: [PATCH 67/73] doc: Fix a broken link MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `/build.md` is absolute, so the link in https://uutils.github.io/coreutils/book/installation.html turns out to be https://uutils.github.io/build.html instead of https://uutils.github.io/coreutils/book/build.html. Reference: [Links ยท Markdown - mdBook Documentation](https://rust-lang.github.io/mdBook/format/markdown.html#links) --- docs/src/installation.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/installation.md b/docs/src/installation.md index da124ead9..54b1e23f3 100644 --- a/docs/src/installation.md +++ b/docs/src/installation.md @@ -6,7 +6,7 @@ This is a list of uutils packages in various distributions and package managers. Note that these are packaged by third-parties and the packages might contain patches. -You can also [build uutils from source](/build.md). +You can also [build uutils from source](build.md). From a064c886566f810c3e58b1b1153762e772e35567 Mon Sep 17 00:00:00 2001 From: cswn Date: Thu, 16 Nov 2023 09:35:32 +0100 Subject: [PATCH 68/73] join: replace match with JoinError with map_err_context --- src/uu/join/src/join.rs | 34 ++++++++-------------------------- 1 file changed, 8 insertions(+), 26 deletions(-) diff --git a/src/uu/join/src/join.rs b/src/uu/join/src/join.rs index c8008c91c..966113887 100644 --- a/src/uu/join/src/join.rs +++ b/src/uu/join/src/join.rs @@ -19,7 +19,7 @@ use std::num::IntErrorKind; #[cfg(unix)] use std::os::unix::ffi::OsStrExt; use uucore::display::Quotable; -use uucore::error::{set_exit_code, UError, UResult, USimpleError}; +use uucore::error::{set_exit_code, FromIo, UError, UResult, USimpleError}; use uucore::line_ending::LineEnding; use uucore::{crash_if_err, format_usage, help_about, help_usage}; @@ -334,20 +334,12 @@ impl<'a> State<'a> { key: usize, line_ending: LineEnding, print_unpaired: bool, - ) -> Result, JoinError> { + ) -> UResult> { let file_buf = if name == "-" { Box::new(stdin.lock()) as Box } else { - match File::open(name) { - Ok(file) => Box::new(BufReader::new(file)) as Box, - Err(err) => { - return Err(JoinError::UnorderedInput(format!( - "{}: {}", - name.maybe_quote(), - err - ))); - } - } + let file = File::open(name).map_err_context(|| format!("{}", name.maybe_quote()))?; + Box::new(BufReader::new(file)) as Box }; Ok(State { @@ -365,12 +357,7 @@ impl<'a> State<'a> { } /// Skip the current unpaired line. - fn skip_line( - &mut self, - writer: &mut impl Write, - input: &Input, - repr: &Repr, - ) -> Result<(), JoinError> { + fn skip_line(&mut self, writer: &mut impl Write, input: &Input, repr: &Repr) -> UResult<()> { if self.print_unpaired { self.print_first_line(writer, repr)?; } @@ -381,7 +368,7 @@ impl<'a> State<'a> { /// Keep reading line sequence until the key does not change, return /// the first line whose key differs. - fn extend(&mut self, input: &Input) -> Result, JoinError> { + fn extend(&mut self, input: &Input) -> UResult> { while let Some(line) = self.next_line(input)? { let diff = input.compare(self.get_current_key(), line.get_field(self.key)); @@ -490,12 +477,7 @@ impl<'a> State<'a> { 0 } - fn finalize( - &mut self, - writer: &mut impl Write, - input: &Input, - repr: &Repr, - ) -> Result<(), JoinError> { + fn finalize(&mut self, writer: &mut impl Write, input: &Input, repr: &Repr) -> UResult<()> { if self.has_line() { if self.print_unpaired { self.print_first_line(writer, repr)?; @@ -843,7 +825,7 @@ FILENUM is 1 or 2, corresponding to FILE1 or FILE2", ) } -fn exec(file1: &str, file2: &str, settings: Settings) -> Result<(), JoinError> { +fn exec(file1: &str, file2: &str, settings: Settings) -> UResult<()> { let stdin = stdin(); let mut state1 = State::new( From 7ff4cb3f4e236724d8bdf0d3a83258cd5daeb228 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Thu, 16 Nov 2023 10:40:31 +0100 Subject: [PATCH 69/73] update of the license file to make it generic (#5545) --- LICENSE | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LICENSE b/LICENSE index 49fdbd4cf..21bd44404 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) Jordi Boggiano and many others +Copyright (c) uutils developers Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in From 65dc70b55396c3a00b70143fc84cc7d96cc25539 Mon Sep 17 00:00:00 2001 From: cswn Date: Thu, 16 Nov 2023 12:02:39 +0100 Subject: [PATCH 70/73] join: remove match in uumain and return exec result --- src/uu/join/src/join.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/uu/join/src/join.rs b/src/uu/join/src/join.rs index 966113887..423af983e 100644 --- a/src/uu/join/src/join.rs +++ b/src/uu/join/src/join.rs @@ -701,10 +701,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { return Err(USimpleError::new(1, "both files cannot be standard input")); } - match exec(file1, file2, settings) { - Ok(_) => Ok(()), - Err(e) => Err(USimpleError::new(1, format!("{e}"))), - } + exec(file1, file2, settings) } pub fn uu_app() -> Command { From 2f9fcf73faad9d60db6f08c2e9ecd57fa845b0bd Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Thu, 16 Nov 2023 16:02:38 +0100 Subject: [PATCH 71/73] clippy: fix warnings introduced by Rust 1.74 --- src/uu/more/src/more.rs | 2 +- tests/by-util/test_dd.rs | 4 ++-- tests/by-util/test_ls.rs | 6 +++--- tests/by-util/test_users.rs | 6 ++---- 4 files changed, 8 insertions(+), 10 deletions(-) diff --git a/src/uu/more/src/more.rs b/src/uu/more/src/more.rs index 02ed0feea..b21b2ab1f 100644 --- a/src/uu/more/src/more.rs +++ b/src/uu/more/src/more.rs @@ -88,7 +88,7 @@ impl Options { #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { let args = args.collect_lossy(); - let matches = match uu_app().try_get_matches_from(&args) { + let matches = match uu_app().try_get_matches_from(args) { Ok(m) => m, Err(e) => return Err(e.into()), }; diff --git a/tests/by-util/test_dd.rs b/tests/by-util/test_dd.rs index f560e3526..d5ac8dc80 100644 --- a/tests/by-util/test_dd.rs +++ b/tests/by-util/test_dd.rs @@ -1470,7 +1470,7 @@ fn test_seek_output_fifo() { .args(&["count=0", "seek=1", "of=fifo", "status=noxfer"]) .run_no_wait(); - std::fs::write(at.plus("fifo"), &vec![0; 512]).unwrap(); + std::fs::write(at.plus("fifo"), vec![0; 512]).unwrap(); child .wait() @@ -1492,7 +1492,7 @@ fn test_skip_input_fifo() { .args(&["count=0", "skip=1", "if=fifo", "status=noxfer"]) .run_no_wait(); - std::fs::write(at.plus("fifo"), &vec![0; 512]).unwrap(); + std::fs::write(at.plus("fifo"), vec![0; 512]).unwrap(); child .wait() diff --git a/tests/by-util/test_ls.rs b/tests/by-util/test_ls.rs index cdd0292e1..07ea8c9cd 100644 --- a/tests/by-util/test_ls.rs +++ b/tests/by-util/test_ls.rs @@ -994,9 +994,9 @@ fn test_ls_long() { fn test_ls_long_format() { let scene = TestScenario::new(util_name!()); let at = &scene.fixtures; - at.mkdir(&at.plus_as_string("test-long-dir")); + at.mkdir(at.plus_as_string("test-long-dir")); at.touch(at.plus_as_string("test-long-dir/test-long-file")); - at.mkdir(&at.plus_as_string("test-long-dir/test-long-dir")); + at.mkdir(at.plus_as_string("test-long-dir/test-long-dir")); for arg in LONG_ARGS { // Assuming sane username do not have spaces within them. @@ -1971,7 +1971,7 @@ fn test_ls_color() { .join("nested_dir") .to_string_lossy() .to_string(); - at.mkdir(&nested_dir); + at.mkdir(nested_dir); at.mkdir("z"); let nested_file = Path::new("a") .join("nested_file") diff --git a/tests/by-util/test_users.rs b/tests/by-util/test_users.rs index 766378a9d..3d87aa9d0 100644 --- a/tests/by-util/test_users.rs +++ b/tests/by-util/test_users.rs @@ -21,11 +21,9 @@ fn test_users_check_name() { #[cfg(target_os = "linux")] let util_name = util_name!(); #[cfg(target_vendor = "apple")] - let util_name = format!("g{}", util_name!()); + let util_name = &format!("g{}", util_name!()); - // note: clippy::needless_borrow *false positive* - #[allow(clippy::needless_borrow)] - let expected = TestScenario::new(&util_name) + let expected = TestScenario::new(util_name) .cmd(util_name) .env("LC_ALL", "C") .succeeds() From eb00c195c6c9e014c89d53b7d0e6feade507da8d Mon Sep 17 00:00:00 2001 From: Yury Zhytkou <54360928+zhitkoff@users.noreply.github.com> Date: Fri, 17 Nov 2023 11:19:10 -0500 Subject: [PATCH 72/73] split: pass GNU tests/b-chunk.sh (#5475) --------- Co-authored-by: Terts Diepraam Co-authored-by: Daniel Hofstetter Co-authored-by: Brandon Elam Barker Co-authored-by: Kostiantyn Hryshchuk Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- src/uu/split/src/split.rs | 691 +++++++++++++++++++----------------- tests/by-util/test_split.rs | 130 ++++--- 2 files changed, 446 insertions(+), 375 deletions(-) diff --git a/src/uu/split/src/split.rs b/src/uu/split/src/split.rs index 17a783d72..592e4eedd 100644 --- a/src/uu/split/src/split.rs +++ b/src/uu/split/src/split.rs @@ -18,11 +18,12 @@ use std::ffi::OsString; use std::fmt; use std::fs::{metadata, File}; use std::io; -use std::io::{stdin, BufRead, BufReader, BufWriter, ErrorKind, Read, Write}; +use std::io::{stdin, BufRead, BufReader, BufWriter, ErrorKind, Read, Seek, SeekFrom, Write}; use std::path::Path; use std::u64; use uucore::display::Quotable; use uucore::error::{FromIo, UIoError, UResult, USimpleError, UUsageError}; +use uucore::parse_size::parse_size_u64; use uucore::uio_error; use uucore::{format_usage, help_about, help_section, help_usage}; @@ -40,11 +41,20 @@ static OPT_HEX_SUFFIXES_SHORT: &str = "-x"; static OPT_SUFFIX_LENGTH: &str = "suffix-length"; static OPT_VERBOSE: &str = "verbose"; static OPT_SEPARATOR: &str = "separator"; -//The ---io and ---io-blksize parameters are consumed and ignored. -//The parameter is included to make GNU coreutils tests pass. -static OPT_IO: &str = "-io"; -static OPT_IO_BLKSIZE: &str = "-io-blksize"; static OPT_ELIDE_EMPTY_FILES: &str = "elide-empty-files"; +static OPT_IO_BLKSIZE: &str = "-io-blksize"; +// Cap ---io-blksize value +// For 64bit systems the max value is the same as in GNU +// and is equivalent of `i32::MAX >> 20 << 20` operation. +// On 32bit systems however, even though it fits within `u32` and `i32`, +// it causes rust-lang `library/alloc/src/raw_vec.rs` to panic with 'capacity overflow' error. +// Could be due to how `std::io::BufReader` handles internal buffers. +// So we use much smaller value for those +static OPT_IO_BLKSIZE_MAX: usize = if usize::BITS >= 64 { + 2_146_435_072 +} else { + 1_000_000_000 +}; static ARG_INPUT: &str = "input"; static ARG_PREFIX: &str = "prefix"; @@ -311,7 +321,6 @@ pub fn uu_app() -> Command { .arg( Arg::new(OPT_NUMERIC_SUFFIXES) .long(OPT_NUMERIC_SUFFIXES) - .alias("numeric") .require_equals(true) .num_args(0..=1) .overrides_with_all([ @@ -338,7 +347,6 @@ pub fn uu_app() -> Command { .arg( Arg::new(OPT_HEX_SUFFIXES) .long(OPT_HEX_SUFFIXES) - .alias("hex") .require_equals(true) .num_args(0..=1) .overrides_with_all([ @@ -373,12 +381,6 @@ pub fn uu_app() -> Command { .action(ArgAction::Append) .help("use SEP instead of newline as the record separator; '\\0' (zero) specifies the NUL character"), ) - .arg( - Arg::new(OPT_IO) - .long("io") - .alias(OPT_IO) - .hide(true), - ) .arg( Arg::new(OPT_IO_BLKSIZE) .long("io-blksize") @@ -419,6 +421,7 @@ struct Settings { /// chunks. If this is `false`, then empty files will not be /// created. elide_empty_files: bool, + io_blksize: Option, } /// An error when parsing settings from command-line arguments. @@ -441,6 +444,9 @@ enum SettingsError { /// r/K/N FilterWithKthChunkNumber, + /// Invalid IO block size + InvalidIOBlockSize(String), + /// The `--filter` option is not supported on Windows. #[cfg(windows)] NotSupported, @@ -471,6 +477,7 @@ impl fmt::Display for SettingsError { Self::FilterWithKthChunkNumber => { write!(f, "--filter does not process a chunk extracted to stdout") } + Self::InvalidIOBlockSize(s) => write!(f, "invalid IO block size: {}", s.quote()), #[cfg(windows)] Self::NotSupported => write!( f, @@ -499,12 +506,29 @@ impl Settings { match first.as_str() { "\\0" => b'\0', s if s.as_bytes().len() == 1 => s.as_bytes()[0], - s => return Err(SettingsError::MultiCharacterSeparator(s.to_owned())), + s => return Err(SettingsError::MultiCharacterSeparator(s.to_string())), } } None => b'\n', }; + let io_blksize: Option = if let Some(s) = matches.get_one::(OPT_IO_BLKSIZE) { + match parse_size_u64(s) { + Ok(n) => { + let n: usize = n + .try_into() + .map_err(|_| SettingsError::InvalidIOBlockSize(s.to_string()))?; + if n > OPT_IO_BLKSIZE_MAX { + return Err(SettingsError::InvalidIOBlockSize(s.to_string())); + } + Some(n) + } + _ => return Err(SettingsError::InvalidIOBlockSize(s.to_string())), + } + } else { + None + }; + let result = Self { prefix: matches.get_one::(ARG_PREFIX).unwrap().clone(), suffix, @@ -514,6 +538,7 @@ impl Settings { verbose: matches.value_source(OPT_VERBOSE) == Some(ValueSource::CommandLine), separator, elide_empty_files: matches.get_flag(OPT_ELIDE_EMPTY_FILES), + io_blksize, }; #[cfg(windows)] @@ -591,6 +616,93 @@ fn custom_write_all( } } +/// Get the size of the input file in bytes +/// Used only for subset of `--number=CHUNKS` strategy, as there is a need +/// to determine input file size upfront in order to know chunk size +/// to be written into each of N files/chunks: +/// * N split into N files based on size of input +/// * K/N output Kth of N to stdout +/// * l/N split into N files without splitting lines/records +/// * l/K/N output Kth of N to stdout without splitting lines/records +/// +/// For most files the size will be determined by either reading entire file content into a buffer +/// or by `len()` function of [`std::fs::metadata`]. +/// +/// However, for some files which report filesystem metadata size that does not match +/// their actual content size, we will need to attempt to find the end of file +/// with direct `seek()` on [`std::fs::File`]. +/// +/// For STDIN stream - read into a buffer up to a limit +/// If input stream does not EOF before that - return an error +/// (i.e. "infinite" input as in `cat /dev/zero | split ...`, `yes | split ...` etc.). +/// +/// Note: The `buf` might end up with either partial or entire input content. +fn get_input_size( + input: &String, + reader: &mut R, + buf: &mut Vec, + io_blksize: &Option, +) -> std::io::Result +where + R: BufRead, +{ + // Set read limit to io_blksize if specified + // Otherwise to OPT_IO_BLKSIZE_MAX + let read_limit = io_blksize.unwrap_or(OPT_IO_BLKSIZE_MAX) as u64; + + // Try to read into buffer up to a limit + let num_bytes = reader + .by_ref() + .take(read_limit) + .read_to_end(buf) + .map(|n| n as u64)?; + + if num_bytes < read_limit { + // Finite file or STDIN stream that fits entirely + // into a buffer within the limit + // Note: files like /dev/null or similar, + // empty STDIN stream, + // and files with true file size 0 + // will also fit here + Ok(num_bytes) + } else if input == "-" { + // STDIN stream that did not fit all content into a buffer + // Most likely continuous/infinite input stream + return Err(io::Error::new( + ErrorKind::Other, + format!("{}: cannot determine input size", input), + )); + } else { + // Could be that file size is larger than set read limit + // Get the file size from filesystem metadata + let metadata = metadata(input)?; + let metadata_size = metadata.len(); + if num_bytes <= metadata_size { + Ok(metadata_size) + } else { + // Could be a file from locations like /dev, /sys, /proc or similar + // which report filesystem metadata size that does not match + // their actual content size + // Attempt direct `seek()` for the end of a file + let mut tmp_fd = File::open(Path::new(input))?; + let end = tmp_fd.seek(SeekFrom::End(0))?; + if end > 0 { + Ok(end) + } else { + // Edge case of either "infinite" file (i.e. /dev/zero) + // or some other "special" non-standard file type + // Give up and return an error + // TODO It might be possible to do more here + // to address all possible file types and edge cases + return Err(io::Error::new( + ErrorKind::Other, + format!("{}: cannot determine file size", input), + )); + } + } + } +} + /// Write a certain number of bytes to one file, then move on to another one. /// /// This struct maintains an underlying writer representing the @@ -1018,155 +1130,110 @@ impl<'a> Write for LineBytesChunkWriter<'a> { } } -/// Split a file into a specific number of chunks by byte. +/// Split a file or STDIN into a specific number of chunks by byte. +/// If in Kth chunk of N mode - print the k-th chunk to STDOUT. /// -/// This function always creates one output file for each chunk, even +/// When file size cannot be evenly divided into the number of chunks of the same size, +/// the first X chunks are 1 byte longer than the rest, +/// where X is a modulus reminder of (file size % number of chunks) +/// +/// In Kth chunk of N mode - writes to stdout the contents of the chunk identified by `kth_chunk` +/// +/// In N chunks mode - 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. +/// the input file is truncated. However, if the `--filter` option is +/// being used, then files will only be created if `$FILE` variable was used +/// in filter command, +/// i.e. `split -n 10 --filter='head -c1 > $FILE' in` /// /// # Errors /// /// This function returns an error if there is a problem reading from -/// `reader` or writing to one of the output files. +/// `reader` or writing to one of the output files or stdout. +/// +/// # See also +/// +/// * [`n_chunks_by_line`], which splits its input into a specific number of chunks by line. /// /// Implements `--number=CHUNKS` /// Where CHUNKS /// * N -fn split_into_n_chunks_by_byte( +/// * K/N +fn n_chunks_by_byte( settings: &Settings, reader: &mut R, num_chunks: u64, + kth_chunk: Option, ) -> UResult<()> where - R: Read, + R: BufRead, { - // 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).map_err(|_| { - USimpleError::new(1, format!("{}: cannot determine file size", settings.input)) - })?; + // Get the size of the input in bytes + let initial_buf = &mut Vec::new(); + let mut num_bytes = get_input_size(&settings.input, reader, initial_buf, &settings.io_blksize)?; + let mut reader = initial_buf.chain(reader); - 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 { - let num_chunks = num_bytes; - let chunk_size = 1; - (num_chunks, chunk_size) + // If input file is empty and we would not have determined the Kth chunk + // in the Kth chunk of N chunk mode, then terminate immediately. + // This happens on `split -n 3/10 /dev/null`, for example. + if kth_chunk.is_some() && num_bytes == 0 { + return Ok(()); + } + + // If the requested number of chunks exceeds the number of bytes + // in the input: + // * in Kth chunk of N mode - just write empty byte string to stdout + // NOTE: the `elide_empty_files` parameter is ignored here + // as we do not generate any files + // and instead writing to stdout + // * In N chunks mode - if 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 num_chunks = if kth_chunk.is_none() && settings.elide_empty_files && num_chunks > num_bytes + { + num_bytes } else { - let chunk_size = (num_bytes / (num_chunks)).max(1); - (num_chunks, chunk_size) + num_chunks }; // If we would have written zero chunks of output, then terminate // immediately. This happens on `split -e -n 3 /dev/null`, for // example. - if num_chunks == 0 || num_bytes == 0 { + if num_chunks == 0 { return Ok(()); } - 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, &settings.suffix)?; - - // Create one writer for each chunk. This will create each - // of the underlying files (if not in `--filter` mode). + // In Kth chunk of N mode - we will write to stdout instead of to a file. + let mut stdout_writer = std::io::stdout().lock(); + // In N chunks mode - we will write to `num_chunks` files 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 = settings.instantiate_current_writer(filename.as_str())?; - writers.push(writer); + + // Calculate chunk size base and modulo reminder + // to be used in calculating chunk_size later on + let chunk_size_base = num_bytes / num_chunks; + let chunk_size_reminder = num_bytes % num_chunks; + + // If in N chunks mode + // Create one writer for each chunk. + // This will create each of the underlying files + // or stdin pipes to child shell/command processes if in `--filter` mode + if kth_chunk.is_none() { + // This object is responsible for creating the filename for each chunk. + let mut filename_iterator = FilenameIterator::new(&settings.prefix, &settings.suffix) + .map_err(|e| io::Error::new(ErrorKind::Other, format!("{e}")))?; + for _ in 0..num_chunks { + let filename = filename_iterator + .next() + .ok_or_else(|| USimpleError::new(1, "output file suffixes exhausted"))?; + let writer = settings.instantiate_current_writer(filename.as_str())?; + writers.push(writer); + } } - // Write `chunk_size` bytes from the reader into each writer - // except the last. - // - // 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. - for writer in writers.iter_mut().take(num_chunks - 1) { - match io::copy(&mut reader.by_ref().take(chunk_size), writer) { - Ok(_) => continue, - Err(e) if ignorable_io_error(&e, settings) => continue, - Err(e) => return Err(uio_error!(e, "input/output error")), - }; - } - - // Write all the remaining bytes to the last chunk. - let i = num_chunks - 1; - let last_chunk_size = num_bytes - (chunk_size * (num_chunks as u64 - 1)); - match io::copy(&mut reader.by_ref().take(last_chunk_size), &mut writers[i]) { - Ok(_) => Ok(()), - Err(e) if ignorable_io_error(&e, settings) => Ok(()), - Err(e) => Err(uio_error!(e, "input/output error")), - } -} - -/// Print the k-th chunk of a file to stdout, splitting by byte. -/// -/// This function is like [`split_into_n_chunks_by_byte`], but instead -/// of writing each chunk to its own file, it only writes to stdout -/// the contents of the chunk identified by `chunk_number` -/// -/// # Errors -/// -/// This function returns an error if there is a problem reading from -/// `reader` or writing to stdout. -/// -/// Implements `--number=CHUNKS` -/// Where CHUNKS -/// * K/N -fn kth_chunks_by_byte( - settings: &Settings, - reader: &mut R, - chunk_number: u64, - num_chunks: u64, -) -> UResult<()> -where - R: BufRead, -{ - // 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 - just write empty byte string to stdout - // NOTE: the `elide_empty_files` parameter is ignored here - // as we do not generate any files - // and instead writing to stdout - let metadata = metadata(&settings.input).map_err(|_| { - USimpleError::new(1, format!("{}: cannot determine file size", settings.input)) - })?; - - let num_bytes = metadata.len(); - // If input file is empty and we would have written zero chunks of output, - // then terminate immediately. - // This happens on `split -e -n 3 /dev/null`, for example. - if num_bytes == 0 { - return Ok(()); - } - - // Write to stdout instead of to a file. - let stdout = std::io::stdout(); - let mut writer = stdout.lock(); - - let chunk_size = (num_bytes / (num_chunks)).max(1); - let mut num_bytes: usize = num_bytes.try_into().unwrap(); - - let mut i = 1; - loop { - let buf: &mut Vec = &mut vec![]; + for i in 1_u64..=num_chunks { + let chunk_size = chunk_size_base + (chunk_size_reminder > i - 1) as u64; + let buf = &mut Vec::new(); if num_bytes > 0 { // Read `chunk_size` bytes from the reader into `buf` // except the last. @@ -1176,15 +1243,17 @@ where // `num_chunks`, we don't leave any bytes behind. let limit = { if i == num_chunks { - num_bytes.try_into().unwrap() + num_bytes } else { chunk_size } }; + let n_bytes_read = reader.by_ref().take(limit).read_to_end(buf); + match n_bytes_read { Ok(n_bytes) => { - num_bytes -= n_bytes; + num_bytes -= n_bytes as u64; } Err(error) => { return Err(USimpleError::new( @@ -1193,11 +1262,20 @@ where )); } } - if i == chunk_number { - writer.write_all(buf)?; - break; + + match kth_chunk { + Some(chunk_number) => { + if i == chunk_number { + stdout_writer.write_all(buf)?; + break; + } + } + None => { + let idx = (i - 1) as usize; + let writer = writers.get_mut(idx).unwrap(); + writer.write_all(buf)?; + } } - i += 1; } else { break; } @@ -1205,12 +1283,17 @@ where Ok(()) } -/// Split a file into a specific number of chunks by line. +/// Split a file or STDIN into a specific number of chunks by line. +/// If in Kth chunk of N mode - print the k-th chunk to STDOUT. /// -/// This function always creates one output file for each chunk, even +/// In Kth chunk of N mode - writes to stdout the contents of the chunk identified by `kth_chunk` +/// +/// In N chunks mode - 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. +/// the input file is truncated. However, if the `--filter` option is +/// being used, then files will only be created if `$FILE` variable was used +/// in filter command, +/// i.e. `split -n l/10 --filter='head -c1 > $FILE' in` /// /// # Errors /// @@ -1219,119 +1302,82 @@ where /// /// # See also /// -/// * [`kth_chunk_by_line`], which splits its input in the same way, -/// but writes only one specified chunk to stdout. +/// * [`n_chunks_by_byte`], which splits its input into a specific number of chunks by byte. /// /// Implements `--number=CHUNKS` /// Where CHUNKS /// * l/N -fn split_into_n_chunks_by_line( +/// * l/K/N +fn n_chunks_by_line( settings: &Settings, reader: &mut R, num_chunks: u64, + kth_chunk: Option, ) -> UResult<()> where R: BufRead, { - // Get the size of the input file in bytes and compute the number + // Get the size of the input in bytes and compute the number // of bytes per chunk. - let metadata = metadata(&settings.input).map_err(|_| { - USimpleError::new(1, format!("{}: cannot determine file size", settings.input)) - })?; - let num_bytes = metadata.len(); + let initial_buf = &mut Vec::new(); + let num_bytes = get_input_size(&settings.input, reader, initial_buf, &settings.io_blksize)?; + let reader = initial_buf.chain(reader); let chunk_size = (num_bytes / num_chunks) as usize; - // This object is responsible for creating the filename for each chunk. - let mut filename_iterator = FilenameIterator::new(&settings.prefix, &settings.suffix)?; - - // 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 = settings.instantiate_current_writer(filename.as_str())?; - writers.push(writer); + // If input file is empty and we would not have determined the Kth chunk + // in the Kth chunk of N chunk mode, then terminate immediately. + // This happens on `split -n l/3/10 /dev/null`, for example. + if kth_chunk.is_some() && num_bytes == 0 { + return Ok(()); } - let mut num_bytes_remaining_in_current_chunk = chunk_size; - let mut i = 0; - let sep = settings.separator; - for line_result in reader.split(sep) { - let line = line_result.unwrap(); - let maybe_writer = writers.get_mut(i); - let writer = maybe_writer.unwrap(); - let bytes = line.as_slice(); - custom_write_all(bytes, writer, settings)?; - custom_write_all(&[sep], writer, settings)?; + // In Kth chunk of N mode - we will write to stdout instead of to a file. + let mut stdout_writer = std::io::stdout().lock(); + // In N chunks mode - we will write to `num_chunks` files + let mut writers = vec![]; - // Add one byte for the separator 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; + // If in N chunks mode + // Create one writer for each chunk. + // This will create each of the underlying files + // or stdin pipes to child shell/command processes if in `--filter` mode + if kth_chunk.is_none() { + // This object is responsible for creating the filename for each chunk. + let mut filename_iterator = FilenameIterator::new(&settings.prefix, &settings.suffix) + .map_err(|e| io::Error::new(ErrorKind::Other, format!("{e}")))?; + for _ in 0..num_chunks { + let filename = filename_iterator + .next() + .ok_or_else(|| USimpleError::new(1, "output file suffixes exhausted"))?; + let writer = settings.instantiate_current_writer(filename.as_str())?; + writers.push(writer); } } - Ok(()) -} - -/// Print the k-th chunk of a file, splitting by line. -/// -/// This function is like [`split_into_n_chunks_by_line`], but instead -/// of writing each chunk to its own file, it only writes to stdout -/// the contents of the chunk identified by `chunk_number`. -/// -/// # Errors -/// -/// This function returns an error if there is a problem reading from -/// `reader` or writing to one of the output files. -/// -/// # See also -/// -/// * [`split_into_n_chunks_by_line`], which splits its input in the -/// same way, but writes each chunk to its own file. -/// -/// Implements `--number=CHUNKS` -/// Where CHUNKS -/// * l/K/N -fn kth_chunk_by_line( - settings: &Settings, - reader: &mut R, - chunk_number: u64, - 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).map_err(|_| { - USimpleError::new(1, format!("{}: cannot determine file size", settings.input)) - })?; - let num_bytes = metadata.len(); - let chunk_size = (num_bytes / num_chunks) as usize; - - // Write to stdout instead of to a file. - let stdout = std::io::stdout(); - let mut writer = stdout.lock(); - let mut num_bytes_remaining_in_current_chunk = chunk_size; let mut i = 1; let sep = settings.separator; + for line_result in reader.split(sep) { - let line = line_result?; + // add separator back in at the end of the line + let mut line = line_result?; + line.push(sep); let bytes = line.as_slice(); - if i == chunk_number { - writer.write_all(bytes)?; - writer.write_all(&[sep])?; + + match kth_chunk { + Some(chunk_number) => { + if i == chunk_number { + stdout_writer.write_all(bytes)?; + } + } + None => { + let idx = (i - 1) as usize; + let maybe_writer = writers.get_mut(idx); + let writer = maybe_writer.unwrap(); + custom_write_all(bytes, writer, settings)?; + } } - // Add one byte for the separator character. - let num_bytes = bytes.len() + 1; + let num_bytes = bytes.len(); if num_bytes >= num_bytes_remaining_in_current_chunk { num_bytes_remaining_in_current_chunk = chunk_size; i += 1; @@ -1339,72 +1385,8 @@ where num_bytes_remaining_in_current_chunk -= num_bytes; } - if i > chunk_number { - break; - } - } - - Ok(()) -} - -/// Split a file into a specific number of chunks by line, but -/// assign lines via round-robin -/// -/// 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. -/// -/// # See also -/// -/// * [`split_into_n_chunks_by_line`], which splits its input in the same way, -/// but without round robin distribution. -/// -/// Implements `--number=CHUNKS` -/// Where CHUNKS -/// * r/N -fn split_into_n_chunks_by_line_round_robin( - settings: &Settings, - reader: &mut R, - num_chunks: u64, -) -> UResult<()> -where - R: BufRead, -{ - // This object is responsible for creating the filename for each chunk. - let mut filename_iterator = FilenameIterator::new(&settings.prefix, &settings.suffix) - .map_err(|e| io::Error::new(ErrorKind::Other, format!("{e}")))?; - - // 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(|| io::Error::new(ErrorKind::Other, "output file suffixes exhausted"))?; - let writer = settings.instantiate_current_writer(filename.as_str())?; - writers.push(writer); - } - - let num_chunks: usize = num_chunks.try_into().unwrap(); - let sep = settings.separator; - let mut closed_writers = 0; - for (i, line_result) in reader.split(sep).enumerate() { - let maybe_writer = writers.get_mut(i % num_chunks); - let writer = maybe_writer.unwrap(); - let mut line = line_result.unwrap(); - line.push(sep); - let bytes = line.as_slice(); - let writer_stdin_open = custom_write_all(bytes, writer, settings)?; - if !writer_stdin_open { - closed_writers += 1; - if closed_writers == num_chunks { - // all writers are closed - stop reading + if let Some(chunk_number) = kth_chunk { + if i > chunk_number { break; } } @@ -1413,14 +1395,17 @@ where Ok(()) } -/// Print the k-th chunk of a file, splitting by line, but -/// assign lines via round-robin to the specified number of output -/// chunks, but output only the *k*th chunk. +/// Split a file or STDIN into a specific number of chunks by line, but +/// assign lines via round-robin /// -/// This function is like [`kth_chunk_by_line`], as it only writes to stdout and -/// prints out only *k*th chunk -/// It is also like [`split_into_n_chunks_by_line_round_robin`], as it is assigning chunks -/// using round robin distribution +/// In Kth chunk of N mode - writes to stdout the contents of the chunk identified by `kth_chunk` +/// +/// In N chunks mode - 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 files will only be created if `$FILE` variable was used +/// in filter command, +/// i.e. `split -n r/10 --filter='head -c1 > $FILE' in` /// /// # Errors /// @@ -1429,46 +1414,83 @@ where /// /// # See also /// -/// * [`split_into_n_chunks_by_line_round_robin`], which splits its input in the -/// same way, but writes each chunk to its own file. +/// * [`n_chunks_by_line`], which splits its input into a specific number of chunks by line. /// /// Implements `--number=CHUNKS` /// Where CHUNKS +/// * r/N /// * r/K/N -fn kth_chunk_by_line_round_robin( +fn n_chunks_by_line_round_robin( settings: &Settings, reader: &mut R, - chunk_number: u64, num_chunks: u64, + kth_chunk: Option, ) -> UResult<()> where R: BufRead, { - // Write to stdout instead of to a file. - let stdout = std::io::stdout(); - let mut writer = stdout.lock(); + // In Kth chunk of N mode - we will write to stdout instead of to a file. + let mut stdout_writer = std::io::stdout().lock(); + // In N chunks mode - we will write to `num_chunks` files + let mut writers = vec![]; - let num_chunks: usize = num_chunks.try_into().unwrap(); - let chunk_number: usize = chunk_number.try_into().unwrap(); - let sep = settings.separator; - // The chunk number is given as a 1-indexed number, but it - // is a little easier to deal with a 0-indexed number - // since `.enumerate()` returns index `i` starting with 0 - let chunk_number = chunk_number - 1; - for (i, line_result) in reader.split(sep).enumerate() { - let line = line_result?; - let bytes = line.as_slice(); - if (i % num_chunks) == chunk_number { - writer.write_all(bytes)?; - writer.write_all(&[sep])?; + // If in N chunks mode + // Create one writer for each chunk. + // This will create each of the underlying files + // or stdin pipes to child shell/command processes if in `--filter` mode + if kth_chunk.is_none() { + // This object is responsible for creating the filename for each chunk. + let mut filename_iterator = FilenameIterator::new(&settings.prefix, &settings.suffix) + .map_err(|e| io::Error::new(ErrorKind::Other, format!("{e}")))?; + for _ in 0..num_chunks { + let filename = filename_iterator + .next() + .ok_or_else(|| USimpleError::new(1, "output file suffixes exhausted"))?; + let writer = settings.instantiate_current_writer(filename.as_str())?; + writers.push(writer); } } + + let num_chunks: usize = num_chunks.try_into().unwrap(); + let sep = settings.separator; + let mut closed_writers = 0; + for (i, line_result) in reader.split(sep).enumerate() { + // add separator back in at the end of the line + let mut line = line_result?; + line.push(sep); + let bytes = line.as_slice(); + + match kth_chunk { + Some(chunk_number) => { + // The `.enumerate()` method returns index `i` starting with 0, + // but chunk number is given as a 1-indexed number, + // so compare to `chunk_number - 1` + if (i % num_chunks) == (chunk_number - 1) as usize { + stdout_writer.write_all(bytes)?; + } + } + None => { + let maybe_writer = writers.get_mut(i % num_chunks); + let writer = maybe_writer.unwrap(); + + let writer_stdin_open = custom_write_all(bytes, writer, settings)?; + if !writer_stdin_open { + closed_writers += 1; + if closed_writers == num_chunks { + // all writers are closed - stop reading + break; + } + } + } + } + } + Ok(()) } #[allow(clippy::cognitive_complexity)] fn split(settings: &Settings) -> UResult<()> { - let mut reader = BufReader::new(if settings.input == "-" { + let r_box = if settings.input == "-" { Box::new(stdin()) as Box } else { let r = File::open(Path::new(&settings.input)).map_err_context(|| { @@ -1478,26 +1500,33 @@ fn split(settings: &Settings) -> UResult<()> { ) })?; Box::new(r) as Box - }); + }; + let mut reader = if let Some(c) = settings.io_blksize { + BufReader::with_capacity(c, r_box) + } else { + BufReader::new(r_box) + }; match settings.strategy { Strategy::Number(NumberType::Bytes(num_chunks)) => { - split_into_n_chunks_by_byte(settings, &mut reader, num_chunks) + // split_into_n_chunks_by_byte(settings, &mut reader, num_chunks) + n_chunks_by_byte(settings, &mut reader, num_chunks, None) } Strategy::Number(NumberType::KthBytes(chunk_number, num_chunks)) => { - kth_chunks_by_byte(settings, &mut reader, chunk_number, num_chunks) + // kth_chunks_by_byte(settings, &mut reader, chunk_number, num_chunks) + n_chunks_by_byte(settings, &mut reader, num_chunks, Some(chunk_number)) } Strategy::Number(NumberType::Lines(num_chunks)) => { - split_into_n_chunks_by_line(settings, &mut reader, num_chunks) + n_chunks_by_line(settings, &mut reader, num_chunks, None) } Strategy::Number(NumberType::KthLines(chunk_number, num_chunks)) => { - kth_chunk_by_line(settings, &mut reader, chunk_number, num_chunks) + n_chunks_by_line(settings, &mut reader, num_chunks, Some(chunk_number)) } Strategy::Number(NumberType::RoundRobin(num_chunks)) => { - split_into_n_chunks_by_line_round_robin(settings, &mut reader, num_chunks) + n_chunks_by_line_round_robin(settings, &mut reader, num_chunks, None) } Strategy::Number(NumberType::KthRoundRobin(chunk_number, num_chunks)) => { - kth_chunk_by_line_round_robin(settings, &mut reader, chunk_number, num_chunks) + n_chunks_by_line_round_robin(settings, &mut reader, num_chunks, Some(chunk_number)) } 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 aec6f0594..0ae2af5cb 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 threebytes asciilowercase fghij klmno pqrst uvwxyz fivelines twohundredfortyonebytes onehundredlines nbbbb dxen ncccc +// spell-checker:ignore xzaaa sixhundredfiftyonebytes ninetyonebytes threebytes asciilowercase ghijkl mnopq rstuv wxyz fivelines twohundredfortyonebytes onehundredlines nbbbb dxen ncccc use crate::common::util::{AtPath, TestScenario}; use rand::{thread_rng, Rng, SeedableRng}; @@ -704,54 +704,41 @@ fn test_split_overflow_bytes_size() { assert_eq!(glob.collate(), at.read_bytes(name)); } -#[test] -#[cfg(target_pointer_width = "32")] -fn test_split_chunks_num_chunks_oversized_32() { - let scene = TestScenario::new(util_name!()); - let at = &scene.fixtures; - at.touch("file"); - scene - .ucmd() - .args(&["--number", "5000000000", "sixhundredfiftyonebytes.txt"]) - .fails() - .code_is(1) - .stderr_only("split: Number of chunks too big\n"); -} - #[test] fn test_split_stdin_num_chunks() { - new_ucmd!() - .args(&["--number=1"]) - .fails() - .code_is(1) - .stderr_only("split: -: cannot determine file size\n"); + let (at, mut ucmd) = at_and_ucmd!(); + ucmd.args(&["--number=1"]).pipe_in("").succeeds(); + assert_eq!(file_read(&at, "xaa"), ""); + assert!(!at.plus("xab").exists()); } #[test] fn test_split_stdin_num_kth_chunk() { new_ucmd!() .args(&["--number=1/2"]) - .fails() - .code_is(1) - .stderr_only("split: -: cannot determine file size\n"); + .pipe_in("1\n2\n3\n4\n5\n") + .succeeds() + .stdout_only("1\n2\n3"); } #[test] fn test_split_stdin_num_line_chunks() { - new_ucmd!() - .args(&["--number=l/2"]) - .fails() - .code_is(1) - .stderr_only("split: -: cannot determine file size\n"); + let (at, mut ucmd) = at_and_ucmd!(); + ucmd.args(&["--number=l/2"]) + .pipe_in("1\n2\n3\n4\n5\n") + .succeeds(); + assert_eq!(file_read(&at, "xaa"), "1\n2\n3\n"); + assert_eq!(file_read(&at, "xab"), "4\n5\n"); + assert!(!at.plus("xac").exists()); } #[test] fn test_split_stdin_num_kth_line_chunk() { new_ucmd!() .args(&["--number=l/2/5"]) - .fails() - .code_is(1) - .stderr_only("split: -: cannot determine file size\n"); + .pipe_in("1\n2\n3\n4\n5\n") + .succeeds() + .stdout_only("2\n"); } fn file_read(at: &AtPath, filename: &str) -> String { @@ -912,6 +899,14 @@ fn test_suffixes_exhausted() { .stderr_only("split: output file suffixes exhausted\n"); } +#[test] +fn test_suffix_length_req() { + new_ucmd!() + .args(&["-n", "100", "-a", "1", "asciilowercase.txt"]) + .fails() + .stderr_only("split: the suffix length needs to be at least 2\n"); +} + #[test] fn test_verbose() { new_ucmd!() @@ -937,11 +932,11 @@ fn test_number_n() { s }; ucmd.args(&["-n", "5", "asciilowercase.txt"]).succeeds(); - assert_eq!(file_read("xaa"), "abcde"); - assert_eq!(file_read("xab"), "fghij"); - assert_eq!(file_read("xac"), "klmno"); - assert_eq!(file_read("xad"), "pqrst"); - assert_eq!(file_read("xae"), "uvwxyz\n"); + assert_eq!(file_read("xaa"), "abcdef"); + assert_eq!(file_read("xab"), "ghijkl"); + assert_eq!(file_read("xac"), "mnopq"); + assert_eq!(file_read("xad"), "rstuv"); + assert_eq!(file_read("xae"), "wxyz\n"); #[cfg(unix)] new_ucmd!() .args(&["--number=100", "/dev/null"]) @@ -954,11 +949,11 @@ fn test_number_kth_of_n() { new_ucmd!() .args(&["--number=3/5", "asciilowercase.txt"]) .succeeds() - .stdout_only("klmno"); + .stdout_only("mnopq"); new_ucmd!() .args(&["--number=5/5", "asciilowercase.txt"]) .succeeds() - .stdout_only("uvwxyz\n"); + .stdout_only("wxyz\n"); new_ucmd!() .args(&["-e", "--number=99/100", "asciilowercase.txt"]) .succeeds() @@ -1046,11 +1041,11 @@ fn test_split_number_with_io_blksize() { }; ucmd.args(&["-n", "5", "asciilowercase.txt", "---io-blksize", "1024"]) .succeeds(); - assert_eq!(file_read("xaa"), "abcde"); - assert_eq!(file_read("xab"), "fghij"); - assert_eq!(file_read("xac"), "klmno"); - assert_eq!(file_read("xad"), "pqrst"); - assert_eq!(file_read("xae"), "uvwxyz\n"); + assert_eq!(file_read("xaa"), "abcdef"); + assert_eq!(file_read("xab"), "ghijkl"); + assert_eq!(file_read("xac"), "mnopq"); + assert_eq!(file_read("xad"), "rstuv"); + assert_eq!(file_read("xae"), "wxyz\n"); } #[test] @@ -1065,6 +1060,32 @@ fn test_split_default_with_io_blksize() { assert_eq!(glob.collate(), at.read_bytes(name)); } +#[test] +fn test_split_invalid_io_blksize() { + new_ucmd!() + .args(&["---io-blksize=XYZ", "threebytes.txt"]) + .fails() + .stderr_only("split: invalid IO block size: 'XYZ'\n"); + new_ucmd!() + .args(&["---io-blksize=5000000000", "threebytes.txt"]) + .fails() + .stderr_only("split: invalid IO block size: '5000000000'\n"); + #[cfg(target_pointer_width = "32")] + new_ucmd!() + .args(&["---io-blksize=2146435072", "threebytes.txt"]) + .fails() + .stderr_only("split: invalid IO block size: '2146435072'\n"); +} + +#[test] +fn test_split_number_oversized_stdin() { + new_ucmd!() + .args(&["--number=3", "---io-blksize=600"]) + .pipe_in_fixture("sixhundredfiftyonebytes.txt") + .fails() + .stderr_only("split: -: cannot determine input size\n"); +} + #[test] fn test_invalid_suffix_length() { new_ucmd!() @@ -1157,6 +1178,18 @@ fn test_elide_dev_null() { assert!(!at.plus("xac").exists()); } +#[test] +#[cfg(unix)] +fn test_dev_zero() { + let (at, mut ucmd) = at_and_ucmd!(); + ucmd.args(&["-n", "3", "/dev/zero"]) + .fails() + .stderr_only("split: /dev/zero: cannot determine file size\n"); + assert!(!at.plus("xaa").exists()); + assert!(!at.plus("xab").exists()); + assert!(!at.plus("xac").exists()); +} + #[test] fn test_lines() { let (at, mut ucmd) = at_and_ucmd!(); @@ -1182,6 +1215,15 @@ fn test_lines_kth() { .stdout_only("20\n21\n22\n23\n24\n25\n26\n27\n28\n29\n"); } +#[test] +#[cfg(unix)] +fn test_lines_kth_dev_null() { + new_ucmd!() + .args(&["-n", "l/3/10", "/dev/null"]) + .succeeds() + .stdout_only(""); +} + #[test] fn test_line_bytes() { let (at, mut ucmd) = at_and_ucmd!(); @@ -1321,7 +1363,7 @@ fn test_numeric_suffix() { } #[test] -fn test_numeric_suffix_alias() { +fn test_numeric_suffix_inferred() { let (at, mut ucmd) = at_and_ucmd!(); ucmd.args(&["-n", "4", "--numeric=9", "threebytes.txt"]) .succeeds() From 58087df02a30bb4c7d6e474dfeee20b9faa51b93 Mon Sep 17 00:00:00 2001 From: clara swanson <69856940+cswn@users.noreply.github.com> Date: Mon, 20 Nov 2023 10:16:18 +0100 Subject: [PATCH 73/73] stdbuf: remove crash macro (#5549) * stdbuf: remove crash! macro * stdbuf: change target_vendor back to apple * tests/stdbuf: change stderr_only to usage_error in test_stdbuf_invalid_mode_fails * stdbuf: add exit code to check_option * stdbuf: remove set_exit_code line from error --- src/uu/stdbuf/src/stdbuf.rs | 29 +++++++++++++++-------------- tests/by-util/test_stdbuf.rs | 2 +- 2 files changed, 16 insertions(+), 15 deletions(-) diff --git a/src/uu/stdbuf/src/stdbuf.rs b/src/uu/stdbuf/src/stdbuf.rs index 857828275..38c4451ca 100644 --- a/src/uu/stdbuf/src/stdbuf.rs +++ b/src/uu/stdbuf/src/stdbuf.rs @@ -7,7 +7,7 @@ use clap::{crate_version, Arg, ArgAction, ArgMatches, Command}; use std::fs::File; -use std::io::{self, Write}; +use std::io::Write; use std::os::unix::process::ExitStatusExt; use std::path::PathBuf; use std::process; @@ -15,7 +15,7 @@ use tempfile::tempdir; use tempfile::TempDir; use uucore::error::{FromIo, UResult, USimpleError, UUsageError}; use uucore::parse_size::parse_size_u64; -use uucore::{crash, format_usage, help_about, help_section, help_usage}; +use uucore::{format_usage, help_about, help_section, help_usage}; const ABOUT: &str = help_about!("stdbuf.md"); const USAGE: &str = help_usage!("stdbuf.md"); @@ -66,13 +66,13 @@ struct ProgramOptionsError(String); target_os = "netbsd", target_os = "dragonflybsd" ))] -fn preload_strings() -> (&'static str, &'static str) { - ("LD_PRELOAD", "so") +fn preload_strings() -> UResult<(&'static str, &'static str)> { + Ok(("LD_PRELOAD", "so")) } #[cfg(target_vendor = "apple")] -fn preload_strings() -> (&'static str, &'static str) { - ("DYLD_LIBRARY_PATH", "dylib") +fn preload_strings() -> UResult<(&'static str, &'static str)> { + Ok(("DYLD_LIBRARY_PATH", "dylib")) } #[cfg(not(any( @@ -83,10 +83,11 @@ fn preload_strings() -> (&'static str, &'static str) { target_os = "dragonflybsd", target_vendor = "apple" )))] -fn preload_strings() -> (&'static str, &'static str) { - use uucore::crash; - - crash!(1, "Command not supported for this operating system!") +fn preload_strings() -> UResult<(&'static str, &'static str)> { + Err(USimpleError::new( + 1, + "Command not supported for this operating system!", + )) } fn check_option(matches: &ArgMatches, name: &str) -> Result { @@ -102,7 +103,7 @@ fn check_option(matches: &ArgMatches, name: &str) -> Result parse_size_u64(x).map_or_else( - |e| crash!(125, "invalid mode {}", e), + |e| Err(ProgramOptionsError(format!("invalid mode {e}"))), |m| { Ok(BufferType::Size(m.try_into().map_err(|_| { ProgramOptionsError(format!( @@ -128,8 +129,8 @@ fn set_command_env(command: &mut process::Command, buffer_name: &str, buffer_typ } } -fn get_preload_env(tmp_dir: &TempDir) -> io::Result<(String, PathBuf)> { - let (preload, extension) = preload_strings(); +fn get_preload_env(tmp_dir: &TempDir) -> UResult<(String, PathBuf)> { + let (preload, extension) = preload_strings()?; let inject_path = tmp_dir.path().join("libstdbuf").with_extension(extension); let mut file = File::create(&inject_path)?; @@ -151,7 +152,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { let command_params: Vec<&str> = command_values.map(|s| s.as_ref()).collect(); let tmp_dir = tempdir().unwrap(); - let (preload_env, libstdbuf) = get_preload_env(&tmp_dir).map_err_context(String::new)?; + let (preload_env, libstdbuf) = get_preload_env(&tmp_dir)?; command.env(preload_env, libstdbuf); set_command_env(&mut command, "_STDBUF_I", &options.stdin); set_command_env(&mut command, "_STDBUF_O", &options.stdout); diff --git a/tests/by-util/test_stdbuf.rs b/tests/by-util/test_stdbuf.rs index 9a67dad9e..50de4c546 100644 --- a/tests/by-util/test_stdbuf.rs +++ b/tests/by-util/test_stdbuf.rs @@ -65,7 +65,7 @@ fn test_stdbuf_invalid_mode_fails() { .args(&[*option, "1024R", "head"]) .fails() .code_is(125) - .stderr_only("stdbuf: invalid mode '1024R': Value too large for defined data type\n"); + .usage_error("invalid mode '1024R': Value too large for defined data type"); #[cfg(not(target_pointer_width = "128"))] new_ucmd!() .args(&[*option, "1Y", "head"])