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 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 #[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 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#[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 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 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 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 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 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 let mut cmd = self.cmd();
396
397 cmd
398 .arg("list")
399 .arg("-pH")
401 .arg("-s").arg("name")
404 ;
405
406 if builder.elements.len() == 0 {
407 cmd
408 .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 },
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 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 pub fn from_env_prefix(prefix: &'static str) -> Self {
498 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 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 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 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 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 child: std::process::Child,
704}
705
706pub struct ZfsRecv {
707 child: std::process::Child,
709}
710
711pub fn send_recv(mut send: ZfsSend, mut recv: ZfsRecv) -> io::Result<u64>
712{
713 let bytes = std::io::copy(send.child.stdout.as_mut().unwrap(), recv.child.stdin.as_mut().unwrap())?;
716
717 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 Force = 1<<0,
738 Resumeable = 1<<1,
740
741 DiscardFirstName = 1<<2,
752 DiscardAllButLastName = 1<<3,
754
755 IgnoreHolds = 1<<4,
760 NoMount = 1<<5,
763
764
765 Verbose = 1<<6,
767 DryRun = 1<<7,
769}
770
771
772#[derive(BitFlags,Copy,Clone,Debug,PartialEq,Eq)]
773pub enum SendFlags {
774 EmbedData = 1<<0,
777 LargeBlock = 1<<1,
779 Compressed = 1<<2,
781 Raw = 1<<3,
783
784 Dedup = 1<<4,
787 IncludeIntermediary = 1<<5,
789 IncludeHolds = 1<<6,
791 IncludeProps = 1<<7,
793 Verbose = 1<<8,
795 DryRun = 1<<9,
797 Parsable = 1<<10,
799 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
813impl 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}