2025-07-29 20:33:36 +02:00
|
|
|
mod fuse;
|
2025-07-27 23:16:56 +02:00
|
|
|
mod inode;
|
|
|
|
|
|
2025-08-01 18:09:45 +02:00
|
|
|
use std::cell::RefCell;
|
2025-07-27 23:16:56 +02:00
|
|
|
use std::collections::BTreeMap;
|
2025-07-31 23:47:45 +02:00
|
|
|
use std::rc::Rc;
|
2025-07-27 14:38:31 +02:00
|
|
|
|
2025-07-31 01:14:28 +02:00
|
|
|
use fat_bits::dir::DirEntry;
|
2025-07-27 14:38:31 +02:00
|
|
|
use fat_bits::{FatFs, SliceLike};
|
2025-07-31 23:47:45 +02:00
|
|
|
use fxhash::FxHashMap;
|
2025-08-01 18:09:45 +02:00
|
|
|
use log::{debug, error};
|
2025-07-27 14:38:31 +02:00
|
|
|
|
2025-08-01 18:09:45 +02:00
|
|
|
use crate::inode::{Inode, InodeRef};
|
2025-07-27 14:38:31 +02:00
|
|
|
|
|
|
|
|
#[allow(dead_code)]
|
2025-07-27 23:16:56 +02:00
|
|
|
pub struct FatFuse {
|
|
|
|
|
fat_fs: FatFs,
|
2025-07-27 14:38:31 +02:00
|
|
|
|
|
|
|
|
uid: u32,
|
|
|
|
|
gid: u32,
|
2025-07-27 23:16:56 +02:00
|
|
|
|
2025-07-30 21:39:45 +02:00
|
|
|
next_ino: u64,
|
2025-07-31 01:14:28 +02:00
|
|
|
next_fh: u64,
|
2025-07-29 20:33:36 +02:00
|
|
|
|
2025-08-01 18:09:45 +02:00
|
|
|
inode_table: BTreeMap<u64, InodeRef>,
|
2025-07-30 21:39:45 +02:00
|
|
|
|
|
|
|
|
ino_by_first_cluster: BTreeMap<u32, u64>,
|
2025-07-31 01:14:28 +02:00
|
|
|
ino_by_fh: BTreeMap<u64, u64>,
|
2025-07-31 23:47:45 +02:00
|
|
|
ino_by_path: FxHashMap<Rc<str>, u64>,
|
2025-07-31 01:14:28 +02:00
|
|
|
}
|
|
|
|
|
|
2025-07-31 23:47:45 +02:00
|
|
|
/// SAFETY
|
|
|
|
|
///
|
|
|
|
|
/// do NOT leak Rc<str> from this type
|
|
|
|
|
unsafe impl Send for FatFuse {}
|
|
|
|
|
|
2025-07-27 23:16:56 +02:00
|
|
|
impl FatFuse {
|
2025-07-31 01:07:01 +02:00
|
|
|
pub fn new<S>(data: S) -> anyhow::Result<FatFuse>
|
|
|
|
|
where
|
|
|
|
|
S: SliceLike + Send + 'static,
|
|
|
|
|
{
|
2025-07-27 14:38:31 +02:00
|
|
|
let uid = unsafe { libc::getuid() };
|
|
|
|
|
let gid = unsafe { libc::getgid() };
|
|
|
|
|
|
|
|
|
|
let fat_fs = FatFs::load(data)?;
|
|
|
|
|
|
2025-07-31 01:14:28 +02:00
|
|
|
let mut fat_fuse = FatFuse {
|
2025-07-27 23:16:56 +02:00
|
|
|
fat_fs,
|
|
|
|
|
uid,
|
|
|
|
|
gid,
|
2025-07-30 21:39:45 +02:00
|
|
|
next_ino: 2, // 0 is reserved and 1 is root
|
2025-07-31 01:14:28 +02:00
|
|
|
next_fh: 0,
|
2025-07-27 23:16:56 +02:00
|
|
|
inode_table: BTreeMap::new(),
|
2025-07-30 21:39:45 +02:00
|
|
|
ino_by_first_cluster: BTreeMap::new(),
|
2025-07-31 01:14:28 +02:00
|
|
|
ino_by_fh: BTreeMap::new(),
|
2025-07-31 23:47:45 +02:00
|
|
|
ino_by_path: FxHashMap::default(),
|
2025-07-31 01:14:28 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// 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)
|
2025-07-27 14:38:31 +02:00
|
|
|
}
|
|
|
|
|
|
2025-07-30 21:39:45 +02:00
|
|
|
fn next_ino(&mut self) -> u64 {
|
|
|
|
|
let ino = self.next_ino;
|
|
|
|
|
|
|
|
|
|
assert!(!self.inode_table.contains_key(&ino));
|
|
|
|
|
|
|
|
|
|
self.next_ino += 1;
|
|
|
|
|
|
|
|
|
|
ino
|
|
|
|
|
}
|
|
|
|
|
|
2025-07-31 01:14:28 +02:00
|
|
|
fn next_fh(&mut self) -> u64 {
|
|
|
|
|
let fh = self.next_fh;
|
|
|
|
|
|
|
|
|
|
assert!(!self.ino_by_fh.contains_key(&fh));
|
|
|
|
|
|
|
|
|
|
self.next_fh += 1;
|
|
|
|
|
|
|
|
|
|
fh
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-01 18:09:45 +02:00
|
|
|
fn insert_inode(&mut self, inode: Inode) -> InodeRef {
|
2025-07-30 21:39:45 +02:00
|
|
|
let ino = inode.ino();
|
|
|
|
|
let generation = inode.generation();
|
|
|
|
|
let first_cluster = inode.first_cluster();
|
|
|
|
|
|
|
|
|
|
// let old_inode = self.inode_table.insert(ino, inode);
|
|
|
|
|
|
2025-08-01 18:09:45 +02:00
|
|
|
let inode = Rc::new(RefCell::new(inode));
|
|
|
|
|
|
2025-07-30 21:39:45 +02:00
|
|
|
let entry = self.inode_table.entry(ino);
|
|
|
|
|
|
2025-08-01 18:09:45 +02:00
|
|
|
let (new_inode, old_inode) = match entry {
|
2025-07-30 21:39:45 +02:00
|
|
|
std::collections::btree_map::Entry::Vacant(vacant_entry) => {
|
|
|
|
|
let new_inode = vacant_entry.insert(inode);
|
2025-08-01 18:09:45 +02:00
|
|
|
(Rc::clone(new_inode), None)
|
2025-07-30 21:39:45 +02:00
|
|
|
}
|
|
|
|
|
std::collections::btree_map::Entry::Occupied(occupied_entry) => {
|
|
|
|
|
let entry_ref = occupied_entry.into_mut();
|
|
|
|
|
|
|
|
|
|
let old_inode = std::mem::replace(entry_ref, inode);
|
|
|
|
|
|
2025-08-01 18:09:45 +02:00
|
|
|
(Rc::clone(entry_ref), Some(old_inode))
|
2025-07-30 21:39:45 +02:00
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
debug!(
|
|
|
|
|
"inserted new inode with ino {} and generation {} (first cluster: {})",
|
|
|
|
|
ino, generation, first_cluster
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if let Some(old_inode) = old_inode {
|
2025-08-01 18:09:45 +02:00
|
|
|
let old_inode = old_inode.borrow();
|
|
|
|
|
|
2025-07-30 21:39:45 +02:00
|
|
|
debug!("ejected inode {} {}", old_inode.ino(), old_inode.generation());
|
|
|
|
|
}
|
|
|
|
|
|
2025-07-31 01:14:28 +02:00
|
|
|
if first_cluster != 0 {
|
|
|
|
|
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);
|
|
|
|
|
}
|
2025-07-30 21:39:45 +02:00
|
|
|
}
|
|
|
|
|
|
2025-08-01 18:09:45 +02:00
|
|
|
let path = new_inode.borrow().path();
|
|
|
|
|
|
|
|
|
|
if let Some(old_ino) = self.ino_by_path.insert(Rc::clone(&path), ino) {
|
|
|
|
|
debug!("ejected old {} -> {} path to ino mapping", path, old_ino);
|
2025-07-31 23:47:45 +02:00
|
|
|
}
|
|
|
|
|
|
2025-07-30 21:39:45 +02:00
|
|
|
new_inode
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-01 18:09:45 +02:00
|
|
|
fn drop_inode(&mut self, inode: InodeRef) {
|
|
|
|
|
let inode = inode.borrow();
|
2025-07-31 01:14:28 +02:00
|
|
|
|
2025-08-01 18:09:45 +02:00
|
|
|
let ino = inode.ino();
|
|
|
|
|
|
|
|
|
|
debug!("dropping inode {}", ino);
|
|
|
|
|
|
2025-08-01 22:42:13 +02:00
|
|
|
if self.inode_table.remove(&ino).is_none() {
|
2025-08-01 18:09:45 +02:00
|
|
|
error!("tried to drop inode with ino {}, but was not in table", ino);
|
2025-07-30 21:39:45 +02:00
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let first_cluster = inode.first_cluster();
|
|
|
|
|
|
2025-07-31 01:14:28 +02:00
|
|
|
if first_cluster != 0 {
|
|
|
|
|
let entry = self.ino_by_first_cluster.entry(first_cluster);
|
|
|
|
|
|
|
|
|
|
match entry {
|
|
|
|
|
std::collections::btree_map::Entry::Vacant(_) => debug!(
|
|
|
|
|
"removed inode with ino {} from table, but it's first cluster did not point to any ino",
|
|
|
|
|
ino
|
|
|
|
|
),
|
|
|
|
|
std::collections::btree_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!(
|
2025-07-31 23:47:45 +02:00
|
|
|
"removed inode with ino {} from table, but its first cluster pointed to ino {} instead",
|
2025-07-31 01:14:28 +02:00
|
|
|
ino, found_ino
|
|
|
|
|
);
|
|
|
|
|
}
|
2025-07-30 21:39:45 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-07-31 23:47:45 +02:00
|
|
|
|
|
|
|
|
{
|
|
|
|
|
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
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-07-30 21:39:45 +02:00
|
|
|
}
|
|
|
|
|
|
2025-08-01 18:09:45 +02:00
|
|
|
fn get_inode(&self, ino: u64) -> Option<&InodeRef> {
|
2025-07-29 20:33:36 +02:00
|
|
|
self.inode_table.get(&ino)
|
2025-07-27 14:38:31 +02:00
|
|
|
}
|
|
|
|
|
|
2025-07-31 23:47:45 +02:00
|
|
|
fn get_or_make_inode_by_dir_entry(
|
|
|
|
|
&mut self,
|
|
|
|
|
dir_entry: &DirEntry,
|
|
|
|
|
parent_ino: u64,
|
|
|
|
|
parent_path: Rc<str>,
|
2025-08-01 18:09:45 +02:00
|
|
|
) -> InodeRef {
|
|
|
|
|
// try to find inode by first cluster first
|
2025-07-31 23:47:45 +02:00
|
|
|
if dir_entry.first_cluster() != 0
|
2025-08-01 18:09:45 +02:00
|
|
|
&& let Some(inode) = self.get_inode_by_first_cluster(dir_entry.first_cluster())
|
2025-07-31 01:14:28 +02:00
|
|
|
{
|
2025-08-01 18:09:45 +02:00
|
|
|
return inode;
|
2025-07-31 01:14:28 +02:00
|
|
|
}
|
|
|
|
|
|
2025-08-01 18:09:45 +02:00
|
|
|
// try to find inode by path
|
|
|
|
|
// mostly for empty files/directories which have a first cluster of 0
|
|
|
|
|
|
2025-07-31 23:47:45 +02:00
|
|
|
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
|
|
|
|
|
};
|
|
|
|
|
|
2025-08-01 18:09:45 +02:00
|
|
|
if let Some(inode) = self.get_inode_by_path(&path) {
|
|
|
|
|
return inode;
|
2025-07-31 23:47:45 +02:00
|
|
|
}
|
|
|
|
|
|
2025-07-31 01:14:28 +02:00
|
|
|
// no inode found, make a new one
|
|
|
|
|
let ino = self.next_ino();
|
|
|
|
|
|
2025-08-01 18:09:45 +02:00
|
|
|
let Some(parent_inode) = self.get_inode(parent_ino).cloned() else {
|
2025-07-31 23:47:45 +02:00
|
|
|
// TODO: what do we do here? should not happen
|
|
|
|
|
panic!("parent_ino {} does not lead to inode", parent_ino);
|
|
|
|
|
};
|
|
|
|
|
|
2025-08-01 18:09:45 +02:00
|
|
|
let inode =
|
|
|
|
|
Inode::new(&self.fat_fs, dir_entry, ino, self.uid, self.gid, path, parent_inode);
|
2025-07-31 01:14:28 +02:00
|
|
|
|
|
|
|
|
self.insert_inode(inode)
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-01 18:09:45 +02:00
|
|
|
pub fn get_inode_by_first_cluster(&self, first_cluster: u32) -> Option<InodeRef> {
|
2025-07-31 01:14:28 +02:00
|
|
|
if first_cluster == 0 {
|
|
|
|
|
debug!("trying to get inode by first cluster 0");
|
|
|
|
|
|
|
|
|
|
return None;
|
|
|
|
|
}
|
|
|
|
|
|
2025-07-30 21:39:45 +02:00
|
|
|
let ino = self.ino_by_first_cluster.get(&first_cluster)?;
|
|
|
|
|
|
|
|
|
|
if let Some(inode) = self.inode_table.get(ino) {
|
2025-08-01 18:09:45 +02:00
|
|
|
Some(Rc::clone(inode))
|
2025-07-30 21:39:45 +02:00
|
|
|
} else {
|
|
|
|
|
debug!(
|
|
|
|
|
"first cluster {} is mapped to ino {}, but inode is not in table",
|
|
|
|
|
first_cluster, ino
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
None
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-01 18:09:45 +02:00
|
|
|
pub fn get_inode_by_fh(&self, fh: u64) -> Option<InodeRef> {
|
2025-07-31 23:47:45 +02:00
|
|
|
let ino = *self.ino_by_fh.get(&fh)?;
|
2025-07-31 01:14:28 +02:00
|
|
|
|
2025-08-01 18:09:45 +02:00
|
|
|
if let Some(inode) = self.get_inode(ino).cloned() {
|
2025-07-31 01:14:28 +02:00
|
|
|
Some(inode)
|
|
|
|
|
} else {
|
|
|
|
|
debug!("fh {} is mapped to ino {}, but inode is not in table", fh, ino);
|
|
|
|
|
|
|
|
|
|
None
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-01 18:09:45 +02:00
|
|
|
pub fn get_inode_by_path(&self, path: &str) -> Option<InodeRef> {
|
2025-07-31 23:47:45 +02:00
|
|
|
let ino = *self.ino_by_path.get(path)?;
|
|
|
|
|
|
2025-08-01 18:09:45 +02:00
|
|
|
if let Some(inode) = self.get_inode(ino).cloned() {
|
2025-07-31 23:47:45 +02:00
|
|
|
Some(inode)
|
|
|
|
|
} else {
|
|
|
|
|
debug!("path {} is mapped to ino {}, but inode is not in table", path, ino);
|
|
|
|
|
|
|
|
|
|
None
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-07-27 14:38:31 +02:00
|
|
|
}
|