1
Fork 0
mirror of https://github.com/RGBCube/uutils-coreutils synced 2025-07-28 11:37:44 +00:00

dd: correctly set file size when conv=sparse

Before this commit, if `sparsefile` were a regular file of non-zero
size whose contents are all null bytes, then

    dd if=sparsefile of=outfile conv=notrunc

would have resulted in `outfile` having zero size as reported by
`stat`. After this commit, `outfile` will have the same size as
`sparsefile` (even if the contents are represented sparsely by the
filesystem).
This commit is contained in:
Jeffrey Finkelstein 2022-11-27 21:01:25 -05:00 committed by Sylvestre Ledru
parent e307f624e8
commit 9632db4f86
2 changed files with 47 additions and 1 deletions

View file

@ -324,6 +324,17 @@ impl Dest {
Self::File(f, _) => f.seek(io::SeekFrom::Start(n)), 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. /// 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 // suppress the error by calling `Result::ok()`. This matches
// the behavior of GNU `dd` when given the command-line // the behavior of GNU `dd` when given the command-line
// argument `of=/dev/null`. // argument `of=/dev/null`.
if !settings.oconv.notrunc { if !settings.oconv.notrunc {
dst.set_len(settings.seek).ok(); dst.set_len(settings.seek).ok();
} }
@ -570,6 +580,18 @@ impl<'a> Output<'a> {
// Flush the output, if configured to do so. // Flush the output, if configured to do so.
self.sync()?; 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. // Print the final read/write statistics.
let prog_update = ProgUpdate::new(rstat, wstat, start.elapsed(), true); let prog_update = ProgUpdate::new(rstat, wstat, start.elapsed(), true);
prog_tx.send(prog_update).unwrap_or(()); prog_tx.send(prog_update).unwrap_or(());

View file

@ -1394,3 +1394,27 @@ fn test_sync_delayed_reader() {
assert_eq!(&output.stdout, &expected); assert_eq!(&output.stdout, &expected);
assert_eq!(&output.stderr, b"0+8 records in\n4+0 records out\n"); 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());
}