diff --git a/Cargo.lock b/Cargo.lock index 45764d0a7..779d4b99e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -134,6 +134,15 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "560c32574a12a89ecd91f5e742165893f86e3ab98d21f8ea548658eb9eef5f40" +[[package]] +name = "byte-unit" +version = "4.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "063197e6eb4b775b64160dedde7a0986bb2836cce140e9492e9e96f28e18bcd8" +dependencies = [ + "utf8-width", +] + [[package]] name = "byteorder" version = "1.3.4" @@ -548,6 +557,12 @@ version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f4f47ca1860a761136924ddd2422ba77b2ea54fe8cc75b9040804a0d9d32ad97" +[[package]] +name = "debug_print" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f215f9b7224f49fb73256115331f677d868b34d18b65dbe4db392e6021eea90" + [[package]] name = "digest" version = "0.6.2" @@ -1370,6 +1385,25 @@ dependencies = [ "generic-array 0.8.4", ] +[[package]] +name = "signal-hook" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "470c5a6397076fae0094aaf06a08e6ba6f37acb77d3b1b91ea92b4d6c8650c39" +dependencies = [ + "libc", + "signal-hook-registry", +] + +[[package]] +name = "signal-hook-registry" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0" +dependencies = [ + "libc", +] + [[package]] name = "smallvec" version = "0.6.14" @@ -1564,6 +1598,12 @@ dependencies = [ "log", ] +[[package]] +name = "utf8-width" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cf7d77f457ef8dfa11e4cd5933c5ddb5dc52a94664071951219a97710f0a32b" + [[package]] name = "uu_arch" version = "0.0.4" @@ -1715,10 +1755,13 @@ dependencies = [ name = "uu_dd" version = "0.0.4" dependencies = [ + "byte-unit", + "debug_print", "gcd", "getopts", "hex-literal", "md-5", + "signal-hook", "uucore", "uucore_procs", ] diff --git a/src/uu/dd/Cargo.toml b/src/uu/dd/Cargo.toml index f521b6824..ce53439b2 100644 --- a/src/uu/dd/Cargo.toml +++ b/src/uu/dd/Cargo.toml @@ -15,11 +15,14 @@ edition = "2018" path = "src/dd.rs" [dependencies] -uucore = { version=">=0.0.7", package="uucore", path="../../uucore" } -uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } +byte-unit = "4.0" +debug_print = "1.0" # Probably best to keep this identical to the version of getopts in the uucore crate getopts = "<= 0.2.21" gcd = "2.0" +signal-hook = "0.3.9" +uucore = { version=">=0.0.7", package="uucore", path="../../uucore" } +uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } [dev-dependencies] md-5 = "0.9" diff --git a/src/uu/dd/src/dd.rs b/src/uu/dd/src/dd.rs index fb12086f2..def82aafb 100644 --- a/src/uu/dd/src/dd.rs +++ b/src/uu/dd/src/dd.rs @@ -18,31 +18,61 @@ mod parseargs; mod conversion_tables; use conversion_tables::*; +use byte_unit::Byte; +#[macro_use] +use debug_print::debug_println; use gcd::Gcd; +use getopts; +use signal_hook::consts::signal; use std::cmp; use std::convert::TryInto; use std::error::Error; +use std::env; use std::fs::{ File, OpenOptions, }; -use getopts; use std::io::{ self, Read, Write, Seek, }; -use std::sync::mpsc; +use std::sync::{ + Arc, atomic::AtomicUsize, mpsc, atomic::Ordering, +}; use std::thread; +use std::time; const SYNTAX: &str = "dd [OPERAND]...\ndd OPTION"; const SUMMARY: &str = "convert, and optionally copy, a file"; const LONG_HELP: &str = ""; - const BUF_INIT_BYTE: u8 = 0xDD; - const RTN_SUCCESS: i32 = 0; const RTN_FAILURE: i32 = 1; // ----- Datatypes ----- +struct ProgUpdate +{ + reads_complete: u64, + reads_partial: u64, + writes_complete: u64, + writes_partial: u64, + bytes_total: u128, + records_truncated: u32, + duration: time::Duration, +} + +struct ReadStat +{ + reads_complete: u64, + reads_partial: u64, + records_truncated: u32, +} + +struct WriteStat +{ + writes_complete: u64, + writes_partial: u64, + bytes_total: u128, +} type Cbs = usize; @@ -112,7 +142,7 @@ pub struct OFlags /// The value of the status cl-option. /// Controls printing of transfer stats -#[derive(PartialEq)] +#[derive(Copy, Clone, PartialEq)] pub enum StatusLevel { Progress, @@ -149,7 +179,7 @@ struct Input src: R, non_ascii: bool, ibs: usize, - xfer_stats: StatusLevel, + xfer_stats: Option, cflags: IConvFlags, iflags: IFlags, } @@ -252,60 +282,96 @@ impl Input /// Fills a given obs-sized buffer. /// Reads in increments of 'self.ibs'. /// The start of each ibs-sized read follows the previous one. - fn fill_consecutive(&mut self, buf: &mut Vec) -> Result> + fn fill_consecutive(&mut self, buf: &mut Vec) -> Result> { + let mut reads_complete = 0; + let mut reads_partial = 0; let mut base_idx = 0; while base_idx < buf.len() { let next_blk = cmp::min(base_idx+self.ibs, buf.len()); - let rlen = self.read(&mut buf[base_idx..next_blk])?; - if rlen > 0 + match self.read(&mut buf[base_idx..next_blk])? { - base_idx += rlen; - } - else - { - break; + rlen if rlen == self.ibs => + { + base_idx += rlen; + reads_complete += 1; + }, + rlen if rlen > 0 => + { + base_idx += rlen; + reads_partial += 1; + }, + _ => + break, } } buf.truncate(base_idx); - Ok(base_idx) + Ok(ReadStat { + reads_complete, + reads_partial, + records_truncated: 0, + }) } /// Fills a given obs-sized buffer. /// Reads in increments of 'self.ibs'. /// The start of each ibs-sized read is aligned to multiples of ibs; remaing space is filled with the 'pad' byte. - fn fill_blocks(&mut self, buf: &mut Vec, obs: usize, pad: u8) -> Result> + fn fill_blocks(&mut self, buf: &mut Vec, obs: usize, pad: u8) -> Result> { + let mut reads_complete = 0; + let mut reads_partial = 0; let mut base_idx = 0; - let mut rbytes = 0; while base_idx < buf.len() { let next_blk = cmp::min(base_idx+self.ibs, buf.len()); let plen = next_blk - base_idx; - let rlen = self.read(&mut buf[base_idx..next_blk])?; - - if rlen < plen + match self.read(&mut buf[base_idx..next_blk])? { - let padding = vec![pad; plen-rlen]; - buf.splice(base_idx+rlen..next_blk, padding.into_iter()); - } - if rlen == 0 - { - break; + 0 => + break, + rlen if rlen < plen => + { + reads_partial += 1; + let padding = vec![pad; plen-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; + // } - rbytes += rlen; base_idx += self.ibs; } buf.truncate(base_idx); - Ok(rbytes) + Ok(ReadStat { + reads_complete, + reads_partial, + records_truncated: 0, + }) } /// Force-fills a buffer, ignoring zero-length reads which would otherwise be @@ -468,46 +534,84 @@ impl Write for Output impl Output { - fn write_blocks(&mut self, buf: Vec) -> io::Result + fn write_blocks(&mut self, buf: Vec) -> io::Result { + let mut writes_complete = 0; + let mut writes_partial = 0; let mut base_idx = 0; while base_idx < buf.len() { let next_blk = cmp::min(base_idx+self.obs, buf.len()); - let wlen = self.write(&buf[base_idx..next_blk])?; - base_idx += wlen; + let plen = next_blk - base_idx; + + match self.write(&buf[base_idx..next_blk])? + { + wlen if wlen < plen => + { + writes_partial += 1; + base_idx += wlen; + }, + wlen => + { + writes_partial += 1; + base_idx += wlen; + }, + } } - Ok(base_idx) + Ok(WriteStat { + writes_complete, + writes_partial, + bytes_total: base_idx.try_into().unwrap_or(0u128), + }) } } impl Output { - fn write_blocks(&mut self, buf: Vec) -> io::Result + fn write_blocks(&mut self, buf: Vec) -> io::Result { + let mut writes_complete = 0; + let mut writes_partial = 0; let mut base_idx = 0; while base_idx < buf.len() { let next_blk = cmp::min(base_idx+self.obs, buf.len()); let wlen = self.write(&buf[base_idx..next_blk])?; + + if wlen == self.obs + { + writes_complete += 1; + } + else + { + writes_partial += 1; + } base_idx += wlen; } - Ok(base_idx) + Ok(WriteStat { + writes_complete, + writes_partial, + bytes_total: base_idx.try_into().unwrap_or(0u128), + }) } } /// Splits the content of buf into cbs-length blocks /// Appends padding as specified by conv=block and cbs=N -fn block(buf: Vec, cbs: usize) -> Vec> +fn block(buf: Vec, cbs: usize, rstats: &mut ReadStat) -> Vec> { let mut blocks = buf.split(| &e | e == '\n' as u8) .fold(Vec::new(), | mut blocks, split | { let mut split = split.to_vec(); + if split.len() > cbs + { + rstats.records_truncated += 1; + } split.resize(cbs, ' ' as u8); blocks.push(split); @@ -561,7 +665,7 @@ fn unblock(buf: Vec, cbs: usize) -> Vec block } - else if let Some(32u8) = block.get(0) + else if let Some(32u8/* ' ' as u8 */) = block.get(0) { vec!['\n' as u8] } @@ -579,7 +683,7 @@ fn unblock(buf: Vec, cbs: usize) -> Vec .collect() } -fn conv_block_unblock_helper(mut buf: Vec, i: &mut Input, o: &Output) -> Result, Box> +fn conv_block_unblock_helper(mut buf: Vec, i: &mut Input, o: &Output, rstats: &mut ReadStat) -> Result, Box> { // Local Predicate Fns ------------------------------------------------- #[inline] @@ -633,7 +737,7 @@ fn conv_block_unblock_helper(mut buf: Vec, i: &mut Input< { // ascii input so perform the block first let cbs = i.cflags.block.unwrap(); - let mut blocks = block(buf, cbs); + let mut blocks = block(buf, cbs, rstats); if let Some(ct) = i.cflags.ctable { @@ -658,7 +762,7 @@ fn conv_block_unblock_helper(mut buf: Vec, i: &mut Input< apply_ct(&mut buf, &ct); } - let blocks = block(buf, cbs) + let blocks = block(buf, cbs, rstats) .into_iter() .flatten() .collect(); @@ -702,7 +806,7 @@ fn conv_block_unblock_helper(mut buf: Vec, i: &mut Input< } } -fn read_helper(i: &mut Input, o: &mut Output, bsize: usize) -> Result<(usize, Vec), Box> +fn read_helper(i: &mut Input, o: &mut Output, bsize: usize) -> Result<(ReadStat, Vec), Box> { // Local Predicate Fns ----------------------------------------------- #[inline] @@ -756,58 +860,151 @@ fn read_helper(i: &mut Input, o: &mut Output, bsize: us { // Read let mut buf = vec![BUF_INIT_BYTE; bsize]; - let rlen = match i.cflags.sync { + let mut rstats = match i.cflags.sync + { Some(ch) => i.fill_blocks(&mut buf, o.obs, ch)?, _ => i.fill_consecutive(&mut buf)?, }; - if rlen == 0 + // Return early if no data + if rstats.reads_complete == 0 && rstats.reads_partial == 0 { - return Ok((0,buf)); + return Ok((rstats,buf)); } - // Conv etc... + // Perform any conv=x[,x...] options if i.cflags.swab { perform_swab(&mut buf); } if is_conv(&i) || is_block(&i) || is_unblock(&i) { - let buf = conv_block_unblock_helper(buf, i, o)?; - Ok((rlen, buf)) + let buf = conv_block_unblock_helper(buf, i, o, &mut rstats)?; + Ok((rstats, buf)) } else { - Ok((rlen, buf)) + Ok((rstats, buf)) } } } +fn print_io_lines(update: &ProgUpdate) +{ + eprintln!("{}+{} records in", update.reads_complete, update.reads_partial); + if update.records_truncated > 0 + { + eprintln!("{} truncated records", update.records_truncated); + } + eprintln!("{}+{} records out", update.writes_complete, update.writes_partial); +} +fn make_prog_line(update: &ProgUpdate) -> String +{ + let btotal_metric = Byte::from_bytes(update.bytes_total) + .get_appropriate_unit(false) + .format(0); + let btotal_bin = Byte::from_bytes(update.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)) + .get_appropriate_unit(false) + .format(1); + + format!("{} bytes ({}, {}) copied, {} s, {}/s", + update.bytes_total, + btotal_metric, + btotal_bin, + safe_millis * 1000, + xfer_rate + ).to_string() +} +fn reprint_prog_line(update: &ProgUpdate) +{ + eprint!("\r{}", make_prog_line(update)); +} +fn print_prog_line(update: &ProgUpdate) +{ + eprint!("{}", make_prog_line(update)); +} +fn print_xfer_stats(update: &ProgUpdate) +{ + print_io_lines(update); + print_prog_line(update); + +} + /// Generate a progress updater that tracks progress, receives updates, and TODO: responds to signals. -fn gen_prog_updater(rx: mpsc::Receiver) -> impl Fn() -> () +fn gen_prog_updater(rx: mpsc::Receiver, xfer_stats: Option) -> impl Fn() -> () { + // -------------------------------------------------------------- + fn posixly_correct() -> bool + { + !env::var("POSIXLY_CORRECT").is_err() + } + // -------------------------------------------------------------- move || { + const SIGUSR1_USIZE: usize = signal::SIGUSR1 as usize; - // TODO: Replace ?? with accurate info - print!("\rProgress ({}/??)", 0); + 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 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{}", e); + } + } loop { - match rx.recv() + // Wait for update + let update = match (rx.recv(), xfer_stats) { - Ok(wr_total) => { - print!("\rProgress ({}/??)", wr_total); + (Ok(update), Some(StatusLevel::Progress)) => + { + reprint_prog_line(&update); + + update }, - Err(_) => { - println!(""); - break + (Ok(update), _) => + { + update }, - } + (Err(e), _) => + { + debug_println!("Internal dd Warning: Error in progress update thread\n\t{}", e); + + continue; + }, + }; + // Handle signals + match sigval.load(Ordering::Relaxed) + { + SIGUSR1_USIZE => + { + print_xfer_stats(&update); + }, + _ => {/* no signals recv'd */}, + }; } } } +/// Calculate a 'good' internal buffer size. +/// For performance of the read/write functions, the buffer should hold +/// both an itegral number of reads and an itegral number of writes. For +/// sane real-world memory use, it should not be too large. I believe +/// the least common multiple is a good representation of these interests. #[inline] fn calc_bsize(ibs: usize, obs: usize) -> usize { @@ -820,43 +1017,60 @@ fn calc_bsize(ibs: usize, obs: usize) -> usize /// Perform the copy/convert opertaions. 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. -fn dd_stdout(mut i: Input, mut o: Output) -> Result<(usize, usize), Box> +fn dd_stdout(mut i: Input, mut o: Output) -> Result<(), Box> { - let mut bytes_in = 0; - let mut bytes_out = 0; + let mut rstats = ReadStat { + reads_complete: 0, + reads_partial: 0, + records_truncated: 0, + }; + let mut wstats = WriteStat { + writes_complete: 0, + writes_partial: 0, + bytes_total: 0, + }; + let start = time::Instant::now(); let bsize = calc_bsize(i.ibs, o.obs); - let prog_tx = if i.xfer_stats == StatusLevel::Progress - { - let (prog_tx, prog_rx) = mpsc::channel(); - thread::spawn(gen_prog_updater(prog_rx)); - Some(prog_tx) - } - else - { - None + let prog_tx = { + let (tx, rx) = mpsc::channel(); + thread::spawn(gen_prog_updater(rx, i.xfer_stats)); + tx }; loop { + // Read/Write match read_helper(&mut i, &mut o, bsize)? { - (0, _) => + (ReadStat { reads_complete: 0, reads_partial: 0, .. }, _) => break, - (rlen, buf) => + (rstat_update, buf) => { - let wlen = o.write_blocks(buf)?; + let wstats_update = o.write_blocks(buf)?; - bytes_in += rlen; - bytes_out += wlen; + rstats = ReadStat { + reads_complete: rstats.reads_complete + rstat_update.reads_complete, + reads_partial: rstats.reads_partial + rstat_update.reads_partial, + records_truncated: rstats.records_truncated + rstat_update.records_truncated, + }; + wstats = WriteStat { + writes_complete: wstats.writes_complete + wstats_update.writes_complete, + writes_partial: wstats.writes_partial + wstats_update.writes_partial, + bytes_total: wstats.bytes_total + wstats_update.bytes_total, + }; }, }; - - // Prog - if let Some(prog_tx) = &prog_tx - { - prog_tx.send(bytes_out)?; - } + // Update Prog + prog_tx.send(ProgUpdate { + reads_complete: rstats.reads_complete, + reads_partial: rstats.reads_partial, + writes_complete: wstats.writes_complete, + writes_partial: wstats.writes_partial, + bytes_total: wstats.bytes_total, + records_truncated: rstats.records_truncated, + duration: start.elapsed(), + })?; } if o.cflags.fsync @@ -868,49 +1082,81 @@ fn dd_stdout(mut i: Input, mut o: Output) -> Result<(usi o.fdatasync()?; } - Ok((bytes_in, bytes_out)) + match i.xfer_stats + { + Some(StatusLevel::Noxfer) | + Some(StatusLevel::None) => {}, + _ => + print_xfer_stats(&ProgUpdate { + reads_complete: rstats.reads_complete, + reads_partial: rstats.reads_partial, + writes_complete: wstats.writes_complete, + writes_partial: wstats.writes_partial, + bytes_total: wstats.bytes_total, + records_truncated: rstats.records_truncated, + duration: start.elapsed(), + }), + } + Ok(()) } /// Perform the copy/convert opertaions. 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. -fn dd_fileout(mut i: Input, mut o: Output) -> Result<(usize, usize), Box> +fn dd_fileout(mut i: Input, mut o: Output) -> Result<(), Box> { - let mut bytes_in = 0; - let mut bytes_out = 0; + let mut rstats = ReadStat { + reads_complete: 0, + reads_partial: 0, + records_truncated: 0, + }; + let mut wstats = WriteStat { + writes_complete: 0, + writes_partial: 0, + bytes_total: 0, + }; + let start = time::Instant::now(); let bsize = calc_bsize(i.ibs, o.obs); - let prog_tx = if i.xfer_stats == StatusLevel::Progress - { - let (prog_tx, prog_rx) = mpsc::channel(); - thread::spawn(gen_prog_updater(prog_rx)); - Some(prog_tx) - } - else - { - None + let prog_tx = { + let (tx, rx) = mpsc::channel(); + thread::spawn(gen_prog_updater(rx, i.xfer_stats)); + tx }; loop { + // Read/Write match read_helper(&mut i, &mut o, bsize)? { - (0, _) => + (ReadStat { reads_complete: 0, reads_partial: 0, .. }, _) => break, - (rlen, buf) => + (rstat_update, buf) => { - let wlen = o.write_blocks(buf)?; + let wstats_update = o.write_blocks(buf)?; - bytes_in += rlen; - bytes_out += wlen; + rstats = ReadStat { + reads_complete: rstats.reads_complete + rstat_update.reads_complete, + reads_partial: rstats.reads_partial + rstat_update.reads_partial, + records_truncated: rstats.records_truncated + rstat_update.records_truncated, + }; + wstats = WriteStat { + writes_complete: wstats.writes_complete + wstats_update.writes_complete, + writes_partial: wstats.writes_partial + wstats_update.writes_partial, + bytes_total: wstats.bytes_total + wstats_update.bytes_total, + }; }, }; - - // Prog - if let Some(prog_tx) = &prog_tx - { - prog_tx.send(bytes_out)?; - } + // Update Prog + prog_tx.send(ProgUpdate { + reads_complete: rstats.reads_complete, + reads_partial: rstats.reads_partial, + writes_complete: wstats.writes_complete, + writes_partial: wstats.writes_partial, + bytes_total: wstats.bytes_total, + records_truncated: rstats.records_truncated, + duration: start.elapsed(), + })?; } if o.cflags.fsync @@ -922,7 +1168,22 @@ fn dd_fileout(mut i: Input, mut o: Output) -> Result<(usize, u o.fdatasync()?; } - Ok((bytes_in, bytes_out)) + match i.xfer_stats + { + Some(StatusLevel::Noxfer) | + Some(StatusLevel::None) => {}, + _ => + print_xfer_stats(&ProgUpdate { + reads_complete: rstats.reads_complete, + reads_partial: rstats.reads_partial, + writes_complete: wstats.writes_complete, + writes_partial: wstats.writes_partial, + bytes_total: wstats.bytes_total, + records_truncated: rstats.records_truncated, + duration: start.elapsed(), + }), + } + Ok(()) } #[macro_export] @@ -1010,9 +1271,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 let dashed_args = args.collect_str() .iter() .fold(Vec::new(), append_dashes_if_not_present); - let matches = build_app!().parse(dashed_args); - let result = match (matches.opt_present("if"), matches.opt_present("of")) { (true, true) => @@ -1052,18 +1311,17 @@ pub fn uumain(args: impl uucore::Args) -> i32 dd_stdout(i,o) }, }; - match result { - Ok((b_in, b_out)) => + Ok(_) => { - // TODO: Print final xfer stats - // print_stats(b_in, b_out); - - RTN_SUCCESS + RTN_SUCCESS + }, + Err(e) => + { + debug_println!("dd exiting with error:\n\t{}", e); + RTN_FAILURE }, - Err(_) => - RTN_FAILURE, } } 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 241088e63..b0a1ba6fc 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,7 +1,18 @@ use super::*; -static NL: u8 = '\n' as u8; -static SPACE: u8 = ' ' as u8; +const NL: u8 = '\n' as u8; +const SPACE: u8 = ' ' as u8; + +macro_rules! rs ( + () => + { + ReadStat { + reads_complete: 0, + reads_partial: 0, + records_truncated: 0, + } + }; + ); macro_rules! make_block_test ( ( $test_id:ident, $test_name:expr, $src:expr, $block:expr, $spec:expr ) => @@ -12,7 +23,7 @@ macro_rules! make_block_test ( src: $src, non_ascii: false, ibs: 512, - xfer_stats: StatusLevel::None, + xfer_stats: None, cflags: IConvFlags { ctable: None, block: $block, @@ -44,7 +55,7 @@ macro_rules! make_unblock_test ( src: $src, non_ascii: false, ibs: 512, - xfer_stats: StatusLevel::None, + xfer_stats: None, cflags: IConvFlags { ctable: None, block: None, @@ -70,8 +81,9 @@ macro_rules! make_unblock_test ( #[test] fn block_test_no_nl() { + let mut rs = rs!(); let buf = vec![0u8, 1u8, 2u8, 3u8]; - let res = block(buf, 4); + let res = block(buf, 4, &mut rs); assert_eq!(res, vec![ vec![0u8, 1u8, 2u8, 3u8], @@ -81,8 +93,9 @@ fn block_test_no_nl() #[test] fn block_test_no_nl_short_record() { + let mut rs = rs!(); let buf = vec![0u8, 1u8, 2u8, 3u8]; - let res = block(buf, 8); + let res = block(buf, 8, &mut rs); assert_eq!(res, vec![ vec![0u8, 1u8, 2u8, 3u8, SPACE, SPACE, SPACE, SPACE], @@ -92,19 +105,22 @@ fn block_test_no_nl_short_record() #[test] fn block_test_no_nl_trunc() { + let mut rs = rs!(); let buf = vec![0u8, 1u8, 2u8, 3u8, 4u8]; - let res = block(buf, 4); + let res = block(buf, 4, &mut rs); 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 buf = vec![0u8, 1u8, 2u8, 3u8, 4u8, NL, 0u8, 1u8, 2u8, 3u8, 4u8, NL, 5u8, 6u8, 7u8, 8u8]; - let res = block(buf, 4); + let res = block(buf, 4, &mut rs); assert_eq!(res, vec![ vec![0u8, 1u8, 2u8, 3u8], @@ -113,13 +129,15 @@ fn block_test_nl_gt_cbs_trunc() // vec![4u8, SPACE, SPACE, SPACE], vec![5u8, 6u8, 7u8, 8u8], ]); + assert_eq!(rs.records_truncated, 2); } #[test] fn block_test_surrounded_nl() { + let mut rs = rs!(); let buf = vec![0u8, 1u8, 2u8, 3u8, NL, 4u8, 5u8, 6u8, 7u8, 8u8]; - let res = block(buf, 8); + let res = block(buf, 8, &mut rs); assert_eq!(res, vec![ vec![0u8, 1u8, 2u8, 3u8, SPACE, SPACE, SPACE, SPACE], @@ -130,8 +148,9 @@ fn block_test_surrounded_nl() #[test] fn block_test_multiple_nl_same_cbs_block() { + let mut rs = rs!(); let buf = vec![0u8, 1u8, 2u8, 3u8, NL, 4u8, NL, 5u8, 6u8, 7u8, 8u8, 9u8]; - let res = block(buf, 8); + let res = block(buf, 8, &mut rs); assert_eq!(res, vec![ vec![0u8, 1u8, 2u8, 3u8, SPACE, SPACE, SPACE, SPACE], @@ -143,8 +162,9 @@ fn block_test_multiple_nl_same_cbs_block() #[test] fn block_test_multiple_nl_diff_cbs_block() { + let mut rs = rs!(); let buf = vec![0u8, 1u8, 2u8, 3u8, NL, 4u8, 5u8, 6u8, 7u8, NL, 8u8, 9u8]; - let res = block(buf, 8); + let res = block(buf, 8, &mut rs); assert_eq!(res, vec![ vec![0u8, 1u8, 2u8, 3u8, SPACE, SPACE, SPACE, SPACE], @@ -156,8 +176,9 @@ fn block_test_multiple_nl_diff_cbs_block() #[test] fn block_test_end_nl_diff_cbs_block() { + let mut rs = rs!(); let buf = vec![0u8, 1u8, 2u8, 3u8, NL]; - let res = block(buf, 4); + let res = block(buf, 4, &mut rs); assert_eq!(res, vec![ vec![0u8, 1u8, 2u8, 3u8], @@ -167,8 +188,9 @@ fn block_test_end_nl_diff_cbs_block() #[test] fn block_test_end_nl_same_cbs_block() { + let mut rs = rs!(); let buf = vec![0u8, 1u8, 2u8, NL]; - let res = block(buf, 4); + let res = block(buf, 4, &mut rs); assert_eq!(res, vec![ vec![0u8, 1u8, 2u8, SPACE] @@ -178,8 +200,9 @@ fn block_test_end_nl_same_cbs_block() #[test] fn block_test_double_end_nl() { + let mut rs = rs!(); let buf = vec![0u8, 1u8, 2u8, NL, NL]; - let res = block(buf, 4); + let res = block(buf, 4, &mut rs); assert_eq!(res, vec![ vec![0u8, 1u8, 2u8, SPACE], @@ -190,8 +213,9 @@ fn block_test_double_end_nl() #[test] fn block_test_start_nl() { + let mut rs = rs!(); let buf = vec![NL, 0u8, 1u8, 2u8, 3u8]; - let res = block(buf, 4); + let res = block(buf, 4, &mut rs); assert_eq!(res, vec![ vec![SPACE, SPACE, SPACE, SPACE], @@ -202,8 +226,9 @@ fn block_test_start_nl() #[test] fn block_test_double_surrounded_nl_no_trunc() { + let mut rs = rs!(); let buf = vec![0u8, 1u8, 2u8, 3u8, NL, NL, 4u8, 5u8, 6u8, 7u8]; - let res = block(buf, 8); + let res = block(buf, 8, &mut rs); assert_eq!(res, vec![ vec![0u8, 1u8, 2u8, 3u8, SPACE, SPACE, SPACE, SPACE], @@ -215,14 +240,16 @@ fn block_test_double_surrounded_nl_no_trunc() #[test] fn block_test_double_surrounded_nl_double_trunc() { + let mut rs = rs!(); let buf = vec![0u8, 1u8, 2u8, 3u8, NL, NL, 4u8, 5u8, 6u8, 7u8, 8u8]; - let res = block(buf, 4); + let res = block(buf, 4, &mut rs); assert_eq!(res, vec![ vec![0u8, 1u8, 2u8, 3u8], vec![SPACE, SPACE, SPACE, SPACE], vec![4u8, 5u8, 6u8, 7u8/*, 8u8*/], ]); + assert_eq!(rs.records_truncated, 1); } make_block_test!( 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 ffb095c4a..070fdb217 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 @@ -9,8 +9,8 @@ impl Read for LazyReader { fn read(&mut self, buf: &mut [u8]) -> io::Result { - let half = buf.len() / 2; - self.src.read(&mut buf[..half]) + let reduced = cmp::max(buf.len() / 2, 1); + self.src.read(&mut buf[..reduced]) } } @@ -23,7 +23,7 @@ macro_rules! make_sync_test ( src: $src, non_ascii: false, ibs: $ibs, - xfer_stats: StatusLevel::None, + xfer_stats: None, cflags: IConvFlags { ctable: None, block: 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 521dd11f4..b7d7ccb9b 100644 --- a/src/uu/dd/src/dd_unit_tests/conversion_tests.rs +++ b/src/uu/dd/src/dd_unit_tests/conversion_tests.rs @@ -9,7 +9,7 @@ macro_rules! make_conv_test ( src: $src, non_ascii: false, ibs: 512, - xfer_stats: StatusLevel::None, + xfer_stats: None, cflags: icf!($ctable), iflags: DEFAULT_IFLAGS, }, @@ -34,7 +34,7 @@ macro_rules! make_icf_test ( src: $src, non_ascii: false, ibs: 512, - xfer_stats: StatusLevel::None, + xfer_stats: None, cflags: $icf, iflags: DEFAULT_IFLAGS, }, @@ -137,7 +137,7 @@ fn all_valid_ascii_ebcdic_ascii_roundtrip_conv_test() src: File::open("./test-resources/all-valid-ascii-chars-37eff01866ba3f538421b30b7cbefcac.test").unwrap(), non_ascii: false, ibs: 128, - xfer_stats: StatusLevel::None, + xfer_stats: None, cflags: icf!(Some(&ASCII_TO_EBCDIC)), iflags: DEFAULT_IFLAGS, }; @@ -159,7 +159,7 @@ fn all_valid_ascii_ebcdic_ascii_roundtrip_conv_test() src: File::open(&tmp_fname_ae).unwrap(), non_ascii: false, ibs: 256, - xfer_stats: StatusLevel::None, + xfer_stats: 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 7e654ec14..8904a2372 100644 --- a/src/uu/dd/src/dd_unit_tests/mod.rs +++ b/src/uu/dd/src/dd_unit_tests/mod.rs @@ -94,7 +94,7 @@ macro_rules! make_spec_test ( src: $src, non_ascii: false, ibs: 512, - xfer_stats: StatusLevel::None, + xfer_stats: 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 6459ad41e..8724474a0 100644 --- a/src/uu/dd/src/dd_unit_tests/sanity_tests.rs +++ b/src/uu/dd/src/dd_unit_tests/sanity_tests.rs @@ -31,7 +31,7 @@ make_spec_test!( src: File::open("./test-resources/random-5828891cb1230748e146f34223bbd3b5.test").unwrap(), non_ascii: false, ibs: 521, - xfer_stats: StatusLevel::None, + xfer_stats: None, cflags: icf!(), iflags: DEFAULT_IFLAGS, }, @@ -52,7 +52,7 @@ make_spec_test!( src: File::open("./test-resources/random-5828891cb1230748e146f34223bbd3b5.test").unwrap(), non_ascii: false, ibs: 1031, - xfer_stats: StatusLevel::None, + xfer_stats: None, cflags: icf!(), iflags: DEFAULT_IFLAGS, }, diff --git a/src/uu/dd/src/parseargs.rs b/src/uu/dd/src/parseargs.rs index f115a6a2c..96e58ffa2 100644 --- a/src/uu/dd/src/parseargs.rs +++ b/src/uu/dd/src/parseargs.rs @@ -297,7 +297,7 @@ fn parse_cbs(matches: &getopts::Matches) -> Result, ParseError> } } -pub fn parse_status_level(matches: &getopts::Matches) -> Result +pub fn parse_status_level(matches: &getopts::Matches) -> Result, ParseError> { unimplemented!() }