Adds plumbing for conv=sparse. Not impl.

- conv=sparse option requires knowledge of File/Stdout to change
  behaviour.
- Unclear how best to impl this.
- Possible option: develop 4 versions of dd<X,Y> for each valid pair of { File, Stdin,
  Stdout }.
This commit is contained in:
Tyler 2021-04-22 10:56:33 -07:00
parent b461f102cc
commit 5aabdf854d
3 changed files with 105 additions and 21 deletions

View file

@ -18,14 +18,18 @@ mod parseargs;
mod conversion_tables; mod conversion_tables;
use conversion_tables::*; use conversion_tables::*;
use std::convert::TryInto;
use std::error::Error; use std::error::Error;
use std::fs::File; use std::fs::{
File, OpenOptions,
};
use getopts;
use std::io::{ use std::io::{
self, Read, Write, self, Read, Write,
Seek,
}; };
use std::sync::mpsc; use std::sync::mpsc;
use std::thread; use std::thread;
use getopts;
const SYNTAX: &str = "dd [OPERAND]...\ndd OPTION"; const SYNTAX: &str = "dd [OPERAND]...\ndd OPTION";
const SUMMARY: &str = "convert, and optionally copy, a file"; const SUMMARY: &str = "convert, and optionally copy, a file";
@ -218,10 +222,16 @@ impl Output<File> {
let obs = parseargs::parse_obs(matches)?; let obs = parseargs::parse_obs(matches)?;
let cf = parseargs::parse_conv_flag_output(matches)?; let cf = parseargs::parse_conv_flag_output(matches)?;
if let Some(fname) = matches.opt_str("if") if let Some(fname) = matches.opt_str("of")
{ {
let dst = OpenOptions::new()
.write(true)
.create(!cf.nocreat)
.truncate(!cf.notrunc)
.open(fname)?;
Ok(Output { Ok(Output {
dst: File::open(fname)?, dst,
obs, obs,
cf, cf,
}) })
@ -233,6 +243,32 @@ impl Output<File> {
} }
} }
impl<W: Write> Seek for Output<W>
{
fn seek(&mut self, pos: io::SeekFrom) -> io::Result<u64>
{
unimplemented!()
}
}
// 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> impl<W: Write> Write for Output<W>
{ {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> fn write(&mut self, buf: &[u8]) -> io::Result<usize>
@ -246,6 +282,11 @@ impl<W: Write> Write for Output<W>
} }
} }
fn is_sparse(buf: &[u8]) -> bool
{
buf.iter().all(| &e | e == 0)
}
fn gen_prog_updater(rx: mpsc::Receiver<usize>) -> impl Fn() -> () fn gen_prog_updater(rx: mpsc::Receiver<usize>) -> impl Fn() -> ()
{ {
move || { move || {
@ -290,8 +331,9 @@ fn dd<R: Read, W: Write>(mut i: Input<R>, mut o: Output<W>) -> Result<(usize, us
loop loop
{ {
let mut buf = vec![DEFAULT_FILL_BYTE; o.obs]; let mut buf = vec![DEFAULT_FILL_BYTE; o.obs];
let r_len =
match i.fill_n(&mut buf, o.obs)? { // Read
let r_len = match i.fill_n(&mut buf, o.obs)? {
SrcStat::Read(len) => SrcStat::Read(len) =>
{ {
bytes_in += len; bytes_in += len;
@ -301,11 +343,19 @@ fn dd<R: Read, W: Write>(mut i: Input<R>, mut o: Output<W>) -> Result<(usize, us
break, break,
}; };
let w_len = o.write(&buf[..r_len])?; // Write
let w_len = if o.cf.sparse && is_sparse(&buf)
// TODO: Some flag (sync?) controls this behaviour {
// o.flush()?; let seek_amt: i64 = r_len.try_into()?;
o.seek(io::SeekFrom::Current(seek_amt))?;
r_len
}
else
{
o.write(&buf[..r_len])?
};
// Prog
bytes_out += w_len; bytes_out += w_len;
if let Some(prog_tx) = &prog_tx if let Some(prog_tx) = &prog_tx
@ -314,6 +364,13 @@ 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
{
o.flush()?;
}
Ok((bytes_in, bytes_out)) Ok((bytes_in, bytes_out))
} }

View file

@ -17,6 +17,7 @@ pub enum ParseError
MultipleFmtTable, MultipleFmtTable,
MultipleUCaseLCase, MultipleUCaseLCase,
MultipleBlockUnblock, MultipleBlockUnblock,
MultipleExclNoCreat,
ConvFlagNoMatch(String), ConvFlagNoMatch(String),
NoMatchingMultiplier(String), NoMatchingMultiplier(String),
MultiplierStringContainsNoValue(String), MultiplierStringContainsNoValue(String),
@ -274,11 +275,9 @@ fn parse_conv_opts(matches: &getopts::Matches) -> Result<Vec<ConvFlag>, ParseErr
if let Some(comma_str) = matches.opt_str("conv") if let Some(comma_str) = matches.opt_str("conv")
{ {
println!("Parsing conv: {}", comma_str);
for s in comma_str.split(",") for s in comma_str.split(",")
{ {
let flag = s.parse()?; let flag = s.parse()?;
println!("found flag: {:?}", &flag);
flags.push(flag); flags.push(flag);
} }
} }
@ -409,9 +408,23 @@ pub fn parse_conv_flag_output(matches: &getopts::Matches) -> Result<ConvFlagOutp
ConvFlag::Sparse => ConvFlag::Sparse =>
sparse = true, sparse = true,
ConvFlag::Excl => ConvFlag::Excl =>
excl = true, if !nocreat
{
excl = true;
}
else
{
return Err(ParseError::MultipleExclNoCreat);
},
ConvFlag::NoCreat => ConvFlag::NoCreat =>
nocreat = true, if !excl
{
nocreat = true;
}
else
{
return Err(ParseError::MultipleExclNoCreat);
},
ConvFlag::NoTrunc => ConvFlag::NoTrunc =>
notrunc = true, notrunc = true,
ConvFlag::FDataSync => ConvFlag::FDataSync =>

View file

@ -76,6 +76,20 @@ fn cfi_block_error()
let cfi_parsed = parse_conv_flag_input(&matches).unwrap(); let cfi_parsed = parse_conv_flag_input(&matches).unwrap();
} }
#[test]
#[should_panic]
fn cfi_creat_error()
{
let args = vec![
String::from("dd"),
String::from("--conv=excl,nocreat"),
];
let matches = build_app!().parse(args);
let cfi_parsed = parse_conv_flag_output(&matches).unwrap();
}
#[test] #[test]
fn parse_cfi_token_ibm() fn parse_cfi_token_ibm()
{ {