diff --git a/src/uu/dd/src/datastructures.rs b/src/uu/dd/src/datastructures.rs index 067058bbe..6529f6602 100644 --- a/src/uu/dd/src/datastructures.rs +++ b/src/uu/dd/src/datastructures.rs @@ -4,7 +4,7 @@ // // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. -// spell-checker:ignore ctable, outfile +// spell-checker:ignore ctable, outfile, iseek, oseek use std::error::Error; @@ -120,6 +120,8 @@ pub mod options { pub const COUNT: &str = "count"; pub const SKIP: &str = "skip"; pub const SEEK: &str = "seek"; + pub const ISEEK: &str = "iseek"; + pub const OSEEK: &str = "oseek"; pub const STATUS: &str = "status"; pub const CONV: &str = "conv"; pub const IFLAG: &str = "iflag"; diff --git a/src/uu/dd/src/dd.rs b/src/uu/dd/src/dd.rs index d8bc3acd3..dff712e92 100644 --- a/src/uu/dd/src/dd.rs +++ b/src/uu/dd/src/dd.rs @@ -5,7 +5,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, behaviour, bmax, bremain, cflags, creat, ctable, ctty, datastructures, doesnt, etoa, fileout, fname, gnudd, iconvflags, nocache, noctty, noerror, nofollow, nolinks, nonblock, oconvflags, outfile, parseargs, rlen, rmax, rremain, rsofar, rstat, sigusr, wlen, wstat seekable +// spell-checker:ignore fname, tname, fpath, specfile, testfile, unspec, ifile, ofile, outfile, fullblock, urand, fileio, atoe, atoibm, behaviour, bmax, bremain, cflags, creat, ctable, ctty, datastructures, doesnt, etoa, fileout, fname, gnudd, iconvflags, iseek, nocache, noctty, noerror, nofollow, nolinks, nonblock, oconvflags, oseek, outfile, parseargs, rlen, rmax, rremain, rsofar, rstat, sigusr, wlen, wstat seekable mod datastructures; use datastructures::*; @@ -61,6 +61,7 @@ impl Input { let cflags = parseargs::parse_conv_flag_input(matches)?; let iflags = parseargs::parse_iflags(matches)?; let skip = parseargs::parse_skip_amt(&ibs, &iflags, matches)?; + let iseek = parseargs::parse_iseek_amt(&ibs, &iflags, matches)?; let count = parseargs::parse_count(&iflags, matches)?; let mut i = Self { @@ -73,7 +74,9 @@ impl Input { iflags, }; - if let Some(amt) = skip { + // The --skip and --iseek flags are additive. On a stream, they discard bytes. + let amt = skip.unwrap_or(0) + iseek.unwrap_or(0); + if amt > 0 { if let Err(e) = i.read_skip(amt) { if let io::ErrorKind::UnexpectedEof = e.kind() { show_error!("'standard input': cannot skip to specified offset"); @@ -132,6 +135,7 @@ impl Input { let cflags = parseargs::parse_conv_flag_input(matches)?; let iflags = parseargs::parse_iflags(matches)?; let skip = parseargs::parse_skip_amt(&ibs, &iflags, matches)?; + let iseek = parseargs::parse_iseek_amt(&ibs, &iflags, matches)?; let count = parseargs::parse_count(&iflags, matches)?; if let Some(fname) = matches.value_of(options::INFILE) { @@ -148,7 +152,9 @@ impl Input { .map_err_context(|| "failed to open input file".to_string())? }; - if let Some(amt) = skip { + // The --skip and --iseek flags are additive. On a file, they seek. + let amt = skip.unwrap_or(0) + iseek.unwrap_or(0); + if amt > 0 { src.seek(io::SeekFrom::Start(amt)) .map_err_context(|| "failed to seek in input file".to_string())?; } @@ -293,11 +299,14 @@ impl OutputTrait for Output { let cflags = parseargs::parse_conv_flag_output(matches)?; let oflags = parseargs::parse_oflags(matches)?; let seek = parseargs::parse_seek_amt(&obs, &oflags, matches)?; + let oseek = parseargs::parse_oseek_amt(&obs, &oflags, matches)?; let mut dst = io::stdout(); + // The --seek and --oseek flags are additive. + let amt = seek.unwrap_or(0) + oseek.unwrap_or(0); // stdout is not seekable, so we just write null bytes. - if let Some(amt) = seek { + if amt > 0 { io::copy(&mut io::repeat(0u8).take(amt as u64), &mut dst) .map_err_context(|| String::from("write error"))?; } @@ -509,6 +518,7 @@ impl OutputTrait for Output { let cflags = parseargs::parse_conv_flag_output(matches)?; let oflags = parseargs::parse_oflags(matches)?; let seek = parseargs::parse_seek_amt(&obs, &oflags, matches)?; + let oseek = parseargs::parse_oseek_amt(&obs, &oflags, matches)?; if let Some(fname) = matches.value_of(options::OUTFILE) { let mut dst = open_dst(Path::new(&fname), &cflags, &oflags) @@ -522,7 +532,9 @@ impl OutputTrait for Output { // 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`. - let i = seek.unwrap_or(0); + + // The --seek and --oseek flags are additive. + let i = seek.unwrap_or(0) + oseek.unwrap_or(0); if !cflags.notrunc { dst.set_len(i).ok(); } @@ -807,6 +819,24 @@ pub fn uu_app<'a>() -> App<'a> { .value_name("N") .help("(alternatively seek=N) seeks N obs-sized records into output before beginning copy/convert operations. See oflag=seek_bytes if seeking N bytes is preferred. Multiplier strings permitted.") ) + .arg( + Arg::new(options::ISEEK) + .long(options::ISEEK) + .overrides_with(options::ISEEK) + .takes_value(true) + .require_equals(true) + .value_name("N") + .help("(alternatively iseek=N) seeks N obs-sized records into input before beginning copy/convert operations. See iflag=seek_bytes if seeking N bytes is preferred. Multiplier strings permitted.") + ) + .arg( + Arg::new(options::OSEEK) + .long(options::OSEEK) + .overrides_with(options::OSEEK) + .takes_value(true) + .require_equals(true) + .value_name("N") + .help("(alternatively oseek=N) seeks N obs-sized records into output before beginning copy/convert operations. See oflag=seek_bytes if seeking N bytes is preferred. Multiplier strings permitted.") + ) .arg( Arg::new(options::COUNT) .long(options::COUNT) diff --git a/src/uu/dd/src/parseargs.rs b/src/uu/dd/src/parseargs.rs index d8639fca9..db2df4132 100644 --- a/src/uu/dd/src/parseargs.rs +++ b/src/uu/dd/src/parseargs.rs @@ -4,7 +4,7 @@ // // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. -// spell-checker:ignore ctty, ctable, iconvflags, oconvflags parseargs +// spell-checker:ignore ctty, ctable, iseek, oseek, iconvflags, oconvflags parseargs #[cfg(test)] mod unit_tests; @@ -776,6 +776,42 @@ pub fn parse_seek_amt( } } +/// Parse the amount of the input file to seek. +pub fn parse_iseek_amt( + ibs: &usize, + iflags: &IFlags, + matches: &Matches, +) -> Result, ParseError> { + if let Some(amt) = matches.value_of(options::ISEEK) { + let n = parse_bytes_with_opt_multiplier(amt)?; + if iflags.skip_bytes { + Ok(Some(n)) + } else { + Ok(Some(*ibs as u64 * n)) + } + } else { + Ok(None) + } +} + +/// Parse the amount of the input file to seek. +pub fn parse_oseek_amt( + obs: &usize, + oflags: &OFlags, + matches: &Matches, +) -> Result, ParseError> { + if let Some(amt) = matches.value_of(options::OSEEK) { + let n = parse_bytes_with_opt_multiplier(amt)?; + if oflags.seek_bytes { + Ok(Some(n)) + } else { + Ok(Some(*obs as u64 * n)) + } + } else { + Ok(None) + } +} + /// Parse the value of count=N and the type of N implied by iflags pub fn parse_count(iflags: &IFlags, matches: &Matches) -> Result, ParseError> { if let Some(amt) = matches.value_of(options::COUNT) { diff --git a/src/uu/dd/src/parseargs/unit_tests.rs b/src/uu/dd/src/parseargs/unit_tests.rs index 1e5b4b930..a29008c2c 100644 --- a/src/uu/dd/src/parseargs/unit_tests.rs +++ b/src/uu/dd/src/parseargs/unit_tests.rs @@ -1,4 +1,4 @@ -// spell-checker:ignore fname, tname, fpath, specfile, testfile, unspec, ifile, ofile, outfile, fullblock, urand, fileio, atoe, atoibm, behaviour, bmax, bremain, btotal, cflags, creat, ctable, ctty, datastructures, doesnt, etoa, fileout, fname, gnudd, iconvflags, nocache, noctty, noerror, nofollow, nolinks, nonblock, oconvflags, outfile, parseargs, rlen, rmax, rposition, rremain, rsofar, rstat, sigusr, sigval, wlen, wstat +// spell-checker:ignore fname, tname, fpath, specfile, testfile, unspec, ifile, ofile, outfile, fullblock, urand, fileio, atoe, atoibm, 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 use super::*; @@ -112,6 +112,8 @@ fn test_all_top_level_args_no_leading_dashes() { String::from("count=2"), String::from("skip=2"), String::from("seek=2"), + String::from("iseek=2"), + String::from("oseek=2"), String::from("status=progress"), String::from("conv=ascii,ucase"), String::from("iflag=count_bytes,skip_bytes"), @@ -150,6 +152,18 @@ fn test_all_top_level_args_no_leading_dashes() { .unwrap() .unwrap() ); + assert_eq!( + 200, + parse_iseek_amt(&100, &IFlags::default(), &matches) + .unwrap() + .unwrap() + ); + assert_eq!( + 200, + parse_oseek_amt(&100, &OFlags::default(), &matches) + .unwrap() + .unwrap() + ); assert_eq!( StatusLevel::Progress, parse_status_level(&matches).unwrap().unwrap() @@ -197,6 +211,8 @@ fn test_all_top_level_args_with_leading_dashes() { String::from("--count=2"), String::from("--skip=2"), String::from("--seek=2"), + String::from("--iseek=2"), + String::from("--oseek=2"), String::from("--status=progress"), String::from("--conv=ascii,ucase"), String::from("--iflag=count_bytes,skip_bytes"), @@ -235,6 +251,18 @@ fn test_all_top_level_args_with_leading_dashes() { .unwrap() .unwrap() ); + assert_eq!( + 200, + parse_iseek_amt(&100, &IFlags::default(), &matches) + .unwrap() + .unwrap() + ); + assert_eq!( + 200, + parse_oseek_amt(&100, &OFlags::default(), &matches) + .unwrap() + .unwrap() + ); assert_eq!( StatusLevel::Progress, parse_status_level(&matches).unwrap().unwrap() @@ -349,6 +377,10 @@ fn test_override_multiple_options() { String::from("--skip=2"), String::from("--seek=0"), String::from("--seek=2"), + String::from("--iseek=0"), + String::from("--iseek=2"), + String::from("--oseek=0"), + String::from("--oseek=2"), String::from("--status=none"), String::from("--status=noxfer"), String::from("--count=512"), @@ -394,6 +426,22 @@ fn test_override_multiple_options() { .unwrap() ); + // iseek + assert_eq!( + 200, + parse_iseek_amt(&100, &IFlags::default(), &matches) + .unwrap() + .unwrap() + ); + + // oseek + assert_eq!( + 200, + parse_oseek_amt(&100, &OFlags::default(), &matches) + .unwrap() + .unwrap() + ); + // count assert_eq!( CountType::Bytes(1024), diff --git a/tests/by-util/test_dd.rs b/tests/by-util/test_dd.rs index acf63bec7..62c420eb2 100644 --- a/tests/by-util/test_dd.rs +++ b/tests/by-util/test_dd.rs @@ -1,4 +1,4 @@ -// 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, nocache, noctty, noerror, nofollow, nolinks, nonblock, oconvflags, outfile, parseargs, rlen, rmax, rposition, rremain, rsofar, rstat, sigusr, sigval, wlen, wstat abcdefghijklm abcdefghi nabcde nabcdefg abcdefg +// 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 use crate::common::util::*; @@ -1139,3 +1139,48 @@ fn test_block_sync() { .stdout_is("012 abcde ") .stderr_is("2+1 records in\n0+1 records out\n1 truncated record\n"); } + +#[test] +fn test_bytes_count_bytes_iflag() { + new_ucmd!() + .args(&["conv=swab", "count=14", "iflag=count_bytes"]) + .pipe_in("0123456789abcdefghijklm") + .succeeds() + .stdout_is("1032547698badc"); +} + +#[test] +fn test_bytes_skip_bytes_iflag() { + new_ucmd!() + .args(&["skip=10", "iflag=skip_bytes"]) + .pipe_in("0123456789abcdefghijklm") + .succeeds() + .stdout_is("abcdefghijklm"); +} + +#[test] +fn test_bytes_skip_bytes_pipe_iflag() { + new_ucmd!() + .args(&["skip=10", "iflag=skip_bytes", "bs=2"]) + .pipe_in("0123456789abcdefghijklm") + .succeeds() + .stdout_is("abcdefghijklm"); +} + +#[test] +fn test_bytes_oseek_bytes_oflag() { + new_ucmd!() + .args(&["seek=8", "oflag=seek_bytes", "bs=2"]) + .pipe_in("abcdefghijklm") + .succeeds() + .stdout_is_fixture_bytes("dd-bytes-alphabet-null.spec"); +} + +#[test] +fn test_bytes_oseek_bytes_trunc_oflag() { + new_ucmd!() + .args(&["seek=8", "oflag=seek_bytes", "bs=2", "count=0"]) + .pipe_in("abcdefghijklm") + .succeeds() + .stdout_is_fixture_bytes("dd-bytes-null-trunc.spec"); +} diff --git a/tests/fixtures/dd/dd-bytes-alphabet-null.spec b/tests/fixtures/dd/dd-bytes-alphabet-null.spec new file mode 100644 index 000000000..1ab5429c1 Binary files /dev/null and b/tests/fixtures/dd/dd-bytes-alphabet-null.spec differ diff --git a/tests/fixtures/dd/dd-bytes-null-trunc.spec b/tests/fixtures/dd/dd-bytes-null-trunc.spec new file mode 100644 index 000000000..1b1cb4d44 Binary files /dev/null and b/tests/fixtures/dd/dd-bytes-null-trunc.spec differ