diff --git a/Cargo.toml b/Cargo.toml index 19083c150..c3819560e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -153,7 +153,6 @@ feat_require_unix = [ "chmod", "chown", "chroot", - "dd", "groups", "hostid", "id", diff --git a/src/uu/dd/src/conversion_tables.rs b/src/uu/dd/src/conversion_tables.rs index 61c36bc04..aca2ef9bc 100644 --- a/src/uu/dd/src/conversion_tables.rs +++ b/src/uu/dd/src/conversion_tables.rs @@ -5,8 +5,6 @@ // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. -/* cspell:disable */ - // Note: Conversion tables are just lookup tables. // eg. The ASCII->EBCDIC table stores the EBCDIC code at the index // obtained by treating the ASCII representation as a number. diff --git a/src/uu/dd/src/datastructures.rs b/src/uu/dd/src/datastructures.rs index 59e770249..6f2b16d67 100644 --- a/src/uu/dd/src/datastructures.rs +++ b/src/uu/dd/src/datastructures.rs @@ -5,23 +5,18 @@ // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. -/* cspell:disable */ - use crate::conversion_tables::*; use std::error::Error; use std::time; pub struct ProgUpdate { - pub reads_complete: u64, - pub reads_partial: u64, - pub writes_complete: u64, - pub writes_partial: u64, - pub bytes_total: u128, - pub records_truncated: u32, + pub read_stat: ReadStat, + pub write_stat: WriteStat, pub duration: time::Duration, } +#[derive(Clone, Copy, Default)] pub struct ReadStat { pub reads_complete: u64, pub reads_partial: u64, @@ -37,6 +32,7 @@ impl std::ops::AddAssign for ReadStat { } } +#[derive(Clone, Copy)] pub struct WriteStat { pub writes_complete: u64, pub writes_partial: u64, @@ -55,6 +51,7 @@ impl std::ops::AddAssign for WriteStat { type Cbs = usize; /// Stores all Conv Flags that apply to the input +#[derive(Debug, Default, PartialEq)] pub struct IConvFlags { pub ctable: Option<&'static ConversionTable>, pub block: Option, @@ -65,7 +62,7 @@ pub struct IConvFlags { } /// Stores all Conv Flags that apply to the output -#[derive(Debug, PartialEq)] +#[derive(Debug, Default, PartialEq)] pub struct OConvFlags { pub sparse: bool, pub excl: bool, @@ -76,32 +73,20 @@ pub struct OConvFlags { } /// Stores all Flags that apply to the input +#[derive(Debug, Default, PartialEq)] pub struct IFlags { - #[allow(dead_code)] pub cio: bool, - #[allow(dead_code)] pub direct: bool, - #[allow(dead_code)] pub directory: bool, - #[allow(dead_code)] pub dsync: bool, - #[allow(dead_code)] pub sync: bool, - #[allow(dead_code)] pub nocache: bool, - #[allow(dead_code)] pub nonblock: bool, - #[allow(dead_code)] pub noatime: bool, - #[allow(dead_code)] pub noctty: bool, - #[allow(dead_code)] pub nofollow: bool, - #[allow(dead_code)] pub nolinks: bool, - #[allow(dead_code)] pub binary: bool, - #[allow(dead_code)] pub text: bool, pub fullblock: bool, pub count_bytes: bool, @@ -109,33 +94,21 @@ pub struct IFlags { } /// Stores all Flags that apply to the output +#[derive(Debug, Default, PartialEq)] pub struct OFlags { pub append: bool, - #[allow(dead_code)] pub cio: bool, - #[allow(dead_code)] pub direct: bool, - #[allow(dead_code)] pub directory: bool, - #[allow(dead_code)] pub dsync: bool, - #[allow(dead_code)] pub sync: bool, - #[allow(dead_code)] pub nocache: bool, - #[allow(dead_code)] pub nonblock: bool, - #[allow(dead_code)] pub noatime: bool, - #[allow(dead_code)] pub noctty: bool, - #[allow(dead_code)] pub nofollow: bool, - #[allow(dead_code)] pub nolinks: bool, - #[allow(dead_code)] pub binary: bool, - #[allow(dead_code)] pub text: bool, pub seek_bytes: bool, } @@ -153,6 +126,7 @@ pub enum StatusLevel { /// Defaults to Reads(N) /// if iflag=count_bytes /// then becomes Bytes(N) +#[derive(Debug, PartialEq)] pub enum CountType { Reads(usize), Bytes(usize), diff --git a/src/uu/dd/src/dd.rs b/src/uu/dd/src/dd.rs index 2b44a47d8..712e10e1e 100644 --- a/src/uu/dd/src/dd.rs +++ b/src/uu/dd/src/dd.rs @@ -5,8 +5,6 @@ // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. -/* cspell:disable */ - #[macro_use] extern crate uucore; use uucore::InvalidEncodingHandling; @@ -25,7 +23,6 @@ use conversion_tables::*; use byte_unit::Byte; use clap::{self, crate_version}; -use debug_print::debug_println; use gcd::Gcd; use signal_hook::consts::signal; use std::cmp; @@ -51,7 +48,7 @@ struct Input { src: R, non_ascii: bool, ibs: usize, - xfer_stats: Option, + print_level: Option, count: Option, cflags: IConvFlags, iflags: IFlags, @@ -61,7 +58,7 @@ impl Input { fn new(matches: &Matches) -> Result> { let ibs = parseargs::parse_ibs(matches)?; let non_ascii = parseargs::parse_input_non_ascii(matches)?; - let xfer_stats = parseargs::parse_status_level(matches)?; + let print_level = parseargs::parse_status_level(matches)?; let cflags = parseargs::parse_conv_flag_input(matches)?; let iflags = parseargs::parse_iflags(matches)?; let skip = parseargs::parse_skip_amt(&ibs, &iflags, matches)?; @@ -71,7 +68,7 @@ impl Input { src: io::stdin(), non_ascii, ibs, - xfer_stats, + print_level, count, cflags, iflags, @@ -88,31 +85,31 @@ impl Input { } #[cfg(target_os = "linux")] -fn make_linux_iflags(oflags: &IFlags) -> Option { +fn make_linux_iflags(iflags: &IFlags) -> Option { let mut flag = 0; - if oflags.direct { + if iflags.direct { flag |= libc::O_DIRECT; } - if oflags.directory { + if iflags.directory { flag |= libc::O_DIRECTORY; } - if oflags.dsync { + if iflags.dsync { flag |= libc::O_DSYNC; } - if oflags.noatime { + if iflags.noatime { flag |= libc::O_NOATIME; } - if oflags.noctty { + if iflags.noctty { flag |= libc::O_NOCTTY; } - if oflags.nofollow { + if iflags.nofollow { flag |= libc::O_NOFOLLOW; } - if oflags.nonblock { + if iflags.nonblock { flag |= libc::O_NONBLOCK; } - if oflags.sync { + if iflags.sync { flag |= libc::O_SYNC; } @@ -127,13 +124,13 @@ impl Input { fn new(matches: &Matches) -> Result> { let ibs = parseargs::parse_ibs(matches)?; let non_ascii = parseargs::parse_input_non_ascii(matches)?; - let xfer_stats = parseargs::parse_status_level(matches)?; + let print_level = parseargs::parse_status_level(matches)?; let cflags = parseargs::parse_conv_flag_input(matches)?; let iflags = parseargs::parse_iflags(matches)?; let skip = parseargs::parse_skip_amt(&ibs, &iflags, matches)?; let count = parseargs::parse_count(&iflags, matches)?; - if let Some(fname) = matches.value_of("if") { + if let Some(fname) = matches.value_of(options::INFILE) { let mut src = { let mut opts = OpenOptions::new(); opts.read(true); @@ -155,7 +152,7 @@ impl Input { src, non_ascii, ibs, - xfer_stats, + print_level, count, cflags, iflags, @@ -198,28 +195,27 @@ impl Input { fn fill_consecutive(&mut self, buf: &mut Vec) -> Result> { let mut reads_complete = 0; let mut reads_partial = 0; - let mut base_idx = 0; + let mut bytes_total = 0; - while base_idx < buf.len() { - let next_blk = cmp::min(base_idx + self.ibs, buf.len()); - - match self.read(&mut buf[base_idx..next_blk])? { + for chunk in buf.chunks_mut(self.ibs) { + match self.read(chunk)? { rlen if rlen == self.ibs => { - base_idx += rlen; + bytes_total += rlen; reads_complete += 1; } rlen if rlen > 0 => { - base_idx += rlen; + bytes_total += rlen; reads_partial += 1; } _ => break, } } - buf.truncate(base_idx); + buf.truncate(bytes_total); Ok(ReadStat { reads_complete, reads_partial, + // Records are not truncated when filling. records_truncated: 0, }) } @@ -234,35 +230,19 @@ impl Input { while base_idx < buf.len() { let next_blk = cmp::min(base_idx + self.ibs, buf.len()); - let plen = next_blk - base_idx; + let target_len = next_blk - base_idx; match self.read(&mut buf[base_idx..next_blk])? { 0 => break, - rlen if rlen < plen => { + rlen if rlen < target_len => { reads_partial += 1; - let padding = vec![pad; plen - rlen]; + let padding = vec![pad; target_len - rlen]; buf.splice(base_idx + rlen..next_blk, padding.into_iter()); } _ => { reads_complete += 1; } } - // TODO: Why does this cause the conv=sync tests to hang? - // let rlen = self.read(&mut buf[base_idx..next_blk])?; - // if rlen < plen - // { - // reads_partial += 1; - // let padding = vec![pad; plen-rlen]; - // buf.splice(base_idx+rlen..next_blk, padding.into_iter()); - // } - // else - // { - // reads_complete += 1; - // } - // if rlen == 0 - // { - // break; - // } base_idx += self.ibs; } @@ -356,11 +336,7 @@ fn make_linux_oflags(oflags: &OFlags) -> Option { impl Output { fn new(matches: &Matches) -> Result> { - fn open_dst( - path: &Path, - cflags: &OConvFlags, - oflags: &OFlags, - ) -> Result> { + fn open_dst(path: &Path, cflags: &OConvFlags, oflags: &OFlags) -> Result { let mut opts = OpenOptions::new(); opts.write(true) .create(!cflags.nocreat) @@ -373,15 +349,14 @@ impl Output { opts.custom_flags(libc_flags); } - let dst = opts.open(path)?; - Ok(dst) + opts.open(path) } let obs = parseargs::parse_obs(matches)?; let cflags = parseargs::parse_conv_flag_output(matches)?; let oflags = parseargs::parse_oflags(matches)?; let seek = parseargs::parse_seek_amt(&obs, &oflags, matches)?; - if let Some(fname) = matches.value_of("of") { + if let Some(fname) = matches.value_of(options::OUTFILE) { let mut dst = open_dst(Path::new(&fname), &cflags, &oflags)?; if let Some(amt) = seek { @@ -418,7 +393,6 @@ impl Seek for Output { impl Write for Output { fn write(&mut self, buf: &[u8]) -> io::Result { - #[inline] fn is_sparse(buf: &[u8]) -> bool { buf.iter().all(|&e| e == 0u8) } @@ -454,20 +428,17 @@ impl Output { fn write_blocks(&mut self, buf: Vec) -> io::Result { let mut writes_complete = 0; let mut writes_partial = 0; - let mut base_idx = 0; + let mut bytes_total = 0; - while base_idx < buf.len() { - let next_blk = cmp::min(base_idx + self.obs, buf.len()); - let plen = next_blk - base_idx; - - match self.write(&buf[base_idx..next_blk])? { - wlen if wlen < plen => { + for chunk in buf.chunks(self.obs) { + match self.write(chunk)? { + wlen if wlen < chunk.len() => { writes_partial += 1; - base_idx += wlen; + bytes_total += wlen; } wlen => { writes_complete += 1; - base_idx += wlen; + bytes_total += wlen; } } } @@ -475,7 +446,7 @@ impl Output { Ok(WriteStat { writes_complete, writes_partial, - bytes_total: base_idx.try_into().unwrap_or(0u128), + bytes_total: bytes_total.try_into().unwrap_or(0u128), }) } } @@ -484,20 +455,17 @@ impl Output { fn write_blocks(&mut self, buf: Vec) -> io::Result { let mut writes_complete = 0; let mut writes_partial = 0; - let mut base_idx = 0; + let mut bytes_total = 0; - while base_idx < buf.len() { - let next_blk = cmp::min(base_idx + self.obs, buf.len()); - let plen = next_blk - base_idx; - - match self.write(&buf[base_idx..next_blk])? { - wlen if wlen < plen => { + for chunk in buf.chunks(self.obs) { + match self.write(chunk)? { + wlen if wlen < chunk.len() => { writes_partial += 1; - base_idx += wlen; + bytes_total += wlen; } wlen => { writes_complete += 1; - base_idx += wlen; + bytes_total += wlen; } } } @@ -505,7 +473,7 @@ impl Output { Ok(WriteStat { writes_complete, writes_partial, - bytes_total: base_idx.try_into().unwrap_or(0u128), + bytes_total: bytes_total.try_into().unwrap_or(0u128), }) } } @@ -538,47 +506,18 @@ fn block(buf: Vec, cbs: usize, rstat: &mut ReadStat) -> Vec> { /// Trims padding from each cbs-length partition of buf /// as specified by conv=unblock and cbs=N fn unblock(buf: Vec, cbs: usize) -> Vec { - // Local Helper Fns ---------------------------------------------------- - #[inline] - fn build_blocks(buf: Vec, cbs: usize) -> Vec> { - let mut blocks = Vec::new(); - let mut curr = buf; - let mut next; - let mut width; + buf.chunks(cbs).fold(Vec::new(), |mut acc, block| { + if let Some(last_char_idx) = block.iter().rposition(|&e| e != b' ') { + // Find last space + acc.extend(&block[..=last_char_idx]); + acc.push(b'\n'); + } else { + // The block is filled with only spaces + acc.push(b'\n'); + }; - while !curr.is_empty() { - width = cmp::min(cbs, curr.len()); - next = curr.split_off(width); - - blocks.push(curr); - - curr = next; - } - - blocks - } - // --------------------------------------------------------------------- - build_blocks(buf, cbs) - .into_iter() - .fold(Vec::new(), |mut unblocks, mut block| { - let block = if let Some(last_char_idx) = block.iter().rposition(|&e| e != b' ') { - block.truncate(last_char_idx + 1); - block.push(b'\n'); - - block - } else if let Some(b' ') = block.get(0) { - vec![b'\n'] - } else { - block - }; - - unblocks.push(block); - - unblocks - }) - .into_iter() - .flatten() - .collect() + acc + }) } fn conv_block_unblock_helper( @@ -587,19 +526,15 @@ fn conv_block_unblock_helper( rstat: &mut ReadStat, ) -> Result, Box> { // Local Predicate Fns ------------------------------------------------- - #[inline] fn should_block_then_conv(i: &Input) -> bool { !i.non_ascii && i.cflags.block.is_some() } - #[inline] fn should_conv_then_block(i: &Input) -> bool { i.non_ascii && i.cflags.block.is_some() } - #[inline] fn should_unblock_then_conv(i: &Input) -> bool { !i.non_ascii && i.cflags.unblock.is_some() } - #[inline] fn should_conv_then_unblock(i: &Input) -> bool { i.non_ascii && i.cflags.unblock.is_some() } @@ -607,8 +542,7 @@ fn conv_block_unblock_helper( i.cflags.ctable.is_some() && i.cflags.block.is_none() && i.cflags.unblock.is_none() } // Local Helper Fns ---------------------------------------------------- - #[inline] - fn apply_ct(buf: &mut [u8], ct: &ConversionTable) { + fn apply_conversion(buf: &mut [u8], ct: &ConversionTable) { for idx in 0..buf.len() { buf[idx] = ct[buf[idx] as usize]; } @@ -617,7 +551,7 @@ fn conv_block_unblock_helper( if conv_only(i) { // no block/unblock let ct = i.cflags.ctable.unwrap(); - apply_ct(&mut buf, ct); + apply_conversion(&mut buf, ct); Ok(buf) } else if should_block_then_conv(i) { @@ -628,7 +562,7 @@ fn conv_block_unblock_helper( if let Some(ct) = i.cflags.ctable { for buf in blocks.iter_mut() { - apply_ct(buf, ct); + apply_conversion(buf, ct); } } @@ -640,7 +574,7 @@ fn conv_block_unblock_helper( let cbs = i.cflags.block.unwrap(); if let Some(ct) = i.cflags.ctable { - apply_ct(&mut buf, ct); + apply_conversion(&mut buf, ct); } let blocks = block(buf, cbs, rstat).into_iter().flatten().collect(); @@ -653,7 +587,7 @@ fn conv_block_unblock_helper( let mut buf = unblock(buf, cbs); if let Some(ct) = i.cflags.ctable { - apply_ct(&mut buf, ct); + apply_conversion(&mut buf, ct); } Ok(buf) @@ -662,7 +596,7 @@ fn conv_block_unblock_helper( let cbs = i.cflags.unblock.unwrap(); if let Some(ct) = i.cflags.ctable { - apply_ct(&mut buf, ct); + apply_conversion(&mut buf, ct); } let buf = unblock(buf, cbs); @@ -683,27 +617,19 @@ fn read_helper( bsize: usize, ) -> Result<(ReadStat, Vec), Box> { // Local Predicate Fns ----------------------------------------------- - #[inline] fn is_conv(i: &Input) -> bool { i.cflags.ctable.is_some() } - #[inline] fn is_block(i: &Input) -> bool { i.cflags.block.is_some() } - #[inline] fn is_unblock(i: &Input) -> bool { i.cflags.unblock.is_some() } // Local Helper Fns ------------------------------------------------- - #[inline] fn perform_swab(buf: &mut [u8]) { - let mut tmp; - for base in (1..buf.len()).step_by(2) { - tmp = buf[base]; - buf[base] = buf[base - 1]; - buf[base - 1] = tmp; + buf.swap(base, base - 1); } } // ------------------------------------------------------------------ @@ -733,35 +659,35 @@ fn read_helper( fn print_io_lines(update: &ProgUpdate) { eprintln!( "{}+{} records in", - update.reads_complete, update.reads_partial + update.read_stat.reads_complete, update.read_stat.reads_partial ); - if update.records_truncated > 0 { - eprintln!("{} truncated records", update.records_truncated); + if update.read_stat.records_truncated > 0 { + eprintln!("{} truncated records", update.read_stat.records_truncated); } eprintln!( "{}+{} records out", - update.writes_complete, update.writes_partial + update.write_stat.writes_complete, update.write_stat.writes_partial ); } fn make_prog_line(update: &ProgUpdate) -> String { - let btotal_metric = Byte::from_bytes(update.bytes_total) + let btotal_metric = Byte::from_bytes(update.write_stat.bytes_total) .get_appropriate_unit(false) .format(0); - let btotal_bin = Byte::from_bytes(update.bytes_total) + let btotal_bin = Byte::from_bytes(update.write_stat.bytes_total) .get_appropriate_unit(true) .format(0); let safe_millis = cmp::max(1, update.duration.as_millis()); - let xfer_rate = Byte::from_bytes(1000 * (update.bytes_total / safe_millis)) + let transfer_rate = Byte::from_bytes(1000 * (update.write_stat.bytes_total / safe_millis)) .get_appropriate_unit(false) .format(1); format!( "{} bytes ({}, {}) copied, {:.1} s, {}/s", - update.bytes_total, + update.write_stat.bytes_total, btotal_metric, btotal_bin, update.duration.as_secs_f64(), - xfer_rate + transfer_rate ) } fn reprint_prog_line(update: &ProgUpdate) { @@ -770,65 +696,60 @@ fn reprint_prog_line(update: &ProgUpdate) { fn print_prog_line(update: &ProgUpdate) { eprintln!("{}", make_prog_line(update)); } -fn print_xfer_stats(update: &ProgUpdate) { +fn print_transfer_stats(update: &ProgUpdate) { print_io_lines(update); print_prog_line(update); } -/// Generate a progress updater that tracks progress, receives updates, and responds to signals. -fn gen_prog_updater(rx: mpsc::Receiver, xfer_stats: Option) -> impl Fn() { +/// Generate a progress updater that tracks progress, receives updates, and responds to progress update requests (signals). +fn gen_prog_updater(rx: mpsc::Receiver, print_level: Option) -> impl Fn() { + // -------------------------------------------------------------- + #[cfg(target_os = "linux")] + const SIGUSR1_USIZE: usize = signal::SIGUSR1 as usize; // -------------------------------------------------------------- fn posixly_correct() -> bool { env::var("POSIXLY_CORRECT").is_ok() } + fn register_signal_handlers(sigval: Arc) -> Result<(), Box> { + #[cfg(target_os = "linux")] + if !posixly_correct() { + signal_hook::flag::register_usize(signal::SIGUSR1, sigval, SIGUSR1_USIZE)?; + } + + Ok(()) + } // -------------------------------------------------------------- move || { - const SIGUSR1_USIZE: usize = signal::SIGUSR1 as usize; - let sigval = Arc::new(AtomicUsize::new(0)); - // TODO: SIGINFO seems to only exist for BSD (and therefore MACOS) - // I will probably want put this behind a feature-gate and may need to pass the value to handle as my own constant. - // This may involve some finagling with the signals library. - // see -> https://unix.stackexchange.com/questions/179481/siginfo-on-gnu-linux-arch-linux-missing - // if let Err(e) = signal_hook::flag::register_usize(signal::SIGINFO, sigval.clone(), signal::SIGINFO as usize) - // { - // debug_println!("Internal dd Warning: Unable to register SIGINFO handler \n\t{}", e); - // } - if !posixly_correct() { - if let Err(e) = - signal_hook::flag::register_usize(signal::SIGUSR1, sigval.clone(), SIGUSR1_USIZE) - { - debug_println!( - "Internal dd Warning: Unable to register SIGUSR1 handler \n\t{}", + register_signal_handlers(sigval.clone()).unwrap_or_else(|e| { + if Some(StatusLevel::None) != print_level { + eprintln!( + "Internal dd Warning: Unable to register signal handler \n\t{}", e ); } - } + }); loop { // Wait for update - let update = match (rx.recv(), xfer_stats) { + let update = match (rx.recv(), print_level) { (Ok(update), Some(StatusLevel::Progress)) => { reprint_prog_line(&update); update } (Ok(update), _) => update, - (Err(_), _) => - // recv only fails permanently - { - break + (Err(_), _) => { + // recv only fails permanently, so we break here to + // avoid recv'ing on a broken pipe + break; } }; // Handle signals - #[allow(clippy::single_match)] - match sigval.load(Ordering::Relaxed) { - SIGUSR1_USIZE => { - print_xfer_stats(&update); - } - // SIGINFO_USIZE => ... - _ => { /* no signals recv'd */ } + #[cfg(target_os = "linux")] + if let SIGUSR1_USIZE = sigval.load(Ordering::Relaxed) { + print_transfer_stats(&update); }; } } @@ -840,7 +761,6 @@ fn gen_prog_updater(rx: mpsc::Receiver, xfer_stats: Option usize { let gcd = Gcd::gcd(ibs, obs); // calculate the lcm from gcd @@ -878,12 +798,10 @@ fn below_count_limit(count: &Option, rstat: &ReadStat, wstat: &WriteS match count { Some(CountType::Reads(n)) => { let n = (*n).try_into().unwrap(); - // debug_assert!(rstat.reads_complete + rstat.reads_partial >= n); rstat.reads_complete + rstat.reads_partial <= n } Some(CountType::Bytes(n)) => { let n = (*n).try_into().unwrap(); - // debug_assert!(wstat.bytes_total >= n); wstat.bytes_total <= n } None => true, @@ -891,8 +809,8 @@ fn below_count_limit(count: &Option, rstat: &ReadStat, wstat: &WriteS } /// Perform the copy/convert operations. Stdout version -// Note: Some of dd's functionality depends on whether the output is actually a file. This breaks the Output abstraction, -// and should be fixed in the future. +/// Note: The body of this function should be kept identical to dd_fileout. This is definitely a problem from a maintenance perspective +/// and should be addressed (TODO). The problem exists because some of dd's functionality depends on whether the output is a file or stdout. fn dd_stdout(mut i: Input, mut o: Output) -> Result<(), Box> { let mut rstat = ReadStat { reads_complete: 0, @@ -909,7 +827,7 @@ fn dd_stdout(mut i: Input, mut o: Output) -> Result<(), let prog_tx = { let (tx, rx) = mpsc::channel(); - thread::spawn(gen_prog_updater(rx, i.xfer_stats)); + thread::spawn(gen_prog_updater(rx, i.print_level)); tx }; @@ -934,12 +852,8 @@ fn dd_stdout(mut i: Input, mut o: Output) -> Result<(), }; // Update Prog prog_tx.send(ProgUpdate { - reads_complete: rstat.reads_complete, - reads_partial: rstat.reads_partial, - writes_complete: wstat.writes_complete, - writes_partial: wstat.writes_partial, - bytes_total: wstat.bytes_total, - records_truncated: rstat.records_truncated, + read_stat: rstat, + write_stat: wstat, duration: start.elapsed(), })?; } @@ -950,15 +864,11 @@ fn dd_stdout(mut i: Input, mut o: Output) -> Result<(), o.fdatasync()?; } - match i.xfer_stats { + match i.print_level { Some(StatusLevel::Noxfer) | Some(StatusLevel::None) => {} - _ => print_xfer_stats(&ProgUpdate { - reads_complete: rstat.reads_complete, - reads_partial: rstat.reads_partial, - writes_complete: wstat.writes_complete, - writes_partial: wstat.writes_partial, - bytes_total: wstat.bytes_total, - records_truncated: rstat.records_truncated, + _ => print_transfer_stats(&ProgUpdate { + read_stat: rstat, + write_stat: wstat, duration: start.elapsed(), }), } @@ -966,8 +876,8 @@ fn dd_stdout(mut i: Input, mut o: Output) -> Result<(), } /// Perform the copy/convert operations. File backed output version -// Note: Some of dd's functionality depends on whether the output is actually a file. This breaks the Output abstraction, -// and should be fixed in the future. +/// Note: The body of this function should be kept identical to dd_stdout. This is definitely a problem from a maintenance perspective +/// and should be addressed (TODO). The problem exists because some of dd's functionality depends on whether the output is a file or stdout. fn dd_fileout(mut i: Input, mut o: Output) -> Result<(), Box> { let mut rstat = ReadStat { reads_complete: 0, @@ -984,7 +894,7 @@ fn dd_fileout(mut i: Input, mut o: Output) -> Result<(), Box(mut i: Input, mut o: Output) -> Result<(), Box(mut i: Input, mut o: Output) -> Result<(), Box {} - _ => print_xfer_stats(&ProgUpdate { - reads_complete: rstat.reads_complete, - reads_partial: rstat.reads_partial, - writes_complete: wstat.writes_complete, - writes_partial: wstat.writes_partial, - bytes_total: wstat.bytes_total, - records_truncated: rstat.records_truncated, + _ => print_transfer_stats(&ProgUpdate { + read_stat: rstat, + write_stat: wstat, duration: start.elapsed(), }), } Ok(()) } -// The compiler does not like Clippy's suggestion to use &str in place of &String here. -#[allow(clippy::ptr_arg)] -fn append_dashes_if_not_present(mut acc: Vec, s: &String) -> Vec { - if Some("--") != s.get(0..=1) { - acc.push(format!("--{}", s)); +fn append_dashes_if_not_present(mut acc: Vec, mut s: String) -> Vec { + if !s.starts_with("--") && !s.starts_with("-") { + s.insert_str(0, "--"); } + acc.push(s); acc } @@ -1074,13 +975,11 @@ pub fn uumain(args: impl uucore::Args) -> i32 { let dashed_args = args .collect_str(InvalidEncodingHandling::Ignore) .accept_any() - .iter() + .into_iter() .fold(Vec::new(), append_dashes_if_not_present); let matches = uu_app() - // TODO: usage, after_help - //.usage(...) - //.after_help(...) + //.after_help(TODO: Add note about multiplier strings here.) .get_matches_from(dashed_args); let result = match ( @@ -1121,7 +1020,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { match result { Ok(_) => RTN_SUCCESS, Err(e) => { - debug_println!("dd exiting with error:\n\t{}", e); + eprintln!("dd exiting with error:\n\t{}", e); RTN_FAILURE } } @@ -1211,7 +1110,7 @@ Printing performance stats is also triggered by the INFO signal (where supported clap::Arg::with_name(options::CONV) .long(options::CONV) .takes_value(true) - .help("conv=CONV[,CONV] (alternatively --conv CONV[,CONV]) specifies a comma-separated list of conversion options or (for legacy reasons) file-flags. Conversion options and file flags may be intermixed. + .help("conv=CONV[,CONV] (alternatively --conv CONV[,CONV]) specifies a comma-separated list of conversion options or (for legacy reasons) file flags. Conversion options and file flags may be intermixed. Conversion options: \t One of {ascii, ebcdic, ibm} will perform an encoding conversion. @@ -1231,7 +1130,7 @@ Conversion options: \t 'swab' swaps each adjacent pair of bytes. If an odd number of bytes is present, the final byte is omitted. \t 'sync' pad each ibs-sided block with zeros. If 'block' or 'unblock' is specified, pad with spaces instead. -Flags: +Conversion Flags: \t One of {excl, nocreat} \t\t 'excl' the output file must be created. Fail if the output file is already present. \t\t 'nocreat' the output file will not be created. Fail if the output file in not already present. @@ -1264,10 +1163,6 @@ General-Flags \t 'noctty' do not assign a controlling tty. \t 'nofollow' do not follow system links. -Output-Flags -\t 'append' open file in append mode. Consider setting conv=notrunc as well. -\t 'seek_bytes' a value to seek=N will be interpreted as bytes. - ") ) .arg( diff --git a/src/uu/dd/src/dd_unit_tests/block_unblock_tests.rs b/src/uu/dd/src/dd_unit_tests/block_unblock_tests.rs index 83058948a..1b9698638 100644 --- a/src/uu/dd/src/dd_unit_tests/block_unblock_tests.rs +++ b/src/uu/dd/src/dd_unit_tests/block_unblock_tests.rs @@ -1,20 +1,7 @@ -/* cspell:disable */ - use super::*; -const NL: u8 = '\n' as u8; -const SPACE: u8 = ' ' as u8; - -macro_rules! rs ( - () => - { - ReadStat { - reads_complete: 0, - reads_partial: 0, - records_truncated: 0, - } - }; - ); +const NL: u8 = b'\n'; +const SPACE: u8 = b' '; macro_rules! make_block_test ( ( $test_id:ident, $test_name:expr, $src:expr, $block:expr, $spec:expr ) => @@ -25,7 +12,7 @@ macro_rules! make_block_test ( src: $src, non_ascii: false, ibs: 512, - xfer_stats: None, + print_level: None, count: None, cflags: IConvFlags { ctable: None, @@ -57,7 +44,7 @@ macro_rules! make_unblock_test ( src: $src, non_ascii: false, ibs: 512, - xfer_stats: None, + print_level: None, count: None, cflags: IConvFlags { ctable: None, @@ -82,7 +69,7 @@ macro_rules! make_unblock_test ( #[test] fn block_test_no_nl() { - let mut rs = rs!(); + let mut rs = ReadStat::default(); let buf = vec![0u8, 1u8, 2u8, 3u8]; let res = block(buf, 4, &mut rs); @@ -91,7 +78,7 @@ fn block_test_no_nl() { #[test] fn block_test_no_nl_short_record() { - let mut rs = rs!(); + let mut rs = ReadStat::default(); let buf = vec![0u8, 1u8, 2u8, 3u8]; let res = block(buf, 8, &mut rs); @@ -103,17 +90,18 @@ fn block_test_no_nl_short_record() { #[test] fn block_test_no_nl_trunc() { - let mut rs = rs!(); + let mut rs = ReadStat::default(); let buf = vec![0u8, 1u8, 2u8, 3u8, 4u8]; let res = block(buf, 4, &mut rs); + // Commented section should be truncated and appear for reference only. assert_eq!(res, vec![vec![0u8, 1u8, 2u8, 3u8 /*, 4u8*/],]); assert_eq!(rs.records_truncated, 1); } #[test] fn block_test_nl_gt_cbs_trunc() { - let mut rs = rs!(); + let mut rs = ReadStat::default(); let buf = vec![ 0u8, 1u8, 2u8, 3u8, 4u8, NL, 0u8, 1u8, 2u8, 3u8, 4u8, NL, 5u8, 6u8, 7u8, 8u8, ]; @@ -122,6 +110,7 @@ fn block_test_nl_gt_cbs_trunc() { assert_eq!( res, vec![ + // Commented lines should be truncated and appear for reference only. vec![0u8, 1u8, 2u8, 3u8], // vec![4u8, SPACE, SPACE, SPACE], vec![0u8, 1u8, 2u8, 3u8], @@ -134,7 +123,7 @@ fn block_test_nl_gt_cbs_trunc() { #[test] fn block_test_surrounded_nl() { - let mut rs = rs!(); + let mut rs = ReadStat::default(); let buf = vec![0u8, 1u8, 2u8, 3u8, NL, 4u8, 5u8, 6u8, 7u8, 8u8]; let res = block(buf, 8, &mut rs); @@ -149,7 +138,7 @@ fn block_test_surrounded_nl() { #[test] fn block_test_multiple_nl_same_cbs_block() { - let mut rs = rs!(); + let mut rs = ReadStat::default(); let buf = vec![0u8, 1u8, 2u8, 3u8, NL, 4u8, NL, 5u8, 6u8, 7u8, 8u8, 9u8]; let res = block(buf, 8, &mut rs); @@ -165,7 +154,7 @@ fn block_test_multiple_nl_same_cbs_block() { #[test] fn block_test_multiple_nl_diff_cbs_block() { - let mut rs = rs!(); + let mut rs = ReadStat::default(); let buf = vec![0u8, 1u8, 2u8, 3u8, NL, 4u8, 5u8, 6u8, 7u8, NL, 8u8, 9u8]; let res = block(buf, 8, &mut rs); @@ -181,7 +170,7 @@ fn block_test_multiple_nl_diff_cbs_block() { #[test] fn block_test_end_nl_diff_cbs_block() { - let mut rs = rs!(); + let mut rs = ReadStat::default(); let buf = vec![0u8, 1u8, 2u8, 3u8, NL]; let res = block(buf, 4, &mut rs); @@ -190,7 +179,7 @@ fn block_test_end_nl_diff_cbs_block() { #[test] fn block_test_end_nl_same_cbs_block() { - let mut rs = rs!(); + let mut rs = ReadStat::default(); let buf = vec![0u8, 1u8, 2u8, NL]; let res = block(buf, 4, &mut rs); @@ -199,7 +188,7 @@ fn block_test_end_nl_same_cbs_block() { #[test] fn block_test_double_end_nl() { - let mut rs = rs!(); + let mut rs = ReadStat::default(); let buf = vec![0u8, 1u8, 2u8, NL, NL]; let res = block(buf, 4, &mut rs); @@ -211,7 +200,7 @@ fn block_test_double_end_nl() { #[test] fn block_test_start_nl() { - let mut rs = rs!(); + let mut rs = ReadStat::default(); let buf = vec![NL, 0u8, 1u8, 2u8, 3u8]; let res = block(buf, 4, &mut rs); @@ -223,7 +212,7 @@ fn block_test_start_nl() { #[test] fn block_test_double_surrounded_nl_no_trunc() { - let mut rs = rs!(); + let mut rs = ReadStat::default(); let buf = vec![0u8, 1u8, 2u8, 3u8, NL, NL, 4u8, 5u8, 6u8, 7u8]; let res = block(buf, 8, &mut rs); @@ -239,13 +228,14 @@ fn block_test_double_surrounded_nl_no_trunc() { #[test] fn block_test_double_surrounded_nl_double_trunc() { - let mut rs = rs!(); + let mut rs = ReadStat::default(); let buf = vec![0u8, 1u8, 2u8, 3u8, NL, NL, 4u8, 5u8, 6u8, 7u8, 8u8]; let res = block(buf, 4, &mut rs); assert_eq!( res, vec![ + // Commented section should be truncated and appear for reference only. vec![0u8, 1u8, 2u8, 3u8], vec![SPACE, SPACE, SPACE, SPACE], vec![4u8, 5u8, 6u8, 7u8 /*, 8u8*/], diff --git a/src/uu/dd/src/dd_unit_tests/conv_sync_tests.rs b/src/uu/dd/src/dd_unit_tests/conv_sync_tests.rs index 5d7dcd5e3..8563985c6 100644 --- a/src/uu/dd/src/dd_unit_tests/conv_sync_tests.rs +++ b/src/uu/dd/src/dd_unit_tests/conv_sync_tests.rs @@ -1,5 +1,3 @@ -/* cspell:disable */ - use super::*; macro_rules! make_sync_test ( @@ -11,7 +9,7 @@ macro_rules! make_sync_test ( src: $src, non_ascii: false, ibs: $ibs, - xfer_stats: None, + print_level: None, count: None, cflags: IConvFlags { ctable: None, diff --git a/src/uu/dd/src/dd_unit_tests/conversion_tests.rs b/src/uu/dd/src/dd_unit_tests/conversion_tests.rs index 738ead5fd..5276f38b3 100644 --- a/src/uu/dd/src/dd_unit_tests/conversion_tests.rs +++ b/src/uu/dd/src/dd_unit_tests/conversion_tests.rs @@ -1,5 +1,3 @@ -/* cspell:disable */ - use super::*; macro_rules! make_conv_test ( @@ -11,7 +9,7 @@ macro_rules! make_conv_test ( src: $src, non_ascii: false, ibs: 512, - xfer_stats: None, + print_level: None, count: None, cflags: icf!($ctable), iflags: DEFAULT_IFLAGS, @@ -36,7 +34,7 @@ macro_rules! make_icf_test ( src: $src, non_ascii: false, ibs: 512, - xfer_stats: None, + print_level: None, count: None, cflags: $icf, iflags: DEFAULT_IFLAGS, @@ -141,7 +139,7 @@ fn all_valid_ascii_ebcdic_ascii_roundtrip_conv_test() { .unwrap(), non_ascii: false, ibs: 128, - xfer_stats: None, + print_level: None, count: None, cflags: icf!(Some(&ASCII_TO_EBCDIC)), iflags: DEFAULT_IFLAGS, @@ -163,7 +161,7 @@ fn all_valid_ascii_ebcdic_ascii_roundtrip_conv_test() { src: File::open(&tmp_fname_ae).unwrap(), non_ascii: false, ibs: 256, - xfer_stats: None, + print_level: None, count: None, cflags: icf!(Some(&EBCDIC_TO_ASCII)), iflags: DEFAULT_IFLAGS, diff --git a/src/uu/dd/src/dd_unit_tests/mod.rs b/src/uu/dd/src/dd_unit_tests/mod.rs index 49edadd97..fc8836e11 100644 --- a/src/uu/dd/src/dd_unit_tests/mod.rs +++ b/src/uu/dd/src/dd_unit_tests/mod.rs @@ -1,5 +1,3 @@ -/* cspell:disable */ - use super::*; mod block_unblock_tests; @@ -39,24 +37,6 @@ const DEFAULT_IFLAGS: IFlags = IFlags { skip_bytes: false, }; -// const DEFAULT_OFLAGS: OFlags = OFlags { -// append: false, -// cio: false, -// direct: false, -// directory: false, -// dsync: false, -// sync: false, -// nocache: false, -// nonblock: false, -// noatime: false, -// noctty: false, -// nofollow: false, -// nolinks: false, -// binary: false, -// text: false, -// seek_bytes: false, -// }; - struct LazyReader { src: R, } @@ -102,7 +82,7 @@ macro_rules! make_spec_test ( src: $src, non_ascii: false, ibs: 512, - xfer_stats: None, + print_level: None, count: None, cflags: icf!(), iflags: DEFAULT_IFLAGS, diff --git a/src/uu/dd/src/dd_unit_tests/sanity_tests.rs b/src/uu/dd/src/dd_unit_tests/sanity_tests.rs index 02355964b..fff942d26 100644 --- a/src/uu/dd/src/dd_unit_tests/sanity_tests.rs +++ b/src/uu/dd/src/dd_unit_tests/sanity_tests.rs @@ -1,5 +1,3 @@ -/* cspell:disable */ - use super::*; const DST_PLACEHOLDER: Vec = Vec::new(); @@ -52,7 +50,7 @@ make_io_test!( src: File::open("./test-resources/random-5828891cb1230748e146f34223bbd3b5.test").unwrap(), non_ascii: false, ibs: 521, - xfer_stats: None, + print_level: None, count: None, cflags: icf!(), iflags: DEFAULT_IFLAGS, @@ -72,7 +70,7 @@ make_io_test!( src: File::open("./test-resources/random-5828891cb1230748e146f34223bbd3b5.test").unwrap(), non_ascii: false, ibs: 1031, - xfer_stats: None, + print_level: None, count: None, cflags: icf!(), iflags: DEFAULT_IFLAGS, @@ -92,7 +90,7 @@ make_io_test!( src: File::open("./test-resources/deadbeef-18d99661a1de1fc9af21b0ec2cd67ba3.test").unwrap(), non_ascii: false, ibs: 1024, - xfer_stats: None, + print_level: None, count: Some(CountType::Reads(32)), cflags: icf!(), iflags: DEFAULT_IFLAGS, @@ -112,7 +110,7 @@ make_io_test!( src: File::open("./test-resources/deadbeef-18d99661a1de1fc9af21b0ec2cd67ba3.test").unwrap(), non_ascii: false, ibs: 531, - xfer_stats: None, + print_level: None, count: Some(CountType::Bytes(32 * 1024)), cflags: icf!(), iflags: DEFAULT_IFLAGS, @@ -132,7 +130,7 @@ make_io_test!( src: File::open("./test-resources/deadbeef-18d99661a1de1fc9af21b0ec2cd67ba3.test").unwrap(), non_ascii: false, ibs: 1024, - xfer_stats: None, + print_level: None, count: Some(CountType::Reads(16)), cflags: icf!(), iflags: DEFAULT_IFLAGS, @@ -152,7 +150,7 @@ make_io_test!( src: File::open("./test-resources/deadbeef-18d99661a1de1fc9af21b0ec2cd67ba3.test").unwrap(), non_ascii: false, ibs: 531, - xfer_stats: None, + print_level: None, count: Some(CountType::Bytes(12345)), cflags: icf!(), iflags: DEFAULT_IFLAGS, @@ -172,7 +170,7 @@ make_io_test!( src: File::open("./test-resources/random-5828891cb1230748e146f34223bbd3b5.test").unwrap(), non_ascii: false, ibs: 1024, - xfer_stats: None, + print_level: None, count: Some(CountType::Reads(32)), cflags: icf!(), iflags: DEFAULT_IFLAGS, @@ -192,7 +190,7 @@ make_io_test!( src: File::open("./test-resources/random-5828891cb1230748e146f34223bbd3b5.test").unwrap(), non_ascii: false, ibs: 521, - xfer_stats: None, + print_level: None, count: Some(CountType::Bytes(32 * 1024)), cflags: icf!(), iflags: DEFAULT_IFLAGS, @@ -215,7 +213,7 @@ make_io_test!( }, non_ascii: false, ibs: 521, - xfer_stats: None, + print_level: None, count: None, cflags: icf!(), iflags: IFlags { diff --git a/src/uu/dd/src/parseargs.rs b/src/uu/dd/src/parseargs.rs index 72645205f..3b9b4dd11 100644 --- a/src/uu/dd/src/parseargs.rs +++ b/src/uu/dd/src/parseargs.rs @@ -5,8 +5,6 @@ // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. -/* cspell:disable */ - #[cfg(test)] mod unit_tests; @@ -24,9 +22,8 @@ pub enum ParseError { MultipleExclNoCreat, FlagNoMatch(String), ConvFlagNoMatch(String), - NoMatchingMultiplier(String), - ByteStringContainsNoValue(String), - MultiplierStringWouldOverflow(String), + MultiplierStringParseFailure(String), + MultiplierStringOverflow(String), BlockUnblockWithoutCBS, StatusLevelNotRecognized(String), Unimplemented(String), @@ -56,13 +53,10 @@ impl std::fmt::Display for ParseError { Self::ConvFlagNoMatch(arg) => { write!(f, "Unrecognized conv=CONV -> {}", arg) } - Self::NoMatchingMultiplier(arg) => { + Self::MultiplierStringParseFailure(arg) => { write!(f, "Unrecognized byte multiplier -> {}", arg) } - Self::ByteStringContainsNoValue(arg) => { - write!(f, "Unrecognized byte value -> {}", arg) - } - Self::MultiplierStringWouldOverflow(arg) => { + Self::MultiplierStringOverflow(arg) => { write!( f, "Multiplier string would overflow on current system -> {}", @@ -302,61 +296,40 @@ impl std::str::FromStr for StatusLevel { } } -fn parse_multiplier(s: &'_ str) -> Result { - let mult: u128 = match s { - "c" => 1, - "w" => 2, - "b" => 512, - "kB" => 1000, - "K" | "KiB" => 1024, - "MB" => 1000 * 1000, - "M" | "MiB" => 1024 * 1024, - "GB" => 1000 * 1000 * 1000, - "G" | "GiB" => 1024 * 1024 * 1024, - "TB" => 1000 * 1000 * 1000 * 1000, - "T" | "TiB" => 1024 * 1024 * 1024 * 1024, - "PB" => 1000 * 1000 * 1000 * 1000 * 1000, - "P" | "PiB" => 1024 * 1024 * 1024 * 1024 * 1024, - "EB" => 1000 * 1000 * 1000 * 1000 * 1000 * 1000, - "E" | "EiB" => 1024 * 1024 * 1024 * 1024 * 1024 * 1024, - "ZB" => 1000 * 1000 * 1000 * 1000 * 1000 * 1000 * 1000, - "Z" | "ZiB" => 1024 * 1024 * 1024 * 1024 * 1024 * 1024 * 1024, - "YB" => 1000 * 1000 * 1000 * 1000 * 1000 * 1000 * 1000 * 1000, - "Y" | "YiB" => 1024 * 1024 * 1024 * 1024 * 1024 * 1024 * 1024 * 1024, - _ => return Err(ParseError::NoMatchingMultiplier(s.to_string())), - }; - - mult.try_into() - .map_err(|_e| ParseError::MultiplierStringWouldOverflow(s.to_string())) -} - +/// Parse bytes using str::parse, then map error if needed. fn parse_bytes_only(s: &str) -> Result { - match s.parse() { - Ok(bytes) => Ok(bytes), - Err(_) => Err(ParseError::ByteStringContainsNoValue(s.to_string())), - } + s.parse() + .map_err(|_| ParseError::MultiplierStringParseFailure(s.to_string())) } +/// Parse byte and multiplier like 512, 5KiB, or 1G. +/// Uses uucore::parse_size, and adds the 'w' and 'c' suffixes which are mentioned +/// in dd's info page. fn parse_bytes_with_opt_multiplier(s: &str) -> Result { - match s.find(char::is_alphabetic) { - Some(idx) => { - let base = parse_bytes_only(&s[..idx])?; - let mult = parse_multiplier(&s[idx..])?; + if let Some(idx) = s.rfind('c') { + parse_bytes_only(&s[..idx]) + } else if let Some(idx) = s.rfind('w') { + let partial = parse_bytes_only(&s[..idx])?; - if let Some(bytes) = base.checked_mul(mult) { - Ok(bytes) - } else { - Err(ParseError::MultiplierStringWouldOverflow(s.to_string())) + partial + .checked_mul(2) + .ok_or_else(|| ParseError::MultiplierStringOverflow(s.to_string())) + } else { + uucore::parse_size::parse_size(s).map_err(|e| match e { + uucore::parse_size::ParseSizeError::ParseFailure(s) => { + ParseError::MultiplierStringParseFailure(s) } - } - _ => parse_bytes_only(s), + uucore::parse_size::ParseSizeError::SizeTooBig(s) => { + ParseError::MultiplierStringOverflow(s) + } + }) } } pub fn parse_ibs(matches: &Matches) -> Result { - if let Some(mixed_str) = matches.value_of("bs") { + if let Some(mixed_str) = matches.value_of(options::BS) { parse_bytes_with_opt_multiplier(mixed_str) - } else if let Some(mixed_str) = matches.value_of("ibs") { + } else if let Some(mixed_str) = matches.value_of(options::IBS) { parse_bytes_with_opt_multiplier(mixed_str) } else { Ok(512) @@ -364,7 +337,7 @@ pub fn parse_ibs(matches: &Matches) -> Result { } fn parse_cbs(matches: &Matches) -> Result, ParseError> { - if let Some(s) = matches.value_of("cbs") { + if let Some(s) = matches.value_of(options::CBS) { let bytes = parse_bytes_with_opt_multiplier(s)?; Ok(Some(bytes)) } else { @@ -373,7 +346,7 @@ fn parse_cbs(matches: &Matches) -> Result, ParseError> { } pub fn parse_status_level(matches: &Matches) -> Result, ParseError> { - match matches.value_of("status") { + match matches.value_of(options::STATUS) { Some(s) => { let st = s.parse()?; Ok(Some(st)) diff --git a/src/uu/dd/src/parseargs/unit_tests.rs b/src/uu/dd/src/parseargs/unit_tests.rs index 25a32eb68..60b027bb6 100644 --- a/src/uu/dd/src/parseargs/unit_tests.rs +++ b/src/uu/dd/src/parseargs/unit_tests.rs @@ -1,5 +1,3 @@ -/* cspell:disable */ - use super::*; use crate::StatusLevel; @@ -7,7 +5,7 @@ use crate::StatusLevel; #[cfg(not(target_os = "linux"))] #[test] fn unimplemented_flags_should_error_non_unix() { - let mut unfailed = Vec::new(); + let mut succeeded = Vec::new(); // The following flags are only implemented in linux for flag in vec![ @@ -28,26 +26,26 @@ fn unimplemented_flags_should_error_non_unix() { let matches = uu_app().get_matches_from_safe(args).unwrap(); match parse_iflags(&matches) { - Ok(_) => unfailed.push(format!("iflag={}", flag)), + Ok(_) => succeeded.push(format!("iflag={}", flag)), Err(_) => { /* expected behaviour :-) */ } } match parse_oflags(&matches) { - Ok(_) => unfailed.push(format!("oflag={}", flag)), + Ok(_) => succeeded.push(format!("oflag={}", flag)), Err(_) => { /* expected behaviour :-) */ } } } - if !unfailed.is_empty() { + if !succeeded.is_empty() { panic!( "The following flags did not panic as expected: {:?}", - unfailed + succeeded ); } } #[test] fn unimplemented_flags_should_error() { - let mut unfailed = Vec::new(); + let mut succeeded = Vec::new(); // The following flags are not implemented for flag in vec!["cio", "nocache", "nolinks", "text", "binary"] { @@ -59,19 +57,19 @@ fn unimplemented_flags_should_error() { let matches = uu_app().get_matches_from_safe(args).unwrap(); match parse_iflags(&matches) { - Ok(_) => unfailed.push(format!("iflag={}", flag)), + Ok(_) => succeeded.push(format!("iflag={}", flag)), Err(_) => { /* expected behaviour :-) */ } } match parse_oflags(&matches) { - Ok(_) => unfailed.push(format!("oflag={}", flag)), + Ok(_) => succeeded.push(format!("oflag={}", flag)), Err(_) => { /* expected behaviour :-) */ } } } - if !unfailed.is_empty() { + if !succeeded.is_empty() { panic!( "The following flags did not panic as expected: {:?}", - unfailed + succeeded ); } } @@ -105,6 +103,176 @@ fn test_status_level_none() { assert_eq!(st, StatusLevel::None); } +#[test] +fn test_all_top_level_args_no_leading_dashes_sep_by_equals() { + let args = vec![ + String::from("dd"), + String::from("if=foo.file"), + String::from("of=bar.file"), + String::from("ibs=10"), + String::from("obs=10"), + String::from("cbs=1"), + String::from("bs=100"), + String::from("count=2"), + String::from("skip=2"), + String::from("seek=2"), + String::from("status=progress"), + String::from("conv=ascii,ucase"), + String::from("iflag=count_bytes,skip_bytes"), + String::from("oflag=append,seek_bytes"), + ]; + let args = args + .into_iter() + .fold(Vec::new(), append_dashes_if_not_present); + + let matches = uu_app().get_matches_from_safe(args).unwrap(); + + assert_eq!(100, parse_ibs(&matches).unwrap()); + assert_eq!(100, parse_obs(&matches).unwrap()); + assert_eq!(1, parse_cbs(&matches).unwrap().unwrap()); + assert_eq!( + CountType::Bytes(2), + parse_count( + &IFlags { + count_bytes: true, + ..IFlags::default() + }, + &matches + ) + .unwrap() + .unwrap() + ); + assert_eq!( + 200, + parse_skip_amt(&100, &IFlags::default(), &matches) + .unwrap() + .unwrap() + ); + assert_eq!( + 200, + parse_seek_amt(&100, &OFlags::default(), &matches) + .unwrap() + .unwrap() + ); + assert_eq!( + StatusLevel::Progress, + parse_status_level(&matches).unwrap().unwrap() + ); + assert_eq!( + IConvFlags { + ctable: Some(&EBCDIC_TO_ASCII_LCASE_TO_UCASE), + ..IConvFlags::default() + }, + parse_conv_flag_input(&matches).unwrap() + ); + assert_eq!( + OConvFlags::default(), + parse_conv_flag_output(&matches).unwrap() + ); + assert_eq!( + IFlags { + count_bytes: true, + skip_bytes: true, + ..IFlags::default() + }, + parse_iflags(&matches).unwrap() + ); + assert_eq!( + OFlags { + append: true, + seek_bytes: true, + ..OFlags::default() + }, + parse_oflags(&matches).unwrap() + ); +} + +#[ignore] +#[test] +// TODO: This should work, but Clap doesn't seem to understand it. Leaving it for now since the traditional dd if=foo.file works just fine. +fn test_all_top_level_args_leading_dashes_sep_by_spaces() { + let args = vec![ + String::from("dd"), + String::from("--if foo.file"), + String::from("--of bar.file"), + String::from("--ibs 10"), + String::from("--obs 10"), + String::from("--cbs 1"), + String::from("--bs 100"), + String::from("--count 2"), + String::from("--skip 2"), + String::from("--seek 2"), + String::from("--status progress"), + String::from("--conv ascii,ucase"), + String::from("--iflag count_bytes,skip_bytes"), + String::from("--oflag append,seek_bytes"), + ]; + let args = args + .into_iter() + .fold(Vec::new(), append_dashes_if_not_present); + + let matches = uu_app().get_matches_from_safe(args).unwrap(); + + assert_eq!(100, parse_ibs(&matches).unwrap()); + assert_eq!(100, parse_obs(&matches).unwrap()); + assert_eq!(1, parse_cbs(&matches).unwrap().unwrap()); + assert_eq!( + CountType::Bytes(2), + parse_count( + &IFlags { + count_bytes: true, + ..IFlags::default() + }, + &matches + ) + .unwrap() + .unwrap() + ); + assert_eq!( + 200, + parse_skip_amt(&100, &IFlags::default(), &matches) + .unwrap() + .unwrap() + ); + assert_eq!( + 200, + parse_seek_amt(&100, &OFlags::default(), &matches) + .unwrap() + .unwrap() + ); + assert_eq!( + StatusLevel::Progress, + parse_status_level(&matches).unwrap().unwrap() + ); + assert_eq!( + IConvFlags { + ctable: Some(&EBCDIC_TO_ASCII_LCASE_TO_UCASE), + ..IConvFlags::default() + }, + parse_conv_flag_input(&matches).unwrap() + ); + assert_eq!( + OConvFlags::default(), + parse_conv_flag_output(&matches).unwrap() + ); + assert_eq!( + IFlags { + count_bytes: true, + skip_bytes: true, + ..IFlags::default() + }, + parse_iflags(&matches).unwrap() + ); + assert_eq!( + OFlags { + append: true, + seek_bytes: true, + ..OFlags::default() + }, + parse_oflags(&matches).unwrap() + ); +} + #[test] fn test_status_level_progress() { let args = vec![ @@ -374,16 +542,6 @@ test_byte_parser!( 6 * 1024 * 1024 * 1024 * 1024 * 1024 * 1024 ); -#[test] -#[should_panic] -#[allow(non_snake_case)] -fn test_KB_multiplier_error() { - // KB is not valid (kB, K, and KiB are) - let bs_str = "2000KB"; - - parse_bytes_with_opt_multiplier(bs_str).unwrap(); -} - #[test] #[should_panic] fn test_overflow_panic() { @@ -395,7 +553,7 @@ fn test_overflow_panic() { #[test] #[should_panic] fn test_neg_panic() { - let bs_str = format!("{}KiB", -1); + let bs_str = format!("{}", -1); parse_bytes_with_opt_multiplier(&bs_str).unwrap(); } diff --git a/tests/by-util/test_dd.rs b/tests/by-util/test_dd.rs index de6e510a9..083a5bf1b 100644 --- a/tests/by-util/test_dd.rs +++ b/tests/by-util/test_dd.rs @@ -1,5 +1,3 @@ -/* cspell:disable */ - use crate::common::util::*; use std::fs::{File, OpenOptions};