tail: fix big number handling and error message quoting (#10155)

* tail: fix big number handling and error message quoting

* fix clippy: remove needless borrows in test_tail
This commit is contained in:
Chris Dryden
2026-01-10 14:25:18 +01:00
committed by GitHub
parent cb6d3396f6
commit 04c81ce8cf
3 changed files with 64 additions and 39 deletions
+3 -9
View File
@@ -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<Optio
Err(USimpleError::new(
1,
match e {
parse::ParseError::OutOfRange => {
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<Optio
}
fn parse_num(src: &str) -> Result<Signum, ParseSizeError> {
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);
+2 -10
View File
@@ -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<Result<ObsoleteArgs, ParseError>
.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<Result<ObsoleteArgs, ParseError>
}
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,
+59 -20
View File
@@ -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 <big> and -n <big>: 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");
// +<big>: 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)
@@ -4767,13 +4807,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()
@@ -4787,20 +4827,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]