2025-07-29 20:33:36 +02:00
|
|
|
use std::cell::{LazyCell, RefCell};
|
2025-07-31 23:47:45 +02:00
|
|
|
use std::rc::Rc;
|
2025-07-27 23:17:03 +02:00
|
|
|
use std::time::SystemTime;
|
|
|
|
|
|
|
|
|
|
use chrono::{NaiveDateTime, NaiveTime};
|
|
|
|
|
use fat_bits::FatFs;
|
2025-07-30 21:39:45 +02:00
|
|
|
use fat_bits::dir::DirEntry;
|
2025-08-01 01:08:48 +02:00
|
|
|
use fat_bits::iter::ClusterChainReader;
|
2025-07-27 23:17:03 +02:00
|
|
|
use fuser::FileAttr;
|
2025-08-01 01:08:48 +02:00
|
|
|
use libc::{EISDIR, ENOTDIR};
|
2025-07-31 01:14:28 +02:00
|
|
|
use log::debug;
|
2025-07-29 20:33:36 +02:00
|
|
|
use rand::{Rng, SeedableRng as _};
|
|
|
|
|
|
|
|
|
|
thread_local! {
|
|
|
|
|
/// SAFETY
|
|
|
|
|
///
|
|
|
|
|
/// do not access this directly, only invoke the get_random_u32 function
|
|
|
|
|
// static RNG: LazyCell<UnsafeCell<rand::rngs::SmallRng>> = LazyCell::new(|| UnsafeCell::new(rand::rngs::SmallRng::from_os_rng()));
|
|
|
|
|
|
|
|
|
|
/// performance should not be a bottleneck here, since we only need to occasionally generate u32s to
|
|
|
|
|
/// be used as generations in inodes
|
|
|
|
|
/// if at some point (contrary to expectations) it should become, can switch it to an UnsafeCell
|
|
|
|
|
static RNG: LazyCell<RefCell<rand::rngs::SmallRng>> = LazyCell::new(|| RefCell::new(rand::rngs::SmallRng::from_os_rng()));
|
|
|
|
|
}
|
|
|
|
|
|
2025-07-30 21:39:45 +02:00
|
|
|
fn get_random<T>() -> T
|
|
|
|
|
where
|
|
|
|
|
rand::distr::StandardUniform: rand::distr::Distribution<T>,
|
|
|
|
|
{
|
2025-07-29 20:33:36 +02:00
|
|
|
// RNG.with(|x| unsafe {
|
|
|
|
|
// let rng = &mut (*x.get());
|
|
|
|
|
|
|
|
|
|
// rng.random::<u32>()
|
|
|
|
|
// })
|
|
|
|
|
|
|
|
|
|
RNG.with(|rng| rng.borrow_mut().random())
|
|
|
|
|
}
|
2025-07-27 23:17:03 +02:00
|
|
|
|
2025-07-29 20:33:36 +02:00
|
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
2025-07-27 23:17:03 +02:00
|
|
|
pub enum Kind {
|
|
|
|
|
File,
|
|
|
|
|
Dir,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl From<Kind> for fuser::FileType {
|
|
|
|
|
fn from(value: Kind) -> Self {
|
|
|
|
|
match value {
|
|
|
|
|
Kind::File => fuser::FileType::RegularFile,
|
|
|
|
|
Kind::Dir => fuser::FileType::Directory,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-07-31 23:47:45 +02:00
|
|
|
pub const ROOT_INO: u64 = 1;
|
2025-07-29 20:33:36 +02:00
|
|
|
|
2025-07-27 23:17:03 +02:00
|
|
|
#[derive(Debug)]
|
|
|
|
|
#[allow(dead_code)]
|
|
|
|
|
pub struct Inode {
|
|
|
|
|
ino: u64,
|
2025-07-30 21:39:45 +02:00
|
|
|
// FUSE uses a u64 for generation, but the Linux kernel only handles u32s anyway, truncating
|
|
|
|
|
// the high bits, so using more is pretty pointless and possibly even detrimental
|
2025-07-29 20:33:36 +02:00
|
|
|
generation: u32,
|
2025-07-27 23:17:03 +02:00
|
|
|
|
2025-07-30 21:39:45 +02:00
|
|
|
ref_count: u64,
|
|
|
|
|
|
2025-07-31 23:47:45 +02:00
|
|
|
parent_ino: u64,
|
|
|
|
|
|
2025-07-27 23:17:03 +02:00
|
|
|
size: u64,
|
|
|
|
|
block_size: u32,
|
|
|
|
|
|
|
|
|
|
kind: Kind,
|
|
|
|
|
|
|
|
|
|
read_only: bool,
|
|
|
|
|
|
|
|
|
|
atime: SystemTime,
|
|
|
|
|
mtime: SystemTime,
|
|
|
|
|
// ctime: SystemTime,
|
|
|
|
|
crtime: SystemTime,
|
|
|
|
|
|
|
|
|
|
uid: u32,
|
|
|
|
|
gid: u32,
|
|
|
|
|
|
|
|
|
|
first_cluster: u32,
|
2025-07-31 23:47:45 +02:00
|
|
|
|
|
|
|
|
path: Rc<str>,
|
2025-07-27 23:17:03 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[allow(dead_code)]
|
|
|
|
|
impl Inode {
|
2025-07-30 21:39:45 +02:00
|
|
|
fn new_generation() -> u32 {
|
|
|
|
|
let rand: u16 = get_random();
|
|
|
|
|
|
|
|
|
|
let secs = SystemTime::UNIX_EPOCH
|
|
|
|
|
.elapsed()
|
|
|
|
|
.map(|dur| dur.as_secs() as u16)
|
|
|
|
|
.unwrap_or(0);
|
|
|
|
|
|
|
|
|
|
((secs as u32) << 16) | rand as u32
|
|
|
|
|
}
|
|
|
|
|
|
2025-07-31 23:47:45 +02:00
|
|
|
pub fn new(
|
|
|
|
|
fat_fs: &FatFs,
|
|
|
|
|
dir_entry: &DirEntry,
|
|
|
|
|
ino: u64,
|
|
|
|
|
uid: u32,
|
|
|
|
|
gid: u32,
|
|
|
|
|
path: impl Into<Rc<str>>,
|
|
|
|
|
parent_ino: u64,
|
|
|
|
|
) -> Inode {
|
2025-07-27 23:17:03 +02:00
|
|
|
assert!(dir_entry.is_file() || dir_entry.is_dir());
|
|
|
|
|
|
2025-07-30 21:39:45 +02:00
|
|
|
let generation = Self::new_generation();
|
2025-07-29 20:33:36 +02:00
|
|
|
|
2025-07-27 23:17:03 +02:00
|
|
|
let kind = if dir_entry.is_dir() {
|
|
|
|
|
Kind::Dir
|
|
|
|
|
} else {
|
|
|
|
|
Kind::File
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let datetime_to_system = |datetime: NaiveDateTime| -> SystemTime {
|
|
|
|
|
datetime
|
|
|
|
|
.and_local_timezone(chrono::Local)
|
|
|
|
|
.single()
|
|
|
|
|
.map(|x| -> SystemTime { x.into() })
|
|
|
|
|
.unwrap_or(SystemTime::UNIX_EPOCH)
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let atime = datetime_to_system(dir_entry.last_access_date().and_time(NaiveTime::default()));
|
|
|
|
|
let mtime = datetime_to_system(dir_entry.write_time());
|
|
|
|
|
let crtime = datetime_to_system(dir_entry.create_time());
|
|
|
|
|
|
2025-07-31 23:47:45 +02:00
|
|
|
let path = path.into();
|
|
|
|
|
|
2025-07-31 01:14:28 +02:00
|
|
|
debug!(
|
2025-07-31 23:47:45 +02:00
|
|
|
"creating new inode: ino: {} name: {} path: {}",
|
2025-07-31 01:14:28 +02:00
|
|
|
ino,
|
2025-07-31 23:47:45 +02:00
|
|
|
dir_entry.name_string(),
|
|
|
|
|
path
|
2025-07-31 01:14:28 +02:00
|
|
|
);
|
|
|
|
|
|
2025-07-27 23:17:03 +02:00
|
|
|
Inode {
|
2025-07-30 21:39:45 +02:00
|
|
|
ino,
|
2025-07-29 20:33:36 +02:00
|
|
|
generation,
|
2025-07-30 21:39:45 +02:00
|
|
|
ref_count: 0,
|
2025-07-31 23:47:45 +02:00
|
|
|
parent_ino,
|
2025-07-27 23:17:03 +02:00
|
|
|
size: dir_entry.file_size() as u64,
|
|
|
|
|
block_size: fat_fs.bpb().bytes_per_sector() as u32,
|
|
|
|
|
kind,
|
|
|
|
|
read_only: dir_entry.is_readonly(),
|
|
|
|
|
atime,
|
|
|
|
|
mtime,
|
|
|
|
|
crtime,
|
|
|
|
|
uid,
|
|
|
|
|
gid,
|
|
|
|
|
first_cluster: dir_entry.first_cluster(),
|
2025-07-31 23:47:45 +02:00
|
|
|
path,
|
2025-07-27 23:17:03 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-07-31 01:14:28 +02:00
|
|
|
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,
|
2025-07-31 23:47:45 +02:00
|
|
|
parent_ino: ROOT_INO, // parent is self
|
2025-07-31 01:14:28 +02:00
|
|
|
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,
|
2025-07-31 23:47:45 +02:00
|
|
|
path: "/".into(),
|
2025-07-31 01:14:28 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-07-30 21:39:45 +02:00
|
|
|
pub fn ino(&self) -> u64 {
|
|
|
|
|
self.ino
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn generation(&self) -> u32 {
|
|
|
|
|
self.generation
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn ref_count(&self) -> u64 {
|
|
|
|
|
self.ref_count
|
|
|
|
|
}
|
|
|
|
|
|
2025-07-31 23:47:45 +02:00
|
|
|
pub fn inc_ref_count(&mut self) {
|
|
|
|
|
debug!(
|
|
|
|
|
"increasing ref_count of ino {} by 1 (new ref_count: {})",
|
|
|
|
|
self.ino(),
|
|
|
|
|
self.ref_count() + 1
|
|
|
|
|
);
|
|
|
|
|
|
2025-07-31 01:14:28 +02:00
|
|
|
self.ref_count += 1;
|
|
|
|
|
}
|
|
|
|
|
|
2025-07-31 23:47:45 +02:00
|
|
|
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),
|
|
|
|
|
);
|
|
|
|
|
|
2025-07-31 01:14:28 +02:00
|
|
|
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
|
|
|
|
|
}
|
|
|
|
|
|
2025-07-31 23:47:45 +02:00
|
|
|
pub fn parent_ino(&self) -> u64 {
|
|
|
|
|
self.parent_ino
|
|
|
|
|
}
|
|
|
|
|
|
2025-07-31 01:14:28 +02:00
|
|
|
pub fn kind(&self) -> Kind {
|
|
|
|
|
self.kind
|
2025-07-30 21:39:45 +02:00
|
|
|
}
|
|
|
|
|
|
2025-08-01 01:08:48 +02:00
|
|
|
pub fn is_file(&self) -> bool {
|
|
|
|
|
self.kind == Kind::File
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn is_dir(&self) -> bool {
|
|
|
|
|
self.kind == Kind::Dir
|
|
|
|
|
}
|
|
|
|
|
|
2025-07-30 21:39:45 +02:00
|
|
|
pub fn first_cluster(&self) -> u32 {
|
|
|
|
|
self.first_cluster
|
|
|
|
|
}
|
|
|
|
|
|
2025-07-31 23:47:45 +02:00
|
|
|
pub fn path(&self) -> Rc<str> {
|
|
|
|
|
Rc::clone(&self.path)
|
|
|
|
|
}
|
|
|
|
|
|
2025-07-31 01:14:28 +02:00
|
|
|
pub fn is_root(&self) -> bool {
|
|
|
|
|
self.ino == ROOT_INO
|
|
|
|
|
}
|
|
|
|
|
|
2025-07-27 23:17:03 +02:00
|
|
|
pub fn file_attr(&self) -> FileAttr {
|
|
|
|
|
let perm = if self.read_only { 0o555 } else { 0o777 };
|
|
|
|
|
|
|
|
|
|
FileAttr {
|
|
|
|
|
ino: self.ino,
|
|
|
|
|
size: self.size,
|
|
|
|
|
blocks: self.size / self.block_size as u64,
|
|
|
|
|
atime: self.atime,
|
|
|
|
|
mtime: self.mtime,
|
|
|
|
|
ctime: self.mtime,
|
|
|
|
|
crtime: self.crtime,
|
|
|
|
|
kind: self.kind.into(),
|
|
|
|
|
perm,
|
|
|
|
|
nlink: 1,
|
|
|
|
|
uid: self.uid,
|
|
|
|
|
gid: self.gid,
|
|
|
|
|
rdev: 0,
|
|
|
|
|
blksize: self.block_size,
|
|
|
|
|
flags: 0,
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-07-29 20:33:36 +02:00
|
|
|
|
2025-07-30 21:39:45 +02:00
|
|
|
pub fn dir_iter(&self, fat_fs: &FatFs) -> Result<impl Iterator<Item = DirEntry>, i32> {
|
|
|
|
|
// anyhow::ensure!(self.kind == Kind::Dir, "cannot dir_iter on a file");
|
2025-07-29 20:33:36 +02:00
|
|
|
|
2025-07-30 21:39:45 +02:00
|
|
|
if self.kind != Kind::Dir {
|
|
|
|
|
return Err(ENOTDIR);
|
|
|
|
|
}
|
2025-07-29 20:33:36 +02:00
|
|
|
|
2025-07-31 01:14:28 +02:00
|
|
|
if self.is_root() {
|
2025-07-29 20:33:36 +02:00
|
|
|
// root dir
|
|
|
|
|
|
|
|
|
|
return Ok(fat_fs.root_dir_iter());
|
|
|
|
|
}
|
|
|
|
|
|
2025-07-30 21:39:45 +02:00
|
|
|
Ok(fat_fs.dir_iter(self.first_cluster))
|
2025-07-29 20:33:36 +02:00
|
|
|
}
|
2025-08-01 01:08:48 +02:00
|
|
|
|
|
|
|
|
pub fn file_reader<'a>(&'a self, fat_fs: &'a FatFs) -> Result<ClusterChainReader<'a>, i32> {
|
|
|
|
|
if self.is_dir() {
|
|
|
|
|
return Err(EISDIR);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Ok(fat_fs.file_reader(self.first_cluster()))
|
|
|
|
|
}
|
2025-07-27 23:17:03 +02:00
|
|
|
}
|