diff --git a/deps/sefs b/deps/sefs index 8d999ffd..0a17ee1e 160000 --- a/deps/sefs +++ b/deps/sefs @@ -1 +1 @@ -Subproject commit 8d999ffdf066cc0601c5a726ccfcca75437ced0b +Subproject commit 0a17ee1ec824181c1cf2db97a05f612e96879645 diff --git a/src/libos/Cargo.lock b/src/libos/Cargo.lock index 7a80310d..8a584c97 100644 --- a/src/libos/Cargo.lock +++ b/src/libos/Cargo.lock @@ -417,6 +417,7 @@ dependencies = [ name = "rcore-fs" version = "0.1.0" dependencies = [ + "bitflags", "spin", ] diff --git a/src/libos/src/error/to_errno.rs b/src/libos/src/error/to_errno.rs index 67c8e996..d901db2c 100644 --- a/src/libos/src/error/to_errno.rs +++ b/src/libos/src/error/to_errno.rs @@ -97,6 +97,7 @@ impl ToErrno for rcore_fs::vfs::FsError { FsError::PermError => EPERM, FsError::NameTooLong => ENAMETOOLONG, FsError::FileTooBig => EFBIG, + FsError::OpNotSupported => EOPNOTSUPP, } } } diff --git a/src/libos/src/fs/file.rs b/src/libos/src/fs/file.rs index 62d588ed..3cd90819 100644 --- a/src/libos/src/fs/file.rs +++ b/src/libos/src/fs/file.rs @@ -91,7 +91,7 @@ pub trait File: Debug + Sync + Send + Any { return_op_unsupported_error!("set_advisory_lock") } - fn fallocate(&self, _mode: u32, _offset: u64, _len: u64) -> Result<()> { + fn fallocate(&self, _flags: FallocateFlags, _offset: usize, _len: usize) -> Result<()> { return_op_unsupported_error!("fallocate") } diff --git a/src/libos/src/fs/file_ops/fallocate.rs b/src/libos/src/fs/file_ops/fallocate.rs index 449f1e87..c655fdb7 100644 --- a/src/libos/src/fs/file_ops/fallocate.rs +++ b/src/libos/src/fs/file_ops/fallocate.rs @@ -1,11 +1,89 @@ use super::*; +use rcore_fs::vfs::{AllocFlags, FallocateMode}; -pub fn do_fallocate(fd: FileDesc, mode: u32, offset: u64, len: u64) -> Result<()> { +pub fn do_fallocate(fd: FileDesc, flags: FallocateFlags, offset: usize, len: usize) -> Result<()> { debug!( - "fallocate: fd: {}, mode: {}, offset: {}, len: {}", - fd, mode, offset, len + "fallocate: fd: {}, flags: {:?}, offset: {}, len: {}", + fd, flags, offset, len ); let file_ref = current!().file(fd)?; - file_ref.fallocate(mode, offset, len)?; + file_ref.fallocate(flags, offset, len)?; Ok(()) } + +bitflags! { + /// Operation mode flags for fallocate + /// Please checkout linux/include/uapi/linux/falloc.h for the details + pub struct FallocateFlags: u32 { + /// File size will not be changed when extend the file + const FALLOC_FL_KEEP_SIZE = 0x01; + /// De-allocates range + const FALLOC_FL_PUNCH_HOLE = 0x02; + /// Remove a range of a file without leaving a hole in the file + const FALLOC_FL_COLLAPSE_RANGE = 0x08; + /// Convert a range of file to zeros + const FALLOC_FL_ZERO_RANGE = 0x10; + /// Insert space within the file size without overwriting any existing data + const FALLOC_FL_INSERT_RANGE = 0x20; + /// Unshare shared blocks within the file size without overwriting any existing data + const FALLOC_FL_UNSHARE_RANGE = 0x40; + } +} + +impl FallocateFlags { + pub fn from_u32(raw_flags: u32) -> Result { + let flags = + Self::from_bits(raw_flags).ok_or_else(|| errno!(EOPNOTSUPP, "invalid flags"))?; + if flags.contains(Self::FALLOC_FL_PUNCH_HOLE) && flags.contains(Self::FALLOC_FL_ZERO_RANGE) + { + return_errno!( + EOPNOTSUPP, + "Punch hole and zero range are mutually exclusive" + ); + } + if flags.contains(Self::FALLOC_FL_PUNCH_HOLE) && !flags.contains(Self::FALLOC_FL_KEEP_SIZE) + { + return_errno!(EOPNOTSUPP, "Punch hole must have keep size set"); + } + if flags.contains(Self::FALLOC_FL_COLLAPSE_RANGE) + && !(flags & !Self::FALLOC_FL_COLLAPSE_RANGE).is_empty() + { + return_errno!(EINVAL, "Collapse range should only be used exclusively"); + } + if flags.contains(Self::FALLOC_FL_INSERT_RANGE) + && !(flags & !Self::FALLOC_FL_INSERT_RANGE).is_empty() + { + return_errno!(EINVAL, "Insert range should only be used exclusively"); + } + if flags.contains(Self::FALLOC_FL_UNSHARE_RANGE) + && !(flags & !(Self::FALLOC_FL_UNSHARE_RANGE | Self::FALLOC_FL_KEEP_SIZE)).is_empty() + { + return_errno!( + EINVAL, + "Unshare range should only be used with allocate mode" + ); + } + Ok(flags) + } +} + +impl From for FallocateMode { + fn from(flags: FallocateFlags) -> FallocateMode { + if flags.contains(FallocateFlags::FALLOC_FL_PUNCH_HOLE) { + FallocateMode::PunchHoleKeepSize + } else if flags.contains(FallocateFlags::FALLOC_FL_ZERO_RANGE) { + if flags.contains(FallocateFlags::FALLOC_FL_KEEP_SIZE) { + FallocateMode::ZeroRangeKeepSize + } else { + FallocateMode::ZeroRange + } + } else if flags.contains(FallocateFlags::FALLOC_FL_COLLAPSE_RANGE) { + FallocateMode::CollapseRange + } else if flags.contains(FallocateFlags::FALLOC_FL_INSERT_RANGE) { + FallocateMode::InsertRange + } else { + let flags = AllocFlags::from_bits_truncate(flags.bits()); + FallocateMode::Allocate(flags) + } + } +} diff --git a/src/libos/src/fs/file_ops/mod.rs b/src/libos/src/fs/file_ops/mod.rs index 4730f6b9..8efeb4a9 100644 --- a/src/libos/src/fs/file_ops/mod.rs +++ b/src/libos/src/fs/file_ops/mod.rs @@ -6,7 +6,7 @@ pub use self::chmod::{do_fchmod, do_fchmodat, FileMode}; pub use self::chown::{do_fchown, do_fchownat, ChownFlags}; pub use self::close::do_close; pub use self::dup::{do_dup, do_dup2, do_dup3}; -pub use self::fallocate::do_fallocate; +pub use self::fallocate::{do_fallocate, FallocateFlags}; pub use self::fcntl::{do_fcntl, FcntlCmd}; pub use self::file_flags::{AccessMode, CreationFlags, StatusFlags}; pub use self::flock::{Flock, FlockType}; diff --git a/src/libos/src/fs/inode_file.rs b/src/libos/src/fs/inode_file.rs index 1d412abe..ad6a122a 100644 --- a/src/libos/src/fs/inode_file.rs +++ b/src/libos/src/fs/inode_file.rs @@ -1,5 +1,6 @@ use super::*; use crate::net::PollEventFlags; +use rcore_fs::vfs::FallocateMode; use rcore_fs_sefs::dev::SefsMac; pub struct INodeFile { @@ -121,8 +122,12 @@ impl File for INodeFile { Ok(()) } - fn fallocate(&self, mode: u32, offset: u64, len: u64) -> Result<()> { - self.inode.fallocate(mode, offset, len)?; + fn fallocate(&self, flags: FallocateFlags, offset: usize, len: usize) -> Result<()> { + if !self.access_mode.writable() { + return_errno!(EBADF, "File is not opened for writing"); + } + let mode = FallocateMode::from(flags); + self.inode.fallocate(&mode, offset, len)?; Ok(()) } diff --git a/src/libos/src/fs/mod.rs b/src/libos/src/fs/mod.rs index e747e7be..2659d1e5 100644 --- a/src/libos/src/fs/mod.rs +++ b/src/libos/src/fs/mod.rs @@ -19,8 +19,9 @@ pub use self::event_file::{AsEvent, EventCreationFlags, EventFile}; pub use self::events::{AtomicIoEvents, IoEvents, IoNotifier}; pub use self::file::{File, FileRef}; pub use self::file_ops::{ - occlum_ocall_ioctl, AccessMode, BuiltinIoctlNum, CreationFlags, FileMode, Flock, FlockType, - IfConf, IoctlCmd, Stat, StatusFlags, StructuredIoctlArgType, StructuredIoctlNum, + occlum_ocall_ioctl, AccessMode, BuiltinIoctlNum, CreationFlags, FallocateFlags, FileMode, + Flock, FlockType, IfConf, IoctlCmd, Stat, StatusFlags, StructuredIoctlArgType, + StructuredIoctlNum, }; pub use self::file_table::{FileDesc, FileTable, FileTableEvent, FileTableNotifier}; pub use self::fs_ops::Statfs; diff --git a/src/libos/src/fs/syscalls.rs b/src/libos/src/fs/syscalls.rs index 21b81fda..cd26ec0b 100644 --- a/src/libos/src/fs/syscalls.rs +++ b/src/libos/src/fs/syscalls.rs @@ -628,12 +628,8 @@ pub fn do_fallocate(fd: FileDesc, mode: u32, offset: off_t, len: off_t) -> Resul "offset was less than 0, or len was less than or equal to 0" ); } - // Current implementation is just the posix_fallocate - // TODO: Support more modes in fallocate - if mode != 0 { - return_errno!(ENOSYS, "unsupported mode"); - } - file_ops::do_fallocate(fd, mode, offset as u64, len as u64)?; + let flags = FallocateFlags::from_u32(mode)?; + file_ops::do_fallocate(fd, flags, offset as usize, len as usize)?; Ok(0) } diff --git a/test/file/main.c b/test/file/main.c index a3dcc986..e00a904c 100644 --- a/test/file/main.c +++ b/test/file/main.c @@ -1,3 +1,4 @@ +#define _GNU_SOURCE #include #include #include @@ -9,6 +10,9 @@ // Helper function // ============================================================================ +#define KB (1024) +#define BLK_SIZE (4 * KB) + static int create_file(const char *file_path) { int fd; int flags = O_RDONLY | O_CREAT | O_TRUNC; @@ -164,12 +168,21 @@ static int __test_lseek(const char *file_path) { } static int __test_posix_fallocate(const char *file_path) { - int fd = open(file_path, O_RDWR); + int fd = open(file_path, O_RDONLY); + if (fd < 0) { + THROW_ERROR("failed to open a file to read"); + } + if (posix_fallocate(fd, 0, 16) != EBADF) { + THROW_ERROR("failed to check the open flags for fallocate"); + } + close(fd); + fd = open(file_path, O_RDWR); if (fd < 0) { THROW_ERROR("failed to open a file to read/write"); } + off_t offset = -1; - off_t len = 100; + off_t len = 128; if (posix_fallocate(fd, offset, len) != EINVAL) { THROW_ERROR("failed to call posix_fallocate with invalid offset"); } @@ -182,6 +195,7 @@ static int __test_posix_fallocate(const char *file_path) { if (posix_fallocate(fd, offset, len) != 0) { THROW_ERROR("failed to call posix_fallocate"); } + struct stat stat_buf; if (fstat(fd, &stat_buf) < 0) { THROW_ERROR("failed to stat file"); @@ -203,6 +217,257 @@ static int __test_posix_fallocate(const char *file_path) { return 0; } +#ifndef __GLIBC__ +#define FALLOC_FL_COLLAPSE_RANGE (0x08) +#define FALLOC_FL_ZERO_RANGE (0x10) +#define FALLOC_FL_INSERT_RANGE (0x20) +#define FALLOC_FL_UNSHARE_RANGE (0x40) +#endif + +static int __test_fallocate_with_invalid_mode(const char *file_path) { + int fd = open(file_path, O_RDWR); + if (fd < 0) { + THROW_ERROR("failed to open a file to read/write"); + } + + off_t len = 2 * BLK_SIZE; + if (fill_file_with_repeated_bytes(fd, len, 0xFF) < 0) { + THROW_ERROR("failed to fill file"); + } + + // Check the mode with expected errno + int mode_with_expected_errno[6][2] = { + {FALLOC_FL_KEEP_SIZE | 0xDEAD, EOPNOTSUPP}, + {FALLOC_FL_PUNCH_HOLE | FALLOC_FL_ZERO_RANGE, EOPNOTSUPP}, + {FALLOC_FL_PUNCH_HOLE, EOPNOTSUPP}, + {FALLOC_FL_INSERT_RANGE | FALLOC_FL_KEEP_SIZE, EINVAL}, + {FALLOC_FL_COLLAPSE_RANGE | FALLOC_FL_KEEP_SIZE, EINVAL}, + {FALLOC_FL_PUNCH_HOLE | FALLOC_FL_KEEP_SIZE | FALLOC_FL_UNSHARE_RANGE, EINVAL}, + }; + int row_cnt = (sizeof(mode_with_expected_errno) / sizeof(int)) / + (sizeof(mode_with_expected_errno[0]) / sizeof(int)); + for (int i = 0; i < row_cnt; i++) { + int mode = mode_with_expected_errno[i][0]; + int expected_errno = mode_with_expected_errno[i][1]; + off_t offset = 0; + off_t half_len = len / 2; + errno = 0; + + int ret = fallocate(fd, mode, offset, half_len); + if (!(ret < 0 && errno == expected_errno)) { + THROW_ERROR("failed to check fallocate with invalid mode"); + } + } + + close(fd); + return 0; +} + +static int __test_fallocate_keep_size(const char *file_path) { + int fd = open(file_path, O_RDWR); + if (fd < 0) { + THROW_ERROR("failed to open a file to read/write"); + } + + int mode = FALLOC_FL_KEEP_SIZE; + off_t offset = 0; + off_t len = 64; + if (fallocate(fd, mode, offset, len) < 0) { + THROW_ERROR("failed to call fallocate with FALLOC_FL_KEEP_SIZE"); + } + + struct stat stat_buf; + if (fstat(fd, &stat_buf) < 0) { + THROW_ERROR("failed to stat file"); + } + if (stat_buf.st_size != 0) { + THROW_ERROR("failed to check the len after fallocate"); + } + + close(fd); + return 0; +} + +static int __test_fallocate_punch_hole(const char *file_path) { + int fd = open(file_path, O_RDWR); + if (fd < 0) { + THROW_ERROR("failed to open a file to read/write"); + } + + off_t len = 64; + if (fill_file_with_repeated_bytes(fd, len, 0xFF) < 0) { + THROW_ERROR("failed to fill file"); + } + + int mode = FALLOC_FL_PUNCH_HOLE | FALLOC_FL_KEEP_SIZE; + off_t offset = 0; + off_t hole_len = len / 2; + if (fallocate(fd, mode, offset, hole_len) < 0) { + THROW_ERROR("failed to call fallocate with FALLOC_FL_PUNCH_HOLE"); + } + + struct stat stat_buf; + if (fstat(fd, &stat_buf) < 0) { + THROW_ERROR("failed to stat file"); + } + if (stat_buf.st_size != len) { + THROW_ERROR("failed to check the len after fallocate"); + } + + if (lseek(fd, offset, SEEK_SET) != offset) { + THROW_ERROR("failed to lseek the file"); + } + if (check_file_with_repeated_bytes(fd, hole_len, 0x00) < 0) { + THROW_ERROR("failed to check file after punch hole"); + } + + close(fd); + return 0; +} + +static int __test_fallocate_zero_range(const char *file_path) { + int fd = open(file_path, O_RDWR); + if (fd < 0) { + THROW_ERROR("failed to open a file to read/write"); + } + + off_t len = 64; + if (fill_file_with_repeated_bytes(fd, len, 0xFF) < 0) { + THROW_ERROR("failed to fill file"); + } + + int mode = FALLOC_FL_ZERO_RANGE; + off_t offset = len / 2; + off_t zero_len = len * 2; + if (fallocate(fd, mode, offset, zero_len) < 0) { + THROW_ERROR("failed to call fallocate with FALLOC_FL_ZERO_RANGE"); + } + + struct stat stat_buf; + if (fstat(fd, &stat_buf) < 0) { + THROW_ERROR("failed to stat file"); + } + if (stat_buf.st_size != offset + zero_len) { + THROW_ERROR("failed to check the len after fallocate"); + } + + if (lseek(fd, offset, SEEK_SET) != offset) { + THROW_ERROR("failed to lseek the file"); + } + if (check_file_with_repeated_bytes(fd, zero_len, 0x00) < 0) { + THROW_ERROR("failed to check file after zero range"); + } + + close(fd); + return 0; +} + +static int __test_fallocate_insert_range(const char *file_path) { + int fd = open(file_path, O_RDWR); + if (fd < 0) { + THROW_ERROR("failed to open a file to read/write"); + } + + off_t len = 4 * BLK_SIZE; + if (fill_file_with_repeated_bytes(fd, len, 0xFF) < 0) { + THROW_ERROR("failed to fill file"); + } + + int mode = FALLOC_FL_INSERT_RANGE; + off_t offset = len; + off_t insert_len = len / 4; + int ret = fallocate(fd, mode, offset, insert_len); + if (ret >= 0 || errno != EINVAL ) { + THROW_ERROR("failed to check insert range with oversized offset"); + } + + // make the offset is not the multiple of the filesystem block size + offset += 1; + ret = fallocate(fd, mode, offset, insert_len); + if (ret >= 0 || errno != EINVAL ) { + THROW_ERROR("failed to check insert range with invalid offset"); + } + + offset = len / 4; + if (fallocate(fd, mode, offset, insert_len) < 0) { + THROW_ERROR("failed to call fallocate with FALLOC_FL_INSERT_RANGE"); + } + + struct stat stat_buf; + if (fstat(fd, &stat_buf) < 0) { + THROW_ERROR("failed to stat file"); + } + if (stat_buf.st_size != len + insert_len) { + THROW_ERROR("failed to check the len after fallocate"); + } + + if (lseek(fd, offset, SEEK_SET) != offset) { + THROW_ERROR("failed to lseek the file"); + } + if (check_file_with_repeated_bytes(fd, insert_len, 0x00) < 0) { + THROW_ERROR("failed to check inserted contents after insert range"); + } + if (lseek(fd, offset + insert_len, SEEK_SET) != offset + insert_len) { + THROW_ERROR("failed to lseek the file"); + } + if (check_file_with_repeated_bytes(fd, len - offset, 0xFF) < 0) { + THROW_ERROR("failed to check shifted contents after insert range"); + } + + close(fd); + return 0; +} + +static int __test_fallocate_collapse_range(const char *file_path) { + int fd = open(file_path, O_RDWR); + if (fd < 0) { + THROW_ERROR("failed to open a file to read/write"); + } + + off_t len = 4 * BLK_SIZE; + if (fill_file_with_repeated_bytes(fd, len, 0xFF) < 0) { + THROW_ERROR("failed to fill file"); + } + + int mode = FALLOC_FL_COLLAPSE_RANGE; + off_t offset = len / 4; + off_t collapse_len = len; + int ret = fallocate(fd, mode, offset, collapse_len); + if (ret >= 0 || errno != EINVAL ) { + THROW_ERROR("failed to check collapse range with oversized end_offset"); + } + + // make the collapse_len is not the multiple of the filesystem block size + collapse_len = len / 4 + 1; + ret = fallocate(fd, mode, offset, collapse_len); + if (ret >= 0 || errno != EINVAL ) { + THROW_ERROR("failed to check collapse range with invalid collapse_len"); + } + + collapse_len = len / 4; + if (fallocate(fd, mode, offset, collapse_len) < 0) { + THROW_ERROR("failed to call fallocate with FALLOC_FL_COLLAPSE_RANGE"); + } + + struct stat stat_buf; + if (fstat(fd, &stat_buf) < 0) { + THROW_ERROR("failed to stat file"); + } + if (stat_buf.st_size != len - collapse_len) { + THROW_ERROR("failed to check the len after fallocate"); + } + + if (lseek(fd, offset, SEEK_SET) != offset) { + THROW_ERROR("failed to lseek the file"); + } + if (check_file_with_repeated_bytes(fd, len - offset - collapse_len, 0xFF) < 0) { + THROW_ERROR("failed to check the moved contents after collapse range"); + } + + close(fd); + return 0; +} + typedef int(*test_file_func_t)(const char *); static int test_file_framework(test_file_func_t fn) { @@ -240,6 +505,30 @@ static int test_posix_fallocate() { return test_file_framework(__test_posix_fallocate); } +static int test_fallocate_with_invalid_mode() { + return test_file_framework(__test_fallocate_with_invalid_mode); +} + +static int test_fallocate_keep_size() { + return test_file_framework(__test_fallocate_keep_size); +} + +static int test_fallocate_punch_hole() { + return test_file_framework(__test_fallocate_punch_hole); +} + +static int test_fallocate_zero_range() { + return test_file_framework(__test_fallocate_zero_range); +} + +static int test_fallocate_insert_range() { + return test_file_framework(__test_fallocate_insert_range); +} + +static int test_fallocate_collapse_range() { + return test_file_framework(__test_fallocate_collapse_range); +} + // ============================================================================ // Test suite main // ============================================================================ @@ -250,6 +539,12 @@ static test_case_t test_cases[] = { TEST_CASE(test_writev_readv), TEST_CASE(test_lseek), TEST_CASE(test_posix_fallocate), + TEST_CASE(test_fallocate_with_invalid_mode), + TEST_CASE(test_fallocate_keep_size), + TEST_CASE(test_fallocate_punch_hole), + TEST_CASE(test_fallocate_zero_range), + TEST_CASE(test_fallocate_insert_range), + TEST_CASE(test_fallocate_collapse_range), }; int main(int argc, const char *argv[]) { diff --git a/test/include/test_fs.h b/test/include/test_fs.h index 641693d4..03e88127 100644 --- a/test/include/test_fs.h +++ b/test/include/test_fs.h @@ -76,22 +76,20 @@ int fill_file_with_repeated_bytes(int fd, size_t len, int byte_val) { } int check_file_with_repeated_bytes(int fd, size_t len, int expected_byte_val) { - size_t remain = len; + int remain = len; char read_buf[512]; while (remain > 0) { int read_nbytes = read(fd, read_buf, sizeof(read_buf)); if (read_nbytes < 0) { - // I/O error - return -1; + THROW_ERROR("I/O error"); } + size_t check_nbytes = remain < read_nbytes ? remain : read_nbytes; remain -= read_nbytes; if (read_nbytes == 0 && remain > 0) { - // Not enough data in the file - return -1; + THROW_ERROR("Not enough data in the file"); } - if (check_bytes_in_buf(read_buf, read_nbytes, expected_byte_val) < 0) { - // Incorrect data - return -1; + if (check_bytes_in_buf(read_buf, check_nbytes, expected_byte_val) < 0) { + THROW_ERROR("Incorrect data"); } } return 0;