1
Fork 0
mirror of https://github.com/RGBCube/uutils-coreutils synced 2025-07-29 03:57:44 +00:00

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", "chmod",
"chown", "chown",
"chroot", "chroot",
"dd",
"groups", "groups",
"hostid", "hostid",
"id", "id",

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,5 +1,3 @@
/* cspell:disable */
use super::*; use super::*;
mod block_unblock_tests; mod block_unblock_tests;
@ -39,24 +37,6 @@ const DEFAULT_IFLAGS: IFlags = IFlags {
skip_bytes: false, 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> { struct LazyReader<R: Read> {
src: R, src: R,
} }
@ -102,7 +82,7 @@ macro_rules! make_spec_test (
src: $src, src: $src,
non_ascii: false, non_ascii: false,
ibs: 512, ibs: 512,
xfer_stats: None, print_level: None,
count: None, count: None,
cflags: icf!(), cflags: icf!(),
iflags: DEFAULT_IFLAGS, iflags: DEFAULT_IFLAGS,

View file

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

View file

@ -5,8 +5,6 @@
// For the full copyright and license information, please view the LICENSE // For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code. // file that was distributed with this source code.
/* cspell:disable */
#[cfg(test)] #[cfg(test)]
mod unit_tests; mod unit_tests;
@ -24,9 +22,8 @@ pub enum ParseError {
MultipleExclNoCreat, MultipleExclNoCreat,
FlagNoMatch(String), FlagNoMatch(String),
ConvFlagNoMatch(String), ConvFlagNoMatch(String),
NoMatchingMultiplier(String), MultiplierStringParseFailure(String),
ByteStringContainsNoValue(String), MultiplierStringOverflow(String),
MultiplierStringWouldOverflow(String),
BlockUnblockWithoutCBS, BlockUnblockWithoutCBS,
StatusLevelNotRecognized(String), StatusLevelNotRecognized(String),
Unimplemented(String), Unimplemented(String),
@ -56,13 +53,10 @@ impl std::fmt::Display for ParseError {
Self::ConvFlagNoMatch(arg) => { Self::ConvFlagNoMatch(arg) => {
write!(f, "Unrecognized conv=CONV -> {}", arg) write!(f, "Unrecognized conv=CONV -> {}", arg)
} }
Self::NoMatchingMultiplier(arg) => { Self::MultiplierStringParseFailure(arg) => {
write!(f, "Unrecognized byte multiplier -> {}", arg) write!(f, "Unrecognized byte multiplier -> {}", arg)
} }
Self::ByteStringContainsNoValue(arg) => { Self::MultiplierStringOverflow(arg) => {
write!(f, "Unrecognized byte value -> {}", arg)
}
Self::MultiplierStringWouldOverflow(arg) => {
write!( write!(
f, f,
"Multiplier string would overflow on current system -> {}", "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> { /// Parse bytes using str::parse, then map error if needed.
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()))
}
fn parse_bytes_only(s: &str) -> Result<usize, ParseError> { fn parse_bytes_only(s: &str) -> Result<usize, ParseError> {
match s.parse() { s.parse()
Ok(bytes) => Ok(bytes), .map_err(|_| ParseError::MultiplierStringParseFailure(s.to_string()))
Err(_) => Err(ParseError::ByteStringContainsNoValue(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> { fn parse_bytes_with_opt_multiplier(s: &str) -> Result<usize, ParseError> {
match s.find(char::is_alphabetic) { if let Some(idx) = s.rfind('c') {
Some(idx) => { parse_bytes_only(&s[..idx])
let base = parse_bytes_only(&s[..idx])?; } else if let Some(idx) = s.rfind('w') {
let mult = parse_multiplier(&s[idx..])?; let partial = parse_bytes_only(&s[..idx])?;
if let Some(bytes) = base.checked_mul(mult) { partial
Ok(bytes) .checked_mul(2)
} else { .ok_or_else(|| ParseError::MultiplierStringOverflow(s.to_string()))
Err(ParseError::MultiplierStringWouldOverflow(s.to_string())) } else {
uucore::parse_size::parse_size(s).map_err(|e| match e {
uucore::parse_size::ParseSizeError::ParseFailure(s) => {
ParseError::MultiplierStringParseFailure(s)
} }
} uucore::parse_size::ParseSizeError::SizeTooBig(s) => {
_ => parse_bytes_only(s), ParseError::MultiplierStringOverflow(s)
}
})
} }
} }
pub fn parse_ibs(matches: &Matches) -> Result<usize, ParseError> { 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) 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) parse_bytes_with_opt_multiplier(mixed_str)
} else { } else {
Ok(512) Ok(512)
@ -364,7 +337,7 @@ pub fn parse_ibs(matches: &Matches) -> Result<usize, ParseError> {
} }
fn parse_cbs(matches: &Matches) -> Result<Option<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)?; let bytes = parse_bytes_with_opt_multiplier(s)?;
Ok(Some(bytes)) Ok(Some(bytes))
} else { } 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> { pub fn parse_status_level(matches: &Matches) -> Result<Option<StatusLevel>, ParseError> {
match matches.value_of("status") { match matches.value_of(options::STATUS) {
Some(s) => { Some(s) => {
let st = s.parse()?; let st = s.parse()?;
Ok(Some(st)) Ok(Some(st))

View file

@ -1,5 +1,3 @@
/* cspell:disable */
use super::*; use super::*;
use crate::StatusLevel; use crate::StatusLevel;
@ -7,7 +5,7 @@ use crate::StatusLevel;
#[cfg(not(target_os = "linux"))] #[cfg(not(target_os = "linux"))]
#[test] #[test]
fn unimplemented_flags_should_error_non_unix() { 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 // The following flags are only implemented in linux
for flag in vec![ 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(); let matches = uu_app().get_matches_from_safe(args).unwrap();
match parse_iflags(&matches) { match parse_iflags(&matches) {
Ok(_) => unfailed.push(format!("iflag={}", flag)), Ok(_) => succeeded.push(format!("iflag={}", flag)),
Err(_) => { /* expected behaviour :-) */ } Err(_) => { /* expected behaviour :-) */ }
} }
match parse_oflags(&matches) { match parse_oflags(&matches) {
Ok(_) => unfailed.push(format!("oflag={}", flag)), Ok(_) => succeeded.push(format!("oflag={}", flag)),
Err(_) => { /* expected behaviour :-) */ } Err(_) => { /* expected behaviour :-) */ }
} }
} }
if !unfailed.is_empty() { if !succeeded.is_empty() {
panic!( panic!(
"The following flags did not panic as expected: {:?}", "The following flags did not panic as expected: {:?}",
unfailed succeeded
); );
} }
} }
#[test] #[test]
fn unimplemented_flags_should_error() { fn unimplemented_flags_should_error() {
let mut unfailed = Vec::new(); let mut succeeded = Vec::new();
// The following flags are not implemented // The following flags are not implemented
for flag in vec!["cio", "nocache", "nolinks", "text", "binary"] { 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(); let matches = uu_app().get_matches_from_safe(args).unwrap();
match parse_iflags(&matches) { match parse_iflags(&matches) {
Ok(_) => unfailed.push(format!("iflag={}", flag)), Ok(_) => succeeded.push(format!("iflag={}", flag)),
Err(_) => { /* expected behaviour :-) */ } Err(_) => { /* expected behaviour :-) */ }
} }
match parse_oflags(&matches) { match parse_oflags(&matches) {
Ok(_) => unfailed.push(format!("oflag={}", flag)), Ok(_) => succeeded.push(format!("oflag={}", flag)),
Err(_) => { /* expected behaviour :-) */ } Err(_) => { /* expected behaviour :-) */ }
} }
} }
if !unfailed.is_empty() { if !succeeded.is_empty() {
panic!( panic!(
"The following flags did not panic as expected: {:?}", "The following flags did not panic as expected: {:?}",
unfailed succeeded
); );
} }
} }
@ -105,6 +103,176 @@ fn test_status_level_none() {
assert_eq!(st, StatusLevel::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] #[test]
fn test_status_level_progress() { fn test_status_level_progress() {
let args = vec![ let args = vec![
@ -374,16 +542,6 @@ test_byte_parser!(
6 * 1024 * 1024 * 1024 * 1024 * 1024 * 1024 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] #[test]
#[should_panic] #[should_panic]
fn test_overflow_panic() { fn test_overflow_panic() {
@ -395,7 +553,7 @@ fn test_overflow_panic() {
#[test] #[test]
#[should_panic] #[should_panic]
fn test_neg_panic() { fn test_neg_panic() {
let bs_str = format!("{}KiB", -1); let bs_str = format!("{}", -1);
parse_bytes_with_opt_multiplier(&bs_str).unwrap(); parse_bytes_with_opt_multiplier(&bs_str).unwrap();
} }

View file

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