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 #[error("invalid number: ‘{0}’: {1}")]
51 InvalidNumberWithErrMsg(String, String),
52}
53
54#[derive(Debug, PartialEq, Default)]
56pub struct Parser {
57 infile: Option<String>,
58 outfile: Option<String>,
59 bs: Option<usize>,
61 ibs: Option<usize>,
63 obs: Option<usize>,
65 cbs: Option<usize>,
66 skip: Num,
67 seek: Num,
68 count: Option<Num>,
69 conv: ConvFlags,
70 is_conv_specified: bool,
72 iflag: IFlags,
73 oflag: OFlags,
74 status: Option<StatusLevel>,
75}
76
77#[derive(Debug, Default, PartialEq, Eq)]
78pub struct ConvFlags {
79 ascii: bool,
80 ebcdic: bool,
81 ibm: bool,
82 ucase: bool,
83 lcase: bool,
84 block: bool,
85 unblock: bool,
86 swab: bool,
87 sync: bool,
88 noerror: bool,
89 sparse: bool,
90 excl: bool,
91 nocreat: bool,
92 notrunc: bool,
93 fdatasync: bool,
94 fsync: bool,
95}
96
97#[derive(Clone, Copy, PartialEq)]
98enum Conversion {
99 Ascii,
100 Ebcdic,
101 Ibm,
102}
103
104#[derive(Clone, Copy)]
105enum Case {
106 Lower,
107 Upper,
108}
109
110#[derive(Clone, Copy)]
111enum Block {
112 Block(usize),
113 Unblock(usize),
114}
115
116macro_rules! linux_only {
118 ($s: expr, $val: expr) => {
119 if cfg!(any(target_os = "linux", target_os = "android")) {
120 $val
121 } else {
122 return Err(ParseError::Unimplemented($s.to_string()).into());
123 }
124 };
125}
126
127impl Parser {
128 pub(crate) fn new() -> Self {
129 Self::default()
130 }
131
132 pub(crate) fn parse(
133 self,
134 operands: impl IntoIterator<Item: AsRef<str>>,
135 ) -> Result<Settings, ParseError> {
136 self.read(operands)?.validate()
137 }
138
139 pub(crate) fn read(
140 mut self,
141 operands: impl IntoIterator<Item: AsRef<str>>,
142 ) -> Result<Self, ParseError> {
143 for operand in operands {
144 self.parse_operand(operand.as_ref())?;
145 }
146
147 Ok(self)
148 }
149
150 pub(crate) fn validate(self) -> Result<Settings, ParseError> {
151 let conv = self.conv;
152 let conversion = match (conv.ascii, conv.ebcdic, conv.ibm) {
153 (false, false, false) => None,
154 (true, false, false) => Some(Conversion::Ascii),
155 (false, true, false) => Some(Conversion::Ebcdic),
156 (false, false, true) => Some(Conversion::Ibm),
157 _ => return Err(ParseError::MultipleFmtTable),
158 };
159
160 let case = match (conv.ucase, conv.lcase) {
161 (false, false) => None,
162 (true, false) => Some(Case::Upper),
163 (false, true) => Some(Case::Lower),
164 (true, true) => return Err(ParseError::MultipleUCaseLCase),
165 };
166
167 let non_ascii = matches!(conversion, Some(Conversion::Ascii));
168 let conversion_table = get_ctable(conversion, case);
169
170 if conv.nocreat && conv.excl {
171 return Err(ParseError::MultipleExclNoCreate);
172 }
173
174 let block = if let Some(cbs) = self.cbs {
185 match conversion {
186 Some(Conversion::Ascii) => Some(Block::Unblock(cbs)),
187 Some(_) => Some(Block::Block(cbs)),
188 None => match (conv.block, conv.unblock) {
189 (false, false) => None,
190 (true, false) => Some(Block::Block(cbs)),
191 (false, true) => Some(Block::Unblock(cbs)),
192 (true, true) => return Err(ParseError::MultipleBlockUnblock),
193 },
194 }
195 } else if conv.block || conv.unblock {
196 return Err(ParseError::BlockUnblockWithoutCBS);
197 } else {
198 None
199 };
200
201 let iconv = IConvFlags {
202 mode: conversion_mode(conversion_table, block, non_ascii, conv.sync),
203 swab: conv.swab,
204 sync: if conv.sync {
205 if block.is_some() {
206 Some(b' ')
207 } else {
208 Some(0u8)
209 }
210 } else {
211 None
212 },
213 noerror: conv.noerror,
214 };
215
216 let oconv = OConvFlags {
217 sparse: conv.sparse,
218 excl: conv.excl,
219 nocreat: conv.nocreat,
220 notrunc: conv.notrunc,
221 fdatasync: conv.fdatasync,
222 fsync: conv.fsync,
223 };
224
225 let (ibs, obs) = match self.bs {
230 None => (self.ibs.unwrap_or(512), self.obs.unwrap_or(512)),
231 Some(bs) => (bs, bs),
232 };
233
234 let buffered = self.bs.is_none() || self.is_conv_specified;
243
244 let skip = self
245 .skip
246 .force_bytes_if(self.iflag.skip_bytes)
247 .to_bytes(ibs as u64);
248 if skip > i64::MAX as u64 {
250 return Err(ParseError::InvalidNumberWithErrMsg(
251 format!("{skip}"),
252 "Value too large for defined data type".to_string(),
253 ));
254 }
255
256 let seek = self
257 .seek
258 .force_bytes_if(self.oflag.seek_bytes)
259 .to_bytes(obs as u64);
260 if seek > i64::MAX as u64 {
262 return Err(ParseError::InvalidNumberWithErrMsg(
263 format!("{seek}"),
264 "Value too large for defined data type".to_string(),
265 ));
266 }
267
268 let count = self.count.map(|c| c.force_bytes_if(self.iflag.count_bytes));
269
270 Ok(Settings {
271 skip,
272 seek,
273 count,
274 iconv,
275 oconv,
276 ibs,
277 obs,
278 buffered,
279 infile: self.infile,
280 outfile: self.outfile,
281 iflags: self.iflag,
282 oflags: self.oflag,
283 status: self.status,
284 })
285 }
286
287 fn parse_operand(&mut self, operand: &str) -> Result<(), ParseError> {
288 match operand.split_once('=') {
289 None => return Err(ParseError::UnrecognizedOperand(operand.to_string())),
290 Some((k, v)) => match k {
291 "bs" => self.bs = Some(Self::parse_bytes(k, v)?),
292 "cbs" => self.cbs = Some(Self::parse_bytes(k, v)?),
293 "conv" => {
294 self.is_conv_specified = true;
295 self.parse_conv_flags(v)?;
296 }
297 "count" => self.count = Some(Self::parse_n(v)?),
298 "ibs" => self.ibs = Some(Self::parse_bytes(k, v)?),
299 "if" => self.infile = Some(v.to_string()),
300 "iflag" => self.parse_input_flags(v)?,
301 "obs" => self.obs = Some(Self::parse_bytes(k, v)?),
302 "of" => self.outfile = Some(v.to_string()),
303 "oflag" => self.parse_output_flags(v)?,
304 "seek" | "oseek" => self.seek = Self::parse_n(v)?,
305 "skip" | "iseek" => self.skip = Self::parse_n(v)?,
306 "status" => self.status = Some(Self::parse_status_level(v)?),
307 _ => return Err(ParseError::UnrecognizedOperand(operand.to_string())),
308 },
309 }
310 Ok(())
311 }
312
313 fn parse_n(val: &str) -> Result<Num, ParseError> {
314 let n = parse_bytes_with_opt_multiplier(val)?;
315 Ok(if val.contains('B') {
316 Num::Bytes(n)
317 } else {
318 Num::Blocks(n)
319 })
320 }
321
322 fn parse_bytes(arg: &str, val: &str) -> Result<usize, ParseError> {
323 parse_bytes_with_opt_multiplier(val)?
324 .try_into()
325 .map_err(|_| ParseError::BsOutOfRange(arg.to_string()))
326 }
327
328 fn parse_status_level(val: &str) -> Result<StatusLevel, ParseError> {
329 match val {
330 "none" => Ok(StatusLevel::None),
331 "noxfer" => Ok(StatusLevel::Noxfer),
332 "progress" => Ok(StatusLevel::Progress),
333 _ => Err(ParseError::StatusLevelNotRecognized(val.to_string())),
334 }
335 }
336
337 #[allow(clippy::cognitive_complexity)]
338 fn parse_input_flags(&mut self, val: &str) -> Result<(), ParseError> {
339 let i = &mut self.iflag;
340 for f in val.split(',') {
341 match f {
342 "cio" => return Err(ParseError::Unimplemented(f.to_string())),
344 "direct" => linux_only!(f, i.direct = true),
345 "directory" => linux_only!(f, i.directory = true),
346 "dsync" => linux_only!(f, i.dsync = true),
347 "sync" => linux_only!(f, i.sync = true),
348 "nocache" => linux_only!(f, i.nocache = true),
349 "nonblock" => linux_only!(f, i.nonblock = true),
350 "noatime" => linux_only!(f, i.noatime = true),
351 "noctty" => linux_only!(f, i.noctty = true),
352 "nofollow" => linux_only!(f, i.nofollow = true),
353 "nolinks" => return Err(ParseError::Unimplemented(f.to_string())),
354 "binary" => return Err(ParseError::Unimplemented(f.to_string())),
355 "text" => return Err(ParseError::Unimplemented(f.to_string())),
356
357 "fullblock" => i.fullblock = true,
359 "count_bytes" => i.count_bytes = true,
360 "skip_bytes" => i.skip_bytes = true,
361 "append" | "seek_bytes" => {}
363 _ => return Err(ParseError::FlagNoMatch(f.to_string())),
364 }
365 }
366 Ok(())
367 }
368
369 #[allow(clippy::cognitive_complexity)]
370 fn parse_output_flags(&mut self, val: &str) -> Result<(), ParseError> {
371 let o = &mut self.oflag;
372 for f in val.split(',') {
373 match f {
374 "cio" => return Err(ParseError::Unimplemented(val.to_string())),
376 "direct" => linux_only!(f, o.direct = true),
377 "directory" => linux_only!(f, o.directory = true),
378 "dsync" => linux_only!(f, o.dsync = true),
379 "sync" => linux_only!(f, o.sync = true),
380 "nocache" => linux_only!(f, o.nocache = true),
381 "nonblock" => linux_only!(f, o.nonblock = true),
382 "noatime" => linux_only!(f, o.noatime = true),
383 "noctty" => linux_only!(f, o.noctty = true),
384 "nofollow" => linux_only!(f, o.nofollow = true),
385 "nolinks" => return Err(ParseError::Unimplemented(f.to_string())),
386 "binary" => return Err(ParseError::Unimplemented(f.to_string())),
387 "text" => return Err(ParseError::Unimplemented(f.to_string())),
388
389 "append" => o.append = true,
391 "seek_bytes" => o.seek_bytes = true,
392 "fullblock" | "count_bytes" | "skip_bytes" => {}
394 _ => return Err(ParseError::FlagNoMatch(f.to_string())),
395 }
396 }
397 Ok(())
398 }
399
400 fn parse_conv_flags(&mut self, val: &str) -> Result<(), ParseError> {
401 let c = &mut self.conv;
402 for f in val.split(',') {
403 match f {
404 "ascii" => c.ascii = true,
406 "ebcdic" => c.ebcdic = true,
407 "ibm" => c.ibm = true,
408
409 "lcase" => c.lcase = true,
411 "ucase" => c.ucase = true,
412
413 "block" => c.block = true,
415 "unblock" => c.unblock = true,
416
417 "swab" => c.swab = true,
419 "sync" => c.sync = true,
420 "noerror" => c.noerror = true,
421
422 "sparse" => c.sparse = true,
424 "excl" => c.excl = true,
425 "nocreat" => c.nocreat = true,
426 "notrunc" => c.notrunc = true,
427 "fdatasync" => c.fdatasync = true,
428 "fsync" => c.fsync = true,
429 _ => return Err(ParseError::ConvFlagNoMatch(f.to_string())),
430 }
431 }
432 Ok(())
433 }
434}
435
436impl UError for ParseError {
437 fn code(&self) -> i32 {
438 1
439 }
440}
441
442fn show_zero_multiplier_warning() {
443 show_warning!(
444 "{}",
445 translate!("dd-warning-zero-multiplier", "zero" => "0x".quote(), "alternative" => "00x".quote())
446 );
447}
448
449fn parse_bytes_only(s: &str, i: usize) -> Result<u64, ParseError> {
451 s[..i]
452 .parse()
453 .map_err(|_| ParseError::MultiplierStringParseFailure(s.to_string()))
454}
455
456fn parse_bytes_no_x(full: &str, s: &str) -> Result<u64, ParseError> {
484 let parser = SizeParser {
485 capital_b_bytes: true,
486 no_empty_numeric: true,
487 ..Default::default()
488 };
489 let (num, multiplier) = match (s.find('c'), s.rfind('w'), s.rfind('b')) {
490 (None, None, None) => match parser.parse_u64(s) {
491 Ok(n) => (n, 1),
492 Err(ParseSizeError::SizeTooBig(_)) => (u64::MAX, 1),
493 Err(_) => return Err(ParseError::InvalidNumber(full.to_string())),
494 },
495 (Some(i), None, None) => (parse_bytes_only(s, i)?, 1),
496 (None, Some(i), None) => (parse_bytes_only(s, i)?, 2),
497 (None, None, Some(i)) => (parse_bytes_only(s, i)?, 512),
498 _ => return Err(ParseError::MultiplierStringParseFailure(full.to_string())),
499 };
500 num.checked_mul(multiplier)
501 .ok_or_else(|| ParseError::MultiplierStringOverflow(full.to_string()))
502}
503
504pub fn parse_bytes_with_opt_multiplier(s: &str) -> Result<u64, ParseError> {
508 let parts: Vec<&str> = s.split('x').collect();
523 if parts.len() == 1 {
524 parse_bytes_no_x(s, parts[0])
525 } else {
526 let mut total: u64 = 1;
527 for part in parts {
528 if part == "0" {
529 show_zero_multiplier_warning();
530 }
531 let num = parse_bytes_no_x(s, part)?;
532 total = total
533 .checked_mul(num)
534 .ok_or_else(|| ParseError::InvalidNumber(s.to_string()))?;
535 }
536 Ok(total)
537 }
538}
539
540fn get_ctable(
541 conversion: Option<Conversion>,
542 case: Option<Case>,
543) -> Option<&'static ConversionTable> {
544 #[allow(clippy::wildcard_imports)]
545 use crate::conversion_tables::*;
546
547 Some(match (conversion, case) {
548 (None, None) => return None,
549 (Some(conv), None) => match conv {
550 Conversion::Ascii => &EBCDIC_TO_ASCII,
551 Conversion::Ebcdic => &ASCII_TO_EBCDIC,
552 Conversion::Ibm => &ASCII_TO_IBM,
553 },
554 (None, Some(case)) => match case {
555 Case::Lower => &ASCII_UCASE_TO_LCASE,
556 Case::Upper => &ASCII_LCASE_TO_UCASE,
557 },
558 (Some(conv), Some(case)) => match (conv, case) {
559 (Conversion::Ascii, Case::Upper) => &EBCDIC_TO_ASCII_LCASE_TO_UCASE,
560 (Conversion::Ascii, Case::Lower) => &EBCDIC_TO_ASCII_UCASE_TO_LCASE,
561 (Conversion::Ebcdic, Case::Upper) => &ASCII_TO_EBCDIC_LCASE_TO_UCASE,
562 (Conversion::Ebcdic, Case::Lower) => &ASCII_TO_EBCDIC_UCASE_TO_LCASE,
563 (Conversion::Ibm, Case::Upper) => &ASCII_TO_IBM_UCASE_TO_LCASE,
564 (Conversion::Ibm, Case::Lower) => &ASCII_TO_IBM_LCASE_TO_UCASE,
565 },
566 })
567}
568
569fn conversion_mode(
578 ctable: Option<&'static ConversionTable>,
579 block: Option<Block>,
580 is_ascii: bool,
581 is_sync: bool,
582) -> Option<ConversionMode> {
583 match (ctable, block) {
584 (Some(ct), None) => Some(ConversionMode::ConvertOnly(ct)),
585 (Some(ct), Some(Block::Block(cbs))) => {
586 if is_ascii {
587 Some(ConversionMode::ConvertThenBlock(ct, cbs, is_sync))
588 } else {
589 Some(ConversionMode::BlockThenConvert(ct, cbs, is_sync))
590 }
591 }
592 (Some(ct), Some(Block::Unblock(cbs))) => {
593 if is_ascii {
594 Some(ConversionMode::ConvertThenUnblock(ct, cbs))
595 } else {
596 Some(ConversionMode::UnblockThenConvert(ct, cbs))
597 }
598 }
599 (None, Some(Block::Block(cbs))) => Some(ConversionMode::BlockOnly(cbs, is_sync)),
600 (None, Some(Block::Unblock(cbs))) => Some(ConversionMode::UnblockOnly(cbs)),
601 (None, None) => None,
602 }
603}
604
605#[cfg(test)]
606mod tests {
607
608 use crate::Num;
609 use crate::parseargs::{Parser, parse_bytes_with_opt_multiplier};
610 use std::matches;
611 const BIG: &str = "9999999999999999999999999999999999999999999999999999999999999";
612
613 #[test]
614 fn test_parse_bytes_with_opt_multiplier_invalid() {
615 assert!(parse_bytes_with_opt_multiplier("123asdf").is_err());
616 }
617
618 #[test]
619 fn test_parse_bytes_with_opt_multiplier_without_x() {
620 assert_eq!(parse_bytes_with_opt_multiplier("123").unwrap(), 123);
621 assert_eq!(parse_bytes_with_opt_multiplier("123c").unwrap(), 123); assert_eq!(parse_bytes_with_opt_multiplier("123w").unwrap(), 123 * 2);
623 assert_eq!(parse_bytes_with_opt_multiplier("123b").unwrap(), 123 * 512);
624 assert_eq!(parse_bytes_with_opt_multiplier("123k").unwrap(), 123 * 1024);
625 assert_eq!(parse_bytes_with_opt_multiplier(BIG).unwrap(), u64::MAX);
626 }
627
628 #[test]
629 fn test_parse_bytes_with_opt_multiplier_with_x() {
630 assert_eq!(parse_bytes_with_opt_multiplier("123x3").unwrap(), 123 * 3);
631 assert_eq!(parse_bytes_with_opt_multiplier("1x2x3").unwrap(), 6); assert_eq!(
633 parse_bytes_with_opt_multiplier("1wx2cx3w").unwrap(),
634 2 * 2 * (3 * 2) );
636 }
637 #[test]
638 fn test_parse_n() {
639 for arg in ["1x8x4", "1c", "123b", "123w"] {
640 assert!(matches!(Parser::parse_n(arg), Ok(Num::Blocks(_))));
641 }
642 for arg in ["1Bx8x4", "2Bx8", "2Bx8B", "2x8B"] {
643 assert!(matches!(Parser::parse_n(arg), Ok(Num::Bytes(_))));
644 }
645 }
646}