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