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}