use std::cell::{LazyCell, RefCell}; use std::time::SystemTime; use chrono::{NaiveDateTime, NaiveTime}; use fat_bits::FatFs; use fat_bits::dir::DirEntry; use fuser::FileAttr; use libc::ENOTDIR; use rand::{Rng, SeedableRng as _}; thread_local! { /// SAFETY /// /// do not access this directly, only invoke the get_random_u32 function // static RNG: LazyCell> = 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> = LazyCell::new(|| RefCell::new(rand::rngs::SmallRng::from_os_rng())); } fn get_random() -> T where rand::distr::StandardUniform: rand::distr::Distribution, { // RNG.with(|x| unsafe { // let rng = &mut (*x.get()); // rng.random::() // }) RNG.with(|rng| rng.borrow_mut().random()) } #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum Kind { File, Dir, } impl From for fuser::FileType { fn from(value: Kind) -> Self { match value { Kind::File => fuser::FileType::RegularFile, Kind::Dir => fuser::FileType::Directory, } } } const ROOT_INO: u64 = 1; #[derive(Debug)] #[allow(dead_code)] pub struct Inode { ino: u64, // 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 generation: u32, ref_count: u64, 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, } #[allow(dead_code)] impl Inode { 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 } 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()); let generation = Self::new_generation(); 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()); Inode { ino, generation, ref_count: 0, 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(), } } pub fn ino(&self) -> u64 { self.ino } pub fn generation(&self) -> u32 { self.generation } pub fn ref_count(&self) -> u64 { self.ref_count } pub fn ref_count_mut(&mut self) -> &mut u64 { &mut self.ref_count } pub fn first_cluster(&self) -> u32 { self.first_cluster } 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, } } pub fn dir_iter(&self, fat_fs: &FatFs) -> Result, i32> { // anyhow::ensure!(self.kind == Kind::Dir, "cannot dir_iter on a file"); if self.kind != Kind::Dir { return Err(ENOTDIR); } if self.ino == ROOT_INO { // root dir return Ok(fat_fs.root_dir_iter()); } Ok(fat_fs.dir_iter(self.first_cluster)) } }