Addresses code-quality issues from testsdiepraam and miDeb.

- Removes dd from feat_require_unix (keeps it in feat_common_core)
- Changes name of make_linux_iflags parameter from oflags to iflags.
- Removes roughed out SIGINFO impl.
- Renames plen -> target_len.
- Removes internal fn def for build_blocks and replaces with a call to
  chunks from std.
- Renames apply_ct to the more descriptive apply_conversion
- Replaces manual swap steps in perform_swab with call to swap from std.
- Replaces manual solution with chunks where appropriate (in Read, and Write impl).
- Renames xfer -> transfer (in local variable names).
- Improves documentation for dd_filout/dd_stdout issue.
- Removes commented debug_assert statements.
- Modifies ProdUpdate to contain ReadStat and WriteStat rather than
  copying their fields.
- Addresses verbose return in `Output<File>::new(...)`
- Resoves compiler warning when built as release when signal handler fails to
  register.
- Derives _Default_ trait on ReadStat.
- Adds comments for truncated lines in block unblock tests
- Removes `as u8` in block unblock tests.
- Removes unecessary `#[inline]`
- Delegates multiplier string parsing to uucore::parse_size.
- Renames 'unfailed' -> 'succeeded' for clairity.
- Removes #dead_code warnings. No clippy warnings on my local machine.
- Reworks signal handler to better accomodate platform-specific signals.
- Removes explicit references to "if" and "of" in dd.rs.
- Removes explicit references to "bs", "ibs", "cbs" and "status" in
  parseargs.rs.
- Removes `#[allow(deadcode)]` for OFlags, and IFlags.
- Removes spellchecker ignore from all dd files.
- Adds tests for 'traditional' and 'modern' CLI.
This commit is contained in:
Tyler 2021-07-14 13:59:11 -07:00
parent c3f9557581
commit 9d9267e08b
12 changed files with 377 additions and 418 deletions

View file

