1
Fork 0
mirror of https://github.com/RGBCube/uutils-coreutils synced 2025-07-30 20:47:46 +00:00

Merge pull request #3256 from chordtoll/iseek-oseek

dd: implement iseek + oseek flags
This commit is contained in:
Sylvestre Ledru 2022-03-22 20:31:05 +01:00 committed by GitHub
commit 291b889d66
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 153 additions and 42 deletions

View file

@ -4,7 +4,7 @@
// //
// For the full copyright and license information, please view the LICENSE // For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code. // file that was distributed with this source code.
// spell-checker:ignore ctable, outfile // spell-checker:ignore ctable, outfile, iseek, oseek
use std::error::Error; use std::error::Error;
@ -120,6 +120,8 @@ pub mod options {
pub const COUNT: &str = "count"; pub const COUNT: &str = "count";
pub const SKIP: &str = "skip"; pub const SKIP: &str = "skip";
pub const SEEK: &str = "seek"; pub const SEEK: &str = "seek";
pub const ISEEK: &str = "iseek";
pub const OSEEK: &str = "oseek";
pub const STATUS: &str = "status"; pub const STATUS: &str = "status";
pub const CONV: &str = "conv"; pub const CONV: &str = "conv";
pub const IFLAG: &str = "iflag"; pub const IFLAG: &str = "iflag";

View file

@ -5,7 +5,7 @@
// For the full copyright and license information, please view the LICENSE // For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code. // 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; mod datastructures;
use datastructures::*; use datastructures::*;
@ -60,7 +60,9 @@ impl Input<io::Stdin> {
let print_level = parseargs::parse_status_level(matches)?; let print_level = parseargs::parse_status_level(matches)?;
let cflags = parseargs::parse_conv_flag_input(matches)?; let cflags = parseargs::parse_conv_flag_input(matches)?;
let iflags = parseargs::parse_iflags(matches)?; let iflags = parseargs::parse_iflags(matches)?;
let skip = parseargs::parse_skip_amt(&ibs, &iflags, matches)?; let skip = parseargs::parse_seek_skip_amt(&ibs, iflags.skip_bytes, matches, options::SKIP)?;
let iseek =
parseargs::parse_seek_skip_amt(&ibs, iflags.skip_bytes, matches, options::ISEEK)?;
let count = parseargs::parse_count(&iflags, matches)?; let count = parseargs::parse_count(&iflags, matches)?;
let mut i = Self { let mut i = Self {
@ -73,7 +75,9 @@ impl Input<io::Stdin> {
iflags, 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 Err(e) = i.read_skip(amt) {
if let io::ErrorKind::UnexpectedEof = e.kind() { if let io::ErrorKind::UnexpectedEof = e.kind() {
show_error!("'standard input': cannot skip to specified offset"); show_error!("'standard input': cannot skip to specified offset");
@ -131,7 +135,9 @@ impl Input<File> {
let print_level = parseargs::parse_status_level(matches)?; let print_level = parseargs::parse_status_level(matches)?;
let cflags = parseargs::parse_conv_flag_input(matches)?; let cflags = parseargs::parse_conv_flag_input(matches)?;
let iflags = parseargs::parse_iflags(matches)?; let iflags = parseargs::parse_iflags(matches)?;
let skip = parseargs::parse_skip_amt(&ibs, &iflags, matches)?; let skip = parseargs::parse_seek_skip_amt(&ibs, iflags.skip_bytes, matches, options::SKIP)?;
let iseek =
parseargs::parse_seek_skip_amt(&ibs, iflags.skip_bytes, matches, options::ISEEK)?;
let count = parseargs::parse_count(&iflags, matches)?; let count = parseargs::parse_count(&iflags, matches)?;
if let Some(fname) = matches.value_of(options::INFILE) { if let Some(fname) = matches.value_of(options::INFILE) {
@ -148,7 +154,9 @@ impl Input<File> {
.map_err_context(|| "failed to open input file".to_string())? .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)) src.seek(io::SeekFrom::Start(amt))
.map_err_context(|| "failed to seek in input file".to_string())?; .map_err_context(|| "failed to seek in input file".to_string())?;
} }
@ -292,12 +300,16 @@ impl OutputTrait for Output<io::Stdout> {
let obs = parseargs::parse_obs(matches)?; let obs = parseargs::parse_obs(matches)?;
let cflags = parseargs::parse_conv_flag_output(matches)?; let cflags = parseargs::parse_conv_flag_output(matches)?;
let oflags = parseargs::parse_oflags(matches)?; let oflags = parseargs::parse_oflags(matches)?;
let seek = parseargs::parse_seek_amt(&obs, &oflags, matches)?; let seek = parseargs::parse_seek_skip_amt(&obs, oflags.seek_bytes, matches, options::SEEK)?;
let oseek =
parseargs::parse_seek_skip_amt(&obs, oflags.seek_bytes, matches, options::OSEEK)?;
let mut dst = io::stdout(); 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. // 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) io::copy(&mut io::repeat(0u8).take(amt as u64), &mut dst)
.map_err_context(|| String::from("write error"))?; .map_err_context(|| String::from("write error"))?;
} }
@ -508,7 +520,9 @@ impl OutputTrait for Output<File> {
let obs = parseargs::parse_obs(matches)?; let obs = parseargs::parse_obs(matches)?;
let cflags = parseargs::parse_conv_flag_output(matches)?; let cflags = parseargs::parse_conv_flag_output(matches)?;
let oflags = parseargs::parse_oflags(matches)?; let oflags = parseargs::parse_oflags(matches)?;
let seek = parseargs::parse_seek_amt(&obs, &oflags, matches)?; let seek = parseargs::parse_seek_skip_amt(&obs, oflags.seek_bytes, matches, options::SEEK)?;
let oseek =
parseargs::parse_seek_skip_amt(&obs, oflags.seek_bytes, matches, options::OSEEK)?;
if let Some(fname) = matches.value_of(options::OUTFILE) { if let Some(fname) = matches.value_of(options::OUTFILE) {
let mut dst = open_dst(Path::new(&fname), &cflags, &oflags) let mut dst = open_dst(Path::new(&fname), &cflags, &oflags)
@ -522,7 +536,9 @@ impl OutputTrait for Output<File> {
// Instead, we suppress the error by calling // Instead, we suppress the error by calling
// `Result::ok()`. This matches the behavior of GNU `dd` // `Result::ok()`. This matches the behavior of GNU `dd`
// when given the command-line argument `of=/dev/null`. // 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 { if !cflags.notrunc {
dst.set_len(i).ok(); dst.set_len(i).ok();
} }
@ -807,6 +823,24 @@ pub fn uu_app<'a>() -> Command<'a> {
.value_name("N") .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.") .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(
Arg::new(options::COUNT) Arg::new(options::COUNT)
.long(options::COUNT) .long(options::COUNT)

View file

@ -4,7 +4,7 @@
// //
// For the full copyright and license information, please view the LICENSE // For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code. // 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)] #[cfg(test)]
mod unit_tests; mod unit_tests;
@ -740,15 +740,15 @@ pub fn parse_oflags(matches: &Matches) -> Result<OFlags, ParseError> {
Ok(oflags) Ok(oflags)
} }
/// Parse the amount of the input file to skip. pub fn parse_seek_skip_amt(
pub fn parse_skip_amt(
ibs: &usize, ibs: &usize,
iflags: &IFlags, bytes: bool,
matches: &Matches, matches: &Matches,
option: &str,
) -> Result<Option<u64>, ParseError> { ) -> Result<Option<u64>, ParseError> {
if let Some(amt) = matches.value_of(options::SKIP) { if let Some(amt) = matches.value_of(option) {
let n = parse_bytes_with_opt_multiplier(amt)?; let n = parse_bytes_with_opt_multiplier(amt)?;
if iflags.skip_bytes { if bytes {
Ok(Some(n)) Ok(Some(n))
} else { } else {
Ok(Some(*ibs as u64 * n)) Ok(Some(*ibs as u64 * n))
@ -758,24 +758,6 @@ pub fn parse_skip_amt(
} }
} }
/// Parse the amount of the output file to seek.
pub fn parse_seek_amt(
obs: &usize,
oflags: &OFlags,
matches: &Matches,
) -> Result<Option<u64>, ParseError> {
if let Some(amt) = matches.value_of(options::SEEK) {
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 /// Parse the value of count=N and the type of N implied by iflags
pub fn parse_count(iflags: &IFlags, matches: &Matches) -> Result<Option<CountType>, ParseError> { pub fn parse_count(iflags: &IFlags, matches: &Matches) -> Result<Option<CountType>, ParseError> {
if let Some(amt) = matches.value_of(options::COUNT) { if let Some(amt) = matches.value_of(options::COUNT) {

View file

@ -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::*; use super::*;
@ -112,6 +112,8 @@ fn test_all_top_level_args_no_leading_dashes() {
String::from("count=2"), String::from("count=2"),
String::from("skip=2"), String::from("skip=2"),
String::from("seek=2"), String::from("seek=2"),
String::from("iseek=2"),
String::from("oseek=2"),
String::from("status=progress"), String::from("status=progress"),
String::from("conv=ascii,ucase"), String::from("conv=ascii,ucase"),
String::from("iflag=count_bytes,skip_bytes"), String::from("iflag=count_bytes,skip_bytes"),
@ -140,13 +142,25 @@ fn test_all_top_level_args_no_leading_dashes() {
); );
assert_eq!( assert_eq!(
200, 200,
parse_skip_amt(&100, &IFlags::default(), &matches) parse_seek_skip_amt(&100, IFlags::default().skip_bytes, &matches, options::SKIP)
.unwrap() .unwrap()
.unwrap() .unwrap()
); );
assert_eq!( assert_eq!(
200, 200,
parse_seek_amt(&100, &OFlags::default(), &matches) parse_seek_skip_amt(&100, OFlags::default().seek_bytes, &matches, options::SEEK)
.unwrap()
.unwrap()
);
assert_eq!(
200,
parse_seek_skip_amt(&100, IFlags::default().skip_bytes, &matches, options::ISEEK)
.unwrap()
.unwrap()
);
assert_eq!(
200,
parse_seek_skip_amt(&100, OFlags::default().seek_bytes, &matches, options::OSEEK)
.unwrap() .unwrap()
.unwrap() .unwrap()
); );
@ -197,6 +211,8 @@ fn test_all_top_level_args_with_leading_dashes() {
String::from("--count=2"), String::from("--count=2"),
String::from("--skip=2"), String::from("--skip=2"),
String::from("--seek=2"), String::from("--seek=2"),
String::from("--iseek=2"),
String::from("--oseek=2"),
String::from("--status=progress"), String::from("--status=progress"),
String::from("--conv=ascii,ucase"), String::from("--conv=ascii,ucase"),
String::from("--iflag=count_bytes,skip_bytes"), String::from("--iflag=count_bytes,skip_bytes"),
@ -225,13 +241,25 @@ fn test_all_top_level_args_with_leading_dashes() {
); );
assert_eq!( assert_eq!(
200, 200,
parse_skip_amt(&100, &IFlags::default(), &matches) parse_seek_skip_amt(&100, IFlags::default().skip_bytes, &matches, options::SKIP)
.unwrap() .unwrap()
.unwrap() .unwrap()
); );
assert_eq!( assert_eq!(
200, 200,
parse_seek_amt(&100, &OFlags::default(), &matches) parse_seek_skip_amt(&100, OFlags::default().seek_bytes, &matches, options::SEEK)
.unwrap()
.unwrap()
);
assert_eq!(
200,
parse_seek_skip_amt(&100, IFlags::default().skip_bytes, &matches, options::ISEEK)
.unwrap()
.unwrap()
);
assert_eq!(
200,
parse_seek_skip_amt(&100, OFlags::default().seek_bytes, &matches, options::OSEEK)
.unwrap() .unwrap()
.unwrap() .unwrap()
); );
@ -349,6 +377,10 @@ fn test_override_multiple_options() {
String::from("--skip=2"), String::from("--skip=2"),
String::from("--seek=0"), String::from("--seek=0"),
String::from("--seek=2"), 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=none"),
String::from("--status=noxfer"), String::from("--status=noxfer"),
String::from("--count=512"), String::from("--count=512"),
@ -381,7 +413,7 @@ fn test_override_multiple_options() {
// skip // skip
assert_eq!( assert_eq!(
200, 200,
parse_skip_amt(&100, &IFlags::default(), &matches) parse_seek_skip_amt(&100, IFlags::default().skip_bytes, &matches, options::SKIP)
.unwrap() .unwrap()
.unwrap() .unwrap()
); );
@ -389,7 +421,23 @@ fn test_override_multiple_options() {
// seek // seek
assert_eq!( assert_eq!(
200, 200,
parse_seek_amt(&100, &OFlags::default(), &matches) parse_seek_skip_amt(&100, OFlags::default().seek_bytes, &matches, options::SEEK)
.unwrap()
.unwrap()
);
// iseek
assert_eq!(
200,
parse_seek_skip_amt(&100, IFlags::default().skip_bytes, &matches, options::ISEEK)
.unwrap()
.unwrap()
);
// oseek
assert_eq!(
200,
parse_seek_skip_amt(&100, OFlags::default().seek_bytes, &matches, options::OSEEK)
.unwrap() .unwrap()
.unwrap() .unwrap()
); );

View file

@ -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::*; use crate::common::util::*;
@ -1139,3 +1139,48 @@ fn test_block_sync() {
.stdout_is("012 abcde ") .stdout_is("012 abcde ")
.stderr_is("2+1 records in\n0+1 records out\n1 truncated record\n"); .stderr_is("2+1 records in\n0+1 records out\n1 truncated record\n");
} }
#[test]
fn test_bytes_iseek_bytes_iflag() {
new_ucmd!()
.args(&["iseek=10", "iflag=skip_bytes", "bs=2"])
.pipe_in("0123456789abcdefghijklm")
.succeeds()
.stdout_is("abcdefghijklm");
}
#[test]
fn test_bytes_iseek_skip_additive() {
new_ucmd!()
.args(&["iseek=5", "skip=5", "iflag=skip_bytes", "bs=2"])
.pipe_in("0123456789abcdefghijklm")
.succeeds()
.stdout_is("abcdefghijklm");
}
#[test]
fn test_bytes_oseek_bytes_oflag() {
new_ucmd!()
.args(&["oseek=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(&["oseek=8", "oflag=seek_bytes", "bs=2", "count=0"])
.pipe_in("abcdefghijklm")
.succeeds()
.stdout_is_fixture_bytes("dd-bytes-null-trunc.spec");
}
#[test]
fn test_bytes_oseek_seek_additive() {
new_ucmd!()
.args(&["oseek=4", "seek=4", "oflag=seek_bytes", "bs=2"])
.pipe_in("abcdefghijklm")
.succeeds()
.stdout_is_fixture_bytes("dd-bytes-alphabet-null.spec");
}

Binary file not shown.

Binary file not shown.