look up inodes by path

also DirEntry::name now no longer returns option invalid chars get
replaced by ?
This commit is contained in:
Moritz Gmeiner 2025-07-31 23:47:45 +02:00
commit 19340bd4ee
6 changed files with 291 additions and 44 deletions

View file

@ -1,4 +1,5 @@
use std::ffi::c_int;
use std::rc::Rc;
use std::time::Duration;
use fat_bits::dir::DirEntry;
@ -28,9 +29,6 @@ impl Filesystem for FatFuse {
name: &std::ffi::OsStr,
reply: fuser::ReplyEntry,
) {
// warn!("[Not Implemented] lookup(parent: {:#x?}, name {:?})", parent, name);
// reply.error(ENOSYS);
let Some(name) = name.to_str() else {
// TODO: add proper handling of non-utf8 strings
debug!("cannot convert OsStr {:?} to str", name);
@ -67,7 +65,7 @@ impl Filesystem for FatFuse {
// .map_err(|_| ENOTDIR)
.and_then(|mut dir_iter| {
dir_iter
.find(|dir_entry| dir_entry.name_string().as_deref() == Some(name))
.find(|dir_entry| &dir_entry.name_string() == name)
.ok_or(ENOENT)
}) {
Ok(dir_entry) => dir_entry,
@ -91,17 +89,23 @@ impl Filesystem for FatFuse {
// }
// };
let inode = self.get_or_make_inode_by_dir_entry(&dir_entry);
let inode = self.get_or_make_inode_by_dir_entry(
&dir_entry,
parent_inode.ino(),
parent_inode.path(),
);
let attr = inode.file_attr();
let generation = inode.generation();
reply.entry(&TTL, &attr, generation as u64);
inode.refcount_inc();
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_mut(ino) else {
debug!("tried to forget {} refs of inode {}, but was not found", ino, nlookup);
@ -110,7 +114,9 @@ impl Filesystem for FatFuse {
// *ref_count = ref_count.saturating_sub(nlookup);
if inode.refcount_dec(nlookup) == 0 {
if inode.dec_ref_count(nlookup) == 0 {
debug!("dropping inode with ino {}", inode.ino());
// no more references, drop inode
self.drop_inode(ino);
}
@ -380,17 +386,17 @@ impl Filesystem for FatFuse {
return;
};
let Some(inode) = self.get_inode_by_fh(fh) else {
let Some(dir_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 {
if dir_inode.ino() != ino {
debug!(
"ino {} of inode associated with fh {} does not match given ino {}",
inode.ino(),
dir_inode.ino(),
fh,
ino
);
@ -406,7 +412,7 @@ impl Filesystem for FatFuse {
next_idx
};
if inode.is_root() {
if dir_inode.is_root() {
if offset == 0 {
debug!("adding . to root dir");
if reply.add(1, next_offset(), FileType::Directory, ".") {
@ -426,7 +432,7 @@ impl Filesystem for FatFuse {
}
}
let Ok(dir_iter) = inode.dir_iter(&self.fat_fs) else {
let Ok(dir_iter) = dir_inode.dir_iter(&self.fat_fs) else {
reply.error(ENOTDIR);
return;
};
@ -435,10 +441,14 @@ impl Filesystem for FatFuse {
// 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 dir_ino = dir_inode.ino();
let dir_path = dir_inode.path();
let inode: &Inode = self.get_or_make_inode_by_dir_entry(&dir_entry);
for dir_entry in dirs {
let name = dir_entry.name_string();
let inode: &Inode =
self.get_or_make_inode_by_dir_entry(&dir_entry, dir_ino, Rc::clone(&dir_path));
debug!("adding entry {} (ino: {})", name, inode.ino());
if reply.add(ino, next_offset(), inode.kind().into(), name) {

View file

@ -1,4 +1,5 @@
use std::cell::{LazyCell, RefCell};
use std::rc::Rc;
use std::time::SystemTime;
use chrono::{NaiveDateTime, NaiveTime};
@ -49,7 +50,7 @@ impl From<Kind> for fuser::FileType {
}
}
const ROOT_INO: u64 = 1;
pub const ROOT_INO: u64 = 1;
#[derive(Debug)]
#[allow(dead_code)]
@ -61,6 +62,8 @@ pub struct Inode {
ref_count: u64,
parent_ino: u64,
size: u64,
block_size: u32,
@ -77,6 +80,8 @@ pub struct Inode {
gid: u32,
first_cluster: u32,
path: Rc<str>,
}
#[allow(dead_code)]
@ -92,7 +97,15 @@ impl Inode {
((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,
path: impl Into<Rc<str>>,
parent_ino: u64,
) -> Inode {
assert!(dir_entry.is_file() || dir_entry.is_dir());
let generation = Self::new_generation();
@ -115,16 +128,20 @@ impl Inode {
let mtime = datetime_to_system(dir_entry.write_time());
let crtime = datetime_to_system(dir_entry.create_time());
let path = path.into();
debug!(
"creating new inode: ino: {} name: {}",
"creating new inode: ino: {} name: {} path: {}",
ino,
dir_entry.name_string().unwrap_or("<invalid>".into())
dir_entry.name_string(),
path
);
Inode {
ino,
generation,
ref_count: 0,
parent_ino,
size: dir_entry.file_size() as u64,
block_size: fat_fs.bpb().bytes_per_sector() as u32,
kind,
@ -135,6 +152,7 @@ impl Inode {
uid,
gid,
first_cluster: dir_entry.first_cluster(),
path,
}
}
@ -147,6 +165,7 @@ impl Inode {
ino: 1,
generation: 0,
ref_count: 0,
parent_ino: ROOT_INO, // parent is self
size: 0,
block_size: fat_fs.bpb().bytes_per_sector() as u32,
kind: Kind::Dir,
@ -157,6 +176,7 @@ impl Inode {
uid,
gid,
first_cluster: root_cluster,
path: "/".into(),
}
}
@ -172,11 +192,24 @@ impl Inode {
self.ref_count
}
pub fn refcount_inc(&mut self) {
pub fn inc_ref_count(&mut self) {
debug!(
"increasing ref_count of ino {} by 1 (new ref_count: {})",
self.ino(),
self.ref_count() + 1
);
self.ref_count += 1;
}
pub fn refcount_dec(&mut self, n: u64) -> u64 {
pub fn dec_ref_count(&mut self, n: u64) -> u64 {
debug!(
"decreasing ref_count of ino {} by {} (new ref_count: {})",
self.ino(),
n,
self.ref_count().saturating_sub(n),
);
if self.ref_count < n {
debug!(
"inode {}: tried to decrement refcount by {}, but is only {}",
@ -191,6 +224,10 @@ impl Inode {
self.ref_count
}
pub fn parent_ino(&self) -> u64 {
self.parent_ino
}
pub fn kind(&self) -> Kind {
self.kind
}
@ -199,6 +236,10 @@ impl Inode {
self.first_cluster
}
pub fn path(&self) -> Rc<str> {
Rc::clone(&self.path)
}
pub fn is_root(&self) -> bool {
self.ino == ROOT_INO
}

View file

@ -2,9 +2,11 @@ mod fuse;
mod inode;
use std::collections::BTreeMap;
use std::rc::Rc;
use fat_bits::dir::DirEntry;
use fat_bits::{FatFs, SliceLike};
use fxhash::FxHashMap;
use log::debug;
use crate::inode::Inode;
@ -23,6 +25,7 @@ pub struct FatFuse {
ino_by_first_cluster: BTreeMap<u32, u64>,
ino_by_fh: BTreeMap<u64, u64>,
ino_by_path: FxHashMap<Rc<str>, u64>,
}
impl Drop for FatFuse {
@ -35,9 +38,16 @@ impl Drop for FatFuse {
}
println!("ino_by_fh: {}", self.ino_by_fh.len());
println!("ino_by_path: {}", self.ino_by_path.len());
}
}
/// SAFETY
///
/// do NOT leak Rc<str> from this type
unsafe impl Send for FatFuse {}
impl FatFuse {
pub fn new<S>(data: S) -> anyhow::Result<FatFuse>
where
@ -57,6 +67,7 @@ impl FatFuse {
inode_table: BTreeMap::new(),
ino_by_first_cluster: BTreeMap::new(),
ino_by_fh: BTreeMap::new(),
ino_by_path: FxHashMap::default(),
};
// TODO: build and insert root dir inode
@ -126,6 +137,10 @@ impl FatFuse {
}
}
if let Some(old_ino) = self.ino_by_path.insert(new_inode.path(), ino) {
debug!("ejected old {} -> {} path to ino mapping", new_inode.path(), old_ino);
}
new_inode
}
@ -157,13 +172,45 @@ impl FatFuse {
} else {
// does not match removed inode, leave it as is
debug!(
"removed inode with ino {} from table, but it's first cluster pointer to ino {} instead",
"removed inode with ino {} from table, but its first cluster pointed to ino {} instead",
ino, found_ino
);
}
}
}
}
{
let entry = self.ino_by_path.entry(inode.path());
match entry {
std::collections::hash_map::Entry::Vacant(_) => debug!(
"removed inode with ino {} from table, but it's path did not point to any ino",
ino
),
std::collections::hash_map::Entry::Occupied(occupied_entry) => {
let found_ino = *occupied_entry.get();
if found_ino == ino {
// matches our inode, remove it
occupied_entry.remove();
} else {
// does not match removed inode, leave it as is
debug!(
"removed inode with ino {} from table, but its path pointed to ino {} instead",
ino, found_ino
);
}
}
}
}
let Some(parent_inode) = self.get_inode_mut(inode.parent_ino()) else {
panic!("parent inode {} does not exists anymore", inode.parent_ino());
};
// dec refcount
parent_inode.dec_ref_count(1);
}
fn get_inode(&self, ino: u64) -> Option<&Inode> {
@ -174,20 +221,51 @@ impl FatFuse {
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()
fn get_or_make_inode_by_dir_entry(
&mut self,
dir_entry: &DirEntry,
parent_ino: u64,
parent_path: Rc<str>,
) -> &mut Inode {
if dir_entry.first_cluster() != 0
&& 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();
}
let path = {
let mut path = parent_path.as_ref().to_owned();
if parent_ino != inode::ROOT_INO {
// root inode already has trailing slash
path.push('/');
}
path += &dir_entry.name_string();
path
};
if self.get_inode_by_path_mut(&path).is_some() {
return self.get_inode_by_path_mut(&path).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);
let Some(parent_inode) = self.get_inode_mut(parent_ino) else {
// TODO: what do we do here? should not happen
panic!("parent_ino {} does not lead to inode", parent_ino);
};
// inc ref of parent
parent_inode.inc_ref_count();
let inode = Inode::new(&self.fat_fs, dir_entry, ino, self.uid, self.gid, path, parent_ino);
self.insert_inode(inode)
}
@ -235,9 +313,9 @@ impl FatFuse {
}
pub fn get_inode_by_fh(&self, fh: u64) -> Option<&Inode> {
let ino = self.ino_by_fh.get(&fh)?;
let ino = *self.ino_by_fh.get(&fh)?;
if let Some(inode) = self.inode_table.get(ino) {
if let Some(inode) = self.get_inode(ino) {
Some(inode)
} else {
debug!("fh {} is mapped to ino {}, but inode is not in table", fh, ino);
@ -247,9 +325,9 @@ impl FatFuse {
}
pub fn get_inode_by_fh_mut(&mut self, fh: u64) -> Option<&mut Inode> {
let ino = self.ino_by_fh.get(&fh)?;
let ino = *self.ino_by_fh.get(&fh)?;
if let Some(inode) = self.inode_table.get_mut(ino) {
if let Some(inode) = self.get_inode_mut(ino) {
Some(inode)
} else {
debug!("fh {} is mapped to ino {}, but inode is not in table", fh, ino);
@ -257,4 +335,28 @@ impl FatFuse {
None
}
}
pub fn get_inode_by_path(&self, path: &str) -> Option<&Inode> {
let ino = *self.ino_by_path.get(path)?;
if let Some(inode) = self.get_inode(ino) {
Some(inode)
} else {
debug!("path {} is mapped to ino {}, but inode is not in table", path, ino);
None
}
}
pub fn get_inode_by_path_mut(&mut self, path: &str) -> Option<&mut Inode> {
let ino = *self.ino_by_path.get(path)?;
if let Some(inode) = self.get_inode_mut(ino) {
Some(inode)
} else {
debug!("path {} is mapped to ino {}, but inode is not in table", path, ino);
None
}
}
}