zfs_cmd_api/
lib.rs

1#![warn(rust_2018_idioms)]
2#[macro_use] extern crate log;
3
4use failure_derive::Fail;
5use enumflags2::BitFlags;
6use std::ops::{Deref,DerefMut};
7use std::env;
8use std::ffi::OsStr;
9use std::process;
10use std::{io,fmt};
11
12mod zpool;
13
14#[derive(Debug,PartialEq,Eq,Clone)]
15pub struct Zfs {
16    // FIXME: we require utf-8 here
17    zfs_cmd: Vec<String>,
18}
19
20#[derive(Debug,PartialEq,Eq,Clone)]
21pub enum ListTypes {
22    Filesystem,
23    Snapshot,
24    Volume,
25    Bookmark,
26}
27
28#[derive(Debug)]
29pub struct CmdInfo {
30    status: process::ExitStatus,
31    stderr: String,
32    cmd: String,
33}
34
35
36#[derive(Debug,Fail)]
37pub enum ZfsError {
38    #[fail(display = "execution of zfs command failed: {}", io)]
39    Exec {
40        io: io::Error
41    },
42
43    #[fail(display = "zfs command returned an error: {:?}", cmd_info)]
44    Process {
45        cmd_info: CmdInfo,
46    },
47
48    // A specific CannotOpen kind
49    #[fail(display = "no such dataset '{}' ({:?})", dataset, cmd_info)]
50    NoDataset {
51        dataset: String,
52        cmd_info: CmdInfo,
53    },
54
55    #[fail(display = "cannot open: {:?}", cmd_info)]
56    CannotOpen {
57        cmd_info: CmdInfo,
58    },
59
60    #[fail(display = "cannot resume send of nonexistent dataset '{}' ({:?})", dataset, cmd_info)]
61    CannotResumeSendDoesNotExist {
62        dataset: String,
63        cmd_info: CmdInfo,
64    },
65
66    #[fail(display = "cannot resume send: {:?}", cmd_info)]
67    CannotResumeSend {
68        cmd_info: CmdInfo,
69    },
70
71    #[fail(display = "cannot recv: failed to read stream ({:?})", cmd_info)]
72    CannotRecvFailedToRead {
73        cmd_info: CmdInfo,
74    },
75
76    #[fail(display = "cannot recv new fs: {:?}", cmd_info)]
77    CannotRecvNewFs {
78        cmd_info: CmdInfo
79    }
80}
81
82#[derive(Debug,PartialEq,Eq,Clone)]
83pub struct ZfsList {
84    out: Vec<u8>,
85}
86
87impl fmt::Display for ZfsList {
88    fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result
89    {
90        write!(fmt, "[")?;
91        for i in self.iter() {
92            write!(fmt, "{},", fmt_extra::AsciiStr(i))?;
93        }
94        write!(fmt, "]")
95    }
96}
97
98impl Default for ZfsList {
99    fn default() -> Self {
100        ZfsList { out: Vec::new() }
101    }
102}
103
104impl ZfsList {
105    pub fn iter(&self) -> impl Iterator<Item=&[u8]>
106    {
107        self.out.split(|&x| x ==  b'\n').filter(|x| x.len() != 0)
108    }
109}
110
111impl<'a> From<&'a ZfsList> for Vec<Vec<String>> {
112    fn from(x: &'a ZfsList) -> Self {
113        let mut h = Vec::default();
114
115        for i in x.iter() {
116            // collect `i` into a Vec<Vec<u8>>
117            let mut vs = Vec::default();
118            let mut v = Vec::default();
119
120            for b in i {
121                if *b == b'\t'  {
122                    vs.push(String::from_utf8(v).unwrap());
123                    v = Vec::default();
124                } else {
125                    v.push(*b);
126                }
127            }
128
129            vs.push(String::from_utf8(v).unwrap());
130            h.push(vs);
131        }
132
133        h
134    }
135}
136
137#[derive(Debug,Default,PartialEq,Eq,Clone)]
138struct TypeSpec {
139    include_fs: bool,
140    include_snap: bool,
141    include_vol: bool,
142    include_bookmark: bool,
143}
144
145impl<'a> From<&'a TypeSpec> for String {
146    fn from(t: &'a TypeSpec) -> Self {
147        let mut v = vec![];
148        if t.include_fs {
149            v.push("filesystem")
150        }
151        if t.include_snap {
152            v.push("snapshot")
153        }
154        if t.include_vol {
155            v.push("volume")
156        }
157        if t.include_bookmark {
158            v.push("bookmark")
159        }
160
161        v.join(",")
162    }
163}
164
165#[derive(Debug,PartialEq,Eq,Clone)]
166enum ListRecurse {
167    No,
168    Depth(usize),
169    Yes,
170}
171
172impl Default for ListRecurse {
173    fn default() -> Self {
174        ListRecurse::No
175    }
176}
177
178/// Note: no support for sorting, folks can do that in rust if they really want it.
179#[derive(Debug,PartialEq,Eq,Clone,Default)]
180pub struct ListBuilder {
181    recursive: ListRecurse,
182    dataset_types: Option<TypeSpec>,
183    elements: Vec<&'static str>,
184    base_dataset: Option<String>
185}
186
187impl ListBuilder {
188    pub fn depth(&mut self, levels: usize) -> &mut Self {
189        self.recursive = ListRecurse::Depth(levels);
190        self
191    }
192
193    pub fn recursive(&mut self) -> &mut Self {
194        self.recursive = ListRecurse::Yes;
195        self
196    }
197
198    pub fn include_filesystems(&mut self) -> &mut Self {
199        self.dataset_types.get_or_insert(TypeSpec::default()).include_fs = true;
200        self
201    }
202
203    pub fn include_snapshots(&mut self) -> &mut Self {
204        self.dataset_types.get_or_insert(TypeSpec::default()).include_snap = true;
205        self
206    }
207
208    pub fn include_bookmarks(&mut self) -> &mut Self {
209        self.dataset_types.get_or_insert(TypeSpec::default()).include_bookmark = true;
210        self
211    }
212
213    pub fn include_volumes(&mut self) -> &mut Self {
214        self.dataset_types.get_or_insert(TypeSpec::default()).include_vol = true;
215        self
216    }
217
218    pub fn with_elements(&mut self, mut elements: &[&'static str]) -> &mut Self {
219        self.elements.extend_from_slice(&mut elements);
220        self
221    }
222
223    pub fn with_dataset<T: Into<String>>(&mut self, dataset: T) -> &mut Self {
224        self.base_dataset = Some(dataset.into());
225        self
226    }
227}
228
229pub struct ListExecutor<'a> {
230    parent: &'a Zfs,
231    builder: ListBuilder,
232}
233
234impl<'a> ListExecutor<'a> {
235    fn from_parent(zfs: &'a Zfs) -> Self {
236        ListExecutor {
237            parent: zfs,
238            builder: Default::default()
239        }
240    }
241
242    pub fn query(&self) -> Result<ZfsList, ZfsError> {
243        self.parent.list_from_builder(self)
244    }
245}
246
247impl<'a> Deref for ListExecutor<'a> {
248    type Target = ListBuilder;
249    fn deref(&self) -> &ListBuilder {
250        &self.builder
251    }
252}
253
254impl<'a> DerefMut for ListExecutor<'a> {
255    fn deref_mut(&mut self) -> &mut ListBuilder {
256        &mut self.builder
257    }
258}
259
260fn cmdinfo_to_error(cmd_info: CmdInfo) -> ZfsError
261{
262    // status: ExitStatus(ExitStatus(256)), stderr: "cannot open \'innerpool/TMP/zoop-test-28239/dst/sub_ds\': dataset does not exist\n"
263    let prefix_ca = "cannot open '";
264    if cmd_info.stderr.starts_with(prefix_ca) {
265        let ds_rest = &cmd_info.stderr[prefix_ca.len()..].to_owned();
266        let mut s = ds_rest.split("': ");
267        let ds = s.next().unwrap();
268        let error = s.next().unwrap();
269        return match error {
270            "dataset does not exist\n" => {
271                ZfsError::NoDataset {
272                    dataset: ds.to_owned(),
273                    cmd_info: cmd_info,
274                }
275            },
276            _ => {
277                ZfsError::CannotOpen {
278                    cmd_info: cmd_info,
279                }
280            }
281        };
282    }
283
284    // Resuming partial recv in tank/backup/zxfer/franklin/franklin/ROOT/arch
285    // cannot resume send: 'franklin/ROOT/arch@znap_2019-10-28-1630_frequent' used in the initial send no longer exists
286    // cannot receive: failed to read from stream
287    // thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: Custom { kind: Other, error: "send or recv failed: Some(255), Some(1)" }>
288    // note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace.
289    let prefix_crs = "cannot resume send: '";
290    if cmd_info.stderr.starts_with(prefix_crs) {
291        let ds_rest = &cmd_info.stderr[prefix_crs.len()..];
292        let mut s = ds_rest.split("' ");
293        let ds = s.next().unwrap().to_owned();
294        let error = s.next().unwrap();
295        return match error {
296            "used in the initial send no longer exists\n" => {
297                ZfsError::CannotResumeSendDoesNotExist {
298                    dataset: ds.to_owned(),
299                    cmd_info: cmd_info,
300                }
301            },
302            _ => {
303                ZfsError::CannotResumeSend {
304                    cmd_info: cmd_info,
305                }
306            }
307        };
308    }
309
310    // run: "zfs" "send" "-eLcw" "mainrust/ROOT@znap_2019-10-01-0446_monthly"
311    // run: "zfs" "recv" "-Fs" "tank/backup/zoop/arnold2/mainrust/ROOT"
312    // cannot receive new filesystem stream: destination has snapshots (eg. tank/backup/zoop/arnold2/mainrust/ROOT@znap_2019-08-23-2348_frequent)
313    // must destroy them to overwrite it
314    // thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: Custom { kind: Other, error: "send or recv failed: Some(0), Some(1)" }', src/libcore/result.rs:1084:5
315    // note: run with `RUST_BACKTRACE=1` environment variable to display a backtra
316    let prefix_crnfs = "cannot receive new filesystem stream: ";
317    if cmd_info.stderr.starts_with(prefix_crnfs) {
318        return ZfsError::CannotRecvNewFs {
319            cmd_info: cmd_info,
320        };
321    }
322
323    //   sending mainrust/ROOT@znap_2019-09-01-0631_monthly
324    //  run: "zfs" "send" "-eLcw" "-i" "mainrust/ROOT@znap_2019-11-22-0334_frequent" "mainrust/ROOT@znap_2019-09-01-0631_monthly"
325    //  run: "zfs" "recv" "-Fs" "tank/backup/zoop/arnold2/mainrust/ROOT"
326    //  warning: cannot send 'mainrust/ROOT@znap_2019-09-01-0631_monthly': not an earlier snapshot from the same fs
327    //  cannot receive: failed to read from stream
328    //  thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: Custom { kind: Other, error: "send or recv failed: Some(1), Some(1)" }', src/libcore/result.rs:1084:5
329    //  note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace.
330
331
332//  zcopy: mainrust/ROOT/gentoo to tank/backup/zoop/arnold2/mainrust/ROOT/gentoo
333//   new filesystem (no basis)
334//   sending mainrust/ROOT/gentoo@znap_2019-09-01-0631_monthly
335//  run: "zfs" "send" "-eLcw" "mainrust/ROOT/gentoo@znap_2019-09-01-0631_monthly"
336//  run: "zfs" "recv" "-Fs" "tank/backup/zoop/arnold2/mainrust/ROOT/gentoo"
337//  umount: /tank/backup/zoop/arnold2/mainrust/ROOT/gentoo/var: no mount point specified.
338//  cannot unmount '/tank/backup/zoop/arnold2/mainrust/ROOT/gentoo/var': umount failed
339//  thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: Custom { kind: Other, error: "send or recv failed: Some(0), Some(1)" }', src/libcore/result.rs:1084:5
340//  note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace.
341
342    match cmd_info.stderr.as_ref() {
343        "cannot receive: failed to read from stream\n" => {
344            ZfsError::CannotRecvFailedToRead {
345                cmd_info: cmd_info,
346            }
347        },
348        _ => {
349            // generic error
350            ZfsError::Process {
351                cmd_info: cmd_info,
352            }
353        }
354    }
355}
356
357
358impl Zfs {
359
360    fn cmd(&self) -> process::Command {
361        let mut cmd = process::Command::new(&self.zfs_cmd[0]);
362        cmd.args(&self.zfs_cmd[1..]);
363        cmd
364    }
365
366    fn run_output(&self, mut cmd: process::Command) -> Result<std::process::Output, ZfsError> {
367        info!("run: {:?}", cmd);
368
369        let output = cmd.output().map_err(|e| ZfsError::Exec{ io: e})?;
370
371        if !output.status.success() {
372            let stderr = String::from_utf8_lossy(&output.stderr[..]).into_owned();
373
374            let cmd_info = CmdInfo {
375                status: output.status,
376                stderr: stderr,
377                cmd: format!("{:?}", cmd),
378            };
379
380            return Err(cmdinfo_to_error(cmd_info))
381        }
382
383        if output.stderr.len() > 0 {
384            warn!("stderr: {}", String::from_utf8_lossy(&output.stderr));
385        }
386
387       Ok(output)
388    }
389
390    pub fn list_from_builder(&self, builder: &ListBuilder) -> Result<ZfsList, ZfsError> {
391        // zfs list -H
392        // '-s <prop>' sort by property (multiple allowed)
393        // '-d <depth>' recurse to depth
394        // '-r' 
395        let mut cmd = self.cmd();
396
397        cmd
398            .arg("list")
399            // +parsable, +scripting mode
400            .arg("-pH")
401            // sorting by name is faster.
402            // TODO: find out why
403            .arg("-s").arg("name")
404            ;
405
406        if builder.elements.len() == 0 {
407            cmd
408                // only name
409                .arg("-o").arg("name")
410                ;
411        } else {
412            let mut elem_arg = String::new();
413            for e in builder.elements.iter() {
414                elem_arg.push_str(e);
415                elem_arg.push(',');
416            }
417
418            cmd.arg("-o").arg(elem_arg);
419        }
420
421        match builder.recursive {
422            ListRecurse::No => {},
423            ListRecurse::Depth(sz) => {
424                cmd.arg("-d").arg(format!("{}",sz));
425            },
426            ListRecurse::Yes => {
427                cmd.arg("-r");
428            }
429        }
430
431        match &builder.dataset_types {
432            &None => {
433                // TODO: should we require this?
434            },
435            &Some(ref v) => {
436                cmd.arg("-t").arg(String::from(v));
437            }
438        }
439
440        match builder.base_dataset {
441            None => {},
442            Some(ref v) => {
443                cmd.arg(v);
444            }
445        }
446
447        let output = self.run_output(cmd)?;
448
449        Ok(ZfsList { out: output.stdout })
450    }
451
452    pub fn list_basic(&self) -> Result<ZfsList, ZfsError>
453    {
454        self.list().query()
455    }
456
457    pub fn list(&self) -> ListExecutor<'_> {
458        ListExecutor::from_parent(self)
459    }
460
461    /// NOTE: manual documents that if `dataset` is a bookmark, no flags are permitted
462    pub fn destroy(&self, flags: BitFlags<DestroyFlags>, dataset: &str) -> Result<std::process::Output, ZfsError> {
463        let mut cmd = self.cmd();
464        cmd.arg("destroy");
465
466        if !flags.is_empty() {
467            let mut opts = "-".to_owned();
468            for flag in flags.iter() {
469                opts.push(match flag {
470                    DestroyFlags::RecursiveDependents => 'R',
471                    DestroyFlags::ForceUmount => 'f',
472                    DestroyFlags::DryRun => 'n',
473                    DestroyFlags::MachineParsable => 'p',
474                    DestroyFlags::RecursiveChildren => 'r',
475                    DestroyFlags::Verbose => 'v',
476                });
477            }
478
479            cmd.arg(opts);
480        }
481        cmd.arg(dataset);
482        self.run_output(cmd)
483    }
484
485    // delete
486    //
487    // hold
488    // release
489    //
490    // create
491    //
492    // send
493    // recv
494    //
495    // get (for resume)
496
497    pub fn from_env_prefix(prefix: &'static str) -> Self {
498        // TODO: consider failing if {}_ZFS_CMD is not a valid OsStr
499        // TODO: parse this into a series of values
500
501        let env_name = format!("{}_ZFS_CMD", prefix);
502        let env_specific = env::var_os(env_name);
503        let env = match env_specific {
504            Some(x) => x,
505            None => env::var_os("ZFS_CMD").unwrap_or(OsStr::new("zfs").to_owned()),
506        };
507
508        let env = env.to_str().expect("env is not utf-8");
509
510        let zfs_cmd = shell_words::split(env).expect("failed to split words");
511
512        Zfs {
513            zfs_cmd: zfs_cmd,
514        }
515    }
516
517    /// Resume sending a stream using `receive_resume_token` from the destination filesystem
518    ///
519    /// flags here is constrained to `[Penv]`
520    pub fn send_resume(&self, receive_resume_token: &str, flags: BitFlags<SendFlags>) -> io::Result<ZfsSend>
521    {
522        let mut cmd = self.cmd();
523
524        cmd.arg("send");
525
526        if !flags.is_empty() {
527            let mut opts = "-".to_owned();
528
529            // forbidden flags:
530            //  - `replicate`: `-R`
531            //  - `props`: `-p`
532            //  - `backup`: `-b`
533            //  - `dedup`: `-D`
534            //  - `holds`: `-h`
535            //  - `redactbook`: `-d` `arg`
536
537            for flag in flags.iter() {
538                match flag {
539                    SendFlags::LargeBlock => { opts.push('L') },
540                    SendFlags::EmbedData => { opts.push('e') },
541                    SendFlags::Compressed => { opts.push('c') },
542                    SendFlags::Raw => { opts.push('w') },
543
544                    SendFlags::Verbose => { opts.push('v') },
545                    SendFlags::DryRun => { opts.push('n') },
546                    SendFlags::Parsable => { opts.push('P') },
547                    _ => { panic!("unsupported flag: {:?}", flag); }
548                }
549            }
550            cmd.arg(opts);
551        }
552
553        cmd.arg("-t").arg(receive_resume_token);
554
555        info!("run: {:?}", cmd);
556
557        Ok(ZfsSend {
558            child: cmd
559                .stdout(std::process::Stdio::piped())
560                .spawn()?
561        })
562    }
563
564    pub fn recv_abort_incomplete(&self, dataset: &str) -> Result<(), ZfsError> {
565        let mut cmd = self.cmd();
566
567        cmd.arg("recv")
568            .arg("-A")
569            .arg(dataset);
570
571        self.run_output(cmd)?;
572        Ok(())
573    }
574
575    pub fn send(&self, snapname: &str, from: Option<&str>, flags: BitFlags<SendFlags>) -> io::Result<ZfsSend>
576    {
577        let mut cmd = self.cmd();
578
579        cmd.arg("send");
580
581        let mut include_intermediary = false;
582        if !flags.is_empty() {
583            let mut opts = "-".to_owned();
584            // realistically, a series of `if flags.contains(*) {*}` statements more susinctly
585            // represents the work needed to be done here. Unfortunately, it isn't clear how to form
586            // that in a way that ensures we have handling for all `SendFlags`.
587            for flag in flags.iter() {
588                match flag {
589                    SendFlags::EmbedData => { opts.push('e') },
590                    SendFlags::LargeBlock => { opts.push('L') },
591                    SendFlags::Compressed => { opts.push('c') },
592                    SendFlags::Raw => { opts.push('w') },
593
594                    SendFlags::Dedup => { opts.push('D') },
595
596                    SendFlags::IncludeIntermediary => {
597                        include_intermediary = true
598                    },
599                    SendFlags::IncludeHolds => { opts.push('h') },
600                    SendFlags::IncludeProps => { opts.push('p') },
601                    SendFlags::Verbose => { opts.push('v') },
602                    SendFlags::DryRun => { opts.push('n') },
603                    SendFlags::Parsable => { opts.push('P') },
604                    SendFlags::Replicate => { opts.push('R') },
605                }
606            }
607
608            cmd.arg(opts);
609        }
610
611        match from {
612            Some(f) => {
613                if include_intermediary {
614                    cmd.arg("-I")
615                } else {
616                    cmd.arg("-i")
617                }.arg(f);
618            }
619            None => {
620                if include_intermediary {
621                    panic!("include_intermediary set to no effect because no `from` was specified");
622                }
623            }
624        }
625
626        cmd.arg(snapname);
627
628        info!("run: {:?}", cmd);
629
630        Ok(ZfsSend {
631            child: cmd
632                .stdout(std::process::Stdio::piped())
633                .spawn()?
634        })
635    }
636
637    // XXX: `set_props` would ideally take an iterator over things that are &str like
638    // 
639    // note: `lzc_receive()` uses bools for `force` and `raw`, and has no other flags. It then has
640    // a seperate `lzc_receive_resumable()` function for resumable (which internally passes another
641    // boolean), `lzc_receive_with_reader()` then exposes an additional `resumable` boolean (but
642    // also provides a mechanism to pass in a `dmu_replay_record_t` which was read from the `fd`
643    // prior to function invocation).
644    pub fn recv(&self, snapname: &str, set_props: &[(&str, &str)], origin: Option<&str>,
645        exclude_props: &[&str], flags: BitFlags<RecvFlags>) ->
646        io::Result<ZfsRecv>
647    {
648        let mut cmd = self.cmd();
649
650        cmd.arg("recv");
651
652        if !flags.is_empty() {
653            let mut opts = "-".to_owned();
654
655            for flag in flags.iter() {
656                match flag {
657                    RecvFlags::Force => opts.push('F'),
658                    RecvFlags::Resumeable => opts.push('s'),
659
660                    RecvFlags::DiscardFirstName => opts.push('d'),
661                    RecvFlags::DiscardAllButLastName => opts.push('e'),
662                    RecvFlags::IgnoreHolds => opts.push('h'),
663                    RecvFlags::DryRun => opts.push('n'),
664                    RecvFlags::NoMount => opts.push('u'),
665                    RecvFlags::Verbose => opts.push('v'),
666                }
667            }
668
669            cmd
670                .arg(opts);
671        }
672
673        for set_prop in set_props.into_iter() {
674            let mut s = String::new();
675            s.push_str(set_prop.0.as_ref());
676            s.push('=');
677            s.push_str(set_prop.1.as_ref());
678            cmd.arg("-o").arg(s);
679        }
680
681        for exclude_prop in exclude_props.into_iter() {
682            cmd.arg("-x").arg(exclude_prop);
683        }
684
685        match origin {
686            Some(o) => { cmd.arg("-o").arg(o); },
687            None => {},
688        }
689
690        cmd.arg(snapname);
691        info!("run: {:?}", cmd);
692
693        Ok(ZfsRecv {
694            child: cmd
695                .stdin(std::process::Stdio::piped())
696                .spawn()?,
697        })
698    }
699}
700
701pub struct ZfsSend {
702    // note: in the lzc case, this is just a `fd`
703    child: std::process::Child,
704}
705
706pub struct ZfsRecv {
707    // note: in the lzc case, this is just a `fd`
708    child: std::process::Child,
709}
710
711pub fn send_recv(mut send: ZfsSend, mut recv: ZfsRecv) -> io::Result<u64>
712{
713    // XXX: It woudl be _really_ nice to be able to consume stderr from both send & recv into our
714    // own data to examine. right now we have to guess about the error cause.
715    let bytes = std::io::copy(send.child.stdout.as_mut().unwrap(), recv.child.stdin.as_mut().unwrap())?;
716
717    // discard the stdin/stdout we left open
718    // (and hope this causes the subprocesses to exit)
719    send.child.stdout.take();
720    recv.child.stdin.take();
721
722    let ss = send.child.wait()?;
723    let rs = recv.child.wait()?;
724
725    if !ss.success() || !rs.success() {
726        return Err(io::Error::new(io::ErrorKind::Other,
727                           format!("send or recv failed: {:?}, {:?}", ss.code(), rs.code())));
728    }
729
730    Ok(bytes)
731}
732
733#[derive(BitFlags,Copy,Clone,Debug,PartialEq,Eq)]
734pub enum RecvFlags {
735    // correspond to `lzc` booleans/functions
736    /// -F
737    Force = 1<<0,
738    /// -s
739    Resumeable = 1<<1,
740
741    // lzc includes a `raw` boolean with no equivelent in the `zfs recv` cmd. It isn't immediately
742    // clear how this gets set by `zfs recv`, but it might be by examining the
743    // `dmu_replay_record_t`.
744    //
745    // Raw,
746
747    // No equive in `lzc`
748    // These appear to essentially be implimented by
749    // examining the `dmu_replay_record_t` and modifying args to `lzc_recieve_with_header()`.
750    /// -d
751    DiscardFirstName = 1<<2,
752    /// -e
753    DiscardAllButLastName = 1<<3,
754
755    // `zfs receive` options with no equive in `lzc`.
756    //
757    // unclear how holds are handled. `zfs send` has a similar mismatch (no flag in `lzc_send()`)
758    /// -h
759    IgnoreHolds = 1<<4,
760    // I really don't know.
761    /// -u
762    NoMount = 1<<5,
763
764
765    /// -v
766    Verbose = 1<<6,
767    /// -n
768    DryRun = 1<<7,
769}
770
771
772#[derive(BitFlags,Copy,Clone,Debug,PartialEq,Eq)]
773pub enum SendFlags {
774    // correspond to lzc SendFlags
775    /// -e
776    EmbedData = 1<<0,
777    /// -L
778    LargeBlock = 1<<1,
779    /// -c
780    Compressed = 1<<2,
781    /// -w
782    Raw = 1<<3,
783
784    // these are additional items corresponding to `zfs send` cmd flags
785    /// -D
786    Dedup = 1<<4,
787    /// -I
788    IncludeIntermediary = 1<<5,
789    /// -h
790    IncludeHolds = 1<<6,
791    /// -p
792    IncludeProps = 1<<7,
793    /// -v
794    Verbose = 1<<8,
795    /// -n
796    DryRun = 1<<9,
797    /// -P
798    Parsable = 1<<10,
799    /// -R
800    Replicate = 1<<11,
801}
802
803#[derive(BitFlags,Copy,Clone,Debug,PartialEq,Eq)]
804pub enum DestroyFlags {
805    RecursiveDependents = 1 << 0,
806    ForceUmount = 1 << 1,
807    DryRun = 1 << 2,
808    MachineParsable = 1 << 3,
809    RecursiveChildren = 1 << 4,
810    Verbose = 1 << 5,
811}
812
813// 
814// send -t <token>
815//  resume send
816// send -D
817//  dedup. depricated
818// send -I <snapshot>
819//  send all intermediary snapshots from <snapshot>
820// send -L
821//  large block
822// send -P
823//  print machine parsable info
824// send -R
825//  replicate (send filesystem and all decendent filesystems up to the named snapshot)
826// send -e
827//  embed (generate a more compact stream)
828// send -c
829//  compress
830// send -w
831//  raw
832// send -h
833//  holds included
834// send -n
835//  dry run
836// send -p
837//  props -- include dataset props in stream
838// send -v
839//  verbose
840// send -i <snapshot>
841//  generate stream from the first <snapshot> [src] to the second <snapshot> [target]
842// 
843
844impl Default for Zfs {
845    fn default() -> Self {
846        Zfs {
847            zfs_cmd: vec![env::var_os("ZFS_CMD").unwrap_or(OsStr::new("zfs").to_owned()).to_str().unwrap().to_owned()],
848        }
849    }
850}