Skip to content

dd: exit when bs/ibs/obs/cbs isn't positive#11630

Merged
sylvestre merged 3 commits into
uutils:mainfrom
iburaky2:fix/dd-bs-not-positive
Apr 20, 2026
Merged

dd: exit when bs/ibs/obs/cbs isn't positive#11630
sylvestre merged 3 commits into
uutils:mainfrom
iburaky2:fix/dd-bs-not-positive

Conversation

@iburaky2

@iburaky2 iburaky2 commented Apr 4, 2026

Copy link
Copy Markdown
Contributor

Match GNU coreutils behavior:

$ dd bs=0
dd: invalid number: '0'
$ dd ibs=0
dd: invalid number: '0'
$ dd obs=0
dd: invalid number: '0'
$ dd cbs=0
dd: invalid number: '0'

$ dd bs=-5
dd: invalid number: '-5'
$ dd ibs=-5
dd: invalid number: '-5'
$ dd obs=-5
dd: invalid number: '-5'
$ dd cbs=-5
dd: invalid number: '-5'

Currently:

Another option would be to check in parse_bytes / parse_bytes_with_opt_multiplier but I think it's better to exit earlier.

@github-actions

github-actions Bot commented Apr 4, 2026

Copy link
Copy Markdown

GNU testsuite comparison:

Note: The gnu test tests/csplit/csplit-heap is now being skipped but was previously passing.
Note: The gnu test tests/dd/no-allocate is now being skipped but was previously passing.
Note: The gnu test tests/tail/tail-n0f is now being skipped but was previously passing.

@oech3

oech3 commented Apr 4, 2026

Copy link
Copy Markdown
Contributor

Our dd already rejects negative value. So this PR might have duplicated logic with it.

@oech3

This comment was marked as resolved.

@iburaky2 iburaky2 force-pushed the fix/dd-bs-not-positive branch from 6f89772 to 3fae276 Compare April 4, 2026 01:54
@iburaky2

iburaky2 commented Apr 4, 2026

Copy link
Copy Markdown
Contributor Author

mb I got the order wrong parse_bytes comes before validate(). Since parse_bytes is only used with those 4 parameters and all should reject 0 we can handle the check there.

Negative numbers are handled here:

fn parse_bytes_no_x(full: &str, s: &str) -> Result<u64, ParseError> {
    let parser = SizeParser {
        capital_b_bytes: true,
        no_empty_numeric: true,
        ..Default::default()
    };
    let (num, multiplier) = match (s.find('c'), s.rfind('w'), s.rfind('b')) {
        (None, None, None) => match parser.parse_u64(s) { // -5 can't be parsed as y64
            Ok(n) => (n, 1),
            Err(ParseSizeError::SizeTooBig(_)) => (u64::MAX, 1),
            Err(_) => return Err(ParseError::InvalidNumber(full.to_string())), // error here
        },
        (Some(i), None, None) => (parse_bytes_only(s, i)?, 1),
        (None, Some(i), None) => (parse_bytes_only(s, i)?, 2),
        (None, None, Some(i)) => (parse_bytes_only(s, i)?, 512),
        _ => return Err(ParseError::MultiplierStringParseFailure(full.to_string())),
    };
    num.checked_mul(multiplier)
        .ok_or_else(|| ParseError::MultiplierStringOverflow(full.to_string()))
}

@github-actions

github-actions Bot commented Apr 4, 2026

Copy link
Copy Markdown

GNU testsuite comparison:

Skip an intermittent issue tests/pr/bounded-memory (was skipped on 'main', now failing)

@sylvestre

Copy link
Copy Markdown
Contributor

Thanks! Two things before this lands:

  1. The negative case doesn't actually match GNU. bs=-5 currently errors via try_into() => BsOutOfRange, which prints bs=N cannot fit into memory, not GNU's invalid number: '-5'. Could you collapse it into a single check? like:
    let num = parse_bytes_with_opt_multiplier(val)?;
    if num <= 0 {
    return Err(ParseError::InvalidNumber(val.to_string()));
    }
  2. Note val.to_string() (not num.to_string()) so inputs like 0x0 are echoed verbatim as GNU does.
  3. The test only checks exit code and empty stdout, so it doesn't actually verify the error message - which is the whole point of the PR. Please add a .stderr_contains("invalid number") so we catch regressions in the wording.

@iburaky2 iburaky2 force-pushed the fix/dd-bs-not-positive branch from 3fae276 to ef02de9 Compare April 6, 2026 02:08
@github-actions

github-actions Bot commented Apr 6, 2026

Copy link
Copy Markdown

GNU testsuite comparison:

Skip an intermittent issue tests/pr/bounded-memory (fails in this run but passes in the 'main' branch)
Skip an intermittent issue tests/rm/isatty (fails in this run but passes in the 'main' branch)
Skipping an intermittent issue tests/date/date-locale-hour (passes in this run but fails in the 'main' branch)

@iburaky2

iburaky2 commented Apr 6, 2026

Copy link
Copy Markdown
Contributor Author

I've fixed points 2 and 3 and also added 0x0 as a testcase

Regarding point 1, bs=-5 does match GNU's error message [see the passing test which now checks error message as proof :) ] because it's handled earlier in parse_bytes_no_x.

$ rust-gdb --args coreutils dd bs=-5
(gdb) b uu_dd::parseargs::parse_bytes_no_x
Breakpoint 1 at 0x1176ac4: file src/uu/dd/src/parseargs.rs, line 491.
(gdb) r
Breakpoint 1, uu_dd::parseargs::parse_bytes_no_x (full="-5", s="-5") at src/uu/dd/src/parseargs.rs:491
491	        ..Default::default()
(gdb) n
488	    let parser = SizeParser {
(gdb) 
493	    let (num, multiplier) = match (s.find('c'), s.rfind('w'), s.rfind('b')) {
(gdb) 
494	        (None, None, None) => match parser.parse_u64(s) {
(gdb) 
497	            Err(_) => return Err(ParseError::InvalidNumber(full.to_string())),
(gdb) 

I'm not fully sure I fully understand what the point of

        num.try_into()
            .map_err(|_| ParseError::BsOutOfRange(arg.to_string()))

is. I guess this would only trigger if we were on a 32 bit system (since the function returns usize) and num (u64) was too large?

@sylvestre sylvestre merged commit e5a6b51 into uutils:main Apr 20, 2026
169 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants