use std::{
ffi::*,
io::{Error, Result},
iter::once,
os::unix::ffi::OsStrExt,
path::Path,
time::SystemTime,
};
use cfg_if::cfg_if;
use crate::{FileInfo, FileType, Filesystem, Request};
#[allow(
dead_code,
unused_variables,
non_camel_case_types,
non_snake_case,
non_upper_case_globals
)]
mod fuse2 {
include!(concat!(env!("OUT_DIR"), "/bindings.rs"));
}
pub struct DirFiller {
func: fuse2::fuse_fill_dir_t,
data: *mut c_void,
}
impl DirFiller {
pub fn push(&mut self, name: &CStr ) -> bool {
match unsafe { self.func.unwrap()(self.data, name.as_ptr(), std::ptr::null(), 0) } {
0 => true,
_ => false,
}
}
}
struct Context {
fs: Box<dyn Filesystem>,
}
pub unsafe fn request() -> (&'static mut dyn Filesystem, Request) {
let ctx = &mut *fuse2::fuse_get_context();
let data = &mut *(ctx.private_data as *mut Context);
let req = Request {
uid: ctx.uid,
gid: ctx.uid,
umask: ctx.umask,
};
(&mut *data.fs, req)
}
fn map_path(path: *const c_char) -> &'static Path {
Path::new(OsStr::from_bytes(
unsafe { CStr::from_ptr(path) }.to_bytes(),
))
}
fn map_time(t: SystemTime) -> fuse2::timespec {
let diff = t.duration_since(SystemTime::UNIX_EPOCH).unwrap();
fuse2::timespec {
tv_sec: diff.as_secs() as i64,
tv_nsec: diff.subsec_nanos() as i64,
}
}
unsafe extern "C" fn fs_getattr(path: *const c_char, st: *mut fuse2::stat) -> c_int {
let path = map_path(path);
let (fs, req) = request();
let st = &mut *st;
match fs.getattr(&req, path) {
Ok(attr) => {
let kind = match attr.kind {
FileType::RegularFile => libc::S_IFREG,
FileType::Directory => libc::S_IFDIR,
FileType::Symlink => libc::S_IFLNK,
FileType::Socket => libc::S_IFSOCK,
FileType::NamedPipe => libc::S_IFIFO,
FileType::CharDevice => libc::S_IFCHR,
FileType::BlockDevice => libc::S_IFBLK,
} as u32;
st.st_ino = attr.ino;
st.st_size = attr.size as i64;
st.st_blocks = attr.blocks as i64;
st.st_atim = map_time(attr.atime);
st.st_mtim = map_time(attr.mtime);
st.st_ctim = map_time(attr.ctime);
cfg_if! {
if #[cfg(target_os = "openbsd")] {
st.__st_birthtim = map_time(attr.btime);
} else if #[cfg(target_os = "freebsd")] {
st.st_birthtim = map_time(attr.btime);
} else {
}
}
st.st_mode = (kind | attr.perm as u32).try_into().unwrap();
st.st_nlink = attr.nlink.try_into().unwrap();
st.st_uid = attr.uid;
st.st_gid = attr.gid;
st.st_rdev = attr.rdev.try_into().unwrap();
st.st_blksize = attr.blksize.try_into().unwrap();
cfg_if! {
if #[cfg(any(target_os = "openbsd", target_os = "freebsd"))] {
st.st_flags = attr.flags;
}
}
0
}
Err(e) => -e.raw_os_error().unwrap_or(libc::EIO),
}
}
impl From<&fuse2::fuse_file_info> for FileInfo {
fn from(info: &fuse2::fuse_file_info) -> Self {
Self {
fh: info.fh,
flags: info.flags,
flush: info.flush() != 0,
direct_io: info.direct_io() != 0,
keep_cache: info.keep_cache() != 0,
nonseekable: info.nonseekable() != 0,
}
}
}
impl FileInfo {
fn write(&self, info: &mut fuse2::fuse_file_info) {
info.fh = self.fh;
info.set_flush(self.flush as u32);
info.set_direct_io(self.direct_io as u32);
info.set_keep_cache(self.keep_cache as u32);
info.set_nonseekable(self.nonseekable as u32);
}
}
unsafe extern "C" fn fs_readdir(
path: *const c_char,
data: *mut c_void,
filler: fuse2::fuse_fill_dir_t,
off: fuse2::off_t,
ffi: *mut fuse2::fuse_file_info,
) -> c_int {
let path = map_path(path);
let (fs, req) = request();
let mut filler = DirFiller { func: filler, data };
let info = FileInfo::from(&*ffi);
match fs.readdir(&req, path, off as u64, &mut filler, &info) {
Ok(()) => 0,
Err(e) => -e.raw_os_error().unwrap_or(libc::EIO),
}
}
unsafe extern "C" fn fs_read(
path: *const c_char,
buf: *mut c_char,
size: usize,
off: fuse2::off_t,
ffi: *mut fuse2::fuse_file_info,
) -> c_int {
let path = map_path(path);
let (fs, req) = request();
let info = FileInfo::from(&*ffi);
let buf = std::slice::from_raw_parts_mut(buf as *mut u8, size);
match fs.read(&req, path, off as u64, buf, &info) {
Ok(n) => n as c_int,
Err(e) => -e.raw_os_error().unwrap_or(libc::EIO),
}
}
unsafe extern "C" fn fs_open(path: *const c_char, ffi: *mut fuse2::fuse_file_info) -> c_int {
let path = map_path(path);
let (fs, req) = request();
let mut info = FileInfo::from(&*ffi);
match fs.open(&req, path, &mut info) {
Ok(()) => {
info.write(&mut *ffi);
0
}
Err(e) => -e.raw_os_error().unwrap_or(libc::EIO),
}
}
unsafe extern "C" fn fs_opendir(path: *const c_char, ffi: *mut fuse2::fuse_file_info) -> c_int {
let path = map_path(path);
let (fs, req) = request();
let mut info = FileInfo::from(&*ffi);
match fs.opendir(&req, path, &mut info) {
Ok(()) => {
info.write(&mut *ffi);
0
}
Err(e) => -e.raw_os_error().unwrap_or(libc::EIO),
}
}
unsafe extern "C" fn fs_statfs(path: *const c_char, st: *mut fuse2::statvfs) -> c_int {
let path = map_path(path);
let st = &mut *st;
let (fs, req) = request();
match fs.statfs(&req, path) {
Ok(s) => {
st.f_bsize = s.bsize.into();
st.f_frsize = s.frsize.into();
st.f_blocks = s.blocks;
st.f_bfree = s.bfree;
st.f_bavail = s.bavail;
st.f_files = s.files;
st.f_ffree = s.ffree;
st.f_favail = s.favail;
0
}
Err(e) => -e.raw_os_error().unwrap_or(libc::EIO),
}
}
unsafe extern "C" fn fs_init(_info: *mut fuse2::fuse_conn_info) -> *mut c_void {
let ctx = &mut *fuse2::fuse_get_context();
let data = &mut *(ctx.private_data as *mut Context);
let req = Request {
uid: ctx.uid,
gid: ctx.uid,
umask: ctx.umask,
};
data.fs.init(&req);
ctx.private_data
}
unsafe extern "C" fn fs_destroy(_ptr: *mut c_void) {
let (fs, _req) = request();
fs.destroy();
}
unsafe extern "C" fn fs_readlink(path: *const c_char, buf: *mut c_char, size: usize) -> c_int {
let path = map_path(path);
let buf = std::slice::from_raw_parts_mut(buf as *mut u8, size);
let (fs, req) = request();
match fs.readlink(&req, path, buf) {
Ok(()) => 0,
Err(e) => -e.raw_os_error().unwrap_or(libc::EIO),
}
}
unsafe extern "C" fn fs_release(path: *const c_char, ffi: *mut fuse2::fuse_file_info) -> c_int {
let path = map_path(path);
let info = FileInfo::from(&*ffi);
let (fs, req) = request();
match fs.release(&req, path, &info) {
Ok(()) => 0,
Err(e) => -e.raw_os_error().unwrap_or(libc::EIO),
}
}
unsafe extern "C" fn fs_releasedir(path: *const c_char, ffi: *mut fuse2::fuse_file_info) -> c_int {
let path = map_path(path);
let info = FileInfo::from(&*ffi);
let (fs, req) = request();
match fs.releasedir(&req, path, &info) {
Ok(()) => 0,
Err(e) => -e.raw_os_error().unwrap_or(libc::EIO),
}
}
static FSOPS: fuse2::fuse_operations = fuse2::fuse_operations {
access: None,
bmap: None,
getattr: Some(fs_getattr),
readlink: Some(fs_readlink),
getdir: None,
mknod: None,
mkdir: None,
unlink: None,
rmdir: None,
symlink: None,
rename: None,
link: None,
chmod: None,
chown: None,
truncate: None,
utime: None,
open: Some(fs_open),
read: Some(fs_read),
write: None,
statfs: Some(fs_statfs),
flush: None,
release: Some(fs_release),
fsync: None,
setxattr: None,
getxattr: None,
listxattr: None,
removexattr: None,
opendir: Some(fs_opendir),
readdir: Some(fs_readdir),
releasedir: Some(fs_releasedir),
fsyncdir: None,
init: Some(fs_init),
destroy: Some(fs_destroy),
create: None,
ftruncate: None,
fgetattr: None,
lock: None,
utimens: None,
..unsafe { std::mem::zeroed() }
};
pub fn xmount(mp: &Path, fs: Box<dyn Filesystem>, opts: Vec<CString>) -> Result<()> {
let mut mp = mp.as_os_str().as_bytes().to_vec();
mp.push(b'\0');
let Ok(mp) = CString::from_vec_with_nul(mp) else {
return Err(Error::from_raw_os_error(libc::EINVAL));
};
let ctx = Box::new(Context { fs });
let mut args = opts
.into_iter()
.chain(once(mp))
.map(|s| s.into_raw())
.chain(once(std::ptr::null_mut()))
.collect::<Vec<_>>();
let argc = args.len() as i32 - 1;
let argv = args.as_mut_ptr();
let ctx = Box::into_raw(ctx) as *mut c_void;
let ec;
cfg_if! {
if #[cfg(any(target_os = "freebsd", target_os = "linux"))] {
ec = unsafe {
fuse2::fuse_main_real(argc, argv, &FSOPS, std::mem::size_of_val(&FSOPS), ctx)
};
} else {
ec = unsafe {
fuse2::fuse_main(argc, argv, &FSOPS, ctx)
};
}
};
match ec {
0 => Ok(()),
_ => Err(Error::from_raw_os_error(libc::EIO)),
}
}