From 43b2b3fbaac2b248031505ab3fd60393687a2ac0 Mon Sep 17 00:00:00 2001 From: Ulrich Hornung Date: Tue, 27 Feb 2024 23:12:04 +0100 Subject: [PATCH 001/144] handle SIGUSR1 directly. not just every 1sec --- src/uu/dd/src/dd.rs | 61 +++++++++++++---- src/uu/dd/src/progress.rs | 141 ++++++++++++++++++++++---------------- 2 files changed, 128 insertions(+), 74 deletions(-) diff --git a/src/uu/dd/src/dd.rs b/src/uu/dd/src/dd.rs index b1c6b563017..fb91f67552c 100644 --- a/src/uu/dd/src/dd.rs +++ b/src/uu/dd/src/dd.rs @@ -21,6 +21,7 @@ use nix::fcntl::FcntlArg::F_SETFL; #[cfg(any(target_os = "linux", target_os = "android"))] use nix::fcntl::OFlag; use parseargs::Parser; +use progress::ProgUpdateType; use progress::{gen_prog_updater, ProgUpdate, ReadStat, StatusLevel, WriteStat}; use uucore::io::OwnedFileDescriptorOrHandle; @@ -39,10 +40,8 @@ use std::os::unix::{ #[cfg(windows)] use std::os::windows::{fs::MetadataExt, io::AsHandle}; use std::path::Path; -use std::sync::{ - atomic::{AtomicBool, Ordering::Relaxed}, - mpsc, Arc, -}; +use std::sync::atomic::AtomicU8; +use std::sync::{atomic::Ordering::Relaxed, mpsc, Arc}; use std::thread; use std::time::{Duration, Instant}; @@ -94,29 +93,41 @@ struct Settings { /// the first caller each interval will yield true. /// /// When all instances are dropped the background thread will exit on the next interval. -#[derive(Debug, Clone)] pub struct Alarm { interval: Duration, - trigger: Arc, + trigger: Arc, } +const TRIGGER_NONE: u8 = 0; +const TRIGGER_TIMER: u8 = 1; +const TRIGGER_SIGNAL: u8 = 2; + impl Alarm { pub fn with_interval(interval: Duration) -> Self { - let trigger = Arc::new(AtomicBool::default()); + let trigger = Arc::new(AtomicU8::default()); let weak_trigger = Arc::downgrade(&trigger); thread::spawn(move || { while let Some(trigger) = weak_trigger.upgrade() { thread::sleep(interval); - trigger.store(true, Relaxed); + trigger.store(TRIGGER_TIMER, Relaxed); } }); Self { interval, trigger } } - pub fn is_triggered(&self) -> bool { - self.trigger.swap(false, Relaxed) + pub fn manual_trigger_fn(&self) -> Box { + let weak_trigger = Arc::downgrade(&self.trigger); + Box::new(move || { + if let Some(trigger) = weak_trigger.upgrade() { + trigger.store(TRIGGER_SIGNAL, Relaxed); + } + }) + } + + pub fn get_trigger(&self) -> u8 { + self.trigger.swap(TRIGGER_NONE, Relaxed) } pub fn get_interval(&self) -> Duration { @@ -1018,6 +1029,18 @@ fn dd_copy(mut i: Input, o: Output) -> std::io::Result<()> { // This avoids the need to query the OS monotonic clock for every block. let alarm = Alarm::with_interval(Duration::from_secs(1)); + // The signal handler spawns an own thread that waits for signals. + // When the signal is received, it calls a handler function. + // We inject a handler function that manually triggers the alarm. + #[cfg(target_os = "linux")] + let signal_handler = progress::SignalHandler::install_signal_handler(alarm.manual_trigger_fn()); + #[cfg(target_os = "linux")] + if let Err(e) = &signal_handler { + if Some(StatusLevel::None) != i.settings.status { + eprintln!("Internal dd Warning: Unable to register signal handler \n\t{e}"); + } + } + // Index in the input file where we are reading bytes and in // the output file where we are writing bytes. // @@ -1086,11 +1109,20 @@ fn dd_copy(mut i: Input, o: Output) -> std::io::Result<()> { // error. rstat += rstat_update; wstat += wstat_update; - if alarm.is_triggered() { - let prog_update = ProgUpdate::new(rstat, wstat, start.elapsed(), false); - prog_tx.send(prog_update).unwrap_or(()); + match alarm.get_trigger() { + TRIGGER_NONE => {} + t @ TRIGGER_TIMER | t @ TRIGGER_SIGNAL => { + let tp = match t { + TRIGGER_TIMER => ProgUpdateType::Periodic, + _ => ProgUpdateType::Signal, + }; + let prog_update = ProgUpdate::new(rstat, wstat, start.elapsed(), tp); + prog_tx.send(prog_update).unwrap_or(()); + } + _ => {} } } + finalize(o, rstat, wstat, start, &prog_tx, output_thread, truncate) } @@ -1118,12 +1150,13 @@ fn finalize( // Print the final read/write statistics. let wstat = wstat + wstat_update; - let prog_update = ProgUpdate::new(rstat, wstat, start.elapsed(), true); + let prog_update = ProgUpdate::new(rstat, wstat, start.elapsed(), ProgUpdateType::Final); prog_tx.send(prog_update).unwrap_or(()); // Wait for the output thread to finish output_thread .join() .expect("Failed to join with the output thread."); + Ok(()) } diff --git a/src/uu/dd/src/progress.rs b/src/uu/dd/src/progress.rs index 238b2ab39a2..062fbbe8df6 100644 --- a/src/uu/dd/src/progress.rs +++ b/src/uu/dd/src/progress.rs @@ -11,8 +11,12 @@ //! updater that runs in its own thread. use std::io::Write; use std::sync::mpsc; +#[cfg(target_os = "linux")] +use std::thread::JoinHandle; use std::time::Duration; +#[cfg(target_os = "linux")] +use signal_hook::iterator::Handle; use uucore::{ error::UResult, format::num_format::{FloatVariant, Formatter}, @@ -20,18 +24,12 @@ use uucore::{ use crate::numbers::{to_magnitude_and_suffix, SuffixType}; -// On Linux, we register a signal handler that prints progress updates. -#[cfg(target_os = "linux")] -use signal_hook::consts::signal; -#[cfg(target_os = "linux")] -use std::{ - env, - error::Error, - sync::{ - atomic::{AtomicUsize, Ordering}, - Arc, - }, -}; +#[derive(PartialEq, Eq)] +pub(crate) enum ProgUpdateType { + Periodic, + Signal, + Final, +} /// Summary statistics for read and write progress of dd for a given duration. pub(crate) struct ProgUpdate { @@ -53,7 +51,7 @@ pub(crate) struct ProgUpdate { /// The status of the write. /// /// True if the write is completed, false if still in-progress. - pub(crate) complete: bool, + pub(crate) update_type: ProgUpdateType, } impl ProgUpdate { @@ -62,13 +60,13 @@ impl ProgUpdate { read_stat: ReadStat, write_stat: WriteStat, duration: Duration, - complete: bool, + update_type: ProgUpdateType, ) -> Self { Self { read_stat, write_stat, duration, - complete, + update_type, } } @@ -433,7 +431,7 @@ pub(crate) fn gen_prog_updater( let mut progress_printed = false; while let Ok(update) = rx.recv() { // Print the final read/write statistics. - if update.complete { + if update.update_type == ProgUpdateType::Final { update.print_final_stats(print_level, progress_printed); return; } @@ -445,6 +443,48 @@ pub(crate) fn gen_prog_updater( } } +#[cfg(target_os = "linux")] +pub(crate) struct SignalHandler { + handle: Handle, + thread: Option>, +} + +#[cfg(target_os = "linux")] +impl SignalHandler { + pub(crate) fn install_signal_handler( + f: Box, + ) -> Result { + use signal_hook::consts::signal::*; + use signal_hook::iterator::Signals; + + let mut signals = Signals::new([SIGUSR1])?; + let handle = signals.handle(); + let thread = std::thread::spawn(move || { + for signal in &mut signals { + match signal { + SIGUSR1 => (*f)(), + _ => unreachable!(), + } + } + }); + + Ok(Self { + handle, + thread: Some(thread), + }) + } +} + +#[cfg(target_os = "linux")] +impl Drop for SignalHandler { + fn drop(&mut self) { + self.handle.close(); + if let Some(thread) = std::mem::take(&mut self.thread) { + thread.join().unwrap(); + } + } +} + /// Return a closure that can be used in its own thread to print progress info. /// /// This function returns a closure that receives [`ProgUpdate`] @@ -459,50 +499,31 @@ pub(crate) fn gen_prog_updater( rx: mpsc::Receiver, print_level: Option, ) -> impl Fn() { - // TODO: SIGINFO: Trigger progress line reprint. BSD-style Linux only. - const SIGUSR1_USIZE: usize = signal::SIGUSR1 as usize; - fn posixly_correct() -> bool { - env::var("POSIXLY_CORRECT").is_ok() - } - fn register_linux_signal_handler(sigval: Arc) -> Result<(), Box> { - if !posixly_correct() { - signal_hook::flag::register_usize(signal::SIGUSR1, sigval, SIGUSR1_USIZE)?; - } - - Ok(()) - } // -------------------------------------------------------------- move || { - let sigval = Arc::new(AtomicUsize::new(0)); - - register_linux_signal_handler(sigval.clone()).unwrap_or_else(|e| { - if Some(StatusLevel::None) != print_level { - eprintln!("Internal dd Warning: Unable to register signal handler \n\t{e}"); - } - }); - // Holds the state of whether we have printed the current progress. // This is needed so that we know whether or not to print a newline // character before outputting non-progress data. let mut progress_printed = false; while let Ok(update) = rx.recv() { - // Print the final read/write statistics. - if update.complete { - update.print_final_stats(print_level, progress_printed); - return; - } - // (Re)print status line if progress is requested. - if Some(StatusLevel::Progress) == print_level && !update.complete { - update.reprint_prog_line(); - progress_printed = true; - } - // Handle signals and set the signal to un-seen. - // This will print a maximum of 1 time per second, even though it - // should be printing on every SIGUSR1. - if let SIGUSR1_USIZE = sigval.swap(0, Ordering::Relaxed) { - update.print_transfer_stats(progress_printed); - // Reset the progress printed, since print_transfer_stats always prints a newline. - progress_printed = false; + match update.update_type { + ProgUpdateType::Final => { + // Print the final read/write statistics. + update.print_final_stats(print_level, progress_printed); + return; + } + ProgUpdateType::Periodic => { + // (Re)print status line if progress is requested. + if Some(StatusLevel::Progress) == print_level { + update.reprint_prog_line(); + progress_printed = true; + } + } + ProgUpdateType::Signal => { + update.print_transfer_stats(progress_printed); + // Reset the progress printed, since print_transfer_stats always prints a newline. + progress_printed = false; + } } } } @@ -524,7 +545,7 @@ mod tests { ..Default::default() }, duration: Duration::new(1, 0), // one second - complete: false, + update_type: super::ProgUpdateType::Periodic, } } @@ -533,7 +554,7 @@ mod tests { read_stat: ReadStat::default(), write_stat: WriteStat::default(), duration, - complete: false, + update_type: super::ProgUpdateType::Periodic, } } @@ -558,12 +579,12 @@ mod tests { let read_stat = ReadStat::new(1, 2, 3, 4); let write_stat = WriteStat::new(4, 5, 6); let duration = Duration::new(789, 0); - let complete = false; + let update_type = super::ProgUpdateType::Periodic; let prog_update = ProgUpdate { read_stat, write_stat, duration, - complete, + update_type, }; let mut cursor = Cursor::new(vec![]); @@ -580,7 +601,7 @@ mod tests { read_stat: ReadStat::default(), write_stat: WriteStat::default(), duration: Duration::new(1, 0), // one second - complete: false, + update_type: super::ProgUpdateType::Periodic, }; let mut cursor = Cursor::new(vec![]); @@ -636,7 +657,7 @@ mod tests { read_stat: ReadStat::default(), write_stat: WriteStat::default(), duration: Duration::new(1, 0), // one second - complete: false, + update_type: super::ProgUpdateType::Periodic, }; let mut cursor = Cursor::new(vec![]); prog_update @@ -657,7 +678,7 @@ mod tests { read_stat: ReadStat::default(), write_stat: WriteStat::default(), duration: Duration::new(1, 0), // one second - complete: false, + update_type: super::ProgUpdateType::Periodic, }; let mut cursor = Cursor::new(vec![]); let rewrite = true; From a626899416f8cddbd9f83af103e63cca394f881d Mon Sep 17 00:00:00 2001 From: Ulrich Hornung Date: Wed, 28 Feb 2024 22:17:50 +0100 Subject: [PATCH 002/144] reduce cognitive complexity by splitting away part of dd_copy --- src/uu/dd/src/dd.rs | 44 +++++++++++++++++++++++---------------- src/uu/dd/src/progress.rs | 1 + 2 files changed, 27 insertions(+), 18 deletions(-) diff --git a/src/uu/dd/src/dd.rs b/src/uu/dd/src/dd.rs index fb91f67552c..c3fca2fa1c0 100644 --- a/src/uu/dd/src/dd.rs +++ b/src/uu/dd/src/dd.rs @@ -86,8 +86,10 @@ struct Settings { /// A timer which triggers on a given interval /// -/// After being constructed with [`Alarm::with_interval`], [`Alarm::is_triggered`] -/// will return true once per the given [`Duration`]. +/// After being constructed with [`Alarm::with_interval`], [`Alarm::get_trigger`] +/// will return [`TRIGGER_TIMER`] once per the given [`Duration`]. +/// Alarm can be manually triggered with closure returned by [`Alarm::manual_trigger_fn`]. +/// [`Alarm::get_trigger`] will return [`TRIGGER_SIGNAL`] in this case. /// /// Can be cloned, but the trigger status is shared across all instances so only /// the first caller each interval will yield true. @@ -933,6 +935,27 @@ impl<'a> BlockWriter<'a> { } } +fn flush_caches_full_length(i: &Input, o: &Output) -> std::io::Result<()> { + // TODO Better error handling for overflowing `len`. + if i.settings.iflags.nocache { + let offset = 0; + #[allow(clippy::useless_conversion)] + let len = i.src.len()?.try_into().unwrap(); + i.discard_cache(offset, len); + } + // Similarly, discard the system cache for the output file. + // + // TODO Better error handling for overflowing `len`. + if i.settings.oflags.nocache { + let offset = 0; + #[allow(clippy::useless_conversion)] + let len = o.dst.len()?.try_into().unwrap(); + o.discard_cache(offset, len); + } + + Ok(()) +} + /// Copy the given input data to this output, consuming both. /// /// This method contains the main loop for the `dd` program. Bytes @@ -992,22 +1015,7 @@ fn dd_copy(mut i: Input, o: Output) -> std::io::Result<()> { // requests that we inform the system that we no longer // need the contents of the input file in a system cache. // - // TODO Better error handling for overflowing `len`. - if i.settings.iflags.nocache { - let offset = 0; - #[allow(clippy::useless_conversion)] - let len = i.src.len()?.try_into().unwrap(); - i.discard_cache(offset, len); - } - // Similarly, discard the system cache for the output file. - // - // TODO Better error handling for overflowing `len`. - if i.settings.oflags.nocache { - let offset = 0; - #[allow(clippy::useless_conversion)] - let len = o.dst.len()?.try_into().unwrap(); - o.discard_cache(offset, len); - } + flush_caches_full_length(&i, &o)?; return finalize( BlockWriter::Unbuffered(o), rstat, diff --git a/src/uu/dd/src/progress.rs b/src/uu/dd/src/progress.rs index 062fbbe8df6..268b3d5f4ea 100644 --- a/src/uu/dd/src/progress.rs +++ b/src/uu/dd/src/progress.rs @@ -443,6 +443,7 @@ pub(crate) fn gen_prog_updater( } } +/// signal handler listens for SIGUSR1 signal and runs provided closure. #[cfg(target_os = "linux")] pub(crate) struct SignalHandler { handle: Handle, From a35dafcd308d90cb27f0e8d9493de57e38bffe16 Mon Sep 17 00:00:00 2001 From: Ulrich Hornung Date: Sat, 9 Mar 2024 21:38:16 +0100 Subject: [PATCH 003/144] consider "fullblock" cmd line arg also for block writes --- src/uu/dd/src/dd.rs | 48 +++++++++++++++++++++++++++++++++------------ 1 file changed, 36 insertions(+), 12 deletions(-) diff --git a/src/uu/dd/src/dd.rs b/src/uu/dd/src/dd.rs index c3fca2fa1c0..7ab55c65a6b 100644 --- a/src/uu/dd/src/dd.rs +++ b/src/uu/dd/src/dd.rs @@ -87,9 +87,9 @@ struct Settings { /// A timer which triggers on a given interval /// /// After being constructed with [`Alarm::with_interval`], [`Alarm::get_trigger`] -/// will return [`TRIGGER_TIMER`] once per the given [`Duration`]. +/// will return [`ALARM_TRIGGER_TIMER`] once per the given [`Duration`]. /// Alarm can be manually triggered with closure returned by [`Alarm::manual_trigger_fn`]. -/// [`Alarm::get_trigger`] will return [`TRIGGER_SIGNAL`] in this case. +/// [`Alarm::get_trigger`] will return [`ALARM_TRIGGER_SIGNAL`] in this case. /// /// Can be cloned, but the trigger status is shared across all instances so only /// the first caller each interval will yield true. @@ -100,9 +100,9 @@ pub struct Alarm { trigger: Arc, } -const TRIGGER_NONE: u8 = 0; -const TRIGGER_TIMER: u8 = 1; -const TRIGGER_SIGNAL: u8 = 2; +pub const ALARM_TRIGGER_NONE: u8 = 0; +pub const ALARM_TRIGGER_TIMER: u8 = 1; +pub const ALARM_TRIGGER_SIGNAL: u8 = 2; impl Alarm { pub fn with_interval(interval: Duration) -> Self { @@ -112,7 +112,7 @@ impl Alarm { thread::spawn(move || { while let Some(trigger) = weak_trigger.upgrade() { thread::sleep(interval); - trigger.store(TRIGGER_TIMER, Relaxed); + trigger.store(ALARM_TRIGGER_TIMER, Relaxed); } }); @@ -123,13 +123,13 @@ impl Alarm { let weak_trigger = Arc::downgrade(&self.trigger); Box::new(move || { if let Some(trigger) = weak_trigger.upgrade() { - trigger.store(TRIGGER_SIGNAL, Relaxed); + trigger.store(ALARM_TRIGGER_SIGNAL, Relaxed); } }) } pub fn get_trigger(&self) -> u8 { - self.trigger.swap(TRIGGER_NONE, Relaxed) + self.trigger.swap(ALARM_TRIGGER_NONE, Relaxed) } pub fn get_interval(&self) -> Duration { @@ -831,6 +831,30 @@ impl<'a> Output<'a> { } } + /// writes a block of data. optionally retries when first try didn't complete + /// + /// this is needed by gnu-test: tests/dd/stats.s + /// the write can be interrupted by a system signal. + /// e.g. SIGUSR1 which is send to report status + /// without retry, the data might not be fully written to destination. + fn write_block(&mut self, chunk: &[u8]) -> io::Result { + let full_len = chunk.len(); + let mut base_idx = 0; + loop { + match self.dst.write(&chunk[base_idx..]) { + Ok(wlen) => { + base_idx += wlen; + // take iflags.fullblock as oflags shall not have this option + if (base_idx >= full_len) || !self.settings.iflags.fullblock { + return Ok(base_idx); + } + } + Err(e) if e.kind() == io::ErrorKind::Interrupted => continue, + Err(e) => return Err(e), + } + } + } + /// Write the given bytes one block at a time. /// /// This may write partial blocks (for example, if the underlying @@ -844,7 +868,7 @@ impl<'a> Output<'a> { let mut bytes_total = 0; for chunk in buf.chunks(self.settings.obs) { - let wlen = self.dst.write(chunk)?; + let wlen = self.write_block(chunk)?; if wlen < self.settings.obs { writes_partial += 1; } else { @@ -1118,10 +1142,10 @@ fn dd_copy(mut i: Input, o: Output) -> std::io::Result<()> { rstat += rstat_update; wstat += wstat_update; match alarm.get_trigger() { - TRIGGER_NONE => {} - t @ TRIGGER_TIMER | t @ TRIGGER_SIGNAL => { + ALARM_TRIGGER_NONE => {} + t @ ALARM_TRIGGER_TIMER | t @ ALARM_TRIGGER_SIGNAL => { let tp = match t { - TRIGGER_TIMER => ProgUpdateType::Periodic, + ALARM_TRIGGER_TIMER => ProgUpdateType::Periodic, _ => ProgUpdateType::Signal, }; let prog_update = ProgUpdate::new(rstat, wstat, start.elapsed(), tp); From 174e9a0af98dd55d0582d983cfc947fca41b7ca2 Mon Sep 17 00:00:00 2001 From: Ulrich Hornung Date: Sat, 23 Mar 2024 16:51:12 +0100 Subject: [PATCH 004/144] add documentation --- src/uu/dd/src/dd.rs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/uu/dd/src/dd.rs b/src/uu/dd/src/dd.rs index 7ab55c65a6b..24fab1e2fb3 100644 --- a/src/uu/dd/src/dd.rs +++ b/src/uu/dd/src/dd.rs @@ -105,6 +105,7 @@ pub const ALARM_TRIGGER_TIMER: u8 = 1; pub const ALARM_TRIGGER_SIGNAL: u8 = 2; impl Alarm { + /// use to construct alarm timer with duration pub fn with_interval(interval: Duration) -> Self { let trigger = Arc::new(AtomicU8::default()); @@ -119,6 +120,11 @@ impl Alarm { Self { interval, trigger } } + /// Returns a closure that allows to manually trigger the alarm + /// + /// This is useful for cases where more than one alarm even source exists + /// In case of `dd` there is the SIGUSR1/SIGINFO case where we want to + /// trigger an manual progress report. pub fn manual_trigger_fn(&self) -> Box { let weak_trigger = Arc::downgrade(&self.trigger); Box::new(move || { @@ -128,10 +134,17 @@ impl Alarm { }) } + /// Use this function to poll for any pending alarm event + /// + /// Returns `ALARM_TRIGGER_NONE` for no pending event. + /// Returns `ALARM_TRIGGER_TIMER` if the event was triggered by timer + /// Returns `ALARM_TRIGGER_SIGNAL` if the event was triggered manually + /// by the closure returned from `manual_trigger_fn` pub fn get_trigger(&self) -> u8 { self.trigger.swap(ALARM_TRIGGER_NONE, Relaxed) } + // Getter function for the configured interval duration pub fn get_interval(&self) -> Duration { self.interval } @@ -959,6 +972,8 @@ impl<'a> BlockWriter<'a> { } } +/// depending on the command line arguments, this function +/// informs the OS to flush/discard the caches for input and/or output file. fn flush_caches_full_length(i: &Input, o: &Output) -> std::io::Result<()> { // TODO Better error handling for overflowing `len`. if i.settings.iflags.nocache { From dcb53b6c9989b956ed4ebc4122cc85de0b384726 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Sat, 23 Mar 2024 22:41:31 +0100 Subject: [PATCH 005/144] head: add missing features --- src/uu/head/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/uu/head/Cargo.toml b/src/uu/head/Cargo.toml index 34b064ef6a9..04816fe82d4 100644 --- a/src/uu/head/Cargo.toml +++ b/src/uu/head/Cargo.toml @@ -17,7 +17,7 @@ path = "src/head.rs" [dependencies] clap = { workspace = true } memchr = { workspace = true } -uucore = { workspace = true, features = ["ringbuffer", "lines"] } +uucore = { workspace = true, features = ["ringbuffer", "lines", "fs"] } [[bin]] name = "head" From 44fa2e960aed29151d78f3de635db72202ba4391 Mon Sep 17 00:00:00 2001 From: Ben Wiederhake Date: Sat, 23 Mar 2024 22:27:09 +0100 Subject: [PATCH 006/144] csplit: correctly handle repeated arguments --- src/uu/csplit/src/csplit.rs | 1 + tests/by-util/test_csplit.rs | 36 ++++++++++++++++++++++++++++++++++++ 2 files changed, 37 insertions(+) diff --git a/src/uu/csplit/src/csplit.rs b/src/uu/csplit/src/csplit.rs index e4d7c243c23..b5004c3623b 100644 --- a/src/uu/csplit/src/csplit.rs +++ b/src/uu/csplit/src/csplit.rs @@ -585,6 +585,7 @@ pub fn uu_app() -> Command { .version(crate_version!()) .about(ABOUT) .override_usage(format_usage(USAGE)) + .args_override_self(true) .infer_long_args(true) .arg( Arg::new(options::SUFFIX_FORMAT) diff --git a/tests/by-util/test_csplit.rs b/tests/by-util/test_csplit.rs index b52c44b0e47..5bd384de31d 100644 --- a/tests/by-util/test_csplit.rs +++ b/tests/by-util/test_csplit.rs @@ -1376,3 +1376,39 @@ fn no_such_file() { .fails() .stderr_contains("cannot access 'in': No such file or directory"); } + +#[test] +fn repeat_everything() { + let (at, mut ucmd) = at_and_ucmd!(); + ucmd.args(&[ + "numbers50.txt", + "--suppress-matched", + "--suppress-matched", + "-kzsn", // spell-checker:disable-line + "2", + "-szkn3", // spell-checker:disable-line + "-b", + "%03d", + "-b%03x", + "-f", + "xxy_", + "-fxxz_", // spell-checker:disable-line + "/13/", + "9", + "{5}", + ]) + .fails() + .no_stdout() + .code_is(1) + .stderr_only("csplit: '9': line number out of range on repetition 5\n"); + let count = glob(&at.plus_as_string("xx*")) + .expect("there should be some splits created") + .count(); + assert_eq!(count, 6); + assert_eq!(at.read("xxz_000"), generate(1, 12 + 1)); + assert_eq!(at.read("xxz_001"), generate(14, 17 + 1)); // FIXME: GNU starts at 15 + assert_eq!(at.read("xxz_002"), generate(19, 26 + 1)); + assert_eq!(at.read("xxz_003"), generate(28, 35 + 1)); + assert_eq!(at.read("xxz_004"), generate(37, 44 + 1)); + assert_eq!(at.read("xxz_005"), generate(46, 50 + 1)); +} From 27fd3e5d3918570fc621a23e98166e28b4533bc1 Mon Sep 17 00:00:00 2001 From: Ben Wiederhake Date: Sat, 23 Mar 2024 23:14:26 +0100 Subject: [PATCH 007/144] csplit: do not emit remainder of input after an error --- src/uu/csplit/src/csplit.rs | 16 +++++++++------- tests/by-util/test_csplit.rs | 31 ++++++++++++------------------- 2 files changed, 21 insertions(+), 26 deletions(-) diff --git a/src/uu/csplit/src/csplit.rs b/src/uu/csplit/src/csplit.rs index b5004c3623b..cd55ef7dfcf 100644 --- a/src/uu/csplit/src/csplit.rs +++ b/src/uu/csplit/src/csplit.rs @@ -99,15 +99,17 @@ where let patterns: Vec = patterns::get_patterns(&patterns[..])?; let ret = do_csplit(&mut split_writer, patterns, &mut input_iter); - // consume the rest - input_iter.rewind_buffer(); - if let Some((_, line)) = input_iter.next() { - split_writer.new_writer()?; - split_writer.writeln(&line?)?; - for (_, line) in input_iter { + // consume the rest, unless there was an error + if ret.is_ok() { + input_iter.rewind_buffer(); + if let Some((_, line)) = input_iter.next() { + split_writer.new_writer()?; split_writer.writeln(&line?)?; + for (_, line) in input_iter { + split_writer.writeln(&line?)?; + } + split_writer.finish_split(); } - split_writer.finish_split(); } // delete files on error by default if ret.is_err() && !options.keep_files { diff --git a/tests/by-util/test_csplit.rs b/tests/by-util/test_csplit.rs index 5bd384de31d..03b8c92fc09 100644 --- a/tests/by-util/test_csplit.rs +++ b/tests/by-util/test_csplit.rs @@ -575,7 +575,7 @@ fn test_skip_to_match_context_underflow() { let (at, mut ucmd) = at_and_ucmd!(); ucmd.args(&["numbers50.txt", "%5%-10"]) .fails() - .stdout_is("141\n") + .stdout_is("") .stderr_is("csplit: '%5%-10': line number out of range\n"); let count = glob(&at.plus_as_string("xx*")) @@ -586,14 +586,13 @@ fn test_skip_to_match_context_underflow() { let (at, mut ucmd) = at_and_ucmd!(); ucmd.args(&["numbers50.txt", "%5%-10", "-k"]) .fails() - .stdout_is("141\n") + .stdout_is("") .stderr_is("csplit: '%5%-10': line number out of range\n"); let count = glob(&at.plus_as_string("xx*")) .expect("counting splits") .count(); - assert_eq!(count, 1); - assert_eq!(at.read("xx00"), generate(1, 51)); + assert_eq!(count, 0); } #[test] @@ -1225,13 +1224,12 @@ fn test_corner_case4() { assert_eq!(at.read("xx02"), generate(26, 51)); } -// NOTE: differs from gnu's output: the empty split is not written #[test] fn test_up_to_match_context_underflow() { let (at, mut ucmd) = at_and_ucmd!(); ucmd.args(&["numbers50.txt", "/5/-10"]) .fails() - .stdout_is("0\n141\n") + .stdout_is("0\n") .stderr_is("csplit: '/5/-10': line number out of range\n"); let count = glob(&at.plus_as_string("xx*")) @@ -1242,26 +1240,24 @@ fn test_up_to_match_context_underflow() { let (at, mut ucmd) = at_and_ucmd!(); ucmd.args(&["numbers50.txt", "/5/-10", "-k"]) .fails() - .stdout_is("0\n141\n") + .stdout_is("0\n") .stderr_is("csplit: '/5/-10': line number out of range\n"); let count = glob(&at.plus_as_string("xx*")) .expect("counting splits") .count(); - assert_eq!(count, 2); + assert_eq!(count, 1); assert_eq!(at.read("xx00"), ""); - assert_eq!(at.read("xx01"), generate(1, 51)); } // the offset is out of range because of the first pattern -// NOTE: output different than gnu's: the empty split is written but the rest of the input file is not #[test] fn test_line_num_range_with_up_to_match1() { let (at, mut ucmd) = at_and_ucmd!(); ucmd.args(&["numbers50.txt", "10", "/12/-5"]) .fails() .stderr_is("csplit: '/12/-5': line number out of range\n") - .stdout_is("18\n0\n123\n"); + .stdout_is("18\n0\n"); let count = glob(&at.plus_as_string("xx*")) .expect("there should be splits created") @@ -1272,26 +1268,24 @@ fn test_line_num_range_with_up_to_match1() { ucmd.args(&["numbers50.txt", "10", "/12/-5", "-k"]) .fails() .stderr_is("csplit: '/12/-5': line number out of range\n") - .stdout_is("18\n0\n123\n"); + .stdout_is("18\n0\n"); let count = glob(&at.plus_as_string("xx*")) .expect("there should be splits created") .count(); - assert_eq!(count, 3); + assert_eq!(count, 2); assert_eq!(at.read("xx00"), generate(1, 10)); assert_eq!(at.read("xx01"), ""); - assert_eq!(at.read("xx02"), generate(10, 51)); } // the offset is out of range because more lines are needed than physically available -// NOTE: output different than gnu's: the empty split is not written but the rest of the input file is #[test] fn test_line_num_range_with_up_to_match2() { let (at, mut ucmd) = at_and_ucmd!(); ucmd.args(&["numbers50.txt", "10", "/12/-15"]) .fails() .stderr_is("csplit: '/12/-15': line number out of range\n") - .stdout_is("18\n0\n123\n"); + .stdout_is("18\n0\n"); let count = glob(&at.plus_as_string("xx*")) .expect("there should be splits created") @@ -1302,15 +1296,14 @@ fn test_line_num_range_with_up_to_match2() { ucmd.args(&["numbers50.txt", "10", "/12/-15", "-k"]) .fails() .stderr_is("csplit: '/12/-15': line number out of range\n") - .stdout_is("18\n0\n123\n"); + .stdout_is("18\n0\n"); let count = glob(&at.plus_as_string("xx*")) .expect("there should be splits created") .count(); - assert_eq!(count, 3); + assert_eq!(count, 2); assert_eq!(at.read("xx00"), generate(1, 10)); assert_eq!(at.read("xx01"), ""); - assert_eq!(at.read("xx02"), generate(10, 51)); } // NOTE: output different than gnu's: the pattern /10/ is matched but should not From 1fa0b032e55cacd3f864eeb4f5e5869f25a5ee7c Mon Sep 17 00:00:00 2001 From: Ben Wiederhake Date: Sat, 23 Mar 2024 19:28:14 +0100 Subject: [PATCH 008/144] comm: permit and test separators that contain a hyphen --- src/uu/comm/src/comm.rs | 1 + tests/by-util/test_comm.rs | 16 ++++++++++++++++ .../comm/ab_delimiter_hyphen_help.expected | 3 +++ .../comm/ab_delimiter_hyphen_one.expected | 3 +++ 4 files changed, 23 insertions(+) create mode 100644 tests/fixtures/comm/ab_delimiter_hyphen_help.expected create mode 100644 tests/fixtures/comm/ab_delimiter_hyphen_one.expected diff --git a/src/uu/comm/src/comm.rs b/src/uu/comm/src/comm.rs index dd49ef53b02..d6366e4e53f 100644 --- a/src/uu/comm/src/comm.rs +++ b/src/uu/comm/src/comm.rs @@ -186,6 +186,7 @@ pub fn uu_app() -> Command { .help("separate columns with STR") .value_name("STR") .default_value(options::DELIMITER_DEFAULT) + .allow_hyphen_values(true) .hide_default_value(true), ) .arg( diff --git a/tests/by-util/test_comm.rs b/tests/by-util/test_comm.rs index e2bcc1c443f..f6608c7ebc3 100644 --- a/tests/by-util/test_comm.rs +++ b/tests/by-util/test_comm.rs @@ -91,6 +91,22 @@ fn output_delimiter() { .stdout_only_fixture("ab_delimiter_word.expected"); } +#[test] +fn output_delimiter_hyphen_one() { + new_ucmd!() + .args(&["--output-delimiter", "-1", "a", "b"]) + .succeeds() + .stdout_only_fixture("ab_delimiter_hyphen_one.expected"); +} + +#[test] +fn output_delimiter_hyphen_help() { + new_ucmd!() + .args(&["--output-delimiter", "--help", "a", "b"]) + .succeeds() + .stdout_only_fixture("ab_delimiter_hyphen_help.expected"); +} + #[test] fn output_delimiter_nul() { new_ucmd!() diff --git a/tests/fixtures/comm/ab_delimiter_hyphen_help.expected b/tests/fixtures/comm/ab_delimiter_hyphen_help.expected new file mode 100644 index 00000000000..c245aa27f13 --- /dev/null +++ b/tests/fixtures/comm/ab_delimiter_hyphen_help.expected @@ -0,0 +1,3 @@ +a +--helpb +--help--helpz diff --git a/tests/fixtures/comm/ab_delimiter_hyphen_one.expected b/tests/fixtures/comm/ab_delimiter_hyphen_one.expected new file mode 100644 index 00000000000..458f98dd42f --- /dev/null +++ b/tests/fixtures/comm/ab_delimiter_hyphen_one.expected @@ -0,0 +1,3 @@ +a +-1b +-1-1z From 801edbbcb4777ef404bb3a1af181815c92e2ebc5 Mon Sep 17 00:00:00 2001 From: Ben Wiederhake Date: Sat, 23 Mar 2024 20:52:58 +0100 Subject: [PATCH 009/144] comm: implement and test correct handling of repeated --output-delimiter --- src/uu/comm/src/comm.rs | 33 +++++++++++++++++++------- tests/by-util/test_comm.rs | 47 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 72 insertions(+), 8 deletions(-) diff --git a/src/uu/comm/src/comm.rs b/src/uu/comm/src/comm.rs index d6366e4e53f..931d0a0544b 100644 --- a/src/uu/comm/src/comm.rs +++ b/src/uu/comm/src/comm.rs @@ -9,7 +9,7 @@ use std::cmp::Ordering; use std::fs::File; use std::io::{self, stdin, BufRead, BufReader, Stdin}; use std::path::Path; -use uucore::error::{FromIo, UResult}; +use uucore::error::{FromIo, UResult, USimpleError}; use uucore::line_ending::LineEnding; use uucore::{format_usage, help_about, help_usage}; @@ -61,12 +61,7 @@ impl LineReader { } } -fn comm(a: &mut LineReader, b: &mut LineReader, opts: &ArgMatches) { - let delim = match opts.get_one::(options::DELIMITER).unwrap().as_str() { - "" => "\0", - delim => delim, - }; - +fn comm(a: &mut LineReader, b: &mut LineReader, delim: &str, opts: &ArgMatches) { let width_col_1 = usize::from(!opts.get_flag(options::COLUMN_1)); let width_col_2 = usize::from(!opts.get_flag(options::COLUMN_2)); @@ -152,7 +147,28 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { let mut f1 = open_file(filename1, line_ending).map_err_context(|| filename1.to_string())?; let mut f2 = open_file(filename2, line_ending).map_err_context(|| filename2.to_string())?; - comm(&mut f1, &mut f2, &matches); + // Due to default_value(), there must be at least one value here, thus unwrap() must not panic. + let all_delimiters = matches + .get_many::(options::DELIMITER) + .unwrap() + .map(String::from) + .collect::>(); + for delim in &all_delimiters[1..] { + // Note that this check is very different from ".conflicts_with_self(true).action(ArgAction::Set)", + // as this accepts duplicate *identical* arguments. + if delim != &all_delimiters[0] { + // Note: This intentionally deviate from the GNU error message by inserting the word "conflicting". + return Err(USimpleError::new( + 1, + "comm: multiple conflicting output delimiters specified", + )); + } + } + let delim = match &*all_delimiters[0] { + "" => "\0", + delim => delim, + }; + comm(&mut f1, &mut f2, delim, &matches); Ok(()) } @@ -187,6 +203,7 @@ pub fn uu_app() -> Command { .value_name("STR") .default_value(options::DELIMITER_DEFAULT) .allow_hyphen_values(true) + .action(ArgAction::Append) .hide_default_value(true), ) .arg( diff --git a/tests/by-util/test_comm.rs b/tests/by-util/test_comm.rs index f6608c7ebc3..ce3814eac83 100644 --- a/tests/by-util/test_comm.rs +++ b/tests/by-util/test_comm.rs @@ -107,6 +107,53 @@ fn output_delimiter_hyphen_help() { .stdout_only_fixture("ab_delimiter_hyphen_help.expected"); } +#[test] +fn output_delimiter_multiple_identical() { + new_ucmd!() + .args(&[ + "--output-delimiter=word", + "--output-delimiter=word", + "a", + "b", + ]) + .succeeds() + .stdout_only_fixture("ab_delimiter_word.expected"); +} + +#[test] +fn output_delimiter_multiple_different() { + new_ucmd!() + .args(&[ + "--output-delimiter=word", + "--output-delimiter=other", + "a", + "b", + ]) + .fails() + .no_stdout() + .stderr_contains("multiple") + .stderr_contains("output") + .stderr_contains("delimiters"); +} + +#[test] +#[ignore = "This is too weird; deviate intentionally."] +fn output_delimiter_multiple_different_prevents_help() { + new_ucmd!() + .args(&[ + "--output-delimiter=word", + "--output-delimiter=other", + "--help", + "a", + "b", + ]) + .fails() + .no_stdout() + .stderr_contains("multiple") + .stderr_contains("output") + .stderr_contains("delimiters"); +} + #[test] fn output_delimiter_nul() { new_ucmd!() From 884ef1f54b9d340285ce9c378973e1da9e29d6fc Mon Sep 17 00:00:00 2001 From: Ben Wiederhake Date: Sat, 23 Mar 2024 21:04:42 +0100 Subject: [PATCH 010/144] comm: implement and test correct handling of repeated flags --- src/uu/comm/src/comm.rs | 1 + tests/by-util/test_comm.rs | 8 ++++++++ 2 files changed, 9 insertions(+) diff --git a/src/uu/comm/src/comm.rs b/src/uu/comm/src/comm.rs index 931d0a0544b..ee15a070ee9 100644 --- a/src/uu/comm/src/comm.rs +++ b/src/uu/comm/src/comm.rs @@ -178,6 +178,7 @@ pub fn uu_app() -> Command { .about(ABOUT) .override_usage(format_usage(USAGE)) .infer_long_args(true) + .args_override_self(true) .arg( Arg::new(options::COLUMN_1) .short('1') diff --git a/tests/by-util/test_comm.rs b/tests/by-util/test_comm.rs index ce3814eac83..2dc385ef3f2 100644 --- a/tests/by-util/test_comm.rs +++ b/tests/by-util/test_comm.rs @@ -75,6 +75,14 @@ fn total_with_suppressed_regular_output() { .stdout_is_fixture("ab_total_suppressed_regular_output.expected"); } +#[test] +fn repeated_flags() { + new_ucmd!() + .args(&["--total", "-123123", "--total", "a", "b"]) + .succeeds() + .stdout_is_fixture("ab_total_suppressed_regular_output.expected"); +} + #[test] fn total_with_output_delimiter() { new_ucmd!() From 388021833e7f7aaf0d70730f612e23c9ec5318a7 Mon Sep 17 00:00:00 2001 From: Ben Wiederhake Date: Sun, 24 Mar 2024 00:06:44 +0100 Subject: [PATCH 011/144] factor: correctly handle repeated flag --- src/uu/factor/src/cli.rs | 1 + tests/by-util/test_factor.rs | 9 +++++++++ 2 files changed, 10 insertions(+) diff --git a/src/uu/factor/src/cli.rs b/src/uu/factor/src/cli.rs index d01ca625c7b..62a7efa6d7e 100644 --- a/src/uu/factor/src/cli.rs +++ b/src/uu/factor/src/cli.rs @@ -103,6 +103,7 @@ pub fn uu_app() -> Command { .override_usage(format_usage(USAGE)) .infer_long_args(true) .disable_help_flag(true) + .args_override_self(true) .arg(Arg::new(options::NUMBER).action(ArgAction::Append)) .arg( Arg::new(options::EXPONENTS) diff --git a/tests/by-util/test_factor.rs b/tests/by-util/test_factor.rs index bcab7d8c967..a0657139865 100644 --- a/tests/by-util/test_factor.rs +++ b/tests/by-util/test_factor.rs @@ -30,6 +30,15 @@ fn test_valid_arg_exponents() { new_ucmd!().arg("--exponents").succeeds().code_is(0); } +#[test] +fn test_repeated_exponents() { + new_ucmd!() + .args(&["-hh", "1234", "10240"]) + .succeeds() + .stdout_only("1234: 2 617\n10240: 2^11 5\n") + .no_stderr(); +} + #[test] #[cfg(feature = "sort")] #[cfg(not(target_os = "android"))] From 3bdb1605a7bf2ab63023c5ecf57525c22233c7bb Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sun, 24 Mar 2024 07:39:46 +0000 Subject: [PATCH 012/144] chore(deps): update rust crate rayon to 1.10 --- Cargo.lock | 4 ++-- Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f4d616dc518..75eccede7a4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1674,9 +1674,9 @@ dependencies = [ [[package]] name = "rayon" -version = "1.9.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4963ed1bc86e4f3ee217022bd855b297cef07fb9eac5dfa1f788b220b49b3bd" +checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" dependencies = [ "either", "rayon-core", diff --git a/Cargo.toml b/Cargo.toml index 68bdf8faed9..5b6ceca4aa2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -309,7 +309,7 @@ platform-info = "2.0.2" quick-error = "2.0.1" rand = { version = "0.8", features = ["small_rng"] } rand_core = "0.6" -rayon = "1.9" +rayon = "1.10" redox_syscall = "0.5" regex = "1.10.4" rstest = "0.18.2" From 3b80d75ee773b29fda1b895baab867ca27c1c150 Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Sun, 24 Mar 2024 10:45:15 +0100 Subject: [PATCH 013/144] show-utils.sh: fix jq query --- util/show-utils.sh | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/util/show-utils.sh b/util/show-utils.sh index f53a92cc817..834c3b8f9b2 100755 --- a/util/show-utils.sh +++ b/util/show-utils.sh @@ -33,6 +33,5 @@ cd "${project_main_dir}" && echo "WARN: missing \`jq\` (install with \`sudo apt install jq\`); falling back to default (only fully cross-platform) utility list" 1>&2 echo "$default_utils" else - cargo metadata "$@" --format-version 1 | jq -r "[.resolve.nodes[] | { id: .id, deps: [.deps[] | { name:.name, pkg:.pkg }] }] | .[] | select(.id|startswith(\"coreutils\")) | [.deps[] | select((.name|startswith(\"uu_\")) or (.pkg|startswith(\"uu_\")))] | [.[].pkg | match(\"^\\\w+\";\"g\")] | [.[].string | sub(\"^uu_\"; \"\")] | sort | join(\" \")" - # cargo metadata "$@" --format-version 1 | jq -r "[.resolve.nodes[] | { id: .id, deps: [.deps[] | { name:.name, pkg:.pkg }] }] | .[] | select(.id|startswith(\"coreutils\")) | [.deps[] | select((.name|startswith(\"uu_\")) or (.pkg|startswith(\"uu_\")))] | [.[].pkg | match(\"^\\\w+\";\"g\")] | [.[].string] | sort | join(\" \")" + cargo metadata "$@" --format-version 1 | jq -r '[.resolve.nodes[] | select(.id|match(".*coreutils#\\d+\\.\\d+\\.\\d+")) | .deps[] | select(.pkg|match("uu_")) | .name | sub("^uu_"; "")] | sort | join(" ")' fi From 5803d3b6836778756023b06ca16d1d20849277a8 Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Sun, 24 Mar 2024 16:09:15 +0100 Subject: [PATCH 014/144] comm: remove "comm" from error msg --- src/uu/comm/src/comm.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/uu/comm/src/comm.rs b/src/uu/comm/src/comm.rs index ee15a070ee9..f8371729284 100644 --- a/src/uu/comm/src/comm.rs +++ b/src/uu/comm/src/comm.rs @@ -160,7 +160,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { // Note: This intentionally deviate from the GNU error message by inserting the word "conflicting". return Err(USimpleError::new( 1, - "comm: multiple conflicting output delimiters specified", + "multiple conflicting output delimiters specified", )); } } From 7b928f792ce5f451ac4725957b6f5a92809c13fc Mon Sep 17 00:00:00 2001 From: Joseph Jon Booker Date: Fri, 22 Mar 2024 21:30:03 -0500 Subject: [PATCH 015/144] fmt: allow negative widths as first argument Also fix error messages for consistency with GNU fmt --- src/uu/fmt/fmt.md | 2 +- src/uu/fmt/src/fmt.rs | 187 +++++++++++++++++++++++++++++++++----- tests/by-util/test_fmt.rs | 35 +++++++ 3 files changed, 199 insertions(+), 25 deletions(-) diff --git a/src/uu/fmt/fmt.md b/src/uu/fmt/fmt.md index 5cf4fa6e632..6ed7d3048a5 100644 --- a/src/uu/fmt/fmt.md +++ b/src/uu/fmt/fmt.md @@ -1,7 +1,7 @@ # fmt ``` -fmt [OPTION]... [FILE]... +fmt [-WIDTH] [OPTION]... [FILE]... ``` Reformat paragraphs from input files (or stdin) to stdout. diff --git a/src/uu/fmt/src/fmt.rs b/src/uu/fmt/src/fmt.rs index 7e10c41e7eb..eedfded0546 100644 --- a/src/uu/fmt/src/fmt.rs +++ b/src/uu/fmt/src/fmt.rs @@ -9,8 +9,8 @@ use clap::{crate_version, Arg, ArgAction, ArgMatches, Command}; use std::fs::File; use std::io::{stdin, stdout, BufReader, BufWriter, Read, Stdout, Write}; use uucore::display::Quotable; -use uucore::error::{FromIo, UResult, USimpleError}; -use uucore::{format_usage, help_about, help_usage, show_warning}; +use uucore::error::{FromIo, UResult, USimpleError, UUsageError}; +use uucore::{format_usage, help_about, help_usage}; use linebreak::break_lines; use parasplit::ParagraphStream; @@ -40,10 +40,11 @@ mod options { pub const GOAL: &str = "goal"; pub const QUICK: &str = "quick"; pub const TAB_WIDTH: &str = "tab-width"; - pub const FILES: &str = "files"; + pub const FILES_OR_WIDTH: &str = "files"; } pub type FileOrStdReader = BufReader>; + pub struct FmtOptions { crown: bool, tagged: bool, @@ -86,16 +87,16 @@ impl FmtOptions { .get_one::(options::SKIP_PREFIX) .map(String::from); - let width_opt = matches.get_one::(options::WIDTH); + let width_opt = extract_width(matches); let goal_opt = matches.get_one::(options::GOAL); let (width, goal) = match (width_opt, goal_opt) { - (Some(&w), Some(&g)) => { + (Some(w), Some(&g)) => { if g > w { return Err(USimpleError::new(1, "GOAL cannot be greater than WIDTH.")); } (w, g) } - (Some(&w), None) => { + (Some(w), None) => { // Only allow a goal of zero if the width is set to be zero let g = (w * DEFAULT_GOAL_TO_WIDTH_RATIO / 100).max(if w == 0 { 0 } else { 1 }); (w, g) @@ -169,16 +170,14 @@ fn process_file( fmt_opts: &FmtOptions, ostream: &mut BufWriter, ) -> UResult<()> { - let mut fp = match file_name { - "-" => BufReader::new(Box::new(stdin()) as Box), - _ => match File::open(file_name) { - Ok(f) => BufReader::new(Box::new(f) as Box), - Err(e) => { - show_warning!("{}: {}", file_name.maybe_quote(), e); - return Ok(()); - } - }, - }; + let mut fp = BufReader::new(match file_name { + "-" => Box::new(stdin()) as Box, + _ => { + let f = File::open(file_name) + .map_err_context(|| format!("cannot open {} for reading", file_name.quote()))?; + Box::new(f) as Box + } + }); let p_stream = ParagraphStream::new(fmt_opts, &mut fp); for para_result in p_stream { @@ -204,14 +203,90 @@ fn process_file( Ok(()) } +/// Extract the file names from the positional arguments, ignoring any negative width in the first +/// position. +/// +/// # Returns +/// A `UResult<()>` with the file names, or an error if one of the file names could not be parsed +/// (e.g., it is given as a negative number not in the first argument and not after a -- +fn extract_files(matches: &ArgMatches) -> UResult> { + let in_first_pos = matches + .index_of(options::FILES_OR_WIDTH) + .is_some_and(|x| x == 1); + let is_neg = |s: &str| s.parse::().is_ok_and(|w| w < 0); + + let files: UResult> = matches + .get_many::(options::FILES_OR_WIDTH) + .into_iter() + .flatten() + .enumerate() + .filter_map(|(i, x)| { + if is_neg(x) { + if in_first_pos && i == 0 { + None + } else { + let first_num = x.chars().nth(1).expect("a negative number should be at least two characters long"); + Some(Err( + UUsageError::new(1, format!("invalid option -- {}; -WIDTH is recognized only when it is the first\noption; use -w N instead", first_num)) + )) + } + } else { + Some(Ok(x.clone())) + } + }) + .collect(); + + if files.as_ref().is_ok_and(|f| f.is_empty()) { + Ok(vec!["-".into()]) + } else { + files + } +} + +fn extract_width(matches: &ArgMatches) -> Option { + let width_opt = matches.get_one::(options::WIDTH); + if let Some(width) = width_opt { + return Some(*width); + } + + if let Some(1) = matches.index_of(options::FILES_OR_WIDTH) { + let width_arg = matches.get_one::(options::FILES_OR_WIDTH).unwrap(); + if let Some(num) = width_arg.strip_prefix('-') { + num.parse::().ok() + } else { + // will be treated as a file name + None + } + } else { + None + } +} + #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let matches = uu_app().try_get_matches_from(args)?; + let args: Vec<_> = args.collect(); - let files: Vec = matches - .get_many::(options::FILES) - .map(|v| v.map(ToString::to_string).collect()) - .unwrap_or(vec!["-".into()]); + // Warn the user if it looks like we're trying to pass a number in the first + // argument with non-numeric characters + if let Some(first_arg) = args.get(1) { + let first_arg = first_arg.to_string_lossy(); + let malformed_number = first_arg.starts_with('-') + && first_arg.chars().nth(1).is_some_and(|c| c.is_ascii_digit()) + && first_arg.chars().skip(2).any(|c| !c.is_ascii_digit()); + if malformed_number { + return Err(USimpleError::new( + 1, + format!( + "invalid width: {}", + first_arg.strip_prefix('-').unwrap().quote() + ), + )); + } + } + + let matches = uu_app().try_get_matches_from(&args)?; + + let files = extract_files(&matches)?; let fmt_opts = FmtOptions::from_matches(&matches)?; @@ -329,7 +404,7 @@ pub fn uu_app() -> Command { Arg::new(options::WIDTH) .short('w') .long("width") - .help("Fill output lines up to a maximum of WIDTH columns, default 75.") + .help("Fill output lines up to a maximum of WIDTH columns, default 75. This can be specified as a negative number in the first argument.") .value_name("WIDTH") .value_parser(clap::value_parser!(usize)), ) @@ -363,8 +438,72 @@ pub fn uu_app() -> Command { .value_name("TABWIDTH"), ) .arg( - Arg::new(options::FILES) + Arg::new(options::FILES_OR_WIDTH) .action(ArgAction::Append) - .value_hint(clap::ValueHint::FilePath), + .value_name("FILES") + .value_hint(clap::ValueHint::FilePath) + .allow_negative_numbers(true), ) } + +#[cfg(test)] +mod tests { + use std::error::Error; + + use crate::uu_app; + use crate::{extract_files, extract_width}; + + #[test] + fn parse_negative_width() -> Result<(), Box> { + let matches = uu_app().try_get_matches_from(vec!["fmt", "-3", "some-file"])?; + + assert_eq!(extract_files(&matches).unwrap(), vec!["some-file"]); + + assert_eq!(extract_width(&matches), Some(3)); + + Ok(()) + } + + #[test] + fn parse_width_as_arg() -> Result<(), Box> { + let matches = uu_app().try_get_matches_from(vec!["fmt", "-w3", "some-file"])?; + + assert_eq!(extract_files(&matches).unwrap(), vec!["some-file"]); + + assert_eq!(extract_width(&matches), Some(3)); + + Ok(()) + } + + #[test] + fn parse_no_args() -> Result<(), Box> { + let matches = uu_app().try_get_matches_from(vec!["fmt"])?; + + assert_eq!(extract_files(&matches).unwrap(), vec!["-"]); + + assert_eq!(extract_width(&matches), None); + + Ok(()) + } + + #[test] + fn parse_just_file_name() -> Result<(), Box> { + let matches = uu_app().try_get_matches_from(vec!["fmt", "some-file"])?; + + assert_eq!(extract_files(&matches).unwrap(), vec!["some-file"]); + + assert_eq!(extract_width(&matches), None); + + Ok(()) + } + + #[test] + fn parse_with_both_widths_positional_first() -> Result<(), Box> { + let matches = uu_app().try_get_matches_from(vec!["fmt", "-10", "-w3", "some-file"])?; + + assert_eq!(extract_files(&matches).unwrap(), vec!["some-file"]); + + assert_eq!(extract_width(&matches), Some(3)); + Ok(()) + } +} diff --git a/tests/by-util/test_fmt.rs b/tests/by-util/test_fmt.rs index 8d50023f38e..3b3c0c5d2de 100644 --- a/tests/by-util/test_fmt.rs +++ b/tests/by-util/test_fmt.rs @@ -37,6 +37,14 @@ fn test_fmt_width() { } } +#[test] +fn test_fmt_positional_width() { + new_ucmd!() + .args(&["-10", "one-word-per-line.txt"]) + .succeeds() + .stdout_is("this is a\nfile with\none word\nper line\n"); +} + #[test] fn test_small_width() { for width in ["0", "1", "2", "3"] { @@ -71,6 +79,24 @@ fn test_fmt_invalid_width() { } } +#[test] +fn test_fmt_positional_width_not_first() { + new_ucmd!() + .args(&["one-word-per-line.txt", "-10"]) + .fails() + .code_is(1) + .stderr_contains("fmt: invalid option -- 1; -WIDTH is recognized only when it is the first\noption; use -w N instead"); +} + +#[test] +fn test_fmt_width_not_valid_number() { + new_ucmd!() + .args(&["-25x", "one-word-per-line.txt"]) + .fails() + .code_is(1) + .stderr_contains("fmt: invalid width: '25x'"); +} + #[ignore] #[test] fn test_fmt_goal() { @@ -104,6 +130,15 @@ fn test_fmt_goal_bigger_than_default_width_of_75() { } } +#[test] +fn test_fmt_non_existent_file() { + new_ucmd!() + .args(&["non-existing"]) + .fails() + .code_is(1) + .stderr_is("fmt: cannot open 'non-existing' for reading: No such file or directory\n"); +} + #[test] fn test_fmt_invalid_goal() { for param in ["-g", "--goal"] { From d060134d9754d182548a8af258f2e9032e3c2696 Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Mon, 25 Mar 2024 09:40:36 +0100 Subject: [PATCH 016/144] `uniq`: print version and help on stdout again (#6123) * uniq: print version and help on stdout again * uniq: format test * uniq: replace redundant closure with fn --------- Co-authored-by: Daniel Hofstetter --- src/uu/uniq/src/uniq.rs | 6 +++--- tests/by-util/test_uniq.rs | 14 ++++++++++++++ 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/src/uu/uniq/src/uniq.rs b/src/uu/uniq/src/uniq.rs index e074ebe42d7..2c4f9b78114 100644 --- a/src/uu/uniq/src/uniq.rs +++ b/src/uu/uniq/src/uniq.rs @@ -518,7 +518,7 @@ fn handle_extract_obs_skip_chars( /// Unfortunately these overrides are necessary, since several GNU tests /// for `uniq` hardcode and require the exact wording of the error message /// and it is not compatible with how Clap formats and displays those error messages. -fn map_clap_errors(clap_error: &Error) -> Box { +fn map_clap_errors(clap_error: Error) -> Box { let footer = "Try 'uniq --help' for more information."; let override_arg_conflict = "--group is mutually exclusive with -c/-d/-D/-u\n".to_string() + footer; @@ -547,7 +547,7 @@ fn map_clap_errors(clap_error: &Error) -> Box { { override_all_repeated_badoption } - _ => clap_error.to_string(), + _ => return clap_error.into(), }; USimpleError::new(1, error_message) } @@ -558,7 +558,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { let matches = uu_app() .try_get_matches_from(args) - .map_err(|e| map_clap_errors(&e))?; + .map_err(map_clap_errors)?; let files = matches.get_many::(ARG_FILES); diff --git a/tests/by-util/test_uniq.rs b/tests/by-util/test_uniq.rs index dd055402f2f..911f1449088 100644 --- a/tests/by-util/test_uniq.rs +++ b/tests/by-util/test_uniq.rs @@ -18,6 +18,20 @@ fn test_invalid_arg() { new_ucmd!().arg("--definitely-invalid").fails().code_is(1); } +#[test] +fn test_help_and_version_on_stdout() { + new_ucmd!() + .arg("--help") + .succeeds() + .no_stderr() + .stdout_contains("Usage"); + new_ucmd!() + .arg("--version") + .succeeds() + .no_stderr() + .stdout_contains("uniq"); +} + #[test] fn test_stdin_default() { new_ucmd!() From e1adc1866e400b8711ed3c41c16af7755d3b98f8 Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Mon, 25 Mar 2024 10:37:15 +0100 Subject: [PATCH 017/144] Cargo.toml: remove unnecessary caret --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 5b6ceca4aa2..c81a235a476 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -263,7 +263,7 @@ binary-heap-plus = "0.5.0" bstr = "1.9.1" bytecount = "0.6.7" byteorder = "1.5.0" -chrono = { version = "^0.4.35", default-features = false, features = [ +chrono = { version = "0.4.35", default-features = false, features = [ "std", "alloc", "clock", From 3bf5582dbbe7ddd2aba8335882ed277850dfbb53 Mon Sep 17 00:00:00 2001 From: Lucas Larson Date: Tue, 26 Mar 2024 13:44:23 -0400 Subject: [PATCH 018/144] docs: date: Remove unimplemented example also treated in #2685 and #3463 Signed-off-by: Lucas Larson --- src/uu/date/date-usage.md | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/uu/date/date-usage.md b/src/uu/date/date-usage.md index bf2dc469d3a..109bfd3988b 100644 --- a/src/uu/date/date-usage.md +++ b/src/uu/date/date-usage.md @@ -79,9 +79,3 @@ Show the time on the west coast of the US (use tzselect(1) to find TZ) ``` TZ='America/Los_Angeles' date ``` - -Show the local time for 9AM next Friday on the west coast of the US - -``` -date --date='TZ="America/Los_Angeles" 09:00 next Fri' -``` From 2b790e4808249209f96b95d276d961cdf8e1def1 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 27 Mar 2024 13:41:05 +0000 Subject: [PATCH 019/144] chore(deps): update rust crate chrono to 0.4.37 --- Cargo.lock | 4 ++-- Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 75eccede7a4..f81aee26a0e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -244,9 +244,9 @@ checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e" [[package]] name = "chrono" -version = "0.4.35" +version = "0.4.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8eaf5903dcbc0a39312feb77df2ff4c76387d591b9fc7b04a238dcf8bb62639a" +checksum = "8a0d04d43504c61aa6c7531f1871dd0d418d91130162063b789da00fd7057a5e" dependencies = [ "android-tzdata", "iana-time-zone", diff --git a/Cargo.toml b/Cargo.toml index c81a235a476..0354c251a4a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -263,7 +263,7 @@ binary-heap-plus = "0.5.0" bstr = "1.9.1" bytecount = "0.6.7" byteorder = "1.5.0" -chrono = { version = "0.4.35", default-features = false, features = [ +chrono = { version = "0.4.37", default-features = false, features = [ "std", "alloc", "clock", From 3222c9225c5fc6df232ea2859da3226ed16f14b3 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Thu, 28 Mar 2024 18:23:59 +0100 Subject: [PATCH 020/144] upgrade to GNU coreutils 9.5 as ref --- .github/workflows/GnuTests.yml | 4 ++-- util/build-gnu.sh | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/GnuTests.yml b/.github/workflows/GnuTests.yml index 6a4676a7913..eefba654c96 100644 --- a/.github/workflows/GnuTests.yml +++ b/.github/workflows/GnuTests.yml @@ -46,7 +46,7 @@ jobs: outputs path_GNU path_GNU_tests path_reference path_UUTILS # repo_default_branch="${{ github.event.repository.default_branch }}" - repo_GNU_ref="v9.4" + repo_GNU_ref="v9.5" repo_reference_branch="${{ github.event.repository.default_branch }}" outputs repo_default_branch repo_GNU_ref repo_reference_branch # @@ -344,7 +344,7 @@ jobs: with: repository: 'coreutils/coreutils' path: 'gnu' - ref: 'v9.4' + ref: 'v9.5' submodules: recursive - uses: dtolnay/rust-toolchain@master with: diff --git a/util/build-gnu.sh b/util/build-gnu.sh index 876e645faed..c1853f765c0 100755 --- a/util/build-gnu.sh +++ b/util/build-gnu.sh @@ -60,7 +60,7 @@ fi ### -release_tag_GNU="v9.4" +release_tag_GNU="v9.5" if test ! -d "${path_GNU}"; then echo "Could not find GNU coreutils (expected at '${path_GNU}')" From d535095d0f5ef340022b3b6581642f9fe302bad8 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Thu, 28 Mar 2024 21:10:09 +0100 Subject: [PATCH 021/144] build-gnu.sh: adjust of some of the path --- util/build-gnu.sh | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/util/build-gnu.sh b/util/build-gnu.sh index c1853f765c0..6a4b09f89d8 100755 --- a/util/build-gnu.sh +++ b/util/build-gnu.sh @@ -258,15 +258,15 @@ sed -i -e "s|invalid suffix in --pages argument|invalid --pages argument|" \ # When decoding an invalid base32/64 string, gnu writes everything it was able to decode until # it hit the decode error, while we don't write anything if the input is invalid. -sed -i "s/\(baddecode.*OUT=>\"\).*\"/\1\"/g" tests/misc/base64.pl -sed -i "s/\(\(b2[ml]_[69]\|b32h_[56]\|z85_8\|z85_35\).*OUT=>\)[^}]*\(.*\)/\1\"\"\3/g" tests/misc/basenc.pl +sed -i "s/\(baddecode.*OUT=>\"\).*\"/\1\"/g" tests/basenc/base64.pl +sed -i "s/\(\(b2[ml]_[69]\|b32h_[56]\|z85_8\|z85_35\).*OUT=>\)[^}]*\(.*\)/\1\"\"\3/g" tests/basenc/basenc.pl # add "error: " to the expected error message -sed -i "s/\$prog: invalid input/\$prog: error: invalid input/g" tests/misc/basenc.pl +sed -i "s/\$prog: invalid input/\$prog: error: invalid input/g" tests/basenc/basenc.pl # basenc: swap out error message for unexpected arg -sed -i "s/ {ERR=>\"\$prog: foobar\\\\n\" \. \$try_help }/ {ERR=>\"error: Found argument '--foobar' which wasn't expected, or isn't valid in this context\n\n If you tried to supply '--foobar' as a value rather than a flag, use '-- --foobar'\n\nUsage: basenc [OPTION]... [FILE]\n\nFor more information try '--help'\n\"}]/" tests/misc/basenc.pl -sed -i "s/ {ERR_SUBST=>\"s\/(unrecognized|unknown) option \[-' \]\*foobar\[' \]\*\/foobar\/\"}],//" tests/misc/basenc.pl +sed -i "s/ {ERR=>\"\$prog: foobar\\\\n\" \. \$try_help }/ {ERR=>\"error: Found argument '--foobar' which wasn't expected, or isn't valid in this context\n\n If you tried to supply '--foobar' as a value rather than a flag, use '-- --foobar'\n\nUsage: basenc [OPTION]... [FILE]\n\nFor more information try '--help'\n\"}]/" tests/basenc/basenc.pl +sed -i "s/ {ERR_SUBST=>\"s\/(unrecognized|unknown) option \[-' \]\*foobar\[' \]\*\/foobar\/\"}],//" tests/basenc/basenc.pl # Remove the check whether a util was built. Otherwise tests against utils like "arch" are not run. sed -i "s|require_built_ |# require_built_ |g" init.cfg From fb6c31a63980cd9d6b829ff10b79e98ae8fa88e5 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Thu, 28 Mar 2024 22:49:38 +0100 Subject: [PATCH 022/144] Doc: Explain that it is allowed to look at OpenBSD or Apple sources and look to Apple impl --- CONTRIBUTING.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index b10d3d11472..dfee460029d 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -18,7 +18,9 @@ Now follows a very important 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. +> the GNU source code** either. It is however possible to look at other implementations +> under a BSD or MIT license like [Apple's implementation](https://opensource.apple.com/source/file_cmds/) +> or [OpenBSD](https://github.com/openbsd/src/tree/master/bin). Finally, feel free to join our [Discord](https://discord.gg/wQVJbvJ)! @@ -303,6 +305,7 @@ completions: - [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) +- [Mac OS](https://opensource.apple.com/source/file_cmds/) - [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) From 76a2f2128bec048bd3d77259585ffbcb56572d10 Mon Sep 17 00:00:00 2001 From: Ben Wiederhake Date: Fri, 29 Mar 2024 00:50:33 +0100 Subject: [PATCH 023/144] tr: guard against regressions of class [:space:] --- tests/by-util/test_tr.rs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/tests/by-util/test_tr.rs b/tests/by-util/test_tr.rs index 6adbc4022a0..c2f69014a86 100644 --- a/tests/by-util/test_tr.rs +++ b/tests/by-util/test_tr.rs @@ -1283,3 +1283,19 @@ fn test_complement_flag_fails_with_more_than_two_operand() { .fails() .stderr_contains("extra operand 'c'"); } + +#[test] +fn check_regression_class_space() { + // This invocation checks: + // 1. that the [:space:] class has exactly 6 characters, + // 2. that the [:space:] class contains at least the given 6 characters (and therefore no other characters), and + // 3. that the given characters occur in exactly this order. + new_ucmd!() + .args(&["[:space:][:upper:]", "123456[:lower:]"]) + // 0x0B = "\v" ("VERTICAL TAB") + // 0x0C = "\f" ("FEED FORWARD") + .pipe_in("A\t\n\u{0B}\u{0C}\r B") + .succeeds() + .no_stderr() + .stdout_only("a123456b"); +} From e9045be593b2b4ab9559993d0233e283be78ef92 Mon Sep 17 00:00:00 2001 From: Ben Wiederhake Date: Fri, 29 Mar 2024 00:55:34 +0100 Subject: [PATCH 024/144] tr: fix order inside class [:blank:] --- src/uu/tr/src/unicode_table.rs | 2 +- tests/by-util/test_tr.rs | 14 ++++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/src/uu/tr/src/unicode_table.rs b/src/uu/tr/src/unicode_table.rs index 5d31ab37569..06687c3926e 100644 --- a/src/uu/tr/src/unicode_table.rs +++ b/src/uu/tr/src/unicode_table.rs @@ -12,4 +12,4 @@ pub static FF: u8 = 0xC; pub static CR: u8 = 0xD; pub static SPACE: u8 = 0x20; pub static SPACES: &[u8] = &[HT, LF, VT, FF, CR, SPACE]; -pub static BLANK: &[u8] = &[SPACE, HT]; +pub static BLANK: &[u8] = &[HT, SPACE]; diff --git a/tests/by-util/test_tr.rs b/tests/by-util/test_tr.rs index c2f69014a86..2502031b130 100644 --- a/tests/by-util/test_tr.rs +++ b/tests/by-util/test_tr.rs @@ -1299,3 +1299,17 @@ fn check_regression_class_space() { .no_stderr() .stdout_only("a123456b"); } + +#[test] +fn check_regression_class_blank() { + // This invocation checks: + // 1. that the [:blank:] class has exactly 2 characters, + // 2. that the [:blank:] class contains at least the given 2 characters (and therefore no other characters), and + // 3. that the given characters occur in exactly this order. + new_ucmd!() + .args(&["[:blank:][:upper:]", "12[:lower:]"]) + .pipe_in("A\t B") + .succeeds() + .no_stderr() + .stdout_only("a12b"); +} From ee198126af6d34f16ad110f46dd1254cd5433527 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Fri, 29 Mar 2024 21:48:38 +0100 Subject: [PATCH 025/144] mktemp: adjust the error message to match 9.5 --- src/uu/mktemp/src/mktemp.rs | 7 ++++++- tests/by-util/test_mktemp.rs | 4 ++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/uu/mktemp/src/mktemp.rs b/src/uu/mktemp/src/mktemp.rs index ae44225c9a6..00f23c50adc 100644 --- a/src/uu/mktemp/src/mktemp.rs +++ b/src/uu/mktemp/src/mktemp.rs @@ -236,8 +236,13 @@ impl Params { let (i, j) = match find_last_contiguous_block_of_xs(&options.template) { None => { let s = match options.suffix { + // If a suffix is specified, the error message includes the template without the suffix. + Some(_) => options + .template + .chars() + .take(options.template.len()) + .collect::(), None => options.template, - Some(s) => format!("{}{}", options.template, s), }; return Err(MkTempError::TooFewXs(s)); } diff --git a/tests/by-util/test_mktemp.rs b/tests/by-util/test_mktemp.rs index 611a42e43c8..8a919948cec 100644 --- a/tests/by-util/test_mktemp.rs +++ b/tests/by-util/test_mktemp.rs @@ -629,7 +629,7 @@ fn test_too_few_xs_suffix() { new_ucmd!() .args(&["--suffix=X", "aXX"]) .fails() - .stderr_only("mktemp: too few X's in template 'aXXX'\n"); + .stderr_only("mktemp: too few X's in template 'aXX'\n"); } #[test] @@ -637,7 +637,7 @@ fn test_too_few_xs_suffix_directory() { new_ucmd!() .args(&["-d", "--suffix=X", "aXX"]) .fails() - .stderr_only("mktemp: too few X's in template 'aXXX'\n"); + .stderr_only("mktemp: too few X's in template 'aXX'\n"); } #[test] From f0286eb77dbb24242af95581950b7aa9f15cf46f Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Sat, 30 Mar 2024 07:27:48 +0100 Subject: [PATCH 026/144] tail: allow multiple usage of --pid to match upstream (regression of 9.5) tested by tests/tail/pid --- src/uu/tail/src/args.rs | 3 ++- tests/by-util/test_tail.rs | 7 +++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/uu/tail/src/args.rs b/src/uu/tail/src/args.rs index 9b172987233..6ae5e68bddb 100644 --- a/src/uu/tail/src/args.rs +++ b/src/uu/tail/src/args.rs @@ -510,7 +510,8 @@ pub fn uu_app() -> Command { Arg::new(options::PID) .long(options::PID) .value_name("PID") - .help("With -f, terminate after process ID, PID dies"), + .help("With -f, terminate after process ID, PID dies") + .overrides_with(options::PID), ) .arg( Arg::new(options::verbosity::QUIET) diff --git a/tests/by-util/test_tail.rs b/tests/by-util/test_tail.rs index c7ca09af8b9..cc5a76c31a1 100644 --- a/tests/by-util/test_tail.rs +++ b/tests/by-util/test_tail.rs @@ -3908,6 +3908,13 @@ fn test_args_when_settings_check_warnings_then_shows_warnings() { .args(&["--pid=1000", "--retry", "data"]) .stderr_to_stdout() .run() + .stdout_only(&expected_stdout) + .success(); + scene + .ucmd() + .args(&["--pid=1000", "--pid=1000", "--retry", "data"]) + .stderr_to_stdout() + .run() .stdout_only(expected_stdout) .success(); } From a2f8084d488645d4c5b29b0bf0afee6cd2a3b5a5 Mon Sep 17 00:00:00 2001 From: Michael Vogt Date: Sat, 30 Mar 2024 11:17:22 +0100 Subject: [PATCH 027/144] tests: fix deprecation warning `timestamp_subsec_nanos()` When building coreutils I got the following deprecation warning: ``` warning: use of deprecated method `chrono::NaiveDateTime::timestamp_subsec_nanos`: use `.and_utc().timestamp_subsec_nanos()` instead --> tests/by-util/test_touch.rs:37:59 | 37 | ..._utc().timestamp(), tm.timestamp_subsec_nanos()) | ^^^^^^^^^^^^^^^^^^^^^^ | = note: `#[warn(deprecated)]` on by default warning: `coreutils` (test "tests") generated 1 warning ``` This commit fixes it. --- tests/by-util/test_touch.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/by-util/test_touch.rs b/tests/by-util/test_touch.rs index 5910da7f8a1..03df2973dbc 100644 --- a/tests/by-util/test_touch.rs +++ b/tests/by-util/test_touch.rs @@ -34,7 +34,7 @@ fn set_file_times(at: &AtPath, path: &str, atime: FileTime, mtime: FileTime) { fn str_to_filetime(format: &str, s: &str) -> FileTime { let tm = chrono::NaiveDateTime::parse_from_str(s, format).unwrap(); - FileTime::from_unix_time(tm.and_utc().timestamp(), tm.timestamp_subsec_nanos()) + FileTime::from_unix_time(tm.and_utc().timestamp(), tm.and_utc().timestamp_subsec_nanos()) } #[test] From b703ec8795bb46a113ede38d8ccf14896e3e6b46 Mon Sep 17 00:00:00 2001 From: Michael Vogt Date: Sat, 30 Mar 2024 12:31:07 +0100 Subject: [PATCH 028/144] tests: fix formating of test_touch.rs Thanks to Sylvestre! Co-authored-by: Sylvestre Ledru --- tests/by-util/test_touch.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/by-util/test_touch.rs b/tests/by-util/test_touch.rs index 03df2973dbc..ca2a8b7d85b 100644 --- a/tests/by-util/test_touch.rs +++ b/tests/by-util/test_touch.rs @@ -34,7 +34,10 @@ fn set_file_times(at: &AtPath, path: &str, atime: FileTime, mtime: FileTime) { fn str_to_filetime(format: &str, s: &str) -> FileTime { let tm = chrono::NaiveDateTime::parse_from_str(s, format).unwrap(); - FileTime::from_unix_time(tm.and_utc().timestamp(), tm.and_utc().timestamp_subsec_nanos()) + FileTime::from_unix_time( + tm.and_utc().timestamp(), + tm.and_utc().timestamp_subsec_nanos(), + ) } #[test] From 3a6bf34284e6b6ac72474cae8ec49cf9922c3746 Mon Sep 17 00:00:00 2001 From: Michael Vogt Date: Sat, 30 Mar 2024 15:17:10 +0100 Subject: [PATCH 029/144] date: fix `date -f dates.txt is failing` (#6148) * date: fix `date -f dates.txt is failing` This commit is a trivial followup for: https://github.com/uutils/coreutils/pull/4917 and https://github.com/uutils/parse_datetime/pull/12 The functionality to parse the datetime was moved into the parse_datetime crate and the only (tiny) piece left is to call it from `date`. It also adds the test-case from the original issue. I did not include the two tests from PR#4917 because they appear to work even without this change. I am happy to include them of course if prefered. Closes: #4657 Thanks to Ben Schofield * tests: tweak changes to test_date.rs to be more idiomatic Co-authored-by: Sylvestre Ledru --------- Co-authored-by: Sylvestre Ledru --- src/uu/date/src/date.rs | 5 ++--- tests/by-util/test_date.rs | 17 +++++++++++++++++ 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/src/uu/date/src/date.rs b/src/uu/date/src/date.rs index bc50a8a2c58..76b245923dd 100644 --- a/src/uu/date/src/date.rs +++ b/src/uu/date/src/date.rs @@ -404,9 +404,8 @@ fn make_format_string(settings: &Settings) -> &str { /// If it fails, return a tuple of the `String` along with its `ParseError`. fn parse_date + Clone>( s: S, -) -> Result, (String, chrono::format::ParseError)> { - // TODO: The GNU date command can parse a wide variety of inputs. - s.as_ref().parse().map_err(|e| (s.as_ref().into(), e)) +) -> Result, (String, parse_datetime::ParseDateTimeError)> { + parse_datetime::parse_datetime(s.as_ref()).map_err(|e| (s.as_ref().into(), e)) } #[cfg(not(any(unix, windows)))] diff --git a/tests/by-util/test_date.rs b/tests/by-util/test_date.rs index def3fa8af00..f45b81cb585 100644 --- a/tests/by-util/test_date.rs +++ b/tests/by-util/test_date.rs @@ -409,3 +409,20 @@ fn test_date_overflow() { .no_stdout() .stderr_contains("invalid date"); } + +#[test] +fn test_date_parse_from_format() { + let (at, mut ucmd) = at_and_ucmd!(); + const FILE: &str = "file-with-dates"; + + at.write( + FILE, + "2023-03-27 08:30:00\n\ + 2023-04-01 12:00:00\n\ + 2023-04-15 18:30:00", + ); + ucmd.arg("-f") + .arg(at.plus(FILE)) + .arg("+%Y-%m-%d %H:%M:%S") + .succeeds(); +} From 765e09e3c4d8114c9bebc23a2705ce41eb681f1f Mon Sep 17 00:00:00 2001 From: Ulrich Hornung Date: Sat, 30 Mar 2024 18:33:05 +0100 Subject: [PATCH 030/144] don't upgrade packages - msys2-runtime upgrade fails in CI --- .github/workflows/CICD.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/CICD.yml b/.github/workflows/CICD.yml index f61b49ea27e..b191b41d33e 100644 --- a/.github/workflows/CICD.yml +++ b/.github/workflows/CICD.yml @@ -624,7 +624,7 @@ jobs: ;; # Update binutils if MinGW due to https://github.com/rust-lang/rust/issues/112368 x86_64-pc-windows-gnu) - C:/msys64/usr/bin/pacman.exe -Syu --needed mingw-w64-x86_64-gcc --noconfirm + C:/msys64/usr/bin/pacman.exe -Sy --needed mingw-w64-x86_64-gcc --noconfirm echo "C:\msys64\mingw64\bin" >> $GITHUB_PATH ;; esac @@ -998,7 +998,7 @@ jobs: esac case '${{ matrix.job.os }}' in # Update binutils if MinGW due to https://github.com/rust-lang/rust/issues/112368 - windows-latest) C:/msys64/usr/bin/pacman.exe -Syu --needed mingw-w64-x86_64-gcc --noconfirm ; echo "C:\msys64\mingw64\bin" >> $GITHUB_PATH ;; + windows-latest) C:/msys64/usr/bin/pacman.exe -Sy --needed mingw-w64-x86_64-gcc --noconfirm ; echo "C:\msys64\mingw64\bin" >> $GITHUB_PATH ;; esac - name: Initialize toolchain-dependent workflow variables id: dep_vars From 9dbf2c362db24973cf282b38169db2dd5e95a35d Mon Sep 17 00:00:00 2001 From: Ben Wiederhake Date: Sat, 30 Mar 2024 16:06:15 +0100 Subject: [PATCH 031/144] dirname: accept repeated flag --- src/uu/dirname/src/dirname.rs | 1 + tests/by-util/test_dirname.rs | 10 ++++++++++ 2 files changed, 11 insertions(+) diff --git a/src/uu/dirname/src/dirname.rs b/src/uu/dirname/src/dirname.rs index a645b05fd72..25daa3a36c8 100644 --- a/src/uu/dirname/src/dirname.rs +++ b/src/uu/dirname/src/dirname.rs @@ -64,6 +64,7 @@ pub fn uu_app() -> Command { .about(ABOUT) .version(crate_version!()) .override_usage(format_usage(USAGE)) + .args_override_self(true) .infer_long_args(true) .arg( Arg::new(options::ZERO) diff --git a/tests/by-util/test_dirname.rs b/tests/by-util/test_dirname.rs index 8471b48c44b..8c465393cfb 100644 --- a/tests/by-util/test_dirname.rs +++ b/tests/by-util/test_dirname.rs @@ -40,6 +40,16 @@ fn test_path_without_trailing_slashes_and_zero() { .stdout_is("/root/alpha/beta/gamma/delta/epsilon\u{0}"); } +#[test] +fn test_repeated_zero() { + new_ucmd!() + .arg("--zero") + .arg("--zero") + .arg("foo/bar") + .succeeds() + .stdout_only("foo\u{0}"); +} + #[test] fn test_root() { new_ucmd!().arg("/").run().stdout_is("/\n"); From 8e794d0654ee0cd02e5c305c7f099febe4244e51 Mon Sep 17 00:00:00 2001 From: Ben Wiederhake Date: Sat, 30 Mar 2024 15:30:11 +0100 Subject: [PATCH 032/144] dircolors: accept repeated flags --- src/uu/dircolors/src/dircolors.rs | 1 + tests/by-util/test_dircolors.rs | 7 +++++++ 2 files changed, 8 insertions(+) diff --git a/src/uu/dircolors/src/dircolors.rs b/src/uu/dircolors/src/dircolors.rs index 531c3ee474c..d581fc5ce2f 100644 --- a/src/uu/dircolors/src/dircolors.rs +++ b/src/uu/dircolors/src/dircolors.rs @@ -258,6 +258,7 @@ pub fn uu_app() -> Command { .about(ABOUT) .after_help(AFTER_HELP) .override_usage(format_usage(USAGE)) + .args_override_self(true) .infer_long_args(true) .arg( Arg::new(options::BOURNE_SHELL) diff --git a/tests/by-util/test_dircolors.rs b/tests/by-util/test_dircolors.rs index 4a256352c76..d6736815482 100644 --- a/tests/by-util/test_dircolors.rs +++ b/tests/by-util/test_dircolors.rs @@ -246,3 +246,10 @@ fn test_dircolors_for_dir_as_file() { "dircolors: expected file, got directory '/'", ); } + +#[test] +fn test_repeated() { + for arg in ["-b", "-c", "--print-database", "--print-ls-colors"] { + new_ucmd!().arg(arg).arg(arg).succeeds().no_stderr(); + } +} From 01ea23ba212400882283068aae5076805ba05ab3 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Mon, 25 Mar 2024 21:23:55 +0100 Subject: [PATCH 033/144] Try to release 0.0.26 to see if the artifacts are generated --- 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 f81aee26a0e..1c0e213e42e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -380,7 +380,7 @@ checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" [[package]] name = "coreutils" -version = "0.0.25" +version = "0.0.26" dependencies = [ "chrono", "clap", @@ -2214,7 +2214,7 @@ checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" [[package]] name = "uu_arch" -version = "0.0.25" +version = "0.0.26" dependencies = [ "clap", "platform-info", @@ -2223,7 +2223,7 @@ dependencies = [ [[package]] name = "uu_base32" -version = "0.0.25" +version = "0.0.26" dependencies = [ "clap", "uucore", @@ -2231,7 +2231,7 @@ dependencies = [ [[package]] name = "uu_base64" -version = "0.0.25" +version = "0.0.26" dependencies = [ "uu_base32", "uucore", @@ -2239,7 +2239,7 @@ dependencies = [ [[package]] name = "uu_basename" -version = "0.0.25" +version = "0.0.26" dependencies = [ "clap", "uucore", @@ -2247,7 +2247,7 @@ dependencies = [ [[package]] name = "uu_basenc" -version = "0.0.25" +version = "0.0.26" dependencies = [ "clap", "uu_base32", @@ -2256,7 +2256,7 @@ dependencies = [ [[package]] name = "uu_cat" -version = "0.0.25" +version = "0.0.26" dependencies = [ "clap", "nix", @@ -2266,7 +2266,7 @@ dependencies = [ [[package]] name = "uu_chcon" -version = "0.0.25" +version = "0.0.26" dependencies = [ "clap", "fts-sys", @@ -2278,7 +2278,7 @@ dependencies = [ [[package]] name = "uu_chgrp" -version = "0.0.25" +version = "0.0.26" dependencies = [ "clap", "uucore", @@ -2286,7 +2286,7 @@ dependencies = [ [[package]] name = "uu_chmod" -version = "0.0.25" +version = "0.0.26" dependencies = [ "clap", "libc", @@ -2295,7 +2295,7 @@ dependencies = [ [[package]] name = "uu_chown" -version = "0.0.25" +version = "0.0.26" dependencies = [ "clap", "uucore", @@ -2303,7 +2303,7 @@ dependencies = [ [[package]] name = "uu_chroot" -version = "0.0.25" +version = "0.0.26" dependencies = [ "clap", "uucore", @@ -2311,7 +2311,7 @@ dependencies = [ [[package]] name = "uu_cksum" -version = "0.0.25" +version = "0.0.26" dependencies = [ "clap", "hex", @@ -2320,7 +2320,7 @@ dependencies = [ [[package]] name = "uu_comm" -version = "0.0.25" +version = "0.0.26" dependencies = [ "clap", "uucore", @@ -2328,7 +2328,7 @@ dependencies = [ [[package]] name = "uu_cp" -version = "0.0.25" +version = "0.0.26" dependencies = [ "clap", "exacl", @@ -2344,7 +2344,7 @@ dependencies = [ [[package]] name = "uu_csplit" -version = "0.0.25" +version = "0.0.26" dependencies = [ "clap", "regex", @@ -2354,7 +2354,7 @@ dependencies = [ [[package]] name = "uu_cut" -version = "0.0.25" +version = "0.0.26" dependencies = [ "bstr", "clap", @@ -2364,7 +2364,7 @@ dependencies = [ [[package]] name = "uu_date" -version = "0.0.25" +version = "0.0.26" dependencies = [ "chrono", "clap", @@ -2376,7 +2376,7 @@ dependencies = [ [[package]] name = "uu_dd" -version = "0.0.25" +version = "0.0.26" dependencies = [ "clap", "gcd", @@ -2388,7 +2388,7 @@ dependencies = [ [[package]] name = "uu_df" -version = "0.0.25" +version = "0.0.26" dependencies = [ "clap", "tempfile", @@ -2398,7 +2398,7 @@ dependencies = [ [[package]] name = "uu_dir" -version = "0.0.25" +version = "0.0.26" dependencies = [ "clap", "uu_ls", @@ -2407,7 +2407,7 @@ dependencies = [ [[package]] name = "uu_dircolors" -version = "0.0.25" +version = "0.0.26" dependencies = [ "clap", "uucore", @@ -2415,7 +2415,7 @@ dependencies = [ [[package]] name = "uu_dirname" -version = "0.0.25" +version = "0.0.26" dependencies = [ "clap", "uucore", @@ -2423,7 +2423,7 @@ dependencies = [ [[package]] name = "uu_du" -version = "0.0.25" +version = "0.0.26" dependencies = [ "chrono", "clap", @@ -2434,7 +2434,7 @@ dependencies = [ [[package]] name = "uu_echo" -version = "0.0.25" +version = "0.0.26" dependencies = [ "clap", "uucore", @@ -2442,7 +2442,7 @@ dependencies = [ [[package]] name = "uu_env" -version = "0.0.25" +version = "0.0.26" dependencies = [ "clap", "nix", @@ -2452,7 +2452,7 @@ dependencies = [ [[package]] name = "uu_expand" -version = "0.0.25" +version = "0.0.26" dependencies = [ "clap", "unicode-width", @@ -2461,7 +2461,7 @@ dependencies = [ [[package]] name = "uu_expr" -version = "0.0.25" +version = "0.0.26" dependencies = [ "clap", "num-bigint", @@ -2472,7 +2472,7 @@ dependencies = [ [[package]] name = "uu_factor" -version = "0.0.25" +version = "0.0.26" dependencies = [ "clap", "coz", @@ -2485,7 +2485,7 @@ dependencies = [ [[package]] name = "uu_false" -version = "0.0.25" +version = "0.0.26" dependencies = [ "clap", "uucore", @@ -2493,7 +2493,7 @@ dependencies = [ [[package]] name = "uu_fmt" -version = "0.0.25" +version = "0.0.26" dependencies = [ "clap", "unicode-width", @@ -2502,7 +2502,7 @@ dependencies = [ [[package]] name = "uu_fold" -version = "0.0.25" +version = "0.0.26" dependencies = [ "clap", "uucore", @@ -2510,7 +2510,7 @@ dependencies = [ [[package]] name = "uu_groups" -version = "0.0.25" +version = "0.0.26" dependencies = [ "clap", "uucore", @@ -2518,7 +2518,7 @@ dependencies = [ [[package]] name = "uu_hashsum" -version = "0.0.25" +version = "0.0.26" dependencies = [ "clap", "hex", @@ -2529,7 +2529,7 @@ dependencies = [ [[package]] name = "uu_head" -version = "0.0.25" +version = "0.0.26" dependencies = [ "clap", "memchr", @@ -2538,7 +2538,7 @@ dependencies = [ [[package]] name = "uu_hostid" -version = "0.0.25" +version = "0.0.26" dependencies = [ "clap", "libc", @@ -2547,7 +2547,7 @@ dependencies = [ [[package]] name = "uu_hostname" -version = "0.0.25" +version = "0.0.26" dependencies = [ "clap", "hostname", @@ -2557,7 +2557,7 @@ dependencies = [ [[package]] name = "uu_id" -version = "0.0.25" +version = "0.0.26" dependencies = [ "clap", "selinux", @@ -2566,7 +2566,7 @@ dependencies = [ [[package]] name = "uu_install" -version = "0.0.25" +version = "0.0.26" dependencies = [ "clap", "file_diff", @@ -2577,7 +2577,7 @@ dependencies = [ [[package]] name = "uu_join" -version = "0.0.25" +version = "0.0.26" dependencies = [ "clap", "memchr", @@ -2586,7 +2586,7 @@ dependencies = [ [[package]] name = "uu_kill" -version = "0.0.25" +version = "0.0.26" dependencies = [ "clap", "nix", @@ -2595,7 +2595,7 @@ dependencies = [ [[package]] name = "uu_link" -version = "0.0.25" +version = "0.0.26" dependencies = [ "clap", "uucore", @@ -2603,7 +2603,7 @@ dependencies = [ [[package]] name = "uu_ln" -version = "0.0.25" +version = "0.0.26" dependencies = [ "clap", "uucore", @@ -2611,7 +2611,7 @@ dependencies = [ [[package]] name = "uu_logname" -version = "0.0.25" +version = "0.0.26" dependencies = [ "clap", "libc", @@ -2620,7 +2620,7 @@ dependencies = [ [[package]] name = "uu_ls" -version = "0.0.25" +version = "0.0.26" dependencies = [ "chrono", "clap", @@ -2638,7 +2638,7 @@ dependencies = [ [[package]] name = "uu_mkdir" -version = "0.0.25" +version = "0.0.26" dependencies = [ "clap", "uucore", @@ -2646,7 +2646,7 @@ dependencies = [ [[package]] name = "uu_mkfifo" -version = "0.0.25" +version = "0.0.26" dependencies = [ "clap", "libc", @@ -2655,7 +2655,7 @@ dependencies = [ [[package]] name = "uu_mknod" -version = "0.0.25" +version = "0.0.26" dependencies = [ "clap", "libc", @@ -2664,7 +2664,7 @@ dependencies = [ [[package]] name = "uu_mktemp" -version = "0.0.25" +version = "0.0.26" dependencies = [ "clap", "rand", @@ -2674,7 +2674,7 @@ dependencies = [ [[package]] name = "uu_more" -version = "0.0.25" +version = "0.0.26" dependencies = [ "clap", "crossterm", @@ -2686,7 +2686,7 @@ dependencies = [ [[package]] name = "uu_mv" -version = "0.0.25" +version = "0.0.26" dependencies = [ "clap", "fs_extra", @@ -2696,7 +2696,7 @@ dependencies = [ [[package]] name = "uu_nice" -version = "0.0.25" +version = "0.0.26" dependencies = [ "clap", "libc", @@ -2706,7 +2706,7 @@ dependencies = [ [[package]] name = "uu_nl" -version = "0.0.25" +version = "0.0.26" dependencies = [ "clap", "regex", @@ -2715,7 +2715,7 @@ dependencies = [ [[package]] name = "uu_nohup" -version = "0.0.25" +version = "0.0.26" dependencies = [ "clap", "libc", @@ -2724,7 +2724,7 @@ dependencies = [ [[package]] name = "uu_nproc" -version = "0.0.25" +version = "0.0.26" dependencies = [ "clap", "libc", @@ -2733,7 +2733,7 @@ dependencies = [ [[package]] name = "uu_numfmt" -version = "0.0.25" +version = "0.0.26" dependencies = [ "clap", "uucore", @@ -2741,7 +2741,7 @@ dependencies = [ [[package]] name = "uu_od" -version = "0.0.25" +version = "0.0.26" dependencies = [ "byteorder", "clap", @@ -2751,7 +2751,7 @@ dependencies = [ [[package]] name = "uu_paste" -version = "0.0.25" +version = "0.0.26" dependencies = [ "clap", "uucore", @@ -2759,7 +2759,7 @@ dependencies = [ [[package]] name = "uu_pathchk" -version = "0.0.25" +version = "0.0.26" dependencies = [ "clap", "libc", @@ -2768,7 +2768,7 @@ dependencies = [ [[package]] name = "uu_pinky" -version = "0.0.25" +version = "0.0.26" dependencies = [ "clap", "uucore", @@ -2776,7 +2776,7 @@ dependencies = [ [[package]] name = "uu_pr" -version = "0.0.25" +version = "0.0.26" dependencies = [ "chrono", "clap", @@ -2788,7 +2788,7 @@ dependencies = [ [[package]] name = "uu_printenv" -version = "0.0.25" +version = "0.0.26" dependencies = [ "clap", "uucore", @@ -2796,7 +2796,7 @@ dependencies = [ [[package]] name = "uu_printf" -version = "0.0.25" +version = "0.0.26" dependencies = [ "clap", "uucore", @@ -2804,7 +2804,7 @@ dependencies = [ [[package]] name = "uu_ptx" -version = "0.0.25" +version = "0.0.26" dependencies = [ "clap", "regex", @@ -2813,7 +2813,7 @@ dependencies = [ [[package]] name = "uu_pwd" -version = "0.0.25" +version = "0.0.26" dependencies = [ "clap", "uucore", @@ -2821,7 +2821,7 @@ dependencies = [ [[package]] name = "uu_readlink" -version = "0.0.25" +version = "0.0.26" dependencies = [ "clap", "uucore", @@ -2829,7 +2829,7 @@ dependencies = [ [[package]] name = "uu_realpath" -version = "0.0.25" +version = "0.0.26" dependencies = [ "clap", "uucore", @@ -2837,7 +2837,7 @@ dependencies = [ [[package]] name = "uu_rm" -version = "0.0.25" +version = "0.0.26" dependencies = [ "clap", "libc", @@ -2848,7 +2848,7 @@ dependencies = [ [[package]] name = "uu_rmdir" -version = "0.0.25" +version = "0.0.26" dependencies = [ "clap", "libc", @@ -2857,7 +2857,7 @@ dependencies = [ [[package]] name = "uu_runcon" -version = "0.0.25" +version = "0.0.26" dependencies = [ "clap", "libc", @@ -2868,7 +2868,7 @@ dependencies = [ [[package]] name = "uu_seq" -version = "0.0.25" +version = "0.0.26" dependencies = [ "bigdecimal", "clap", @@ -2879,7 +2879,7 @@ dependencies = [ [[package]] name = "uu_shred" -version = "0.0.25" +version = "0.0.26" dependencies = [ "clap", "libc", @@ -2889,7 +2889,7 @@ dependencies = [ [[package]] name = "uu_shuf" -version = "0.0.25" +version = "0.0.26" dependencies = [ "clap", "memchr", @@ -2900,7 +2900,7 @@ dependencies = [ [[package]] name = "uu_sleep" -version = "0.0.25" +version = "0.0.26" dependencies = [ "clap", "fundu", @@ -2909,7 +2909,7 @@ dependencies = [ [[package]] name = "uu_sort" -version = "0.0.25" +version = "0.0.26" dependencies = [ "binary-heap-plus", "clap", @@ -2928,7 +2928,7 @@ dependencies = [ [[package]] name = "uu_split" -version = "0.0.25" +version = "0.0.26" dependencies = [ "clap", "memchr", @@ -2937,7 +2937,7 @@ dependencies = [ [[package]] name = "uu_stat" -version = "0.0.25" +version = "0.0.26" dependencies = [ "chrono", "clap", @@ -2946,7 +2946,7 @@ dependencies = [ [[package]] name = "uu_stdbuf" -version = "0.0.25" +version = "0.0.26" dependencies = [ "clap", "tempfile", @@ -2956,7 +2956,7 @@ dependencies = [ [[package]] name = "uu_stdbuf_libstdbuf" -version = "0.0.25" +version = "0.0.26" dependencies = [ "cpp", "cpp_build", @@ -2965,7 +2965,7 @@ dependencies = [ [[package]] name = "uu_stty" -version = "0.0.25" +version = "0.0.26" dependencies = [ "clap", "nix", @@ -2974,7 +2974,7 @@ dependencies = [ [[package]] name = "uu_sum" -version = "0.0.25" +version = "0.0.26" dependencies = [ "clap", "uucore", @@ -2982,7 +2982,7 @@ dependencies = [ [[package]] name = "uu_sync" -version = "0.0.25" +version = "0.0.26" dependencies = [ "clap", "libc", @@ -2993,7 +2993,7 @@ dependencies = [ [[package]] name = "uu_tac" -version = "0.0.25" +version = "0.0.26" dependencies = [ "clap", "memchr", @@ -3004,7 +3004,7 @@ dependencies = [ [[package]] name = "uu_tail" -version = "0.0.25" +version = "0.0.26" dependencies = [ "clap", "fundu", @@ -3020,7 +3020,7 @@ dependencies = [ [[package]] name = "uu_tee" -version = "0.0.25" +version = "0.0.26" dependencies = [ "clap", "libc", @@ -3029,7 +3029,7 @@ dependencies = [ [[package]] name = "uu_test" -version = "0.0.25" +version = "0.0.26" dependencies = [ "clap", "libc", @@ -3039,7 +3039,7 @@ dependencies = [ [[package]] name = "uu_timeout" -version = "0.0.25" +version = "0.0.26" dependencies = [ "clap", "libc", @@ -3049,7 +3049,7 @@ dependencies = [ [[package]] name = "uu_touch" -version = "0.0.25" +version = "0.0.26" dependencies = [ "chrono", "clap", @@ -3061,7 +3061,7 @@ dependencies = [ [[package]] name = "uu_tr" -version = "0.0.25" +version = "0.0.26" dependencies = [ "clap", "nom", @@ -3070,7 +3070,7 @@ dependencies = [ [[package]] name = "uu_true" -version = "0.0.25" +version = "0.0.26" dependencies = [ "clap", "uucore", @@ -3078,7 +3078,7 @@ dependencies = [ [[package]] name = "uu_truncate" -version = "0.0.25" +version = "0.0.26" dependencies = [ "clap", "uucore", @@ -3086,7 +3086,7 @@ dependencies = [ [[package]] name = "uu_tsort" -version = "0.0.25" +version = "0.0.26" dependencies = [ "clap", "uucore", @@ -3094,7 +3094,7 @@ dependencies = [ [[package]] name = "uu_tty" -version = "0.0.25" +version = "0.0.26" dependencies = [ "clap", "nix", @@ -3103,7 +3103,7 @@ dependencies = [ [[package]] name = "uu_uname" -version = "0.0.25" +version = "0.0.26" dependencies = [ "clap", "platform-info", @@ -3112,7 +3112,7 @@ dependencies = [ [[package]] name = "uu_unexpand" -version = "0.0.25" +version = "0.0.26" dependencies = [ "clap", "unicode-width", @@ -3121,7 +3121,7 @@ dependencies = [ [[package]] name = "uu_uniq" -version = "0.0.25" +version = "0.0.26" dependencies = [ "clap", "uucore", @@ -3129,7 +3129,7 @@ dependencies = [ [[package]] name = "uu_unlink" -version = "0.0.25" +version = "0.0.26" dependencies = [ "clap", "uucore", @@ -3137,7 +3137,7 @@ dependencies = [ [[package]] name = "uu_uptime" -version = "0.0.25" +version = "0.0.26" dependencies = [ "chrono", "clap", @@ -3146,7 +3146,7 @@ dependencies = [ [[package]] name = "uu_users" -version = "0.0.25" +version = "0.0.26" dependencies = [ "clap", "uucore", @@ -3154,7 +3154,7 @@ dependencies = [ [[package]] name = "uu_vdir" -version = "0.0.25" +version = "0.0.26" dependencies = [ "clap", "uu_ls", @@ -3163,7 +3163,7 @@ dependencies = [ [[package]] name = "uu_wc" -version = "0.0.25" +version = "0.0.26" dependencies = [ "bytecount", "clap", @@ -3176,7 +3176,7 @@ dependencies = [ [[package]] name = "uu_who" -version = "0.0.25" +version = "0.0.26" dependencies = [ "clap", "uucore", @@ -3184,7 +3184,7 @@ dependencies = [ [[package]] name = "uu_whoami" -version = "0.0.25" +version = "0.0.26" dependencies = [ "clap", "libc", @@ -3194,7 +3194,7 @@ dependencies = [ [[package]] name = "uu_yes" -version = "0.0.25" +version = "0.0.26" dependencies = [ "clap", "itertools", @@ -3204,7 +3204,7 @@ dependencies = [ [[package]] name = "uucore" -version = "0.0.25" +version = "0.0.26" dependencies = [ "blake2b_simd", "blake3", @@ -3241,7 +3241,7 @@ dependencies = [ [[package]] name = "uucore_procs" -version = "0.0.25" +version = "0.0.26" dependencies = [ "proc-macro2", "quote", @@ -3250,7 +3250,7 @@ dependencies = [ [[package]] name = "uuhelp_parser" -version = "0.0.25" +version = "0.0.26" [[package]] name = "uuid" diff --git a/Cargo.toml b/Cargo.toml index 0354c251a4a..36b0cad807d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,7 +5,7 @@ [package] name = "coreutils" -version = "0.0.25" +version = "0.0.26" authors = ["uutils developers"] license = "MIT" description = "coreutils ~ GNU coreutils (updated); implemented as universal (cross-platform) utils, written in Rust" @@ -363,109 +363,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.25", package = "uu_test", path = "src/uu/test" } +uu_test = { optional = true, version = "0.0.26", package = "uu_test", path = "src/uu/test" } # -arch = { optional = true, version = "0.0.25", package = "uu_arch", path = "src/uu/arch" } -base32 = { optional = true, version = "0.0.25", package = "uu_base32", path = "src/uu/base32" } -base64 = { optional = true, version = "0.0.25", package = "uu_base64", path = "src/uu/base64" } -basename = { optional = true, version = "0.0.25", package = "uu_basename", path = "src/uu/basename" } -basenc = { optional = true, version = "0.0.25", package = "uu_basenc", path = "src/uu/basenc" } -cat = { optional = true, version = "0.0.25", package = "uu_cat", path = "src/uu/cat" } -chcon = { optional = true, version = "0.0.25", package = "uu_chcon", path = "src/uu/chcon" } -chgrp = { optional = true, version = "0.0.25", package = "uu_chgrp", path = "src/uu/chgrp" } -chmod = { optional = true, version = "0.0.25", package = "uu_chmod", path = "src/uu/chmod" } -chown = { optional = true, version = "0.0.25", package = "uu_chown", path = "src/uu/chown" } -chroot = { optional = true, version = "0.0.25", package = "uu_chroot", path = "src/uu/chroot" } -cksum = { optional = true, version = "0.0.25", package = "uu_cksum", path = "src/uu/cksum" } -comm = { optional = true, version = "0.0.25", package = "uu_comm", path = "src/uu/comm" } -cp = { optional = true, version = "0.0.25", package = "uu_cp", path = "src/uu/cp" } -csplit = { optional = true, version = "0.0.25", package = "uu_csplit", path = "src/uu/csplit" } -cut = { optional = true, version = "0.0.25", package = "uu_cut", path = "src/uu/cut" } -date = { optional = true, version = "0.0.25", package = "uu_date", path = "src/uu/date" } -dd = { optional = true, version = "0.0.25", package = "uu_dd", path = "src/uu/dd" } -df = { optional = true, version = "0.0.25", package = "uu_df", path = "src/uu/df" } -dir = { optional = true, version = "0.0.25", package = "uu_dir", path = "src/uu/dir" } -dircolors = { optional = true, version = "0.0.25", package = "uu_dircolors", path = "src/uu/dircolors" } -dirname = { optional = true, version = "0.0.25", package = "uu_dirname", path = "src/uu/dirname" } -du = { optional = true, version = "0.0.25", package = "uu_du", path = "src/uu/du" } -echo = { optional = true, version = "0.0.25", package = "uu_echo", path = "src/uu/echo" } -env = { optional = true, version = "0.0.25", package = "uu_env", path = "src/uu/env" } -expand = { optional = true, version = "0.0.25", package = "uu_expand", path = "src/uu/expand" } -expr = { optional = true, version = "0.0.25", package = "uu_expr", path = "src/uu/expr" } -factor = { optional = true, version = "0.0.25", package = "uu_factor", path = "src/uu/factor" } -false = { optional = true, version = "0.0.25", package = "uu_false", path = "src/uu/false" } -fmt = { optional = true, version = "0.0.25", package = "uu_fmt", path = "src/uu/fmt" } -fold = { optional = true, version = "0.0.25", package = "uu_fold", path = "src/uu/fold" } -groups = { optional = true, version = "0.0.25", package = "uu_groups", path = "src/uu/groups" } -hashsum = { optional = true, version = "0.0.25", package = "uu_hashsum", path = "src/uu/hashsum" } -head = { optional = true, version = "0.0.25", package = "uu_head", path = "src/uu/head" } -hostid = { optional = true, version = "0.0.25", package = "uu_hostid", path = "src/uu/hostid" } -hostname = { optional = true, version = "0.0.25", package = "uu_hostname", path = "src/uu/hostname" } -id = { optional = true, version = "0.0.25", package = "uu_id", path = "src/uu/id" } -install = { optional = true, version = "0.0.25", package = "uu_install", path = "src/uu/install" } -join = { optional = true, version = "0.0.25", package = "uu_join", path = "src/uu/join" } -kill = { optional = true, version = "0.0.25", package = "uu_kill", path = "src/uu/kill" } -link = { optional = true, version = "0.0.25", package = "uu_link", path = "src/uu/link" } -ln = { optional = true, version = "0.0.25", package = "uu_ln", path = "src/uu/ln" } -ls = { optional = true, version = "0.0.25", package = "uu_ls", path = "src/uu/ls" } -logname = { optional = true, version = "0.0.25", package = "uu_logname", path = "src/uu/logname" } -mkdir = { optional = true, version = "0.0.25", package = "uu_mkdir", path = "src/uu/mkdir" } -mkfifo = { optional = true, version = "0.0.25", package = "uu_mkfifo", path = "src/uu/mkfifo" } -mknod = { optional = true, version = "0.0.25", package = "uu_mknod", path = "src/uu/mknod" } -mktemp = { optional = true, version = "0.0.25", package = "uu_mktemp", path = "src/uu/mktemp" } -more = { optional = true, version = "0.0.25", package = "uu_more", path = "src/uu/more" } -mv = { optional = true, version = "0.0.25", package = "uu_mv", path = "src/uu/mv" } -nice = { optional = true, version = "0.0.25", package = "uu_nice", path = "src/uu/nice" } -nl = { optional = true, version = "0.0.25", package = "uu_nl", path = "src/uu/nl" } -nohup = { optional = true, version = "0.0.25", package = "uu_nohup", path = "src/uu/nohup" } -nproc = { optional = true, version = "0.0.25", package = "uu_nproc", path = "src/uu/nproc" } -numfmt = { optional = true, version = "0.0.25", package = "uu_numfmt", path = "src/uu/numfmt" } -od = { optional = true, version = "0.0.25", package = "uu_od", path = "src/uu/od" } -paste = { optional = true, version = "0.0.25", package = "uu_paste", path = "src/uu/paste" } -pathchk = { optional = true, version = "0.0.25", package = "uu_pathchk", path = "src/uu/pathchk" } -pinky = { optional = true, version = "0.0.25", package = "uu_pinky", path = "src/uu/pinky" } -pr = { optional = true, version = "0.0.25", package = "uu_pr", path = "src/uu/pr" } -printenv = { optional = true, version = "0.0.25", package = "uu_printenv", path = "src/uu/printenv" } -printf = { optional = true, version = "0.0.25", package = "uu_printf", path = "src/uu/printf" } -ptx = { optional = true, version = "0.0.25", package = "uu_ptx", path = "src/uu/ptx" } -pwd = { optional = true, version = "0.0.25", package = "uu_pwd", path = "src/uu/pwd" } -readlink = { optional = true, version = "0.0.25", package = "uu_readlink", path = "src/uu/readlink" } -realpath = { optional = true, version = "0.0.25", package = "uu_realpath", path = "src/uu/realpath" } -rm = { optional = true, version = "0.0.25", package = "uu_rm", path = "src/uu/rm" } -rmdir = { optional = true, version = "0.0.25", package = "uu_rmdir", path = "src/uu/rmdir" } -runcon = { optional = true, version = "0.0.25", package = "uu_runcon", path = "src/uu/runcon" } -seq = { optional = true, version = "0.0.25", package = "uu_seq", path = "src/uu/seq" } -shred = { optional = true, version = "0.0.25", package = "uu_shred", path = "src/uu/shred" } -shuf = { optional = true, version = "0.0.25", package = "uu_shuf", path = "src/uu/shuf" } -sleep = { optional = true, version = "0.0.25", package = "uu_sleep", path = "src/uu/sleep" } -sort = { optional = true, version = "0.0.25", package = "uu_sort", path = "src/uu/sort" } -split = { optional = true, version = "0.0.25", package = "uu_split", path = "src/uu/split" } -stat = { optional = true, version = "0.0.25", package = "uu_stat", path = "src/uu/stat" } -stdbuf = { optional = true, version = "0.0.25", package = "uu_stdbuf", path = "src/uu/stdbuf" } -stty = { optional = true, version = "0.0.25", package = "uu_stty", path = "src/uu/stty" } -sum = { optional = true, version = "0.0.25", package = "uu_sum", path = "src/uu/sum" } -sync = { optional = true, version = "0.0.25", package = "uu_sync", path = "src/uu/sync" } -tac = { optional = true, version = "0.0.25", package = "uu_tac", path = "src/uu/tac" } -tail = { optional = true, version = "0.0.25", package = "uu_tail", path = "src/uu/tail" } -tee = { optional = true, version = "0.0.25", package = "uu_tee", path = "src/uu/tee" } -timeout = { optional = true, version = "0.0.25", package = "uu_timeout", path = "src/uu/timeout" } -touch = { optional = true, version = "0.0.25", package = "uu_touch", path = "src/uu/touch" } -tr = { optional = true, version = "0.0.25", package = "uu_tr", path = "src/uu/tr" } -true = { optional = true, version = "0.0.25", package = "uu_true", path = "src/uu/true" } -truncate = { optional = true, version = "0.0.25", package = "uu_truncate", path = "src/uu/truncate" } -tsort = { optional = true, version = "0.0.25", package = "uu_tsort", path = "src/uu/tsort" } -tty = { optional = true, version = "0.0.25", package = "uu_tty", path = "src/uu/tty" } -uname = { optional = true, version = "0.0.25", package = "uu_uname", path = "src/uu/uname" } -unexpand = { optional = true, version = "0.0.25", package = "uu_unexpand", path = "src/uu/unexpand" } -uniq = { optional = true, version = "0.0.25", package = "uu_uniq", path = "src/uu/uniq" } -unlink = { optional = true, version = "0.0.25", package = "uu_unlink", path = "src/uu/unlink" } -uptime = { optional = true, version = "0.0.25", package = "uu_uptime", path = "src/uu/uptime" } -users = { optional = true, version = "0.0.25", package = "uu_users", path = "src/uu/users" } -vdir = { optional = true, version = "0.0.25", package = "uu_vdir", path = "src/uu/vdir" } -wc = { optional = true, version = "0.0.25", package = "uu_wc", path = "src/uu/wc" } -who = { optional = true, version = "0.0.25", package = "uu_who", path = "src/uu/who" } -whoami = { optional = true, version = "0.0.25", package = "uu_whoami", path = "src/uu/whoami" } -yes = { optional = true, version = "0.0.25", package = "uu_yes", path = "src/uu/yes" } +arch = { optional = true, version = "0.0.26", package = "uu_arch", path = "src/uu/arch" } +base32 = { optional = true, version = "0.0.26", package = "uu_base32", path = "src/uu/base32" } +base64 = { optional = true, version = "0.0.26", package = "uu_base64", path = "src/uu/base64" } +basename = { optional = true, version = "0.0.26", package = "uu_basename", path = "src/uu/basename" } +basenc = { optional = true, version = "0.0.26", package = "uu_basenc", path = "src/uu/basenc" } +cat = { optional = true, version = "0.0.26", package = "uu_cat", path = "src/uu/cat" } +chcon = { optional = true, version = "0.0.26", package = "uu_chcon", path = "src/uu/chcon" } +chgrp = { optional = true, version = "0.0.26", package = "uu_chgrp", path = "src/uu/chgrp" } +chmod = { optional = true, version = "0.0.26", package = "uu_chmod", path = "src/uu/chmod" } +chown = { optional = true, version = "0.0.26", package = "uu_chown", path = "src/uu/chown" } +chroot = { optional = true, version = "0.0.26", package = "uu_chroot", path = "src/uu/chroot" } +cksum = { optional = true, version = "0.0.26", package = "uu_cksum", path = "src/uu/cksum" } +comm = { optional = true, version = "0.0.26", package = "uu_comm", path = "src/uu/comm" } +cp = { optional = true, version = "0.0.26", package = "uu_cp", path = "src/uu/cp" } +csplit = { optional = true, version = "0.0.26", package = "uu_csplit", path = "src/uu/csplit" } +cut = { optional = true, version = "0.0.26", package = "uu_cut", path = "src/uu/cut" } +date = { optional = true, version = "0.0.26", package = "uu_date", path = "src/uu/date" } +dd = { optional = true, version = "0.0.26", package = "uu_dd", path = "src/uu/dd" } +df = { optional = true, version = "0.0.26", package = "uu_df", path = "src/uu/df" } +dir = { optional = true, version = "0.0.26", package = "uu_dir", path = "src/uu/dir" } +dircolors = { optional = true, version = "0.0.26", package = "uu_dircolors", path = "src/uu/dircolors" } +dirname = { optional = true, version = "0.0.26", package = "uu_dirname", path = "src/uu/dirname" } +du = { optional = true, version = "0.0.26", package = "uu_du", path = "src/uu/du" } +echo = { optional = true, version = "0.0.26", package = "uu_echo", path = "src/uu/echo" } +env = { optional = true, version = "0.0.26", package = "uu_env", path = "src/uu/env" } +expand = { optional = true, version = "0.0.26", package = "uu_expand", path = "src/uu/expand" } +expr = { optional = true, version = "0.0.26", package = "uu_expr", path = "src/uu/expr" } +factor = { optional = true, version = "0.0.26", package = "uu_factor", path = "src/uu/factor" } +false = { optional = true, version = "0.0.26", package = "uu_false", path = "src/uu/false" } +fmt = { optional = true, version = "0.0.26", package = "uu_fmt", path = "src/uu/fmt" } +fold = { optional = true, version = "0.0.26", package = "uu_fold", path = "src/uu/fold" } +groups = { optional = true, version = "0.0.26", package = "uu_groups", path = "src/uu/groups" } +hashsum = { optional = true, version = "0.0.26", package = "uu_hashsum", path = "src/uu/hashsum" } +head = { optional = true, version = "0.0.26", package = "uu_head", path = "src/uu/head" } +hostid = { optional = true, version = "0.0.26", package = "uu_hostid", path = "src/uu/hostid" } +hostname = { optional = true, version = "0.0.26", package = "uu_hostname", path = "src/uu/hostname" } +id = { optional = true, version = "0.0.26", package = "uu_id", path = "src/uu/id" } +install = { optional = true, version = "0.0.26", package = "uu_install", path = "src/uu/install" } +join = { optional = true, version = "0.0.26", package = "uu_join", path = "src/uu/join" } +kill = { optional = true, version = "0.0.26", package = "uu_kill", path = "src/uu/kill" } +link = { optional = true, version = "0.0.26", package = "uu_link", path = "src/uu/link" } +ln = { optional = true, version = "0.0.26", package = "uu_ln", path = "src/uu/ln" } +ls = { optional = true, version = "0.0.26", package = "uu_ls", path = "src/uu/ls" } +logname = { optional = true, version = "0.0.26", package = "uu_logname", path = "src/uu/logname" } +mkdir = { optional = true, version = "0.0.26", package = "uu_mkdir", path = "src/uu/mkdir" } +mkfifo = { optional = true, version = "0.0.26", package = "uu_mkfifo", path = "src/uu/mkfifo" } +mknod = { optional = true, version = "0.0.26", package = "uu_mknod", path = "src/uu/mknod" } +mktemp = { optional = true, version = "0.0.26", package = "uu_mktemp", path = "src/uu/mktemp" } +more = { optional = true, version = "0.0.26", package = "uu_more", path = "src/uu/more" } +mv = { optional = true, version = "0.0.26", package = "uu_mv", path = "src/uu/mv" } +nice = { optional = true, version = "0.0.26", package = "uu_nice", path = "src/uu/nice" } +nl = { optional = true, version = "0.0.26", package = "uu_nl", path = "src/uu/nl" } +nohup = { optional = true, version = "0.0.26", package = "uu_nohup", path = "src/uu/nohup" } +nproc = { optional = true, version = "0.0.26", package = "uu_nproc", path = "src/uu/nproc" } +numfmt = { optional = true, version = "0.0.26", package = "uu_numfmt", path = "src/uu/numfmt" } +od = { optional = true, version = "0.0.26", package = "uu_od", path = "src/uu/od" } +paste = { optional = true, version = "0.0.26", package = "uu_paste", path = "src/uu/paste" } +pathchk = { optional = true, version = "0.0.26", package = "uu_pathchk", path = "src/uu/pathchk" } +pinky = { optional = true, version = "0.0.26", package = "uu_pinky", path = "src/uu/pinky" } +pr = { optional = true, version = "0.0.26", package = "uu_pr", path = "src/uu/pr" } +printenv = { optional = true, version = "0.0.26", package = "uu_printenv", path = "src/uu/printenv" } +printf = { optional = true, version = "0.0.26", package = "uu_printf", path = "src/uu/printf" } +ptx = { optional = true, version = "0.0.26", package = "uu_ptx", path = "src/uu/ptx" } +pwd = { optional = true, version = "0.0.26", package = "uu_pwd", path = "src/uu/pwd" } +readlink = { optional = true, version = "0.0.26", package = "uu_readlink", path = "src/uu/readlink" } +realpath = { optional = true, version = "0.0.26", package = "uu_realpath", path = "src/uu/realpath" } +rm = { optional = true, version = "0.0.26", package = "uu_rm", path = "src/uu/rm" } +rmdir = { optional = true, version = "0.0.26", package = "uu_rmdir", path = "src/uu/rmdir" } +runcon = { optional = true, version = "0.0.26", package = "uu_runcon", path = "src/uu/runcon" } +seq = { optional = true, version = "0.0.26", package = "uu_seq", path = "src/uu/seq" } +shred = { optional = true, version = "0.0.26", package = "uu_shred", path = "src/uu/shred" } +shuf = { optional = true, version = "0.0.26", package = "uu_shuf", path = "src/uu/shuf" } +sleep = { optional = true, version = "0.0.26", package = "uu_sleep", path = "src/uu/sleep" } +sort = { optional = true, version = "0.0.26", package = "uu_sort", path = "src/uu/sort" } +split = { optional = true, version = "0.0.26", package = "uu_split", path = "src/uu/split" } +stat = { optional = true, version = "0.0.26", package = "uu_stat", path = "src/uu/stat" } +stdbuf = { optional = true, version = "0.0.26", package = "uu_stdbuf", path = "src/uu/stdbuf" } +stty = { optional = true, version = "0.0.26", package = "uu_stty", path = "src/uu/stty" } +sum = { optional = true, version = "0.0.26", package = "uu_sum", path = "src/uu/sum" } +sync = { optional = true, version = "0.0.26", package = "uu_sync", path = "src/uu/sync" } +tac = { optional = true, version = "0.0.26", package = "uu_tac", path = "src/uu/tac" } +tail = { optional = true, version = "0.0.26", package = "uu_tail", path = "src/uu/tail" } +tee = { optional = true, version = "0.0.26", package = "uu_tee", path = "src/uu/tee" } +timeout = { optional = true, version = "0.0.26", package = "uu_timeout", path = "src/uu/timeout" } +touch = { optional = true, version = "0.0.26", package = "uu_touch", path = "src/uu/touch" } +tr = { optional = true, version = "0.0.26", package = "uu_tr", path = "src/uu/tr" } +true = { optional = true, version = "0.0.26", package = "uu_true", path = "src/uu/true" } +truncate = { optional = true, version = "0.0.26", package = "uu_truncate", path = "src/uu/truncate" } +tsort = { optional = true, version = "0.0.26", package = "uu_tsort", path = "src/uu/tsort" } +tty = { optional = true, version = "0.0.26", package = "uu_tty", path = "src/uu/tty" } +uname = { optional = true, version = "0.0.26", package = "uu_uname", path = "src/uu/uname" } +unexpand = { optional = true, version = "0.0.26", package = "uu_unexpand", path = "src/uu/unexpand" } +uniq = { optional = true, version = "0.0.26", package = "uu_uniq", path = "src/uu/uniq" } +unlink = { optional = true, version = "0.0.26", package = "uu_unlink", path = "src/uu/unlink" } +uptime = { optional = true, version = "0.0.26", package = "uu_uptime", path = "src/uu/uptime" } +users = { optional = true, version = "0.0.26", package = "uu_users", path = "src/uu/users" } +vdir = { optional = true, version = "0.0.26", package = "uu_vdir", path = "src/uu/vdir" } +wc = { optional = true, version = "0.0.26", package = "uu_wc", path = "src/uu/wc" } +who = { optional = true, version = "0.0.26", package = "uu_who", path = "src/uu/who" } +whoami = { optional = true, version = "0.0.26", package = "uu_whoami", path = "src/uu/whoami" } +yes = { optional = true, version = "0.0.26", 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 fa2940acde1..10b64c20a9b 100644 --- a/src/uu/arch/Cargo.toml +++ b/src/uu/arch/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_arch" -version = "0.0.25" +version = "0.0.26" 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 5c8b23cba1f..07eb19832ed 100644 --- a/src/uu/base32/Cargo.toml +++ b/src/uu/base32/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_base32" -version = "0.0.25" +version = "0.0.26" 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 5df285f89a5..b7545729dc5 100644 --- a/src/uu/base64/Cargo.toml +++ b/src/uu/base64/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_base64" -version = "0.0.25" +version = "0.0.26" 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 9262b483a3f..6738190decc 100644 --- a/src/uu/basename/Cargo.toml +++ b/src/uu/basename/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_basename" -version = "0.0.25" +version = "0.0.26" 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 842d80876d2..c84e395ad01 100644 --- a/src/uu/basenc/Cargo.toml +++ b/src/uu/basenc/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_basenc" -version = "0.0.25" +version = "0.0.26" 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 79609c1cedd..250e668f657 100644 --- a/src/uu/cat/Cargo.toml +++ b/src/uu/cat/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_cat" -version = "0.0.25" +version = "0.0.26" 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 7498b7cc99d..27890583d32 100644 --- a/src/uu/chcon/Cargo.toml +++ b/src/uu/chcon/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_chcon" -version = "0.0.25" +version = "0.0.26" 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 21745317e9c..cf27b0be774 100644 --- a/src/uu/chgrp/Cargo.toml +++ b/src/uu/chgrp/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_chgrp" -version = "0.0.25" +version = "0.0.26" 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 2a6a46474cd..d8de8ddddc5 100644 --- a/src/uu/chmod/Cargo.toml +++ b/src/uu/chmod/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_chmod" -version = "0.0.25" +version = "0.0.26" 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 ae4c969c3c9..866822c6911 100644 --- a/src/uu/chown/Cargo.toml +++ b/src/uu/chown/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_chown" -version = "0.0.25" +version = "0.0.26" 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 d6090ec8c18..1382e733800 100644 --- a/src/uu/chroot/Cargo.toml +++ b/src/uu/chroot/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_chroot" -version = "0.0.25" +version = "0.0.26" 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 406aa75df77..f6a5a138a83 100644 --- a/src/uu/cksum/Cargo.toml +++ b/src/uu/cksum/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_cksum" -version = "0.0.25" +version = "0.0.26" 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 41e2e3b7459..c83cee1b534 100644 --- a/src/uu/comm/Cargo.toml +++ b/src/uu/comm/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_comm" -version = "0.0.25" +version = "0.0.26" 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 1e379b72f9f..62aa228ae19 100644 --- a/src/uu/cp/Cargo.toml +++ b/src/uu/cp/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_cp" -version = "0.0.25" +version = "0.0.26" authors = [ "Jordy Dickinson ", "Joshua S. Miller ", diff --git a/src/uu/csplit/Cargo.toml b/src/uu/csplit/Cargo.toml index 8f06524de12..39dc9f45bb4 100644 --- a/src/uu/csplit/Cargo.toml +++ b/src/uu/csplit/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_csplit" -version = "0.0.25" +version = "0.0.26" 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 17ec0c52d87..83403332543 100644 --- a/src/uu/cut/Cargo.toml +++ b/src/uu/cut/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_cut" -version = "0.0.25" +version = "0.0.26" 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 bd7dd254271..1d1695f30d0 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.25" +version = "0.0.26" 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 2e32722f6fc..df2e32cbd81 100644 --- a/src/uu/dd/Cargo.toml +++ b/src/uu/dd/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_dd" -version = "0.0.25" +version = "0.0.26" 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 788db50406d..b4ab60430dd 100644 --- a/src/uu/df/Cargo.toml +++ b/src/uu/df/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_df" -version = "0.0.25" +version = "0.0.26" 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 24da984af03..03499f4b439 100644 --- a/src/uu/dir/Cargo.toml +++ b/src/uu/dir/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_dir" -version = "0.0.25" +version = "0.0.26" 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 15f943a4637..fd12b5aee51 100644 --- a/src/uu/dircolors/Cargo.toml +++ b/src/uu/dircolors/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_dircolors" -version = "0.0.25" +version = "0.0.26" 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 49ebcb653c3..0e2f7ab1777 100644 --- a/src/uu/dirname/Cargo.toml +++ b/src/uu/dirname/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_dirname" -version = "0.0.25" +version = "0.0.26" 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 03c5e1f8f20..bb9a2a97b02 100644 --- a/src/uu/du/Cargo.toml +++ b/src/uu/du/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_du" -version = "0.0.25" +version = "0.0.26" 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 bd70c5eee58..afd913369ae 100644 --- a/src/uu/echo/Cargo.toml +++ b/src/uu/echo/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_echo" -version = "0.0.25" +version = "0.0.26" 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 03bebdde86d..211560d1854 100644 --- a/src/uu/env/Cargo.toml +++ b/src/uu/env/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_env" -version = "0.0.25" +version = "0.0.26" 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 703552476eb..e8d924cd906 100644 --- a/src/uu/expand/Cargo.toml +++ b/src/uu/expand/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_expand" -version = "0.0.25" +version = "0.0.26" 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 efd6c166543..cd3fa142694 100644 --- a/src/uu/expr/Cargo.toml +++ b/src/uu/expr/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_expr" -version = "0.0.25" +version = "0.0.26" 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 ea42131f27d..159f8e8d355 100644 --- a/src/uu/factor/Cargo.toml +++ b/src/uu/factor/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_factor" -version = "0.0.25" +version = "0.0.26" 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 332dffe96cd..0bb70b2adcb 100644 --- a/src/uu/false/Cargo.toml +++ b/src/uu/false/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_false" -version = "0.0.25" +version = "0.0.26" 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 e628408cf28..b2a0f3350f2 100644 --- a/src/uu/fmt/Cargo.toml +++ b/src/uu/fmt/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_fmt" -version = "0.0.25" +version = "0.0.26" 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 7a45d25b9c2..dd65421bbfe 100644 --- a/src/uu/fold/Cargo.toml +++ b/src/uu/fold/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_fold" -version = "0.0.25" +version = "0.0.26" 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 8d0a9d310d2..29de9776d01 100644 --- a/src/uu/groups/Cargo.toml +++ b/src/uu/groups/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_groups" -version = "0.0.25" +version = "0.0.26" 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 668d8a1f385..f3799aedbf4 100644 --- a/src/uu/hashsum/Cargo.toml +++ b/src/uu/hashsum/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_hashsum" -version = "0.0.25" +version = "0.0.26" 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 04816fe82d4..7bbf0514f84 100644 --- a/src/uu/head/Cargo.toml +++ b/src/uu/head/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_head" -version = "0.0.25" +version = "0.0.26" 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 7df3107d1d0..c755c72ca83 100644 --- a/src/uu/hostid/Cargo.toml +++ b/src/uu/hostid/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_hostid" -version = "0.0.25" +version = "0.0.26" 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 95343b5d3f9..d456d0b136c 100644 --- a/src/uu/hostname/Cargo.toml +++ b/src/uu/hostname/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_hostname" -version = "0.0.25" +version = "0.0.26" 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 b15e9909ee8..b0c4656eb60 100644 --- a/src/uu/id/Cargo.toml +++ b/src/uu/id/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_id" -version = "0.0.25" +version = "0.0.26" 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 e8754929970..78b0b467017 100644 --- a/src/uu/install/Cargo.toml +++ b/src/uu/install/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_install" -version = "0.0.25" +version = "0.0.26" 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 54dc547ce8f..9b7f3dd4dc0 100644 --- a/src/uu/join/Cargo.toml +++ b/src/uu/join/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_join" -version = "0.0.25" +version = "0.0.26" 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 3425bff55b4..7e79d868334 100644 --- a/src/uu/kill/Cargo.toml +++ b/src/uu/kill/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_kill" -version = "0.0.25" +version = "0.0.26" 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 56025f96c10..649fd0607d6 100644 --- a/src/uu/link/Cargo.toml +++ b/src/uu/link/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_link" -version = "0.0.25" +version = "0.0.26" 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 33e4588b81e..3326238c6fd 100644 --- a/src/uu/ln/Cargo.toml +++ b/src/uu/ln/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_ln" -version = "0.0.25" +version = "0.0.26" 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 fcb5af8822d..61c88eba0ac 100644 --- a/src/uu/logname/Cargo.toml +++ b/src/uu/logname/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_logname" -version = "0.0.25" +version = "0.0.26" 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 56f76849464..1dae3f033e9 100644 --- a/src/uu/ls/Cargo.toml +++ b/src/uu/ls/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_ls" -version = "0.0.25" +version = "0.0.26" 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 f9ab373a286..4c5b0222dab 100644 --- a/src/uu/mkdir/Cargo.toml +++ b/src/uu/mkdir/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_mkdir" -version = "0.0.25" +version = "0.0.26" 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 3d6247a906e..be6959eb682 100644 --- a/src/uu/mkfifo/Cargo.toml +++ b/src/uu/mkfifo/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_mkfifo" -version = "0.0.25" +version = "0.0.26" 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 7af4f74c369..ac72055a4ce 100644 --- a/src/uu/mknod/Cargo.toml +++ b/src/uu/mknod/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_mknod" -version = "0.0.25" +version = "0.0.26" 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 25703caf464..b5fa9149fd8 100644 --- a/src/uu/mktemp/Cargo.toml +++ b/src/uu/mktemp/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_mktemp" -version = "0.0.25" +version = "0.0.26" 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 81dc0a289d2..7d8ec31dd70 100644 --- a/src/uu/more/Cargo.toml +++ b/src/uu/more/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_more" -version = "0.0.25" +version = "0.0.26" 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 7ee7dcac07f..a39894858dd 100644 --- a/src/uu/mv/Cargo.toml +++ b/src/uu/mv/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_mv" -version = "0.0.25" +version = "0.0.26" 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 b0ad0186420..2c46b0c2c0d 100644 --- a/src/uu/nice/Cargo.toml +++ b/src/uu/nice/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_nice" -version = "0.0.25" +version = "0.0.26" 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 8f0507a0284..709d50ee03d 100644 --- a/src/uu/nl/Cargo.toml +++ b/src/uu/nl/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_nl" -version = "0.0.25" +version = "0.0.26" 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 9f4c8943589..cf1e2f0b5ed 100644 --- a/src/uu/nohup/Cargo.toml +++ b/src/uu/nohup/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_nohup" -version = "0.0.25" +version = "0.0.26" 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 76c58d566be..a97a61a24b6 100644 --- a/src/uu/nproc/Cargo.toml +++ b/src/uu/nproc/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_nproc" -version = "0.0.25" +version = "0.0.26" 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 4fe4db0fdf3..a333b403891 100644 --- a/src/uu/numfmt/Cargo.toml +++ b/src/uu/numfmt/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_numfmt" -version = "0.0.25" +version = "0.0.26" 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 7a80de290ca..ea9477468c3 100644 --- a/src/uu/od/Cargo.toml +++ b/src/uu/od/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_od" -version = "0.0.25" +version = "0.0.26" 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 afc001141ce..b8abf0f6b92 100644 --- a/src/uu/paste/Cargo.toml +++ b/src/uu/paste/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_paste" -version = "0.0.25" +version = "0.0.26" 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 c24ad4b8f50..4af477c8385 100644 --- a/src/uu/pathchk/Cargo.toml +++ b/src/uu/pathchk/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_pathchk" -version = "0.0.25" +version = "0.0.26" 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 e29155ffc74..e5d98b51cc7 100644 --- a/src/uu/pinky/Cargo.toml +++ b/src/uu/pinky/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_pinky" -version = "0.0.25" +version = "0.0.26" 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 629e07c2f32..de932a52801 100644 --- a/src/uu/pr/Cargo.toml +++ b/src/uu/pr/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_pr" -version = "0.0.25" +version = "0.0.26" 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 613eaa47325..0b4c0fa727f 100644 --- a/src/uu/printenv/Cargo.toml +++ b/src/uu/printenv/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_printenv" -version = "0.0.25" +version = "0.0.26" 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 7f98554db8f..8b33bfb74d6 100644 --- a/src/uu/printf/Cargo.toml +++ b/src/uu/printf/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_printf" -version = "0.0.25" +version = "0.0.26" 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 112f49ecf84..5f7d0f26f82 100644 --- a/src/uu/ptx/Cargo.toml +++ b/src/uu/ptx/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_ptx" -version = "0.0.25" +version = "0.0.26" 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 7bfbb36678f..4d43de714d8 100644 --- a/src/uu/pwd/Cargo.toml +++ b/src/uu/pwd/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_pwd" -version = "0.0.25" +version = "0.0.26" 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 52f20a52213..72ec7cb7a9d 100644 --- a/src/uu/readlink/Cargo.toml +++ b/src/uu/readlink/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_readlink" -version = "0.0.25" +version = "0.0.26" 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 c4cc02bdd8c..90556d1194e 100644 --- a/src/uu/realpath/Cargo.toml +++ b/src/uu/realpath/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_realpath" -version = "0.0.25" +version = "0.0.26" 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 1eafc3a30f9..412e1520b26 100644 --- a/src/uu/rm/Cargo.toml +++ b/src/uu/rm/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_rm" -version = "0.0.25" +version = "0.0.26" 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 8c1807019ca..e3f75ad55ea 100644 --- a/src/uu/rmdir/Cargo.toml +++ b/src/uu/rmdir/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_rmdir" -version = "0.0.25" +version = "0.0.26" 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 0b73e0b6c25..4dd466d0c0b 100644 --- a/src/uu/runcon/Cargo.toml +++ b/src/uu/runcon/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_runcon" -version = "0.0.25" +version = "0.0.26" 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 3016c72c2da..a67b4e9d318 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.25" +version = "0.0.26" 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 24c5fe5eb1e..b0b8b121260 100644 --- a/src/uu/shred/Cargo.toml +++ b/src/uu/shred/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_shred" -version = "0.0.25" +version = "0.0.26" 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 80696189a63..3235acab181 100644 --- a/src/uu/shuf/Cargo.toml +++ b/src/uu/shuf/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_shuf" -version = "0.0.25" +version = "0.0.26" 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 4b9214fafce..f4e3104e620 100644 --- a/src/uu/sleep/Cargo.toml +++ b/src/uu/sleep/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_sleep" -version = "0.0.25" +version = "0.0.26" 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 2454138d569..e59cf05c8e9 100644 --- a/src/uu/sort/Cargo.toml +++ b/src/uu/sort/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_sort" -version = "0.0.25" +version = "0.0.26" 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 4a421ea2f7d..305fb27a889 100644 --- a/src/uu/split/Cargo.toml +++ b/src/uu/split/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_split" -version = "0.0.25" +version = "0.0.26" 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 181e82531da..3964d2282e3 100644 --- a/src/uu/stat/Cargo.toml +++ b/src/uu/stat/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_stat" -version = "0.0.25" +version = "0.0.26" 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 218b9826288..9fdce5a7b13 100644 --- a/src/uu/stdbuf/Cargo.toml +++ b/src/uu/stdbuf/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_stdbuf" -version = "0.0.25" +version = "0.0.26" 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.25", package = "uu_stdbuf_libstdbuf", path = "src/libstdbuf" } +libstdbuf = { version = "0.0.26", 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 9eda03b1855..1392b3583a3 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.25" +version = "0.0.26" 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 8dc47d008ca..62c6115c1c9 100644 --- a/src/uu/stty/Cargo.toml +++ b/src/uu/stty/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_stty" -version = "0.0.25" +version = "0.0.26" 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 e59072fb8b6..66189d90431 100644 --- a/src/uu/sum/Cargo.toml +++ b/src/uu/sum/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_sum" -version = "0.0.25" +version = "0.0.26" 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 5859ba33bb6..ade88c7508e 100644 --- a/src/uu/sync/Cargo.toml +++ b/src/uu/sync/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_sync" -version = "0.0.25" +version = "0.0.26" 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 646ecaa0fa3..eb5ccfbb14d 100644 --- a/src/uu/tac/Cargo.toml +++ b/src/uu/tac/Cargo.toml @@ -2,7 +2,7 @@ [package] name = "uu_tac" -version = "0.0.25" +version = "0.0.26" 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 d711fe37451..5e7cba3201d 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.25" +version = "0.0.26" 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 14011a763d4..a1d6afc348e 100644 --- a/src/uu/tee/Cargo.toml +++ b/src/uu/tee/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_tee" -version = "0.0.25" +version = "0.0.26" 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 d58ee83e2c0..3b1dbe8a45b 100644 --- a/src/uu/test/Cargo.toml +++ b/src/uu/test/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_test" -version = "0.0.25" +version = "0.0.26" 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 9f6a51486ef..591f96fa34b 100644 --- a/src/uu/timeout/Cargo.toml +++ b/src/uu/timeout/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_timeout" -version = "0.0.25" +version = "0.0.26" 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 aa763e598f8..ddc828f0e93 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.25" +version = "0.0.26" 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 aa7fa9d8bfa..b4d42b5bd33 100644 --- a/src/uu/tr/Cargo.toml +++ b/src/uu/tr/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_tr" -version = "0.0.25" +version = "0.0.26" 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 6d4947aa5d7..890d1160a3d 100644 --- a/src/uu/true/Cargo.toml +++ b/src/uu/true/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_true" -version = "0.0.25" +version = "0.0.26" 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 151af3eb36a..fa73c74677d 100644 --- a/src/uu/truncate/Cargo.toml +++ b/src/uu/truncate/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_truncate" -version = "0.0.25" +version = "0.0.26" 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 398a6e163bf..999d4b3efa4 100644 --- a/src/uu/tsort/Cargo.toml +++ b/src/uu/tsort/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_tsort" -version = "0.0.25" +version = "0.0.26" 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 4cefb9267ae..acc3b962c9d 100644 --- a/src/uu/tty/Cargo.toml +++ b/src/uu/tty/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_tty" -version = "0.0.25" +version = "0.0.26" 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 53f1b916c99..cdc85d05b06 100644 --- a/src/uu/uname/Cargo.toml +++ b/src/uu/uname/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_uname" -version = "0.0.25" +version = "0.0.26" 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 a0644c04dfd..a6e31d0849f 100644 --- a/src/uu/unexpand/Cargo.toml +++ b/src/uu/unexpand/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_unexpand" -version = "0.0.25" +version = "0.0.26" 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 cc2fed4eeea..a7fd694f370 100644 --- a/src/uu/uniq/Cargo.toml +++ b/src/uu/uniq/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_uniq" -version = "0.0.25" +version = "0.0.26" 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 c94a73ac027..be3777bc7e7 100644 --- a/src/uu/unlink/Cargo.toml +++ b/src/uu/unlink/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_unlink" -version = "0.0.25" +version = "0.0.26" 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 922d566c0de..447728433f7 100644 --- a/src/uu/uptime/Cargo.toml +++ b/src/uu/uptime/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_uptime" -version = "0.0.25" +version = "0.0.26" 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 d483d5d2661..977ef6f7c95 100644 --- a/src/uu/users/Cargo.toml +++ b/src/uu/users/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_users" -version = "0.0.25" +version = "0.0.26" 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 26df48c109a..feecad3af67 100644 --- a/src/uu/vdir/Cargo.toml +++ b/src/uu/vdir/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_vdir" -version = "0.0.25" +version = "0.0.26" 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 7ca801884c4..810c0099fc2 100644 --- a/src/uu/wc/Cargo.toml +++ b/src/uu/wc/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_wc" -version = "0.0.25" +version = "0.0.26" 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 867ad3ee02b..4a74bd927ad 100644 --- a/src/uu/who/Cargo.toml +++ b/src/uu/who/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_who" -version = "0.0.25" +version = "0.0.26" 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 74fb37cc30a..cb9550b1917 100644 --- a/src/uu/whoami/Cargo.toml +++ b/src/uu/whoami/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_whoami" -version = "0.0.25" +version = "0.0.26" 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 1d1bbcbafa0..53bf3f277cf 100644 --- a/src/uu/yes/Cargo.toml +++ b/src/uu/yes/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_yes" -version = "0.0.25" +version = "0.0.26" 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 53e2aa1e2a5..635ee140397 100644 --- a/src/uucore/Cargo.toml +++ b/src/uucore/Cargo.toml @@ -2,7 +2,7 @@ [package] name = "uucore" -version = "0.0.25" +version = "0.0.26" 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 f1817d2a2d9..38948e05166 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.25" +version = "0.0.26" 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.25" } +uuhelp_parser = { path = "../uuhelp_parser", version = "0.0.26" } diff --git a/src/uuhelp_parser/Cargo.toml b/src/uuhelp_parser/Cargo.toml index f3948e13c06..7eb70084524 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.25" +version = "0.0.26" 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 5127677da3a..44ef2ca5396 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.24" -TO="0.0.25" +FROM="0.0.25" +TO="0.0.26" PROGS=$(ls -1d src/uu/*/Cargo.toml src/uu/stdbuf/src/libstdbuf/Cargo.toml src/uucore/Cargo.toml Cargo.toml) From 5c36ed92b8d49001af59a6ab3b5f7f45825f423a Mon Sep 17 00:00:00 2001 From: Ben Wiederhake Date: Sun, 31 Mar 2024 16:47:35 +0200 Subject: [PATCH 034/144] tee: remove unnecessary memory indirection This removes two layers of Box>. The indirection appears to be unintentional, and makes it harder to understand and change the code. --- src/uu/tee/src/tee.rs | 50 +++++++++++++++++++------------------------ 1 file changed, 22 insertions(+), 28 deletions(-) diff --git a/src/uu/tee/src/tee.rs b/src/uu/tee/src/tee.rs index ca6e8a7c611..dea95ca959c 100644 --- a/src/uu/tee/src/tee.rs +++ b/src/uu/tee/src/tee.rs @@ -150,12 +150,7 @@ fn tee(options: &Options) -> Result<()> { .files .clone() .into_iter() - .map(|file| { - Ok(NamedWriter { - name: file.clone(), - inner: open(file, options.append, options.output_error.as_ref())?, - }) - }) + .map(|file| open(file, options.append, options.output_error.as_ref())) .collect::>>()?; writers.insert( @@ -188,31 +183,30 @@ fn tee(options: &Options) -> Result<()> { } } -fn open( - name: String, - append: bool, - output_error: Option<&OutputErrorMode>, -) -> Result> { +fn open(name: String, append: bool, output_error: Option<&OutputErrorMode>) -> Result { let path = PathBuf::from(name.clone()); - let inner: Box = { - let mut options = OpenOptions::new(); - let mode = if append { - options.append(true) - } else { - options.truncate(true) - }; - match mode.write(true).create(true).open(path.as_path()) { - Ok(file) => Box::new(file), - Err(f) => { - show_error!("{}: {}", name.maybe_quote(), f); - match output_error { - Some(OutputErrorMode::Exit | OutputErrorMode::ExitNoPipe) => return Err(f), - _ => Box::new(sink()), - } + let mut options = OpenOptions::new(); + let mode = if append { + options.append(true) + } else { + options.truncate(true) + }; + match mode.write(true).create(true).open(path.as_path()) { + Ok(file) => Ok(NamedWriter { + inner: Box::new(file), + name, + }), + Err(f) => { + show_error!("{}: {}", name.maybe_quote(), f); + match output_error { + Some(OutputErrorMode::Exit | OutputErrorMode::ExitNoPipe) => Err(f), + _ => Ok(NamedWriter { + inner: Box::new(sink()), + name, + }), } } - }; - Ok(Box::new(NamedWriter { inner, name }) as Box) + } } struct MultiWriter { From 1c0adba1ca911a5b459d9da3751f6cbad2ca1e3e Mon Sep 17 00:00:00 2001 From: Ulrich Hornung Date: Sun, 31 Mar 2024 17:41:22 +0200 Subject: [PATCH 035/144] fix macos ci instability on clippy with retry --- .github/workflows/code-quality.yml | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/.github/workflows/code-quality.yml b/.github/workflows/code-quality.yml index 1879bfc78f6..3f7737cdcb8 100644 --- a/.github/workflows/code-quality.yml +++ b/.github/workflows/code-quality.yml @@ -117,16 +117,21 @@ jobs: macos-latest) brew install coreutils ;; # needed for show-utils.sh esac - name: "`cargo clippy` lint testing" - shell: bash - run: | - ## `cargo clippy` lint testing - unset fault - CLIPPY_FLAGS="-W clippy::default_trait_access -W clippy::manual_string_new -W clippy::cognitive_complexity -W clippy::implicit_clone -W clippy::range-plus-one -W clippy::redundant-clone" - fault_type="${{ steps.vars.outputs.FAULT_TYPE }}" - fault_prefix=$(echo "$fault_type" | tr '[:lower:]' '[:upper:]') - # * convert any warnings to GHA UI annotations; ref: - S=$(cargo clippy --all-targets ${{ matrix.job.cargo-options }} ${{ steps.vars.outputs.CARGO_UTILITY_LIST_OPTIONS }} -- ${CLIPPY_FLAGS} -D warnings 2>&1) && printf "%s\n" "$S" || { printf "%s\n" "$S" ; printf "%s" "$S" | sed -E -n -e '/^error:/{' -e "N; s/^error:[[:space:]]+(.*)\\n[[:space:]]+-->[[:space:]]+(.*):([0-9]+):([0-9]+).*$/::${fault_type} file=\2,line=\3,col=\4::${fault_prefix}: \`cargo clippy\`: \1 (file:'\2', line:\3)/p;" -e '}' ; fault=true ; } - if [ -n "${{ steps.vars.outputs.FAIL_ON_FAULT }}" ] && [ -n "$fault" ]; then exit 1 ; fi + uses: nick-fields/retry@v2 + with: + max_attempts: 3 + retry_on: error + timeout_minutes: 90 + shell: bash + command: | + ## `cargo clippy` lint testing + unset fault + CLIPPY_FLAGS="-W clippy::default_trait_access -W clippy::manual_string_new -W clippy::cognitive_complexity -W clippy::implicit_clone -W clippy::range-plus-one -W clippy::redundant-clone" + fault_type="${{ steps.vars.outputs.FAULT_TYPE }}" + fault_prefix=$(echo "$fault_type" | tr '[:lower:]' '[:upper:]') + # * convert any warnings to GHA UI annotations; ref: + S=$(cargo clippy --all-targets ${{ matrix.job.cargo-options }} ${{ steps.vars.outputs.CARGO_UTILITY_LIST_OPTIONS }} -- ${CLIPPY_FLAGS} -D warnings 2>&1) && printf "%s\n" "$S" || { printf "%s\n" "$S" ; printf "%s" "$S" | sed -E -n -e '/^error:/{' -e "N; s/^error:[[:space:]]+(.*)\\n[[:space:]]+-->[[:space:]]+(.*):([0-9]+):([0-9]+).*$/::${fault_type} file=\2,line=\3,col=\4::${fault_prefix}: \`cargo clippy\`: \1 (file:'\2', line:\3)/p;" -e '}' ; fault=true ; } + if [ -n "${{ steps.vars.outputs.FAIL_ON_FAULT }}" ] && [ -n "$fault" ]; then exit 1 ; fi style_spellcheck: name: Style/spelling From 8ab825c49f218da7bc64a16083de66ce4bf50959 Mon Sep 17 00:00:00 2001 From: Ben Wiederhake Date: Sun, 31 Mar 2024 16:56:48 +0200 Subject: [PATCH 036/144] tee: correctly handle writing to read-only files --- src/uu/tee/src/tee.rs | 27 ++++++++++++++++----------- tests/by-util/test_tee.rs | 22 ++++++++++++++++++++++ 2 files changed, 38 insertions(+), 11 deletions(-) diff --git a/src/uu/tee/src/tee.rs b/src/uu/tee/src/tee.rs index dea95ca959c..0278cb40470 100644 --- a/src/uu/tee/src/tee.rs +++ b/src/uu/tee/src/tee.rs @@ -5,7 +5,7 @@ use clap::{builder::PossibleValue, crate_version, Arg, ArgAction, Command}; use std::fs::OpenOptions; -use std::io::{copy, sink, stdin, stdout, Error, ErrorKind, Read, Result, Write}; +use std::io::{copy, stdin, stdout, Error, ErrorKind, Read, Result, Write}; use std::path::PathBuf; use uucore::display::Quotable; use uucore::error::UResult; @@ -150,8 +150,9 @@ fn tee(options: &Options) -> Result<()> { .files .clone() .into_iter() - .map(|file| open(file, options.append, options.output_error.as_ref())) + .filter_map(|file| open(file, options.append, options.output_error.as_ref())) .collect::>>()?; + let had_open_errors = writers.len() != options.files.len(); writers.insert( 0, @@ -176,14 +177,21 @@ fn tee(options: &Options) -> Result<()> { _ => Ok(()), }; - if res.is_err() || output.flush().is_err() || output.error_occurred() { + if had_open_errors || res.is_err() || output.flush().is_err() || output.error_occurred() { Err(Error::from(ErrorKind::Other)) } else { Ok(()) } } -fn open(name: String, append: bool, output_error: Option<&OutputErrorMode>) -> Result { +/// Tries to open the indicated file and return it. Reports an error if that's not possible. +/// If that error should lead to program termination, this function returns Some(Err()), +/// otherwise it returns None. +fn open( + name: String, + append: bool, + output_error: Option<&OutputErrorMode>, +) -> Option> { let path = PathBuf::from(name.clone()); let mut options = OpenOptions::new(); let mode = if append { @@ -192,18 +200,15 @@ fn open(name: String, append: bool, output_error: Option<&OutputErrorMode>) -> R options.truncate(true) }; match mode.write(true).create(true).open(path.as_path()) { - Ok(file) => Ok(NamedWriter { + Ok(file) => Some(Ok(NamedWriter { inner: Box::new(file), name, - }), + })), Err(f) => { show_error!("{}: {}", name.maybe_quote(), f); match output_error { - Some(OutputErrorMode::Exit | OutputErrorMode::ExitNoPipe) => Err(f), - _ => Ok(NamedWriter { - inner: Box::new(sink()), - name, - }), + Some(OutputErrorMode::Exit | OutputErrorMode::ExitNoPipe) => Some(Err(f)), + _ => None, } } } diff --git a/tests/by-util/test_tee.rs b/tests/by-util/test_tee.rs index c186682785a..9ff4ea7dcf8 100644 --- a/tests/by-util/test_tee.rs +++ b/tests/by-util/test_tee.rs @@ -3,6 +3,7 @@ // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. use crate::common::util::TestScenario; +use regex::Regex; #[cfg(target_os = "linux")] use std::fmt::Write; @@ -92,6 +93,27 @@ fn test_tee_no_more_writeable_1() { assert_eq!(at.read(file_out), content); } +#[test] +fn test_readonly() { + let (at, mut ucmd) = at_and_ucmd!(); + let content_tee = "hello"; + let content_file = "world"; + let file_out = "tee_file_out"; + let writable_file = "tee_file_out2"; + at.write(file_out, content_file); + at.set_readonly(file_out); + ucmd.arg(file_out) + .arg(writable_file) + .pipe_in(content_tee) + .ignore_stdin_write_error() + .fails() + .stdout_is(content_tee) + // Windows says "Access is denied" for some reason. + .stderr_matches(&Regex::new("(Permission|Access is) denied").unwrap()); + assert_eq!(at.read(file_out), content_file); + assert_eq!(at.read(writable_file), content_tee); +} + #[test] #[cfg(target_os = "linux")] fn test_tee_no_more_writeable_2() { From 675dd9404a9482c445a414c18b369a62b1aeec65 Mon Sep 17 00:00:00 2001 From: Ben Wiederhake Date: Sun, 31 Mar 2024 16:59:34 +0200 Subject: [PATCH 037/144] tee: avoid unnecessarily cloning argument list --- src/uu/tee/src/tee.rs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/uu/tee/src/tee.rs b/src/uu/tee/src/tee.rs index 0278cb40470..b1443dbb95b 100644 --- a/src/uu/tee/src/tee.rs +++ b/src/uu/tee/src/tee.rs @@ -148,8 +148,7 @@ fn tee(options: &Options) -> Result<()> { } let mut writers: Vec = options .files - .clone() - .into_iter() + .iter() .filter_map(|file| open(file, options.append, options.output_error.as_ref())) .collect::>>()?; let had_open_errors = writers.len() != options.files.len(); @@ -188,11 +187,11 @@ fn tee(options: &Options) -> Result<()> { /// If that error should lead to program termination, this function returns Some(Err()), /// otherwise it returns None. fn open( - name: String, + name: &str, append: bool, output_error: Option<&OutputErrorMode>, ) -> Option> { - let path = PathBuf::from(name.clone()); + let path = PathBuf::from(name); let mut options = OpenOptions::new(); let mode = if append { options.append(true) @@ -202,7 +201,7 @@ fn open( match mode.write(true).create(true).open(path.as_path()) { Ok(file) => Some(Ok(NamedWriter { inner: Box::new(file), - name, + name: name.to_owned(), })), Err(f) => { show_error!("{}: {}", name.maybe_quote(), f); From af0ba8665793594f72f397318984723b9b6d5bf9 Mon Sep 17 00:00:00 2001 From: Michael Vogt Date: Sun, 31 Mar 2024 21:15:46 +0200 Subject: [PATCH 038/144] date: support `-f -` to read from stdin So far `date -f -` was not reading from stdin. This commit fixes this. Closes: #6058 --- src/uu/date/src/date.rs | 11 ++++++++++- tests/by-util/test_date.rs | 18 ++++++++++++++++++ 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/src/uu/date/src/date.rs b/src/uu/date/src/date.rs index 76b245923dd..5d6e8fd22e0 100644 --- a/src/uu/date/src/date.rs +++ b/src/uu/date/src/date.rs @@ -91,6 +91,7 @@ enum DateSource { Now, Custom(String), File(PathBuf), + Stdin, Human(TimeDelta), } @@ -173,7 +174,10 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { DateSource::Custom(date.into()) } } else if let Some(file) = matches.get_one::(OPT_FILE) { - DateSource::File(file.into()) + match file.as_ref() { + "-" => DateSource::Stdin, + _ => DateSource::File(file.into()), + } } else { DateSource::Now }; @@ -240,6 +244,11 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { } } } + DateSource::Stdin => { + let lines = BufReader::new(std::io::stdin()).lines(); + let iter = lines.map_while(Result::ok).map(parse_date); + Box::new(iter) + } DateSource::File(ref path) => { if path.is_dir() { return Err(USimpleError::new( diff --git a/tests/by-util/test_date.rs b/tests/by-util/test_date.rs index f45b81cb585..16a01c6558d 100644 --- a/tests/by-util/test_date.rs +++ b/tests/by-util/test_date.rs @@ -426,3 +426,21 @@ fn test_date_parse_from_format() { .arg("+%Y-%m-%d %H:%M:%S") .succeeds(); } + +#[test] +fn test_date_from_stdin() { + new_ucmd!() + .arg("-f") + .arg("-") + .pipe_in( + "2023-03-27 08:30:00\n\ + 2023-04-01 12:00:00\n\ + 2023-04-15 18:30:00\n", + ) + .succeeds() + .stdout_is( + "Mon Mar 27 08:30:00 2023\n\ + Sat Apr 1 12:00:00 2023\n\ + Sat Apr 15 18:30:00 2023\n", + ); +} From 9e040a3df15119a115ce5b132fd1a5af2b851a29 Mon Sep 17 00:00:00 2001 From: Paolo Barbolini Date: Sun, 31 Mar 2024 22:48:56 +0200 Subject: [PATCH 039/144] chore(deps): drop conv dev-dependency --- Cargo.lock | 16 ---------------- Cargo.toml | 1 - tests/by-util/test_factor.rs | 4 +--- 3 files changed, 1 insertion(+), 20 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1c0e213e42e..0b87e23b842 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -363,15 +363,6 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f7144d30dcf0fafbce74250a3963025d8d52177934239851c917d29f1df280c2" -[[package]] -name = "conv" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78ff10625fd0ac447827aa30ea8b861fead473bb60aeb73af6c1c58caf0d1299" -dependencies = [ - "custom_derive", -] - [[package]] name = "core-foundation-sys" version = "0.8.3" @@ -386,7 +377,6 @@ dependencies = [ "clap", "clap_complete", "clap_mangen", - "conv", "filetime", "glob", "hex-literal", @@ -687,12 +677,6 @@ dependencies = [ "windows-sys 0.52.0", ] -[[package]] -name = "custom_derive" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef8ae57c4978a2acd8b869ce6b9ca1dfe817bff704c220209fdef2c0b75a01b9" - [[package]] name = "data-encoding" version = "2.5.0" diff --git a/Cargo.toml b/Cargo.toml index 36b0cad807d..81312f31602 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -477,7 +477,6 @@ yes = { optional = true, version = "0.0.26", package = "uu_yes", path = "src/uu/ [dev-dependencies] chrono = { workspace = true } -conv = "0.3" filetime = { workspace = true } glob = { workspace = true } libc = { workspace = true } diff --git a/tests/by-util/test_factor.rs b/tests/by-util/test_factor.rs index a0657139865..ae4f908a77e 100644 --- a/tests/by-util/test_factor.rs +++ b/tests/by-util/test_factor.rs @@ -154,9 +154,7 @@ fn test_cli_args() { #[test] fn test_random() { - use conv::prelude::ValueFrom; - - let log_num_primes = f64::value_from(NUM_PRIMES).unwrap().log2().ceil(); + let log_num_primes = f64::from(u32::try_from(NUM_PRIMES).unwrap()).log2().ceil(); let primes = Sieve::primes().take(NUM_PRIMES).collect::>(); let rng_seed = SystemTime::now() From 39b60599106040562bca705297d7a7768b2587f5 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Mon, 1 Apr 2024 00:56:22 +0200 Subject: [PATCH 040/144] rustfmt the expr fuzzer --- fuzz/fuzz_targets/fuzz_expr.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/fuzz/fuzz_targets/fuzz_expr.rs b/fuzz/fuzz_targets/fuzz_expr.rs index 8bc18fae47e..4d55155b188 100644 --- a/fuzz/fuzz_targets/fuzz_expr.rs +++ b/fuzz/fuzz_targets/fuzz_expr.rs @@ -22,7 +22,8 @@ static CMD_PATH: &str = "expr"; fn generate_expr(max_depth: u32) -> String { let mut rng = rand::thread_rng(); let ops = [ - "+", "-", "*", "/", "%", "<", ">", "=", "&", "|", "!=", "<=", ">=", ":", "index", "length", "substr", + "+", "-", "*", "/", "%", "<", ">", "=", "&", "|", "!=", "<=", ">=", ":", "index", "length", + "substr", ]; let mut expr = String::new(); From 08f324bf4073af217ea59528eb2454b7d723951c Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Mon, 1 Apr 2024 01:01:39 +0200 Subject: [PATCH 041/144] Silent some clippy warnings --- fuzz/fuzz_targets/fuzz_echo.rs | 1 + fuzz/fuzz_targets/fuzz_test.rs | 1 + 2 files changed, 2 insertions(+) diff --git a/fuzz/fuzz_targets/fuzz_echo.rs b/fuzz/fuzz_targets/fuzz_echo.rs index 3f15b257e6e..c5f986b8d08 100644 --- a/fuzz/fuzz_targets/fuzz_echo.rs +++ b/fuzz/fuzz_targets/fuzz_echo.rs @@ -21,6 +21,7 @@ fn generate_echo() -> String { // Randomly decide whether to include options let include_n = rng.gen_bool(0.1); // 10% chance let include_e = rng.gen_bool(0.1); // 10% chance + #[allow(non_snake_case)] let include_E = rng.gen_bool(0.1); // 10% chance if include_n { diff --git a/fuzz/fuzz_targets/fuzz_test.rs b/fuzz/fuzz_targets/fuzz_test.rs index bed7ca77088..045462fb34c 100644 --- a/fuzz/fuzz_targets/fuzz_test.rs +++ b/fuzz/fuzz_targets/fuzz_test.rs @@ -18,6 +18,7 @@ use crate::fuzz_common::{ compare_result, generate_and_run_uumain, generate_random_string, run_gnu_cmd, }; +#[allow(clippy::upper_case_acronyms)] #[derive(PartialEq, Debug, Clone)] enum ArgType { STRING, From ffda2a4b4014b92f1569fbe4c6264875065d3b1b Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sun, 31 Mar 2024 23:41:43 +0000 Subject: [PATCH 042/144] chore(deps): update nick-fields/retry action to v3 --- .github/workflows/code-quality.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/code-quality.yml b/.github/workflows/code-quality.yml index 3f7737cdcb8..0d6d3b41415 100644 --- a/.github/workflows/code-quality.yml +++ b/.github/workflows/code-quality.yml @@ -117,7 +117,7 @@ jobs: macos-latest) brew install coreutils ;; # needed for show-utils.sh esac - name: "`cargo clippy` lint testing" - uses: nick-fields/retry@v2 + uses: nick-fields/retry@v3 with: max_attempts: 3 retry_on: error From 49c7e65f5dea0f048b0cd4a4f88e8ae84a019767 Mon Sep 17 00:00:00 2001 From: Carbrex <95964955+Carbrex@users.noreply.github.com> Date: Mon, 1 Apr 2024 16:48:19 +0530 Subject: [PATCH 043/144] Use time-style only if time is provided --- src/uu/du/src/du.rs | 9 +++++++-- tests/by-util/test_du.rs | 10 ++++++++++ 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/uu/du/src/du.rs b/src/uu/du/src/du.rs index b71d5f44c13..0de16121aa5 100644 --- a/src/uu/du/src/du.rs +++ b/src/uu/du/src/du.rs @@ -721,6 +721,12 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { excludes: build_exclude_patterns(&matches)?, }; + let time_format = if time.is_some() { + parse_time_style(matches.get_one::("time-style").map(|s| s.as_str()))?.to_string() + } else { + "%Y-%m-%d %H:%M".to_string() + }; + let stat_printer = StatPrinter { max_depth, size_format, @@ -737,8 +743,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { .transpose()?, apparent_size: matches.get_flag(options::APPARENT_SIZE) || matches.get_flag(options::BYTES), time, - time_format: parse_time_style(matches.get_one::("time-style").map(|s| s.as_str()))? - .to_string(), + time_format, line_ending: LineEnding::from_zero_flag(matches.get_flag(options::NULL)), }; diff --git a/tests/by-util/test_du.rs b/tests/by-util/test_du.rs index 92469b6f52d..dbd2c9c946d 100644 --- a/tests/by-util/test_du.rs +++ b/tests/by-util/test_du.rs @@ -1116,3 +1116,13 @@ fn test_du_files0_from_combined() { assert!(stderr.contains("file operands cannot be combined with --files0-from")); } + +#[test] +fn test_invalid_time_style() { + let ts = TestScenario::new(util_name!()); + ts.ucmd() + .arg("-s") + .arg("--time-style=banana") + .succeeds() + .stdout_does_not_contain("du: invalid argument 'banana' for 'time style'"); +} From cac7155fba4260763ecd48894ca3be3ef087a2d9 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 1 Apr 2024 13:29:20 +0000 Subject: [PATCH 044/144] chore(deps): update rust crate hostname to 0.4 --- Cargo.lock | 33 +++++++++++++++++++++++---------- Cargo.toml | 2 +- 2 files changed, 24 insertions(+), 11 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0b87e23b842..e10ce8c10a6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1034,13 +1034,13 @@ checksum = "6fe2267d4ed49bc07b63801559be28c718ea06c4738b7a03c94df7386d2cde46" [[package]] name = "hostname" -version = "0.3.1" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c731c3e10504cc8ed35cfe2f1db4c9274c3d35fa486e3b31df46f068ef3e867" +checksum = "f9c7c7c8ac16c798734b8a24560c1362120597c40d5e1459f09498f8f6c8f2ba" dependencies = [ + "cfg-if", "libc", - "match_cfg", - "winapi", + "windows", ] [[package]] @@ -1233,12 +1233,6 @@ dependencies = [ "nu-ansi-term", ] -[[package]] -name = "match_cfg" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffbee8634e0d45d258acb448e7eaab3fce7a0a467395d4d9f228e3c1f01fb2e4" - [[package]] name = "md-5" version = "0.10.6" @@ -3378,6 +3372,25 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e48a53791691ab099e5e2ad123536d0fff50652600abaf43bbf952894110d0be" +dependencies = [ + "windows-core", + "windows-targets 0.52.0", +] + +[[package]] +name = "windows-core" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" +dependencies = [ + "windows-targets 0.52.0", +] + [[package]] name = "windows-sys" version = "0.45.0" diff --git a/Cargo.toml b/Cargo.toml index 81312f31602..7b699b9ef21 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -285,7 +285,7 @@ fundu = "2.0.0" gcd = "2.3" glob = "0.3.1" half = "2.4" -hostname = "0.3" +hostname = "0.4" indicatif = "0.17" itertools = "0.12.1" libc = "0.2.153" From 58aab48ab294cdbea853b796f99f92e15b2570b5 Mon Sep 17 00:00:00 2001 From: Ben Wiederhake Date: Mon, 1 Apr 2024 20:51:27 +0200 Subject: [PATCH 045/144] fuzz: also generate the empty string sometimes Inspired by #6167, #6175, and the observation that 'echo hello "" world | hd' outputs extra spaces. --- fuzz/fuzz_targets/fuzz_common.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fuzz/fuzz_targets/fuzz_common.rs b/fuzz/fuzz_targets/fuzz_common.rs index cf56268d75a..6c261cf8165 100644 --- a/fuzz/fuzz_targets/fuzz_common.rs +++ b/fuzz/fuzz_targets/fuzz_common.rs @@ -374,7 +374,7 @@ pub fn generate_random_string(max_length: usize) -> String { let invalid_utf8 = [0xC3, 0x28]; // Invalid UTF-8 sequence let mut result = String::new(); - for _ in 0..rng.gen_range(1..=max_length) { + for _ in 0..rng.gen_range(0..=max_length) { if rng.gen_bool(0.9) { let ch = valid_utf8.choose(&mut rng).unwrap(); result.push(*ch); From 4a1bd78f482f31b183d07ff4d91cfbcd95ebd5cd Mon Sep 17 00:00:00 2001 From: Ben Wiederhake Date: Sun, 31 Mar 2024 21:15:37 +0200 Subject: [PATCH 046/144] ls: compute correct exit code on error Note in particular that this seems to be the only tool where invalid stringly-enum values cause a different exit code than invalid arguments. --- src/uu/ls/src/ls.rs | 20 +++++++++++++-- tests/by-util/test_ls.rs | 53 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 71 insertions(+), 2 deletions(-) diff --git a/src/uu/ls/src/ls.rs b/src/uu/ls/src/ls.rs index 333360f50ec..209745adebb 100644 --- a/src/uu/ls/src/ls.rs +++ b/src/uu/ls/src/ls.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) somegroup nlink tabsize dired subdired dtype colorterm +// spell-checker:ignore (ToDO) somegroup nlink tabsize dired subdired dtype colorterm stringly use clap::{ builder::{NonEmptyStringValueParser, ValueParser}, @@ -36,6 +36,7 @@ use std::{ }; use term_grid::{Cell, Direction, Filling, Grid, GridOptions}; use unicode_width::UnicodeWidthStr; +use uucore::error::USimpleError; #[cfg(all(unix, not(any(target_os = "android", target_os = "macos"))))] use uucore::fsxattr::has_acl; #[cfg(any( @@ -1150,7 +1151,22 @@ impl Config { pub fn uumain(args: impl uucore::Args) -> UResult<()> { let command = uu_app(); - let matches = command.try_get_matches_from(args)?; + let matches = match command.try_get_matches_from(args) { + // clap successfully parsed the arguments: + Ok(matches) => matches, + // --help, --version, etc.: + Err(e) if e.exit_code() == 0 => { + return Err(e.into()); + } + // Errors in argument *values* cause exit code 1: + Err(e) if e.kind() == clap::error::ErrorKind::InvalidValue => { + return Err(USimpleError::new(1, e.to_string())); + } + // All other argument parsing errors cause exit code 2: + Err(e) => { + return Err(USimpleError::new(2, e.to_string())); + } + }; let config = Config::from(&matches)?; diff --git a/tests/by-util/test_ls.rs b/tests/by-util/test_ls.rs index 3f0154f7af9..efe9e1056f9 100644 --- a/tests/by-util/test_ls.rs +++ b/tests/by-util/test_ls.rs @@ -44,11 +44,64 @@ const COMMA_ARGS: &[&str] = &["-m", "--format=commas", "--for=commas"]; const COLUMN_ARGS: &[&str] = &["-C", "--format=columns", "--for=columns"]; +#[test] +fn test_invalid_flag() { + new_ucmd!() + .arg("--invalid-argument") + .fails() + .no_stdout() + .code_is(2); +} + +#[test] +fn test_invalid_value_returns_1() { + // Invalid values to flags *sometimes* result in error code 1: + for flag in [ + "--classify", + "--color", + "--format", + "--hyperlink", + "--indicator-style", + "--quoting-style", + "--sort", + "--time", + ] { + new_ucmd!() + .arg(&format!("{flag}=definitely_invalid_value")) + .fails() + .no_stdout() + .code_is(1); + } +} + +#[test] +fn test_invalid_value_returns_2() { + // Invalid values to flags *sometimes* result in error code 2: + for flag in ["--block-size", "--width", "--tab-size"] { + new_ucmd!() + .arg(&format!("{flag}=definitely_invalid_value")) + .fails() + .no_stdout() + .code_is(2); + } +} + #[test] fn test_ls_ls() { new_ucmd!().succeeds(); } +#[test] +fn test_ls_help() { + // Because we have to work around a lot of clap's error handling and + // modify the exit-code, this is actually non-trivial. + new_ucmd!() + .arg("--help") + .succeeds() + .stdout_contains("--version") + .no_stderr(); +} + #[test] fn test_ls_i() { new_ucmd!().arg("-i").succeeds(); From 714b4ff589a4f04bc1702937882b64dc13c76f50 Mon Sep 17 00:00:00 2001 From: Ben Wiederhake Date: Mon, 1 Apr 2024 04:41:24 +0200 Subject: [PATCH 047/144] ls: fix exit code for --time-style when used --- src/uu/ls/src/ls.rs | 7 ++++++- tests/by-util/test_ls.rs | 25 +++++++++++++++++++++++++ 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/src/uu/ls/src/ls.rs b/src/uu/ls/src/ls.rs index 209745adebb..ec7fab4e9d2 100644 --- a/src/uu/ls/src/ls.rs +++ b/src/uu/ls/src/ls.rs @@ -966,7 +966,12 @@ impl Config { let mut quoting_style = extract_quoting_style(options, show_control); let indicator_style = extract_indicator_style(options); - let time_style = parse_time_style(options)?; + // Only parse the value to "--time-style" if it will become relevant. + let time_style = if format == Format::Long { + parse_time_style(options)? + } else { + TimeStyle::Iso + }; let mut ignore_patterns: Vec = Vec::new(); diff --git a/tests/by-util/test_ls.rs b/tests/by-util/test_ls.rs index efe9e1056f9..2f48e4460bb 100644 --- a/tests/by-util/test_ls.rs +++ b/tests/by-util/test_ls.rs @@ -86,6 +86,31 @@ fn test_invalid_value_returns_2() { } } +#[test] +fn test_invalid_value_time_style() { + // This is the only flag which does not raise an error if it is invalid but not actually used: + new_ucmd!() + .arg("--time-style=definitely_invalid_value") + .succeeds() + .no_stderr() + .code_is(0); + // If it is used, error: + new_ucmd!() + .arg("-g") + .arg("--time-style=definitely_invalid_value") + .fails() + .no_stdout() + .code_is(2); + // If it only looks temporarily like it might be used, no error: + new_ucmd!() + .arg("-l") + .arg("--time-style=definitely_invalid_value") + .arg("--format=single-column") + .succeeds() + .no_stderr() + .code_is(0); +} + #[test] fn test_ls_ls() { new_ucmd!().succeeds(); From 7919b4e3cd76c0e7021ae617399894b2acd3916e Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Mon, 1 Apr 2024 01:03:14 +0200 Subject: [PATCH 048/144] fuzz: start fuzzing env & tr --- fuzz/Cargo.toml | 14 +++++ fuzz/fuzz_targets/fuzz_env.rs | 97 +++++++++++++++++++++++++++++++++++ fuzz/fuzz_targets/fuzz_tr.rs | 73 ++++++++++++++++++++++++++ 3 files changed, 184 insertions(+) create mode 100644 fuzz/fuzz_targets/fuzz_env.rs create mode 100644 fuzz/fuzz_targets/fuzz_tr.rs diff --git a/fuzz/Cargo.toml b/fuzz/Cargo.toml index dfb62aba5e1..a97054192f1 100644 --- a/fuzz/Cargo.toml +++ b/fuzz/Cargo.toml @@ -25,6 +25,8 @@ uu_sort = { path = "../src/uu/sort/" } uu_wc = { path = "../src/uu/wc/" } uu_cut = { path = "../src/uu/cut/" } uu_split = { path = "../src/uu/split/" } +uu_tr = { path = "../src/uu/tr/" } +uu_env = { path = "../src/uu/env/" } # Prevent this from interfering with workspaces [workspace] @@ -107,3 +109,15 @@ name = "fuzz_parse_time" path = "fuzz_targets/fuzz_parse_time.rs" test = false doc = false + +[[bin]] +name = "fuzz_tr" +path = "fuzz_targets/fuzz_tr.rs" +test = false +doc = false + +[[bin]] +name = "fuzz_env" +path = "fuzz_targets/fuzz_env.rs" +test = false +doc = false diff --git a/fuzz/fuzz_targets/fuzz_env.rs b/fuzz/fuzz_targets/fuzz_env.rs new file mode 100644 index 00000000000..955ba414916 --- /dev/null +++ b/fuzz/fuzz_targets/fuzz_env.rs @@ -0,0 +1,97 @@ +// This file is part of the uutils coreutils package. +// +// For the full copyright and license information, please view the LICENSE +// file that was distributed with this source code. +// spell-checker:ignore chdir + +#![no_main] +use libfuzzer_sys::fuzz_target; +use uu_env::uumain; + +use std::ffi::OsString; + +mod fuzz_common; +use crate::fuzz_common::{ + compare_result, generate_and_run_uumain, generate_random_string, run_gnu_cmd, CommandResult, +}; +use rand::Rng; + +static CMD_PATH: &str = "env"; + +fn generate_env_args() -> Vec { + let mut rng = rand::thread_rng(); + let mut args = Vec::new(); + + let opts = ["-i", "-0", "-v", "-vv"]; + for opt in &opts { + if rng.gen_bool(0.2) { + args.push(opt.to_string()); + } + } + + if rng.gen_bool(0.3) { + args.push(format!( + "-u={}", + generate_random_string(rng.gen_range(3..10)) + )); + } + + if rng.gen_bool(0.2) { + args.push(format!("--chdir={}", "/tmp")); // Simplified example + } + + /* + Options not implemented for now + if rng.gen_bool(0.15) { + let sig_opts = ["--block-signal"];//, /*"--default-signal",*/ "--ignore-signal"]; + let chosen_sig_opt = sig_opts[rng.gen_range(0..sig_opts.len())]; + args.push(chosen_sig_opt.to_string()); + // Simplify by assuming SIGPIPE for demonstration + if !chosen_sig_opt.ends_with("list-signal-handling") { + args.push(String::from("SIGPIPE")); + } + }*/ + + // Adding a few random NAME=VALUE pairs + for _ in 0..rng.gen_range(0..3) { + args.push(format!( + "{}={}", + generate_random_string(5), + generate_random_string(5) + )); + } + + args +} + +fuzz_target!(|_data: &[u8]| { + let env_args = generate_env_args(); + let mut args = vec![OsString::from("env")]; + args.extend(env_args.iter().map(OsString::from)); + let input_lines = generate_random_string(10); + + let rust_result = generate_and_run_uumain(&args, uumain, Some(&input_lines)); + + let gnu_result = match run_gnu_cmd(CMD_PATH, &args[1..], false, None) { + 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( + "env", + &format!("{:?}", &args[1..]), + None, + &rust_result, + &gnu_result, + false, + ); +}); diff --git a/fuzz/fuzz_targets/fuzz_tr.rs b/fuzz/fuzz_targets/fuzz_tr.rs new file mode 100644 index 00000000000..d67046be4b5 --- /dev/null +++ b/fuzz/fuzz_targets/fuzz_tr.rs @@ -0,0 +1,73 @@ +// 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. + +#![no_main] +use libfuzzer_sys::fuzz_target; +use std::ffi::OsString; +use uu_tr::uumain; + +use rand::Rng; + +mod fuzz_common; +use crate::fuzz_common::{ + compare_result, generate_and_run_uumain, generate_random_string, run_gnu_cmd, CommandResult, +}; +static CMD_PATH: &str = "tr"; + +fn generate_tr_args() -> Vec { + let mut rng = rand::thread_rng(); + let mut args = Vec::new(); + + // Translate, squeeze, and/or delete characters + let opts = ["-c", "-d", "-s", "-t"]; + for opt in &opts { + if rng.gen_bool(0.25) { + args.push(opt.to_string()); + } + } + + // Generating STRING1 and optionally STRING2 + let string1 = generate_random_string(rng.gen_range(1..=20)); + args.push(string1); + if rng.gen_bool(0.7) { + // Higher chance to add STRING2 for translation + let string2 = generate_random_string(rng.gen_range(1..=20)); + args.push(string2); + } + + args +} + +fuzz_target!(|_data: &[u8]| { + let tr_args = generate_tr_args(); + let mut args = vec![OsString::from("tr")]; + args.extend(tr_args.iter().map(OsString::from)); + + let input_chars = generate_random_string(100); + + let rust_result = generate_and_run_uumain(&args, uumain, Some(&input_chars)); + let gnu_result = match run_gnu_cmd(CMD_PATH, &args[1..], false, Some(&input_chars)) { + 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( + "tr", + &format!("{:?}", &args[1..]), + Some(&input_chars), + &rust_result, + &gnu_result, + false, + ); +}); From 14ef1a204c90ef9ecca90580df34b185ca00c0bf Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Mon, 1 Apr 2024 01:05:20 +0200 Subject: [PATCH 049/144] fuzz: run the new fuzzers in the CI --- .github/workflows/fuzzing.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/fuzzing.yml b/.github/workflows/fuzzing.yml index c60c01ff4be..e045d9b4f04 100644 --- a/.github/workflows/fuzzing.yml +++ b/.github/workflows/fuzzing.yml @@ -53,6 +53,8 @@ jobs: - { name: fuzz_wc, should_pass: false } - { name: fuzz_cut, should_pass: false } - { name: fuzz_split, should_pass: false } + - { name: fuzz_tr, should_pass: false } + - { name: fuzz_env, should_pass: false } - { name: fuzz_parse_glob, should_pass: true } - { name: fuzz_parse_size, should_pass: true } - { name: fuzz_parse_time, should_pass: true } From da0b580504f320b11f9701253825d64a9996292e Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Tue, 2 Apr 2024 14:23:29 +0200 Subject: [PATCH 050/144] CI: change publish step condition Hopefully, this fixes our release artifact problems. --- .github/workflows/CICD.yml | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/.github/workflows/CICD.yml b/.github/workflows/CICD.yml index b191b41d33e..30e1ae7a878 100644 --- a/.github/workflows/CICD.yml +++ b/.github/workflows/CICD.yml @@ -541,9 +541,6 @@ jobs: PKG_BASENAME=${PROJECT_NAME}-${REF_TAG:-$REF_SHAS}-${{ matrix.job.target }} PKG_NAME=${PKG_BASENAME}${PKG_suffix} outputs PKG_suffix PKG_BASENAME PKG_NAME - # deployable tag? (ie, leading "vM" or "M"; M == version number) - unset DEPLOY ; if [[ $REF_TAG =~ ^[vV]?[0-9].* ]]; then DEPLOY='true' ; fi - outputs DEPLOY # DPKG architecture? unset DPKG_ARCH case ${{ matrix.job.target }} in @@ -749,7 +746,7 @@ jobs: fi - name: Publish uses: softprops/action-gh-release@v2 - if: steps.vars.outputs.DEPLOY + if: startsWith(github.ref, 'refs/tags/') with: files: | ${{ steps.vars.outputs.STAGING }}/${{ steps.vars.outputs.PKG_NAME }} From 2fe5dc874ec5319c41bf9c12f920d7e1b0e2cf97 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Tue, 2 Apr 2024 23:29:37 +0200 Subject: [PATCH 051/144] Fix clippy warning match_bool --- src/uu/cp/src/cp.rs | 25 +++++++++++++--------- src/uu/cut/src/cut.rs | 11 ++++++---- src/uu/ls/src/ls.rs | 7 +++--- src/uu/timeout/src/timeout.rs | 17 +++++++-------- src/uucore/src/lib/features/format/spec.rs | 19 ++++++++-------- 5 files changed, 43 insertions(+), 36 deletions(-) diff --git a/src/uu/cp/src/cp.rs b/src/uu/cp/src/cp.rs index 778ddf843b6..6a8649dc179 100644 --- a/src/uu/cp/src/cp.rs +++ b/src/uu/cp/src/cp.rs @@ -1065,10 +1065,13 @@ impl Options { #[cfg(unix)] fn preserve_mode(&self) -> (bool, bool) { match self.attributes.mode { - Preserve::No { explicit } => match explicit { - true => (false, true), - false => (false, false), - }, + Preserve::No { explicit } => { + if explicit { + (false, true) + } else { + (false, false) + } + } Preserve::Yes { .. } => (true, false), } } @@ -2034,9 +2037,10 @@ fn handle_no_preserve_mode(options: &Options, org_mode: u32) -> u32 { { const MODE_RW_UGO: u32 = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH; const S_IRWXUGO: u32 = S_IRWXU | S_IRWXG | S_IRWXO; - match is_explicit_no_preserve_mode { - true => return MODE_RW_UGO, - false => return org_mode & S_IRWXUGO, + if is_explicit_no_preserve_mode { + return MODE_RW_UGO; + } else { + return org_mode & S_IRWXUGO; }; } @@ -2051,9 +2055,10 @@ fn handle_no_preserve_mode(options: &Options, org_mode: u32) -> u32 { const MODE_RW_UGO: u32 = (S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH) as u32; const S_IRWXUGO: u32 = (S_IRWXU | S_IRWXG | S_IRWXO) as u32; - match is_explicit_no_preserve_mode { - true => return MODE_RW_UGO, - false => return org_mode & S_IRWXUGO, + if is_explicit_no_preserve_mode { + return MODE_RW_UGO; + } else { + return org_mode & S_IRWXUGO; }; } } diff --git a/src/uu/cut/src/cut.rs b/src/uu/cut/src/cut.rs index 1b9194c170b..35152c85b66 100644 --- a/src/uu/cut/src/cut.rs +++ b/src/uu/cut/src/cut.rs @@ -408,10 +408,13 @@ fn get_delimiters( } } } - None => match whitespace_delimited { - true => Delimiter::Whitespace, - false => Delimiter::default(), - }, + None => { + if whitespace_delimited { + Delimiter::Whitespace + } else { + Delimiter::default() + } + } }; let out_delim = matches .get_one::(options::OUTPUT_DELIMITER) diff --git a/src/uu/ls/src/ls.rs b/src/uu/ls/src/ls.rs index ec7fab4e9d2..65e0969cbd7 100644 --- a/src/uu/ls/src/ls.rs +++ b/src/uu/ls/src/ls.rs @@ -763,9 +763,10 @@ fn extract_indicator_style(options: &clap::ArgMatches) -> IndicatorStyle { } fn parse_width(s: &str) -> Result { - let radix = match s.starts_with('0') && s.len() > 1 { - true => 8, - false => 10, + let radix = if s.starts_with('0') && s.len() > 1 { + 8 + } else { + 10 }; match u16::from_str_radix(s, radix) { Ok(x) => Ok(x), diff --git a/src/uu/timeout/src/timeout.rs b/src/uu/timeout/src/timeout.rs index ccc97403d51..19016900ac2 100644 --- a/src/uu/timeout/src/timeout.rs +++ b/src/uu/timeout/src/timeout.rs @@ -202,15 +202,14 @@ fn send_signal(process: &mut Child, signal: usize, foreground: bool) { // NOTE: GNU timeout doesn't check for errors of signal. // The subprocess might have exited just after the timeout. // Sending a signal now would return "No such process", but we should still try to kill the children. - match foreground { - true => _ = process.send_signal(signal), - false => { - _ = process.send_signal_group(signal); - let kill_signal = signal_by_name_or_value("KILL").unwrap(); - let continued_signal = signal_by_name_or_value("CONT").unwrap(); - if signal != kill_signal && signal != continued_signal { - _ = process.send_signal_group(continued_signal); - } + if foreground { + let _ = process.send_signal(signal); + } else { + let _ = process.send_signal_group(signal); + let kill_signal = signal_by_name_or_value("KILL").unwrap(); + let continued_signal = signal_by_name_or_value("CONT").unwrap(); + if signal != kill_signal && signal != continued_signal { + _ = process.send_signal_group(continued_signal); } } } diff --git a/src/uucore/src/lib/features/format/spec.rs b/src/uucore/src/lib/features/format/spec.rs index 7c173a3a9b6..543346bf43d 100644 --- a/src/uucore/src/lib/features/format/spec.rs +++ b/src/uucore/src/lib/features/format/spec.rs @@ -217,10 +217,7 @@ impl Spec { if *c == b'u' && flags.hash { return Err(&start[..index]); } - let prefix = match flags.hash { - false => Prefix::No, - true => Prefix::Yes, - }; + let prefix = if flags.hash { Prefix::Yes } else { Prefix::No }; let variant = match c { b'u' => UnsignedIntVariant::Decimal, b'o' => UnsignedIntVariant::Octal(prefix), @@ -245,13 +242,15 @@ impl Spec { b'a' | b'A' => FloatVariant::Hexadecimal, _ => unreachable!(), }, - force_decimal: match flags.hash { - false => ForceDecimal::No, - true => ForceDecimal::Yes, + force_decimal: if flags.hash { + ForceDecimal::Yes + } else { + ForceDecimal::No }, - case: match c.is_ascii_uppercase() { - false => Case::Lowercase, - true => Case::Uppercase, + case: if c.is_ascii_uppercase() { + Case::Uppercase + } else { + Case::Lowercase }, alignment, positive_sign, From 0a0a7b9ba1fb4315bbc4dae4403f79cea4bc7d04 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Tue, 2 Apr 2024 23:30:28 +0200 Subject: [PATCH 052/144] clippy: enable match_bool --- .github/workflows/code-quality.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/code-quality.yml b/.github/workflows/code-quality.yml index 0d6d3b41415..009cf788f83 100644 --- a/.github/workflows/code-quality.yml +++ b/.github/workflows/code-quality.yml @@ -126,7 +126,7 @@ jobs: command: | ## `cargo clippy` lint testing unset fault - CLIPPY_FLAGS="-W clippy::default_trait_access -W clippy::manual_string_new -W clippy::cognitive_complexity -W clippy::implicit_clone -W clippy::range-plus-one -W clippy::redundant-clone" + CLIPPY_FLAGS="-W clippy::default_trait_access -W clippy::manual_string_new -W clippy::cognitive_complexity -W clippy::implicit_clone -W clippy::range-plus-one -W clippy::redundant-clone -W clippy::match_bool" fault_type="${{ steps.vars.outputs.FAULT_TYPE }}" fault_prefix=$(echo "$fault_type" | tr '[:lower:]' '[:upper:]') # * convert any warnings to GHA UI annotations; ref: From efd7b6116cdf255330d5f5d3699476b18b65e055 Mon Sep 17 00:00:00 2001 From: maxer137 Date: Wed, 3 Apr 2024 16:07:04 +0200 Subject: [PATCH 053/144] seq: remove is_minus_zero_float check on parse_exponent_no_decimal --- src/uu/seq/src/numberparse.rs | 23 ++++++++++------------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/src/uu/seq/src/numberparse.rs b/src/uu/seq/src/numberparse.rs index df7c1f7d1dd..20b42638a1a 100644 --- a/src/uu/seq/src/numberparse.rs +++ b/src/uu/seq/src/numberparse.rs @@ -103,20 +103,17 @@ fn parse_exponent_no_decimal(s: &str, j: usize) -> Result Date: Wed, 3 Apr 2024 16:07:31 +0200 Subject: [PATCH 054/144] seq: remove exponent exponent less than 0 check. When exponent is greater than 0 we previously created a new string causing us to create a new string with a much larger size. This would the get passed to the BigDecimal crate which would get stuck. --- src/uu/seq/src/numberparse.rs | 28 +++++++++++----------------- 1 file changed, 11 insertions(+), 17 deletions(-) diff --git a/src/uu/seq/src/numberparse.rs b/src/uu/seq/src/numberparse.rs index 20b42638a1a..1a3ffd6b32e 100644 --- a/src/uu/seq/src/numberparse.rs +++ b/src/uu/seq/src/numberparse.rs @@ -117,24 +117,18 @@ fn parse_exponent_no_decimal(s: &str, j: usize) -> Result Date: Wed, 3 Apr 2024 19:20:27 +0200 Subject: [PATCH 055/144] seq: Reverted change in is_minus_zero check I had made the mistake of not running all the tests. This is indeed needed. However, you still need to add the exponent if it's positive for 0 numbers. This way, 0 numbers (such as 0e+5) will be counted as having a width of 5. This change also removes the memory allocation needed for the previous implementation. Where the string itself would be padded with zeros on the right side. Creating large numbers this method would cause large allocations in memory. This does not seem to fix issue #6182. --- src/uu/seq/src/numberparse.rs | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/src/uu/seq/src/numberparse.rs b/src/uu/seq/src/numberparse.rs index 1a3ffd6b32e..0ac1f93c906 100644 --- a/src/uu/seq/src/numberparse.rs +++ b/src/uu/seq/src/numberparse.rs @@ -104,16 +104,24 @@ fn parse_exponent_no_decimal(s: &str, j: usize) -> Result 0 { + 2usize + exponent as usize + } else { + 2usize + } } else { - result + let total = j as i64 + exponent; + let result = if total < 1 { + 1 + } else { + total.try_into().unwrap() + }; + if x.sign() == Sign::Minus { + result + 1 + } else { + result + } }; let num_fractional_digits = if exponent < 0 { -exponent as usize } else { 0 }; From 4312f3c43e3d3f7445350fba79c9476bcab5709f Mon Sep 17 00:00:00 2001 From: Jadi Date: Wed, 3 Apr 2024 22:08:36 +0330 Subject: [PATCH 056/144] cksum: adding -b as the short form for --base64 closes #5706 --- src/uu/cksum/src/cksum.rs | 1 + tests/by-util/test_cksum.rs | 16 +++++++++------- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/src/uu/cksum/src/cksum.rs b/src/uu/cksum/src/cksum.rs index c5c362c5936..679d06ff7c8 100644 --- a/src/uu/cksum/src/cksum.rs +++ b/src/uu/cksum/src/cksum.rs @@ -448,6 +448,7 @@ pub fn uu_app() -> Command { .arg( Arg::new(options::BASE64) .long(options::BASE64) + .short('b') .help("emit a base64 digest, not hexadecimal") .action(ArgAction::SetTrue) // Even though this could easily just override an earlier '--raw', diff --git a/tests/by-util/test_cksum.rs b/tests/by-util/test_cksum.rs index 818b7e79929..71b32bdfacf 100644 --- a/tests/by-util/test_cksum.rs +++ b/tests/by-util/test_cksum.rs @@ -379,13 +379,15 @@ fn test_base64_raw_conflicts() { #[test] fn test_base64_single_file() { for algo in ALGOS { - new_ucmd!() - .arg("--base64") - .arg("lorem_ipsum.txt") - .arg(format!("--algorithm={algo}")) - .succeeds() - .no_stderr() - .stdout_is_fixture_bytes(format!("base64/{algo}_single_file.expected")); + for base64_option in ["--base64", "-b"] { + new_ucmd!() + .arg(base64_option) + .arg("lorem_ipsum.txt") + .arg(format!("--algorithm={algo}")) + .succeeds() + .no_stderr() + .stdout_is_fixture_bytes(format!("base64/{algo}_single_file.expected")); + } } } #[test] From d11878403cbbf553740aca954933e2c4bb1a6857 Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Thu, 4 Apr 2024 14:51:47 +0200 Subject: [PATCH 057/144] Readme: fix links to docs --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index dbcd7d86021..895e3c9a908 100644 --- a/README.md +++ b/README.md @@ -53,7 +53,7 @@ that scripts can be easily transferred between platforms. ## Documentation uutils has both user and developer documentation available: -- [User Manual](https://uutils.github.io/coreutils/book/) +- [User Manual](https://uutils.github.io/coreutils/docs/) - [Developer Documentation](https://docs.rs/crate/coreutils/) Both can also be generated locally, the instructions for that can be found in @@ -302,7 +302,7 @@ make PREFIX=/my/path uninstall Below is the evolution of how many GNU tests uutils passes. A more detailed breakdown of the GNU test results of the main branch can be found -[in the user manual](https://uutils.github.io/coreutils/book/test_coverage.html). +[in the user manual](https://uutils.github.io/coreutils/docs/test_coverage.html). See for the main meta bugs (many are missing). From 1137d9a62d7ef5d05c4b8417ab41b5de814c51fc Mon Sep 17 00:00:00 2001 From: Jadi Date: Thu, 4 Apr 2024 21:33:22 +0330 Subject: [PATCH 058/144] cut: two new tests; -d conflict & no arg --- tests/by-util/test_cut.rs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/tests/by-util/test_cut.rs b/tests/by-util/test_cut.rs index 50d158f966b..86d3ddf0f3d 100644 --- a/tests/by-util/test_cut.rs +++ b/tests/by-util/test_cut.rs @@ -117,6 +117,17 @@ fn test_whitespace_with_char() { .code_is(1); } +#[test] +fn test_delimiter_with_byte_and_char() { + for conflicting_arg in ["-c", "-b"] { + new_ucmd!() + .args(&[conflicting_arg, COMPLEX_SEQUENCE.sequence, "-d="]) + .fails() + .stderr_is("cut: invalid input: The '--delimiter' ('-d') option only usable if printing a sequence of fields\n") + .code_is(1); + } +} + #[test] fn test_too_large() { new_ucmd!() @@ -289,6 +300,13 @@ fn test_multiple_mode_args() { } } +#[test] +fn test_no_argument() { + new_ucmd!().fails().stderr_is( + "cut: invalid usage: expects one of --fields (-f), --chars (-c) or --bytes (-b)\n", + ); +} + #[test] #[cfg(unix)] fn test_8bit_non_utf8_delimiter() { From ac12957307f871770037b1108a4edbfced4d9169 Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Fri, 5 Apr 2024 07:14:37 +0200 Subject: [PATCH 059/144] cksum: fix code formatting --- src/uu/cksum/src/cksum.rs | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/src/uu/cksum/src/cksum.rs b/src/uu/cksum/src/cksum.rs index 679d06ff7c8..312e4355f23 100644 --- a/src/uu/cksum/src/cksum.rs +++ b/src/uu/cksum/src/cksum.rs @@ -436,24 +436,27 @@ pub fn uu_app() -> Command { .long(options::LENGTH) .value_parser(value_parser!(usize)) .short('l') - .help("digest length in bits; must not exceed the max for the blake2 algorithm and must be a multiple of 8") + .help( + "digest length in bits; must not exceed the max for the blake2 algorithm \ + and must be a multiple of 8", + ) .action(ArgAction::Set), ) .arg( Arg::new(options::RAW) - .long(options::RAW) - .help("emit a raw binary digest, not hexadecimal") - .action(ArgAction::SetTrue), + .long(options::RAW) + .help("emit a raw binary digest, not hexadecimal") + .action(ArgAction::SetTrue), ) .arg( Arg::new(options::BASE64) - .long(options::BASE64) - .short('b') - .help("emit a base64 digest, not hexadecimal") - .action(ArgAction::SetTrue) - // Even though this could easily just override an earlier '--raw', - // GNU cksum does not permit these flags to be combined: - .conflicts_with(options::RAW), + .long(options::BASE64) + .short('b') + .help("emit a base64 digest, not hexadecimal") + .action(ArgAction::SetTrue) + // Even though this could easily just override an earlier '--raw', + // GNU cksum does not permit these flags to be combined: + .conflicts_with(options::RAW), ) .after_help(AFTER_HELP) } From 0fdefc3767a00a7047948d27e5f17ff334c0d489 Mon Sep 17 00:00:00 2001 From: maxer137 Date: Fri, 5 Apr 2024 15:01:18 +0200 Subject: [PATCH 060/144] seq: remove zero padding from `parse_decimal_and_exponent` This adds the same fix applied to `parse_exponent_no_decimal`. This way we don't need to create large strings --- src/uu/seq/src/numberparse.rs | 31 ++++++------------------------- 1 file changed, 6 insertions(+), 25 deletions(-) diff --git a/src/uu/seq/src/numberparse.rs b/src/uu/seq/src/numberparse.rs index 0ac1f93c906..5a5c64bb991 100644 --- a/src/uu/seq/src/numberparse.rs +++ b/src/uu/seq/src/numberparse.rs @@ -206,7 +206,11 @@ fn parse_decimal_and_exponent( let minimum: usize = { let integral_part: f64 = s[..j].parse().map_err(|_| ParseNumberError::Float)?; if integral_part.is_sign_negative() { - 2 + if exponent > 0 { + 2usize + exponent as usize + } else { + 2usize + } } else { 1 } @@ -233,30 +237,7 @@ fn parse_decimal_and_exponent( .unwrap() }; - if num_digits_between_decimal_point_and_e <= exponent { - if is_minus_zero_float(s, &val) { - Ok(PreciseNumber::new( - ExtendedBigDecimal::MinusZero, - num_integral_digits, - num_fractional_digits, - )) - } else { - let zeros: String = "0".repeat( - (exponent - num_digits_between_decimal_point_and_e) - .try_into() - .unwrap(), - ); - let expanded = [&s[0..i], &s[i + 1..j], &zeros].concat(); - let n = expanded - .parse::() - .map_err(|_| ParseNumberError::Float)?; - Ok(PreciseNumber::new( - ExtendedBigDecimal::BigDecimal(n), - num_integral_digits, - num_fractional_digits, - )) - } - } else if is_minus_zero_float(s, &val) { + if is_minus_zero_float(s, &val) { Ok(PreciseNumber::new( ExtendedBigDecimal::MinusZero, num_integral_digits, From 4b605cb43f50771e4e15cb2d268fdd61a1572967 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 5 Apr 2024 22:02:53 +0000 Subject: [PATCH 061/144] chore(deps): update vmactions/freebsd-vm action to v1.0.7 --- .github/workflows/freebsd.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/freebsd.yml b/.github/workflows/freebsd.yml index a2b3562ca3a..f15495f4df5 100644 --- a/.github/workflows/freebsd.yml +++ b/.github/workflows/freebsd.yml @@ -39,7 +39,7 @@ jobs: - name: Run sccache-cache uses: mozilla-actions/sccache-action@v0.0.4 - name: Prepare, build and test - uses: vmactions/freebsd-vm@v1.0.6 + uses: vmactions/freebsd-vm@v1.0.7 with: usesh: true sync: rsync @@ -131,7 +131,7 @@ jobs: - name: Run sccache-cache uses: mozilla-actions/sccache-action@v0.0.4 - name: Prepare, build and test - uses: vmactions/freebsd-vm@v1.0.6 + uses: vmactions/freebsd-vm@v1.0.7 with: usesh: true sync: rsync From 5d17bf7d78be97bdacbf2c59a43203a6ea00e071 Mon Sep 17 00:00:00 2001 From: Jadi Date: Fri, 5 Apr 2024 23:13:37 +0330 Subject: [PATCH 062/144] head: two new tests. Improves function coverage from 38 to 75 --- tests/by-util/test_head.rs | 18 ++++++++++++++++++ .../lorem_ipsum_backwards_15_lines.expected | 9 +++++++++ 2 files changed, 27 insertions(+) create mode 100644 tests/fixtures/head/lorem_ipsum_backwards_15_lines.expected diff --git a/tests/by-util/test_head.rs b/tests/by-util/test_head.rs index b72e77281ad..db9ab766634 100644 --- a/tests/by-util/test_head.rs +++ b/tests/by-util/test_head.rs @@ -438,3 +438,21 @@ fn test_read_backwards_bytes_sys_kernel_profiling() { assert_eq!(stdout_str.len(), 1); assert!(stdout_str == "0" || stdout_str == "1"); } + +#[test] +fn test_value_too_large() { + const MAX: u64 = u64::MAX; + + new_ucmd!() + .args(&["-n", format!("{MAX}0").as_str(), "lorem_ipsum.txt"]) + .fails() + .stderr_contains("Value too large for defined data type"); +} + +#[test] +fn test_all_but_last_lines() { + new_ucmd!() + .args(&["-n", "-15", "lorem_ipsum.txt"]) + .succeeds() + .stdout_is_fixture("lorem_ipsum_backwards_15_lines.expected"); +} diff --git a/tests/fixtures/head/lorem_ipsum_backwards_15_lines.expected b/tests/fixtures/head/lorem_ipsum_backwards_15_lines.expected new file mode 100644 index 00000000000..9865c1b35ab --- /dev/null +++ b/tests/fixtures/head/lorem_ipsum_backwards_15_lines.expected @@ -0,0 +1,9 @@ +Lorem ipsum dolor sit amet, +consectetur adipiscing elit. +Nunc interdum suscipit sem vel ornare. +Proin euismod, +justo sed mollis dictum, +eros urna ultricies augue, +eu pharetra mi ex id ante. +Duis convallis porttitor aliquam. +Nunc vitae tincidunt ex. From eca8130a4ac17fe69647d84c0d3b3c30908a1169 Mon Sep 17 00:00:00 2001 From: Ulrich Hornung Date: Sat, 30 Mar 2024 21:19:35 +0100 Subject: [PATCH 063/144] feature: env argv0 overwrite (unix only) --- src/uu/env/src/env.rs | 82 +++++++++++--- tests/by-util/test_env.rs | 233 +++++++++++++++++++++++++++++++++++++- 2 files changed, 297 insertions(+), 18 deletions(-) diff --git a/src/uu/env/src/env.rs b/src/uu/env/src/env.rs index 4f2790dc8b4..3908e9e78e8 100644 --- a/src/uu/env/src/env.rs +++ b/src/uu/env/src/env.rs @@ -27,7 +27,7 @@ use std::io::{self, Write}; use std::ops::Deref; #[cfg(unix)] -use std::os::unix::process::ExitStatusExt; +use std::os::unix::process::{CommandExt, ExitStatusExt}; use std::process::{self}; use uucore::display::Quotable; use uucore::error::{ExitCode, UError, UResult, USimpleError, UUsageError}; @@ -48,6 +48,7 @@ struct Options<'a> { unsets: Vec<&'a OsStr>, sets: Vec<(Cow<'a, OsStr>, Cow<'a, OsStr>)>, program: Vec<&'a OsStr>, + argv0: Option<&'a OsStr>, } // print name=value env pairs on screen @@ -173,7 +174,7 @@ pub fn uu_app() -> Command { Arg::new("debug") .short('v') .long("debug") - .action(ArgAction::SetTrue) + .action(ArgAction::Count) .help("print verbose information for each processing step"), ) .arg( @@ -184,6 +185,16 @@ pub fn uu_app() -> Command { .action(ArgAction::Set) .value_parser(ValueParser::os_string()) .help("process and split S into separate arguments; used to pass multiple arguments on shebang lines") + ).arg( + Arg::new("argv0") + .overrides_with("argv0") + .short('a') + .long("argv0") + .value_name("a") + .action(ArgAction::Set) + .value_parser(ValueParser::os_string()) + .help("Override the zeroth argument passed to the command being executed.\ + Without this option a default value of `command` is used.") ) .arg( Arg::new("vars") @@ -248,6 +259,7 @@ fn check_and_handle_string_args( #[derive(Default)] struct EnvAppData { do_debug_printing: bool, + do_input_debug_printing: Option, had_string_argument: bool, } @@ -273,14 +285,19 @@ impl EnvAppData { b if check_and_handle_string_args(b, "-S", &mut all_args, None)? => { self.had_string_argument = true; } + b if check_and_handle_string_args(b, "-vS", &mut all_args, None)? => { + self.do_debug_printing = true; + self.had_string_argument = true; + } b if check_and_handle_string_args( b, - "-vS", + "-vvS", &mut all_args, Some(original_args), )? => { self.do_debug_printing = true; + self.do_input_debug_printing = Some(false); // already done self.had_string_argument = true; } _ => { @@ -323,10 +340,15 @@ impl EnvAppData { fn run_env(&mut self, original_args: impl uucore::Args) -> UResult<()> { let (original_args, matches) = self.parse_arguments(original_args)?; - let did_debug_printing_before = self.do_debug_printing; // could have been done already as part of the "-vS" string parsing - let do_debug_printing = self.do_debug_printing || matches.get_flag("debug"); - if do_debug_printing && !did_debug_printing_before { - debug_print_args(&original_args); + self.do_debug_printing = self.do_debug_printing || (0 != matches.get_count("debug")); + self.do_input_debug_printing = self + .do_input_debug_printing + .or(Some(matches.get_count("debug") >= 2)); + if let Some(value) = self.do_input_debug_printing { + if value { + debug_print_args(&original_args); + self.do_input_debug_printing = Some(false); + } } let mut opts = make_options(&matches)?; @@ -349,7 +371,7 @@ impl EnvAppData { // no program provided, so just dump all env vars to stdout print_env(opts.line_ending); } else { - return self.run_program(opts, do_debug_printing); + return self.run_program(opts, self.do_debug_printing); } Ok(()) @@ -361,14 +383,11 @@ impl EnvAppData { do_debug_printing: bool, ) -> Result<(), Box> { let prog = Cow::from(opts.program[0]); + #[cfg(unix)] + let mut arg0 = prog.clone(); + #[cfg(not(unix))] + let arg0 = prog.clone(); let args = &opts.program[1..]; - if do_debug_printing { - eprintln!("executable: {}", prog.quote()); - for (i, arg) in args.iter().enumerate() { - eprintln!("arg[{}]: {}", i, arg.quote()); - } - } - // we need to execute a command /* * On Unix-like systems Command::status either ends up calling either fork or posix_spawnp @@ -376,7 +395,36 @@ impl EnvAppData { * standard library contains many checks and fail-safes to ensure the process ends up being * created. This is much simpler than dealing with the hassles of calling execvp directly. */ - match process::Command::new(&*prog).args(args).status() { + let mut cmd = process::Command::new(&*prog); + cmd.args(args); + + if let Some(_argv0) = opts.argv0 { + #[cfg(unix)] + { + cmd.arg0(_argv0); + arg0 = Cow::Borrowed(_argv0); + if do_debug_printing { + eprintln!("argv0: {}", arg0.quote()); + } + } + + #[cfg(not(unix))] + return Err(USimpleError::new( + 2, + "--argv0 is currently not supported on this platform", + )); + } + + if do_debug_printing { + eprintln!("executing: {}", prog.maybe_quote()); + let arg_prefix = " arg"; + eprintln!("{}[{}]= {}", arg_prefix, 0, arg0.quote()); + for (i, arg) in args.iter().enumerate() { + eprintln!("{}[{}]= {}", arg_prefix, i + 1, arg.quote()); + } + } + + match cmd.status() { Ok(exit) if !exit.success() => { #[cfg(unix)] if let Some(exit_code) = exit.code() { @@ -443,6 +491,7 @@ fn make_options(matches: &clap::ArgMatches) -> UResult> { Some(v) => v.map(|s| s.as_os_str()).collect(), None => Vec::with_capacity(0), }; + let argv0 = matches.get_one::("argv0").map(|s| s.as_os_str()); let mut opts = Options { ignore_env, @@ -452,6 +501,7 @@ fn make_options(matches: &clap::ArgMatches) -> UResult> { unsets, sets: vec![], program: vec![], + argv0, }; let mut begin_prog_opts = false; diff --git a/tests/by-util/test_env.rs b/tests/by-util/test_env.rs index 13535e4161f..0df1da752b2 100644 --- a/tests/by-util/test_env.rs +++ b/tests/by-util/test_env.rs @@ -8,6 +8,7 @@ use crate::common::util::expected_result; use crate::common::util::TestScenario; use ::env::native_int_str::{Convert, NCvt}; +use regex::Regex; use std::env; use std::path::Path; use tempfile::tempdir; @@ -55,6 +56,99 @@ fn test_if_windows_batch_files_can_be_executed() { assert!(result.stdout_str().contains("Hello Windows World!")); } +#[test] +fn test_debug_1() { + let ts = TestScenario::new(util_name!()); + let result = ts + .ucmd() + .arg("-v") + .arg(&ts.bin_path) + .args(&["echo", "hello"]) + .succeeds(); + result.stderr_matches( + &Regex::new(concat!( + r"executing: [^\n]+(\/|\\)coreutils(\.exe)?\n", + r" arg\[0\]= '[^\n]+(\/|\\)coreutils(\.exe)?'\n", + r" arg\[1\]= 'echo'\n", + r" arg\[2\]= 'hello'" + )) + .unwrap(), + ); +} + +#[test] +fn test_debug_2() { + let ts = TestScenario::new(util_name!()); + let result = ts + .ucmd() + .arg("-vv") + .arg(ts.bin_path) + .args(&["echo", "hello2"]) + .succeeds(); + result.stderr_matches( + &Regex::new(concat!( + r"input args:\n", + r"arg\[0\]: 'env'\n", + r"arg\[1\]: '-vv'\n", + r"arg\[2\]: '[^\n]+(\/|\\)coreutils(.exe)?'\n", + r"arg\[3\]: 'echo'\n", + r"arg\[4\]: 'hello2'\n", + r"executing: [^\n]+(\/|\\)coreutils(.exe)?\n", + r" arg\[0\]= '[^\n]+(\/|\\)coreutils(.exe)?'\n", + r" arg\[1\]= 'echo'\n", + r" arg\[2\]= 'hello2'" + )) + .unwrap(), + ); +} + +#[test] +fn test_debug1_part_of_string_arg() { + let ts = TestScenario::new(util_name!()); + + let result = ts + .ucmd() + .arg("-vS FOO=BAR") + .arg(ts.bin_path) + .args(&["echo", "hello1"]) + .succeeds(); + result.stderr_matches( + &Regex::new(concat!( + r"executing: [^\n]+(\/|\\)coreutils(\.exe)?\n", + r" arg\[0\]= '[^\n]+(\/|\\)coreutils(\.exe)?'\n", + r" arg\[1\]= 'echo'\n", + r" arg\[2\]= 'hello1'" + )) + .unwrap(), + ); +} + +#[test] +fn test_debug2_part_of_string_arg() { + let ts = TestScenario::new(util_name!()); + let result = ts + .ucmd() + .arg("-vvS FOO=BAR") + .arg(ts.bin_path) + .args(&["echo", "hello2"]) + .succeeds(); + result.stderr_matches( + &Regex::new(concat!( + r"input args:\n", + r"arg\[0\]: 'env'\n", + r"arg\[1\]: '-vvS FOO=BAR'\n", + r"arg\[2\]: '[^\n]+(\/|\\)coreutils(.exe)?'\n", + r"arg\[3\]: 'echo'\n", + r"arg\[4\]: 'hello2'\n", + r"executing: [^\n]+(\/|\\)coreutils(.exe)?\n", + r" arg\[0\]= '[^\n]+(\/|\\)coreutils(.exe)?'\n", + r" arg\[1\]= 'echo'\n", + r" arg\[2\]= 'hello2'" + )) + .unwrap(), + ); +} + #[test] fn test_file_option() { let out = new_ucmd!() @@ -345,10 +439,15 @@ fn test_split_string_into_args_debug_output_whitespace_handling() { let out = scene .ucmd() - .args(&["-vS printf x%sx\\n A \t B \x0B\x0C\r\n"]) + .args(&["-vvS printf x%sx\\n A \t B \x0B\x0C\r\n"]) .succeeds(); assert_eq!(out.stdout_str(), "xAx\nxBx\n"); - assert_eq!(out.stderr_str(), "input args:\narg[0]: 'env'\narg[1]: $'-vS printf x%sx\\\\n A \\t B \\x0B\\x0C\\r\\n'\nexecutable: 'printf'\narg[0]: $'x%sx\\n'\narg[1]: 'A'\narg[2]: 'B'\n"); + assert_eq!( + out.stderr_str(), + "input args:\narg[0]: 'env'\narg[1]: $\ + '-vvS printf x%sx\\\\n A \\t B \\x0B\\x0C\\r\\n'\nexecuting: printf\ + \n arg[0]= 'printf'\n arg[1]= $'x%sx\\n'\n arg[2]= 'A'\n arg[3]= 'B'\n" + ); } // FixMe: This test fails on MACOS: @@ -564,6 +663,136 @@ fn test_env_with_gnu_reference_empty_executable_double_quotes() { .stderr_is("env: '': No such file or directory\n"); } +#[test] +#[cfg(unix)] +fn test_env_overwrite_arg0() { + let ts = TestScenario::new(util_name!()); + + let bin = ts.bin_path.clone(); + + ts.ucmd() + .args(&["--argv0", "echo"]) + .arg(&bin) + .args(&["-n", "hello", "world!"]) + .succeeds() + .stdout_is("hello world!") + .stderr_is(""); + + ts.ucmd() + .args(&["-a", "dirname"]) + .arg(bin) + .args(&["aa/bb/cc"]) + .succeeds() + .stdout_is("aa/bb\n") + .stderr_is(""); +} + +#[test] +#[cfg(unix)] +fn test_env_arg_argv0_overwrite() { + let ts = TestScenario::new(util_name!()); + + let bin = ts.bin_path.clone(); + + // overwrite --argv0 by --argv0 + ts.ucmd() + .args(&["--argv0", "dirname"]) + .args(&["--argv0", "echo"]) + .arg(&bin) + .args(&["aa/bb/cc"]) + .succeeds() + .stdout_is("aa/bb/cc\n") + .stderr_is(""); + + // overwrite -a by -a + ts.ucmd() + .args(&["-a", "dirname"]) + .args(&["-a", "echo"]) + .arg(&bin) + .args(&["aa/bb/cc"]) + .succeeds() + .stdout_is("aa/bb/cc\n") + .stderr_is(""); + + // overwrite --argv0 by -a + ts.ucmd() + .args(&["--argv0", "dirname"]) + .args(&["-a", "echo"]) + .arg(&bin) + .args(&["aa/bb/cc"]) + .succeeds() + .stdout_is("aa/bb/cc\n") + .stderr_is(""); + + // overwrite -a by --argv0 + ts.ucmd() + .args(&["-a", "dirname"]) + .args(&["--argv0", "echo"]) + .arg(&bin) + .args(&["aa/bb/cc"]) + .succeeds() + .stdout_is("aa/bb/cc\n") + .stderr_is(""); +} + +#[test] +#[cfg(unix)] +fn test_env_arg_argv0_overwrite_mixed_with_string_args() { + let ts = TestScenario::new(util_name!()); + + let bin = ts.bin_path.clone(); + + // string arg following normal + ts.ucmd() + .args(&["-S--argv0 dirname"]) + .args(&["--argv0", "echo"]) + .arg(&bin) + .args(&["aa/bb/cc"]) + .succeeds() + .stdout_is("aa/bb/cc\n") + .stderr_is(""); + + // normal following string arg + ts.ucmd() + .args(&["-a", "dirname"]) + .args(&["-S-a echo"]) + .arg(&bin) + .args(&["aa/bb/cc"]) + .succeeds() + .stdout_is("aa/bb/cc\n") + .stderr_is(""); + + // one large string arg + ts.ucmd() + .args(&["-S--argv0 dirname -a echo"]) + .arg(&bin) + .args(&["aa/bb/cc"]) + .succeeds() + .stdout_is("aa/bb/cc\n") + .stderr_is(""); + + // two string args + ts.ucmd() + .args(&["-S-a dirname"]) + .args(&["-S--argv0 echo"]) + .arg(&bin) + .args(&["aa/bb/cc"]) + .succeeds() + .stdout_is("aa/bb/cc\n") + .stderr_is(""); + + // three args: normal, string, normal + ts.ucmd() + .args(&["-a", "sleep"]) + .args(&["-S-a dirname"]) + .args(&["-a", "echo"]) + .arg(&bin) + .args(&["aa/bb/cc"]) + .succeeds() + .stdout_is("aa/bb/cc\n") + .stderr_is(""); +} + #[cfg(test)] mod tests_split_iterator { From abdeead005c4e2cfc7719eabc53473145f0b098a Mon Sep 17 00:00:00 2001 From: Ulrich Hornung Date: Tue, 2 Apr 2024 22:50:08 +0200 Subject: [PATCH 064/144] add sleep explicitly to makefile --- GNUmakefile | 2 ++ 1 file changed, 2 insertions(+) diff --git a/GNUmakefile b/GNUmakefile index ad2d38081f3..0b4f2d04ce7 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -147,6 +147,7 @@ UNIX_PROGS := \ nohup \ pathchk \ pinky \ + sleep \ stat \ stdbuf \ timeout \ @@ -221,6 +222,7 @@ TEST_PROGS := \ rmdir \ runcon \ seq \ + sleep \ sort \ split \ stat \ From de37baaf839bb41e2d388d4eb42a3e486458df1c Mon Sep 17 00:00:00 2001 From: Ben Wiederhake Date: Sat, 6 Apr 2024 23:25:30 +0200 Subject: [PATCH 065/144] tests: test multi-call logic Thankfully, this revealed no bugs. Let's prevent regressions! --- tests/test_util_name.rs | 151 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 151 insertions(+) diff --git a/tests/test_util_name.rs b/tests/test_util_name.rs index 45edc7dc782..9fcd2e5710f 100644 --- a/tests/test_util_name.rs +++ b/tests/test_util_name.rs @@ -92,3 +92,154 @@ fn util_name_single() { scenario.fixtures.plus("uu-sort").display() ))); } + +#[test] +#[cfg(any(unix, windows))] +fn util_invalid_name_help() { + use std::{ + io::Write, + process::{Command, Stdio}, + }; + + let scenario = TestScenario::new("invalid_name"); + symlink_file(scenario.bin_path, scenario.fixtures.plus("invalid_name")).unwrap(); + let child = Command::new(scenario.fixtures.plus("invalid_name")) + .arg("--help") + .stdin(Stdio::piped()) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .spawn() + .unwrap(); + let output = child.wait_with_output().unwrap(); + assert_eq!(output.status.code(), Some(0)); + assert_eq!(output.stderr, b""); + let output_str = String::from_utf8(output.stdout).unwrap(); + assert!( + output_str.contains("(multi-call binary)"), + "{:?}", + output_str + ); + assert!( + output_str.contains("Usage: invalid_name [function "), + "{:?}", + output_str + ); +} + +#[test] +// The exact set of permitted filenames depends on many factors. Non-UTF-8 strings +// work on very few platforms, but linux works, especially because it also increases +// the likelihood that a filesystem is being used that supports non-UTF-8 filenames. +#[cfg(target_os = "linux")] +fn util_non_utf8_name_help() { + // Make sure we don't crash even if the util name is invalid UTF-8. + use std::{ + ffi::OsStr, + io::Write, + os::unix::ffi::OsStrExt, + path::Path, + process::{Command, Stdio}, + }; + + let scenario = TestScenario::new("invalid_name"); + let non_utf8_path = scenario.fixtures.plus(OsStr::from_bytes(b"\xff")); + symlink_file(scenario.bin_path, &non_utf8_path).unwrap(); + let child = Command::new(&non_utf8_path) + .arg("--help") + .stdin(Stdio::piped()) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .spawn() + .unwrap(); + let output = child.wait_with_output().unwrap(); + assert_eq!(output.status.code(), Some(0)); + assert_eq!(output.stderr, b""); + let output_str = String::from_utf8(output.stdout).unwrap(); + assert!( + output_str.contains("(multi-call binary)"), + "{:?}", + output_str + ); + assert!( + output_str.contains("Usage: [function "), + "{:?}", + output_str + ); +} + +#[test] +#[cfg(any(unix, windows))] +fn util_invalid_name_invalid_command() { + use std::{ + io::Write, + process::{Command, Stdio}, + }; + + let scenario = TestScenario::new("invalid_name"); + symlink_file(scenario.bin_path, scenario.fixtures.plus("invalid_name")).unwrap(); + let child = Command::new(scenario.fixtures.plus("invalid_name")) + .arg("definitely_invalid") + .stdin(Stdio::piped()) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .spawn() + .unwrap(); + let output = child.wait_with_output().unwrap(); + assert_eq!(output.status.code(), Some(1)); + assert_eq!(output.stderr, b""); + assert_eq!( + output.stdout, + b"definitely_invalid: function/utility not found\n" + ); +} + +#[test] +fn util_completion() { + use std::{ + io::Write, + process::{Command, Stdio}, + }; + + let scenario = TestScenario::new("completion"); + let child = Command::new(scenario.bin_path) + .arg("completion") + .arg("true") + .arg("powershell") + .stdin(Stdio::piped()) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .spawn() + .unwrap(); + let output = child.wait_with_output().unwrap(); + assert_eq!(output.status.code(), Some(0)); + assert_eq!(output.stderr, b""); + let output_str = String::from_utf8(output.stdout).unwrap(); + assert!( + output_str.contains("using namespace System.Management.Automation"), + "{:?}", + output_str + ); +} + +#[test] +fn util_manpage() { + use std::{ + io::Write, + process::{Command, Stdio}, + }; + + let scenario = TestScenario::new("completion"); + let child = Command::new(scenario.bin_path) + .arg("manpage") + .arg("true") + .stdin(Stdio::piped()) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .spawn() + .unwrap(); + let output = child.wait_with_output().unwrap(); + assert_eq!(output.status.code(), Some(0)); + assert_eq!(output.stderr, b""); + let output_str = String::from_utf8(output.stdout).unwrap(); + assert!(output_str.contains("\n.TH true 1 "), "{:?}", output_str); +} From d5e7f9a4a415140bc7fc61ebc5ddf0f557d0a85f Mon Sep 17 00:00:00 2001 From: Ben Wiederhake Date: Sun, 7 Apr 2024 02:11:47 +0200 Subject: [PATCH 066/144] wc: count ASCII control characters as word characters --- src/uu/wc/src/wc.rs | 3 +-- tests/by-util/test_wc.rs | 24 +++++++++++++++++------- 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/src/uu/wc/src/wc.rs b/src/uu/wc/src/wc.rs index b7600cb2090..06f9be9168e 100644 --- a/src/uu/wc/src/wc.rs +++ b/src/uu/wc/src/wc.rs @@ -580,9 +580,8 @@ fn process_chunk< if SHOW_WORDS { if ch.is_whitespace() { *in_word = false; - } else if ch.is_ascii_control() { - // These count as characters but do not affect the word state } else if !(*in_word) { + // This also counts control characters! (As of GNU coreutils 9.5) *in_word = true; total.words += 1; } diff --git a/tests/by-util/test_wc.rs b/tests/by-util/test_wc.rs index c0a4a341319..0202ba4e889 100644 --- a/tests/by-util/test_wc.rs +++ b/tests/by-util/test_wc.rs @@ -71,7 +71,7 @@ fn test_utf8_words() { .arg("-w") .pipe_in_fixture("UTF_8_weirdchars.txt") .run() - .stdout_is("87\n"); + .stdout_is("89\n"); } #[test] @@ -80,7 +80,7 @@ fn test_utf8_line_length_words() { .arg("-Lw") .pipe_in_fixture("UTF_8_weirdchars.txt") .run() - .stdout_is(" 87 48\n"); + .stdout_is(" 89 48\n"); } #[test] @@ -98,7 +98,7 @@ fn test_utf8_line_length_chars_words() { .arg("-Lmw") .pipe_in_fixture("UTF_8_weirdchars.txt") .run() - .stdout_is(" 87 442 48\n"); + .stdout_is(" 89 442 48\n"); } #[test] @@ -143,7 +143,7 @@ fn test_utf8_chars_words() { .arg("-mw") .pipe_in_fixture("UTF_8_weirdchars.txt") .run() - .stdout_is(" 87 442\n"); + .stdout_is(" 89 442\n"); } #[test] @@ -161,7 +161,7 @@ fn test_utf8_line_length_lines_words() { .arg("-Llw") .pipe_in_fixture("UTF_8_weirdchars.txt") .run() - .stdout_is(" 25 87 48\n"); + .stdout_is(" 25 89 48\n"); } #[test] @@ -179,7 +179,7 @@ fn test_utf8_lines_words_chars() { .arg("-mlw") .pipe_in_fixture("UTF_8_weirdchars.txt") .run() - .stdout_is(" 25 87 442\n"); + .stdout_is(" 25 89 442\n"); } #[test] @@ -197,7 +197,17 @@ fn test_utf8_all() { .arg("-lwmcL") .pipe_in_fixture("UTF_8_weirdchars.txt") .run() - .stdout_is(" 25 87 442 513 48\n"); + .stdout_is(" 25 89 442 513 48\n"); +} + +#[test] +fn test_ascii_control() { + // GNU coreutils "d1" test + new_ucmd!() + .arg("-w") + .pipe_in(*b"\x01\n") + .run() + .stdout_is("1\n"); } #[test] From 0375bc98a65f157a70dd219480d32b94943dcb59 Mon Sep 17 00:00:00 2001 From: Ben Wiederhake Date: Sun, 7 Apr 2024 22:45:35 +0200 Subject: [PATCH 067/144] dd: fix flaky test_null_stats If the first four decimal digits are zero, GNU dd elides them altogether. Therefore, this test just contained an overly-strict regex. See also ede944e1f856cc0011852a25d6826b9eac6f2f11. --- tests/by-util/test_dd.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/by-util/test_dd.rs b/tests/by-util/test_dd.rs index a3b007909e5..a8cb0aa6473 100644 --- a/tests/by-util/test_dd.rs +++ b/tests/by-util/test_dd.rs @@ -396,7 +396,7 @@ fn test_null_stats() { .arg("if=null.txt") .run() .stderr_contains("0+0 records in\n0+0 records out\n0 bytes copied, ") - .stderr_matches(&Regex::new(r"\d\.\d+(e-\d\d)? s, ").unwrap()) + .stderr_matches(&Regex::new(r"\d(\.\d+)?(e-\d\d)? s, ").unwrap()) .stderr_contains("0.0 B/s") .success(); } From 6e4c9119fba9407a568b8436b8b8eee312f02dba Mon Sep 17 00:00:00 2001 From: Haisham <47662901+m-haisham@users.noreply.github.com> Date: Sun, 7 Apr 2024 21:42:06 +0500 Subject: [PATCH 068/144] kill: print signals vertically --- src/uu/kill/src/kill.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/uu/kill/src/kill.rs b/src/uu/kill/src/kill.rs index 87de0ff54d3..98b919d6112 100644 --- a/src/uu/kill/src/kill.rs +++ b/src/uu/kill/src/kill.rs @@ -161,7 +161,7 @@ fn print_signal(signal_name_or_value: &str) -> UResult<()> { fn print_signals() { for (idx, signal) in ALL_SIGNALS.iter().enumerate() { if idx > 0 { - print!(" "); + println!(); } print!("{signal}"); } From 843407faf887217ea50c7df62bdb35b2094f6d75 Mon Sep 17 00:00:00 2001 From: Haisham <47662901+m-haisham@users.noreply.github.com> Date: Sun, 7 Apr 2024 23:01:47 +0500 Subject: [PATCH 069/144] kill: removed unnecessary calls to print in print_signals --- src/uu/kill/src/kill.rs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/uu/kill/src/kill.rs b/src/uu/kill/src/kill.rs index 98b919d6112..abb55f84526 100644 --- a/src/uu/kill/src/kill.rs +++ b/src/uu/kill/src/kill.rs @@ -159,13 +159,9 @@ fn print_signal(signal_name_or_value: &str) -> UResult<()> { } fn print_signals() { - for (idx, signal) in ALL_SIGNALS.iter().enumerate() { - if idx > 0 { - println!(); - } - print!("{signal}"); + for signal in ALL_SIGNALS.iter() { + println!("{signal}"); } - println!(); } fn list(arg: Option<&String>) -> UResult<()> { From 997cef0c5cc27685ba14a9873cda6ed34edaf5f6 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 8 Apr 2024 03:05:26 +0000 Subject: [PATCH 070/144] chore(deps): update davidanson/markdownlint-cli2-action action to v16 --- .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 30e1ae7a878..5ba0924856b 100644 --- a/.github/workflows/CICD.yml +++ b/.github/workflows/CICD.yml @@ -138,7 +138,7 @@ jobs: shell: bash run: | RUSTDOCFLAGS="-Dwarnings" cargo doc ${{ steps.vars.outputs.CARGO_FEATURES_OPTION }} --no-deps --workspace --document-private-items - - uses: DavidAnson/markdownlint-cli2-action@v15 + - uses: DavidAnson/markdownlint-cli2-action@v16 with: fix: "true" globs: | From 9b9c0cc237e3eb6f9855cab3a40b0f6bf6a0b456 Mon Sep 17 00:00:00 2001 From: Vikrant2691 <37099198+Vikrant2691@users.noreply.github.com> Date: Wed, 13 Mar 2024 23:31:11 +0000 Subject: [PATCH 071/144] cp: handle update prompt with and without interactive mode enabled --- src/uu/cp/src/cp.rs | 14 +++++--------- tests/by-util/test_cp.rs | 31 ++++++++++++++----------------- 2 files changed, 19 insertions(+), 26 deletions(-) diff --git a/src/uu/cp/src/cp.rs b/src/uu/cp/src/cp.rs index 6a8649dc179..d45f994180d 100644 --- a/src/uu/cp/src/cp.rs +++ b/src/uu/cp/src/cp.rs @@ -1543,7 +1543,9 @@ fn handle_existing_dest( return Err(format!("{} and {} are the same file", source.quote(), dest.quote()).into()); } - options.overwrite.verify(dest)?; + if options.update != UpdateMode::ReplaceIfOlder { + options.overwrite.verify(dest)?; + } let backup_path = backup_control::get_backup_path(options.backup, dest, &options.backup_suffix); if let Some(backup_path) = backup_path { @@ -1770,6 +1772,8 @@ fn handle_copy_mode( if src_time <= dest_time { return Ok(()); } else { + options.overwrite.verify(dest)?; + copy_helper( source, dest, @@ -1866,14 +1870,6 @@ fn copy_file( copied_files: &mut HashMap, source_in_command_line: bool, ) -> CopyResult<()> { - if (options.update == UpdateMode::ReplaceIfOlder || options.update == UpdateMode::ReplaceNone) - && options.overwrite == OverwriteMode::Interactive(ClobberMode::Standard) - { - // `cp -i --update old new` when `new` exists doesn't copy anything - // and exit with 0 - return Ok(()); - } - // Fail if dest is a dangling symlink or a symlink this program created previously if dest.is_symlink() { if FileInformation::from_path(dest, false) diff --git a/tests/by-util/test_cp.rs b/tests/by-util/test_cp.rs index e3b373da19d..bdfc9c8e8a4 100644 --- a/tests/by-util/test_cp.rs +++ b/tests/by-util/test_cp.rs @@ -274,20 +274,6 @@ fn test_cp_target_directory_is_file() { .stderr_contains(format!("'{TEST_HOW_ARE_YOU_SOURCE}' is not a directory")); } -#[test] -// FixMe: for FreeBSD, flaky test; track repair progress at GH:uutils/coreutils/issue/4725 -#[cfg(not(target_os = "freebsd"))] -fn test_cp_arg_update_interactive() { - new_ucmd!() - .arg(TEST_HELLO_WORLD_SOURCE) - .arg(TEST_HOW_ARE_YOU_SOURCE) - .arg("-i") - .arg("--update") - .succeeds() - .no_stdout() - .no_stderr(); -} - #[test] fn test_cp_arg_update_interactive_error() { new_ucmd!() @@ -498,7 +484,7 @@ fn test_cp_arg_interactive() { #[test] #[cfg(not(any(target_os = "android", target_os = "freebsd")))] -fn test_cp_arg_interactive_update() { +fn test_cp_arg_interactive_update_overwrite_newer() { // -u -i won't show the prompt to validate the override or not // Therefore, the error code will be 0 let (at, mut ucmd) = at_and_ucmd!(); @@ -517,12 +503,13 @@ fn test_cp_arg_interactive_update() { #[test] #[cfg(not(any(target_os = "android", target_os = "freebsd")))] -#[ignore = "known issue #6019"] -fn test_cp_arg_interactive_update_newer() { +fn test_cp_arg_interactive_update_overwrite_older() { // -u -i *WILL* show the prompt to validate the override. // Therefore, the error code depends on the prompt response. + // Option N let (at, mut ucmd) = at_and_ucmd!(); at.touch("b"); + std::thread::sleep(Duration::from_secs(1)); at.touch("a"); ucmd.args(&["-i", "-u", "a", "b"]) .pipe_in("N\n") @@ -530,6 +517,16 @@ fn test_cp_arg_interactive_update_newer() { .code_is(1) .no_stdout() .stderr_is("cp: overwrite 'b'? "); + + // Option Y + let (at, mut ucmd) = at_and_ucmd!(); + at.touch("b"); + std::thread::sleep(Duration::from_secs(1)); + at.touch("a"); + ucmd.args(&["-i", "-u", "a", "b"]) + .pipe_in("Y\n") + .succeeds() + .no_stdout(); } #[test] From 68de6f57f073858cdcb55196a249e3af656c234f Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 9 Apr 2024 13:22:14 +0000 Subject: [PATCH 072/144] chore(deps): update rust crate rstest to 0.19.0 --- Cargo.lock | 8 ++++---- Cargo.toml | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e10ce8c10a6..1d76171fcea 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1746,9 +1746,9 @@ checksum = "b833d8d034ea094b1ea68aa6d5c740e0d04bad9d16568d08ba6f76823a114316" [[package]] name = "rstest" -version = "0.18.2" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97eeab2f3c0a199bc4be135c36c924b6590b88c377d416494288c14f2db30199" +checksum = "9d5316d2a1479eeef1ea21e7f9ddc67c191d497abc8fc3ba2467857abbb68330" dependencies = [ "futures", "futures-timer", @@ -1758,9 +1758,9 @@ dependencies = [ [[package]] name = "rstest_macros" -version = "0.18.2" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d428f8247852f894ee1be110b375111b586d4fa431f6c46e64ba5a0dcccbe605" +checksum = "04a9df72cc1f67020b0d63ad9bfe4a323e459ea7eb68e03bd9824db49f9a4c25" dependencies = [ "cfg-if", "glob", diff --git a/Cargo.toml b/Cargo.toml index 7b699b9ef21..95abf0955ec 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -312,7 +312,7 @@ rand_core = "0.6" rayon = "1.10" redox_syscall = "0.5" regex = "1.10.4" -rstest = "0.18.2" +rstest = "0.19.0" rust-ini = "0.19.0" same-file = "1.0.6" self_cell = "1.0.3" From 226d6a263d66d9630af8d6d9a1d2411da50cc8d6 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 9 Apr 2024 14:02:29 +0000 Subject: [PATCH 073/144] chore(deps): update rust crate rust-ini to 0.21.0 --- Cargo.lock | 19 +++++++++++++------ Cargo.toml | 2 +- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1d76171fcea..2abc1b68cc3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1010,9 +1010,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.13.2" +version = "0.14.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" +checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" [[package]] name = "hermit-abi" @@ -1409,9 +1409,9 @@ dependencies = [ [[package]] name = "ordered-multimap" -version = "0.6.0" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ed8acf08e98e744e5384c8bc63ceb0364e68a6854187221c18df61c4797690e" +checksum = "49203cdcae0030493bad186b28da2fa25645fa276a51b6fec8010d281e02ef79" dependencies = [ "dlv-list", "hashbrown", @@ -1775,12 +1775,13 @@ dependencies = [ [[package]] name = "rust-ini" -version = "0.19.0" +version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e2a3bcec1f113553ef1c88aae6c020a369d03d55b58de9869a0908930385091" +checksum = "0d625ed57d8f49af6cfa514c42e1a71fadcff60eb0b1c517ff82fe41aa025b41" dependencies = [ "cfg-if", "ordered-multimap", + "trim-in-place", ] [[package]] @@ -2142,6 +2143,12 @@ dependencies = [ "crunchy", ] +[[package]] +name = "trim-in-place" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "343e926fc669bc8cde4fa3129ab681c63671bae288b1f1081ceee6d9d37904fc" + [[package]] name = "typenum" version = "1.15.0" diff --git a/Cargo.toml b/Cargo.toml index 95abf0955ec..86cc2baa091 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -313,7 +313,7 @@ rayon = "1.10" redox_syscall = "0.5" regex = "1.10.4" rstest = "0.19.0" -rust-ini = "0.19.0" +rust-ini = "0.21.0" same-file = "1.0.6" self_cell = "1.0.3" selinux = "0.4" From 11c9351a9cd074dc8c5b4f83418acfc668361d05 Mon Sep 17 00:00:00 2001 From: Qiu Chaofan Date: Wed, 10 Apr 2024 19:40:44 +0800 Subject: [PATCH 074/144] Initial AIX support (#6209) * Initial AIX support Add support to build on AIX operating system. Most of the changes are in fs part. Since AIX is still tier-3 target, current support is minimal and does not require passing all tests. * Fix spell checking failure --- Cargo.lock | 4 +- src/uucore/src/lib/features/fs.rs | 3 ++ src/uucore/src/lib/features/fsext.rs | 19 +++++++--- src/uucore/src/lib/features/signals.rs | 51 +++++++++++++++++++++++++- 4 files changed, 69 insertions(+), 8 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2abc1b68cc3..cc8b06d8612 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1960,9 +1960,9 @@ dependencies = [ [[package]] name = "signal-hook-registry" -version = "1.4.0" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0" +checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" dependencies = [ "libc", ] diff --git a/src/uucore/src/lib/features/fs.rs b/src/uucore/src/lib/features/fs.rs index c7fb1f2fcb8..f43c18ebd4b 100644 --- a/src/uucore/src/lib/features/fs.rs +++ b/src/uucore/src/lib/features/fs.rs @@ -111,6 +111,7 @@ impl FileInformation { #[cfg(all( unix, not(target_vendor = "apple"), + not(target_os = "aix"), not(target_os = "android"), not(target_os = "freebsd"), not(target_os = "netbsd"), @@ -142,6 +143,8 @@ impl FileInformation { ) ))] return self.0.st_nlink.into(); + #[cfg(target_os = "aix")] + return self.0.st_nlink.try_into().unwrap(); #[cfg(windows)] return self.0.number_of_links(); } diff --git a/src/uucore/src/lib/features/fsext.rs b/src/uucore/src/lib/features/fsext.rs index 0acedb4e1bb..7f29a2dc626 100644 --- a/src/uucore/src/lib/features/fsext.rs +++ b/src/uucore/src/lib/features/fsext.rs @@ -11,7 +11,7 @@ const LINUX_MTAB: &str = "/etc/mtab"; #[cfg(any(target_os = "linux", target_os = "android"))] const LINUX_MOUNTINFO: &str = "/proc/self/mountinfo"; -#[cfg(all(unix, not(target_os = "redox")))] +#[cfg(all(unix, not(any(target_os = "aix", target_os = "redox"))))] static MOUNT_OPT_BIND: &str = "bind"; #[cfg(windows)] const MAX_PATH: usize = 266; @@ -83,6 +83,7 @@ use std::time::UNIX_EPOCH; ))] pub use libc::statfs as StatFs; #[cfg(any( + target_os = "aix", target_os = "netbsd", target_os = "bitrig", target_os = "dragonfly", @@ -101,6 +102,7 @@ pub use libc::statvfs as StatFs; ))] pub use libc::statfs as statfs_fn; #[cfg(any( + target_os = "aix", target_os = "netbsd", target_os = "bitrig", target_os = "illumos", @@ -304,7 +306,7 @@ impl From for MountInfo { } } -#[cfg(all(unix, not(target_os = "redox")))] +#[cfg(all(unix, not(any(target_os = "aix", target_os = "redox"))))] fn is_dummy_filesystem(fs_type: &str, mount_option: &str) -> bool { // spell-checker:disable match fs_type { @@ -323,14 +325,14 @@ fn is_dummy_filesystem(fs_type: &str, mount_option: &str) -> bool { // spell-checker:enable } -#[cfg(all(unix, not(target_os = "redox")))] +#[cfg(all(unix, not(any(target_os = "aix", target_os = "redox"))))] fn is_remote_filesystem(dev_name: &str, fs_type: &str) -> bool { dev_name.find(':').is_some() || (dev_name.starts_with("//") && fs_type == "smbfs" || fs_type == "cifs") || dev_name == "-hosts" } -#[cfg(all(unix, not(target_os = "redox")))] +#[cfg(all(unix, not(any(target_os = "aix", target_os = "redox"))))] fn mount_dev_id(mount_dir: &str) -> String { use std::os::unix::fs::MetadataExt; @@ -472,7 +474,12 @@ pub fn read_fs_list() -> Result, std::io::Error> { } Ok(mounts) } - #[cfg(any(target_os = "redox", target_os = "illumos", target_os = "solaris"))] + #[cfg(any( + target_os = "aix", + target_os = "redox", + target_os = "illumos", + target_os = "solaris" + ))] { // No method to read mounts, yet Ok(Vec::new()) @@ -633,6 +640,7 @@ impl FsMeta for StatFs { #[cfg(all( not(target_env = "musl"), not(target_vendor = "apple"), + not(target_os = "aix"), not(target_os = "android"), not(target_os = "freebsd"), not(target_os = "openbsd"), @@ -658,6 +666,7 @@ impl FsMeta for StatFs { return self.f_bsize.into(); #[cfg(any( target_env = "musl", + target_os = "aix", target_os = "freebsd", target_os = "illumos", target_os = "solaris", diff --git a/src/uucore/src/lib/features/signals.rs b/src/uucore/src/lib/features/signals.rs index 0c3f968d975..8c9ca61953a 100644 --- a/src/uucore/src/lib/features/signals.rs +++ b/src/uucore/src/lib/features/signals.rs @@ -4,7 +4,7 @@ // file that was distributed with this source code. // spell-checker:ignore (vars/api) fcntl setrlimit setitimer rubout pollable 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 +// spell-checker:ignore (vars/signals) ABRT ALRM CHLD SEGV SIGABRT SIGALRM SIGBUS SIGCHLD SIGCONT SIGDANGER SIGEMT SIGFPE SIGHUP SIGILL SIGINFO SIGINT SIGIO SIGIOT SIGKILL SIGMIGRATE SIGMSG SIGPIPE SIGPRE SIGPROF SIGPWR SIGQUIT SIGSEGV SIGSTOP SIGSYS SIGTALRM SIGTERM SIGTRAP SIGTSTP SIGTHR SIGTTIN SIGTTOU SIGURG SIGUSR SIGVIRT SIGVTALRM SIGWINCH SIGXCPU SIGXFSZ STKFLT PWR THR TSTP TTIN TTOU VIRT VTALRM XCPU XFSZ SIGCLD SIGPOLL SIGWAITING SIGAIOCANCEL SIGLWP SIGFREEZE SIGTHAW SIGCANCEL SIGLOST SIGXRES SIGJVM SIGRTMIN SIGRT SIGRTMAX TALRM AIOCANCEL XRES RTMIN RTMAX #[cfg(unix)] use nix::errno::Errno; #[cfg(unix)] @@ -290,6 +290,55 @@ static ALL_SIGNALS: [&str; SIGNALS_SIZE] = [ "RTMAX", ]; +/* + The following signals are defined in AIX: + + SIGHUP hangup, generated when terminal disconnects + SIGINT interrupt, generated from terminal special char + SIGQUIT quit, generated from terminal special char + SIGILL illegal instruction (not reset when caught) + SIGTRAP trace trap (not reset when caught) + SIGABRT abort process + SIGEMT EMT instruction + SIGFPE floating point exception + SIGKILL kill (cannot be caught or ignored) + SIGBUS bus error (specification exception) + SIGSEGV segmentation violation + SIGSYS bad argument to system call + SIGPIPE write on a pipe with no one to read it + SIGALRM alarm clock timeout + SIGTERM software termination signal + SIGURG urgent condition on I/O channel + SIGSTOP stop (cannot be caught or ignored) + SIGTSTP interactive stop + SIGCONT continue (cannot be caught or ignored) + SIGCHLD sent to parent on child stop or exit + SIGTTIN background read attempted from control terminal + SIGTTOU background write attempted to control terminal + SIGIO I/O possible, or completed + SIGXCPU cpu time limit exceeded (see setrlimit()) + SIGXFSZ file size limit exceeded (see setrlimit()) + SIGMSG input data is in the ring buffer + SIGWINCH window size changed + SIGPWR power-fail restart + SIGUSR1 user defined signal 1 + SIGUSR2 user defined signal 2 + SIGPROF profiling time alarm (see setitimer) + SIGDANGER system crash imminent; free up some page space + SIGVTALRM virtual time alarm (see setitimer) + SIGMIGRATE migrate process + SIGPRE programming exception + SIGVIRT AIX virtual time alarm + SIGTALRM per-thread alarm clock +*/ +#[cfg(target_os = "aix")] +pub static ALL_SIGNALS: [&str; 37] = [ + "HUP", "INT", "QUIT", "ILL", "TRAP", "ABRT", "EMT", "FPE", "KILL", "BUS", "SEGV", "SYS", + "PIPE", "ALRM", "TERM", "URG", "STOP", "TSTP", "CONT", "CHLD", "TTIN", "TTOU", "IO", "XCPU", + "XFSZ", "MSG", "WINCH", "PWR", "USR1", "USR2", "PROF", "DANGER", "VTALRM", "MIGRATE", "PRE", + "VIRT", "TALRM", +]; + 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 edb16c386ce817b079b0706dcb83d63acd787204 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 11 Apr 2024 08:10:57 +0000 Subject: [PATCH 075/144] chore(deps): update rust crate platform-info to 2.0.3 --- Cargo.lock | 4 ++-- Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index cc8b06d8612..caeba80b404 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1523,9 +1523,9 @@ checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160" [[package]] name = "platform-info" -version = "2.0.2" +version = "2.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6259c4860e53bf665016f1b2f46a8859cadfa717581dc9d597ae4069de6300f" +checksum = "d5ff316b9c4642feda973c18f0decd6c8b0919d4722566f6e4337cce0dd88217" dependencies = [ "libc", "winapi", diff --git a/Cargo.toml b/Cargo.toml index 86cc2baa091..e58e813b923 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -305,7 +305,7 @@ onig = { version = "~6.4", default-features = false } parse_datetime = "0.5.0" phf = "0.11.2" phf_codegen = "0.11.2" -platform-info = "2.0.2" +platform-info = "2.0.3" quick-error = "2.0.1" rand = { version = "0.8", features = ["small_rng"] } rand_core = "0.6" From 9d82fa3b9abd3d359afae0b6b9eb2a22b93b9a1c Mon Sep 17 00:00:00 2001 From: Chad Williamson Date: Thu, 11 Apr 2024 12:54:58 -0500 Subject: [PATCH 076/144] mv: avoid attempting to set xattr on redox --- src/uu/mv/src/mv.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/uu/mv/src/mv.rs b/src/uu/mv/src/mv.rs index 9f24cf77092..3722f134f27 100644 --- a/src/uu/mv/src/mv.rs +++ b/src/uu/mv/src/mv.rs @@ -27,7 +27,7 @@ use uucore::fs::{ are_hardlinks_or_one_way_symlink_to_same_file, are_hardlinks_to_same_file, path_ends_with_terminator, }; -#[cfg(all(unix, not(target_os = "macos")))] +#[cfg(all(unix, not(any(target_os = "macos", target_os = "redox"))))] use uucore::fsxattr; use uucore::update_control; @@ -634,7 +634,7 @@ fn rename_with_fallback( None }; - #[cfg(all(unix, not(target_os = "macos")))] + #[cfg(all(unix, not(any(target_os = "macos", target_os = "redox"))))] let xattrs = fsxattr::retrieve_xattrs(from).unwrap_or_else(|_| std::collections::HashMap::new()); @@ -648,7 +648,7 @@ fn rename_with_fallback( move_dir(from, to, &options) }; - #[cfg(all(unix, not(target_os = "macos")))] + #[cfg(all(unix, not(any(target_os = "macos", target_os = "redox"))))] fsxattr::apply_xattrs(to, xattrs).unwrap(); if let Err(err) = result { @@ -661,11 +661,11 @@ fn rename_with_fallback( }; } } else { - #[cfg(all(unix, not(target_os = "macos")))] + #[cfg(all(unix, not(any(target_os = "macos", target_os = "redox"))))] fs::copy(from, to) .and_then(|_| fsxattr::copy_xattrs(&from, &to)) .and_then(|_| fs::remove_file(from))?; - #[cfg(any(target_os = "macos", not(unix)))] + #[cfg(any(target_os = "macos", target_os = "redox", not(unix)))] fs::copy(from, to).and_then(|_| fs::remove_file(from))?; } } From 693149d68325ab59eb02b1677b7db3138eb7a0ce Mon Sep 17 00:00:00 2001 From: Haisham <47662901+m-haisham@users.noreply.github.com> Date: Wed, 10 Apr 2024 18:01:47 +0500 Subject: [PATCH 077/144] kill: support multiple signals for --list --- src/uu/kill/src/kill.rs | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/src/uu/kill/src/kill.rs b/src/uu/kill/src/kill.rs index abb55f84526..5e2ca2a151e 100644 --- a/src/uu/kill/src/kill.rs +++ b/src/uu/kill/src/kill.rs @@ -71,7 +71,10 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { table(); Ok(()) } - Mode::List => list(pids_or_signals.first()), + Mode::List => { + list(&pids_or_signals); + Ok(()) + } } } @@ -164,12 +167,14 @@ fn print_signals() { } } -fn list(arg: Option<&String>) -> UResult<()> { - match arg { - Some(x) => print_signal(x), - None => { - print_signals(); - Ok(()) +fn list(signals: &Vec) { + if signals.is_empty() { + print_signals(); + } else { + for signal in signals { + if let Err(e) = print_signal(signal) { + uucore::show!(e) + } } } } From c4c81291b7b0cf4690b9df922a3468d4f3a601af Mon Sep 17 00:00:00 2001 From: Haisham <47662901+m-haisham@users.noreply.github.com> Date: Thu, 11 Apr 2024 08:45:53 +0500 Subject: [PATCH 078/144] kill: add tests to check multiple output --- tests/by-util/test_kill.rs | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/tests/by-util/test_kill.rs b/tests/by-util/test_kill.rs index a9094ecf6a7..33b850c8a73 100644 --- a/tests/by-util/test_kill.rs +++ b/tests/by-util/test_kill.rs @@ -92,6 +92,28 @@ fn test_kill_list_one_signal_from_name() { .stdout_matches(&Regex::new("\\b9\\b").unwrap()); } +#[test] +fn test_kill_list_two_signal_from_name() { + new_ucmd!() + .arg("-l") + .arg("INT") + .arg("KILL") + .succeeds() + .stdout_matches(&Regex::new("\\d\n\\d").unwrap()); +} + +#[test] +fn test_kill_list_three_signal_first_unknown() { + new_ucmd!() + .arg("-l") + .arg("IAMNOTASIGNAL") // spell-checker:disable-line + .arg("INT") + .arg("KILL") + .fails() + .stderr_contains("unknown signal") + .stdout_matches(&Regex::new("\\d\n\\d").unwrap()); +} + #[test] fn test_kill_set_bad_signal_name() { // spell-checker:disable-line From d56c7bbaeeed064e51bea7f15fd113d32cb204d2 Mon Sep 17 00:00:00 2001 From: Haisham <47662901+m-haisham@users.noreply.github.com> Date: Thu, 11 Apr 2024 08:48:15 +0500 Subject: [PATCH 079/144] kill: add test for vertical --list --- tests/by-util/test_kill.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/tests/by-util/test_kill.rs b/tests/by-util/test_kill.rs index 33b850c8a73..54e2ea2cf6e 100644 --- a/tests/by-util/test_kill.rs +++ b/tests/by-util/test_kill.rs @@ -92,6 +92,16 @@ fn test_kill_list_one_signal_from_name() { .stdout_matches(&Regex::new("\\b9\\b").unwrap()); } +#[test] +fn test_kill_list_all_vertically() { + // Check for a few signals. Do not try to be comprehensive. + let command = new_ucmd!().arg("-l").succeeds(); + let signals = command.stdout_str().split('\n').collect::>(); + assert!(signals.contains(&"KILL")); + assert!(signals.contains(&"TERM")); + assert!(signals.contains(&"HUP")); +} + #[test] fn test_kill_list_two_signal_from_name() { new_ucmd!() From 2f64cd83756e256309cad3d4f7ae870b8c0e0793 Mon Sep 17 00:00:00 2001 From: Ulrich Hornung Date: Sat, 13 Apr 2024 19:43:57 +0200 Subject: [PATCH 080/144] use features arg for stable and nightly build --- .github/workflows/CICD.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/CICD.yml b/.github/workflows/CICD.yml index 5ba0924856b..cd38463afbd 100644 --- a/.github/workflows/CICD.yml +++ b/.github/workflows/CICD.yml @@ -309,7 +309,7 @@ jobs: - name: Run sccache-cache uses: mozilla-actions/sccache-action@v0.0.4 - name: Test - run: cargo nextest run --hide-progress-bar --profile ci ${{ steps.vars.outputs.CARGO_FEATURES_OPTION }} + run: cargo nextest run --hide-progress-bar --profile ci --features ${{ matrix.job.features }} env: RUST_BACKTRACE: "1" @@ -336,7 +336,7 @@ jobs: - name: Run sccache-cache uses: mozilla-actions/sccache-action@v0.0.4 - name: Test - run: cargo nextest run --hide-progress-bar --profile ci ${{ steps.vars.outputs.CARGO_FEATURES_OPTION }} + run: cargo nextest run --hide-progress-bar --profile ci --features ${{ matrix.job.features }} env: RUST_BACKTRACE: "1" From aaaf4c3f9173c9871510c9c1b66be0f39861f674 Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Sat, 13 Apr 2024 16:40:30 +0200 Subject: [PATCH 081/144] kill: don't show EXIT with --list --- src/uu/kill/src/kill.rs | 3 ++- tests/by-util/test_kill.rs | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/uu/kill/src/kill.rs b/src/uu/kill/src/kill.rs index 5e2ca2a151e..75144946278 100644 --- a/src/uu/kill/src/kill.rs +++ b/src/uu/kill/src/kill.rs @@ -162,7 +162,8 @@ fn print_signal(signal_name_or_value: &str) -> UResult<()> { } fn print_signals() { - for signal in ALL_SIGNALS.iter() { + // GNU kill doesn't list the EXIT signal with --list, so we ignore it, too + for signal in ALL_SIGNALS.iter().filter(|x| **x != "EXIT") { println!("{signal}"); } } diff --git a/tests/by-util/test_kill.rs b/tests/by-util/test_kill.rs index 54e2ea2cf6e..a9a94ba5c56 100644 --- a/tests/by-util/test_kill.rs +++ b/tests/by-util/test_kill.rs @@ -62,7 +62,8 @@ fn test_kill_list_all_signals() { .succeeds() .stdout_contains("KILL") .stdout_contains("TERM") - .stdout_contains("HUP"); + .stdout_contains("HUP") + .stdout_does_not_contain("EXIT"); } #[test] From 9b4a787be7bd9b30cabfa008e28a342073117f0f Mon Sep 17 00:00:00 2001 From: Jadi Date: Sun, 14 Apr 2024 15:43:15 +0330 Subject: [PATCH 082/144] kill: return 1 and gnu style stderr in case of no pid (#6225) * kill: return 1 and gnu style stderr in case of no pid closes #6221 * Update src/uu/kill/src/kill.rs Co-authored-by: Ben Wiederhake --------- Co-authored-by: Ben Wiederhake --- src/uu/kill/src/kill.rs | 12 ++++++++++-- tests/by-util/test_kill.rs | 8 ++++++++ 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/src/uu/kill/src/kill.rs b/src/uu/kill/src/kill.rs index 75144946278..a641ff0c822 100644 --- a/src/uu/kill/src/kill.rs +++ b/src/uu/kill/src/kill.rs @@ -64,8 +64,16 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { .try_into() .map_err(|e| std::io::Error::from_raw_os_error(e as i32))?; let pids = parse_pids(&pids_or_signals)?; - kill(sig, &pids); - Ok(()) + if pids.is_empty() { + Err(USimpleError::new( + 1, + "no process ID specified\n\ + Try --help for more information.", + )) + } else { + kill(sig, &pids); + Ok(()) + } } Mode::Table => { table(); diff --git a/tests/by-util/test_kill.rs b/tests/by-util/test_kill.rs index a9a94ba5c56..61d18686a7d 100644 --- a/tests/by-util/test_kill.rs +++ b/tests/by-util/test_kill.rs @@ -204,3 +204,11 @@ fn test_kill_with_signal_prefixed_name_new_form() { .succeeds(); assert_eq!(target.wait_for_signal(), Some(libc::SIGKILL)); } + +#[test] +fn test_kill_no_pid_provided() { + // spell-checker:disable-line + new_ucmd!() + .fails() + .stderr_contains("no process ID specified"); +} From 333e4d9fe91f836902831d260951d0ee0bf2a71b Mon Sep 17 00:00:00 2001 From: Haisham <47662901+m-haisham@users.noreply.github.com> Date: Sun, 14 Apr 2024 18:08:51 +0500 Subject: [PATCH 083/144] kill: print --table as vertical (#6216) * kill: print --table as vertical * kill: remove signal padding on --table * kill: skip exit signal in --table * kill: replace "skip" with "filter" --------- Co-authored-by: Daniel Hofstetter --- src/uu/kill/src/kill.rs | 15 +++++++-------- tests/by-util/test_kill.rs | 23 +++++++++++++++++++++++ 2 files changed, 30 insertions(+), 8 deletions(-) diff --git a/src/uu/kill/src/kill.rs b/src/uu/kill/src/kill.rs index a641ff0c822..9ea51694dff 100644 --- a/src/uu/kill/src/kill.rs +++ b/src/uu/kill/src/kill.rs @@ -142,15 +142,14 @@ fn handle_obsolete(args: &mut Vec) -> Option { } fn table() { - let name_width = ALL_SIGNALS.iter().map(|n| n.len()).max().unwrap(); - - for (idx, signal) in ALL_SIGNALS.iter().enumerate() { - print!("{0: >#2} {1: <#2$}", idx, signal, name_width + 2); - if (idx + 1) % 7 == 0 { - println!(); - } + // GNU kill doesn't list the EXIT signal with --table, so we ignore it, too + for (idx, signal) in ALL_SIGNALS + .iter() + .enumerate() + .filter(|(_, s)| **s != "EXIT") + { + println!("{0: >#2} {1}", idx, signal); } - println!(); } fn print_signal(signal_name_or_value: &str) -> UResult<()> { diff --git a/tests/by-util/test_kill.rs b/tests/by-util/test_kill.rs index 61d18686a7d..5393d163adb 100644 --- a/tests/by-util/test_kill.rs +++ b/tests/by-util/test_kill.rs @@ -83,6 +83,29 @@ fn test_kill_list_all_signals_as_table() { .stdout_contains("HUP"); } +#[test] +fn test_kill_table_starts_at_1() { + new_ucmd!() + .arg("-t") + .succeeds() + .stdout_matches(&Regex::new("^\\s?1\\sHUP").unwrap()); +} + +#[test] +fn test_kill_table_lists_all_vertically() { + // Check for a few signals. Do not try to be comprehensive. + let command = new_ucmd!().arg("-t").succeeds(); + let signals = command + .stdout_str() + .split('\n') + .flat_map(|line| line.trim().split(" ").nth(1)) + .collect::>(); + + assert!(signals.contains(&"KILL")); + assert!(signals.contains(&"TERM")); + assert!(signals.contains(&"HUP")); +} + #[test] fn test_kill_list_one_signal_from_name() { // Use SIGKILL because it is 9 on all unixes. From 4ec82948b64a7e7712b2fb941c485aa6cad4f3e9 Mon Sep 17 00:00:00 2001 From: Ben Wiederhake Date: Mon, 1 Apr 2024 06:39:00 +0200 Subject: [PATCH 084/144] uucore: properly handle aliases in ShortcutValueParser --- .../src/lib/parser/shortcut_value_parser.rs | 30 +++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/src/uucore/src/lib/parser/shortcut_value_parser.rs b/src/uucore/src/lib/parser/shortcut_value_parser.rs index bdc96b8ef59..125d53ee2c4 100644 --- a/src/uucore/src/lib/parser/shortcut_value_parser.rs +++ b/src/uucore/src/lib/parser/shortcut_value_parser.rs @@ -66,7 +66,7 @@ impl TypedValueParser for ShortcutValueParser { let matched_values: Vec<_> = self .0 .iter() - .filter(|x| x.get_name().starts_with(value)) + .filter(|x| x.get_name_and_aliases().any(|name| name.starts_with(value))) .collect(); match matched_values.len() { @@ -101,7 +101,7 @@ where mod tests { use std::ffi::OsStr; - use clap::{builder::TypedValueParser, error::ErrorKind, Command}; + use clap::{builder::PossibleValue, builder::TypedValueParser, error::ErrorKind, Command}; use super::ShortcutValueParser; @@ -166,4 +166,30 @@ mod tests { let result = parser.parse_ref(&cmd, None, OsStr::from_bytes(&[0xc3, 0x28])); assert_eq!(ErrorKind::InvalidUtf8, result.unwrap_err().kind()); } + + #[test] + fn test_ambiguous_word_same_meaning() { + let cmd = Command::new("cmd"); + let parser = ShortcutValueParser::new([ + PossibleValue::new("atime").alias("access"), + "status".into(), + ]); + // Even though "a" is ambiguous (it might mean "atime" or "access"), + // the meaning is uniquely defined, therefore accept it. + let atime_values = [ + // spell-checker:disable-next-line + "atime", "atim", "at", "a", "access", "acces", "acce", "acc", "ac", + ]; + // spell-checker:disable-next-line + let status_values = ["status", "statu", "stat", "sta", "st", "st"]; + + for value in atime_values { + let result = parser.parse_ref(&cmd, None, OsStr::new(value)); + assert_eq!("atime", result.unwrap()); + } + for value in status_values { + let result = parser.parse_ref(&cmd, None, OsStr::new(value)); + assert_eq!("status", result.unwrap()); + } + } } From bfc7411dece9fbcc8674adc00fb76fbc3b02c6bf Mon Sep 17 00:00:00 2001 From: Ben Wiederhake Date: Mon, 1 Apr 2024 07:45:47 +0200 Subject: [PATCH 085/144] docs: document existing ls --sort=name feature --- docs/src/extensions.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/src/extensions.md b/docs/src/extensions.md index 79746498f2d..2319b2a6f1a 100644 --- a/docs/src/extensions.md +++ b/docs/src/extensions.md @@ -75,6 +75,9 @@ number of spaces representing a tab when determining the line length. GNU `ls` provides two ways to use a long listing format: `-l` and `--format=long`. We support a third way: `--long`. +GNU `ls --sort=VALUE` only supports special non-default sort orders. +We support `--sort=name`, which makes it possible to override an earlier value. + ## `du` `du` allows `birth` and `creation` as values for the `--time` argument to show the creation time. It From d4546ced26e2c75931ab630f8488c9cee86d46ff Mon Sep 17 00:00:00 2001 From: Ben Wiederhake Date: Mon, 1 Apr 2024 06:00:15 +0200 Subject: [PATCH 086/144] cp: accept shortcuts for stringly-enum arguments --- src/uu/cp/src/cp.rs | 24 +--- src/uucore/src/lib/features/update_control.rs | 3 +- tests/by-util/test_cp.rs | 124 +++++++++++------- 3 files changed, 88 insertions(+), 63 deletions(-) diff --git a/src/uu/cp/src/cp.rs b/src/uu/cp/src/cp.rs index d45f994180d..fba2db7ab38 100644 --- a/src/uu/cp/src/cp.rs +++ b/src/uu/cp/src/cp.rs @@ -37,8 +37,8 @@ use uucore::{backup_control, update_control}; // requires these enum. pub use uucore::{backup_control::BackupMode, update_control::UpdateMode}; use uucore::{ - 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, + shortcut_value_parser::ShortcutValueParser, show_error, show_warning, util_name, }; use crate::copydir::copy_directory; @@ -396,22 +396,14 @@ static PRESERVABLE_ATTRIBUTES: &[&str] = &[ "ownership", "timestamps", "context", - "link", "links", "xattr", "all", ]; #[cfg(not(unix))] -static PRESERVABLE_ATTRIBUTES: &[&str] = &[ - "mode", - "timestamps", - "context", - "link", - "links", - "xattr", - "all", -]; +static PRESERVABLE_ATTRIBUTES: &[&str] = + &["mode", "timestamps", "context", "links", "xattr", "all"]; pub fn uu_app() -> Command { const MODE_ARGS: &[&str] = &[ @@ -543,7 +535,7 @@ pub fn uu_app() -> Command { .overrides_with_all(MODE_ARGS) .require_equals(true) .default_missing_value("always") - .value_parser(["auto", "always", "never"]) + .value_parser(ShortcutValueParser::new(["auto", "always", "never"])) .num_args(0..=1) .help("control clone/CoW copies. See below"), ) @@ -559,9 +551,7 @@ pub fn uu_app() -> Command { .long(options::PRESERVE) .action(ArgAction::Append) .use_value_delimiter(true) - .value_parser(clap::builder::PossibleValuesParser::new( - PRESERVABLE_ATTRIBUTES, - )) + .value_parser(ShortcutValueParser::new(PRESERVABLE_ATTRIBUTES)) .num_args(0..) .require_equals(true) .value_name("ATTR_LIST") @@ -655,7 +645,7 @@ pub fn uu_app() -> Command { Arg::new(options::SPARSE) .long(options::SPARSE) .value_name("WHEN") - .value_parser(["never", "auto", "always"]) + .value_parser(ShortcutValueParser::new(["never", "auto", "always"])) .help("control creation of sparse files. See below"), ) // TODO: implement the following args diff --git a/src/uucore/src/lib/features/update_control.rs b/src/uucore/src/lib/features/update_control.rs index bd429251452..83700e5512f 100644 --- a/src/uucore/src/lib/features/update_control.rs +++ b/src/uucore/src/lib/features/update_control.rs @@ -61,6 +61,7 @@ pub enum UpdateMode { } pub mod arguments { + use crate::shortcut_value_parser::ShortcutValueParser; use clap::ArgAction; pub static OPT_UPDATE: &str = "update"; @@ -71,7 +72,7 @@ pub mod arguments { clap::Arg::new(OPT_UPDATE) .long("update") .help("move only when the SOURCE file is newer than the destination file or when the destination file is missing") - .value_parser(["none", "all", "older"]) + .value_parser(ShortcutValueParser::new(["none", "all", "older"])) .num_args(0..=1) .default_missing_value("older") .require_equals(true) diff --git a/tests/by-util/test_cp.rs b/tests/by-util/test_cp.rs index bdfc9c8e8a4..1f81011dbb9 100644 --- a/tests/by-util/test_cp.rs +++ b/tests/by-util/test_cp.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 (flags) reflink (fs) tmpfs (linux) rlimit Rlim NOFILE clob btrfs ROOTDIR USERDIR procfs outfile uufs xattrs +// spell-checker:ignore (flags) reflink (fs) tmpfs (linux) rlimit Rlim NOFILE clob btrfs neve ROOTDIR USERDIR procfs outfile uufs xattrs use crate::common::util::TestScenario; #[cfg(not(windows))] @@ -286,16 +286,18 @@ fn test_cp_arg_update_interactive_error() { #[test] fn test_cp_arg_update_none() { - let (at, mut ucmd) = at_and_ucmd!(); + for argument in ["--update=none", "--update=non", "--update=n"] { + let (at, mut ucmd) = at_and_ucmd!(); - ucmd.arg(TEST_HELLO_WORLD_SOURCE) - .arg(TEST_HOW_ARE_YOU_SOURCE) - .arg("--update=none") - .succeeds() - .no_stderr() - .no_stdout(); + ucmd.arg(TEST_HELLO_WORLD_SOURCE) + .arg(TEST_HOW_ARE_YOU_SOURCE) + .arg(argument) + .succeeds() + .no_stderr() + .no_stdout(); - assert_eq!(at.read(TEST_HOW_ARE_YOU_SOURCE), "How are you?\n"); + assert_eq!(at.read(TEST_HOW_ARE_YOU_SOURCE), "How are you?\n"); + } } #[test] @@ -1402,29 +1404,28 @@ fn test_cp_preserve_no_args_before_opts() { #[test] fn test_cp_preserve_all() { - let (at, mut ucmd) = at_and_ucmd!(); - let src_file = "a"; - let dst_file = "b"; + for argument in ["--preserve=all", "--preserve=al"] { + let (at, mut ucmd) = at_and_ucmd!(); + let src_file = "a"; + let dst_file = "b"; - // Prepare the source file - at.touch(src_file); - #[cfg(unix)] - at.set_mode(src_file, 0o0500); + // Prepare the source file + at.touch(src_file); + #[cfg(unix)] + at.set_mode(src_file, 0o0500); - // TODO: create a destination that does not allow copying of xattr and context - // Copy - ucmd.arg(src_file) - .arg(dst_file) - .arg("--preserve=all") - .succeeds(); + // TODO: create a destination that does not allow copying of xattr and context + // Copy + ucmd.arg(src_file).arg(dst_file).arg(argument).succeeds(); - #[cfg(all(unix, not(target_os = "freebsd")))] - { - // Assert that the mode, ownership, and timestamps are preserved - // NOTICE: the ownership is not modified on the src file, because that requires root permissions - let metadata_src = at.metadata(src_file); - let metadata_dst = at.metadata(dst_file); - assert_metadata_eq!(metadata_src, metadata_dst); + #[cfg(all(unix, not(target_os = "freebsd")))] + { + // Assert that the mode, ownership, and timestamps are preserved + // NOTICE: the ownership is not modified on the src file, because that requires root permissions + let metadata_src = at.metadata(src_file); + let metadata_dst = at.metadata(dst_file); + assert_metadata_eq!(metadata_src, metadata_dst); + } } } @@ -1472,6 +1473,35 @@ fn test_cp_preserve_all_context_fails_on_non_selinux() { .fails(); } +#[test] +fn test_cp_preserve_link_parses() { + // TODO: Also check whether --preserve=link did the right thing! + for argument in [ + "--preserve=links", + "--preserve=link", + "--preserve=li", + "--preserve=l", + ] { + new_ucmd!() + .arg(argument) + .arg(TEST_COPY_FROM_FOLDER_FILE) + .arg(TEST_HELLO_WORLD_DEST) + .succeeds() + .no_output(); + } +} + +#[test] +fn test_cp_preserve_invalid_rejected() { + new_ucmd!() + .arg("--preserve=invalid-value") + .arg(TEST_COPY_FROM_FOLDER_FILE) + .arg(TEST_HELLO_WORLD_DEST) + .fails() + .code_is(1) + .no_stdout(); +} + #[test] #[cfg(target_os = "android")] #[cfg(disabled_until_fixed)] // FIXME: the test looks to .succeed on android @@ -2196,14 +2226,16 @@ fn test_cp_reflink_none() { #[test] #[cfg(any(target_os = "linux", target_os = "android", target_os = "macos"))] fn test_cp_reflink_never() { - let (at, mut ucmd) = at_and_ucmd!(); - ucmd.arg("--reflink=never") - .arg(TEST_HELLO_WORLD_SOURCE) - .arg(TEST_EXISTING_FILE) - .succeeds(); + for argument in ["--reflink=never", "--reflink=neve", "--reflink=n"] { + let (at, mut ucmd) = at_and_ucmd!(); + ucmd.arg(argument) + .arg(TEST_HELLO_WORLD_SOURCE) + .arg(TEST_EXISTING_FILE) + .succeeds(); - // Check the content of the destination file - assert_eq!(at.read(TEST_EXISTING_FILE), "Hello, World!\n"); + // Check the content of the destination file + assert_eq!(at.read(TEST_EXISTING_FILE), "Hello, World!\n"); + } } #[test] @@ -2286,19 +2318,21 @@ fn test_cp_sparse_never_empty() { #[cfg(any(target_os = "linux", target_os = "android"))] #[test] fn test_cp_sparse_always_empty() { - let (at, mut ucmd) = at_and_ucmd!(); + for argument in ["--sparse=always", "--sparse=alway", "--sparse=al"] { + let (at, mut ucmd) = at_and_ucmd!(); - const BUFFER_SIZE: usize = 4096 * 4; - let buf: [u8; BUFFER_SIZE] = [0; BUFFER_SIZE]; + const BUFFER_SIZE: usize = 4096 * 4; + let buf: [u8; BUFFER_SIZE] = [0; BUFFER_SIZE]; - at.make_file("src_file1"); - at.write_bytes("src_file1", &buf); + at.make_file("src_file1"); + at.write_bytes("src_file1", &buf); - ucmd.args(&["--sparse=always", "src_file1", "dst_file_sparse"]) - .succeeds(); + ucmd.args(&[argument, "src_file1", "dst_file_sparse"]) + .succeeds(); - assert_eq!(at.read_bytes("dst_file_sparse"), buf); - assert_eq!(at.metadata("dst_file_sparse").blocks(), 0); + assert_eq!(at.read_bytes("dst_file_sparse"), buf); + assert_eq!(at.metadata("dst_file_sparse").blocks(), 0); + } } #[cfg(any(target_os = "linux", target_os = "android"))] From 27a81f3d32f5f7a1da0f50b851cf384917b05dac Mon Sep 17 00:00:00 2001 From: Ben Wiederhake Date: Mon, 1 Apr 2024 06:00:15 +0200 Subject: [PATCH 087/144] du: accept shortcuts for stringly-enum arguments --- src/uu/du/src/du.rs | 9 +++++++-- tests/by-util/test_du.rs | 18 ++++++++++-------- 2 files changed, 17 insertions(+), 10 deletions(-) diff --git a/src/uu/du/src/du.rs b/src/uu/du/src/du.rs index 0de16121aa5..1935248dafb 100644 --- a/src/uu/du/src/du.rs +++ b/src/uu/du/src/du.rs @@ -4,7 +4,7 @@ // file that was distributed with this source code. use chrono::{DateTime, Local}; -use clap::{crate_version, Arg, ArgAction, ArgMatches, Command}; +use clap::{builder::PossibleValue, crate_version, Arg, ArgAction, ArgMatches, Command}; use glob::Pattern; use std::collections::HashSet; use std::env; @@ -30,6 +30,7 @@ use uucore::error::{set_exit_code, FromIo, UError, UResult, USimpleError}; use uucore::line_ending::LineEnding; use uucore::parse_glob; use uucore::parse_size::{parse_size_u64, ParseSizeError}; +use uucore::shortcut_value_parser::ShortcutValueParser; use uucore::{format_usage, help_about, help_section, help_usage, show, show_error, show_warning}; #[cfg(windows)] use windows_sys::Win32::Foundation::HANDLE; @@ -1040,7 +1041,11 @@ pub fn uu_app() -> Command { .value_name("WORD") .require_equals(true) .num_args(0..) - .value_parser(["atime", "access", "use", "ctime", "status", "birth", "creation"]) + .value_parser(ShortcutValueParser::new([ + PossibleValue::new("atime").alias("access").alias("use"), + PossibleValue::new("ctime").alias("status"), + PossibleValue::new("creation").alias("birth"), + ])) .help( "show time of the last modification of any file in the \ directory, or any of its subdirectories. If WORD is given, show time as WORD instead \ diff --git a/tests/by-util/test_du.rs b/tests/by-util/test_du.rs index dbd2c9c946d..2bc694acbc0 100644 --- a/tests/by-util/test_du.rs +++ b/tests/by-util/test_du.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 (paths) sublink subwords azerty azeaze xcwww azeaz amaz azea qzerty tazerty tsublink testfile1 testfile2 filelist testdir testfile +// spell-checker:ignore (paths) atim sublink subwords azerty azeaze xcwww azeaz amaz azea qzerty tazerty tsublink testfile1 testfile2 filelist testdir testfile #[cfg(not(windows))] use regex::Regex; @@ -576,13 +576,15 @@ fn test_du_time() { .succeeds(); result.stdout_only("0\t2016-06-16 00:00\tdate_test\n"); - let result = ts - .ucmd() - .env("TZ", "UTC") - .arg("--time=atime") - .arg("date_test") - .succeeds(); - result.stdout_only("0\t2015-05-15 00:00\tdate_test\n"); + for argument in ["--time=atime", "--time=atim", "--time=a"] { + let result = ts + .ucmd() + .env("TZ", "UTC") + .arg(argument) + .arg("date_test") + .succeeds(); + result.stdout_only("0\t2015-05-15 00:00\tdate_test\n"); + } let result = ts .ucmd() From 3877d1450452509931d88bb2aa7707d08b907dfd Mon Sep 17 00:00:00 2001 From: Ben Wiederhake Date: Mon, 1 Apr 2024 08:06:18 +0200 Subject: [PATCH 088/144] ls: accept shortcuts for stringly-enum arguments --- src/uu/ls/src/ls.rs | 61 ++++++++++++++----------- tests/by-util/test_ls.rs | 99 +++++++++++++++++++++++++++++++++++++--- 2 files changed, 128 insertions(+), 32 deletions(-) diff --git a/src/uu/ls/src/ls.rs b/src/uu/ls/src/ls.rs index 65e0969cbd7..0221ae09681 100644 --- a/src/uu/ls/src/ls.rs +++ b/src/uu/ls/src/ls.rs @@ -6,7 +6,7 @@ // spell-checker:ignore (ToDO) somegroup nlink tabsize dired subdired dtype colorterm stringly use clap::{ - builder::{NonEmptyStringValueParser, ValueParser}, + builder::{NonEmptyStringValueParser, PossibleValue, ValueParser}, crate_version, Arg, ArgAction, Command, }; use glob::{MatchOptions, Pattern}; @@ -62,6 +62,7 @@ use uucore::{ format_usage, fs::display_permissions, parse_size::parse_size_u64, + shortcut_value_parser::ShortcutValueParser, version_cmp::version_cmp, }; use uucore::{help_about, help_section, help_usage, parse_glob, show, show_error, show_warning}; @@ -1203,7 +1204,7 @@ pub fn uu_app() -> Command { Arg::new(options::FORMAT) .long(options::FORMAT) .help("Set the display format.") - .value_parser([ + .value_parser(ShortcutValueParser::new([ "long", "verbose", "single-column", @@ -1212,7 +1213,7 @@ pub fn uu_app() -> Command { "across", "horizontal", "commas", - ]) + ])) .hide_possible_values(true) .require_equals(true) .overrides_with_all([ @@ -1303,9 +1304,11 @@ pub fn uu_app() -> Command { Arg::new(options::HYPERLINK) .long(options::HYPERLINK) .help("hyperlink file names WHEN") - .value_parser([ - "always", "yes", "force", "auto", "tty", "if-tty", "never", "no", "none", - ]) + .value_parser(ShortcutValueParser::new([ + PossibleValue::new("always").alias("yes").alias("force"), + PossibleValue::new("auto").alias("tty").alias("if-tty"), + PossibleValue::new("never").alias("no").alias("none"), + ])) .require_equals(true) .num_args(0..=1) .default_missing_value("always") @@ -1351,15 +1354,15 @@ pub fn uu_app() -> Command { Arg::new(options::QUOTING_STYLE) .long(options::QUOTING_STYLE) .help("Set quoting style.") - .value_parser([ - "literal", - "shell", - "shell-always", - "shell-escape", - "shell-escape-always", - "c", - "escape", - ]) + .value_parser(ShortcutValueParser::new([ + PossibleValue::new("literal"), + PossibleValue::new("shell"), + PossibleValue::new("shell-escape"), + PossibleValue::new("shell-always"), + PossibleValue::new("shell-escape-always"), + PossibleValue::new("c").alias("c-maybe"), + PossibleValue::new("escape"), + ])) .overrides_with_all([ options::QUOTING_STYLE, options::quoting::LITERAL, @@ -1434,9 +1437,11 @@ pub fn uu_app() -> Command { \tbirth time: birth, creation;", ) .value_name("field") - .value_parser([ - "atime", "access", "use", "ctime", "status", "birth", "creation", - ]) + .value_parser(ShortcutValueParser::new([ + PossibleValue::new("atime").alias("access").alias("use"), + PossibleValue::new("ctime").alias("status"), + PossibleValue::new("birth").alias("creation"), + ])) .hide_possible_values(true) .require_equals(true) .overrides_with_all([options::TIME, options::time::ACCESS, options::time::CHANGE]), @@ -1496,7 +1501,7 @@ pub fn uu_app() -> Command { .long(options::SORT) .help("Sort by : name, none (-U), time (-t), size (-S), extension (-X) or width") .value_name("field") - .value_parser(["name", "none", "time", "size", "version", "extension", "width"]) + .value_parser(ShortcutValueParser::new(["name", "none", "time", "size", "version", "extension", "width"])) .require_equals(true) .overrides_with_all([ options::SORT, @@ -1744,9 +1749,11 @@ pub fn uu_app() -> Command { Arg::new(options::COLOR) .long(options::COLOR) .help("Color output based on file type.") - .value_parser([ - "always", "yes", "force", "auto", "tty", "if-tty", "never", "no", "none", - ]) + .value_parser(ShortcutValueParser::new([ + PossibleValue::new("always").alias("yes").alias("force"), + PossibleValue::new("auto").alias("tty").alias("if-tty"), + PossibleValue::new("never").alias("no").alias("none"), + ])) .require_equals(true) .num_args(0..=1), ) @@ -1757,7 +1764,7 @@ pub fn uu_app() -> Command { "Append indicator with style WORD to entry names: \ none (default), slash (-p), file-type (--file-type), classify (-F)", ) - .value_parser(["none", "slash", "file-type", "classify"]) + .value_parser(ShortcutValueParser::new(["none", "slash", "file-type", "classify"])) .overrides_with_all([ options::indicator_style::FILE_TYPE, options::indicator_style::SLASH, @@ -1788,9 +1795,11 @@ pub fn uu_app() -> Command { --dereference-command-line-symlink-to-dir options are specified.", ) .value_name("when") - .value_parser([ - "always", "yes", "force", "auto", "tty", "if-tty", "never", "no", "none", - ]) + .value_parser(ShortcutValueParser::new([ + PossibleValue::new("always").alias("yes").alias("force"), + PossibleValue::new("auto").alias("tty").alias("if-tty"), + PossibleValue::new("never").alias("no").alias("none"), + ])) .default_missing_value("always") .require_equals(true) .num_args(0..=1) diff --git a/tests/by-util/test_ls.rs b/tests/by-util/test_ls.rs index 2f48e4460bb..099f18fb807 100644 --- a/tests/by-util/test_ls.rs +++ b/tests/by-util/test_ls.rs @@ -27,6 +27,7 @@ const LONG_ARGS: &[&str] = &[ "-l", "--long", "--format=long", + "--format=lon", "--for=long", "--format=verbose", "--for=verbose", @@ -35,6 +36,7 @@ const LONG_ARGS: &[&str] = &[ const ACROSS_ARGS: &[&str] = &[ "-x", "--format=across", + "--format=acr", "--format=horizontal", "--for=across", "--for=horizontal", @@ -999,6 +1001,8 @@ fn test_ls_zero() { let ignored_opts = [ "--quoting-style=c", "--color=always", + "--color=alway", + "--color=al", "-m", "--hide-control-chars", ]; @@ -1601,6 +1605,24 @@ fn test_ls_deref() { .succeeds(); assert!(re.is_match(result.stdout_str().trim())); + let result = scene + .ucmd() + .arg("-l") + .arg("--color=neve") // spell-checker:disable-line + .arg("test-long") + .arg("test-long.link") + .succeeds(); + assert!(re.is_match(result.stdout_str().trim())); + + let result = scene + .ucmd() + .arg("-l") + .arg("--color=n") + .arg("test-long") + .arg("test-long.link") + .succeeds(); + assert!(re.is_match(result.stdout_str().trim())); + let result = scene .ucmd() .arg("-L") @@ -1676,6 +1698,10 @@ fn test_ls_sort_none() { // Order is not specified so we just check that it doesn't // give any errors. scene.ucmd().arg("--sort=none").succeeds(); + scene.ucmd().arg("--sort=non").succeeds(); + scene.ucmd().arg("--sort=no").succeeds(); + // scene.ucmd().arg("--sort=n").succeeds(); + // We refuse to accept "--sort=n", since this is too confusable with "--sort=name", which is our own extension. scene.ucmd().arg("-U").succeeds(); } @@ -1693,6 +1719,16 @@ fn test_ls_sort_name() { .arg("--sort=name") .succeeds() .stdout_is("test-1\ntest-2\ntest-3\n"); + scene + .ucmd() + .arg("--sort=nam") + .succeeds() + .stdout_is("test-1\ntest-2\ntest-3\n"); + scene + .ucmd() + .arg("--sort=na") + .succeeds() + .stdout_is("test-1\ntest-2\ntest-3\n"); let scene_dot = TestScenario::new(util_name!()); let at = &scene_dot.fixtures; @@ -1729,6 +1765,16 @@ fn test_ls_sort_width() { .arg("--sort=width") .succeeds() .stdout_is("d\nzz\nabc\nbbb\neee\ncccc\naaaaa\nbcdef\nfffff\n"); + scene + .ucmd() + .arg("--sort=widt") // spell-checker:disable-line + .succeeds() + .stdout_is("d\nzz\nabc\nbbb\neee\ncccc\naaaaa\nbcdef\nfffff\n"); + scene + .ucmd() + .arg("--sort=w") + .succeeds() + .stdout_is("d\nzz\nabc\nbbb\neee\ncccc\naaaaa\nbcdef\nfffff\n"); } #[test] @@ -1757,6 +1803,12 @@ fn test_ls_order_size() { let result = scene.ucmd().arg("--sort=size").succeeds(); result.stdout_only("test-4\ntest-3\ntest-2\ntest-1\n"); + let result = scene.ucmd().arg("--sort=siz").succeeds(); + result.stdout_only("test-4\ntest-3\ntest-2\ntest-1\n"); + + let result = scene.ucmd().arg("--sort=s").succeeds(); + result.stdout_only("test-4\ntest-3\ntest-2\ntest-1\n"); + let result = scene.ucmd().arg("--sort=size").arg("-r").succeeds(); result.stdout_only("test-1\ntest-2\ntest-3\ntest-4\n"); } @@ -1961,7 +2013,14 @@ fn test_ls_order_time() { // 3 was accessed last in the read // So the order should be 2 3 4 1 - for arg in ["-u", "--time=atime", "--time=access", "--time=use"] { + for arg in [ + "-u", + "--time=atime", + "--time=atim", // spell-checker:disable-line + "--time=a", + "--time=access", + "--time=use", + ] { let result = scene.ucmd().arg("-t").arg(arg).succeeds(); at.open("test-3").metadata().unwrap().accessed().unwrap(); at.open("test-4").metadata().unwrap().accessed().unwrap(); @@ -2216,12 +2275,16 @@ fn test_ls_indicator_style() { for opt in [ "--indicator-style=classify", "--ind=classify", + "--indicator-style=clas", // spell-checker:disable-line + "--indicator-style=c", "--indicator-style=file-type", "--ind=file-type", "--indicator-style=slash", "--ind=slash", "--classify", "--classify=always", + "--classify=alway", // spell-checker:disable-line + "--classify=al", "--classify=yes", "--classify=force", "--class", @@ -2236,10 +2299,13 @@ fn test_ls_indicator_style() { // Classify, Indicator options should not contain any indicators when value is none. for opt in [ "--indicator-style=none", + "--indicator-style=n", "--ind=none", "--classify=none", "--classify=never", + "--classify=non", "--classify=no", + "--classify=n", ] { // Verify that there are no indicators for any of the file types. scene @@ -2553,6 +2619,12 @@ fn test_ls_version_sort() { expected ); + let result = scene.ucmd().arg("-1").arg("--sort=v").succeeds(); + assert_eq!( + result.stdout_str().split('\n').collect::>(), + expected + ); + let result = scene.ucmd().arg("-a1v").succeeds(); expected.insert(expected.len() - 1, ".."); expected.insert(0, "."); @@ -2589,19 +2661,27 @@ fn test_ls_quoting_style() { for (arg, correct) in [ ("--quoting-style=literal", "one?two"), + ("--quoting-style=litera", "one?two"), // spell-checker:disable-line + ("--quoting-style=li", "one?two"), ("-N", "one?two"), ("--literal", "one?two"), ("--l", "one?two"), ("--quoting-style=c", "\"one\\ntwo\""), + ("--quoting-style=c-", "\"one\\ntwo\""), + ("--quoting-style=c-maybe", "\"one\\ntwo\""), ("-Q", "\"one\\ntwo\""), ("--quote-name", "\"one\\ntwo\""), ("--quoting-style=escape", "one\\ntwo"), + ("--quoting-style=escap", "one\\ntwo"), // spell-checker:disable-line ("-b", "one\\ntwo"), ("--escape", "one\\ntwo"), ("--quoting-style=shell-escape", "'one'$'\\n''two'"), ("--quoting-style=shell-escape-always", "'one'$'\\n''two'"), + ("--quoting-style=shell-escape-alway", "'one'$'\\n''two'"), + ("--quoting-style=shell-escape-a", "'one'$'\\n''two'"), ("--quoting-style=shell", "one?two"), ("--quoting-style=shell-always", "'one?two'"), + ("--quoting-style=shell-a", "'one?two'"), ] { scene .ucmd() @@ -4244,11 +4324,18 @@ fn test_ls_hyperlink() { .stdout_str() .contains(&format!("{path}{separator}{file}\x07{file}\x1b]8;;\x07"))); - scene - .ucmd() - .arg("--hyperlink=never") - .succeeds() - .stdout_is(format!("{file}\n")); + for argument in [ + "--hyperlink=never", + "--hyperlink=neve", // spell-checker:disable-line + "--hyperlink=ne", // spell-checker:disable-line + "--hyperlink=n", + ] { + scene + .ucmd() + .arg(argument) + .succeeds() + .stdout_is(format!("{file}\n")); + } } // spell-checker: disable From 2646944bee98fc65c9af16b67a5b312560c27867 Mon Sep 17 00:00:00 2001 From: Ben Wiederhake Date: Mon, 1 Apr 2024 08:06:18 +0200 Subject: [PATCH 089/144] numfmt: accept shortcuts for stringly-enum arguments --- src/uu/numfmt/src/numfmt.rs | 9 ++++++++- tests/by-util/test_numfmt.rs | 4 ++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/src/uu/numfmt/src/numfmt.rs b/src/uu/numfmt/src/numfmt.rs index 80a2051bd44..2b9932ee577 100644 --- a/src/uu/numfmt/src/numfmt.rs +++ b/src/uu/numfmt/src/numfmt.rs @@ -15,6 +15,7 @@ use units::{IEC_BASES, SI_BASES}; use uucore::display::Quotable; use uucore::error::UResult; use uucore::ranges::Range; +use uucore::shortcut_value_parser::ShortcutValueParser; use uucore::{format_usage, help_about, help_section, help_usage, show, show_error}; pub mod errors; @@ -340,7 +341,13 @@ pub fn uu_app() -> Command { .help("use METHOD for rounding when scaling") .value_name("METHOD") .default_value("from-zero") - .value_parser(["up", "down", "from-zero", "towards-zero", "nearest"]), + .value_parser(ShortcutValueParser::new([ + "up", + "down", + "from-zero", + "towards-zero", + "nearest", + ])), ) .arg( Arg::new(options::SUFFIX) diff --git a/tests/by-util/test_numfmt.rs b/tests/by-util/test_numfmt.rs index bb80502d584..1dddedfddeb 100644 --- a/tests/by-util/test_numfmt.rs +++ b/tests/by-util/test_numfmt.rs @@ -550,10 +550,14 @@ fn test_delimiter_with_padding_and_fields() { fn test_round() { for (method, exp) in [ ("from-zero", ["9.1K", "-9.1K", "9.1K", "-9.1K"]), + ("from-zer", ["9.1K", "-9.1K", "9.1K", "-9.1K"]), // spell-checker:disable-line + ("f", ["9.1K", "-9.1K", "9.1K", "-9.1K"]), ("towards-zero", ["9.0K", "-9.0K", "9.0K", "-9.0K"]), ("up", ["9.1K", "-9.0K", "9.1K", "-9.0K"]), ("down", ["9.0K", "-9.1K", "9.0K", "-9.1K"]), ("nearest", ["9.0K", "-9.0K", "9.1K", "-9.1K"]), + ("near", ["9.0K", "-9.0K", "9.1K", "-9.1K"]), + ("n", ["9.0K", "-9.0K", "9.1K", "-9.1K"]), ] { new_ucmd!() .args(&[ From 1dd7d8e0dbf6739ffd0b9c39c31cb6bf6311c08e Mon Sep 17 00:00:00 2001 From: Ben Wiederhake Date: Mon, 1 Apr 2024 08:06:18 +0200 Subject: [PATCH 090/144] od: accept shortcuts for stringly-enum arguments --- src/uu/od/src/od.rs | 3 ++- tests/by-util/test_od.rs | 24 ++++++++++++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/src/uu/od/src/od.rs b/src/uu/od/src/od.rs index 769dae98e22..1c9548f50f8 100644 --- a/src/uu/od/src/od.rs +++ b/src/uu/od/src/od.rs @@ -43,6 +43,7 @@ use clap::{crate_version, parser::ValueSource, Arg, ArgMatches, Command}; use uucore::display::Quotable; use uucore::error::{UResult, USimpleError}; use uucore::parse_size::ParseSizeError; +use uucore::shortcut_value_parser::ShortcutValueParser; use uucore::{format_usage, help_about, help_section, help_usage, show_error, show_warning}; const PEEK_BUFFER_SIZE: usize = 4; // utf-8 can be 4 bytes @@ -287,7 +288,7 @@ pub fn uu_app() -> Command { Arg::new(options::ENDIAN) .long(options::ENDIAN) .help("byte order to use for multi-byte formats") - .value_parser(["big", "little"]) + .value_parser(ShortcutValueParser::new(["big", "little"])) .value_name("big|little"), ) .arg( diff --git a/tests/by-util/test_od.rs b/tests/by-util/test_od.rs index 78c4e1b0439..569d096c172 100644 --- a/tests/by-util/test_od.rs +++ b/tests/by-util/test_od.rs @@ -53,6 +53,20 @@ fn test_file() { .no_stderr() .stdout_is(unindent(ALPHA_OUT)); + new_ucmd!() + .arg("--endian=littl") // spell-checker:disable-line + .arg(file.as_os_str()) + .succeeds() + .no_stderr() + .stdout_is(unindent(ALPHA_OUT)); + + new_ucmd!() + .arg("--endian=l") + .arg(file.as_os_str()) + .succeeds() + .no_stderr() + .stdout_is(unindent(ALPHA_OUT)); + // Ensure that default format matches `-t o2`, and that `-t` does not absorb file argument new_ucmd!() .arg("--endian=little") @@ -463,6 +477,16 @@ fn test_big_endian() { .run_piped_stdin(&input[..]) .no_stderr() .success() + .stdout_is(&expected_output); + new_ucmd!() + .arg("--endian=b") + .arg("-F") + .arg("-f") + .arg("-X") + .arg("-x") + .run_piped_stdin(&input[..]) + .no_stderr() + .success() .stdout_is(expected_output); } From 70d84e168c1620c03a171429acfeeb48068dbc5f Mon Sep 17 00:00:00 2001 From: Ben Wiederhake Date: Mon, 1 Apr 2024 08:06:18 +0200 Subject: [PATCH 091/144] shred: accept shortcuts for stringly-enum arguments --- src/uu/shred/src/shred.rs | 5 +++-- tests/by-util/test_shred.rs | 41 +++++++++++++++++++++---------------- 2 files changed, 26 insertions(+), 20 deletions(-) diff --git a/src/uu/shred/src/shred.rs b/src/uu/shred/src/shred.rs index d023b62107a..763d6cfd4bf 100644 --- a/src/uu/shred/src/shred.rs +++ b/src/uu/shred/src/shred.rs @@ -17,6 +17,7 @@ use std::path::{Path, PathBuf}; use uucore::display::Quotable; use uucore::error::{FromIo, UResult, USimpleError, UUsageError}; use uucore::parse_size::parse_size_u64; +use uucore::shortcut_value_parser::ShortcutValueParser; use uucore::{format_usage, help_about, help_section, help_usage, show_error, show_if_err}; const ABOUT: &str = help_about!("shred.md"); @@ -315,11 +316,11 @@ pub fn uu_app() -> Command { Arg::new(options::REMOVE) .long(options::REMOVE) .value_name("HOW") - .value_parser([ + .value_parser(ShortcutValueParser::new([ options::remove::UNLINK, options::remove::WIPE, options::remove::WIPESYNC, - ]) + ])) .num_args(0..=1) .require_equals(true) .default_missing_value(options::remove::WIPESYNC) diff --git a/tests/by-util/test_shred.rs b/tests/by-util/test_shred.rs index 1ff847afcd8..82e421839ae 100644 --- a/tests/by-util/test_shred.rs +++ b/tests/by-util/test_shred.rs @@ -17,6 +17,11 @@ fn test_invalid_remove_arg() { new_ucmd!().arg("--remove=unknown").fails().code_is(1); } +#[test] +fn test_ambiguous_remove_arg() { + new_ucmd!().arg("--remove=wip").fails().code_is(1); +} + #[test] fn test_shred() { let (at, mut ucmd) = at_and_ucmd!(); @@ -49,15 +54,15 @@ fn test_shred_remove() { #[test] fn test_shred_remove_unlink() { - let (at, mut ucmd) = at_and_ucmd!(); - - let file = "test_shred_remove_unlink"; - at.touch(file); - - ucmd.arg("--remove=unlink").arg(file).succeeds(); - - // File was deleted - assert!(!at.file_exists(file)); + // spell-checker:disable-next-line + for argument in ["--remove=unlink", "--remove=unlin", "--remove=u"] { + let (at, mut ucmd) = at_and_ucmd!(); + let file = "test_shred_remove_unlink"; + at.touch(file); + ucmd.arg(argument).arg(file).succeeds(); + // File was deleted + assert!(!at.file_exists(file)); + } } #[test] @@ -75,15 +80,15 @@ fn test_shred_remove_wipe() { #[test] fn test_shred_remove_wipesync() { - let (at, mut ucmd) = at_and_ucmd!(); - - let file = "test_shred_remove_wipesync"; - at.touch(file); - - ucmd.arg("--remove=wipesync").arg(file).succeeds(); - - // File was deleted - assert!(!at.file_exists(file)); + // spell-checker:disable-next-line + for argument in ["--remove=wipesync", "--remove=wipesyn", "--remove=wipes"] { + let (at, mut ucmd) = at_and_ucmd!(); + let file = "test_shred_remove_wipesync"; + at.touch(file); + ucmd.arg(argument).arg(file).succeeds(); + // File was deleted + assert!(!at.file_exists(file)); + } } #[test] From 872ec050e4bcfe98c488d380b922461ab868069a Mon Sep 17 00:00:00 2001 From: Ben Wiederhake Date: Mon, 1 Apr 2024 08:06:18 +0200 Subject: [PATCH 092/144] sort: accept shortcuts for stringly-enum arguments --- src/uu/sort/src/sort.rs | 9 +++--- tests/by-util/test_sort.rs | 64 ++++++++++++++++++++++++++++++++++---- 2 files changed, 63 insertions(+), 10 deletions(-) diff --git a/src/uu/sort/src/sort.rs b/src/uu/sort/src/sort.rs index 07420d9c1d4..1bc413aaac2 100644 --- a/src/uu/sort/src/sort.rs +++ b/src/uu/sort/src/sort.rs @@ -43,6 +43,7 @@ use uucore::display::Quotable; use uucore::error::{set_exit_code, strip_errno, UError, UResult, USimpleError, UUsageError}; use uucore::line_ending::LineEnding; use uucore::parse_size::{ParseSizeError, Parser}; +use uucore::shortcut_value_parser::ShortcutValueParser; use uucore::version_cmp::version_cmp; use uucore::{format_usage, help_about, help_section, help_usage}; @@ -1297,14 +1298,14 @@ pub fn uu_app() -> Command { .arg( Arg::new(options::modes::SORT) .long(options::modes::SORT) - .value_parser([ + .value_parser(ShortcutValueParser::new([ "general-numeric", "human-numeric", "month", "numeric", "version", "random", - ]) + ])) .conflicts_with_all(options::modes::ALL_SORT_MODES), ) .arg(make_sort_mode_arg( @@ -1363,11 +1364,11 @@ pub fn uu_app() -> Command { .long(options::check::CHECK) .require_equals(true) .num_args(0..) - .value_parser([ + .value_parser(ShortcutValueParser::new([ options::check::SILENT, options::check::QUIET, options::check::DIAGNOSE_FIRST, - ]) + ])) .conflicts_with(options::OUTPUT) .help("check for sorted input; do not sort"), ) diff --git a/tests/by-util/test_sort.rs b/tests/by-util/test_sort.rs index 2e114348b46..83c925af917 100644 --- a/tests/by-util/test_sort.rs +++ b/tests/by-util/test_sort.rs @@ -126,7 +126,16 @@ fn test_ext_sort_zero_terminated() { #[test] fn test_months_whitespace() { - test_helper("months-whitespace", &["-M", "--month-sort", "--sort=month"]); + test_helper( + "months-whitespace", + &[ + "-M", + "--month-sort", + "--sort=month", + "--sort=mont", // spell-checker:disable-line + "--sort=m", + ], + ); } #[test] @@ -141,6 +150,16 @@ fn test_version_sort_unstable() { .pipe_in("0.1\n0.02\n0.2\n0.002\n0.3\n") .succeeds() .stdout_is("0.1\n0.002\n0.02\n0.2\n0.3\n"); + new_ucmd!() + .arg("--sort=versio") // spell-checker:disable-line + .pipe_in("0.1\n0.02\n0.2\n0.002\n0.3\n") + .succeeds() + .stdout_is("0.1\n0.002\n0.02\n0.2\n0.3\n"); + new_ucmd!() + .arg("--sort=v") + .pipe_in("0.1\n0.02\n0.2\n0.002\n0.3\n") + .succeeds() + .stdout_is("0.1\n0.002\n0.02\n0.2\n0.3\n"); } #[test] @@ -157,7 +176,14 @@ fn test_version_sort_stable() { fn test_human_numeric_whitespace() { test_helper( "human-numeric-whitespace", - &["-h", "--human-numeric-sort", "--sort=human-numeric"], + &[ + "-h", + "--human-numeric-sort", + "--sort=human-numeric", + "--sort=human-numeri", // spell-checker:disable-line + "--sort=human", + "--sort=h", + ], ); } @@ -177,7 +203,14 @@ fn test_ext_sort_as64_bailout() { fn test_multiple_decimals_general() { test_helper( "multiple_decimals_general", - &["-g", "--general-numeric-sort", "--sort=general-numeric"], + &[ + "-g", + "--general-numeric-sort", + "--sort=general-numeric", + "--sort=general-numeri", // spell-checker:disable-line + "--sort=general", + "--sort=g", + ], ); } @@ -185,7 +218,7 @@ fn test_multiple_decimals_general() { fn test_multiple_decimals_numeric() { test_helper( "multiple_decimals_numeric", - &["-n", "--numeric-sort", "--sort=numeric"], + &["-n", "--numeric-sort", "--sort=numeric", "--sort=n"], ); } @@ -784,7 +817,13 @@ fn test_pipe() { #[test] fn test_check() { - for diagnose_arg in ["-c", "--check", "--check=diagnose-first"] { + for diagnose_arg in [ + "-c", + "--check", + "--check=diagnose-first", + "--check=diagnose", + "--check=d", + ] { new_ucmd!() .arg(diagnose_arg) .arg("check_fail.txt") @@ -802,12 +841,25 @@ fn test_check() { #[test] fn test_check_silent() { - for silent_arg in ["-C", "--check=silent", "--check=quiet"] { + for silent_arg in [ + "-C", + "--check=silent", + "--check=quiet", + "--check=silen", // spell-checker:disable-line + "--check=quie", // spell-checker:disable-line + "--check=s", + "--check=q", + ] { new_ucmd!() .arg(silent_arg) .arg("check_fail.txt") .fails() .stdout_is(""); + new_ucmd!() + .arg(silent_arg) + .arg("empty.txt") + .succeeds() + .no_output(); } } From 88a2ea4f3b48de859fb1940c673dac8a2e278376 Mon Sep 17 00:00:00 2001 From: Ben Wiederhake Date: Mon, 1 Apr 2024 08:06:18 +0200 Subject: [PATCH 093/144] tail: accept shortcuts for stringly-enum arguments --- src/uu/tail/src/args.rs | 3 ++- tests/by-util/test_tail.rs | 55 ++++++++++++++++++++------------------ 2 files changed, 31 insertions(+), 27 deletions(-) diff --git a/src/uu/tail/src/args.rs b/src/uu/tail/src/args.rs index 6ae5e68bddb..5cadac60869 100644 --- a/src/uu/tail/src/args.rs +++ b/src/uu/tail/src/args.rs @@ -16,6 +16,7 @@ use std::io::IsTerminal; use std::time::Duration; use uucore::error::{UResult, USimpleError, UUsageError}; use uucore::parse_size::{parse_size_u64, ParseSizeError}; +use uucore::shortcut_value_parser::ShortcutValueParser; use uucore::{format_usage, help_about, help_usage, show_warning}; const ABOUT: &str = help_about!("tail.md"); @@ -494,7 +495,7 @@ pub fn uu_app() -> Command { .default_missing_value("descriptor") .num_args(0..=1) .require_equals(true) - .value_parser(["descriptor", "name"]) + .value_parser(ShortcutValueParser::new(["descriptor", "name"])) .overrides_with(options::FOLLOW) .help("Print the file as it grows"), ) diff --git a/tests/by-util/test_tail.rs b/tests/by-util/test_tail.rs index cc5a76c31a1..f11eac190d2 100644 --- a/tests/by-util/test_tail.rs +++ b/tests/by-util/test_tail.rs @@ -533,37 +533,40 @@ fn test_follow_multiple() { #[test] #[cfg(not(target_os = "windows"))] // FIXME: test times out fn test_follow_name_multiple() { - let (at, mut ucmd) = at_and_ucmd!(); - let mut child = ucmd - .arg("--follow=name") - .arg(FOOBAR_TXT) - .arg(FOOBAR_2_TXT) - .run_no_wait(); + // spell-checker:disable-next-line + for argument in ["--follow=name", "--follo=nam", "--f=n"] { + let (at, mut ucmd) = at_and_ucmd!(); + let mut child = ucmd + .arg(argument) + .arg(FOOBAR_TXT) + .arg(FOOBAR_2_TXT) + .run_no_wait(); - child - .make_assertion_with_delay(500) - .is_alive() - .with_current_output() - .stdout_only_fixture("foobar_follow_multiple.expected"); + child + .make_assertion_with_delay(500) + .is_alive() + .with_current_output() + .stdout_only_fixture("foobar_follow_multiple.expected"); - let first_append = "trois\n"; - at.append(FOOBAR_2_TXT, first_append); + let first_append = "trois\n"; + at.append(FOOBAR_2_TXT, first_append); - child - .make_assertion_with_delay(DEFAULT_SLEEP_INTERVAL_MILLIS) - .with_current_output() - .stdout_only(first_append); + child + .make_assertion_with_delay(DEFAULT_SLEEP_INTERVAL_MILLIS) + .with_current_output() + .stdout_only(first_append); - let second_append = "twenty\nthirty\n"; - at.append(FOOBAR_TXT, second_append); + let second_append = "twenty\nthirty\n"; + at.append(FOOBAR_TXT, second_append); - child - .make_assertion_with_delay(DEFAULT_SLEEP_INTERVAL_MILLIS) - .with_current_output() - .stdout_only_fixture("foobar_follow_multiple_appended.expected"); + child + .make_assertion_with_delay(DEFAULT_SLEEP_INTERVAL_MILLIS) + .with_current_output() + .stdout_only_fixture("foobar_follow_multiple_appended.expected"); - child.make_assertion().is_alive(); - child.kill(); + child.make_assertion().is_alive(); + child.kill(); + } } #[test] @@ -844,7 +847,7 @@ fn test_follow_missing() { // Ensure that --follow=name does not imply --retry. // Ensure that --follow={descriptor,name} (without --retry) does *not wait* for the // file to appear. - for follow_mode in &["--follow=descriptor", "--follow=name"] { + for follow_mode in &["--follow=descriptor", "--follow=name", "--fo=d", "--fo=n"] { new_ucmd!() .arg(follow_mode) .arg("missing") From 25245bde650c03d290883e611ab2495a5863ff1f Mon Sep 17 00:00:00 2001 From: Ben Wiederhake Date: Mon, 1 Apr 2024 08:06:18 +0200 Subject: [PATCH 094/144] tee: accept shortcuts for stringly-enum arguments --- src/uu/tee/src/tee.rs | 5 +++-- tests/by-util/test_tee.rs | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 37 insertions(+), 2 deletions(-) diff --git a/src/uu/tee/src/tee.rs b/src/uu/tee/src/tee.rs index b1443dbb95b..2cf693af675 100644 --- a/src/uu/tee/src/tee.rs +++ b/src/uu/tee/src/tee.rs @@ -9,6 +9,7 @@ use std::io::{copy, stdin, stdout, Error, ErrorKind, Read, Result, Write}; use std::path::PathBuf; use uucore::display::Quotable; use uucore::error::UResult; +use uucore::shortcut_value_parser::ShortcutValueParser; use uucore::{format_usage, help_about, help_section, help_usage, show_error}; // spell-checker:ignore nopipe @@ -119,7 +120,7 @@ pub fn uu_app() -> Command { .long(options::OUTPUT_ERROR) .require_equals(true) .num_args(0..=1) - .value_parser([ + .value_parser(ShortcutValueParser::new([ PossibleValue::new("warn") .help("produce warnings for errors writing to any output"), PossibleValue::new("warn-nopipe") @@ -127,7 +128,7 @@ pub fn uu_app() -> Command { PossibleValue::new("exit").help("exit on write errors to any output"), PossibleValue::new("exit-nopipe") .help("exit on write errors to any output that are not pipe errors (equivalent to exit on non-unix platforms)"), - ]) + ])) .help("set write error behavior") .conflicts_with(options::IGNORE_PIPE_ERRORS), ) diff --git a/tests/by-util/test_tee.rs b/tests/by-util/test_tee.rs index 9ff4ea7dcf8..8b94fe77f00 100644 --- a/tests/by-util/test_tee.rs +++ b/tests/by-util/test_tee.rs @@ -311,6 +311,23 @@ mod linux_only { expect_correct(file_out_a, &at, content.as_str()); } + #[test] + fn test_pipe_error_warn_nopipe_3_shortcut() { + let (at, mut ucmd) = at_and_ucmd!(); + + let file_out_a = "tee_file_out_a"; + + let proc = ucmd + .arg("--output-error=warn-") + .arg(file_out_a) + .set_stdout(make_broken_pipe()); + + let (content, output) = run_tee(proc); + + expect_success(&output); + expect_correct(file_out_a, &at, content.as_str()); + } + #[test] fn test_pipe_error_warn() { let (at, mut ucmd) = at_and_ucmd!(); @@ -362,6 +379,23 @@ mod linux_only { expect_correct(file_out_a, &at, content.as_str()); } + #[test] + fn test_pipe_error_exit_nopipe_shortcut() { + let (at, mut ucmd) = at_and_ucmd!(); + + let file_out_a = "tee_file_out_a"; + + let proc = ucmd + .arg("--output-error=exit-nop") + .arg(file_out_a) + .set_stdout(make_broken_pipe()); + + let (content, output) = run_tee(proc); + + expect_success(&output); + expect_correct(file_out_a, &at, content.as_str()); + } + #[test] fn test_space_error_default() { let (at, mut ucmd) = at_and_ucmd!(); From 3285f95eb3dec517831ce3c43ac53e48b0b69ef7 Mon Sep 17 00:00:00 2001 From: Ben Wiederhake Date: Mon, 1 Apr 2024 08:06:18 +0200 Subject: [PATCH 095/144] touch: accept shortcuts for stringly-enum arguments --- src/uu/touch/src/touch.rs | 8 ++++++-- tests/by-util/test_touch.rs | 11 +++++++++-- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/src/uu/touch/src/touch.rs b/src/uu/touch/src/touch.rs index fe1783b214a..4192a7a8380 100644 --- a/src/uu/touch/src/touch.rs +++ b/src/uu/touch/src/touch.rs @@ -10,7 +10,7 @@ use chrono::{ DateTime, Datelike, Duration, Local, LocalResult, NaiveDate, NaiveDateTime, NaiveTime, TimeZone, Timelike, }; -use clap::builder::ValueParser; +use clap::builder::{PossibleValue, ValueParser}; use clap::{crate_version, Arg, ArgAction, ArgGroup, ArgMatches, Command}; use filetime::{set_file_times, set_symlink_file_times, FileTime}; use std::ffi::OsString; @@ -18,6 +18,7 @@ use std::fs::{self, File}; use std::path::{Path, PathBuf}; use uucore::display::Quotable; use uucore::error::{FromIo, UResult, USimpleError}; +use uucore::shortcut_value_parser::ShortcutValueParser; use uucore::{format_usage, help_about, help_usage, show}; const ABOUT: &str = help_about!("touch.md"); @@ -216,7 +217,10 @@ pub fn uu_app() -> Command { equivalent to -m", ) .value_name("WORD") - .value_parser(["access", "atime", "use", "modify", "mtime"]), + .value_parser(ShortcutValueParser::new([ + PossibleValue::new("atime").alias("access").alias("use"), + PossibleValue::new("mtime").alias("modify"), + ])), ) .arg( Arg::new(ARG_FILES) diff --git a/tests/by-util/test_touch.rs b/tests/by-util/test_touch.rs index ca2a8b7d85b..44a198452e3 100644 --- a/tests/by-util/test_touch.rs +++ b/tests/by-util/test_touch.rs @@ -192,7 +192,14 @@ fn test_touch_set_cymdhms_time() { #[test] fn test_touch_set_only_atime() { - let atime_args = ["-a", "--time=access", "--time=atime", "--time=use"]; + let atime_args = [ + "-a", + "--time=access", + "--time=atime", + "--time=atim", // spell-checker:disable-line + "--time=a", + "--time=use", + ]; let file = "test_touch_set_only_atime"; for atime_arg in atime_args { @@ -293,7 +300,7 @@ fn test_touch_set_both_time_and_date() { #[test] fn test_touch_set_only_mtime() { - let mtime_args = ["-m", "--time=modify", "--time=mtime"]; + let mtime_args = ["-m", "--time=modify", "--time=mtime", "--time=m"]; let file = "test_touch_set_only_mtime"; for mtime_arg in mtime_args { From a699bfd1fb186b48962436f4b46e09829f28de53 Mon Sep 17 00:00:00 2001 From: Ben Wiederhake Date: Mon, 1 Apr 2024 08:06:18 +0200 Subject: [PATCH 096/144] uniq: accept shortcuts for stringly-enum arguments --- src/uu/uniq/src/uniq.rs | 9 +++--- tests/by-util/test_uniq.rs | 60 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 65 insertions(+), 4 deletions(-) diff --git a/src/uu/uniq/src/uniq.rs b/src/uu/uniq/src/uniq.rs index 2c4f9b78114..4084a7b3f22 100644 --- a/src/uu/uniq/src/uniq.rs +++ b/src/uu/uniq/src/uniq.rs @@ -14,6 +14,7 @@ use std::num::IntErrorKind; use uucore::display::Quotable; use uucore::error::{FromIo, UError, UResult, USimpleError}; use uucore::posix::{posix_version, OBSOLETE}; +use uucore::shortcut_value_parser::ShortcutValueParser; use uucore::{format_usage, help_about, help_section, help_usage}; const ABOUT: &str = help_about!("uniq.md"); @@ -609,11 +610,11 @@ pub fn uu_app() -> Command { Arg::new(options::ALL_REPEATED) .short('D') .long(options::ALL_REPEATED) - .value_parser([ + .value_parser(ShortcutValueParser::new([ "none", "prepend", "separate" - ]) + ])) .help("print all duplicate lines. Delimiting is done with blank lines. [default: none]") .value_name("delimit-method") .num_args(0..=1) @@ -623,12 +624,12 @@ pub fn uu_app() -> Command { .arg( Arg::new(options::GROUP) .long(options::GROUP) - .value_parser([ + .value_parser(ShortcutValueParser::new([ "separate", "prepend", "append", "both", - ]) + ])) .help("show all items, separating groups with an empty line. [default: separate]") .value_name("group-method") .num_args(0..=1) diff --git a/tests/by-util/test_uniq.rs b/tests/by-util/test_uniq.rs index 911f1449088..8585ebd835f 100644 --- a/tests/by-util/test_uniq.rs +++ b/tests/by-util/test_uniq.rs @@ -145,6 +145,21 @@ fn test_stdin_all_repeated() { .pipe_in_fixture(INPUT) .run() .stdout_is_fixture("sorted-all-repeated.expected"); + new_ucmd!() + .args(&["--all-repeated=none"]) + .pipe_in_fixture(INPUT) + .run() + .stdout_is_fixture("sorted-all-repeated.expected"); + new_ucmd!() + .args(&["--all-repeated=non"]) + .pipe_in_fixture(INPUT) + .run() + .stdout_is_fixture("sorted-all-repeated.expected"); + new_ucmd!() + .args(&["--all-repeated=n"]) + .pipe_in_fixture(INPUT) + .run() + .stdout_is_fixture("sorted-all-repeated.expected"); } #[test] @@ -167,6 +182,16 @@ fn test_stdin_all_repeated_separate() { .pipe_in_fixture(INPUT) .run() .stdout_is_fixture("sorted-all-repeated-separate.expected"); + new_ucmd!() + .args(&["--all-repeated=separat"]) // spell-checker:disable-line + .pipe_in_fixture(INPUT) + .run() + .stdout_is_fixture("sorted-all-repeated-separate.expected"); + new_ucmd!() + .args(&["--all-repeated=s"]) + .pipe_in_fixture(INPUT) + .run() + .stdout_is_fixture("sorted-all-repeated-separate.expected"); } #[test] @@ -176,6 +201,16 @@ fn test_stdin_all_repeated_prepend() { .pipe_in_fixture(INPUT) .run() .stdout_is_fixture("sorted-all-repeated-prepend.expected"); + new_ucmd!() + .args(&["--all-repeated=prepen"]) // spell-checker:disable-line + .pipe_in_fixture(INPUT) + .run() + .stdout_is_fixture("sorted-all-repeated-prepend.expected"); + new_ucmd!() + .args(&["--all-repeated=p"]) + .pipe_in_fixture(INPUT) + .run() + .stdout_is_fixture("sorted-all-repeated-prepend.expected"); } #[test] @@ -253,6 +288,11 @@ fn test_group_prepend() { .pipe_in_fixture(INPUT) .run() .stdout_is_fixture("group-prepend.expected"); + new_ucmd!() + .args(&["--group=p"]) + .pipe_in_fixture(INPUT) + .run() + .stdout_is_fixture("group-prepend.expected"); } #[test] @@ -262,6 +302,11 @@ fn test_group_append() { .pipe_in_fixture(INPUT) .run() .stdout_is_fixture("group-append.expected"); + new_ucmd!() + .args(&["--group=a"]) + .pipe_in_fixture(INPUT) + .run() + .stdout_is_fixture("group-append.expected"); } #[test] @@ -271,6 +316,16 @@ fn test_group_both() { .pipe_in_fixture(INPUT) .run() .stdout_is_fixture("group-both.expected"); + new_ucmd!() + .args(&["--group=bot"]) + .pipe_in_fixture(INPUT) + .run() + .stdout_is_fixture("group-both.expected"); + new_ucmd!() + .args(&["--group=b"]) + .pipe_in_fixture(INPUT) + .run() + .stdout_is_fixture("group-both.expected"); } #[test] @@ -280,6 +335,11 @@ fn test_group_separate() { .pipe_in_fixture(INPUT) .run() .stdout_is_fixture("group.expected"); + new_ucmd!() + .args(&["--group=s"]) + .pipe_in_fixture(INPUT) + .run() + .stdout_is_fixture("group.expected"); } #[test] From 91679fc747164ef065565ddfbb26efe19c236a3d Mon Sep 17 00:00:00 2001 From: Ben Wiederhake Date: Mon, 1 Apr 2024 08:06:18 +0200 Subject: [PATCH 097/144] wc: accept shortcuts for stringly-enum arguments --- src/uu/wc/src/wc.rs | 5 ++++- tests/by-util/test_wc.rs | 22 ++++++++++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/src/uu/wc/src/wc.rs b/src/uu/wc/src/wc.rs index 06f9be9168e..33b70ee62f5 100644 --- a/src/uu/wc/src/wc.rs +++ b/src/uu/wc/src/wc.rs @@ -29,6 +29,7 @@ use uucore::{ error::{FromIo, UError, UResult}, format_usage, help_about, help_usage, quoting_style::{escape_name, QuotingStyle}, + shortcut_value_parser::ShortcutValueParser, show, }; @@ -439,7 +440,9 @@ pub fn uu_app() -> Command { .arg( Arg::new(options::TOTAL) .long(options::TOTAL) - .value_parser(["auto", "always", "only", "never"]) + .value_parser(ShortcutValueParser::new([ + "auto", "always", "only", "never", + ])) .value_name("WHEN") .hide_possible_values(true) .help(concat!( diff --git a/tests/by-util/test_wc.rs b/tests/by-util/test_wc.rs index 0202ba4e889..d5d955c9b4e 100644 --- a/tests/by-util/test_wc.rs +++ b/tests/by-util/test_wc.rs @@ -531,6 +531,10 @@ fn test_total_auto() { .args(&["lorem_ipsum.txt", "--total=auto"]) .run() .stdout_is(" 13 109 772 lorem_ipsum.txt\n"); + new_ucmd!() + .args(&["lorem_ipsum.txt", "--tot=au"]) + .run() + .stdout_is(" 13 109 772 lorem_ipsum.txt\n"); new_ucmd!() .args(&["lorem_ipsum.txt", "moby_dick.txt", "--total=auto"]) @@ -551,6 +555,13 @@ fn test_total_always() { " 13 109 772 lorem_ipsum.txt\n", " 13 109 772 total\n", )); + new_ucmd!() + .args(&["lorem_ipsum.txt", "--total=al"]) + .run() + .stdout_is(concat!( + " 13 109 772 lorem_ipsum.txt\n", + " 13 109 772 total\n", + )); new_ucmd!() .args(&["lorem_ipsum.txt", "moby_dick.txt", "--total=always"]) @@ -576,6 +587,13 @@ fn test_total_never() { " 13 109 772 lorem_ipsum.txt\n", " 18 204 1115 moby_dick.txt\n", )); + new_ucmd!() + .args(&["lorem_ipsum.txt", "moby_dick.txt", "--total=n"]) + .run() + .stdout_is(concat!( + " 13 109 772 lorem_ipsum.txt\n", + " 18 204 1115 moby_dick.txt\n", + )); } #[test] @@ -589,6 +607,10 @@ fn test_total_only() { .args(&["lorem_ipsum.txt", "moby_dick.txt", "--total=only"]) .run() .stdout_is("31 313 1887\n"); + new_ucmd!() + .args(&["lorem_ipsum.txt", "moby_dick.txt", "--t=o"]) + .run() + .stdout_is("31 313 1887\n"); } #[test] From f5f8cf08e0836524ebdafedd9ad9b49a53b7c91f Mon Sep 17 00:00:00 2001 From: Ben Wiederhake Date: Sun, 31 Mar 2024 21:08:01 +0200 Subject: [PATCH 098/144] hostid: return correct exit code on error --- src/uu/hostid/src/hostid.rs | 2 +- tests/by-util/test_hostid.rs | 9 +++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/src/uu/hostid/src/hostid.rs b/src/uu/hostid/src/hostid.rs index a5c18d07563..157cfc420b4 100644 --- a/src/uu/hostid/src/hostid.rs +++ b/src/uu/hostid/src/hostid.rs @@ -19,7 +19,7 @@ extern "C" { #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - uu_app().get_matches_from(args); + uu_app().try_get_matches_from(args)?; hostid(); Ok(()) } diff --git a/tests/by-util/test_hostid.rs b/tests/by-util/test_hostid.rs index e9336116bca..7525f5e0882 100644 --- a/tests/by-util/test_hostid.rs +++ b/tests/by-util/test_hostid.rs @@ -10,3 +10,12 @@ fn test_normal() { let re = Regex::new(r"^[0-9a-f]{8}").unwrap(); new_ucmd!().succeeds().stdout_matches(&re); } + +#[test] +fn test_invalid_flag() { + new_ucmd!() + .arg("--invalid-argument") + .fails() + .no_stdout() + .code_is(1); +} From 95273417146574fdbe10413f12128c396131445c Mon Sep 17 00:00:00 2001 From: Ben Wiederhake Date: Sun, 31 Mar 2024 21:19:24 +0200 Subject: [PATCH 099/144] pr: return correct exit code on error --- src/uu/pr/src/pr.rs | 8 +------- tests/by-util/test_pr.rs | 9 +++++++++ 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/src/uu/pr/src/pr.rs b/src/uu/pr/src/pr.rs index 010183d3192..aba71c341c7 100644 --- a/src/uu/pr/src/pr.rs +++ b/src/uu/pr/src/pr.rs @@ -386,13 +386,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { let opt_args = recreate_arguments(&args); let mut command = uu_app(); - let matches = match command.try_get_matches_from_mut(opt_args) { - Ok(m) => m, - Err(e) => { - e.print()?; - return Ok(()); - } - }; + let matches = command.try_get_matches_from_mut(opt_args)?; let mut files = matches .get_many::(options::FILES) diff --git a/tests/by-util/test_pr.rs b/tests/by-util/test_pr.rs index 823f0718f4b..c886b6452e2 100644 --- a/tests/by-util/test_pr.rs +++ b/tests/by-util/test_pr.rs @@ -43,6 +43,15 @@ fn valid_last_modified_template_vars(from: DateTime) -> Vec Date: Sun, 31 Mar 2024 23:08:51 +0200 Subject: [PATCH 100/144] tee: ensure that -h and --help are identical --- src/uu/tee/src/tee.rs | 10 ++++++++++ tests/by-util/test_tee.rs | 16 ++++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/src/uu/tee/src/tee.rs b/src/uu/tee/src/tee.rs index b1443dbb95b..69d66fd608b 100644 --- a/src/uu/tee/src/tee.rs +++ b/src/uu/tee/src/tee.rs @@ -89,6 +89,16 @@ pub fn uu_app() -> Command { .override_usage(format_usage(USAGE)) .after_help(AFTER_HELP) .infer_long_args(true) + // Since we use value-specific help texts for "--output-error", clap's "short help" and "long help" differ. + // However, this is something that the GNU tests explicitly test for, so we *always* show the long help instead. + .disable_help_flag(true) + .arg( + Arg::new("--help") + .short('h') + .long("help") + .help("Print help") + .action(ArgAction::HelpLong) + ) .arg( Arg::new(options::APPEND) .long(options::APPEND) diff --git a/tests/by-util/test_tee.rs b/tests/by-util/test_tee.rs index 9ff4ea7dcf8..6058be07696 100644 --- a/tests/by-util/test_tee.rs +++ b/tests/by-util/test_tee.rs @@ -18,6 +18,22 @@ fn test_invalid_arg() { new_ucmd!().arg("--definitely-invalid").fails().code_is(1); } +#[test] +fn test_short_help_is_long_help() { + // I can't believe that this test is necessary. + let help_short = new_ucmd!() + .arg("-h") + .succeeds() + .no_stderr() + .stdout_str() + .to_owned(); + new_ucmd!() + .arg("--help") + .succeeds() + .no_stderr() + .stdout_is(help_short); +} + #[test] fn test_tee_processing_multiple_operands() { // POSIX says: "Processing of at least 13 file operands shall be supported." From 120f0312e7c5312878b751f812e4b5a0b39c36ae Mon Sep 17 00:00:00 2001 From: Ben Wiederhake Date: Sun, 31 Mar 2024 23:10:54 +0200 Subject: [PATCH 101/144] util: undo custom GNU exit codes, reduce number of special cases --- util/build-gnu.sh | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/util/build-gnu.sh b/util/build-gnu.sh index 6a4b09f89d8..8bdadddcf94 100755 --- a/util/build-gnu.sh +++ b/util/build-gnu.sh @@ -279,14 +279,11 @@ sed -i "s|\$PACKAGE_VERSION|[0-9]*|g" tests/rm/fail-2eperm.sh tests/mv/sticky-to # with the option -/ is used, clap is returning a better error than GNU's. Adjust the GNU test sed -i -e "s~ grep \" '\*/'\*\" err || framework_failure_~ grep \" '*-/'*\" err || framework_failure_~" tests/misc/usage_vs_getopt.sh sed -i -e "s~ sed -n \"1s/'\\\/'/'OPT'/p\" < err >> pat || framework_failure_~ sed -n \"1s/'-\\\/'/'OPT'/p\" < err >> pat || framework_failure_~" tests/misc/usage_vs_getopt.sh -# Ignore some binaries (not built) -# And change the default error code to 2 -# see issue #3331 (clap limitation). -# Upstream returns 1 for most of the program. We do for cp, truncate & pr -# So, keep it as it -sed -i -e "s/rcexp=1$/rcexp=2\n case \"\$prg\" in chcon|runcon) return;; esac/" -e "s/rcexp=125 ;;/rcexp=2 ;;\ncp|truncate|pr) rcexp=1;;/" tests/misc/usage_vs_getopt.sh +# Ignore runcon, it needs some extra attention +# For all other tools, we want drop-in compatibility, and that includes the exit code. +sed -i -e "s/rcexp=1$/rcexp=1\n case \"\$prg\" in runcon|stdbuf) return;; esac/" tests/misc/usage_vs_getopt.sh # GNU has option=[SUFFIX], clap is -sed -i -e "s/cat opts/sed -i -e \"s| <.\*>$||g\" opts/" tests/misc/usage_vs_getopt.sh +sed -i -e "s/cat opts/sed -i -e \"s| <.\*$||g\" opts/" tests/misc/usage_vs_getopt.sh # for some reasons, some stuff are duplicated, strip that sed -i -e "s/provoked error./provoked error\ncat pat |sort -u > pat/" tests/misc/usage_vs_getopt.sh From b977d61f6727965b3b6fdbabc681922c65e0f2ca Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Sun, 14 Apr 2024 20:05:00 +0200 Subject: [PATCH 102/144] hashsum: implement the ignore-missing option Tested by gnu/tests/cksum/md5sum.pl --- src/uu/hashsum/src/hashsum.rs | 17 ++++++++++++++++- tests/by-util/test_hashsum.rs | 29 ++++++++++++++++++++++++++++- 2 files changed, 44 insertions(+), 2 deletions(-) diff --git a/src/uu/hashsum/src/hashsum.rs b/src/uu/hashsum/src/hashsum.rs index 8f57522d625..4b049e8cf7e 100644 --- a/src/uu/hashsum/src/hashsum.rs +++ b/src/uu/hashsum/src/hashsum.rs @@ -21,7 +21,7 @@ use std::iter; use std::num::ParseIntError; use std::path::Path; use uucore::error::USimpleError; -use uucore::error::{FromIo, UError, UResult}; +use uucore::error::{set_exit_code, FromIo, UError, UResult}; use uucore::sum::{ Blake2b, Blake3, Digest, DigestWriter, Md5, Sha1, Sha224, Sha256, Sha384, Sha3_224, Sha3_256, Sha3_384, Sha3_512, Sha512, Shake128, Shake256, @@ -46,6 +46,7 @@ struct Options { warn: bool, output_bits: usize, zero: bool, + ignore_missing: bool, } /// Creates a Blake2b hasher instance based on the specified length argument. @@ -345,6 +346,7 @@ pub fn uumain(mut args: impl uucore::Args) -> UResult<()> { let strict = matches.get_flag("strict"); let warn = matches.get_flag("warn") && !status; let zero = matches.get_flag("zero"); + let ignore_missing = matches.get_flag("ignore-missing"); let opts = Options { algoname: name, @@ -359,6 +361,7 @@ pub fn uumain(mut args: impl uucore::Args) -> UResult<()> { strict, warn, zero, + ignore_missing, }; match matches.get_many::("FILE") { @@ -431,6 +434,12 @@ pub fn uu_app_common() -> Command { .help("exit non-zero for improperly formatted checksum lines") .action(ArgAction::SetTrue), ) + .arg( + Arg::new("ignore-missing") + .long("ignore-missing") + .help("don't fail or report status for missing files") + .action(ArgAction::SetTrue), + ) .arg( Arg::new("warn") .short('w') @@ -705,6 +714,11 @@ where let (ck_filename_unescaped, prefix) = unescape_filename(&ck_filename); let f = match File::open(ck_filename_unescaped) { Err(_) => { + if options.ignore_missing { + // No need to show an error + continue; + } + failed_open_file += 1; println!( "{}: {}: No such file or directory", @@ -712,6 +726,7 @@ where ck_filename ); println!("{ck_filename}: FAILED open or read"); + set_exit_code(1); continue; } Ok(file) => file, diff --git a/tests/by-util/test_hashsum.rs b/tests/by-util/test_hashsum.rs index 22a028a320a..4e590902dd6 100644 --- a/tests/by-util/test_hashsum.rs +++ b/tests/by-util/test_hashsum.rs @@ -129,6 +129,33 @@ fn test_check_sha1() { .stderr_is(""); } +#[test] +fn test_check_md5_ignore_missing() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + + at.write("testf", "foobar\n"); + at.write( + "testf.sha1", + "14758f1afd44c09b7992073ccf00b43d testf\n14758f1afd44c09b7992073ccf00b43d testf2\n", + ); + scene + .ccmd("md5sum") + .arg("-c") + .arg(at.subdir.join("testf.sha1")) + .fails() + .stdout_contains("testf2: FAILED open or read"); + + scene + .ccmd("md5sum") + .arg("-c") + .arg("--ignore-missing") + .arg(at.subdir.join("testf.sha1")) + .succeeds() + .stdout_is("testf: OK\n") + .stderr_is(""); +} + #[test] fn test_check_b2sum_length_option_0() { let scene = TestScenario::new(util_name!()); @@ -208,7 +235,7 @@ fn test_check_file_not_found_warning() { .ccmd("sha1sum") .arg("-c") .arg(at.subdir.join("testf.sha1")) - .succeeds() + .fails() .stdout_is("sha1sum: testf: No such file or directory\ntestf: FAILED open or read\n") .stderr_is("sha1sum: warning: 1 listed file could not be read\n"); } From ffab3a12e772a35ee27c555740fb50a1872d25c5 Mon Sep 17 00:00:00 2001 From: Lucas Larson Date: Sun, 14 Apr 2024 15:30:17 -0400 Subject: [PATCH 103/144] =?UTF-8?q?fix:=20swap=20`cp`=E2=80=99s=20short=20?= =?UTF-8?q?`-r`=20and=20alias=20`-R`=20recursive=20options?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit the only implementable portion of #6223 is “to switch `-R` and `-r` to `-r` being the alias because `-R` is the POSIX flag” https://github.com/uutils/coreutils/issues/6223#issuecomment-2054097049 Signed-off-by: Lucas Larson --- src/uu/cp/src/cp.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/uu/cp/src/cp.rs b/src/uu/cp/src/cp.rs index d45f994180d..9806c125bb4 100644 --- a/src/uu/cp/src/cp.rs +++ b/src/uu/cp/src/cp.rs @@ -475,8 +475,8 @@ pub fn uu_app() -> Command { ) .arg( Arg::new(options::RECURSIVE) - .short('r') - .visible_short_alias('R') + .short('R') + .visible_short_alias('r') .long(options::RECURSIVE) // --archive sets this option .help("copy directories recursively") From 524be6e4ae9f864f07b4662b17114d3cb7bdfe00 Mon Sep 17 00:00:00 2001 From: Haisham <47662901+m-haisham@users.noreply.github.com> Date: Sun, 14 Apr 2024 22:46:00 +0500 Subject: [PATCH 104/144] kill: accept all casings for signal names in --list --- src/uu/kill/src/kill.rs | 4 +++- tests/by-util/test_kill.rs | 19 +++++++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/src/uu/kill/src/kill.rs b/src/uu/kill/src/kill.rs index 9ea51694dff..110e8a7a683 100644 --- a/src/uu/kill/src/kill.rs +++ b/src/uu/kill/src/kill.rs @@ -154,7 +154,9 @@ fn table() { fn print_signal(signal_name_or_value: &str) -> UResult<()> { for (value, &signal) in ALL_SIGNALS.iter().enumerate() { - if signal == signal_name_or_value || (format!("SIG{signal}")) == signal_name_or_value { + if signal.eq_ignore_ascii_case(signal_name_or_value) + || format!("SIG{signal}").eq_ignore_ascii_case(signal_name_or_value) + { println!("{value}"); return Ok(()); } else if signal_name_or_value == value.to_string() { diff --git a/tests/by-util/test_kill.rs b/tests/by-util/test_kill.rs index 5393d163adb..73b98f218f5 100644 --- a/tests/by-util/test_kill.rs +++ b/tests/by-util/test_kill.rs @@ -116,6 +116,25 @@ fn test_kill_list_one_signal_from_name() { .stdout_matches(&Regex::new("\\b9\\b").unwrap()); } +#[test] +fn test_kill_list_one_signal_ignore_case() { + // Use SIGKILL because it is 9 on all unixes. + new_ucmd!() + .arg("-l") + .arg("KiLl") + .succeeds() + .stdout_matches(&Regex::new("\\b9\\b").unwrap()); +} + +#[test] +fn test_kill_list_unknown_must_match_input_case() { + new_ucmd!() + .arg("-l") + .arg("IaMnOtAsIgNaL") // spell-checker:disable-line + .fails() + .stderr_contains("IaMnOtAsIgNaL"); // spell-checker:disable-line +} + #[test] fn test_kill_list_all_vertically() { // Check for a few signals. Do not try to be comprehensive. From b61787637264b392b45029c08b941e0c81021ac3 Mon Sep 17 00:00:00 2001 From: Haisham <47662901+m-haisham@users.noreply.github.com> Date: Sun, 14 Apr 2024 23:13:17 +0500 Subject: [PATCH 105/144] kill: ignore signal case on -s --- src/uu/kill/src/kill.rs | 3 ++- tests/by-util/test_kill.rs | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/src/uu/kill/src/kill.rs b/src/uu/kill/src/kill.rs index 110e8a7a683..632055b8003 100644 --- a/src/uu/kill/src/kill.rs +++ b/src/uu/kill/src/kill.rs @@ -190,7 +190,8 @@ fn list(signals: &Vec) { } fn parse_signal_value(signal_name: &str) -> UResult { - let optional_signal_value = signal_by_name_or_value(signal_name); + let signal_name_upcase = signal_name.to_uppercase(); + let optional_signal_value = signal_by_name_or_value(&signal_name_upcase); match optional_signal_value { Some(x) => Ok(x), None => Err(USimpleError::new( diff --git a/tests/by-util/test_kill.rs b/tests/by-util/test_kill.rs index 73b98f218f5..4a6dd017b29 100644 --- a/tests/by-util/test_kill.rs +++ b/tests/by-util/test_kill.rs @@ -236,6 +236,17 @@ fn test_kill_with_signal_name_new_form() { assert_eq!(target.wait_for_signal(), Some(libc::SIGKILL)); } +#[test] +fn test_kill_with_signal_name_new_form_ignore_case() { + let mut target = Target::new(); + new_ucmd!() + .arg("-s") + .arg("KiLl") + .arg(format!("{}", target.pid())) + .succeeds(); + assert_eq!(target.wait_for_signal(), Some(libc::SIGKILL)); +} + #[test] fn test_kill_with_signal_prefixed_name_new_form() { let mut target = Target::new(); @@ -247,6 +258,29 @@ fn test_kill_with_signal_prefixed_name_new_form() { assert_eq!(target.wait_for_signal(), Some(libc::SIGKILL)); } +#[test] +fn test_kill_with_signal_prefixed_name_new_form_ignore_case() { + let mut target = Target::new(); + new_ucmd!() + .arg("-s") + .arg("SiGKiLl") + .arg(format!("{}", target.pid())) + .succeeds(); + assert_eq!(target.wait_for_signal(), Some(libc::SIGKILL)); +} + +#[test] +fn test_kill_with_signal_name_new_form_unknown_must_match_input_case() { + let target = Target::new(); + new_ucmd!() + .arg("-s") + .arg("IaMnOtAsIgNaL") // spell-checker:disable-line + .arg(format!("{}", target.pid())) + .fails() + .stderr_contains("unknown signal") + .stderr_contains("IaMnOtAsIgNaL"); // spell-checker:disable-line +} + #[test] fn test_kill_no_pid_provided() { // spell-checker:disable-line From 10def29b9600fdd082c3157b960c2e50be496d56 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Mon, 15 Apr 2024 08:07:06 +0200 Subject: [PATCH 106/144] seq: fuzz PreciseNumber::from_str (#6183) * fuzz the seq parse number functions * run fuzz_parse_number into the CI --- .github/workflows/fuzzing.yml | 2 ++ fuzz/Cargo.toml | 6 ++++++ fuzz/fuzz_targets/fuzz_seq_parse_number.rs | 15 +++++++++++++++ src/uu/seq/src/seq.rs | 4 ++++ 4 files changed, 27 insertions(+) create mode 100644 fuzz/fuzz_targets/fuzz_seq_parse_number.rs diff --git a/.github/workflows/fuzzing.yml b/.github/workflows/fuzzing.yml index e045d9b4f04..df40b123679 100644 --- a/.github/workflows/fuzzing.yml +++ b/.github/workflows/fuzzing.yml @@ -58,6 +58,8 @@ jobs: - { name: fuzz_parse_glob, should_pass: true } - { name: fuzz_parse_size, should_pass: true } - { name: fuzz_parse_time, should_pass: true } + - { name: fuzz_seq_parse_number, should_pass: true } + steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@nightly diff --git a/fuzz/Cargo.toml b/fuzz/Cargo.toml index a97054192f1..e8ce7b697ce 100644 --- a/fuzz/Cargo.toml +++ b/fuzz/Cargo.toml @@ -92,6 +92,12 @@ path = "fuzz_targets/fuzz_test.rs" test = false doc = false +[[bin]] +name = "fuzz_seq_parse_number" +path = "fuzz_targets/fuzz_seq_parse_number.rs" +test = false +doc = false + [[bin]] name = "fuzz_parse_glob" path = "fuzz_targets/fuzz_parse_glob.rs" diff --git a/fuzz/fuzz_targets/fuzz_seq_parse_number.rs b/fuzz/fuzz_targets/fuzz_seq_parse_number.rs new file mode 100644 index 00000000000..04da6d47f99 --- /dev/null +++ b/fuzz/fuzz_targets/fuzz_seq_parse_number.rs @@ -0,0 +1,15 @@ +// 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. +#![no_main] + +use libfuzzer_sys::fuzz_target; +use std::str::FromStr; +use uu_seq::number::PreciseNumber; + +fuzz_target!(|data: &[u8]| { + if let Ok(s) = std::str::from_utf8(data) { + let _ = PreciseNumber::from_str(s); + } +}); diff --git a/src/uu/seq/src/seq.rs b/src/uu/seq/src/seq.rs index 33b7636edbc..96ae83ba0a6 100644 --- a/src/uu/seq/src/seq.rs +++ b/src/uu/seq/src/seq.rs @@ -14,6 +14,10 @@ use uucore::{format_usage, help_about, help_usage}; mod error; mod extendedbigdecimal; +// public to allow fuzzing +#[cfg(fuzzing)] +pub mod number; +#[cfg(not(fuzzing))] mod number; mod numberparse; use crate::error::SeqError; From d07192f1078f0f86527db84256272652b0393880 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 15 Apr 2024 11:39:30 +0000 Subject: [PATCH 107/144] chore(deps): update rust crate chrono to 0.4.38 --- Cargo.lock | 4 ++-- Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index caeba80b404..c6b24ae73b1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -244,9 +244,9 @@ checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e" [[package]] name = "chrono" -version = "0.4.37" +version = "0.4.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a0d04d43504c61aa6c7531f1871dd0d418d91130162063b789da00fd7057a5e" +checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" dependencies = [ "android-tzdata", "iana-time-zone", diff --git a/Cargo.toml b/Cargo.toml index e58e813b923..b33a62bb0d6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -263,7 +263,7 @@ binary-heap-plus = "0.5.0" bstr = "1.9.1" bytecount = "0.6.7" byteorder = "1.5.0" -chrono = { version = "0.4.37", default-features = false, features = [ +chrono = { version = "0.4.38", default-features = false, features = [ "std", "alloc", "clock", From c9137a807572ffed8f73b420f09d15417555c770 Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Mon, 15 Apr 2024 15:16:21 +0200 Subject: [PATCH 108/144] od: remove print_width_block field of OutputInfo --- src/uu/od/src/output_info.rs | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/uu/od/src/output_info.rs b/src/uu/od/src/output_info.rs index 993bba32918..16329217200 100644 --- a/src/uu/od/src/output_info.rs +++ b/src/uu/od/src/output_info.rs @@ -38,8 +38,6 @@ pub struct OutputInfo { /// The number of bytes in a block. (This is the size of the largest datatype in `spaced_formatters`.) pub byte_size_block: usize, - /// The width of a block in human readable format. (The size of the largest format.) - pub print_width_block: usize, /// All formats. spaced_formatters: Vec, /// determines if duplicate output lines should be printed, or @@ -78,7 +76,6 @@ impl OutputInfo { byte_size_line: line_bytes, print_width_line, byte_size_block, - print_width_block, spaced_formatters, output_duplicates, } From dd25bab9b5dbdfaf72adede8363e98f7eac67c62 Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Mon, 15 Apr 2024 16:10:11 +0200 Subject: [PATCH 109/144] factor: remove unused method "zero" --- src/uu/factor/src/numeric/montgomery.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/uu/factor/src/numeric/montgomery.rs b/src/uu/factor/src/numeric/montgomery.rs index 135b22e39a0..4807fc44f7d 100644 --- a/src/uu/factor/src/numeric/montgomery.rs +++ b/src/uu/factor/src/numeric/montgomery.rs @@ -43,12 +43,10 @@ pub(crate) trait Arithmetic: Copy + Sized { fn one(&self) -> Self::ModInt { self.to_mod(1) } + fn minus_one(&self) -> Self::ModInt { self.to_mod(self.modulus() - 1) } - fn zero(&self) -> Self::ModInt { - self.to_mod(0) - } } #[derive(Clone, Copy, Debug)] From d60fe6e9e9d564b24e9f3fb4ad4006377606323f Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Mon, 15 Apr 2024 16:40:11 +0200 Subject: [PATCH 110/144] factor: suppress some "never used" warnings --- src/uu/factor/src/numeric/traits.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/uu/factor/src/numeric/traits.rs b/src/uu/factor/src/numeric/traits.rs index c3528a4ad29..ea69e82c2ea 100644 --- a/src/uu/factor/src/numeric/traits.rs +++ b/src/uu/factor/src/numeric/traits.rs @@ -13,6 +13,7 @@ use num_traits::{ }; use std::fmt::{Debug, Display}; +#[allow(dead_code)] // Rust doesn't recognize the use in the macro pub(crate) trait Int: Display + Debug + PrimInt + OverflowingAdd + WrappingNeg + WrappingSub + WrappingMul { @@ -23,6 +24,7 @@ pub(crate) trait Int: fn as_u128(&self) -> u128; } +#[allow(dead_code)] // Rust doesn't recognize the use in the macro pub(crate) trait DoubleInt: Int { /// An integer type with twice the width of `Self`. /// In particular, multiplications (of `Int` values) can be performed in From f817018f90cf4a041f23b40a1dd2cd36eafe8016 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Mon, 15 Apr 2024 22:19:44 +0200 Subject: [PATCH 111/144] hashsum: --ignore-missing needs -c --- src/uu/hashsum/src/hashsum.rs | 12 +++++++++++- tests/by-util/test_hashsum.rs | 7 +++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/src/uu/hashsum/src/hashsum.rs b/src/uu/hashsum/src/hashsum.rs index 4b049e8cf7e..7742358a5d0 100644 --- a/src/uu/hashsum/src/hashsum.rs +++ b/src/uu/hashsum/src/hashsum.rs @@ -348,6 +348,11 @@ pub fn uumain(mut args: impl uucore::Args) -> UResult<()> { let zero = matches.get_flag("zero"); let ignore_missing = matches.get_flag("ignore-missing"); + if ignore_missing && !check { + // --ignore-missing needs -c + return Err(HashsumError::IgnoreNotCheck.into()); + } + let opts = Options { algoname: name, digest: algo, @@ -573,6 +578,7 @@ fn uu_app(binary_name: &str) -> Command { enum HashsumError { InvalidRegex, InvalidFormat, + IgnoreNotCheck, } impl Error for HashsumError {} @@ -583,6 +589,10 @@ impl std::fmt::Display for HashsumError { match self { Self::InvalidRegex => write!(f, "invalid regular expression"), Self::InvalidFormat => Ok(()), + Self::IgnoreNotCheck => write!( + f, + "the --ignore-missing option is meaningful only when verifying checksums" + ), } } } @@ -715,7 +725,7 @@ where let f = match File::open(ck_filename_unescaped) { Err(_) => { if options.ignore_missing { - // No need to show an error + // No need to show or return an error. continue; } diff --git a/tests/by-util/test_hashsum.rs b/tests/by-util/test_hashsum.rs index 4e590902dd6..e5e3e1e98d4 100644 --- a/tests/by-util/test_hashsum.rs +++ b/tests/by-util/test_hashsum.rs @@ -154,6 +154,13 @@ fn test_check_md5_ignore_missing() { .succeeds() .stdout_is("testf: OK\n") .stderr_is(""); + + scene + .ccmd("md5sum") + .arg("--ignore-missing") + .arg(at.subdir.join("testf.sha1")) + .fails() + .stderr_contains("the --ignore-missing option is meaningful only when verifying checksums"); } #[test] From 9d1d4cc5f6333ac98823055561b44a60677859d0 Mon Sep 17 00:00:00 2001 From: binlingyu Date: Wed, 17 Apr 2024 11:14:03 +0800 Subject: [PATCH 112/144] Fix warning when executing Clippy --- tests/by-util/test_chmod.rs | 2 +- tests/by-util/test_kill.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/by-util/test_chmod.rs b/tests/by-util/test_chmod.rs index 35197f85ee4..1a7c3346bef 100644 --- a/tests/by-util/test_chmod.rs +++ b/tests/by-util/test_chmod.rs @@ -278,7 +278,7 @@ fn test_chmod_many_options() { #[test] #[allow(clippy::unreadable_literal)] fn test_chmod_reference_file() { - let tests = vec![ + let tests = [ TestCase { args: vec!["--reference", REFERENCE_FILE, TEST_FILE], before: 0o100070, diff --git a/tests/by-util/test_kill.rs b/tests/by-util/test_kill.rs index 4a6dd017b29..a664ff00979 100644 --- a/tests/by-util/test_kill.rs +++ b/tests/by-util/test_kill.rs @@ -98,7 +98,7 @@ fn test_kill_table_lists_all_vertically() { let signals = command .stdout_str() .split('\n') - .flat_map(|line| line.trim().split(" ").nth(1)) + .flat_map(|line| line.trim().split(' ').nth(1)) .collect::>(); assert!(signals.contains(&"KILL")); From 8d4763bdec96d063c326f32c034d186c36672955 Mon Sep 17 00:00:00 2001 From: Sandro-Alessio Gierens Date: Tue, 16 Apr 2024 23:22:05 +0200 Subject: [PATCH 113/144] feat(github): add CI/test_separately job Signed-off-by: Sandro-Alessio Gierens --- .github/workflows/CICD.yml | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/.github/workflows/CICD.yml b/.github/workflows/CICD.yml index 5ba0924856b..639f50135bf 100644 --- a/.github/workflows/CICD.yml +++ b/.github/workflows/CICD.yml @@ -1047,3 +1047,23 @@ jobs: flags: ${{ steps.vars.outputs.CODECOV_FLAGS }} name: codecov-umbrella fail_ci_if_error: false + + test_separately: + name: Separate Builds + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, macos-latest, windows-latest] + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + - uses: Swatinem/rust-cache@v2 + - name: build and test all programs individually + shell: bash + run: | + for f in $(util/show-utils.sh) + do + echo "Building and testing $f" + cargo test -p "uu_$f" || exit 1 + done From 1c582ee7eb1fd639fbf2df9c32fa02063617d4a6 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 19 Apr 2024 10:13:05 +0000 Subject: [PATCH 114/144] chore(deps): update rust crate bytecount to 0.6.8 --- Cargo.lock | 4 ++-- Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c6b24ae73b1..f33bba3d194 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -205,9 +205,9 @@ checksum = "572f695136211188308f16ad2ca5c851a712c464060ae6974944458eb83880ba" [[package]] name = "bytecount" -version = "0.6.7" +version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1e5f035d16fc623ae5f74981db80a439803888314e3a555fd6f04acd51a3205" +checksum = "5ce89b21cab1437276d2650d57e971f9d548a2d9037cc231abdc0562b97498ce" [[package]] name = "byteorder" diff --git a/Cargo.toml b/Cargo.toml index b33a62bb0d6..72233921d31 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -261,7 +261,7 @@ test = ["uu_test"] bigdecimal = "0.4" binary-heap-plus = "0.5.0" bstr = "1.9.1" -bytecount = "0.6.7" +bytecount = "0.6.8" byteorder = "1.5.0" chrono = { version = "0.4.38", default-features = false, features = [ "std", From 6a08e2df87c18756684805c4638479787b8923d6 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 20 Apr 2024 22:10:25 +0000 Subject: [PATCH 115/144] chore(deps): update rust crate zip to v1 --- Cargo.lock | 44 +++++++++++++++++++++++++++++++------------- Cargo.toml | 2 +- 2 files changed, 32 insertions(+), 14 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f33bba3d194..2704dfaa0a8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -80,6 +80,15 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "arbitrary" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d5a26814d8dcb93b0e5a0ff3c6d80a8843bafb21b39e8e18a6f05471870e110" +dependencies = [ + "derive_arbitrary", +] + [[package]] name = "arrayref" version = "0.3.6" @@ -578,9 +587,9 @@ dependencies = [ [[package]] name = "crc32fast" -version = "1.3.2" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" +checksum = "b3855a8a784b474f333699ef2bbca9db2c4a1f6d9088a90a2d25b1eb53111eaa" dependencies = [ "cfg-if", ] @@ -619,12 +628,9 @@ dependencies = [ [[package]] name = "crossbeam-utils" -version = "0.8.18" +version = "0.8.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3a430a770ebd84726f584a90ee7f020d28db52c6d02138900f22341f866d39c" -dependencies = [ - "cfg-if", -] +checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345" [[package]] name = "crossterm" @@ -703,6 +709,17 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "derive_arbitrary" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67e77553c4162a157adbf834ebae5b415acbecbeafc7a74b0e886657506a7611" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.32", +] + [[package]] name = "diff" version = "0.1.13" @@ -816,9 +833,9 @@ dependencies = [ [[package]] name = "flate2" -version = "1.0.24" +version = "1.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f82b0f4c27ad9f8bfd1f3208d882da2b09c301bc1c828fd3a00d0216d2fbbff6" +checksum = "46303f565772937ffe1d394a4fac6f411c6013172fadde9dcdb1e147a086940e" dependencies = [ "crc32fast", "miniz_oxide", @@ -1266,9 +1283,9 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "miniz_oxide" -version = "0.5.4" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96590ba8f175222643a85693f33d26e9c8a015f599c216509b1a6894af675d34" +checksum = "9d811f3e15f28568be3407c8e7fdb6514c1cda3cb30683f15b6a1a1dc4ea14a7" dependencies = [ "adler", ] @@ -3621,10 +3638,11 @@ checksum = "2a599daf1b507819c1121f0bf87fa37eb19daac6aff3aefefd4e6e2e0f2020fc" [[package]] name = "zip" -version = "0.6.6" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "760394e246e4c28189f19d488c058bf16f564016aefac5d32bb1f3b51d5e9261" +checksum = "f21968e6da56f847a155a89581ba846507afa14854e041f3053edb6ddd19f807" dependencies = [ + "arbitrary", "byteorder", "crc32fast", "crossbeam-utils", diff --git a/Cargo.toml b/Cargo.toml index 72233921d31..1e32bcd1bfa 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -332,7 +332,7 @@ walkdir = "2.5" winapi-util = "0.1.6" windows-sys = { version = "0.48.0", default-features = false } xattr = "1.3.1" -zip = { version = "0.6.6", default-features = false, features = ["deflate"] } +zip = { version = "1.1.0", default-features = false, features = ["deflate"] } hex = "0.4.3" md-5 = "0.10.6" From bde10cd9c60f364ed1f9cf87c385284e6d987e63 Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Wed, 17 Apr 2024 07:42:14 +0200 Subject: [PATCH 116/144] ci: use -pcoreutils when running clippy instead of using -putil_1 -putil2 etc. --- .github/workflows/code-quality.yml | 19 +------------------ 1 file changed, 1 insertion(+), 18 deletions(-) diff --git a/.github/workflows/code-quality.yml b/.github/workflows/code-quality.yml index 009cf788f83..bd34b721748 100644 --- a/.github/workflows/code-quality.yml +++ b/.github/workflows/code-quality.yml @@ -99,23 +99,6 @@ jobs: *) FAIL_ON_FAULT=true ; FAULT_TYPE=error ;; esac; outputs FAIL_ON_FAULT FAULT_TYPE - # target-specific options - # * CARGO_FEATURES_OPTION - CARGO_FEATURES_OPTION='--all-features' ; - if [ -n "${{ matrix.job.features }}" ]; then CARGO_FEATURES_OPTION='--features ${{ matrix.job.features }}' ; fi - outputs CARGO_FEATURES_OPTION - # * determine sub-crate utility list - UTILITY_LIST="$(./util/show-utils.sh ${CARGO_FEATURES_OPTION})" - echo UTILITY_LIST=${UTILITY_LIST} - CARGO_UTILITY_LIST_OPTIONS="$(for u in ${UTILITY_LIST}; do echo -n "-puu_${u} "; done;)" - outputs CARGO_UTILITY_LIST_OPTIONS - - name: Install/setup prerequisites - shell: bash - run: | - ## Install/setup prerequisites - case '${{ matrix.job.os }}' in - macos-latest) brew install coreutils ;; # needed for show-utils.sh - esac - name: "`cargo clippy` lint testing" uses: nick-fields/retry@v3 with: @@ -130,7 +113,7 @@ jobs: fault_type="${{ steps.vars.outputs.FAULT_TYPE }}" fault_prefix=$(echo "$fault_type" | tr '[:lower:]' '[:upper:]') # * convert any warnings to GHA UI annotations; ref: - S=$(cargo clippy --all-targets ${{ matrix.job.cargo-options }} ${{ steps.vars.outputs.CARGO_UTILITY_LIST_OPTIONS }} -- ${CLIPPY_FLAGS} -D warnings 2>&1) && printf "%s\n" "$S" || { printf "%s\n" "$S" ; printf "%s" "$S" | sed -E -n -e '/^error:/{' -e "N; s/^error:[[:space:]]+(.*)\\n[[:space:]]+-->[[:space:]]+(.*):([0-9]+):([0-9]+).*$/::${fault_type} file=\2,line=\3,col=\4::${fault_prefix}: \`cargo clippy\`: \1 (file:'\2', line:\3)/p;" -e '}' ; fault=true ; } + S=$(cargo clippy --all-targets --features ${{ matrix.job.features }} -pcoreutils -- ${CLIPPY_FLAGS} -D warnings 2>&1) && printf "%s\n" "$S" || { printf "%s\n" "$S" ; printf "%s" "$S" | sed -E -n -e '/^error:/{' -e "N; s/^error:[[:space:]]+(.*)\\n[[:space:]]+-->[[:space:]]+(.*):([0-9]+):([0-9]+).*$/::${fault_type} file=\2,line=\3,col=\4::${fault_prefix}: \`cargo clippy\`: \1 (file:'\2', line:\3)/p;" -e '}' ; fault=true ; } if [ -n "${{ steps.vars.outputs.FAIL_ON_FAULT }}" ] && [ -n "$fault" ]; then exit 1 ; fi style_spellcheck: From 2ee782fcf15f966a40d098600a079cae759c1c88 Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Wed, 17 Apr 2024 16:17:51 +0200 Subject: [PATCH 117/144] ci: remove unused CARGO_FEATURES_OPTION --- .github/workflows/code-quality.yml | 5 ----- 1 file changed, 5 deletions(-) diff --git a/.github/workflows/code-quality.yml b/.github/workflows/code-quality.yml index bd34b721748..dca7ee59252 100644 --- a/.github/workflows/code-quality.yml +++ b/.github/workflows/code-quality.yml @@ -49,11 +49,6 @@ jobs: *) FAIL_ON_FAULT=true ; FAULT_TYPE=error ;; esac; outputs FAIL_ON_FAULT FAULT_TYPE - # target-specific options - # * CARGO_FEATURES_OPTION - CARGO_FEATURES_OPTION='' ; - if [ -n "${{ matrix.job.features }}" ]; then CARGO_FEATURES_OPTION='--features "${{ matrix.job.features }}"' ; fi - outputs CARGO_FEATURES_OPTION - name: "`cargo fmt` testing" shell: bash run: | From 29e5d0b42bd4d0e6f265939c4969a495cfbc5ab5 Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Wed, 17 Apr 2024 16:57:38 +0200 Subject: [PATCH 118/144] clippy: fix redundant_clone lint warnings in tests --- tests/by-util/test_chown.rs | 4 ++-- tests/by-util/test_chroot.rs | 2 +- tests/by-util/test_env.rs | 30 +++++++++++++++--------------- tests/by-util/test_install.rs | 2 +- 4 files changed, 19 insertions(+), 19 deletions(-) diff --git a/tests/by-util/test_chown.rs b/tests/by-util/test_chown.rs index 0a2e23c6d32..f893d2611a3 100644 --- a/tests/by-util/test_chown.rs +++ b/tests/by-util/test_chown.rs @@ -471,7 +471,7 @@ fn test_chown_fail_id() { #[test] fn test_chown_only_user_id_nonexistent_user() { let ts = TestScenario::new(util_name!()); - let at = ts.fixtures.clone(); + let at = &ts.fixtures; at.touch("f"); if let Ok(result) = run_ucmd_as_root(&ts, &["12345", "f"]) { result.success().no_stdout().no_stderr(); @@ -537,7 +537,7 @@ fn test_chown_only_group_id() { #[test] fn test_chown_only_group_id_nonexistent_group() { let ts = TestScenario::new(util_name!()); - let at = ts.fixtures.clone(); + let at = &ts.fixtures; at.touch("f"); if let Ok(result) = run_ucmd_as_root(&ts, &[":12345", "f"]) { result.success().no_stdout().no_stderr(); diff --git a/tests/by-util/test_chroot.rs b/tests/by-util/test_chroot.rs index 128d0b812c1..bf6b2ce16f1 100644 --- a/tests/by-util/test_chroot.rs +++ b/tests/by-util/test_chroot.rs @@ -165,7 +165,7 @@ fn test_chroot_skip_chdir_not_root() { #[test] fn test_chroot_skip_chdir() { let ts = TestScenario::new(util_name!()); - let at = ts.fixtures.clone(); + let at = &ts.fixtures; let dirs = ["/", "/.", "/..", "isroot"]; at.symlink_file("/", "isroot"); for dir in dirs { diff --git a/tests/by-util/test_env.rs b/tests/by-util/test_env.rs index 0df1da752b2..94b73284dbe 100644 --- a/tests/by-util/test_env.rs +++ b/tests/by-util/test_env.rs @@ -7,7 +7,7 @@ #[cfg(target_os = "linux")] use crate::common::util::expected_result; use crate::common::util::TestScenario; -use ::env::native_int_str::{Convert, NCvt}; +use env::native_int_str::{Convert, NCvt}; use regex::Regex; use std::env; use std::path::Path; @@ -475,8 +475,8 @@ fn test_gnu_e20() { #[test] fn test_split_string_misc() { - use ::env::native_int_str::NCvt; - use ::env::parse_args_from_str; + use env::native_int_str::NCvt; + use env::parse_args_from_str; assert_eq!( NCvt::convert(vec!["A=B", "FOO=AR", "sh", "-c", "echo $A$FOO"]), @@ -692,13 +692,13 @@ fn test_env_overwrite_arg0() { fn test_env_arg_argv0_overwrite() { let ts = TestScenario::new(util_name!()); - let bin = ts.bin_path.clone(); + let bin = &ts.bin_path; // overwrite --argv0 by --argv0 ts.ucmd() .args(&["--argv0", "dirname"]) .args(&["--argv0", "echo"]) - .arg(&bin) + .arg(bin) .args(&["aa/bb/cc"]) .succeeds() .stdout_is("aa/bb/cc\n") @@ -708,7 +708,7 @@ fn test_env_arg_argv0_overwrite() { ts.ucmd() .args(&["-a", "dirname"]) .args(&["-a", "echo"]) - .arg(&bin) + .arg(bin) .args(&["aa/bb/cc"]) .succeeds() .stdout_is("aa/bb/cc\n") @@ -718,7 +718,7 @@ fn test_env_arg_argv0_overwrite() { ts.ucmd() .args(&["--argv0", "dirname"]) .args(&["-a", "echo"]) - .arg(&bin) + .arg(bin) .args(&["aa/bb/cc"]) .succeeds() .stdout_is("aa/bb/cc\n") @@ -728,7 +728,7 @@ fn test_env_arg_argv0_overwrite() { ts.ucmd() .args(&["-a", "dirname"]) .args(&["--argv0", "echo"]) - .arg(&bin) + .arg(bin) .args(&["aa/bb/cc"]) .succeeds() .stdout_is("aa/bb/cc\n") @@ -740,13 +740,13 @@ fn test_env_arg_argv0_overwrite() { fn test_env_arg_argv0_overwrite_mixed_with_string_args() { let ts = TestScenario::new(util_name!()); - let bin = ts.bin_path.clone(); + let bin = &ts.bin_path; // string arg following normal ts.ucmd() .args(&["-S--argv0 dirname"]) .args(&["--argv0", "echo"]) - .arg(&bin) + .arg(bin) .args(&["aa/bb/cc"]) .succeeds() .stdout_is("aa/bb/cc\n") @@ -756,7 +756,7 @@ fn test_env_arg_argv0_overwrite_mixed_with_string_args() { ts.ucmd() .args(&["-a", "dirname"]) .args(&["-S-a echo"]) - .arg(&bin) + .arg(bin) .args(&["aa/bb/cc"]) .succeeds() .stdout_is("aa/bb/cc\n") @@ -765,7 +765,7 @@ fn test_env_arg_argv0_overwrite_mixed_with_string_args() { // one large string arg ts.ucmd() .args(&["-S--argv0 dirname -a echo"]) - .arg(&bin) + .arg(bin) .args(&["aa/bb/cc"]) .succeeds() .stdout_is("aa/bb/cc\n") @@ -775,7 +775,7 @@ fn test_env_arg_argv0_overwrite_mixed_with_string_args() { ts.ucmd() .args(&["-S-a dirname"]) .args(&["-S--argv0 echo"]) - .arg(&bin) + .arg(bin) .args(&["aa/bb/cc"]) .succeeds() .stdout_is("aa/bb/cc\n") @@ -786,7 +786,7 @@ fn test_env_arg_argv0_overwrite_mixed_with_string_args() { .args(&["-a", "sleep"]) .args(&["-S-a dirname"]) .args(&["-a", "echo"]) - .arg(&bin) + .arg(bin) .args(&["aa/bb/cc"]) .succeeds() .stdout_is("aa/bb/cc\n") @@ -916,8 +916,8 @@ mod tests_split_iterator { use std::ffi::OsString; - use ::env::parse_error::ParseError; use env::native_int_str::{from_native_int_representation_owned, Convert, NCvt}; + use env::parse_error::ParseError; fn split(input: &str) -> Result, ParseError> { ::env::split_iterator::split(&NCvt::convert(input)).map(|vec| { diff --git a/tests/by-util/test_install.rs b/tests/by-util/test_install.rs index 5790c685fc2..b07da43dc75 100644 --- a/tests/by-util/test_install.rs +++ b/tests/by-util/test_install.rs @@ -1689,7 +1689,7 @@ fn test_target_file_ends_with_slash() { #[test] fn test_install_root_combined() { let ts = TestScenario::new(util_name!()); - let at = ts.fixtures.clone(); + let at = &ts.fixtures; at.touch("a"); at.touch("c"); From c83cec7c0c00a8ebc7eb3321dfe4b9ed5b1c8423 Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Wed, 17 Apr 2024 17:30:31 +0200 Subject: [PATCH 119/144] env: move unit tests to env.rs --- src/uu/env/src/env.rs | 36 ++++++++++++++++++++++++++++++++++++ tests/by-util/test_env.rs | 35 ----------------------------------- 2 files changed, 36 insertions(+), 35 deletions(-) diff --git a/src/uu/env/src/env.rs b/src/uu/env/src/env.rs index 3908e9e78e8..f4282326fef 100644 --- a/src/uu/env/src/env.rs +++ b/src/uu/env/src/env.rs @@ -606,3 +606,39 @@ fn apply_specified_env_vars(opts: &Options<'_>) { pub fn uumain(args: impl uucore::Args) -> UResult<()> { EnvAppData::default().run_env(args) } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_split_string_environment_vars_test() { + std::env::set_var("FOO", "BAR"); + assert_eq!( + NCvt::convert(vec!["FOO=bar", "sh", "-c", "echo xBARx =$FOO="]), + parse_args_from_str(&NCvt::convert(r#"FOO=bar sh -c "echo x${FOO}x =\$FOO=""#)) + .unwrap(), + ); + } + + #[test] + fn test_split_string_misc() { + assert_eq!( + NCvt::convert(vec!["A=B", "FOO=AR", "sh", "-c", "echo $A$FOO"]), + parse_args_from_str(&NCvt::convert(r#"A=B FOO=AR sh -c "echo \$A\$FOO""#)).unwrap(), + ); + assert_eq!( + NCvt::convert(vec!["A=B", "FOO=AR", "sh", "-c", "echo $A$FOO"]), + parse_args_from_str(&NCvt::convert(r#"A=B FOO=AR sh -c 'echo $A$FOO'"#)).unwrap() + ); + assert_eq!( + NCvt::convert(vec!["A=B", "FOO=AR", "sh", "-c", "echo $A$FOO"]), + parse_args_from_str(&NCvt::convert(r#"A=B FOO=AR sh -c 'echo $A$FOO'"#)).unwrap() + ); + + assert_eq!( + NCvt::convert(vec!["-i", "A=B ' C"]), + parse_args_from_str(&NCvt::convert(r#"-i A='B \' C'"#)).unwrap() + ); + } +} diff --git a/tests/by-util/test_env.rs b/tests/by-util/test_env.rs index 94b73284dbe..18edc5dd650 100644 --- a/tests/by-util/test_env.rs +++ b/tests/by-util/test_env.rs @@ -7,7 +7,6 @@ #[cfg(target_os = "linux")] use crate::common::util::expected_result; use crate::common::util::TestScenario; -use env::native_int_str::{Convert, NCvt}; use regex::Regex; use std::env; use std::path::Path; @@ -473,40 +472,6 @@ fn test_gnu_e20() { assert_eq!(out.stdout_str(), output); } -#[test] -fn test_split_string_misc() { - use env::native_int_str::NCvt; - use env::parse_args_from_str; - - assert_eq!( - NCvt::convert(vec!["A=B", "FOO=AR", "sh", "-c", "echo $A$FOO"]), - parse_args_from_str(&NCvt::convert(r#"A=B FOO=AR sh -c "echo \$A\$FOO""#)).unwrap(), - ); - assert_eq!( - NCvt::convert(vec!["A=B", "FOO=AR", "sh", "-c", "echo $A$FOO"]), - parse_args_from_str(&NCvt::convert(r#"A=B FOO=AR sh -c 'echo $A$FOO'"#)).unwrap() - ); - assert_eq!( - NCvt::convert(vec!["A=B", "FOO=AR", "sh", "-c", "echo $A$FOO"]), - parse_args_from_str(&NCvt::convert(r#"A=B FOO=AR sh -c 'echo $A$FOO'"#)).unwrap() - ); - - assert_eq!( - NCvt::convert(vec!["-i", "A=B ' C"]), - parse_args_from_str(&NCvt::convert(r#"-i A='B \' C'"#)).unwrap() - ); -} - -#[test] -fn test_split_string_environment_vars_test() { - std::env::set_var("FOO", "BAR"); - assert_eq!( - NCvt::convert(vec!["FOO=bar", "sh", "-c", "echo xBARx =$FOO="]), - ::env::parse_args_from_str(&NCvt::convert(r#"FOO=bar sh -c "echo x${FOO}x =\$FOO=""#)) - .unwrap(), - ); -} - #[macro_export] macro_rules! compare_with_gnu { ( $ts:expr, $args:expr ) => {{ From beb7395c84aafd95ba60d94b54e1b92a7eac9238 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Thu, 18 Apr 2024 12:28:33 +0200 Subject: [PATCH 120/144] hashsum: move handle_captures & gnu_re_template move away from the hashsum function --- src/uu/hashsum/src/hashsum.rs | 84 +++++++++++++++++------------------ 1 file changed, 41 insertions(+), 43 deletions(-) diff --git a/src/uu/hashsum/src/hashsum.rs b/src/uu/hashsum/src/hashsum.rs index 7742358a5d0..8c574f06600 100644 --- a/src/uu/hashsum/src/hashsum.rs +++ b/src/uu/hashsum/src/hashsum.rs @@ -597,6 +597,47 @@ impl std::fmt::Display for HashsumError { } } +/// Creates a Regex for parsing lines based on the given format. +/// The default value of `gnu_re` created with this function has to be recreated +/// after the initial line has been parsed, as this line dictates the format +/// for the rest of them, and mixing of formats is disallowed. +fn gnu_re_template(bytes_marker: &str, format_marker: &str) -> Result { + Regex::new(&format!( + r"^(?P[a-fA-F0-9]{bytes_marker}) {format_marker}(?P.*)" + )) + .map_err(|_| HashsumError::InvalidRegex) +} + +fn handle_captures( + caps: &Captures, + bytes_marker: &str, + bsd_reversed: &mut Option, + gnu_re: &mut Regex, +) -> Result<(String, String, bool), HashsumError> { + if bsd_reversed.is_none() { + let is_bsd_reversed = caps.name("binary").is_none(); + let format_marker = if is_bsd_reversed { + "" + } else { + r"(?P[ \*])" + } + .to_string(); + + *bsd_reversed = Some(is_bsd_reversed); + *gnu_re = gnu_re_template(bytes_marker, &format_marker)?; + } + + Ok(( + caps.name("fileName").unwrap().as_str().to_string(), + caps.name("digest").unwrap().as_str().to_ascii_lowercase(), + if *bsd_reversed == Some(false) { + caps.name("binary").unwrap().as_str() == "*" + } else { + false + }, + )) +} + #[allow(clippy::cognitive_complexity)] fn hashsum<'a, I>(mut options: Options, files: I) -> UResult<()> where @@ -636,19 +677,6 @@ where // BSD reversed mode format is similar to the default mode, but doesn’t use a character to distinguish binary and text modes. let mut bsd_reversed = None; - /// Creates a Regex for parsing lines based on the given format. - /// The default value of `gnu_re` created with this function has to be recreated - /// after the initial line has been parsed, as this line dictates the format - /// for the rest of them, and mixing of formats is disallowed. - fn gnu_re_template( - bytes_marker: &str, - format_marker: &str, - ) -> Result { - Regex::new(&format!( - r"^(?P[a-fA-F0-9]{bytes_marker}) {format_marker}(?P.*)" - )) - .map_err(|_| HashsumError::InvalidRegex) - } let mut gnu_re = gnu_re_template(&bytes_marker, r"(?P[ \*])?")?; let bsd_re = Regex::new(&format!( // it can start with \ @@ -658,36 +686,6 @@ where )) .map_err(|_| HashsumError::InvalidRegex)?; - fn handle_captures( - caps: &Captures, - bytes_marker: &str, - bsd_reversed: &mut Option, - gnu_re: &mut Regex, - ) -> Result<(String, String, bool), HashsumError> { - if bsd_reversed.is_none() { - let is_bsd_reversed = caps.name("binary").is_none(); - let format_marker = if is_bsd_reversed { - "" - } else { - r"(?P[ \*])" - } - .to_string(); - - *bsd_reversed = Some(is_bsd_reversed); - *gnu_re = gnu_re_template(bytes_marker, &format_marker)?; - } - - Ok(( - caps.name("fileName").unwrap().as_str().to_string(), - caps.name("digest").unwrap().as_str().to_ascii_lowercase(), - if *bsd_reversed == Some(false) { - caps.name("binary").unwrap().as_str() == "*" - } else { - false - }, - )) - } - let buffer = file; for (i, maybe_line) in buffer.lines().enumerate() { let line = match maybe_line { From 94f5e82dbd316bfe38c08af0a9a8e498142abdc8 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Fri, 19 Apr 2024 00:00:26 +0200 Subject: [PATCH 121/144] hashsum: ignore empty lines in --check --- src/uu/hashsum/src/hashsum.rs | 4 ++++ tests/by-util/test_hashsum.rs | 18 ++++++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/src/uu/hashsum/src/hashsum.rs b/src/uu/hashsum/src/hashsum.rs index 8c574f06600..597bca565a5 100644 --- a/src/uu/hashsum/src/hashsum.rs +++ b/src/uu/hashsum/src/hashsum.rs @@ -692,6 +692,10 @@ where Ok(l) => l, Err(e) => return Err(e.map_err_context(|| "failed to read file".to_string())), }; + if line.is_empty() { + // empty line, skip it + continue; + } let (ck_filename, sum, binary_check) = match gnu_re.captures(&line) { Some(caps) => { handle_captures(&caps, &bytes_marker, &mut bsd_reversed, &mut gnu_re)? diff --git a/tests/by-util/test_hashsum.rs b/tests/by-util/test_hashsum.rs index e5e3e1e98d4..535a4d0ba88 100644 --- a/tests/by-util/test_hashsum.rs +++ b/tests/by-util/test_hashsum.rs @@ -457,6 +457,24 @@ fn test_with_escape_filename_zero_text() { assert!(stdout.contains("a\nb")); } +#[test] +fn test_check_empty_line() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + + at.touch("f"); + at.write( + "in.md5", + "d41d8cd98f00b204e9800998ecf8427e f\n\nd41d8cd98f00b204e9800998ecf8427e f\ninvalid\n\n", + ); + scene + .ccmd("md5sum") + .arg("--check") + .arg(at.subdir.join("in.md5")) + .fails() + .stderr_contains("WARNING: 1 line is improperly formatted"); +} + #[test] #[cfg(not(windows))] fn test_check_with_escape_filename() { From 128c0bc6b5c9870c086d3d9a8c7cc892431afc31 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Fri, 19 Apr 2024 00:44:45 +0200 Subject: [PATCH 122/144] hashsum: gnu compat no need to replicate this output with hashsum --- util/build-gnu.sh | 3 +++ 1 file changed, 3 insertions(+) diff --git a/util/build-gnu.sh b/util/build-gnu.sh index 6a4b09f89d8..e8095fe6cc2 100755 --- a/util/build-gnu.sh +++ b/util/build-gnu.sh @@ -346,3 +346,6 @@ sed -i -e "s|WARNING: 1 line is improperly formatted|warning: 1 line is improper echo "n_stat1 = \$n_stat1"\n\ echo "n_stat2 = \$n_stat2"\n\ test \$n_stat1 -ge \$n_stat2 \\' tests/ls/stat-free-color.sh + +# no need to replicate this output with hashsum +sed -i -e "s|Try 'md5sum --help' for more information.\\\n||" tests/cksum/md5sum.pl From 6ef08d7f1c48af230c6c8d86e3b8c7ba935bae36 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Fri, 19 Apr 2024 01:20:06 +0200 Subject: [PATCH 123/144] hashsum: improve the error management to match GNU Should make tests/cksum/md5sum.pl and tests/cksum/sha1sum.pl pass --- src/uu/hashsum/src/hashsum.rs | 78 ++++++++++++++++++----- src/uucore/src/lib/macros.rs | 8 +++ tests/by-util/test_hashsum.rs | 115 +++++++++++++++++++++++++++++++++- util/build-gnu.sh | 3 - 4 files changed, 183 insertions(+), 21 deletions(-) diff --git a/src/uu/hashsum/src/hashsum.rs b/src/uu/hashsum/src/hashsum.rs index 597bca565a5..d4c5aacbb6a 100644 --- a/src/uu/hashsum/src/hashsum.rs +++ b/src/uu/hashsum/src/hashsum.rs @@ -26,7 +26,8 @@ use uucore::sum::{ Blake2b, Blake3, Digest, DigestWriter, Md5, Sha1, Sha224, Sha256, Sha384, Sha3_224, Sha3_256, Sha3_384, Sha3_512, Sha512, Shake128, Shake256, }; -use uucore::{display::Quotable, show_warning}; +use uucore::util_name; +use uucore::{display::Quotable, show_warning_caps}; use uucore::{format_usage, help_about, help_usage}; const NAME: &str = "hashsum"; @@ -577,7 +578,6 @@ fn uu_app(binary_name: &str) -> Command { #[derive(Debug)] enum HashsumError { InvalidRegex, - InvalidFormat, IgnoreNotCheck, } @@ -588,7 +588,6 @@ impl std::fmt::Display for HashsumError { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { match self { Self::InvalidRegex => write!(f, "invalid regular expression"), - Self::InvalidFormat => Ok(()), Self::IgnoreNotCheck => write!( f, "the --ignore-missing option is meaningful only when verifying checksums" @@ -644,8 +643,10 @@ where I: Iterator, { let mut bad_format = 0; + let mut correct_format = 0; let mut failed_cksum = 0; let mut failed_open_file = 0; + let mut skip_summary = false; let binary_marker = if options.binary { "*" } else { " " }; for filename in files { let filename = Path::new(filename); @@ -680,13 +681,14 @@ where let mut gnu_re = gnu_re_template(&bytes_marker, r"(?P[ \*])?")?; let bsd_re = Regex::new(&format!( // it can start with \ - r"^(|\\){algorithm} \((?P.*)\) = (?P[a-fA-F0-9]{digest_size})", + r"^(\\)?{algorithm}\s*\((?P.*)\)\s*=\s*(?P[a-fA-F0-9]{digest_size})$", algorithm = options.algoname, digest_size = bytes_marker, )) .map_err(|_| HashsumError::InvalidRegex)?; let buffer = file; + // iterate on the lines of the file for (i, maybe_line) in buffer.lines().enumerate() { let line = match maybe_line { Ok(l) => l, @@ -701,6 +703,7 @@ where handle_captures(&caps, &bytes_marker, &mut bsd_reversed, &mut gnu_re)? } None => match bsd_re.captures(&line) { + // if the GNU style parsing failed, try the BSD style Some(caps) => ( caps.name("fileName").unwrap().as_str().to_string(), caps.name("digest").unwrap().as_str().to_ascii_lowercase(), @@ -709,11 +712,14 @@ where None => { bad_format += 1; if options.strict { - return Err(HashsumError::InvalidFormat.into()); + // if we use strict, the warning "lines are improperly formatted" + // will trigger an exit code of 1 + set_exit_code(1); } if options.warn { - show_warning!( - "{}: {}: improperly formatted {} checksum line", + eprintln!( + "{}: {}: {}: improperly formatted {} checksum line", + util_name(), filename.maybe_quote(), i + 1, options.algoname @@ -727,7 +733,8 @@ where let f = match File::open(ck_filename_unescaped) { Err(_) => { if options.ignore_missing { - // No need to show or return an error. + // No need to show or return an error + // except when the file doesn't have any successful checks continue; } @@ -762,6 +769,7 @@ where // and display it using uucore::display::print_verbatim(). This is // easier (and more important) on Unix than on Windows. if sum == real_sum { + correct_format += 1; if !options.quiet { println!("{prefix}{ck_filename}: OK"); } @@ -770,6 +778,7 @@ where println!("{prefix}{ck_filename}: FAILED"); } failed_cksum += 1; + set_exit_code(1); } } } else { @@ -795,20 +804,57 @@ where println!("{}{} {}{}", prefix, sum, binary_marker, escaped_filename); } } + if bad_format > 0 && failed_cksum == 0 && correct_format == 0 && !options.status { + // we have only bad format. we didn't have anything correct. + // GNU has a different error message for this (with the filename) + set_exit_code(1); + eprintln!( + "{}: {}: no properly formatted checksum lines found", + util_name(), + filename.maybe_quote(), + ); + skip_summary = true; + } + if options.ignore_missing && correct_format == 0 { + // we have only bad format + // and we had ignore-missing + eprintln!( + "{}: {}: no file was verified", + util_name(), + filename.maybe_quote(), + ); + skip_summary = true; + set_exit_code(1); + } } - if !options.status { + + if !options.status && !skip_summary { match bad_format.cmp(&1) { - Ordering::Equal => show_warning!("{} line is improperly formatted", bad_format), - Ordering::Greater => show_warning!("{} lines are improperly formatted", bad_format), + Ordering::Equal => { + show_warning_caps!("{} line is improperly formatted", bad_format) + } + Ordering::Greater => { + show_warning_caps!("{} lines are improperly formatted", bad_format) + } Ordering::Less => {} }; - if failed_cksum > 0 { - show_warning!("{} computed checksum did NOT match", failed_cksum); - } + + match failed_cksum.cmp(&1) { + Ordering::Equal => { + show_warning_caps!("{} computed checksum did NOT match", failed_cksum) + } + Ordering::Greater => { + show_warning_caps!("{} computed checksums did NOT match", failed_cksum) + } + Ordering::Less => {} + }; + match failed_open_file.cmp(&1) { - Ordering::Equal => show_warning!("{} listed file could not be read", failed_open_file), + Ordering::Equal => { + show_warning_caps!("{} listed file could not be read", failed_open_file) + } Ordering::Greater => { - show_warning!("{} listed files could not be read", failed_open_file); + show_warning_caps!("{} listed files could not be read", failed_open_file); } Ordering::Less => {} } diff --git a/src/uucore/src/lib/macros.rs b/src/uucore/src/lib/macros.rs index d1a09c281ab..359c9d00b9a 100644 --- a/src/uucore/src/lib/macros.rs +++ b/src/uucore/src/lib/macros.rs @@ -182,6 +182,14 @@ macro_rules! show_warning( }) ); +#[macro_export] +macro_rules! show_warning_caps( + ($($args:tt)+) => ({ + eprint!("{}: WARNING: ", $crate::util_name()); + eprintln!($($args)+); + }) +); + /// Display an error and [`std::process::exit`] /// /// Displays the provided error message using [`show_error!`], then invokes diff --git a/tests/by-util/test_hashsum.rs b/tests/by-util/test_hashsum.rs index 535a4d0ba88..432eb3ddf6f 100644 --- a/tests/by-util/test_hashsum.rs +++ b/tests/by-util/test_hashsum.rs @@ -244,7 +244,7 @@ fn test_check_file_not_found_warning() { .arg(at.subdir.join("testf.sha1")) .fails() .stdout_is("sha1sum: testf: No such file or directory\ntestf: FAILED open or read\n") - .stderr_is("sha1sum: warning: 1 listed file could not be read\n"); + .stderr_is("sha1sum: WARNING: 1 listed file could not be read\n"); } // Asterisk `*` is a reserved paths character on win32, nor the path can end with a whitespace. @@ -471,7 +471,7 @@ fn test_check_empty_line() { .ccmd("md5sum") .arg("--check") .arg(at.subdir.join("in.md5")) - .fails() + .succeeds() .stderr_contains("WARNING: 1 line is improperly formatted"); } @@ -498,3 +498,114 @@ fn test_check_with_escape_filename() { .succeeds(); result.stdout_is("\\a\\nb: OK\n"); } + +#[test] +fn test_check_strict_error() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + + at.write("f", ""); + at.write( + "in.md5", + "ERR\nERR\nd41d8cd98f00b204e9800998ecf8427e f\nERR\n", + ); + scene + .ccmd("md5sum") + .arg("--check") + .arg("--strict") + .arg(at.subdir.join("in.md5")) + .fails() + .stderr_contains("WARNING: 3 lines are improperly formatted"); +} + +#[test] +fn test_check_warn() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + + at.write("f", ""); + at.write( + "in.md5", + "d41d8cd98f00b204e9800998ecf8427e f\nd41d8cd98f00b204e9800998ecf8427e f\ninvalid\n", + ); + scene + .ccmd("md5sum") + .arg("--check") + .arg("--warn") + .arg(at.subdir.join("in.md5")) + .succeeds() + .stderr_contains("in.md5: 3: improperly formatted MD5 checksum line") + .stderr_contains("WARNING: 1 line is improperly formatted"); + + // with strict, we should fail the execution + scene + .ccmd("md5sum") + .arg("--check") + .arg("--strict") + .arg(at.subdir.join("in.md5")) + .fails(); +} + +#[test] +fn test_check_status() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + + at.write("f", ""); + at.write("in.md5", "MD5(f)= d41d8cd98f00b204e9800998ecf8427f\n"); + scene + .ccmd("md5sum") + .arg("--check") + .arg("--status") + .arg(at.subdir.join("in.md5")) + .fails() + .no_output(); +} + +#[test] +fn test_check_status_code() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + + at.write("f", ""); + at.write("in.md5", "d41d8cd98f00b204e9800998ecf8427f f\n"); + scene + .ccmd("md5sum") + .arg("--check") + .arg("--status") + .arg(at.subdir.join("in.md5")) + .fails() + .stderr_is("") + .stdout_is(""); +} + +#[test] +fn test_check_no_backslash_no_space() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + + at.write("f", ""); + at.write("in.md5", "MD5(f)= d41d8cd98f00b204e9800998ecf8427e\n"); + scene + .ccmd("md5sum") + .arg("--check") + .arg(at.subdir.join("in.md5")) + .succeeds() + .stdout_is("f: OK\n"); +} + +#[test] +fn test_check_check_ignore_no_file() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + + at.write("f", ""); + at.write("in.md5", "d41d8cd98f00b204e9800998ecf8427f missing\n"); + scene + .ccmd("md5sum") + .arg("--check") + .arg("--ignore-missing") + .arg(at.subdir.join("in.md5")) + .fails() + .stderr_contains("in.md5: no file was verified"); +} diff --git a/util/build-gnu.sh b/util/build-gnu.sh index e8095fe6cc2..debb7bdd93b 100755 --- a/util/build-gnu.sh +++ b/util/build-gnu.sh @@ -337,9 +337,6 @@ ls: invalid --time-style argument 'XX'\nPossible values are: [\"full-iso\", \"lo # "hostid BEFORE --help AFTER " same for this sed -i -e "s/env \$prog \$BEFORE \$opt > out2/env \$prog \$BEFORE \$opt > out2 #/" -e "s/env \$prog \$BEFORE \$opt AFTER > out3/env \$prog \$BEFORE \$opt AFTER > out3 #/" -e "s/compare exp out2/compare exp out2 #/" -e "s/compare exp out3/compare exp out3 #/" tests/help/help-version-getopt.sh -# The case doesn't really matter here -sed -i -e "s|WARNING: 1 line is improperly formatted|warning: 1 line is improperly formatted|" tests/cksum/md5sum-bsd.sh - # Add debug info + we have less syscall then GNU's. Adjust our check. # Use GNU sed for /c command "${SED}" -i -e '/test \$n_stat1 = \$n_stat2 \\/c\ From a1717436a40507bced147edb39818818aa124320 Mon Sep 17 00:00:00 2001 From: sreehari prasad <52113972+matrixhead@users.noreply.github.com> Date: Sun, 21 Apr 2024 13:19:45 +0530 Subject: [PATCH 124/144] cp: gnu "same-file" test case compatibility (#6190) * cp: -b doesn't ignore "version control" env * cp: gnu "same-file" test compatibility fix --- src/uu/cp/src/cp.rs | 140 ++- src/uucore/src/lib/features/backup_control.rs | 17 +- src/uucore/src/lib/features/fs.rs | 24 + tests/by-util/test_cp.rs | 926 +++++++++++++++++- 4 files changed, 1069 insertions(+), 38 deletions(-) diff --git a/src/uu/cp/src/cp.rs b/src/uu/cp/src/cp.rs index 0a307ee9e99..cb3275ff9c4 100644 --- a/src/uu/cp/src/cp.rs +++ b/src/uu/cp/src/cp.rs @@ -29,8 +29,9 @@ use platform::copy_on_write; use uucore::display::Quotable; use uucore::error::{set_exit_code, UClapError, UError, UResult, UUsageError}; use uucore::fs::{ - are_hardlinks_to_same_file, canonicalize, is_symlink_loop, path_ends_with_terminator, - paths_refer_to_same_file, FileInformation, MissingHandling, ResolveMode, + are_hardlinks_to_same_file, canonicalize, get_filename, is_symlink_loop, + path_ends_with_terminator, paths_refer_to_same_file, FileInformation, MissingHandling, + ResolveMode, }; use uucore::{backup_control, update_control}; // These are exposed for projects (e.g. nushell) that want to create an `Options` value, which @@ -1468,16 +1469,23 @@ pub(crate) fn copy_attributes( fn symlink_file( source: &Path, dest: &Path, - context: &str, symlinked_files: &mut HashSet, ) -> CopyResult<()> { #[cfg(not(windows))] { - std::os::unix::fs::symlink(source, dest).context(context)?; + std::os::unix::fs::symlink(source, dest).context(format!( + "cannot create symlink {} to {}", + get_filename(dest).unwrap_or("invalid file name").quote(), + get_filename(source).unwrap_or("invalid file name").quote() + ))?; } #[cfg(windows)] { - std::os::windows::fs::symlink_file(source, dest).context(context)?; + std::os::windows::fs::symlink_file(source, dest).context(format!( + "cannot create symlink {} to {}", + get_filename(dest).unwrap_or("invalid file name").quote(), + get_filename(source).unwrap_or("invalid file name").quote() + ))?; } if let Ok(file_info) = FileInformation::from_path(dest, false) { symlinked_files.insert(file_info); @@ -1489,10 +1497,11 @@ fn context_for(src: &Path, dest: &Path) -> String { format!("{} -> {}", src.quote(), dest.quote()) } -/// Implements a simple backup copy for the destination file. +/// Implements a simple backup copy for the destination file . +/// if is_dest_symlink flag is set to true dest will be renamed to backup_path /// TODO: for the backup, should this function be replaced by `copy_file(...)`? -fn backup_dest(dest: &Path, backup_path: &Path) -> CopyResult { - if dest.is_symlink() { +fn backup_dest(dest: &Path, backup_path: &Path, is_dest_symlink: bool) -> CopyResult { + if is_dest_symlink { fs::rename(dest, backup_path)?; } else { fs::copy(dest, backup_path)?; @@ -1513,11 +1522,38 @@ fn is_forbidden_to_copy_to_same_file( ) -> bool { // TODO To match the behavior of GNU cp, we also need to check // that the file is a regular file. + let source_is_symlink = source.is_symlink(); + let dest_is_symlink = dest.is_symlink(); + // only disable dereference if both source and dest is symlink and dereference flag is disabled let dereference_to_compare = - options.dereference(source_in_command_line) || !source.is_symlink(); - paths_refer_to_same_file(source, dest, dereference_to_compare) - && !(options.force() && options.backup != BackupMode::NoBackup) - && !(dest.is_symlink() && options.backup != BackupMode::NoBackup) + options.dereference(source_in_command_line) || (!source_is_symlink || !dest_is_symlink); + if !paths_refer_to_same_file(source, dest, dereference_to_compare) { + return false; + } + if options.backup != BackupMode::NoBackup { + if options.force() && !source_is_symlink { + return false; + } + if source_is_symlink && !options.dereference { + return false; + } + if dest_is_symlink { + return false; + } + if !dest_is_symlink && !source_is_symlink && dest != source { + return false; + } + } + if options.copy_mode == CopyMode::Link { + return false; + } + if options.copy_mode == CopyMode::SymLink && dest_is_symlink { + return false; + } + if dest_is_symlink && source_is_symlink && !options.dereference { + return false; + } + true } /// Back up, remove, or leave intact the destination file, depending on the options. @@ -1526,6 +1562,7 @@ fn handle_existing_dest( dest: &Path, options: &Options, source_in_command_line: bool, + copied_files: &mut HashMap, ) -> CopyResult<()> { // Disallow copying a file to itself, unless `--force` and // `--backup` are both specified. @@ -1537,6 +1574,7 @@ fn handle_existing_dest( options.overwrite.verify(dest)?; } + let mut is_dest_removed = false; let backup_path = backup_control::get_backup_path(options.backup, dest, &options.backup_suffix); if let Some(backup_path) = backup_path { if paths_refer_to_same_file(source, &backup_path, true) { @@ -1547,13 +1585,16 @@ fn handle_existing_dest( ) .into()); } else { - backup_dest(dest, &backup_path)?; + is_dest_removed = dest.is_symlink(); + backup_dest(dest, &backup_path, is_dest_removed)?; } } match options.overwrite { // FIXME: print that the file was removed if --verbose is enabled OverwriteMode::Clobber(ClobberMode::Force) => { - if is_symlink_loop(dest) || fs::metadata(dest)?.permissions().readonly() { + if !is_dest_removed + && (is_symlink_loop(dest) || fs::metadata(dest)?.permissions().readonly()) + { fs::remove_file(dest)?; } } @@ -1574,7 +1615,19 @@ fn handle_existing_dest( // `dest/src/f` and `dest/src/f` has the contents of // `src/f`, we delete the existing file to allow the hard // linking. - if options.preserve_hard_links() { + + if options.preserve_hard_links() + // only try to remove dest file only if the current source + // is hardlink to a file that is already copied + && copied_files.contains_key( + &FileInformation::from_path( + source, + options.dereference(source_in_command_line), + ) + .context(format!("cannot stat {}", source.quote()))?, + ) + && !is_dest_removed + { fs::remove_file(dest)?; } } @@ -1700,7 +1753,7 @@ fn handle_copy_mode( let backup_path = backup_control::get_backup_path(options.backup, dest, &options.backup_suffix); if let Some(backup_path) = backup_path { - backup_dest(dest, &backup_path)?; + backup_dest(dest, &backup_path, dest.is_symlink())?; fs::remove_file(dest)?; } if options.overwrite == OverwriteMode::Clobber(ClobberMode::Force) { @@ -1714,7 +1767,11 @@ fn handle_copy_mode( } else { fs::hard_link(source, dest) } - .context(context)?; + .context(format!( + "cannot create hard link {} to {}", + get_filename(dest).unwrap_or("invalid file name").quote(), + get_filename(source).unwrap_or("invalid file name").quote() + ))?; } CopyMode::Copy => { copy_helper( @@ -1731,7 +1788,7 @@ fn handle_copy_mode( if dest.exists() && options.overwrite == OverwriteMode::Clobber(ClobberMode::Force) { fs::remove_file(dest)?; } - symlink_file(source, dest, context, symlinked_files)?; + symlink_file(source, dest, symlinked_files)?; } CopyMode::Update => { if dest.exists() { @@ -1860,8 +1917,10 @@ fn copy_file( copied_files: &mut HashMap, source_in_command_line: bool, ) -> CopyResult<()> { + let source_is_symlink = source.is_symlink(); + let dest_is_symlink = dest.is_symlink(); // Fail if dest is a dangling symlink or a symlink this program created previously - if dest.is_symlink() { + if dest_is_symlink { if FileInformation::from_path(dest, false) .map(|info| symlinked_files.contains(&info)) .unwrap_or(false) @@ -1872,7 +1931,7 @@ fn copy_file( dest.display() ))); } - let copy_contents = options.dereference(source_in_command_line) || !source.is_symlink(); + let copy_contents = options.dereference(source_in_command_line) || !source_is_symlink; if copy_contents && !dest.exists() && !matches!( @@ -1898,6 +1957,7 @@ fn copy_file( } if are_hardlinks_to_same_file(source, dest) + && source != dest && matches!( options.overwrite, OverwriteMode::Clobber(ClobberMode::RemoveDestination) @@ -1913,19 +1973,37 @@ fn copy_file( OverwriteMode::Clobber(ClobberMode::RemoveDestination) )) { - if are_hardlinks_to_same_file(source, dest) - && !options.force() - && options.backup == BackupMode::NoBackup - && source != dest - || (source == dest && options.copy_mode == CopyMode::Link) - { - return Ok(()); + if paths_refer_to_same_file(source, dest, true) && options.copy_mode == CopyMode::Link { + if source_is_symlink { + if !dest_is_symlink { + return Ok(()); + } + if !options.dereference { + return Ok(()); + } + } else if options.backup != BackupMode::NoBackup && !dest_is_symlink { + if source == dest { + if !options.force() { + return Ok(()); + } + } else { + return Ok(()); + } + } + } + handle_existing_dest(source, dest, options, source_in_command_line, copied_files)?; + if are_hardlinks_to_same_file(source, dest) { + if options.copy_mode == CopyMode::Copy && options.backup != BackupMode::NoBackup { + return Ok(()); + } + if options.copy_mode == CopyMode::Link && (!source_is_symlink || !dest_is_symlink) { + return Ok(()); + } } - handle_existing_dest(source, dest, options, source_in_command_line)?; } if options.attributes_only - && source.is_symlink() + && source_is_symlink && !matches!( options.overwrite, OverwriteMode::Clobber(ClobberMode::RemoveDestination) @@ -1981,7 +2059,7 @@ fn copy_file( )?; // TODO: implement something similar to gnu's lchown - if !dest.is_symlink() { + if !dest_is_symlink { // Here, to match GNU semantics, we quietly ignore an error // if a user does not have the correct ownership to modify // the permissions of a file. @@ -2130,7 +2208,7 @@ fn copy_link( if dest.is_symlink() || dest.is_file() { fs::remove_file(dest)?; } - symlink_file(&link, dest, &context_for(&link, dest), symlinked_files) + symlink_file(&link, dest, symlinked_files) } /// Generate an error message if `target` is not the correct `target_type` diff --git a/src/uucore/src/lib/features/backup_control.rs b/src/uucore/src/lib/features/backup_control.rs index 99889a6fff3..9086acb197e 100644 --- a/src/uucore/src/lib/features/backup_control.rs +++ b/src/uucore/src/lib/features/backup_control.rs @@ -354,8 +354,13 @@ pub fn determine_backup_mode(matches: &ArgMatches) -> UResult { } } else if matches.get_flag(arguments::OPT_BACKUP_NO_ARG) { // the short form of this option, -b does not accept any argument. - // Using -b is equivalent to using --backup=existing. - Ok(BackupMode::ExistingBackup) + // if VERSION_CONTROL is not set then using -b is equivalent to + // using --backup=existing. + if let Ok(method) = env::var("VERSION_CONTROL") { + match_method(&method, "$VERSION_CONTROL") + } else { + Ok(BackupMode::ExistingBackup) + } } else { // No option was present at all Ok(BackupMode::NoBackup) @@ -578,16 +583,16 @@ mod tests { assert_eq!(result, BackupMode::SimpleBackup); } - // -b ignores the "VERSION_CONTROL" environment variable + // -b doesn't ignores the "VERSION_CONTROL" environment variable #[test] - fn test_backup_mode_short_only_ignore_env() { + fn test_backup_mode_short_does_not_ignore_env() { let _dummy = TEST_MUTEX.lock().unwrap(); - env::set_var(ENV_VERSION_CONTROL, "none"); + env::set_var(ENV_VERSION_CONTROL, "numbered"); let matches = make_app().get_matches_from(vec!["command", "-b"]); let result = determine_backup_mode(&matches).unwrap(); - assert_eq!(result, BackupMode::ExistingBackup); + assert_eq!(result, BackupMode::NumberedBackup); env::remove_var(ENV_VERSION_CONTROL); } diff --git a/src/uucore/src/lib/features/fs.rs b/src/uucore/src/lib/features/fs.rs index f43c18ebd4b..73c61e0a3e4 100644 --- a/src/uucore/src/lib/features/fs.rs +++ b/src/uucore/src/lib/features/fs.rs @@ -770,6 +770,25 @@ pub mod sane_blksize { } } +/// Extracts the filename component from the given `file` path and returns it as an `Option<&str>`. +/// +/// If the `file` path contains a filename, this function returns `Some(filename)` where `filename` is +/// the extracted filename as a string slice (`&str`). If the `file` path does not have a filename +/// component or if the filename is not valid UTF-8, it returns `None`. +/// +/// # Arguments +/// +/// * `file`: A reference to a `Path` representing the file path from which to extract the filename. +/// +/// # Returns +/// +/// * `Some(filename)`: If a valid filename exists in the `file` path, where `filename` is the +/// extracted filename as a string slice (`&str`). +/// * `None`: If the `file` path does not contain a valid filename or if the filename is not valid UTF-8. +pub fn get_filename(file: &Path) -> Option<&str> { + file.file_name().and_then(|filename| filename.to_str()) +} + #[cfg(test)] mod tests { // Note this useful idiom: importing names from outer (for mod tests) scope. @@ -1006,4 +1025,9 @@ mod tests { assert_eq!(0x2000_0000, sane_blksize::sane_blksize(0x2000_0000)); assert_eq!(512, sane_blksize::sane_blksize(0x2000_0001)); } + #[test] + fn test_get_file_name() { + let file_path = PathBuf::from("~/foo.txt"); + assert!(matches!(get_filename(&file_path), Some("foo.txt"))); + } } diff --git a/tests/by-util/test_cp.rs b/tests/by-util/test_cp.rs index 1f81011dbb9..e417e40b195 100644 --- a/tests/by-util/test_cp.rs +++ b/tests/by-util/test_cp.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 (flags) reflink (fs) tmpfs (linux) rlimit Rlim NOFILE clob btrfs neve ROOTDIR USERDIR procfs outfile uufs xattrs - +// spell-checker:ignore bdfl hlsl use crate::common::util::TestScenario; #[cfg(not(windows))] use std::fs::set_permissions; @@ -3886,3 +3886,927 @@ fn test_cp_no_dereference_attributes_only_with_symlink() { "file2 content does not match expected" ); } +#[cfg(all(unix, not(target_os = "android")))] +#[cfg(test)] +/// contains the test for cp when the source and destination points to the same file +mod same_file { + + use crate::common::util::TestScenario; + + const FILE_NAME: &str = "foo"; + const SYMLINK_NAME: &str = "symlink"; + const CONTENTS: &str = "abcd"; + + // the following tests tries to copy a file to the symlink of the same file with + // various options + #[test] + fn test_same_file_from_file_to_symlink() { + for option in ["-d", "-f", "-df"] { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + at.write(FILE_NAME, CONTENTS); + at.symlink_file(FILE_NAME, SYMLINK_NAME); + scene + .ucmd() + .args(&[option, FILE_NAME, SYMLINK_NAME]) + .fails() + .stderr_contains("'foo' and 'symlink' are the same file"); + assert!(at.symlink_exists(SYMLINK_NAME)); + assert_eq!(FILE_NAME, at.resolve_link(SYMLINK_NAME)); + assert!(at.file_exists(FILE_NAME)); + assert_eq!(at.read(FILE_NAME), CONTENTS,); + } + } + + #[test] + fn test_same_file_from_file_to_symlink_with_rem_option() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + at.write(FILE_NAME, CONTENTS); + at.symlink_file(FILE_NAME, SYMLINK_NAME); + scene + .ucmd() + .args(&["--rem", FILE_NAME, SYMLINK_NAME]) + .succeeds(); + assert!(at.file_exists(SYMLINK_NAME)); + assert_eq!(at.read(FILE_NAME), CONTENTS,); + assert!(at.file_exists(FILE_NAME)); + assert_eq!(at.read(FILE_NAME), CONTENTS,); + } + + #[test] + fn test_same_file_from_file_to_symlink_with_backup_option() { + for option in ["-b", "-bd", "-bf", "-bdf"] { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + let backup = "symlink~"; + at.write(FILE_NAME, CONTENTS); + at.symlink_file(FILE_NAME, SYMLINK_NAME); + scene + .ucmd() + .args(&[option, FILE_NAME, SYMLINK_NAME]) + .succeeds(); + assert!(at.symlink_exists(backup)); + assert_eq!(FILE_NAME, at.resolve_link(backup)); + assert!(at.file_exists(SYMLINK_NAME)); + assert_eq!(at.read(SYMLINK_NAME), CONTENTS,); + assert!(at.file_exists(FILE_NAME)); + assert_eq!(at.read(FILE_NAME), CONTENTS,); + } + } + + #[test] + fn test_same_file_from_file_to_symlink_with_link_option() { + for option in ["-l", "-dl"] { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + at.write(FILE_NAME, CONTENTS); + at.symlink_file(FILE_NAME, SYMLINK_NAME); + scene + .ucmd() + .args(&[option, FILE_NAME, SYMLINK_NAME]) + .fails() + .stderr_contains("cp: cannot create hard link 'symlink' to 'foo'"); + assert!(at.symlink_exists(SYMLINK_NAME)); + assert_eq!(FILE_NAME, at.resolve_link(SYMLINK_NAME)); + assert!(at.file_exists(FILE_NAME)); + assert_eq!(at.read(FILE_NAME), CONTENTS,); + } + } + + #[test] + fn test_same_file_from_file_to_symlink_with_options_link_and_force() { + for option in ["-fl", "-dfl"] { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + at.write(FILE_NAME, CONTENTS); + at.symlink_file(FILE_NAME, SYMLINK_NAME); + scene + .ucmd() + .args(&[option, FILE_NAME, SYMLINK_NAME]) + .succeeds(); + assert!(at.file_exists(FILE_NAME)); + assert!(at.file_exists(SYMLINK_NAME)); + assert_eq!(at.read(FILE_NAME), CONTENTS,); + } + } + + #[test] + fn test_same_file_from_file_to_symlink_with_options_backup_and_link() { + for option in ["-bl", "-bdl", "-bfl", "-bdfl"] { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + let backup = "symlink~"; + at.write(FILE_NAME, CONTENTS); + at.symlink_file(FILE_NAME, SYMLINK_NAME); + scene + .ucmd() + .args(&[option, FILE_NAME, SYMLINK_NAME]) + .succeeds(); + assert!(at.file_exists(FILE_NAME)); + assert!(at.file_exists(SYMLINK_NAME)); + assert!(at.symlink_exists(backup)); + assert_eq!(FILE_NAME, at.resolve_link(backup)); + assert_eq!(at.read(FILE_NAME), CONTENTS,); + } + } + + #[test] + fn test_same_file_from_file_to_symlink_with_options_symlink() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + at.write(FILE_NAME, CONTENTS); + at.symlink_file(FILE_NAME, SYMLINK_NAME); + scene + .ucmd() + .args(&["-s", FILE_NAME, SYMLINK_NAME]) + .fails() + .stderr_contains("cp: cannot create symlink 'symlink' to 'foo'"); + assert!(at.file_exists(FILE_NAME)); + assert!(at.symlink_exists(SYMLINK_NAME)); + assert_eq!(FILE_NAME, at.resolve_link(SYMLINK_NAME)); + assert_eq!(at.read(FILE_NAME), CONTENTS,); + } + + #[test] + fn test_same_file_from_file_to_symlink_with_options_symlink_and_force() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + at.write(FILE_NAME, CONTENTS); + at.symlink_file(FILE_NAME, SYMLINK_NAME); + scene + .ucmd() + .args(&["-sf", FILE_NAME, SYMLINK_NAME]) + .succeeds(); + assert!(at.file_exists(FILE_NAME)); + assert!(at.symlink_exists(SYMLINK_NAME)); + assert_eq!(FILE_NAME, at.resolve_link(SYMLINK_NAME)); + assert_eq!(at.read(FILE_NAME), CONTENTS,); + } + // the following tests tries to copy a symlink to the file that symlink points to with + // various options + #[test] + fn test_same_file_from_symlink_to_file() { + for option in ["-d", "-f", "-df", "--rem"] { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + at.write(FILE_NAME, CONTENTS); + at.symlink_file(FILE_NAME, SYMLINK_NAME); + scene + .ucmd() + .args(&[option, SYMLINK_NAME, FILE_NAME]) + .fails() + .stderr_contains("'symlink' and 'foo' are the same file"); + assert!(at.file_exists(FILE_NAME)); + assert!(at.symlink_exists(SYMLINK_NAME)); + assert_eq!(FILE_NAME, at.resolve_link(SYMLINK_NAME)); + assert_eq!(at.read(FILE_NAME), CONTENTS,); + } + } + + #[test] + fn test_same_file_from_symlink_to_file_with_option_backup() { + for option in ["-b", "-bf"] { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + at.write(FILE_NAME, CONTENTS); + at.symlink_file(FILE_NAME, SYMLINK_NAME); + scene + .ucmd() + .args(&[option, SYMLINK_NAME, FILE_NAME]) + .fails() + .stderr_contains("'symlink' and 'foo' are the same file"); + assert!(at.file_exists(FILE_NAME)); + assert!(at.symlink_exists(SYMLINK_NAME)); + assert_eq!(FILE_NAME, at.resolve_link(SYMLINK_NAME)); + assert_eq!(at.read(FILE_NAME), CONTENTS,); + } + } + #[test] + fn test_same_file_from_symlink_to_file_with_option_backup_without_deref() { + for option in ["-bd", "-bdf"] { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + let backup = "foo~"; + at.write(FILE_NAME, CONTENTS); + at.symlink_file(FILE_NAME, SYMLINK_NAME); + scene + .ucmd() + .args(&[option, SYMLINK_NAME, FILE_NAME]) + .succeeds(); + assert!(at.file_exists(backup)); + assert!(at.symlink_exists(FILE_NAME)); + // this doesn't makes sense but this is how gnu does it + assert_eq!(FILE_NAME, at.resolve_link(FILE_NAME)); + assert_eq!(FILE_NAME, at.resolve_link(SYMLINK_NAME)); + assert_eq!(at.read(backup), CONTENTS,); + } + } + + #[test] + fn test_same_file_from_symlink_to_file_with_options_link() { + for option in ["-l", "-dl", "-fl", "-bl", "-bfl"] { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + at.write(FILE_NAME, CONTENTS); + at.symlink_file(FILE_NAME, SYMLINK_NAME); + scene + .ucmd() + .args(&[option, SYMLINK_NAME, FILE_NAME]) + .succeeds(); + assert!(at.file_exists(FILE_NAME)); + assert!(at.symlink_exists(SYMLINK_NAME)); + assert_eq!(FILE_NAME, at.resolve_link(SYMLINK_NAME)); + assert_eq!(at.read(FILE_NAME), CONTENTS,); + } + } + + #[test] + fn test_same_file_from_symlink_to_file_with_option_symlink() { + for option in ["-s", "-sf"] { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + at.write(FILE_NAME, CONTENTS); + at.symlink_file(FILE_NAME, SYMLINK_NAME); + scene + .ucmd() + .args(&[option, SYMLINK_NAME, FILE_NAME]) + .fails() + .stderr_contains("'symlink' and 'foo' are the same file"); + assert!(at.file_exists(FILE_NAME)); + assert!(at.symlink_exists(SYMLINK_NAME)); + assert_eq!(FILE_NAME, at.resolve_link(SYMLINK_NAME)); + assert_eq!(at.read(FILE_NAME), CONTENTS,); + } + } + + // the following tests tries to copy a file to the same file with various options + #[test] + fn test_same_file_from_file_to_file() { + for option in ["-d", "-f", "-df", "--rem"] { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + at.write(FILE_NAME, CONTENTS); + scene + .ucmd() + .args(&[option, FILE_NAME, FILE_NAME]) + .fails() + .stderr_contains("'foo' and 'foo' are the same file"); + assert!(at.file_exists(FILE_NAME)); + assert_eq!(at.read(FILE_NAME), CONTENTS,); + } + } + #[test] + fn test_same_file_from_file_to_file_with_backup() { + for option in ["-b", "-bd"] { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + at.write(FILE_NAME, CONTENTS); + scene + .ucmd() + .args(&[option, FILE_NAME, FILE_NAME]) + .fails() + .stderr_contains("'foo' and 'foo' are the same file"); + assert!(at.file_exists(FILE_NAME)); + assert_eq!(at.read(FILE_NAME), CONTENTS,); + } + } + + #[test] + fn test_same_file_from_file_to_file_with_options_backup_and_no_deref() { + for option in ["-bf", "-bdf"] { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + let backup = "foo~"; + at.write(FILE_NAME, CONTENTS); + scene + .ucmd() + .args(&[option, FILE_NAME, FILE_NAME]) + .succeeds(); + assert!(at.file_exists(FILE_NAME)); + assert!(at.file_exists(backup)); + assert_eq!(at.read(FILE_NAME), CONTENTS,); + assert_eq!(at.read(backup), CONTENTS,); + } + } + + #[test] + fn test_same_file_from_file_to_file_with_options_link() { + for option in ["-l", "-dl", "-fl", "-dfl", "-bl", "-bdl"] { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + let backup = "foo~"; + at.write(FILE_NAME, CONTENTS); + scene + .ucmd() + .args(&[option, FILE_NAME, FILE_NAME]) + .succeeds(); + assert!(at.file_exists(FILE_NAME)); + assert!(!at.file_exists(backup)); + assert_eq!(at.read(FILE_NAME), CONTENTS,); + } + } + + #[test] + fn test_same_file_from_file_to_file_with_options_link_and_backup_and_force() { + for option in ["-bfl", "-bdfl"] { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + let backup = "foo~"; + at.write(FILE_NAME, CONTENTS); + scene + .ucmd() + .args(&[option, FILE_NAME, FILE_NAME]) + .succeeds(); + assert!(at.file_exists(FILE_NAME)); + assert!(at.file_exists(backup)); + assert_eq!(at.read(FILE_NAME), CONTENTS,); + assert_eq!(at.read(backup), CONTENTS,); + } + } + + #[test] + fn test_same_file_from_file_to_file_with_options_symlink() { + for option in ["-s", "-sf"] { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + at.write(FILE_NAME, CONTENTS); + scene + .ucmd() + .args(&[option, FILE_NAME, FILE_NAME]) + .fails() + .stderr_contains("'foo' and 'foo' are the same file"); + assert!(at.file_exists(FILE_NAME)); + assert_eq!(at.read(FILE_NAME), CONTENTS,); + } + } + + // the following tests tries to copy a symlink that points to a file to a symlink + // that points to the same file with various options + #[test] + fn test_same_file_from_symlink_to_symlink_with_option_no_deref() { + for option in ["-d", "-df"] { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + let symlink1 = "sl1"; + let symlink2 = "sl2"; + at.write(FILE_NAME, CONTENTS); + at.symlink_file(FILE_NAME, symlink1); + at.symlink_file(FILE_NAME, symlink2); + scene.ucmd().args(&[option, symlink1, symlink2]).succeeds(); + assert!(at.file_exists(FILE_NAME)); + assert_eq!(at.read(FILE_NAME), CONTENTS,); + assert_eq!(FILE_NAME, at.resolve_link(symlink1)); + assert_eq!(FILE_NAME, at.resolve_link(symlink2)); + } + } + + #[test] + fn test_same_file_from_symlink_to_symlink_with_option_force() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + let symlink1 = "sl1"; + let symlink2 = "sl2"; + at.write(FILE_NAME, CONTENTS); + at.symlink_file(FILE_NAME, symlink1); + at.symlink_file(FILE_NAME, symlink2); + scene + .ucmd() + .args(&["-f", symlink1, symlink2]) + .fails() + .stderr_contains("'sl1' and 'sl2' are the same file"); + assert!(at.file_exists(FILE_NAME)); + assert_eq!(at.read(FILE_NAME), CONTENTS,); + assert_eq!(FILE_NAME, at.resolve_link(symlink1)); + assert_eq!(FILE_NAME, at.resolve_link(symlink2)); + } + + #[test] + fn test_same_file_from_symlink_to_symlink_with_option_rem() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + let symlink1 = "sl1"; + let symlink2 = "sl2"; + at.write(FILE_NAME, CONTENTS); + at.symlink_file(FILE_NAME, symlink1); + at.symlink_file(FILE_NAME, symlink2); + scene.ucmd().args(&["--rem", symlink1, symlink2]).succeeds(); + assert!(at.file_exists(FILE_NAME)); + assert_eq!(at.read(FILE_NAME), CONTENTS,); + assert_eq!(FILE_NAME, at.resolve_link(symlink1)); + assert!(at.file_exists(symlink2)); + assert_eq!(at.read(symlink2), CONTENTS,); + } + + #[test] + fn test_same_file_from_symlink_to_symlink_with_option_backup() { + for option in ["-b", "-bf"] { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + let symlink1 = "sl1"; + let symlink2 = "sl2"; + let backup = "sl2~"; + at.write(FILE_NAME, CONTENTS); + at.symlink_file(FILE_NAME, symlink1); + at.symlink_file(FILE_NAME, symlink2); + scene.ucmd().args(&[option, symlink1, symlink2]).succeeds(); + assert!(at.file_exists(FILE_NAME)); + assert_eq!(at.read(FILE_NAME), CONTENTS,); + assert_eq!(FILE_NAME, at.resolve_link(symlink1)); + assert!(at.file_exists(symlink2)); + assert_eq!(at.read(symlink2), CONTENTS,); + assert_eq!(FILE_NAME, at.resolve_link(backup)); + } + } + + #[test] + fn test_same_file_from_symlink_to_symlink_with_option_backup_and_no_deref() { + for option in ["-bd", "-bdf"] { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + let symlink1 = "sl1"; + let symlink2 = "sl2"; + let backup = "sl2~"; + at.write(FILE_NAME, CONTENTS); + at.symlink_file(FILE_NAME, symlink1); + at.symlink_file(FILE_NAME, symlink2); + scene.ucmd().args(&[option, symlink1, symlink2]).succeeds(); + assert!(at.file_exists(FILE_NAME)); + assert_eq!(at.read(FILE_NAME), CONTENTS,); + assert_eq!(FILE_NAME, at.resolve_link(symlink1)); + assert_eq!(FILE_NAME, at.resolve_link(symlink2)); + assert_eq!(FILE_NAME, at.resolve_link(backup)); + } + } + #[test] + fn test_same_file_from_symlink_to_symlink_with_option_link() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + let symlink1 = "sl1"; + let symlink2 = "sl2"; + at.write(FILE_NAME, CONTENTS); + at.symlink_file(FILE_NAME, symlink1); + at.symlink_file(FILE_NAME, symlink2); + scene + .ucmd() + .args(&["-l", symlink1, symlink2]) + .fails() + .stderr_contains("cannot create hard link 'sl2' to 'sl1'"); + assert!(at.file_exists(FILE_NAME)); + assert_eq!(at.read(FILE_NAME), CONTENTS,); + assert_eq!(FILE_NAME, at.resolve_link(symlink1)); + assert_eq!(FILE_NAME, at.resolve_link(symlink2)); + } + + #[test] + fn test_same_file_from_symlink_to_symlink_with_option_force_link() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + let symlink1 = "sl1"; + let symlink2 = "sl2"; + at.write(FILE_NAME, CONTENTS); + at.symlink_file(FILE_NAME, symlink1); + at.symlink_file(FILE_NAME, symlink2); + scene.ucmd().args(&["-fl", symlink1, symlink2]).succeeds(); + assert!(at.file_exists(FILE_NAME)); + assert_eq!(at.read(FILE_NAME), CONTENTS,); + assert_eq!(FILE_NAME, at.resolve_link(symlink1)); + assert!(at.file_exists(symlink2)); + assert_eq!(at.read(symlink2), CONTENTS,); + } + + #[test] + fn test_same_file_from_symlink_to_symlink_with_option_backup_and_link() { + for option in ["-bl", "-bfl"] { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + let symlink1 = "sl1"; + let symlink2 = "sl2"; + let backup = "sl2~"; + at.write(FILE_NAME, CONTENTS); + at.symlink_file(FILE_NAME, symlink1); + at.symlink_file(FILE_NAME, symlink2); + scene.ucmd().args(&[option, symlink1, symlink2]).succeeds(); + assert!(at.file_exists(FILE_NAME)); + assert_eq!(at.read(FILE_NAME), CONTENTS,); + assert_eq!(FILE_NAME, at.resolve_link(symlink1)); + assert!(at.file_exists(symlink2)); + assert_eq!(at.read(symlink2), CONTENTS,); + assert_eq!(FILE_NAME, at.resolve_link(backup)); + } + } + + #[test] + fn test_same_file_from_symlink_to_symlink_with_option_symlink() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + let symlink1 = "sl1"; + let symlink2 = "sl2"; + at.write(FILE_NAME, CONTENTS); + at.symlink_file(FILE_NAME, symlink1); + at.symlink_file(FILE_NAME, symlink2); + scene + .ucmd() + .args(&["-s", symlink1, symlink2]) + .fails() + .stderr_contains("cannot create symlink 'sl2' to 'sl1'"); + assert!(at.file_exists(FILE_NAME)); + assert_eq!(at.read(FILE_NAME), CONTENTS,); + assert_eq!(FILE_NAME, at.resolve_link(symlink1)); + assert_eq!(FILE_NAME, at.resolve_link(symlink2)); + } + + #[test] + fn test_same_file_from_symlink_to_symlink_with_option_symlink_and_force() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + let symlink1 = "sl1"; + let symlink2 = "sl2"; + at.write(FILE_NAME, CONTENTS); + at.symlink_file(FILE_NAME, symlink1); + at.symlink_file(FILE_NAME, symlink2); + scene.ucmd().args(&["-sf", symlink1, symlink2]).succeeds(); + assert!(at.file_exists(FILE_NAME)); + assert_eq!(at.read(FILE_NAME), CONTENTS,); + assert_eq!(FILE_NAME, at.resolve_link(symlink1)); + assert_eq!(symlink1, at.resolve_link(symlink2)); + } + + // the following tests tries to copy file to a hardlink of the same file with + // various options + #[test] + fn test_same_file_from_file_to_hardlink() { + for option in ["-d", "-f", "-df"] { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + let hardlink = "hardlink"; + at.write(FILE_NAME, CONTENTS); + at.hard_link(FILE_NAME, hardlink); + + scene + .ucmd() + .args(&[option, FILE_NAME, hardlink]) + .fails() + .stderr_contains("'foo' and 'hardlink' are the same file"); + assert!(at.file_exists(FILE_NAME)); + assert!(at.file_exists(hardlink)); + assert_eq!(at.read(FILE_NAME), CONTENTS,); + } + } + + #[test] + fn test_same_file_from_file_to_hardlink_with_option_rem() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + let hardlink = "hardlink"; + at.write(FILE_NAME, CONTENTS); + at.hard_link(FILE_NAME, hardlink); + scene + .ucmd() + .args(&["--rem", FILE_NAME, hardlink]) + .succeeds(); + assert!(at.file_exists(FILE_NAME)); + assert!(at.file_exists(hardlink)); + assert_eq!(at.read(FILE_NAME), CONTENTS,); + } + + #[test] + fn test_same_file_from_file_to_hardlink_with_option_backup() { + for option in ["-b", "-bd", "-bf", "-bdf"] { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + let hardlink = "hardlink"; + let backup = "hardlink~"; + at.write(FILE_NAME, CONTENTS); + at.hard_link(FILE_NAME, hardlink); + scene.ucmd().args(&[option, FILE_NAME, hardlink]).succeeds(); + assert!(at.file_exists(FILE_NAME)); + assert!(at.file_exists(hardlink)); + assert!(at.file_exists(backup)); + assert_eq!(at.read(FILE_NAME), CONTENTS,); + } + } + + #[test] + fn test_same_file_from_file_to_hardlink_with_option_link() { + for option in ["-l", "-dl", "-fl", "-dfl", "-bl", "-bdl", "-bfl", "-bdfl"] { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + let hardlink = "hardlink"; + at.write(FILE_NAME, CONTENTS); + at.hard_link(FILE_NAME, hardlink); + scene.ucmd().args(&[option, FILE_NAME, hardlink]).succeeds(); + assert!(at.file_exists(FILE_NAME)); + assert!(at.file_exists(hardlink)); + assert_eq!(at.read(FILE_NAME), CONTENTS,); + } + } + + #[test] + fn test_same_file_from_file_to_hardlink_with_option_symlink() { + for option in ["-s", "-sf"] { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + let hardlink = "hardlink"; + at.write(FILE_NAME, CONTENTS); + at.hard_link(FILE_NAME, hardlink); + scene + .ucmd() + .args(&[option, FILE_NAME, hardlink]) + .fails() + .stderr_contains("'foo' and 'hardlink' are the same file"); + assert!(at.file_exists(FILE_NAME)); + assert!(at.file_exists(hardlink)); + assert_eq!(at.read(FILE_NAME), CONTENTS,); + } + } + + // the following tests tries to copy symlink to a hardlink of the same symlink with + // various options + #[test] + fn test_same_file_from_hard_link_of_symlink_to_symlink() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + let hardlink_to_symlink = "hlsl"; + at.write(FILE_NAME, CONTENTS); + at.symlink_file(FILE_NAME, SYMLINK_NAME); + at.hard_link(SYMLINK_NAME, hardlink_to_symlink); + scene + .ucmd() + .args(&[hardlink_to_symlink, SYMLINK_NAME]) + .fails() + .stderr_contains("cp: 'hlsl' and 'symlink' are the same file"); + assert!(at.file_exists(FILE_NAME)); + assert!(at.symlink_exists(SYMLINK_NAME)); + assert!(at.symlink_exists(hardlink_to_symlink)); + assert_eq!(FILE_NAME, at.resolve_link(SYMLINK_NAME)); + assert_eq!(FILE_NAME, at.resolve_link(hardlink_to_symlink)); + assert_eq!(at.read(FILE_NAME), CONTENTS,); + } + + #[test] + fn test_same_file_from_hard_link_of_symlink_to_symlink_with_option_force() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + let hardlink_to_symlink = "hlsl"; + at.write(FILE_NAME, CONTENTS); + at.symlink_file(FILE_NAME, SYMLINK_NAME); + at.hard_link(SYMLINK_NAME, hardlink_to_symlink); + scene + .ucmd() + .args(&["-f", hardlink_to_symlink, SYMLINK_NAME]) + .fails() + .stderr_contains("cp: 'hlsl' and 'symlink' are the same file"); + assert!(at.file_exists(FILE_NAME)); + assert!(at.symlink_exists(SYMLINK_NAME)); + assert!(at.symlink_exists(hardlink_to_symlink)); + assert_eq!(FILE_NAME, at.resolve_link(SYMLINK_NAME)); + assert_eq!(FILE_NAME, at.resolve_link(hardlink_to_symlink)); + assert_eq!(at.read(FILE_NAME), CONTENTS,); + } + + #[test] + fn test_same_file_from_hard_link_of_symlink_to_symlink_with_option_no_deref() { + for option in ["-d", "-df"] { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + let hardlink_to_symlink = "hlsl"; + at.write(FILE_NAME, CONTENTS); + at.symlink_file(FILE_NAME, SYMLINK_NAME); + at.hard_link(SYMLINK_NAME, hardlink_to_symlink); + scene + .ucmd() + .args(&[option, hardlink_to_symlink, SYMLINK_NAME]) + .succeeds(); + assert!(at.file_exists(FILE_NAME)); + assert!(at.symlink_exists(SYMLINK_NAME)); + assert!(at.symlink_exists(hardlink_to_symlink)); + assert_eq!(FILE_NAME, at.resolve_link(SYMLINK_NAME)); + assert_eq!(FILE_NAME, at.resolve_link(hardlink_to_symlink)); + assert_eq!(at.read(FILE_NAME), CONTENTS,); + } + } + + #[test] + fn test_same_file_from_hard_link_of_symlink_to_symlink_with_option_rem() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + let hardlink_to_symlink = "hlsl"; + at.write(FILE_NAME, CONTENTS); + at.symlink_file(FILE_NAME, SYMLINK_NAME); + at.hard_link(SYMLINK_NAME, hardlink_to_symlink); + scene + .ucmd() + .args(&["--rem", hardlink_to_symlink, SYMLINK_NAME]) + .succeeds(); + assert!(at.file_exists(FILE_NAME)); + assert!(at.file_exists(SYMLINK_NAME)); + assert!(!at.symlink_exists(SYMLINK_NAME)); + assert!(at.symlink_exists(hardlink_to_symlink)); + assert_eq!(FILE_NAME, at.resolve_link(hardlink_to_symlink)); + assert_eq!(at.read(FILE_NAME), CONTENTS,); + assert_eq!(at.read(SYMLINK_NAME), CONTENTS,); + } + + #[test] + fn test_same_file_from_hard_link_of_symlink_to_symlink_with_option_backup() { + for option in ["-b", "-bf"] { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + let backup = "symlink~"; + let hardlink_to_symlink = "hlsl"; + at.write(FILE_NAME, CONTENTS); + at.symlink_file(FILE_NAME, SYMLINK_NAME); + at.hard_link(SYMLINK_NAME, hardlink_to_symlink); + scene + .ucmd() + .args(&[option, hardlink_to_symlink, SYMLINK_NAME]) + .succeeds(); + assert!(at.file_exists(FILE_NAME)); + assert!(at.file_exists(SYMLINK_NAME)); + assert!(!at.symlink_exists(SYMLINK_NAME)); + assert!(at.symlink_exists(hardlink_to_symlink)); + assert_eq!(FILE_NAME, at.resolve_link(hardlink_to_symlink)); + assert!(at.symlink_exists(backup)); + assert_eq!(FILE_NAME, at.resolve_link(backup)); + assert_eq!(at.read(FILE_NAME), CONTENTS,); + assert_eq!(at.read(SYMLINK_NAME), CONTENTS,); + } + } + + #[test] + fn test_same_file_from_hard_link_of_symlink_to_symlink_with_option_backup_and_no_deref() { + for option in ["-bd", "-bdf"] { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + let backup = "symlink~"; + let hardlink_to_symlink = "hlsl"; + at.write(FILE_NAME, CONTENTS); + at.symlink_file(FILE_NAME, SYMLINK_NAME); + at.hard_link(SYMLINK_NAME, hardlink_to_symlink); + scene + .ucmd() + .args(&[option, hardlink_to_symlink, SYMLINK_NAME]) + .succeeds(); + assert!(at.file_exists(FILE_NAME)); + assert!(at.symlink_exists(SYMLINK_NAME)); + assert_eq!(FILE_NAME, at.resolve_link(SYMLINK_NAME)); + assert!(at.symlink_exists(hardlink_to_symlink)); + assert_eq!(FILE_NAME, at.resolve_link(hardlink_to_symlink)); + assert!(at.symlink_exists(backup)); + assert_eq!(FILE_NAME, at.resolve_link(backup)); + assert_eq!(at.read(FILE_NAME), CONTENTS,); + } + } + + #[test] + fn test_same_file_from_hard_link_of_symlink_to_symlink_with_option_link() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + let hardlink_to_symlink = "hlsl"; + at.write(FILE_NAME, CONTENTS); + at.symlink_file(FILE_NAME, SYMLINK_NAME); + at.hard_link(SYMLINK_NAME, hardlink_to_symlink); + scene + .ucmd() + .args(&["-l", hardlink_to_symlink, SYMLINK_NAME]) + .fails() + .stderr_contains("cannot create hard link 'symlink' to 'hlsl'"); + assert!(at.file_exists(FILE_NAME)); + assert!(at.symlink_exists(SYMLINK_NAME)); + assert!(at.symlink_exists(hardlink_to_symlink)); + assert_eq!(FILE_NAME, at.resolve_link(SYMLINK_NAME)); + assert_eq!(FILE_NAME, at.resolve_link(hardlink_to_symlink)); + assert_eq!(at.read(FILE_NAME), CONTENTS,); + } + + #[test] + fn test_same_file_from_hard_link_of_symlink_to_symlink_with_option_link_and_no_deref() { + for option in ["-dl", "-dfl"] { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + let hardlink_to_symlink = "hlsl"; + at.write(FILE_NAME, CONTENTS); + at.symlink_file(FILE_NAME, SYMLINK_NAME); + at.hard_link(SYMLINK_NAME, hardlink_to_symlink); + scene + .ucmd() + .args(&[option, hardlink_to_symlink, SYMLINK_NAME]) + .succeeds(); + assert!(at.file_exists(FILE_NAME)); + assert!(at.symlink_exists(SYMLINK_NAME)); + assert!(at.symlink_exists(hardlink_to_symlink)); + assert_eq!(FILE_NAME, at.resolve_link(SYMLINK_NAME)); + assert_eq!(FILE_NAME, at.resolve_link(hardlink_to_symlink)); + assert_eq!(at.read(FILE_NAME), CONTENTS,); + } + } + + #[test] + fn test_same_file_from_hard_link_of_symlink_to_symlink_with_option_link_and_force() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + let hardlink_to_symlink = "hlsl"; + at.write(FILE_NAME, CONTENTS); + at.symlink_file(FILE_NAME, SYMLINK_NAME); + at.hard_link(SYMLINK_NAME, hardlink_to_symlink); + scene + .ucmd() + .args(&["-fl", hardlink_to_symlink, SYMLINK_NAME]) + .succeeds(); + assert!(at.file_exists(FILE_NAME)); + assert!(at.file_exists(SYMLINK_NAME)); + assert!(!at.symlink_exists(SYMLINK_NAME)); + assert!(at.symlink_exists(hardlink_to_symlink)); + assert_eq!(FILE_NAME, at.resolve_link(hardlink_to_symlink)); + assert_eq!(at.read(FILE_NAME), CONTENTS,); + } + + #[test] + fn test_same_file_from_hard_link_of_symlink_to_symlink_with_option_link_and_backup() { + for option in ["-bl", "-bfl"] { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + let backup = "symlink~"; + let hardlink_to_symlink = "hlsl"; + at.write(FILE_NAME, CONTENTS); + at.symlink_file(FILE_NAME, SYMLINK_NAME); + at.hard_link(SYMLINK_NAME, hardlink_to_symlink); + scene + .ucmd() + .args(&[option, hardlink_to_symlink, SYMLINK_NAME]) + .succeeds(); + assert!(at.file_exists(FILE_NAME)); + assert!(at.file_exists(SYMLINK_NAME)); + assert!(!at.symlink_exists(SYMLINK_NAME)); + assert!(at.symlink_exists(hardlink_to_symlink)); + assert_eq!(FILE_NAME, at.resolve_link(hardlink_to_symlink)); + assert!(at.symlink_exists(backup)); + assert_eq!(FILE_NAME, at.resolve_link(backup)); + assert_eq!(at.read(FILE_NAME), CONTENTS,); + } + } + + #[test] + fn test_same_file_from_hard_link_of_symlink_to_symlink_with_options_backup_link_no_deref() { + for option in ["-bdl", "-bdfl"] { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + let hardlink_to_symlink = "hlsl"; + at.write(FILE_NAME, CONTENTS); + at.symlink_file(FILE_NAME, SYMLINK_NAME); + at.hard_link(SYMLINK_NAME, hardlink_to_symlink); + scene + .ucmd() + .args(&[option, hardlink_to_symlink, SYMLINK_NAME]) + .succeeds(); + assert!(at.file_exists(FILE_NAME)); + assert!(at.symlink_exists(SYMLINK_NAME)); + assert!(at.symlink_exists(hardlink_to_symlink)); + assert_eq!(FILE_NAME, at.resolve_link(SYMLINK_NAME)); + assert_eq!(FILE_NAME, at.resolve_link(hardlink_to_symlink)); + assert_eq!(at.read(FILE_NAME), CONTENTS,); + } + } + + #[test] + fn test_same_file_from_hard_link_of_symlink_to_symlink_with_option_symlink() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + let hardlink_to_symlink = "hlsl"; + at.write(FILE_NAME, CONTENTS); + at.symlink_file(FILE_NAME, SYMLINK_NAME); + at.hard_link(SYMLINK_NAME, hardlink_to_symlink); + scene + .ucmd() + .args(&["-s", hardlink_to_symlink, SYMLINK_NAME]) + .fails() + .stderr_contains("cannot create symlink 'symlink' to 'hlsl'"); + assert!(at.file_exists(FILE_NAME)); + assert!(at.symlink_exists(SYMLINK_NAME)); + assert!(at.symlink_exists(hardlink_to_symlink)); + assert_eq!(FILE_NAME, at.resolve_link(SYMLINK_NAME)); + assert_eq!(FILE_NAME, at.resolve_link(hardlink_to_symlink)); + assert_eq!(at.read(FILE_NAME), CONTENTS,); + } + + #[test] + fn test_same_file_from_hard_link_of_symlink_to_symlink_with_option_symlink_and_force() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + let hardlink_to_symlink = "hlsl"; + at.write(FILE_NAME, CONTENTS); + at.symlink_file(FILE_NAME, SYMLINK_NAME); + at.hard_link(SYMLINK_NAME, hardlink_to_symlink); + scene + .ucmd() + .args(&["-sf", hardlink_to_symlink, SYMLINK_NAME]) + .succeeds(); + assert!(at.file_exists(FILE_NAME)); + assert!(at.symlink_exists(SYMLINK_NAME)); + assert!(at.symlink_exists(hardlink_to_symlink)); + assert_eq!(hardlink_to_symlink, at.resolve_link(SYMLINK_NAME)); + assert_eq!(FILE_NAME, at.resolve_link(hardlink_to_symlink)); + assert_eq!(at.read(FILE_NAME), CONTENTS,); + } +} From d202baba97d05d471d056d7890f3de5de03d6177 Mon Sep 17 00:00:00 2001 From: Ulrich Hornung Date: Sat, 20 Apr 2024 23:29:26 +0200 Subject: [PATCH 125/144] use num_prime for factorisation to fix gnu test --- Cargo.lock | 101 ++++- Cargo.toml | 2 + deny.toml | 2 + src/uu/factor/Cargo.toml | 4 +- src/uu/factor/build.rs | 103 ----- src/uu/factor/sieve.rs | 215 ---------- src/uu/factor/src/cli.rs | 121 ------ src/uu/factor/src/factor.rs | 378 +++++------------- src/uu/factor/src/miller_rabin.rs | 222 ---------- src/uu/factor/src/numeric/gcd.rs | 126 ------ src/uu/factor/src/numeric/mod.rs | 15 - src/uu/factor/src/numeric/modular_inverse.rs | 84 ---- src/uu/factor/src/numeric/montgomery.rs | 246 ------------ src/uu/factor/src/numeric/traits.rs | 89 ----- src/uu/factor/src/rho.rs | 43 -- src/uu/factor/src/table.rs | 98 ----- tests/benches/factor/Cargo.toml | 6 +- tests/benches/factor/benches/gcd.rs | 33 -- tests/benches/factor/benches/table.rs | 19 +- tests/by-util/test_factor.rs | 58 ++- util/build-gnu.sh | 2 +- util/gnu-patches/tests_factor_factor.pl.patch | 21 + 22 files changed, 298 insertions(+), 1690 deletions(-) delete mode 100644 src/uu/factor/build.rs delete mode 100644 src/uu/factor/sieve.rs delete mode 100644 src/uu/factor/src/cli.rs delete mode 100644 src/uu/factor/src/miller_rabin.rs delete mode 100644 src/uu/factor/src/numeric/gcd.rs delete mode 100644 src/uu/factor/src/numeric/mod.rs delete mode 100644 src/uu/factor/src/numeric/modular_inverse.rs delete mode 100644 src/uu/factor/src/numeric/montgomery.rs delete mode 100644 src/uu/factor/src/numeric/traits.rs delete mode 100644 src/uu/factor/src/rho.rs delete mode 100644 src/uu/factor/src/table.rs delete mode 100644 tests/benches/factor/benches/gcd.rs create mode 100644 util/gnu-patches/tests_factor_factor.pl.patch diff --git a/Cargo.lock b/Cargo.lock index 2704dfaa0a8..f015fd0ebcd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8,6 +8,17 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +[[package]] +name = "ahash" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "891477e0c6a8957309ee5c45a6368af3ae14bb510732d2684ffa19af310920f9" +dependencies = [ + "getrandom", + "once_cell", + "version_check", +] + [[package]] name = "aho-corasick" version = "1.0.4" @@ -162,6 +173,18 @@ version = "2.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf" +[[package]] +name = "bitvec" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" +dependencies = [ + "funty", + "radium", + "tap", + "wyz", +] + [[package]] name = "blake2b_simd" version = "1.0.2" @@ -391,6 +414,7 @@ dependencies = [ "hex-literal", "libc", "nix", + "num-prime", "once_cell", "phf", "phf_codegen", @@ -887,6 +911,12 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "76a889e633afd839fb5b04fe53adfd588cefe518e71ec8d3c929698c6daf2acd" +[[package]] +name = "funty" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" + [[package]] name = "futures" version = "0.3.28" @@ -1025,6 +1055,15 @@ dependencies = [ "crunchy", ] +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +dependencies = [ + "ahash", +] + [[package]] name = "hashbrown" version = "0.14.3" @@ -1241,6 +1280,15 @@ version = "0.4.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" +[[package]] +name = "lru" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999beba7b6e8345721bd280141ed958096a2e4abdf74f67ff4ce49b4b54e47a" +dependencies = [ + "hashbrown 0.12.3", +] + [[package]] name = "lscolors" version = "0.16.0" @@ -1360,6 +1408,7 @@ dependencies = [ "autocfg", "num-integer", "num-traits", + "rand", ] [[package]] @@ -1372,6 +1421,33 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-modular" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64a5fe11d4135c3bcdf3a95b18b194afa9608a5f6ff034f5d857bc9a27fb0119" +dependencies = [ + "num-bigint", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-prime" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f4e3bc495f6e95bc15a6c0c55ac00421504a5a43d09e3cc455d1fea7015581d" +dependencies = [ + "bitvec", + "either", + "lru", + "num-bigint", + "num-integer", + "num-modular", + "num-traits", + "rand", +] + [[package]] name = "num-traits" version = "0.2.18" @@ -1431,7 +1507,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49203cdcae0030493bad186b28da2fa25645fa276a51b6fec8010d281e02ef79" dependencies = [ "dlv-list", - "hashbrown", + "hashbrown 0.14.3", ] [[package]] @@ -1628,6 +1704,12 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "radium" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" + [[package]] name = "rand" version = "0.8.5" @@ -2058,6 +2140,12 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + [[package]] name = "tempfile" version = "3.10.1" @@ -2478,6 +2566,8 @@ version = "0.0.26" dependencies = [ "clap", "coz", + "num-bigint", + "num-prime", "num-traits", "quickcheck", "rand", @@ -3613,6 +3703,15 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" +[[package]] +name = "wyz" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" +dependencies = [ + "tap", +] + [[package]] name = "xattr" version = "1.3.1" diff --git a/Cargo.toml b/Cargo.toml index 1e32bcd1bfa..ee1a1876ffc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -298,6 +298,7 @@ nix = { version = "0.28", default-features = false } nom = "7.1.3" notify = { version = "=6.0.1", features = ["macos_kqueue"] } num-bigint = "0.4.4" +num-prime = "0.4.3" num-traits = "0.2.18" number_prefix = "0.4" once_cell = "1.19.0" @@ -480,6 +481,7 @@ chrono = { workspace = true } filetime = { workspace = true } glob = { workspace = true } libc = { workspace = true } +num-prime = { workspace = true } pretty_assertions = "1" rand = { workspace = true } regex = { workspace = true } diff --git a/deny.toml b/deny.toml index 943fcdfa918..e1affa560fe 100644 --- a/deny.toml +++ b/deny.toml @@ -101,6 +101,8 @@ skip = [ { name = "terminal_size", version = "0.2.6" }, # filetime, parking_lot_core { name = "redox_syscall", version = "0.4.1" }, + # num-prime, rust-ini + { name = "hashbrown", version = "0.12.3" }, ] # spell-checker: enable diff --git a/src/uu/factor/Cargo.toml b/src/uu/factor/Cargo.toml index 159f8e8d355..98b04cddcc0 100644 --- a/src/uu/factor/Cargo.toml +++ b/src/uu/factor/Cargo.toml @@ -21,6 +21,8 @@ num-traits = { workspace = true } rand = { workspace = true } smallvec = { workspace = true } uucore = { workspace = true } +num-bigint = { workspace = true } +num-prime = { workspace = true } [dev-dependencies] quickcheck = "1.0.3" @@ -30,4 +32,4 @@ name = "factor" path = "src/main.rs" [lib] -path = "src/cli.rs" +path = "src/factor.rs" diff --git a/src/uu/factor/build.rs b/src/uu/factor/build.rs deleted file mode 100644 index ac22a5566d5..00000000000 --- a/src/uu/factor/build.rs +++ /dev/null @@ -1,103 +0,0 @@ -// 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. - -//! Generate a table of the multiplicative inverses of `p_i` mod 2^64 -//! for the first 1027 odd primes (all 13 bit and smaller primes). -//! You can supply a command line argument to override the default -//! value of 1027 for the number of entries in the table. -//! -//! 2 has no multiplicative inverse mode 2^64 because 2 | 2^64, -//! and in any case divisibility by two is trivial by checking the LSB. - -#![cfg_attr(test, allow(dead_code))] - -use std::env::{self, args}; -use std::fs::File; -use std::io::Write; -use std::path::Path; - -use self::sieve::Sieve; - -#[cfg(test)] -use miller_rabin::is_prime; - -#[path = "src/numeric/modular_inverse.rs"] -mod modular_inverse; -#[allow(unused_imports)] // imports there are used, but invisible from build.rs -#[path = "src/numeric/traits.rs"] -mod traits; -use modular_inverse::modular_inverse; - -mod sieve; - -#[cfg_attr(test, allow(dead_code))] -fn main() { - let out_dir = env::var("OUT_DIR").unwrap(); - let mut file = File::create(Path::new(&out_dir).join("prime_table.rs")).unwrap(); - - // By default, we print the multiplicative inverses mod 2^64 of the first 1k primes - const DEFAULT_SIZE: usize = 320; - let n = args() - .nth(1) - .and_then(|s| s.parse::().ok()) - .unwrap_or(DEFAULT_SIZE); - - write!(file, "{PREAMBLE}").unwrap(); - let mut cols = 3; - - // we want a total of n + 1 values - let mut primes = Sieve::odd_primes().take(n + 1); - - // in each iteration of the for loop, we use the value yielded - // by the previous iteration. This leaves one value left at the - // end, which we call NEXT_PRIME. - let mut x = primes.next().unwrap(); - for next in primes { - // format the table - let output = format!("({}, {}, {}),", x, modular_inverse(x), std::u64::MAX / x); - if cols + output.len() > MAX_WIDTH { - write!(file, "\n {output}").unwrap(); - cols = 4 + output.len(); - } else { - write!(file, " {output}").unwrap(); - cols += 1 + output.len(); - } - - x = next; - } - - write!( - file, - "\n];\n\n#[allow(dead_code)]\npub const NEXT_PRIME: u64 = {x};\n" - ) - .unwrap(); -} - -#[test] -fn test_generator_is_prime() { - assert_eq!(Sieve::odd_primes.take(10_000).all(is_prime)); -} - -#[test] -fn test_generator_10001() { - let prime_10001 = Sieve::primes().skip(10_000).next(); - assert_eq!(prime_10001, Some(104_743)); -} - -const MAX_WIDTH: usize = 102; -const PREAMBLE: &str = r"/* -* 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. -*/ - -// *** NOTE: this file was automatically generated. -// Please do not edit by hand. Instead, modify and -// re-run src/factor/gen_tables.rs. - -#[allow(clippy::unreadable_literal)] -pub const PRIME_INVERSIONS_U64: &[(u64, u64, u64)] = &[ - "; diff --git a/src/uu/factor/sieve.rs b/src/uu/factor/sieve.rs deleted file mode 100644 index e2211ce051a..00000000000 --- a/src/uu/factor/sieve.rs +++ /dev/null @@ -1,215 +0,0 @@ -// This file is part of the uutils coreutils package. -// -// For the full copyright and license information, please view the LICENSE -// file that was distributed with this source code. - -// spell-checker:ignore (ToDO) filts, minidx, minkey paridx - -use std::iter::{Chain, Copied, Cycle}; -use std::slice::Iter; - -/// A lazy Sieve of Eratosthenes. -/// -/// This is a reasonably efficient implementation based on -/// O'Neill, M. E. "[The Genuine Sieve of Eratosthenes.](http://dx.doi.org/10.1017%2FS0956796808007004)" -/// Journal of Functional Programming, Volume 19, Issue 1, 2009, pp. 95--106. -#[derive(Default)] -pub struct Sieve { - inner: Wheel, - filts: PrimeHeap, -} - -impl Iterator for Sieve { - type Item = u64; - - #[inline] - fn size_hint(&self) -> (usize, Option) { - self.inner.size_hint() - } - - #[inline] - fn next(&mut self) -> Option { - for n in &mut self.inner { - let mut prime = true; - while let Some((next, inc)) = self.filts.peek() { - // need to keep checking the min element of the heap - // until we've found an element that's greater than n - if next > n { - break; // next heap element is bigger than n - } - - if next == n { - // n == next, and is composite. - prime = false; - } - // Increment the element in the prime heap. - self.filts.replace((next + inc, inc)); - } - - if prime { - // this is a prime; add it to the heap - self.filts.insert(n); - return Some(n); - } - } - None - } -} - -impl Sieve { - fn new() -> Self { - Self::default() - } - - #[allow(dead_code)] - #[inline] - pub fn primes() -> PrimeSieve { - INIT_PRIMES.iter().copied().chain(Self::new()) - } - - #[allow(dead_code)] - #[inline] - pub fn odd_primes() -> PrimeSieve { - INIT_PRIMES[1..].iter().copied().chain(Self::new()) - } -} - -pub type PrimeSieve = Chain>, Sieve>; - -/// An iterator that generates an infinite list of numbers that are -/// not divisible by any of 2, 3, 5, or 7. -struct Wheel { - next: u64, - increment: Cycle>, -} - -impl Iterator for Wheel { - type Item = u64; - - #[inline] - fn size_hint(&self) -> (usize, Option) { - (1, None) - } - - #[inline] - fn next(&mut self) -> Option { - let increment = self.increment.next().unwrap(); // infinite iterator, no check necessary - let ret = self.next; - self.next = ret + increment; - Some(ret) - } -} - -impl Wheel { - #[inline] - fn new() -> Self { - Self { - next: 11u64, - increment: WHEEL_INCS.iter().cycle(), - } - } -} - -impl Default for Wheel { - fn default() -> Self { - Self::new() - } -} - -/// The increments of a wheel of circumference 210 -/// (i.e., a wheel that skips all multiples of 2, 3, 5, 7) -const WHEEL_INCS: &[u64] = &[ - 2, 4, 2, 4, 6, 2, 6, 4, 2, 4, 6, 6, 2, 6, 4, 2, 6, 4, 6, 8, 4, 2, 4, 2, 4, 8, 6, 4, 6, 2, 4, 6, - 2, 6, 6, 4, 2, 4, 6, 2, 6, 4, 2, 4, 2, 10, 2, 10, -]; -const INIT_PRIMES: &[u64] = &[2, 3, 5, 7]; - -/// A min-heap of "infinite lists" of prime multiples, where a list is -/// represented as (head, increment). -#[derive(Debug, Default)] -struct PrimeHeap { - data: Vec<(u64, u64)>, -} - -impl PrimeHeap { - fn peek(&self) -> Option<(u64, u64)> { - if let Some(&(x, y)) = self.data.first() { - Some((x, y)) - } else { - None - } - } - - fn insert(&mut self, next: u64) { - let mut idx = self.data.len(); - let key = next * next; - - let item = (key, next); - self.data.push(item); - loop { - // break if we've bubbled to the top - if idx == 0 { - break; - } - - let paridx = (idx - 1) / 2; - let (k, _) = self.data[paridx]; - if key < k { - // bubble up, found a smaller key - self.data.swap(idx, paridx); - idx = paridx; - } else { - // otherwise, parent is smaller, so we're done - break; - } - } - } - - fn remove(&mut self) -> (u64, u64) { - let ret = self.data.swap_remove(0); - - let mut idx = 0; - let len = self.data.len(); - let (key, _) = self.data[0]; - loop { - let child1 = 2 * idx + 1; - let child2 = 2 * idx + 2; - - // no more children - if child1 >= len { - break; - } - - // find lesser child - let (c1key, _) = self.data[child1]; - let (minidx, minkey) = if child2 >= len { - (child1, c1key) - } else { - let (c2key, _) = self.data[child2]; - if c1key < c2key { - (child1, c1key) - } else { - (child2, c2key) - } - }; - - if minkey < key { - self.data.swap(minidx, idx); - idx = minidx; - continue; - } - - // smaller than both children, so done - break; - } - - ret - } - - /// More efficient than inserting and removing in two steps - /// because we save one traversal of the heap. - fn replace(&mut self, next: (u64, u64)) -> (u64, u64) { - self.data.push(next); - self.remove() - } -} diff --git a/src/uu/factor/src/cli.rs b/src/uu/factor/src/cli.rs deleted file mode 100644 index 62a7efa6d7e..00000000000 --- a/src/uu/factor/src/cli.rs +++ /dev/null @@ -1,121 +0,0 @@ -// This file is part of the uutils coreutils package. -// -// For the full copyright and license information, please view the LICENSE -// file that was distributed with this source code. - -use std::io::BufRead; -use std::io::{self, stdin, stdout, Write}; - -mod factor; -use clap::{crate_version, Arg, ArgAction, Command}; -pub use factor::*; -use uucore::display::Quotable; -use uucore::error::{set_exit_code, FromIo, UResult}; -use uucore::{format_usage, help_about, help_usage, show_error, show_warning}; - -mod miller_rabin; -pub mod numeric; -mod rho; -pub mod table; - -const ABOUT: &str = help_about!("factor.md"); -const USAGE: &str = help_usage!("factor.md"); - -mod options { - pub static EXPONENTS: &str = "exponents"; - pub static HELP: &str = "help"; - pub static NUMBER: &str = "NUMBER"; -} - -fn print_factors_str( - num_str: &str, - w: &mut io::BufWriter, - print_exponents: bool, -) -> io::Result<()> { - let x = match num_str.trim().parse::() { - Ok(x) => x, - Err(e) => { - // We return Ok() instead of Err(), because it's non-fatal and we should try the next - // number. - show_warning!("{}: {}", num_str.maybe_quote(), e); - set_exit_code(1); - return Ok(()); - } - }; - - // If print_exponents is true, use the alternate format specifier {:#} from fmt to print the factors - // of x in the form of p^e. - if print_exponents { - writeln!(w, "{}:{:#}", x, factor(x))?; - } else { - writeln!(w, "{}:{}", x, factor(x))?; - } - w.flush() -} - -#[uucore::main] -pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let matches = uu_app().try_get_matches_from(args)?; - - // If matches find --exponents flag than variable print_exponents is true and p^e output format will be used. - let print_exponents = matches.get_flag(options::EXPONENTS); - - let stdout = stdout(); - // We use a smaller buffer here to pass a gnu test. 4KiB appears to be the default pipe size for bash. - let mut w = io::BufWriter::with_capacity(4 * 1024, stdout.lock()); - - if let Some(values) = matches.get_many::(options::NUMBER) { - for number in values { - print_factors_str(number, &mut w, print_exponents) - .map_err_context(|| "write error".into())?; - } - } else { - let stdin = stdin(); - let lines = stdin.lock().lines(); - for line in lines { - match line { - Ok(line) => { - for number in line.split_whitespace() { - print_factors_str(number, &mut w, print_exponents) - .map_err_context(|| "write error".into())?; - } - } - Err(e) => { - set_exit_code(1); - show_error!("error reading input: {}", e); - return Ok(()); - } - } - } - } - - if let Err(e) = w.flush() { - show_error!("{}", e); - } - - Ok(()) -} - -pub fn uu_app() -> Command { - Command::new(uucore::util_name()) - .version(crate_version!()) - .about(ABOUT) - .override_usage(format_usage(USAGE)) - .infer_long_args(true) - .disable_help_flag(true) - .args_override_self(true) - .arg(Arg::new(options::NUMBER).action(ArgAction::Append)) - .arg( - Arg::new(options::EXPONENTS) - .short('h') - .long(options::EXPONENTS) - .help("Print factors in the form p^e") - .action(ArgAction::SetTrue), - ) - .arg( - Arg::new(options::HELP) - .long(options::HELP) - .help("Print help information.") - .action(ArgAction::Help), - ) -} diff --git a/src/uu/factor/src/factor.rs b/src/uu/factor/src/factor.rs index 7c5097b5522..b56dbf3d108 100644 --- a/src/uu/factor/src/factor.rs +++ b/src/uu/factor/src/factor.rs @@ -3,302 +3,142 @@ // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. -#![allow(clippy::items_after_test_module)] -use smallvec::SmallVec; -use std::cell::RefCell; -use std::fmt; - -use crate::numeric::{Arithmetic, Montgomery}; -use crate::{miller_rabin, rho, table}; - -type Exponent = u8; - -#[derive(Clone, Debug, Default)] -struct Decomposition(SmallVec<[(u64, Exponent); NUM_FACTORS_INLINE]>); - -// spell-checker:ignore (names) Erdős–Kac * Erdős Kac -// The number of factors to inline directly into a `Decomposition` object. -// As a consequence of the Erdős–Kac theorem, the average number of prime factors -// of integers < 10²⁵ ≃ 2⁸³ is 4, so we can use a slightly higher value. -const NUM_FACTORS_INLINE: usize = 5; - -impl Decomposition { - fn one() -> Self { - Self::default() - } - - fn add(&mut self, factor: u64, exp: Exponent) { - debug_assert!(exp > 0); - - if let Some((_, e)) = self.0.iter_mut().find(|(f, _)| *f == factor) { - *e += exp; - } else { - self.0.push((factor, exp)); - } - } - - #[cfg(test)] - fn product(&self) -> u64 { - self.0 - .iter() - .fold(1, |acc, (p, exp)| acc * p.pow(*exp as u32)) - } - - fn get(&self, p: u64) -> Option<&(u64, u8)> { - self.0.iter().find(|(q, _)| *q == p) - } -} - -impl PartialEq for Decomposition { - fn eq(&self, other: &Self) -> bool { - for p in &self.0 { - if other.get(p.0) != Some(p) { - return false; - } - } - - for p in &other.0 { - if self.get(p.0) != Some(p) { - return false; - } - } - - true - } +// spell-checker:ignore funcs + +use std::collections::BTreeMap; +use std::io::BufRead; +use std::io::{self, stdin, stdout, Write}; + +use clap::{crate_version, Arg, ArgAction, Command}; +use num_bigint::BigUint; +use num_traits::FromPrimitive; +use uucore::display::Quotable; +use uucore::error::{set_exit_code, FromIo, UResult, USimpleError}; +use uucore::{format_usage, help_about, help_usage, show_error, show_warning}; + +const ABOUT: &str = help_about!("factor.md"); +const USAGE: &str = help_usage!("factor.md"); + +mod options { + pub static EXPONENTS: &str = "exponents"; + pub static HELP: &str = "help"; + pub static NUMBER: &str = "NUMBER"; } -impl Eq for Decomposition {} -#[derive(Clone, Debug, Eq, PartialEq)] -pub struct Factors(RefCell); +fn print_factors_str( + num_str: &str, + w: &mut io::BufWriter, + print_exponents: bool, +) -> UResult<()> { + let rx = num_str.trim().parse::(); + let Ok(x) = rx else { + // return Ok(). it's non-fatal and we should try the next number. + show_warning!("{}: {}", num_str.maybe_quote(), rx.unwrap_err()); + set_exit_code(1); + return Ok(()); + }; -impl Factors { - pub fn one() -> Self { - Self(RefCell::new(Decomposition::one())) - } + let (factorization, remaining) = if x > BigUint::from_u32(1).unwrap() { + num_prime::nt_funcs::factors(x.clone(), None) + } else { + (BTreeMap::new(), None) + }; - pub fn add(&mut self, prime: u64, exp: Exponent) { - debug_assert!(miller_rabin::is_prime(prime)); - self.0.borrow_mut().add(prime, exp); + if let Some(_remaining) = remaining { + return Err(USimpleError::new( + 1, + "Factorization incomplete. Remainders exists.", + )); } - pub fn push(&mut self, prime: u64) { - self.add(prime, 1); - } + write_result(w, x, factorization, print_exponents).map_err_context(|| "write error".into())?; - #[cfg(test)] - fn product(&self) -> u64 { - self.0.borrow().product() - } + Ok(()) } -impl fmt::Display for Factors { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let v = &mut (self.0).borrow_mut().0; - v.sort_unstable(); - - let include_exponents = f.alternate(); - for (p, exp) in v { - if include_exponents && *exp > 1 { - write!(f, " {p}^{exp}")?; +fn write_result( + w: &mut io::BufWriter, + x: BigUint, + factorization: BTreeMap, + print_exponents: bool, +) -> io::Result<()> { + write!(w, "{x}:")?; + for (factor, n) in factorization { + if print_exponents { + if n > 1 { + write!(w, " {}^{}", factor, n)?; } else { - for _ in 0..*exp { - write!(f, " {p}")?; - } + write!(w, " {}", factor)?; } - } - - Ok(()) - } -} - -fn _factor(num: u64, f: Factors) -> Factors { - use miller_rabin::Result::*; - - // Shadow the name, so the recursion automatically goes from “Big” arithmetic to small. - let _factor = |n, f| { - if n < (1 << 32) { - _factor::>(n, f) } else { - _factor::(n, f) - } - }; - - if num == 1 { - return f; - } - - let n = A::new(num); - let divisor = match miller_rabin::test::(n) { - Prime => { - #[cfg(feature = "coz")] - coz::progress!("factor found"); - let mut r = f; - r.push(num); - return r; + w.write_all(format!(" {}", factor).repeat(n).as_bytes())?; } - - Composite(d) => d, - Pseudoprime => rho::find_divisor::(n), - }; - - let f = _factor(divisor, f); - _factor(num / divisor, f) -} - -pub fn factor(mut n: u64) -> Factors { - #[cfg(feature = "coz")] - coz::begin!("factorization"); - let mut factors = Factors::one(); - - if n < 2 { - return factors; - } - - let n_zeros = n.trailing_zeros(); - if n_zeros > 0 { - factors.add(2, n_zeros as Exponent); - n >>= n_zeros; - } - - if n == 1 { - #[cfg(feature = "coz")] - coz::end!("factorization"); - return factors; } - - table::factor(&mut n, &mut factors); - - #[allow(clippy::let_and_return)] - let r = if n < (1 << 32) { - _factor::>(n, factors) - } else { - _factor::>(n, factors) - }; - - #[cfg(feature = "coz")] - coz::end!("factorization"); - - r + writeln!(w)?; + w.flush() } -#[cfg(test)] -mod tests { - use super::{factor, Decomposition, Exponent, Factors}; - use quickcheck::quickcheck; - use smallvec::smallvec; - use std::cell::RefCell; - - #[test] - fn factor_2044854919485649() { - let f = Factors(RefCell::new(Decomposition(smallvec![ - (503, 1), - (2423, 1), - (40961, 2) - ]))); - assert_eq!(factor(f.product()), f); - } - - #[test] - fn factor_recombines_small() { - assert!((1..10_000) - .map(|i| 2 * i + 1) - .all(|i| factor(i).product() == i)); - } - - #[test] - fn factor_recombines_overflowing() { - assert!((0..250) - .map(|i| 2 * i + 2u64.pow(32) + 1) - .all(|i| factor(i).product() == i)); - } +#[uucore::main] +pub fn uumain(args: impl uucore::Args) -> UResult<()> { + let matches = uu_app().try_get_matches_from(args)?; - #[test] - fn factor_recombines_strong_pseudoprime() { - // This is a strong pseudoprime (wrt. miller_rabin::BASIS) - // and triggered a bug in rho::factor's code path handling - // miller_rabbin::Result::Composite - let pseudoprime = 17_179_869_183; - for _ in 0..20 { - // Repeat the test 20 times, as it only fails some fraction - // of the time. - assert!(factor(pseudoprime).product() == pseudoprime); - } - } + // If matches find --exponents flag than variable print_exponents is true and p^e output format will be used. + let print_exponents = matches.get_flag(options::EXPONENTS); - quickcheck! { - fn factor_recombines(i: u64) -> bool { - i == 0 || factor(i).product() == i - } + let stdout = stdout(); + // We use a smaller buffer here to pass a gnu test. 4KiB appears to be the default pipe size for bash. + let mut w = io::BufWriter::with_capacity(4 * 1024, stdout.lock()); - fn recombines_factors(f: Factors) -> () { - assert_eq!(factor(f.product()), f); + if let Some(values) = matches.get_many::(options::NUMBER) { + for number in values { + print_factors_str(number, &mut w, print_exponents)?; } - - fn exponentiate_factors(f: Factors, e: Exponent) -> () { - if e == 0 { return; } - if let Some(fe) = f.product().checked_pow(e.into()) { - assert_eq!(factor(fe), f ^ e); - } - } - } -} - -#[cfg(test)] -use rand::{ - distributions::{Distribution, Standard}, - Rng, -}; -#[cfg(test)] -impl Distribution for Standard { - fn sample(&self, rng: &mut R) -> Factors { - let mut f = Factors::one(); - let mut g = 1u64; - let mut n = u64::MAX; - - // spell-checker:ignore (names) Adam Kalai * Kalai's - // Adam Kalai's algorithm for generating uniformly-distributed - // integers and their factorization. - // - // See Generating Random Factored Numbers, Easily, J. Cryptology (2003) - 'attempt: loop { - while n > 1 { - n = rng.gen_range(1..n); - if miller_rabin::is_prime(n) { - if let Some(h) = g.checked_mul(n) { - f.push(n); - g = h; - } else { - // We are overflowing u64, retry - continue 'attempt; + } else { + let stdin = stdin(); + let lines = stdin.lock().lines(); + for line in lines { + match line { + Ok(line) => { + for number in line.split_whitespace() { + print_factors_str(number, &mut w, print_exponents)?; } } + Err(e) => { + set_exit_code(1); + show_error!("error reading input: {}", e); + return Ok(()); + } } - - return f; } } -} -#[cfg(test)] -impl quickcheck::Arbitrary for Factors { - fn arbitrary(g: &mut quickcheck::Gen) -> Self { - factor(u64::arbitrary(g)) + if let Err(e) = w.flush() { + show_error!("{}", e); } -} - -#[cfg(test)] -impl std::ops::BitXor for Factors { - type Output = Self; - fn bitxor(self, rhs: Exponent) -> Self { - debug_assert_ne!(rhs, 0); - let mut r = Self::one(); - #[allow(clippy::explicit_iter_loop)] - for (p, e) in self.0.borrow().0.iter() { - r.add(*p, rhs * e); - } + Ok(()) +} - debug_assert_eq!(r.product(), self.product().pow(rhs.into())); - r - } +pub fn uu_app() -> Command { + Command::new(uucore::util_name()) + .version(crate_version!()) + .about(ABOUT) + .override_usage(format_usage(USAGE)) + .infer_long_args(true) + .disable_help_flag(true) + .args_override_self(true) + .arg(Arg::new(options::NUMBER).action(ArgAction::Append)) + .arg( + Arg::new(options::EXPONENTS) + .short('h') + .long(options::EXPONENTS) + .help("Print factors in the form p^e") + .action(ArgAction::SetTrue), + ) + .arg( + Arg::new(options::HELP) + .long(options::HELP) + .help("Print help information.") + .action(ArgAction::Help), + ) } diff --git a/src/uu/factor/src/miller_rabin.rs b/src/uu/factor/src/miller_rabin.rs deleted file mode 100644 index 13a05806a9c..00000000000 --- a/src/uu/factor/src/miller_rabin.rs +++ /dev/null @@ -1,222 +0,0 @@ -// This file is part of the uutils coreutils package. -// -// For the full copyright and license information, please view the LICENSE -// file that was distributed with this source code. - -// spell-checker:ignore (URL) appspot - -use crate::numeric::*; - -pub(crate) trait Basis { - const BASIS: &'static [u64]; -} - -impl Basis for Montgomery { - // Small set of bases for the Miller-Rabin prime test, valid for all 64b integers; - // discovered by Jim Sinclair on 2011-04-20, see miller-rabin.appspot.com - const BASIS: &'static [u64] = &[2, 325, 9375, 28178, 450_775, 9_780_504, 1_795_265_022]; -} - -impl Basis for Montgomery { - // spell-checker:ignore (names) Steve Worley - // Small set of bases for the Miller-Rabin prime test, valid for all 32b integers; - // discovered by Steve Worley on 2013-05-27, see miller-rabin.appspot.com - const BASIS: &'static [u64] = &[ - 4_230_279_247_111_683_200, - 14_694_767_155_120_705_706, - 16_641_139_526_367_750_375, - ]; -} - -#[derive(Eq, PartialEq)] -#[must_use = "Ignoring the output of a primality test."] -pub(crate) enum Result { - Prime, - Pseudoprime, - Composite(u64), -} - -impl Result { - pub(crate) fn is_prime(&self) -> bool { - *self == Self::Prime - } -} - -// Deterministic Miller-Rabin primality-checking algorithm, adapted to extract -// (some) dividers; it will fail to factor strong pseudoprimes. -#[allow(clippy::many_single_char_names)] -pub(crate) fn test(m: A) -> Result { - use self::Result::*; - - let n = m.modulus(); - debug_assert!(n > 1); - debug_assert!(n % 2 != 0); - - // n-1 = r 2ⁱ - let i = (n - 1).trailing_zeros(); - let r = (n - 1) >> i; - - let one = m.one(); - let minus_one = m.minus_one(); - - 'witness: for _a in A::BASIS { - let _a = _a % n; - if _a == 0 { - continue; - } - - let a = m.to_mod(_a); - - // x = a^r mod n - let mut x = m.pow(a, r); - - if x == one || x == minus_one { - continue; - } - - for _ in 1..i { - let y = m.mul(x, x); - if y == one { - return Composite(gcd(m.to_u64(x) - 1, m.modulus())); - } else if y == minus_one { - // This basis element is not a witness of `n` being composite. - // Keep looking. - continue 'witness; - } - x = y; - } - - return Pseudoprime; - } - - Prime -} - -// Used by build.rs' tests and debug assertions -#[allow(dead_code)] -pub(crate) fn is_prime(n: u64) -> bool { - if n < 2 { - false - } else if n % 2 == 0 { - n == 2 - } else { - test::>(Montgomery::new(n)).is_prime() - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::numeric::{traits::DoubleInt, Arithmetic, Montgomery}; - use quickcheck::quickcheck; - use std::iter; - const LARGEST_U64_PRIME: u64 = 0xFFFF_FFFF_FFFF_FFC5; - - fn primes() -> impl Iterator { - iter::once(2).chain(odd_primes()) - } - - fn odd_primes() -> impl Iterator { - use crate::table::{NEXT_PRIME, PRIME_INVERSIONS_U64}; - PRIME_INVERSIONS_U64 - .iter() - .map(|(p, _, _)| *p) - .chain(iter::once(NEXT_PRIME)) - } - - #[test] - fn largest_prime() { - assert!(is_prime(LARGEST_U64_PRIME)); - } - - #[test] - fn largest_composites() { - for i in LARGEST_U64_PRIME + 1..=u64::MAX { - assert!(!is_prime(i), "2⁶⁴ - {} reported prime", u64::MAX - i + 1); - } - } - - #[test] - fn two() { - assert!(is_prime(2)); - } - - fn first_primes() - where - Montgomery: Basis, - { - for p in odd_primes() { - assert!( - test(Montgomery::::new(p)).is_prime(), - "{p} reported composite" - ); - } - } - - #[test] - fn first_primes_u32() { - first_primes::(); - } - - #[test] - fn first_primes_u64() { - first_primes::(); - } - - #[test] - fn one() { - assert!(!is_prime(1)); - } - #[test] - fn zero() { - assert!(!is_prime(0)); - } - - #[test] - fn first_composites() { - for (p, q) in primes().zip(odd_primes()) { - for i in p + 1..q { - assert!(!is_prime(i), "{i} reported prime"); - } - } - } - - #[test] - fn issue_1556() { - // 10 425 511 = 2441 × 4271 - assert!(!is_prime(10_425_511)); - } - - fn small_semiprimes() - where - Montgomery: Basis, - { - for p in odd_primes() { - for q in odd_primes().take_while(|q| *q <= p) { - let n = p * q; - let m = Montgomery::::new(n); - assert!(!test(m).is_prime(), "{n} = {p} × {q} reported prime"); - } - } - } - - #[test] - fn small_semiprimes_u32() { - small_semiprimes::(); - } - - #[test] - fn small_semiprimes_u64() { - small_semiprimes::(); - } - - quickcheck! { - fn composites(i: u64, j: u64) -> bool { - // TODO: #1559 factor n > 2^64 - 1 - match i.checked_mul(j) { - Some(n) => i < 2 || j < 2 || !is_prime(n), - _ => true, - } - } - } -} diff --git a/src/uu/factor/src/numeric/gcd.rs b/src/uu/factor/src/numeric/gcd.rs deleted file mode 100644 index 43c6ce9b71b..00000000000 --- a/src/uu/factor/src/numeric/gcd.rs +++ /dev/null @@ -1,126 +0,0 @@ -// This file is part of the uutils coreutils package. -// -// For the full copyright and license information, please view the LICENSE -// file that was distributed with this source code. - -// spell-checker:ignore (vars) kgcdab gcdac gcdbc - -use std::cmp::min; -use std::mem::swap; - -pub fn gcd(mut u: u64, mut v: u64) -> u64 { - // Stein's binary GCD algorithm - // Base cases: gcd(n, 0) = gcd(0, n) = n - if u == 0 { - return v; - } else if v == 0 { - return u; - } - - // gcd(2ⁱ u, 2ʲ v) = 2ᵏ gcd(u, v) with u, v odd and k = min(i, j) - // 2ᵏ is the greatest power of two that divides both u and v - let k = { - let i = u.trailing_zeros(); - let j = v.trailing_zeros(); - u >>= i; - v >>= j; - min(i, j) - }; - - loop { - // Loop invariant: u and v are odd - debug_assert!(u % 2 == 1, "u = {u} is even"); - debug_assert!(v % 2 == 1, "v = {v} is even"); - - // gcd(u, v) = gcd(|u - v|, min(u, v)) - if u > v { - swap(&mut u, &mut v); - } - v -= u; - - if v == 0 { - // Reached the base case; gcd is 2ᵏ u - return u << k; - } - - // gcd(u, 2ʲ v) = gcd(u, v) as u is odd - v >>= v.trailing_zeros(); - } -} - -#[cfg(test)] -mod tests { - use super::*; - use quickcheck::{quickcheck, TestResult}; - - quickcheck! { - fn euclidean(a: u64, b: u64) -> bool { - // Test against the Euclidean algorithm - let g = { - let (mut a, mut b) = (a, b); - while b > 0 { - a %= b; - swap(&mut a, &mut b); - } - a - }; - gcd(a, b) == g - } - - fn one(a: u64) -> bool { - gcd(1, a) == 1 - } - - fn zero(a: u64) -> bool { - gcd(0, a) == a - } - - fn divisor(a: u64, b: u64) -> TestResult { - // Test that gcd(a, b) divides a and b, unless a == b == 0 - if a == 0 && b == 0 { return TestResult::discard(); } // restrict test domain to !(a == b == 0) - - let g = gcd(a, b); - TestResult::from_bool( g != 0 && a % g == 0 && b % g == 0 ) - } - - fn commutative(a: u64, b: u64) -> bool { - gcd(a, b) == gcd(b, a) - } - - fn associative(a: u64, b: u64, c: u64) -> bool { - gcd(a, gcd(b, c)) == gcd(gcd(a, b), c) - } - - fn scalar_multiplication(a: u64, b: u64, k: u64) -> bool { - // TODO: #1559 factor n > 2^64 - 1 - match (k.checked_mul(a), k.checked_mul(b), k.checked_mul(gcd(a, b))) { - (Some(ka), Some(kb), Some(kgcdab)) => gcd(ka, kb) == kgcdab, - _ => true - } - } - - fn multiplicative(a: u64, b: u64, c: u64) -> bool { - // TODO: #1559 factor n > 2^64 - 1 - match (a.checked_mul(b), gcd(a, c).checked_mul(gcd(b, c))) { - (Some(ab), Some(gcdac_gcdbc)) => { - // gcd(ab, c) = gcd(a, c) gcd(b, c) when a and b coprime - gcd(a, b) != 1 || gcd(ab, c) == gcdac_gcdbc - }, - _ => true, - } - } - - fn linearity(a: u64, b: u64, k: u64) -> bool { - // TODO: #1559 factor n > 2^64 - 1 - match k.checked_mul(b) { - Some(kb) => { - match a.checked_add(kb) { - Some(a_plus_kb) => gcd(a_plus_kb, b) == gcd(a, b), - _ => true, - } - } - _ => true, - } - } - } -} diff --git a/src/uu/factor/src/numeric/mod.rs b/src/uu/factor/src/numeric/mod.rs deleted file mode 100644 index d4c0b5dc7f7..00000000000 --- a/src/uu/factor/src/numeric/mod.rs +++ /dev/null @@ -1,15 +0,0 @@ -// 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. - -mod gcd; -pub use gcd::gcd; - -pub(crate) mod traits; - -mod modular_inverse; -pub(crate) use modular_inverse::modular_inverse; - -mod montgomery; -pub(crate) use montgomery::{Arithmetic, Montgomery}; diff --git a/src/uu/factor/src/numeric/modular_inverse.rs b/src/uu/factor/src/numeric/modular_inverse.rs deleted file mode 100644 index bacf57d9ce6..00000000000 --- a/src/uu/factor/src/numeric/modular_inverse.rs +++ /dev/null @@ -1,84 +0,0 @@ -// This file is part of the uutils coreutils package. -// -// For the full copyright and license information, please view the LICENSE -// file that was distributed with this source code. - -use super::traits::Int; - -// extended Euclid algorithm -// precondition: a is odd -pub(crate) fn modular_inverse(a: T) -> T { - let zero = T::zero(); - let one = T::one(); - debug_assert!(a % (one + one) == one, "{a:?} is not odd"); - - let mut t = zero; - let mut new_t = one; - let mut r = zero; - let mut new_r = a; - - while new_r != zero { - let quot = if r == zero { - // special case when we're just starting out - // This works because we know that - // a does not divide 2^64, so floor(2^64 / a) == floor((2^64-1) / a); - T::max_value() - } else { - r - } / new_r; - - let new_tp = t.wrapping_sub(".wrapping_mul(&new_t)); - t = new_t; - new_t = new_tp; - - let new_rp = r.wrapping_sub(".wrapping_mul(&new_r)); - r = new_r; - new_r = new_rp; - } - - debug_assert_eq!(r, one); - t -} - -#[cfg(test)] -mod tests { - use super::{super::traits::Int, *}; - use quickcheck::quickcheck; - - fn small_values() { - // All odd integers from 1 to 20 000 - let one = T::from(1).unwrap(); - let two = T::from(2).unwrap(); - let mut test_values = (0..10_000) - .map(|i| T::from(i).unwrap()) - .map(|i| two * i + one); - - assert!(test_values.all(|x| x.wrapping_mul(&modular_inverse(x)) == one)); - } - - #[test] - fn small_values_u32() { - small_values::(); - } - - #[test] - fn small_values_u64() { - small_values::(); - } - - quickcheck! { - fn random_values_u32(n: u32) -> bool { - match 2_u32.checked_mul(n) { - Some(n) => modular_inverse(n + 1).wrapping_mul(n + 1) == 1, - _ => true, - } - } - - fn random_values_u64(n: u64) -> bool { - match 2_u64.checked_mul(n) { - Some(n) => modular_inverse(n + 1).wrapping_mul(n + 1) == 1, - _ => true, - } - } - } -} diff --git a/src/uu/factor/src/numeric/montgomery.rs b/src/uu/factor/src/numeric/montgomery.rs deleted file mode 100644 index 4807fc44f7d..00000000000 --- a/src/uu/factor/src/numeric/montgomery.rs +++ /dev/null @@ -1,246 +0,0 @@ -// This file is part of the uutils coreutils package. -// -// For the full copyright and license information, please view the LICENSE -// file that was distributed with this source code. - -use super::*; - -use traits::{DoubleInt, Int, One, OverflowingAdd, Zero}; - -pub(crate) trait Arithmetic: Copy + Sized { - // The type of integers mod m, in some opaque representation - type ModInt: Copy + Sized + Eq; - - fn new(m: u64) -> Self; - fn modulus(&self) -> u64; - fn to_mod(&self, n: u64) -> Self::ModInt; - fn to_u64(&self, n: Self::ModInt) -> u64; - fn add(&self, a: Self::ModInt, b: Self::ModInt) -> Self::ModInt; - fn mul(&self, a: Self::ModInt, b: Self::ModInt) -> Self::ModInt; - - fn pow(&self, mut a: Self::ModInt, mut b: u64) -> Self::ModInt { - let (_a, _b) = (a, b); - let mut result = self.one(); - while b > 0 { - if b & 1 != 0 { - result = self.mul(result, a); - } - a = self.mul(a, a); - b >>= 1; - } - - // Check that r (reduced back to the usual representation) equals - // a^b % n, unless the latter computation overflows - debug_assert!(self - .to_u64(_a) - .checked_pow(_b as u32) - .map(|r| r % self.modulus() == self.to_u64(result)) - .unwrap_or(true)); - - result - } - - fn one(&self) -> Self::ModInt { - self.to_mod(1) - } - - fn minus_one(&self) -> Self::ModInt { - self.to_mod(self.modulus() - 1) - } -} - -#[derive(Clone, Copy, Debug)] -pub(crate) struct Montgomery { - a: T, - n: T, -} - -impl Montgomery { - /// computes x/R mod n efficiently - fn reduce(&self, x: T::DoubleWidth) -> T { - let t_bits = T::zero().count_zeros() as usize; - - debug_assert!(x < (self.n.as_double_width()) << t_bits); - // TODO: optimize - let Self { a, n } = self; - let m = T::from_double_width(x).wrapping_mul(a); - let nm = (n.as_double_width()) * (m.as_double_width()); - let (xnm, overflow) = x.overflowing_add(&nm); // x + n*m - debug_assert_eq!( - xnm % (T::DoubleWidth::one() << T::zero().count_zeros() as usize), - T::DoubleWidth::zero() - ); - - // (x + n*m) / R - // in case of overflow, this is (2¹²⁸ + xnm)/2⁶⁴ - n = xnm/2⁶⁴ + (2⁶⁴ - n) - let y = T::from_double_width(xnm >> t_bits) - + if overflow { - n.wrapping_neg() - } else { - T::zero() - }; - - if y >= *n { - y - *n - } else { - y - } - } -} - -impl Arithmetic for Montgomery { - // Montgomery transform, R=2⁶⁴ - // Provides fast arithmetic mod n (n odd, u64) - type ModInt = T; - - fn new(n: u64) -> Self { - debug_assert!(T::zero().count_zeros() >= 64 || n < (1 << T::zero().count_zeros() as usize)); - let n = T::from_u64(n); - let a = modular_inverse(n).wrapping_neg(); - debug_assert_eq!(n.wrapping_mul(&a), T::one().wrapping_neg()); - Self { a, n } - } - - fn modulus(&self) -> u64 { - self.n.as_u64() - } - - fn to_mod(&self, x: u64) -> Self::ModInt { - // TODO: optimize! - debug_assert!(x < self.n.as_u64()); - let r = T::from_double_width( - ((T::DoubleWidth::from_u64(x)) << T::zero().count_zeros() as usize) - % self.n.as_double_width(), - ); - debug_assert_eq!(x, self.to_u64(r)); - r - } - - fn to_u64(&self, n: Self::ModInt) -> u64 { - self.reduce(n.as_double_width()).as_u64() - } - - fn add(&self, a: Self::ModInt, b: Self::ModInt) -> Self::ModInt { - let (r, overflow) = a.overflowing_add(&b); - - // In case of overflow, a+b = 2⁶⁴ + r = (2⁶⁴ - n) + r (working mod n) - let r = if overflow { - r + self.n.wrapping_neg() - } else { - r - }; - - // Normalize to [0; n[ - let r = if r < self.n { r } else { r - self.n }; - - // Check that r (reduced back to the usual representation) equals - // a+b % n - #[cfg(debug_assertions)] - { - let a_r = self.to_u64(a) as u128; - let b_r = self.to_u64(b) as u128; - let r_r = self.to_u64(r); - let r_2 = ((a_r + b_r) % self.n.as_u128()) as u64; - debug_assert_eq!( - r_r, r_2, - "[{}] = {} ≠ {} = {} + {} = [{}] + [{}] mod {}; a = {}", - r, r_r, r_2, a_r, b_r, a, b, self.n, self.a - ); - } - r - } - - fn mul(&self, a: Self::ModInt, b: Self::ModInt) -> Self::ModInt { - let r = self.reduce(a.as_double_width() * b.as_double_width()); - - // Check that r (reduced back to the usual representation) equals - // a*b % n - #[cfg(debug_assertions)] - { - let a_r = self.to_u64(a) as u128; - let b_r = self.to_u64(b) as u128; - let r_r = self.to_u64(r); - let r_2: u64 = ((a_r * b_r) % self.n.as_u128()) as u64; - debug_assert_eq!( - r_r, r_2, - "[{}] = {} ≠ {} = {} * {} = [{}] * [{}] mod {}; a = {}", - r, r_r, r_2, a_r, b_r, a, b, self.n, self.a - ); - } - r - } -} - -#[cfg(test)] -mod tests { - use super::*; - fn test_add() { - for n in 0..100 { - let n = 2 * n + 1; - let m = Montgomery::::new(n); - for x in 0..n { - let m_x = m.to_mod(x); - for y in 0..=x { - let m_y = m.to_mod(y); - println!("{n:?}, {x:?}, {y:?}"); - assert_eq!((x + y) % n, m.to_u64(m.add(m_x, m_y))); - } - } - } - } - - #[test] - fn add_u32() { - test_add::(); - } - - #[test] - fn add_u64() { - test_add::(); - } - - fn test_multiplication() { - for n in 0..100 { - let n = 2 * n + 1; - let m = Montgomery::::new(n); - for x in 0..n { - let m_x = m.to_mod(x); - for y in 0..=x { - let m_y = m.to_mod(y); - assert_eq!((x * y) % n, m.to_u64(m.mul(m_x, m_y))); - } - } - } - } - - #[test] - fn multiplication_u32() { - test_multiplication::(); - } - - #[test] - fn multiplication_u64() { - test_multiplication::(); - } - - fn test_roundtrip() { - for n in 0..100 { - let n = 2 * n + 1; - let m = Montgomery::::new(n); - for x in 0..n { - let x_ = m.to_mod(x); - assert_eq!(x, m.to_u64(x_)); - } - } - } - - #[test] - fn roundtrip_u32() { - test_roundtrip::(); - } - - #[test] - fn roundtrip_u64() { - test_roundtrip::(); - } -} diff --git a/src/uu/factor/src/numeric/traits.rs b/src/uu/factor/src/numeric/traits.rs deleted file mode 100644 index ea69e82c2ea..00000000000 --- a/src/uu/factor/src/numeric/traits.rs +++ /dev/null @@ -1,89 +0,0 @@ -// 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. - -pub(crate) use num_traits::{ - identities::{One, Zero}, - ops::overflowing::OverflowingAdd, -}; -use num_traits::{ - int::PrimInt, - ops::wrapping::{WrappingMul, WrappingNeg, WrappingSub}, -}; -use std::fmt::{Debug, Display}; - -#[allow(dead_code)] // Rust doesn't recognize the use in the macro -pub(crate) trait Int: - Display + Debug + PrimInt + OverflowingAdd + WrappingNeg + WrappingSub + WrappingMul -{ - fn as_u64(&self) -> u64; - fn from_u64(n: u64) -> Self; - - #[cfg(debug_assertions)] - fn as_u128(&self) -> u128; -} - -#[allow(dead_code)] // Rust doesn't recognize the use in the macro -pub(crate) trait DoubleInt: Int { - /// An integer type with twice the width of `Self`. - /// In particular, multiplications (of `Int` values) can be performed in - /// `Self::DoubleWidth` without possibility of overflow. - type DoubleWidth: Int; - - fn as_double_width(self) -> Self::DoubleWidth; - fn from_double_width(n: Self::DoubleWidth) -> Self; -} - -macro_rules! int { - ( $x:ty ) => { - impl Int for $x { - fn as_u64(&self) -> u64 { - *self as u64 - } - fn from_u64(n: u64) -> Self { - n as _ - } - #[cfg(debug_assertions)] - fn as_u128(&self) -> u128 { - *self as u128 - } - } - }; -} -macro_rules! double_int { - ( $x:ty, $y:ty ) => { - int!($x); - impl DoubleInt for $x { - type DoubleWidth = $y; - - fn as_double_width(self) -> $y { - self as _ - } - fn from_double_width(n: $y) -> Self { - n as _ - } - } - }; -} -double_int!(u32, u64); -double_int!(u64, u128); -int!(u128); - -/// Helper macro for instantiating tests over u32 and u64 -#[cfg(test)] -#[macro_export] -macro_rules! parametrized_check { - ( $f:ident ) => { - paste::item! { - #[test] - fn [< $f _ u32 >]() { - $f::() - } - #[test] - fn [< $f _ u64 >]() { - $f::() - } - } - }; -} diff --git a/src/uu/factor/src/rho.rs b/src/uu/factor/src/rho.rs deleted file mode 100644 index 9375aa822f2..00000000000 --- a/src/uu/factor/src/rho.rs +++ /dev/null @@ -1,43 +0,0 @@ -// This file is part of the uutils coreutils package. -// -// For the full copyright and license information, please view the LICENSE -// file that was distributed with this source code. - -use rand::distributions::{Distribution, Uniform}; -use rand::rngs::SmallRng; -use rand::{thread_rng, SeedableRng}; -use std::cmp::{max, min}; - -use crate::numeric::*; - -pub(crate) fn find_divisor(input: A) -> u64 { - let mut rand = { - let range = Uniform::new(1, input.modulus()); - let mut rng = SmallRng::from_rng(&mut thread_rng()).unwrap(); - move || input.to_mod(range.sample(&mut rng)) - }; - - let quadratic = |a, b| move |x| input.add(input.mul(a, input.mul(x, x)), b); - - loop { - let f = quadratic(rand(), rand()); - let mut x = rand(); - let mut y = x; - - loop { - x = f(x); - y = f(f(y)); - let d = { - let _x = input.to_u64(x); - let _y = input.to_u64(y); - gcd(input.modulus(), max(_x, _y) - min(_x, _y)) - }; - if d == input.modulus() { - // Failure, retry with a different quadratic - break; - } else if d > 1 { - return d; - } - } - } -} diff --git a/src/uu/factor/src/table.rs b/src/uu/factor/src/table.rs deleted file mode 100644 index e9b525a3af5..00000000000 --- a/src/uu/factor/src/table.rs +++ /dev/null @@ -1,98 +0,0 @@ -// This file is part of the uutils coreutils package. -// -// For the full copyright and license information, please view the LICENSE -// file that was distributed with this source code. - -// spell-checker: ignore (ToDO) INVS - -use crate::Factors; - -include!(concat!(env!("OUT_DIR"), "/prime_table.rs")); - -pub fn factor(num: &mut u64, factors: &mut Factors) { - for &(prime, inv, ceil) in PRIME_INVERSIONS_U64 { - if *num == 1 { - break; - } - - // inv = prime^-1 mod 2^64 - // ceil = floor((2^64-1) / prime) - // if (num * inv) mod 2^64 <= ceil, then prime divides num - // See https://math.stackexchange.com/questions/1251327/ - // for a nice explanation. - let mut k = 0; - loop { - let x = num.wrapping_mul(inv); - - // While prime divides num - if x <= ceil { - *num = x; - k += 1; - #[cfg(feature = "coz")] - coz::progress!("factor found"); - } else { - if k > 0 { - factors.add(prime, k); - } - break; - } - } - } -} - -pub const CHUNK_SIZE: usize = 8; -pub fn factor_chunk(n_s: &mut [u64; CHUNK_SIZE], f_s: &mut [Factors; CHUNK_SIZE]) { - for &(prime, inv, ceil) in PRIME_INVERSIONS_U64 { - if n_s[0] == 1 && n_s[1] == 1 && n_s[2] == 1 && n_s[3] == 1 { - break; - } - - for (num, factors) in n_s.iter_mut().zip(f_s.iter_mut()) { - if *num == 1 { - continue; - } - let mut k = 0; - loop { - let x = num.wrapping_mul(inv); - - // While prime divides num - if x <= ceil { - *num = x; - k += 1; - } else { - if k > 0 { - factors.add(prime, k); - } - break; - } - } - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::Factors; - use quickcheck::quickcheck; - use rand::{rngs::SmallRng, Rng, SeedableRng}; - - quickcheck! { - fn chunk_vs_iter(seed: u64) -> () { - let mut rng = SmallRng::seed_from_u64(seed); - let mut n_c: [u64; CHUNK_SIZE] = rng.gen(); - let mut f_c: [Factors; CHUNK_SIZE] = rng.gen(); - - let mut n_i = n_c; - let mut f_i = f_c.clone(); - for (n, f) in n_i.iter_mut().zip(f_i.iter_mut()) { - factor(n, f); - } - - factor_chunk(&mut n_c, &mut f_c); - - assert_eq!(n_i, n_c); - assert_eq!(f_i, f_c); - } - } -} diff --git a/tests/benches/factor/Cargo.toml b/tests/benches/factor/Cargo.toml index 26900e78a91..5ffcd66fb4c 100644 --- a/tests/benches/factor/Cargo.toml +++ b/tests/benches/factor/Cargo.toml @@ -17,10 +17,10 @@ array-init = "2.0.0" criterion = "0.3" rand = "0.8" rand_chacha = "0.3.1" +num-bigint = "0.4.4" +num-prime = "0.4.3" +num-traits = "0.2.18" -[[bench]] -name = "gcd" -harness = false [[bench]] name = "table" diff --git a/tests/benches/factor/benches/gcd.rs b/tests/benches/factor/benches/gcd.rs deleted file mode 100644 index 61545145f98..00000000000 --- a/tests/benches/factor/benches/gcd.rs +++ /dev/null @@ -1,33 +0,0 @@ -// This file is part of the uutils coreutils package. -// -// For the full copyright and license information, please view the LICENSE -// file that was distributed with this source code. -use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion}; -use uu_factor::numeric; - -fn gcd(c: &mut Criterion) { - let inputs = { - // Deterministic RNG; use an explicitly-named RNG to guarantee stability - use rand::{RngCore, SeedableRng}; - use rand_chacha::ChaCha8Rng; - const SEED: u64 = 0xab4d_1dea_dead_cafe; - let mut rng = ChaCha8Rng::seed_from_u64(SEED); - - std::iter::repeat_with(move || (rng.next_u64(), rng.next_u64())) - }; - - let mut group = c.benchmark_group("gcd"); - for (n, m) in inputs.take(10) { - group.bench_with_input( - BenchmarkId::from_parameter(format!("{}_{}", n, m)), - &(n, m), - |b, &(n, m)| { - b.iter(|| numeric::gcd(n, m)); - }, - ); - } - group.finish(); -} - -criterion_group!(benches, gcd); -criterion_main!(benches); diff --git a/tests/benches/factor/benches/table.rs b/tests/benches/factor/benches/table.rs index f666d72d510..cc05ee0ca6b 100644 --- a/tests/benches/factor/benches/table.rs +++ b/tests/benches/factor/benches/table.rs @@ -2,9 +2,11 @@ // // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. + +// spell-checker:ignore funcs + use array_init::array_init; use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion, Throughput}; -use uu_factor::{table::*, Factors}; fn table(c: &mut Criterion) { #[cfg(target_os = "linux")] @@ -26,21 +28,10 @@ fn table(c: &mut Criterion) { group.throughput(Throughput::Elements(INPUT_SIZE as _)); for a in inputs.take(10) { let a_str = format!("{:?}", a); - group.bench_with_input(BenchmarkId::new("factor_chunk", &a_str), &a, |b, &a| { - b.iter(|| { - let mut n_s = a; - let mut f_s: [_; INPUT_SIZE] = array_init(|_| Factors::one()); - for (n_s, f_s) in n_s.chunks_mut(CHUNK_SIZE).zip(f_s.chunks_mut(CHUNK_SIZE)) { - factor_chunk(n_s.try_into().unwrap(), f_s.try_into().unwrap()); - } - }); - }); group.bench_with_input(BenchmarkId::new("factor", &a_str), &a, |b, &a| { b.iter(|| { - let mut n_s = a; - let mut f_s: [_; INPUT_SIZE] = array_init(|_| Factors::one()); - for (n, f) in n_s.iter_mut().zip(f_s.iter_mut()) { - factor(n, f); + for n in a { + let _r = num_prime::nt_funcs::factors(n, None); } }); }); diff --git a/tests/by-util/test_factor.rs b/tests/by-util/test_factor.rs index ae4f908a77e..f643a404997 100644 --- a/tests/by-util/test_factor.rs +++ b/tests/by-util/test_factor.rs @@ -3,16 +3,12 @@ // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. -// spell-checker:ignore (methods) hexdigest +// spell-checker:ignore (methods) hexdigest funcs nprimes use crate::common::util::TestScenario; use std::time::{Duration, SystemTime}; -#[path = "../../src/uu/factor/sieve.rs"] -mod sieve; - -use self::sieve::Sieve; use rand::distributions::{Distribution, Uniform}; use rand::{rngs::SmallRng, Rng, SeedableRng}; @@ -155,7 +151,7 @@ fn test_cli_args() { #[test] fn test_random() { let log_num_primes = f64::from(u32::try_from(NUM_PRIMES).unwrap()).log2().ceil(); - let primes = Sieve::primes().take(NUM_PRIMES).collect::>(); + let primes = num_prime::nt_funcs::nprimes(NUM_PRIMES); let rng_seed = SystemTime::now() .duration_since(SystemTime::UNIX_EPOCH) @@ -1269,3 +1265,53 @@ const PRIMES50: &[u64] = &[ fn fails_on_directory() { new_ucmd!().pipe_in(".").fails(); } + +#[test] +fn succeeds_with_numbers_larger_than_u64() { + new_ucmd!() + .arg("158909489063877810457") + .succeeds() + .stdout_is("158909489063877810457: 3401347 3861211 12099721\n"); + new_ucmd!() + .arg("222087527029934481871") + .succeeds() + .stdout_is("222087527029934481871: 15601 26449 111427 4830277\n"); + new_ucmd!() + .arg("12847291069740315094892340035") + .succeeds() + .stdout_is( + "12847291069740315094892340035: \ + 5 4073 18899 522591721 63874247821\n", + ); +} + +#[test] +fn succeeds_with_numbers_larger_than_u128() { + new_ucmd!() + .arg("-h") + .arg("340282366920938463463374607431768211456") + .succeeds() + .stdout_is("340282366920938463463374607431768211456: 2^128\n"); + new_ucmd!() + .arg("+170141183460469231731687303715884105729") + .succeeds() + .stdout_is( + "170141183460469231731687303715884105729: \ + 3 56713727820156410577229101238628035243\n", + ); +} + +#[test] +fn succeeds_with_numbers_larger_than_u256() { + new_ucmd!() + .arg("-h") + .arg( + "115792089237316195423570985008687907853\ + 269984665640564039457584007913129639936", + ) + .succeeds() + .stdout_is( + "115792089237316195423570985008687907853\ + 269984665640564039457584007913129639936: 2^256\n", + ); +} diff --git a/util/build-gnu.sh b/util/build-gnu.sh index debb7bdd93b..a3ec6316bea 100755 --- a/util/build-gnu.sh +++ b/util/build-gnu.sh @@ -221,7 +221,7 @@ grep -rlE '/usr/local/bin/\s?/usr/local/bin' init.cfg tests/* | xargs -r sed -Ei # we should not regress our project just to match what GNU is going. # So, do some changes on the fly -patch -N -r - -d "$path_GNU" -p 1 -i "`realpath \"$path_UUTILS/util/gnu-patches/tests_env_env-S.pl.patch\"`" || true +eval cat "$path_UUTILS/util/gnu-patches/*.patch" | patch -N -r - -d "$path_GNU" -p 1 -i - || true sed -i -e "s|rm: cannot remove 'e/slink'|rm: cannot remove 'e'|g" tests/rm/fail-eacces.sh diff --git a/util/gnu-patches/tests_factor_factor.pl.patch b/util/gnu-patches/tests_factor_factor.pl.patch new file mode 100644 index 00000000000..fc8b988fea5 --- /dev/null +++ b/util/gnu-patches/tests_factor_factor.pl.patch @@ -0,0 +1,21 @@ +diff --git a/tests/factor/factor.pl b/tests/factor/factor.pl +index 6e612e418..f19c06ca0 100755 +--- a/tests/factor/factor.pl ++++ b/tests/factor/factor.pl +@@ -61,12 +61,13 @@ my @Tests = + # Map newer glibc diagnostic to expected. + # Also map OpenBSD 5.1's "unknown option" to expected "invalid option". + {ERR_SUBST => q!s/'1'/1/;s/unknown/invalid/!}, +- {ERR => "$prog: invalid option -- 1\n" +- . "Try '$prog --help' for more information.\n"}, ++ {ERR => "error: unexpected argument '-1' found\n\n" ++ . " tip: to pass '-1' as a value, use '-- -1'\n\n" ++ . "Usage: factor [OPTION]... [NUMBER]...\n"}, + {EXIT => 1}], + ['cont', 'a 4', + {OUT => "4: 2 2\n"}, +- {ERR => "$prog: 'a' is not a valid positive integer\n"}, ++ {ERR => "$prog: warning: a: invalid digit found in string\n"}, + {EXIT => 1}], + ['bug-2012-a', '465658903', {OUT => '15259 30517'}], + ['bug-2012-b', '2242724851', {OUT => '33487 66973'}], From 6e1394d0db0699675b1ae8eeaf28637232f569f5 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sun, 21 Apr 2024 10:51:07 +0000 Subject: [PATCH 126/144] chore(deps): update rust crate zip to 1.1.1 --- Cargo.lock | 4 ++-- Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f015fd0ebcd..ac299596a23 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3737,9 +3737,9 @@ checksum = "2a599daf1b507819c1121f0bf87fa37eb19daac6aff3aefefd4e6e2e0f2020fc" [[package]] name = "zip" -version = "1.1.0" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f21968e6da56f847a155a89581ba846507afa14854e041f3053edb6ddd19f807" +checksum = "f2655979068a1f8fa91cb9e8e5b9d3ee54d18e0ddc358f2f4a395afc0929a84b" dependencies = [ "arbitrary", "byteorder", diff --git a/Cargo.toml b/Cargo.toml index ee1a1876ffc..8160e09e755 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -333,7 +333,7 @@ walkdir = "2.5" winapi-util = "0.1.6" windows-sys = { version = "0.48.0", default-features = false } xattr = "1.3.1" -zip = { version = "1.1.0", default-features = false, features = ["deflate"] } +zip = { version = "1.1.1", default-features = false, features = ["deflate"] } hex = "0.4.3" md-5 = "0.10.6" From 3a72d5c5cb3773647a00e4b7f5d2d91b769f16d0 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Sun, 21 Apr 2024 13:20:38 +0200 Subject: [PATCH 127/144] test tail: replace fixtures by at for consistency --- tests/by-util/test_tail.rs | 86 +++++++++++++++++++------------------- 1 file changed, 43 insertions(+), 43 deletions(-) diff --git a/tests/by-util/test_tail.rs b/tests/by-util/test_tail.rs index f11eac190d2..482d0329409 100644 --- a/tests/by-util/test_tail.rs +++ b/tests/by-util/test_tail.rs @@ -3620,11 +3620,11 @@ fn test_when_argument_file_is_non_existent_unix_socket_address_then_error() { #[cfg(disabled_until_fixed)] fn test_when_argument_files_are_simple_combinations_of_stdin_and_regular_file() { let scene = TestScenario::new(util_name!()); - let fixtures = &scene.fixtures; + let at = &scene.fixtures; - fixtures.write("empty", ""); - fixtures.write("data", "file data"); - fixtures.write("fifo", "fifo data"); + at.write("empty", ""); + at.write("data", "file data"); + at.write("fifo", "fifo data"); let expected = "==> standard input <==\n\ fifo data\n\ @@ -3632,7 +3632,7 @@ fn test_when_argument_files_are_simple_combinations_of_stdin_and_regular_file() scene .ucmd() .args(&["-c", "+0", "-", "empty"]) - .set_stdin(File::open(fixtures.plus("fifo")).unwrap()) + .set_stdin(File::open(at.plus("fifo")).unwrap()) .run() .success() .stdout_only(expected); @@ -3666,7 +3666,7 @@ fn test_when_argument_files_are_simple_combinations_of_stdin_and_regular_file() scene .ucmd() .args(&["-c", "+0", "empty", "-"]) - .set_stdin(File::open(fixtures.plus("fifo")).unwrap()) + .set_stdin(File::open(at.plus("fifo")).unwrap()) .run() .success() .stdout_only(expected); @@ -3712,7 +3712,7 @@ fn test_when_argument_files_are_simple_combinations_of_stdin_and_regular_file() scene .ucmd() .args(&["-c", "+0", "-", "-"]) - .set_stdin(File::open(fixtures.plus("fifo")).unwrap()) + .set_stdin(File::open(at.plus("fifo")).unwrap()) .run() .success() .stdout_only(expected); @@ -3722,11 +3722,11 @@ fn test_when_argument_files_are_simple_combinations_of_stdin_and_regular_file() #[cfg(disabled_until_fixed)] fn test_when_argument_files_are_triple_combinations_of_fifo_pipe_and_regular_file() { let scene = TestScenario::new(util_name!()); - let fixtures = &scene.fixtures; + let at = &scene.fixtures; - fixtures.write("empty", ""); - fixtures.write("data", "file data"); - fixtures.write("fifo", "fifo data"); + at.write("empty", ""); + at.write("data", "file data"); + at.write("fifo", "fifo data"); let expected = "==> standard input <==\n\ \n\ @@ -3737,7 +3737,7 @@ fn test_when_argument_files_are_triple_combinations_of_fifo_pipe_and_regular_fil scene .ucmd() .args(&["-c", "+0", "-", "empty", "-"]) - .set_stdin(File::open(fixtures.plus("empty")).unwrap()) + .set_stdin(File::open(at.plus("empty")).unwrap()) .run() .stdout_only(expected) .success(); @@ -3817,7 +3817,7 @@ fn test_when_argument_files_are_triple_combinations_of_fifo_pipe_and_regular_fil scene .ucmd() .args(&["-c", "+0", "-", "data", "-"]) - .set_stdin(File::open(fixtures.plus("fifo")).unwrap()) + .set_stdin(File::open(at.plus("fifo")).unwrap()) .run() .stdout_only(expected) .success(); @@ -3833,10 +3833,10 @@ fn test_when_argument_files_are_triple_combinations_of_fifo_pipe_and_regular_fil #[cfg(disable_until_fixed)] fn test_when_follow_retry_then_initial_print_of_file_is_written_to_stdout() { let scene = TestScenario::new(util_name!()); - let fixtures = &scene.fixtures; + let at = &scene.fixtures; let expected_stdout = "file data"; - fixtures.write("data", expected_stdout); + at.write("data", expected_stdout); let mut child = scene .ucmd() @@ -3855,10 +3855,10 @@ fn test_when_follow_retry_then_initial_print_of_file_is_written_to_stdout() { #[test] fn test_args_when_settings_check_warnings_then_shows_warnings() { let scene = TestScenario::new(util_name!()); - let fixtures = &scene.fixtures; + let at = &scene.fixtures; let file_data = "file data\n"; - fixtures.write("data", file_data); + at.write("data", file_data); let expected_stdout = format!( "tail: warning: --retry ignored; --retry is useful only when following\n\ @@ -4051,7 +4051,7 @@ fn test_args_when_settings_check_warnings_follow_indefinitely_then_warning() { #[cfg(unix)] fn test_args_when_settings_check_warnings_follow_indefinitely_then_no_warning() { let scene = TestScenario::new(util_name!()); - let fixtures = &scene.fixtures; + let at = &scene.fixtures; #[cfg(target_vendor = "apple")] let delay = 1000; @@ -4062,8 +4062,8 @@ fn test_args_when_settings_check_warnings_follow_indefinitely_then_no_warning() let fifo_data = "fifo data\n"; let fifo_name = "fifo"; let file_name = "data"; - fixtures.write(file_name, file_data); - fixtures.write(fifo_name, fifo_data); + at.write(file_name, file_data); + at.write(fifo_name, fifo_data); let pipe_data = "pipe data"; let expected_stdout = format!( @@ -4106,7 +4106,7 @@ fn test_args_when_settings_check_warnings_follow_indefinitely_then_no_warning() let mut child = scene .ucmd() .args(&["--follow=descriptor", "-", file_name]) - .set_stdin(File::open(fixtures.plus(fifo_name)).unwrap()) + .set_stdin(File::open(at.plus(fifo_name)).unwrap()) .stderr_to_stdout() .run_no_wait(); @@ -4126,7 +4126,7 @@ fn test_args_when_settings_check_warnings_follow_indefinitely_then_no_warning() let mut child = scene .ucmd() .args(&["--follow=descriptor", "--pid=0", "-", file_name]) - .set_stdin(File::open(fixtures.plus(fifo_name)).unwrap()) + .set_stdin(File::open(at.plus(fifo_name)).unwrap()) .stderr_to_stdout() .run_no_wait(); @@ -4144,13 +4144,13 @@ fn test_args_when_settings_check_warnings_follow_indefinitely_then_no_warning() #[cfg(disable_until_fixed)] fn test_follow_when_files_are_pointing_to_same_relative_file_and_data_is_appended() { let scene = TestScenario::new(util_name!()); - let fixtures = &scene.fixtures; + let at = &scene.fixtures; let file_data = "file data"; let relative_path_name = "data"; - fixtures.write(relative_path_name, file_data); - let absolute_path = fixtures.plus("data").canonicalize().unwrap(); + at.write(relative_path_name, file_data); + let absolute_path = at.plus("data").canonicalize().unwrap(); // run with relative path first and then the absolute path let mut child = scene @@ -4165,7 +4165,7 @@ fn test_follow_when_files_are_pointing_to_same_relative_file_and_data_is_appende let more_data = "more data"; child.delay(500); - fixtures.append(relative_path_name, more_data); + at.append(relative_path_name, more_data); let expected_stdout = format!( "==> {0} <==\n\ @@ -4190,7 +4190,7 @@ fn test_follow_when_files_are_pointing_to_same_relative_file_and_data_is_appende .stderr_only(expected_stdout); // run with absolute path first and then the relative path - fixtures.write(relative_path_name, file_data); + at.write(relative_path_name, file_data); let mut child = scene .ucmd() .args(&[ @@ -4202,7 +4202,7 @@ fn test_follow_when_files_are_pointing_to_same_relative_file_and_data_is_appende child.delay(500); let more_data = "more data"; - fixtures.append(relative_path_name, more_data); + at.append(relative_path_name, more_data); let expected_stdout = format!( "==> {0} <==\n\ @@ -4232,13 +4232,13 @@ fn test_follow_when_files_are_pointing_to_same_relative_file_and_data_is_appende #[cfg(disable_until_fixed)] fn test_follow_when_files_are_pointing_to_same_relative_file_and_file_is_truncated() { let scene = TestScenario::new(util_name!()); - let fixtures = &scene.fixtures; + let at = &scene.fixtures; let file_data = "file data"; let relative_path_name = "data"; - fixtures.write(relative_path_name, file_data); - let absolute_path = fixtures.plus("data").canonicalize().unwrap(); + at.write(relative_path_name, file_data); + let absolute_path = at.plus("data").canonicalize().unwrap(); let mut child = scene .ucmd() @@ -4254,7 +4254,7 @@ fn test_follow_when_files_are_pointing_to_same_relative_file_and_file_is_truncat child.delay(500); let less_data = "less"; - fixtures.write(relative_path_name, "less"); + at.write(relative_path_name, "less"); let expected_stdout = format!( "==> {0} <==\n\ @@ -4285,14 +4285,14 @@ fn test_follow_when_files_are_pointing_to_same_relative_file_and_file_is_truncat #[cfg(disable_until_fixed)] fn test_follow_when_file_and_symlink_are_pointing_to_same_file_and_append_data() { let scene = TestScenario::new(util_name!()); - let fixtures = &scene.fixtures; + let at = &scene.fixtures; let file_data = "file data"; let path_name = "data"; let link_name = "link"; - fixtures.write(path_name, file_data); - fixtures.symlink_file(path_name, link_name); + at.write(path_name, file_data); + at.symlink_file(path_name, link_name); let mut child = scene .ucmd() @@ -4307,7 +4307,7 @@ fn test_follow_when_file_and_symlink_are_pointing_to_same_file_and_append_data() child.delay(500); let more_data = "more data"; - fixtures.append(path_name, more_data); + at.append(path_name, more_data); let expected_stdout = format!( "==> {0} <==\n\ @@ -4331,7 +4331,7 @@ fn test_follow_when_file_and_symlink_are_pointing_to_same_file_and_append_data() .with_current_output() .stdout_only(expected_stdout); - fixtures.write(path_name, file_data); + at.write(path_name, file_data); let mut child = scene .ucmd() .args(&[ @@ -4345,7 +4345,7 @@ fn test_follow_when_file_and_symlink_are_pointing_to_same_file_and_append_data() child.delay(500); let more_data = "more data"; - fixtures.append(path_name, more_data); + at.append(path_name, more_data); let expected_stdout = format!( "==> {0} <==\n\ @@ -4373,10 +4373,10 @@ fn test_follow_when_file_and_symlink_are_pointing_to_same_file_and_append_data() #[test] fn test_args_when_directory_given_shorthand_big_f_together_with_retry() { let scene = TestScenario::new(util_name!()); - let fixtures = &scene.fixtures; + let at = &scene.fixtures; let dirname = "dir"; - fixtures.mkdir(dirname); + at.mkdir(dirname); let expected_stderr = format!( "tail: error reading '{0}': Is a directory\n\ tail: {0}: cannot follow end of this type of file\n", @@ -4432,12 +4432,12 @@ fn test_args_when_directory_given_shorthand_big_f_together_with_retry() { ))] fn test_follow_when_files_are_pointing_to_same_relative_file_and_file_stays_same_size() { let scene = TestScenario::new(util_name!()); - let fixtures = &scene.fixtures; + let at = &scene.fixtures; let file_data = "file data"; let relative_path_name = "data"; - fixtures.write(relative_path_name, file_data); + at.write(relative_path_name, file_data); let absolute_path = scene.fixtures.plus("data").canonicalize().unwrap(); let mut child = scene @@ -4453,7 +4453,7 @@ fn test_follow_when_files_are_pointing_to_same_relative_file_and_file_stays_same child.delay(500); let same_data = "same data"; // equal size to file_data - fixtures.write(relative_path_name, same_data); + at.write(relative_path_name, same_data); let expected_stdout = format!( "==> {0} <==\n\ From 13cfe2d8e5051ca597b37986c408df5f066cfb41 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Sun, 21 Apr 2024 13:23:24 +0200 Subject: [PATCH 128/144] test: replace at.write by at.touch --- tests/by-util/test_hashsum.rs | 12 ++++++------ tests/by-util/test_tail.rs | 4 ++-- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/tests/by-util/test_hashsum.rs b/tests/by-util/test_hashsum.rs index 432eb3ddf6f..a3a5849ff97 100644 --- a/tests/by-util/test_hashsum.rs +++ b/tests/by-util/test_hashsum.rs @@ -504,7 +504,7 @@ fn test_check_strict_error() { let scene = TestScenario::new(util_name!()); let at = &scene.fixtures; - at.write("f", ""); + at.touch("f"); at.write( "in.md5", "ERR\nERR\nd41d8cd98f00b204e9800998ecf8427e f\nERR\n", @@ -523,7 +523,7 @@ fn test_check_warn() { let scene = TestScenario::new(util_name!()); let at = &scene.fixtures; - at.write("f", ""); + at.touch("f"); at.write( "in.md5", "d41d8cd98f00b204e9800998ecf8427e f\nd41d8cd98f00b204e9800998ecf8427e f\ninvalid\n", @@ -551,7 +551,7 @@ fn test_check_status() { let scene = TestScenario::new(util_name!()); let at = &scene.fixtures; - at.write("f", ""); + at.touch("f"); at.write("in.md5", "MD5(f)= d41d8cd98f00b204e9800998ecf8427f\n"); scene .ccmd("md5sum") @@ -567,7 +567,7 @@ fn test_check_status_code() { let scene = TestScenario::new(util_name!()); let at = &scene.fixtures; - at.write("f", ""); + at.touch("f"); at.write("in.md5", "d41d8cd98f00b204e9800998ecf8427f f\n"); scene .ccmd("md5sum") @@ -584,7 +584,7 @@ fn test_check_no_backslash_no_space() { let scene = TestScenario::new(util_name!()); let at = &scene.fixtures; - at.write("f", ""); + at.touch("f"); at.write("in.md5", "MD5(f)= d41d8cd98f00b204e9800998ecf8427e\n"); scene .ccmd("md5sum") @@ -599,7 +599,7 @@ fn test_check_check_ignore_no_file() { let scene = TestScenario::new(util_name!()); let at = &scene.fixtures; - at.write("f", ""); + at.touch("f"); at.write("in.md5", "d41d8cd98f00b204e9800998ecf8427f missing\n"); scene .ccmd("md5sum") diff --git a/tests/by-util/test_tail.rs b/tests/by-util/test_tail.rs index 482d0329409..426d62476f7 100644 --- a/tests/by-util/test_tail.rs +++ b/tests/by-util/test_tail.rs @@ -3622,7 +3622,7 @@ fn test_when_argument_files_are_simple_combinations_of_stdin_and_regular_file() let scene = TestScenario::new(util_name!()); let at = &scene.fixtures; - at.write("empty", ""); + at.touch("empty"); at.write("data", "file data"); at.write("fifo", "fifo data"); @@ -3724,7 +3724,7 @@ fn test_when_argument_files_are_triple_combinations_of_fifo_pipe_and_regular_fil let scene = TestScenario::new(util_name!()); let at = &scene.fixtures; - at.write("empty", ""); + at.touch("empty"); at.write("data", "file data"); at.write("fifo", "fifo data"); From d63bc4a4e1774fffc22b5d376eca9eb8e615057d Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Sun, 21 Apr 2024 14:48:03 +0200 Subject: [PATCH 129/144] seq: add the unit test even if they are failing for now (#6236) * seq: add the unit test even if they are failing for now * improve test Co-authored-by: Daniel Hofstetter --------- Co-authored-by: Daniel Hofstetter --- tests/by-util/test_seq.rs | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/tests/by-util/test_seq.rs b/tests/by-util/test_seq.rs index b29d898a87a..a8bd1fb8359 100644 --- a/tests/by-util/test_seq.rs +++ b/tests/by-util/test_seq.rs @@ -758,6 +758,33 @@ fn test_format_option() { .stdout_only("0.00\n0.10\n0.20\n0.30\n0.40\n0.50\n"); } +#[test] +#[ignore = "Need issue #6233 to be fixed"] +fn test_auto_precision() { + new_ucmd!() + .args(&["1", "0x1p-1", "2"]) + .succeeds() + .stdout_only("1\n1.5\n2\n"); +} + +#[test] +#[ignore = "Need issue #6234 to be fixed"] +fn test_undefined() { + new_ucmd!() + .args(&["1e-9223372036854775808"]) + .succeeds() + .no_output(); +} + +#[test] +#[ignore = "Need issue #6235 to be fixed"] +fn test_invalid_float_point_fail_properly() { + new_ucmd!() + .args(&["66000e000000000000000000000000000000000000000000000000000009223372036854775807"]) + .fails() + .stdout_only(""); // might need to be updated +} + #[test] fn test_invalid_zero_increment_value() { new_ucmd!() From a2bc0ffdb0a4d05f2f124aa9521b5d9d6be7be60 Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Mon, 22 Apr 2024 14:55:14 +0200 Subject: [PATCH 130/144] env: remove dependency on GNU env in tests --- tests/by-util/test_env.rs | 119 +++++++++++++++----------------------- 1 file changed, 48 insertions(+), 71 deletions(-) diff --git a/tests/by-util/test_env.rs b/tests/by-util/test_env.rs index 18edc5dd650..f32a70b13f9 100644 --- a/tests/by-util/test_env.rs +++ b/tests/by-util/test_env.rs @@ -4,8 +4,6 @@ // file that was distributed with this source code. // spell-checker:ignore (words) bamf chdir rlimit prlimit COMSPEC cout cerr FFFD -#[cfg(target_os = "linux")] -use crate::common::util::expected_result; use crate::common::util::TestScenario; use regex::Regex; use std::env; @@ -472,127 +470,105 @@ fn test_gnu_e20() { assert_eq!(out.stdout_str(), output); } -#[macro_export] -macro_rules! compare_with_gnu { - ( $ts:expr, $args:expr ) => {{ - println!("=========================================================================="); - let result = $ts.ucmd().args($args).run(); - - #[cfg(target_os = "linux")] - { - let reference = expected_result(&$ts, $args); - if let Ok(reference) = reference { - let success = result.code() == reference.code() - && result.stdout_str() == reference.stdout_str() - && result.stderr_str() == reference.stderr_str(); - if !success { - println!("reference.code: {}", reference.code()); - println!(" result.code: {}", result.code()); - println!("reference.cout: {}", reference.stdout_str()); - println!(" result.cout: {}", result.stdout_str()); - println!("reference.cerr: {}", reference.stderr_str_lossy()); - println!(" result.cerr: {}", result.stderr_str_lossy()); - } - assert_eq!(result.code(), reference.code()); - assert_eq!(result.stdout_str(), reference.stdout_str()); - assert_eq!(result.stderr_str(), reference.stderr_str()); - } else { - println!( - "gnu reference test skipped. Reason: {:?}", - reference.unwrap_err() - ); - } - } - - result - }}; -} - #[test] #[allow(clippy::cognitive_complexity)] // Ignore clippy lint of too long function sign -fn test_env_with_gnu_reference_parsing_errors() { +fn test_env_parsing_errors() { let ts = TestScenario::new(util_name!()); - compare_with_gnu!(ts, &["-S\\|echo hallo"]) // no quotes, invalid escape sequence | - .failure() + ts.ucmd() + .arg("-S\\|echo hallo") // no quotes, invalid escape sequence | + .fails() .code_is(125) .no_stdout() .stderr_is("env: invalid sequence '\\|' in -S\n"); - compare_with_gnu!(ts, &["-S\\a"]) // no quotes, invalid escape sequence a - .failure() + ts.ucmd() + .arg("-S\\a") // no quotes, invalid escape sequence a + .fails() .code_is(125) .no_stdout() .stderr_is("env: invalid sequence '\\a' in -S\n"); - compare_with_gnu!(ts, &["-S\"\\a\""]) // double quotes, invalid escape sequence a - .failure() + ts.ucmd() + .arg("-S\"\\a\"") // double quotes, invalid escape sequence a + .fails() .code_is(125) .no_stdout() .stderr_is("env: invalid sequence '\\a' in -S\n"); - compare_with_gnu!(ts, &[r#"-S"\a""#]) // same as before, just using r#""# - .failure() + ts.ucmd() + .arg(r#"-S"\a""#) // same as before, just using r#""# + .fails() .code_is(125) .no_stdout() .stderr_is("env: invalid sequence '\\a' in -S\n"); - compare_with_gnu!(ts, &["-S'\\a'"]) // single quotes, invalid escape sequence a - .failure() + ts.ucmd() + .arg("-S'\\a'") // single quotes, invalid escape sequence a + .fails() .code_is(125) .no_stdout() .stderr_is("env: invalid sequence '\\a' in -S\n"); - compare_with_gnu!(ts, &[r#"-S\|\&\;"#]) // no quotes, invalid escape sequence | - .failure() + ts.ucmd() + .arg(r#"-S\|\&\;"#) // no quotes, invalid escape sequence | + .fails() .code_is(125) .no_stdout() .stderr_is("env: invalid sequence '\\|' in -S\n"); - compare_with_gnu!(ts, &[r#"-S\<\&\;"#]) // no quotes, invalid escape sequence < - .failure() + ts.ucmd() + .arg(r#"-S\<\&\;"#) // no quotes, invalid escape sequence < + .fails() .code_is(125) .no_stdout() .stderr_is("env: invalid sequence '\\<' in -S\n"); - compare_with_gnu!(ts, &[r#"-S\>\&\;"#]) // no quotes, invalid escape sequence > - .failure() + ts.ucmd() + .arg(r#"-S\>\&\;"#) // no quotes, invalid escape sequence > + .fails() .code_is(125) .no_stdout() .stderr_is("env: invalid sequence '\\>' in -S\n"); - compare_with_gnu!(ts, &[r#"-S\`\&\;"#]) // no quotes, invalid escape sequence ` - .failure() + ts.ucmd() + .arg(r#"-S\`\&\;"#) // no quotes, invalid escape sequence ` + .fails() .code_is(125) .no_stdout() .stderr_is("env: invalid sequence '\\`' in -S\n"); - compare_with_gnu!(ts, &[r#"-S"\`\&\;""#]) // double quotes, invalid escape sequence ` - .failure() + ts.ucmd() + .arg(r#"-S"\`\&\;""#) // double quotes, invalid escape sequence ` + .fails() .code_is(125) .no_stdout() .stderr_is("env: invalid sequence '\\`' in -S\n"); - compare_with_gnu!(ts, &[r#"-S'\`\&\;'"#]) // single quotes, invalid escape sequence ` - .failure() + ts.ucmd() + .arg(r#"-S'\`\&\;'"#) // single quotes, invalid escape sequence ` + .fails() .code_is(125) .no_stdout() .stderr_is("env: invalid sequence '\\`' in -S\n"); - compare_with_gnu!(ts, &[r#"-S\`"#]) // ` escaped without quotes - .failure() + ts.ucmd() + .arg(r#"-S\`"#) // ` escaped without quotes + .fails() .code_is(125) .no_stdout() .stderr_is("env: invalid sequence '\\`' in -S\n"); - compare_with_gnu!(ts, &[r#"-S"\`""#]) // ` escaped in double quotes - .failure() + ts.ucmd() + .arg(r#"-S"\`""#) // ` escaped in double quotes + .fails() .code_is(125) .no_stdout() .stderr_is("env: invalid sequence '\\`' in -S\n"); - compare_with_gnu!(ts, &[r#"-S'\`'"#]) // ` escaped in single quotes - .failure() + ts.ucmd() + .arg(r#"-S'\`'"#) // ` escaped in single quotes + .fails() .code_is(125) .no_stdout() .stderr_is("env: invalid sequence '\\`' in -S\n"); @@ -606,7 +582,7 @@ fn test_env_with_gnu_reference_parsing_errors() { } #[test] -fn test_env_with_gnu_reference_empty_executable_single_quotes() { +fn test_env_with_empty_executable_single_quotes() { let ts = TestScenario::new(util_name!()); ts.ucmd() @@ -618,11 +594,12 @@ fn test_env_with_gnu_reference_empty_executable_single_quotes() { } #[test] -fn test_env_with_gnu_reference_empty_executable_double_quotes() { +fn test_env_with_empty_executable_double_quotes() { let ts = TestScenario::new(util_name!()); - compare_with_gnu!(ts, &["-S\"\""]) // empty double quotes, considered as program name - .failure() + ts.ucmd() + .args(&["-S\"\""]) // empty double quotes, considered as program name + .fails() .code_is(127) .no_stdout() .stderr_is("env: '': No such file or directory\n"); From d8792d7b69423942a4be88815cb9e55c64361aad Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Mon, 22 Apr 2024 16:56:21 +0200 Subject: [PATCH 131/144] env: add missing space to help output --- src/uu/env/src/env.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/uu/env/src/env.rs b/src/uu/env/src/env.rs index f4282326fef..342fb737d63 100644 --- a/src/uu/env/src/env.rs +++ b/src/uu/env/src/env.rs @@ -193,7 +193,7 @@ pub fn uu_app() -> Command { .value_name("a") .action(ArgAction::Set) .value_parser(ValueParser::os_string()) - .help("Override the zeroth argument passed to the command being executed.\ + .help("Override the zeroth argument passed to the command being executed. \ Without this option a default value of `command` is used.") ) .arg( From 421b820ec2105bf8b2192d39541beeec52e60e51 Mon Sep 17 00:00:00 2001 From: Anirban Halder Date: Mon, 22 Apr 2024 20:32:21 +0530 Subject: [PATCH 132/144] Fix the debug results in `cp --debug` (#6220) --- src/uu/cp/src/platform/linux.rs | 455 ++++++++++++++++++++++- tests/by-util/test_cp.rs | 638 +++++++++++++++++++++++++++++++- 2 files changed, 1075 insertions(+), 18 deletions(-) diff --git a/src/uu/cp/src/platform/linux.rs b/src/uu/cp/src/platform/linux.rs index 674e66ea575..637b8969c45 100644 --- a/src/uu/cp/src/platform/linux.rs +++ b/src/uu/cp/src/platform/linux.rs @@ -2,10 +2,14 @@ // // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. -// spell-checker:ignore ficlone reflink ftruncate pwrite fiemap +// spell-checker:ignore ficlone reflink ftruncate pwrite fiemap lseek + +use libc::{SEEK_DATA, SEEK_HOLE}; use std::fs::{File, OpenOptions}; use std::io::Read; -use std::os::unix::fs::OpenOptionsExt; +use std::os::unix::fs::FileExt; +use std::os::unix::fs::MetadataExt; +use std::os::unix::fs::{FileTypeExt, OpenOptionsExt}; use std::os::unix::io::AsRawFd; use std::path::Path; @@ -32,6 +36,25 @@ enum CloneFallback { /// Use [`std::fs::copy`]. FSCopy, + + /// Use sparse_copy + SparseCopy, + + /// Use sparse_copy_without_hole + SparseCopyWithoutHole, +} + +/// Type of method used for copying files +#[derive(Clone, Copy)] +enum CopyMethod { + /// Do a sparse copy + SparseCopy, + /// Use [`std::fs::copy`]. + FSCopy, + /// Default (can either be sparse_copy or FSCopy) + Default, + /// Use sparse_copy_without_hole + SparseCopyWithoutHole, } /// Use the Linux `ioctl_ficlone` API to do a copy-on-write clone. @@ -53,17 +76,109 @@ where match fallback { CloneFallback::Error => Err(std::io::Error::last_os_error()), CloneFallback::FSCopy => std::fs::copy(source, dest).map(|_| ()), + CloneFallback::SparseCopy => sparse_copy(source, dest), + CloneFallback::SparseCopyWithoutHole => sparse_copy_without_hole(source, dest), + } +} + +/// Checks whether a file contains any non null bytes i.e. any byte != 0x0 +/// This function returns a tuple of (bool, u64, u64) signifying a tuple of (whether a file has +/// data, its size, no of blocks it has allocated in disk) +#[cfg(any(target_os = "linux", target_os = "android"))] +fn check_for_data(source: &Path) -> Result<(bool, u64, u64), std::io::Error> { + let mut src_file = File::open(source)?; + let metadata = src_file.metadata()?; + + let size = metadata.size(); + let blocks = metadata.blocks(); + // checks edge case of virtual files in /proc which have a size of zero but contains data + if size == 0 { + let mut buf: Vec = vec![0; metadata.blksize() as usize]; // Directly use metadata.blksize() + let _ = src_file.read(&mut buf)?; + return Ok((buf.iter().any(|&x| x != 0x0), size, 0)); + } + + let src_fd = src_file.as_raw_fd(); + + let result = unsafe { libc::lseek(src_fd, 0, SEEK_DATA) }; + + match result { + -1 => Ok((false, size, blocks)), // No data found or end of file + _ if result >= 0 => Ok((true, size, blocks)), // Data found + _ => Err(std::io::Error::last_os_error()), + } +} + +#[cfg(any(target_os = "linux", target_os = "android"))] +/// Checks whether a file is sparse i.e. it contains holes, uses the crude heuristic blocks < size / 512 +/// Reference:`` +fn check_sparse_detection(source: &Path) -> Result { + let src_file = File::open(source)?; + let metadata = src_file.metadata()?; + let size = metadata.size(); + let blocks = metadata.blocks(); + + if blocks < size / 512 { + return Ok(true); } + Ok(false) } +/// Optimized sparse_copy, doesn't create holes for large sequences of zeros in non sparse_files +/// Used when --sparse=auto +#[cfg(any(target_os = "linux", target_os = "android"))] +fn sparse_copy_without_hole

(source: P, dest: P) -> std::io::Result<()> +where + P: AsRef, +{ + let src_file = File::open(source)?; + let dst_file = File::create(dest)?; + let dst_fd = dst_file.as_raw_fd(); + + let size = src_file.metadata()?.size(); + if unsafe { libc::ftruncate(dst_fd, size.try_into().unwrap()) } < 0 { + return Err(std::io::Error::last_os_error()); + } + let src_fd = src_file.as_raw_fd(); + let mut current_offset: isize = 0; + loop { + let result = unsafe { libc::lseek(src_fd, current_offset.try_into().unwrap(), SEEK_DATA) } + .try_into() + .unwrap(); + + current_offset = result; + let hole: isize = + unsafe { libc::lseek(src_fd, current_offset.try_into().unwrap(), SEEK_HOLE) } + .try_into() + .unwrap(); + if result == -1 || hole == -1 { + break; + } + if result <= -2 || hole <= -2 { + return Err(std::io::Error::last_os_error()); + } + let len: isize = hole - current_offset; + let mut buf: Vec = vec![0x0; len as usize]; + src_file.read_exact_at(&mut buf, current_offset as u64)?; + unsafe { + libc::pwrite( + dst_fd, + buf.as_ptr() as *const libc::c_void, + len as usize, + current_offset.try_into().unwrap(), + ) + }; + current_offset = hole; + } + Ok(()) +} /// Perform a sparse copy from one file to another. +/// Creates a holes for large sequences of zeros in non_sparse_files, used for --sparse=always #[cfg(any(target_os = "linux", target_os = "android"))] fn sparse_copy

(source: P, dest: P) -> std::io::Result<()> where P: AsRef, { - use std::os::unix::prelude::MetadataExt; - let mut src_file = File::open(source)?; let dst_file = File::create(dest)?; let dst_fd = dst_file.as_raw_fd(); @@ -97,6 +212,18 @@ where Ok(()) } +#[cfg(any(target_os = "linux", target_os = "android"))] +/// Checks whether an existing destination is a fifo +fn check_dest_is_fifo(dest: &Path) -> bool { + // If our destination file exists and its a fifo , we do a standard copy . + let file_type = std::fs::metadata(dest); + match file_type { + Ok(f) => f.file_type().is_fifo(), + + _ => false, + } +} + /// Copy the contents of the given source FIFO to the given file. fn copy_fifo_contents

(source: P, dest: P) -> std::io::Result where @@ -151,35 +278,121 @@ pub(crate) fn copy_on_write( reflink: OffloadReflinkDebug::Unsupported, sparse_detection: SparseDebug::No, }; - let result = match (reflink_mode, sparse_mode) { (ReflinkMode::Never, SparseMode::Always) => { copy_debug.sparse_detection = SparseDebug::Zeros; - copy_debug.offload = OffloadReflinkDebug::Avoided; + // Default SparseDebug val for SparseMode::Always copy_debug.reflink = OffloadReflinkDebug::No; - sparse_copy(source, dest) + if source_is_fifo { + copy_debug.offload = OffloadReflinkDebug::Avoided; + + copy_fifo_contents(source, dest).map(|_| ()) + } else { + let mut copy_method = CopyMethod::Default; + let result = handle_reflink_never_sparse_always(source, dest); + if let Ok((debug, method)) = result { + copy_debug = debug; + copy_method = method; + } + + match copy_method { + CopyMethod::FSCopy => std::fs::copy(source, dest).map(|_| ()), + _ => sparse_copy(source, dest), + } + } } - (ReflinkMode::Never, _) => { - copy_debug.sparse_detection = SparseDebug::No; + (ReflinkMode::Never, SparseMode::Never) => { copy_debug.reflink = OffloadReflinkDebug::No; - std::fs::copy(source, dest).map(|_| ()) + + if source_is_fifo { + copy_debug.offload = OffloadReflinkDebug::Avoided; + + copy_fifo_contents(source, dest).map(|_| ()) + } else { + let result = handle_reflink_never_sparse_never(source); + if let Ok(debug) = result { + copy_debug = debug; + } + std::fs::copy(source, dest).map(|_| ()) + } + } + (ReflinkMode::Never, SparseMode::Auto) => { + copy_debug.reflink = OffloadReflinkDebug::No; + + if source_is_fifo { + copy_debug.offload = OffloadReflinkDebug::Avoided; + copy_fifo_contents(source, dest).map(|_| ()) + } else { + let mut copy_method = CopyMethod::Default; + let result = handle_reflink_never_sparse_auto(source, dest); + if let Ok((debug, method)) = result { + copy_debug = debug; + copy_method = method; + } + + match copy_method { + CopyMethod::SparseCopyWithoutHole => sparse_copy_without_hole(source, dest), + _ => std::fs::copy(source, dest).map(|_| ()), + } + } } (ReflinkMode::Auto, SparseMode::Always) => { - copy_debug.offload = OffloadReflinkDebug::Avoided; - copy_debug.sparse_detection = SparseDebug::Zeros; - copy_debug.reflink = OffloadReflinkDebug::Unsupported; - sparse_copy(source, dest) + copy_debug.sparse_detection = SparseDebug::Zeros; // Default SparseDebug val for + // SparseMode::Always + if source_is_fifo { + copy_debug.offload = OffloadReflinkDebug::Avoided; + + copy_fifo_contents(source, dest).map(|_| ()) + } else { + let mut copy_method = CopyMethod::Default; + let result = handle_reflink_auto_sparse_always(source, dest); + if let Ok((debug, method)) = result { + copy_debug = debug; + copy_method = method; + } + + match copy_method { + CopyMethod::FSCopy => clone(source, dest, CloneFallback::FSCopy), + _ => clone(source, dest, CloneFallback::SparseCopy), + } + } } - (ReflinkMode::Auto, _) => { - copy_debug.sparse_detection = SparseDebug::No; - copy_debug.reflink = OffloadReflinkDebug::Unsupported; + (ReflinkMode::Auto, SparseMode::Never) => { + copy_debug.reflink = OffloadReflinkDebug::No; if source_is_fifo { + copy_debug.offload = OffloadReflinkDebug::Avoided; copy_fifo_contents(source, dest).map(|_| ()) } else { + let result = handle_reflink_auto_sparse_never(source); + if let Ok(debug) = result { + copy_debug = debug; + } + clone(source, dest, CloneFallback::FSCopy) } } + (ReflinkMode::Auto, SparseMode::Auto) => { + if source_is_fifo { + copy_debug.offload = OffloadReflinkDebug::Unsupported; + copy_fifo_contents(source, dest).map(|_| ()) + } else { + let mut copy_method = CopyMethod::Default; + let result = handle_reflink_auto_sparse_auto(source, dest); + if let Ok((debug, method)) = result { + copy_debug = debug; + copy_method = method; + } + + match copy_method { + CopyMethod::SparseCopyWithoutHole => { + clone(source, dest, CloneFallback::SparseCopyWithoutHole) + } + _ => clone(source, dest, CloneFallback::FSCopy), + } + } + } + (ReflinkMode::Always, SparseMode::Auto) => { copy_debug.sparse_detection = SparseDebug::No; copy_debug.reflink = OffloadReflinkDebug::Yes; @@ -193,3 +406,211 @@ pub(crate) fn copy_on_write( result.context(context)?; Ok(copy_debug) } + +/// Handles debug results when flags are "--reflink=auto" and "--sparse=always" and specifies what +/// type of copy should be used +fn handle_reflink_auto_sparse_always( + source: &Path, + dest: &Path, +) -> Result<(CopyDebug, CopyMethod), std::io::Error> { + let mut copy_debug = CopyDebug { + offload: OffloadReflinkDebug::Unknown, + reflink: OffloadReflinkDebug::Unsupported, + sparse_detection: SparseDebug::Zeros, + }; + let mut copy_method = CopyMethod::Default; + let (data_flag, size, blocks) = check_for_data(source)?; + let sparse_flag = check_sparse_detection(source)?; + + if data_flag || size < 512 { + copy_debug.offload = OffloadReflinkDebug::Avoided; + } + match (sparse_flag, data_flag, blocks) { + (true, true, 0) => { + // Handling funny files with 0 block allocation but has data + // in it + copy_method = CopyMethod::FSCopy; + copy_debug.sparse_detection = SparseDebug::SeekHoleZeros; + } + (false, true, 0) => copy_method = CopyMethod::FSCopy, + + (true, false, 0) => copy_debug.sparse_detection = SparseDebug::SeekHole, + (true, true, _) => copy_debug.sparse_detection = SparseDebug::SeekHoleZeros, + + (true, false, _) => copy_debug.sparse_detection = SparseDebug::SeekHole, + + (_, _, _) => (), + } + if check_dest_is_fifo(dest) { + copy_method = CopyMethod::FSCopy; + } + Ok((copy_debug, copy_method)) +} + +/// Handles debug results when flags are "--reflink=auto" and "--sparse=auto" and specifies what +/// type of copy should be used +fn handle_reflink_never_sparse_never(source: &Path) -> Result { + let mut copy_debug = CopyDebug { + offload: OffloadReflinkDebug::Unknown, + reflink: OffloadReflinkDebug::No, + sparse_detection: SparseDebug::No, + }; + let (data_flag, size, _blocks) = check_for_data(source)?; + let sparse_flag = check_sparse_detection(source)?; + + if sparse_flag { + copy_debug.sparse_detection = SparseDebug::SeekHole; + } + + if data_flag || size < 512 { + copy_debug.offload = OffloadReflinkDebug::Avoided; + } + Ok(copy_debug) +} + +/// Handles debug results when flags are "--reflink=auto" and "--sparse=never", files will be copied +/// through cloning them with fallback switching to std::fs::copy +fn handle_reflink_auto_sparse_never(source: &Path) -> Result { + let mut copy_debug = CopyDebug { + offload: OffloadReflinkDebug::Unknown, + reflink: OffloadReflinkDebug::No, + sparse_detection: SparseDebug::No, + }; + + let (data_flag, size, _blocks) = check_for_data(source)?; + let sparse_flag = check_sparse_detection(source)?; + + if sparse_flag { + copy_debug.sparse_detection = SparseDebug::SeekHole; + } + + if data_flag || size < 512 { + copy_debug.offload = OffloadReflinkDebug::Avoided; + } + Ok(copy_debug) +} + +/// Handles debug results when flags are "--reflink=auto" and "--sparse=auto" and specifies what +/// type of copy should be used +fn handle_reflink_auto_sparse_auto( + source: &Path, + dest: &Path, +) -> Result<(CopyDebug, CopyMethod), std::io::Error> { + let mut copy_debug = CopyDebug { + offload: OffloadReflinkDebug::Unknown, + reflink: OffloadReflinkDebug::Unsupported, + sparse_detection: SparseDebug::No, + }; + + let mut copy_method = CopyMethod::Default; + let (data_flag, size, blocks) = check_for_data(source)?; + let sparse_flag = check_sparse_detection(source)?; + + if (data_flag && size != 0) || (size > 0 && size < 512) { + copy_debug.offload = OffloadReflinkDebug::Yes; + } + + if data_flag && size == 0 { + // Handling /proc/ files + copy_debug.offload = OffloadReflinkDebug::Unsupported; + } + if sparse_flag { + if blocks == 0 && data_flag { + // Handling other "virtual" files + copy_debug.offload = OffloadReflinkDebug::Unsupported; + + copy_method = CopyMethod::FSCopy; // Doing a standard copy for the virtual files + } else { + copy_method = CopyMethod::SparseCopyWithoutHole; + } // Since sparse_flag is true, sparse_detection shall be SeekHole for any non virtual + // regular sparse file and the file will be sparsely copied + copy_debug.sparse_detection = SparseDebug::SeekHole; + } + + if check_dest_is_fifo(dest) { + copy_method = CopyMethod::FSCopy; + } + Ok((copy_debug, copy_method)) +} + +/// Handles debug results when flags are "--reflink=never" and "--sparse=auto" and specifies what +/// type of copy should be used +fn handle_reflink_never_sparse_auto( + source: &Path, + dest: &Path, +) -> Result<(CopyDebug, CopyMethod), std::io::Error> { + let mut copy_debug = CopyDebug { + offload: OffloadReflinkDebug::Unknown, + reflink: OffloadReflinkDebug::No, + sparse_detection: SparseDebug::No, + }; + + let (data_flag, size, blocks) = check_for_data(source)?; + let sparse_flag = check_sparse_detection(source)?; + + let mut copy_method = CopyMethod::Default; + if data_flag || size < 512 { + copy_debug.offload = OffloadReflinkDebug::Avoided; + } + + if sparse_flag { + if blocks == 0 && data_flag { + copy_method = CopyMethod::FSCopy; // Handles virtual files which have size > 0 but no + // disk allocation + } else { + copy_method = CopyMethod::SparseCopyWithoutHole; // Handles regular sparse-files + } + copy_debug.sparse_detection = SparseDebug::SeekHole; + } + + if check_dest_is_fifo(dest) { + copy_method = CopyMethod::FSCopy; + } + Ok((copy_debug, copy_method)) +} + +/// Handles debug results when flags are "--reflink=never" and "--sparse=always" and specifies what +/// type of copy should be used +fn handle_reflink_never_sparse_always( + source: &Path, + dest: &Path, +) -> Result<(CopyDebug, CopyMethod), std::io::Error> { + let mut copy_debug = CopyDebug { + offload: OffloadReflinkDebug::Unknown, + reflink: OffloadReflinkDebug::No, + sparse_detection: SparseDebug::Zeros, + }; + let mut copy_method = CopyMethod::SparseCopy; + + let (data_flag, size, blocks) = check_for_data(source)?; + let sparse_flag = check_sparse_detection(source)?; + + if data_flag || size < 512 { + copy_debug.offload = OffloadReflinkDebug::Avoided; + } + match (sparse_flag, data_flag, blocks) { + (true, true, 0) => { + // Handling funny files with 0 block allocation but has data + // in it, e.g. files in /sys and other virtual files + copy_method = CopyMethod::FSCopy; + copy_debug.sparse_detection = SparseDebug::SeekHoleZeros; + } + (false, true, 0) => copy_method = CopyMethod::FSCopy, // Handling data containing zero sized + // files in /proc + (true, false, 0) => copy_debug.sparse_detection = SparseDebug::SeekHole, // Handles files + // with 0 blocks allocated in disk and + (true, true, _) => copy_debug.sparse_detection = SparseDebug::SeekHoleZeros, // Any + // sparse_files with data in it will display SeekHoleZeros + (true, false, _) => { + copy_debug.offload = OffloadReflinkDebug::Unknown; + copy_debug.sparse_detection = SparseDebug::SeekHole; + } + + (_, _, _) => (), + } + if check_dest_is_fifo(dest) { + copy_method = CopyMethod::FSCopy; + } + + Ok((copy_debug, copy_method)) +} diff --git a/tests/by-util/test_cp.rs b/tests/by-util/test_cp.rs index e417e40b195..8ab6ccc0ec1 100644 --- a/tests/by-util/test_cp.rs +++ b/tests/by-util/test_cp.rs @@ -3581,7 +3581,7 @@ fn test_cp_debug_sparse_never() { .arg("b") .succeeds(); let stdout_str = result.stdout_str(); - if !stdout_str.contains("copy offload: unknown, reflink: unsupported, sparse detection: no") { + if !stdout_str.contains("copy offload: avoided, reflink: no, sparse detection: no") { panic!("Failure: stdout was \n{stdout_str}"); } } @@ -3831,6 +3831,642 @@ fn test_acl_preserve() { assert!(compare_xattrs(&file, &file_target)); } +#[test] +#[cfg(any(target_os = "linux", target_os = "android"))] +fn test_cp_debug_reflink_never_with_hole() { + let ts = TestScenario::new(util_name!()); + let at = &ts.fixtures; + at.touch("a"); + at.write("a", "hello"); + let f = std::fs::OpenOptions::new() + .write(true) + .open(at.plus("a")) + .unwrap(); + f.set_len(10000).unwrap(); + + let result = ts + .ucmd() + .arg("--debug") + .arg("--reflink=never") + .arg("a") + .arg("b") + .succeeds(); + let dst_file_metadata = std::fs::metadata(at.plus("b")).unwrap(); + let src_file_metadata = std::fs::metadata(at.plus("a")).unwrap(); + if dst_file_metadata.blocks() != src_file_metadata.blocks() { + panic!("File not sparsely copied"); + } + + let stdout_str = result.stdout_str(); + if !stdout_str.contains("copy offload: avoided, reflink: no, sparse detection: SEEK_HOLE") { + panic!("Failure: stdout was \n{stdout_str}"); + } +} +#[test] +#[cfg(any(target_os = "linux", target_os = "android"))] +fn test_cp_debug_reflink_never_empty_file_with_hole() { + let ts = TestScenario::new(util_name!()); + let at = &ts.fixtures; + at.touch("a"); + let f = std::fs::OpenOptions::new() + .write(true) + .open(at.plus("a")) + .unwrap(); + f.set_len(10000).unwrap(); + + let result = ts + .ucmd() + .arg("--debug") + .arg("--reflink=never") + .arg("a") + .arg("b") + .succeeds(); + let dst_file_metadata = std::fs::metadata(at.plus("b")).unwrap(); + let src_file_metadata = std::fs::metadata(at.plus("a")).unwrap(); + if dst_file_metadata.blocks() != src_file_metadata.blocks() { + panic!("File not sparsely copied"); + } + + let stdout_str = result.stdout_str(); + if !stdout_str.contains("copy offload: unknown, reflink: no, sparse detection: SEEK_HOLE") { + panic!("Failure: stdout was \n{stdout_str}"); + } +} + +#[test] +#[cfg(any(target_os = "linux", target_os = "android"))] +fn test_cp_debug_default_with_hole() { + let ts = TestScenario::new(util_name!()); + let at = &ts.fixtures; + at.touch("a"); + let f = std::fs::OpenOptions::new() + .write(true) + .open(at.plus("a")) + .unwrap(); + f.set_len(10000).unwrap(); + + at.append_bytes("a", "hello".as_bytes()); + let result = ts.ucmd().arg("--debug").arg("a").arg("b").succeeds(); + let dst_file_metadata = std::fs::metadata(at.plus("b")).unwrap(); + let src_file_metadata = std::fs::metadata(at.plus("a")).unwrap(); + if dst_file_metadata.blocks() != src_file_metadata.blocks() { + panic!("File not sparsely copied"); + } + + let stdout_str = result.stdout_str(); + if !stdout_str.contains("copy offload: yes, reflink: unsupported, sparse detection: SEEK_HOLE") + { + panic!("Failure: stdout was \n{stdout_str}"); + } +} + +#[test] +#[cfg(any(target_os = "linux", target_os = "android"))] +fn test_cp_debug_default_less_than_512_bytes() { + let ts = TestScenario::new(util_name!()); + + let at = &ts.fixtures; + at.touch("a"); + at.append_bytes("a", "hello".as_bytes()); + let f = std::fs::OpenOptions::new() + .write(true) + .open(at.plus("a")) + .unwrap(); + f.set_len(400).unwrap(); + + let result = ts + .ucmd() + .arg("--debug") + .arg("--reflink=auto") + .arg("--sparse=auto") + .arg("a") + .arg("b") + .succeeds(); + + let stdout_str = result.stdout_str(); + if !stdout_str.contains("copy offload: yes, reflink: unsupported, sparse detection: no") { + panic!("Failure: stdout was \n{stdout_str}"); + } +} + +#[test] +#[cfg(any(target_os = "linux", target_os = "android"))] +fn test_cp_debug_default_without_hole() { + let ts = TestScenario::new(util_name!()); + + let at = &ts.fixtures; + at.touch("a"); + at.append_bytes("a", "hello".as_bytes()); + + let filler_bytes = [0_u8; 10000]; + + at.append_bytes("a", &filler_bytes); + + let result = ts.ucmd().arg("--debug").arg("a").arg("b").succeeds(); + + let stdout_str = result.stdout_str(); + if !stdout_str.contains("copy offload: yes, reflink: unsupported, sparse detection: no") { + panic!("Failure: stdout was \n{stdout_str}"); + } +} +#[test] +#[cfg(any(target_os = "linux", target_os = "android"))] +fn test_cp_debug_default_empty_file_with_hole() { + let ts = TestScenario::new(util_name!()); + let at = &ts.fixtures; + at.touch("a"); + let f = std::fs::OpenOptions::new() + .write(true) + .open(at.plus("a")) + .unwrap(); + f.set_len(10000).unwrap(); + + let result = ts.ucmd().arg("--debug").arg("a").arg("b").succeeds(); + let dst_file_metadata = std::fs::metadata(at.plus("b")).unwrap(); + let src_file_metadata = std::fs::metadata(at.plus("a")).unwrap(); + if dst_file_metadata.blocks() != src_file_metadata.blocks() { + panic!("File not sparsely copied"); + } + + let stdout_str = result.stdout_str(); + if !stdout_str + .contains("copy offload: unknown, reflink: unsupported, sparse detection: SEEK_HOLE") + { + panic!("Failure: stdout was \n{stdout_str}"); + } +} + +#[test] +#[cfg(any(target_os = "linux", target_os = "android"))] +fn test_cp_debug_reflink_never_sparse_always_with_hole() { + let ts = TestScenario::new(util_name!()); + let at = &ts.fixtures; + at.touch("a"); + at.write("a", "hello"); + let f = std::fs::OpenOptions::new() + .write(true) + .open(at.plus("a")) + .unwrap(); + f.set_len(10000).unwrap(); + + let result = ts + .ucmd() + .arg("--debug") + .arg("--reflink=never") + .arg("--sparse=always") + .arg("a") + .arg("b") + .succeeds(); + let dst_file_metadata = std::fs::metadata(at.plus("b")).unwrap(); + let src_file_metadata = std::fs::metadata(at.plus("a")).unwrap(); + if dst_file_metadata.blocks() != src_file_metadata.blocks() { + panic!("File not sparsely copied"); + } + let stdout_str = result.stdout_str(); + if !stdout_str + .contains("copy offload: avoided, reflink: no, sparse detection: SEEK_HOLE + zeros") + { + panic!("Failure: stdout was \n{stdout_str}"); + } +} + +#[test] +#[cfg(any(target_os = "linux", target_os = "android"))] +fn test_cp_debug_reflink_never_sparse_always_without_hole() { + let ts = TestScenario::new(util_name!()); + let empty_bytes = [0_u8; 10000]; + let at = &ts.fixtures; + at.touch("a"); + at.write("a", "hello"); + at.append_bytes("a", &empty_bytes); + + let result = ts + .ucmd() + .arg("--debug") + .arg("--reflink=never") + .arg("--sparse=always") + .arg("a") + .arg("b") + .succeeds(); + let dst_file_metadata = std::fs::metadata(at.plus("b")).unwrap(); + + if dst_file_metadata.blocks() != dst_file_metadata.blksize() / 512 { + panic!("Zero sequenced blocks not removed"); + } + let stdout_str = result.stdout_str(); + if !stdout_str.contains("copy offload: avoided, reflink: no, sparse detection: zeros") { + panic!("Failure: stdout was \n{stdout_str}"); + } +} + +#[test] +#[cfg(any(target_os = "linux", target_os = "android"))] +fn test_cp_debug_reflink_never_sparse_always_empty_file_with_hole() { + let ts = TestScenario::new(util_name!()); + let at = &ts.fixtures; + at.touch("a"); + let f = std::fs::OpenOptions::new() + .write(true) + .open(at.plus("a")) + .unwrap(); + f.set_len(10000).unwrap(); + + let result = ts + .ucmd() + .arg("--debug") + .arg("--reflink=never") + .arg("--sparse=always") + .arg("a") + .arg("b") + .succeeds(); + + let stdout_str = result.stdout_str(); + if !stdout_str.contains("copy offload: unknown, reflink: no, sparse detection: SEEK_HOLE") { + panic!("Failure: stdout was \n{stdout_str}"); + } +} + +#[test] +#[cfg(target_os = "linux")] +fn test_cp_default_virtual_file() { + use std::os::unix::prelude::MetadataExt; + let ts = TestScenario::new(util_name!()); + let at = &ts.fixtures; + ts.ucmd() + .arg("/sys/kernel/address_bits") + .arg("b") + .succeeds(); + + let dest_size = std::fs::metadata(at.plus("b")) + .expect("Metadata of copied file cannot be read") + .size(); + if dest_size == 0 { + panic!("Copy unsuccessful"); + } +} +#[test] +#[cfg(any(target_os = "linux", target_os = "android"))] +fn test_cp_debug_reflink_auto_sparse_always_non_sparse_file_with_long_zero_sequence() { + let ts = TestScenario::new(util_name!()); + + let buf: Vec = vec![0; 4096 * 4]; + let at = &ts.fixtures; + at.touch("a"); + at.append_bytes("a", &buf); + at.append_bytes("a", "hello".as_bytes()); + let result = ts + .ucmd() + .arg("--debug") + .arg("--sparse=always") + .arg("a") + .arg("b") + .succeeds(); + + let dst_file_metadata = std::fs::metadata(at.plus("b")).unwrap(); + + if dst_file_metadata.blocks() != dst_file_metadata.blksize() / 512 { + panic!("Zero sequenced blocks not removed"); + } + + let stdout_str = result.stdout_str(); + if !stdout_str.contains("copy offload: avoided, reflink: unsupported, sparse detection: zeros") + { + panic!("Failure: stdout was \n{stdout_str}"); + } +} + +#[test] +#[cfg(target_os = "linux")] +fn test_cp_debug_sparse_never_empty_sparse_file() { + let ts = TestScenario::new(util_name!()); + let at = &ts.fixtures; + at.touch("a"); + let result = ts + .ucmd() + .arg("--debug") + .arg("--sparse=never") + .arg("a") + .arg("b") + .succeeds(); + let stdout_str = result.stdout_str(); + if !stdout_str.contains("copy offload: avoided, reflink: no, sparse detection: no") { + panic!("Failure: stdout was \n{stdout_str}"); + } +} + +#[test] +#[cfg(any(target_os = "linux", target_os = "android"))] +fn test_cp_debug_reflink_never_sparse_always_non_sparse_file_with_long_zero_sequence() { + let ts = TestScenario::new(util_name!()); + + let buf: Vec = vec![0; 4096 * 4]; + let at = &ts.fixtures; + at.touch("a"); + at.append_bytes("a", &buf); + at.append_bytes("a", "hello".as_bytes()); + let result = ts + .ucmd() + .arg("--debug") + .arg("--reflink=never") + .arg("--sparse=always") + .arg("a") + .arg("b") + .succeeds(); + + let dst_file_metadata = std::fs::metadata(at.plus("b")).unwrap(); + + if dst_file_metadata.blocks() != dst_file_metadata.blksize() / 512 { + panic!("Zero sequenced blocks not removed"); + } + + let stdout_str = result.stdout_str(); + if !stdout_str.contains("copy offload: avoided, reflink: no, sparse detection: zeros") { + panic!("Failure: stdout was \n{stdout_str}"); + } +} + +#[test] +#[cfg(target_os = "linux")] +fn test_cp_debug_sparse_always_sparse_virtual_file() { + let ts = TestScenario::new(util_name!()); + let result = ts + .ucmd() + .arg("--debug") + .arg("--sparse=always") + .arg("/sys/kernel/address_bits") + .arg("b") + .succeeds(); + + let stdout_str = result.stdout_str(); + if !stdout_str.contains( + "copy offload: avoided, reflink: unsupported, sparse detection: SEEK_HOLE + zeros", + ) { + panic!("Failure: stdout was \n{stdout_str}"); + } +} +#[test] +#[cfg(any(target_os = "linux", target_os = "android"))] +fn test_cp_debug_reflink_never_less_than_512_bytes() { + let ts = TestScenario::new(util_name!()); + + let at = &ts.fixtures; + at.touch("a"); + at.append_bytes("a", "hello".as_bytes()); + let f = std::fs::OpenOptions::new() + .write(true) + .open(at.plus("a")) + .unwrap(); + f.set_len(400).unwrap(); + + let result = ts + .ucmd() + .arg("--debug") + .arg("--reflink=never") + .arg("a") + .arg("b") + .succeeds(); + + let stdout_str = result.stdout_str(); + if !stdout_str.contains("copy offload: avoided, reflink: no, sparse detection: no") { + panic!("Failure: stdout was \n{stdout_str}"); + } +} + +#[test] +#[cfg(any(target_os = "linux", target_os = "android"))] +fn test_cp_debug_reflink_never_sparse_never_empty_file_with_hole() { + let ts = TestScenario::new(util_name!()); + let at = &ts.fixtures; + at.touch("a"); + let f = std::fs::OpenOptions::new() + .write(true) + .open(at.plus("a")) + .unwrap(); + f.set_len(10000).unwrap(); + + let result = ts + .ucmd() + .arg("--debug") + .arg("--reflink=never") + .arg("--sparse=never") + .arg("a") + .arg("b") + .succeeds(); + let stdout_str = result.stdout_str(); + if !stdout_str.contains("copy offload: unknown, reflink: no, sparse detection: SEEK_HOLE") { + panic!("Failure: stdout was \n{stdout_str}"); + } +} +#[test] +#[cfg(any(target_os = "linux", target_os = "android"))] +fn test_cp_debug_reflink_never_file_with_hole() { + let ts = TestScenario::new(util_name!()); + let at = &ts.fixtures; + at.touch("a"); + let f = std::fs::OpenOptions::new() + .write(true) + .open(at.plus("a")) + .unwrap(); + f.set_len(10000).unwrap(); + at.append_bytes("a", "hello".as_bytes()); + let result = ts + .ucmd() + .arg("--debug") + .arg("--reflink=never") + .arg("--sparse=never") + .arg("a") + .arg("b") + .succeeds(); + let stdout_str = result.stdout_str(); + if !stdout_str.contains("copy offload: avoided, reflink: no, sparse detection: SEEK_HOLE") { + panic!("Failure: stdout was \n{stdout_str}"); + } +} + +#[test] +#[cfg(any(target_os = "linux", target_os = "android"))] +fn test_cp_debug_sparse_never_less_than_512_bytes() { + let ts = TestScenario::new(util_name!()); + + let at = &ts.fixtures; + at.touch("a"); + at.append_bytes("a", "hello".as_bytes()); + let f = std::fs::OpenOptions::new() + .write(true) + .open(at.plus("a")) + .unwrap(); + f.set_len(400).unwrap(); + + let result = ts + .ucmd() + .arg("--debug") + .arg("--reflink=auto") + .arg("--sparse=never") + .arg("a") + .arg("b") + .succeeds(); + + let stdout_str = result.stdout_str(); + if !stdout_str.contains("copy offload: avoided, reflink: no, sparse detection: no") { + panic!("Failure: stdout was \n{stdout_str}"); + } +} + +#[test] +#[cfg(any(target_os = "linux", target_os = "android"))] +fn test_cp_debug_sparse_never_without_hole() { + let ts = TestScenario::new(util_name!()); + + let at = &ts.fixtures; + at.touch("a"); + at.append_bytes("a", "hello".as_bytes()); + + let filler_bytes = [0_u8; 10000]; + + at.append_bytes("a", &filler_bytes); + + let result = ts + .ucmd() + .arg("--reflink=auto") + .arg("--sparse=never") + .arg("--debug") + .arg("a") + .arg("b") + .succeeds(); + + let stdout_str = result.stdout_str(); + if !stdout_str.contains("copy offload: avoided, reflink: no, sparse detection: no") { + panic!("Failure: stdout was \n{stdout_str}"); + } +} +#[test] +#[cfg(any(target_os = "linux", target_os = "android"))] +fn test_cp_debug_sparse_never_empty_file_with_hole() { + let ts = TestScenario::new(util_name!()); + let at = &ts.fixtures; + at.touch("a"); + let f = std::fs::OpenOptions::new() + .write(true) + .open(at.plus("a")) + .unwrap(); + f.set_len(10000).unwrap(); + + let result = ts + .ucmd() + .arg("--debug") + .arg("--reflink=auto") + .arg("--sparse=never") + .arg("a") + .arg("b") + .succeeds(); + let stdout_str = result.stdout_str(); + if !stdout_str.contains("copy offload: unknown, reflink: no, sparse detection: SEEK_HOLE") { + panic!("Failure: stdout was \n{stdout_str}"); + } +} +#[test] +#[cfg(any(target_os = "linux", target_os = "android"))] +fn test_cp_debug_sparse_never_file_with_hole() { + let ts = TestScenario::new(util_name!()); + let at = &ts.fixtures; + at.touch("a"); + let f = std::fs::OpenOptions::new() + .write(true) + .open(at.plus("a")) + .unwrap(); + f.set_len(10000).unwrap(); + at.append_bytes("a", "hello".as_bytes()); + let result = ts + .ucmd() + .arg("--debug") + .arg("--reflink=auto") + .arg("--sparse=never") + .arg("a") + .arg("b") + .succeeds(); + let stdout_str = result.stdout_str(); + if !stdout_str.contains("copy offload: avoided, reflink: no, sparse detection: SEEK_HOLE") { + panic!("Failure: stdout was \n{stdout_str}"); + } +} + +#[test] +#[cfg(target_os = "linux")] +fn test_cp_debug_default_sparse_virtual_file() { + let ts = TestScenario::new(util_name!()); + let result = ts + .ucmd() + .arg("--debug") + .arg("/sys/kernel/address_bits") + .arg("b") + .succeeds(); + + let stdout_str = result.stdout_str(); + if !stdout_str + .contains("copy offload: unsupported, reflink: unsupported, sparse detection: SEEK_HOLE") + { + panic!("Failure: stdout was \n{stdout_str}"); + } +} + +#[test] +#[cfg(target_os = "linux")] +fn test_cp_debug_sparse_never_zero_sized_virtual_file() { + let ts = TestScenario::new(util_name!()); + let result = ts + .ucmd() + .arg("--debug") + .arg("--sparse=never") + .arg("/proc/version") + .arg("b") + .succeeds(); + + let stdout_str = result.stdout_str(); + if !stdout_str.contains("copy offload: avoided, reflink: no, sparse detection: no") { + panic!("Failure: stdout was \n{stdout_str}"); + } +} + +#[test] +#[cfg(target_os = "linux")] +fn test_cp_debug_default_zero_sized_virtual_file() { + let ts = TestScenario::new(util_name!()); + let result = ts + .ucmd() + .arg("--debug") + .arg("/proc/version") + .arg("b") + .succeeds(); + + let stdout_str = result.stdout_str(); + if !stdout_str.contains("copy offload: unsupported, reflink: unsupported, sparse detection: no") + { + panic!("Failure: stdout was \n{stdout_str}"); + } +} + +#[test] +#[cfg(any(target_os = "linux", target_os = "android"))] +fn test_cp_debug_reflink_never_without_hole() { + let ts = TestScenario::new(util_name!()); + let filler_bytes = [0_u8; 1000]; + let at = &ts.fixtures; + at.touch("a"); + at.write("a", "hello"); + at.append_bytes("a", &filler_bytes); + let result = ts + .ucmd() + .arg("--debug") + .arg("--reflink=never") + .arg("a") + .arg("b") + .succeeds(); + + let stdout_str = result.stdout_str(); + if !stdout_str.contains("copy offload: avoided, reflink: no, sparse detection: no") { + panic!("Failure: stdout was \n{stdout_str}"); + } +} #[test] fn test_cp_force_remove_destination_attributes_only_with_symlink() { From fabe9ac128d038c93e0b7ce1ec5d842d2ee95c87 Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Tue, 23 Apr 2024 15:28:22 +0200 Subject: [PATCH 133/144] cp: remove unnecessary calls of touch() in tests --- tests/by-util/test_cp.rs | 28 +++++----------------------- 1 file changed, 5 insertions(+), 23 deletions(-) diff --git a/tests/by-util/test_cp.rs b/tests/by-util/test_cp.rs index 8ab6ccc0ec1..7bed1072298 100644 --- a/tests/by-util/test_cp.rs +++ b/tests/by-util/test_cp.rs @@ -2606,10 +2606,8 @@ fn test_copy_through_just_created_symlink() { at.mkdir("b"); at.mkdir("c"); at.relative_symlink_file("../t", "a/1"); - at.touch("b/1"); at.write("b/1", "hello"); if create_t { - at.touch("t"); at.write("t", "world"); } ucmd.arg("--no-dereference") @@ -2839,7 +2837,6 @@ fn test_copy_no_dereference_1() { let (at, mut ucmd) = at_and_ucmd!(); at.mkdir("a"); at.mkdir("b"); - at.touch("a/foo"); at.write("a/foo", "bar"); at.relative_symlink_file("../a/foo", "b/foo"); ucmd.args(&["-P", "a/foo", "b"]).fails(); @@ -2852,10 +2849,8 @@ fn test_abuse_existing() { at.mkdir("b"); at.mkdir("c"); at.relative_symlink_file("../t", "a/1"); - at.touch("b/1"); at.write("b/1", "hello"); at.relative_symlink_file("../t", "c/1"); - at.touch("t"); at.write("t", "i"); ucmd.args(&["-dR", "a/1", "b/1", "c"]) .fails() @@ -2967,7 +2962,6 @@ fn test_cp_copy_symlink_contents_recursive() { let (at, mut ucmd) = at_and_ucmd!(); at.mkdir("src-dir"); at.mkdir("dest-dir"); - at.touch("f"); at.write("f", "f"); at.relative_symlink_file("f", "slink"); at.relative_symlink_file("no-file", &path_concat!("src-dir", "slink")); @@ -2985,7 +2979,6 @@ fn test_cp_copy_symlink_contents_recursive() { fn test_cp_mode_symlink() { for from in ["file", "slink", "slink2"] { let (at, mut ucmd) = at_and_ucmd!(); - at.touch("file"); at.write("file", "f"); at.relative_symlink_file("file", "slink"); at.relative_symlink_file("slink", "slink2"); @@ -3001,7 +2994,6 @@ fn test_cp_mode_symlink() { fn test_cp_mode_hardlink() { for from in ["file", "slink", "slink2"] { let (at, mut ucmd) = at_and_ucmd!(); - at.touch("file"); at.write("file", "f"); at.relative_symlink_file("file", "slink"); at.relative_symlink_file("slink", "slink2"); @@ -3019,7 +3011,6 @@ fn test_cp_mode_hardlink() { #[test] fn test_cp_mode_hardlink_no_dereference() { let (at, mut ucmd) = at_and_ucmd!(); - at.touch("file"); at.write("file", "f"); at.relative_symlink_file("file", "slink"); at.relative_symlink_file("slink", "slink2"); @@ -3836,7 +3827,6 @@ fn test_acl_preserve() { fn test_cp_debug_reflink_never_with_hole() { let ts = TestScenario::new(util_name!()); let at = &ts.fixtures; - at.touch("a"); at.write("a", "hello"); let f = std::fs::OpenOptions::new() .write(true) @@ -3926,8 +3916,7 @@ fn test_cp_debug_default_less_than_512_bytes() { let ts = TestScenario::new(util_name!()); let at = &ts.fixtures; - at.touch("a"); - at.append_bytes("a", "hello".as_bytes()); + at.write_bytes("a", "hello".as_bytes()); let f = std::fs::OpenOptions::new() .write(true) .open(at.plus("a")) @@ -3955,8 +3944,7 @@ fn test_cp_debug_default_without_hole() { let ts = TestScenario::new(util_name!()); let at = &ts.fixtures; - at.touch("a"); - at.append_bytes("a", "hello".as_bytes()); + at.write_bytes("a", "hello".as_bytes()); let filler_bytes = [0_u8; 10000]; @@ -4001,7 +3989,6 @@ fn test_cp_debug_default_empty_file_with_hole() { fn test_cp_debug_reflink_never_sparse_always_with_hole() { let ts = TestScenario::new(util_name!()); let at = &ts.fixtures; - at.touch("a"); at.write("a", "hello"); let f = std::fs::OpenOptions::new() .write(true) @@ -4036,7 +4023,6 @@ fn test_cp_debug_reflink_never_sparse_always_without_hole() { let ts = TestScenario::new(util_name!()); let empty_bytes = [0_u8; 10000]; let at = &ts.fixtures; - at.touch("a"); at.write("a", "hello"); at.append_bytes("a", &empty_bytes); @@ -4210,8 +4196,7 @@ fn test_cp_debug_reflink_never_less_than_512_bytes() { let ts = TestScenario::new(util_name!()); let at = &ts.fixtures; - at.touch("a"); - at.append_bytes("a", "hello".as_bytes()); + at.write_bytes("a", "hello".as_bytes()); let f = std::fs::OpenOptions::new() .write(true) .open(at.plus("a")) @@ -4289,8 +4274,7 @@ fn test_cp_debug_sparse_never_less_than_512_bytes() { let ts = TestScenario::new(util_name!()); let at = &ts.fixtures; - at.touch("a"); - at.append_bytes("a", "hello".as_bytes()); + at.write_bytes("a", "hello".as_bytes()); let f = std::fs::OpenOptions::new() .write(true) .open(at.plus("a")) @@ -4318,8 +4302,7 @@ fn test_cp_debug_sparse_never_without_hole() { let ts = TestScenario::new(util_name!()); let at = &ts.fixtures; - at.touch("a"); - at.append_bytes("a", "hello".as_bytes()); + at.write_bytes("a", "hello".as_bytes()); let filler_bytes = [0_u8; 10000]; @@ -4451,7 +4434,6 @@ fn test_cp_debug_reflink_never_without_hole() { let ts = TestScenario::new(util_name!()); let filler_bytes = [0_u8; 1000]; let at = &ts.fixtures; - at.touch("a"); at.write("a", "hello"); at.append_bytes("a", &filler_bytes); let result = ts From 423494421b28f79f10306fffe1dea7287d4ed5be Mon Sep 17 00:00:00 2001 From: Anton Patrushev Date: Tue, 23 Apr 2024 19:48:21 +0500 Subject: [PATCH 134/144] more: use dev tty instead of mio to avoid panics (#6262) * more: use dev tty instead of mio to avoid panics * move more specific dependecy to more package --- Cargo.lock | 12 ++++++++++++ src/uu/more/Cargo.toml | 3 +++ 2 files changed, 15 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index ac299596a23..26606d4800c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -664,6 +664,7 @@ checksum = "f476fe445d41c9e991fd07515a6f463074b782242ccf4a5b7b1d1012e70824df" dependencies = [ "bitflags 2.4.2", "crossterm_winapi", + "filedescriptor", "libc", "mio", "parking_lot", @@ -843,6 +844,17 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "31a7a908b8f32538a2143e59a6e4e2508988832d5d4d6f7c156b3cbc762643a5" +[[package]] +name = "filedescriptor" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7199d965852c3bac31f779ef99cbb4537f80e952e2d6aa0ffeb30cce00f4f46e" +dependencies = [ + "libc", + "thiserror", + "winapi", +] + [[package]] name = "filetime" version = "0.2.23" diff --git a/src/uu/more/Cargo.toml b/src/uu/more/Cargo.toml index 7d8ec31dd70..11d498a76f7 100644 --- a/src/uu/more/Cargo.toml +++ b/src/uu/more/Cargo.toml @@ -24,6 +24,9 @@ unicode-segmentation = { workspace = true } [target.'cfg(all(unix, not(target_os = "fuchsia")))'.dependencies] nix = { workspace = true } +[target.'cfg(target_os = "macos")'.dependencies] +crossterm = { workspace = true, features = ["use-dev-tty"] } + [[bin]] name = "more" path = "src/main.rs" From b42f7e0940351ea461d5c4059d3b440c1604326b Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 23 Apr 2024 14:49:52 +0000 Subject: [PATCH 135/144] chore(deps): update rust crate winapi-util to 0.1.7 --- Cargo.lock | 6 +++--- Cargo.toml | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 26606d4800c..705593af7e6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3485,11 +3485,11 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" -version = "0.1.6" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596" +checksum = "134306a13c5647ad6453e8deaec55d3a44d6021970129e6188735e74bf546697" dependencies = [ - "winapi", + "windows-sys 0.52.0", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 8160e09e755..faa5c885c70 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -330,7 +330,7 @@ unicode-segmentation = "1.11.0" unicode-width = "0.1.11" utf-8 = "0.7.6" walkdir = "2.5" -winapi-util = "0.1.6" +winapi-util = "0.1.7" windows-sys = { version = "0.48.0", default-features = false } xattr = "1.3.1" zip = { version = "1.1.1", default-features = false, features = ["deflate"] } From d07fb736307003799999fd14e2563edbf92cff88 Mon Sep 17 00:00:00 2001 From: Michael Vogt Date: Mon, 1 Apr 2024 20:57:22 +0200 Subject: [PATCH 136/144] ls,uucore: extract display `human_readable()` helper from ls This commit extract the `display_size()` helper from `ls` into `uucore` as `human_readable` to be similar to the gnulib helper so that the human readable display of sizes can be shared between ls, du, df. --- Cargo.lock | 1 + src/uu/ls/src/ls.rs | 41 +------------- src/uucore/Cargo.toml | 1 + src/uucore/src/lib/features/format/human.rs | 63 +++++++++++++++++++++ src/uucore/src/lib/features/format/mod.rs | 1 + 5 files changed, 68 insertions(+), 39 deletions(-) create mode 100644 src/uucore/src/lib/features/format/human.rs diff --git a/Cargo.lock b/Cargo.lock index 705593af7e6..e5e479b4867 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3325,6 +3325,7 @@ dependencies = [ "md-5", "memchr", "nix", + "number_prefix", "once_cell", "os_display", "sha1", diff --git a/src/uu/ls/src/ls.rs b/src/uu/ls/src/ls.rs index 0221ae09681..829ddb45638 100644 --- a/src/uu/ls/src/ls.rs +++ b/src/uu/ls/src/ls.rs @@ -12,7 +12,6 @@ use clap::{ use glob::{MatchOptions, Pattern}; use lscolors::{LsColors, Style}; -use number_prefix::NumberPrefix; use std::{cell::OnceCell, num::IntErrorKind}; use std::{collections::HashSet, io::IsTerminal}; @@ -37,6 +36,7 @@ use std::{ use term_grid::{Cell, Direction, Filling, Grid, GridOptions}; use unicode_width::UnicodeWidthStr; use uucore::error::USimpleError; +use uucore::format::human::{human_readable, SizeFormat}; #[cfg(all(unix, not(any(target_os = "android", target_os = "macos"))))] use uucore::fsxattr::has_acl; #[cfg(any( @@ -313,13 +313,6 @@ enum Sort { Width, } -#[derive(PartialEq)] -enum SizeFormat { - Bytes, - Binary, // Powers of 1024, --human-readable, -h - Decimal, // Powers of 1000, --si -} - #[derive(PartialEq, Eq)] enum Files { All, @@ -3038,30 +3031,6 @@ fn display_date(metadata: &Metadata, config: &Config) -> String { } } -// There are a few peculiarities to how GNU formats the sizes: -// 1. One decimal place is given if and only if the size is smaller than 10 -// 2. It rounds sizes up. -// 3. The human-readable format uses powers for 1024, but does not display the "i" -// that is commonly used to denote Kibi, Mebi, etc. -// 4. Kibi and Kilo are denoted differently ("k" and "K", respectively) -fn format_prefixed(prefixed: &NumberPrefix) -> String { - match prefixed { - NumberPrefix::Standalone(bytes) => bytes.to_string(), - NumberPrefix::Prefixed(prefix, bytes) => { - // Remove the "i" from "Ki", "Mi", etc. if present - let prefix_str = prefix.symbol().trim_end_matches('i'); - - // Check whether we get more than 10 if we round up to the first decimal - // because we want do display 9.81 as "9.9", not as "10". - if (10.0 * bytes).ceil() >= 100.0 { - format!("{:.0}{}", bytes.ceil(), prefix_str) - } else { - format!("{:.1}{}", (10.0 * bytes).ceil() / 10.0, prefix_str) - } - } - } -} - #[allow(dead_code)] enum SizeOrDeviceId { Size(String), @@ -3104,13 +3073,7 @@ fn display_len_or_rdev(metadata: &Metadata, config: &Config) -> SizeOrDeviceId { } fn display_size(size: u64, config: &Config) -> String { - // NOTE: The human-readable behavior deviates from the GNU ls. - // The GNU ls uses binary prefixes by default. - match config.size_format { - SizeFormat::Binary => format_prefixed(&NumberPrefix::binary(size as f64)), - SizeFormat::Decimal => format_prefixed(&NumberPrefix::decimal(size as f64)), - SizeFormat::Bytes => size.to_string(), - } + human_readable(size, config.size_format) } #[cfg(unix)] diff --git a/src/uucore/Cargo.toml b/src/uucore/Cargo.toml index 635ee140397..b13903bc6af 100644 --- a/src/uucore/Cargo.toml +++ b/src/uucore/Cargo.toml @@ -20,6 +20,7 @@ path = "src/lib/lib.rs" [dependencies] clap = { workspace = true } uucore_procs = { workspace = true } +number_prefix = { workspace = true } dns-lookup = { version = "2.0.4", optional = true } dunce = { version = "1.0.4", optional = true } wild = "2.2" diff --git a/src/uucore/src/lib/features/format/human.rs b/src/uucore/src/lib/features/format/human.rs new file mode 100644 index 00000000000..8767af90e1d --- /dev/null +++ b/src/uucore/src/lib/features/format/human.rs @@ -0,0 +1,63 @@ +// 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. + +//! `human`-size formating +//! +//! Format sizes like gnulibs human_readable() would + +use number_prefix::NumberPrefix; + +#[derive(Copy, Clone, PartialEq)] +pub enum SizeFormat { + Bytes, + Binary, // Powers of 1024, --human-readable, -h + Decimal, // Powers of 1000, --si +} + +// There are a few peculiarities to how GNU formats the sizes: +// 1. One decimal place is given if and only if the size is smaller than 10 +// 2. It rounds sizes up. +// 3. The human-readable format uses powers for 1024, but does not display the "i" +// that is commonly used to denote Kibi, Mebi, etc. +// 4. Kibi and Kilo are denoted differently ("k" and "K", respectively) +fn format_prefixed(prefixed: &NumberPrefix) -> String { + match prefixed { + NumberPrefix::Standalone(bytes) => bytes.to_string(), + NumberPrefix::Prefixed(prefix, bytes) => { + // Remove the "i" from "Ki", "Mi", etc. if present + let prefix_str = prefix.symbol().trim_end_matches('i'); + + // Check whether we get more than 10 if we round up to the first decimal + // because we want do display 9.81 as "9.9", not as "10". + if (10.0 * bytes).ceil() >= 100.0 { + format!("{:.0}{}", bytes.ceil(), prefix_str) + } else { + format!("{:.1}{}", (10.0 * bytes).ceil() / 10.0, prefix_str) + } + } + } +} + +pub fn human_readable(size: u64, sfmt: SizeFormat) -> String { + match sfmt { + SizeFormat::Binary => format_prefixed(&NumberPrefix::binary(size as f64)), + SizeFormat::Decimal => format_prefixed(&NumberPrefix::decimal(size as f64)), + SizeFormat::Bytes => size.to_string(), + } +} + +#[cfg(test)] +#[test] +fn test_human_readable() { + let test_cases = [ + (133456345, SizeFormat::Binary, "128M"), + (12 * 1024 * 1024, SizeFormat::Binary, "12M"), + (8500, SizeFormat::Binary, "8.4K"), + ]; + + for &(size, sfmt, expected_str) in &test_cases { + assert_eq!(human_readable(size, sfmt), expected_str); + } +} diff --git a/src/uucore/src/lib/features/format/mod.rs b/src/uucore/src/lib/features/format/mod.rs index 8f662080dcb..b82b5f62acf 100644 --- a/src/uucore/src/lib/features/format/mod.rs +++ b/src/uucore/src/lib/features/format/mod.rs @@ -32,6 +32,7 @@ mod argument; mod escape; +pub mod human; pub mod num_format; pub mod num_parser; mod spec; From 61e0450c66368c1e61c68d1d7f643a20ac06b052 Mon Sep 17 00:00:00 2001 From: Michael Vogt Date: Mon, 1 Apr 2024 09:59:21 +0200 Subject: [PATCH 137/144] du: give `-h` output the same precision as GNU coreutils When printing the `du -h` output GNU coreutils does autoscale the size, e.g. ``` $ truncate -s12M a $ truncate -s8500 b $ truncate -s133456345 c $ truncate -s56990456345 d $ du -h --apparent-size a b c d 12M a 8,4K b 128M c 54G d ``` Align our version to do the same by sharing the code with `ls`. Closes: #6159 --- src/uu/du/Cargo.toml | 2 +- src/uu/du/src/du.rs | 30 ++++++++++++------------------ tests/by-util/test_du.rs | 28 ++++++++++++++++++++++++++++ 3 files changed, 41 insertions(+), 19 deletions(-) diff --git a/src/uu/du/Cargo.toml b/src/uu/du/Cargo.toml index bb9a2a97b02..bc53986b3c5 100644 --- a/src/uu/du/Cargo.toml +++ b/src/uu/du/Cargo.toml @@ -19,7 +19,7 @@ chrono = { workspace = true } # For the --exclude & --exclude-from options glob = { workspace = true } clap = { workspace = true } -uucore = { workspace = true } +uucore = { workspace = true, features = ["format"] } [target.'cfg(target_os = "windows")'.dependencies] windows-sys = { workspace = true, features = [ diff --git a/src/uu/du/src/du.rs b/src/uu/du/src/du.rs index 1935248dafb..74fa4154db5 100644 --- a/src/uu/du/src/du.rs +++ b/src/uu/du/src/du.rs @@ -75,9 +75,6 @@ const ABOUT: &str = help_about!("du.md"); const AFTER_HELP: &str = help_section!("after help", "du.md"); const USAGE: &str = help_usage!("du.md"); -// TODO: Support Z & Y (currently limited by size of u64) -const UNITS: [(char, u32); 6] = [('E', 6), ('P', 5), ('T', 4), ('G', 3), ('M', 2), ('K', 1)]; - struct TraversalOptions { all: bool, separate_dirs: bool, @@ -117,7 +114,8 @@ enum Time { #[derive(Clone)] enum SizeFormat { - Human(u64), + HumanDecimal, + HumanBinary, BlockSize(u64), } @@ -549,18 +547,14 @@ impl StatPrinter { return size.to_string(); } match self.size_format { - SizeFormat::Human(multiplier) => { - if size == 0 { - return "0".to_string(); - } - for &(unit, power) in &UNITS { - let limit = multiplier.pow(power); - if size >= limit { - return format!("{:.1}{}", (size as f64) / (limit as f64), unit); - } - } - format!("{size}B") - } + SizeFormat::HumanDecimal => uucore::format::human::human_readable( + size, + uucore::format::human::SizeFormat::Decimal, + ), + SizeFormat::HumanBinary => uucore::format::human::human_readable( + size, + uucore::format::human::SizeFormat::Binary, + ), SizeFormat::BlockSize(block_size) => div_ceil(size, block_size).to_string(), } } @@ -688,9 +682,9 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { }); let size_format = if matches.get_flag(options::HUMAN_READABLE) { - SizeFormat::Human(1024) + SizeFormat::HumanBinary } else if matches.get_flag(options::SI) { - SizeFormat::Human(1000) + SizeFormat::HumanDecimal } else if matches.get_flag(options::BYTES) { SizeFormat::BlockSize(1) } else if matches.get_flag(options::BLOCK_SIZE_1K) { diff --git a/tests/by-util/test_du.rs b/tests/by-util/test_du.rs index 2bc694acbc0..a71cd462397 100644 --- a/tests/by-util/test_du.rs +++ b/tests/by-util/test_du.rs @@ -543,6 +543,34 @@ fn test_du_h_flag_empty_file() { .stdout_only("0\tempty.txt\n"); } +#[test] +fn test_du_h_precision() { + let test_cases = [ + (133456345, "128M"), + (12 * 1024 * 1024, "12M"), + (8500, "8.4K"), + ]; + + for &(test_len, expected_output) in &test_cases { + let (at, mut ucmd) = at_and_ucmd!(); + + let fpath = at.plus("test.txt"); + std::fs::File::create(&fpath) + .expect("cannot create test file") + .set_len(test_len) + .expect("cannot truncate test len to size"); + ucmd.arg("-h") + .arg("--apparent-size") + .arg(&fpath) + .succeeds() + .stdout_only(format!( + "{}\t{}\n", + expected_output, + &fpath.to_string_lossy() + )); + } +} + #[cfg(feature = "touch")] #[test] fn test_du_time() { From 89af8b976978c33edf8a297cd362fe76e6e7c2a3 Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Wed, 24 Apr 2024 16:45:16 +0200 Subject: [PATCH 138/144] du,uucore: add words to spell-checker:ignore --- src/uucore/src/lib/features/format/human.rs | 4 +++- tests/by-util/test_du.rs | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/uucore/src/lib/features/format/human.rs b/src/uucore/src/lib/features/format/human.rs index 8767af90e1d..28d143a42e3 100644 --- a/src/uucore/src/lib/features/format/human.rs +++ b/src/uucore/src/lib/features/format/human.rs @@ -3,7 +3,9 @@ // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. -//! `human`-size formating +// spell-checker:ignore gnulibs sfmt + +//! `human`-size formatting //! //! Format sizes like gnulibs human_readable() would diff --git a/tests/by-util/test_du.rs b/tests/by-util/test_du.rs index a71cd462397..a1c7bfb9374 100644 --- a/tests/by-util/test_du.rs +++ b/tests/by-util/test_du.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 (paths) atim sublink subwords azerty azeaze xcwww azeaz amaz azea qzerty tazerty tsublink testfile1 testfile2 filelist testdir testfile +// spell-checker:ignore (paths) atim sublink subwords azerty azeaze xcwww azeaz amaz azea qzerty tazerty tsublink testfile1 testfile2 filelist fpath testdir testfile #[cfg(not(windows))] use regex::Regex; From 91b4ce6426efbc06ebbad8e2960a7f27870cf39c Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Wed, 24 Apr 2024 17:06:13 +0200 Subject: [PATCH 139/144] ls: add uucore/format feature --- src/uu/ls/Cargo.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/src/uu/ls/Cargo.toml b/src/uu/ls/Cargo.toml index 1dae3f033e9..cfd85cf9d72 100644 --- a/src/uu/ls/Cargo.toml +++ b/src/uu/ls/Cargo.toml @@ -26,6 +26,7 @@ lscolors = { workspace = true } uucore = { workspace = true, features = [ "colors", "entries", + "format", "fs", "fsxattr", "quoting-style", From ac87b73244c24087531b2cf72a915e8f60e486fb Mon Sep 17 00:00:00 2001 From: Jadi Date: Wed, 24 Apr 2024 15:24:43 +0000 Subject: [PATCH 140/144] Fixing the build issue on NetBSD. The NetBSD was missing for target-os checks on fsext.rs Fixed #6261 --- src/uucore/src/lib/features/fsext.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/uucore/src/lib/features/fsext.rs b/src/uucore/src/lib/features/fsext.rs index 7f29a2dc626..99db194f8de 100644 --- a/src/uucore/src/lib/features/fsext.rs +++ b/src/uucore/src/lib/features/fsext.rs @@ -643,6 +643,7 @@ impl FsMeta for StatFs { not(target_os = "aix"), not(target_os = "android"), not(target_os = "freebsd"), + not(target_os = "netbsd"), not(target_os = "openbsd"), not(target_os = "illumos"), not(target_os = "solaris"), @@ -654,6 +655,7 @@ impl FsMeta for StatFs { #[cfg(all( not(target_env = "musl"), not(target_os = "freebsd"), + not(target_os = "netbsd"), not(target_os = "redox"), any( target_arch = "s390x", @@ -668,6 +670,7 @@ impl FsMeta for StatFs { target_env = "musl", target_os = "aix", target_os = "freebsd", + target_os = "netbsd", target_os = "illumos", target_os = "solaris", target_os = "redox", From 8db76c900c5be99213e4ffc32cb1e62a76104239 Mon Sep 17 00:00:00 2001 From: Roy Ivy III Date: Wed, 24 Apr 2024 12:38:28 -0500 Subject: [PATCH 141/144] Revert "CI: change publish step condition" This reverts commit da0b580504f320b11f9701253825d64a9996292e. --- .github/workflows/CICD.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/CICD.yml b/.github/workflows/CICD.yml index c5aa7ee805f..fbc4bccac05 100644 --- a/.github/workflows/CICD.yml +++ b/.github/workflows/CICD.yml @@ -541,6 +541,9 @@ jobs: PKG_BASENAME=${PROJECT_NAME}-${REF_TAG:-$REF_SHAS}-${{ matrix.job.target }} PKG_NAME=${PKG_BASENAME}${PKG_suffix} outputs PKG_suffix PKG_BASENAME PKG_NAME + # deployable tag? (ie, leading "vM" or "M"; M == version number) + unset DEPLOY ; if [[ $REF_TAG =~ ^[vV]?[0-9].* ]]; then DEPLOY='true' ; fi + outputs DEPLOY # DPKG architecture? unset DPKG_ARCH case ${{ matrix.job.target }} in @@ -746,7 +749,7 @@ jobs: fi - name: Publish uses: softprops/action-gh-release@v2 - if: startsWith(github.ref, 'refs/tags/') + if: steps.vars.outputs.DEPLOY with: files: | ${{ steps.vars.outputs.STAGING }}/${{ steps.vars.outputs.PKG_NAME }} From df3c3f31e198467e2baeee111b6310628a73774c Mon Sep 17 00:00:00 2001 From: Roy Ivy III Date: Wed, 24 Apr 2024 13:18:00 -0500 Subject: [PATCH 142/144] fix/CI ~ re-enable artifact deployment for version tagged commits --- .github/workflows/CICD.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/CICD.yml b/.github/workflows/CICD.yml index fbc4bccac05..301946816ff 100644 --- a/.github/workflows/CICD.yml +++ b/.github/workflows/CICD.yml @@ -19,6 +19,7 @@ on: pull_request: push: tags: + - '*' branches: - main From 798435c825e19f81dbcacfbca22a36cca1713c98 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 25 Apr 2024 03:25:44 +0000 Subject: [PATCH 143/144] chore(deps): update rust crate winapi-util to 0.1.8 --- Cargo.lock | 4 ++-- Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e5e479b4867..e77169a2327 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3486,9 +3486,9 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" -version = "0.1.7" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "134306a13c5647ad6453e8deaec55d3a44d6021970129e6188735e74bf546697" +checksum = "4d4cc384e1e73b93bafa6fb4f1df8c41695c8a91cf9c4c64358067d15a7b6c6b" dependencies = [ "windows-sys 0.52.0", ] diff --git a/Cargo.toml b/Cargo.toml index faa5c885c70..a36581c449b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -330,7 +330,7 @@ unicode-segmentation = "1.11.0" unicode-width = "0.1.11" utf-8 = "0.7.6" walkdir = "2.5" -winapi-util = "0.1.7" +winapi-util = "0.1.8" windows-sys = { version = "0.48.0", default-features = false } xattr = "1.3.1" zip = { version = "1.1.1", default-features = false, features = ["deflate"] } From 5d763358fa202a958d39ff6be8c399ed640e5b1e Mon Sep 17 00:00:00 2001 From: Ulrich Hornung Date: Fri, 26 Apr 2024 12:07:20 +0200 Subject: [PATCH 144/144] android CI: incremental install when retry --- util/android-commands.sh | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/util/android-commands.sh b/util/android-commands.sh index 48fdd86d844..ac84917444b 100755 --- a/util/android-commands.sh +++ b/util/android-commands.sh @@ -515,7 +515,10 @@ snapshot() { echo "Installing cargo-nextest" # We need to install nextest via cargo currently, since there is no pre-built binary for android x86 - command="export CARGO_TERM_COLOR=always && cargo install cargo-nextest" + # explicitly set CARGO_TARGET_DIR as otherwise a random generated tmp directory is used, + # which prevents incremental build for the retries. + command="export CARGO_TERM_COLOR=always && export CARGO_TARGET_DIR=\"cargo_install_target_dir\" && cargo install cargo-nextest" + run_with_retry 3 run_command_via_ssh "$command" return_code=$?