diff --git a/src/uu/dd/src/dd.rs b/src/uu/dd/src/dd.rs index b75565a93..206f4480e 100644 --- a/src/uu/dd/src/dd.rs +++ b/src/uu/dd/src/dd.rs @@ -324,6 +324,17 @@ impl Dest { Self::File(f, _) => f.seek(io::SeekFrom::Start(n)), } } + + /// Truncate the underlying file to the current stream position, if possible. + fn truncate(&mut self) -> io::Result<()> { + match self { + Self::Stdout(_) => Ok(()), + Self::File(f, _) => { + let pos = f.stream_position()?; + f.set_len(pos) + } + } + } } /// Decide whether the given buffer is all zeros. @@ -406,7 +417,6 @@ impl<'a> Output<'a> { // suppress the error by calling `Result::ok()`. This matches // the behavior of GNU `dd` when given the command-line // argument `of=/dev/null`. - if !settings.oconv.notrunc { dst.set_len(settings.seek).ok(); } @@ -570,6 +580,18 @@ impl<'a> Output<'a> { // Flush the output, if configured to do so. self.sync()?; + // Truncate the file to the final cursor location. + // + // Calling `set_len()` may result in an error (for example, + // when calling it on `/dev/null`), but we don't want to + // terminate the process when that happens. Instead, we + // suppress the error by calling `Result::ok()`. This matches + // the behavior of GNU `dd` when given the command-line + // argument `of=/dev/null`. + if !self.settings.oconv.notrunc { + self.dst.truncate().ok(); + } + // Print the final read/write statistics. let prog_update = ProgUpdate::new(rstat, wstat, start.elapsed(), true); prog_tx.send(prog_update).unwrap_or(()); diff --git a/tests/by-util/test_dd.rs b/tests/by-util/test_dd.rs index d0998ecda..03877d1d5 100644 --- a/tests/by-util/test_dd.rs +++ b/tests/by-util/test_dd.rs @@ -1394,3 +1394,27 @@ fn test_sync_delayed_reader() { assert_eq!(&output.stdout, &expected); assert_eq!(&output.stderr, b"0+8 records in\n4+0 records out\n"); } + +/// Test for making a sparse copy of the input file. +#[test] +fn test_sparse() { + let (at, mut ucmd) = at_and_ucmd!(); + + // Create a file and make it a large sparse file. + // + // On common Linux filesystems, setting the length to one megabyte + // should cause the file to become a sparse file, but it depends + // on the system. + std::fs::File::create(at.plus("infile")) + .unwrap() + .set_len(1024 * 1024) + .unwrap(); + + // Perform a sparse copy. + ucmd.args(&["bs=32K", "if=infile", "of=outfile", "conv=sparse"]) + .succeeds(); + + // The number of bytes in the file should be accurate though the + // number of blocks stored on disk may be zero. + assert_eq!(at.metadata("infile").len(), at.metadata("outfile").len()); +}