dd: move ConversionMode parsing to parseargs mod.

Move the code for parsing the `ConversionMode` to use up to the
`parseargs` module. This location makes more sense for it because the
conversion mode can be determined entirely from the command-line
arguments at the time of parsing just like the other parameters. Using
an enum for this purpose also eliminates the amount of code we need
later on.
This commit is contained in:
Jeffrey Finkelstein 2022-03-06 23:39:04 -05:00 committed by Sylvestre Ledru
parent b98bccf9cc
commit f856bfc479
4 changed files with 85 additions and 73 deletions

View file

@ -33,10 +33,8 @@ pub(crate) enum ConversionMode<'a> {
/// Stores all Conv Flags that apply to the input
#[derive(Debug, Default, PartialEq)]
pub struct IConvFlags {
pub ctable: Option<&'static ConversionTable>,
pub block: Option<Cbs>,
pub unblock: Option<Cbs>,
pub(crate) struct IConvFlags {
pub mode: Option<ConversionMode<'static>>,
pub swab: bool,
pub sync: Option<u8>,
pub noerror: bool,

View file

@ -45,7 +45,6 @@ const BUF_INIT_BYTE: u8 = 0xDD;
struct Input<R: Read> {
src: R,
non_ascii: bool,
ibs: usize,
print_level: Option<StatusLevel>,
count: Option<CountType>,
@ -56,7 +55,6 @@ struct Input<R: Read> {
impl Input<io::Stdin> {
fn new(matches: &Matches) -> UResult<Self> {
let ibs = parseargs::parse_ibs(matches)?;
let non_ascii = parseargs::parse_input_non_ascii(matches)?;
let print_level = parseargs::parse_status_level(matches)?;
let cflags = parseargs::parse_conv_flag_input(matches)?;
let iflags = parseargs::parse_iflags(matches)?;
@ -67,7 +65,6 @@ impl Input<io::Stdin> {
let mut i = Self {
src: io::stdin(),
non_ascii,
ibs,
print_level,
count,
@ -131,7 +128,6 @@ fn make_linux_iflags(iflags: &IFlags) -> Option<libc::c_int> {
impl Input<File> {
fn new(matches: &Matches) -> UResult<Self> {
let ibs = parseargs::parse_ibs(matches)?;
let non_ascii = parseargs::parse_input_non_ascii(matches)?;
let print_level = parseargs::parse_status_level(matches)?;
let cflags = parseargs::parse_conv_flag_input(matches)?;
let iflags = parseargs::parse_iflags(matches)?;
@ -163,7 +159,6 @@ impl Input<File> {
let i = Self {
src,
non_ascii,
ibs,
print_level,
count,
@ -605,47 +600,6 @@ impl Write for Output<io::Stdout> {
}
}
/// Given the various command-line parameters, determine the conversion mode.
///
/// The `conv` command-line option can take many different values,
/// each of which may combine with others. For example, `conv=ascii`,
/// `conv=lcase`, `conv=sync`, and so on. The arguments to this
/// function represent the settings of those various command-line
/// parameters. This function translates those settings to a
/// [`ConversionMode`].
fn conversion_mode(
ctable: Option<&ConversionTable>,
block: Option<usize>,
unblock: Option<usize>,
non_ascii: bool,
is_sync: bool,
) -> Option<ConversionMode> {
match (ctable, block, unblock) {
(Some(ct), None, None) => Some(ConversionMode::ConvertOnly(ct)),
(Some(ct), Some(cbs), None) => {
if non_ascii {
Some(ConversionMode::ConvertThenBlock(ct, cbs, is_sync))
} else {
Some(ConversionMode::BlockThenConvert(ct, cbs, is_sync))
}
}
(Some(ct), None, Some(cbs)) => {
if non_ascii {
Some(ConversionMode::ConvertThenUnblock(ct, cbs))
} else {
Some(ConversionMode::UnblockThenConvert(ct, cbs))
}
}
(None, Some(cbs), None) => Some(ConversionMode::BlockOnly(cbs, is_sync)),
(None, None, Some(cbs)) => Some(ConversionMode::UnblockOnly(cbs)),
(None, None, None) => None,
// The remaining variants should never happen because the
// argument parsing above should result in an error before
// getting to this line of code.
_ => unreachable!(),
}
}
/// Read helper performs read operations common to all dd reads, and dispatches the buffer to relevant helper functions as dictated by the operations requested by the user.
fn read_helper<R: Read>(i: &mut Input<R>, bsize: usize) -> std::io::Result<(ReadStat, Vec<u8>)> {
// Local Helper Fns -------------------------------------------------
@ -671,14 +625,7 @@ fn read_helper<R: Read>(i: &mut Input<R>, bsize: usize) -> std::io::Result<(Read
perform_swab(&mut buf);
}
let mode = conversion_mode(
i.cflags.ctable,
i.cflags.block,
i.cflags.unblock,
i.non_ascii,
i.cflags.sync.is_some(),
);
match mode {
match i.cflags.mode {
Some(ref mode) => {
let buf = conv_block_unblock_helper(buf, mode, &mut rstat);
Ok((rstat, buf))
@ -1129,7 +1076,6 @@ mod tests {
src: LazyReader {
src: File::open("./test-resources/deadbeef-16.test").unwrap(),
},
non_ascii: false,
ibs: 16,
print_level: None,
count: None,
@ -1176,7 +1122,6 @@ mod tests {
src: File::open("./test-resources/random-5828891cb1230748e146f34223bbd3b5.test")
.unwrap(),
},
non_ascii: false,
ibs: 521,
print_level: None,
count: None,

View file

@ -535,9 +535,50 @@ fn parse_flag_list<T: std::str::FromStr<Err = ParseError>>(
.collect()
}
/// Given the various command-line parameters, determine the conversion mode.
///
/// The `conv` command-line option can take many different values,
/// each of which may combine with others. For example, `conv=ascii`,
/// `conv=lcase`, `conv=sync`, and so on. The arguments to this
/// function represent the settings of those various command-line
/// parameters. This function translates those settings to a
/// [`ConversionMode`].
fn conversion_mode(
ctable: Option<&ConversionTable>,
block: Option<usize>,
unblock: Option<usize>,
non_ascii: bool,
is_sync: bool,
) -> Option<ConversionMode> {
match (ctable, block, unblock) {
(Some(ct), None, None) => Some(ConversionMode::ConvertOnly(ct)),
(Some(ct), Some(cbs), None) => {
if non_ascii {
Some(ConversionMode::ConvertThenBlock(ct, cbs, is_sync))
} else {
Some(ConversionMode::BlockThenConvert(ct, cbs, is_sync))
}
}
(Some(ct), None, Some(cbs)) => {
if non_ascii {
Some(ConversionMode::ConvertThenUnblock(ct, cbs))
} else {
Some(ConversionMode::UnblockThenConvert(ct, cbs))
}
}
(None, Some(cbs), None) => Some(ConversionMode::BlockOnly(cbs, is_sync)),
(None, None, Some(cbs)) => Some(ConversionMode::UnblockOnly(cbs)),
(None, None, None) => None,
// The remaining variants should never happen because the
// argument parsing above should result in an error before
// getting to this line of code.
_ => unreachable!(),
}
}
/// Parse Conversion Options (Input Variety)
/// Construct and validate a IConvFlags
pub fn parse_conv_flag_input(matches: &Matches) -> Result<IConvFlags, ParseError> {
pub(crate) fn parse_conv_flag_input(matches: &Matches) -> Result<IConvFlags, ParseError> {
let mut iconvflags = IConvFlags::default();
let mut fmt = None;
let mut case = None;
@ -546,6 +587,9 @@ pub fn parse_conv_flag_input(matches: &Matches) -> Result<IConvFlags, ParseError
let flags = parse_flag_list(options::CONV, matches)?;
let cbs = parse_cbs(matches)?;
let mut block = None;
let mut unblock = None;
for flag in flags {
match flag {
ConvFlag::FmtEtoA => {
@ -565,7 +609,7 @@ pub fn parse_conv_flag_input(matches: &Matches) -> Result<IConvFlags, ParseError
//
// -- https://www.gnu.org/software/coreutils/manual/html_node/dd-invocation.html
if cbs.is_some() {
iconvflags.unblock = cbs;
unblock = cbs;
}
}
}
@ -585,7 +629,7 @@ pub fn parse_conv_flag_input(matches: &Matches) -> Result<IConvFlags, ParseError
//
// -- https://www.gnu.org/software/coreutils/manual/html_node/dd-invocation.html
if cbs.is_some() {
iconvflags.block = cbs;
block = cbs;
}
}
}
@ -603,13 +647,13 @@ pub fn parse_conv_flag_input(matches: &Matches) -> Result<IConvFlags, ParseError
case = Some(flag);
}
}
ConvFlag::Block => match (cbs, iconvflags.unblock) {
(Some(cbs), None) => iconvflags.block = Some(cbs),
ConvFlag::Block => match (cbs, unblock) {
(Some(cbs), None) => block = Some(cbs),
(None, _) => return Err(ParseError::BlockUnblockWithoutCBS),
(_, Some(_)) => return Err(ParseError::MultipleBlockUnblock),
},
ConvFlag::Unblock => match (cbs, iconvflags.block) {
(Some(cbs), None) => iconvflags.unblock = Some(cbs),
ConvFlag::Unblock => match (cbs, block) {
(Some(cbs), None) => unblock = Some(cbs),
(None, _) => return Err(ParseError::BlockUnblockWithoutCBS),
(_, Some(_)) => return Err(ParseError::MultipleBlockUnblock),
},
@ -630,7 +674,7 @@ pub fn parse_conv_flag_input(matches: &Matches) -> Result<IConvFlags, ParseError
// block implies sync with ' '
// unblock implies sync with 0
// So the final value can't be set until all flags are parsed.
let sync = if is_sync && (iconvflags.block.is_some() || iconvflags.unblock.is_some()) {
let sync = if is_sync && (block.is_some() || unblock.is_some()) {
Some(b' ')
} else if is_sync {
Some(0u8)
@ -638,8 +682,27 @@ pub fn parse_conv_flag_input(matches: &Matches) -> Result<IConvFlags, ParseError
None
};
// Some user options, such as the presence of conversion tables,
// will determine whether the input is assumed to be ascii. This
// parser sets the non_ascii flag accordingly.
//
// Examples:
//
// - If conv=ebcdic or conv=ibm is specified then block,
// unblock or swab must be performed before the conversion
// happens since the source will start in ascii.
// - If conv=ascii is specified then block, unblock or swab
// must be performed after the conversion since the source
// starts in ebcdic.
// - If no conversion is specified then the source is assumed
// to be in ascii.
//
// For more info see `info dd`.
let non_ascii = parseargs::parse_input_non_ascii(matches)?;
let mode = conversion_mode(ctable, block, unblock, non_ascii, is_sync);
Ok(IConvFlags {
ctable,
mode,
sync,
..iconvflags
})

View file

@ -170,8 +170,11 @@ fn test_all_top_level_args_no_leading_dashes() {
);
assert_eq!(
IConvFlags {
ctable: Some(&EBCDIC_TO_ASCII_LCASE_TO_UCASE),
unblock: Some(1), // because ascii implies unblock
// ascii implies unblock
mode: Some(ConversionMode::ConvertThenUnblock(
&EBCDIC_TO_ASCII_LCASE_TO_UCASE,
1
)),
..IConvFlags::default()
},
parse_conv_flag_input(&matches).unwrap()
@ -269,8 +272,11 @@ fn test_all_top_level_args_with_leading_dashes() {
);
assert_eq!(
IConvFlags {
ctable: Some(&EBCDIC_TO_ASCII_LCASE_TO_UCASE),
unblock: Some(1), // because ascii implies unblock
// ascii implies unblock
mode: Some(ConversionMode::ConvertThenUnblock(
&EBCDIC_TO_ASCII_LCASE_TO_UCASE,
1
)),
..IConvFlags::default()
},
parse_conv_flag_input(&matches).unwrap()