ls/ll works now

This commit is contained in:
Moritz Gmeiner 2025-07-31 01:14:28 +02:00
commit 18c45d5141
3 changed files with 332 additions and 81 deletions

View file

@ -2,13 +2,15 @@ use std::ffi::c_int;
use std::time::Duration; use std::time::Duration;
use fat_bits::dir::DirEntry; use fat_bits::dir::DirEntry;
use fuser::Filesystem; use fuser::{FileType, Filesystem};
use libc::{EIO, ENOENT, ENOSYS, EPERM}; use libc::{EINVAL, EIO, ENOENT, ENOSYS, ENOTDIR, EPERM};
use log::{debug, warn}; use log::{debug, warn};
use crate::FatFuse; use crate::FatFuse;
use crate::inode::Inode; use crate::inode::Inode;
const TTL: Duration = Duration::from_secs(1);
impl Filesystem for FatFuse { impl Filesystem for FatFuse {
fn init( fn init(
&mut self, &mut self,
@ -32,14 +34,18 @@ impl Filesystem for FatFuse {
let Some(name) = name.to_str() else { let Some(name) = name.to_str() else {
// TODO: add proper handling of non-utf8 strings // TODO: add proper handling of non-utf8 strings
debug!("cannot convert OsStr {:?} to str", name);
reply.error(ENOSYS); reply.error(ENOSYS);
return; return;
}; };
debug!("looking up file {} with parent ino {}", name, parent);
let Some(parent_inode) = self.get_inode(parent) else { let Some(parent_inode) = self.get_inode(parent) else {
// parent inode does not exist // parent inode does not exist
// TODO: how can we make sure this does not happed? // TODO: how can we make sure this does not happed?
// TODO: panic? // TODO: panic?
debug!("could not find inode for parent ino {}", parent);
reply.error(EIO); reply.error(EIO);
return; return;
@ -67,29 +73,33 @@ impl Filesystem for FatFuse {
}) { }) {
Ok(dir_entry) => dir_entry, Ok(dir_entry) => dir_entry,
Err(err) => { Err(err) => {
debug!("error: {}", err);
reply.error(err); reply.error(err);
return; return;
} }
}; };
let inode = match self.get_inode_by_first_cluster(dir_entry.first_cluster()) { // let inode = match self.get_inode_by_first_cluster(dir_entry.first_cluster()) {
Some(inode) => inode, // Some(inode) => inode,
None => { // None => {
// no inode found, make a new one // // no inode found, make a new one
// let ino = self.next_ino();
let ino = self.next_ino(); // let inode = Inode::new(&self.fat_fs, &dir_entry, ino, self.uid, self.gid);
let inode = Inode::new(&self.fat_fs, dir_entry, ino, self.uid, self.gid); // self.insert_inode(inode)
// }
// };
self.insert_inode(inode) let inode = self.get_or_make_inode_by_dir_entry(&dir_entry);
}
};
let attr = inode.file_attr(); let attr = inode.file_attr();
let generation = inode.generation(); let generation = inode.generation();
reply.entry(&Duration::from_secs(1), &attr, generation as u64); reply.entry(&TTL, &attr, generation as u64);
inode.refcount_inc();
} }
fn forget(&mut self, _req: &fuser::Request<'_>, ino: u64, nlookup: u64) { fn forget(&mut self, _req: &fuser::Request<'_>, ino: u64, nlookup: u64) {
@ -99,18 +109,9 @@ impl Filesystem for FatFuse {
return; return;
}; };
let ref_count = inode.ref_count_mut(); // *ref_count = ref_count.saturating_sub(nlookup);
if *ref_count < nlookup { if inode.refcount_dec(nlookup) == 0 {
debug!(
"tried to forget {} refs of inode {}, but ref_count is only {}",
nlookup, ino, *ref_count
);
}
*ref_count = ref_count.saturating_sub(nlookup);
if *ref_count == 0 {
// no more references, drop inode // no more references, drop inode
self.drop_inode(ino); self.drop_inode(ino);
} }
@ -123,8 +124,28 @@ impl Filesystem for FatFuse {
fh: Option<u64>, fh: Option<u64>,
reply: fuser::ReplyAttr, reply: fuser::ReplyAttr,
) { ) {
warn!("[Not Implemented] getattr(ino: {:#x?}, fh: {:#x?})", ino, fh); // warn!("[Not Implemented] getattr(ino: {:#x?}, fh: {:#x?})", ino, fh);
reply.error(ENOSYS); // reply.error(ENOSYS);
let inode = if let Some(fh) = fh {
let Some(inode) = self.get_inode_by_fh(fh) else {
reply.error(EIO);
return;
};
inode
} else if let Some(inode) = self.get_inode(ino) {
inode
} else {
reply.error(EIO);
return;
};
let attr = inode.file_attr();
reply.attr(&TTL, &attr);
} }
fn setattr( fn setattr(
@ -232,27 +253,19 @@ impl Filesystem for FatFuse {
reply.error(ENOSYS); reply.error(ENOSYS);
} }
fn link( fn open(&mut self, _req: &fuser::Request<'_>, ino: u64, _flags: i32, reply: fuser::ReplyOpen) {
&mut self, if !self.inode_table.contains_key(&ino) {
_req: &fuser::Request<'_>, reply.error(EINVAL);
ino: u64, return;
newparent: u64, }
newname: &std::ffi::OsStr,
reply: fuser::ReplyEntry, let fh = self.next_fh();
) {
debug!(
"[Not Implemented] link(ino: {:#x?}, newparent: {:#x?}, newname: {:?})",
ino, newparent, newname
);
reply.error(EPERM);
}
if let Some(old_ino) = self.ino_by_fh.insert(fh, ino) { 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!("fh {} was associated with ino {}, now with ino {}", fh, old_ino, ino);
} }
fn open(&mut self, _req: &fuser::Request<'_>, _ino: u64, _flags: i32, reply: fuser::ReplyOpen) { reply.opened(fh, 0);
reply.opened(0, 0);
} }
fn read( fn read(
@ -343,11 +356,17 @@ impl Filesystem for FatFuse {
fn opendir( fn opendir(
&mut self, &mut self,
_req: &fuser::Request<'_>, _req: &fuser::Request<'_>,
_ino: u64, ino: u64,
_flags: i32, _flags: i32,
reply: fuser::ReplyOpen, reply: fuser::ReplyOpen,
) { ) {
reply.opened(0, 0); 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( fn readdir(
@ -356,10 +375,79 @@ impl Filesystem for FatFuse {
ino: u64, ino: u64,
fh: u64, fh: u64,
offset: i64, offset: i64,
reply: fuser::ReplyDirectory, mut reply: fuser::ReplyDirectory,
) { ) {
warn!("[Not Implemented] readdir(ino: {:#x?}, fh: {}, offset: {})", ino, fh, offset); let Ok(mut offset): Result<usize, _> = offset.try_into() else {
reply.error(ENOSYS); return;
};
let Some(inode) = self.get_inode_by_fh(fh) else {
debug!("could not find inode accociated with fh {} (ino: {})", fh, ino);
reply.error(EINVAL);
return;
};
if inode.ino() != ino {
debug!(
"ino {} of inode associated with fh {} does not match given ino {}",
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 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) = 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<DirEntry> = dir_iter.skip(offset).collect();
for dir_entry in dirs {
let name = dir_entry.name_string().unwrap_or("<invalid>".into());
let inode: &Inode = self.get_or_make_inode_by_dir_entry(&dir_entry);
debug!("adding entry {} (ino: {})", name, inode.ino());
if reply.add(ino, next_offset(), inode.kind().into(), name) {
return;
}
}
reply.ok();
} }
fn readdirplus( fn readdirplus(
@ -380,11 +468,34 @@ impl Filesystem for FatFuse {
fn releasedir( fn releasedir(
&mut self, &mut self,
_req: &fuser::Request<'_>, _req: &fuser::Request<'_>,
_ino: u64, ino: u64,
_fh: u64, fh: u64,
_flags: i32, _flags: i32,
reply: fuser::ReplyEmpty, reply: fuser::ReplyEmpty,
) { ) {
let Some(ino) = self.ino_by_fh.remove(&fh) else {
debug!("can't find inode {} by fh {}", ino, fh);
reply.error(EIO);
return;
};
let Some(inode) = self.inode_table.get(&ino) else {
debug!("ino {} not associated with an inode", ino);
reply.ok();
return;
};
if inode.ino() != ino {
debug!(
"inode with ino {}, associated with fh {}, does not have expected ino {}",
inode.ino(),
fh,
ino
);
}
reply.ok(); reply.ok();
} }

View file

@ -6,6 +6,7 @@ use fat_bits::FatFs;
use fat_bits::dir::DirEntry; use fat_bits::dir::DirEntry;
use fuser::FileAttr; use fuser::FileAttr;
use libc::ENOTDIR; use libc::ENOTDIR;
use log::debug;
use rand::{Rng, SeedableRng as _}; use rand::{Rng, SeedableRng as _};
thread_local! { thread_local! {
@ -91,7 +92,7 @@ impl Inode {
((secs as u32) << 16) | rand as u32 ((secs as u32) << 16) | rand as u32
} }
pub fn new(fat_fs: &FatFs, dir_entry: DirEntry, ino: u64, uid: u32, gid: u32) -> Inode { pub fn new(fat_fs: &FatFs, dir_entry: &DirEntry, ino: u64, uid: u32, gid: u32) -> Inode {
assert!(dir_entry.is_file() || dir_entry.is_dir()); assert!(dir_entry.is_file() || dir_entry.is_dir());
let generation = Self::new_generation(); let generation = Self::new_generation();
@ -114,6 +115,12 @@ impl Inode {
let mtime = datetime_to_system(dir_entry.write_time()); let mtime = datetime_to_system(dir_entry.write_time());
let crtime = datetime_to_system(dir_entry.create_time()); let crtime = datetime_to_system(dir_entry.create_time());
debug!(
"creating new inode: ino: {} name: {}",
ino,
dir_entry.name_string().unwrap_or("<invalid>".into())
);
Inode { Inode {
ino, ino,
generation, generation,
@ -131,6 +138,28 @@ impl Inode {
} }
} }
pub fn root_inode(fat_fs: &FatFs, uid: u32, gid: u32) -> Inode {
// let generation = Self::new_generation();
let root_cluster = fat_fs.bpb().root_cluster().unwrap_or(0);
Inode {
ino: 1,
generation: 0,
ref_count: 0,
size: 0,
block_size: fat_fs.bpb().bytes_per_sector() as u32,
kind: Kind::Dir,
read_only: false,
atime: SystemTime::UNIX_EPOCH,
mtime: SystemTime::UNIX_EPOCH,
crtime: SystemTime::UNIX_EPOCH,
uid,
gid,
first_cluster: root_cluster,
}
}
pub fn ino(&self) -> u64 { pub fn ino(&self) -> u64 {
self.ino self.ino
} }
@ -143,14 +172,37 @@ impl Inode {
self.ref_count self.ref_count
} }
pub fn ref_count_mut(&mut self) -> &mut u64 { pub fn refcount_inc(&mut self) {
&mut self.ref_count self.ref_count += 1;
}
pub fn refcount_dec(&mut self, n: u64) -> u64 {
if self.ref_count < n {
debug!(
"inode {}: tried to decrement refcount by {}, but is only {}",
self.ino(),
n,
self.ref_count
);
}
self.ref_count = self.ref_count.saturating_sub(n);
self.ref_count
}
pub fn kind(&self) -> Kind {
self.kind
} }
pub fn first_cluster(&self) -> u32 { pub fn first_cluster(&self) -> u32 {
self.first_cluster self.first_cluster
} }
pub fn is_root(&self) -> bool {
self.ino == ROOT_INO
}
pub fn file_attr(&self) -> FileAttr { pub fn file_attr(&self) -> FileAttr {
let perm = if self.read_only { 0o555 } else { 0o777 }; let perm = if self.read_only { 0o555 } else { 0o777 };
@ -180,7 +232,7 @@ impl Inode {
return Err(ENOTDIR); return Err(ENOTDIR);
} }
if self.ino == ROOT_INO { if self.is_root() {
// root dir // root dir
return Ok(fat_fs.root_dir_iter()); return Ok(fat_fs.root_dir_iter());

View file

@ -5,6 +5,7 @@ use std::cell::RefCell;
use std::collections::BTreeMap; use std::collections::BTreeMap;
use std::rc::Rc; use std::rc::Rc;
use fat_bits::dir::DirEntry;
use fat_bits::{FatFs, SliceLike}; use fat_bits::{FatFs, SliceLike};
use log::debug; use log::debug;
@ -18,11 +19,25 @@ pub struct FatFuse {
gid: u32, gid: u32,
next_ino: u64, next_ino: u64,
next_fd: u32, next_fh: u64,
inode_table: BTreeMap<u64, Inode>, inode_table: BTreeMap<u64, Inode>,
ino_by_first_cluster: BTreeMap<u32, u64>, ino_by_first_cluster: BTreeMap<u32, u64>,
ino_by_fh: BTreeMap<u64, u64>,
}
impl Drop for FatFuse {
fn drop(&mut self) {
println!("inode_table: {}", self.inode_table.len());
println!("ino_by_first_cluster: {}", self.ino_by_first_cluster.len());
for (&first_cluster, &ino) in self.ino_by_first_cluster.iter() {
println!("{} -> {}", first_cluster, ino);
}
println!("ino_by_fh: {}", self.ino_by_fh.len());
}
} }
impl FatFuse { impl FatFuse {
@ -35,17 +50,24 @@ impl FatFuse {
let fat_fs = FatFs::load(data)?; let fat_fs = FatFs::load(data)?;
// TODO: build and insert root dir inode let mut fat_fuse = FatFuse {
Ok(FatFuse {
fat_fs, fat_fs,
uid, uid,
gid, gid,
next_ino: 2, // 0 is reserved and 1 is root next_ino: 2, // 0 is reserved and 1 is root
next_fd: 0, next_fh: 0,
inode_table: BTreeMap::new(), inode_table: BTreeMap::new(),
ino_by_first_cluster: BTreeMap::new(), ino_by_first_cluster: BTreeMap::new(),
}) ino_by_fh: BTreeMap::new(),
};
// TODO: build and insert root dir inode
let root_inode = Inode::root_inode(&fat_fuse.fat_fs, uid, gid);
fat_fuse.insert_inode(root_inode);
Ok(fat_fuse)
} }
fn next_ino(&mut self) -> u64 { fn next_ino(&mut self) -> u64 {
@ -58,6 +80,16 @@ impl FatFuse {
ino ino
} }
fn next_fh(&mut self) -> u64 {
let fh = self.next_fh;
assert!(!self.ino_by_fh.contains_key(&fh));
self.next_fh += 1;
fh
}
fn insert_inode(&mut self, inode: Inode) -> &mut Inode { fn insert_inode(&mut self, inode: Inode) -> &mut Inode {
let ino = inode.ino(); let ino = inode.ino();
let generation = inode.generation(); let generation = inode.generation();
@ -81,8 +113,6 @@ impl FatFuse {
} }
}; };
let old_ino = self.ino_by_first_cluster.insert(first_cluster, ino);
debug!( debug!(
"inserted new inode with ino {} and generation {} (first cluster: {})", "inserted new inode with ino {} and generation {} (first cluster: {})",
ino, generation, first_cluster ino, generation, first_cluster
@ -92,14 +122,18 @@ impl FatFuse {
debug!("ejected inode {} {}", old_inode.ino(), old_inode.generation()); debug!("ejected inode {} {}", old_inode.ino(), old_inode.generation());
} }
if let Some(old_ino) = old_ino { if first_cluster != 0 {
debug!("ejected old {} -> {} cluster to ino mapping", first_cluster, old_ino); if let Some(old_ino) = self.ino_by_first_cluster.insert(first_cluster, ino) {
debug!("ejected old {} -> {} cluster to ino mapping", first_cluster, old_ino);
}
} }
new_inode new_inode
} }
fn drop_inode(&mut self, ino: u64) { fn drop_inode(&mut self, ino: u64) {
debug!("dropping ino {}", ino);
let Some(inode) = self.inode_table.remove(&ino) else { let Some(inode) = self.inode_table.remove(&ino) else {
debug!("tried to drop inode with ino {}, but was not in table", ino); debug!("tried to drop inode with ino {}, but was not in table", ino);
@ -108,30 +142,30 @@ impl FatFuse {
let first_cluster = inode.first_cluster(); let first_cluster = inode.first_cluster();
let entry = self.ino_by_first_cluster.entry(first_cluster); if first_cluster != 0 {
let entry = self.ino_by_first_cluster.entry(first_cluster);
match entry { match entry {
std::collections::btree_map::Entry::Vacant(_) => debug!( std::collections::btree_map::Entry::Vacant(_) => debug!(
"removed inode with ino {} from table, but it's first cluster did not point to any ino", "removed inode with ino {} from table, but it's first cluster did not point to any ino",
ino ino
), ),
std::collections::btree_map::Entry::Occupied(occupied_entry) => { std::collections::btree_map::Entry::Occupied(occupied_entry) => {
let found_ino = *occupied_entry.get(); let found_ino = *occupied_entry.get();
if found_ino == ino { if found_ino == ino {
// matches our inode, remove it // matches our inode, remove it
occupied_entry.remove(); occupied_entry.remove();
} else { } else {
// does not match removed inode, leave it as is // does not match removed inode, leave it as is
debug!( debug!(
"removed inode with ino {} from table, but it's first cluster pointer to ino {} instead", "removed inode with ino {} from table, but it's first cluster pointer to ino {} instead",
ino, found_ino ino, found_ino
); );
}
} }
} }
} }
todo!()
} }
fn get_inode(&self, ino: u64) -> Option<&Inode> { fn get_inode(&self, ino: u64) -> Option<&Inode> {
@ -142,7 +176,31 @@ impl FatFuse {
self.inode_table.get_mut(&ino) self.inode_table.get_mut(&ino)
} }
fn get_or_make_inode_by_dir_entry(&mut self, dir_entry: &DirEntry) -> &mut Inode {
if self
.get_inode_by_first_cluster_mut(dir_entry.first_cluster())
.is_some()
{
return self
.get_inode_by_first_cluster_mut(dir_entry.first_cluster())
.unwrap();
}
// no inode found, make a new one
let ino = self.next_ino();
let inode = Inode::new(&self.fat_fs, dir_entry, ino, self.uid, self.gid);
self.insert_inode(inode)
}
pub fn get_inode_by_first_cluster(&self, first_cluster: u32) -> Option<&Inode> { pub fn get_inode_by_first_cluster(&self, first_cluster: u32) -> Option<&Inode> {
if first_cluster == 0 {
debug!("trying to get inode by first cluster 0");
return None;
}
let ino = self.ino_by_first_cluster.get(&first_cluster)?; let ino = self.ino_by_first_cluster.get(&first_cluster)?;
if let Some(inode) = self.inode_table.get(ino) { if let Some(inode) = self.inode_table.get(ino) {
@ -158,6 +216,12 @@ impl FatFuse {
} }
pub fn get_inode_by_first_cluster_mut(&mut self, first_cluster: u32) -> Option<&mut Inode> { pub fn get_inode_by_first_cluster_mut(&mut self, first_cluster: u32) -> Option<&mut Inode> {
if first_cluster == 0 {
debug!("trying to get inode by first cluster 0");
return None;
}
let ino = self.ino_by_first_cluster.get(&first_cluster)?; let ino = self.ino_by_first_cluster.get(&first_cluster)?;
if let Some(inode) = self.inode_table.get_mut(ino) { if let Some(inode) = self.inode_table.get_mut(ino) {
@ -171,4 +235,28 @@ impl FatFuse {
None None
} }
} }
pub fn get_inode_by_fh(&self, fh: u64) -> Option<&Inode> {
let ino = self.ino_by_fh.get(&fh)?;
if let Some(inode) = self.inode_table.get(ino) {
Some(inode)
} else {
debug!("fh {} is mapped to ino {}, but inode is not in table", fh, ino);
None
}
}
pub fn get_inode_by_fh_mut(&mut self, fh: u64) -> Option<&mut Inode> {
let ino = self.ino_by_fh.get(&fh)?;
if let Some(inode) = self.inode_table.get_mut(ino) {
Some(inode)
} else {
debug!("fh {} is mapped to ino {}, but inode is not in table", fh, ino);
None
}
}
} }