From d345a280bf588b325babb29c60dc7d84d2c22832 Mon Sep 17 00:00:00 2001 From: John Shin Date: Sun, 30 Jul 2023 14:37:17 -0700 Subject: [PATCH 01/27] seq: update inf and nan parsing --- src/uu/seq/src/numberparse.rs | 40 +++++++++++++++++------------------ 1 file changed, 19 insertions(+), 21 deletions(-) diff --git a/src/uu/seq/src/numberparse.rs b/src/uu/seq/src/numberparse.rs index 23d94ea2b..1921e2e4c 100644 --- a/src/uu/seq/src/numberparse.rs +++ b/src/uu/seq/src/numberparse.rs @@ -69,27 +69,25 @@ fn parse_no_decimal_no_exponent(s: &str) -> Result { // Possibly "NaN" or "inf". - // - // TODO In Rust v1.53.0, this change - // https://github.com/rust-lang/rust/pull/78618 improves the - // parsing of floats to include being able to parse "NaN" - // and "inf". So when the minimum version of this crate is - // increased to 1.53.0, we should just use the built-in - // `f32` parsing instead. - if s.eq_ignore_ascii_case("inf") { - Ok(PreciseNumber::new( - Number::Float(ExtendedBigDecimal::Infinity), - 0, - 0, - )) - } else if s.eq_ignore_ascii_case("-inf") { - Ok(PreciseNumber::new( - Number::Float(ExtendedBigDecimal::MinusInfinity), - 0, - 0, - )) - } else if s.eq_ignore_ascii_case("nan") || s.eq_ignore_ascii_case("-nan") { - Err(ParseNumberError::Nan) + if let Ok(num) = f32::from_str(s) { + // pattern matching on floating point literal is not encouraged 'https://github.com/rust-lang/rust/issues/41620' + if num == f32::INFINITY { + Ok(PreciseNumber::new( + Number::Float(ExtendedBigDecimal::Infinity), + 0, + 0, + )) + } else if num == f32::NEG_INFINITY { + Ok(PreciseNumber::new( + Number::Float(ExtendedBigDecimal::MinusInfinity), + 0, + 0, + )) + } else if num.is_nan() { + Err(ParseNumberError::Nan) + } else { + Err(ParseNumberError::Float) + } } else { Err(ParseNumberError::Float) } From 2bb56d44a4161b1b40225f27f8e30090910aae38 Mon Sep 17 00:00:00 2001 From: John Shin Date: Sat, 5 Aug 2023 18:23:29 -0700 Subject: [PATCH 02/27] seq: add tests for infinity and -infinity args --- src/uu/seq/src/numberparse.rs | 12 ++++++++++++ tests/by-util/test_seq.rs | 10 ++++++++++ 2 files changed, 22 insertions(+) diff --git a/src/uu/seq/src/numberparse.rs b/src/uu/seq/src/numberparse.rs index 1921e2e4c..156f80fb9 100644 --- a/src/uu/seq/src/numberparse.rs +++ b/src/uu/seq/src/numberparse.rs @@ -477,11 +477,23 @@ mod tests { #[test] fn test_parse_inf() { assert_eq!(parse("inf"), Number::Float(ExtendedBigDecimal::Infinity)); + assert_eq!( + parse("infinity"), + Number::Float(ExtendedBigDecimal::Infinity) + ); assert_eq!(parse("+inf"), Number::Float(ExtendedBigDecimal::Infinity)); + assert_eq!( + parse("+infinity"), + Number::Float(ExtendedBigDecimal::Infinity) + ); assert_eq!( parse("-inf"), Number::Float(ExtendedBigDecimal::MinusInfinity) ); + assert_eq!( + parse("-infinity"), + Number::Float(ExtendedBigDecimal::MinusInfinity) + ); } #[test] diff --git a/tests/by-util/test_seq.rs b/tests/by-util/test_seq.rs index de0781912..95de2f3cd 100644 --- a/tests/by-util/test_seq.rs +++ b/tests/by-util/test_seq.rs @@ -619,11 +619,21 @@ fn test_neg_inf() { run(&["--", "-inf", "0"], b"-inf\n-inf\n-inf\n"); } +#[test] +fn test_neg_infinity() { + run(&["--", "-infinity", "0"], b"-inf\n-inf\n-inf\n"); +} + #[test] fn test_inf() { run(&["inf"], b"1\n2\n3\n"); } +#[test] +fn test_infinity() { + run(&["infinity"], b"1\n2\n3\n"); +} + #[test] fn test_inf_width() { run( From db342556c2ce93be472abccd6594b27e932aa7f6 Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Mon, 14 Aug 2023 13:40:05 +0200 Subject: [PATCH 03/27] nl: implement -d/--section-delimiter --- src/uu/nl/src/helper.rs | 9 +++++ src/uu/nl/src/nl.rs | 44 ++++++++++++++++----- tests/by-util/test_nl.rs | 83 +++++++++++++++++++++++++++++++++++++++- 3 files changed, 125 insertions(+), 11 deletions(-) diff --git a/src/uu/nl/src/helper.rs b/src/uu/nl/src/helper.rs index fe550e6a0..ae14a6d59 100644 --- a/src/uu/nl/src/helper.rs +++ b/src/uu/nl/src/helper.rs @@ -13,6 +13,15 @@ pub fn parse_options(settings: &mut crate::Settings, opts: &clap::ArgMatches) -> // This vector holds error messages encountered. let mut errs: Vec = vec![]; settings.renumber = opts.get_flag(options::NO_RENUMBER); + if let Some(delimiter) = opts.get_one::(options::SECTION_DELIMITER) { + // check whether the delimiter is a single ASCII char (1 byte) + // because GNU nl doesn't add a ':' to single non-ASCII chars + settings.section_delimiter = if delimiter.len() == 1 { + format!("{delimiter}:") + } else { + delimiter.to_owned() + }; + } if let Some(val) = opts.get_one::(options::NUMBER_SEPARATOR) { settings.number_separator = val.to_owned(); } diff --git a/src/uu/nl/src/nl.rs b/src/uu/nl/src/nl.rs index 61a0a9f35..839ec35db 100644 --- a/src/uu/nl/src/nl.rs +++ b/src/uu/nl/src/nl.rs @@ -23,7 +23,7 @@ pub struct Settings { body_numbering: NumberingStyle, footer_numbering: NumberingStyle, // The variable corresponding to -d - section_delimiter: [char; 2], + section_delimiter: String, // The variables corresponding to the options -v, -i, -l, -w. starting_line_number: i64, line_increment: i64, @@ -43,7 +43,7 @@ impl Default for Settings { header_numbering: NumberingStyle::None, body_numbering: NumberingStyle::NonEmpty, footer_numbering: NumberingStyle::None, - section_delimiter: ['\\', ':'], + section_delimiter: String::from("\\:"), starting_line_number: 1, line_increment: 1, join_blank_lines: 1, @@ -120,6 +120,32 @@ impl NumberFormat { } } +enum SectionDelimiter { + Header, + Body, + Footer, +} + +impl SectionDelimiter { + // A valid section delimiter contains the pattern one to three times, + // and nothing else. + fn parse(s: &str, pattern: &str) -> Option { + if s.is_empty() || pattern.is_empty() { + return None; + } + + let pattern_count = s.matches(pattern).count(); + let is_length_ok = pattern_count * pattern.len() == s.len(); + + match (pattern_count, is_length_ok) { + (3, true) => Some(Self::Header), + (2, true) => Some(Self::Body), + (1, true) => Some(Self::Footer), + _ => None, + } + } +} + pub mod options { pub const HELP: &str = "help"; pub const FILE: &str = "file"; @@ -299,14 +325,12 @@ fn nl(reader: &mut BufReader, settings: &Settings) -> UResult<()> { consecutive_empty_lines = 0; }; - // FIXME section delimiters are hardcoded and settings.section_delimiter is ignored - // because --section-delimiter is not correctly implemented yet - let _ = settings.section_delimiter; // XXX suppress "field never read" warning - let new_numbering_style = match line.as_str() { - "\\:\\:\\:" => Some(&settings.header_numbering), - "\\:\\:" => Some(&settings.body_numbering), - "\\:" => Some(&settings.footer_numbering), - _ => None, + let new_numbering_style = match SectionDelimiter::parse(&line, &settings.section_delimiter) + { + Some(SectionDelimiter::Header) => Some(&settings.header_numbering), + Some(SectionDelimiter::Body) => Some(&settings.body_numbering), + Some(SectionDelimiter::Footer) => Some(&settings.footer_numbering), + None => None, }; if let Some(new_style) = new_numbering_style { diff --git a/tests/by-util/test_nl.rs b/tests/by-util/test_nl.rs index b58c0c206..e21bb5037 100644 --- a/tests/by-util/test_nl.rs +++ b/tests/by-util/test_nl.rs @@ -2,7 +2,8 @@ // // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. -// spell-checker:ignore binvalid finvalid hinvalid iinvalid linvalid ninvalid vinvalid winvalid +// +// spell-checker:ignore binvalid finvalid hinvalid iinvalid linvalid nabcabc nabcabcabc ninvalid vinvalid winvalid use crate::common::util::TestScenario; #[test] @@ -486,3 +487,83 @@ fn test_line_number_overflow() { .stdout_is(format!("{}\ta\n", i64::MIN)) .stderr_is("nl: line number overflow\n"); } + +#[test] +fn test_section_delimiter() { + for arg in ["-dabc", "--section-delimiter=abc"] { + new_ucmd!() + .arg(arg) + .pipe_in("a\nabcabcabc\nb") // header section + .succeeds() + .stdout_is(" 1\ta\n\n b\n"); + + new_ucmd!() + .arg(arg) + .pipe_in("a\nabcabc\nb") // body section + .succeeds() + .stdout_is(" 1\ta\n\n 1\tb\n"); + + new_ucmd!() + .arg(arg) + .pipe_in("a\nabc\nb") // footer section + .succeeds() + .stdout_is(" 1\ta\n\n b\n"); + } +} + +#[test] +fn test_one_char_section_delimiter_expansion() { + for arg in ["-da", "--section-delimiter=a"] { + new_ucmd!() + .arg(arg) + .pipe_in("a\na:a:a:\nb") // header section + .succeeds() + .stdout_is(" 1\ta\n\n b\n"); + + new_ucmd!() + .arg(arg) + .pipe_in("a\na:a:\nb") // body section + .succeeds() + .stdout_is(" 1\ta\n\n 1\tb\n"); + + new_ucmd!() + .arg(arg) + .pipe_in("a\na:\nb") // footer section + .succeeds() + .stdout_is(" 1\ta\n\n b\n"); + } +} + +#[test] +fn test_non_ascii_one_char_section_delimiter() { + for arg in ["-dä", "--section-delimiter=ä"] { + new_ucmd!() + .arg(arg) + .pipe_in("a\näää\nb") // header section + .succeeds() + .stdout_is(" 1\ta\n\n b\n"); + + new_ucmd!() + .arg(arg) + .pipe_in("a\nää\nb") // body section + .succeeds() + .stdout_is(" 1\ta\n\n 1\tb\n"); + + new_ucmd!() + .arg(arg) + .pipe_in("a\nä\nb") // footer section + .succeeds() + .stdout_is(" 1\ta\n\n b\n"); + } +} + +#[test] +fn test_empty_section_delimiter() { + for arg in ["-d ''", "--section-delimiter=''"] { + new_ucmd!() + .arg(arg) + .pipe_in("a\n\nb") + .succeeds() + .stdout_is(" 1\ta\n \n 2\tb\n"); + } +} From 4623575a6641bba9c4b45303d28386f7e92cc2b1 Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Mon, 28 Aug 2023 22:51:45 +0200 Subject: [PATCH 04/27] echo: fix wrapping behavior of octal sequences --- src/uu/echo/src/echo.rs | 109 ++++++++++++++++++------------------- tests/by-util/test_echo.rs | 17 ++++++ 2 files changed, 71 insertions(+), 55 deletions(-) diff --git a/src/uu/echo/src/echo.rs b/src/uu/echo/src/echo.rs index cd9467714..4aba703a1 100644 --- a/src/uu/echo/src/echo.rs +++ b/src/uu/echo/src/echo.rs @@ -21,73 +21,72 @@ mod options { pub const DISABLE_BACKSLASH_ESCAPE: &str = "disable_backslash_escape"; } -fn parse_code( - input: &mut Peekable, - base: u32, - max_digits: u32, - bits_per_digit: u32, -) -> Option { - let mut ret = 0x8000_0000; - for _ in 0..max_digits { - match input.peek().and_then(|c| c.to_digit(base)) { - Some(n) => ret = (ret << bits_per_digit) | n, +/// Parse the numeric part of the `\xHHH` and `\0NNN` escape sequences +fn parse_code(input: &mut Peekable, base: u8, max_digits: u32) -> Option { + // All arithmetic on `ret` needs to be wrapping, because octal input can + // take 3 digits, which is 9 bits, and therefore more than what fits in a + // `u8`. GNU just seems to wrap these values. + // Note that if we instead make `ret` a `u32` and use `char::from_u32` will + // yield incorrect results because it will interpret values larger than + // `u8::MAX` as unicode. + let mut ret = input.peek().and_then(|c| c.to_digit(base as u32))? as u8; + + // We can safely ifgnore the None case because we just peeked it. + let _ = input.next(); + + for _ in 1..max_digits { + match input.peek().and_then(|c| c.to_digit(base as u32)) { + Some(n) => ret = ret.wrapping_mul(base).wrapping_add(n as u8), None => break, } - input.next(); + // We can safely ifgnore the None case because we just peeked it. + let _ = input.next(); } - std::char::from_u32(ret) + + Some(ret.into()) } fn print_escaped(input: &str, mut output: impl Write) -> io::Result { - let mut should_stop = false; - - let mut buffer = ['\\'; 2]; - - // TODO `cargo +nightly clippy` complains that `.peek()` is never - // called on `iter`. However, `peek()` is called inside the - // `parse_code()` function that borrows `iter`. let mut iter = input.chars().peekable(); - while let Some(mut c) = iter.next() { - let mut start = 1; - - if c == '\\' { - if let Some(next) = iter.next() { - c = match next { - '\\' => '\\', - 'a' => '\x07', - 'b' => '\x08', - 'c' => { - should_stop = true; - break; - } - 'e' => '\x1b', - 'f' => '\x0c', - 'n' => '\n', - 'r' => '\r', - 't' => '\t', - 'v' => '\x0b', - 'x' => parse_code(&mut iter, 16, 2, 4).unwrap_or_else(|| { - start = 0; - next - }), - '0' => parse_code(&mut iter, 8, 3, 3).unwrap_or('\0'), - _ => { - start = 0; - next - } - }; - } + while let Some(c) = iter.next() { + if c != '\\' { + write!(output, "{c}")?; + continue; } - buffer[1] = c; - - // because printing char slices is apparently not available in the standard library - for ch in &buffer[start..] { - write!(output, "{ch}")?; + if let Some(next) = iter.next() { + let unescaped = match next { + '\\' => '\\', + 'a' => '\x07', + 'b' => '\x08', + 'c' => return Ok(true), + 'e' => '\x1b', + 'f' => '\x0c', + 'n' => '\n', + 'r' => '\r', + 't' => '\t', + 'v' => '\x0b', + 'x' => { + if let Some(c) = parse_code(&mut iter, 16, 2) { + c + } else { + write!(output, "\\")?; + 'x' + } + } + '0' => parse_code(&mut iter, 8, 3).unwrap_or('\0'), + c => { + write!(output, "\\")?; + c + } + }; + write!(output, "{unescaped}")?; + } else { + write!(output, "\\")?; } } - Ok(should_stop) + Ok(false) } #[uucore::main] diff --git a/tests/by-util/test_echo.rs b/tests/by-util/test_echo.rs index 3a8e7f86b..82137c715 100644 --- a/tests/by-util/test_echo.rs +++ b/tests/by-util/test_echo.rs @@ -236,3 +236,20 @@ fn test_hyphen_values_between() { .success() .stdout_is("dumdum dum dum dum -e dum\n"); } + +#[test] +fn wrapping_octal() { + // Some odd behavior of GNU. Values of \0400 and greater do not fit in the + // u8 that we write to stdout. So we test that it wraps: + // + // We give it this input: + // \o501 = 1_0100_0001 (yes, **9** bits) + // This should be wrapped into: + // \o101 = 'A' = 0100_0001, + // because we only write a single character + new_ucmd!() + .arg("-e") + .arg("\\0501") + .succeeds() + .stdout_is("A\n"); +} From 1ad10dd371cfe341993c23d302c7a002a85953ae Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Tue, 29 Aug 2023 21:54:19 +0200 Subject: [PATCH 05/27] echo: add support for \NNN octal escape sequence --- src/uu/echo/src/echo.rs | 10 ++++++++++ tests/by-util/test_echo.rs | 27 +++++++++++++++++++++++++++ 2 files changed, 37 insertions(+) diff --git a/src/uu/echo/src/echo.rs b/src/uu/echo/src/echo.rs index 4aba703a1..565166842 100644 --- a/src/uu/echo/src/echo.rs +++ b/src/uu/echo/src/echo.rs @@ -54,6 +54,16 @@ fn print_escaped(input: &str, mut output: impl Write) -> io::Result { continue; } + // This is for the \NNN syntax for octal sequences. + // Note that '0' is intentionally omitted because that + // would be the \0NNN syntax. + if let Some('1'..='8') = iter.peek() { + if let Some(parsed) = parse_code(&mut iter, 8, 3) { + write!(output, "{parsed}")?; + continue; + } + } + if let Some(next) = iter.next() { let unescaped = match next { '\\' => '\\', diff --git a/tests/by-util/test_echo.rs b/tests/by-util/test_echo.rs index 82137c715..7de963973 100644 --- a/tests/by-util/test_echo.rs +++ b/tests/by-util/test_echo.rs @@ -253,3 +253,30 @@ fn wrapping_octal() { .succeeds() .stdout_is("A\n"); } + +#[test] +fn old_octal_syntax() { + new_ucmd!() + .arg("-e") + .arg("\\1foo") + .succeeds() + .stdout_is("\x01foo\n"); + + new_ucmd!() + .arg("-e") + .arg("\\43foo") + .succeeds() + .stdout_is("#foo\n"); + + new_ucmd!() + .arg("-e") + .arg("\\101foo") + .succeeds() + .stdout_is("Afoo\n"); + + new_ucmd!() + .arg("-e") + .arg("\\1011") + .succeeds() + .stdout_is("A1\n"); +} From 45487d47b88d7aeb792869f48250f7c01f58507e Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Mon, 25 Sep 2023 11:43:21 +0200 Subject: [PATCH 06/27] extract Base enum --- src/uu/echo/src/echo.rs | 32 ++++++++++++++++++++++++-------- tests/by-util/test_echo.rs | 4 ++-- 2 files changed, 26 insertions(+), 10 deletions(-) diff --git a/src/uu/echo/src/echo.rs b/src/uu/echo/src/echo.rs index 565166842..5ccc6a32a 100644 --- a/src/uu/echo/src/echo.rs +++ b/src/uu/echo/src/echo.rs @@ -21,8 +21,24 @@ mod options { pub const DISABLE_BACKSLASH_ESCAPE: &str = "disable_backslash_escape"; } +#[repr(u8)] +#[derive(Clone, Copy)] +enum Base { + Oct = 8, + Hex = 16, +} + +impl Base { + fn max_digits(&self) -> u8 { + match self { + Self::Oct => 3, + Self::Hex => 2, + } + } +} + /// Parse the numeric part of the `\xHHH` and `\0NNN` escape sequences -fn parse_code(input: &mut Peekable, base: u8, max_digits: u32) -> Option { +fn parse_code(input: &mut Peekable, base: Base) -> Option { // All arithmetic on `ret` needs to be wrapping, because octal input can // take 3 digits, which is 9 bits, and therefore more than what fits in a // `u8`. GNU just seems to wrap these values. @@ -31,15 +47,15 @@ fn parse_code(input: &mut Peekable, base: u8, max_digits: u32) -> Option< // `u8::MAX` as unicode. let mut ret = input.peek().and_then(|c| c.to_digit(base as u32))? as u8; - // We can safely ifgnore the None case because we just peeked it. + // We can safely ignore the None case because we just peeked it. let _ = input.next(); - for _ in 1..max_digits { + for _ in 1..base.max_digits() { match input.peek().and_then(|c| c.to_digit(base as u32)) { - Some(n) => ret = ret.wrapping_mul(base).wrapping_add(n as u8), + Some(n) => ret = ret.wrapping_mul(base as u8).wrapping_add(n as u8), None => break, } - // We can safely ifgnore the None case because we just peeked it. + // We can safely ignore the None case because we just peeked it. let _ = input.next(); } @@ -58,7 +74,7 @@ fn print_escaped(input: &str, mut output: impl Write) -> io::Result { // Note that '0' is intentionally omitted because that // would be the \0NNN syntax. if let Some('1'..='8') = iter.peek() { - if let Some(parsed) = parse_code(&mut iter, 8, 3) { + if let Some(parsed) = parse_code(&mut iter, Base::Oct) { write!(output, "{parsed}")?; continue; } @@ -77,14 +93,14 @@ fn print_escaped(input: &str, mut output: impl Write) -> io::Result { 't' => '\t', 'v' => '\x0b', 'x' => { - if let Some(c) = parse_code(&mut iter, 16, 2) { + if let Some(c) = parse_code(&mut iter, Base::Hex) { c } else { write!(output, "\\")?; 'x' } } - '0' => parse_code(&mut iter, 8, 3).unwrap_or('\0'), + '0' => parse_code(&mut iter, Base::Oct).unwrap_or('\0'), c => { write!(output, "\\")?; c diff --git a/tests/by-util/test_echo.rs b/tests/by-util/test_echo.rs index 7de963973..dce5a4c95 100644 --- a/tests/by-util/test_echo.rs +++ b/tests/by-util/test_echo.rs @@ -270,9 +270,9 @@ fn old_octal_syntax() { new_ucmd!() .arg("-e") - .arg("\\101foo") + .arg("\\101 foo") .succeeds() - .stdout_is("Afoo\n"); + .stdout_is("A foo\n"); new_ucmd!() .arg("-e") From 9c0b9cae5610a81c2c9d152edb7fcca1c77e83dd Mon Sep 17 00:00:00 2001 From: John Shin Date: Wed, 27 Sep 2023 14:55:08 -0700 Subject: [PATCH 07/27] seq: simplyfy nan and inf parsing --- src/uu/seq/src/numberparse.rs | 29 +++++++---------------------- 1 file changed, 7 insertions(+), 22 deletions(-) diff --git a/src/uu/seq/src/numberparse.rs b/src/uu/seq/src/numberparse.rs index 156f80fb9..07d853d58 100644 --- a/src/uu/seq/src/numberparse.rs +++ b/src/uu/seq/src/numberparse.rs @@ -69,28 +69,13 @@ fn parse_no_decimal_no_exponent(s: &str) -> Result { // Possibly "NaN" or "inf". - if let Ok(num) = f32::from_str(s) { - // pattern matching on floating point literal is not encouraged 'https://github.com/rust-lang/rust/issues/41620' - if num == f32::INFINITY { - Ok(PreciseNumber::new( - Number::Float(ExtendedBigDecimal::Infinity), - 0, - 0, - )) - } else if num == f32::NEG_INFINITY { - Ok(PreciseNumber::new( - Number::Float(ExtendedBigDecimal::MinusInfinity), - 0, - 0, - )) - } else if num.is_nan() { - Err(ParseNumberError::Nan) - } else { - Err(ParseNumberError::Float) - } - } else { - Err(ParseNumberError::Float) - } + let float_val = match s.to_ascii_lowercase().as_str() { + "inf" | "infinity" => ExtendedBigDecimal::Infinity, + "-inf" | "-infinity" => ExtendedBigDecimal::MinusInfinity, + "nan" | "-nan" => return Err(ParseNumberError::Nan), + _ => return Err(ParseNumberError::Float), + }; + Ok(PreciseNumber::new(Number::Float(float_val), 0, 0)) } } } From f0f64bd3b728c905944aaf3534f618d85ded30c6 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Thu, 28 Sep 2023 08:42:30 +0200 Subject: [PATCH 08/27] fuzz: move the common duplicated code into a function --- fuzz/fuzz_targets/fuzz_common.rs | 47 ++++++++++++++++++++++++ fuzz/fuzz_targets/fuzz_expr.rs | 49 ++----------------------- fuzz/fuzz_targets/fuzz_test.rs | 61 ++++++-------------------------- 3 files changed, 59 insertions(+), 98 deletions(-) diff --git a/fuzz/fuzz_targets/fuzz_common.rs b/fuzz/fuzz_targets/fuzz_common.rs index fb1f498e9..a33d603f1 100644 --- a/fuzz/fuzz_targets/fuzz_common.rs +++ b/fuzz/fuzz_targets/fuzz_common.rs @@ -3,6 +3,8 @@ // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. +use libc::{dup, dup2, STDOUT_FILENO}; +use std::ffi::OsString; use std::process::Command; use std::sync::atomic::Ordering; use std::sync::{atomic::AtomicBool, Once}; @@ -28,3 +30,48 @@ pub fn is_gnu_cmd(cmd_path: &str) -> Result<(), std::io::Error> { panic!("Not the GNU implementation"); } } + +pub fn generate_and_run_uumain(args: &mut Vec, uumain_function: F) -> (String, i32) +where + F: FnOnce(std::vec::IntoIter) -> i32, +{ + let uumain_exit_status; + + let original_stdout_fd = unsafe { dup(STDOUT_FILENO) }; + println!("Running test {:?}", &args[1..]); + let mut pipe_fds = [-1; 2]; + unsafe { libc::pipe(pipe_fds.as_mut_ptr()) }; + + { + unsafe { dup2(pipe_fds[1], STDOUT_FILENO) }; + uumain_exit_status = uumain_function(args.clone().into_iter()); + unsafe { dup2(original_stdout_fd, STDOUT_FILENO) }; + unsafe { libc::close(original_stdout_fd) }; + } + unsafe { libc::close(pipe_fds[1]) }; + + let mut captured_output = Vec::new(); + let mut read_buffer = [0; 1024]; + loop { + let bytes_read = unsafe { + libc::read( + pipe_fds[0], + read_buffer.as_mut_ptr() as *mut libc::c_void, + read_buffer.len(), + ) + }; + if bytes_read <= 0 { + break; + } + captured_output.extend_from_slice(&read_buffer[..bytes_read as usize]); + } + + unsafe { libc::close(pipe_fds[0]) }; + + let my_output = String::from_utf8_lossy(&captured_output) + .to_string() + .trim() + .to_owned(); + + (my_output, uumain_exit_status) +} diff --git a/fuzz/fuzz_targets/fuzz_expr.rs b/fuzz/fuzz_targets/fuzz_expr.rs index e364342b8..28fded99e 100644 --- a/fuzz/fuzz_targets/fuzz_expr.rs +++ b/fuzz/fuzz_targets/fuzz_expr.rs @@ -12,9 +12,9 @@ use rand::seq::SliceRandom; use rand::Rng; use std::ffi::OsString; -use libc::{dup, dup2, STDOUT_FILENO}; use std::process::Command; mod fuzz_common; +use crate::fuzz_common::generate_and_run_uumain; use crate::fuzz_common::is_gnu_cmd; static CMD_PATH: &str = "expr"; @@ -108,52 +108,7 @@ fuzz_target!(|_data: &[u8]| { let mut args = vec![OsString::from("expr")]; args.extend(expr.split_whitespace().map(OsString::from)); - // Save the original stdout file descriptor - let original_stdout_fd = unsafe { dup(STDOUT_FILENO) }; - - // Create a pipe to capture stdout - let mut pipe_fds = [-1; 2]; - unsafe { libc::pipe(pipe_fds.as_mut_ptr()) }; - let uumain_exit_code; - { - // Redirect stdout to the write end of the pipe - unsafe { dup2(pipe_fds[1], STDOUT_FILENO) }; - - // Run uumain with the provided arguments - uumain_exit_code = uumain(args.clone().into_iter()); - - // Restore original stdout - unsafe { dup2(original_stdout_fd, STDOUT_FILENO) }; - unsafe { libc::close(original_stdout_fd) }; - } - // Close the write end of the pipe - unsafe { libc::close(pipe_fds[1]) }; - - // Read captured output from the read end of the pipe - let mut captured_output = Vec::new(); - let mut read_buffer = [0; 1024]; - loop { - let bytes_read = unsafe { - libc::read( - pipe_fds[0], - read_buffer.as_mut_ptr() as *mut libc::c_void, - read_buffer.len(), - ) - }; - if bytes_read <= 0 { - break; - } - captured_output.extend_from_slice(&read_buffer[..bytes_read as usize]); - } - - // Close the read end of the pipe - unsafe { libc::close(pipe_fds[0]) }; - - // Convert captured output to a string - let rust_output = String::from_utf8_lossy(&captured_output) - .to_string() - .trim() - .to_owned(); + let (rust_output, uumain_exit_code) = generate_and_run_uumain(&mut args, uumain); // Run GNU expr with the provided arguments and compare the output match run_gnu_expr(&args[1..]) { diff --git a/fuzz/fuzz_targets/fuzz_test.rs b/fuzz/fuzz_targets/fuzz_test.rs index 537e21abd..535696b45 100644 --- a/fuzz/fuzz_targets/fuzz_test.rs +++ b/fuzz/fuzz_targets/fuzz_test.rs @@ -12,9 +12,11 @@ use rand::seq::SliceRandom; use rand::Rng; use std::ffi::OsString; -use libc::{dup, dup2, STDOUT_FILENO}; use std::process::Command; +mod fuzz_common; +use crate::fuzz_common::generate_and_run_uumain; + #[derive(PartialEq, Debug, Clone)] enum ArgType { STRING, @@ -26,8 +28,11 @@ enum ArgType { // Add any other types as needed } +static CMD_PATH: &str = "/usr/bin/test"; + fn run_gnu_test(args: &[OsString]) -> Result<(String, i32), std::io::Error> { - let mut command = Command::new("test"); + let mut command = Command::new(CMD_PATH); + for arg in args { command.arg(arg); } @@ -210,58 +215,12 @@ fuzz_target!(|_data: &[u8]| { let mut rng = rand::thread_rng(); let max_args = rng.gen_range(1..=6); let mut args = vec![OsString::from("test")]; - let uumain_exit_status; for _ in 0..max_args { args.push(OsString::from(generate_test_arg())); } - // Save the original stdout file descriptor - let original_stdout_fd = unsafe { dup(STDOUT_FILENO) }; - println!("Running test {:?}", &args[1..]); - // Create a pipe to capture stdout - let mut pipe_fds = [-1; 2]; - unsafe { libc::pipe(pipe_fds.as_mut_ptr()) }; - - { - // Redirect stdout to the write end of the pipe - unsafe { dup2(pipe_fds[1], STDOUT_FILENO) }; - - // Run uumain with the provided arguments - uumain_exit_status = uumain(args.clone().into_iter()); - - // Restore original stdout - unsafe { dup2(original_stdout_fd, STDOUT_FILENO) }; - unsafe { libc::close(original_stdout_fd) }; - } - // Close the write end of the pipe - unsafe { libc::close(pipe_fds[1]) }; - - // Read captured output from the read end of the pipe - let mut captured_output = Vec::new(); - let mut read_buffer = [0; 1024]; - loop { - let bytes_read = unsafe { - libc::read( - pipe_fds[0], - read_buffer.as_mut_ptr() as *mut libc::c_void, - read_buffer.len(), - ) - }; - if bytes_read <= 0 { - break; - } - captured_output.extend_from_slice(&read_buffer[..bytes_read as usize]); - } - - // Close the read end of the pipe - unsafe { libc::close(pipe_fds[0]) }; - - // Convert captured output to a string - let my_output = String::from_utf8_lossy(&captured_output) - .to_string() - .trim() - .to_owned(); + let (rust_output, uumain_exit_status) = generate_and_run_uumain(&mut args, uumain); // Run GNU test with the provided arguments and compare the output match run_gnu_test(&args[1..]) { @@ -269,10 +228,10 @@ fuzz_target!(|_data: &[u8]| { let gnu_output = gnu_output.trim().to_owned(); println!("gnu_exit_status {}", gnu_exit_status); println!("uumain_exit_status {}", uumain_exit_status); - if my_output != gnu_output || uumain_exit_status != gnu_exit_status { + if rust_output != gnu_output || uumain_exit_status != gnu_exit_status { println!("Discrepancy detected!"); println!("Test: {:?}", &args[1..]); - println!("My output: {}", my_output); + println!("My output: {}", rust_output); println!("GNU output: {}", gnu_output); println!("My exit status: {}", uumain_exit_status); println!("GNU exit status: {}", gnu_exit_status); From 51eb20a15d768b0b24b60a78b98c27602ed5cc50 Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Thu, 28 Sep 2023 10:50:56 +0200 Subject: [PATCH 09/27] fmt: use clap's value parser for goal & width --- src/uu/fmt/src/fmt.rs | 30 ++++++++---------------------- tests/by-util/test_fmt.rs | 22 ++++++++++++++++++++++ 2 files changed, 30 insertions(+), 22 deletions(-) diff --git a/src/uu/fmt/src/fmt.rs b/src/uu/fmt/src/fmt.rs index c5eac7073..c30d923b7 100644 --- a/src/uu/fmt/src/fmt.rs +++ b/src/uu/fmt/src/fmt.rs @@ -131,16 +131,8 @@ fn parse_arguments(args: impl uucore::Args) -> UResult<(Vec, FmtOptions) fmt_opts.use_anti_prefix = true; }; - if let Some(s) = matches.get_one::(OPT_WIDTH) { - fmt_opts.width = match s.parse::() { - Ok(t) => t, - Err(e) => { - return Err(USimpleError::new( - 1, - format!("Invalid WIDTH specification: {}: {}", s.quote(), e), - )); - } - }; + if let Some(width) = matches.get_one::(OPT_WIDTH) { + fmt_opts.width = *width; if fmt_opts.width > MAX_WIDTH { return Err(USimpleError::new( 1, @@ -156,16 +148,8 @@ fn parse_arguments(args: impl uucore::Args) -> UResult<(Vec, FmtOptions) ); }; - if let Some(s) = matches.get_one::(OPT_GOAL) { - fmt_opts.goal = match s.parse::() { - Ok(t) => t, - Err(e) => { - return Err(USimpleError::new( - 1, - format!("Invalid GOAL specification: {}: {}", s.quote(), e), - )); - } - }; + if let Some(goal) = matches.get_one::(OPT_GOAL) { + fmt_opts.goal = *goal; if !matches.contains_id(OPT_WIDTH) { fmt_opts.width = cmp::max( fmt_opts.goal * 100 / DEFAULT_GOAL_TO_WIDTH_RATIO, @@ -372,14 +356,16 @@ pub fn uu_app() -> Command { .short('w') .long("width") .help("Fill output lines up to a maximum of WIDTH columns, default 75.") - .value_name("WIDTH"), + .value_name("WIDTH") + .value_parser(clap::value_parser!(usize)), ) .arg( Arg::new(OPT_GOAL) .short('g') .long("goal") .help("Goal width, default of 93% of WIDTH. Must be less than WIDTH.") - .value_name("GOAL"), + .value_name("GOAL") + .value_parser(clap::value_parser!(usize)), ) .arg( Arg::new(OPT_QUICK) diff --git a/tests/by-util/test_fmt.rs b/tests/by-util/test_fmt.rs index 7d23cbd52..4fd059080 100644 --- a/tests/by-util/test_fmt.rs +++ b/tests/by-util/test_fmt.rs @@ -48,6 +48,17 @@ fn test_fmt_width_too_big() { } } +#[test] +fn test_fmt_invalid_width() { + for param in ["-w", "--width"] { + new_ucmd!() + .args(&["one-word-per-line.txt", param, "invalid"]) + .fails() + .code_is(1) + .stderr_contains("invalid value 'invalid'"); + } +} + #[ignore] #[test] fn test_fmt_goal() { @@ -70,6 +81,17 @@ fn test_fmt_goal_too_big() { } } +#[test] +fn test_fmt_invalid_goal() { + for param in ["-g", "--goal"] { + new_ucmd!() + .args(&["one-word-per-line.txt", param, "invalid"]) + .fails() + .code_is(1) + .stderr_contains("invalid value 'invalid'"); + } +} + #[test] fn test_fmt_set_goal_not_contain_width() { for param in ["-g", "--goal"] { From a17ede9ef02ca864dc7fcd5bda681e990339b674 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Thu, 28 Sep 2023 15:36:06 +0200 Subject: [PATCH 10/27] Remove the full path to test --- fuzz/fuzz_targets/fuzz_test.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fuzz/fuzz_targets/fuzz_test.rs b/fuzz/fuzz_targets/fuzz_test.rs index 535696b45..dbd2db54a 100644 --- a/fuzz/fuzz_targets/fuzz_test.rs +++ b/fuzz/fuzz_targets/fuzz_test.rs @@ -28,7 +28,7 @@ enum ArgType { // Add any other types as needed } -static CMD_PATH: &str = "/usr/bin/test"; +static CMD_PATH: &str = "test"; fn run_gnu_test(args: &[OsString]) -> Result<(String, i32), std::io::Error> { let mut command = Command::new(CMD_PATH); From e6f9e358d4f3365ea62ef86ddbe5b5b3f71a3dbf Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Thu, 28 Sep 2023 21:48:34 +0200 Subject: [PATCH 11/27] fuzz: create a function run_gnu_cmd to deduplicate the code --- fuzz/fuzz_targets/fuzz_common.rs | 30 ++++++++++++++++++++++++++++++ fuzz/fuzz_targets/fuzz_expr.rs | 28 ++-------------------------- fuzz/fuzz_targets/fuzz_test.rs | 20 ++------------------ 3 files changed, 34 insertions(+), 44 deletions(-) diff --git a/fuzz/fuzz_targets/fuzz_common.rs b/fuzz/fuzz_targets/fuzz_common.rs index a33d603f1..351a7e8e8 100644 --- a/fuzz/fuzz_targets/fuzz_common.rs +++ b/fuzz/fuzz_targets/fuzz_common.rs @@ -5,6 +5,7 @@ use libc::{dup, dup2, STDOUT_FILENO}; use std::ffi::OsString; +use std::io; use std::process::Command; use std::sync::atomic::Ordering; use std::sync::{atomic::AtomicBool, Once}; @@ -75,3 +76,32 @@ where (my_output, uumain_exit_status) } + +pub fn run_gnu_cmd( + cmd_path: &str, + args: &[OsString], + check_gnu: bool, +) -> Result<(String, i32), io::Error> { + if check_gnu { + is_gnu_cmd(cmd_path)?; // Check if it's a GNU implementation + } + + let mut command = Command::new(cmd_path); + for arg in args { + command.arg(arg); + } + + let output = command.output()?; + let exit_code = output.status.code().unwrap_or(-1); + if output.status.success() || !check_gnu { + Ok(( + String::from_utf8_lossy(&output.stdout).to_string(), + exit_code, + )) + } else { + Err(io::Error::new( + io::ErrorKind::Other, + format!("GNU command execution failed with exit code {}", exit_code), + )) + } +} diff --git a/fuzz/fuzz_targets/fuzz_expr.rs b/fuzz/fuzz_targets/fuzz_expr.rs index 28fded99e..6344c0525 100644 --- a/fuzz/fuzz_targets/fuzz_expr.rs +++ b/fuzz/fuzz_targets/fuzz_expr.rs @@ -12,35 +12,11 @@ use rand::seq::SliceRandom; use rand::Rng; use std::ffi::OsString; -use std::process::Command; mod fuzz_common; -use crate::fuzz_common::generate_and_run_uumain; -use crate::fuzz_common::is_gnu_cmd; +use crate::fuzz_common::{generate_and_run_uumain, run_gnu_cmd}; static CMD_PATH: &str = "expr"; -fn run_gnu_expr(args: &[OsString]) -> Result<(String, i32), std::io::Error> { - is_gnu_cmd(CMD_PATH)?; // Check if it's a GNU implementation - - let mut command = Command::new(CMD_PATH); - for arg in args { - command.arg(arg); - } - let output = command.output()?; - let exit_code = output.status.code().unwrap_or(-1); - if output.status.success() { - Ok(( - String::from_utf8_lossy(&output.stdout).to_string(), - exit_code, - )) - } else { - Err(std::io::Error::new( - std::io::ErrorKind::Other, - format!("GNU expr execution failed with exit code {}", exit_code), - )) - } -} - fn generate_random_string(max_length: usize) -> String { let mut rng = rand::thread_rng(); let valid_utf8: Vec = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789" @@ -111,7 +87,7 @@ fuzz_target!(|_data: &[u8]| { let (rust_output, uumain_exit_code) = generate_and_run_uumain(&mut args, uumain); // Run GNU expr with the provided arguments and compare the output - match run_gnu_expr(&args[1..]) { + match run_gnu_cmd(CMD_PATH, &args[1..], true) { Ok((gnu_output, gnu_exit_code)) => { let gnu_output = gnu_output.trim().to_owned(); if uumain_exit_code != gnu_exit_code { diff --git a/fuzz/fuzz_targets/fuzz_test.rs b/fuzz/fuzz_targets/fuzz_test.rs index dbd2db54a..bfde25246 100644 --- a/fuzz/fuzz_targets/fuzz_test.rs +++ b/fuzz/fuzz_targets/fuzz_test.rs @@ -12,10 +12,8 @@ use rand::seq::SliceRandom; use rand::Rng; use std::ffi::OsString; -use std::process::Command; - mod fuzz_common; -use crate::fuzz_common::generate_and_run_uumain; +use crate::fuzz_common::{generate_and_run_uumain, run_gnu_cmd}; #[derive(PartialEq, Debug, Clone)] enum ArgType { @@ -30,20 +28,6 @@ enum ArgType { static CMD_PATH: &str = "test"; -fn run_gnu_test(args: &[OsString]) -> Result<(String, i32), std::io::Error> { - let mut command = Command::new(CMD_PATH); - - for arg in args { - command.arg(arg); - } - let output = command.output()?; - let exit_status = output.status.code().unwrap_or(-1); // Capture the exit status code - Ok(( - String::from_utf8_lossy(&output.stdout).to_string(), - exit_status, - )) -} - fn generate_random_string(max_length: usize) -> String { let mut rng = rand::thread_rng(); let valid_utf8: Vec = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789" @@ -223,7 +207,7 @@ fuzz_target!(|_data: &[u8]| { let (rust_output, uumain_exit_status) = generate_and_run_uumain(&mut args, uumain); // Run GNU test with the provided arguments and compare the output - match run_gnu_test(&args[1..]) { + match run_gnu_cmd(CMD_PATH, &args[1..], false) { Ok((gnu_output, gnu_exit_status)) => { let gnu_output = gnu_output.trim().to_owned(); println!("gnu_exit_status {}", gnu_exit_status); From a576054d42373ee3e502f015ffb998cc232df824 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Thu, 28 Sep 2023 21:52:26 +0200 Subject: [PATCH 12/27] fuzz: fix clippy warnings --- fuzz/fuzz_targets/fuzz_common.rs | 4 ++-- fuzz/fuzz_targets/fuzz_date.rs | 2 +- fuzz/fuzz_targets/fuzz_expr.rs | 14 +++++++------- fuzz/fuzz_targets/fuzz_parse_glob.rs | 2 +- fuzz/fuzz_targets/fuzz_test.rs | 4 ++-- 5 files changed, 13 insertions(+), 13 deletions(-) diff --git a/fuzz/fuzz_targets/fuzz_common.rs b/fuzz/fuzz_targets/fuzz_common.rs index 351a7e8e8..a94963ef0 100644 --- a/fuzz/fuzz_targets/fuzz_common.rs +++ b/fuzz/fuzz_targets/fuzz_common.rs @@ -32,7 +32,7 @@ pub fn is_gnu_cmd(cmd_path: &str) -> Result<(), std::io::Error> { } } -pub fn generate_and_run_uumain(args: &mut Vec, uumain_function: F) -> (String, i32) +pub fn generate_and_run_uumain(args: &[OsString], uumain_function: F) -> (String, i32) where F: FnOnce(std::vec::IntoIter) -> i32, { @@ -45,7 +45,7 @@ where { unsafe { dup2(pipe_fds[1], STDOUT_FILENO) }; - uumain_exit_status = uumain_function(args.clone().into_iter()); + uumain_exit_status = uumain_function(args.to_owned().into_iter()); unsafe { dup2(original_stdout_fd, STDOUT_FILENO) }; unsafe { libc::close(original_stdout_fd) }; } diff --git a/fuzz/fuzz_targets/fuzz_date.rs b/fuzz/fuzz_targets/fuzz_date.rs index 96c56cc6b..0f9cb262c 100644 --- a/fuzz/fuzz_targets/fuzz_date.rs +++ b/fuzz/fuzz_targets/fuzz_date.rs @@ -9,6 +9,6 @@ fuzz_target!(|data: &[u8]| { let args = data .split(|b| *b == delim) .filter_map(|e| std::str::from_utf8(e).ok()) - .map(|e| OsString::from(e)); + .map(OsString::from); uumain(args); }); diff --git a/fuzz/fuzz_targets/fuzz_expr.rs b/fuzz/fuzz_targets/fuzz_expr.rs index 6344c0525..fb7b17309 100644 --- a/fuzz/fuzz_targets/fuzz_expr.rs +++ b/fuzz/fuzz_targets/fuzz_expr.rs @@ -84,7 +84,7 @@ fuzz_target!(|_data: &[u8]| { let mut args = vec![OsString::from("expr")]; args.extend(expr.split_whitespace().map(OsString::from)); - let (rust_output, uumain_exit_code) = generate_and_run_uumain(&mut args, uumain); + let (rust_output, uumain_exit_code) = generate_and_run_uumain(&args, uumain); // Run GNU expr with the provided arguments and compare the output match run_gnu_cmd(CMD_PATH, &args[1..], true) { @@ -96,16 +96,16 @@ fuzz_target!(|_data: &[u8]| { println!("GNU code: {}", gnu_exit_code); panic!("Different error codes"); } - if rust_output != gnu_output { - println!("Expression: {}", expr); - println!("Rust output: {}", rust_output); - println!("GNU output: {}", gnu_output); - panic!("Different output between Rust & GNU"); - } else { + if rust_output == gnu_output { println!( "Outputs matched for expression: {} => Result: {}", expr, rust_output ); + } else { + println!("Expression: {}", expr); + println!("Rust output: {}", rust_output); + println!("GNU output: {}", gnu_output); + panic!("Different output between Rust & GNU"); } } Err(_) => { diff --git a/fuzz/fuzz_targets/fuzz_parse_glob.rs b/fuzz/fuzz_targets/fuzz_parse_glob.rs index 061569bc4..e235c0c9d 100644 --- a/fuzz/fuzz_targets/fuzz_parse_glob.rs +++ b/fuzz/fuzz_targets/fuzz_parse_glob.rs @@ -5,6 +5,6 @@ use uucore::parse_glob; fuzz_target!(|data: &[u8]| { if let Ok(s) = std::str::from_utf8(data) { - _ = parse_glob::from_str(s) + _ = parse_glob::from_str(s); } }); diff --git a/fuzz/fuzz_targets/fuzz_test.rs b/fuzz/fuzz_targets/fuzz_test.rs index bfde25246..4805a41af 100644 --- a/fuzz/fuzz_targets/fuzz_test.rs +++ b/fuzz/fuzz_targets/fuzz_test.rs @@ -142,7 +142,7 @@ fn generate_test_arg() -> String { 0 => { arg.push_str(&rng.gen_range(-100..=100).to_string()); } - 1 | 2 | 3 => { + 1..=3 => { let test_arg = test_args .choose(&mut rng) .expect("Failed to choose a random test argument"); @@ -204,7 +204,7 @@ fuzz_target!(|_data: &[u8]| { args.push(OsString::from(generate_test_arg())); } - let (rust_output, uumain_exit_status) = generate_and_run_uumain(&mut args, uumain); + let (rust_output, uumain_exit_status) = generate_and_run_uumain(&args, uumain); // Run GNU test with the provided arguments and compare the output match run_gnu_cmd(CMD_PATH, &args[1..], false) { From 55fd1f3617958209180b596c5e75fd017a2b1fb9 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Thu, 28 Sep 2023 21:25:38 +0200 Subject: [PATCH 13/27] Replace list of digit by is_ascii_digit and some ride along simplification --- src/uu/chmod/src/chmod.rs | 14 +++++------ src/uu/install/src/mode.rs | 5 +--- src/uu/mkdir/src/mkdir.rs | 36 +++++++++++++---------------- src/uu/mknod/src/parsemode.rs | 3 +-- src/uucore/src/lib/features/mode.rs | 3 +-- 5 files changed, 26 insertions(+), 35 deletions(-) diff --git a/src/uu/chmod/src/chmod.rs b/src/uu/chmod/src/chmod.rs index b007bb1d7..31663b1af 100644 --- a/src/uu/chmod/src/chmod.rs +++ b/src/uu/chmod/src/chmod.rs @@ -335,9 +335,7 @@ impl Chmoder { let mut new_mode = fperm; let mut naively_expected_new_mode = new_mode; for mode in cmode_unwrapped.split(',') { - // cmode is guaranteed to be Some in this case - let arr: &[char] = &['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']; - let result = if mode.contains(arr) { + let result = if mode.chars().any(|c| c.is_ascii_digit()) { mode::parse_numeric(new_mode, mode, file.is_dir()).map(|v| (v, v)) } else { mode::parse_symbolic(new_mode, mode, get_umask(), file.is_dir()).map(|m| { @@ -352,20 +350,22 @@ impl Chmoder { (m, naive_mode) }) }; + match result { Ok((mode, naive_mode)) => { new_mode = mode; naively_expected_new_mode = naive_mode; } Err(f) => { - if self.quiet { - return Err(ExitCode::new(1)); + return if self.quiet { + Err(ExitCode::new(1)) } else { - return Err(USimpleError::new(1, f)); - } + Err(USimpleError::new(1, f)) + }; } } } + self.change_file(fperm, new_mode, file)?; // if a permission would have been removed if umask was 0, but it wasn't because umask was not 0, print an error and fail if (new_mode & !naively_expected_new_mode) != 0 { diff --git a/src/uu/install/src/mode.rs b/src/uu/install/src/mode.rs index f9018e16f..ebdec14af 100644 --- a/src/uu/install/src/mode.rs +++ b/src/uu/install/src/mode.rs @@ -9,10 +9,7 @@ use uucore::mode; /// Takes a user-supplied string and tries to parse to u16 mode bitmask. pub fn parse(mode_string: &str, considering_dir: bool, umask: u32) -> Result { - let numbers: &[char] = &['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']; - - // Passing 000 as the existing permissions seems to mirror GNU behavior. - if mode_string.contains(numbers) { + if mode_string.chars().any(|c| c.is_ascii_digit()) { mode::parse_numeric(0, mode_string, considering_dir) } else { mode::parse_symbolic(0, mode_string, umask, considering_dir) diff --git a/src/uu/mkdir/src/mkdir.rs b/src/uu/mkdir/src/mkdir.rs index 2044855e4..76aa51f07 100644 --- a/src/uu/mkdir/src/mkdir.rs +++ b/src/uu/mkdir/src/mkdir.rs @@ -38,31 +38,27 @@ fn get_mode(_matches: &ArgMatches, _mode_had_minus_prefix: bool) -> Result Result { - let digits: &[char] = &['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']; - // Translate a ~str in octal form to u16, default to 777 // Not tested on Windows let mut new_mode = DEFAULT_PERM; - match matches.get_one::(options::MODE) { - Some(m) => { - for mode in m.split(',') { - if mode.contains(digits) { - new_mode = mode::parse_numeric(new_mode, m, true)?; + + if let Some(m) = matches.get_one::(options::MODE) { + for mode in m.split(',') { + if mode.chars().any(|c| c.is_ascii_digit()) { + new_mode = mode::parse_numeric(new_mode, m, true)?; + } else { + let cmode = if mode_had_minus_prefix { + // clap parsing is finished, now put prefix back + format!("-{mode}") } else { - let cmode = if mode_had_minus_prefix { - // clap parsing is finished, now put prefix back - format!("-{mode}") - } else { - mode.to_string() - }; - new_mode = mode::parse_symbolic(new_mode, &cmode, mode::get_umask(), true)?; - } + mode.to_string() + }; + new_mode = mode::parse_symbolic(new_mode, &cmode, mode::get_umask(), true)?; } - Ok(new_mode) - } - None => { - // If no mode argument is specified return the mode derived from umask - Ok(!mode::get_umask() & 0o0777) } + Ok(new_mode) + } else { + // If no mode argument is specified return the mode derived from umask + Ok(!mode::get_umask() & 0o0777) } } diff --git a/src/uu/mknod/src/parsemode.rs b/src/uu/mknod/src/parsemode.rs index adacaa45b..c38800bcb 100644 --- a/src/uu/mknod/src/parsemode.rs +++ b/src/uu/mknod/src/parsemode.rs @@ -11,8 +11,7 @@ use uucore::mode; pub const MODE_RW_UGO: mode_t = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH; pub fn parse_mode(mode: &str) -> Result { - let arr: &[char] = &['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']; - let result = if mode.contains(arr) { + let result = if mode.chars().any(|c| c.is_ascii_digit()) { mode::parse_numeric(MODE_RW_UGO as u32, mode) } else { mode::parse_symbolic(MODE_RW_UGO as u32, mode, true) diff --git a/src/uucore/src/lib/features/mode.rs b/src/uucore/src/lib/features/mode.rs index cbaea71bf..147624891 100644 --- a/src/uucore/src/lib/features/mode.rs +++ b/src/uucore/src/lib/features/mode.rs @@ -147,8 +147,7 @@ pub fn parse_mode(mode: &str) -> Result { #[cfg(any(target_os = "freebsd", target_vendor = "apple", target_os = "android"))] let fperm = (S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH) as u32; - let arr: &[char] = &['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']; - let result = if mode.contains(arr) { + let result = if mode.chars().any(|c| c.is_ascii_digit()) { parse_numeric(fperm, mode, true) } else { parse_symbolic(fperm, mode, get_umask(), true) From f92066cee2554813aacd5dceac19c5d79b571b0f Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 30 Sep 2023 13:25:14 +0000 Subject: [PATCH 14/27] chore(deps): update rust crate regex to 1.9.6 --- Cargo.lock | 8 ++++---- Cargo.toml | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c00755198..4dc36c88f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1740,9 +1740,9 @@ checksum = "f1bfbf25d7eb88ddcbb1ec3d755d0634da8f7657b2cb8b74089121409ab8228f" [[package]] name = "regex" -version = "1.9.5" +version = "1.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "697061221ea1b4a94a624f67d0ae2bfe4e22b8a17b6a192afb11046542cc8c47" +checksum = "ebee201405406dbf528b8b672104ae6d6d63e6d118cb10e4d51abbc7b58044ff" dependencies = [ "aho-corasick", "memchr", @@ -1752,9 +1752,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.3.8" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2f401f4955220693b56f8ec66ee9c78abffd8d1c4f23dc41a23839eb88f0795" +checksum = "59b23e92ee4318893fa3fe3e6fb365258efbfe6ac6ab30f090cdcbb7aa37efa9" dependencies = [ "aho-corasick", "memchr", diff --git a/Cargo.toml b/Cargo.toml index a40d066d4..831d4ae48 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -310,7 +310,7 @@ rand = { version = "0.8", features = ["small_rng"] } rand_core = "0.6" rayon = "1.8" redox_syscall = "0.4" -regex = "1.9.5" +regex = "1.9.6" rstest = "0.18.2" rust-ini = "0.19.0" same-file = "1.0.6" From 95ccc54d05892db5630e1a9413474041605fe2f0 Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Sat, 30 Sep 2023 15:35:05 +0200 Subject: [PATCH 15/27] nl: defer showing "line number overflow" error --- src/uu/nl/src/nl.rs | 17 ++++++++++------- tests/by-util/test_nl.rs | 16 ++++++++++++++++ 2 files changed, 26 insertions(+), 7 deletions(-) diff --git a/src/uu/nl/src/nl.rs b/src/uu/nl/src/nl.rs index ea37e00dc..71b4aac28 100644 --- a/src/uu/nl/src/nl.rs +++ b/src/uu/nl/src/nl.rs @@ -56,14 +56,14 @@ impl Default for Settings { } struct Stats { - line_number: i64, + line_number: Option, consecutive_empty_lines: u64, } impl Stats { fn new(starting_line_number: i64) -> Self { Self { - line_number: starting_line_number, + line_number: Some(starting_line_number), consecutive_empty_lines: 0, } } @@ -344,7 +344,7 @@ fn nl(reader: &mut BufReader, stats: &mut Stats, settings: &Settings if let Some(new_style) = new_numbering_style { current_numbering_style = new_style; if settings.renumber { - stats.line_number = settings.starting_line_number; + stats.line_number = Some(settings.starting_line_number); } println!(); } else { @@ -364,18 +364,21 @@ fn nl(reader: &mut BufReader, stats: &mut Stats, settings: &Settings }; if is_line_numbered { + let Some(line_number) = stats.line_number else { + return Err(USimpleError::new(1, "line number overflow")); + }; println!( "{}{}{}", settings .number_format - .format(stats.line_number, settings.number_width), + .format(line_number, settings.number_width), settings.number_separator, line ); // update line number for the potential next line - match stats.line_number.checked_add(settings.line_increment) { - Some(new_line_number) => stats.line_number = new_line_number, - None => return Err(USimpleError::new(1, "line number overflow")), + match line_number.checked_add(settings.line_increment) { + Some(new_line_number) => stats.line_number = Some(new_line_number), + None => stats.line_number = None, // overflow } } else { let spaces = " ".repeat(settings.number_width + 1); diff --git a/tests/by-util/test_nl.rs b/tests/by-util/test_nl.rs index 87f218166..78c8975a8 100644 --- a/tests/by-util/test_nl.rs +++ b/tests/by-util/test_nl.rs @@ -539,6 +539,22 @@ fn test_line_number_overflow() { .stderr_is("nl: line number overflow\n"); } +#[test] +fn test_line_number_no_overflow() { + new_ucmd!() + .arg(format!("--starting-line-number={}", i64::MAX)) + .pipe_in("a\n\\:\\:\nb") + .succeeds() + .stdout_is(format!("{0}\ta\n\n{0}\tb\n", i64::MAX)); + + new_ucmd!() + .arg(format!("--starting-line-number={}", i64::MIN)) + .arg("--line-increment=-1") + .pipe_in("a\n\\:\\:\nb") + .succeeds() + .stdout_is(format!("{0}\ta\n\n{0}\tb\n", i64::MIN)); +} + #[test] fn test_section_delimiter() { for arg in ["-dabc", "--section-delimiter=abc"] { From 7337cd51691b0345308c1e1c37f2a7d1e1b3390e Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Sun, 1 Oct 2023 09:58:09 +0200 Subject: [PATCH 16/27] ls -R1: add a test to replicate GNU's recursive.sh --- tests/by-util/test_ls.rs | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/tests/by-util/test_ls.rs b/tests/by-util/test_ls.rs index 23dfafa32..7d0f86298 100644 --- a/tests/by-util/test_ls.rs +++ b/tests/by-util/test_ls.rs @@ -1928,6 +1928,35 @@ fn test_ls_recursive() { result.stdout_contains("a\\b:\nb"); } +#[test] +fn test_ls_recursive_1() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + at.mkdir("x"); + at.mkdir("y"); + at.mkdir("a"); + at.mkdir("b"); + at.mkdir("c"); + at.mkdir("a/1"); + at.mkdir("a/2"); + at.mkdir("a/3"); + at.touch("f"); + at.touch("a/1/I"); + at.touch("a/1/II"); + #[cfg(unix)] + let out = "a:\n1\n2\n3\n\na/1:\nI\nII\n\na/2:\n\na/3:\n\nb:\n\nc:\n"; + #[cfg(windows)] + let out = "a:\n1\n2\n3\n\na\\1:\nI\nII\n\na\\2:\n\na\\3:\n\nb:\n\nc:\n"; + scene + .ucmd() + .arg("-R1") + .arg("a") + .arg("b") + .arg("c") + .succeeds() + .stdout_is(out); +} + #[test] fn test_ls_color() { let scene = TestScenario::new(util_name!()); From c27fcc4084cf0b3de255b16a34071e82e96d67c6 Mon Sep 17 00:00:00 2001 From: Daniel Clarke Date: Sun, 1 Oct 2023 12:01:46 -0400 Subject: [PATCH 17/27] Update parse_datetime to 0.5.0 (#5313) --- Cargo.lock | 4 ++-- Cargo.toml | 2 +- src/uu/date/src/date.rs | 4 +++- src/uu/touch/src/touch.rs | 41 +++++++++++++------------------------ tests/by-util/test_touch.rs | 12 +++++++++++ 5 files changed, 32 insertions(+), 31 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4dc36c88f..98f95032f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1499,9 +1499,9 @@ dependencies = [ [[package]] name = "parse_datetime" -version = "0.4.0" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fecceaede7767a9a98058687a321bc91742eff7670167a34104afb30fc8757df" +checksum = "3bbf4e25b13841080e018a1e666358adfe5e39b6d353f986ca5091c210b586a1" dependencies = [ "chrono", "regex", diff --git a/Cargo.toml b/Cargo.toml index 831d4ae48..d6feeb0ca 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -301,7 +301,7 @@ num-traits = "0.2.16" number_prefix = "0.4" once_cell = "1.18.0" onig = { version = "~6.4", default-features = false } -parse_datetime = "0.4.0" +parse_datetime = "0.5.0" phf = "0.11.2" phf_codegen = "0.11.2" platform-info = "2.0.2" diff --git a/src/uu/date/src/date.rs b/src/uu/date/src/date.rs index 745fd5423..b5ab8993a 100644 --- a/src/uu/date/src/date.rs +++ b/src/uu/date/src/date.rs @@ -166,7 +166,9 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { }; let date_source = if let Some(date) = matches.get_one::(OPT_DATE) { - if let Ok(duration) = parse_datetime::from_str(date.as_str()) { + let ref_time = Local::now(); + if let Ok(new_time) = parse_datetime::parse_datetime_at_date(ref_time, date.as_str()) { + let duration = new_time.signed_duration_since(ref_time); DateSource::Human(duration) } else { DateSource::Custom(date.into()) diff --git a/src/uu/touch/src/touch.rs b/src/uu/touch/src/touch.rs index 85eb97bc4..6555773ee 100644 --- a/src/uu/touch/src/touch.rs +++ b/src/uu/touch/src/touch.rs @@ -68,6 +68,10 @@ fn datetime_to_filetime(dt: &DateTime) -> FileTime { FileTime::from_unix_time(dt.timestamp(), dt.timestamp_subsec_nanos()) } +fn filetime_to_datetime(ft: &FileTime) -> Option> { + Some(DateTime::from_timestamp(ft.unix_seconds(), ft.nanoseconds())?.into()) +} + #[uucore::main] #[allow(clippy::cognitive_complexity)] pub fn uumain(args: impl uucore::Args) -> UResult<()> { @@ -88,35 +92,19 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { ) { (Some(reference), Some(date)) => { let (atime, mtime) = stat(Path::new(reference), !matches.get_flag(options::NO_DEREF))?; - if let Ok(offset) = parse_datetime::from_str(date) { - let seconds = offset.num_seconds(); - let nanos = offset.num_nanoseconds().unwrap_or(0) % 1_000_000_000; - - let ref_atime_secs = atime.unix_seconds(); - let ref_atime_nanos = atime.nanoseconds(); - let atime = FileTime::from_unix_time( - ref_atime_secs + seconds, - ref_atime_nanos + nanos as u32, - ); - - let ref_mtime_secs = mtime.unix_seconds(); - let ref_mtime_nanos = mtime.nanoseconds(); - let mtime = FileTime::from_unix_time( - ref_mtime_secs + seconds, - ref_mtime_nanos + nanos as u32, - ); - - (atime, mtime) - } else { - let timestamp = parse_date(date)?; - (timestamp, timestamp) - } + let atime = filetime_to_datetime(&atime).ok_or_else(|| { + USimpleError::new(1, "Could not process the reference access time") + })?; + let mtime = filetime_to_datetime(&mtime).ok_or_else(|| { + USimpleError::new(1, "Could not process the reference modification time") + })?; + (parse_date(atime, date)?, parse_date(mtime, date)?) } (Some(reference), None) => { stat(Path::new(reference), !matches.get_flag(options::NO_DEREF))? } (None, Some(date)) => { - let timestamp = parse_date(date)?; + let timestamp = parse_date(Local::now(), date)?; (timestamp, timestamp) } (None, None) => { @@ -336,7 +324,7 @@ fn stat(path: &Path, follow: bool) -> UResult<(FileTime, FileTime)> { )) } -fn parse_date(s: &str) -> UResult { +fn parse_date(ref_time: DateTime, s: &str) -> UResult { // This isn't actually compatible with GNU touch, but there doesn't seem to // be any simple specification for what format this parameter allows and I'm // not about to implement GNU parse_datetime. @@ -385,8 +373,7 @@ fn parse_date(s: &str) -> UResult { } } - if let Ok(duration) = parse_datetime::from_str(s) { - let dt = Local::now() + duration; + if let Ok(dt) = parse_datetime::parse_datetime_at_date(ref_time, s) { return Ok(datetime_to_filetime(&dt)); } diff --git a/tests/by-util/test_touch.rs b/tests/by-util/test_touch.rs index c9c0d700e..7b659fc51 100644 --- a/tests/by-util/test_touch.rs +++ b/tests/by-util/test_touch.rs @@ -844,3 +844,15 @@ fn test_touch_dash() { ucmd.args(&["-h", "-"]).succeeds().no_stderr().no_stdout(); } + +#[test] +// Chrono panics for now +#[ignore] +fn test_touch_invalid_date_format() { + let (_at, mut ucmd) = at_and_ucmd!(); + let file = "test_touch_invalid_date_format"; + + ucmd.args(&["-m", "-t", "+1000000000000 years", file]) + .fails() + .stderr_contains("touch: invalid date format ‘+1000000000000 years’"); +} From d4220e9bb75a524707474dfca4f3c04f71f1d275 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sun, 1 Oct 2023 20:03:02 +0000 Subject: [PATCH 18/27] chore(deps): update rust crate bytecount to 0.6.4 --- Cargo.lock | 4 ++-- Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 98f95032f..76e1dd72c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -205,9 +205,9 @@ checksum = "572f695136211188308f16ad2ca5c851a712c464060ae6974944458eb83880ba" [[package]] name = "bytecount" -version = "0.6.3" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c676a478f63e9fa2dd5368a42f28bba0d6c560b775f38583c8bbaa7fcd67c9c" +checksum = "ad152d03a2c813c80bb94fedbf3a3f02b28f793e39e7c214c8a0bcc196343de7" [[package]] name = "byteorder" diff --git a/Cargo.toml b/Cargo.toml index d6feeb0ca..c4c3fc2ff 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.6" -bytecount = "0.6.3" +bytecount = "0.6.4" byteorder = "1.4.3" chrono = { version = "^0.4.31", default-features = false, features = [ "std", From fda762b91cfe1833830583d03668dda6ff0c0e28 Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Mon, 2 Oct 2023 14:31:41 +0200 Subject: [PATCH 19/27] mv: show no "skipped" msg with -vi/-vin --- src/uu/mv/src/mv.rs | 10 +--------- tests/by-util/test_mv.rs | 5 +++-- 2 files changed, 4 insertions(+), 11 deletions(-) diff --git a/src/uu/mv/src/mv.rs b/src/uu/mv/src/mv.rs index 9f7a96618..43f8eb6b6 100644 --- a/src/uu/mv/src/mv.rs +++ b/src/uu/mv/src/mv.rs @@ -448,19 +448,11 @@ fn rename( match b.overwrite { OverwriteMode::NoClobber => { - let err_msg = if b.verbose { - println!("skipped {}", to.quote()); - String::new() - } else { - format!("not replacing {}", to.quote()) - }; + let err_msg = format!("not replacing {}", to.quote()); return Err(io::Error::new(io::ErrorKind::Other, err_msg)); } OverwriteMode::Interactive => { if !prompt_yes!("overwrite {}?", to.quote()) { - if b.verbose { - println!("skipped {}", to.quote()); - } return Err(io::Error::new(io::ErrorKind::Other, "")); } } diff --git a/tests/by-util/test_mv.rs b/tests/by-util/test_mv.rs index eb8a30ac4..0490d4119 100644 --- a/tests/by-util/test_mv.rs +++ b/tests/by-util/test_mv.rs @@ -1350,7 +1350,7 @@ fn test_mv_arg_interactive_skipped() { .ignore_stdin_write_error() .fails() .stderr_is("mv: overwrite 'b'? ") - .stdout_is("skipped 'b'\n"); + .no_stdout(); } #[test] @@ -1360,7 +1360,8 @@ fn test_mv_arg_interactive_skipped_vin() { at.touch("b"); ucmd.args(&["-vin", "a", "b"]) .fails() - .stdout_is("skipped 'b'\n"); + .stderr_is("mv: not replacing 'b'\n") + .no_stdout(); } #[test] From 8ee69d2b92b7cffa54e7118c989b0044d854ce60 Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Mon, 2 Oct 2023 15:29:13 +0200 Subject: [PATCH 20/27] cp: show no "skipped" msg with -vi/-vin --- src/uu/cp/src/cp.rs | 19 ++++++------------- tests/by-util/test_cp.rs | 6 ++++-- 2 files changed, 10 insertions(+), 15 deletions(-) diff --git a/src/uu/cp/src/cp.rs b/src/uu/cp/src/cp.rs index b6270719c..f9f6d8763 100644 --- a/src/uu/cp/src/cp.rs +++ b/src/uu/cp/src/cp.rs @@ -1276,23 +1276,16 @@ fn copy_source( } impl OverwriteMode { - fn verify(&self, path: &Path, verbose: bool) -> CopyResult<()> { + fn verify(&self, path: &Path) -> CopyResult<()> { match *self { Self::NoClobber => { - if verbose { - println!("skipped {}", path.quote()); - } else { - eprintln!("{}: not replacing {}", util_name(), path.quote()); - } + eprintln!("{}: not replacing {}", util_name(), path.quote()); Err(Error::NotAllFilesCopied) } Self::Interactive(_) => { if prompt_yes!("overwrite {}?", path.quote()) { Ok(()) } else { - if verbose { - println!("skipped {}", path.quote()); - } Err(Error::Skipped) } } @@ -1500,7 +1493,7 @@ fn handle_existing_dest( return Err(format!("{} and {} are the same file", source.quote(), dest.quote()).into()); } - options.overwrite.verify(dest, options.verbose)?; + 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 { @@ -1895,7 +1888,7 @@ fn copy_helper( File::create(dest).context(dest.display().to_string())?; } else if source_is_fifo && options.recursive && !options.copy_contents { #[cfg(unix)] - copy_fifo(dest, options.overwrite, options.verbose)?; + copy_fifo(dest, options.overwrite)?; } else if source_is_symlink { copy_link(source, dest, symlinked_files)?; } else { @@ -1920,9 +1913,9 @@ fn copy_helper( // "Copies" a FIFO by creating a new one. This workaround is because Rust's // built-in fs::copy does not handle FIFOs (see rust-lang/rust/issues/79390). #[cfg(unix)] -fn copy_fifo(dest: &Path, overwrite: OverwriteMode, verbose: bool) -> CopyResult<()> { +fn copy_fifo(dest: &Path, overwrite: OverwriteMode) -> CopyResult<()> { if dest.exists() { - overwrite.verify(dest, verbose)?; + overwrite.verify(dest)?; fs::remove_file(dest)?; } diff --git a/tests/by-util/test_cp.rs b/tests/by-util/test_cp.rs index 1ce74572d..70d4038bd 100644 --- a/tests/by-util/test_cp.rs +++ b/tests/by-util/test_cp.rs @@ -483,7 +483,8 @@ fn test_cp_arg_interactive_verbose() { ucmd.args(&["-vi", "a", "b"]) .pipe_in("N\n") .fails() - .stdout_is("skipped 'b'\n"); + .stderr_is("cp: overwrite 'b'? ") + .no_stdout(); } #[test] @@ -494,7 +495,8 @@ fn test_cp_arg_interactive_verbose_clobber() { at.touch("b"); ucmd.args(&["-vin", "a", "b"]) .fails() - .stdout_is("skipped 'b'\n"); + .stderr_is("cp: not replacing 'b'\n") + .no_stdout(); } #[test] From 9f6a720582f8a373cce9521cfe54610dab06f89c Mon Sep 17 00:00:00 2001 From: Yury Zhytkou <54360928+zhitkoff@users.noreply.github.com> Date: Mon, 2 Oct 2023 15:46:00 -0400 Subject: [PATCH 21/27] Introducing DEVELOPMENT.md (#5209) --- CONTRIBUTING.md | 225 +-------------------------------- DEVELOPMENT.md | 329 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 335 insertions(+), 219 deletions(-) create mode 100644 DEVELOPMENT.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 6f67eb828..695e5ad18 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -38,205 +38,15 @@ CI. However, you can use `#[cfg(...)]` attributes to create platform dependent f VirtualBox and Parallels) for development: -## Tools +## Setting up your development environment -We have an extensive CI that will check your code before it can be merged. This -section explains how to run those checks locally to avoid waiting for the CI. +To setup your local development environment for this project please follow [DEVELOPMENT.md guide](DEVELOPMENT.md) -### pre-commit hooks +It covers [installation of necessary tools and prerequisites](DEVELOPMENT.md#tools) as well as using those tools to [test your code changes locally](DEVELOPMENT.md#testing) -A configuration for `pre-commit` is provided in the repository. It allows -automatically checking every git commit you make to ensure it compiles, and -passes `clippy` and `rustfmt` without warnings. +## Improving the GNU compatibility -To use the provided hook: - -1. [Install `pre-commit`](https://pre-commit.com/#install) -1. Run `pre-commit install` while in the repository directory - -Your git commits will then automatically be checked. If a check fails, an error -message will explain why, and your commit will be canceled. You can then make -the suggested changes, and run `git commit ...` again. - -### clippy - -```shell -cargo clippy --all-targets --all-features -``` - -The `msrv` key in the clippy configuration file `clippy.toml` is used to disable -lints pertaining to newer features by specifying the minimum supported Rust -version (MSRV). - -### rustfmt - -```shell -cargo fmt --all -``` - -### cargo-deny - -This project uses [cargo-deny](https://github.com/EmbarkStudios/cargo-deny/) to -detect duplicate dependencies, checks licenses, etc. To run it locally, first -install it and then run with: - -``` -cargo deny --all-features check all -``` - -### Markdown linter - -We use [markdownlint](https://github.com/DavidAnson/markdownlint) to lint the -Markdown files in the repository. - -### Spell checker - -We use `cspell` as spell checker for all files in the project. If you are using -VS Code, you can install the -[code spell checker](https://marketplace.visualstudio.com/items?itemName=streetsidesoftware.code-spell-checker) -extension to enable spell checking within your editor. Otherwise, you can -install [cspell](https://cspell.org/) separately. - -If you want to make the spell checker ignore a word, you can add - -```rust -// spell-checker:ignore word_to_ignore -``` - -at the top of the file. - -## Testing - -Testing can be done using either Cargo or `make`. - -### Testing with Cargo - -Just like with building, we follow the standard procedure for testing using -Cargo: - -```shell -cargo test -``` - -By default, `cargo test` only runs the common programs. To run also platform -specific tests, run: - -```shell -cargo test --features unix -``` - -If you would prefer to test a select few utilities: - -```shell -cargo test --features "chmod mv tail" --no-default-features -``` - -If you also want to test the core utilities: - -```shell -cargo test -p uucore -p coreutils -``` - -Or to test the pure Rust tests in the utility itself: - -```shell -cargo test -p uu_ls --lib -``` - -Running the complete test suite might take a while. We use [nextest](https://nexte.st/index.html) in -the CI and you might want to try it out locally. It can speed up the execution time of the whole -test run significantly if the cpu has multiple cores. - -```shell -cargo nextest run --features unix --no-fail-fast -``` - -To debug: - -```shell -gdb --args target/debug/coreutils ls -(gdb) b ls.rs:79 -(gdb) run -``` - -### Testing with GNU Make - -To simply test all available utilities: - -```shell -make test -``` - -To test all but a few of the available utilities: - -```shell -make SKIP_UTILS='UTILITY_1 UTILITY_2' test -``` - -To test only a few of the available utilities: - -```shell -make UTILS='UTILITY_1 UTILITY_2' test -``` - -To include tests for unimplemented behavior: - -```shell -make UTILS='UTILITY_1 UTILITY_2' SPEC=y test -``` - -To run tests with `nextest` just use the nextest target. Note you'll need to -[install](https://nexte.st/book/installation.html) `nextest` first. The `nextest` target accepts the -same arguments like the default `test` target, so it's possible to pass arguments to `nextest run` -via `CARGOFLAGS`: - -```shell -make CARGOFLAGS='--no-fail-fast' UTILS='UTILITY_1 UTILITY_2' nextest -``` - -### Run Busybox Tests - -This testing functionality is only available on *nix operating systems and -requires `make`. - -To run busybox tests for all utilities for which busybox has tests - -```shell -make busytest -``` - -To run busybox tests for a few of the available utilities - -```shell -make UTILS='UTILITY_1 UTILITY_2' busytest -``` - -To pass an argument like "-v" to the busybox test runtime - -```shell -make UTILS='UTILITY_1 UTILITY_2' RUNTEST_ARGS='-v' busytest -``` - -### Comparing with GNU - -To run uutils against the GNU test suite locally, run the following commands: - -```shell -bash util/build-gnu.sh -# Build uutils without release optimizations -UU_MAKE_PROFILE=debug bash util/build-gnu.sh -bash util/run-gnu-test.sh -# To run a single test: -bash util/run-gnu-test.sh tests/touch/not-owner.sh # for example -# To run several tests: -bash util/run-gnu-test.sh tests/touch/not-owner.sh tests/rm/no-give-up.sh # for example -# If this is a perl (.pl) test, to run in debug: -DEBUG=1 bash util/run-gnu-test.sh tests/misc/sm3sum.pl -``` - -Note that it relies on individual utilities (not the multicall binary). - -### Improving the GNU compatibility +Please make sure you have installed [GNU utils and prerequisites](DEVELOPMENT.md#gnu-utils-and-prerequisites) and can execute commands described in [Comparing with GNU](DEVELOPMENT.md#comparing-with-gnu) section of [DEVELOPMENT.md](DEVELOPMENT.md) The Python script `./util/remaining-gnu-error.py` shows the list of failing tests in the CI. @@ -326,30 +136,7 @@ gitignore: add temporary files ## Code coverage - - -Code coverage report can be generated using [grcov](https://github.com/mozilla/grcov). - -### Using Nightly Rust - -To generate [gcov-based](https://github.com/mozilla/grcov#example-how-to-generate-gcda-files-for-a-rust-project) coverage report - -```shell -export CARGO_INCREMENTAL=0 -export RUSTFLAGS="-Zprofile -Ccodegen-units=1 -Copt-level=0 -Clink-dead-code -Coverflow-checks=off -Zpanic_abort_tests -Cpanic=abort" -export RUSTDOCFLAGS="-Cpanic=abort" -cargo build # e.g., --features feat_os_unix -cargo test # e.g., --features feat_os_unix test_pathchk -grcov . -s . --binary-path ./target/debug/ -t html --branch --ignore-not-existing --ignore build.rs --excl-br-line "^\s*((debug_)?assert(_eq|_ne)?\#\[derive\()" -o ./target/debug/coverage/ -# open target/debug/coverage/index.html in browser -``` - -if changes are not reflected in the report then run `cargo clean` and run the above commands. - -### Using Stable Rust - -If you are using stable version of Rust that doesn't enable code coverage instrumentation by default -then add `-Z-Zinstrument-coverage` flag to `RUSTFLAGS` env variable specified above. +To generate code coverage report locally please follow [Code coverage report](DEVELOPMENT.md#code-coverage-report) section of [DEVELOPMENT.md](DEVELOPMENT.md) ## Other implementations diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md new file mode 100644 index 000000000..24a1bdeb5 --- /dev/null +++ b/DEVELOPMENT.md @@ -0,0 +1,329 @@ + + +# Setting up your local development environment + +For contributing rules and best practices please refer to [CONTRIBUTING.md](CONTRIBUTING.md) + +## Before you start + +For this guide we assume that you already have GitHub account and have `git` and your favorite code editor or IDE installed and configured. +Before you start working on coreutils, please follow these steps: + +1. Fork the [coreutils repository](https://github.com/uutils/coreutils) to your GitHub account. +***Tip:*** See [this GitHub guide](https://docs.github.com/en/get-started/quickstart/fork-a-repo) for more information on this step. +2. Clone that fork to your local development environment: + +```shell +git clone https://github.com/YOUR-GITHUB-ACCOUNT/coreutils +cd coreutils +``` + +## Tools + +You will need the tools mentioned in this section to build and test your code changes locally. +This section will explain how to install and configure these tools. +We also have an extensive CI that uses these tools and will check your code before it can be merged. +The next section [Testing](#testing) will explain how to run those checks locally to avoid waiting for the CI. + +### Rust toolchain + +[Install Rust](https://www.rust-lang.org/tools/install) + +If you're using rustup to install and manage your Rust toolchains, `clippy` and `rustfmt` are usually already installed. If you are using one of the alternative methods, please make sure to install them manually. See following sub-sections for their usage: [clippy](#clippy) [rustfmt](#rustfmt). + +***Tip*** You might also need to add 'llvm-tools' component if you are going to [generate code coverage reports locally](#code-coverage-report): + +```shell +rustup component add llvm-tools-preview +``` + +### GNU utils and prerequisites + +If you are developing on Linux, most likely you already have all/most GNU utilities and prerequisites installed. + +To make sure, please check GNU coreutils [README-prereq](https://github.com/coreutils/coreutils/blob/master/README-prereq). + +You will need these to [run uutils against the GNU test suite locally](#comparing-with-gnu). + +For MacOS and Windows platform specific setup please check [MacOS GNU utils](#macos-gnu-utils) and [Windows GNU utils](#windows-gnu-utils) sections respectfully. + +### pre-commit hooks + +A configuration for `pre-commit` is provided in the repository. It allows +automatically checking every git commit you make to ensure it compiles, and +passes `clippy` and `rustfmt` without warnings. + +To use the provided hook: + +1. [Install `pre-commit`](https://pre-commit.com/#install) +1. Run `pre-commit install` while in the repository directory + +Your git commits will then automatically be checked. If a check fails, an error +message will explain why, and your commit will be canceled. You can then make +the suggested changes, and run `git commit ...` again. + +**NOTE: On MacOS** the pre-commit hooks are currently broken. There are workarounds involving switching to unstable nightly Rust and components. + +### clippy + +```shell +cargo clippy --all-targets --all-features +``` + +The `msrv` key in the clippy configuration file `clippy.toml` is used to disable +lints pertaining to newer features by specifying the minimum supported Rust +version (MSRV). + +### rustfmt + +```shell +cargo fmt --all +``` + +### cargo-deny + +This project uses [cargo-deny](https://github.com/EmbarkStudios/cargo-deny/) to +detect duplicate dependencies, checks licenses, etc. To run it locally, first +install it and then run with: + +```shell +cargo deny --all-features check all +``` + +### Markdown linter + +We use [markdownlint](https://github.com/DavidAnson/markdownlint) to lint the +Markdown files in the repository. + +### Spell checker + +We use `cspell` as spell checker for all files in the project. If you are using +VS Code, you can install the +[code spell checker](https://marketplace.visualstudio.com/items?itemName=streetsidesoftware.code-spell-checker) +extension to enable spell checking within your editor. Otherwise, you can +install [cspell](https://cspell.org/) separately. + +If you want to make the spell checker ignore a word, you can add + +```rust +// spell-checker:ignore word_to_ignore +``` + +at the top of the file. + +## Testing + +This section explains how to run our CI checks locally. +Testing can be done using either Cargo or `make`. + +### Testing with Cargo + +Just like with building, we follow the standard procedure for testing using +Cargo: + +```shell +cargo test +``` + +By default, `cargo test` only runs the common programs. To run also platform +specific tests, run: + +```shell +cargo test --features unix +``` + +If you would prefer to test a select few utilities: + +```shell +cargo test --features "chmod mv tail" --no-default-features +``` + +If you also want to test the core utilities: + +```shell +cargo test -p uucore -p coreutils +``` + +Running the complete test suite might take a while. We use [nextest](https://nexte.st/index.html) in +the CI and you might want to try it out locally. It can speed up the execution time of the whole +test run significantly if the cpu has multiple cores. + +```shell +cargo nextest run --features unix --no-fail-fast +``` + +To debug: + +```shell +gdb --args target/debug/coreutils ls +(gdb) b ls.rs:79 +(gdb) run +``` + +### Testing with GNU Make + +To simply test all available utilities: + +```shell +make test +``` + +To test all but a few of the available utilities: + +```shell +make SKIP_UTILS='UTILITY_1 UTILITY_2' test +``` + +To test only a few of the available utilities: + +```shell +make UTILS='UTILITY_1 UTILITY_2' test +``` + +To include tests for unimplemented behavior: + +```shell +make UTILS='UTILITY_1 UTILITY_2' SPEC=y test +``` + +To run tests with `nextest` just use the nextest target. Note you'll need to +[install](https://nexte.st/book/installation.html) `nextest` first. The `nextest` target accepts the +same arguments like the default `test` target, so it's possible to pass arguments to `nextest run` +via `CARGOFLAGS`: + +```shell +make CARGOFLAGS='--no-fail-fast' UTILS='UTILITY_1 UTILITY_2' nextest +``` + +### Run Busybox Tests + +This testing functionality is only available on *nix operating systems and +requires `make`. + +To run busybox tests for all utilities for which busybox has tests + +```shell +make busytest +``` + +To run busybox tests for a few of the available utilities + +```shell +make UTILS='UTILITY_1 UTILITY_2' busytest +``` + +To pass an argument like "-v" to the busybox test runtime + +```shell +make UTILS='UTILITY_1 UTILITY_2' RUNTEST_ARGS='-v' busytest +``` + +### Comparing with GNU + +To run uutils against the GNU test suite locally, run the following commands: + +```shell +bash util/build-gnu.sh +# Build uutils without release optimizations +UU_MAKE_PROFILE=debug bash util/build-gnu.sh +bash util/run-gnu-test.sh +# To run a single test: +bash util/run-gnu-test.sh tests/touch/not-owner.sh # for example +# To run several tests: +bash util/run-gnu-test.sh tests/touch/not-owner.sh tests/rm/no-give-up.sh # for example +# If this is a perl (.pl) test, to run in debug: +DEBUG=1 bash util/run-gnu-test.sh tests/misc/sm3sum.pl +``` + +***Tip:*** First time you run `bash util/build-gnu.sh` command, it will provide instructions on how to checkout GNU coreutils repository at the correct release tag. Please follow those instructions and when done, run `bash util/build-gnu.sh` command again. + +Note that GNU test suite relies on individual utilities (not the multicall binary). + +## Code coverage report + +Code coverage report can be generated using [grcov](https://github.com/mozilla/grcov). + +### Using Nightly Rust + +To generate [gcov-based](https://github.com/mozilla/grcov#example-how-to-generate-gcda-files-for-a-rust-project) coverage report + +```shell +export CARGO_INCREMENTAL=0 +export RUSTFLAGS="-Zprofile -Ccodegen-units=1 -Copt-level=0 -Clink-dead-code -Coverflow-checks=off -Zpanic_abort_tests -Cpanic=abort" +export RUSTDOCFLAGS="-Cpanic=abort" +cargo build # e.g., --features feat_os_unix +cargo test # e.g., --features feat_os_unix test_pathchk +grcov . -s . --binary-path ./target/debug/ -t html --branch --ignore-not-existing --ignore build.rs --excl-br-line "^\s*((debug_)?assert(_eq|_ne)?\#\[derive\()" -o ./target/debug/coverage/ +# open target/debug/coverage/index.html in browser +``` + +if changes are not reflected in the report then run `cargo clean` and run the above commands. + +### Using Stable Rust + +If you are using stable version of Rust that doesn't enable code coverage instrumentation by default +then add `-Z-Zinstrument-coverage` flag to `RUSTFLAGS` env variable specified above. + +## Tips for setting up on Mac + +### C Compiler and linker + +On MacOS you'll need to install C compiler & linker: + +```shell +xcode-select --install +``` + +### MacOS GNU utils + +On MacOS you will need to install [Homebrew](https://docs.brew.sh/Installation) and use it to install the following Homebrew formulas: + +```shell +brew install \ + coreutils \ + autoconf \ + gettext \ + wget \ + texinfo \ + xz \ + automake \ + gnu-sed \ + m4 \ + bison \ + pre-commit \ + findutils +``` + +After installing these Homebrew formulas, please make sure to add the following lines to your `zsh` or `bash` rc file, i.e. `~/.profile` or `~/.zshrc` or `~/.bashrc` ... +(assuming Homebrew is installed at default location `/opt/homebrew`): + +```shell +eval "$(/opt/homebrew/bin/brew shellenv)" +export PATH="/opt/homebrew/opt/coreutils/libexec/gnubin:$PATH" +export PATH="/opt/homebrew/opt/bison/bin:$PATH" +export PATH="/opt/homebrew/opt/findutils/libexec/gnubin:$PATH" +``` + +Last step is to link Homebrew coreutils version of `timeout` to `/usr/local/bin` (as admin user): + +```shell +sudo ln -s /opt/homebrew/bin/timeout /usr/local/bin/timeout +``` + +Do not forget to either source updated rc file or restart you terminal session to update environment variables. + +## Tips for setting up on Windows + +### MSVC build tools + +On Windows you'll need the MSVC build tools for Visual Studio 2013 or later. + +If you are using `rustup-init.exe` to install Rust toolchain, it will guide you through the process of downloading and installing these prerequisites. + +Otherwise please follow [this guide](https://learn.microsoft.com/en-us/windows/dev-environment/rust/setup). + +### Windows GNU utils + +If you have used [Git for Windows](https://gitforwindows.org) to install `git` on you Windows system you might already have some GNU core utilities installed as part of "GNU Bash" included in Git for Windows package, but it is not a complete package. [This article](https://gist.github.com/evanwill/0207876c3243bbb6863e65ec5dc3f058) provides instruction on how to add more to it. + +Alternatively you can install [Cygwin](https://www.cygwin.com) and/or use [WSL2](https://learn.microsoft.com/en-us/windows/wsl/compare-versions#whats-new-in-wsl-2) to get access to all GNU core utilities on Windows. From c5a0aa92f8b31741e2be6128fbb9c5f29d22ec69 Mon Sep 17 00:00:00 2001 From: Yury Zhytkou <54360928+zhitkoff@users.noreply.github.com> Date: Mon, 2 Oct 2023 18:42:46 -0400 Subject: [PATCH 22/27] split: implementing separator option (#5331) * split: implementing separator option * split: separator option - handle multiple update * split: style * split: separator tests * split: separator tests - stdin in ci/cd * split: tests - ci/cd stdin errors * split: refactor based on feedback * split: improve test coverage * split: fix broken pipe error in tests with stdin * split: fix for handle_multiple_separator_options * split: comments * split: refactor separator code * split: changes based on feedback * split: changes based on feedback --- src/uu/split/split.md | 13 + src/uu/split/src/split.rs | 133 ++++++++--- tests/by-util/test_split.rs | 239 +++++++++++++++++++ tests/fixtures/split/separator_nul.txt | Bin 0 -> 10 bytes tests/fixtures/split/separator_semicolon.txt | 1 + 5 files changed, 348 insertions(+), 38 deletions(-) create mode 100644 tests/fixtures/split/separator_nul.txt create mode 100644 tests/fixtures/split/separator_semicolon.txt diff --git a/src/uu/split/split.md b/src/uu/split/split.md index d3a481fd3..836e3a0c6 100644 --- a/src/uu/split/split.md +++ b/src/uu/split/split.md @@ -11,3 +11,16 @@ Create output files containing consecutive or interleaved sections of input ## After Help Output fixed-size pieces of INPUT to PREFIXaa, PREFIXab, ...; default size is 1000, and default PREFIX is 'x'. With no INPUT, or when INPUT is -, read standard input. + +The SIZE argument is an integer and optional unit (example: 10K is 10*1024). +Units are K,M,G,T,P,E,Z,Y,R,Q (powers of 1024) or KB,MB,... (powers of 1000). +Binary prefixes can be used, too: KiB=K, MiB=M, and so on. + +CHUNKS may be: + +- N split into N files based on size of input +- K/N output Kth of N to stdout +- l/N split into N files without splitting lines/records +- l/K/N output Kth of N to stdout without splitting lines/records +- r/N like 'l' but use round robin distribution +- r/K/N likewise but only output Kth of N to stdout diff --git a/src/uu/split/src/split.rs b/src/uu/split/src/split.rs index a61c0e812..756248539 100644 --- a/src/uu/split/src/split.rs +++ b/src/uu/split/src/split.rs @@ -11,7 +11,7 @@ mod platform; use crate::filenames::FilenameIterator; use crate::filenames::SuffixType; -use clap::{crate_version, parser::ValueSource, Arg, ArgAction, ArgMatches, Command}; +use clap::{crate_version, parser::ValueSource, Arg, ArgAction, ArgMatches, Command, ValueHint}; use std::env; use std::ffi::OsString; use std::fmt; @@ -39,6 +39,7 @@ static OPT_HEX_SUFFIXES_SHORT: &str = "-x"; static OPT_SUFFIX_LENGTH: &str = "suffix-length"; static OPT_DEFAULT_SUFFIX_LENGTH: &str = "0"; static OPT_VERBOSE: &str = "verbose"; +static OPT_SEPARATOR: &str = "separator"; //The ---io and ---io-blksize parameters are consumed and ignored. //The parameter is included to make GNU coreutils tests pass. static OPT_IO: &str = "-io"; @@ -55,7 +56,6 @@ const AFTER_HELP: &str = help_section!("after help", "split.md"); #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { let (args, obs_lines) = handle_obsolete(args); - let matches = uu_app().try_get_matches_from(args)?; match Settings::from(&matches, &obs_lines) { @@ -145,6 +145,7 @@ fn should_extract_obs_lines( && !slice.starts_with("-C") && !slice.starts_with("-l") && !slice.starts_with("-n") + && !slice.starts_with("-t") } /// Helper function to [`filter_args`] @@ -208,13 +209,18 @@ fn handle_preceding_options( || &slice[2..] == OPT_ADDITIONAL_SUFFIX || &slice[2..] == OPT_FILTER || &slice[2..] == OPT_NUMBER - || &slice[2..] == OPT_SUFFIX_LENGTH; + || &slice[2..] == OPT_SUFFIX_LENGTH + || &slice[2..] == OPT_SEPARATOR; } // capture if current slice is a preceding short option that requires value and does not have value in the same slice (value separated by whitespace) // following slice should be treaded as value for this option // even if it starts with '-' (which would be treated as hyphen prefixed value) - *preceding_short_opt_req_value = - slice == "-b" || slice == "-C" || slice == "-l" || slice == "-n" || slice == "-a"; + *preceding_short_opt_req_value = slice == "-b" + || slice == "-C" + || slice == "-l" + || slice == "-n" + || slice == "-a" + || slice == "-t"; // slice is a value // reset preceding option flags if !slice.starts_with('-') { @@ -278,7 +284,7 @@ pub fn uu_app() -> Command { .long(OPT_FILTER) .allow_hyphen_values(true) .value_name("COMMAND") - .value_hint(clap::ValueHint::CommandName) + .value_hint(ValueHint::CommandName) .help( "write to shell COMMAND; file name is $FILE (Currently not implemented for Windows)", ), @@ -293,7 +299,7 @@ pub fn uu_app() -> Command { .arg( Arg::new(OPT_NUMERIC_SUFFIXES_SHORT) .short('d') - .action(clap::ArgAction::SetTrue) + .action(ArgAction::SetTrue) .overrides_with_all([ OPT_NUMERIC_SUFFIXES, OPT_NUMERIC_SUFFIXES_SHORT, @@ -314,12 +320,13 @@ pub fn uu_app() -> Command { OPT_HEX_SUFFIXES, OPT_HEX_SUFFIXES_SHORT ]) + .value_name("FROM") .help("same as -d, but allow setting the start value"), ) .arg( Arg::new(OPT_HEX_SUFFIXES_SHORT) .short('x') - .action(clap::ArgAction::SetTrue) + .action(ArgAction::SetTrue) .overrides_with_all([ OPT_NUMERIC_SUFFIXES, OPT_NUMERIC_SUFFIXES_SHORT, @@ -340,6 +347,7 @@ pub fn uu_app() -> Command { OPT_HEX_SUFFIXES, OPT_HEX_SUFFIXES_SHORT ]) + .value_name("FROM") .help("same as -x, but allow setting the start value"), ) .arg( @@ -357,6 +365,15 @@ pub fn uu_app() -> Command { .help("print a diagnostic just before each output file is opened") .action(ArgAction::SetTrue), ) + .arg( + Arg::new(OPT_SEPARATOR) + .short('t') + .long(OPT_SEPARATOR) + .allow_hyphen_values(true) + .value_name("SEP") + .action(ArgAction::Append) + .help("use SEP instead of newline as the record separator; '\0' (zero) specifies the NUL character"), + ) .arg( Arg::new(OPT_IO) .long("io") @@ -372,7 +389,7 @@ pub fn uu_app() -> Command { .arg( Arg::new(ARG_INPUT) .default_value("-") - .value_hint(clap::ValueHint::FilePath), + .value_hint(ValueHint::FilePath), ) .arg( Arg::new(ARG_PREFIX) @@ -696,6 +713,7 @@ struct Settings { filter: Option, strategy: Strategy, verbose: bool, + separator: u8, /// Whether to *not* produce empty files when using `-n`. /// @@ -722,6 +740,12 @@ enum SettingsError { /// Suffix is not large enough to split into specified chunks SuffixTooSmall(usize), + /// Multi-character (Invalid) separator + MultiCharacterSeparator(String), + + /// Multiple different separator characters + MultipleSeparatorCharacters, + /// The `--filter` option is not supported on Windows. #[cfg(windows)] NotSupported, @@ -743,6 +767,12 @@ impl fmt::Display for SettingsError { Self::Strategy(e) => e.fmt(f), Self::SuffixNotParsable(s) => write!(f, "invalid suffix length: {}", s.quote()), Self::SuffixTooSmall(i) => write!(f, "the suffix length needs to be at least {i}"), + Self::MultiCharacterSeparator(s) => { + write!(f, "multi-character separator {}", s.quote()) + } + Self::MultipleSeparatorCharacters => { + write!(f, "multiple separator characters specified") + } Self::SuffixContainsSeparator(s) => write!( f, "invalid suffix {}, contains directory separator", @@ -783,6 +813,26 @@ impl Settings { } } } + + // Make sure that separator is only one UTF8 character (if specified) + // defaults to '\n' - newline character + // If the same separator (the same value) was used multiple times - `split` should NOT fail + // If the separator was used multiple times but with different values (not all values are the same) - `split` should fail + let separator = match matches.get_many::(OPT_SEPARATOR) { + Some(mut sep_values) => { + let first = sep_values.next().unwrap(); // it is safe to just unwrap here since Clap should not return empty ValuesRef<'_,String> in the option from get_many() call + if !sep_values.all(|s| s == first) { + return Err(SettingsError::MultipleSeparatorCharacters); + } + match first.as_str() { + "\\0" => b'\0', + s if s.as_bytes().len() == 1 => s.as_bytes()[0], + s => return Err(SettingsError::MultiCharacterSeparator(s.to_owned())), + } + } + None => b'\n', + }; + let result = Self { suffix_length: suffix_length_str .parse() @@ -791,6 +841,7 @@ impl Settings { suffix_start, additional_suffix, verbose: matches.value_source("verbose") == Some(ValueSource::CommandLine), + separator, strategy, input: matches.get_one::(ARG_INPUT).unwrap().to_owned(), prefix: matches.get_one::(ARG_PREFIX).unwrap().to_owned(), @@ -1019,7 +1070,8 @@ impl<'a> Write for LineChunkWriter<'a> { // corresponds to the current chunk number. let mut prev = 0; let mut total_bytes_written = 0; - for i in memchr::memchr_iter(b'\n', buf) { + let sep = self.settings.separator; + for i in memchr::memchr_iter(sep, buf) { // If we have exceeded the number of lines to write in the // current chunk, then start a new chunk and its // corresponding writer. @@ -1036,8 +1088,8 @@ impl<'a> Write for LineChunkWriter<'a> { } // Write the line, starting from *after* the previous - // newline character and ending *after* the current - // newline character. + // separator character and ending *after* the current + // separator character. let n = self.inner.write(&buf[prev..i + 1])?; total_bytes_written += n; prev = i + 1; @@ -1175,21 +1227,22 @@ impl<'a> Write for LineBytesChunkWriter<'a> { self.num_bytes_remaining_in_current_chunk = self.chunk_size.try_into().unwrap(); } - // Find the first newline character in the buffer. - match memchr::memchr(b'\n', buf) { - // If there is no newline character and the buffer is + // Find the first separator (default - newline character) in the buffer. + let sep = self.settings.separator; + match memchr::memchr(sep, buf) { + // If there is no separator character and the buffer is // not empty, then write as many bytes as we can and // then move on to the next chunk if necessary. None => { let end = self.num_bytes_remaining_in_current_chunk; // This is ugly but here to match GNU behavior. If the input - // doesn't end with a \n, pretend that it does for handling + // doesn't end with a separator, pretend that it does for handling // the second to last segment chunk. See `line-bytes.sh`. if end == buf.len() && self.num_bytes_remaining_in_current_chunk < self.chunk_size.try_into().unwrap() - && buf[buf.len() - 1] != b'\n' + && buf[buf.len() - 1] != sep { self.num_bytes_remaining_in_current_chunk = 0; } else { @@ -1200,8 +1253,8 @@ impl<'a> Write for LineBytesChunkWriter<'a> { } } - // If there is a newline character and the line - // (including the newline character) will fit in the + // If there is a separator character and the line + // (including the separator character) will fit in the // current chunk, then write the entire line and // continue to the next iteration. (See chunk 1 in the // example comment above.) @@ -1212,8 +1265,8 @@ impl<'a> Write for LineBytesChunkWriter<'a> { buf = &buf[num_bytes_written..]; } - // If there is a newline character, the line - // (including the newline character) will not fit in + // If there is a separator character, the line + // (including the separator character) will not fit in // the current chunk, *and* no other lines have been // written to the current chunk, then write as many // bytes as we can and continue to the next @@ -1230,8 +1283,8 @@ impl<'a> Write for LineBytesChunkWriter<'a> { buf = &buf[num_bytes_written..]; } - // If there is a newline character, the line - // (including the newline character) will not fit in + // If there is a separator character, the line + // (including the separator character) will not fit in // the current chunk, and at least one other line has // been written to the current chunk, then signal to // the next iteration that a new chunk needs to be @@ -1489,15 +1542,16 @@ where let mut num_bytes_remaining_in_current_chunk = chunk_size; let mut i = 0; - for line_result in reader.lines() { + let sep = settings.separator; + for line_result in reader.split(sep) { let line = line_result.unwrap(); let maybe_writer = writers.get_mut(i); let writer = maybe_writer.unwrap(); - let bytes = line.as_bytes(); + let bytes = line.as_slice(); writer.write_all(bytes)?; - writer.write_all(b"\n")?; + writer.write_all(&[sep])?; - // Add one byte for the newline character. + // Add one byte for the separator character. let num_bytes = bytes.len() + 1; if num_bytes > num_bytes_remaining_in_current_chunk { num_bytes_remaining_in_current_chunk = chunk_size; @@ -1546,15 +1600,16 @@ where let mut num_bytes_remaining_in_current_chunk = chunk_size; let mut i = 0; - for line_result in reader.lines() { + let sep = settings.separator; + for line_result in reader.split(sep) { let line = line_result?; - let bytes = line.as_bytes(); + let bytes = line.as_slice(); if i == chunk_number { writer.write_all(bytes)?; - writer.write_all(b"\n")?; + writer.write_all(&[sep])?; } - // Add one byte for the newline character. + // Add one byte for the separator character. let num_bytes = bytes.len() + 1; if num_bytes >= num_bytes_remaining_in_current_chunk { num_bytes_remaining_in_current_chunk = chunk_size; @@ -1601,13 +1656,14 @@ where } let num_chunks: usize = num_chunks.try_into().unwrap(); - for (i, line_result) in reader.lines().enumerate() { + let sep = settings.separator; + for (i, line_result) in reader.split(sep).enumerate() { let line = line_result.unwrap(); let maybe_writer = writers.get_mut(i % num_chunks); let writer = maybe_writer.unwrap(); - let bytes = line.as_bytes(); + let bytes = line.as_slice(); writer.write_all(bytes)?; - writer.write_all(b"\n")?; + writer.write_all(&[sep])?; } Ok(()) @@ -1632,7 +1688,7 @@ where /// * [`split_into_n_chunks_by_line_round_robin`], which splits its input in the /// same way, but writes each chunk to its own file. fn kth_chunk_by_line_round_robin( - _settings: &Settings, + settings: &Settings, reader: &mut R, chunk_number: u64, num_chunks: u64, @@ -1646,12 +1702,13 @@ where let num_chunks: usize = num_chunks.try_into().unwrap(); let chunk_number: usize = chunk_number.try_into().unwrap(); - for (i, line_result) in reader.lines().enumerate() { + let sep = settings.separator; + for (i, line_result) in reader.split(sep).enumerate() { let line = line_result?; - let bytes = line.as_bytes(); + let bytes = line.as_slice(); if (i % num_chunks) == chunk_number { writer.write_all(bytes)?; - writer.write_all(b"\n")?; + writer.write_all(&[sep])?; } } Ok(()) diff --git a/tests/by-util/test_split.rs b/tests/by-util/test_split.rs index 7c6d271e6..6fc3a3706 100644 --- a/tests/by-util/test_split.rs +++ b/tests/by-util/test_split.rs @@ -1483,3 +1483,242 @@ fn test_split_non_utf8_argument_windows() { .fails() .stderr_contains("error: invalid UTF-8 was detected in one or more arguments"); } + +// Test '--separator' / '-t' option following GNU tests example +// test separators: '\n' , '\0' , ';' +// test with '--lines=2' , '--line-bytes=4' , '--number=l/3' , '--number=r/3' , '--number=l/1/3' , '--number=r/1/3' +#[test] +fn test_split_separator_nl_lines() { + let (at, mut ucmd) = at_and_ucmd!(); + ucmd.args(&["--lines=2", "-t", "\n"]) + .pipe_in("1\n2\n3\n4\n5\n") + .succeeds(); + + assert_eq!(file_read(&at, "xaa"), "1\n2\n"); + assert_eq!(file_read(&at, "xab"), "3\n4\n"); + assert_eq!(file_read(&at, "xac"), "5\n"); + assert!(!at.plus("xad").exists()); +} + +#[test] +fn test_split_separator_nl_line_bytes() { + let (at, mut ucmd) = at_and_ucmd!(); + ucmd.args(&["--line-bytes=4", "-t", "\n"]) + .pipe_in("1\n2\n3\n4\n5\n") + .succeeds(); + + assert_eq!(file_read(&at, "xaa"), "1\n2\n"); + assert_eq!(file_read(&at, "xab"), "3\n4\n"); + assert_eq!(file_read(&at, "xac"), "5\n"); + assert!(!at.plus("xad").exists()); +} + +#[test] +fn test_split_separator_nl_number_l() { + let (at, mut ucmd) = at_and_ucmd!(); + ucmd.args(&["--number=l/3", "--separator=\n", "fivelines.txt"]) + .succeeds(); + + assert_eq!(file_read(&at, "xaa"), "1\n2\n"); + assert_eq!(file_read(&at, "xab"), "3\n4\n"); + assert_eq!(file_read(&at, "xac"), "5\n"); + assert!(!at.plus("xad").exists()); +} + +#[test] +fn test_split_separator_nl_number_r() { + let (at, mut ucmd) = at_and_ucmd!(); + ucmd.args(&["--number=r/3", "--separator", "\n", "fivelines.txt"]) + .succeeds(); + + assert_eq!(file_read(&at, "xaa"), "1\n4\n"); + assert_eq!(file_read(&at, "xab"), "2\n5\n"); + assert_eq!(file_read(&at, "xac"), "3\n"); + assert!(!at.plus("xad").exists()); +} + +#[test] +fn test_split_separator_nul_lines() { + let (at, mut ucmd) = at_and_ucmd!(); + ucmd.args(&["--lines=2", "-t", "\\0", "separator_nul.txt"]) + .succeeds(); + + assert_eq!(file_read(&at, "xaa"), "1\02\0"); + assert_eq!(file_read(&at, "xab"), "3\04\0"); + assert_eq!(file_read(&at, "xac"), "5\0"); + assert!(!at.plus("xad").exists()); +} + +#[test] +fn test_split_separator_nul_line_bytes() { + let (at, mut ucmd) = at_and_ucmd!(); + ucmd.args(&["--line-bytes=4", "-t", "\\0", "separator_nul.txt"]) + .succeeds(); + + assert_eq!(file_read(&at, "xaa"), "1\02\0"); + assert_eq!(file_read(&at, "xab"), "3\04\0"); + assert_eq!(file_read(&at, "xac"), "5\0"); + assert!(!at.plus("xad").exists()); +} + +#[test] +fn test_split_separator_nul_number_l() { + let (at, mut ucmd) = at_and_ucmd!(); + ucmd.args(&["--number=l/3", "--separator=\\0", "separator_nul.txt"]) + .succeeds(); + + assert_eq!(file_read(&at, "xaa"), "1\02\0"); + assert_eq!(file_read(&at, "xab"), "3\04\0"); + assert_eq!(file_read(&at, "xac"), "5\0"); + assert!(!at.plus("xad").exists()); +} + +#[test] +fn test_split_separator_nul_number_r() { + let (at, mut ucmd) = at_and_ucmd!(); + ucmd.args(&["--number=r/3", "--separator=\\0", "separator_nul.txt"]) + .succeeds(); + + assert_eq!(file_read(&at, "xaa"), "1\04\0"); + assert_eq!(file_read(&at, "xab"), "2\05\0"); + assert_eq!(file_read(&at, "xac"), "3\0"); + assert!(!at.plus("xad").exists()); +} + +#[test] +fn test_split_separator_semicolon_lines() { + let (at, mut ucmd) = at_and_ucmd!(); + ucmd.args(&["--lines=2", "-t", ";", "separator_semicolon.txt"]) + .succeeds(); + + assert_eq!(file_read(&at, "xaa"), "1;2;"); + assert_eq!(file_read(&at, "xab"), "3;4;"); + assert_eq!(file_read(&at, "xac"), "5;"); + assert!(!at.plus("xad").exists()); +} + +#[test] +fn test_split_separator_semicolon_line_bytes() { + let (at, mut ucmd) = at_and_ucmd!(); + ucmd.args(&["--line-bytes=4", "-t", ";", "separator_semicolon.txt"]) + .succeeds(); + + assert_eq!(file_read(&at, "xaa"), "1;2;"); + assert_eq!(file_read(&at, "xab"), "3;4;"); + assert_eq!(file_read(&at, "xac"), "5;"); + assert!(!at.plus("xad").exists()); +} + +#[test] +fn test_split_separator_semicolon_number_l() { + let (at, mut ucmd) = at_and_ucmd!(); + ucmd.args(&["--number=l/3", "--separator=;", "separator_semicolon.txt"]) + .succeeds(); + + assert_eq!(file_read(&at, "xaa"), "1;2;"); + assert_eq!(file_read(&at, "xab"), "3;4;"); + assert_eq!(file_read(&at, "xac"), "5;"); + assert!(!at.plus("xad").exists()); +} + +#[test] +fn test_split_separator_semicolon_number_r() { + let (at, mut ucmd) = at_and_ucmd!(); + ucmd.args(&["--number=r/3", "--separator=;", "separator_semicolon.txt"]) + .succeeds(); + + assert_eq!(file_read(&at, "xaa"), "1;4;"); + assert_eq!(file_read(&at, "xab"), "2;5;"); + assert_eq!(file_read(&at, "xac"), "3;"); + assert!(!at.plus("xad").exists()); +} + +#[test] +fn test_split_separator_semicolon_number_kth_l() { + new_ucmd!() + .args(&[ + "--number=l/1/3", + "--separator", + ";", + "separator_semicolon.txt", + ]) + .succeeds() + .stdout_only("1;2;"); +} + +#[test] +fn test_split_separator_semicolon_number_kth_r() { + new_ucmd!() + .args(&[ + "--number=r/1/3", + "--separator", + ";", + "separator_semicolon.txt", + ]) + .succeeds() + .stdout_only("1;4;"); +} + +// Test error edge cases for separator option +#[test] +fn test_split_separator_no_value() { + new_ucmd!() + .args(&["-t"]) + .ignore_stdin_write_error() + .pipe_in("a\n") + .fails() + .stderr_contains( + "error: a value is required for '--separator ' but none was supplied", + ); +} + +#[test] +fn test_split_separator_invalid_usage() { + let scene = TestScenario::new(util_name!()); + scene + .ucmd() + .args(&["--separator=xx"]) + .ignore_stdin_write_error() + .pipe_in("a\n") + .fails() + .no_stdout() + .stderr_contains("split: multi-character separator 'xx'"); + scene + .ucmd() + .args(&["-ta", "-tb"]) + .ignore_stdin_write_error() + .pipe_in("a\n") + .fails() + .no_stdout() + .stderr_contains("split: multiple separator characters specified"); + scene + .ucmd() + .args(&["-t'\n'", "-tb"]) + .ignore_stdin_write_error() + .pipe_in("a\n") + .fails() + .no_stdout() + .stderr_contains("split: multiple separator characters specified"); +} + +// Test using same separator multiple times +#[test] +fn test_split_separator_same_multiple() { + let scene = TestScenario::new(util_name!()); + scene + .ucmd() + .args(&["--separator=:", "--separator=:", "fivelines.txt"]) + .succeeds(); + scene + .ucmd() + .args(&["-t:", "--separator=:", "fivelines.txt"]) + .succeeds(); + scene + .ucmd() + .args(&["-t", ":", "-t", ":", "fivelines.txt"]) + .succeeds(); + scene + .ucmd() + .args(&["-t:", "-t:", "-t,", "fivelines.txt"]) + .fails(); +} diff --git a/tests/fixtures/split/separator_nul.txt b/tests/fixtures/split/separator_nul.txt new file mode 100644 index 0000000000000000000000000000000000000000..c4c49609dbd79d27ec1457c489741e63995dc080 GIT binary patch literal 10 RcmXqHFk&!fFkvud000H>0RR91 literal 0 HcmV?d00001 diff --git a/tests/fixtures/split/separator_semicolon.txt b/tests/fixtures/split/separator_semicolon.txt new file mode 100644 index 000000000..a8396d8ee --- /dev/null +++ b/tests/fixtures/split/separator_semicolon.txt @@ -0,0 +1 @@ +1;2;3;4;5; \ No newline at end of file From 252d01ac33b2eb4fdd0640a07840d0ef3318f886 Mon Sep 17 00:00:00 2001 From: PGIII Date: Mon, 2 Oct 2023 18:48:22 -0400 Subject: [PATCH 23/27] cp: fail when trying to copy to read only file on mac (#5261) * cp: fail when trying to copy to read only file * fix spelling error in macos.rs * simplify permissions check * add comment * fix typo --- src/uu/cp/src/platform/macos.rs | 11 +++++++++-- tests/by-util/test_cp.rs | 16 ++++++++++++++++ 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/src/uu/cp/src/platform/macos.rs b/src/uu/cp/src/platform/macos.rs index 8c62c78d9..77bdbbbdb 100644 --- a/src/uu/cp/src/platform/macos.rs +++ b/src/uu/cp/src/platform/macos.rs @@ -63,8 +63,15 @@ pub(crate) fn copy_on_write( { // clonefile(2) fails if the destination exists. Remove it and try again. Do not // bother to check if removal worked because we're going to try to clone again. - let _ = fs::remove_file(dest); - error = pfn(src.as_ptr(), dst.as_ptr(), 0); + // first lets make sure the dest file is not read only + if fs::metadata(dest).map_or(false, |md| !md.permissions().readonly()) { + // remove and copy again + // TODO: rewrite this to better match linux behavior + // linux first opens the source file and destination file then uses the file + // descriptors to do the clone. + let _ = fs::remove_file(dest); + error = pfn(src.as_ptr(), dst.as_ptr(), 0); + } } } } diff --git a/tests/by-util/test_cp.rs b/tests/by-util/test_cp.rs index 1ce74572d..fe56d1160 100644 --- a/tests/by-util/test_cp.rs +++ b/tests/by-util/test_cp.rs @@ -3440,3 +3440,19 @@ fn test_cp_only_source_no_target() { panic!("Failure: stderr was \n{stderr_str}"); } } + +#[test] +fn test_cp_dest_no_permissions() { + let ts = TestScenario::new(util_name!()); + let at = &ts.fixtures; + + at.touch("valid.txt"); + at.touch("invalid_perms.txt"); + at.set_readonly("invalid_perms.txt"); + + ts.ucmd() + .args(&["valid.txt", "invalid_perms.txt"]) + .fails() + .stderr_contains("invalid_perms.txt") + .stderr_contains("denied"); +} From a107374c8286796b170fbabbd65a6a28656a7415 Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Tue, 3 Oct 2023 12:10:20 +0200 Subject: [PATCH 24/27] echo: use controlflow instead of bool --- src/uu/echo/src/echo.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/uu/echo/src/echo.rs b/src/uu/echo/src/echo.rs index 5ccc6a32a..947887210 100644 --- a/src/uu/echo/src/echo.rs +++ b/src/uu/echo/src/echo.rs @@ -6,6 +6,7 @@ use clap::{crate_version, Arg, ArgAction, Command}; use std::io::{self, Write}; use std::iter::Peekable; +use std::ops::ControlFlow; use std::str::Chars; use uucore::error::{FromIo, UResult}; use uucore::{format_usage, help_about, help_section, help_usage}; @@ -62,7 +63,7 @@ fn parse_code(input: &mut Peekable, base: Base) -> Option { Some(ret.into()) } -fn print_escaped(input: &str, mut output: impl Write) -> io::Result { +fn print_escaped(input: &str, mut output: impl Write) -> io::Result> { let mut iter = input.chars().peekable(); while let Some(c) = iter.next() { if c != '\\' { @@ -85,7 +86,7 @@ fn print_escaped(input: &str, mut output: impl Write) -> io::Result { '\\' => '\\', 'a' => '\x07', 'b' => '\x08', - 'c' => return Ok(true), + 'c' => return Ok(ControlFlow::Break(())), 'e' => '\x1b', 'f' => '\x0c', 'n' => '\n', @@ -112,7 +113,7 @@ fn print_escaped(input: &str, mut output: impl Write) -> io::Result { } } - Ok(false) + Ok(ControlFlow::Continue(())) } #[uucore::main] @@ -173,8 +174,7 @@ fn execute(no_newline: bool, escaped: bool, free: &[String]) -> io::Result<()> { write!(output, " ")?; } if escaped { - let should_stop = print_escaped(input, &mut output)?; - if should_stop { + if print_escaped(input, &mut output)?.is_break() { break; } } else { From c29bcb219f6d10028ba5b9137730f45c158f93b1 Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Tue, 3 Oct 2023 14:37:15 +0200 Subject: [PATCH 25/27] echo: don't output "\n" if "\c" is encountered --- src/uu/echo/src/echo.rs | 2 +- tests/by-util/test_echo.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/uu/echo/src/echo.rs b/src/uu/echo/src/echo.rs index 947887210..b3707b6f8 100644 --- a/src/uu/echo/src/echo.rs +++ b/src/uu/echo/src/echo.rs @@ -175,7 +175,7 @@ fn execute(no_newline: bool, escaped: bool, free: &[String]) -> io::Result<()> { } if escaped { if print_escaped(input, &mut output)?.is_break() { - break; + return Ok(()); } } else { write!(output, "{input}")?; diff --git a/tests/by-util/test_echo.rs b/tests/by-util/test_echo.rs index dce5a4c95..875ff66cb 100644 --- a/tests/by-util/test_echo.rs +++ b/tests/by-util/test_echo.rs @@ -122,7 +122,7 @@ fn test_escape_no_further_output() { new_ucmd!() .args(&["-e", "a\\cb", "c"]) .succeeds() - .stdout_only("a\n"); + .stdout_only("a"); } #[test] From 54ba81ecbb9a810d3672ae6e378effb5c0dcb352 Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Tue, 3 Oct 2023 15:09:20 +0200 Subject: [PATCH 26/27] mv: fix typo in test function name test_mv_info_self -> test_mv_into_self --- tests/by-util/test_mv.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/by-util/test_mv.rs b/tests/by-util/test_mv.rs index 0490d4119..f7f9622f5 100644 --- a/tests/by-util/test_mv.rs +++ b/tests/by-util/test_mv.rs @@ -1323,7 +1323,7 @@ fn test_mv_interactive_error() { } #[test] -fn test_mv_info_self() { +fn test_mv_into_self() { let scene = TestScenario::new(util_name!()); let at = &scene.fixtures; let dir1 = "dir1"; From b591bedcab64851ec8d5091cc092db1144687369 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 3 Oct 2023 15:34:37 +0000 Subject: [PATCH 27/27] chore(deps): update rust crate memmap2 to 0.9 --- Cargo.lock | 4 ++-- Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 76e1dd72c..33ee7ff14 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1291,9 +1291,9 @@ checksum = "5486aed0026218e61b8a01d5fbd5a0a134649abb71a0e53b7bc088529dced86e" [[package]] name = "memmap2" -version = "0.8.0" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43a5a03cefb0d953ec0be133036f14e109412fa594edc2f77227249db66cc3ed" +checksum = "deaba38d7abf1d4cca21cc89e932e542ba2b9258664d2a9ef0e61512039c9375" dependencies = [ "libc", ] diff --git a/Cargo.toml b/Cargo.toml index c4c3fc2ff..e7fc2851b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -292,7 +292,7 @@ lscolors = { version = "0.15.0", default-features = false, features = [ "nu-ansi-term", ] } memchr = "2" -memmap2 = "0.8" +memmap2 = "0.9" nix = { version = "0.27", default-features = false } nom = "7.1.3" notify = { version = "=6.0.1", features = ["macos_kqueue"] }