use std::path::Path;
pub const NETWORK_FILESYSTEMS: &[&str] = &[
"nfs",
"nfs4",
"cifs",
"smb3",
"smbfs",
"afs",
"9p",
"ceph",
"glusterfs",
"fuse.sshfs",
"fuse.rclone",
"fuse.s3fs",
"fuse.davfs",
"davfs",
"ftpfs",
"autofs",
];
const MEMORY_FILESYSTEMS: &[&str] = &["tmpfs", "ramfs", "devtmpfs"];
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Locality {
Local,
Memory,
Network,
Object,
Unknown,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Source {
pub fstype: String,
pub locality: Locality,
}
impl Source {
pub fn label(&self) -> &str {
&self.fstype
}
pub fn notable(&self) -> bool {
!matches!(self.locality, Locality::Local)
}
pub fn network(&self) -> bool {
self.locality == Locality::Network
}
pub fn from_fstype(fstype: &str) -> Self {
if let Some(scheme) = ["s3", "gs", "http", "https", "az", "hdfs"]
.into_iter()
.find(|s| *s == fstype)
{
return Self {
fstype: scheme.to_string(),
locality: Locality::Object,
};
}
Self {
locality: classify(fstype),
fstype: fstype.to_string(),
}
}
}
#[derive(Debug, Clone, Default)]
pub struct Mounts {
entries: Vec<(String, String)>,
}
impl Mounts {
pub fn current() -> Self {
std::fs::read_to_string("/proc/self/mountinfo")
.map(|s| Self::parse(&s))
.unwrap_or_default()
}
pub fn parse(mountinfo: &str) -> Self {
let mut entries = Vec::new();
for line in mountinfo.lines() {
let Some((before, after)) = line.split_once(" - ") else {
continue;
};
let Some(point) = before.split_whitespace().nth(4) else {
continue;
};
let Some(fstype) = after.split_whitespace().next() else {
continue;
};
entries.push((point.to_string(), fstype.to_string()));
}
Self { entries }
}
pub fn fstype_for(&self, path: &Path) -> Option<&str> {
let joined;
let path = if path.has_root() {
path
} else {
match std::env::current_dir() {
Ok(cwd) => {
joined = cwd.join(path);
&joined
}
Err(_) => path,
}
};
let mut best: Option<(usize, &str)> = None;
for (point, fstype) in &self.entries {
if !path.starts_with(point) {
continue;
}
let len = point.len();
if best.is_none_or(|(n, _)| len >= n) {
best = Some((len, fstype));
}
}
best.map(|(_, f)| f)
}
pub fn describe(&self, path: &Path) -> Source {
if let Some(scheme) = object_scheme(path) {
return Source {
fstype: scheme,
locality: Locality::Object,
};
}
match self.fstype_for(path) {
Some(fstype) => Source {
locality: classify(fstype),
fstype: fstype.to_string(),
},
None => Source {
fstype: "unknown".to_string(),
locality: Locality::Unknown,
},
}
}
pub fn is_network(&self, path: &Path) -> bool {
self.describe(path).network()
}
}
fn classify(fstype: &str) -> Locality {
if NETWORK_FILESYSTEMS.contains(&fstype) {
Locality::Network
} else if MEMORY_FILESYSTEMS.contains(&fstype) {
Locality::Memory
} else {
Locality::Local
}
}
pub fn object_scheme(path: &Path) -> Option<String> {
match crate::source::input_source(path) {
crate::source::InputSource::Local(_) => None,
crate::source::InputSource::S3(_) => Some("s3".to_string()),
crate::source::InputSource::Gcs(_) => Some("gs".to_string()),
crate::source::InputSource::Http(_) => Some("http".to_string()),
}
}