diff --git a/SQUIRRELFS_CONFIG b/SQUIRRELFS_CONFIG index d0d4cc3f3..20a72c8c0 100644 --- a/SQUIRRELFS_CONFIG +++ b/SQUIRRELFS_CONFIG @@ -5197,4 +5197,4 @@ CONFIG_ARCH_USE_MEMTEST=y CONFIG_RUST_OVERFLOW_CHECKS=y # CONFIG_RUST_BUILD_ASSERT_ALLOW is not set # end of Rust hacking -# end of Kernel hacking +# end of Kernel hacking \ No newline at end of file diff --git a/fs/hayleyfs/defs.rs b/fs/hayleyfs/defs.rs index efb1014ed..73ead1499 100644 --- a/fs/hayleyfs/defs.rs +++ b/fs/hayleyfs/defs.rs @@ -25,6 +25,7 @@ pub(crate) const MAX_FILENAME_LEN: usize = 110; pub(crate) const MAX_PAGES: u64 = u64::MAX; pub(crate) const MAX_LINKS: u16 = u16::MAX; pub(crate) const DENTRIES_PER_PAGE: usize = 32; +pub(crate) const MAX_FILE_SIZE: u64 = 1000000; // TODO: get a correct number for this. /// Reserved pages #[allow(dead_code)] diff --git a/fs/hayleyfs/h_file.rs b/fs/hayleyfs/h_file.rs index 33fe2516f..f819e0772 100644 --- a/fs/hayleyfs/h_file.rs +++ b/fs/hayleyfs/h_file.rs @@ -3,6 +3,7 @@ use crate::defs::*; use crate::h_inode::*; use crate::typestate::*; use crate::volatile::*; +use crate::namei::*; use crate::{end_timing, fence_vec, init_timing, start_timing}; use core::{ffi, marker::Sync, ptr, sync::atomic::Ordering}; use kernel::prelude::*; @@ -12,6 +13,7 @@ use kernel::{ iomap, mm, }; + pub(crate) struct Adapter {} impl file::OpenAdapter for Adapter { @@ -20,6 +22,21 @@ impl file::OpenAdapter for Adapter { } } +// FLags for fallocate modes + +#[repr(i32)] +#[allow(non_camel_case_types)] +enum FALLOC_FLAG { + FALLOC_FL_KEEP_SIZE = 0x01, + FALLOC_FL_PUNCH_HOLE = 0x02, + // FALLOC_FL_NO_HIDE_STALE = 0x04, + FALLOC_FL_COLLAPSE_RANGE = 0x08, + FALLOC_FL_ZERO_RANGE = 0x10, + FALLOC_FL_INSERT_RANGE = 0x20, + // FALLOC_FL_UNSHARE_RANGE = 0x40, +} + + pub(crate) struct FileOps; #[vtable] impl file::Operations for FileOps { @@ -85,7 +102,7 @@ impl file::Operations for FileOps { Ok((bytes_written, _)) => Ok(bytes_written.try_into()?), Err(e) => Err(e), } - } + } fn read( _data: (), @@ -128,13 +145,19 @@ impl file::Operations for FileOps { } fn fallocate( - _data: (), - _file: &file::File, - _mode: i32, - _offset: i64, - _len: i64, + data: (), + file: &file::File, + mode: i32, + offset: i64, + len: i64, ) -> Result { - Err(EINVAL) + let inode: &mut fs::INode = unsafe { &mut *file.inode().cast() }; + + let sb = inode.i_sb(); + let fs_info_raw = unsafe { (*sb).s_fs_info }; + let sbi = unsafe { &mut *(fs_info_raw as *mut SbInfo) }; + + return hayleyfs_fallocate(inode, sbi, data, file, mode, offset, len); } fn ioctl(data: (), file: &file::File, cmd: &mut file::IoctlCommand) -> Result { @@ -151,6 +174,115 @@ impl file::Operations for FileOps { } } + +fn hayleyfs_fallocate( + inode: &mut fs::INode, + sbi: &mut SbInfo, + data: (), + file: &file::File, + mode: i32, + offset: i64, + len: i64, +) -> Result { + let pi = sbi.get_init_reg_inode_by_vfs_inode(inode.get_inner())?; + + // let pi_info = pi.get_inode_info()?; + let initial_size: u64 = pi.get_size() as u64; + + /* + * Error checks beforehand, sourced from below: + * https://man7.org/linux/man-pages/man2/fallocate.2.html#ERRORS + */ + let falloc_fl_insert_range = mode & FALLOC_FLAG::FALLOC_FL_INSERT_RANGE as i32 == 1; + let falloc_fl_collapse_range = mode & FALLOC_FLAG::FALLOC_FL_COLLAPSE_RANGE as i32 == 1; + let falloc_fl_keep_size = mode & FALLOC_FLAG::FALLOC_FL_KEEP_SIZE as i32 == 1; + let falloc_fl_zero_range = mode & FALLOC_FLAG::FALLOC_FL_ZERO_RANGE as i32 == 1; + let falloc_fl_punch_hole = mode & FALLOC_FLAG::FALLOC_FL_PUNCH_HOLE as i32 == 1; + + /* + * EINVAL: offset was less than 0, or len was less than or equal to 0. + */ + if offset < 0 || len <= 0 { + return Err(EINVAL); + } + + pr_info!("Gets past offset and length check"); + + let len_u64: u64 = len.try_into().unwrap(); + let offset_u64: u64 = offset.try_into().unwrap(); + let final_file_size : u64 = len_u64 + offset_u64; + + /* + * EFBIG: offset+len exceeds the maximum file size. + */ + if final_file_size > MAX_FILE_SIZE { + return Err(EFBIG); + } + + pr_info!("Gets past file size check"); + + /* + * EFBIG: mode is FALLOC_FL_INSERT_RANGE, and the current file + * size+len exceeds the maximum file size. + */ + if falloc_fl_insert_range && (initial_size + len_u64 > MAX_FILE_SIZE) { + pr_info!("Fails the insert_range check."); + return Err(EFBIG); + } + + /* + * EINTR: A signal was caught during execution; see signal(7). + */ + // TODO: implement me! + + /* + * EINVAL: mode is FALLOC_FL_COLLAPSE_RANGE and the range specified + * by offset plus len reaches or passes the end of the file. + * + * EINVAL: mode is FALLOC_FL_COLLAPSE_RANGE or + * FALLOC_FL_INSERT_RANGE, but either offset or len is not a + * multiple of the filesystem block size. + */ + if falloc_fl_collapse_range && + offset_u64 + len_u64 >= initial_size || + (offset_u64 % HAYLEYFS_PAGESIZE != 0 || len_u64 % HAYLEYFS_PAGESIZE != 0) // Treat pages as blocks? + { + return Err(EINVAL); + } + + /* + * EINVAL: mode is FALLOC_FL_INSERT_RANGE and the range specified by + * offset reaches or passes the end of the file. + * + * EINVAL: mode is FALLOC_FL_COLLAPSE_RANGE or + * FALLOC_FL_INSERT_RANGE, but either offset or len is not a + * multiple of the filesystem block size. + */ + if falloc_fl_insert_range && + (offset_u64 >= initial_size) || + (offset_u64 % HAYLEYFS_PAGESIZE != 0 || len_u64 % HAYLEYFS_PAGESIZE != 0) + { + return Err(EINVAL); + } + + /* + * EINVAL: mode contains one of FALLOC_FL_COLLAPSE_RANGE or + * FALLOC_FL_INSERT_RANGE and also other flags; no other + * flags are permitted with FALLOC_FL_COLLAPSE_RANGE or + * FALLOC_FL_INSERT_RANGE. + */ + if (falloc_fl_insert_range && falloc_fl_collapse_range) || + ( + (falloc_fl_insert_range ^ falloc_fl_collapse_range) && + falloc_fl_keep_size || falloc_fl_zero_range || falloc_fl_punch_hole + ) + { + return Err(EINVAL); + } + + Ok(0) +} + #[allow(dead_code)] fn hayleyfs_write<'a>( sbi: &'a SbInfo, diff --git a/fs/hayleyfs/namei.rs b/fs/hayleyfs/namei.rs index 49647572c..b497c3334 100644 --- a/fs/hayleyfs/namei.rs +++ b/fs/hayleyfs/namei.rs @@ -1926,7 +1926,7 @@ fn hayleyfs_symlink<'a>( } // TODO: return a type indicating that the truncate has completed -fn hayleyfs_truncate<'a>( +pub(crate) fn hayleyfs_truncate<'a>( sbi: &SbInfo, pi: InodeWrapper<'a, Clean, Start, RegInode>, size: i64, diff --git a/fs/hayleyfs/update_fs.sh b/fs/hayleyfs/update_fs.sh new file mode 100755 index 000000000..43b5cbe6d --- /dev/null +++ b/fs/hayleyfs/update_fs.sh @@ -0,0 +1,58 @@ +#!/bin/bash +source /home/rustfs/.bashrc +source /home/rustfs/.profile + +t_flag=0 + +# Use getopts to process the flags +while getopts "t" opt; do + case $opt in + t) + t_flag=1 + ;; + \?) + echo "Invalid option: -$OPTARG" >&2 + ;; + esac +done + +cd ../../ +pwd + +# Makes sure the step runs. +step() { + echo -n "Executing: $@ " + "$@" + + result=$? + if [ $result -ne 0 ]; then + echo "Command failed" + exit 0 + else + echo "✅" + fi +} + + +sudo umount /dev/pmem0 +sudo rmmod hayleyfs + +echo +echo "Removed old file system. Now starting mount of new one." +echo + +# step make LLVM=-14 fs/hayleyfs/hayleyfs.ko +# step sudo insmod fs/hayleyfs/hayleyfs.ko +# step sudo mount -o init -t hayleyfs /dev/pmem0 /mnt/pmem +echo + +echo "File system module rebuilt, loaded, and mounted successfully." + +if [ $t_flag -eq 1 ]; then + echo "-t was set; Running tests." + + cd tests/ + + pwd + ./test_fs.sh +fi diff --git a/tests/test_fs.sh b/tests/test_fs.sh new file mode 100755 index 000000000..2dd01cd74 --- /dev/null +++ b/tests/test_fs.sh @@ -0,0 +1,240 @@ +#!/bin/bash + +# Test parameters +FS_MOUNT_PATH="/mnt/pmem" +FILE_PATH="test.txt" +ALLOCATION_SIZE=1 # Allocate 1 Megabyte +HAYLEYFS_PAGESIZE=4096 +BIG_NUM=100000000000000000000000000000 +#!/bin/bash + + +EXPECTED_VALUE="" +expect() { + EXPECTED_VALUE=$1 +} + +run_command() { + stdout_file=$(mktemp) + stderr_file=$(mktemp) + + # Run the command, capturing stdout and stderr to their respective files + "$@" > "$stdout_file" 2> "$stderr_file" + + status=0 + + # Check if there was any output to stdout + if [ -s "$stdout_file" ]; then + echo "---- STDOUT ----" + cat "$stdout_file" + fi + + # Check if there was any output to stderr + if [ -s "$stderr_file" ]; then + echo "---- STDERR ----" + file_content=$(cat "$stderr_file") + + echo $file_content + + if [ "$file_content" = "$EXPECTED_VALUE" ]; then + status=0 + else + status=1 + fi + fi + + # Clean up temporary files + rm -f "$stdout_file" "$stderr_file" + + # Clean up expect + EXPECTED_VALUE="" + + return $status +} + +start_test() { + s="========================================================================================================" + echo $s + echo "(T$1) $2" + echo $s +} + +end_test() { + echo +} + + +# First, CD into the right directory +cd $FS_MOUNT_PATH + +# Create the testing file if it doesn't exist +if [ ! -f $FILE_PATH ]; then + touch $FILE_PATH +fi + +echo + +<< TEST-1 + +This test tests fallocate with 1 byte passed to it. Just a dummy test. + +TEST-1 +{ + start_test 1 "Testing fallocate with 1 byte" + + run_command fallocate -l $ALLOCATION_SIZE $FILE_PATH + STATUS=$? + + ACTUAL_SIZE=$(stat --format=%s "$FILE_PATH") + EXPECTED_SIZE=$(($ALLOCATION_SIZE)) + + if [ $ACTUAL_SIZE -eq $EXPECTED_SIZE ]; then + echo "Test passed ✅: File size matches the allocated size." + else + echo + echo "Test failed ❌: File size does not match the allocated size ($ACTUAL_SIZE bytes)." + fi + + # Step 3: Clean up - remove the file + rm -f $FILE_PATH + + end_test +} + +<< TEST-2 + +This test checks that fallocate fails when trying to allocate beyond the maximum file size. + +TEST-2 +{ + start_test 2 "EFBIG: offset+len exceeds the maximum file size" + + touch $FILE_PATH # create the file again + + expect "fallocate: invalid offset value specified" + run_command fallocate -o $BIG_NUM -l 1 $FILE_PATH + STATUS=$? + + if [ $STATUS -eq 0 ]; then + echo "Test passed ✅: fallocate failed as expected with EFBIG." + else + echo + echo "Test failed ❌: fallocate did not fail with EFBIG as expected." + fi + + rm -f $FILE_PATH + + end_test +} + +<< TEST-3 + +This test verifies that fallocate with FALLOC_FL_INSERT_RANGE fails when the resulting +file size would exceed the maximum. + +TEST-3 +{ + start_test 3 "EFBIG: FALLOC_FL_INSERT_RANGE exceeds the maximum file size" + + touch $FILE_PATH # create the file again + + # trying with invalid size + expect "fallocate: invalid length value specified" + run_command fallocate --insert-range -l $BIG_NUM $FILE_PATH + STATUS=$? + + if [ $STATUS -eq 0 ]; then + echo "Test passed ✅: fallocate failed as expected with EFBIG using FALLOC_FL_INSERT_RANGE." + else + echo + echo "Test failed ❌: fallocate did not fail with EFBIG as expected using FALLOC_FL_INSERT_RANGE." + fi + + rm -f $FILE_PATH + + end_test +} + +<< TEST-4 + +This test ensures fallocate fails with EINVAL for negative offset or non-positive length. + +TEST-4 +{ + start_test 4 "EINVAL: Negative offset or non-positive length" + + touch $FILE_PATH # create the file again + + expect "fallocate: invalid offset value specified" + run_command fallocate -o -1 -l $ALLOCATION_SIZE $FILE_PATH + STATUS=$? + + if [ $STATUS -eq 0 ]; then + echo "Test passed ✅: fallocate failed as expected with EINVAL due to negative offset." + else + echo + echo "Test failed ❌: fallocate did not fail with EINVAL as expected due to negative offset." + fi + + rm -f $FILE_PATH + + end_test +} + +<< TEST-5 + +This test checks fallocate fails with EINVAL for FALLOC_FL_COLLAPSE_RANGE or FALLOC_FL_INSERT_RANGE +when offset or length is not aligned with block size. + +TEST-5 +{ + start_test 5 "EINVAL: FALLOC_FL_COLLAPSE_RANGE/FALLOC_FL_INSERT_RANGE with unaligned offset or length" + + HAYLEYFS_PAGESIZE=${HAYLEYFS_PAGESIZE:-4096} # if not defined. + + touch $FILE_PATH # create the file again + + # Assuming an example block size for illustration; adjust as necessary + run_command fallocate --collapse-range -o $((HAYLEYFS_PAGESIZE + 1)) -l $ALLOCATION_SIZE $FILE_PATH + STATUS=$? + + if [ $STATUS -eq 0 ]; then + echo "Test passed ✅: fallocate failed as expected with EINVAL due to unaligned offset/length." + else + echo + echo "Test failed ❌: fallocate did not fail with EINVAL as expected due to unaligned offset/length." + fi + + rm -f $FILE_PATH + + end_test +} + +<< TEST-6 + +This test verifies that fallocate fails with EINVAL when combining FALLOC_FL_COLLAPSE_RANGE or FALLOC_FL_INSERT_RANGE with other incompatible flags. + +TEST-6 +{ + start_test 6 "EINVAL: Incompatible flag combination" + + touch $FILE_PATH # create the file again + + # Example command combining flags; specifics depend on actual command syntax/availability + expect "fallocate: fallocate failed: Invalid argument" + run_command fallocate --insert-range --keep-size -l $ALLOCATION_SIZE $FILE_PATH + STATUS=$? + + if [ $STATUS -eq 0 ]; then + echo "Test passed ✅: fallocate failed as expected with EINVAL due to incompatible flags." + else + echo + echo "Test failed ❌: fallocate did not fail with EINVAL as expected due to incompatible flags." + fi + + rm -f $FILE_PATH + + end_test +} + +echo "Test suite complete: fallocate passes all tests" \ No newline at end of file