Skip to content

Commit 8d1b220

Browse files
committed
feat(trickfs): trickfs now can signal ENOSPC
So far, this is the fs-wide flag that when turned on will reject all writes (and even fsync) returning the ENOSPC errors.
1 parent e35bd51 commit 8d1b220

File tree

1 file changed

+105
-16
lines changed

1 file changed

+105
-16
lines changed

trickfs/src/lib.rs

Lines changed: 105 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use std::{
33
ffi::{OsStr, OsString},
44
fmt,
55
path::Path,
6-
sync::LazyLock,
6+
sync::{atomic::AtomicBool, Arc, LazyLock, Mutex},
77
time::{Duration, UNIX_EPOCH},
88
u64,
99
};
@@ -306,24 +306,33 @@ pub struct Trick {
306306
/// Note that inodes are 1-based and this vector is 0-based.
307307
inodes: Vec<InodeData>,
308308
freelist: Vec<Inode>,
309+
trigger_enospc: Arc<AtomicBool>,
309310
}
310311

311312
impl Trick {
312-
pub fn new() -> Self {
313+
fn new() -> (Self, TrickHandle) {
314+
let trigger_enospc = Arc::new(AtomicBool::new(false));
315+
313316
let tree = Tree::new();
314317
let inodes = Vec::new();
315318
let freelist = Vec::new();
316319
let mut fs = Trick {
317320
tree,
318321
inodes,
319322
freelist,
323+
trigger_enospc: trigger_enospc.clone(),
320324
};
321325
// Initialize the root directory. Parent of the ROOT is ROOT.
322326
fs.register_inode(InodeData::new_dir(Inode::ROOT));
323327
fs.tree
324328
.ino_to_container
325329
.insert(Inode::ROOT, Container::new());
326-
fs
330+
331+
let handle = TrickHandle {
332+
bg_sess: None.into(),
333+
trigger_enospc,
334+
};
335+
(fs, handle)
327336
}
328337

329338
fn lookup_inode(&self, ino: Inode) -> Option<&InodeData> {
@@ -478,6 +487,13 @@ impl fuser::Filesystem for Trick {
478487
) {
479488
// we don't really care about these parameters.
480489
let _ = (mode, umask, flags);
490+
if self
491+
.trigger_enospc
492+
.load(std::sync::atomic::Ordering::Relaxed)
493+
{
494+
reply.error(libc::ENOSPC);
495+
return;
496+
}
481497
let Some(parent_container) = self.tree.ino_to_container.get(&Inode(parent)) else {
482498
log::trace!("parent inode doesn't exist");
483499
reply.error(libc::ENOENT);
@@ -568,6 +584,9 @@ impl fuser::Filesystem for Trick {
568584
reply: fuser::ReplyWrite,
569585
) {
570586
let _ = (fh, flags, write_flags, lock_owner);
587+
let trigger_enospc = self
588+
.trigger_enospc
589+
.load(std::sync::atomic::Ordering::Relaxed);
571590
let Some(inode_data) = self.lookup_inode_mut(Inode(ino)) else {
572591
reply.error(libc::ENOENT);
573592
return;
@@ -580,7 +599,12 @@ impl fuser::Filesystem for Trick {
580599
reply.error(libc::EFBIG);
581600
return;
582601
}
602+
// Extension: if the file size is less than the new size, we should extend the file.
583603
if inode_data.size() < new_file_sz as u64 {
604+
if trigger_enospc {
605+
reply.error(libc::ENOSPC);
606+
return;
607+
}
584608
inode_data.set_size(new_file_sz as u64);
585609
}
586610
inode_data.content_mut()[offset..offset + len].copy_from_slice(data);
@@ -597,6 +621,13 @@ impl fuser::Filesystem for Trick {
597621
reply: fuser::ReplyEntry,
598622
) {
599623
let _ = (mode, umask);
624+
if self
625+
.trigger_enospc
626+
.load(std::sync::atomic::Ordering::Relaxed)
627+
{
628+
reply.error(libc::ENOSPC);
629+
return;
630+
}
600631
let Some(inode_data) = self.lookup_inode_mut(Inode(parent)) else {
601632
reply.error(libc::ENOENT);
602633
return;
@@ -757,14 +788,21 @@ impl fuser::Filesystem for Trick {
757788
reply: fuser::ReplyEmpty,
758789
) {
759790
let _ = (fh, mode);
791+
let trigger_enospc = self
792+
.trigger_enospc
793+
.load(std::sync::atomic::Ordering::Relaxed);
760794
let Some(inode_data) = self.lookup_inode_mut(Inode(ino)) else {
761795
reply.error(libc::ENOENT);
762796
return;
763797
};
764798
// fallocate should preallocate stuff. We here just gon pretend that we are preallocating.
765-
// we should set the length though.
799+
// Here we should extend the file if the offset + length is greater than the current file.
766800
let new_size = u64::try_from(offset).unwrap() + u64::try_from(length).unwrap();
767801
if inode_data.size() < new_size {
802+
if trigger_enospc {
803+
reply.error(libc::ENOSPC);
804+
return;
805+
}
768806
inode_data.set_size(new_size);
769807
}
770808
reply.ok();
@@ -780,6 +818,13 @@ impl fuser::Filesystem for Trick {
780818
) {
781819
// fsync doesn't do anything since we are working in-memory, so just return OK.
782820
let _ = (ino, fh, datasync);
821+
if self
822+
.trigger_enospc
823+
.load(std::sync::atomic::Ordering::Relaxed)
824+
{
825+
reply.error(libc::ENOSPC);
826+
return;
827+
}
783828
reply.ok();
784829
}
785830

@@ -793,6 +838,13 @@ impl fuser::Filesystem for Trick {
793838
) {
794839
// just like fsync, fsyncdir doesn't do anything.
795840
let _ = (req, ino, fh, datasync);
841+
if self
842+
.trigger_enospc
843+
.load(std::sync::atomic::Ordering::Relaxed)
844+
{
845+
reply.error(libc::ENOSPC);
846+
return;
847+
}
796848
reply.ok();
797849
}
798850
}
@@ -802,13 +854,21 @@ fn has_odirect(flags: i32) -> bool {
802854
}
803855

804856
pub struct TrickHandle {
805-
bg_sess: fuser::BackgroundSession,
857+
bg_sess: Mutex<Option<fuser::BackgroundSession>>,
858+
trigger_enospc: Arc<AtomicBool>,
806859
}
807860

808861
impl TrickHandle {
809862
/// Sets whether the file system should return ENOSPC on the subsequent write operations.
863+
pub fn set_trigger_enospc(&self, on: bool) {
864+
self.trigger_enospc
865+
.store(on, std::sync::atomic::Ordering::Relaxed);
866+
}
867+
810868
pub fn unmount_and_join(self) {
811-
self.bg_sess.join();
869+
if let Some(bg_sess) = self.bg_sess.lock().unwrap().take() {
870+
bg_sess.join();
871+
}
812872
}
813873
}
814874

@@ -822,9 +882,9 @@ pub fn spawn_trick<P: AsRef<Path>>(mountpoint: P) -> std::io::Result<TrickHandle
822882
MountOption::AutoUnmount,
823883
MountOption::FSName("trick".to_string()),
824884
];
825-
let fs = Trick::new();
826-
let bg_sess = fuser::spawn_mount2(fs, &mountpoint, options)?;
827-
Ok(TrickHandle { bg_sess })
885+
let (fs, mut handle) = Trick::new();
886+
handle.bg_sess = Some(fuser::spawn_mount2(fs, &mountpoint, options)?).into();
887+
Ok(handle)
828888
}
829889

830890
#[cfg(test)]
@@ -852,7 +912,7 @@ mod tests {
852912
init_log();
853913
let mountpoint = tempfile::tempdir().unwrap();
854914
let options = &[MountOption::RO, MountOption::FSName("trick".to_string())];
855-
let fs = Trick::new();
915+
let (fs, _handle) = Trick::new();
856916
let mount_handle = fuser::spawn_mount2(fs, &mountpoint, options).unwrap();
857917
drop(mount_handle);
858918
}
@@ -863,7 +923,7 @@ mod tests {
863923
init_log();
864924
let mountpoint = tempfile::tempdir().unwrap();
865925
let options = &[MountOption::RW, MountOption::FSName("trick".to_string())];
866-
let fs = Trick::new();
926+
let (fs, _handle) = Trick::new();
867927
let mount_handle = fuser::spawn_mount2(fs, &mountpoint, options).unwrap();
868928
let filename = mountpoint.path().join("file");
869929
let file = fs::File::create(&filename).unwrap();
@@ -876,7 +936,7 @@ mod tests {
876936
init_log();
877937
let mountpoint = tempfile::tempdir().unwrap();
878938
let options = &[MountOption::RW, MountOption::FSName("trick".to_string())];
879-
let fs = Trick::new();
939+
let (fs, _handle) = Trick::new();
880940
let mount_handle = fuser::spawn_mount2(fs, &mountpoint, options).unwrap();
881941
let filename = mountpoint.path().join("file");
882942
let file = fs::File::create(&filename).unwrap();
@@ -895,7 +955,7 @@ mod tests {
895955
MountOption::AutoUnmount,
896956
MountOption::FSName("trick".to_string()),
897957
];
898-
let fs = Trick::new();
958+
let (fs, _handle) = Trick::new();
899959
let mount_handle = fuser::spawn_mount2(fs, &mountpoint, options).unwrap();
900960
let filename = mountpoint.path().join("file");
901961
let mut file = fs::File::options()
@@ -928,7 +988,7 @@ mod tests {
928988
MountOption::AutoUnmount,
929989
MountOption::FSName("trick".to_string()),
930990
];
931-
let fs = Trick::new();
991+
let (fs, _handle) = Trick::new();
932992
let mount_handle = fuser::spawn_mount2(fs, &mountpoint, options).unwrap();
933993
let mut files = Vec::new();
934994
for i in 0..100 {
@@ -959,7 +1019,7 @@ mod tests {
9591019
MountOption::AutoUnmount,
9601020
MountOption::FSName("trick".to_string()),
9611021
];
962-
let fs = Trick::new();
1022+
let (fs, _handle) = Trick::new();
9631023
let mount_handle = fuser::spawn_mount2(fs, &mountpoint, options).unwrap();
9641024

9651025
// Create a file
@@ -981,7 +1041,7 @@ mod tests {
9811041
MountOption::AutoUnmount,
9821042
MountOption::FSName("trick".to_string()),
9831043
];
984-
let fs = Trick::new();
1044+
let (fs, _handle) = Trick::new();
9851045
let mount_handle = fuser::spawn_mount2(fs, &mountpoint, options).unwrap();
9861046

9871047
let filename = mountpoint.path().join("file");
@@ -1013,4 +1073,33 @@ mod tests {
10131073
drop(file);
10141074
drop(mount_handle);
10151075
}
1076+
1077+
#[test]
1078+
fn out_of_space() {
1079+
init_log();
1080+
let mountpoint = tempfile::tempdir().unwrap();
1081+
let options = &[
1082+
MountOption::RW,
1083+
MountOption::AutoUnmount,
1084+
MountOption::FSName("trick".to_string()),
1085+
];
1086+
let (fs, handle) = Trick::new();
1087+
let mount_handle = fuser::spawn_mount2(fs, &mountpoint, options).unwrap();
1088+
1089+
let filename = mountpoint.path().join("file");
1090+
let mut file = fs::File::options()
1091+
.create_new(true)
1092+
.write(true)
1093+
.open(&filename)
1094+
.unwrap();
1095+
1096+
let test_data = b"hello world";
1097+
handle.set_trigger_enospc(true);
1098+
let _ = file.write_all(test_data).unwrap_err();
1099+
handle.set_trigger_enospc(false);
1100+
let _ = file.write_all(test_data).unwrap();
1101+
1102+
drop(file);
1103+
drop(mount_handle);
1104+
}
10161105
}

0 commit comments

Comments
 (0)