@ -153,7 +153,6 @@ feat_require_unix = [
"chmod",
"chown",
"chroot",
"dd",
"groups",
"hostid",
"id",

View file

@ -5,8 +5,6 @@
// For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code.
/* cspell:disable */
// Note: Conversion tables are just lookup tables.
// eg. The ASCII->EBCDIC table stores the EBCDIC code at the index
// obtained by treating the ASCII representation as a number.

View file

@ -5,23 +5,18 @@
// For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code.
/* cspell:disable */
use crate::conversion_tables::*;
use std::error::Error;
use std::time;
pub struct ProgUpdate {
pub reads_complete: u64,
pub reads_partial: u64,
pub writes_complete: u64,
pub writes_partial: u64,
pub bytes_total: u128,
pub records_truncated: u32,
pub read_stat: ReadStat,
pub write_stat: WriteStat,
pub duration: time::Duration,
}
#[derive(Clone, Copy, Default)]
pub struct ReadStat {
pub reads_complete: u64,
pub reads_partial: u64,
@ -37,6 +32,7 @@ impl std::ops::AddAssign for ReadStat {
}
}
#[derive(Clone, Copy)]
pub struct WriteStat {
pub writes_complete: u64,
pub writes_partial: u64,
@ -55,6 +51,7 @@ impl std::ops::AddAssign for WriteStat {
type Cbs = usize;
/// Stores all Conv Flags that apply to the input
#[derive(Debug, Default, PartialEq)]
pub struct IConvFlags {
pub ctable: Option<&'static ConversionTable>,
pub block: Option<Cbs>,
@ -65,7 +62,7 @@ pub struct IConvFlags {
}
/// Stores all Conv Flags that apply to the output
#[derive(Debug, PartialEq)]
#[derive(Debug, Default, PartialEq)]
pub struct OConvFlags {
pub sparse: bool,
pub excl: bool,
@ -76,32 +73,20 @@ pub struct OConvFlags {
}
/// Stores all Flags that apply to the input
#[derive(Debug, Default, PartialEq)]
pub struct IFlags {
#[allow(dead_code)]
pub cio: bool,
#[allow(dead_code)]
pub direct: bool,
#[allow(dead_code)]
pub directory: bool,
#[allow(dead_code)]
pub dsync: bool,
#[allow(dead_code)]
pub sync: bool,
#[allow(dead_code)]
pub nocache: bool,
#[allow(dead_code)]
pub nonblock: bool,
#[allow(dead_code)]
pub noatime: bool,
#[allow(dead_code)]
pub noctty: bool,
#[allow(dead_code)]
pub nofollow: bool,
#[allow(dead_code)]
pub nolinks: bool,
#[allow(dead_code)]
pub binary: bool,
#[allow(dead_code)]
pub text: bool,
pub fullblock: bool,
pub count_bytes: bool,
@ -109,33 +94,21 @@ pub struct IFlags {
}
/// Stores all Flags that apply to the output
#[derive(Debug, Default, PartialEq)]
pub struct OFlags {
pub append: bool,
#[allow(dead_code)]
pub cio: bool,
#[allow(dead_code)]
pub direct: bool,
#[allow(dead_code)]
pub directory: bool,
#[allow(dead_code)]
pub dsync: bool,
#[allow(dead_code)]
pub sync: bool,
#[allow(dead_code)]
pub nocache: bool,
#[allow(dead_code)]
pub nonblock: bool,
#[allow(dead_code)]
pub noatime: bool,
#[allow(dead_code)]
pub noctty: bool,
#[allow(dead_code)]
pub nofollow: bool,
#[allow(dead_code)]
pub nolinks: bool,
#[allow(dead_code)]
pub binary: bool,
#[allow(dead_code)]
pub text: bool,
pub seek_bytes: bool,
}
@ -153,6 +126,7 @@ pub enum StatusLevel {
/// Defaults to Reads(N)
/// if iflag=count_bytes
/// then becomes Bytes(N)
#[derive(Debug, PartialEq)]
pub enum CountType {
Reads(usize),
Bytes(usize),

View file

@ -5,8 +5,6 @@
// For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code.
/* cspell:disable */
#[macro_use]
extern crate uucore;
use uucore::InvalidEncodingHandling;
@ -25,7 +23,6 @@ use conversion_tables::*;
use byte_unit::Byte;
use clap::{self, crate_version};
use debug_print::debug_println;
use gcd::Gcd;
use signal_hook::consts::signal;
use std::cmp;
@ -51,7 +48,7 @@ struct Input<R: Read> {
src: R,
non_ascii: bool,
ibs: usize,
xfer_stats: Option<StatusLevel>,
print_level: Option<StatusLevel>,
count: Option<CountType>,
cflags: IConvFlags,
iflags: IFlags,
@ -61,7 +58,7 @@ impl Input<io::Stdin> {
fn new(matches: &Matches) -> Result<Self, Box<dyn Error>> {
let ibs = parseargs::parse_ibs(matches)?;
let non_ascii = parseargs::parse_input_non_ascii(matches)?;
let xfer_stats = parseargs::parse_status_level(matches)?;
let print_level = parseargs::parse_status_level(matches)?;
let cflags = parseargs::parse_conv_flag_input(matches)?;
let iflags = parseargs::parse_iflags(matches)?;
let skip = parseargs::parse_skip_amt(&ibs, &iflags, matches)?;
@ -71,7 +68,7 @@ impl Input<io::Stdin> {
src: io::stdin(),
non_ascii,
ibs,
xfer_stats,
print_level,
count,
cflags,
iflags,
@ -88,31 +85,31 @@ impl Input<io::Stdin> {
}
#[cfg(target_os = "linux")]
fn make_linux_iflags(oflags: &IFlags) -> Option<libc::c_int> {
fn make_linux_iflags(iflags: &IFlags) -> Option<libc::c_int> {
let mut flag = 0;
if oflags.direct {
if iflags.direct {
flag |= libc::O_DIRECT;
}
if oflags.directory {
if iflags.directory {
flag |= libc::O_DIRECTORY;
}
if oflags.dsync {
if iflags.dsync {
flag |= libc::O_DSYNC;
}
if oflags.noatime {
if iflags.noatime {
flag |= libc::O_NOATIME;
}
if oflags.noctty {
if iflags.noctty {
flag |= libc::O_NOCTTY;
}
if oflags.nofollow {
if iflags.nofollow {
flag |= libc::O_NOFOLLOW;
}
if oflags.nonblock {
if iflags.nonblock {
flag |= libc::O_NONBLOCK;
}
if oflags.sync {
if iflags.sync {
flag |= libc::O_SYNC;
}
@ -127,13 +124,13 @@ impl Input<File> {
fn new(matches: &Matches) -> Result<Self, Box<dyn Error>> {
let ibs = parseargs::parse_ibs(matches)?;
let non_ascii = parseargs::parse_input_non_ascii(matches)?;
let xfer_stats = parseargs::parse_status_level(matches)?;
let print_level = parseargs::parse_status_level(matches)?;
let cflags = parseargs::parse_conv_flag_input(matches)?;
let iflags = parseargs::parse_iflags(matches)?;
let skip = parseargs::parse_skip_amt(&ibs, &iflags, matches)?;
let count = parseargs::parse_count(&iflags, matches)?;
if let Some(fname) = matches.value_of("if") {
if let Some(fname) = matches.value_of(options::INFILE) {
let mut src = {
let mut opts = OpenOptions::new();
opts.read(true);
@ -155,7 +152,7 @@ impl Input<File> {
src,
non_ascii,
ibs,
xfer_stats,
print_level,
count,
cflags,
iflags,
@ -198,28 +195,27 @@ impl<R: Read> Input<R> {
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 bytes_total = 0;
while base_idx < buf.len() {
let next_blk = cmp::min(base_idx + self.ibs, buf.len());
match self.read(&mut buf[base_idx..next_blk])? {
for chunk in buf.chunks_mut(self.ibs) {
match self.read(chunk)? {
rlen if rlen == self.ibs => {
base_idx += rlen;
bytes_total += rlen;
reads_complete += 1;
}
rlen if rlen > 0 => {
base_idx += rlen;
bytes_total += rlen;
reads_partial += 1;
}
_ => break,
}
}
buf.truncate(base_idx);
buf.truncate(bytes_total);
Ok(ReadStat {
reads_complete,
reads_partial,
// Records are not truncated when filling.
records_truncated: 0,
})
}
@ -234,35 +230,19 @@ impl<R: Read> Input<R> {
while base_idx < buf.len() {
let next_blk = cmp::min(base_idx + self.ibs, buf.len());
let plen = next_blk - base_idx;
let target_len = next_blk - base_idx;
match self.read(&mut buf[base_idx..next_blk])? {
0 => break,
rlen if rlen < plen => {
rlen if rlen < target_len => {
reads_partial += 1;
let padding = vec![pad; plen - rlen];
let padding = vec![pad; target_len - rlen];
buf.splice(base_idx + rlen..next_blk, padding.into_iter());
}
_ => {
reads_complete += 1;
}
}
// TODO: Why does this cause the conv=sync tests to hang?
// let rlen = self.read(&mut buf[base_idx..next_blk])?;
// if rlen < plen
// {
// reads_partial += 1;
// let padding = vec![pad; plen-rlen];
// buf.splice(base_idx+rlen..next_blk, padding.into_iter());
// }
// else
// {
// reads_complete += 1;
// }
// if rlen == 0
// {
// break;
// }
base_idx += self.ibs;
}
@ -356,11 +336,7 @@ fn make_linux_oflags(oflags: &OFlags) -> Option<libc::c_int> {
impl Output<File> {
fn new(matches: &Matches) -> Result<Self, Box<dyn Error>> {
fn open_dst(
path: &Path,
cflags: &OConvFlags,
oflags: &OFlags,
) -> Result<File, Box<dyn Error>> {
fn open_dst(path: &Path, cflags: &OConvFlags, oflags: &OFlags) -> Result<File, io::Error> {
let mut opts = OpenOptions::new();
opts.write(true)
.create(!cflags.nocreat)
@ -373,15 +349,14 @@ impl Output<File> {
opts.custom_flags(libc_flags);
}
let dst = opts.open(path)?;
Ok(dst)
opts.open(path)
}
let obs = parseargs::parse_obs(matches)?;
let cflags = parseargs::parse_conv_flag_output(matches)?;
let oflags = parseargs::parse_oflags(matches)?;
let seek = parseargs::parse_seek_amt(&obs, &oflags, matches)?;
if let Some(fname) = matches.value_of("of") {
if let Some(fname) = matches.value_of(options::OUTFILE) {
let mut dst = open_dst(Path::new(&fname), &cflags, &oflags)?;
if let Some(amt) = seek {
@ -418,7 +393,6 @@ impl Seek for Output<File> {
impl Write for Output<File> {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
#[inline]
fn is_sparse(buf: &[u8]) -> bool {
buf.iter().all(|&e| e == 0u8)
}
@ -454,20 +428,17 @@ impl Output<io::Stdout> {
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 bytes_total = 0;
while base_idx < buf.len() {
let next_blk = cmp::min(base_idx + self.obs, buf.len());
let plen = next_blk - base_idx;
match self.write(&buf[base_idx..next_blk])? {
wlen if wlen < plen => {
for chunk in buf.chunks(self.obs) {
match self.write(chunk)? {
wlen if wlen < chunk.len() => {
writes_partial += 1;
base_idx += wlen;
bytes_total += wlen;
}
wlen => {
writes_complete += 1;
base_idx += wlen;
bytes_total += wlen;
}
}
}
@ -475,7 +446,7 @@ impl Output<io::Stdout> {
Ok(WriteStat {
writes_complete,
writes_partial,
bytes_total: base_idx.try_into().unwrap_or(0u128),
bytes_total: bytes_total.try_into().unwrap_or(0u128),
})
}
}
@ -484,20 +455,17 @@ impl Output<File> {
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 bytes_total = 0;
while base_idx < buf.len() {
let next_blk = cmp::min(base_idx + self.obs, buf.len());
let plen = next_blk - base_idx;
match self.write(&buf[base_idx..next_blk])? {
wlen if wlen < plen => {
for chunk in buf.chunks(self.obs) {
match self.write(chunk)? {
wlen if wlen < chunk.len() => {
writes_partial += 1;
base_idx += wlen;
bytes_total += wlen;
}
wlen => {
writes_complete += 1;
base_idx += wlen;
bytes_total += wlen;
}
}
}
@ -505,7 +473,7 @@ impl Output<File> {
Ok(WriteStat {
writes_complete,
writes_partial,
bytes_total: base_idx.try_into().unwrap_or(0u128),
bytes_total: bytes_total.try_into().unwrap_or(0u128),
})
}
}
@ -538,47 +506,18 @@ fn block(buf: Vec<u8>, cbs: usize, rstat: &mut ReadStat) -> Vec<Vec<u8>> {
/// Trims padding from each cbs-length partition of buf
/// as specified by conv=unblock and cbs=N
fn unblock(buf: Vec<u8>, cbs: usize) -> Vec<u8> {
// Local Helper Fns ----------------------------------------------------
#[inline]
fn build_blocks(buf: Vec<u8>, cbs: usize) -> Vec<Vec<u8>> {
let mut blocks = Vec::new();
let mut curr = buf;
let mut next;
let mut width;
buf.chunks(cbs).fold(Vec::new(), |mut acc, block| {
if let Some(last_char_idx) = block.iter().rposition(|&e| e != b' ') {
// Find last space
acc.extend(&block[..=last_char_idx]);
acc.push(b'\n');
} else {
// The block is filled with only spaces
acc.push(b'\n');
};
while !curr.is_empty() {
width = cmp::min(cbs, curr.len());
next = curr.split_off(width);
blocks.push(curr);
curr = next;
}
blocks
}
// ---------------------------------------------------------------------
build_blocks(buf, cbs)
.into_iter()
.fold(Vec::new(), |mut unblocks, mut block| {
let block = if let Some(last_char_idx) = block.iter().rposition(|&e| e != b' ') {
block.truncate(last_char_idx + 1);
block.push(b'\n');
block
} else if let Some(b' ') = block.get(0) {
vec![b'\n']
} else {
block
};
unblocks.push(block);
unblocks
})
.into_iter()
.flatten()
.collect()
acc
})
}
fn conv_block_unblock_helper<R: Read>(
@ -587,19 +526,15 @@ fn conv_block_unblock_helper<R: Read>(
rstat: &mut ReadStat,
) -> Result<Vec<u8>, Box<dyn Error>> {
// Local Predicate Fns -------------------------------------------------
#[inline]
fn should_block_then_conv<R: Read>(i: &Input<R>) -> bool {
!i.non_ascii && i.cflags.block.is_some()
}
#[inline]
fn should_conv_then_block<R: Read>(i: &Input<R>) -> bool {
i.non_ascii && i.cflags.block.is_some()
}
#[inline]
fn should_unblock_then_conv<R: Read>(i: &Input<R>) -> bool {
!i.non_ascii && i.cflags.unblock.is_some()
}
#[inline]
fn should_conv_then_unblock<R: Read>(i: &Input<R>) -> bool {
i.non_ascii && i.cflags.unblock.is_some()
}
@ -607,8 +542,7 @@ fn conv_block_unblock_helper<R: Read>(
i.cflags.ctable.is_some() && i.cflags.block.is_none() && i.cflags.unblock.is_none()
}
// Local Helper Fns ----------------------------------------------------
#[inline]
fn apply_ct(buf: &mut [u8], ct: &ConversionTable) {
fn apply_conversion(buf: &mut [u8], ct: &ConversionTable) {
for idx in 0..buf.len() {
buf[idx] = ct[buf[idx] as usize];
}
@ -617,7 +551,7 @@ fn conv_block_unblock_helper<R: Read>(
if conv_only(i) {
// no block/unblock
let ct = i.cflags.ctable.unwrap();
apply_ct(&mut buf, ct);
apply_conversion(&mut buf, ct);
Ok(buf)
} else if should_block_then_conv(i) {
@ -628,7 +562,7 @@ fn conv_block_unblock_helper<R: Read>(
if let Some(ct) = i.cflags.ctable {
for buf in blocks.iter_mut() {
apply_ct(buf, ct);
apply_conversion(buf, ct);
}
}
@ -640,7 +574,7 @@ fn conv_block_unblock_helper<R: Read>(
let cbs = i.cflags.block.unwrap();
if let Some(ct) = i.cflags.ctable {
apply_ct(&mut buf, ct);
apply_conversion(&mut buf, ct);
}
let blocks = block(buf, cbs, rstat).into_iter().flatten().collect();
@ -653,7 +587,7 @@ fn conv_block_unblock_helper<R: Read>(
let mut buf = unblock(buf, cbs);
if let Some(ct) = i.cflags.ctable {
apply_ct(&mut buf, ct);
apply_conversion(&mut buf, ct);
}
Ok(buf)
@ -662,7 +596,7 @@ fn conv_block_unblock_helper<R: Read>(
let cbs = i.cflags.unblock.unwrap();
if let Some(ct) = i.cflags.ctable {
apply_ct(&mut buf, ct);
apply_conversion(&mut buf, ct);
}
let buf = unblock(buf, cbs);
@ -683,27 +617,19 @@ fn read_helper<R: Read>(
bsize: usize,
) -> Result<(ReadStat, Vec<u8>), Box<dyn Error>> {
// Local Predicate Fns -----------------------------------------------
#[inline]
fn is_conv<R: Read>(i: &Input<R>) -> bool {
i.cflags.ctable.is_some()
}
#[inline]
fn is_block<R: Read>(i: &Input<R>) -> bool {
i.cflags.block.is_some()
}
#[inline]
fn is_unblock<R: Read>(i: &Input<R>) -> bool {
i.cflags.unblock.is_some()
}
// Local Helper Fns -------------------------------------------------
#[inline]
fn perform_swab(buf: &mut [u8]) {
let mut tmp;
for base in (1..buf.len()).step_by(2) {
tmp = buf[base];
buf[base] = buf[base - 1];
buf[base - 1] = tmp;
buf.swap(base, base - 1);
}
}
// ------------------------------------------------------------------
@ -733,35 +659,35 @@ fn read_helper<R: Read>(
fn print_io_lines(update: &ProgUpdate) {
eprintln!(
"{}+{} records in",
update.reads_complete, update.reads_partial
update.read_stat.reads_complete, update.read_stat.reads_partial
);
if update.records_truncated > 0 {
eprintln!("{} truncated records", update.records_truncated);
if update.read_stat.records_truncated > 0 {
eprintln!("{} truncated records", update.read_stat.records_truncated);
}
eprintln!(
"{}+{} records out",
update.writes_complete, update.writes_partial
update.write_stat.writes_complete, update.write_stat.writes_partial
);
}
fn make_prog_line(update: &ProgUpdate) -> String {
let btotal_metric = Byte::from_bytes(update.bytes_total)
let btotal_metric = Byte::from_bytes(update.write_stat.bytes_total)
.get_appropriate_unit(false)
.format(0);
let btotal_bin = Byte::from_bytes(update.bytes_total)
let btotal_bin = Byte::from_bytes(update.write_stat.bytes_total)
.get_appropriate_unit(true)
.format(0);
let safe_millis = cmp::max(1, update.duration.as_millis());
let xfer_rate = Byte::from_bytes(1000 * (update.bytes_total / safe_millis))
let transfer_rate = Byte::from_bytes(1000 * (update.write_stat.bytes_total / safe_millis))
.get_appropriate_unit(false)
.format(1);
format!(
"{} bytes ({}, {}) copied, {:.1} s, {}/s",
update.bytes_total,
update.write_stat.bytes_total,
btotal_metric,
btotal_bin,
update.duration.as_secs_f64(),
xfer_rate
transfer_rate
)
}
fn reprint_prog_line(update: &ProgUpdate) {
@ -770,65 +696,60 @@ fn reprint_prog_line(update: &ProgUpdate) {
fn print_prog_line(update: &ProgUpdate) {
eprintln!("{}", make_prog_line(update));
}
fn print_xfer_stats(update: &ProgUpdate) {
fn print_transfer_stats(update: &ProgUpdate) {
print_io_lines(update);
print_prog_line(update);
}
/// Generate a progress updater that tracks progress, receives updates, and responds to signals.
fn gen_prog_updater(rx: mpsc::Receiver<ProgUpdate>, xfer_stats: Option<StatusLevel>) -> impl Fn() {
/// Generate a progress updater that tracks progress, receives updates, and responds to progress update requests (signals).
fn gen_prog_updater(rx: mpsc::Receiver<ProgUpdate>, print_level: Option<StatusLevel>) -> impl Fn() {
// --------------------------------------------------------------
#[cfg(target_os = "linux")]
const SIGUSR1_USIZE: usize = signal::SIGUSR1 as usize;
// --------------------------------------------------------------
fn posixly_correct() -> bool {
env::var("POSIXLY_CORRECT").is_ok()
}
fn register_signal_handlers(sigval: Arc<AtomicUsize>) -> Result<(), Box<dyn Error>> {
#[cfg(target_os = "linux")]
if !posixly_correct() {
signal_hook::flag::register_usize(signal::SIGUSR1, sigval, SIGUSR1_USIZE)?;
}
Ok(())
}
// --------------------------------------------------------------
move || {
const SIGUSR1_USIZE: usize = signal::SIGUSR1 as usize;
let sigval = Arc::new(AtomicUsize::new(0));
// TODO: SIGINFO seems to only exist for BSD (and therefore MACOS)
// I will probably want put this behind a feature-gate and may need to pass the value to handle as my own constant.
// This may involve some finagling with the signals library.
// see -> https://unix.stackexchange.com/questions/179481/siginfo-on-gnu-linux-arch-linux-missing
// if let Err(e) = signal_hook::flag::register_usize(signal::SIGINFO, sigval.clone(), signal::SIGINFO as usize)
// {
// debug_println!("Internal dd Warning: Unable to register SIGINFO handler \n\t{}", e);
// }
if !posixly_correct() {
if let Err(e) =
signal_hook::flag::register_usize(signal::SIGUSR1, sigval.clone(), SIGUSR1_USIZE)
{
debug_println!(
"Internal dd Warning: Unable to register SIGUSR1 handler \n\t{}",
register_signal_handlers(sigval.clone()).unwrap_or_else(|e| {
if Some(StatusLevel::None) != print_level {
eprintln!(
"Internal dd Warning: Unable to register signal handler \n\t{}",
e
);
}
}
});
loop {
// Wait for update
let update = match (rx.recv(), xfer_stats) {
let update = match (rx.recv(), print_level) {
(Ok(update), Some(StatusLevel::Progress)) => {
reprint_prog_line(&update);
update
}
(Ok(update), _) => update,
(Err(_), _) =>
// recv only fails permanently
{
break
(Err(_), _) => {
// recv only fails permanently, so we break here to
// avoid recv'ing on a broken pipe
break;
}
};
// Handle signals
#[allow(clippy::single_match)]
match sigval.load(Ordering::Relaxed) {
SIGUSR1_USIZE => {
print_xfer_stats(&update);
}
// SIGINFO_USIZE => ...
_ => { /* no signals recv'd */ }
#[cfg(target_os = "linux")]
if let SIGUSR1_USIZE = sigval.load(Ordering::Relaxed) {
print_transfer_stats(&update);
};
}
}
@ -840,7 +761,6 @@ fn gen_prog_updater(rx: mpsc::Receiver<ProgUpdate>, xfer_stats: Option<StatusLev
/// sane real-world memory use, it should not be too large. I believe
/// the least common multiple is a good representation of these interests.
/// https://en.wikipedia.org/wiki/Least_common_multiple#Using_the_greatest_common_divisor
#[inline]
fn calc_bsize(ibs: usize, obs: usize) -> usize {
let gcd = Gcd::gcd(ibs, obs);
// calculate the lcm from gcd
@ -878,12 +798,10 @@ fn below_count_limit(count: &Option<CountType>, rstat: &ReadStat, wstat: &WriteS
match count {
Some(CountType::Reads(n)) => {
let n = (*n).try_into().unwrap();
// debug_assert!(rstat.reads_complete + rstat.reads_partial >= n);
rstat.reads_complete + rstat.reads_partial <= n
}
Some(CountType::Bytes(n)) => {
let n = (*n).try_into().unwrap();
// debug_assert!(wstat.bytes_total >= n);
wstat.bytes_total <= n
}
None => true,
@ -891,8 +809,8 @@ fn below_count_limit(count: &Option<CountType>, rstat: &ReadStat, wstat: &WriteS
}
/// Perform the copy/convert operations. Stdout version
// Note: Some of dd's functionality depends on whether the output is actually a file. This breaks the Output<Write> abstraction,
// and should be fixed in the future.
/// Note: The body of this function should be kept identical to dd_fileout. This is definitely a problem from a maintenance perspective
/// and should be addressed (TODO). The problem exists because some of dd's functionality depends on whether the output is a file or stdout.
fn dd_stdout<R: Read>(mut i: Input<R>, mut o: Output<io::Stdout>) -> Result<(), Box<dyn Error>> {
let mut rstat = ReadStat {
reads_complete: 0,
@ -909,7 +827,7 @@ fn dd_stdout<R: Read>(mut i: Input<R>, mut o: Output<io::Stdout>) -> Result<(),
let prog_tx = {
let (tx, rx) = mpsc::channel();
thread::spawn(gen_prog_updater(rx, i.xfer_stats));
thread::spawn(gen_prog_updater(rx, i.print_level));
tx
};
@ -934,12 +852,8 @@ fn dd_stdout<R: Read>(mut i: Input<R>, mut o: Output<io::Stdout>) -> Result<(),
};
// Update Prog
prog_tx.send(ProgUpdate {
reads_complete: rstat.reads_complete,
reads_partial: rstat.reads_partial,
writes_complete: wstat.writes_complete,
writes_partial: wstat.writes_partial,
bytes_total: wstat.bytes_total,
records_truncated: rstat.records_truncated,
read_stat: rstat,
write_stat: wstat,
duration: start.elapsed(),
})?;
}
@ -950,15 +864,11 @@ fn dd_stdout<R: Read>(mut i: Input<R>, mut o: Output<io::Stdout>) -> Result<(),
o.fdatasync()?;
}
match i.xfer_stats {
match i.print_level {
Some(StatusLevel::Noxfer) | Some(StatusLevel::None) => {}
_ => print_xfer_stats(&ProgUpdate {
reads_complete: rstat.reads_complete,
reads_partial: rstat.reads_partial,
writes_complete: wstat.writes_complete,
writes_partial: wstat.writes_partial,
bytes_total: wstat.bytes_total,
records_truncated: rstat.records_truncated,
_ => print_transfer_stats(&ProgUpdate {
read_stat: rstat,
write_stat: wstat,
duration: start.elapsed(),
}),
}
@ -966,8 +876,8 @@ fn dd_stdout<R: Read>(mut i: Input<R>, mut o: Output<io::Stdout>) -> Result<(),
}
/// Perform the copy/convert operations. File backed output version
// Note: Some of dd's functionality depends on whether the output is actually a file. This breaks the Output<Write> abstraction,
// and should be fixed in the future.
/// Note: The body of this function should be kept identical to dd_stdout. This is definitely a problem from a maintenance perspective
/// and should be addressed (TODO). The problem exists because some of dd's functionality depends on whether the output is a file or stdout.
fn dd_fileout<R: Read>(mut i: Input<R>, mut o: Output<File>) -> Result<(), Box<dyn Error>> {
let mut rstat = ReadStat {
reads_complete: 0,
@ -984,7 +894,7 @@ fn dd_fileout<R: Read>(mut i: Input<R>, mut o: Output<File>) -> Result<(), Box<d
let prog_tx = {
let (tx, rx) = mpsc::channel();
thread::spawn(gen_prog_updater(rx, i.xfer_stats));
thread::spawn(gen_prog_updater(rx, i.print_level));
tx
};
@ -1009,12 +919,8 @@ fn dd_fileout<R: Read>(mut i: Input<R>, mut o: Output<File>) -> Result<(), Box<d
};
// Update Prog
prog_tx.send(ProgUpdate {
reads_complete: rstat.reads_complete,
reads_partial: rstat.reads_partial,
writes_complete: wstat.writes_complete,
writes_partial: wstat.writes_partial,
bytes_total: wstat.bytes_total,
records_truncated: rstat.records_truncated,
read_stat: rstat,
write_stat: wstat,
duration: start.elapsed(),
})?;
}
@ -1025,27 +931,22 @@ fn dd_fileout<R: Read>(mut i: Input<R>, mut o: Output<File>) -> Result<(), Box<d
o.fdatasync()?;
}
match i.xfer_stats {
match i.print_level {
Some(StatusLevel::Noxfer) | Some(StatusLevel::None) => {}
_ => print_xfer_stats(&ProgUpdate {
reads_complete: rstat.reads_complete,
reads_partial: rstat.reads_partial,
writes_complete: wstat.writes_complete,
writes_partial: wstat.writes_partial,
bytes_total: wstat.bytes_total,
records_truncated: rstat.records_truncated,
_ => print_transfer_stats(&ProgUpdate {
read_stat: rstat,
write_stat: wstat,
duration: start.elapsed(),
}),
}
Ok(())
}
// The compiler does not like Clippy's suggestion to use &str in place of &String here.
#[allow(clippy::ptr_arg)]
fn append_dashes_if_not_present(mut acc: Vec<String>, s: &String) -> Vec<String> {
if Some("--") != s.get(0..=1) {
acc.push(format!("--{}", s));
fn append_dashes_if_not_present(mut acc: Vec<String>, mut s: String) -> Vec<String> {
if !s.starts_with("--") && !s.starts_with("-") {
s.insert_str(0, "--");
}
acc.push(s);
acc
}
@ -1074,13 +975,11 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
let dashed_args = args
.collect_str(InvalidEncodingHandling::Ignore)
.accept_any()
.iter()
.into_iter()
.fold(Vec::new(), append_dashes_if_not_present);
let matches = uu_app()
// TODO: usage, after_help
//.usage(...)
//.after_help(...)
//.after_help(TODO: Add note about multiplier strings here.)
.get_matches_from(dashed_args);
let result = match (
@ -1121,7 +1020,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
match result {
Ok(_) => RTN_SUCCESS,
Err(e) => {
debug_println!("dd exiting with error:\n\t{}", e);
eprintln!("dd exiting with error:\n\t{}", e);
RTN_FAILURE
}
}
@ -1211,7 +1110,7 @@ Printing performance stats is also triggered by the INFO signal (where supported
clap::Arg::with_name(options::CONV)
.long(options::CONV)
.takes_value(true)
.help("conv=CONV[,CONV] (alternatively --conv CONV[,CONV]) specifies a comma-separated list of conversion options or (for legacy reasons) file-flags. Conversion options and file flags may be intermixed.
.help("conv=CONV[,CONV] (alternatively --conv CONV[,CONV]) specifies a comma-separated list of conversion options or (for legacy reasons) file flags. Conversion options and file flags may be intermixed.
Conversion options:
\t One of {ascii, ebcdic, ibm} will perform an encoding conversion.
@ -1231,7 +1130,7 @@ Conversion options:
\t 'swab' swaps each adjacent pair of bytes. If an odd number of bytes is present, the final byte is omitted.
\t 'sync' pad each ibs-sided block with zeros. If 'block' or 'unblock' is specified, pad with spaces instead.
Flags:
Conversion Flags:
\t One of {excl, nocreat}
\t\t 'excl' the output file must be created. Fail if the output file is already present.
\t\t 'nocreat' the output file will not be created. Fail if the output file in not already present.
@ -1264,10 +1163,6 @@ General-Flags
\t 'noctty' do not assign a controlling tty.
\t 'nofollow' do not follow system links.
Output-Flags
\t 'append' open file in append mode. Consider setting conv=notrunc as well.
\t 'seek_bytes' a value to seek=N will be interpreted as bytes.
")
)
.arg(

View file

@ -1,20 +1,7 @@
/* cspell:disable */
use super::*;
const NL: u8 = '\n' as u8;
const SPACE: u8 = ' ' as u8;
macro_rules! rs (
() =>
{
ReadStat {
reads_complete: 0,
reads_partial: 0,
records_truncated: 0,
}
};
);
const NL: u8 = b'\n';
const SPACE: u8 = b' ';
macro_rules! make_block_test (
( $test_id:ident, $test_name:expr, $src:expr, $block:expr, $spec:expr ) =>
@ -25,7 +12,7 @@ macro_rules! make_block_test (
src: $src,
non_ascii: false,
ibs: 512,
xfer_stats: None,
print_level: None,
count: None,
cflags: IConvFlags {
ctable: None,
@ -57,7 +44,7 @@ macro_rules! make_unblock_test (
src: $src,
non_ascii: false,
ibs: 512,
xfer_stats: None,
print_level: None,
count: None,
cflags: IConvFlags {
ctable: None,
@ -82,7 +69,7 @@ macro_rules! make_unblock_test (
#[test]
fn block_test_no_nl() {
let mut rs = rs!();
let mut rs = ReadStat::default();
let buf = vec![0u8, 1u8, 2u8, 3u8];
let res = block(buf, 4, &mut rs);
@ -91,7 +78,7 @@ fn block_test_no_nl() {
#[test]
fn block_test_no_nl_short_record() {
let mut rs = rs!();
let mut rs = ReadStat::default();
let buf = vec![0u8, 1u8, 2u8, 3u8];
let res = block(buf, 8, &mut rs);
@ -103,17 +90,18 @@ fn block_test_no_nl_short_record() {
#[test]
fn block_test_no_nl_trunc() {
let mut rs = rs!();
let mut rs = ReadStat::default();
let buf = vec![0u8, 1u8, 2u8, 3u8, 4u8];
let res = block(buf, 4, &mut rs);
// Commented section should be truncated and appear for reference only.
assert_eq!(res, vec![vec![0u8, 1u8, 2u8, 3u8 /*, 4u8*/],]);
assert_eq!(rs.records_truncated, 1);
}
#[test]
fn block_test_nl_gt_cbs_trunc() {
let mut rs = rs!();
let mut rs = ReadStat::default();
let buf = vec![
0u8, 1u8, 2u8, 3u8, 4u8, NL, 0u8, 1u8, 2u8, 3u8, 4u8, NL, 5u8, 6u8, 7u8, 8u8,
];
@ -122,6 +110,7 @@ fn block_test_nl_gt_cbs_trunc() {
assert_eq!(
res,
vec![
// Commented lines should be truncated and appear for reference only.
vec![0u8, 1u8, 2u8, 3u8],
// vec![4u8, SPACE, SPACE, SPACE],
vec![0u8, 1u8, 2u8, 3u8],
@ -134,7 +123,7 @@ fn block_test_nl_gt_cbs_trunc() {
#[test]
fn block_test_surrounded_nl() {
let mut rs = rs!();
let mut rs = ReadStat::default();
let buf = vec![0u8, 1u8, 2u8, 3u8, NL, 4u8, 5u8, 6u8, 7u8, 8u8];
let res = block(buf, 8, &mut rs);
@ -149,7 +138,7 @@ fn block_test_surrounded_nl() {
#[test]
fn block_test_multiple_nl_same_cbs_block() {
let mut rs = rs!();
let mut rs = ReadStat::default();
let buf = vec![0u8, 1u8, 2u8, 3u8, NL, 4u8, NL, 5u8, 6u8, 7u8, 8u8, 9u8];
let res = block(buf, 8, &mut rs);
@ -165,7 +154,7 @@ fn block_test_multiple_nl_same_cbs_block() {
#[test]
fn block_test_multiple_nl_diff_cbs_block() {
let mut rs = rs!();
let mut rs = ReadStat::default();
let buf = vec![0u8, 1u8, 2u8, 3u8, NL, 4u8, 5u8, 6u8, 7u8, NL, 8u8, 9u8];
let res = block(buf, 8, &mut rs);
@ -181,7 +170,7 @@ fn block_test_multiple_nl_diff_cbs_block() {
#[test]
fn block_test_end_nl_diff_cbs_block() {
let mut rs = rs!();
let mut rs = ReadStat::default();
let buf = vec![0u8, 1u8, 2u8, 3u8, NL];
let res = block(buf, 4, &mut rs);
@ -190,7 +179,7 @@ fn block_test_end_nl_diff_cbs_block() {
#[test]
fn block_test_end_nl_same_cbs_block() {
let mut rs = rs!();
let mut rs = ReadStat::default();
let buf = vec![0u8, 1u8, 2u8, NL];
let res = block(buf, 4, &mut rs);
@ -199,7 +188,7 @@ fn block_test_end_nl_same_cbs_block() {
#[test]
fn block_test_double_end_nl() {
let mut rs = rs!();
let mut rs = ReadStat::default();
let buf = vec![0u8, 1u8, 2u8, NL, NL];
let res = block(buf, 4, &mut rs);
@ -211,7 +200,7 @@ fn block_test_double_end_nl() {
#[test]
fn block_test_start_nl() {
let mut rs = rs!();
let mut rs = ReadStat::default();
let buf = vec![NL, 0u8, 1u8, 2u8, 3u8];
let res = block(buf, 4, &mut rs);
@ -223,7 +212,7 @@ fn block_test_start_nl() {
#[test]
fn block_test_double_surrounded_nl_no_trunc() {
let mut rs = rs!();
let mut rs = ReadStat::default();
let buf = vec![0u8, 1u8, 2u8, 3u8, NL, NL, 4u8, 5u8, 6u8, 7u8];
let res = block(buf, 8, &mut rs);
@ -239,13 +228,14 @@ fn block_test_double_surrounded_nl_no_trunc() {
#[test]
fn block_test_double_surrounded_nl_double_trunc() {
let mut rs = rs!();
let mut rs = ReadStat::default();
let buf = vec![0u8, 1u8, 2u8, 3u8, NL, NL, 4u8, 5u8, 6u8, 7u8, 8u8];
let res = block(buf, 4, &mut rs);
assert_eq!(
res,
vec![
// Commented section should be truncated and appear for reference only.
vec![0u8, 1u8, 2u8, 3u8],
vec![SPACE, SPACE, SPACE, SPACE],
vec![4u8, 5u8, 6u8, 7u8 /*, 8u8*/],

View file

@ -1,5 +1,3 @@
/* cspell:disable */
use super::*;
macro_rules! make_sync_test (
@ -11,7 +9,7 @@ macro_rules! make_sync_test (
src: $src,
non_ascii: false,
ibs: $ibs,
xfer_stats: None,
print_level: None,
count: None,
cflags: IConvFlags {
ctable: None,

View file

@ -1,5 +1,3 @@
/* cspell:disable */
use super::*;
macro_rules! make_conv_test (
@ -11,7 +9,7 @@ macro_rules! make_conv_test (
src: $src,
non_ascii: false,
ibs: 512,
xfer_stats: None,
print_level: None,
count: None,
cflags: icf!($ctable),
iflags: DEFAULT_IFLAGS,
@ -36,7 +34,7 @@ macro_rules! make_icf_test (
src: $src,
non_ascii: false,
ibs: 512,
xfer_stats: None,
print_level: None,
count: None,
cflags: $icf,
iflags: DEFAULT_IFLAGS,
@ -141,7 +139,7 @@ fn all_valid_ascii_ebcdic_ascii_roundtrip_conv_test() {
.unwrap(),
non_ascii: false,
ibs: 128,
xfer_stats: None,
print_level: None,
count: None,
cflags: icf!(Some(&ASCII_TO_EBCDIC)),
iflags: DEFAULT_IFLAGS,
@ -163,7 +161,7 @@ fn all_valid_ascii_ebcdic_ascii_roundtrip_conv_test() {
src: File::open(&tmp_fname_ae).unwrap(),
non_ascii: false,
ibs: 256,
xfer_stats: None,
print_level: None,
count: None,
cflags: icf!(Some(&EBCDIC_TO_ASCII)),
iflags: DEFAULT_IFLAGS,

View file

@ -1,5 +1,3 @@
/* cspell:disable */
use super::*;
mod block_unblock_tests;
@ -39,24 +37,6 @@ const DEFAULT_IFLAGS: IFlags = IFlags {
skip_bytes: false,
};
// const DEFAULT_OFLAGS: OFlags = OFlags {
// append: false,
// cio: false,
// direct: false,
// directory: false,
// dsync: false,
// sync: false,
// nocache: false,
// nonblock: false,
// noatime: false,
// noctty: false,
// nofollow: false,
// nolinks: false,
// binary: false,
// text: false,
// seek_bytes: false,
// };
struct LazyReader<R: Read> {
src: R,
}
@ -102,7 +82,7 @@ macro_rules! make_spec_test (
src: $src,
non_ascii: false,
ibs: 512,
xfer_stats: None,
print_level: None,
count: None,
cflags: icf!(),
iflags: DEFAULT_IFLAGS,

View file

@ -1,5 +1,3 @@
/* cspell:disable */
use super::*;
const DST_PLACEHOLDER: Vec<u8> = Vec::new();
@ -52,7 +50,7 @@ make_io_test!(
src: File::open("./test-resources/random-5828891cb1230748e146f34223bbd3b5.test").unwrap(),
non_ascii: false,
ibs: 521,
xfer_stats: None,
print_level: None,
count: None,
cflags: icf!(),
iflags: DEFAULT_IFLAGS,
@ -72,7 +70,7 @@ make_io_test!(
src: File::open("./test-resources/random-5828891cb1230748e146f34223bbd3b5.test").unwrap(),
non_ascii: false,
ibs: 1031,
xfer_stats: None,
print_level: None,
count: None,
cflags: icf!(),
iflags: DEFAULT_IFLAGS,
@ -92,7 +90,7 @@ make_io_test!(
src: File::open("./test-resources/deadbeef-18d99661a1de1fc9af21b0ec2cd67ba3.test").unwrap(),
non_ascii: false,
ibs: 1024,
xfer_stats: None,
print_level: None,
count: Some(CountType::Reads(32)),
cflags: icf!(),
iflags: DEFAULT_IFLAGS,
@ -112,7 +110,7 @@ make_io_test!(
src: File::open("./test-resources/deadbeef-18d99661a1de1fc9af21b0ec2cd67ba3.test").unwrap(),
non_ascii: false,
ibs: 531,
xfer_stats: None,
print_level: None,
count: Some(CountType::Bytes(32 * 1024)),
cflags: icf!(),
iflags: DEFAULT_IFLAGS,
@ -132,7 +130,7 @@ make_io_test!(
src: File::open("./test-resources/deadbeef-18d99661a1de1fc9af21b0ec2cd67ba3.test").unwrap(),
non_ascii: false,
ibs: 1024,
xfer_stats: None,
print_level: None,
count: Some(CountType::Reads(16)),
cflags: icf!(),
iflags: DEFAULT_IFLAGS,
@ -152,7 +150,7 @@ make_io_test!(
src: File::open("./test-resources/deadbeef-18d99661a1de1fc9af21b0ec2cd67ba3.test").unwrap(),
non_ascii: false,
ibs: 531,
xfer_stats: None,
print_level: None,
count: Some(CountType::Bytes(12345)),
cflags: icf!(),
iflags: DEFAULT_IFLAGS,
@ -172,7 +170,7 @@ make_io_test!(
src: File::open("./test-resources/random-5828891cb1230748e146f34223bbd3b5.test").unwrap(),
non_ascii: false,
ibs: 1024,
xfer_stats: None,
print_level: None,
count: Some(CountType::Reads(32)),
cflags: icf!(),
iflags: DEFAULT_IFLAGS,
@ -192,7 +190,7 @@ make_io_test!(
src: File::open("./test-resources/random-5828891cb1230748e146f34223bbd3b5.test").unwrap(),
non_ascii: false,
ibs: 521,
xfer_stats: None,
print_level: None,
count: Some(CountType::Bytes(32 * 1024)),
cflags: icf!(),
iflags: DEFAULT_IFLAGS,
@ -215,7 +213,7 @@ make_io_test!(
},
non_ascii: false,
ibs: 521,
xfer_stats: None,
print_level: None,
count: None,
cflags: icf!(),
iflags: IFlags {

View file

@ -5,8 +5,6 @@
// For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code.
/* cspell:disable */
#[cfg(test)]
mod unit_tests;
@ -24,9 +22,8 @@ pub enum ParseError {
MultipleExclNoCreat,
FlagNoMatch(String),
ConvFlagNoMatch(String),
NoMatchingMultiplier(String),
ByteStringContainsNoValue(String),
MultiplierStringWouldOverflow(String),
MultiplierStringParseFailure(String),
MultiplierStringOverflow(String),
BlockUnblockWithoutCBS,
StatusLevelNotRecognized(String),
Unimplemented(String),
@ -56,13 +53,10 @@ impl std::fmt::Display for ParseError {
Self::ConvFlagNoMatch(arg) => {
write!(f, "Unrecognized conv=CONV -> {}", arg)
}
Self::NoMatchingMultiplier(arg) => {
Self::MultiplierStringParseFailure(arg) => {
write!(f, "Unrecognized byte multiplier -> {}", arg)
}
Self::ByteStringContainsNoValue(arg) => {
write!(f, "Unrecognized byte value -> {}", arg)
}
Self::MultiplierStringWouldOverflow(arg) => {
Self::MultiplierStringOverflow(arg) => {
write!(
f,
"Multiplier string would overflow on current system -> {}",
@ -302,61 +296,40 @@ impl std::str::FromStr for StatusLevel {
}
}
fn parse_multiplier(s: &'_ str) -> Result<usize, ParseError> {
let mult: u128 = match s {
"c" => 1,
"w" => 2,
"b" => 512,
"kB" => 1000,
"K" | "KiB" => 1024,
"MB" => 1000 * 1000,
"M" | "MiB" => 1024 * 1024,
"GB" => 1000 * 1000 * 1000,
"G" | "GiB" => 1024 * 1024 * 1024,
"TB" => 1000 * 1000 * 1000 * 1000,
"T" | "TiB" => 1024 * 1024 * 1024 * 1024,
"PB" => 1000 * 1000 * 1000 * 1000 * 1000,
"P" | "PiB" => 1024 * 1024 * 1024 * 1024 * 1024,
"EB" => 1000 * 1000 * 1000 * 1000 * 1000 * 1000,
"E" | "EiB" => 1024 * 1024 * 1024 * 1024 * 1024 * 1024,
"ZB" => 1000 * 1000 * 1000 * 1000 * 1000 * 1000 * 1000,
"Z" | "ZiB" => 1024 * 1024 * 1024 * 1024 * 1024 * 1024 * 1024,
"YB" => 1000 * 1000 * 1000 * 1000 * 1000 * 1000 * 1000 * 1000,
"Y" | "YiB" => 1024 * 1024 * 1024 * 1024 * 1024 * 1024 * 1024 * 1024,
_ => return Err(ParseError::NoMatchingMultiplier(s.to_string())),
};
mult.try_into()
.map_err(|_e| ParseError::MultiplierStringWouldOverflow(s.to_string()))
}
/// Parse bytes using str::parse, then map error if needed.
fn parse_bytes_only(s: &str) -> Result<usize, ParseError> {
match s.parse() {
Ok(bytes) => Ok(bytes),
Err(_) => Err(ParseError::ByteStringContainsNoValue(s.to_string())),
}
s.parse()
.map_err(|_| ParseError::MultiplierStringParseFailure(s.to_string()))
}
/// Parse byte and multiplier like 512, 5KiB, or 1G.
/// Uses uucore::parse_size, and adds the 'w' and 'c' suffixes which are mentioned
/// in dd's info page.
fn parse_bytes_with_opt_multiplier(s: &str) -> Result<usize, ParseError> {
match s.find(char::is_alphabetic) {
Some(idx) => {
let base = parse_bytes_only(&s[..idx])?;
let mult = parse_multiplier(&s[idx..])?;
if let Some(idx) = s.rfind('c') {
parse_bytes_only(&s[..idx])
} else if let Some(idx) = s.rfind('w') {
let partial = parse_bytes_only(&s[..idx])?;
if let Some(bytes) = base.checked_mul(mult) {
Ok(bytes)
} else {
Err(ParseError::MultiplierStringWouldOverflow(s.to_string()))
partial
.checked_mul(2)
.ok_or_else(|| ParseError::MultiplierStringOverflow(s.to_string()))
} else {
uucore::parse_size::parse_size(s).map_err(|e| match e {
uucore::parse_size::ParseSizeError::ParseFailure(s) => {
ParseError::MultiplierStringParseFailure(s)
}
}
_ => parse_bytes_only(s),
uucore::parse_size::ParseSizeError::SizeTooBig(s) => {
ParseError::MultiplierStringOverflow(s)
}
})
}
}
pub fn parse_ibs(matches: &Matches) -> Result<usize, ParseError> {
if let Some(mixed_str) = matches.value_of("bs") {
if let Some(mixed_str) = matches.value_of(options::BS) {
parse_bytes_with_opt_multiplier(mixed_str)
} else if let Some(mixed_str) = matches.value_of("ibs") {
} else if let Some(mixed_str) = matches.value_of(options::IBS) {
parse_bytes_with_opt_multiplier(mixed_str)
} else {
Ok(512)
@ -364,7 +337,7 @@ pub fn parse_ibs(matches: &Matches) -> Result<usize, ParseError> {
}
fn parse_cbs(matches: &Matches) -> Result<Option<usize>, ParseError> {
if let Some(s) = matches.value_of("cbs") {
if let Some(s) = matches.value_of(options::CBS) {
let bytes = parse_bytes_with_opt_multiplier(s)?;
Ok(Some(bytes))
} else {
@ -373,7 +346,7 @@ fn parse_cbs(matches: &Matches) -> Result<Option<usize>, ParseError> {
}
pub fn parse_status_level(matches: &Matches) -> Result<Option<StatusLevel>, ParseError> {
match matches.value_of("status") {
match matches.value_of(options::STATUS) {
Some(s) => {
let st = s.parse()?;
Ok(Some(st))

View file

@ -1,5 +1,3 @@
/* cspell:disable */
use super::*;
use crate::StatusLevel;
@ -7,7 +5,7 @@ use crate::StatusLevel;
#[cfg(not(target_os = "linux"))]
#[test]
fn unimplemented_flags_should_error_non_unix() {
let mut unfailed = Vec::new();
let mut succeeded = Vec::new();
// The following flags are only implemented in linux
for flag in vec![
@ -28,26 +26,26 @@ fn unimplemented_flags_should_error_non_unix() {
let matches = uu_app().get_matches_from_safe(args).unwrap();
match parse_iflags(&matches) {
Ok(_) => unfailed.push(format!("iflag={}", flag)),
Ok(_) => succeeded.push(format!("iflag={}", flag)),
Err(_) => { /* expected behaviour :-) */ }
}
match parse_oflags(&matches) {
Ok(_) => unfailed.push(format!("oflag={}", flag)),
Ok(_) => succeeded.push(format!("oflag={}", flag)),
Err(_) => { /* expected behaviour :-) */ }
}
}
if !unfailed.is_empty() {
if !succeeded.is_empty() {
panic!(
"The following flags did not panic as expected: {:?}",
unfailed
succeeded
);
}
}
#[test]
fn unimplemented_flags_should_error() {
let mut unfailed = Vec::new();
let mut succeeded = Vec::new();
// The following flags are not implemented
for flag in vec!["cio", "nocache", "nolinks", "text", "binary"] {
@ -59,19 +57,19 @@ fn unimplemented_flags_should_error() {
let matches = uu_app().get_matches_from_safe(args).unwrap();
match parse_iflags(&matches) {
Ok(_) => unfailed.push(format!("iflag={}", flag)),
Ok(_) => succeeded.push(format!("iflag={}", flag)),
Err(_) => { /* expected behaviour :-) */ }
}
match parse_oflags(&matches) {
Ok(_) => unfailed.push(format!("oflag={}", flag)),
Ok(_) => succeeded.push(format!("oflag={}", flag)),
Err(_) => { /* expected behaviour :-) */ }
}
}
if !unfailed.is_empty() {
if !succeeded.is_empty() {
panic!(
"The following flags did not panic as expected: {:?}",
unfailed
succeeded
);
}
}
@ -105,6 +103,176 @@ fn test_status_level_none() {
assert_eq!(st, StatusLevel::None);
}
#[test]
fn test_all_top_level_args_no_leading_dashes_sep_by_equals() {
let args = vec![
String::from("dd"),
String::from("if=foo.file"),
String::from("of=bar.file"),
String::from("ibs=10"),
String::from("obs=10"),
String::from("cbs=1"),
String::from("bs=100"),
String::from("count=2"),
String::from("skip=2"),
String::from("seek=2"),
String::from("status=progress"),
String::from("conv=ascii,ucase"),
String::from("iflag=count_bytes,skip_bytes"),
String::from("oflag=append,seek_bytes"),
];
let args = args
.into_iter()
.fold(Vec::new(), append_dashes_if_not_present);
let matches = uu_app().get_matches_from_safe(args).unwrap();
assert_eq!(100, parse_ibs(&matches).unwrap());
assert_eq!(100, parse_obs(&matches).unwrap());
assert_eq!(1, parse_cbs(&matches).unwrap().unwrap());
assert_eq!(
CountType::Bytes(2),
parse_count(
&IFlags {
count_bytes: true,
..IFlags::default()
},
&matches
)
.unwrap()
.unwrap()
);
assert_eq!(
200,
parse_skip_amt(&100, &IFlags::default(), &matches)
.unwrap()
.unwrap()
);
assert_eq!(
200,
parse_seek_amt(&100, &OFlags::default(), &matches)
.unwrap()
.unwrap()
);
assert_eq!(
StatusLevel::Progress,
parse_status_level(&matches).unwrap().unwrap()
);
assert_eq!(
IConvFlags {
ctable: Some(&EBCDIC_TO_ASCII_LCASE_TO_UCASE),
..IConvFlags::default()
},
parse_conv_flag_input(&matches).unwrap()
);
assert_eq!(
OConvFlags::default(),
parse_conv_flag_output(&matches).unwrap()
);
assert_eq!(
IFlags {
count_bytes: true,
skip_bytes: true,
..IFlags::default()
},
parse_iflags(&matches).unwrap()
);
assert_eq!(
OFlags {
append: true,
seek_bytes: true,
..OFlags::default()
},
parse_oflags(&matches).unwrap()
);
}
#[ignore]
#[test]
// TODO: This should work, but Clap doesn't seem to understand it. Leaving it for now since the traditional dd if=foo.file works just fine.
fn test_all_top_level_args_leading_dashes_sep_by_spaces() {
let args = vec![
String::from("dd"),
String::from("--if foo.file"),
String::from("--of bar.file"),
String::from("--ibs 10"),
String::from("--obs 10"),
String::from("--cbs 1"),
String::from("--bs 100"),
String::from("--count 2"),
String::from("--skip 2"),
String::from("--seek 2"),
String::from("--status progress"),
String::from("--conv ascii,ucase"),
String::from("--iflag count_bytes,skip_bytes"),
String::from("--oflag append,seek_bytes"),
];
let args = args
.into_iter()
.fold(Vec::new(), append_dashes_if_not_present);
let matches = uu_app().get_matches_from_safe(args).unwrap();
assert_eq!(100, parse_ibs(&matches).unwrap());
assert_eq!(100, parse_obs(&matches).unwrap());
assert_eq!(1, parse_cbs(&matches).unwrap().unwrap());
assert_eq!(
CountType::Bytes(2),
parse_count(
&IFlags {
count_bytes: true,
..IFlags::default()
},
&matches
)
.unwrap()
.unwrap()
);
assert_eq!(
200,
parse_skip_amt(&100, &IFlags::default(), &matches)
.unwrap()
.unwrap()
);
assert_eq!(
200,
parse_seek_amt(&100, &OFlags::default(), &matches)
.unwrap()
.unwrap()
);
assert_eq!(
StatusLevel::Progress,
parse_status_level(&matches).unwrap().unwrap()
);
assert_eq!(
IConvFlags {
ctable: Some(&EBCDIC_TO_ASCII_LCASE_TO_UCASE),
..IConvFlags::default()
},
parse_conv_flag_input(&matches).unwrap()
);
assert_eq!(
OConvFlags::default(),
parse_conv_flag_output(&matches).unwrap()
);
assert_eq!(
IFlags {
count_bytes: true,
skip_bytes: true,
..IFlags::default()
},
parse_iflags(&matches).unwrap()
);
assert_eq!(
OFlags {
append: true,
seek_bytes: true,
..OFlags::default()
},
parse_oflags(&matches).unwrap()
);
}
#[test]
fn test_status_level_progress() {
let args = vec![
@ -374,16 +542,6 @@ test_byte_parser!(
6 * 1024 * 1024 * 1024 * 1024 * 1024 * 1024
);
#[test]
#[should_panic]
#[allow(non_snake_case)]
fn test_KB_multiplier_error() {
// KB is not valid (kB, K, and KiB are)
let bs_str = "2000KB";
parse_bytes_with_opt_multiplier(bs_str).unwrap();
}
#[test]
#[should_panic]
fn test_overflow_panic() {
@ -395,7 +553,7 @@ fn test_overflow_panic() {
#[test]
#[should_panic]
fn test_neg_panic() {
let bs_str = format!("{}KiB", -1);
let bs_str = format!("{}", -1);
parse_bytes_with_opt_multiplier(&bs_str).unwrap();
}

View file

@ -1,5 +1,3 @@
/* cspell:disable */
use crate::common::util::*;
use std::fs::{File, OpenOptions};