use std::io;
pub fn mount_support() -> io::Result<()> {
check()
}
fn missing(what: &str) -> io::Error {
io::Error::new(io::ErrorKind::Unsupported, what.to_string())
}
#[cfg(target_os = "macos")]
fn check() -> io::Result<()> {
use std::ffi::{CStr, c_char, c_int};
const OK: c_int = 1;
const OTHER_API: c_int = 2;
const OTHER_MAJOR: c_int = 3;
const TOO_OLD: c_int = 4;
unsafe extern "C" {
fn virtx_fuse_t_status() -> c_int;
fn virtx_fuse_t_api() -> c_int;
fn virtx_fuse_t_release() -> *const c_char;
fn virtx_fuse_t_checked() -> *const c_char;
fn virtx_fuse_t_min() -> *const c_char;
}
let (status, api, release, checked, min) = unsafe {
(
virtx_fuse_t_status(),
virtx_fuse_t_api(),
CStr::from_ptr(virtx_fuse_t_release()).to_string_lossy(),
CStr::from_ptr(virtx_fuse_t_checked()).to_string_lossy(),
CStr::from_ptr(virtx_fuse_t_min()).to_string_lossy(),
)
};
let install = format!(
"install FUSE-T {checked} from https://github.com/macos-fuse-t/fuse-t/releases/tag/{checked}"
);
match status {
OK => {}
OTHER_API => {
return Err(missing(&format!(
"the installed FUSE-T speaks libfuse API {api}, and virtx is built for \
libfuse 2 (26 to 29): {install}"
)));
}
OTHER_MAJOR => {
return Err(missing(&format!(
"FUSE-T {release} is installed, and virtx is checked against FUSE-T 1.x \
(last {checked}): {install}"
)));
}
TOO_OLD => {
return Err(missing(&format!(
"FUSE-T {release} is installed, and virtx needs FUSE-T {min} or later: \
brew upgrade --cask fuse-t, or install it from \
https://github.com/macos-fuse-t/fuse-t/releases"
)));
}
_ => {
return Err(missing(
"mounting on macOS needs FUSE-T, which is not installed: \
brew install --cask fuse-t, or the installer from https://www.fuse-t.org",
));
}
}
if !std::path::Path::new("/usr/local/bin/go-nfsv4").is_file() {
return Err(missing(
"FUSE-T is installed without its server (/usr/local/bin/go-nfsv4): \
reinstall it with brew reinstall --cask fuse-t",
));
}
Ok(())
}
#[cfg(windows)]
fn check() -> io::Result<()> {
use winapi::um::libloaderapi::LoadLibraryW;
const INSTALL: &str = "install Dokany 2 from https://github.com/dokan-dev/dokany/releases \
(DokanSetup.exe), or winget install --id dokan-dev.Dokany";
let name: Vec<u16> = "dokan2.dll\0".encode_utf16().collect();
if unsafe { LoadLibraryW(name.as_ptr()) }.is_null() {
return Err(missing(&format!(
"mounting on Windows needs Dokany, and dokan2.dll is not installed: {INSTALL}"
)));
}
let root = std::env::var_os("SystemRoot").unwrap_or_else(|| r"C:\Windows".into());
let driver = std::path::Path::new(&root).join(r"System32\drivers\dokan2.sys");
if !driver.is_file() {
return Err(missing(&format!(
"mounting on Windows needs the Dokany driver, and {} is not installed: {INSTALL}",
driver.display()
)));
}
Ok(())
}
#[cfg(all(unix, not(target_os = "macos")))]
fn check() -> io::Result<()> {
if !std::path::Path::new("/dev/fuse").exists() {
return Err(missing(
"mounting needs /dev/fuse, which this host does not have: load the module with \
modprobe fuse, or start the container with --device /dev/fuse",
));
}
let root = unsafe { libc::geteuid() } == 0;
let on_path = |name: &str| {
std::env::var_os("PATH")
.is_some_and(|path| std::env::split_paths(&path).any(|dir| dir.join(name).is_file()))
};
if !root && !on_path("fusermount3") && !on_path("fusermount") {
return Err(missing(
"mounting as a user other than root needs fusermount3, which is not installed: \
install fuse3 (apt install fuse3, dnf install fuse3)",
));
}
Ok(())
}