Repository navigation
dd: retry short writes so the error behind them is reported - #16196
thelastbackspace wants to merge 3 commits into
Conversation
agammann
left a comment
There was a problem hiding this comment.
The new o.take_failed_wstat() call runs before c.finish(), but finish() writes the padded tail of a conv=block record through write_to(). If a capped destination fails partway through that final write, Output records the partial bytes only after they have already been collected here, so the final report still undercounts them. Could the failed-write stats be collected after finish() as well, with an EOF-padding case where RLIMIT_FSIZE falls inside the padded block?
|
Good catch, you're right. I collected the failed-write stats before |
Merging this PR will not alter performance1 warning
Comparing Footnotes
|
| .limit(Resource::FSIZE, CAP, CAP) | ||
| .ignore_sigxfsz() | ||
| .fails(); | ||
| if args[0] == "bs=512K" { |
There was a problem hiding this comment.
why only check the stats for bs=?
with obs=2M the error comes from the flush in finalize, which returns before printing any stats, and failed_wstat is never added there.
please handle that path and check the records/bytes for all three cases
There was a problem hiding this comment.
Right, the flush path in finalize returned before sending the stats. In 5099fe3 it now adds what the failed flush wrote and prints the stats before returning the error. I also count the block that was read when writing it fails, which GNU does (we said 1+0 records in, GNU says 2+0).
The test now checks records in/out and bytes for all three cases. Compared with GNU dd 9.4 under the same 768K limit:
ibs=512K obs=512K:2+0in,1+1out, 786432 bytes, same as GNUibs=512K obs=2M:2+0in,0+1out, 786432 bytes, same as GNU (we printed nothing before)bs=512K: GNU prints1+0 records outwith 786432 bytes, so it counts the bytes of the short write but not the record. I kept1+1so it matches the obs= case, but I can change it to follow GNU exactly if you prefer.
A short write was only retried with iflag=fullblock. Otherwise the rest of the block was dropped, so a copy cut short by a full disk or a file size limit on its last block exited 0 with a truncated output. Always write the whole block, like GNU dd, and keep what a failed write got through in the final statistics. Fixes uutils#15747
The partial write stats were collected before finish() wrote the padded tail of the last record, so a failure there was undercounted.
With a large obs=, the only write can be the flush in finalize(), which returned before printing any statistics and never added what the failed write got through. Print the stats on that path too, and count the block that was read when writing it fails, as GNU dd does (2+0 records in). Check records in/out and bytes for all three cases of the short-write test.
5099fe3 to
63cd6da
Compare
Output::write_blockonly retried a short write wheniflag=fullblockwas set. Without it, the rest of the block was dropped. If that short write was the last one of the copy (full disk orRLIMIT_FSIZEhitting the last block), dd exited 0 and left a truncated file. GNU dd keeps writing the rest of the block, gets theENOSPC/EFBIG, and fails.What changed:
write_blocknow loops until the whole block is written.EINTRis retried and any other error is returned. Awritethat returns 0 becomes aWriteZeroerror, so it can't spin forever.1+1 records out, the byte count),Outputremembers what a failedwrite_blockscall got through, anddd_copyadds it to the final stats. This also covers blocks written earlier in the same call, which used to be lost from the stats when a later one failed.Output::newso the struct's new field doesn't have to be spelled out in every constructor and test.Testing:
test_short_last_write_failsintests/by-util/test_dd.rsrunsbs=512K count=2,ibs=512K obs=512K count=2andibs=512K obs=2M count=2under a 768 KiBRLIMIT_FSIZE. It fails on main (all three exit 0) and passes with this change.cargo test --no-default-features --features dd -- test_dd(160 passed),cargo test -p uu_dd,cargo fmt --check,cargo clippy -p uu_dd --all-targets.Fixes #15747