use std::{collections::BTreeMap, path::Path, path::PathBuf, process::Command as ExecProcess};
use proc_mounts::MountIter;
use rayon::iter::Either;
use rayon::prelude::*;
use which::which;
use crate::data::filesystem_map::{DatasetMetadata, FilesystemType, MountType};
use crate::library::results::{HttmError, HttmResult};
use crate::library::utility::{get_common_path, get_fs_type_from_hidden_dir};
use crate::parse::snaps::precompute_snap_mounts;
use crate::{
MapOfDatasets, MapOfSnaps, OptBtrfsCommonSnapDir, VecOfFilterDirs, ZFS_SNAPSHOT_DIRECTORY,
};
pub const ZFS_FSTYPE: &str = "zfs";
pub const BTRFS_FSTYPE: &str = "btrfs";
pub const SMB_FSTYPE: &str = "smbfs";
pub const NFS_FSTYPE: &str = "nfs";
pub const AFP_FSTYPE: &str = "afpfs";
pub fn parse_mounts_exec() -> HttmResult<(MapOfDatasets, MapOfSnaps, VecOfFilterDirs)> {
let (map_of_datasets, vec_filter_dirs) = if cfg!(target_os = "linux") {
parse_from_proc_mounts()?
} else {
parse_from_mount_cmd()?
};
let map_of_snaps = precompute_snap_mounts(&map_of_datasets)?;
Ok((map_of_datasets, map_of_snaps, vec_filter_dirs))
}
fn parse_from_proc_mounts() -> HttmResult<(MapOfDatasets, VecOfFilterDirs)> {
let (map_of_datasets, filter_dirs): (MapOfDatasets, Vec<PathBuf>) = MountIter::new()?
.par_bridge()
.flatten()
.filter(|mount_info| {
!mount_info
.dest
.to_string_lossy()
.contains(ZFS_SNAPSHOT_DIRECTORY)
})
.partition_map(|mount_info| match &mount_info.fstype.as_str() {
&ZFS_FSTYPE => Either::Left((
mount_info.dest,
DatasetMetadata {
name: mount_info.source.to_string_lossy().into_owned(),
fs_type: FilesystemType::Zfs,
mount_type: MountType::Local,
},
)),
&SMB_FSTYPE | &AFP_FSTYPE | &NFS_FSTYPE => {
match get_fs_type_from_hidden_dir(&mount_info.dest) {
Ok(FilesystemType::Zfs) => Either::Left((
mount_info.dest,
DatasetMetadata {
name: mount_info.source.to_string_lossy().into_owned(),
fs_type: FilesystemType::Zfs,
mount_type: MountType::Network,
},
)),
Ok(FilesystemType::Btrfs) => Either::Left((
mount_info.dest,
DatasetMetadata {
name: mount_info.source.to_string_lossy().into_owned(),
fs_type: FilesystemType::Btrfs,
mount_type: MountType::Network,
},
)),
Err(_) => Either::Right(mount_info.dest),
}
}
&BTRFS_FSTYPE => {
let keyed_options: BTreeMap<String, String> = mount_info
.options
.par_iter()
.filter(|line| line.contains('='))
.filter_map(|line| {
line.split_once(&"=")
.map(|(key, value)| (key.to_owned(), value.to_owned()))
})
.collect();
let name = match keyed_options.get("subvol") {
Some(subvol) => subvol.clone(),
None => mount_info.source.to_string_lossy().into_owned(),
};
let fs_type = FilesystemType::Btrfs;
let mount_type = MountType::Local;
Either::Left((
mount_info.dest,
DatasetMetadata {
name,
fs_type,
mount_type,
},
))
}
_ => Either::Right(mount_info.dest),
});
if map_of_datasets.is_empty() {
Err(HttmError::new("httm could not find any valid datasets on the system.").into())
} else {
Ok((map_of_datasets, filter_dirs))
}
}
fn parse_from_mount_cmd() -> HttmResult<(MapOfDatasets, VecOfFilterDirs)> {
fn parse(mount_command: &Path) -> HttmResult<(MapOfDatasets, VecOfFilterDirs)> {
let command_output =
std::str::from_utf8(&ExecProcess::new(mount_command).output()?.stdout)?.to_owned();
let (map_of_datasets, filter_dirs): (MapOfDatasets, Vec<PathBuf>) = command_output
.par_lines()
.filter(|line| !line.contains(ZFS_SNAPSHOT_DIRECTORY))
.filter_map(|line|
if line.contains("type") {
line.split_once(&" type")
} else {
line.split_once(&" (")
}
)
.map(|(filesystem_and_mount,_)| filesystem_and_mount )
.filter_map(|filesystem_and_mount| filesystem_and_mount.split_once(&" on "))
.map(|(filesystem, mount)| (filesystem.to_owned(), PathBuf::from(mount)))
.partition_map(|(filesystem, mount)| {
match get_fs_type_from_hidden_dir(&mount) {
Ok(FilesystemType::Zfs) => {
Either::Left((mount, DatasetMetadata {
name: filesystem,
fs_type: FilesystemType::Zfs,
mount_type: MountType::Local
}))
},
Ok(FilesystemType::Btrfs) => {
Either::Left((mount, DatasetMetadata{
name: filesystem,
fs_type: FilesystemType::Btrfs,
mount_type: MountType::Local
}))
},
Err(_) => {
Either::Right(mount)
}
}
});
if map_of_datasets.is_empty() {
Err(HttmError::new("httm could not find any valid datasets on the system.").into())
} else {
Ok((map_of_datasets, filter_dirs))
}
}
if let Ok(mount_command) = which("mount") {
parse(&mount_command)
} else {
Err(HttmError::new(
"'mount' command not be found. Make sure the command 'mount' is in your path.",
)
.into())
}
}
pub fn get_common_snap_dir(
map_of_datasets: &MapOfDatasets,
map_of_snaps: &MapOfSnaps,
) -> OptBtrfsCommonSnapDir {
let btrfs_datasets: Vec<&PathBuf> = map_of_datasets
.par_iter()
.filter(|(_mount, dataset_info)| dataset_info.fs_type == FilesystemType::Btrfs)
.map(|(mount, _dataset_info)| mount)
.collect();
if !btrfs_datasets.is_empty() {
let vec_snaps: Vec<&PathBuf> = btrfs_datasets
.into_par_iter()
.filter_map(|mount| map_of_snaps.get(mount))
.flat_map(|snap_info| snap_info)
.collect();
get_common_path(vec_snaps)
} else {
None
}
}