#[macro_use]
extern crate log;
use std::path::{Path, PathBuf};
#[derive(Debug, PartialEq, Eq)]
pub struct MountInfo {
mount_point: PathBuf,
filesystem: FileSystems,
options: MountOptions,
}
impl MountInfo {
pub fn is_local(&self) -> bool {
use FileSystems::*;
match self.filesystem {
Cifs | Nfs => false,
_ => true,
}
}
pub fn is_case_sensitive(&self) -> bool {
use FileSystems::*;
match self.filesystem {
Cifs => false,
_ => true,
}
}
}
#[derive(Debug, PartialEq, Eq)]
pub enum MountOptions {
RelATime,
NoATime,
Unknown,
}
#[derive(Debug, PartialEq, Eq)]
pub enum FileSystems {
Btrfs,
Cifs,
Ext2,
Ext3,
Ext4,
Nfs,
Sysfs,
Sshfs,
Tmpfs,
Fat,
Unknown(String),
}
fn parse_fs(fs: &str) -> FileSystems {
use FileSystems::*;
match fs.trim() {
"btrfs" => Btrfs,
"cifs" => Cifs,
"ext2" => Ext2,
"ext3" => Ext3,
"ext4" => Ext4,
"nfs" | "nfs4" => Nfs,
"sshfs" | "fuse.sshfs" => Sshfs,
"sysfs" => Sysfs,
"tmpfs" => Tmpfs,
"vfat" => Fat,
u => {
warn!("unknown filesystem {:?}, please sumbmit a PR/MR", u);
Unknown(fs.to_owned())
}
}
}
pub fn detect<P: AsRef<Path>>(target_dir: P) -> std::io::Result<MountInfo> {
let target_dir = target_dir.as_ref();
let target_dir = target_dir.canonicalize()?;
let target_dir = target_dir
.components()
.map(|c| Some(c))
.chain(std::iter::once(None));
let mounts = &std::fs::read("/proc/mounts")?;
let mounts = String::from_utf8_lossy(mounts);
let root = Path::new("/");
let mut mounted_filesystem = None;
let mut longest_match = 0;
for line in mounts.lines() {
let mut parts = line.trim().split(" ");
parts.next();
let mut mount_point = parts.collect::<Vec<_>>();
let filesystem = mount_point[1];
let mount_options = mount_point[mount_point.len() - 3].split(',');
mount_point.truncate(mount_point.len() - 4);
let mount_point = mount_point.join(" ");
let mount_point = Path::new(&mount_point);
let mut matching_parts = 0;
for (o, mp) in target_dir.clone().zip(mount_point.components()) {
if let Some(o) = o {
if o == mp {
matching_parts += 1;
} else {
matching_parts = 0;
break;
}
} else {
matching_parts = 0;
break;
}
}
if (mount_point == root && longest_match <= 1)
|| (mount_point != root && matching_parts > longest_match)
{
longest_match = matching_parts;
mounted_filesystem =
Some((mount_point.to_owned(), parse_fs(filesystem), mount_options));
}
}
let (mount_point, filesystem, options) = if let Some((mount_point, filesystem, mount_options)) =
mounted_filesystem
{
if mount_options.clone().any(|a| a == "relatime") {
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());
(mount_point, filesystem, MountOptions::RelATime)
} else if mount_options.clone().any(|a| a == "noatime") {
(mount_point, filesystem, MountOptions::NoATime)
} else {
warn!(
"neither relatime nor noatime found: {}",
mount_options.collect::<Vec<_>>().join(",")
);
(mount_point, filesystem, MountOptions::Unknown)
}
} else {
warn!("unable to find mounted filesystem");
(
PathBuf::from("/"),
FileSystems::Unknown("<not found>".to_string()),
MountOptions::Unknown,
)
};
Ok(MountInfo {
mount_point,
filesystem,
options,
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn root() {
let info = detect("/").unwrap();
assert_eq!(PathBuf::from("/"), info.mount_point);
}
#[test]
fn path_ref() {
let _ = detect(&Path::new("/"));
}
#[test]
fn path_buf_ref() {
use std::path::PathBuf;
let _ = detect(&PathBuf::from("/"));
}
#[test]
fn path() {
let _ = detect(Path::new("/"));
}
#[test]
fn path_buf() {
use std::path::PathBuf;
let _ = detect(PathBuf::from("/"));
}
#[test]
fn str() {
let _ = detect("/");
}
#[test]
fn string() {
let _ = detect("/".to_string());
}
}