dd_lib/opts/
mod.rs

1//! Configuration and parsing.
2//!
3//! - bs=n. Set both input and output block size to n bytes, superseding the
4//! ibs and obs operands.  If no conversion values other than noerror, notrunc
5//! or sync are specified, then each input block is copied to the output as a
6//! single block without any aggregation of short blocks.
7//!
8//! - cbs=n Set the conversion record size to n bytes.  The conversion record
9//!   size is required by the record oriented conversion values.
10//!
11//! - conv=value\[,value ...\] Set conversion flags according to the
12//!   comma-separated symbol lsit.
13//!  See [flags/cflag]
14//!
15//! - files=n:   <UNIMPLEMENTED> Copy n input files before terminating.  This
16//!   operand is only   applicable when the input device is a tape.
17//!
18//! - ibs=n:   Set the input block size to n bytes instead of the default 512.
19//!
20//! - if=file:   Read input from file instead of the standard input.
21//!
22//! - iflags=value\[,value ...\] [opts::IFlag] Set input flags according to the
23//!   comma-separated symbol list. See [flags/iflag]
24//!
25//!
26//! - iseek=n:  Seek on the input file n blocks.  This is synonymous with
27//!   skip=n.
28//!
29//!
30//! - obs=n: Set the output block size to n bytes instead of the default 512.
31//!
32//! - of=file Write output to file instead of the standard output.  Any regular
33//!   output file is truncated unless the notrunc conversion value is specified.
34//!   If
35//! an initial portion of the output file is seeked past (see the oseek
36//! operand), the output file is truncated at that point. - oseek=n   Seek on
37//! the output file n blocks.  This is synonymous with seek=n.
38//!
39//! - oflags=value\[,value ...\]:  [flags::Out] Set output flags according to
40//!   the   comma-separated symbol list. See [`flags/oflag`]
41//!
42//! - seek=n    Seek n blocks from the beginning of the output before copying.
43//!   On non-tape
44//! devices, an lseek(2) operation is used.  Otherwise, existing blocks are read
45//! and the data discarded.  If the user does not have read permission for the
46//! tape, it is positioned using the tape ioctl(2) function calls. If the seek
47//! operation is past the end of file, space from the current end of file to the
48//! specified offset is filled with blocks of NUL bytes.
49//!
50//! - skip=n    Skip n blocks from the beginning of the input before copying. If
51//!   the input
52//! doesn't support seeks (i.e, is [Stdin][std::io::stdin])  Otherwise, input
53//! data is read and discarded.  For pipes, the correct number of bytes is read.
54//! For all other devices, the correct number of blocks is read without
55//! distinguishing between a partial or complete block being read.
56//! error types and handling for command line options
57//!
58//! - status=[LEVEL][StatusLevel] The LEVEL of information to   print to stderr
59//!     - none suppresses everything but error messages
60//!     - noxfer suppresses the final transfer statistics,
61//!     - progress shows periodic transfer statistics
62
63
64pub use self::flags::cflag::CFlag;
65
66/// Input (read) flags, specified by the `iflag` option
67pub use self::flags::iflag::IFlag;
68
69/// Output (write) flags, specified by the `oflag` option
70pub use self::flags::oflag::OFlag;
71
72/// errors dealing with handling user options
73pub use self::error::{Error, Result};
74
75pub use self::number::parse;
76// parsing of numbers
77mod number;
78
79mod error;
80/// parsing of input, output, and conversion flags
81mod flags;
82/// conversion of strings to unsized integers & unit conversions
83
84/// input, output, and conversion flags
85
86/// Conversion flags, specified by the `conv` option
87
88#[derive(Clone, Debug, Default)]
89/// The parsed and handled user options.
90pub struct Opts {
91    /// convert the file as per the comma separated symbol list
92    pub cflags: CFlag,
93
94    /// handle the input as per the comma-separated symbol list (see
95    /// [`opts::IFlag`])
96    pub iflags: IFlag,
97    /// handle the output as per the comma-separated symbol list (see
98    /// [`flags::Out`])
99    pub oflags: OFlag,
100    /// The main mode to run in. Can be `Mode::Standard, Mode::Block(usize),
101    /// Mode::Unblock(usize)`
102    pub mode: Mode,
103    /// The limit in bytes or blocks to read
104    pub count: Option<usize>,
105    /// <NOT IMPLEMENTED> Copy n input files before terminating.  This operand
106    /// is only applicable when the input device is a tape.
107    pub files: Option<usize>,
108    /// The block size of the input, in bytes. Default is 512.
109    pub input_block_size: usize,
110    /// The input file, if any. `input_file == None` -> stdin
111    pub input_file: Option<String>,
112    /// How many `input_block_sized` blocks to seek on the input. If also
113    /// `opts::IFlag::SEEK_BYTES`, seek that many _bytes_ instead
114    pub input_seek: Option<usize>,
115    /// The block size of the output, in bytes. Default is 512.
116    pub output_block_size: usize,
117    /// The output file, if any. `output_file == None` -> stdout
118    pub output_file: Option<String>,
119    /// How many `output_block_sized` blocks  to seek on the output file.
120    /// Incompatible with `output_file==None`
121    pub output_seek: Option<usize>,
122
123    pub status: StatusLevel,
124}
125
126#[derive(Clone, Copy, Debug, PartialEq, Eq)]
127/// Enumeration of the four kinds of command line options: BasicOptions,
128/// conversion flags, input flags, output flags
129pub enum Kind {
130    BasicOption,
131    CFlag,
132    IFlag,
133    OFlag,
134}
135
136/// The LEVEL of information to print to stderr
137#[derive(Copy, Clone, PartialEq, Eq, Debug)]
138pub enum StatusLevel {
139    /// default: report only on function completion
140    TransferOnly,
141    /// suppress everything but error messages
142    SuppressAll,
143    /// suppress the final transfer statistics
144    NoTransfer,
145    /// report periodic transfer statistics
146    Progress,
147}
148/// Helper trait for reporting errors
149#[doc(hidden)]
150pub trait Unimplemented {
151    /// Kind of option; used for reporting errors
152    const KIND: Kind;
153    /// Slice of valid but as yet unimplemented options
154    const UNIMPLEMENTED: &'static [&'static str];
155
156    /// check if a flag is valid but unimplemented
157    fn check_if_implemented(flag: &str) -> Result<()> {
158        for s in Self::UNIMPLEMENTED {
159            if flag == *s {
160                return Err(Error::Unimplemented(Self::KIND, *s));
161            }
162        }
163        Ok(())
164    }
165    /// helper function; an invalid flag key
166    fn unknown<T>(flag: String) -> Result<T> { Err(Error::Unknown(Self::KIND, flag)) }
167}
168
169impl Unimplemented for Opts {
170    const KIND: Kind = Kind::BasicOption;
171    const UNIMPLEMENTED: &'static [&'static str] = &["count", "files"];
172}
173
174impl Default for StatusLevel {
175    fn default() -> Self { StatusLevel::TransferOnly }
176}
177
178impl std::str::FromStr for StatusLevel {
179    type Err = Error;
180
181    fn from_str(s: &str) -> Result<Self> {
182        match s {
183            "none" => Ok(StatusLevel::SuppressAll),
184            "noxfer" => Ok(StatusLevel::NoTransfer),
185            "progress" => Ok(StatusLevel::Progress),
186            s => Err(Error::Unknown(Kind::BasicOption, format!("status={}", s))),
187        }
188    }
189}
190
191/// The Mode that DD runs in.
192#[derive(Copy, Clone, Debug, PartialEq, Eq)]
193pub enum Mode {
194    /// Byte for byte IO. This is the default.
195    Standard,
196    /// Fixed-sized records separated by newlines, padded or trunctated to
197    /// `usize`. Specified by input option "block"
198    Block(usize),
199    /// Read fixed numbers of bytes and output separated by newlines, ignoring
200    /// trailing spaces. Specified by input option "unblock"
201    Unblock(usize),
202}
203
204impl Default for Mode {
205    fn default() -> Self { Mode::Standard }
206}
207
208impl Mode {
209    fn new(block: bool, unblock: bool, conversion_record_size: Option<usize>) -> Result<Self> {
210        match (block, unblock, conversion_record_size) {
211            (false, false, None) => Ok(Mode::Standard),
212            (true, false, Some(n)) => Ok(Mode::Block(n)),
213            (false, true, Some(n)) => Ok(Mode::Unblock(n)),
214            (false, false, Some(_)) => Err(Error::ConflictingOption(
215                "a conversion record size option is meaningless if 'block' or 'unblock' are not specified",
216            )),
217            (true, true, _) => Err(Error::ConflictingOption(
218                "can't specify both 'block' and 'unblock' conversion options",
219            )),
220
221            (true, false, None) | (false, true, None) => Err(Error::ConflictingOption(
222                "must specify a conversion record size if 'block' or 'unblock' is selected",
223            )),
224        }
225    }
226}
227
228impl Opts {
229    const DEFAULT_INPUT_BLOCKSIZE: usize = 512;
230    const DEFAULT_OUTPUT_BLOCKSIZE: usize = 512;
231
232    #[allow(unreachable_patterns)]
233    pub fn new<I>(args: I) -> Result<Self>
234    where
235        I: IntoIterator<Item = String>,
236    {
237        let mut block_size = <Option<usize>>::default();
238        let (mut count, mut files) = <(Option<usize>, Option<usize>)>::default(); // unimplemented!
239        let mut cflags = CFlag::default();
240        let mut conv_block_size = None;
241        let (mut iflags, mut oflags) = (IFlag::default(), OFlag::default());
242        let (mut in_block_size, mut out_block_size) = (Self::DEFAULT_INPUT_BLOCKSIZE, Self::DEFAULT_OUTPUT_BLOCKSIZE);
243        let (mut input_file, mut output_file) = <(Option<String>, Option<String>)>::default();
244        let (mut input_seek, mut output_seek) = <(Option<usize>, Option<usize>)>::default();
245        let mut status = StatusLevel::default();
246        for arg in args.into_iter().skip(1) {
247            if let Some((key, val)) = keyval(&arg) {
248                Self::check_if_implemented(key)?;
249                match key {
250                    "conv" => cflags = CFlag::new(val)?,
251                    "iflag" => iflags = IFlag::new(val)?,
252                    "oflag" => oflags = OFlag::new(val)?,
253                    "bs" => block_size = number::parse_opt(val)?,
254                    "cbs" => conv_block_size = number::parse_opt(val)?,
255                    "count" => count = number::parse_opt(val)?, // not yet implemented
256                    "files" => files = number::parse_opt(val)?, // not yet implemented
257                    "ibs" => in_block_size = number::parse(val)?,
258                    "if" => input_file = Some(val.to_string()),
259                    "iseek" | "skip" => input_seek = number::parse_opt(val)?,
260                    "obs" => out_block_size = val.parse()?,
261                    "of" => output_file = Some(val.to_string()),
262                    "os" | "seek" => output_seek = number::parse_opt(val)?,
263                    "status" => status = val.parse()?,
264
265                    _ => return Self::unknown(key.to_owned()),
266                }
267                continue;
268            };
269
270            return Self::unknown(arg);
271        }
272
273        validate_directory(&input_file, iflags.contains(IFlag::DIRECTORY))?;
274        validate_directory(&output_file, oflags.contains(OFlag::DIRECTORY))?;
275        Ok(Opts {
276            mode: Mode::new(
277                cflags.contains(CFlag::BLOCK),
278                cflags.contains(CFlag::UNBLOCK),
279                conv_block_size,
280            )?,
281
282            count,
283            files,
284            status,
285
286            cflags,
287            iflags,
288            oflags,
289
290            input_block_size: block_size.unwrap_or(in_block_size),
291            output_block_size: block_size.unwrap_or(out_block_size),
292
293            input_file,
294            output_file,
295
296            input_seek,
297            output_seek,
298        })
299    }
300
301    /// iflag returns true if the specified iflag is set
302    /// ```
303    /// # use dd_lib::opts;
304    /// # use dd_lib::opts::{Opts, IFlag};
305    /// let mut o = opts::Opts::default();
306    /// o.iflags = IFlag::APPEND | IFlag::SYNC;
307    /// assert!(o.iflag(IFlag::APPEND));
308    /// assert!(!o.iflag(IFlag::NOFOLLOW));
309    /// ```
310    pub fn iflag(&self, iflag: IFlag) -> bool { self.iflags.contains(iflag) }
311
312    /// oflag returns true if the specified conversion flag is set
313    pub fn oflag(&self, oflag: OFlag) -> bool { self.oflags.contains(oflag) }
314
315    /// cflag returns true if the specified conversion flag is set
316    pub fn cflag(&self, cflag: CFlag) -> bool { self.cflags.contains(cflag) }
317}
318
319fn validate_directory(path: &Option<String>, dir_flag: bool) -> Result<()> {
320    match path {
321        Some(path) if !std::path::Path::is_dir(path.as_ref()) && dir_flag => Err(Error::NotDirectory(path.to_string())),
322        _ => Ok(()),
323    }
324}
325
326/// split a string of the form {key}={val} into a two-tuple, if possible.
327/// if it would be larger than a two-tuple, returns None
328/// ```
329/// # use dd_lib::opts::keyval;
330/// assert_eq!(Some(("foo", "bar")), keyval("foo=bar"));
331/// assert!(keyval("asd").is_none());
332/// assert!(keyval("hello=myasndlaksnd=name").is_none());
333/// ```
334pub fn keyval(s: &str) -> Option<(&str, &str)> {
335    let mut split = s.split("=");
336    if let (Some(key), Some(val), None) = (split.next(), split.next(), split.next()) {
337        Some((key, val))
338    } else {
339        None
340    }
341}