1
Fork 0
mirror of https://github.com/RGBCube/uutils-coreutils synced 2025-08-01 05:27:45 +00:00

Implements status=LEVEL

- Adds print fn's
- Modifies internal fn's as needed to track read/write state
- Modifies status update thread to respect status level
- Adds signal handler for SIGUSR1 (print xfer stats)
This commit is contained in:
Tyler 2021-06-11 17:00:25 -07:00
parent a511db504b
commit fc110bb656
9 changed files with 476 additions and 145 deletions

43
Cargo.lock generated
View file

@ -134,6 +134,15 @@ version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "560c32574a12a89ecd91f5e742165893f86e3ab98d21f8ea548658eb9eef5f40" 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]] [[package]]
name = "byteorder" name = "byteorder"
version = "1.3.4" version = "1.3.4"
@ -548,6 +557,12 @@ version = "2.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f4f47ca1860a761136924ddd2422ba77b2ea54fe8cc75b9040804a0d9d32ad97" checksum = "f4f47ca1860a761136924ddd2422ba77b2ea54fe8cc75b9040804a0d9d32ad97"
[[package]]
name = "debug_print"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f215f9b7224f49fb73256115331f677d868b34d18b65dbe4db392e6021eea90"
[[package]] [[package]]
name = "digest" name = "digest"
version = "0.6.2" version = "0.6.2"
@ -1370,6 +1385,25 @@ dependencies = [
"generic-array 0.8.4", "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]] [[package]]
name = "smallvec" name = "smallvec"
version = "0.6.14" version = "0.6.14"
@ -1564,6 +1598,12 @@ dependencies = [
"log", "log",
] ]
[[package]]
name = "utf8-width"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7cf7d77f457ef8dfa11e4cd5933c5ddb5dc52a94664071951219a97710f0a32b"
[[package]] [[package]]
name = "uu_arch" name = "uu_arch"
version = "0.0.4" version = "0.0.4"
@ -1715,10 +1755,13 @@ dependencies = [
name = "uu_dd" name = "uu_dd"
version = "0.0.4" version = "0.0.4"
dependencies = [ dependencies = [
"byte-unit",
"debug_print",
"gcd", "gcd",
"getopts", "getopts",
"hex-literal", "hex-literal",
"md-5", "md-5",
"signal-hook",
"uucore", "uucore",
"uucore_procs", "uucore_procs",
] ]

View file

@ -15,11 +15,14 @@ edition = "2018"
path = "src/dd.rs" path = "src/dd.rs"
[dependencies] [dependencies]
uucore = { version=">=0.0.7", package="uucore", path="../../uucore" } byte-unit = "4.0"
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } debug_print = "1.0"
# Probably best to keep this identical to the version of getopts in the uucore crate # Probably best to keep this identical to the version of getopts in the uucore crate
getopts = "<= 0.2.21" getopts = "<= 0.2.21"
gcd = "2.0" 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] [dev-dependencies]
md-5 = "0.9" md-5 = "0.9"

View file

