// SPDX-FileCopyrightText: edef // SPDX-License-Identifier: OSL-3.0 use { lazy_static::lazy_static, libc::{c_int, ENOENT, ENOSYS, EROFS}, log::{debug, warn}, std::{ time::{Duration, SystemTime, UNIX_EPOCH}, }, }; lazy_static! { static ref EPOCH_PLUS_ONE: SystemTime = UNIX_EPOCH + Duration::from_secs(1); } enum Kind { Link, File, Directory, } fn file_attr(ino: u64, kind: Kind, size: u64) -> fuser::FileAttr { let blksize = 512; fuser::FileAttr { /// Inode number ino, /// Size in bytes size, /// Size in blocks // TODO(edef): switch to u64::div_ceil blocks: (size + blksize as u64 - 1) / (blksize as u64), /// Time of last access atime: *EPOCH_PLUS_ONE, /// Time of last modification mtime: *EPOCH_PLUS_ONE, /// Time of last change ctime: *EPOCH_PLUS_ONE, /// Time of creation (macOS only) crtime: *EPOCH_PLUS_ONE, /// Kind of file (directory, file, pipe, etc) kind: match kind { Kind::Directory => fuser::FileType::Directory, Kind::File => fuser::FileType::RegularFile, Kind::Link => fuser::FileType::Symlink, }, /// Permissions perm: match kind { Kind::Directory => 0o755, _ => 0o644, }, /// Number of hard links nlink: 1, /// User id uid: 1000, /// Group id gid: 100, /// Rdev rdev: 0, /// Block size blksize, /// Flags (macOS only, see chflags(2)) flags: 0, } } fn main() { env_logger::init(); let store = fossil::Store::open("fossil.db").unwrap(); fuser::mount2( Filesystem::open(store), "mnt", &[fuser::MountOption::DefaultPermissions], ) .unwrap(); } struct Filesystem { store: fossil::Store, } impl Filesystem { fn open(store: fossil::Store) -> Filesystem { Filesystem { store } } } impl fuser::Filesystem for Filesystem { fn init( &mut self, _req: &fuser::Request<'_>, _config: &mut fuser::KernelConfig, ) -> Result<(), c_int> { Ok(()) } fn destroy(&mut self) {} fn lookup( &mut self, _req: &fuser::Request<'_>, parent: u64, name: &std::ffi::OsStr, reply: fuser::ReplyEntry, ) { if parent != 1 { warn!( "[Not Implemented] lookup(parent: {:#x?}, name {:?})", parent, name ); reply.error(ENOSYS); return; } if name != "hello" { reply.error(ENOENT); return; } reply.entry( &Duration::ZERO, &file_attr(2, Kind::File, "world".len() as u64), 0, ); } fn forget(&mut self, _req: &fuser::Request<'_>, _ino: u64, _nlookup: u64) {} fn getattr(&mut self, _req: &fuser::Request<'_>, ino: u64, reply: fuser::ReplyAttr) { match ino { 1 => reply.attr(&Duration::ZERO, &file_attr(ino, Kind::Directory, 2)), 2 => reply.attr( &Duration::ZERO, &file_attr(ino, Kind::File, "world".len() as u64), ), _ => { warn!("[Not Implemented] getattr(ino: {:#x?})", ino); reply.error(ENOSYS); } } } fn setattr( &mut self, _req: &fuser::Request<'_>, _ino: u64, _mode: Option, _uid: Option, _gid: Option, _size: Option, _atime: Option, _mtime: Option, _ctime: Option, _fh: Option, _crtime: Option, _chgtime: Option, _bkuptime: Option, _flags: Option, reply: fuser::ReplyAttr, ) { reply.error(EROFS); } fn readlink(&mut self, _req: &fuser::Request<'_>, ino: u64, reply: fuser::ReplyData) { debug!("[Not Implemented] readlink(ino: {:#x?})", ino); reply.error(ENOSYS); } fn mknod( &mut self, _req: &fuser::Request<'_>, _parent: u64, _name: &std::ffi::OsStr, _mode: u32, _umask: u32, _rdev: u32, reply: fuser::ReplyEntry, ) { reply.error(EROFS); } fn mkdir( &mut self, _req: &fuser::Request<'_>, _parent: u64, _name: &std::ffi::OsStr, _mode: u32, _umask: u32, reply: fuser::ReplyEntry, ) { reply.error(EROFS); } fn unlink( &mut self, _req: &fuser::Request<'_>, _parent: u64, _name: &std::ffi::OsStr, reply: fuser::ReplyEmpty, ) { reply.error(EROFS); } fn rmdir( &mut self, _req: &fuser::Request<'_>, parent: u64, name: &std::ffi::OsStr, reply: fuser::ReplyEmpty, ) { debug!( "[Not Implemented] rmdir(parent: {:#x?}, name: {:?})", parent, name, ); reply.error(ENOSYS); } fn symlink( &mut self, _req: &fuser::Request<'_>, _parent: u64, _name: &std::ffi::OsStr, _link: &std::path::Path, reply: fuser::ReplyEntry, ) { reply.error(EROFS); } fn rename( &mut self, _req: &fuser::Request<'_>, _parent: u64, _name: &std::ffi::OsStr, _newparent: u64, _newname: &std::ffi::OsStr, _flags: u32, reply: fuser::ReplyEmpty, ) { reply.error(EROFS); } fn link( &mut self, _req: &fuser::Request<'_>, _ino: u64, _newparent: u64, _newname: &std::ffi::OsStr, reply: fuser::ReplyEntry, ) { reply.error(EROFS); } fn open(&mut self, _req: &fuser::Request<'_>, _ino: u64, _flags: i32, reply: fuser::ReplyOpen) { reply.opened(0, 0); } fn read( &mut self, _req: &fuser::Request<'_>, ino: u64, fh: u64, offset: i64, size: u32, flags: i32, lock_owner: Option, reply: fuser::ReplyData, ) { match ino { 2 => { let offset = offset as usize; let size = size as usize; let content = b"world"; let mut buffer = content.get(offset..).unwrap_or_default(); if buffer.len() > size { buffer = &buffer[..size]; } reply.data(buffer); } _ => { warn!( "[Not Implemented] read(ino: {:#x?}, fh: {}, offset: {}, size: {}, \ flags: {:#x?}, lock_owner: {:?})", ino, fh, offset, size, flags, lock_owner ); reply.error(ENOSYS); } } } fn write( &mut self, _req: &fuser::Request<'_>, _ino: u64, _fh: u64, _offset: i64, _data: &[u8], _write_flags: u32, _flags: i32, _lock_owner: Option, reply: fuser::ReplyWrite, ) { reply.error(EROFS); } fn flush( &mut self, _req: &fuser::Request<'_>, ino: u64, fh: u64, lock_owner: u64, reply: fuser::ReplyEmpty, ) { debug!( "[Not Implemented] flush(ino: {:#x?}, fh: {}, lock_owner: {:?})", ino, fh, lock_owner ); reply.error(ENOSYS); } fn release( &mut self, _req: &fuser::Request<'_>, _ino: u64, _fh: u64, _flags: i32, _lock_owner: Option, _flush: bool, reply: fuser::ReplyEmpty, ) { reply.ok(); } fn fsync( &mut self, _req: &fuser::Request<'_>, ino: u64, fh: u64, datasync: bool, reply: fuser::ReplyEmpty, ) { debug!( "[Not Implemented] fsync(ino: {:#x?}, fh: {}, datasync: {})", ino, fh, datasync ); reply.error(ENOSYS); } fn opendir( &mut self, _req: &fuser::Request<'_>, _ino: u64, _flags: i32, reply: fuser::ReplyOpen, ) { reply.opened(0, 0); } fn readdir( &mut self, _req: &fuser::Request<'_>, ino: u64, fh: u64, offset: i64, mut reply: fuser::ReplyDirectory, ) { if ino != 1 { warn!( "[Not Implemented] readdir(ino: {:#x?}, fh: {}, offset: {})", ino, fh, offset ); reply.error(ENOSYS); return; } let children = &[ (ino, fuser::FileType::Directory, "."), (ino, fuser::FileType::Directory, ".."), (0x2, fuser::FileType::RegularFile, "hello"), ]; for (offset, &(ino, kind, name)) in children.iter().enumerate().skip(offset as usize) { if reply.add(ino, (offset + 1) as i64, kind, name) { break; } } reply.ok(); } fn readdirplus( &mut self, _req: &fuser::Request<'_>, ino: u64, fh: u64, offset: i64, reply: fuser::ReplyDirectoryPlus, ) { debug!( "[Not Implemented] readdirplus(ino: {:#x?}, fh: {}, offset: {})", ino, fh, offset ); reply.error(ENOSYS); } fn releasedir( &mut self, _req: &fuser::Request<'_>, _ino: u64, _fh: u64, _flags: i32, reply: fuser::ReplyEmpty, ) { reply.ok(); } fn fsyncdir( &mut self, _req: &fuser::Request<'_>, ino: u64, fh: u64, datasync: bool, reply: fuser::ReplyEmpty, ) { debug!( "[Not Implemented] fsyncdir(ino: {:#x?}, fh: {}, datasync: {})", ino, fh, datasync ); reply.error(ENOSYS); } fn statfs(&mut self, _req: &fuser::Request<'_>, _ino: u64, reply: fuser::ReplyStatfs) { reply.statfs(0, 0, 0, 0, 0, 512, 255, 0); } fn setxattr( &mut self, _req: &fuser::Request<'_>, _ino: u64, _name: &std::ffi::OsStr, _value: &[u8], _flags: i32, _position: u32, reply: fuser::ReplyEmpty, ) { reply.error(EROFS); } fn getxattr( &mut self, _req: &fuser::Request<'_>, _ino: u64, _name: &std::ffi::OsStr, _size: u32, reply: fuser::ReplyXattr, ) { reply.error(ENOSYS); } fn listxattr( &mut self, _req: &fuser::Request<'_>, _ino: u64, _size: u32, reply: fuser::ReplyXattr, ) { reply.error(ENOSYS); } fn removexattr( &mut self, _req: &fuser::Request<'_>, _ino: u64, _name: &std::ffi::OsStr, reply: fuser::ReplyEmpty, ) { reply.error(EROFS); } fn access(&mut self, _req: &fuser::Request<'_>, ino: u64, mask: i32, reply: fuser::ReplyEmpty) { debug!("[Not Implemented] access(ino: {:#x?}, mask: {})", ino, mask); reply.error(ENOSYS); } fn create( &mut self, _req: &fuser::Request<'_>, _parent: u64, _name: &std::ffi::OsStr, _mode: u32, _umask: u32, _flags: i32, reply: fuser::ReplyCreate, ) { reply.error(EROFS); } fn getlk( &mut self, _req: &fuser::Request<'_>, _ino: u64, _fh: u64, _lock_owner: u64, _start: u64, _end: u64, _typ: i32, _pid: u32, reply: fuser::ReplyLock, ) { reply.error(ENOSYS); } fn setlk( &mut self, _req: &fuser::Request<'_>, _ino: u64, _fh: u64, _lock_owner: u64, _start: u64, _end: u64, _typ: i32, _pid: u32, _sleep: bool, reply: fuser::ReplyEmpty, ) { reply.error(ENOSYS); } fn bmap( &mut self, _req: &fuser::Request<'_>, _ino: u64, _blocksize: u32, _idx: u64, reply: fuser::ReplyBmap, ) { reply.error(ENOSYS); } fn ioctl( &mut self, _req: &fuser::Request<'_>, _ino: u64, _fh: u64, _flags: u32, _cmd: u32, _in_data: &[u8], _out_size: u32, reply: fuser::ReplyIoctl, ) { reply.error(ENOSYS); } fn fallocate( &mut self, _req: &fuser::Request<'_>, _ino: u64, _fh: u64, _offset: i64, _length: i64, _mode: i32, reply: fuser::ReplyEmpty, ) { reply.error(EROFS); } fn lseek( &mut self, _req: &fuser::Request<'_>, ino: u64, fh: u64, offset: i64, whence: i32, reply: fuser::ReplyLseek, ) { debug!( "[Not Implemented] lseek(ino: {:#x?}, fh: {}, offset: {}, whence: {})", ino, fh, offset, whence ); reply.error(ENOSYS); } fn copy_file_range( &mut self, _req: &fuser::Request<'_>, _ino_in: u64, _fh_in: u64, _offset_in: i64, _ino_out: u64, _fh_out: u64, _offset_out: i64, _len: u64, _flags: u32, reply: fuser::ReplyWrite, ) { reply.error(EROFS); } }