1use crate::error::{EntryBuilderError, MountPointError};
7use crate::fstype::FsType;
8use crate::options::Options;
9use crate::spec::Spec;
10use std::fmt;
11use std::path::{Path, PathBuf};
12
13#[derive(Debug, Clone, PartialEq, Eq, Hash)]
25#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
26pub struct MountPoint(PathBuf);
27
28impl MountPoint {
29 pub fn new(path: impl Into<PathBuf>) -> Result<Self, MountPointError> {
39 let path = path.into();
40 let s = path.to_string_lossy();
41 if s.is_empty() {
42 return Err(MountPointError::Empty);
43 }
44 if s == "none" || s.starts_with('/') {
45 Ok(MountPoint(path))
46 } else {
47 Err(MountPointError::NotAbsolute)
48 }
49 }
50
51 #[must_use]
53 pub fn swap() -> Self {
54 MountPoint(PathBuf::from("none"))
55 }
56
57 #[must_use]
59 pub fn is_swap(&self) -> bool {
60 self.0.to_string_lossy() == "none"
61 }
62
63 #[must_use]
67 pub fn is_root(&self) -> bool {
68 let normalized: PathBuf = self.0.components().collect();
69 normalized == Path::new("/")
70 }
71
72 #[must_use]
74 pub fn as_path(&self) -> &Path {
75 &self.0
76 }
77}
78
79impl std::ops::Deref for MountPoint {
80 type Target = Path;
81
82 fn deref(&self) -> &Self::Target {
86 &self.0
87 }
88}
89
90impl fmt::Display for MountPoint {
91 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
92 write!(f, "{}", self.0.display())
93 }
94}
95
96impl AsRef<Path> for MountPoint {
97 fn as_ref(&self) -> &Path {
98 &self.0
99 }
100}
101
102impl TryFrom<&str> for MountPoint {
106 type Error = MountPointError;
107
108 fn try_from(s: &str) -> Result<Self, Self::Error> {
109 MountPoint::new(s)
110 }
111}
112
113impl TryFrom<String> for MountPoint {
117 type Error = MountPointError;
118
119 fn try_from(s: String) -> Result<Self, Self::Error> {
120 MountPoint::new(s)
121 }
122}
123
124#[derive(Debug, Clone, PartialEq, Eq, Hash)]
145#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
146pub struct Entry {
147 pub spec: Spec,
149 pub file: MountPoint,
151 pub vfstype: FsType,
153 pub options: Options,
155 pub freq: u32,
157 pub passno: u32,
159 pub comment: Option<String>,
161}
162
163impl Entry {
164 #[must_use]
184 pub fn new(spec: Spec, file: MountPoint, vfstype: FsType, options: Options) -> Self {
185 Entry {
186 spec,
187 file,
188 vfstype,
189 options,
190 freq: 0,
191 passno: 0,
192 comment: None,
193 }
194 }
195
196 #[must_use]
215 pub fn builder() -> EntryBuilder {
216 EntryBuilder::default()
217 }
218
219 #[must_use]
221 pub fn is_swap(&self) -> bool {
222 self.vfstype.is_swap()
223 }
224
225 #[must_use]
227 pub fn is_bind_mount(&self) -> bool {
228 self.vfstype.is_bind()
229 }
230
231 #[must_use]
238 pub fn is_root(&self) -> bool {
239 self.file.is_root() && self.passno == 1
240 }
241
242 #[must_use]
244 pub fn is_network(&self) -> bool {
245 self.vfstype.is_network() || self.options.is_netdev()
246 }
247
248 #[must_use]
250 pub fn comment(&self) -> Option<&str> {
251 self.comment.as_deref()
252 }
253}
254
255#[derive(Debug, Clone, Default)]
273#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
274pub struct EntryBuilder {
275 spec: Option<Spec>,
276 file: Option<MountPoint>,
277 vfstype: Option<FsType>,
278 options: Options,
279 freq: u32,
280 passno: u32,
281 comment: Option<String>,
282}
283
284impl EntryBuilder {
285 pub fn spec(mut self, spec: Spec) -> Self {
287 self.spec = Some(spec);
288 self
289 }
290 pub fn file(mut self, file: MountPoint) -> Self {
292 self.file = Some(file);
293 self
294 }
295 pub fn vfstype(mut self, vfstype: FsType) -> Self {
297 self.vfstype = Some(vfstype);
298 self
299 }
300 pub fn options(mut self, options: Options) -> Self {
302 self.options = options;
303 self
304 }
305 pub fn freq(mut self, freq: u32) -> Self {
307 self.freq = freq;
308 self
309 }
310 pub fn passno(mut self, passno: u32) -> Self {
312 self.passno = passno;
313 self
314 }
315 pub fn comment(mut self, comment: impl Into<String>) -> Self {
317 self.comment = Some(comment.into());
318 self
319 }
320
321 pub fn build(self) -> Result<Entry, EntryBuilderError> {
328 Ok(Entry {
329 spec: self.spec.ok_or(EntryBuilderError::MissingSpec)?,
330 file: self.file.ok_or(EntryBuilderError::MissingFile)?,
331 vfstype: self.vfstype.ok_or(EntryBuilderError::MissingFsType)?,
332 options: self.options,
333 freq: self.freq,
334 passno: self.passno,
335 comment: self.comment,
336 })
337 }
338}
339
340#[derive(Debug, Clone, Default, PartialEq, Eq)]
354#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
355pub struct Fstab {
356 pub intro_comment: Option<String>,
358 pub entries: Vec<Entry>,
360 pub trailing_comment: Option<String>,
362}
363
364impl Fstab {
365 #[must_use]
367 pub fn new() -> Self {
368 Fstab::default()
369 }
370
371 #[must_use]
373 pub fn len(&self) -> usize {
374 self.entries.len()
375 }
376
377 #[must_use]
379 pub fn is_empty(&self) -> bool {
380 self.entries.is_empty()
381 }
382
383 #[must_use]
385 pub fn entries(&self) -> &[Entry] {
386 &self.entries
387 }
388
389 #[must_use]
405 pub fn from_entries(entries: Vec<Entry>) -> Self {
406 Fstab {
407 entries,
408 intro_comment: None,
409 trailing_comment: None,
410 }
411 }
412
413 #[must_use]
430 pub fn into_entries(self) -> Vec<Entry> {
431 self.entries
432 }
433
434 pub fn add(&mut self, entry: Entry) -> &mut Self {
453 self.entries.push(entry);
454 self
455 }
456
457 pub fn insert(
466 &mut self,
467 index: usize,
468 entry: Entry,
469 ) -> Result<&mut Self, crate::error::FstabError> {
470 if index > self.entries.len() {
471 return Err(crate::error::FstabError::IndexOutOfBounds(
472 index,
473 self.entries.len(),
474 ));
475 }
476 self.entries.insert(index, entry);
477 Ok(self)
478 }
479
480 pub fn remove(&mut self, index: usize) -> Option<Entry> {
498 if index < self.entries.len() {
499 Some(self.entries.remove(index))
500 } else {
501 None
502 }
503 }
504
505 pub fn replace(&mut self, index: usize, entry: Entry) -> Option<Entry> {
507 if index < self.entries.len() {
508 Some(std::mem::replace(&mut self.entries[index], entry))
509 } else {
510 None
511 }
512 }
513
514 pub fn clear(&mut self) {
516 self.entries.clear();
517 self.intro_comment = None;
518 self.trailing_comment = None;
519 }
520
521 #[must_use]
523 pub fn find_by_source(&self, source: &str) -> Vec<&Entry> {
524 self.entries
525 .iter()
526 .filter(|e| e.spec.to_string().contains(source))
527 .collect()
528 }
529
530 #[must_use]
548 pub fn find_by_mountpoint(&self, mp: &Path) -> Option<&Entry> {
549 self.entries.iter().find(|e| e.file.as_path() == mp)
550 }
551
552 #[must_use]
554 pub fn root(&self) -> Option<&Entry> {
555 self.entries
556 .iter()
557 .find(|e| e.passno == 1 && e.file.is_root())
558 }
559}
560
561impl IntoIterator for Fstab {
562 type Item = Entry;
563 type IntoIter = std::vec::IntoIter<Entry>;
564
565 fn into_iter(self) -> Self::IntoIter {
567 self.entries.into_iter()
568 }
569}
570
571impl<'a> IntoIterator for &'a Fstab {
572 type Item = &'a Entry;
573 type IntoIter = std::slice::Iter<'a, Entry>;
574
575 fn into_iter(self) -> Self::IntoIter {
577 self.entries.iter()
578 }
579}
580
581#[cfg(test)]
582mod tests {
583 use super::*;
584 use crate::error::EntryBuilderError;
585 use crate::fstype::FsType;
586 use crate::options::Options;
587 use crate::spec::Spec;
588 use std::path::Path;
589
590 #[test]
593 fn mount_point_new_absolute_path() {
594 let mp = MountPoint::new("/mnt/data").unwrap();
595 assert_eq!(mp.as_path(), Path::new("/mnt/data"));
596 assert!(!mp.is_swap());
597 assert!(!mp.is_root());
598 }
599
600 #[test]
601 fn mount_point_new_root() {
602 let mp = MountPoint::new("/").unwrap();
603 assert!(mp.is_root());
604 assert!(!mp.is_swap());
605 }
606
607 #[test]
608 fn mount_point_new_none() {
609 let mp = MountPoint::new("none").unwrap();
610 assert!(mp.is_swap());
611 assert!(!mp.is_root());
612 }
613
614 #[test]
615 fn mount_point_new_empty() {
616 let err = MountPoint::new("").unwrap_err();
617 assert_eq!(err, MountPointError::Empty);
618 }
619
620 #[test]
621 fn mount_point_new_not_absolute() {
622 let err = MountPoint::new("relative/path").unwrap_err();
623 assert_eq!(err, MountPointError::NotAbsolute);
624 }
625
626 #[test]
627 fn mount_point_swap_constructor() {
628 let mp = MountPoint::swap();
629 assert!(mp.is_swap());
630 assert_eq!(mp.as_path(), Path::new("none"));
631 }
632
633 #[test]
634 fn mount_point_deref() {
635 let mp = MountPoint::new("/etc").unwrap();
636 let path: &Path = &*mp;
637 assert_eq!(path, Path::new("/etc"));
638 assert!(mp.is_absolute());
640 assert!(mp.parent() == Some(Path::new("/")));
641 }
642
643 #[test]
646 fn entry_new_defaults() {
647 let spec = Spec::Device("/dev/sda1".into());
648 let file = MountPoint::new("/").unwrap();
649 let fstype = FsType::new("ext4").unwrap();
650 let opts = Options::defaults();
651 let entry = Entry::new(spec.clone(), file.clone(), fstype.clone(), opts.clone());
652
653 assert_eq!(entry.spec, spec);
654 assert_eq!(entry.file, file);
655 assert_eq!(entry.vfstype, fstype);
656 assert_eq!(entry.options, opts);
657 assert_eq!(entry.freq, 0);
658 assert_eq!(entry.passno, 0);
659 assert_eq!(entry.comment, None);
660 }
661
662 #[test]
663 fn entry_is_swap() {
664 let entry = Entry {
665 spec: Spec::Keyword("none".into()),
666 file: MountPoint::new("none").unwrap(),
667 vfstype: FsType::swap(),
668 options: Options::defaults(),
669 freq: 0,
670 passno: 0,
671 comment: None,
672 };
673 assert!(entry.is_swap());
674 }
675
676 #[test]
677 fn entry_is_bind_mount() {
678 let entry = Entry {
679 spec: Spec::Device("/dev/sda1".into()),
680 file: MountPoint::new("/mnt/bind").unwrap(),
681 vfstype: FsType::bind(),
682 options: Options::defaults(),
683 freq: 0,
684 passno: 0,
685 comment: None,
686 };
687 assert!(entry.is_bind_mount());
688 }
689
690 #[test]
691 fn entry_is_root() {
692 let entry = Entry {
693 spec: Spec::Device("/dev/sda1".into()),
694 file: MountPoint::new("/").unwrap(),
695 vfstype: FsType::new("ext4").unwrap(),
696 options: Options::defaults(),
697 freq: 0,
698 passno: 1,
699 comment: None,
700 };
701 assert!(entry.is_root());
702 }
703
704 #[test]
705 fn entry_is_root_requires_passno() {
706 let entry = Entry {
707 spec: Spec::Device("/dev/sda1".into()),
708 file: MountPoint::new("/").unwrap(),
709 vfstype: FsType::new("ext4").unwrap(),
710 options: Options::defaults(),
711 freq: 0,
712 passno: 0,
713 comment: None,
714 };
715 assert!(!entry.is_root());
716 }
717
718 #[test]
719 fn entry_is_network_by_fstype() {
720 let entry = Entry {
721 spec: Spec::NetworkMount {
722 host: "server".into(),
723 path: "/export".into(),
724 },
725 file: MountPoint::new("/mnt/nfs").unwrap(),
726 vfstype: FsType::new("nfs").unwrap(),
727 options: Options::new(),
728 freq: 0,
729 passno: 0,
730 comment: None,
731 };
732 assert!(entry.is_network());
733 }
734
735 #[test]
736 fn entry_is_network_by_netdev_option() {
737 let entry = Entry {
738 spec: Spec::Device("/dev/sda1".into()),
739 file: MountPoint::new("/mnt/data").unwrap(),
740 vfstype: FsType::new("ext4").unwrap(),
741 options: Options::parse("_netdev").unwrap(),
742 freq: 0,
743 passno: 0,
744 comment: None,
745 };
746 assert!(entry.is_network());
747 }
748
749 #[test]
750 fn entry_builder_minimal() {
751 let entry = Entry::builder()
752 .spec(Spec::parse("/dev/sda1").unwrap())
753 .file(MountPoint::new("/").unwrap())
754 .vfstype(FsType::parse("ext4").unwrap())
755 .build()
756 .unwrap();
757 assert_eq!(entry.spec, Spec::Device("/dev/sda1".into()));
758 assert!(entry.file.is_root());
759 assert_eq!(entry.vfstype.as_str(), "ext4");
760 assert!(entry.options.is_empty());
761 assert_eq!(entry.freq, 0);
762 assert_eq!(entry.passno, 0);
763 }
764
765 #[test]
766 fn entry_builder_all_fields() {
767 let entry = Entry::builder()
768 .spec(Spec::parse("UUID=root").unwrap())
769 .file(MountPoint::new("/").unwrap())
770 .vfstype(FsType::parse("ext4").unwrap())
771 .options(Options::parse("defaults,noatime").unwrap())
772 .freq(0)
773 .passno(1)
774 .comment("# Root filesystem")
775 .build()
776 .unwrap();
777 assert_eq!(entry.spec, Spec::Uuid("root".into()));
778 assert!(entry.file.is_root());
779 assert_eq!(entry.passno, 1);
780 assert_eq!(entry.comment, Some("# Root filesystem".into()));
781 }
782
783 #[test]
784 fn entry_builder_missing_spec() {
785 let err = Entry::builder()
786 .file(MountPoint::new("/").unwrap())
787 .vfstype(FsType::parse("ext4").unwrap())
788 .build()
789 .unwrap_err();
790 assert_eq!(err, EntryBuilderError::MissingSpec);
791 }
792
793 #[test]
794 fn entry_builder_missing_file() {
795 let err = Entry::builder()
796 .spec(Spec::parse("/dev/sda1").unwrap())
797 .vfstype(FsType::parse("ext4").unwrap())
798 .build()
799 .unwrap_err();
800 assert_eq!(err, EntryBuilderError::MissingFile);
801 }
802
803 #[test]
804 fn entry_builder_missing_vfstype() {
805 let err = Entry::builder()
806 .spec(Spec::parse("/dev/sda1").unwrap())
807 .file(MountPoint::new("/").unwrap())
808 .build()
809 .unwrap_err();
810 assert_eq!(err, EntryBuilderError::MissingFsType);
811 }
812
813 #[test]
814 fn entry_comment_accessor() {
815 let entry = Entry {
816 comment: Some("# Test".into()),
817 ..Entry::new(
818 Spec::Device("/dev/sda1".into()),
819 MountPoint::new("/").unwrap(),
820 FsType::new("ext4").unwrap(),
821 Options::defaults(),
822 )
823 };
824 assert_eq!(entry.comment(), Some("# Test"));
825 }
826
827 #[test]
830 fn fstab_new_is_empty() {
831 let fstab = Fstab::new();
832 assert!(fstab.is_empty());
833 assert_eq!(fstab.len(), 0);
834 }
835
836 #[test]
837 fn fstab_add_entry() {
838 let mut fstab = Fstab::new();
839 let entry = Entry::new(
840 Spec::Device("/dev/sda1".into()),
841 MountPoint::new("/").unwrap(),
842 FsType::new("ext4").unwrap(),
843 Options::defaults(),
844 );
845 fstab.add(entry);
846 assert_eq!(fstab.len(), 1);
847 assert!(!fstab.is_empty());
848 }
849
850 #[test]
851 fn fstab_add_chaining() {
852 let mut fstab = Fstab::new();
853 let e1 = Entry::new(
854 Spec::Device("/dev/sda1".into()),
855 MountPoint::new("/").unwrap(),
856 FsType::new("ext4").unwrap(),
857 Options::defaults(),
858 );
859 let e2 = Entry::new(
860 Spec::Device("/dev/sda2".into()),
861 MountPoint::new("/home").unwrap(),
862 FsType::new("ext4").unwrap(),
863 Options::defaults(),
864 );
865 fstab.add(e1).add(e2);
866 assert_eq!(fstab.len(), 2);
867 }
868
869 #[test]
870 fn fstab_insert_valid() {
871 let mut fstab = Fstab::new();
872 let e1 = Entry::new(
873 Spec::Device("/dev/sda1".into()),
874 MountPoint::new("/").unwrap(),
875 FsType::new("ext4").unwrap(),
876 Options::defaults(),
877 );
878 let e2 = Entry::new(
879 Spec::Device("/dev/sda2".into()),
880 MountPoint::new("/home").unwrap(),
881 FsType::new("ext4").unwrap(),
882 Options::defaults(),
883 );
884 fstab.add(e1);
885 let e3 = Entry::new(
886 Spec::Device("/dev/sdb1".into()),
887 MountPoint::new("/mnt/data").unwrap(),
888 FsType::new("xfs").unwrap(),
889 Options::defaults(),
890 );
891 assert!(fstab.insert(1, e3).is_ok());
893 assert_eq!(fstab.len(), 2);
894 assert!(fstab.insert(0, e2).is_ok());
896 assert_eq!(fstab.len(), 3);
897 }
898
899 #[test]
900 fn fstab_insert_out_of_bounds() {
901 let mut fstab = Fstab::new();
902 let entry = Entry::new(
903 Spec::Device("/dev/sda1".into()),
904 MountPoint::new("/").unwrap(),
905 FsType::new("ext4").unwrap(),
906 Options::defaults(),
907 );
908 let result = fstab.insert(1, entry);
909 assert!(result.is_err());
910 }
911
912 #[test]
913 fn fstab_remove() {
914 let mut fstab = Fstab::new();
915 let entry = Entry::new(
916 Spec::Device("/dev/sda1".into()),
917 MountPoint::new("/").unwrap(),
918 FsType::new("ext4").unwrap(),
919 Options::defaults(),
920 );
921 fstab.add(entry);
922 let removed = fstab.remove(0);
923 assert!(removed.is_some());
924 assert!(fstab.is_empty());
925
926 let none = fstab.remove(0);
927 assert!(none.is_none());
928 }
929
930 #[test]
931 fn fstab_replace() {
932 let mut fstab = Fstab::new();
933 let e1 = Entry::new(
934 Spec::Device("/dev/sda1".into()),
935 MountPoint::new("/").unwrap(),
936 FsType::new("ext4").unwrap(),
937 Options::defaults(),
938 );
939 let e2 = Entry::new(
940 Spec::Device("/dev/sdb1".into()),
941 MountPoint::new("/mnt/data").unwrap(),
942 FsType::new("xfs").unwrap(),
943 Options::defaults(),
944 );
945 fstab.add(e1);
946 let old = fstab.replace(0, e2);
947 assert!(old.is_some());
948 assert_eq!(fstab.len(), 1);
949
950 let none = fstab.replace(5, old.unwrap());
951 assert!(none.is_none());
952 }
953
954 #[test]
955 fn fstab_clear() {
956 let mut fstab = Fstab::new();
957 fstab.intro_comment = Some("# intro".into());
958 fstab.add(Entry::new(
959 Spec::Device("/dev/sda1".into()),
960 MountPoint::new("/").unwrap(),
961 FsType::new("ext4").unwrap(),
962 Options::defaults(),
963 ));
964 fstab.trailing_comment = Some("# trailing".into());
965 fstab.clear();
966 assert!(fstab.is_empty());
967 assert!(fstab.intro_comment.is_none());
968 assert!(fstab.trailing_comment.is_none());
969 }
970
971 #[test]
972 fn fstab_find_by_source() {
973 let mut fstab = Fstab::new();
974 fstab.add(Entry::new(
975 Spec::Device("/dev/sda1".into()),
976 MountPoint::new("/").unwrap(),
977 FsType::new("ext4").unwrap(),
978 Options::defaults(),
979 ));
980 fstab.add(Entry::new(
981 Spec::Uuid("abc-123".into()),
982 MountPoint::new("/home").unwrap(),
983 FsType::new("ext4").unwrap(),
984 Options::defaults(),
985 ));
986 let results = fstab.find_by_source("sda1");
987 assert_eq!(results.len(), 1);
988 let results = fstab.find_by_source("ext4");
989 assert_eq!(results.len(), 0);
990 }
991
992 #[test]
993 fn fstab_find_by_source_label() {
994 let mut fstab = Fstab::new();
995 fstab.add(Entry::new(
996 Spec::Label("ROOT".into()),
997 MountPoint::new("/").unwrap(),
998 FsType::new("ext4").unwrap(),
999 Options::defaults(),
1000 ));
1001 let results = fstab.find_by_source("LABEL=ROOT");
1002 assert_eq!(results.len(), 1);
1003 }
1004
1005 #[test]
1006 fn fstab_find_by_mountpoint() {
1007 let mut fstab = Fstab::new();
1008 fstab.add(Entry::new(
1009 Spec::Device("/dev/sda1".into()),
1010 MountPoint::new("/").unwrap(),
1011 FsType::new("ext4").unwrap(),
1012 Options::defaults(),
1013 ));
1014 fstab.add(Entry::new(
1015 Spec::Device("/dev/sda2".into()),
1016 MountPoint::new("/home").unwrap(),
1017 FsType::new("ext4").unwrap(),
1018 Options::defaults(),
1019 ));
1020 let found = fstab.find_by_mountpoint(Path::new("/home"));
1021 assert!(found.is_some());
1022 assert!(
1023 fstab
1024 .find_by_mountpoint(Path::new("/nonexistent"))
1025 .is_none()
1026 );
1027 }
1028
1029 #[test]
1030 fn fstab_root() {
1031 let mut fstab = Fstab::new();
1032 let root_entry = Entry {
1033 spec: Spec::Device("/dev/sda1".into()),
1034 file: MountPoint::new("/").unwrap(),
1035 vfstype: FsType::new("ext4").unwrap(),
1036 options: Options::defaults(),
1037 freq: 0,
1038 passno: 1,
1039 comment: None,
1040 };
1041 fstab.add(Entry::new(
1042 Spec::Device("/dev/sda2".into()),
1043 MountPoint::new("/home").unwrap(),
1044 FsType::new("ext4").unwrap(),
1045 Options::defaults(),
1046 ));
1047 fstab.add(root_entry);
1048 let root = fstab.root();
1049 assert!(root.is_some());
1050 assert!(root.unwrap().is_root());
1051 }
1052
1053 #[test]
1054 fn fstab_root_no_root_entry() {
1055 let mut fstab = Fstab::new();
1056 fstab.add(Entry::new(
1057 Spec::Device("/dev/sda1".into()),
1058 MountPoint::new("/home").unwrap(),
1059 FsType::new("ext4").unwrap(),
1060 Options::defaults(),
1061 ));
1062 assert!(fstab.root().is_none());
1063 }
1064
1065 #[test]
1066 fn fstab_entries_slice() {
1067 let mut fstab = Fstab::new();
1068 fstab.add(Entry::new(
1069 Spec::Device("/dev/sda1".into()),
1070 MountPoint::new("/").unwrap(),
1071 FsType::new("ext4").unwrap(),
1072 Options::defaults(),
1073 ));
1074 assert_eq!(fstab.entries().len(), 1);
1075 }
1076
1077 #[test]
1078 fn fstab_into_iter() {
1079 let mut fstab = Fstab::new();
1080 fstab.add(Entry::new(
1081 Spec::Device("/dev/sda1".into()),
1082 MountPoint::new("/").unwrap(),
1083 FsType::new("ext4").unwrap(),
1084 Options::defaults(),
1085 ));
1086 fstab.add(Entry::new(
1087 Spec::Device("/dev/sda2".into()),
1088 MountPoint::new("/home").unwrap(),
1089 FsType::new("ext4").unwrap(),
1090 Options::defaults(),
1091 ));
1092
1093 let count = fstab.into_iter().count();
1094 assert_eq!(count, 2);
1095 }
1096
1097 #[test]
1098 fn fstab_ref_into_iter() {
1099 let mut fstab = Fstab::new();
1100 fstab.add(Entry::new(
1101 Spec::Device("/dev/sda1".into()),
1102 MountPoint::new("/").unwrap(),
1103 FsType::new("ext4").unwrap(),
1104 Options::defaults(),
1105 ));
1106
1107 let entries: Vec<&Entry> = (&fstab).into_iter().collect();
1108 assert_eq!(entries.len(), 1);
1109 }
1110
1111 #[test]
1114 fn spec_display_device() {
1115 let spec = Spec::Device("/dev/sda1".into());
1116 assert_eq!(spec.to_string(), "/dev/sda1");
1117 }
1118
1119 #[test]
1120 fn spec_display_label() {
1121 let spec = Spec::Label("ROOT".into());
1122 assert_eq!(spec.to_string(), "LABEL=ROOT");
1123 }
1124
1125 #[test]
1126 fn spec_display_uuid() {
1127 let spec = Spec::Uuid("abc-123".into());
1128 assert_eq!(spec.to_string(), "UUID=abc-123");
1129 }
1130
1131 #[test]
1132 fn spec_display_partlabel() {
1133 let spec = Spec::PartLabel("System".into());
1134 assert_eq!(spec.to_string(), "PARTLABEL=System");
1135 }
1136
1137 #[test]
1138 fn spec_display_partuuid() {
1139 let spec = Spec::PartUuid("abc-def".into());
1140 assert_eq!(spec.to_string(), "PARTUUID=abc-def");
1141 }
1142
1143 #[test]
1144 fn spec_display_id() {
1145 #[allow(deprecated)]
1146 let spec = Spec::Id("wwn-0x50014ee2".into());
1147 assert_eq!(spec.to_string(), "ID=wwn-0x50014ee2");
1148 }
1149
1150 #[test]
1151 fn spec_display_network() {
1152 let spec = Spec::NetworkMount {
1153 host: "server".into(),
1154 path: "/export".into(),
1155 };
1156 assert_eq!(spec.to_string(), "server:/export");
1157 }
1158
1159 #[test]
1160 fn spec_display_keyword() {
1161 let spec = Spec::Keyword("proc".into());
1162 assert_eq!(spec.to_string(), "proc");
1163 }
1164}