use anyhow::{Context, Result};
use std::fs;
use std::process::Command;
pub fn sshfs_installed() -> bool {
std::env::var_os("PATH")
.map(|paths| std::env::split_paths(&paths).any(|dir| dir.join("sshfs").is_file()))
.unwrap_or(false)
}
pub struct Mount {
pub remote: String,
pub local: String,
}
impl Mount {
pub fn describe(&self) -> String {
format!("{} ← {}", self.local, self.remote)
}
}
pub fn list() -> Vec<Mount> {
let Ok(text) = fs::read_to_string("/proc/mounts") else {
return Vec::new();
};
text.lines()
.filter_map(|line| {
let mut f = line.split_whitespace();
let dev = f.next()?;
let mp = f.next()?;
let fstype = f.next()?;
(fstype == "fuse.sshfs").then(|| Mount {
remote: unescape(dev),
local: unescape(mp),
})
})
.collect()
}
pub fn unmount(local: &str) -> Result<()> {
match Command::new("fusermount").args(["-u", local]).output() {
Ok(o) if o.status.success() => Ok(()),
Ok(o) => try_umount(local).map_err(|_| anyhow_from_stderr(&o.stderr)),
Err(_) => try_umount(local).context("fusermount not found and umount failed"),
}
}
pub fn unmount_lazy(local: &str) -> Result<()> {
match Command::new("fusermount").args(["-u", "-z", local]).output() {
Ok(o) if o.status.success() => Ok(()),
Ok(o) => try_umount_lazy(local).map_err(|_| anyhow_from_stderr(&o.stderr)),
Err(_) => try_umount_lazy(local).context("fusermount not found and umount -l failed"),
}
}
fn try_umount_lazy(local: &str) -> Result<()> {
let o = Command::new("umount").args(["-l", local]).output().context("running umount -l")?;
if o.status.success() {
Ok(())
} else {
Err(anyhow_from_stderr(&o.stderr))
}
}
fn try_umount(local: &str) -> Result<()> {
let o = Command::new("umount").arg(local).output().context("running umount")?;
if o.status.success() {
Ok(())
} else {
Err(anyhow_from_stderr(&o.stderr))
}
}
fn anyhow_from_stderr(stderr: &[u8]) -> anyhow::Error {
let msg = String::from_utf8_lossy(stderr).trim().to_string();
if msg.is_empty() {
anyhow::anyhow!("unmount failed")
} else {
anyhow::anyhow!("{msg}")
}
}
fn unescape(s: &str) -> String {
s.replace("\\040", " ")
.replace("\\011", "\t")
.replace("\\012", "\n")
.replace("\\134", "\\")
}