linux_mount_options 0.1.0

Detect file system, mount point and noatime of a folder the process has access to.
Documentation
#[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())
        }
    }
}

/// Parsing `/proc/mounts` to build `MountInfo`.
///
/// Make sure the folder exists with `std::fs::create_dir_all(target_dir)?;`
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() {
        // sysfs /sys sysfs rw,nosuid,nodev,noexec,relatime 0 0
        let mut parts = line.trim().split(" ");

        // skip source
        parts.next();

        // chop of last parts
        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());
    }
}