mirror of
https://github.com/RGBCube/uutils-coreutils
synced 2026-01-20 04:01:06 +00:00
Implements functionality for seeking output files
- Adds duplicate dd fn :-( for differentiating between File backed and non-File outputs. - Implements cflag=sparse,fsync,fdatasync which were previously blocked. - Adds plumbing for IFlags & OFlags incl parsing. - Partial impl for seek=N and skip=N which were previously blocked.
This commit is contained in:
parent
3c3af72d9a
commit
4d7be2f098
4 changed files with 612 additions and 129 deletions
|
|
@ -47,8 +47,8 @@ enum SrcStat
|
|||
EOF,
|
||||
}
|
||||
|
||||
/// Captures all Conv Flags that apply to the input
|
||||
pub struct ConvFlagInput
|
||||
/// Stores all Conv Flags that apply to the input
|
||||
pub struct IConvFlags
|
||||
{
|
||||
ctable: Option<&'static ConversionTable>,
|
||||
block: bool,
|
||||
|
|
@ -58,9 +58,9 @@ pub struct ConvFlagInput
|
|||
noerror: bool,
|
||||
}
|
||||
|
||||
/// Captures all Conv Flags that apply to the output
|
||||
/// Stores all Conv Flags that apply to the output
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub struct ConvFlagOutput
|
||||
pub struct OConvFlags
|
||||
{
|
||||
sparse: bool,
|
||||
excl: bool,
|
||||
|
|
@ -70,6 +70,47 @@ pub struct ConvFlagOutput
|
|||
fsync: bool,
|
||||
}
|
||||
|
||||
/// Stores all Flags that apply to the input
|
||||
pub struct IFlags
|
||||
{
|
||||
cio: bool,
|
||||
direct: bool,
|
||||
directory: bool,
|
||||
dsync: bool,
|
||||
sync: bool,
|
||||
nocache: bool,
|
||||
nonblock: bool,
|
||||
noatime: bool,
|
||||
noctty: bool,
|
||||
nofollow: bool,
|
||||
nolinks: bool,
|
||||
binary: bool,
|
||||
text: bool,
|
||||
fullblock: bool,
|
||||
count_bytes: bool,
|
||||
skip_bytes: bool,
|
||||
}
|
||||
|
||||
/// Stores all Flags that apply to the output
|
||||
pub struct OFlags
|
||||
{
|
||||
append: bool,
|
||||
cio: bool,
|
||||
direct: bool,
|
||||
directory: bool,
|
||||
dsync: bool,
|
||||
sync: bool,
|
||||
nocache: bool,
|
||||
nonblock: bool,
|
||||
noatime: bool,
|
||||
noctty: bool,
|
||||
nofollow: bool,
|
||||
nolinks: bool,
|
||||
binary: bool,
|
||||
text: bool,
|
||||
seek_bytes: bool,
|
||||
}
|
||||
|
||||
/// The value of the status cl-option.
|
||||
/// Controls printing of transfer stats
|
||||
#[derive(PartialEq)]
|
||||
|
|
@ -101,7 +142,8 @@ struct Input<R: Read>
|
|||
src: R,
|
||||
ibs: usize,
|
||||
xfer_stats: StatusLevel,
|
||||
cf: ConvFlagInput,
|
||||
cflags: IConvFlags,
|
||||
iflags: IFlags,
|
||||
}
|
||||
|
||||
impl<R: Read> Read for Input<R>
|
||||
|
|
@ -110,7 +152,7 @@ impl<R: Read> Read for Input<R>
|
|||
{
|
||||
let len = self.src.read(&mut buf)?;
|
||||
|
||||
if let Some(ct) = self.cf.ctable
|
||||
if let Some(ct) = self.cflags.ctable
|
||||
{
|
||||
for idx in 0..len
|
||||
{
|
||||
|
|
@ -118,7 +160,7 @@ impl<R: Read> Read for Input<R>
|
|||
}
|
||||
}
|
||||
|
||||
if self.cf.swab
|
||||
if self.cflags.swab
|
||||
{
|
||||
let mut tmp = DEFAULT_FILL_BYTE;
|
||||
|
||||
|
|
@ -140,17 +182,25 @@ impl Input<io::Stdin>
|
|||
{
|
||||
let ibs = parseargs::parse_ibs(matches)?;
|
||||
let xfer_stats = parseargs::parse_status_level(matches)?;
|
||||
let cf = parseargs::parse_conv_flag_input(matches)?;
|
||||
let cflags = parseargs::parse_conv_flag_input(matches)?;
|
||||
let iflags = parseargs::parse_iflags(matches)?;
|
||||
let skip = parseargs::parse_skip_amt(matches)?;
|
||||
|
||||
Ok(
|
||||
Input {
|
||||
src: io::stdin(),
|
||||
ibs,
|
||||
xfer_stats,
|
||||
cf,
|
||||
}
|
||||
)
|
||||
let mut i = Input {
|
||||
src: io::stdin(),
|
||||
ibs,
|
||||
xfer_stats,
|
||||
cflags,
|
||||
iflags,
|
||||
};
|
||||
|
||||
if let Some(skip_amt) = skip
|
||||
{
|
||||
let mut buf = vec![DEFAULT_FILL_BYTE; skip_amt];
|
||||
i.read(&mut buf)?;
|
||||
}
|
||||
|
||||
Ok(i)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -160,16 +210,29 @@ impl Input<File>
|
|||
{
|
||||
let ibs = parseargs::parse_ibs(matches)?;
|
||||
let xfer_stats = parseargs::parse_status_level(matches)?;
|
||||
let cf = parseargs::parse_conv_flag_input(matches)?;
|
||||
let cflags = parseargs::parse_conv_flag_input(matches)?;
|
||||
let iflags = parseargs::parse_iflags(matches)?;
|
||||
let skip = parseargs::parse_skip_amt(matches)?;
|
||||
|
||||
if let Some(fname) = matches.opt_str("if")
|
||||
{
|
||||
Ok(Input {
|
||||
src: File::open(fname)?,
|
||||
let mut src = File::open(fname)?;
|
||||
|
||||
if let Some(skip_amt) = skip
|
||||
{
|
||||
let skip_amt: u64 = skip_amt.try_into()?;
|
||||
src.seek(io::SeekFrom::Start(skip_amt))?;
|
||||
}
|
||||
|
||||
let i = Input {
|
||||
src,
|
||||
ibs,
|
||||
xfer_stats,
|
||||
cf,
|
||||
})
|
||||
cflags,
|
||||
iflags,
|
||||
};
|
||||
|
||||
Ok(i)
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
@ -209,22 +272,23 @@ struct Output<W: Write>
|
|||
{
|
||||
dst: W,
|
||||
obs: usize,
|
||||
cf: ConvFlagOutput,
|
||||
cflags: OConvFlags,
|
||||
oflags: OFlags,
|
||||
}
|
||||
|
||||
impl Output<io::Stdout> {
|
||||
fn new(matches: &getopts::Matches) -> Result<Self, Box<dyn Error>>
|
||||
{
|
||||
let obs = parseargs::parse_obs(matches)?;
|
||||
let cf = parseargs::parse_conv_flag_output(matches)?;
|
||||
let cflags = parseargs::parse_conv_flag_output(matches)?;
|
||||
let oflags = parseargs::parse_oflags(matches)?;
|
||||
|
||||
Ok(
|
||||
Output {
|
||||
Ok(Output {
|
||||
dst: io::stdout(),
|
||||
obs,
|
||||
cf,
|
||||
}
|
||||
)
|
||||
cflags,
|
||||
oflags,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -232,20 +296,28 @@ impl Output<File> {
|
|||
fn new(matches: &getopts::Matches) -> Result<Self, Box<dyn Error>>
|
||||
{
|
||||
let obs = parseargs::parse_obs(matches)?;
|
||||
let cf = parseargs::parse_conv_flag_output(matches)?;
|
||||
let cflags = parseargs::parse_conv_flag_output(matches)?;
|
||||
let seek = parseargs::parse_seek_amt(matches)?;
|
||||
let oflags = parseargs::parse_oflags(matches)?;
|
||||
|
||||
if let Some(fname) = matches.opt_str("of")
|
||||
{
|
||||
let dst = OpenOptions::new()
|
||||
let mut dst = OpenOptions::new()
|
||||
.write(true)
|
||||
.create(!cf.nocreat)
|
||||
.truncate(!cf.notrunc)
|
||||
.create(!cflags.nocreat)
|
||||
.truncate(!cflags.notrunc)
|
||||
.open(fname)?;
|
||||
|
||||
if let Some(seek_amt) = seek
|
||||
{
|
||||
dst.seek(io::SeekFrom::Start(seek_amt))?;
|
||||
}
|
||||
|
||||
Ok(Output {
|
||||
dst,
|
||||
obs,
|
||||
cf,
|
||||
cflags,
|
||||
oflags,
|
||||
})
|
||||
}
|
||||
else
|
||||
|
|
@ -255,32 +327,14 @@ impl Output<File> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<W: Write> Seek for Output<W>
|
||||
impl Seek for Output<File>
|
||||
{
|
||||
fn seek(&mut self, pos: io::SeekFrom) -> io::Result<u64>
|
||||
{
|
||||
unimplemented!()
|
||||
self.dst.seek(pos)
|
||||
}
|
||||
}
|
||||
|
||||
// impl Seek for Output<io::Stdout>
|
||||
// {
|
||||
// fn seek(&mut self, pos: io::SeekFrom) -> io::Result<u64>
|
||||
// {
|
||||
// // Default method. Called when output dst not backed by a traditional file and
|
||||
// // should not be seeked (eg. stdout)
|
||||
// Ok(0)
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// impl Seek for Output<File>
|
||||
// {
|
||||
// fn seek(&mut self, pos: io::SeekFrom) -> io::Result<u64>
|
||||
// {
|
||||
// self.dst.seek(pos)
|
||||
// }
|
||||
// }
|
||||
|
||||
impl<W: Write> Write for Output<W>
|
||||
{
|
||||
fn write(&mut self, buf: &[u8]) -> io::Result<usize>
|
||||
|
|
@ -322,7 +376,29 @@ fn gen_prog_updater(rx: mpsc::Receiver<usize>) -> impl Fn() -> ()
|
|||
}
|
||||
}
|
||||
|
||||
fn dd<R: Read, W: Write>(mut i: Input<R>, mut o: Output<W>) -> Result<(usize, usize), Box<dyn Error>>
|
||||
#[inline]
|
||||
fn dd_read_helper<R: Read, W: Write>(mut buf: &mut [u8], i: &mut Input<R>, o: &Output<W>) -> Result<SrcStat, Box<dyn Error>>
|
||||
{
|
||||
match i.fill_n(&mut buf, o.obs)
|
||||
{
|
||||
Ok(ss) =>
|
||||
Ok(ss),
|
||||
Err(e) =>
|
||||
if !i.cflags.noerror
|
||||
{
|
||||
return Err(e);
|
||||
}
|
||||
else
|
||||
{
|
||||
Ok(SrcStat::Read(0))
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Perform the copy/convert opertaions. Non file backed output version
|
||||
// Note: Some of dd's functionality depends on whether the output is actually a file. This breaks the Output<Write> abstraction,
|
||||
// and should be fixed in the future.
|
||||
fn dd_stdout<R: Read>(mut i: Input<R>, mut o: Output<io::Stdout>) -> Result<(usize, usize), Box<dyn Error>>
|
||||
{
|
||||
let prog_tx = if i.xfer_stats == StatusLevel::Progress
|
||||
{
|
||||
|
|
@ -345,24 +421,81 @@ fn dd<R: Read, W: Write>(mut i: Input<R>, mut o: Output<W>) -> Result<(usize, us
|
|||
let mut buf = vec![DEFAULT_FILL_BYTE; o.obs];
|
||||
|
||||
// Read
|
||||
let r_len = match i.fill_n(&mut buf, o.obs) {
|
||||
Ok(SrcStat::Read(len)) =>
|
||||
let r_len = match dd_read_helper(&mut buf, &mut i, &o)?
|
||||
{
|
||||
SrcStat::Read(0) =>
|
||||
continue,
|
||||
SrcStat::Read(len) =>
|
||||
{
|
||||
bytes_in += len;
|
||||
len
|
||||
},
|
||||
Ok(SrcStat::EOF) =>
|
||||
SrcStat::EOF =>
|
||||
break,
|
||||
Err(e) =>
|
||||
if !i.cf.noerror {
|
||||
return Err(e);
|
||||
} else {
|
||||
continue
|
||||
},
|
||||
};
|
||||
|
||||
// Write
|
||||
let w_len = if o.cf.sparse && is_sparse(&buf)
|
||||
let w_len = o.write(&buf[..r_len])?;
|
||||
|
||||
// Prog
|
||||
bytes_out += w_len;
|
||||
|
||||
if let Some(prog_tx) = &prog_tx
|
||||
{
|
||||
prog_tx.send(bytes_out)?;
|
||||
}
|
||||
}
|
||||
|
||||
if o.cflags.fsync || o.cflags.fdatasync
|
||||
{
|
||||
o.flush()?;
|
||||
}
|
||||
|
||||
Ok((bytes_in, bytes_out))
|
||||
}
|
||||
|
||||
/// Perform the copy/convert opertaions. File backed output version
|
||||
// Note: Some of dd's functionality depends on whether the output is actually a file. This breaks the Output<Write> abstraction,
|
||||
// and should be fixed in the future.
|
||||
fn dd_fileout<R: Read>(mut i: Input<R>, mut o: Output<File>) -> Result<(usize, usize), Box<dyn Error>>
|
||||
{
|
||||
let prog_tx = if i.xfer_stats == StatusLevel::Progress
|
||||
{
|
||||
let (prog_tx, prog_rx) = mpsc::channel();
|
||||
|
||||
thread::spawn(gen_prog_updater(prog_rx));
|
||||
|
||||
Some(prog_tx)
|
||||
}
|
||||
else
|
||||
{
|
||||
None
|
||||
};
|
||||
|
||||
let mut bytes_in = 0;
|
||||
let mut bytes_out = 0;
|
||||
|
||||
loop
|
||||
{
|
||||
let mut buf = vec![DEFAULT_FILL_BYTE; o.obs];
|
||||
|
||||
// Read
|
||||
let r_len = match dd_read_helper(&mut buf, &mut i, &o)?
|
||||
{
|
||||
SrcStat::Read(0) =>
|
||||
continue,
|
||||
SrcStat::Read(len) =>
|
||||
{
|
||||
bytes_in += len;
|
||||
len
|
||||
},
|
||||
SrcStat::EOF =>
|
||||
break,
|
||||
};
|
||||
|
||||
|
||||
// Write
|
||||
let w_len = if o.cflags.sparse && is_sparse(&buf)
|
||||
{
|
||||
let seek_amt: i64 = r_len.try_into()?;
|
||||
o.seek(io::SeekFrom::Current(seek_amt))?;
|
||||
|
|
@ -382,11 +515,15 @@ fn dd<R: Read, W: Write>(mut i: Input<R>, mut o: Output<W>) -> Result<(usize, us
|
|||
}
|
||||
}
|
||||
|
||||
// TODO: Also ensure file metadata is written when fsync option is specified. When _wouldn't_ this happen?
|
||||
// See fs::File::sync_all && fs::File::sync_data methods!
|
||||
if o.cf.fsync || o.cf.fdatasync
|
||||
if o.cflags.fsync
|
||||
{
|
||||
o.flush()?;
|
||||
o.dst.sync_all()?;
|
||||
}
|
||||
else if o.cflags.fdatasync
|
||||
{
|
||||
o.flush()?;
|
||||
o.dst.sync_data()?;
|
||||
}
|
||||
|
||||
Ok((bytes_in, bytes_out))
|
||||
|
|
@ -397,36 +534,68 @@ macro_rules! build_app (
|
|||
() =>
|
||||
{
|
||||
app!(SYNTAX, SUMMARY, LONG_HELP)
|
||||
.optopt(
|
||||
"",
|
||||
"skip",
|
||||
"Skip N ‘ibs’-byte blocks in the input file before copying. If ‘iflag=skip_bytes’ is specified, N is interpreted as a byte count rather than a block count.",
|
||||
"N"
|
||||
)
|
||||
.optopt(
|
||||
"",
|
||||
"seek",
|
||||
"Skip N ‘obs’-byte blocks in the input file before copying. If ‘oflag=skip_bytes’ is specified, N is interpreted as a byte count rather than a block count.",
|
||||
"N"
|
||||
)
|
||||
.optopt(
|
||||
"",
|
||||
"count",
|
||||
"Copy N ‘ibs’-byte blocks from the input file, instead of everything until the end of the file. if ‘iflag=count_bytes’ is specified, N is interpreted as a byte count rather than a block count. Note if the input may return short reads as could be the case when reading
|
||||
from a pipe for example, ‘iflag=fullblock’ will ensure that ‘count=’ corresponds to complete input blocks rather than the traditional POSIX specified behavior of counting input read operations.",
|
||||
"BYTES"
|
||||
)
|
||||
.optopt(
|
||||
"",
|
||||
"bs",
|
||||
"Set both input and output block sizes to BYTES. This makes ‘dd’ read and write BYTES per block, overriding any ‘ibs’ and ‘obs’ settings. In addition, if no data-transforming ‘conv’ option is specified, input is copied to the output as soon as it’s read, even
|
||||
if it is smaller than the block size.",
|
||||
"BYTES"
|
||||
)
|
||||
.optopt(
|
||||
"",
|
||||
"if",
|
||||
"The input file",
|
||||
"Read from FILE instead of standard input.",
|
||||
"FILE"
|
||||
)
|
||||
.optopt(
|
||||
"",
|
||||
"ibs",
|
||||
"read up to BYTES bytes at a time (default: 512)",
|
||||
"Set the input block size to BYTES. This makes ‘dd’ read BYTES per block. The default is 512 bytes.",
|
||||
"BYTES"
|
||||
)
|
||||
.optopt(
|
||||
"",
|
||||
"of",
|
||||
"The output file",
|
||||
"Write to FILE instead of standard output. Unless ‘conv=notrunc’ is given, ‘dd’ truncates FILE to zero bytes (or the size specified with ‘seek=’).",
|
||||
"FILE"
|
||||
)
|
||||
.optopt(
|
||||
"",
|
||||
"obs",
|
||||
"write BYTES bytes at a time (default: 512)",
|
||||
"Set the output block size to BYTES. This makes ‘dd’ write BYTES per block. The default is 512 bytes.",
|
||||
"BYTES"
|
||||
)
|
||||
.optopt(
|
||||
"",
|
||||
"conv",
|
||||
"One or more conversion options as a comma-serparated list",
|
||||
"Convert the file as specified by the CONVERSION argument(s). (No spaces around any comma(s).)",
|
||||
"OPT[,OPT]..."
|
||||
)
|
||||
.optopt(
|
||||
"",
|
||||
"cbs",
|
||||
"Set the conversion block size to BYTES. When converting variable-length records to fixed-length ones (‘conv=block’) or the reverse (‘conv=unblock’), use BYTES as the fixed record length.",
|
||||
"BYTES"
|
||||
)
|
||||
}
|
||||
);
|
||||
|
||||
|
|
@ -457,7 +626,7 @@ pub fn uumain(args: impl uucore::Args) -> i32
|
|||
let o = Output::<File>::new(&matches)
|
||||
.expect("TODO: Return correct error code");
|
||||
|
||||
dd(i,o)
|
||||
dd_fileout(i,o)
|
||||
},
|
||||
(true, false) =>
|
||||
{
|
||||
|
|
@ -466,7 +635,7 @@ pub fn uumain(args: impl uucore::Args) -> i32
|
|||
let o = Output::<io::Stdout>::new(&matches)
|
||||
.expect("TODO: Return correct error code");
|
||||
|
||||
dd(i,o)
|
||||
dd_stdout(i,o)
|
||||
},
|
||||
(false, true) =>
|
||||
{
|
||||
|
|
@ -475,7 +644,7 @@ pub fn uumain(args: impl uucore::Args) -> i32
|
|||
let o = Output::<File>::new(&matches)
|
||||
.expect("TODO: Return correct error code");
|
||||
|
||||
dd(i,o)
|
||||
dd_fileout(i,o)
|
||||
},
|
||||
(false, false) =>
|
||||
{
|
||||
|
|
@ -484,7 +653,7 @@ pub fn uumain(args: impl uucore::Args) -> i32
|
|||
let o = Output::<io::Stdout>::new(&matches)
|
||||
.expect("TODO: Return correct error code");
|
||||
|
||||
dd(i,o)
|
||||
dd_stdout(i,o)
|
||||
},
|
||||
};
|
||||
|
||||
|
|
@ -492,7 +661,8 @@ pub fn uumain(args: impl uucore::Args) -> i32
|
|||
{
|
||||
Ok((b_in, b_out)) =>
|
||||
{
|
||||
// TODO: Print output stats, unless noxfer
|
||||
// TODO: Print final xfer stats
|
||||
// print_stats(b_in, b_out);
|
||||
|
||||
RTN_SUCCESS
|
||||
},
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ use hex_literal::hex;
|
|||
// use tempfile::tempfile;
|
||||
// TODO: (Maybe) Use tempfiles in the tests.
|
||||
|
||||
const DEFAULT_CFO: ConvFlagOutput = ConvFlagOutput {
|
||||
const DEFAULT_CFO: OConvFlags = OConvFlags {
|
||||
sparse: false,
|
||||
excl: false,
|
||||
nocreat: false,
|
||||
|
|
@ -18,15 +18,52 @@ const DEFAULT_CFO: ConvFlagOutput = ConvFlagOutput {
|
|||
fsync: false,
|
||||
};
|
||||
|
||||
const DEFAULT_IFLAGS: IFlags = IFlags {
|
||||
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,
|
||||
fullblock: false,
|
||||
count_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,
|
||||
};
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! cfi (
|
||||
macro_rules! icf (
|
||||
() =>
|
||||
{
|
||||
cfi!(None)
|
||||
icf!(None)
|
||||
};
|
||||
( $ctable:expr ) =>
|
||||
{
|
||||
ConvFlagInput {
|
||||
IConvFlags {
|
||||
ctable: $ctable,
|
||||
block: false,
|
||||
unblock: false,
|
||||
|
|
@ -51,12 +88,13 @@ macro_rules! make_spec_test (
|
|||
src: $src,
|
||||
ibs: 512,
|
||||
xfer_stats: StatusLevel::None,
|
||||
cf: cfi!(),
|
||||
cflags: icf!(),
|
||||
iflags: DEFAULT_IFLAGS,
|
||||
},
|
||||
Output {
|
||||
dst: File::create(format!("./test-resources/FAILED-{}.test", $test_name)).unwrap(),
|
||||
obs: 512,
|
||||
cf: DEFAULT_CFO,
|
||||
cflags: DEFAULT_CFO,
|
||||
},
|
||||
$spec,
|
||||
format!("./test-resources/FAILED-{}.test", $test_name)
|
||||
|
|
@ -67,7 +105,7 @@ macro_rules! make_spec_test (
|
|||
#[test]
|
||||
fn $test_id()
|
||||
{
|
||||
dd($i,$o).unwrap();
|
||||
dd_fileout($i,$o).unwrap();
|
||||
|
||||
let res = File::open($tmp_fname).unwrap();
|
||||
let res = BufReader::new(res);
|
||||
|
|
@ -94,12 +132,13 @@ macro_rules! make_conv_test (
|
|||
src: $src,
|
||||
ibs: 512,
|
||||
xfer_stats: StatusLevel::None,
|
||||
cf: cfi!($ctable),
|
||||
cflags: icf!($ctable),
|
||||
iflags: DEFAULT_IFLAGS,
|
||||
},
|
||||
Output {
|
||||
dst: File::create(format!("./test-resources/FAILED-{}.test", $test_name)).unwrap(),
|
||||
obs: 512,
|
||||
cf: DEFAULT_CFO,
|
||||
cflags: DEFAULT_CFO,
|
||||
},
|
||||
$spec,
|
||||
format!("./test-resources/FAILED-{}.test", $test_name)
|
||||
|
|
@ -107,8 +146,8 @@ macro_rules! make_conv_test (
|
|||
};
|
||||
);
|
||||
|
||||
macro_rules! make_cfi_test (
|
||||
( $test_id:ident, $test_name:expr, $src:expr, $cfi:expr, $spec:expr ) =>
|
||||
macro_rules! make_icf_test (
|
||||
( $test_id:ident, $test_name:expr, $src:expr, $icf:expr, $spec:expr ) =>
|
||||
{
|
||||
make_spec_test!($test_id,
|
||||
$test_name,
|
||||
|
|
@ -116,12 +155,13 @@ macro_rules! make_cfi_test (
|
|||
src: $src,
|
||||
ibs: 512,
|
||||
xfer_stats: StatusLevel::None,
|
||||
cf: $cfi,
|
||||
cflags: $icf,
|
||||
iflags: DEFAULT_IFLAGS,
|
||||
},
|
||||
Output {
|
||||
dst: File::create(format!("./test-resources/FAILED-{}.test", $test_name)).unwrap(),
|
||||
obs: 512,
|
||||
cf: DEFAULT_CFO,
|
||||
cflags: DEFAULT_CFO,
|
||||
},
|
||||
$spec,
|
||||
format!("./test-resources/FAILED-{}.test", $test_name)
|
||||
|
|
@ -240,16 +280,17 @@ fn all_valid_ascii_ebcdic_ascii_roundtrip_conv_test()
|
|||
src: File::open("./test-resources/all-valid-ascii-chars-37eff01866ba3f538421b30b7cbefcac.test").unwrap(),
|
||||
ibs: 128,
|
||||
xfer_stats: StatusLevel::None,
|
||||
cf: cfi!(Some(&ASCII_TO_EBCDIC)),
|
||||
cflags: icf!(Some(&ASCII_TO_EBCDIC)),
|
||||
iflags: DEFAULT_IFLAGS,
|
||||
};
|
||||
|
||||
let o = Output {
|
||||
dst: File::create(&tmp_fname_ae).unwrap(),
|
||||
obs: 1024,
|
||||
cf: DEFAULT_CFO,
|
||||
cflags: DEFAULT_CFO,
|
||||
};
|
||||
|
||||
dd(i,o).unwrap();
|
||||
dd_fileout(i,o).unwrap();
|
||||
|
||||
// EBCDIC->ASCII
|
||||
let test_name = "all-valid-ebcdic-to-ascii";
|
||||
|
|
@ -259,16 +300,17 @@ fn all_valid_ascii_ebcdic_ascii_roundtrip_conv_test()
|
|||
src: File::open(&tmp_fname_ae).unwrap(),
|
||||
ibs: 256,
|
||||
xfer_stats: StatusLevel::None,
|
||||
cf: cfi!(Some(&EBCDIC_TO_ASCII)),
|
||||
cflags: icf!(Some(&EBCDIC_TO_ASCII)),
|
||||
iflags: DEFAULT_IFLAGS,
|
||||
};
|
||||
|
||||
let o = Output {
|
||||
dst: File::create(&tmp_fname_ea).unwrap(),
|
||||
obs: 1024,
|
||||
cf: DEFAULT_CFO,
|
||||
cflags: DEFAULT_CFO,
|
||||
};
|
||||
|
||||
dd(i,o).unwrap();
|
||||
dd_fileout(i,o).unwrap();
|
||||
|
||||
let res = {
|
||||
let res = File::open(&tmp_fname_ea).unwrap();
|
||||
|
|
@ -289,11 +331,11 @@ fn all_valid_ascii_ebcdic_ascii_roundtrip_conv_test()
|
|||
fs::remove_file(&tmp_fname_ea).unwrap();
|
||||
}
|
||||
|
||||
make_cfi_test!(
|
||||
make_icf_test!(
|
||||
swab_256_test,
|
||||
"swab-256",
|
||||
File::open("./test-resources/seq-byte-values.test").unwrap(),
|
||||
ConvFlagInput {
|
||||
IConvFlags {
|
||||
ctable: None,
|
||||
block: false,
|
||||
unblock: false,
|
||||
|
|
@ -304,11 +346,11 @@ make_cfi_test!(
|
|||
File::open("./test-resources/seq-byte-values-swapped.test").unwrap()
|
||||
);
|
||||
|
||||
make_cfi_test!(
|
||||
make_icf_test!(
|
||||
swab_257_test,
|
||||
"swab-257",
|
||||
File::open("./test-resources/seq-byte-values-odd.test").unwrap(),
|
||||
ConvFlagInput {
|
||||
IConvFlags {
|
||||
ctable: None,
|
||||
block: false,
|
||||
unblock: false,
|
||||
|
|
|
|||
|
|
@ -3,13 +3,15 @@ mod test;
|
|||
|
||||
use crate::conversion_tables::*;
|
||||
use crate::{
|
||||
ConvFlagInput, ConvFlagOutput,
|
||||
IConvFlags, OConvFlags,
|
||||
StatusLevel,
|
||||
};
|
||||
use crate::{
|
||||
IFlags, OFlags,
|
||||
};
|
||||
|
||||
use std::error::Error;
|
||||
|
||||
|
||||
/// Parser Errors describe errors with input
|
||||
#[derive(Debug)]
|
||||
pub enum ParseError
|
||||
|
|
@ -18,6 +20,7 @@ pub enum ParseError
|
|||
MultipleUCaseLCase,
|
||||
MultipleBlockUnblock,
|
||||
MultipleExclNoCreat,
|
||||
FlagNoMatch(String),
|
||||
ConvFlagNoMatch(String),
|
||||
NoMatchingMultiplier(String),
|
||||
MultiplierStringContainsNoValue(String),
|
||||
|
|
@ -106,6 +109,84 @@ impl std::str::FromStr for ConvFlag
|
|||
}
|
||||
}
|
||||
|
||||
enum Flag
|
||||
{
|
||||
// Input only
|
||||
FullBlock,
|
||||
CountBytes,
|
||||
SkipBytes,
|
||||
// Either
|
||||
Cio,
|
||||
Direct,
|
||||
Directory,
|
||||
Dsync,
|
||||
Sync,
|
||||
NoCache,
|
||||
NonBlock,
|
||||
NoATime,
|
||||
NoCtty,
|
||||
NoFollow,
|
||||
NoLinks,
|
||||
Binary,
|
||||
Text,
|
||||
// Output only
|
||||
Append,
|
||||
SeekBytes,
|
||||
}
|
||||
|
||||
impl std::str::FromStr for Flag
|
||||
{
|
||||
type Err = ParseError;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err>
|
||||
{
|
||||
match s
|
||||
{
|
||||
// Input only
|
||||
"fullblock" =>
|
||||
Ok(Self::FullBlock),
|
||||
"count_bytes" =>
|
||||
Ok(Self::CountBytes),
|
||||
"skip_bytes" =>
|
||||
Ok(Self::SkipBytes),
|
||||
// Either
|
||||
"cio" =>
|
||||
Ok(Self::Cio),
|
||||
"direct" =>
|
||||
Ok(Self::Direct),
|
||||
"directory" =>
|
||||
Ok(Self::Directory),
|
||||
"dsync" =>
|
||||
Ok(Self::Dsync),
|
||||
"sync" =>
|
||||
Ok(Self::Sync),
|
||||
"nocache" =>
|
||||
Ok(Self::NoCache),
|
||||
"nonblock" =>
|
||||
Ok(Self::NonBlock),
|
||||
"noatime" =>
|
||||
Ok(Self::NoATime),
|
||||
"noctty" =>
|
||||
Ok(Self::NoCtty),
|
||||
"nofollow" =>
|
||||
Ok(Self::NoFollow),
|
||||
"nolinks" =>
|
||||
Ok(Self::NoLinks),
|
||||
"binary" =>
|
||||
Ok(Self::Binary),
|
||||
"text" =>
|
||||
Ok(Self::Text),
|
||||
// Output only
|
||||
"append" =>
|
||||
Ok(Self::Append),
|
||||
"seek_bytes" =>
|
||||
Ok(Self::SeekBytes),
|
||||
_ =>
|
||||
Err(ParseError::FlagNoMatch(String::from(s))),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_multiplier<'a>(s: &'a str) -> Result<usize, ParseError>
|
||||
{
|
||||
match s
|
||||
|
|
@ -269,11 +350,11 @@ fn parse_ctable(fmt: Option<ConvFlag>, case: Option<ConvFlag>) -> Option<&'stati
|
|||
}
|
||||
}
|
||||
|
||||
fn parse_conv_opts(matches: &getopts::Matches) -> Result<Vec<ConvFlag>, ParseError>
|
||||
fn parse_flag_list<T: std::str::FromStr<Err = ParseError>>(tag: &str, matches: &getopts::Matches) -> Result<Vec<T>, ParseError>
|
||||
{
|
||||
let mut flags = Vec::new();
|
||||
|
||||
if let Some(comma_str) = matches.opt_str("conv")
|
||||
if let Some(comma_str) = matches.opt_str(tag)
|
||||
{
|
||||
for s in comma_str.split(",")
|
||||
{
|
||||
|
|
@ -286,10 +367,10 @@ fn parse_conv_opts(matches: &getopts::Matches) -> Result<Vec<ConvFlag>, ParseErr
|
|||
}
|
||||
|
||||
/// Parse Conversion Options (Input Variety)
|
||||
/// Construct and validate a ConvFlagInput
|
||||
pub fn parse_conv_flag_input(matches: &getopts::Matches) -> Result<ConvFlagInput, ParseError>
|
||||
/// Construct and validate a IConvFlags
|
||||
pub fn parse_conv_flag_input(matches: &getopts::Matches) -> Result<IConvFlags, ParseError>
|
||||
{
|
||||
let flags = parse_conv_opts(matches)?;
|
||||
let flags = parse_flag_list("conv", matches)?;
|
||||
|
||||
let mut fmt = None;
|
||||
let mut case = None;
|
||||
|
|
@ -378,7 +459,7 @@ pub fn parse_conv_flag_input(matches: &getopts::Matches) -> Result<ConvFlagInput
|
|||
|
||||
let ctable = parse_ctable(fmt, case);
|
||||
|
||||
Ok(ConvFlagInput {
|
||||
Ok(IConvFlags {
|
||||
ctable,
|
||||
block,
|
||||
unblock,
|
||||
|
|
@ -389,10 +470,10 @@ pub fn parse_conv_flag_input(matches: &getopts::Matches) -> Result<ConvFlagInput
|
|||
}
|
||||
|
||||
/// Parse Conversion Options (Output Variety)
|
||||
/// Construct and validate a ConvFlagOutput
|
||||
pub fn parse_conv_flag_output(matches: &getopts::Matches) -> Result<ConvFlagOutput, ParseError>
|
||||
/// Construct and validate a OConvFlags
|
||||
pub fn parse_conv_flag_output(matches: &getopts::Matches) -> Result<OConvFlags, ParseError>
|
||||
{
|
||||
let flags = parse_conv_opts(matches)?;
|
||||
let flags = parse_flag_list("conv", matches)?;
|
||||
|
||||
let mut sparse = false;
|
||||
let mut excl = false;
|
||||
|
|
@ -435,7 +516,7 @@ pub fn parse_conv_flag_output(matches: &getopts::Matches) -> Result<ConvFlagOutp
|
|||
}
|
||||
}
|
||||
|
||||
Ok(ConvFlagOutput {
|
||||
Ok(OConvFlags {
|
||||
sparse,
|
||||
excl,
|
||||
nocreat,
|
||||
|
|
@ -444,3 +525,193 @@ pub fn parse_conv_flag_output(matches: &getopts::Matches) -> Result<ConvFlagOutp
|
|||
fsync,
|
||||
})
|
||||
}
|
||||
|
||||
/// Parse IFlags struct from CL-input
|
||||
pub fn parse_iflags(matches: &getopts::Matches) -> Result<IFlags, ParseError>
|
||||
{
|
||||
let mut cio = false;
|
||||
let mut direct = false;
|
||||
let mut directory = false;
|
||||
let mut dsync = false;
|
||||
let mut sync = false;
|
||||
let mut nocache = false;
|
||||
let mut nonblock = false;
|
||||
let mut noatime = false;
|
||||
let mut noctty = false;
|
||||
let mut nofollow = false;
|
||||
let mut nolinks = false;
|
||||
let mut binary = false;
|
||||
let mut text = false;
|
||||
let mut fullblock = false;
|
||||
let mut count_bytes = false;
|
||||
let mut skip_bytes = false;
|
||||
|
||||
let flags = parse_flag_list("iflag", matches)?;
|
||||
|
||||
for flag in flags
|
||||
{
|
||||
match flag
|
||||
{
|
||||
Flag::Cio =>
|
||||
cio = true,
|
||||
Flag::Direct =>
|
||||
direct = true,
|
||||
Flag::Directory =>
|
||||
directory = true,
|
||||
Flag::Dsync =>
|
||||
dsync = true,
|
||||
Flag::Sync =>
|
||||
sync = true,
|
||||
Flag::NoCache =>
|
||||
nocache = true,
|
||||
Flag::NoCache =>
|
||||
nocache = true,
|
||||
Flag::NonBlock =>
|
||||
nonblock = true,
|
||||
Flag::NoATime =>
|
||||
noatime = true,
|
||||
Flag::NoCtty =>
|
||||
noctty = true,
|
||||
Flag::NoFollow =>
|
||||
nofollow = true,
|
||||
Flag::NoLinks =>
|
||||
nolinks = true,
|
||||
Flag::Binary =>
|
||||
binary = true,
|
||||
Flag::Text =>
|
||||
text = true,
|
||||
Flag::FullBlock =>
|
||||
fullblock = true,
|
||||
Flag::CountBytes =>
|
||||
count_bytes = true,
|
||||
Flag::SkipBytes =>
|
||||
skip_bytes = true,
|
||||
_ => {},
|
||||
}
|
||||
}
|
||||
|
||||
Ok(IFlags{
|
||||
cio,
|
||||
direct,
|
||||
directory,
|
||||
dsync,
|
||||
sync,
|
||||
nocache,
|
||||
nonblock,
|
||||
noatime,
|
||||
noctty,
|
||||
nofollow,
|
||||
nolinks,
|
||||
binary,
|
||||
text,
|
||||
fullblock,
|
||||
count_bytes,
|
||||
skip_bytes,
|
||||
})
|
||||
}
|
||||
|
||||
/// Parse OFlags struct from CL-input
|
||||
pub fn parse_oflags(matches: &getopts::Matches) -> Result<OFlags, ParseError>
|
||||
{
|
||||
let mut append = false;
|
||||
let mut cio = false;
|
||||
let mut direct = false;
|
||||
let mut directory = false;
|
||||
let mut dsync = false;
|
||||
let mut sync = false;
|
||||
let mut nocache = false;
|
||||
let mut nonblock = false;
|
||||
let mut noatime = false;
|
||||
let mut noctty = false;
|
||||
let mut nofollow = false;
|
||||
let mut nolinks = false;
|
||||
let mut binary = false;
|
||||
let mut text = false;
|
||||
let mut seek_bytes = false;
|
||||
|
||||
let flags = parse_flag_list("oflag", matches)?;
|
||||
|
||||
for flag in flags
|
||||
{
|
||||
match flag
|
||||
{
|
||||
Flag::Append =>
|
||||
append = true,
|
||||
Flag::Cio =>
|
||||
cio = true,
|
||||
Flag::Direct =>
|
||||
direct = true,
|
||||
Flag::Directory =>
|
||||
directory = true,
|
||||
Flag::Dsync =>
|
||||
dsync = true,
|
||||
Flag::Sync =>
|
||||
sync = true,
|
||||
Flag::NoCache =>
|
||||
nocache = true,
|
||||
Flag::NoCache =>
|
||||
nocache = true,
|
||||
Flag::NonBlock =>
|
||||
nonblock = true,
|
||||
Flag::NoATime =>
|
||||
noatime = true,
|
||||
Flag::NoCtty =>
|
||||
noctty = true,
|
||||
Flag::NoFollow =>
|
||||
nofollow = true,
|
||||
Flag::NoLinks =>
|
||||
nolinks = true,
|
||||
Flag::Binary =>
|
||||
binary = true,
|
||||
Flag::Text =>
|
||||
text = true,
|
||||
Flag::SeekBytes =>
|
||||
seek_bytes = true,
|
||||
_ => {},
|
||||
}
|
||||
}
|
||||
|
||||
Ok(OFlags {
|
||||
append,
|
||||
cio,
|
||||
direct,
|
||||
directory,
|
||||
dsync,
|
||||
sync,
|
||||
nocache,
|
||||
nonblock,
|
||||
noatime,
|
||||
noctty,
|
||||
nofollow,
|
||||
nolinks,
|
||||
binary,
|
||||
text,
|
||||
seek_bytes,
|
||||
})
|
||||
}
|
||||
|
||||
/// Parse the amount of the input file to skip.
|
||||
pub fn parse_skip_amt(matches: &getopts::Matches) -> Result<Option<usize>, ParseError>
|
||||
{
|
||||
if let Some(skip_amt) = matches.opt_str("skip")
|
||||
{
|
||||
unimplemented!()
|
||||
}
|
||||
else
|
||||
{
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse the amount of the output file to seek.
|
||||
pub fn parse_seek_amt(matches: &getopts::Matches) -> Result<Option<u64>, ParseError>
|
||||
{
|
||||
if let Some(seek_amt) = matches.opt_str("seek")
|
||||
{
|
||||
unimplemented!()
|
||||
}
|
||||
else
|
||||
{
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,16 +3,16 @@ use super::*;
|
|||
use crate::{
|
||||
build_app,
|
||||
SYNTAX, SUMMARY, LONG_HELP,
|
||||
ConvFlagInput, ConvFlagOutput,
|
||||
IConvFlags, OConvFlags,
|
||||
StatusLevel,
|
||||
};
|
||||
|
||||
// ----- ConvFlagInput/Output -----
|
||||
// ----- IConvFlags/Output -----
|
||||
|
||||
#[test]
|
||||
fn build_cfi()
|
||||
fn build_icf()
|
||||
{
|
||||
let cfi_expd = ConvFlagInput {
|
||||
let icf_expd = IConvFlags {
|
||||
ctable: Some(&ASCII_TO_IBM),
|
||||
block: false,
|
||||
unblock: false,
|
||||
|
|
@ -28,15 +28,15 @@ fn build_cfi()
|
|||
|
||||
let matches = build_app!().parse(args);
|
||||
|
||||
let cfi_parsed = parse_conv_flag_input(&matches).unwrap();
|
||||
let icf_parsed = parse_conv_flag_input(&matches).unwrap();
|
||||
|
||||
unimplemented!()
|
||||
// assert_eq!(cfi_expd, cfi_parsed);
|
||||
// assert_eq!(icf_expd, icf_parsed);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn cfi_ctable_error()
|
||||
fn icf_ctable_error()
|
||||
{
|
||||
let args = vec![
|
||||
String::from("dd"),
|
||||
|
|
@ -45,12 +45,12 @@ fn cfi_ctable_error()
|
|||
|
||||
let matches = build_app!().parse(args);
|
||||
|
||||
let cfi_parsed = parse_conv_flag_input(&matches).unwrap();
|
||||
let icf_parsed = parse_conv_flag_input(&matches).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn cfi_case_error()
|
||||
fn icf_case_error()
|
||||
{
|
||||
let args = vec![
|
||||
String::from("dd"),
|
||||
|
|
@ -59,12 +59,12 @@ fn cfi_case_error()
|
|||
|
||||
let matches = build_app!().parse(args);
|
||||
|
||||
let cfi_parsed = parse_conv_flag_input(&matches).unwrap();
|
||||
let icf_parsed = parse_conv_flag_input(&matches).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn cfi_block_error()
|
||||
fn icf_block_error()
|
||||
{
|
||||
let args = vec![
|
||||
String::from("dd"),
|
||||
|
|
@ -73,12 +73,12 @@ fn cfi_block_error()
|
|||
|
||||
let matches = build_app!().parse(args);
|
||||
|
||||
let cfi_parsed = parse_conv_flag_input(&matches).unwrap();
|
||||
let icf_parsed = parse_conv_flag_input(&matches).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn cfi_creat_error()
|
||||
fn icf_creat_error()
|
||||
{
|
||||
let args = vec![
|
||||
String::from("dd"),
|
||||
|
|
@ -87,11 +87,11 @@ fn cfi_creat_error()
|
|||
|
||||
let matches = build_app!().parse(args);
|
||||
|
||||
let cfi_parsed = parse_conv_flag_output(&matches).unwrap();
|
||||
let icf_parsed = parse_conv_flag_output(&matches).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_cfi_token_ibm()
|
||||
fn parse_icf_token_ibm()
|
||||
{
|
||||
let exp = vec![
|
||||
ConvFlag::FmtAtoI,
|
||||
|
|
@ -113,7 +113,7 @@ fn parse_cfi_token_ibm()
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn parse_cfi_tokens_elu()
|
||||
fn parse_icf_tokens_elu()
|
||||
{
|
||||
let exp = vec![
|
||||
ConvFlag::FmtEtoA,
|
||||
|
|
@ -136,7 +136,7 @@ fn parse_cfi_tokens_elu()
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn parse_cfi_tokens_remaining()
|
||||
fn parse_icf_tokens_remaining()
|
||||
{
|
||||
let exp = vec![
|
||||
ConvFlag::FmtAtoE,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue