1mod blocks;
7mod columns;
8mod filesystem;
9mod table;
10
11use blocks::HumanReadable;
12use clap::builder::ValueParser;
13use table::HeaderMode;
14use uucore::display::Quotable;
15use uucore::error::{UError, UResult, USimpleError};
16use uucore::fsext::{read_fs_list, MountInfo};
17use uucore::parse_size::ParseSizeError;
18use uucore::{format_usage, help_about, help_section, help_usage, show};
19
20use clap::{crate_version, parser::ValueSource, Arg, ArgAction, ArgMatches, Command};
21
22use std::error::Error;
23use std::ffi::OsString;
24use std::fmt;
25use std::path::Path;
26
27use crate::blocks::{read_block_size, BlockSize};
28use crate::columns::{Column, ColumnError};
29use crate::filesystem::Filesystem;
30use crate::filesystem::FsError;
31use crate::table::Table;
32
33const ABOUT: &str = help_about!("df.md");
34const USAGE: &str = help_usage!("df.md");
35const AFTER_HELP: &str = help_section!("after help", "df.md");
36
37static OPT_HELP: &str = "help";
38static OPT_ALL: &str = "all";
39static OPT_BLOCKSIZE: &str = "blocksize";
40static OPT_TOTAL: &str = "total";
41static OPT_HUMAN_READABLE_BINARY: &str = "human-readable-binary";
42static OPT_HUMAN_READABLE_DECIMAL: &str = "human-readable-decimal";
43static OPT_INODES: &str = "inodes";
44static OPT_KILO: &str = "kilo";
45static OPT_LOCAL: &str = "local";
46static OPT_NO_SYNC: &str = "no-sync";
47static OPT_OUTPUT: &str = "output";
48static OPT_PATHS: &str = "paths";
49static OPT_PORTABILITY: &str = "portability";
50static OPT_SYNC: &str = "sync";
51static OPT_TYPE: &str = "type";
52static OPT_PRINT_TYPE: &str = "print-type";
53static OPT_EXCLUDE_TYPE: &str = "exclude-type";
54static OUTPUT_FIELD_LIST: [&str; 12] = [
55 "source", "fstype", "itotal", "iused", "iavail", "ipcent", "size", "used", "avail", "pcent",
56 "file", "target",
57];
58
59struct Options {
65 show_local_fs: bool,
66 show_all_fs: bool,
67 human_readable: Option<HumanReadable>,
68 block_size: BlockSize,
69 header_mode: HeaderMode,
70
71 include: Option<Vec<String>>,
76
77 exclude: Option<Vec<String>>,
82
83 sync: bool,
85
86 show_total: bool,
88
89 columns: Vec<Column>,
91}
92
93impl Default for Options {
94 fn default() -> Self {
95 Self {
96 show_local_fs: Default::default(),
97 show_all_fs: Default::default(),
98 block_size: BlockSize::default(),
99 human_readable: Option::default(),
100 header_mode: HeaderMode::default(),
101 include: Option::default(),
102 exclude: Option::default(),
103 sync: Default::default(),
104 show_total: Default::default(),
105 columns: vec![
106 Column::Source,
107 Column::Size,
108 Column::Used,
109 Column::Avail,
110 Column::Pcent,
111 Column::Target,
112 ],
113 }
114 }
115}
116
117#[derive(Debug)]
118enum OptionsError {
119 BlockSizeTooLarge(String),
120 InvalidBlockSize(String),
121 InvalidSuffix(String),
122
123 ColumnError(ColumnError),
125
126 FilesystemTypeBothSelectedAndExcluded(Vec<String>),
127}
128
129impl fmt::Display for OptionsError {
130 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
131 match self {
132 Self::BlockSizeTooLarge(s) => {
135 write!(f, "--block-size argument {} too large", s.quote())
136 }
137 Self::InvalidBlockSize(s) => write!(f, "invalid --block-size argument {s}"),
140 Self::InvalidSuffix(s) => write!(f, "invalid suffix in --block-size argument {s}"),
143 Self::ColumnError(ColumnError::MultipleColumns(s)) => write!(
144 f,
145 "option --output: field {} used more than once",
146 s.quote()
147 ),
148 #[allow(clippy::print_in_format_impl)]
149 Self::FilesystemTypeBothSelectedAndExcluded(types) => {
150 for t in types {
151 eprintln!(
152 "{}: file system type {} both selected and excluded",
153 uucore::util_name(),
154 t.quote()
155 );
156 }
157 Ok(())
158 }
159 }
160 }
161}
162
163impl Options {
164 fn from(matches: &ArgMatches) -> Result<Self, OptionsError> {
166 let include: Option<Vec<_>> = matches
167 .get_many::<OsString>(OPT_TYPE)
168 .map(|v| v.map(|s| s.to_string_lossy().to_string()).collect());
169 let exclude: Option<Vec<_>> = matches
170 .get_many::<OsString>(OPT_EXCLUDE_TYPE)
171 .map(|v| v.map(|s| s.to_string_lossy().to_string()).collect());
172
173 if let (Some(include), Some(exclude)) = (&include, &exclude) {
174 if let Some(types) = Self::get_intersected_types(include, exclude) {
175 return Err(OptionsError::FilesystemTypeBothSelectedAndExcluded(types));
176 }
177 }
178
179 Ok(Self {
180 show_local_fs: matches.get_flag(OPT_LOCAL),
181 show_all_fs: matches.get_flag(OPT_ALL),
182 sync: matches.get_flag(OPT_SYNC),
183 block_size: read_block_size(matches).map_err(|e| match e {
184 ParseSizeError::InvalidSuffix(s) => OptionsError::InvalidSuffix(s),
185 ParseSizeError::SizeTooBig(_) => OptionsError::BlockSizeTooLarge(
186 matches
187 .get_one::<String>(OPT_BLOCKSIZE)
188 .unwrap()
189 .to_string(),
190 ),
191 ParseSizeError::ParseFailure(s) => OptionsError::InvalidBlockSize(s),
192 })?,
193 header_mode: {
194 if matches.get_flag(OPT_HUMAN_READABLE_BINARY)
195 || matches.get_flag(OPT_HUMAN_READABLE_DECIMAL)
196 {
197 HeaderMode::HumanReadable
198 } else if matches.get_flag(OPT_PORTABILITY) {
199 HeaderMode::PosixPortability
200 } else if matches.value_source(OPT_OUTPUT) == Some(ValueSource::CommandLine) {
203 HeaderMode::Output
204 } else {
205 HeaderMode::Default
206 }
207 },
208 human_readable: {
209 if matches.get_flag(OPT_HUMAN_READABLE_BINARY) {
210 Some(HumanReadable::Binary)
211 } else if matches.get_flag(OPT_HUMAN_READABLE_DECIMAL) {
212 Some(HumanReadable::Decimal)
213 } else {
214 None
215 }
216 },
217 include,
218 exclude,
219 show_total: matches.get_flag(OPT_TOTAL),
220 columns: Column::from_matches(matches).map_err(OptionsError::ColumnError)?,
221 })
222 }
223
224 fn get_intersected_types(include: &[String], exclude: &[String]) -> Option<Vec<String>> {
225 let mut intersected_types = Vec::new();
226
227 for t in include {
228 if exclude.contains(t) {
229 intersected_types.push(t.clone());
230 }
231 }
232
233 (!intersected_types.is_empty()).then_some(intersected_types)
234 }
235}
236
237fn is_included(mi: &MountInfo, opt: &Options) -> bool {
239 if mi.remote && opt.show_local_fs {
241 return false;
242 }
243
244 if mi.dummy && !opt.show_all_fs {
246 return false;
247 }
248
249 if let Some(ref excludes) = opt.exclude {
251 if excludes.contains(&mi.fs_type) {
252 return false;
253 }
254 }
255 if let Some(ref includes) = opt.include {
256 if !includes.contains(&mi.fs_type) {
257 return false;
258 }
259 }
260
261 true
262}
263
264fn mount_info_lt(m1: &MountInfo, m2: &MountInfo) -> bool {
269 if m1.dev_name.starts_with('/') && !m2.dev_name.starts_with('/') {
271 return false;
272 }
273
274 let m1_nearer_root = m1.mount_dir.len() < m2.mount_dir.len();
275 let m2_below_root = !m1.mount_root.is_empty()
277 && !m2.mount_root.is_empty()
278 && m1.mount_root.len() > m2.mount_root.len();
279 if m1_nearer_root && !m2_below_root {
281 return false;
282 }
283
284 if m1.dev_name != m2.dev_name && m1.mount_dir == m2.mount_dir {
289 return false;
290 }
291
292 true
293}
294
295fn is_best(previous: &[MountInfo], mi: &MountInfo) -> bool {
300 for seen in previous {
301 if seen.dev_id == mi.dev_id && mount_info_lt(mi, seen) {
302 return false;
303 }
304 }
305 true
306}
307
308fn filter_mount_list(vmi: Vec<MountInfo>, opt: &Options) -> Vec<MountInfo> {
316 let mut result = vec![];
317 for mi in vmi {
318 if is_included(&mi, opt) && is_best(&result, &mi) {
324 result.push(mi);
325 }
326 }
327 result
328}
329
330fn get_all_filesystems(opt: &Options) -> UResult<Vec<Filesystem>> {
335 if opt.sync {
337 #[cfg(not(any(windows, target_os = "redox")))]
338 unsafe {
339 #[cfg(not(target_os = "android"))]
340 uucore::libc::sync();
341 #[cfg(target_os = "android")]
342 uucore::libc::syscall(uucore::libc::SYS_sync);
343 }
344 }
345
346 let mounts: Vec<MountInfo> = filter_mount_list(read_fs_list()?, opt);
351
352 #[cfg(not(windows))]
355 {
356 let maybe_mount = |m| Filesystem::from_mount(&mounts, &m, None).ok();
357 Ok(mounts
358 .clone()
359 .into_iter()
360 .filter_map(maybe_mount)
361 .filter(|fs| opt.show_all_fs || fs.usage.blocks > 0)
362 .collect())
363 }
364 #[cfg(windows)]
365 {
366 let maybe_mount = |m| Filesystem::from_mount(&m, None).ok();
367 Ok(mounts
368 .into_iter()
369 .filter_map(maybe_mount)
370 .filter(|fs| opt.show_all_fs || fs.usage.blocks > 0)
371 .collect())
372 }
373}
374
375fn get_named_filesystems<P>(paths: &[P], opt: &Options) -> UResult<Vec<Filesystem>>
377where
378 P: AsRef<Path>,
379{
380 let mounts: Vec<MountInfo> = filter_mount_list(read_fs_list()?, opt)
387 .into_iter()
388 .filter(|mi| mi.fs_type != "lofs" && !mi.dummy)
389 .collect();
390
391 let mut result = vec![];
392
393 if mounts.is_empty() {
395 show!(USimpleError::new(1, "no file systems processed"));
396 return Ok(result);
397 }
398
399 for path in paths {
402 match Filesystem::from_path(&mounts, path) {
403 Ok(fs) => result.push(fs),
404 Err(FsError::InvalidPath) => {
405 show!(USimpleError::new(
406 1,
407 format!("{}: No such file or directory", path.as_ref().display())
408 ));
409 }
410 Err(FsError::MountMissing) => {
411 show!(USimpleError::new(1, "no file systems processed"));
412 }
413 #[cfg(not(windows))]
414 Err(FsError::OverMounted) => {
415 show!(USimpleError::new(
416 1,
417 format!(
418 "cannot access {}: over-mounted by another device",
419 path.as_ref().quote()
420 )
421 ));
422 }
423 }
424 }
425 Ok(result)
426}
427
428#[derive(Debug)]
429enum DfError {
430 OptionsError(OptionsError),
432}
433
434impl Error for DfError {}
435
436impl UError for DfError {
437 fn usage(&self) -> bool {
438 matches!(self, Self::OptionsError(OptionsError::ColumnError(_)))
439 }
440}
441
442impl fmt::Display for DfError {
443 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
444 match self {
445 Self::OptionsError(e) => e.fmt(f),
446 }
447 }
448}
449
450#[uucore::main]
451pub fn uumain(args: impl uucore::Args) -> UResult<()> {
452 let matches = uu_app().try_get_matches_from(args)?;
453
454 #[cfg(windows)]
455 {
456 if matches.get_flag(OPT_INODES) {
457 println!("{}: doesn't support -i option", uucore::util_name());
458 return Ok(());
459 }
460 }
461
462 let opt = Options::from(&matches).map_err(DfError::OptionsError)?;
463 let filesystems: Vec<Filesystem> = match matches.get_many::<String>(OPT_PATHS) {
465 None => {
466 let filesystems = get_all_filesystems(&opt).map_err(|e| {
467 let context = "cannot read table of mounted file systems";
468 USimpleError::new(e.code(), format!("{context}: {e}"))
469 })?;
470
471 if filesystems.is_empty() {
472 return Err(USimpleError::new(1, "no file systems processed"));
473 }
474
475 filesystems
476 }
477 Some(paths) => {
478 let paths: Vec<_> = paths.collect();
479 let filesystems = get_named_filesystems(&paths, &opt).map_err(|e| {
480 let context = "cannot read table of mounted file systems";
481 USimpleError::new(e.code(), format!("{context}: {e}"))
482 })?;
483
484 if filesystems.is_empty() {
487 return Ok(());
488 }
489
490 filesystems
491 }
492 };
493
494 println!("{}", Table::new(&opt, filesystems));
495
496 Ok(())
497}
498
499pub fn uu_app() -> Command {
500 Command::new(uucore::util_name())
501 .version(crate_version!())
502 .about(ABOUT)
503 .override_usage(format_usage(USAGE))
504 .after_help(AFTER_HELP)
505 .infer_long_args(true)
506 .disable_help_flag(true)
507 .arg(
508 Arg::new(OPT_HELP)
509 .long(OPT_HELP)
510 .help("Print help information.")
511 .action(ArgAction::Help),
512 )
513 .arg(
514 Arg::new(OPT_ALL)
515 .short('a')
516 .long("all")
517 .overrides_with(OPT_ALL)
518 .help("include dummy file systems")
519 .action(ArgAction::SetTrue),
520 )
521 .arg(
522 Arg::new(OPT_BLOCKSIZE)
523 .short('B')
524 .long("block-size")
525 .value_name("SIZE")
526 .overrides_with_all([OPT_KILO, OPT_BLOCKSIZE])
527 .help(
528 "scale sizes by SIZE before printing them; e.g.\
529 '-BM' prints sizes in units of 1,048,576 bytes",
530 ),
531 )
532 .arg(
533 Arg::new(OPT_TOTAL)
534 .long("total")
535 .overrides_with(OPT_TOTAL)
536 .help("produce a grand total")
537 .action(ArgAction::SetTrue),
538 )
539 .arg(
540 Arg::new(OPT_HUMAN_READABLE_BINARY)
541 .short('h')
542 .long("human-readable")
543 .overrides_with_all([OPT_HUMAN_READABLE_DECIMAL, OPT_HUMAN_READABLE_BINARY])
544 .help("print sizes in human readable format (e.g., 1K 234M 2G)")
545 .action(ArgAction::SetTrue),
546 )
547 .arg(
548 Arg::new(OPT_HUMAN_READABLE_DECIMAL)
549 .short('H')
550 .long("si")
551 .overrides_with_all([OPT_HUMAN_READABLE_BINARY, OPT_HUMAN_READABLE_DECIMAL])
552 .help("likewise, but use powers of 1000 not 1024")
553 .action(ArgAction::SetTrue),
554 )
555 .arg(
556 Arg::new(OPT_INODES)
557 .short('i')
558 .long("inodes")
559 .overrides_with(OPT_INODES)
560 .help("list inode information instead of block usage")
561 .action(ArgAction::SetTrue),
562 )
563 .arg(
564 Arg::new(OPT_KILO)
565 .short('k')
566 .help("like --block-size=1K")
567 .overrides_with_all([OPT_BLOCKSIZE, OPT_KILO])
568 .action(ArgAction::SetTrue),
569 )
570 .arg(
571 Arg::new(OPT_LOCAL)
572 .short('l')
573 .long("local")
574 .overrides_with(OPT_LOCAL)
575 .help("limit listing to local file systems")
576 .action(ArgAction::SetTrue),
577 )
578 .arg(
579 Arg::new(OPT_NO_SYNC)
580 .long("no-sync")
581 .overrides_with_all([OPT_SYNC, OPT_NO_SYNC])
582 .help("do not invoke sync before getting usage info (default)")
583 .action(ArgAction::SetTrue),
584 )
585 .arg(
586 Arg::new(OPT_OUTPUT)
587 .long("output")
588 .value_name("FIELD_LIST")
589 .action(ArgAction::Append)
590 .num_args(0..)
591 .require_equals(true)
592 .use_value_delimiter(true)
593 .value_parser(OUTPUT_FIELD_LIST)
594 .default_missing_values(OUTPUT_FIELD_LIST)
595 .default_values(["source", "size", "used", "avail", "pcent", "target"])
596 .conflicts_with_all([OPT_INODES, OPT_PORTABILITY, OPT_PRINT_TYPE])
597 .help(
598 "use the output format defined by FIELD_LIST, \
599 or print all fields if FIELD_LIST is omitted.",
600 ),
601 )
602 .arg(
603 Arg::new(OPT_PORTABILITY)
604 .short('P')
605 .long("portability")
606 .overrides_with(OPT_PORTABILITY)
607 .help("use the POSIX output format")
608 .action(ArgAction::SetTrue),
609 )
610 .arg(
611 Arg::new(OPT_SYNC)
612 .long("sync")
613 .overrides_with_all([OPT_NO_SYNC, OPT_SYNC])
614 .help("invoke sync before getting usage info (non-windows only)")
615 .action(ArgAction::SetTrue),
616 )
617 .arg(
618 Arg::new(OPT_TYPE)
619 .short('t')
620 .long("type")
621 .value_parser(ValueParser::os_string())
622 .value_name("TYPE")
623 .action(ArgAction::Append)
624 .help("limit listing to file systems of type TYPE"),
625 )
626 .arg(
627 Arg::new(OPT_PRINT_TYPE)
628 .short('T')
629 .long("print-type")
630 .overrides_with(OPT_PRINT_TYPE)
631 .help("print file system type")
632 .action(ArgAction::SetTrue),
633 )
634 .arg(
635 Arg::new(OPT_EXCLUDE_TYPE)
636 .short('x')
637 .long("exclude-type")
638 .action(ArgAction::Append)
639 .value_parser(ValueParser::os_string())
640 .value_name("TYPE")
641 .use_value_delimiter(true)
642 .help("limit listing to file systems not of type TYPE"),
643 )
644 .arg(
645 Arg::new(OPT_PATHS)
646 .action(ArgAction::Append)
647 .value_hint(clap::ValueHint::AnyPath),
648 )
649}
650
651#[cfg(test)]
652mod tests {
653
654 mod mount_info_lt {
655
656 use crate::mount_info_lt;
657 use uucore::fsext::MountInfo;
658
659 fn mount_info(dev_name: &str, mount_root: &str, mount_dir: &str) -> MountInfo {
661 MountInfo {
662 dev_id: String::new(),
663 dev_name: String::from(dev_name),
664 fs_type: String::new(),
665 mount_dir: String::from(mount_dir),
666 mount_option: String::new(),
667 mount_root: String::from(mount_root),
668 remote: false,
669 dummy: false,
670 }
671 }
672
673 #[test]
674 fn test_absolute() {
675 let m1 = mount_info("/dev/foo", "/", "/mnt/bar");
677 let m2 = mount_info("dev_foo", "/", "/mnt/bar");
678 assert!(!mount_info_lt(&m1, &m2));
679 }
680
681 #[test]
682 fn test_shorter() {
683 let m1 = mount_info("/dev/foo", "/", "/mnt/bar");
685 let m2 = mount_info("/dev/foo", "/", "/mnt/bar/baz");
686 assert!(!mount_info_lt(&m1, &m2));
687
688 let m1 = mount_info("/dev/foo", "/root", "/mnt/bar");
690 let m2 = mount_info("/dev/foo", "/", "/mnt/bar/baz");
691 assert!(mount_info_lt(&m1, &m2));
692 }
693
694 #[test]
695 fn test_over_mounted() {
696 let m1 = mount_info("/dev/foo", "/", "/mnt/baz");
699 let m2 = mount_info("/dev/bar", "/", "/mnt/baz");
700 assert!(!mount_info_lt(&m1, &m2));
701 }
702 }
703
704 mod is_best {
705
706 use crate::is_best;
707 use uucore::fsext::MountInfo;
708
709 fn mount_info(dev_id: &str, mount_dir: &str) -> MountInfo {
711 MountInfo {
712 dev_id: String::from(dev_id),
713 dev_name: String::new(),
714 fs_type: String::new(),
715 mount_dir: String::from(mount_dir),
716 mount_option: String::new(),
717 mount_root: String::new(),
718 remote: false,
719 dummy: false,
720 }
721 }
722
723 #[test]
724 fn test_empty() {
725 let m = mount_info("0", "/mnt/bar");
726 assert!(is_best(&[], &m));
727 }
728
729 #[test]
730 fn test_different_dev_id() {
731 let m1 = mount_info("0", "/mnt/bar");
732 let m2 = mount_info("1", "/mnt/bar");
733 assert!(is_best(&[m1.clone()], &m2));
734 assert!(is_best(&[m2], &m1));
735 }
736
737 #[test]
738 fn test_same_dev_id() {
739 let m1 = mount_info("0", "/mnt/bar");
743 let m2 = mount_info("0", "/mnt/bar/baz");
744 assert!(!is_best(&[m1.clone()], &m2));
745 assert!(is_best(&[m2], &m1));
746 }
747 }
748
749 mod is_included {
750
751 use crate::{is_included, Options};
752 use uucore::fsext::MountInfo;
753
754 fn mount_info(fs_type: &str, mount_dir: &str, remote: bool, dummy: bool) -> MountInfo {
756 MountInfo {
757 dev_id: String::new(),
758 dev_name: String::new(),
759 fs_type: String::from(fs_type),
760 mount_dir: String::from(mount_dir),
761 mount_option: String::new(),
762 mount_root: String::new(),
763 remote,
764 dummy,
765 }
766 }
767
768 #[test]
769 fn test_remote_included() {
770 let opt = Options::default();
771 let m = mount_info("ext4", "/mnt/foo", true, false);
772 assert!(is_included(&m, &opt));
773 }
774
775 #[test]
776 fn test_remote_excluded() {
777 let opt = Options {
778 show_local_fs: true,
779 ..Default::default()
780 };
781 let m = mount_info("ext4", "/mnt/foo", true, false);
782 assert!(!is_included(&m, &opt));
783 }
784
785 #[test]
786 fn test_dummy_included() {
787 let opt = Options {
788 show_all_fs: true,
789 ..Default::default()
790 };
791 let m = mount_info("ext4", "/mnt/foo", false, true);
792 assert!(is_included(&m, &opt));
793 }
794
795 #[test]
796 fn test_dummy_excluded() {
797 let opt = Options::default();
798 let m = mount_info("ext4", "/mnt/foo", false, true);
799 assert!(!is_included(&m, &opt));
800 }
801
802 #[test]
803 fn test_exclude_match() {
804 let exclude = Some(vec![String::from("ext4")]);
805 let opt = Options {
806 exclude,
807 ..Default::default()
808 };
809 let m = mount_info("ext4", "/mnt/foo", false, false);
810 assert!(!is_included(&m, &opt));
811 }
812
813 #[test]
814 fn test_exclude_no_match() {
815 let exclude = Some(vec![String::from("tmpfs")]);
816 let opt = Options {
817 exclude,
818 ..Default::default()
819 };
820 let m = mount_info("ext4", "/mnt/foo", false, false);
821 assert!(is_included(&m, &opt));
822 }
823
824 #[test]
825 fn test_include_match() {
826 let include = Some(vec![String::from("ext4")]);
827 let opt = Options {
828 include,
829 ..Default::default()
830 };
831 let m = mount_info("ext4", "/mnt/foo", false, false);
832 assert!(is_included(&m, &opt));
833 }
834
835 #[test]
836 fn test_include_no_match() {
837 let include = Some(vec![String::from("tmpfs")]);
838 let opt = Options {
839 include,
840 ..Default::default()
841 };
842 let m = mount_info("ext4", "/mnt/foo", false, false);
843 assert!(!is_included(&m, &opt));
844 }
845
846 #[test]
847 fn test_include_and_exclude_match_neither() {
848 let include = Some(vec![String::from("tmpfs")]);
849 let exclude = Some(vec![String::from("squashfs")]);
850 let opt = Options {
851 include,
852 exclude,
853 ..Default::default()
854 };
855 let m = mount_info("ext4", "/mnt/foo", false, false);
856 assert!(!is_included(&m, &opt));
857 }
858
859 #[test]
860 fn test_include_and_exclude_match_exclude() {
861 let include = Some(vec![String::from("tmpfs")]);
862 let exclude = Some(vec![String::from("ext4")]);
863 let opt = Options {
864 include,
865 exclude,
866 ..Default::default()
867 };
868 let m = mount_info("ext4", "/mnt/foo", false, false);
869 assert!(!is_included(&m, &opt));
870 }
871
872 #[test]
873 fn test_include_and_exclude_match_include() {
874 let include = Some(vec![String::from("ext4")]);
875 let exclude = Some(vec![String::from("squashfs")]);
876 let opt = Options {
877 include,
878 exclude,
879 ..Default::default()
880 };
881 let m = mount_info("ext4", "/mnt/foo", false, false);
882 assert!(is_included(&m, &opt));
883 }
884 }
885
886 mod filter_mount_list {
887
888 use crate::{filter_mount_list, Options};
889
890 #[test]
891 fn test_empty() {
892 let opt = Options::default();
893 let mount_infos = vec![];
894 assert!(filter_mount_list(mount_infos, &opt).is_empty());
895 }
896 }
897}