You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
calc_loop_bsize derives the remaining byte budget for count=N iflag=count_bytes from the bytes written so far (wstat.bytes_total), while below_count_limit gates the copy loop on bytes read. Any conversion that writes more than it reads — conv=block record expansion, conv=sync padding of partial reads — pushes the written total past N while the read total is still below it, and the u128 subtraction in
$ yes aaaaaaaaaa | head -c 2000 > input.txt
$ cargo run -p uu_dd -- if=input.txt of=out.bin conv=block cbs=1024 count=1000 iflag=count_bytesthread 'main' panicked at src/uu/dd/src/dd.rs:1436:33:attempt to subtract with overflow
The first loop iteration reads 512 bytes and block-expands them to ~47 KiB of output; on the second iteration 1000 − 47_104 underflows.
Release builds: the count limit is silently bypassed
With overflow checks off the subtraction wraps, min(ideal_bsize, huge) returns the full block size again, and every following iteration reads a full-sized block — count no longer limits anything. Observable with the reproducer above (release build reads 1512 input bytes instead of 1000; GNU reads exactly 1000), or via strace with conv=sync on a pipe with partial reads, where the read sizes go 512 → 488 → 488 → back to 512 once the wrap happens.
Notes
GNU dd for the reproducer above outputs 93,184 bytes (91 records from exactly 1000 input bytes).
After fixing the budget computation, uutils outputs 94,208 bytes (92 records): uutils converts each copy-loop chunk independently, so a record spanning the 512-byte chunk boundary is padded early. That divergence is pre-existing and independent of count (same input without any count=: uutils 189,440 vs GNU 186,368 bytes) and out of scope here.
I have a fix ready (derive the budget from rstat.bytes_total, matching below_count_limit, with a saturating subtraction) and will open a PR right after filing this.
Summary
calc_loop_bsizederives the remaining byte budget forcount=N iflag=count_bytesfrom the bytes written so far (wstat.bytes_total), whilebelow_count_limitgates the copy loop on bytes read. Any conversion that writes more than it reads —conv=blockrecord expansion,conv=syncpadding of partial reads — pushes the written total pastNwhile the read total is still below it, and theu128subtraction inhttps://github.com/uutils/coreutils/blob/058a2a656ea3ac0d5eee5b6a8f6e5e10be817bd8/src/uu/dd/src/dd.rs#L1436
underflows.
Debug builds: panic
Deterministic reproducer (any input whose
conv=blockoutput exceeds the byte count works):The first loop iteration reads 512 bytes and block-expands them to ~47 KiB of output; on the second iteration
1000 − 47_104underflows.Release builds: the count limit is silently bypassed
With overflow checks off the subtraction wraps,
min(ideal_bsize, huge)returns the full block size again, and every following iteration reads a full-sized block —countno longer limits anything. Observable with the reproducer above (release build reads 1512 input bytes instead of 1000; GNU reads exactly 1000), or viastracewithconv=syncon a pipe with partial reads, where the read sizes go 512 → 488 → 488 → back to 512 once the wrap happens.Notes
count(same input without anycount=: uutils 189,440 vs GNU 186,368 bytes) and out of scope here.I have a fix ready (derive the budget from
rstat.bytes_total, matchingbelow_count_limit, with a saturating subtraction) and will open a PR right after filing this.