fuse2rs 0.0.3

FUSE2 bindings for Rust (mainly for OpenBSD)
Documentation
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 /* TODO: off, st */) -> 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,

	// this is _very_ ugly
	..unsafe { std::mem::zeroed() }
};

pub fn xmount(mp: &Path, fs: Box<dyn Filesystem>, opts: Vec<CString>) -> Result<()> {
	// TODO: this sucks, find something better
	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)),
	}
}