// This example requires fuse 7.11 or later. Run with: // // cargo run --example ioctl --features abi-7-11 /tmp/foobar use clap::{crate_version, Arg, ArgAction, Command}; use fuser::{ FileAttr, FileType, Filesystem, MountOption, ReplyAttr, ReplyData, ReplyDirectory, ReplyEntry, Request, }; use libc::{EINVAL, ENOENT}; use log::debug; use std::ffi::OsStr; use std::time::{Duration, UNIX_EPOCH}; const TTL: Duration = Duration::from_secs(1); // 1 second struct FiocFS { content: Vec, root_attr: FileAttr, fioc_file_attr: FileAttr, } impl FiocFS { fn new() -> Self { let uid = unsafe { libc::getuid() }; let gid = unsafe { libc::getgid() }; let root_attr = FileAttr { ino: 1, size: 0, blocks: 0, atime: UNIX_EPOCH, // 1970-01-01 00:00:00 mtime: UNIX_EPOCH, ctime: UNIX_EPOCH, crtime: UNIX_EPOCH, kind: FileType::Directory, perm: 0o755, nlink: 2, uid, gid, rdev: 0, flags: 0, blksize: 512, }; let fioc_file_attr = FileAttr { ino: 2, size: 0, blocks: 1, atime: UNIX_EPOCH, // 1970-01-01 00:00:00 mtime: UNIX_EPOCH, ctime: UNIX_EPOCH, crtime: UNIX_EPOCH, kind: FileType::RegularFile, perm: 0o644, nlink: 1, uid, gid, rdev: 0, flags: 0, blksize: 512, }; Self { content: vec![], root_attr, fioc_file_attr, } } } impl Filesystem for FiocFS { fn lookup(&mut self, _req: &Request, parent: u64, name: &OsStr, reply: ReplyEntry) { if parent == 1 && name.to_str() == Some("fioc") { reply.entry(&TTL, &self.fioc_file_attr, 0); } else { reply.error(ENOENT); } } fn getattr(&mut self, _req: &Request, ino: u64, _fh: Option, reply: ReplyAttr) { match ino { 1 => reply.attr(&TTL, &self.root_attr), 2 => reply.attr(&TTL, &self.fioc_file_attr), _ => reply.error(ENOENT), } } fn read( &mut self, _req: &Request, ino: u64, _fh: u64, offset: i64, _size: u32, _flags: i32, _lock: Option, reply: ReplyData, ) { if ino == 2 { reply.data(&self.content[offset as usize..]) } else { reply.error(ENOENT); } } fn readdir( &mut self, _req: &Request, ino: u64, _fh: u64, offset: i64, mut reply: ReplyDirectory, ) { if ino != 1 { reply.error(ENOENT); return; } let entries = vec![ (1, FileType::Directory, "."), (1, FileType::Directory, ".."), (2, FileType::RegularFile, "fioc"), ]; for (i, entry) in entries.into_iter().enumerate().skip(offset as usize) { // i + 1 means the index of the next entry if reply.add(entry.0, (i + 1) as i64, entry.1, entry.2) { break; } } reply.ok(); } fn ioctl( &mut self, _req: &Request<'_>, ino: u64, _fh: u64, _flags: u32, cmd: u32, in_data: &[u8], _out_size: u32, reply: fuser::ReplyIoctl, ) { if ino != 2 { reply.error(EINVAL); return; } const FIOC_GET_SIZE: u64 = nix::request_code_read!('E', 0, std::mem::size_of::()); const FIOC_SET_SIZE: u64 = nix::request_code_write!('E', 1, std::mem::size_of::()); match cmd.into() { FIOC_GET_SIZE => { let size_bytes = self.content.len().to_ne_bytes(); reply.ioctl(0, &size_bytes); } FIOC_SET_SIZE => { let new_size = usize::from_ne_bytes(in_data.try_into().unwrap()); self.content = vec![0_u8; new_size]; reply.ioctl(0, &[]); } _ => { debug!("unknown ioctl: {}", cmd); reply.error(EINVAL); } } } } fn main() { let matches = Command::new("hello") .version(crate_version!()) .author("Colin Marc") .arg( Arg::new("MOUNT_POINT") .required(true) .index(1) .help("Act as a client, and mount FUSE at given path"), ) .arg( Arg::new("auto_unmount") .long("auto_unmount") .action(ArgAction::SetTrue) .help("Automatically unmount on process exit"), ) .arg( Arg::new("allow-root") .long("allow-root") .action(ArgAction::SetTrue) .help("Allow root user to access filesystem"), ) .get_matches(); env_logger::init(); let mountpoint = matches.get_one::("MOUNT_POINT").unwrap(); let mut options = vec![MountOption::FSName("fioc".to_string())]; if matches.get_flag("auto_unmount") { options.push(MountOption::AutoUnmount); } if matches.get_flag("allow-root") { options.push(MountOption::AllowRoot); } let fs = FiocFS::new(); fuser::mount2(fs, mountpoint, &options).unwrap(); }