Skip to main content

linux_mount_options/
lib.rs

1#[macro_use]
2extern crate log;
3
4use std::path::{Path, PathBuf};
5
6#[derive(Debug, PartialEq, Eq)]
7pub struct MountInfo {
8    mount_point: PathBuf,
9    filesystem: FileSystems,
10    options: MountOptions,
11}
12
13impl MountInfo {
14    pub fn is_local(&self) -> bool {
15        use FileSystems::*;
16        match self.filesystem {
17            Cifs | Nfs => false,
18            _ => true,
19        }
20    }
21
22    pub fn is_case_sensitive(&self) -> bool {
23        use FileSystems::*;
24        match self.filesystem {
25            Cifs => false,
26            _ => true,
27        }
28    }
29}
30
31#[derive(Debug, PartialEq, Eq)]
32pub enum MountOptions {
33    RelATime,
34    NoATime,
35    Unknown,
36}
37
38#[derive(Debug, PartialEq, Eq)]
39pub enum FileSystems {
40    Btrfs,
41    Cifs,
42    Ext2,
43    Ext3,
44    Ext4,
45    Nfs,
46    Sysfs,
47    Sshfs,
48    Tmpfs,
49    Fat,
50    Unknown(String),
51}
52
53fn parse_fs(fs: &str) -> FileSystems {
54    use FileSystems::*;
55    match fs.trim() {
56        "btrfs" => Btrfs,
57        "cifs" => Cifs,
58        "ext2" => Ext2,
59        "ext3" => Ext3,
60        "ext4" => Ext4,
61        "nfs" | "nfs4" => Nfs,
62        "sshfs" | "fuse.sshfs" => Sshfs,
63        "sysfs" => Sysfs,
64        "tmpfs" => Tmpfs,
65        "vfat" => Fat,
66        u => {
67            warn!("unknown filesystem {:?}, please sumbmit a PR/MR", u);
68            Unknown(fs.to_owned())
69        }
70    }
71}
72
73/// Parsing `/proc/mounts` to build `MountInfo`.
74///
75/// Make sure the folder exists with `std::fs::create_dir_all(target_dir)?;`
76pub fn detect<P: AsRef<Path>>(target_dir: P) -> std::io::Result<MountInfo> {
77    let target_dir = target_dir.as_ref();
78    let target_dir = target_dir.canonicalize()?;
79    let target_dir = target_dir
80        .components()
81        .map(|c| Some(c))
82        .chain(std::iter::once(None));
83
84    let mounts = &std::fs::read("/proc/mounts")?;
85    let mounts = String::from_utf8_lossy(mounts);
86    let root = Path::new("/");
87
88    let mut mounted_filesystem = None;
89    let mut longest_match = 0;
90
91    for line in mounts.lines() {
92        // sysfs /sys sysfs rw,nosuid,nodev,noexec,relatime 0 0
93        let mut parts = line.trim().split(" ");
94
95        // skip source
96        parts.next();
97
98        // chop of last parts
99        let mut mount_point = parts.collect::<Vec<_>>();
100        let filesystem = mount_point[1];
101        let mount_options = mount_point[mount_point.len() - 3].split(',');
102        mount_point.truncate(mount_point.len() - 4);
103
104        let mount_point = mount_point.join(" ");
105        let mount_point = Path::new(&mount_point);
106
107        let mut matching_parts = 0;
108        for (o, mp) in target_dir.clone().zip(mount_point.components()) {
109            if let Some(o) = o {
110                if o == mp {
111                    matching_parts += 1;
112                } else {
113                    matching_parts = 0;
114                    break;
115                }
116            } else {
117                matching_parts = 0;
118                break;
119            }
120        }
121        if (mount_point == root && longest_match <= 1)
122            || (mount_point != root && matching_parts > longest_match)
123        {
124            longest_match = matching_parts;
125            mounted_filesystem =
126                Some((mount_point.to_owned(), parse_fs(filesystem), mount_options));
127        }
128    }
129
130    let (mount_point, filesystem, options) = if let Some((mount_point, filesystem, mount_options)) =
131        mounted_filesystem
132    {
133        if mount_options.clone().any(|a| a == "relatime") {
134            warn!("the target_dir is mounted on a filesystem \"{}\" with the \"relatime\" attribute! This can lead to problems with fsfreeze. Consider mounting it with \"noatime\"", mount_point.display());
135            (mount_point, filesystem, MountOptions::RelATime)
136        } else if mount_options.clone().any(|a| a == "noatime") {
137            (mount_point, filesystem, MountOptions::NoATime)
138        } else {
139            warn!(
140                "neither relatime nor noatime found: {}",
141                mount_options.collect::<Vec<_>>().join(",")
142            );
143            (mount_point, filesystem, MountOptions::Unknown)
144        }
145    } else {
146        warn!("unable to find mounted filesystem");
147        (
148            PathBuf::from("/"),
149            FileSystems::Unknown("<not found>".to_string()),
150            MountOptions::Unknown,
151        )
152    };
153
154    Ok(MountInfo {
155        mount_point,
156        filesystem,
157        options,
158    })
159}
160
161#[cfg(test)]
162mod tests {
163    use super::*;
164
165    #[test]
166    fn root() {
167        let info = detect("/").unwrap();
168        assert_eq!(PathBuf::from("/"), info.mount_point);
169    }
170
171    #[test]
172    fn path_ref() {
173        let _ = detect(&Path::new("/"));
174    }
175
176    #[test]
177    fn path_buf_ref() {
178        use std::path::PathBuf;
179
180        let _ = detect(&PathBuf::from("/"));
181    }
182
183    #[test]
184    fn path() {
185        let _ = detect(Path::new("/"));
186    }
187
188    #[test]
189    fn path_buf() {
190        use std::path::PathBuf;
191
192        let _ = detect(PathBuf::from("/"));
193    }
194
195    #[test]
196    fn str() {
197        let _ = detect("/");
198    }
199
200    #[test]
201    fn string() {
202        let _ = detect("/".to_string());
203    }
204}