From 1af709f642c8406625e5d9006fa43d342fb9be33 Mon Sep 17 00:00:00 2001 From: Jeffrey Finkelstein Date: Sat, 5 Feb 2022 13:58:05 -0500 Subject: [PATCH] dd: truncate to specified seek length When specifying `seek=N` and *not* specifying `conv=notrunc`, truncate the output file to `N` blocks instead of truncating it to zero before starting to write output. For example $ printf "abc" > outfile $ printf "123" | dd bs=1 skip=1 seek=1 count=1 status=noxfer of=outfile 1+0 records in 1+0 records out $ cat outfile a2 Fixes #3068. --- src/uu/dd/src/dd.rs | 13 ++++++------- tests/by-util/test_dd.rs | 22 ++++++++++++++++++++++ 2 files changed, 28 insertions(+), 7 deletions(-) diff --git a/src/uu/dd/src/dd.rs b/src/uu/dd/src/dd.rs index 54e3190ce..448eaf937 100644 --- a/src/uu/dd/src/dd.rs +++ b/src/uu/dd/src/dd.rs @@ -469,7 +469,6 @@ impl OutputTrait for Output { let mut opts = OpenOptions::new(); opts.write(true) .create(!cflags.nocreat) - .truncate(!cflags.notrunc) .create_new(cflags.excl) .append(oflags.append); @@ -489,13 +488,13 @@ impl OutputTrait for Output { let mut dst = open_dst(Path::new(&fname), &cflags, &oflags) .map_err_context(|| format!("failed to open {}", fname.quote()))?; - if let Some(amt) = seek { - let amt: u64 = amt - .try_into() - .map_err(|_| USimpleError::new(1, "failed to parse seek amount"))?; - dst.seek(io::SeekFrom::Start(amt)) - .map_err_context(|| "failed to seek in output file".to_string())?; + let i = seek.unwrap_or(0).try_into().unwrap(); + if !cflags.notrunc { + dst.set_len(i) + .map_err_context(|| "failed to truncate output file".to_string())?; } + dst.seek(io::SeekFrom::Start(i)) + .map_err_context(|| "failed to seek in output file".to_string())?; Ok(Self { dst, obs, cflags }) } else { diff --git a/tests/by-util/test_dd.rs b/tests/by-util/test_dd.rs index e73fe0673..688c629ef 100644 --- a/tests/by-util/test_dd.rs +++ b/tests/by-util/test_dd.rs @@ -604,5 +604,27 @@ fn test_seek_bytes() { .stdout_is("\0\0\0\0\0\0\0\0abcdefghijklm\n"); } +#[test] +fn test_seek_do_not_overwrite() { + let (at, mut ucmd) = at_and_ucmd!(); + let mut outfile = at.make_file("outfile"); + outfile.write_all(b"abc").unwrap(); + // Skip the first byte of the input, seek past the first byte of + // the output, and write only one byte to the output. + ucmd.args(&[ + "bs=1", + "skip=1", + "seek=1", + "count=1", + "status=noxfer", + "of=outfile", + ]) + .pipe_in("123") + .succeeds() + .stderr_is("1+0 records in\n1+0 records out\n") + .no_stdout(); + assert_eq!(at.read("outfile"), "a2"); +} + // conv=[ascii,ebcdic,ibm], conv=[ucase,lcase], conv=[block,unblock], conv=sync // TODO: Move conv tests from unit test module