1mod zfs_finder;
2
3use crate::zfs_finder::pick_zfs_path_for_sudo;
4use std::collections::BTreeMap;
5use std::io::BufWriter;
6use std::io::Read;
7use std::io::Write;
8use std::path::PathBuf;
9use std::process::Command;
10
11#[derive(thiserror::Error, Debug)]
12pub enum ZfsError {
13 #[error("System error: {0}")]
14 SystemError(String),
15 #[error("Dataset {0} not found")]
16 DatasetNotFound(String),
17 #[error(
18 "Command returned unexpected state for key is available, other than 'available' and 'unavailable': {0}"
19 )]
20 UnexpectedStateForKey(String),
21 #[error("Command returned unexpected state for mount, other than 'yes' and 'no': {0}")]
22 UnexpectedStateForMount(String),
23 #[error("Command to check whether dataset {0} is mounted failed: {1}")]
24 IsMountedCheckCallFailed(String, String),
25 #[error("Command to list datasets mount points failed: {0}")]
26 ListDatasetsMountPointsCallFailed(String),
27 #[error("Command to list unmounted datasets failed: {0}")]
28 ListUnmountedDatasetsCallFailed(String),
29 #[error("Command to check whether key for dataset {0} is loaded failed: {1}")]
30 KeyLoadedCheckFailed(String, String),
31 #[error("Load key command for dataset {0} failed: {1}")]
32 LoadKeyCmdFailed(String, String),
33 #[error("Unload key command for dataset {0} failed: {1}")]
34 UnloadKeyCmdFailed(String, String),
35 #[error("Key must be loaded before mount for dataset {0}")]
36 KeyNotLoadedForMount(String),
37 #[error("Mount command for dataset {0} failed: {1}")]
38 MountCmdFailed(String, String),
39 #[error("Unmount command for dataset {0} failed: {1}")]
40 UnmountCmdFailed(String, String),
41 #[error("Dataset name is invalid: {0}")]
42 DatasetNameIsInvalid(String),
43 #[error("Failed to enumerate executable paths for {0}: {1}")]
44 WhichAllFailed(String, String),
45 #[error("Failed to run `sudo -n -l`: {0}")]
46 SudoListFailed(String),
47 #[error("No matching NOPASSWD sudoers rule for: {0}")]
48 SudoersNoMatchingRule(String),
49 #[error("No `zfs` executable found in PATH")]
50 ZfsNotFoundInPath,
51}
52
53#[derive(Debug, Clone, Eq, PartialEq)]
54pub struct DatasetMountedState {
55 pub dataset_name: String,
56 pub is_mounted: bool,
57 pub is_key_loaded: bool,
58}
59
60fn parse_key_available_state(state: impl AsRef<str>) -> Result<bool, ZfsError> {
61 match state.as_ref().trim() {
62 "available" => Ok(true),
63 "unavailable" => Ok(false),
64 _ => Err(ZfsError::UnexpectedStateForKey(state.as_ref().to_string())),
65 }
66}
67
68fn parse_dataset_mounted_state(state: impl AsRef<str>) -> Result<bool, ZfsError> {
69 match state.as_ref().trim() {
70 "yes" => Ok(true),
71 "no" => Ok(false),
72 _ => Err(ZfsError::UnexpectedStateForMount(
73 state.as_ref().to_string(),
74 )),
75 }
76}
77
78fn check_and_sanitize_zfs_dataset_name(zfs_dataset: impl AsRef<str>) -> Result<String, ZfsError> {
81 const ALLOWED_SYMBOLS: [char; 4] = ['-', '_', '.', ':'];
82
83 let dataset = zfs_dataset.as_ref().trim();
84
85 let check_func = |part: &str| {
86 part.chars()
87 .all(|c| c.is_ascii_alphanumeric() || ALLOWED_SYMBOLS.contains(&c))
88 && part.chars().all(|c| !c.is_whitespace())
89 && !part.is_empty()
90 && !part.starts_with(ALLOWED_SYMBOLS) };
92
93 check_func(dataset);
95
96 if !dataset.split('/').all(check_func) {
97 Err(ZfsError::DatasetNameIsInvalid(dataset.to_string()))
98 } else {
99 Ok(dataset.to_string())
100 }
101}
102
103pub fn zfs_load_key(
108 zfs_dataset: impl AsRef<str>,
109 passphrase: impl AsRef<str>,
110) -> Result<(), ZfsError> {
111 let passphrase = passphrase.as_ref();
112 let dataset = check_and_sanitize_zfs_dataset_name(zfs_dataset)?;
113
114 match zfs_is_key_loaded(&dataset)? {
115 Some(loaded) => {
116 if loaded {
117 return Ok(());
118 }
119 }
120 None => return Err(ZfsError::DatasetNotFound(dataset.to_string())),
121 }
122
123 let zfs_exe = pick_zfs_path_for_sudo()?;
124
125 let mut child = Command::new("sudo")
127 .arg("-n") .arg(zfs_exe)
129 .arg("load-key")
130 .arg(&dataset)
131 .stdin(std::process::Stdio::piped())
132 .stdout(std::process::Stdio::piped())
133 .stderr(std::process::Stdio::piped())
134 .spawn()
135 .map_err(|e| ZfsError::LoadKeyCmdFailed(dataset.to_string(), e.to_string()))?;
136
137 if let Some(mut stdin) = child.stdin.take() {
139 let mut writer = BufWriter::new(&mut stdin);
141 writeln!(writer, "{}", passphrase).map_err(|e| ZfsError::SystemError(e.to_string()))?;
142 writer
143 .flush()
144 .map_err(|e| ZfsError::SystemError(e.to_string()))?;
145 }
146
147 let mut stdout = child.stdout.take().expect("Failed to capture stdout");
149 let mut stderr = child.stderr.take().expect("Failed to capture stderr");
150
151 let mut stdout_string = String::new();
153 stdout
154 .read_to_string(&mut stdout_string)
155 .map_err(|e| ZfsError::SystemError(e.to_string()))?;
156 let mut stderr_string = String::new();
157 stderr
158 .read_to_string(&mut stderr_string)
159 .map_err(|e| ZfsError::SystemError(e.to_string()))?;
160
161 let status = child
163 .wait()
164 .map_err(|e| ZfsError::SystemError(e.to_string()))?;
165
166 if status.success() {
168 Ok(())
169 } else {
170 Err(ZfsError::LoadKeyCmdFailed(
171 dataset.to_string(),
172 stderr_string,
173 ))
174 }
175}
176
177pub fn zfs_unload_key(zfs_dataset: impl AsRef<str>) -> Result<(), ZfsError> {
182 let dataset = check_and_sanitize_zfs_dataset_name(zfs_dataset)?;
183
184 match zfs_is_key_loaded(&dataset)? {
185 Some(loaded) => match loaded {
186 true => (),
187 false => return Ok(()),
188 },
189 None => return Err(ZfsError::DatasetNotFound(dataset.to_string())),
190 }
191
192 let zfs_exe = pick_zfs_path_for_sudo()?;
193
194 let mut child = Command::new("sudo")
196 .arg("-n") .arg(zfs_exe)
198 .arg("unload-key")
199 .arg(&dataset)
200 .stdin(std::process::Stdio::piped())
201 .stdout(std::process::Stdio::piped())
202 .stderr(std::process::Stdio::piped())
203 .spawn()
204 .map_err(|e| ZfsError::UnloadKeyCmdFailed(dataset.to_string(), e.to_string()))?;
205
206 let mut stdout = child.stdout.take().expect("Failed to capture stdout");
208 let mut stderr = child.stderr.take().expect("Failed to capture stderr");
209
210 let mut stdout_string = String::new();
212 stdout
213 .read_to_string(&mut stdout_string)
214 .map_err(|e| ZfsError::SystemError(e.to_string()))?;
215 let mut stderr_string = String::new();
216 stderr
217 .read_to_string(&mut stderr_string)
218 .map_err(|e| ZfsError::SystemError(e.to_string()))?;
219
220 let status = child
222 .wait()
223 .map_err(|e| ZfsError::SystemError(e.to_string()))?;
224
225 if status.success() {
227 Ok(())
228 } else {
229 Err(ZfsError::UnloadKeyCmdFailed(
230 dataset.to_string(),
231 stderr_string,
232 ))
233 }
234}
235
236pub fn zfs_mount_dataset(zfs_dataset: impl AsRef<str>) -> Result<(), ZfsError> {
241 let dataset = check_and_sanitize_zfs_dataset_name(zfs_dataset)?;
242
243 match zfs_is_key_loaded(&dataset)? {
244 Some(loaded) => match loaded {
245 true => (),
246 false => return Err(ZfsError::KeyNotLoadedForMount(dataset.to_string())),
247 },
248 None => return Err(ZfsError::DatasetNotFound(dataset.to_string())),
249 }
250
251 match zfs_is_dataset_mounted(&dataset)? {
252 Some(mounted) => {
253 if mounted {
254 return Ok(());
255 }
256 }
257 None => return Err(ZfsError::DatasetNotFound(dataset.to_string())),
258 }
259
260 let zfs_exe = pick_zfs_path_for_sudo()?;
261
262 let mut child = Command::new("sudo")
264 .arg("-n") .arg(zfs_exe)
266 .arg("mount")
267 .arg(&dataset)
268 .stdin(std::process::Stdio::piped())
269 .stdout(std::process::Stdio::piped())
270 .stderr(std::process::Stdio::piped())
271 .spawn()
272 .map_err(|e| ZfsError::MountCmdFailed(dataset.to_string(), e.to_string()))?;
273
274 let mut stdout = child.stdout.take().expect("Failed to capture stdout");
276 let mut stderr = child.stderr.take().expect("Failed to capture stderr");
277
278 let mut stdout_string = String::new();
280 stdout
281 .read_to_string(&mut stdout_string)
282 .map_err(|e| ZfsError::SystemError(e.to_string()))?;
283 let mut stderr_string = String::new();
284 stderr
285 .read_to_string(&mut stderr_string)
286 .map_err(|e| ZfsError::SystemError(e.to_string()))?;
287
288 let status = child
290 .wait()
291 .map_err(|e| ZfsError::SystemError(e.to_string()))?;
292
293 if status.success() {
295 Ok(())
296 } else {
297 Err(ZfsError::MountCmdFailed(dataset.to_string(), stderr_string))
298 }
299}
300
301pub fn zfs_unmount_dataset(zfs_dataset: impl AsRef<str>) -> Result<(), ZfsError> {
306 let dataset = check_and_sanitize_zfs_dataset_name(zfs_dataset)?;
307
308 match zfs_is_dataset_mounted(&dataset)? {
309 Some(mounted) => match mounted {
310 true => (),
311 false => return Ok(()),
312 },
313 None => return Err(ZfsError::DatasetNotFound(dataset.to_string())),
314 }
315
316 let zfs_exe = pick_zfs_path_for_sudo()?;
317
318 let mut child = Command::new("sudo")
320 .arg("-n") .arg(zfs_exe)
322 .arg("umount")
323 .arg(&dataset)
324 .stdin(std::process::Stdio::piped())
325 .stdout(std::process::Stdio::piped())
326 .stderr(std::process::Stdio::piped())
327 .spawn()
328 .map_err(|e| ZfsError::UnmountCmdFailed(dataset.to_string(), e.to_string()))?;
329
330 let mut stdout = child.stdout.take().expect("Failed to capture stdout");
332 let mut stderr = child.stderr.take().expect("Failed to capture stderr");
333
334 let mut stdout_string = String::new();
336 stdout
337 .read_to_string(&mut stdout_string)
338 .map_err(|e| ZfsError::SystemError(e.to_string()))?;
339 let mut stderr_string = String::new();
340 stderr
341 .read_to_string(&mut stderr_string)
342 .map_err(|e| ZfsError::SystemError(e.to_string()))?;
343
344 let status = child
346 .wait()
347 .map_err(|e| ZfsError::SystemError(e.to_string()))?;
348
349 if status.success() {
351 Ok(())
352 } else {
353 Err(ZfsError::UnmountCmdFailed(
354 dataset.to_string(),
355 stderr_string,
356 ))
357 }
358}
359
360pub fn zfs_is_key_loaded(zfs_dataset: impl AsRef<str>) -> Result<Option<bool>, ZfsError> {
366 let dataset = check_and_sanitize_zfs_dataset_name(zfs_dataset)?;
367
368 let zfs_exe = pick_zfs_path_for_sudo()?;
369
370 let mut child = Command::new(zfs_exe)
372 .arg("get")
373 .arg("keystatus")
374 .arg("-H") .arg("-o")
376 .arg("name,value") .stdin(std::process::Stdio::piped())
378 .stdout(std::process::Stdio::piped())
379 .stderr(std::process::Stdio::piped())
380 .spawn()
381 .map_err(|e| ZfsError::KeyLoadedCheckFailed(dataset.to_string(), e.to_string()))?;
382
383 let mut stdout = child.stdout.take().expect("Failed to capture stdout");
385 let mut stderr = child.stderr.take().expect("Failed to capture stderr");
386
387 let mut stdout_string = String::new();
389 stdout
390 .read_to_string(&mut stdout_string)
391 .map_err(|e| ZfsError::SystemError(e.to_string()))?;
392 let mut stderr_string = String::new();
393 stderr
394 .read_to_string(&mut stderr_string)
395 .map_err(|e| ZfsError::SystemError(e.to_string()))?;
396
397 let status = child
399 .wait()
400 .map_err(|e| ZfsError::SystemError(e.to_string()))?;
401
402 if status.success() {
404 let lines = stdout_string.lines();
405 let datasets_results = lines
406 .into_iter()
407 .map(|l| l.split_whitespace().collect::<Vec<_>>())
408 .filter(|v| v.len() >= 2)
409 .map(|v| (v[0], v[1]))
410 .collect::<BTreeMap<&str, &str>>();
411 match datasets_results.get(&*dataset) {
412 Some(is_key_available) => parse_key_available_state(is_key_available).map(Some),
413 None => Ok(None),
414 }
415 } else {
416 Err(ZfsError::KeyLoadedCheckFailed(
417 dataset.to_string(),
418 stderr_string,
419 ))
420 }
421}
422
423pub fn zfs_is_dataset_mounted(zfs_dataset: impl AsRef<str>) -> Result<Option<bool>, ZfsError> {
429 let dataset = check_and_sanitize_zfs_dataset_name(zfs_dataset)?;
430
431 let zfs_exe = pick_zfs_path_for_sudo()?;
432
433 let mut child = Command::new(zfs_exe)
435 .arg("list")
436 .arg("-H") .arg("-o")
438 .arg("name,mounted") .stdin(std::process::Stdio::piped())
440 .stdout(std::process::Stdio::piped())
441 .stderr(std::process::Stdio::piped())
442 .spawn()
443 .map_err(|e| ZfsError::IsMountedCheckCallFailed(dataset.to_string(), e.to_string()))?;
444
445 let mut stdout = child.stdout.take().expect("Failed to capture stdout");
447 let mut stderr = child.stderr.take().expect("Failed to capture stderr");
448
449 let mut stdout_string = String::new();
451 stdout
452 .read_to_string(&mut stdout_string)
453 .map_err(|e| ZfsError::SystemError(e.to_string()))?;
454 let mut stderr_string = String::new();
455 stderr
456 .read_to_string(&mut stderr_string)
457 .map_err(|e| ZfsError::SystemError(e.to_string()))?;
458
459 let status = child
461 .wait()
462 .map_err(|e| ZfsError::SystemError(e.to_string()))?;
463
464 if status.success() {
466 let lines = stdout_string.lines();
467 let datasets_results = lines
468 .into_iter()
469 .map(|l| l.split_whitespace().collect::<Vec<_>>())
470 .filter(|v| v.len() >= 2)
471 .map(|v| (v[0], v[1]))
472 .collect::<BTreeMap<&str, &str>>();
473 match datasets_results.get(&*dataset) {
474 Some(is_dataset_mounted) => match *is_dataset_mounted {
475 "yes" => Ok(Some(true)),
476 "no" => Ok(Some(false)),
477 _ => Err(ZfsError::UnexpectedStateForMount(
478 is_dataset_mounted.to_string(),
479 )),
480 },
481 None => Ok(None),
482 }
483 } else {
484 Err(ZfsError::IsMountedCheckCallFailed(
485 dataset.to_string(),
486 stderr_string,
487 ))
488 }
489}
490
491pub fn zfs_list_datasets_mountpoints() -> Result<BTreeMap<String, PathBuf>, ZfsError> {
492 let zfs_exe = pick_zfs_path_for_sudo()?;
493
494 let mut child = Command::new(zfs_exe)
496 .arg("list")
497 .arg("-H") .arg("-o")
499 .arg("name,mountpoint") .stdin(std::process::Stdio::piped())
501 .stdout(std::process::Stdio::piped())
502 .stderr(std::process::Stdio::piped())
503 .spawn()
504 .map_err(|e| ZfsError::ListDatasetsMountPointsCallFailed(e.to_string()))?;
505
506 let mut stdout = child.stdout.take().expect("Failed to capture stdout");
508 let mut stderr = child.stderr.take().expect("Failed to capture stderr");
509
510 let mut stdout_string = String::new();
512 stdout
513 .read_to_string(&mut stdout_string)
514 .map_err(|e| ZfsError::SystemError(e.to_string()))?;
515 let mut stderr_string = String::new();
516 stderr
517 .read_to_string(&mut stderr_string)
518 .map_err(|e| ZfsError::SystemError(e.to_string()))?;
519
520 let status = child
522 .wait()
523 .map_err(|e| ZfsError::SystemError(e.to_string()))?;
524
525 if status.success() {
527 let lines = stdout_string.lines();
528 let datasets_results = lines
529 .into_iter()
530 .map(|l| l.split_whitespace().collect::<Vec<_>>())
531 .filter(|v| v.len() >= 2)
532 .map(|v| (v[0].to_string(), PathBuf::from(v[1])))
533 .collect::<BTreeMap<String, PathBuf>>();
534 Ok(datasets_results)
535 } else {
536 Err(ZfsError::ListDatasetsMountPointsCallFailed(stderr_string))
537 }
538}
539
540pub fn zfs_list_encrypted_datasets() -> Result<BTreeMap<String, DatasetMountedState>, ZfsError> {
541 let zfs_exe = pick_zfs_path_for_sudo()?;
542
543 let mut child = Command::new(zfs_exe)
545 .arg("list")
546 .arg("-H") .arg("-o")
548 .arg("name,mounted,keystatus") .stdin(std::process::Stdio::piped())
550 .stdout(std::process::Stdio::piped())
551 .stderr(std::process::Stdio::piped())
552 .spawn()
553 .map_err(|e| ZfsError::ListDatasetsMountPointsCallFailed(e.to_string()))?;
554
555 let mut stdout = child.stdout.take().expect("Failed to capture stdout");
557 let mut stderr = child.stderr.take().expect("Failed to capture stderr");
558
559 let mut stdout_string = String::new();
561 stdout
562 .read_to_string(&mut stdout_string)
563 .map_err(|e| ZfsError::SystemError(e.to_string()))?;
564 let mut stderr_string = String::new();
565 stderr
566 .read_to_string(&mut stderr_string)
567 .map_err(|e| ZfsError::SystemError(e.to_string()))?;
568
569 let status = child
571 .wait()
572 .map_err(|e| ZfsError::SystemError(e.to_string()))?;
573
574 if status.success() {
576 let lines = stdout_string.lines();
577 let datasets_results = lines
578 .into_iter()
579 .map(|l| l.split_whitespace().collect::<Vec<_>>())
580 .filter(|v| v.len() >= 3)
581 .filter(|v| v[2].trim() != "-") .map(|v| {
583 let dataset_name = v[0].to_string();
584 let is_mounted = parse_dataset_mounted_state(v[1])?;
585 let is_key_loaded = parse_key_available_state(v[2])?;
586 Ok((
587 dataset_name.clone(),
588 DatasetMountedState {
589 dataset_name,
590 is_mounted,
591 is_key_loaded,
592 },
593 ))
594 })
595 .collect::<Result<BTreeMap<String, DatasetMountedState>, _>>()?;
596 Ok(datasets_results)
597 } else {
598 Err(ZfsError::ListUnmountedDatasetsCallFailed(stderr_string))
599 }
600}
601
602#[cfg(test)]
603mod tests {
604 use super::*;
605
606 #[test]
607 fn basic() {
608 let hostname = "ZFSAutomountTester";
610 let ds_name = "SamRandomPool/EncryptedDataset1";
611 let passphrase = "abcdefghijklmnop";
612 let mount_point = "/SamRandomPoolEncryptedDS1";
613
614 if hostname::get().unwrap().to_string_lossy().to_lowercase() == hostname.to_lowercase() {
615 assert_eq!(zfs_is_key_loaded("some_random_stuff").unwrap(), None);
617
618 zfs_unmount_dataset(ds_name).unwrap();
620
621 zfs_unload_key(ds_name).unwrap();
623 assert_eq!(zfs_is_key_loaded(ds_name).unwrap(), Some(false));
624 assert_eq!(
625 zfs_list_encrypted_datasets()
626 .unwrap()
627 .get(ds_name)
628 .unwrap()
629 .is_key_loaded,
630 false
631 );
632 zfs_load_key(ds_name, passphrase).unwrap();
633 assert_eq!(zfs_is_key_loaded(ds_name).unwrap(), Some(true));
634 assert_eq!(
635 zfs_list_encrypted_datasets()
636 .unwrap()
637 .get(ds_name)
638 .unwrap()
639 .is_key_loaded,
640 true
641 );
642 zfs_unload_key(ds_name).unwrap();
643 assert_eq!(
644 zfs_list_encrypted_datasets()
645 .unwrap()
646 .get(ds_name)
647 .unwrap()
648 .is_key_loaded,
649 false
650 );
651 assert_eq!(zfs_is_key_loaded(ds_name).unwrap(), Some(false));
652
653 zfs_load_key(ds_name, passphrase).unwrap();
654 assert_eq!(
655 zfs_list_encrypted_datasets()
656 .unwrap()
657 .get(ds_name)
658 .unwrap()
659 .is_key_loaded,
660 true
661 );
662 assert_eq!(zfs_is_key_loaded(ds_name).unwrap(), Some(true));
663
664 zfs_unmount_dataset(ds_name).unwrap();
665 assert_eq!(zfs_is_dataset_mounted(ds_name).unwrap(), Some(false));
666 assert_eq!(
667 zfs_list_encrypted_datasets()
668 .unwrap()
669 .get(ds_name)
670 .unwrap()
671 .is_mounted,
672 false
673 );
674 zfs_mount_dataset(ds_name).unwrap();
675 assert_eq!(zfs_is_dataset_mounted(ds_name).unwrap(), Some(true));
676 assert_eq!(
677 zfs_list_encrypted_datasets()
678 .unwrap()
679 .get(ds_name)
680 .unwrap()
681 .is_mounted,
682 true
683 );
684 zfs_unmount_dataset(ds_name).unwrap();
685 assert_eq!(zfs_is_dataset_mounted(ds_name).unwrap(), Some(false));
686 assert_eq!(
687 zfs_list_encrypted_datasets()
688 .unwrap()
689 .get(ds_name)
690 .unwrap()
691 .is_mounted,
692 false
693 );
694
695 zfs_unload_key(ds_name).unwrap();
696 assert_eq!(zfs_is_key_loaded(ds_name).unwrap(), Some(false));
697
698 let mount_points = zfs_list_datasets_mountpoints().unwrap();
699 assert_eq!(
700 mount_points.get(ds_name).unwrap().to_string_lossy(),
701 mount_point,
702 );
703 } else {
704 let err = "WARNING: No tests were run. Update the tests to test on your machine.";
705 println!("{}", err);
706 eprintln!("{}", err);
707 }
708 }
709
710 #[test]
711 fn test_valid_zfs_dataset_names() {
712 let f = check_and_sanitize_zfs_dataset_name;
713
714 f("pool/dataset1").unwrap();
715 f("pool/dataset_2").unwrap();
716 f("pool.dataset/dataset-3").unwrap();
717 f("pool.dataset/dataset:3").unwrap();
718 f("pool:1/dataset.with.multiple.levels").unwrap();
719 f(" pool:1/dataset.with.multiple.levels").unwrap();
720 f(" pool:1/dataset.with.multiple.levels ").unwrap();
721 }
722
723 #[test]
724 fn test_invalid_zfs_dataset_names() {
725 let f = check_and_sanitize_zfs_dataset_name;
726
727 f("").unwrap_err();
728 f("_R").unwrap_err();
729 f("-R").unwrap_err();
730 f(":R").unwrap_err();
731 f(".R").unwrap_err();
732 f(" _R").unwrap_err();
733 f(" -R").unwrap_err();
734 f(" :R").unwrap_err();
735 f(" .R").unwrap_err();
736 f("pool/_R").unwrap_err();
737 f("pool/-R").unwrap_err();
738 f("pool/:R").unwrap_err();
739 f("pool/.R").unwrap_err();
740 f("pool/ _R").unwrap_err();
741 f("pool/ -R").unwrap_err();
742 f("pool/ :R").unwrap_err();
743 f("pool/ .R").unwrap_err();
744 f("pool/dataset name").unwrap_err();
745 f("pool/dataset!").unwrap_err();
746 f("pool/dataset@name").unwrap_err();
747 f("pool//dataset").unwrap_err();
748 f("pool/ dataset").unwrap_err();
749 }
750
751 #[test]
752 fn key_loaded_state() {
753 assert_eq!(parse_key_available_state("available").unwrap(), true);
754 assert_eq!(parse_key_available_state("unavailable").unwrap(), false);
755 assert_eq!(parse_key_available_state(" available").unwrap(), true);
756 assert_eq!(parse_key_available_state(" unavailable").unwrap(), false);
757 assert_eq!(parse_key_available_state("available ").unwrap(), true);
758 assert_eq!(parse_key_available_state("unavailable ").unwrap(), false);
759 assert_eq!(parse_key_available_state(" available ").unwrap(), true);
760 assert_eq!(parse_key_available_state(" unavailable ").unwrap(), false);
761
762 parse_key_available_state("yes").unwrap_err();
763 parse_key_available_state("no").unwrap_err();
764 parse_key_available_state(" ").unwrap_err();
765 parse_key_available_state(".").unwrap_err();
766 parse_key_available_state("2222").unwrap_err();
767 }
768
769 #[test]
770 fn is_mounted_state() {
771 assert_eq!(parse_dataset_mounted_state("yes").unwrap(), true);
772 assert_eq!(parse_dataset_mounted_state("no").unwrap(), false);
773 assert_eq!(parse_dataset_mounted_state(" yes").unwrap(), true);
774 assert_eq!(parse_dataset_mounted_state(" no").unwrap(), false);
775 assert_eq!(parse_dataset_mounted_state("yes ").unwrap(), true);
776 assert_eq!(parse_dataset_mounted_state("no ").unwrap(), false);
777 assert_eq!(parse_dataset_mounted_state(" yes ").unwrap(), true);
778 assert_eq!(parse_dataset_mounted_state(" no ").unwrap(), false);
779
780 parse_dataset_mounted_state("available").unwrap_err();
781 parse_dataset_mounted_state("unavailable").unwrap_err();
782 parse_dataset_mounted_state(" ").unwrap_err();
783 parse_dataset_mounted_state(".").unwrap_err();
784 parse_dataset_mounted_state("2222").unwrap_err();
785 }
786}