dd-lib 0.2.1

library functions for a clone of the unix coreutil dd
Documentation
use opts::{CFlag, Error::ConflictingOption, OFlag, Opts};
use results::{Error, Result};
use std::{
    fs::{File, OpenOptions},
    io::{self, BufWriter, Seek, SeekFrom, Stdout, StdoutLock, Write},
    path::Path,
};
/// Writers write to the specified [File][std::fs::File] or
/// [Stdout][std::io::Stdout]
pub struct Writer<'a, W: Write> {
    w: Inner<'a, W>,
    sparse: bool,
}

enum Inner<'a, W: Write> {
    W(BufWriter<W>),
    Stdout(BufWriter<StdoutLock<'a>>),
}

impl<'a, W: Write + Seek> Write for Writer<'a, W> {
    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
        match (&mut self.w, self.sparse && is_null(buf)) {
            (Inner::W(w), true) => {
                w.seek(SeekFrom::Current(buf.len() as i64))?;
                Ok(buf.len())
            },
            // "skip" ahead by not not writing to stdout at all
            (Inner::Stdout(_), true) => Ok(buf.len()),
            (w, _) => w.write(buf),
        }
    }

    fn flush(&mut self) -> std::io::Result<()> { self.w.flush() }
}

impl<'a, W: Write> Writer<'a, W> {
    fn from_stdout(s: &'a Stdout, cap: usize, sparse: bool) -> Self {
        let w = Inner::Stdout(BufWriter::with_capacity(cap, s.lock()));
        Writer { w, sparse }
    }

    fn from_w(w: W, cap: usize, sparse: bool) -> Self {
        let w = Inner::W(BufWriter::with_capacity(cap, w));
        Writer { w, sparse }
    }
}

impl<'a> Writer<'a, File> {
    pub fn new(stdout: &'a Stdout, o: &Opts) -> Result<Self> {
        match (o.output_file.clone(), o.output_seek) {
            (Some(ref path), _) if o.oflag(OFlag::DIRECTORY) && !Path::is_dir(path.as_ref()) => Err(Error::from(
                ConflictingOption("output flag 'directory' specified, but the path was not a directory"),
            )),
            (None, Some(_)) => Err(Error::from(ConflictingOption("cannot 'seek' on stdout"))),
            (None, None) if o.oflag(OFlag::APPEND) => Err(Error::from(ConflictingOption("cannot 'append' on stdout"))),

            (None, None) => Ok(Writer::from_stdout(stdout, o.input_block_size, o.cflag(CFlag::SPARSE))),
            (Some(path), None) => {
                let file = open(&path, o.oflag(OFlag::APPEND), !o.cflag(CFlag::NOTRUNC))?;
                Ok(Writer::from_w(file, o.input_block_size, o.cflag(CFlag::SPARSE)))
            },
            (Some(path), Some(blocks)) => {
                let mut file = open(&path, o.oflag(OFlag::APPEND), !o.cflag(CFlag::NOTRUNC))?;
                file.seek(SeekFrom::Start(blocks as u64 * o.input_block_size as u64))?;
                Ok(Writer::from_w(file, o.input_block_size, o.cflag(CFlag::SPARSE)))
            },
        }
    }

    #[allow(dead_code)]
    // get a reference to the file inside. this isn't safe for all varieties!
    pub unsafe fn get_file(self) -> File {
        match self.w {
            Inner::W(file) => file.into_inner().unwrap(),
            _ => panic!("shouldn't have called this!"),
        }
    }
}

/// open a file for creation and writing, appending and/or truncating if
/// specified
fn open(path: &str, append: bool, truncate: bool) -> Result<File> {
    let file = OpenOptions::new()
        .create(true)
        .write(true)
        .append(append)
        .truncate(truncate)
        .open(path)?;
    Ok(file)
}

impl<'a, W: Write> Write for Inner<'a, W> {
    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
        match self {
            Inner::W(w) => w.write(buf),
            Inner::Stdout(stdout) => stdout.write(buf),
        }
    }

    fn flush(&mut self) -> io::Result<()> {
        match self {
            Inner::W(w) => w.flush(),
            Inner::Stdout(stdout) => stdout.flush(),
        }
    }
}
/// all bytes are zero
fn is_null(b: &[u8]) -> bool { b.iter().all(|b| *b == 0) }