lfs_core/linux/
read_mountinfos.rs1use {
2 crate::*,
3 lazy_regex::*,
4 snafu::prelude::*,
5 std::path::PathBuf,
6};
7
8#[derive(Debug, Snafu)]
9#[snafu(display("Could not parse {line} as mount info"))]
10pub struct ParseMountInfoError {
11 line: String,
12}
13
14#[cfg(target_os = "linux")]
15impl std::str::FromStr for MountInfo {
16 type Err = ParseMountInfoError;
17 fn from_str(line: &str) -> Result<Self, Self::Err> {
18 (|| {
19 let mut tokens = line.split_whitespace();
23
24 let id = tokens.next()?.parse().ok()?;
25 let parent = tokens.next()?.parse().ok()?;
26
27 let id = Some(id);
30 let parent = Some(parent);
31
32 let dev = tokens.next()?.parse().ok()?;
33 let root = str_to_pathbuf(tokens.next()?);
34 let mount_point = str_to_pathbuf(tokens.next()?);
35
36 let options = regex_captures_iter!("(?:^|,)([^=,]+)(?:=([^=,]*))?", tokens.next()?,);
37 let options = options
38 .map(|c| {
39 let name = c.get(1).unwrap().as_str().to_string();
40 let value = c.get(2).map(|v| v.as_str().to_string());
41 MountOption { name, value }
42 })
43 .collect();
44
45 loop {
48 let token = tokens.next()?;
49 if token == "-" {
50 break;
51 }
52 }
53
54 let fs_type = tokens.next()?.to_string();
55 let fs = tokens.next()?.to_string();
56 Some(Self {
57 id,
58 parent,
59 dev,
60 root,
61 mount_point,
62 options,
63 fs,
64 fs_type,
65 bound: false, })
67 })()
68 .with_context(|| ParseMountInfoSnafu { line })
69 }
70}
71
72#[cfg(target_os = "linux")]
77fn str_to_pathbuf(s: &str) -> PathBuf {
78 PathBuf::from(sys::decode_string(s))
79}
80
81#[cfg(target_os = "linux")]
83pub fn read_all_mountinfos() -> Result<Vec<MountInfo>, Error> {
84 let mut mounts: Vec<MountInfo> = Vec::new();
85 let path = "/proc/self/mountinfo";
86 let file_content = sys::read_file(path).context(CantReadDirSnafu { path })?;
87 for line in file_content.trim().split('\n') {
88 let mut mount: MountInfo = line
89 .parse()
90 .map_err(|source| Error::ParseMountInfo { source })?;
91 mount.bound = mounts.iter().any(|m| m.dev == mount.dev);
92 mounts.push(mount);
93 }
94 Ok(mounts)
95}
96
97#[cfg(target_os = "linux")]
98#[allow(clippy::bool_assert_comparison)]
99#[test]
100fn test_from_str() {
101 use std::str::FromStr;
102
103 let mi = MountInfo::from_str(
104 "47 21 0:41 / /dev/hugepages rw,relatime shared:27 - hugetlbfs hugetlbfs rw,pagesize=2M",
105 )
106 .unwrap();
107 assert_eq!(mi.id, Some(47));
108 assert_eq!(mi.dev, DeviceId::new(0, 41));
109 assert_eq!(mi.root, PathBuf::from("/"));
110 assert_eq!(mi.mount_point, PathBuf::from("/dev/hugepages"));
111 assert_eq!(mi.options_string(), "rw,relatime".to_string());
112
113 let mi = MountInfo::from_str(
114 "106 26 8:17 / /home/dys/dev rw,noatime,compress=zstd:3 shared:57 - btrfs /dev/sdb1 rw,attr2,inode64,noquota"
115 ).unwrap();
116 assert_eq!(mi.id, Some(106));
117 assert_eq!(mi.dev, DeviceId::new(8, 17));
118 assert_eq!(&mi.fs, "/dev/sdb1");
119 assert_eq!(&mi.fs_type, "btrfs");
120 let mut options = mi.options.clone().into_iter();
121 assert_eq!(options.next(), Some(MountOption::new("rw", None)),);
122 assert_eq!(options.next(), Some(MountOption::new("noatime", None)));
123 assert_eq!(
124 options.next(),
125 Some(MountOption::new("compress", Some("zstd:3")))
126 );
127 assert_eq!(options.next(), None);
128 assert_eq!(mi.has_option("noatime"), true);
129 assert_eq!(mi.has_option("relatime"), false);
130 assert_eq!(mi.option_value("thing"), None);
131 assert_eq!(mi.option_value("compress"), Some("zstd:3"));
132}