Skip to content

Commit 04c81ce

Browse files
authored
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
1 parent cb6d339 commit 04c81ce

File tree

3 files changed

+64
-39
lines changed

3 files changed

+64
-39
lines changed

src/uu/tail/src/args.rs

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ use std::ffi::OsString;
1313
use std::io::IsTerminal;
1414
use std::time::Duration;
1515
use uucore::error::{UResult, USimpleError, UUsageError};
16-
use uucore::parser::parse_signed_num::{SignPrefix, parse_signed_num};
16+
use uucore::parser::parse_signed_num::{SignPrefix, parse_signed_num_max};
1717
use uucore::parser::parse_size::ParseSizeError;
1818
use uucore::parser::parse_time;
1919
use uucore::parser::shortcut_value_parser::ShortcutValueParser;
@@ -78,7 +78,7 @@ impl FilterMode {
7878
Err(e) => {
7979
return Err(USimpleError::new(
8080
1,
81-
translate!("tail-error-invalid-number-of-bytes", "arg" => format!("'{e}'")),
81+
translate!("tail-error-invalid-number-of-bytes", "arg" => e.to_string()),
8282
));
8383
}
8484
}
@@ -366,12 +366,6 @@ pub fn parse_obsolete(arg: &OsString, input: Option<&OsString>) -> UResult<Optio
366366
Err(USimpleError::new(
367367
1,
368368
match e {
369-
parse::ParseError::OutOfRange => {
370-
translate!("tail-error-invalid-number-out-of-range", "arg" => arg.quote())
371-
}
372-
parse::ParseError::Overflow => {
373-
translate!("tail-error-invalid-number-overflow", "arg" => arg.quote())
374-
}
375369
// this ensures compatibility to GNU's error message (as tested in misc/tail)
376370
parse::ParseError::Context => {
377371
translate!(
@@ -389,7 +383,7 @@ pub fn parse_obsolete(arg: &OsString, input: Option<&OsString>) -> UResult<Optio
389383
}
390384

391385
fn parse_num(src: &str) -> Result<Signum, ParseSizeError> {
392-
let result = parse_signed_num(src)?;
386+
let result = parse_signed_num_max(src)?;
393387
// tail: '+' means "starting from line/byte N", default/'-' means "last N"
394388
let is_plus = result.sign == Some(SignPrefix::Plus);
395389

src/uu/tail/src/parse.rs

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,6 @@ impl Default for ObsoleteArgs {
2626

2727
#[derive(PartialEq, Eq, Debug)]
2828
pub enum ParseError {
29-
OutOfRange,
30-
Overflow,
3129
Context,
3230
InvalidEncoding,
3331
}
@@ -52,11 +50,7 @@ pub fn parse_obsolete(src: &OsString) -> Option<Result<ObsoleteArgs, ParseError>
5250
.unwrap_or(rest.len());
5351
let has_num = !rest[..end_num].is_empty();
5452
let num: u64 = if has_num {
55-
if let Ok(num) = rest[..end_num].parse() {
56-
num
57-
} else {
58-
return Some(Err(ParseError::OutOfRange));
59-
}
53+
rest[..end_num].parse().unwrap_or(u64::MAX)
6054
} else {
6155
10
6256
};
@@ -85,9 +79,7 @@ pub fn parse_obsolete(src: &OsString) -> Option<Result<ObsoleteArgs, ParseError>
8579
}
8680

8781
let multiplier = if mode == 'b' { 512 } else { 1 };
88-
let Some(num) = num.checked_mul(multiplier) else {
89-
return Some(Err(ParseError::Overflow));
90-
};
82+
let num = num.saturating_mul(multiplier);
9183

9284
Some(Ok(ObsoleteArgs {
9385
num,

tests/by-util/test_tail.rs

Lines changed: 59 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1155,23 +1155,63 @@ fn test_invalid_num() {
11551155
.fails()
11561156
.stderr_str()
11571157
.starts_with("tail: invalid number of lines: '1024R'");
1158+
// 1Y overflows to u64::MAX (like GNU tail 9.9.x), so it succeeds
11581159
new_ucmd!()
1159-
.args(&["-c", "1Y", "emptyfile.txt"])
1160-
.fails()
1161-
.stderr_str()
1162-
.starts_with("tail: invalid number of bytes: '1Y': Value too large for defined data type");
1160+
.args(&["-c", "1Y"])
1161+
.pipe_in("x")
1162+
.succeeds()
1163+
.stdout_is("x");
11631164
new_ucmd!()
1164-
.args(&["-n", "1Y", "emptyfile.txt"])
1165-
.fails()
1166-
.stderr_str()
1167-
.starts_with("tail: invalid number of lines: '1Y': Value too large for defined data type");
1165+
.args(&["-n", "1Y"])
1166+
.pipe_in("x\n")
1167+
.succeeds()
1168+
.stdout_is("x\n");
11681169
new_ucmd!()
11691170
.args(&["-c", "-³"])
11701171
.fails()
11711172
.stderr_str()
11721173
.starts_with("tail: invalid number of bytes: '³'");
11731174
}
11741175

1176+
#[test]
1177+
fn test_oversized_num() {
1178+
const BIG: &str = "99999999999999999999999999999";
1179+
const DATA: &str = "abcd";
1180+
// -c <big> and -n <big>: output all (request more than available)
1181+
new_ucmd!()
1182+
.args(&["-c", BIG])
1183+
.pipe_in(DATA)
1184+
.succeeds()
1185+
.stdout_is(DATA);
1186+
new_ucmd!()
1187+
.args(&["-n", BIG])
1188+
.pipe_in("a\nb\n")
1189+
.succeeds()
1190+
.stdout_is("a\nb\n");
1191+
// +<big>: skip beyond input (empty output)
1192+
new_ucmd!()
1193+
.args(&["-c", &format!("+{BIG}")])
1194+
.pipe_in(DATA)
1195+
.succeeds()
1196+
.no_stdout();
1197+
new_ucmd!()
1198+
.args(&["-n", &format!("+{BIG}")])
1199+
.pipe_in("a\nb\n")
1200+
.succeeds()
1201+
.no_stdout();
1202+
// Obsolete syntax
1203+
new_ucmd!()
1204+
.arg(format!("+{BIG}c"))
1205+
.pipe_in(DATA)
1206+
.succeeds()
1207+
.no_stdout();
1208+
new_ucmd!()
1209+
.arg(format!("-{BIG}c"))
1210+
.pipe_in(DATA)
1211+
.succeeds()
1212+
.stdout_is(DATA);
1213+
}
1214+
11751215
#[test]
11761216
fn test_num_with_undocumented_sign_bytes() {
11771217
// tail: '-' is not documented (8.32 man pages)
@@ -4767,13 +4807,13 @@ fn test_gnu_args_err() {
47674807
.fails_with_code(1)
47684808
.no_stdout()
47694809
.stderr_is("tail: option used in invalid context -- 2\n");
4770-
// err-5
4810+
// err-5: large numbers now clamp to u64::MAX
47714811
scene
47724812
.ucmd()
47734813
.arg("-c99999999999999999999")
4774-
.fails_with_code(1)
4775-
.no_stdout()
4776-
.stderr_is("tail: invalid number of bytes: '99999999999999999999'\n");
4814+
.pipe_in("x")
4815+
.succeeds()
4816+
.stdout_is("x");
47774817
// err-6
47784818
scene
47794819
.ucmd()
@@ -4787,20 +4827,19 @@ fn test_gnu_args_err() {
47874827
.fails_with_code(1)
47884828
.no_stdout()
47894829
.stderr_is("tail: option used in invalid context -- 5\n");
4830+
// Large obsolete-syntax numbers clamp to u64::MAX
47904831
scene
47914832
.ucmd()
47924833
.arg("-9999999999999999999b")
4793-
.fails_with_code(1)
4794-
.no_stdout()
4795-
.stderr_is("tail: invalid number: '-9999999999999999999b'\n");
4834+
.pipe_in("x")
4835+
.succeeds()
4836+
.stdout_is("x");
47964837
scene
47974838
.ucmd()
47984839
.arg("-999999999999999999999b")
4799-
.fails_with_code(1)
4800-
.no_stdout()
4801-
.stderr_is(
4802-
"tail: invalid number: '-999999999999999999999b': Numerical result out of range\n",
4803-
);
4840+
.pipe_in("x")
4841+
.succeeds()
4842+
.stdout_is("x");
48044843
}
48054844

48064845
#[test]

0 commit comments

Comments
 (0)