Skip to content

Commit a24b4ad

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 7117d04 commit a24b4ad

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
};
@@ -302,24 +302,33 @@ pub struct Trick {
302302
/// Note that inodes are 1-based and this vector is 0-based.
303303
inodes: Vec<InodeData>,
304304
freelist: Vec<Inode>,
305+
trigger_enospc: Arc<AtomicBool>,
305306
}
306307

307308
impl Trick {
308-
pub fn new() -> Self {
309+
fn new() -> (Self, TrickHandle) {
310+
let trigger_enospc = Arc::new(AtomicBool::new(false));
311+
309312
let tree = Tree::new();
310313
let inodes = Vec::new();
311314
let freelist = Vec::new();
312315
let mut fs = Trick {
313316
tree,
314317
inodes,
315318
freelist,
319+
trigger_enospc: trigger_enospc.clone(),
316320
};
317321
// Initialize the root directory. Parent of the ROOT is ROOT.
318322
fs.register_inode(InodeData::new_dir(Inode::ROOT));
319323
fs.tree
320324
.ino_to_container
321325
.insert(Inode::ROOT, Container::new());
322-
fs
326+
327+
let handle = TrickHandle {
328+
bg_sess: None.into(),
329+
trigger_enospc,
330+
};
331+
(fs, handle)
323332
}
324333

325334
fn lookup_inode(&self, ino: Inode) -> Option<&InodeData> {
@@ -474,6 +483,13 @@ impl fuser::Filesystem for Trick {
474483
) {
475484
// we don't really care about these parameters.
476485
let _ = (mode, umask, flags);
486+
if self
487+
.trigger_enospc
488+
.load(std::sync::atomic::Ordering::Relaxed)
489+
{
490+
reply.error(libc::ENOSPC);
491+
return;
492+
}
477493
let Some(parent_container) = self.tree.ino_to_container.get(&Inode(parent)) else {
478494
log::trace!("parent inode doesn't exist");
479495
reply.error(libc::ENOENT);
@@ -564,6 +580,9 @@ impl fuser::Filesystem for Trick {
564580
reply: fuser::ReplyWrite,
565581
) {
566582
let _ = (fh, flags, write_flags, lock_owner);
583+
let trigger_enospc = self
584+
.trigger_enospc
585+
.load(std::sync::atomic::Ordering::Relaxed);
567586
let Some(inode_data) = self.lookup_inode_mut(Inode(ino)) else {
568587
reply.error(libc::ENOENT);
569588
return;
@@ -576,7 +595,12 @@ impl fuser::Filesystem for Trick {
576595
reply.error(libc::EFBIG);
577596
return;
578597
}
598+
// Extension: if the file size is less than the new size, we should extend the file.
579599
if inode_data.size() < new_file_sz as u64 {
600+
if trigger_enospc {
601+
reply.error(libc::ENOSPC);
602+
return;
603+
}
580604
inode_data.set_size(new_file_sz as u64);
581605
}
582606
inode_data.content_mut()[offset..offset + len].copy_from_slice(data);
@@ -593,6 +617,13 @@ impl fuser::Filesystem for Trick {
593617
reply: fuser::ReplyEntry,
594618
) {
595619
let _ = (mode, umask);
620+
if self
621+
.trigger_enospc
622+
.load(std::sync::atomic::Ordering::Relaxed)
623+
{
624+
reply.error(libc::ENOSPC);
625+
return;
626+
}
596627
let Some(inode_data) = self.lookup_inode_mut(Inode(parent)) else {
597628
reply.error(libc::ENOENT);
598629
return;
@@ -753,14 +784,21 @@ impl fuser::Filesystem for Trick {
753784
reply: fuser::ReplyEmpty,
754785
) {
755786
let _ = (fh, mode);
787+
let trigger_enospc = self
788+
.trigger_enospc
789+
.load(std::sync::atomic::Ordering::Relaxed);
756790
let Some(inode_data) = self.lookup_inode_mut(Inode(ino)) else {
757791
reply.error(libc::ENOENT);
758792
return;
759793
};
760794
// fallocate should preallocate stuff. We here just gon pretend that we are preallocating.
761-
// we should set the length though.
795+
// Here we should extend the file if the offset + length is greater than the current file.
762796
let new_size = u64::try_from(offset).unwrap() + u64::try_from(length).unwrap();
763797
if inode_data.size() < new_size {
798+
if trigger_enospc {
799+
reply.error(libc::ENOSPC);
800+
return;
801+
}
764802
inode_data.set_size(new_size);
765803
}
766804
reply.ok();
@@ -776,6 +814,13 @@ impl fuser::Filesystem for Trick {
776814
) {
777815
// fsync doesn't do anything since we are working in-memory, so just return OK.
778816
let _ = (ino, fh, datasync);
817+
if self
818+
.trigger_enospc
819+
.load(std::sync::atomic::Ordering::Relaxed)
820+
{
821+
reply.error(libc::ENOSPC);
822+
return;
823+
}
779824
reply.ok();
780825
}
781826

@@ -789,6 +834,13 @@ impl fuser::Filesystem for Trick {
789834
) {
790835
// just like fsync, fsyncdir doesn't do anything.
791836
let _ = (req, ino, fh, datasync);
837+
if self
838+
.trigger_enospc
839+
.load(std::sync::atomic::Ordering::Relaxed)
840+
{
841+
reply.error(libc::ENOSPC);
842+
return;
843+
}
792844
reply.ok();
793845
}
794846
}
@@ -798,13 +850,21 @@ fn has_odirect(flags: i32) -> bool {
798850
}
799851

800852
pub struct TrickHandle {
801-
bg_sess: fuser::BackgroundSession,
853+
bg_sess: Mutex<Option<fuser::BackgroundSession>>,
854+
trigger_enospc: Arc<AtomicBool>,
802855
}
803856

804857
impl TrickHandle {
805858
/// Sets whether the file system should return ENOSPC on the subsequent write operations.
859+
pub fn set_trigger_enospc(&self, on: bool) {
860+
self.trigger_enospc
861+
.store(on, std::sync::atomic::Ordering::Relaxed);
862+
}
863+
806864
pub fn unmount_and_join(self) {
807-
self.bg_sess.join();
865+
if let Some(bg_sess) = self.bg_sess.lock().unwrap().take() {
866+
bg_sess.join();
867+
}
808868
}
809869
}
810870

@@ -818,9 +878,9 @@ pub fn spawn_trick<P: AsRef<Path>>(mountpoint: P) -> std::io::Result<TrickHandle
818878
MountOption::AutoUnmount,
819879
MountOption::FSName("trick".to_string()),
820880
];
821-
let fs = Trick::new();
822-
let bg_sess = fuser::spawn_mount2(fs, &mountpoint, options)?;
823-
Ok(TrickHandle { bg_sess })
881+
let (fs, mut handle) = Trick::new();
882+
handle.bg_sess = Some(fuser::spawn_mount2(fs, &mountpoint, options)?).into();
883+
Ok(handle)
824884
}
825885

826886
#[cfg(test)]
@@ -848,7 +908,7 @@ mod tests {
848908
init_log();
849909
let mountpoint = tempfile::tempdir().unwrap();
850910
let options = &[MountOption::RO, MountOption::FSName("trick".to_string())];
851-
let fs = Trick::new();
911+
let (fs, _handle) = Trick::new();
852912
let mount_handle = fuser::spawn_mount2(fs, &mountpoint, options).unwrap();
853913
drop(mount_handle);
854914
}
@@ -859,7 +919,7 @@ mod tests {
859919
init_log();
860920
let mountpoint = tempfile::tempdir().unwrap();
861921
let options = &[MountOption::RW, MountOption::FSName("trick".to_string())];
862-
let fs = Trick::new();
922+
let (fs, _handle) = Trick::new();
863923
let mount_handle = fuser::spawn_mount2(fs, &mountpoint, options).unwrap();
864924
let filename = mountpoint.path().join("file");
865925
let file = fs::File::create(&filename).unwrap();
@@ -872,7 +932,7 @@ mod tests {
872932
init_log();
873933
let mountpoint = tempfile::tempdir().unwrap();
874934
let options = &[MountOption::RW, MountOption::FSName("trick".to_string())];
875-
let fs = Trick::new();
935+
let (fs, _handle) = Trick::new();
876936
let mount_handle = fuser::spawn_mount2(fs, &mountpoint, options).unwrap();
877937
let filename = mountpoint.path().join("file");
878938
let file = fs::File::create(&filename).unwrap();
@@ -891,7 +951,7 @@ mod tests {
891951
MountOption::AutoUnmount,
892952
MountOption::FSName("trick".to_string()),
893953
];
894-
let fs = Trick::new();
954+
let (fs, _handle) = Trick::new();
895955
let mount_handle = fuser::spawn_mount2(fs, &mountpoint, options).unwrap();
896956
let filename = mountpoint.path().join("file");
897957
let mut file = fs::File::options()
@@ -924,7 +984,7 @@ mod tests {
924984
MountOption::AutoUnmount,
925985
MountOption::FSName("trick".to_string()),
926986
];
927-
let fs = Trick::new();
987+
let (fs, _handle) = Trick::new();
928988
let mount_handle = fuser::spawn_mount2(fs, &mountpoint, options).unwrap();
929989
let mut files = Vec::new();
930990
for i in 0..100 {
@@ -955,7 +1015,7 @@ mod tests {
9551015
MountOption::AutoUnmount,
9561016
MountOption::FSName("trick".to_string()),
9571017
];
958-
let fs = Trick::new();
1018+
let (fs, _handle) = Trick::new();
9591019
let mount_handle = fuser::spawn_mount2(fs, &mountpoint, options).unwrap();
9601020

9611021
// Create a file
@@ -977,7 +1037,7 @@ mod tests {
9771037
MountOption::AutoUnmount,
9781038
MountOption::FSName("trick".to_string()),
9791039
];
980-
let fs = Trick::new();
1040+
let (fs, _handle) = Trick::new();
9811041
let mount_handle = fuser::spawn_mount2(fs, &mountpoint, options).unwrap();
9821042

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

0 commit comments

Comments
 (0)