dd-lib 0.1.0

library functions for a clone of the unix coreutil dd
Documentation
use opts::{self, error::Error::ConflictingOption};
use results::{Error, Result};
use std::{
    fs::File,
    io::{BufRead, Read, Seek, SeekFrom, Stdin, StdinLock},
};

/// Readers read blocks from the specified file or [Stdin][std::io::Stdin]
pub struct Reader<'a, R: Read> {
    inner: InnerReader<'a, R>,
    /// internal buffer to implement BufRead
    pub buf: Vec<u8>,
    /// the front of the buffer; beyond here is free space
    pub front: usize,
    /// the remaining bytes to read. None means to continue reading until EOF.
    pub remaining_bytes: Option<usize>,
    /// the mode (`Standard`, `Block`, or `Unblock`) that dd will run in
    pub mode: opts::Mode,
    /// if true, pad every read blocks to self.buf.len() size
    pub sync: bool,
}

#[derive(Debug)]
pub enum InnerReader<'a, R: Read> {
    Stdin(StdinLock<'a>),
    R(R),
}

impl<'a> Reader<'a, File> {
    pub fn new(stdin: &'a Stdin, o: &opts::Opts) -> Result<Self> {
        // let fullblock = opts.iflags.contains(In::FULLBLOCK),
        use opts::flags::In;

        Ok(Reader {
            buf: vec![0; o.input_block_size],
            front: 0,
            mode: o.mode,
            inner: InnerReader::new(stdin, o)?,
            remaining_bytes: match (o.count, o.iflag(In::COUNT_BYTES)) {
                (Some(bytes), true) => Some(bytes),
                (Some(blocks), false) => Some(blocks * o.input_block_size),
                _ => None,
            },
            sync: o.iflag(In::SYNC),
        })
    }
}
impl<'a, R: Read> Reader<'a, R> {
    pub fn basic(inner: InnerReader<'a, R>, block_size: usize) -> Self {
        Reader {
            buf: vec![0; block_size],
            front: 0,
            mode: opts::Mode::Standard,
            inner,
            remaining_bytes: None,
            sync: false,
        }
    }
}

impl<'a, R: Read> Read for Reader<'a, R> {
    #[inline]
    fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> { self.inner.read(buf) }
}

impl<'a, R: Read> BufRead for Reader<'a, R> {
    #[inline]
    fn fill_buf(&mut self) -> std::io::Result<&[u8]> {
        self.front += match self.remaining_bytes {
            Some(0) => return Ok(&self.buf[0..0]),
            None => self.inner.read(&mut self.buf[self.front..])?,
            Some(n) => {
                let back = usize::min(n, self.buf.len());
                self.inner.read(&mut self.buf[self.front..back])?
            },
        };
        if self.sync {
            // Pad every input block to the input buffer size.  Spaces are used for pad
            // bytes if a block oriented conversion value is specified,
            // otherwise NUL bytes are used.
            if self.mode == opts::Mode::Standard {
                self.buf.iter_mut().for_each(|b| *b = 0);
            } else {
                self.buf.iter_mut().for_each(|b| *b = b' ');
            }

            Ok(&self.buf)
        } else {
            Ok(&self.buf[..self.front])
        }
    }

    #[inline]
    fn consume(&mut self, bytes: usize) { self.front -= bytes }
}

impl<'a> InnerReader<'a, File> {
    fn new(stdin: &'a Stdin, o: &opts::Opts) -> Result<Self> {
        use opts::flags::In;
        let ibs = o.input_block_size;
        match &o.input_file {
            None if o.iflag(In::APPEND) => {
                return Err(Error::from(ConflictingOption(
                    "iflag 'append' is only usable with an input file",
                )))
            },
            None if o.input_seek.is_some() || o.iflag(In::SKIP_BYTES) => {
                return Err(Error::from(ConflictingOption(
                    "options 'input_seek' and 'seek_bytes' are only usable with an input file",
                )))
            },

            // valid stdin
            None => Ok(InnerReader::Stdin(stdin.lock())),
            Some(path) => {
                let mut f = File::open(path)?;
                match (o.input_seek, o.iflag(In::SKIP_BYTES)) {
                    (None, true) => {
                        return Err(Error::from(ConflictingOption(
                            "cannot use 'seek_bytes' with no 'input_seek' distance selected",
                        )))
                    },
                    (None, false) => {},
                    (Some(bytes), true) => {
                        f.seek(SeekFrom::Start(bytes as u64))?;
                    },
                    (Some(blocks), false) => {
                        f.seek(SeekFrom::Start(blocks as u64 * ibs as u64))?;
                    },
                };
                Ok(InnerReader::R(f))
            },
        }
    }
}

impl<'a, T: BufRead> From<StdinLock<'a>> for InnerReader<'a, T> {
    fn from(s: StdinLock) -> InnerReader<T> { InnerReader::Stdin(s) }
}

impl<'a> From<File> for InnerReader<'a, File> {
    fn from(f: File) -> InnerReader<'a, File> { InnerReader::R(f) }
}

impl<'a, T: Read> Read for InnerReader<'a, T> {
    fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
        match self {
            InnerReader::R(f) => f.read(buf),
            InnerReader::Stdin(s) => s.read(buf),
        }
    }
}