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
use opts::{flags, Error::ConflictingOption, 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 from_opts(stdout: &'a Stdout, o: &Opts) -> Result<Self> {
        use self::flags::{Conv, Out};

        match (o.output_file.clone(), o.output_seek) {
            (Some(ref path), _) if o.oflag(Out::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(Out::APPEND) => Err(Error::from(ConflictingOption("cannot 'append' on stdout"))),

            (None, None) => Ok(Writer::from_stdout(stdout, o.input_block_size, o.cflag(Conv::SPARSE))), 
            (Some(path), None) => {
                let file = open(&path, o.oflag(Out::APPEND), !o.cflag(Conv::NOTRUNC))?;
                Ok(Writer::from_w(file, o.input_block_size, o.cflag(Conv::SPARSE)))
            },
            (Some(path), Some(blocks)) => {
                let mut file = open(&path, o.oflag(Out::APPEND), !o.cflag(Conv::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(Conv::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) }