diff --git a/src/uu/tail/src/args.rs b/src/uu/tail/src/args.rs index 5f3404fbf61..d5159ab7a49 100644 --- a/src/uu/tail/src/args.rs +++ b/src/uu/tail/src/args.rs @@ -13,7 +13,7 @@ use std::ffi::OsString; use std::io::IsTerminal; use std::time::Duration; use uucore::error::{UResult, USimpleError, UUsageError}; -use uucore::parser::parse_signed_num::{SignPrefix, parse_signed_num}; +use uucore::parser::parse_signed_num::{SignPrefix, parse_signed_num_max}; use uucore::parser::parse_size::ParseSizeError; use uucore::parser::parse_time; use uucore::parser::shortcut_value_parser::ShortcutValueParser; @@ -78,7 +78,7 @@ impl FilterMode { Err(e) => { return Err(USimpleError::new( 1, - translate!("tail-error-invalid-number-of-bytes", "arg" => format!("'{e}'")), + translate!("tail-error-invalid-number-of-bytes", "arg" => e.to_string()), )); } } @@ -366,12 +366,6 @@ pub fn parse_obsolete(arg: &OsString, input: Option<&OsString>) -> UResult { - translate!("tail-error-invalid-number-out-of-range", "arg" => arg.quote()) - } - parse::ParseError::Overflow => { - translate!("tail-error-invalid-number-overflow", "arg" => arg.quote()) - } // this ensures compatibility to GNU's error message (as tested in misc/tail) parse::ParseError::Context => { translate!( @@ -389,7 +383,7 @@ pub fn parse_obsolete(arg: &OsString, input: Option<&OsString>) -> UResult Result { - let result = parse_signed_num(src)?; + let result = parse_signed_num_max(src)?; // tail: '+' means "starting from line/byte N", default/'-' means "last N" let is_plus = result.sign == Some(SignPrefix::Plus); diff --git a/src/uu/tail/src/parse.rs b/src/uu/tail/src/parse.rs index 2e768d1c913..846ba49b8d3 100644 --- a/src/uu/tail/src/parse.rs +++ b/src/uu/tail/src/parse.rs @@ -26,8 +26,6 @@ impl Default for ObsoleteArgs { #[derive(PartialEq, Eq, Debug)] pub enum ParseError { - OutOfRange, - Overflow, Context, InvalidEncoding, } @@ -52,11 +50,7 @@ pub fn parse_obsolete(src: &OsString) -> Option .unwrap_or(rest.len()); let has_num = !rest[..end_num].is_empty(); let num: u64 = if has_num { - if let Ok(num) = rest[..end_num].parse() { - num - } else { - return Some(Err(ParseError::OutOfRange)); - } + rest[..end_num].parse().unwrap_or(u64::MAX) } else { 10 }; @@ -85,9 +79,7 @@ pub fn parse_obsolete(src: &OsString) -> Option } let multiplier = if mode == 'b' { 512 } else { 1 }; - let Some(num) = num.checked_mul(multiplier) else { - return Some(Err(ParseError::Overflow)); - }; + let num = num.saturating_mul(multiplier); Some(Ok(ObsoleteArgs { num, diff --git a/tests/by-util/test_tail.rs b/tests/by-util/test_tail.rs index 50b404c9161..189831459c4 100644 --- a/tests/by-util/test_tail.rs +++ b/tests/by-util/test_tail.rs @@ -1155,16 +1155,17 @@ fn test_invalid_num() { .fails() .stderr_str() .starts_with("tail: invalid number of lines: '1024R'"); + // 1Y overflows to u64::MAX (like GNU tail 9.9.x), so it succeeds new_ucmd!() - .args(&["-c", "1Y", "emptyfile.txt"]) - .fails() - .stderr_str() - .starts_with("tail: invalid number of bytes: '1Y': Value too large for defined data type"); + .args(&["-c", "1Y"]) + .pipe_in("x") + .succeeds() + .stdout_is("x"); new_ucmd!() - .args(&["-n", "1Y", "emptyfile.txt"]) - .fails() - .stderr_str() - .starts_with("tail: invalid number of lines: '1Y': Value too large for defined data type"); + .args(&["-n", "1Y"]) + .pipe_in("x\n") + .succeeds() + .stdout_is("x\n"); new_ucmd!() .args(&["-c", "-³"]) .fails() @@ -1172,6 +1173,45 @@ fn test_invalid_num() { .starts_with("tail: invalid number of bytes: '³'"); } +#[test] +fn test_oversized_num() { + const BIG: &str = "99999999999999999999999999999"; + const DATA: &str = "abcd"; + // -c and -n : output all (request more than available) + new_ucmd!() + .args(&["-c", BIG]) + .pipe_in(DATA) + .succeeds() + .stdout_is(DATA); + new_ucmd!() + .args(&["-n", BIG]) + .pipe_in("a\nb\n") + .succeeds() + .stdout_is("a\nb\n"); + // +: skip beyond input (empty output) + new_ucmd!() + .args(&["-c", &format!("+{BIG}")]) + .pipe_in(DATA) + .succeeds() + .no_stdout(); + new_ucmd!() + .args(&["-n", &format!("+{BIG}")]) + .pipe_in("a\nb\n") + .succeeds() + .no_stdout(); + // Obsolete syntax + new_ucmd!() + .arg(format!("+{BIG}c")) + .pipe_in(DATA) + .succeeds() + .no_stdout(); + new_ucmd!() + .arg(format!("-{BIG}c")) + .pipe_in(DATA) + .succeeds() + .stdout_is(DATA); +} + #[test] fn test_num_with_undocumented_sign_bytes() { // tail: '-' is not documented (8.32 man pages) @@ -4728,13 +4768,13 @@ fn test_gnu_args_err() { .fails_with_code(1) .no_stdout() .stderr_is("tail: option used in invalid context -- 2\n"); - // err-5 + // err-5: large numbers now clamp to u64::MAX scene .ucmd() .arg("-c99999999999999999999") - .fails_with_code(1) - .no_stdout() - .stderr_is("tail: invalid number of bytes: '99999999999999999999'\n"); + .pipe_in("x") + .succeeds() + .stdout_is("x"); // err-6 scene .ucmd() @@ -4748,20 +4788,19 @@ fn test_gnu_args_err() { .fails_with_code(1) .no_stdout() .stderr_is("tail: option used in invalid context -- 5\n"); + // Large obsolete-syntax numbers clamp to u64::MAX scene .ucmd() .arg("-9999999999999999999b") - .fails_with_code(1) - .no_stdout() - .stderr_is("tail: invalid number: '-9999999999999999999b'\n"); + .pipe_in("x") + .succeeds() + .stdout_is("x"); scene .ucmd() .arg("-999999999999999999999b") - .fails_with_code(1) - .no_stdout() - .stderr_is( - "tail: invalid number: '-999999999999999999999b': Numerical result out of range\n", - ); + .pipe_in("x") + .succeeds() + .stdout_is("x"); } #[test]