implemented setattr (properly)
This commit is contained in:
parent
c9e8833ac6
commit
bdd01bd70e
8 changed files with 356 additions and 199 deletions
|
|
@ -5,6 +5,7 @@ edition = "2024"
|
|||
|
||||
[dependencies]
|
||||
anyhow = "1.0.98"
|
||||
bitflags = "2.9.1"
|
||||
chrono = { version = "0.4.41", default-features = false, features = ["alloc", "clock", "std"] }
|
||||
compact_string = "0.1.0"
|
||||
fat-bits = { version = "0.1.0", path = "../fat-bits" }
|
||||
|
|
|
|||
|
|
@ -1,16 +1,54 @@
|
|||
use std::ffi::c_int;
|
||||
use std::io::{Read, Write};
|
||||
use std::time::Duration;
|
||||
use std::time::{Duration, SystemTime};
|
||||
|
||||
use bitflags::bitflags;
|
||||
use fat_bits::dir::DirEntry;
|
||||
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 crate::FatFuse;
|
||||
use crate::inode::InodeRef;
|
||||
|
||||
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 {
|
||||
fn init(
|
||||
&mut self,
|
||||
|
|
@ -82,6 +120,8 @@ impl Filesystem for FatFuse {
|
|||
reply.entry(&TTL, &attr, generation as u64);
|
||||
|
||||
inode.inc_ref_count();
|
||||
|
||||
// TODO: update access time
|
||||
}
|
||||
|
||||
fn forget(&mut self, _req: &fuser::Request<'_>, ino: u64, nlookup: u64) {
|
||||
|
|
@ -112,63 +152,81 @@ impl Filesystem for FatFuse {
|
|||
fh: Option<u64>,
|
||||
reply: fuser::ReplyAttr,
|
||||
) {
|
||||
let inode = if let Some(fh) = fh {
|
||||
let Some(inode) = self.get_inode_by_fh(fh) else {
|
||||
reply.error(EBADF);
|
||||
let inode = match get_inode_by_fh_or_ino(self, fh, ino) {
|
||||
Ok(inode) => inode,
|
||||
Err(err) => {
|
||||
reply.error(err);
|
||||
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();
|
||||
|
||||
inode.update_atime(SystemTime::now());
|
||||
|
||||
reply.attr(&TTL, &attr);
|
||||
}
|
||||
|
||||
#[allow(unused_variables)]
|
||||
fn setattr(
|
||||
&mut self,
|
||||
_req: &fuser::Request<'_>,
|
||||
ino: u64,
|
||||
mode: Option<u32>,
|
||||
uid: Option<u32>,
|
||||
gid: Option<u32>,
|
||||
_mode: Option<u32>,
|
||||
_uid: Option<u32>,
|
||||
_gid: Option<u32>,
|
||||
size: Option<u64>,
|
||||
_atime: Option<fuser::TimeOrNow>,
|
||||
_mtime: Option<fuser::TimeOrNow>,
|
||||
atime: Option<fuser::TimeOrNow>,
|
||||
mtime: Option<fuser::TimeOrNow>,
|
||||
_ctime: Option<std::time::SystemTime>,
|
||||
fh: Option<u64>,
|
||||
_crtime: Option<std::time::SystemTime>,
|
||||
_chgtime: Option<std::time::SystemTime>,
|
||||
_bkuptime: Option<std::time::SystemTime>,
|
||||
flags: Option<u32>,
|
||||
_flags: Option<u32>,
|
||||
reply: fuser::ReplyAttr,
|
||||
) {
|
||||
// debug!(
|
||||
// "[Not Implemented] setattr(ino: {:#x?}, mode: {:?}, uid: {:?}, \
|
||||
// gid: {:?}, size: {:?}, fh: {:?}, flags: {:?})",
|
||||
// ino, mode, uid, gid, size, fh, flags
|
||||
// );
|
||||
// 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;
|
||||
let inode = match get_inode_by_fh_or_ino(self, fh, ino) {
|
||||
Ok(inode) => inode,
|
||||
Err(err) => {
|
||||
reply.error(err);
|
||||
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) {
|
||||
|
|
@ -281,12 +339,32 @@ impl Filesystem for FatFuse {
|
|||
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) {
|
||||
reply.error(ENOENT);
|
||||
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();
|
||||
|
||||
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);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
|
|
@ -327,7 +417,7 @@ impl Filesystem for FatFuse {
|
|||
return;
|
||||
};
|
||||
|
||||
let inode = inode.borrow();
|
||||
let mut inode = inode.borrow_mut();
|
||||
|
||||
if inode.ino() != 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}");
|
||||
}
|
||||
|
||||
inode.update_atime(SystemTime::now());
|
||||
|
||||
reply.data(&buf[..bytes_read]);
|
||||
|
||||
// TODO: update access time
|
||||
}
|
||||
|
||||
fn write(
|
||||
|
|
@ -501,14 +595,18 @@ impl Filesystem for FatFuse {
|
|||
|
||||
let new_file_size = offset + bytes_written as u64;
|
||||
|
||||
if let Err(err) = inode.update_size(&self.fat_fs, new_file_size) {
|
||||
debug!("error while updating size: {err}");
|
||||
inode.update_size(new_file_size);
|
||||
|
||||
if let Err(err) = inode.write_back(&self.fat_fs) {
|
||||
debug!("error while writing back inode: {err}");
|
||||
|
||||
reply.error(EIO);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: update write and access time
|
||||
|
||||
reply.written(bytes_written as u32);
|
||||
}
|
||||
|
||||
|
|
@ -520,30 +618,34 @@ impl Filesystem for FatFuse {
|
|||
_lock_owner: u64,
|
||||
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}");
|
||||
|
||||
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");
|
||||
|
||||
reply.error(EBADF);
|
||||
return;
|
||||
};
|
||||
|
||||
if found_ino != ino {
|
||||
let inode = inode.borrow();
|
||||
|
||||
if inode.ino() != ino {
|
||||
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);
|
||||
return;
|
||||
}
|
||||
|
||||
if inode.is_dir() {
|
||||
debug!("called flush on directory (ino: {ino}, fh: {fh}");
|
||||
|
||||
reply.error(EISDIR);
|
||||
return;
|
||||
}
|
||||
|
||||
reply.ok();
|
||||
}
|
||||
|
||||
|
|
@ -579,11 +681,38 @@ impl Filesystem for FatFuse {
|
|||
_req: &fuser::Request<'_>,
|
||||
ino: u64,
|
||||
fh: u64,
|
||||
datasync: bool,
|
||||
_datasync: bool,
|
||||
reply: fuser::ReplyEmpty,
|
||||
) {
|
||||
debug!("[Not Implemented] fsync(ino: {:#x?}, fh: {}, datasync: {})", ino, fh, datasync);
|
||||
reply.error(ENOSYS);
|
||||
debug!("flushing ino={ino} fh={fh}");
|
||||
|
||||
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(
|
||||
|
|
@ -614,7 +743,7 @@ impl Filesystem for FatFuse {
|
|||
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);
|
||||
|
||||
reply.error(EBADF);
|
||||
|
|
@ -685,6 +814,8 @@ impl Filesystem for FatFuse {
|
|||
}
|
||||
|
||||
reply.ok();
|
||||
|
||||
// TODO: update access time
|
||||
}
|
||||
|
||||
fn readdirplus(
|
||||
|
|
@ -774,20 +905,4 @@ impl Filesystem for FatFuse {
|
|||
);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -60,29 +60,42 @@ pub struct Inode {
|
|||
|
||||
ref_count: u64,
|
||||
|
||||
uid: u32,
|
||||
gid: u32,
|
||||
|
||||
path: Rc<str>,
|
||||
|
||||
parent: Option<InodeRef>,
|
||||
|
||||
size: u64,
|
||||
block_size: u32,
|
||||
|
||||
kind: Kind,
|
||||
|
||||
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,
|
||||
mtime: SystemTime,
|
||||
// ctime: SystemTime,
|
||||
crtime: SystemTime,
|
||||
|
||||
uid: u32,
|
||||
gid: 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 {
|
||||
fn new_generation() -> u32 {
|
||||
let rand: u16 = get_random();
|
||||
|
|
@ -144,6 +157,7 @@ impl Inode {
|
|||
block_size: fat_fs.bytes_per_sector() as u32,
|
||||
kind,
|
||||
read_only: dir_entry.is_readonly(),
|
||||
dirty: false,
|
||||
atime,
|
||||
mtime,
|
||||
crtime,
|
||||
|
|
@ -166,6 +180,7 @@ impl Inode {
|
|||
block_size: fat_fs.bytes_per_sector() as u32,
|
||||
kind: Kind::Dir,
|
||||
read_only: false,
|
||||
dirty: false,
|
||||
atime: SystemTime::UNIX_EPOCH,
|
||||
mtime: SystemTime::UNIX_EPOCH,
|
||||
crtime: SystemTime::UNIX_EPOCH,
|
||||
|
|
@ -228,39 +243,6 @@ impl Inode {
|
|||
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 {
|
||||
self.kind
|
||||
}
|
||||
|
|
@ -346,9 +328,67 @@ impl Inode {
|
|||
Ok(fat_fs.file_writer(self.first_cluster()))
|
||||
}
|
||||
|
||||
// pub fn write_back(&self, fat_fs: &FatFs) {
|
||||
// // let
|
||||
pub fn update_size(&mut self, new_size: u64) {
|
||||
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(())
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)?;
|
||||
|
||||
if let Some(inode) = self.get_inode(ino).cloned() {
|
||||
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);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue