Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 20 additions & 2 deletions src/uu/dd/src/dd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1191,14 +1191,20 @@ fn dd_copy(mut i: Input, o: Output) -> io::Result<()> {
// blocks to this output. Read/write statistics are updated on
// each iteration and cumulative statistics are reported to
// the progress reporting thread.
// A failure ends the loop, so the statistics gathered so far still get reported.
let mut copy_error = None;
while below_count_limit(i.settings.count, &rstat) {
// Read a block from the input then write the block to the output.
//
// As an optimization, make an educated guess about the
// best buffer size for reading based on the number of
// blocks already read and the number of blocks remaining.
let loop_bsize = calc_loop_bsize(i.settings.count, &rstat, i.settings.ibs, bsize);
let rstat_update = read_helper(&mut i, &mut buf, loop_bsize)?;
let Ok(rstat_update) =
read_helper(&mut i, &mut buf, loop_bsize).map_err(|e| copy_error = Some(e))
else {
break;
};
if rstat_update.is_empty() {
if input_nocache {
i.discard_cache(read_offset, 0);
Expand All @@ -1208,7 +1214,9 @@ fn dd_copy(mut i: Input, o: Output) -> io::Result<()> {
}
break;
}
let wstat_update = o.write_blocks(&buf)?;
let Ok(wstat_update) = o.write_blocks(&buf).map_err(|e| copy_error = Some(e)) else {
break;
};

// Discard the system file cache for the read portion of
// the input file.
Expand Down Expand Up @@ -1255,6 +1263,16 @@ fn dd_copy(mut i: Input, o: Output) -> io::Result<()> {
prog_tx.send(prog_update).unwrap_or(());
}

if let Some(e) = copy_error {
// Flushing and syncing are pointless now, but the caller still wants the statistics.
let prog_update = ProgUpdate::new(rstat, wstat, start.elapsed(), ProgUpdateType::Final);
prog_tx.send(prog_update).unwrap_or(());
output_thread
.join()
.expect("Failed to join with the output thread.");
return Err(e);
}

finalize(o, rstat, wstat, start, &prog_tx, output_thread, truncate)
}

Expand Down
39 changes: 38 additions & 1 deletion tests/by-util/test_dd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
//
// For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code.
// spell-checker:ignore fname, tname, fpath, specfile, testfile, unspec, ifile, ofile, outfile, fullblock, urand, fileio, atoe, atoibm, availible, behaviour, bmax, bremain, btotal, cflags, creat, ctable, ctty, datastructures, doesnt, etoa, fileout, fname, gnudd, iconvflags, iseek, nocache, noctty, noerror, nofollow, nolinks, nonblock, oconvflags, oseek, outfile, parseargs, rlen, rmax, rposition, rremain, rsofar, rstat, sigusr, sigval, wlen, wstat abcdefghijklm abcdefghi nabcde nabcdefg abcdefg fifoname FADV DONTNEED
// spell-checker:ignore fname, tname, fpath, specfile, testfile, unspec, ifile, ofile, outfile, fullblock, urand, fileio, atoe, atoibm, availible, behaviour, bmax, bremain, btotal, cflags, creat, ctable, ctty, datastructures, doesnt, etoa, fileout, fname, gnudd, iconvflags, iseek, nocache, noctty, noerror, nofollow, nolinks, nonblock, oconvflags, oseek, outfile, parseargs, rlen, rmax, rposition, rremain, rsofar, rstat, sigusr, sigval, wlen, wstat abcdefghijklm abcdefghi nabcde nabcdefg abcdefg fifoname FADV DONTNEED FSIZE SIGXFSZ sighandler

use uutests::at_and_ucmd;
use uutests::new_ucmd;
Expand Down Expand Up @@ -2209,3 +2209,40 @@ fn test_count_bytes_with_expanding_block_conv() {
assert_eq!(bytecount::count(&output, b'a'), 1000);
assert!(!output.contains(&b'Z'));
}

// A failed copy still has to report what it transferred, including complete
// and partial records.
#[test]
#[cfg(all(unix, not(target_os = "macos")))]
fn test_stats_are_reported_when_a_write_fails() {
use rlimit::Resource;

// Restores the previous SIGXFSZ disposition even if an assertion panics.
struct SigxfszGuard(libc::sighandler_t);
impl Drop for SigxfszGuard {
fn drop(&mut self) {
// SAFETY: restoring the disposition saved below.
unsafe { libc::signal(libc::SIGXFSZ, self.0) };
}
}

const CAP: u64 = 768 * 1024;

// The child inherits the ignored SIGXFSZ, so exceeding RLIMIT_FSIZE shows
// up as a short write() instead of killing the process.
// SAFETY: signal() with SIG_IGN is async-signal-safe and the guard puts
// the old handler back.
let _sigxfsz = SigxfszGuard(unsafe { libc::signal(libc::SIGXFSZ, libc::SIG_IGN) });

let (at, mut ucmd) = at_and_ucmd!();
let result = ucmd
.args(&["if=/dev/zero", "of=capped.bin", "bs=512K", "count=3"])
.limit(Resource::FSIZE, CAP, CAP)
.fails();

// Under a 768 KiB cap, the first 512 KiB block is written in full, the
// second one is cut short at 256 KiB, and the third write fails.
result.stderr_contains("1+1 records out");
result.stderr_contains("786432 bytes");
assert_eq!(at.metadata("capped.bin").len(), CAP);
}
Loading