@ -18,31 +18,61 @@ mod parseargs;
mod conversion_tables; mod conversion_tables;
use conversion_tables::*; use conversion_tables::*;
use byte_unit::Byte;
#[macro_use]
use debug_print::debug_println;
use gcd::Gcd; use gcd::Gcd;
use getopts;
use signal_hook::consts::signal;
use std::cmp; use std::cmp;
use std::convert::TryInto; use std::convert::TryInto;
use std::error::Error; use std::error::Error;
use std::env;
use std::fs::{ use std::fs::{
File, OpenOptions, File, OpenOptions,
}; };
use getopts;
use std::io::{ use std::io::{
self, Read, Write, self, Read, Write,
Seek, Seek,
}; };
use std::sync::mpsc; use std::sync::{
Arc, atomic::AtomicUsize, mpsc, atomic::Ordering,
};
use std::thread; use std::thread;
use std::time;
const SYNTAX: &str = "dd [OPERAND]...\ndd OPTION"; const SYNTAX: &str = "dd [OPERAND]...\ndd OPTION";
const SUMMARY: &str = "convert, and optionally copy, a file"; const SUMMARY: &str = "convert, and optionally copy, a file";
const LONG_HELP: &str = ""; const LONG_HELP: &str = "";
const BUF_INIT_BYTE: u8 = 0xDD; const BUF_INIT_BYTE: u8 = 0xDD;
const RTN_SUCCESS: i32 = 0; const RTN_SUCCESS: i32 = 0;
const RTN_FAILURE: i32 = 1; const RTN_FAILURE: i32 = 1;
// ----- Datatypes ----- // ----- 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; type Cbs = usize;
@ -112,7 +142,7 @@ pub struct OFlags
/// The value of the status cl-option. /// The value of the status cl-option.
/// Controls printing of transfer stats /// Controls printing of transfer stats
#[derive(PartialEq)] #[derive(Copy, Clone, PartialEq)]
pub enum StatusLevel pub enum StatusLevel
{ {
Progress, Progress,
@ -149,7 +179,7 @@ struct Input<R: Read>
src: R, src: R,
non_ascii: bool, non_ascii: bool,
ibs: usize, ibs: usize,
xfer_stats: StatusLevel, xfer_stats: Option<StatusLevel>,
cflags: IConvFlags, cflags: IConvFlags,
iflags: IFlags, iflags: IFlags,
} }
@ -252,60 +282,96 @@ impl<R: Read> Input<R>
/// Fills a given obs-sized buffer. /// Fills a given obs-sized buffer.
/// Reads in increments of 'self.ibs'. /// Reads in increments of 'self.ibs'.
/// The start of each ibs-sized read follows the previous one. /// The start of each ibs-sized read follows the previous one.
fn fill_consecutive(&mut self, buf: &mut Vec<u8>) -> Result<usize, Box<dyn Error>> fn fill_consecutive(&mut self, buf: &mut Vec<u8>) -> Result<ReadStat, Box<dyn Error>>
{ {
let mut reads_complete = 0;
let mut reads_partial = 0;
let mut base_idx = 0; let mut base_idx = 0;
while base_idx < buf.len() while base_idx < buf.len()
{ {
let next_blk = cmp::min(base_idx+self.ibs, buf.len()); let next_blk = cmp::min(base_idx+self.ibs, buf.len());
let rlen = self.read(&mut buf[base_idx..next_blk])?; match self.read(&mut buf[base_idx..next_blk])?
if rlen > 0
{ {
base_idx += rlen; rlen if rlen == self.ibs =>
} {
else base_idx += rlen;
{ reads_complete += 1;
break; },
rlen if rlen > 0 =>
{
base_idx += rlen;
reads_partial += 1;
},
_ =>
break,
} }
} }
buf.truncate(base_idx); buf.truncate(base_idx);
Ok(base_idx) Ok(ReadStat {
reads_complete,
reads_partial,
records_truncated: 0,
})
} }
/// Fills a given obs-sized buffer. /// Fills a given obs-sized buffer.
/// Reads in increments of 'self.ibs'. /// 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. /// 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<u8>, obs: usize, pad: u8) -> Result<usize, Box<dyn Error>> fn fill_blocks(&mut self, buf: &mut Vec<u8>, obs: usize, pad: u8) -> Result<ReadStat, Box<dyn Error>>
{ {
let mut reads_complete = 0;
let mut reads_partial = 0;
let mut base_idx = 0; let mut base_idx = 0;
let mut rbytes = 0;
while base_idx < buf.len() while base_idx < buf.len()
{ {
let next_blk = cmp::min(base_idx+self.ibs, buf.len()); let next_blk = cmp::min(base_idx+self.ibs, buf.len());
let plen = next_blk - base_idx; let plen = next_blk - base_idx;
let rlen = self.read(&mut buf[base_idx..next_blk])?; match self.read(&mut buf[base_idx..next_blk])?
if rlen < plen
{ {
let padding = vec![pad; plen-rlen]; 0 =>
buf.splice(base_idx+rlen..next_blk, padding.into_iter()); break,
} rlen if rlen < plen =>
if rlen == 0 {
{ reads_partial += 1;
break; 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; base_idx += self.ibs;
} }
buf.truncate(base_idx); 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 /// Force-fills a buffer, ignoring zero-length reads which would otherwise be
@ -468,46 +534,84 @@ impl Write for Output<io::Stdout>
impl Output<io::Stdout> impl Output<io::Stdout>
{ {
fn write_blocks(&mut self, buf: Vec<u8>) -> io::Result<usize> fn write_blocks(&mut self, buf: Vec<u8>) -> io::Result<WriteStat>
{ {
let mut writes_complete = 0;
let mut writes_partial = 0;
let mut base_idx = 0; let mut base_idx = 0;
while base_idx < buf.len() while base_idx < buf.len()
{ {
let next_blk = cmp::min(base_idx+self.obs, buf.len()); let next_blk = cmp::min(base_idx+self.obs, buf.len());
let wlen = self.write(&buf[base_idx..next_blk])?; let plen = next_blk - base_idx;
base_idx += wlen;
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<File> impl Output<File>
{ {
fn write_blocks(&mut self, buf: Vec<u8>) -> io::Result<usize> fn write_blocks(&mut self, buf: Vec<u8>) -> io::Result<WriteStat>
{ {
let mut writes_complete = 0;
let mut writes_partial = 0;
let mut base_idx = 0; let mut base_idx = 0;
while base_idx < buf.len() while base_idx < buf.len()
{ {
let next_blk = cmp::min(base_idx+self.obs, buf.len()); let next_blk = cmp::min(base_idx+self.obs, buf.len());
let wlen = self.write(&buf[base_idx..next_blk])?; let wlen = self.write(&buf[base_idx..next_blk])?;
if wlen == self.obs
{
writes_complete += 1;
}
else
{
writes_partial += 1;
}
base_idx += wlen; 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 /// Splits the content of buf into cbs-length blocks
/// Appends padding as specified by conv=block and cbs=N /// Appends padding as specified by conv=block and cbs=N
fn block(buf: Vec<u8>, cbs: usize) -> Vec<Vec<u8>> fn block(buf: Vec<u8>, cbs: usize, rstats: &mut ReadStat) -> Vec<Vec<u8>>
{ {
let mut blocks = buf.split(| &e | e == '\n' as u8) let mut blocks = buf.split(| &e | e == '\n' as u8)
.fold(Vec::new(), | mut blocks, split | .fold(Vec::new(), | mut blocks, split |
{ {
let mut split = split.to_vec(); let mut split = split.to_vec();
if split.len() > cbs
{
rstats.records_truncated += 1;
}
split.resize(cbs, ' ' as u8); split.resize(cbs, ' ' as u8);
blocks.push(split); blocks.push(split);
@ -561,7 +665,7 @@ fn unblock(buf: Vec<u8>, cbs: usize) -> Vec<u8>
block block
} }
else if let Some(32u8) = block.get(0) else if let Some(32u8/* ' ' as u8 */) = block.get(0)
{ {
vec!['\n' as u8] vec!['\n' as u8]
} }
@ -579,7 +683,7 @@ fn unblock(buf: Vec<u8>, cbs: usize) -> Vec<u8>
.collect() .collect()
} }
fn conv_block_unblock_helper<R: Read, W: Write>(mut buf: Vec<u8>, i: &mut Input<R>, o: &Output<W>) -> Result<Vec<u8>, Box<dyn Error>> fn conv_block_unblock_helper<R: Read, W: Write>(mut buf: Vec<u8>, i: &mut Input<R>, o: &Output<W>, rstats: &mut ReadStat) -> Result<Vec<u8>, Box<dyn Error>>
{ {
// Local Predicate Fns ------------------------------------------------- // Local Predicate Fns -------------------------------------------------
#[inline] #[inline]
@ -633,7 +737,7 @@ fn conv_block_unblock_helper<R: Read, W: Write>(mut buf: Vec<u8>, i: &mut Input<
{ // ascii input so perform the block first { // ascii input so perform the block first
let cbs = i.cflags.block.unwrap(); 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 if let Some(ct) = i.cflags.ctable
{ {
@ -658,7 +762,7 @@ fn conv_block_unblock_helper<R: Read, W: Write>(mut buf: Vec<u8>, i: &mut Input<
apply_ct(&mut buf, &ct); apply_ct(&mut buf, &ct);
} }
let blocks = block(buf, cbs) let blocks = block(buf, cbs, rstats)
.into_iter() .into_iter()
.flatten() .flatten()
.collect(); .collect();
@ -702,7 +806,7 @@ fn conv_block_unblock_helper<R: Read, W: Write>(mut buf: Vec<u8>, i: &mut Input<
} }
} }
fn read_helper<R: Read, W: Write>(i: &mut Input<R>, o: &mut Output<W>, bsize: usize) -> Result<(usize, Vec<u8>), Box<dyn Error>> fn read_helper<R: Read, W: Write>(i: &mut Input<R>, o: &mut Output<W>, bsize: usize) -> Result<(ReadStat, Vec<u8>), Box<dyn Error>>
{ {
// Local Predicate Fns ----------------------------------------------- // Local Predicate Fns -----------------------------------------------
#[inline] #[inline]
@ -756,58 +860,151 @@ fn read_helper<R: Read, W: Write>(i: &mut Input<R>, o: &mut Output<W>, bsize: us
{ {
// Read // Read
let mut buf = vec![BUF_INIT_BYTE; bsize]; let mut buf = vec![BUF_INIT_BYTE; bsize];
let rlen = match i.cflags.sync { let mut rstats = match i.cflags.sync
{
Some(ch) => Some(ch) =>
i.fill_blocks(&mut buf, o.obs, ch)?, i.fill_blocks(&mut buf, o.obs, ch)?,
_ => _ =>
i.fill_consecutive(&mut buf)?, 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 if i.cflags.swab
{ {
perform_swab(&mut buf); perform_swab(&mut buf);
} }
if is_conv(&i) || is_block(&i) || is_unblock(&i) if is_conv(&i) || is_block(&i) || is_unblock(&i)
{ {
let buf = conv_block_unblock_helper(buf, i, o)?; let buf = conv_block_unblock_helper(buf, i, o, &mut rstats)?;
Ok((rlen, buf)) Ok((rstats, buf))
} }
else 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. /// Generate a progress updater that tracks progress, receives updates, and TODO: responds to signals.
fn gen_prog_updater(rx: mpsc::Receiver<usize>) -> impl Fn() -> () fn gen_prog_updater(rx: mpsc::Receiver<ProgUpdate>, xfer_stats: Option<StatusLevel>) -> impl Fn() -> ()
{ {
// --------------------------------------------------------------
fn posixly_correct() -> bool
{
!env::var("POSIXLY_CORRECT").is_err()
}
// --------------------------------------------------------------
move || { move || {
const SIGUSR1_USIZE: usize = signal::SIGUSR1 as usize;
// TODO: Replace ?? with accurate info let sigval = Arc::new(AtomicUsize::new(0));
print!("\rProgress ({}/??)", 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 loop
{ {
match rx.recv() // Wait for update
let update = match (rx.recv(), xfer_stats)
{ {
Ok(wr_total) => { (Ok(update), Some(StatusLevel::Progress)) =>
print!("\rProgress ({}/??)", wr_total); {
reprint_prog_line(&update);
update
}, },
Err(_) => { (Ok(update), _) =>
println!(""); {
break 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] #[inline]
fn calc_bsize(ibs: usize, obs: usize) -> usize 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 /// 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<Write> abstraction, // Note: Some of dd's functionality depends on whether the output is actually a file. This breaks the Output<Write> abstraction,
// and should be fixed in the future. // and should be fixed in the future.
fn dd_stdout<R: Read>(mut i: Input<R>, mut o: Output<io::Stdout>) -> Result<(usize, usize), Box<dyn Error>> fn dd_stdout<R: Read>(mut i: Input<R>, mut o: Output<io::Stdout>) -> Result<(), Box<dyn Error>>
{ {
let mut bytes_in = 0; let mut rstats = ReadStat {
let mut bytes_out = 0; 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 bsize = calc_bsize(i.ibs, o.obs);
let prog_tx = if i.xfer_stats == StatusLevel::Progress let prog_tx = {
{ let (tx, rx) = mpsc::channel();
let (prog_tx, prog_rx) = mpsc::channel(); thread::spawn(gen_prog_updater(rx, i.xfer_stats));
thread::spawn(gen_prog_updater(prog_rx)); tx
Some(prog_tx)
}
else
{
None
}; };
loop loop
{ {
// Read/Write
match read_helper(&mut i, &mut o, bsize)? match read_helper(&mut i, &mut o, bsize)?
{ {
(0, _) => (ReadStat { reads_complete: 0, reads_partial: 0, .. }, _) =>
break, break,
(rlen, buf) => (rstat_update, buf) =>
{ {
let wlen = o.write_blocks(buf)?; let wstats_update = o.write_blocks(buf)?;
bytes_in += rlen; rstats = ReadStat {
bytes_out += wlen; 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,
};
}, },
}; };
// Update Prog
// Prog prog_tx.send(ProgUpdate {
if let Some(prog_tx) = &prog_tx reads_complete: rstats.reads_complete,
{ reads_partial: rstats.reads_partial,
prog_tx.send(bytes_out)?; 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 if o.cflags.fsync
@ -868,49 +1082,81 @@ fn dd_stdout<R: Read>(mut i: Input<R>, mut o: Output<io::Stdout>) -> Result<(usi
o.fdatasync()?; 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 /// 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<Write> abstraction, // Note: Some of dd's functionality depends on whether the output is actually a file. This breaks the Output<Write> abstraction,
// and should be fixed in the future. // and should be fixed in the future.
fn dd_fileout<R: Read>(mut i: Input<R>, mut o: Output<File>) -> Result<(usize, usize), Box<dyn Error>> fn dd_fileout<R: Read>(mut i: Input<R>, mut o: Output<File>) -> Result<(), Box<dyn Error>>
{ {
let mut bytes_in = 0; let mut rstats = ReadStat {
let mut bytes_out = 0; 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 bsize = calc_bsize(i.ibs, o.obs);
let prog_tx = if i.xfer_stats == StatusLevel::Progress let prog_tx = {
{ let (tx, rx) = mpsc::channel();
let (prog_tx, prog_rx) = mpsc::channel(); thread::spawn(gen_prog_updater(rx, i.xfer_stats));
thread::spawn(gen_prog_updater(prog_rx)); tx
Some(prog_tx)
}
else
{
None
}; };
loop loop
{ {
// Read/Write
match read_helper(&mut i, &mut o, bsize)? match read_helper(&mut i, &mut o, bsize)?
{ {
(0, _) => (ReadStat { reads_complete: 0, reads_partial: 0, .. }, _) =>
break, break,
(rlen, buf) => (rstat_update, buf) =>
{ {
let wlen = o.write_blocks(buf)?; let wstats_update = o.write_blocks(buf)?;
bytes_in += rlen; rstats = ReadStat {
bytes_out += wlen; 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,
};
}, },
}; };
// Update Prog
// Prog prog_tx.send(ProgUpdate {
if let Some(prog_tx) = &prog_tx reads_complete: rstats.reads_complete,
{ reads_partial: rstats.reads_partial,
prog_tx.send(bytes_out)?; 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 if o.cflags.fsync
@ -922,7 +1168,22 @@ fn dd_fileout<R: Read>(mut i: Input<R>, mut o: Output<File>) -> Result<(usize, u
o.fdatasync()?; 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] #[macro_export]
@ -1010,9 +1271,7 @@ pub fn uumain(args: impl uucore::Args) -> i32
let dashed_args = args.collect_str() let dashed_args = args.collect_str()
.iter() .iter()
.fold(Vec::new(), append_dashes_if_not_present); .fold(Vec::new(), append_dashes_if_not_present);
let matches = build_app!().parse(dashed_args); let matches = build_app!().parse(dashed_args);
let result = match (matches.opt_present("if"), matches.opt_present("of")) let result = match (matches.opt_present("if"), matches.opt_present("of"))
{ {
(true, true) => (true, true) =>
@ -1052,18 +1311,17 @@ pub fn uumain(args: impl uucore::Args) -> i32
dd_stdout(i,o) dd_stdout(i,o)
}, },
}; };
match result match result
{ {
Ok((b_in, b_out)) => Ok(_) =>
{ {
// TODO: Print final xfer stats RTN_SUCCESS
// print_stats(b_in, b_out); },
Err(e) =>
RTN_SUCCESS {
debug_println!("dd exiting with error:\n\t{}", e);
RTN_FAILURE
}, },
Err(_) =>
RTN_FAILURE,
} }
} }

View file

@ -1,7 +1,18 @@
use super::*; use super::*;
static NL: u8 = '\n' as u8; const NL: u8 = '\n' as u8;
static SPACE: u8 = ' ' 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 ( macro_rules! make_block_test (
( $test_id:ident, $test_name:expr, $src:expr, $block:expr, $spec:expr ) => ( $test_id:ident, $test_name:expr, $src:expr, $block:expr, $spec:expr ) =>
@ -12,7 +23,7 @@ macro_rules! make_block_test (
src: $src, src: $src,
non_ascii: false, non_ascii: false,
ibs: 512, ibs: 512,
xfer_stats: StatusLevel::None, xfer_stats: None,
cflags: IConvFlags { cflags: IConvFlags {
ctable: None, ctable: None,
block: $block, block: $block,
@ -44,7 +55,7 @@ macro_rules! make_unblock_test (
src: $src, src: $src,
non_ascii: false, non_ascii: false,
ibs: 512, ibs: 512,
xfer_stats: StatusLevel::None, xfer_stats: None,
cflags: IConvFlags { cflags: IConvFlags {
ctable: None, ctable: None,
block: None, block: None,
@ -70,8 +81,9 @@ macro_rules! make_unblock_test (
#[test] #[test]
fn block_test_no_nl() fn block_test_no_nl()
{ {
let mut rs = rs!();
let buf = vec![0u8, 1u8, 2u8, 3u8]; let buf = vec![0u8, 1u8, 2u8, 3u8];
let res = block(buf, 4); let res = block(buf, 4, &mut rs);
assert_eq!(res, vec![ assert_eq!(res, vec![
vec![0u8, 1u8, 2u8, 3u8], vec![0u8, 1u8, 2u8, 3u8],
@ -81,8 +93,9 @@ fn block_test_no_nl()
#[test] #[test]
fn block_test_no_nl_short_record() fn block_test_no_nl_short_record()
{ {
let mut rs = rs!();
let buf = vec![0u8, 1u8, 2u8, 3u8]; let buf = vec![0u8, 1u8, 2u8, 3u8];
let res = block(buf, 8); let res = block(buf, 8, &mut rs);
assert_eq!(res, vec![ assert_eq!(res, vec![
vec![0u8, 1u8, 2u8, 3u8, SPACE, SPACE, SPACE, SPACE], vec![0u8, 1u8, 2u8, 3u8, SPACE, SPACE, SPACE, SPACE],
@ -92,19 +105,22 @@ fn block_test_no_nl_short_record()
#[test] #[test]
fn block_test_no_nl_trunc() fn block_test_no_nl_trunc()
{ {
let mut rs = rs!();
let buf = vec![0u8, 1u8, 2u8, 3u8, 4u8]; let buf = vec![0u8, 1u8, 2u8, 3u8, 4u8];
let res = block(buf, 4); let res = block(buf, 4, &mut rs);
assert_eq!(res, vec![ assert_eq!(res, vec![
vec![0u8, 1u8, 2u8, 3u8/*, 4u8*/], vec![0u8, 1u8, 2u8, 3u8/*, 4u8*/],
]); ]);
assert_eq!(rs.records_truncated, 1);
} }
#[test] #[test]
fn block_test_nl_gt_cbs_trunc() 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 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![ assert_eq!(res, vec![
vec![0u8, 1u8, 2u8, 3u8], vec![0u8, 1u8, 2u8, 3u8],
@ -113,13 +129,15 @@ fn block_test_nl_gt_cbs_trunc()
// vec![4u8, SPACE, SPACE, SPACE], // vec![4u8, SPACE, SPACE, SPACE],
vec![5u8, 6u8, 7u8, 8u8], vec![5u8, 6u8, 7u8, 8u8],
]); ]);
assert_eq!(rs.records_truncated, 2);
} }
#[test] #[test]
fn block_test_surrounded_nl() fn block_test_surrounded_nl()
{ {
let mut rs = rs!();
let buf = vec![0u8, 1u8, 2u8, 3u8, NL, 4u8, 5u8, 6u8, 7u8, 8u8]; 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![ assert_eq!(res, vec![
vec![0u8, 1u8, 2u8, 3u8, SPACE, SPACE, SPACE, SPACE], vec![0u8, 1u8, 2u8, 3u8, SPACE, SPACE, SPACE, SPACE],
@ -130,8 +148,9 @@ fn block_test_surrounded_nl()
#[test] #[test]
fn block_test_multiple_nl_same_cbs_block() 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 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![ assert_eq!(res, vec![
vec![0u8, 1u8, 2u8, 3u8, SPACE, SPACE, SPACE, SPACE], vec![0u8, 1u8, 2u8, 3u8, SPACE, SPACE, SPACE, SPACE],
@ -143,8 +162,9 @@ fn block_test_multiple_nl_same_cbs_block()
#[test] #[test]
fn block_test_multiple_nl_diff_cbs_block() 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 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![ assert_eq!(res, vec![
vec![0u8, 1u8, 2u8, 3u8, SPACE, SPACE, SPACE, SPACE], vec![0u8, 1u8, 2u8, 3u8, SPACE, SPACE, SPACE, SPACE],
@ -156,8 +176,9 @@ fn block_test_multiple_nl_diff_cbs_block()
#[test] #[test]
fn block_test_end_nl_diff_cbs_block() fn block_test_end_nl_diff_cbs_block()
{ {
let mut rs = rs!();
let buf = vec![0u8, 1u8, 2u8, 3u8, NL]; let buf = vec![0u8, 1u8, 2u8, 3u8, NL];
let res = block(buf, 4); let res = block(buf, 4, &mut rs);
assert_eq!(res, vec![ assert_eq!(res, vec![
vec![0u8, 1u8, 2u8, 3u8], vec![0u8, 1u8, 2u8, 3u8],
@ -167,8 +188,9 @@ fn block_test_end_nl_diff_cbs_block()
#[test] #[test]
fn block_test_end_nl_same_cbs_block() fn block_test_end_nl_same_cbs_block()
{ {
let mut rs = rs!();
let buf = vec![0u8, 1u8, 2u8, NL]; let buf = vec![0u8, 1u8, 2u8, NL];
let res = block(buf, 4); let res = block(buf, 4, &mut rs);
assert_eq!(res, vec![ assert_eq!(res, vec![
vec![0u8, 1u8, 2u8, SPACE] vec![0u8, 1u8, 2u8, SPACE]
@ -178,8 +200,9 @@ fn block_test_end_nl_same_cbs_block()
#[test] #[test]
fn block_test_double_end_nl() fn block_test_double_end_nl()
{ {
let mut rs = rs!();
let buf = vec![0u8, 1u8, 2u8, NL, NL]; let buf = vec![0u8, 1u8, 2u8, NL, NL];
let res = block(buf, 4); let res = block(buf, 4, &mut rs);
assert_eq!(res, vec![ assert_eq!(res, vec![
vec![0u8, 1u8, 2u8, SPACE], vec![0u8, 1u8, 2u8, SPACE],
@ -190,8 +213,9 @@ fn block_test_double_end_nl()
#[test] #[test]
fn block_test_start_nl() fn block_test_start_nl()
{ {
let mut rs = rs!();
let buf = vec![NL, 0u8, 1u8, 2u8, 3u8]; let buf = vec![NL, 0u8, 1u8, 2u8, 3u8];
let res = block(buf, 4); let res = block(buf, 4, &mut rs);
assert_eq!(res, vec![ assert_eq!(res, vec![
vec![SPACE, SPACE, SPACE, SPACE], vec![SPACE, SPACE, SPACE, SPACE],
@ -202,8 +226,9 @@ fn block_test_start_nl()
#[test] #[test]
fn block_test_double_surrounded_nl_no_trunc() 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 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![ assert_eq!(res, vec![
vec![0u8, 1u8, 2u8, 3u8, SPACE, SPACE, SPACE, SPACE], vec![0u8, 1u8, 2u8, 3u8, SPACE, SPACE, SPACE, SPACE],
@ -215,14 +240,16 @@ fn block_test_double_surrounded_nl_no_trunc()
#[test] #[test]
fn block_test_double_surrounded_nl_double_trunc() 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 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![ assert_eq!(res, vec![
vec![0u8, 1u8, 2u8, 3u8], vec![0u8, 1u8, 2u8, 3u8],
vec![SPACE, SPACE, SPACE, SPACE], vec![SPACE, SPACE, SPACE, SPACE],
vec![4u8, 5u8, 6u8, 7u8/*, 8u8*/], vec![4u8, 5u8, 6u8, 7u8/*, 8u8*/],
]); ]);
assert_eq!(rs.records_truncated, 1);
} }
make_block_test!( make_block_test!(

View file

@ -9,8 +9,8 @@ impl<R: Read> Read for LazyReader<R>
{ {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> fn read(&mut self, buf: &mut [u8]) -> io::Result<usize>
{ {
let half = buf.len() / 2; let reduced = cmp::max(buf.len() / 2, 1);
self.src.read(&mut buf[..half]) self.src.read(&mut buf[..reduced])
} }
} }
@ -23,7 +23,7 @@ macro_rules! make_sync_test (
src: $src, src: $src,
non_ascii: false, non_ascii: false,
ibs: $ibs, ibs: $ibs,
xfer_stats: StatusLevel::None, xfer_stats: None,
cflags: IConvFlags { cflags: IConvFlags {
ctable: None, ctable: None,
block: None, block: None,

View file

@ -9,7 +9,7 @@ macro_rules! make_conv_test (
src: $src, src: $src,
non_ascii: false, non_ascii: false,
ibs: 512, ibs: 512,
xfer_stats: StatusLevel::None, xfer_stats: None,
cflags: icf!($ctable), cflags: icf!($ctable),
iflags: DEFAULT_IFLAGS, iflags: DEFAULT_IFLAGS,
}, },
@ -34,7 +34,7 @@ macro_rules! make_icf_test (
src: $src, src: $src,
non_ascii: false, non_ascii: false,
ibs: 512, ibs: 512,
xfer_stats: StatusLevel::None, xfer_stats: None,
cflags: $icf, cflags: $icf,
iflags: DEFAULT_IFLAGS, 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(), src: File::open("./test-resources/all-valid-ascii-chars-37eff01866ba3f538421b30b7cbefcac.test").unwrap(),
non_ascii: false, non_ascii: false,
ibs: 128, ibs: 128,
xfer_stats: StatusLevel::None, xfer_stats: None,
cflags: icf!(Some(&ASCII_TO_EBCDIC)), cflags: icf!(Some(&ASCII_TO_EBCDIC)),
iflags: DEFAULT_IFLAGS, iflags: DEFAULT_IFLAGS,
}; };
@ -159,7 +159,7 @@ fn all_valid_ascii_ebcdic_ascii_roundtrip_conv_test()
src: File::open(&tmp_fname_ae).unwrap(), src: File::open(&tmp_fname_ae).unwrap(),
non_ascii: false, non_ascii: false,
ibs: 256, ibs: 256,
xfer_stats: StatusLevel::None, xfer_stats: None,
cflags: icf!(Some(&EBCDIC_TO_ASCII)), cflags: icf!(Some(&EBCDIC_TO_ASCII)),
iflags: DEFAULT_IFLAGS, iflags: DEFAULT_IFLAGS,
}; };

View file

@ -94,7 +94,7 @@ macro_rules! make_spec_test (
src: $src, src: $src,
non_ascii: false, non_ascii: false,
ibs: 512, ibs: 512,
xfer_stats: StatusLevel::None, xfer_stats: None,
cflags: icf!(), cflags: icf!(),
iflags: DEFAULT_IFLAGS, iflags: DEFAULT_IFLAGS,
}, },

View file

@ -31,7 +31,7 @@ make_spec_test!(
src: File::open("./test-resources/random-5828891cb1230748e146f34223bbd3b5.test").unwrap(), src: File::open("./test-resources/random-5828891cb1230748e146f34223bbd3b5.test").unwrap(),
non_ascii: false, non_ascii: false,
ibs: 521, ibs: 521,
xfer_stats: StatusLevel::None, xfer_stats: None,
cflags: icf!(), cflags: icf!(),
iflags: DEFAULT_IFLAGS, iflags: DEFAULT_IFLAGS,
}, },
@ -52,7 +52,7 @@ make_spec_test!(
src: File::open("./test-resources/random-5828891cb1230748e146f34223bbd3b5.test").unwrap(), src: File::open("./test-resources/random-5828891cb1230748e146f34223bbd3b5.test").unwrap(),
non_ascii: false, non_ascii: false,
ibs: 1031, ibs: 1031,
xfer_stats: StatusLevel::None, xfer_stats: None,
cflags: icf!(), cflags: icf!(),
iflags: DEFAULT_IFLAGS, iflags: DEFAULT_IFLAGS,
}, },

View file

@ -297,7 +297,7 @@ fn parse_cbs(matches: &getopts::Matches) -> Result<Option<usize>, ParseError>
} }
} }
pub fn parse_status_level(matches: &getopts::Matches) -> Result<StatusLevel, ParseError> pub fn parse_status_level(matches: &getopts::Matches) -> Result<Option<StatusLevel>, ParseError>
{ {
unimplemented!() unimplemented!()
} }