From 88dfb8d374e2e9f94f9cfbda9681122116ad9655 Mon Sep 17 00:00:00 2001 From: Omer Tuchfeld Date: Sun, 6 Feb 2022 21:21:07 +0100 Subject: [PATCH] Fix type-error when calling `parse_size` from dd --- src/uu/dd/src/datastructures.rs | 4 +- src/uu/dd/src/dd.rs | 61 +++++++++++++--------------- src/uu/dd/src/parseargs.rs | 70 +++++++++++++++++++++++---------- 3 files changed, 80 insertions(+), 55 deletions(-) diff --git a/src/uu/dd/src/datastructures.rs b/src/uu/dd/src/datastructures.rs index c9c89e858..067058bbe 100644 --- a/src/uu/dd/src/datastructures.rs +++ b/src/uu/dd/src/datastructures.rs @@ -83,8 +83,8 @@ pub struct OFlags { /// then becomes Bytes(N) #[derive(Debug, PartialEq)] pub enum CountType { - Reads(usize), - Bytes(usize), + Reads(u64), + Bytes(u64), } #[derive(Debug)] diff --git a/src/uu/dd/src/dd.rs b/src/uu/dd/src/dd.rs index 7cc6fb082..d8bc3acd3 100644 --- a/src/uu/dd/src/dd.rs +++ b/src/uu/dd/src/dd.rs @@ -37,9 +37,8 @@ use std::time; use clap::{crate_version, App, AppSettings, Arg, ArgMatches}; use gcd::Gcd; use uucore::display::Quotable; -use uucore::error::{FromIo, UResult, USimpleError}; -use uucore::show_error; -use uucore::InvalidEncodingHandling; +use uucore::error::{FromIo, UResult}; +use uucore::{show_error, InvalidEncodingHandling}; const ABOUT: &str = "copy, and optionally convert, a file system resource"; const BUF_INIT_BYTE: u8 = 0xDD; @@ -75,11 +74,13 @@ impl Input { }; if let Some(amt) = skip { - let num_bytes_read = i - .force_fill(amt.try_into().unwrap()) - .map_err_context(|| "failed to read input".to_string())?; - if num_bytes_read < amt { - show_error!("'standard input': cannot skip to specified offset"); + if let Err(e) = i.read_skip(amt) { + if let io::ErrorKind::UnexpectedEof = e.kind() { + show_error!("'standard input': cannot skip to specified offset"); + } else { + return io::Result::Err(e) + .map_err_context(|| "I/O error while skipping".to_string()); + } } } @@ -148,9 +149,6 @@ impl Input { }; if let Some(amt) = skip { - let amt: u64 = amt - .try_into() - .map_err(|_| USimpleError::new(1, "failed to parse seek amount"))?; src.seek(io::SeekFrom::Start(amt)) .map_err_context(|| "failed to seek in input file".to_string())?; } @@ -262,19 +260,18 @@ impl Input { }) } - /// Read the specified number of bytes from this reader. - /// - /// On success, this method returns the number of bytes read. If - /// this reader has fewer than `n` bytes available, then it reads - /// as many as possible. In that case, this method returns a - /// number less than `n`. - /// - /// # Errors - /// - /// If there is a problem reading. - fn force_fill(&mut self, n: u64) -> std::io::Result { - let mut buf = vec![]; - self.take(n).read_to_end(&mut buf) + /// Skips amount_to_read bytes from the Input by copying into a sink + fn read_skip(&mut self, amount_to_read: u64) -> std::io::Result<()> { + let copy_result = io::copy(&mut self.src.by_ref().take(amount_to_read), &mut io::sink()); + if let Ok(n) = copy_result { + if n != amount_to_read { + io::Result::Err(io::Error::new(io::ErrorKind::UnexpectedEof, "")) + } else { + Ok(()) + } + } else { + io::Result::Err(copy_result.unwrap_err()) + } } } @@ -301,8 +298,7 @@ impl OutputTrait for Output { // stdout is not seekable, so we just write null bytes. if let Some(amt) = seek { - let bytes = vec![b'\0'; amt]; - dst.write_all(&bytes) + io::copy(&mut io::repeat(0u8).take(amt as u64), &mut dst) .map_err_context(|| String::from("write error"))?; } @@ -526,7 +522,7 @@ impl OutputTrait for Output { // Instead, we suppress the error by calling // `Result::ok()`. This matches the behavior of GNU `dd` // when given the command-line argument `of=/dev/null`. - let i = seek.unwrap_or(0).try_into().unwrap(); + let i = seek.unwrap_or(0); if !cflags.notrunc { dst.set_len(i).ok(); } @@ -658,15 +654,14 @@ fn calc_loop_bsize( ) -> usize { match count { Some(CountType::Reads(rmax)) => { - let rmax: u64 = (*rmax).try_into().unwrap(); let rsofar = rstat.reads_complete + rstat.reads_partial; - let rremain: usize = (rmax - rsofar).try_into().unwrap(); - cmp::min(ideal_bsize, rremain * ibs) + let rremain = rmax - rsofar; + cmp::min(ideal_bsize as u64, rremain * ibs as u64) as usize } Some(CountType::Bytes(bmax)) => { let bmax: u128 = (*bmax).try_into().unwrap(); - let bremain: usize = (bmax - wstat.bytes_total).try_into().unwrap(); - cmp::min(ideal_bsize, bremain) + let bremain: u128 = bmax - wstat.bytes_total; + cmp::min(ideal_bsize as u128, bremain as u128) as usize } None => ideal_bsize, } @@ -677,7 +672,7 @@ fn calc_loop_bsize( fn below_count_limit(count: &Option, rstat: &ReadStat, wstat: &WriteStat) -> bool { match count { Some(CountType::Reads(n)) => { - let n = (*n).try_into().unwrap(); + let n = *n; rstat.reads_complete + rstat.reads_partial <= n } Some(CountType::Bytes(n)) => { diff --git a/src/uu/dd/src/parseargs.rs b/src/uu/dd/src/parseargs.rs index 7a0bad851..c8324c4ca 100644 --- a/src/uu/dd/src/parseargs.rs +++ b/src/uu/dd/src/parseargs.rs @@ -31,6 +31,10 @@ pub enum ParseError { BlockUnblockWithoutCBS, StatusLevelNotRecognized(String), Unimplemented(String), + BsOutOfRange, + IbsOutOfRange, + ObsOutOfRange, + CbsOutOfRange, } impl ParseError { @@ -48,6 +52,10 @@ impl ParseError { Self::BlockUnblockWithoutCBS => Self::BlockUnblockWithoutCBS, Self::StatusLevelNotRecognized(_) => Self::StatusLevelNotRecognized(s), Self::Unimplemented(_) => Self::Unimplemented(s), + Self::BsOutOfRange => Self::BsOutOfRange, + Self::IbsOutOfRange => Self::IbsOutOfRange, + Self::ObsOutOfRange => Self::ObsOutOfRange, + Self::CbsOutOfRange => Self::CbsOutOfRange, } } } @@ -92,6 +100,18 @@ impl std::fmt::Display for ParseError { Self::StatusLevelNotRecognized(arg) => { write!(f, "status=LEVEL not recognized -> {}", arg) } + ParseError::BsOutOfRange => { + write!(f, "bs=N cannot fit into memory") + } + ParseError::IbsOutOfRange => { + write!(f, "ibs=N cannot fit into memory") + } + ParseError::ObsOutOfRange => { + write!(f, "ibs=N cannot fit into memory") + } + ParseError::CbsOutOfRange => { + write!(f, "cbs=N cannot fit into memory") + } Self::Unimplemented(arg) => { write!(f, "feature not implemented on this system -> {}", arg) } @@ -334,7 +354,7 @@ fn show_zero_multiplier_warning() { } /// Parse bytes using str::parse, then map error if needed. -fn parse_bytes_only(s: &str) -> Result { +fn parse_bytes_only(s: &str) -> Result { s.parse() .map_err(|_| ParseError::MultiplierStringParseFailure(s.to_string())) } @@ -364,7 +384,7 @@ fn parse_bytes_only(s: &str) -> Result { /// assert_eq!(parse_bytes_no_x("2b").unwrap(), 2 * 512); /// assert_eq!(parse_bytes_no_x("2k").unwrap(), 2 * 1024); /// ``` -fn parse_bytes_no_x(s: &str) -> Result { +fn parse_bytes_no_x(s: &str) -> Result { let (num, multiplier) = match (s.find('c'), s.rfind('w'), s.rfind('b')) { (None, None, None) => match uucore::parse_size::parse_size(s) { Ok(n) => (n, 1), @@ -387,7 +407,7 @@ fn parse_bytes_no_x(s: &str) -> Result { /// 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 { +fn parse_bytes_with_opt_multiplier(s: &str) -> Result { // TODO On my Linux system, there seems to be a maximum block size of 4096 bytes: // // $ printf "%0.sa" {1..10000} | dd bs=4095 count=1 status=none | wc -c @@ -420,9 +440,27 @@ fn parse_bytes_with_opt_multiplier(s: &str) -> Result { pub fn parse_ibs(matches: &Matches) -> Result { if let Some(mixed_str) = matches.value_of(options::BS) { - parse_bytes_with_opt_multiplier(mixed_str) + parse_bytes_with_opt_multiplier(mixed_str)? + .try_into() + .map_err(|_| ParseError::BsOutOfRange) } 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)? + .try_into() + .map_err(|_| ParseError::IbsOutOfRange) + } else { + Ok(512) + } +} + +pub fn parse_obs(matches: &Matches) -> Result { + if let Some(mixed_str) = matches.value_of("bs") { + parse_bytes_with_opt_multiplier(mixed_str)? + .try_into() + .map_err(|_| ParseError::BsOutOfRange) + } else if let Some(mixed_str) = matches.value_of("obs") { + parse_bytes_with_opt_multiplier(mixed_str)? + .try_into() + .map_err(|_| ParseError::ObsOutOfRange) } else { Ok(512) } @@ -430,7 +468,9 @@ pub fn parse_ibs(matches: &Matches) -> Result { fn parse_cbs(matches: &Matches) -> Result, ParseError> { 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)? + .try_into() + .map_err(|_| ParseError::CbsOutOfRange)?; Ok(Some(bytes)) } else { Ok(None) @@ -447,16 +487,6 @@ pub(crate) fn parse_status_level(matches: &Matches) -> Result Result { - if let Some(mixed_str) = matches.value_of("bs") { - parse_bytes_with_opt_multiplier(mixed_str) - } else if let Some(mixed_str) = matches.value_of("obs") { - parse_bytes_with_opt_multiplier(mixed_str) - } else { - Ok(512) - } -} - fn parse_ctable(fmt: Option, case: Option) -> Option<&'static ConversionTable> { fn parse_conv_and_case_table( fmt: &ConvFlag, @@ -715,13 +745,13 @@ pub fn parse_skip_amt( ibs: &usize, iflags: &IFlags, matches: &Matches, -) -> Result, ParseError> { +) -> Result, ParseError> { if let Some(amt) = matches.value_of(options::SKIP) { let n = parse_bytes_with_opt_multiplier(amt)?; if iflags.skip_bytes { Ok(Some(n)) } else { - Ok(Some(ibs * n)) + Ok(Some(*ibs as u64 * n)) } } else { Ok(None) @@ -733,13 +763,13 @@ pub fn parse_seek_amt( obs: &usize, oflags: &OFlags, matches: &Matches, -) -> Result, ParseError> { +) -> Result, ParseError> { if let Some(amt) = matches.value_of(options::SEEK) { let n = parse_bytes_with_opt_multiplier(amt)?; if oflags.seek_bytes { Ok(Some(n)) } else { - Ok(Some(obs * n)) + Ok(Some(*obs as u64 * n)) } } else { Ok(None)