1#[cfg(test)]
8mod unit_tests;
9
10use super::{ConversionMode, IConvFlags, IFlags, Num, OConvFlags, OFlags, Settings, StatusLevel};
11use crate::conversion_tables::ConversionTable;
12use thiserror::Error;
13use uucore::display::Quotable;
14use uucore::error::UError;
15use uucore::parser::parse_size::{ParseSizeError, Parser as SizeParser};
16use uucore::show_warning;
17use uucore::translate;
18
19#[derive(Debug, PartialEq, Eq, Error)]
21pub enum ParseError {
22 #[error("{}", translate!("dd-error-unrecognized-operand", "operand" => .0.clone()))]
23 UnrecognizedOperand(String),
24 #[error("{}", translate!("dd-error-multiple-format-table"))]
25 MultipleFmtTable,
26 #[error("{}", translate!("dd-error-multiple-case"))]
27 MultipleUCaseLCase,
28 #[error("{}", translate!("dd-error-multiple-block"))]
29 MultipleBlockUnblock,
30 #[error("{}", translate!("dd-error-multiple-excl"))]
31 MultipleExclNoCreate,
32 #[error("{}", translate!("dd-error-invalid-flag", "flag" => .0.clone(), "cmd" => uucore::execution_phrase()))]
33 FlagNoMatch(String),
34 #[error("{}", translate!("dd-error-conv-flag-no-match", "flag" => .0.clone()))]
35 ConvFlagNoMatch(String),
36 #[error("{}", translate!("dd-error-multiplier-parse-failure", "input" => .0.clone()))]
37 MultiplierStringParseFailure(String),
38 #[error("{}", translate!("dd-error-multiplier-overflow", "input" => .0.clone()))]
39 MultiplierStringOverflow(String),
40 #[error("{}", translate!("dd-error-block-without-cbs"))]
41 BlockUnblockWithoutCBS,
42 #[error("{}", translate!("dd-error-status-not-recognized", "level" => .0.clone()))]
43 StatusLevelNotRecognized(String),
44 #[error("{}", translate!("dd-error-unimplemented", "feature" => .0.clone()))]
45 Unimplemented(String),
46 #[error("{}", translate!("dd-error-bs-out-of-range", "param" => .0.clone()))]
47 BsOutOfRange(String),
48 #[error("{}", translate!("dd-error-invalid-number", "input" => .0.clone()))]
49 InvalidNumber(String),
50}
51
52#[derive(Debug, PartialEq, Default)]
54pub struct Parser {
55 infile: Option<String>,
56 outfile: Option<String>,
57 bs: Option<usize>,
59 ibs: Option<usize>,
61 obs: Option<usize>,
63 cbs: Option<usize>,
64 skip: Num,
65 seek: Num,
66 count: Option<Num>,
67 conv: ConvFlags,
68 is_conv_specified: bool,
70 iflag: IFlags,
71 oflag: OFlags,
72 status: Option<StatusLevel>,
73}
74
75#[derive(Debug, Default, PartialEq, Eq)]
76pub struct ConvFlags {
77 ascii: bool,
78 ebcdic: bool,
79 ibm: bool,
80 ucase: bool,
81 lcase: bool,
82 block: bool,
83 unblock: bool,
84 swab: bool,
85 sync: bool,
86 noerror: bool,
87 sparse: bool,
88 excl: bool,
89 nocreat: bool,
90 notrunc: bool,
91 fdatasync: bool,
92 fsync: bool,
93}
94
95#[derive(Clone, Copy, PartialEq)]
96enum Conversion {
97 Ascii,
98 Ebcdic,
99 Ibm,
100}
101
102#[derive(Clone, Copy)]
103enum Case {
104 Lower,
105 Upper,
106}
107
108#[derive(Clone, Copy)]
109enum Block {
110 Block(usize),
111 Unblock(usize),
112}
113
114macro_rules! linux_only {
116 ($s: expr, $val: expr) => {
117 if cfg!(any(target_os = "linux", target_os = "android")) {
118 $val
119 } else {
120 return Err(ParseError::Unimplemented($s.to_string()).into());
121 }
122 };
123}
124
125impl Parser {
126 pub(crate) fn new() -> Self {
127 Self::default()
128 }
129
130 pub(crate) fn parse(
131 self,
132 operands: impl IntoIterator<Item: AsRef<str>>,
133 ) -> Result<Settings, ParseError> {
134 self.read(operands)?.validate()
135 }
136
137 pub(crate) fn read(
138 mut self,
139 operands: impl IntoIterator<Item: AsRef<str>>,
140 ) -> Result<Self, ParseError> {
141 for operand in operands {
142 self.parse_operand(operand.as_ref())?;
143 }
144
145 Ok(self)
146 }
147
148 pub(crate) fn validate(self) -> Result<Settings, ParseError> {
149 let conv = self.conv;
150 let conversion = match (conv.ascii, conv.ebcdic, conv.ibm) {
151 (false, false, false) => None,
152 (true, false, false) => Some(Conversion::Ascii),
153 (false, true, false) => Some(Conversion::Ebcdic),
154 (false, false, true) => Some(Conversion::Ibm),
155 _ => return Err(ParseError::MultipleFmtTable),
156 };
157
158 let case = match (conv.ucase, conv.lcase) {
159 (false, false) => None,
160 (true, false) => Some(Case::Upper),
161 (false, true) => Some(Case::Lower),
162 (true, true) => return Err(ParseError::MultipleUCaseLCase),
163 };
164
165 let non_ascii = matches!(conversion, Some(Conversion::Ascii));
166 let conversion_table = get_ctable(conversion, case);
167
168 if conv.nocreat && conv.excl {
169 return Err(ParseError::MultipleExclNoCreate);
170 }
171
172 let block = if let Some(cbs) = self.cbs {
183 match conversion {
184 Some(Conversion::Ascii) => Some(Block::Unblock(cbs)),
185 Some(_) => Some(Block::Block(cbs)),
186 None => match (conv.block, conv.unblock) {
187 (false, false) => None,
188 (true, false) => Some(Block::Block(cbs)),
189 (false, true) => Some(Block::Unblock(cbs)),
190 (true, true) => return Err(ParseError::MultipleBlockUnblock),
191 },
192 }
193 } else if conv.block || conv.unblock {
194 return Err(ParseError::BlockUnblockWithoutCBS);
195 } else {
196 None
197 };
198
199 let iconv = IConvFlags {
200 mode: conversion_mode(conversion_table, block, non_ascii, conv.sync),
201 swab: conv.swab,
202 sync: if conv.sync {
203 if block.is_some() {
204 Some(b' ')
205 } else {
206 Some(0u8)
207 }
208 } else {
209 None
210 },
211 noerror: conv.noerror,
212 };
213
214 let oconv = OConvFlags {
215 sparse: conv.sparse,
216 excl: conv.excl,
217 nocreat: conv.nocreat,
218 notrunc: conv.notrunc,
219 fdatasync: conv.fdatasync,
220 fsync: conv.fsync,
221 };
222
223 let (ibs, obs) = match self.bs {
228 None => (self.ibs.unwrap_or(512), self.obs.unwrap_or(512)),
229 Some(bs) => (bs, bs),
230 };
231
232 let buffered = self.bs.is_none() || self.is_conv_specified;
241
242 let skip = self
243 .skip
244 .force_bytes_if(self.iflag.skip_bytes)
245 .to_bytes(ibs as u64);
246
247 let seek = self
248 .seek
249 .force_bytes_if(self.oflag.seek_bytes)
250 .to_bytes(obs as u64);
251
252 let count = self.count.map(|c| c.force_bytes_if(self.iflag.count_bytes));
253
254 Ok(Settings {
255 skip,
256 seek,
257 count,
258 iconv,
259 oconv,
260 ibs,
261 obs,
262 buffered,
263 infile: self.infile,
264 outfile: self.outfile,
265 iflags: self.iflag,
266 oflags: self.oflag,
267 status: self.status,
268 })
269 }
270
271 fn parse_operand(&mut self, operand: &str) -> Result<(), ParseError> {
272 match operand.split_once('=') {
273 None => return Err(ParseError::UnrecognizedOperand(operand.to_string())),
274 Some((k, v)) => match k {
275 "bs" => self.bs = Some(Self::parse_bytes(k, v)?),
276 "cbs" => self.cbs = Some(Self::parse_bytes(k, v)?),
277 "conv" => {
278 self.is_conv_specified = true;
279 self.parse_conv_flags(v)?;
280 }
281 "count" => self.count = Some(Self::parse_n(v)?),
282 "ibs" => self.ibs = Some(Self::parse_bytes(k, v)?),
283 "if" => self.infile = Some(v.to_string()),
284 "iflag" => self.parse_input_flags(v)?,
285 "obs" => self.obs = Some(Self::parse_bytes(k, v)?),
286 "of" => self.outfile = Some(v.to_string()),
287 "oflag" => self.parse_output_flags(v)?,
288 "seek" | "oseek" => self.seek = Self::parse_n(v)?,
289 "skip" | "iseek" => self.skip = Self::parse_n(v)?,
290 "status" => self.status = Some(Self::parse_status_level(v)?),
291 _ => return Err(ParseError::UnrecognizedOperand(operand.to_string())),
292 },
293 }
294 Ok(())
295 }
296
297 fn parse_n(val: &str) -> Result<Num, ParseError> {
298 let n = parse_bytes_with_opt_multiplier(val)?;
299 Ok(if val.contains('B') {
300 Num::Bytes(n)
301 } else {
302 Num::Blocks(n)
303 })
304 }
305
306 fn parse_bytes(arg: &str, val: &str) -> Result<usize, ParseError> {
307 parse_bytes_with_opt_multiplier(val)?
308 .try_into()
309 .map_err(|_| ParseError::BsOutOfRange(arg.to_string()))
310 }
311
312 fn parse_status_level(val: &str) -> Result<StatusLevel, ParseError> {
313 match val {
314 "none" => Ok(StatusLevel::None),
315 "noxfer" => Ok(StatusLevel::Noxfer),
316 "progress" => Ok(StatusLevel::Progress),
317 _ => Err(ParseError::StatusLevelNotRecognized(val.to_string())),
318 }
319 }
320
321 #[allow(clippy::cognitive_complexity)]
322 fn parse_input_flags(&mut self, val: &str) -> Result<(), ParseError> {
323 let i = &mut self.iflag;
324 for f in val.split(',') {
325 match f {
326 "cio" => return Err(ParseError::Unimplemented(f.to_string())),
328 "direct" => linux_only!(f, i.direct = true),
329 "directory" => linux_only!(f, i.directory = true),
330 "dsync" => linux_only!(f, i.dsync = true),
331 "sync" => linux_only!(f, i.sync = true),
332 "nocache" => linux_only!(f, i.nocache = true),
333 "nonblock" => linux_only!(f, i.nonblock = true),
334 "noatime" => linux_only!(f, i.noatime = true),
335 "noctty" => linux_only!(f, i.noctty = true),
336 "nofollow" => linux_only!(f, i.nofollow = true),
337 "nolinks" => return Err(ParseError::Unimplemented(f.to_string())),
338 "binary" => return Err(ParseError::Unimplemented(f.to_string())),
339 "text" => return Err(ParseError::Unimplemented(f.to_string())),
340
341 "fullblock" => i.fullblock = true,
343 "count_bytes" => i.count_bytes = true,
344 "skip_bytes" => i.skip_bytes = true,
345 "append" | "seek_bytes" => {}
347 _ => return Err(ParseError::FlagNoMatch(f.to_string())),
348 }
349 }
350 Ok(())
351 }
352
353 #[allow(clippy::cognitive_complexity)]
354 fn parse_output_flags(&mut self, val: &str) -> Result<(), ParseError> {
355 let o = &mut self.oflag;
356 for f in val.split(',') {
357 match f {
358 "cio" => return Err(ParseError::Unimplemented(val.to_string())),
360 "direct" => linux_only!(f, o.direct = true),
361 "directory" => linux_only!(f, o.directory = true),
362 "dsync" => linux_only!(f, o.dsync = true),
363 "sync" => linux_only!(f, o.sync = true),
364 "nocache" => linux_only!(f, o.nocache = true),
365 "nonblock" => linux_only!(f, o.nonblock = true),
366 "noatime" => linux_only!(f, o.noatime = true),
367 "noctty" => linux_only!(f, o.noctty = true),
368 "nofollow" => linux_only!(f, o.nofollow = true),
369 "nolinks" => return Err(ParseError::Unimplemented(f.to_string())),
370 "binary" => return Err(ParseError::Unimplemented(f.to_string())),
371 "text" => return Err(ParseError::Unimplemented(f.to_string())),
372
373 "append" => o.append = true,
375 "seek_bytes" => o.seek_bytes = true,
376 "fullblock" | "count_bytes" | "skip_bytes" => {}
378 _ => return Err(ParseError::FlagNoMatch(f.to_string())),
379 }
380 }
381 Ok(())
382 }
383
384 fn parse_conv_flags(&mut self, val: &str) -> Result<(), ParseError> {
385 let c = &mut self.conv;
386 for f in val.split(',') {
387 match f {
388 "ascii" => c.ascii = true,
390 "ebcdic" => c.ebcdic = true,
391 "ibm" => c.ibm = true,
392
393 "lcase" => c.lcase = true,
395 "ucase" => c.ucase = true,
396
397 "block" => c.block = true,
399 "unblock" => c.unblock = true,
400
401 "swab" => c.swab = true,
403 "sync" => c.sync = true,
404 "noerror" => c.noerror = true,
405
406 "sparse" => c.sparse = true,
408 "excl" => c.excl = true,
409 "nocreat" => c.nocreat = true,
410 "notrunc" => c.notrunc = true,
411 "fdatasync" => c.fdatasync = true,
412 "fsync" => c.fsync = true,
413 _ => return Err(ParseError::ConvFlagNoMatch(f.to_string())),
414 }
415 }
416 Ok(())
417 }
418}
419
420impl UError for ParseError {
421 fn code(&self) -> i32 {
422 1
423 }
424}
425
426fn show_zero_multiplier_warning() {
427 show_warning!(
428 "{}",
429 translate!("dd-warning-zero-multiplier", "zero" => "0x".quote(), "alternative" => "00x".quote())
430 );
431}
432
433fn parse_bytes_only(s: &str, i: usize) -> Result<u64, ParseError> {
435 s[..i]
436 .parse()
437 .map_err(|_| ParseError::MultiplierStringParseFailure(s.to_string()))
438}
439
440fn parse_bytes_no_x(full: &str, s: &str) -> Result<u64, ParseError> {
468 let parser = SizeParser {
469 capital_b_bytes: true,
470 no_empty_numeric: true,
471 ..Default::default()
472 };
473 let (num, multiplier) = match (s.find('c'), s.rfind('w'), s.rfind('b')) {
474 (None, None, None) => match parser.parse_u64(s) {
475 Ok(n) => (n, 1),
476 Err(ParseSizeError::SizeTooBig(_)) => (u64::MAX, 1),
477 Err(_) => return Err(ParseError::InvalidNumber(full.to_string())),
478 },
479 (Some(i), None, None) => (parse_bytes_only(s, i)?, 1),
480 (None, Some(i), None) => (parse_bytes_only(s, i)?, 2),
481 (None, None, Some(i)) => (parse_bytes_only(s, i)?, 512),
482 _ => return Err(ParseError::MultiplierStringParseFailure(full.to_string())),
483 };
484 num.checked_mul(multiplier)
485 .ok_or_else(|| ParseError::MultiplierStringOverflow(full.to_string()))
486}
487
488pub fn parse_bytes_with_opt_multiplier(s: &str) -> Result<u64, ParseError> {
492 let parts: Vec<&str> = s.split('x').collect();
507 if parts.len() == 1 {
508 parse_bytes_no_x(s, parts[0])
509 } else {
510 let mut total: u64 = 1;
511 for part in parts {
512 if part == "0" {
513 show_zero_multiplier_warning();
514 }
515 let num = parse_bytes_no_x(s, part)?;
516 total = total
517 .checked_mul(num)
518 .ok_or_else(|| ParseError::InvalidNumber(s.to_string()))?;
519 }
520 Ok(total)
521 }
522}
523
524fn get_ctable(
525 conversion: Option<Conversion>,
526 case: Option<Case>,
527) -> Option<&'static ConversionTable> {
528 use crate::conversion_tables::*;
529 Some(match (conversion, case) {
530 (None, None) => return None,
531 (Some(conv), None) => match conv {
532 Conversion::Ascii => &EBCDIC_TO_ASCII,
533 Conversion::Ebcdic => &ASCII_TO_EBCDIC,
534 Conversion::Ibm => &ASCII_TO_IBM,
535 },
536 (None, Some(case)) => match case {
537 Case::Lower => &ASCII_UCASE_TO_LCASE,
538 Case::Upper => &ASCII_LCASE_TO_UCASE,
539 },
540 (Some(conv), Some(case)) => match (conv, case) {
541 (Conversion::Ascii, Case::Upper) => &EBCDIC_TO_ASCII_LCASE_TO_UCASE,
542 (Conversion::Ascii, Case::Lower) => &EBCDIC_TO_ASCII_UCASE_TO_LCASE,
543 (Conversion::Ebcdic, Case::Upper) => &ASCII_TO_EBCDIC_LCASE_TO_UCASE,
544 (Conversion::Ebcdic, Case::Lower) => &ASCII_TO_EBCDIC_UCASE_TO_LCASE,
545 (Conversion::Ibm, Case::Upper) => &ASCII_TO_IBM_UCASE_TO_LCASE,
546 (Conversion::Ibm, Case::Lower) => &ASCII_TO_IBM_LCASE_TO_UCASE,
547 },
548 })
549}
550
551fn conversion_mode(
560 ctable: Option<&'static ConversionTable>,
561 block: Option<Block>,
562 is_ascii: bool,
563 is_sync: bool,
564) -> Option<ConversionMode> {
565 match (ctable, block) {
566 (Some(ct), None) => Some(ConversionMode::ConvertOnly(ct)),
567 (Some(ct), Some(Block::Block(cbs))) => {
568 if is_ascii {
569 Some(ConversionMode::ConvertThenBlock(ct, cbs, is_sync))
570 } else {
571 Some(ConversionMode::BlockThenConvert(ct, cbs, is_sync))
572 }
573 }
574 (Some(ct), Some(Block::Unblock(cbs))) => {
575 if is_ascii {
576 Some(ConversionMode::ConvertThenUnblock(ct, cbs))
577 } else {
578 Some(ConversionMode::UnblockThenConvert(ct, cbs))
579 }
580 }
581 (None, Some(Block::Block(cbs))) => Some(ConversionMode::BlockOnly(cbs, is_sync)),
582 (None, Some(Block::Unblock(cbs))) => Some(ConversionMode::UnblockOnly(cbs)),
583 (None, None) => None,
584 }
585}
586
587#[cfg(test)]
588mod tests {
589
590 use crate::Num;
591 use crate::parseargs::{Parser, parse_bytes_with_opt_multiplier};
592 use std::matches;
593 const BIG: &str = "9999999999999999999999999999999999999999999999999999999999999";
594
595 #[test]
596 fn test_parse_bytes_with_opt_multiplier_invalid() {
597 assert!(parse_bytes_with_opt_multiplier("123asdf").is_err());
598 }
599
600 #[test]
601 fn test_parse_bytes_with_opt_multiplier_without_x() {
602 assert_eq!(parse_bytes_with_opt_multiplier("123").unwrap(), 123);
603 assert_eq!(parse_bytes_with_opt_multiplier("123c").unwrap(), 123); assert_eq!(parse_bytes_with_opt_multiplier("123w").unwrap(), 123 * 2);
605 assert_eq!(parse_bytes_with_opt_multiplier("123b").unwrap(), 123 * 512);
606 assert_eq!(parse_bytes_with_opt_multiplier("123k").unwrap(), 123 * 1024);
607 assert_eq!(parse_bytes_with_opt_multiplier(BIG).unwrap(), u64::MAX);
608 }
609
610 #[test]
611 fn test_parse_bytes_with_opt_multiplier_with_x() {
612 assert_eq!(parse_bytes_with_opt_multiplier("123x3").unwrap(), 123 * 3);
613 assert_eq!(parse_bytes_with_opt_multiplier("1x2x3").unwrap(), 6); assert_eq!(
615 parse_bytes_with_opt_multiplier("1wx2cx3w").unwrap(),
616 2 * 2 * (3 * 2) );
618 }
619 #[test]
620 fn test_parse_n() {
621 for arg in ["1x8x4", "1c", "123b", "123w"] {
622 assert!(matches!(Parser::parse_n(arg), Ok(Num::Blocks(_))));
623 }
624 for arg in ["1Bx8x4", "2Bx8", "2Bx8B", "2x8B"] {
625 assert!(matches!(Parser::parse_n(arg), Ok(Num::Bytes(_))));
626 }
627 }
628}