use std::ffi::c_int; use std::io::{Read, Write}; use std::rc::Rc; use std::time::Duration; use fat_bits::dir::DirEntry; use fuser::{FileType, Filesystem}; use libc::{EBADF, EINVAL, EIO, EISDIR, ENOENT, ENOSYS, ENOTDIR}; use log::{debug, error}; use crate::FatFuse; const TTL: Duration = Duration::from_secs(1); impl Filesystem for FatFuse { fn init( &mut self, _req: &fuser::Request<'_>, _config: &mut fuser::KernelConfig, ) -> Result<(), c_int> { Ok(()) } fn destroy(&mut self) { debug!("inode_table: {}", self.inode_table.len()); debug!("ino_by_first_cluster: {}", self.ino_by_first_cluster.len()); for (&first_cluster, &ino) in self.ino_by_first_cluster.iter() { debug!("{} -> {}", first_cluster, ino); } debug!("ino_by_fh: {}", self.ino_by_fh.len()); debug!("ino_by_path: {}", self.ino_by_path.len()); } fn lookup( &mut self, _req: &fuser::Request<'_>, parent: u64, name: &std::ffi::OsStr, reply: fuser::ReplyEntry, ) { let Some(name) = name.to_str() else { // TODO: add proper handling of non-utf8 strings debug!("cannot convert OsStr {:?} to str", name); reply.error(ENOSYS); return; }; debug!("looking up file {} with parent ino {}", name, parent); let Some(parent_inode) = self.get_inode(parent).cloned() else { // parent inode does not exist // TODO: how can we make sure this does not happed? // TODO: panic? debug!("could not find inode for parent ino {}", parent); reply.error(ENOENT); return; }; let parent_inode = parent_inode.borrow(); let dir_entry: DirEntry = match parent_inode .dir_iter(&self.fat_fs) .and_then(|mut dir_iter| { dir_iter .find(|dir_entry| &dir_entry.name_string() == name) .ok_or(ENOENT) }) { Ok(dir_entry) => dir_entry, Err(err) => { debug!("error: {}", err); reply.error(err); return; } }; let inode = self.get_or_make_inode_by_dir_entry( &dir_entry, parent_inode.ino(), parent_inode.path(), ); let mut inode = inode.borrow_mut(); let attr = inode.file_attr(); let generation = inode.generation(); reply.entry(&TTL, &attr, generation as u64); inode.inc_ref_count(); } fn forget(&mut self, _req: &fuser::Request<'_>, ino: u64, nlookup: u64) { debug!("forgetting ino {} ({} times)", ino, nlookup); let Some(inode) = self.get_inode(ino).cloned() else { debug!("tried to forget {} refs of inode {}, but was not found", ino, nlookup); return; }; let mut inode_ref = inode.borrow_mut(); if inode_ref.dec_ref_count(nlookup) == 0 { debug!("dropping inode {}", inode_ref.ino()); drop(inode_ref); // no more references, drop inode self.drop_inode(inode); } } fn getattr( &mut self, _req: &fuser::Request<'_>, ino: u64, fh: Option, reply: fuser::ReplyAttr, ) { let inode = if let Some(fh) = fh { let Some(inode) = self.get_inode_by_fh(fh) else { reply.error(EBADF); return; }; inode } else if let Some(inode) = self.get_inode(ino).cloned() { inode } else { reply.error(ENOENT); return; }; let inode = inode.borrow(); let attr = inode.file_attr(); reply.attr(&TTL, &attr); } #[allow(unused_variables)] 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, ) { // debug!( // "[Not Implemented] setattr(ino: {:#x?}, mode: {:?}, uid: {:?}, \ // gid: {:?}, size: {:?}, fh: {:?}, flags: {:?})", // ino, mode, uid, gid, size, fh, flags // ); // reply.error(ENOSYS); // return; // TODO: implement this properly let Some(inode) = self.get_inode(ino) else { debug!("tried to get inode {ino}, but not found"); reply.error(ENOENT); return; }; reply.attr(&TTL, &inode.borrow().file_attr()); } 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, ) { debug!( "[Not Implemented] mknod(parent: {:#x?}, name: {:?}, mode: {}, \ umask: {:#x?}, rdev: {})", parent, name, mode, umask, rdev ); reply.error(ENOSYS); } fn mkdir( &mut self, _req: &fuser::Request<'_>, parent: u64, name: &std::ffi::OsStr, mode: u32, umask: u32, reply: fuser::ReplyEntry, ) { debug!( "[Not Implemented] mkdir(parent: {:#x?}, name: {:?}, mode: {}, umask: {:#x?})", parent, name, mode, umask ); reply.error(ENOSYS); } fn unlink( &mut self, _req: &fuser::Request<'_>, parent: u64, name: &std::ffi::OsStr, reply: fuser::ReplyEmpty, ) { debug!("[Not Implemented] unlink(parent: {:#x?}, name: {:?})", parent, name,); reply.error(ENOSYS); } 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 rename( &mut self, _req: &fuser::Request<'_>, parent: u64, name: &std::ffi::OsStr, newparent: u64, newname: &std::ffi::OsStr, flags: u32, reply: fuser::ReplyEmpty, ) { debug!( "[Not Implemented] rename(parent: {:#x?}, name: {:?}, newparent: {:#x?}, \ newname: {:?}, flags: {})", parent, name, newparent, newname, flags, ); reply.error(ENOSYS); } fn open(&mut self, _req: &fuser::Request<'_>, ino: u64, _flags: i32, reply: fuser::ReplyOpen) { if !self.inode_table.contains_key(&ino) { reply.error(ENOENT); return; } let fh = self.next_fh(); if let Some(old_ino) = self.ino_by_fh.insert(fh, ino) { debug!("fh {} was associated with ino {}, now with ino {}", fh, old_ino, ino); } debug!("opened inode {}: fh {}", ino, fh); reply.opened(fh, 0); } fn read( &mut self, _req: &fuser::Request<'_>, ino: u64, fh: u64, offset: i64, mut size: u32, _flags: i32, _lock_owner: Option, reply: fuser::ReplyData, ) { debug!("trying to read {size} bytes at offset {offset} from inode {ino} (fh: {fh})"); if offset < 0 { debug!("tried to read with negative offset {offset}"); reply.error(EINVAL); return; } let offset = offset as u64; let Some(inode) = self.get_inode_by_fh(fh) else { debug!("fh {fh} is not associated by any inode"); reply.error(EBADF); return; }; let inode = inode.borrow(); if inode.ino() != ino { debug!("fh {fh} is associated with inode {} instead of {ino}", inode.ino()); reply.error(EINVAL); return; } if !inode.is_file() { debug!("tried to use read on directory {ino}"); reply.error(EISDIR); return; } let file_size = inode.size(); debug!("file_size: {}", file_size); if offset > file_size { debug!("tried to read after EOF"); // offset is beyond file size, nothing to do here, just bail reply.data(&[]); return; } if offset + size as u64 > file_size { // tried to read beyond end of file, truncate size so we don't overflow debug!( "tried to read {size} bytes at offset {offset} from inode {ino}, but size is only {file_size}" ); size = (file_size - offset) as u32; debug!("truncated read request size to {size}"); } let mut reader = match inode.file_reader(&self.fat_fs) { Ok(reader) => reader, Err(err) => { reply.error(err); return; } }; if reader.skip(offset) != offset { // this should not happen as we checked for valid bounds earlier reply.error(EIO); return; } let mut buf = vec![0; size as usize]; let bytes_read = match reader.read(&mut buf) { Ok(n) => n, Err(err) => { error!("error while reading: {err}"); reply.error(EIO); return; } }; if bytes_read != size as usize { debug!("expected to read {size} bytes, but only read {bytes_read}"); } reply.data(&buf[..bytes_read]); } 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, ) { debug!("new write request: ino={ino} fh={fh} offset={offset} data={data:?}"); if offset < 0 { debug!("tried to write with negative offset {offset}"); reply.error(EINVAL); return; } let offset = offset as u64; let Some(inode) = self.get_inode_by_fh(fh) else { debug!("no inode associated with fh {fh} (given ino: {ino}"); reply.error(EBADF); return; }; // borrow mut so we can potentially update the file size later let mut inode = inode.borrow_mut(); if inode.is_read_only() { reply.error(EBADF); return; } if inode.ino() != ino { debug!("fh {fh} points to ino {}, but ino {ino} was given", inode.ino()); reply.error(EINVAL); return; } if !inode.is_file() { debug!("tried to use read on directory {ino}"); reply.error(EISDIR); return; } let mut writer = match inode.file_writer(&self.fat_fs) { Ok(writer) => writer, Err(err) => { reply.error(err); return; } }; // if writer.skip(offset) != offset { // // writer is at EOF, bail // } let cur_offset = writer.skip(offset); // can't seek more than we requested assert!(cur_offset <= offset); let mut bytes_written = 0; if cur_offset < offset { // tried to set offset beyond EOF // fill with zeros let zeros = vec![0; (offset - cur_offset) as usize]; debug!("writing {} zeros", zeros.len()); if let Err(err) = writer.write_all(&zeros) { debug!("writing zeros returned error: {err}"); reply.error(EIO); return; } bytes_written += zeros.len(); } if let Err(err) = writer.write_all(&data) { debug!("writing data returned error: {err}"); reply.error(EIO); return; } bytes_written += data.len(); if offset + bytes_written as u64 > inode.size() { debug!("write increased file size, updating..."); let new_file_size = offset + bytes_written as u64; if let Err(err) = inode.update_size(&self.fat_fs, new_file_size) { debug!("error while updating size: {err}"); reply.error(EIO); return; } } reply.written(bytes_written as u32); } 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); debug!("flushing ino={ino} fh={fh}"); let Some(&found_ino) = self.ino_by_fh.get(&fh) else { debug!("expected fh {fh} to be mapped to ino {ino}, but not found instead"); reply.error(EBADF); return; }; if found_ino != ino { debug!( "expected fh {fh} to be mapped to ino {ino}, but was mapped to {found_ino} instead" ); reply.error(EBADF); return; } reply.ok(); } fn release( &mut self, _req: &fuser::Request<'_>, ino: u64, fh: u64, _flags: i32, _lock_owner: Option, _flush: bool, reply: fuser::ReplyEmpty, ) { let Some(found_ino) = self.ino_by_fh.remove(&fh) else { debug!("tried to release fh {fh} with ino {ino}, but no ino was found in mapping"); reply.error(EBADF); return; }; if found_ino != ino { debug!("tried to release fh {fh} with ino {ino}, but found ino is {found_ino} instead"); reply.error(EINVAL); return; } 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, ) { let fh = self.next_fh(); if let Some(old_ino) = self.ino_by_fh.insert(fh, ino) { debug!("fh {} was already associated with ino {}, now with ino {}", fh, old_ino, ino); } reply.opened(fh, 0); } fn readdir( &mut self, _req: &fuser::Request<'_>, ino: u64, fh: u64, offset: i64, mut reply: fuser::ReplyDirectory, ) { let Ok(mut offset): Result = offset.try_into() else { return; }; let Some(dir_inode) = self.get_inode_by_fh(fh) else { debug!("could not find inode accociated with fh {} (ino: {})", fh, ino); reply.error(EBADF); return; }; let dir_inode = dir_inode.borrow(); if dir_inode.ino() != ino { debug!( "ino {} of inode associated with fh {} does not match given ino {}", dir_inode.ino(), fh, ino ); reply.error(EINVAL); return; } let mut _next_idx = 1; let mut next_offset = || { let next_idx = _next_idx; _next_idx += 1; next_idx }; if dir_inode.is_root() { if offset == 0 { debug!("adding . to root dir"); if reply.add(1, next_offset(), FileType::Directory, ".") { return; } } else { offset -= 1; } if offset == 0 { debug!("adding .. to root dir"); if reply.add(1, next_offset(), FileType::Directory, "..") { return; } } else { offset -= 1; } } let Ok(dir_iter) = dir_inode.dir_iter(&self.fat_fs) else { reply.error(ENOTDIR); return; }; // need to drop dir_iter here so we can borrow self mut again // also skip over `offset` entries let dirs: Vec = dir_iter.skip(offset).collect(); let dir_ino = dir_inode.ino(); let dir_path = dir_inode.path(); for dir_entry in dirs { let name = dir_entry.name_string(); let inode = self.get_or_make_inode_by_dir_entry(&dir_entry, dir_ino, Rc::clone(&dir_path)); let inode = inode.borrow(); debug!("adding entry {} (ino: {})", name, inode.ino()); if reply.add(ino, next_offset(), inode.kind().into(), name) { return; } } 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, ) { let Some(ino) = self.ino_by_fh.remove(&fh) else { debug!("can't find inode {} by fh {}", ino, fh); reply.error(EBADF); return; }; let Some(inode) = self.get_inode(ino) else { debug!("ino {} not associated with an inode", ino); reply.ok(); return; }; let inode = inode.borrow(); if inode.ino() != ino { debug!( "inode with ino {}, associated with fh {}, does not have expected ino {}", inode.ino(), fh, ino ); } 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 create( &mut self, _req: &fuser::Request<'_>, parent: u64, name: &std::ffi::OsStr, mode: u32, umask: u32, flags: i32, reply: fuser::ReplyCreate, ) { debug!( "[Not Implemented] create(parent: {:#x?}, name: {:?}, mode: {}, umask: {:#x?}, \ flags: {:#x?})", parent, name, mode, umask, flags ); reply.error(ENOSYS); } 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); } }