implemented setattr (properly)

This commit is contained in:
Moritz Gmeiner 2025-08-02 17:22:32 +02:00
commit bdd01bd70e
8 changed files with 356 additions and 199 deletions

2
Cargo.lock generated
View file

@ -238,6 +238,7 @@ dependencies = [
"chrono", "chrono",
"compact_str", "compact_str",
"enum_dispatch", "enum_dispatch",
"log",
"static_assertions", "static_assertions",
"thiserror 2.0.12", "thiserror 2.0.12",
] ]
@ -255,6 +256,7 @@ name = "fat-fuse"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"bitflags",
"chrono", "chrono",
"compact_string", "compact_string",
"fat-bits", "fat-bits",

View file

@ -12,5 +12,6 @@ chrono = { version = "0.4.41", default-features = false, features = [
] } ] }
compact_str = "0.9.0" compact_str = "0.9.0"
enum_dispatch = "0.3.13" enum_dispatch = "0.3.13"
log = "0.4.27"
static_assertions = "1.1.0" static_assertions = "1.1.0"
thiserror = "2.0.12" thiserror = "2.0.12"

View file

@ -1,8 +1,8 @@
use std::time::SystemTime; use std::time::SystemTime;
use chrono::{DateTime, Datelike, NaiveDate, NaiveTime, Timelike, Utc}; use chrono::{DateTime, Datelike, Local, NaiveDate, NaiveTime, Timelike};
#[derive(Debug)] #[derive(Debug, Clone, Copy)]
pub struct Date { pub struct Date {
repr: u16, repr: u16,
} }
@ -22,8 +22,7 @@ impl Date {
Ok(date) Ok(date)
} }
#[allow(dead_code)] fn from_day_month_year(day: u8, month: u8, year: u16) -> anyhow::Result<Date> {
pub fn from_day_month_year(day: u8, month: u8, year: u16) -> anyhow::Result<Date> {
anyhow::ensure!(day <= 31, "invalid day: {}", day); anyhow::ensure!(day <= 31, "invalid day: {}", day);
anyhow::ensure!(month <= 12, "invalid month: {}", month); anyhow::ensure!(month <= 12, "invalid month: {}", month);
anyhow::ensure!(1980 <= year && year <= 2107, "invalid year: {}", year); anyhow::ensure!(1980 <= year && year <= 2107, "invalid year: {}", year);
@ -33,10 +32,7 @@ impl Date {
Ok(Date { repr }) Ok(Date { repr })
} }
#[allow(dead_code)] pub fn from_datetime(datetime: DateTime<Local>) -> anyhow::Result<Date> {
pub fn from_system_time(time: SystemTime) -> anyhow::Result<Date> {
let datetime: DateTime<Utc> = time.into();
let date = datetime.date_naive(); let date = datetime.date_naive();
Date::from_day_month_year( Date::from_day_month_year(
@ -67,7 +63,7 @@ impl Date {
} }
} }
#[derive(Debug)] #[derive(Debug, Clone, Copy)]
pub struct Time { pub struct Time {
repr: u16, repr: u16,
} }
@ -79,8 +75,7 @@ impl Time {
Ok(time) Ok(time)
} }
#[allow(dead_code)] fn from_seconds_minutes_hours(seconds: u8, minutes: u8, hours: u8) -> anyhow::Result<Time> {
pub fn from_seconds_minutes_hours(seconds: u8, minutes: u8, hours: u8) -> anyhow::Result<Time> {
anyhow::ensure!(seconds <= 58 && seconds % 2 == 0, "invalid seconds: {}", seconds); anyhow::ensure!(seconds <= 58 && seconds % 2 == 0, "invalid seconds: {}", seconds);
anyhow::ensure!(minutes <= 59, "invalid minutes: {}", minutes); anyhow::ensure!(minutes <= 59, "invalid minutes: {}", minutes);
anyhow::ensure!(hours <= 23, "invalid hours: {}", hours); anyhow::ensure!(hours <= 23, "invalid hours: {}", hours);
@ -90,10 +85,7 @@ impl Time {
Ok(Time { repr }) Ok(Time { repr })
} }
#[allow(dead_code)] pub fn from_datetime(datetime: DateTime<Local>) -> anyhow::Result<Time> {
pub fn from_system_time(time: SystemTime) -> anyhow::Result<Time> {
let datetime: DateTime<Utc> = time.into();
let time = datetime.time(); let time = datetime.time();
let seconds = (time.second() as u8) & !0x01; let seconds = (time.second() as u8) & !0x01;

View file

@ -1,9 +1,11 @@
use std::fmt::Display; use std::fmt::Display;
use std::io::{Read, Write}; use std::io::{Read, Write};
use std::time::SystemTime;
use bitflags::bitflags; use bitflags::bitflags;
use chrono::{NaiveDate, NaiveDateTime, TimeDelta}; use chrono::{DateTime, Local, NaiveDate, NaiveDateTime, TimeDelta, Timelike};
use compact_str::CompactString; use compact_str::CompactString;
use log::debug;
use crate::FatFs; use crate::FatFs;
use crate::datetime::{Date, Time}; use crate::datetime::{Date, Time};
@ -52,7 +54,7 @@ impl Display for Attr {
/// represents an entry in a diectory /// represents an entry in a diectory
#[derive(Debug)] #[derive(Debug)]
pub struct DirEntry { pub struct DirEntry {
name: [u8; 13], name: [u8; 11],
attr: Attr, attr: Attr,
create_time_tenths: u8, create_time_tenths: u8,
@ -71,6 +73,8 @@ pub struct DirEntry {
checksum: u8, checksum: u8,
long_name: Option<CompactString>, long_name: Option<CompactString>,
n_longname_slots: u8,
offset: u64, offset: u64,
} }
@ -96,62 +100,12 @@ impl Display for DirEntry {
} }
impl DirEntry { impl DirEntry {
fn load_name(bytes: [u8; 11], attr: &Attr) -> [u8; 13] {
let mut name = [0; 13];
let mut iter = name.iter_mut();
if attr.contains(Attr::Hidden) && !attr.contains(Attr::VolumeId) {
// hidden file or folder
*iter.next().unwrap() = b'.';
}
fn truncate_trailing_spaces(mut bytes: &[u8]) -> &[u8] {
while bytes.last() == Some(&0x20) {
bytes = &bytes[..bytes.len() - 1];
}
bytes
}
let stem_bytes = truncate_trailing_spaces(&bytes[..8]);
let ext_bytes = truncate_trailing_spaces(&bytes[8..]);
for &c in stem_bytes {
let c = match c {
// reject non-ascii
// TODO: implement code pages? probably not...
c if c >= 128 => b'?',
c => c,
};
*iter.next().unwrap() = c;
}
if !ext_bytes.is_empty() {
*iter.next().unwrap() = b'.';
for &c in ext_bytes {
let c = match c {
// reject non-ascii
// TODO: implement code pages? probably not...
c if c >= 128 => b'?',
c => c,
};
*iter.next().unwrap() = c;
}
}
name
}
pub fn load(bytes: &[u8], offset: u64) -> anyhow::Result<DirEntry> { pub fn load(bytes: &[u8], offset: u64) -> anyhow::Result<DirEntry> {
assert_eq!(bytes.len(), 32); assert_eq!(bytes.len(), 32);
let attr = Attr::from_bits_truncate(bytes[11]); let attr = Attr::from_bits_truncate(bytes[11]);
let name = Self::load_name(bytes[..11].try_into().unwrap(), &attr); let name = bytes[..11].try_into().unwrap();
let create_time_tenths = bytes[13]; let create_time_tenths = bytes[13];
anyhow::ensure!( anyhow::ensure!(
@ -200,11 +154,39 @@ impl DirEntry {
write_date, write_date,
file_size, file_size,
long_name: None, long_name: None,
n_longname_slots: 0,
checksum: Self::checksum(&bytes[..11]), checksum: Self::checksum(&bytes[..11]),
offset, offset,
}) })
} }
pub fn create(name: &str, attr: Attr) -> anyhow::Result<Self> {
let now: DateTime<Local> = SystemTime::now().into();
let create_date = Date::from_datetime(now)?;
let create_time = Time::from_datetime(now)?;
let create_time_tenths = (now.time().nanosecond() / 100_000_000) as u8;
let name = [0; 11];
Ok(DirEntry {
name,
attr,
create_time_tenths,
create_time,
create_date,
last_access_date: create_date,
first_cluster: 0,
write_time: create_time,
write_date: create_date,
file_size: 0,
checksum: Self::checksum(&name),
long_name: None,
n_longname_slots: 0,
offset: !0,
})
}
fn write(&self, mut writer: impl Write) -> std::io::Result<()> { fn write(&self, mut writer: impl Write) -> std::io::Result<()> {
let mut buf = [0; 32]; let mut buf = [0; 32];
@ -263,7 +245,7 @@ impl DirEntry {
} }
/// write this DisEntry back to the underlying data /// write this DisEntry back to the underlying data
pub fn update(&self, fat_fs: &FatFs) -> std::io::Result<()> { pub fn write_back(&self, fat_fs: &FatFs) -> std::io::Result<()> {
eprintln!("making new SubSliceMut at offset {:#X}", self.offset); eprintln!("making new SubSliceMut at offset {:#X}", self.offset);
let sub_slice = SubSliceMut::new(fat_fs.inner.clone(), self.offset, 32); let sub_slice = SubSliceMut::new(fat_fs.inner.clone(), self.offset, 32);
@ -279,6 +261,8 @@ impl DirEntry {
// paste over with zeros // paste over with zeros
sub_slice.write_all(&[0; 31]) sub_slice.write_all(&[0; 31])
// TODO: erase long filename entries as well
} }
/// indicates this DirEntry is empty /// indicates this DirEntry is empty
@ -329,13 +313,7 @@ impl DirEntry {
} }
pub fn name(&self) -> &[u8] { pub fn name(&self) -> &[u8] {
let mut name: &[u8] = &self.name; &self.name
while let Some(&0) = name.last() {
name = &name[..name.len() - 1];
}
name
} }
pub fn stem(&self) -> &[u8] { pub fn stem(&self) -> &[u8] {
@ -386,7 +364,7 @@ impl DirEntry {
{ {
'?' '?'
} else { } else {
c as char (c as char).to_ascii_uppercase()
} }
} }
@ -405,8 +383,9 @@ impl DirEntry {
self.long_name.as_deref() self.long_name.as_deref()
} }
pub fn set_long_name(&mut self, long_name: CompactString) { pub fn set_long_name(&mut self, long_name: CompactString, n_slots: u8) {
self.long_name = Some(long_name); self.long_name = Some(long_name);
self.n_longname_slots = n_slots;
} }
pub fn attr(&self) -> Attr { pub fn attr(&self) -> Attr {
@ -428,6 +407,15 @@ impl DirEntry {
self.last_access_date.to_naive_date() self.last_access_date.to_naive_date()
} }
pub fn update_last_access_date(
&mut self,
time: impl Into<DateTime<Local>>,
) -> anyhow::Result<()> {
self.last_access_date = Date::from_datetime(time.into())?;
Ok(())
}
pub fn first_cluster(&self) -> u32 { pub fn first_cluster(&self) -> u32 {
self.first_cluster self.first_cluster
} }
@ -439,6 +427,15 @@ impl DirEntry {
NaiveDateTime::new(date, time) NaiveDateTime::new(date, time)
} }
pub fn update_write_time(&mut self, time: impl Into<DateTime<Local>>) -> anyhow::Result<()> {
let time = time.into();
self.write_date = Date::from_datetime(time)?;
self.write_time = Time::from_datetime(time)?;
Ok(())
}
pub fn file_size(&self) -> u32 { pub fn file_size(&self) -> u32 {
self.file_size self.file_size
} }
@ -461,6 +458,7 @@ impl DirEntry {
/// long filename entry in a directory /// long filename entry in a directory
/// ///
/// this should not be exposed to end users, only for internal consumption in the DirIter /// this should not be exposed to end users, only for internal consumption in the DirIter
#[derive(Debug)]
struct LongNameDirEntry { struct LongNameDirEntry {
ordinal: u8, ordinal: u8,
is_last: bool, is_last: bool,
@ -535,6 +533,7 @@ impl LongNameDirEntry {
/// ///
/// should not be exposed publicly, end users only see DirEntries /// should not be exposed publicly, end users only see DirEntries
/// just for making the bytes -> DirEntry loading a bit easier /// just for making the bytes -> DirEntry loading a bit easier
#[derive(Debug)]
enum DirEntryWrapper { enum DirEntryWrapper {
Regular(DirEntry), Regular(DirEntry),
LongName(LongNameDirEntry), LongName(LongNameDirEntry),
@ -561,6 +560,7 @@ struct LongFilenameBuf {
rev_buf: Vec<u16>, rev_buf: Vec<u16>,
checksum: Option<u8>, checksum: Option<u8>,
last_ordinal: Option<u8>, last_ordinal: Option<u8>,
n_slots: u8,
} }
impl LongFilenameBuf { impl LongFilenameBuf {
@ -568,6 +568,7 @@ impl LongFilenameBuf {
self.rev_buf.clear(); self.rev_buf.clear();
self.checksum = None; self.checksum = None;
self.last_ordinal = None; self.last_ordinal = None;
self.n_slots = 0;
} }
pub fn next(&mut self, dir_entry: LongNameDirEntry) -> anyhow::Result<()> { pub fn next(&mut self, dir_entry: LongNameDirEntry) -> anyhow::Result<()> {
@ -598,6 +599,8 @@ impl LongFilenameBuf {
self.checksum = Some(dir_entry.checksum()); self.checksum = Some(dir_entry.checksum());
self.last_ordinal = Some(dir_entry.ordinal()); self.last_ordinal = Some(dir_entry.ordinal());
self.n_slots += 1;
return Ok(()); return Ok(());
} }
@ -629,7 +632,10 @@ impl LongFilenameBuf {
self.rev_buf.extend(name.iter().rev()); self.rev_buf.extend(name.iter().rev());
} }
pub fn get_buf(&mut self, checksum: u8) -> anyhow::Result<Option<impl Iterator<Item = u16>>> { pub fn get_buf(
&mut self,
checksum: u8,
) -> anyhow::Result<Option<(impl Iterator<Item = u16>, u8)>> {
if self.checksum.is_none() { if self.checksum.is_none() {
return Ok(None); return Ok(None);
} }
@ -653,7 +659,7 @@ impl LongFilenameBuf {
anyhow::ensure!(self.rev_buf.len() <= 255, "long filename too long"); anyhow::ensure!(self.rev_buf.len() <= 255, "long filename too long");
Ok(Some(self.rev_buf.iter().copied().rev())) Ok(Some((self.rev_buf.iter().copied().rev(), self.n_slots)))
} }
} }
@ -713,7 +719,7 @@ impl Iterator for DirIter<'_> {
return next_impl(me); return next_impl(me);
} }
if let Some(iter) = me if let Some((iter, n_slots)) = me
.long_filename_buf .long_filename_buf
.get_buf(dir_entry.checksum) .get_buf(dir_entry.checksum)
.map_err(|e| { .map_err(|e| {
@ -729,7 +735,7 @@ impl Iterator for DirIter<'_> {
let long_filename: CompactString = let long_filename: CompactString =
char::decode_utf16(iter).filter_map(|x| x.ok()).collect(); char::decode_utf16(iter).filter_map(|x| x.ok()).collect();
dir_entry.set_long_name(long_filename); dir_entry.set_long_name(long_filename, n_slots);
} }
me.long_filename_buf.reset(); me.long_filename_buf.reset();
@ -741,7 +747,7 @@ impl Iterator for DirIter<'_> {
Ok(x) => x, Ok(x) => x,
Err(e) => { Err(e) => {
// print error message, try next // print error message, try next
eprintln!("{}", e); debug!("{}", e);
self.next() self.next()
} }

View file

@ -5,6 +5,7 @@ edition = "2024"
[dependencies] [dependencies]
anyhow = "1.0.98" anyhow = "1.0.98"
bitflags = "2.9.1"
chrono = { version = "0.4.41", default-features = false, features = ["alloc", "clock", "std"] } chrono = { version = "0.4.41", default-features = false, features = ["alloc", "clock", "std"] }
compact_string = "0.1.0" compact_string = "0.1.0"
fat-bits = { version = "0.1.0", path = "../fat-bits" } fat-bits = { version = "0.1.0", path = "../fat-bits" }

View file

@ -1,16 +1,54 @@
use std::ffi::c_int; use std::ffi::c_int;
use std::io::{Read, Write}; use std::io::{Read, Write};
use std::time::Duration; use std::time::{Duration, SystemTime};
use bitflags::bitflags;
use fat_bits::dir::DirEntry; use fat_bits::dir::DirEntry;
use fuser::{FileType, Filesystem}; use fuser::{FileType, Filesystem};
use libc::{EBADF, EINVAL, EIO, EISDIR, ENOENT, ENOSYS, ENOTDIR}; use libc::{EACCES, EBADF, EINVAL, EIO, EISDIR, ENOENT, ENOSYS, ENOTDIR};
use log::{debug, error}; use log::{debug, error};
use crate::FatFuse; use crate::FatFuse;
use crate::inode::InodeRef;
const TTL: Duration = Duration::from_secs(1); const TTL: Duration = Duration::from_secs(1);
bitflags! {
#[derive(Debug)]
struct OpenFlags: i32 {
const ReadOnly = libc::O_RDONLY;
const WriteOnly = libc::O_WRONLY;
const ReadWrite = libc::O_RDWR;
const Read = libc::O_RDONLY | libc::O_RDWR;
const Write = libc::O_WRONLY | libc::O_RDWR;
const Append = libc::O_APPEND;
const Create = libc::O_CREAT;
const Exclusive = libc::O_EXCL;
const Truncate = libc::O_TRUNC;
const _ = !0;
}
}
fn get_inode_by_fh_or_ino(fat_fuse: &FatFuse, fh: Option<u64>, ino: u64) -> Result<&InodeRef, i32> {
fh.map(|fh| {
let inode = fat_fuse.get_inode_by_fh(fh).ok_or(EBADF)?;
let found_ino = inode.borrow().ino();
if found_ino != ino {
debug!("inode associated with fh {} has ino {}, but expected {ino}", fh, found_ino);
return Err(EBADF);
}
Ok(inode)
})
.unwrap_or_else(|| fat_fuse.get_inode(ino).ok_or(ENOENT))
}
impl Filesystem for FatFuse { impl Filesystem for FatFuse {
fn init( fn init(
&mut self, &mut self,
@ -82,6 +120,8 @@ impl Filesystem for FatFuse {
reply.entry(&TTL, &attr, generation as u64); reply.entry(&TTL, &attr, generation as u64);
inode.inc_ref_count(); inode.inc_ref_count();
// TODO: update access time
} }
fn forget(&mut self, _req: &fuser::Request<'_>, ino: u64, nlookup: u64) { fn forget(&mut self, _req: &fuser::Request<'_>, ino: u64, nlookup: u64) {
@ -112,63 +152,81 @@ impl Filesystem for FatFuse {
fh: Option<u64>, fh: Option<u64>,
reply: fuser::ReplyAttr, reply: fuser::ReplyAttr,
) { ) {
let inode = if let Some(fh) = fh { let inode = match get_inode_by_fh_or_ino(self, fh, ino) {
let Some(inode) = self.get_inode_by_fh(fh) else { Ok(inode) => inode,
reply.error(EBADF); Err(err) => {
reply.error(err);
return; return;
}; }
inode
} else if let Some(inode) = self.get_inode(ino).cloned() {
inode
} else {
reply.error(ENOENT);
return;
}; };
let inode = inode.borrow(); let mut inode = inode.borrow_mut();
let attr = inode.file_attr(); let attr = inode.file_attr();
inode.update_atime(SystemTime::now());
reply.attr(&TTL, &attr); reply.attr(&TTL, &attr);
} }
#[allow(unused_variables)]
fn setattr( fn setattr(
&mut self, &mut self,
_req: &fuser::Request<'_>, _req: &fuser::Request<'_>,
ino: u64, ino: u64,
mode: Option<u32>, _mode: Option<u32>,
uid: Option<u32>, _uid: Option<u32>,
gid: Option<u32>, _gid: Option<u32>,
size: Option<u64>, size: Option<u64>,
_atime: Option<fuser::TimeOrNow>, atime: Option<fuser::TimeOrNow>,
_mtime: Option<fuser::TimeOrNow>, mtime: Option<fuser::TimeOrNow>,
_ctime: Option<std::time::SystemTime>, _ctime: Option<std::time::SystemTime>,
fh: Option<u64>, fh: Option<u64>,
_crtime: Option<std::time::SystemTime>, _crtime: Option<std::time::SystemTime>,
_chgtime: Option<std::time::SystemTime>, _chgtime: Option<std::time::SystemTime>,
_bkuptime: Option<std::time::SystemTime>, _bkuptime: Option<std::time::SystemTime>,
flags: Option<u32>, _flags: Option<u32>,
reply: fuser::ReplyAttr, reply: fuser::ReplyAttr,
) { ) {
// debug!( let inode = match get_inode_by_fh_or_ino(self, fh, ino) {
// "[Not Implemented] setattr(ino: {:#x?}, mode: {:?}, uid: {:?}, \ Ok(inode) => inode,
// gid: {:?}, size: {:?}, fh: {:?}, flags: {:?})", Err(err) => {
// ino, mode, uid, gid, size, fh, flags reply.error(err);
// ); return;
// reply.error(ENOSYS); }
// return;
// TODO: implement this properly
let Some(inode) = self.get_inode(ino) else {
debug!("tried to get inode {ino}, but not found");
reply.error(ENOENT);
return;
}; };
reply.attr(&TTL, &inode.borrow().file_attr()); let mut inode = inode.borrow_mut();
if let Some(new_size) = size {
inode.update_size(new_size);
}
if let Some(atime) = atime {
let atime = match atime {
fuser::TimeOrNow::SpecificTime(system_time) => system_time,
fuser::TimeOrNow::Now => SystemTime::now(),
};
inode.update_atime(atime);
}
if let Some(mtime) = mtime {
let mtime = match mtime {
fuser::TimeOrNow::SpecificTime(system_time) => system_time,
fuser::TimeOrNow::Now => SystemTime::now(),
};
inode.update_mtime(mtime);
}
if let Err(err) = inode.write_back(&self.fat_fs) {
debug!("writing back inode failed: {err}");
reply.error(EIO);
return;
}
reply.attr(&TTL, &inode.file_attr());
} }
fn readlink(&mut self, _req: &fuser::Request<'_>, ino: u64, reply: fuser::ReplyData) { fn readlink(&mut self, _req: &fuser::Request<'_>, ino: u64, reply: fuser::ReplyData) {
@ -281,12 +339,32 @@ impl Filesystem for FatFuse {
reply.error(ENOSYS); reply.error(ENOSYS);
} }
fn open(&mut self, _req: &fuser::Request<'_>, ino: u64, _flags: i32, reply: fuser::ReplyOpen) { fn open(&mut self, _req: &fuser::Request<'_>, ino: u64, flags: i32, reply: fuser::ReplyOpen) {
if !self.inode_table.contains_key(&ino) { if !self.inode_table.contains_key(&ino) {
reply.error(ENOENT); reply.error(ENOENT);
return; return;
} }
let flags = OpenFlags::from_bits_truncate(flags);
debug!("flags: {flags:?}");
let Some(inode) = self.get_inode(ino).cloned() else {
debug!("inode {ino} not found");
reply.error(ENOENT);
return;
};
let mut inode = inode.borrow_mut();
if flags.intersects(OpenFlags::Write) && inode.is_read_only() {
debug!("tried to open read-only inode {ino} with write access");
reply.error(EACCES);
return;
}
let fh = self.next_fh(); let fh = self.next_fh();
if let Some(old_ino) = self.ino_by_fh.insert(fh, ino) { if let Some(old_ino) = self.ino_by_fh.insert(fh, ino) {
@ -295,6 +373,18 @@ impl Filesystem for FatFuse {
debug!("opened inode {}: fh {}", ino, fh); debug!("opened inode {}: fh {}", ino, fh);
if flags.contains(OpenFlags::Truncate) {
inode.update_size(0);
inode.update_mtime(SystemTime::now());
if let Err(err) = inode.write_back(&self.fat_fs) {
debug!("writing back inode failed: {err}");
reply.error(EIO);
return;
}
}
reply.opened(fh, 0); reply.opened(fh, 0);
} }
@ -327,7 +417,7 @@ impl Filesystem for FatFuse {
return; return;
}; };
let inode = inode.borrow(); let mut inode = inode.borrow_mut();
if inode.ino() != ino { if inode.ino() != ino {
debug!("fh {fh} is associated with inode {} instead of {ino}", inode.ino()); debug!("fh {fh} is associated with inode {} instead of {ino}", inode.ino());
@ -395,7 +485,11 @@ impl Filesystem for FatFuse {
debug!("expected to read {size} bytes, but only read {bytes_read}"); debug!("expected to read {size} bytes, but only read {bytes_read}");
} }
inode.update_atime(SystemTime::now());
reply.data(&buf[..bytes_read]); reply.data(&buf[..bytes_read]);
// TODO: update access time
} }
fn write( fn write(
@ -501,14 +595,18 @@ impl Filesystem for FatFuse {
let new_file_size = offset + bytes_written as u64; let new_file_size = offset + bytes_written as u64;
if let Err(err) = inode.update_size(&self.fat_fs, new_file_size) { inode.update_size(new_file_size);
debug!("error while updating size: {err}");
if let Err(err) = inode.write_back(&self.fat_fs) {
debug!("error while writing back inode: {err}");
reply.error(EIO); reply.error(EIO);
return; return;
} }
} }
// TODO: update write and access time
reply.written(bytes_written as u32); reply.written(bytes_written as u32);
} }
@ -520,30 +618,34 @@ impl Filesystem for FatFuse {
_lock_owner: u64, _lock_owner: u64,
reply: fuser::ReplyEmpty, reply: fuser::ReplyEmpty,
) { ) {
// debug!(
// "[Not Implemented] flush(ino: {:#x?}, fh: {}, lock_owner: {:?})",
// ino, fh, lock_owner
// );
// reply.error(ENOSYS);
debug!("flushing ino={ino} fh={fh}"); debug!("flushing ino={ino} fh={fh}");
let Some(&found_ino) = self.ino_by_fh.get(&fh) else { let Some(inode) = self.get_inode_by_fh(fh) else {
debug!("expected fh {fh} to be mapped to ino {ino}, but not found instead"); debug!("expected fh {fh} to be mapped to ino {ino}, but not found instead");
reply.error(EBADF); reply.error(EBADF);
return; return;
}; };
if found_ino != ino { let inode = inode.borrow();
if inode.ino() != ino {
debug!( debug!(
"expected fh {fh} to be mapped to ino {ino}, but was mapped to {found_ino} instead" "expected fh {fh} to be mapped to ino {ino}, but was mapped to {} instead",
inode.ino()
); );
reply.error(EBADF); reply.error(EBADF);
return; return;
} }
if inode.is_dir() {
debug!("called flush on directory (ino: {ino}, fh: {fh}");
reply.error(EISDIR);
return;
}
reply.ok(); reply.ok();
} }
@ -579,11 +681,38 @@ impl Filesystem for FatFuse {
_req: &fuser::Request<'_>, _req: &fuser::Request<'_>,
ino: u64, ino: u64,
fh: u64, fh: u64,
datasync: bool, _datasync: bool,
reply: fuser::ReplyEmpty, reply: fuser::ReplyEmpty,
) { ) {
debug!("[Not Implemented] fsync(ino: {:#x?}, fh: {}, datasync: {})", ino, fh, datasync); debug!("flushing ino={ino} fh={fh}");
reply.error(ENOSYS);
let Some(inode) = self.get_inode_by_fh(fh) else {
debug!("expected fh {fh} to be mapped to ino {ino}, but not found instead");
reply.error(EBADF);
return;
};
let inode = inode.borrow();
if inode.ino() != ino {
debug!(
"expected fh {fh} to be mapped to ino {ino}, but was mapped to {} instead",
inode.ino()
);
reply.error(EBADF);
return;
}
if !inode.is_dir() {
debug!("called fsync on directory (ino: {ino}, fh: {fh}");
reply.error(EISDIR);
return;
}
reply.ok();
} }
fn opendir( fn opendir(
@ -614,7 +743,7 @@ impl Filesystem for FatFuse {
return; return;
}; };
let Some(dir_inode) = self.get_inode_by_fh(fh) else { let Some(dir_inode) = self.get_inode_by_fh(fh).cloned() else {
debug!("could not find inode accociated with fh {} (ino: {})", fh, ino); debug!("could not find inode accociated with fh {} (ino: {})", fh, ino);
reply.error(EBADF); reply.error(EBADF);
@ -685,6 +814,8 @@ impl Filesystem for FatFuse {
} }
reply.ok(); reply.ok();
// TODO: update access time
} }
fn readdirplus( fn readdirplus(
@ -774,20 +905,4 @@ impl Filesystem for FatFuse {
); );
reply.error(ENOSYS); reply.error(ENOSYS);
} }
fn lseek(
&mut self,
_req: &fuser::Request<'_>,
ino: u64,
fh: u64,
offset: i64,
whence: i32,
reply: fuser::ReplyLseek,
) {
debug!(
"[Not Implemented] lseek(ino: {:#x?}, fh: {}, offset: {}, whence: {})",
ino, fh, offset, whence
);
reply.error(ENOSYS);
}
} }

View file

@ -60,29 +60,42 @@ pub struct Inode {
ref_count: u64, ref_count: u64,
uid: u32,
gid: u32,
path: Rc<str>,
parent: Option<InodeRef>, parent: Option<InodeRef>,
size: u64,
block_size: u32, block_size: u32,
kind: Kind, kind: Kind,
read_only: bool, read_only: bool,
dirty: bool,
// these are the fields that have to get written back to the DirEntry on write_back
size: u64,
atime: SystemTime, atime: SystemTime,
mtime: SystemTime, mtime: SystemTime,
// ctime: SystemTime,
crtime: SystemTime, crtime: SystemTime,
uid: u32,
gid: u32,
first_cluster: u32, first_cluster: u32,
path: Rc<str>,
} }
#[allow(dead_code)] impl Drop for Inode {
fn drop(&mut self) {
// since we don't have a handle on the FatFs we can't do the write-back here
assert!(
!self.dirty,
"inode {} is dirty, but was not written back to FS before being destroyed",
self.ino()
);
}
}
impl Inode { impl Inode {
fn new_generation() -> u32 { fn new_generation() -> u32 {
let rand: u16 = get_random(); let rand: u16 = get_random();
@ -144,6 +157,7 @@ impl Inode {
block_size: fat_fs.bytes_per_sector() as u32, block_size: fat_fs.bytes_per_sector() as u32,
kind, kind,
read_only: dir_entry.is_readonly(), read_only: dir_entry.is_readonly(),
dirty: false,
atime, atime,
mtime, mtime,
crtime, crtime,
@ -166,6 +180,7 @@ impl Inode {
block_size: fat_fs.bytes_per_sector() as u32, block_size: fat_fs.bytes_per_sector() as u32,
kind: Kind::Dir, kind: Kind::Dir,
read_only: false, read_only: false,
dirty: false,
atime: SystemTime::UNIX_EPOCH, atime: SystemTime::UNIX_EPOCH,
mtime: SystemTime::UNIX_EPOCH, mtime: SystemTime::UNIX_EPOCH,
crtime: SystemTime::UNIX_EPOCH, crtime: SystemTime::UNIX_EPOCH,
@ -228,39 +243,6 @@ impl Inode {
self.size self.size
} }
pub fn update_size(&mut self, fat_fs: &FatFs, new_size: u64) -> anyhow::Result<()> {
let Some(parent_inode) = self.parent() else {
anyhow::bail!("parent inode of {} does not exist", self.ino);
};
let parent_inode = parent_inode.borrow();
// since we just wrote to the file with this inode, first cluster should not be zero
let Some(mut dir_entry) = parent_inode
.dir_iter(fat_fs)
.unwrap()
.find(|dir_entry| dir_entry.first_cluster() == self.first_cluster())
else {
anyhow::bail!("could not find dir_entry corresponding to self in parent inode");
};
debug!("new file size: {new_size}");
assert!(new_size <= u32::MAX as u64);
dir_entry.update_file_size(new_size as u32);
if dir_entry.update(fat_fs).is_err() {
anyhow::bail!("failed to update dir_entry for inode {}", self.ino);
}
drop(parent_inode);
self.size = new_size;
Ok(())
}
pub fn kind(&self) -> Kind { pub fn kind(&self) -> Kind {
self.kind self.kind
} }
@ -346,9 +328,67 @@ impl Inode {
Ok(fat_fs.file_writer(self.first_cluster())) Ok(fat_fs.file_writer(self.first_cluster()))
} }
// pub fn write_back(&self, fat_fs: &FatFs) { pub fn update_size(&mut self, new_size: u64) {
// // let self.size = new_size;
self.dirty = true;
}
// todo!() pub fn update_atime(&mut self, atime: SystemTime) {
// } self.atime = atime;
self.dirty = true;
}
pub fn update_mtime(&mut self, mtime: SystemTime) {
self.mtime = mtime;
self.dirty = true;
}
pub fn write_back(&mut self, fat_fs: &FatFs) -> anyhow::Result<()> {
if !self.dirty {
return Ok(());
}
let Some(parent_inode) = self.parent() else {
anyhow::bail!("parent inode of {} does not exist", self.ino);
};
let parent_inode = parent_inode.borrow();
// since we just wrote to the file with this inode, first cluster should not be zero
let Some(mut dir_entry) = parent_inode
.dir_iter(fat_fs)
.unwrap()
.find(|dir_entry| dir_entry.first_cluster() == self.first_cluster())
else {
anyhow::bail!("could not find dir_entry corresponding to self in parent inode");
};
drop(parent_inode);
// update stats on DirEntry
assert!(self.size <= u32::MAX as u64);
dir_entry.update_file_size(self.size as u32);
dir_entry
.update_last_access_date(self.atime)
.map_err(|err| {
anyhow::anyhow!("failed to update atime for inode {}: {err}", self.ino)
})?;
dir_entry.update_write_time(self.mtime).map_err(|err| {
anyhow::anyhow!("failed to update mtime for inode {}: {err}", self.ino)
})?;
// update cr_time as well?
// write DirEntry back to device
dir_entry.write_back(fat_fs).map_err(|err| {
anyhow::anyhow!("failed to write back dir_entry for inode {}: {err}", self.ino)
})?;
self.dirty = false;
Ok(())
}
} }

View file

@ -271,10 +271,10 @@ impl FatFuse {
} }
} }
pub fn get_inode_by_fh(&self, fh: u64) -> Option<InodeRef> { pub fn get_inode_by_fh(&self, fh: u64) -> Option<&InodeRef> {
let ino = *self.ino_by_fh.get(&fh)?; let ino = *self.ino_by_fh.get(&fh)?;
if let Some(inode) = self.get_inode(ino).cloned() { if let Some(inode) = self.get_inode(ino) {
Some(inode) Some(inode)
} else { } else {
debug!("fh {} is mapped to ino {}, but inode is not in table", fh, ino); debug!("fh {} is mapped to ino {}, but inode is not in table", fh, ino);