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"
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",
]

View file

@ -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"

View file

@ -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<R: Read>
src: R,
non_ascii: bool,
ibs: usize,
xfer_stats: StatusLevel,
xfer_stats: Option<StatusLevel>,
cflags: IConvFlags,
iflags: IFlags,
}
@ -252,60 +282,96 @@ impl<R: Read> Input<R>
/// 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<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;
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<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 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<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;
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<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;
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<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)
.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<u8>, cbs: usize) -> Vec<u8>
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<u8>, cbs: usize) -> Vec<u8>
.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 -------------------------------------------------
#[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
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<R: Read, W: Write>(mut buf: Vec<u8>, 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<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 -----------------------------------------------
#[inline]
@ -756,58 +860,151 @@ fn read_helper<R: Read, W: Write>(i: &mut Input<R>, o: &mut Output<W>, 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<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 || {
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<Write> abstraction,
// 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 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<R: Read>(mut i: Input<R>, mut o: Output<io::Stdout>) -> 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<Write> abstraction,
// 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 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<R: Read>(mut i: Input<R>, mut o: Output<File>) -> 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,
}
}

View file

@ -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!(

View file

@ -9,8 +9,8 @@ impl<R: Read> Read for LazyReader<R>
{
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize>
{
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,

View file

@ -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,
};

View file

@ -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,
},

View file

@ -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,
},

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!()
}