diff --git a/regression/syscall_test/Makefile b/regression/syscall_test/Makefile index 60b047e94..8cea5da4d 100644 --- a/regression/syscall_test/Makefile +++ b/regression/syscall_test/Makefile @@ -2,7 +2,7 @@ TESTS ?= chmod_test fsync_test getdents_test link_test lseek_test mkdir_test \ open_create_test open_test pty_test read_test rename_test stat_test \ - statfs_test symlink_test sync_test uidgid_test unlink_test \ + statfs_test symlink_test sync_test truncate_test uidgid_test unlink_test \ vdso_clock_gettime_test write_test MKFILE_PATH := $(abspath $(lastword $(MAKEFILE_LIST))) diff --git a/regression/syscall_test/blocklists/truncate_test b/regression/syscall_test/blocklists/truncate_test new file mode 100644 index 000000000..b66244cc3 --- /dev/null +++ b/regression/syscall_test/blocklists/truncate_test @@ -0,0 +1,5 @@ +FixtureTruncateTest.Truncate +FixtureTruncateTest.Ftruncate +FixtureTruncateTest.FtruncateShrinkGrow +TruncateTest.TruncateNonWriteable +TruncateTest.FtruncateVirtualTmp_NoRandomSave \ No newline at end of file diff --git a/services/libs/aster-std/src/fs/devpts/mod.rs b/services/libs/aster-std/src/fs/devpts/mod.rs index 4af087374..89f1e5d09 100644 --- a/services/libs/aster-std/src/fs/devpts/mod.rs +++ b/services/libs/aster-std/src/fs/devpts/mod.rs @@ -137,7 +137,7 @@ impl RootInode { } impl Inode for RootInode { - fn len(&self) -> usize { + fn size(&self) -> usize { self.metadata.size } diff --git a/services/libs/aster-std/src/fs/devpts/ptmx.rs b/services/libs/aster-std/src/fs/devpts/ptmx.rs index 1d2312069..6e9adac48 100644 --- a/services/libs/aster-std/src/fs/devpts/ptmx.rs +++ b/services/libs/aster-std/src/fs/devpts/ptmx.rs @@ -63,7 +63,7 @@ impl Ptmx { // Many methods are left to do nothing because every time the ptmx is being opened, // it returns the pty master. So the ptmx can not be used at upper layer. impl Inode for Ptmx { - fn len(&self) -> usize { + fn size(&self) -> usize { self.metadata.size } diff --git a/services/libs/aster-std/src/fs/devpts/slave.rs b/services/libs/aster-std/src/fs/devpts/slave.rs index 1f0b29b79..9b7aeef3b 100644 --- a/services/libs/aster-std/src/fs/devpts/slave.rs +++ b/services/libs/aster-std/src/fs/devpts/slave.rs @@ -43,7 +43,7 @@ impl Inode for PtySlaveInode { false } - fn len(&self) -> usize { + fn size(&self) -> usize { self.metadata.size } diff --git a/services/libs/aster-std/src/fs/ext2/impl_for_vfs/inode.rs b/services/libs/aster-std/src/fs/ext2/impl_for_vfs/inode.rs index 83d936a3b..94fe3a14e 100644 --- a/services/libs/aster-std/src/fs/ext2/impl_for_vfs/inode.rs +++ b/services/libs/aster-std/src/fs/ext2/impl_for_vfs/inode.rs @@ -12,7 +12,7 @@ use aster_rights::Full; use core::time::Duration; impl Inode for Ext2Inode { - fn len(&self) -> usize { + fn size(&self) -> usize { self.file_size() as _ } diff --git a/services/libs/aster-std/src/fs/ext2/inode.rs b/services/libs/aster-std/src/fs/ext2/inode.rs index ca79ada0e..e5a638476 100644 --- a/services/libs/aster-std/src/fs/ext2/inode.rs +++ b/services/libs/aster-std/src/fs/ext2/inode.rs @@ -630,8 +630,8 @@ impl Inner { } pub fn resize(&mut self, new_size: usize) -> Result<()> { - self.page_cache.pages().resize(new_size)?; self.inode_impl.resize(new_size)?; + self.page_cache.pages().resize(new_size)?; Ok(()) } diff --git a/services/libs/aster-std/src/fs/file_handle.rs b/services/libs/aster-std/src/fs/file_handle.rs index 23e0156fd..8c92f51a1 100644 --- a/services/libs/aster-std/src/fs/file_handle.rs +++ b/services/libs/aster-std/src/fs/file_handle.rs @@ -29,6 +29,10 @@ pub trait FileLike: Send + Sync + Any { IoEvents::empty() } + fn resize(&self, new_size: usize) -> Result<()> { + return_errno_with_message!(Errno::EINVAL, "resize is not supported"); + } + fn flush(&self) -> Result<()> { Ok(()) } diff --git a/services/libs/aster-std/src/fs/inode_handle/dyn_cap.rs b/services/libs/aster-std/src/fs/inode_handle/dyn_cap.rs index 3e62d1b00..487cc1da4 100644 --- a/services/libs/aster-std/src/fs/inode_handle/dyn_cap.rs +++ b/services/libs/aster-std/src/fs/inode_handle/dyn_cap.rs @@ -93,6 +93,13 @@ impl FileLike for InodeHandle { self.0.ioctl(cmd, arg) } + fn resize(&self, new_size: usize) -> Result<()> { + if !self.1.contains(Rights::WRITE) { + return_errno_with_message!(Errno::EINVAL, "File is not writable"); + } + self.0.resize(new_size) + } + fn metadata(&self) -> Metadata { self.dentry().inode_metadata() } diff --git a/services/libs/aster-std/src/fs/inode_handle/mod.rs b/services/libs/aster-std/src/fs/inode_handle/mod.rs index 695edfab0..71299dd0a 100644 --- a/services/libs/aster-std/src/fs/inode_handle/mod.rs +++ b/services/libs/aster-std/src/fs/inode_handle/mod.rs @@ -57,7 +57,7 @@ impl InodeHandle_ { } if self.status_flags().contains(StatusFlags::O_APPEND) { - *offset = self.dentry.inode_len(); + *offset = self.dentry.inode_size(); } let len = if self.status_flags().contains(StatusFlags::O_DIRECT) { self.dentry.inode().write_direct_at(*offset, buf)? @@ -92,7 +92,7 @@ impl InodeHandle_ { off as isize } SeekFrom::End(off /* as isize */) => { - let file_size = self.dentry.inode_len() as isize; + let file_size = self.dentry.inode_size() as isize; assert!(file_size >= 0); file_size .checked_add(off) @@ -116,8 +116,15 @@ impl InodeHandle_ { *offset } - pub fn len(&self) -> usize { - self.dentry.inode_len() + pub fn size(&self) -> usize { + self.dentry.inode_size() + } + + pub fn resize(&self, new_size: usize) -> Result<()> { + if self.status_flags().contains(StatusFlags::O_APPEND) { + return_errno_with_message!(Errno::EPERM, "can not resize append-only file"); + } + self.dentry.set_inode_size(new_size) } pub fn access_mode(&self) -> AccessMode { diff --git a/services/libs/aster-std/src/fs/procfs/template/dir.rs b/services/libs/aster-std/src/fs/procfs/template/dir.rs index 22076b9ad..b39a3d130 100644 --- a/services/libs/aster-std/src/fs/procfs/template/dir.rs +++ b/services/libs/aster-std/src/fs/procfs/template/dir.rs @@ -56,7 +56,7 @@ impl ProcDir { } impl Inode for ProcDir { - fn len(&self) -> usize { + fn size(&self) -> usize { self.info.size() } diff --git a/services/libs/aster-std/src/fs/procfs/template/file.rs b/services/libs/aster-std/src/fs/procfs/template/file.rs index 5312f2600..4d22376f2 100644 --- a/services/libs/aster-std/src/fs/procfs/template/file.rs +++ b/services/libs/aster-std/src/fs/procfs/template/file.rs @@ -28,7 +28,7 @@ impl ProcFile { } impl Inode for ProcFile { - fn len(&self) -> usize { + fn size(&self) -> usize { self.info.size() } diff --git a/services/libs/aster-std/src/fs/procfs/template/sym.rs b/services/libs/aster-std/src/fs/procfs/template/sym.rs index 68e22496d..c3a8acdaf 100644 --- a/services/libs/aster-std/src/fs/procfs/template/sym.rs +++ b/services/libs/aster-std/src/fs/procfs/template/sym.rs @@ -28,7 +28,7 @@ impl ProcSym { } impl Inode for ProcSym { - fn len(&self) -> usize { + fn size(&self) -> usize { self.info.size() } diff --git a/services/libs/aster-std/src/fs/ramfs/fs.rs b/services/libs/aster-std/src/fs/ramfs/fs.rs index 7a6ab7c49..238f6e319 100644 --- a/services/libs/aster-std/src/fs/ramfs/fs.rs +++ b/services/libs/aster-std/src/fs/ramfs/fs.rs @@ -412,8 +412,9 @@ impl RamInode { } impl PageCacheBackend for RamInode { - fn read_page(&self, _idx: usize, _frame: &VmFrame) -> Result<()> { - // do nothing + fn read_page(&self, _idx: usize, frame: &VmFrame) -> Result<()> { + // Initially, any block/page in a RamFs inode contains all zeros + frame.zero(); Ok(()) } @@ -446,9 +447,9 @@ impl Inode for RamInode { return_errno_with_message!(Errno::EISDIR, "read is not supported"); }; let (offset, read_len) = { - let file_len = self_inode.metadata.size; - let start = file_len.min(offset); - let end = file_len.min(offset + buf.len()); + let file_size = self_inode.metadata.size; + let start = file_size.min(offset); + let end = file_size.min(offset + buf.len()); (start, end - start) }; page_cache @@ -470,17 +471,17 @@ impl Inode for RamInode { let Some(page_cache) = self_inode.inner.as_file() else { return_errno_with_message!(Errno::EISDIR, "write is not supported"); }; - let file_len = self_inode.metadata.size; - let new_len = offset + buf.len(); - let should_expand_len = new_len > file_len; - if should_expand_len { - page_cache.pages().resize(new_len)?; + let file_size = self_inode.metadata.size; + let new_size = offset + buf.len(); + let should_expand_size = new_size > file_size; + if should_expand_size { + page_cache.pages().resize(new_size)?; } page_cache.pages().write_bytes(offset, buf)?; - if should_expand_len { + if should_expand_size { // Turn the read guard into a write guard without releasing the lock. let mut self_inode = self_inode.upgrade(); - self_inode.resize(new_len); + self_inode.resize(new_size); } Ok(buf.len()) } @@ -489,12 +490,26 @@ impl Inode for RamInode { self.write_at(offset, buf) } - fn len(&self) -> usize { + fn size(&self) -> usize { self.0.read().metadata.size } fn resize(&self, new_size: usize) -> Result<()> { - self.0.write().resize(new_size); + let self_inode = self.0.upread(); + if self_inode.inner.as_file().is_none() { + return_errno!(Errno::EISDIR); + } + + let file_size = self_inode.metadata.size; + if file_size == new_size { + return Ok(()); + } + + let mut self_inode = self_inode.upgrade(); + self_inode.resize(new_size); + let page_cache = self_inode.inner.as_file().unwrap(); + page_cache.pages().resize(new_size)?; + Ok(()) } diff --git a/services/libs/aster-std/src/fs/utils/dentry.rs b/services/libs/aster-std/src/fs/utils/dentry.rs index 21cf04717..b58176ed3 100644 --- a/services/libs/aster-std/src/fs/utils/dentry.rs +++ b/services/libs/aster-std/src/fs/utils/dentry.rs @@ -410,9 +410,14 @@ impl Dentry { self.inode.set_mode(mode) } - /// Get the inode length - pub fn inode_len(&self) -> usize { - self.inode.len() + /// Gets the size of the inode + pub fn inode_size(&self) -> usize { + self.inode.size() + } + + /// Sets the size of the inode + pub fn set_inode_size(&self, new_size: usize) -> Result<()> { + self.inode.resize(new_size) } /// Get the access timestamp diff --git a/services/libs/aster-std/src/fs/utils/inode.rs b/services/libs/aster-std/src/fs/utils/inode.rs index e61150370..6b2eb40df 100644 --- a/services/libs/aster-std/src/fs/utils/inode.rs +++ b/services/libs/aster-std/src/fs/utils/inode.rs @@ -227,11 +227,7 @@ impl Metadata { } pub trait Inode: Any + Sync + Send { - fn len(&self) -> usize; - - fn is_empty(&self) -> bool { - self.len() == 0 - } + fn size(&self) -> usize; fn resize(&self, new_size: usize) -> Result<()>; @@ -364,11 +360,11 @@ impl dyn Inode { return_errno!(Errno::EISDIR); } - let file_len = self.len(); - if buf.len() < file_len { - buf.resize(file_len, 0); + let file_size = self.size(); + if buf.len() < file_size { + buf.resize(file_size, 0); } - self.read_at(0, &mut buf[..file_len]) + self.read_at(0, &mut buf[..file_size]) } pub fn read_direct_to_end(&self, buf: &mut Vec) -> Result { @@ -376,11 +372,11 @@ impl dyn Inode { return_errno!(Errno::EISDIR); } - let file_len = self.len(); - if buf.len() < file_len { - buf.resize(file_len, 0); + let file_size = self.size(); + if buf.len() < file_size { + buf.resize(file_size, 0); } - self.read_direct_at(0, &mut buf[..file_len]) + self.read_direct_at(0, &mut buf[..file_size]) } pub fn writer(&self, from_offset: usize) -> InodeWriter { diff --git a/services/libs/aster-std/src/fs/utils/page_cache.rs b/services/libs/aster-std/src/fs/utils/page_cache.rs index 2e2876b81..67670fccb 100644 --- a/services/libs/aster-std/src/fs/utils/page_cache.rs +++ b/services/libs/aster-std/src/fs/utils/page_cache.rs @@ -56,6 +56,16 @@ impl PageCache { } } +impl Drop for PageCache { + fn drop(&mut self) { + // TODO: + // The default destruction procedure exhibits slow performance. + // In contrast, resizing the `VMO` to zero greatly accelerates the process. + // We need to find out the underlying cause of this discrepancy. + let _ = self.pages.resize(0); + } +} + impl Debug for PageCache { fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { f.debug_struct("PageCache") @@ -147,7 +157,9 @@ impl Pager for PageCacheManager { let mut pages = self.pages.lock(); if let Some(page) = pages.pop(&idx) { if let PageState::Dirty = page.state() { - let backend = self.backend(); + let Some(backend) = self.backend.upgrade() else { + return Ok(()); + }; if idx < backend.npages() { backend.write_page(idx, page.frame())?; } diff --git a/services/libs/aster-std/src/syscall/mod.rs b/services/libs/aster-std/src/syscall/mod.rs index 54ef4b542..54e88ef76 100644 --- a/services/libs/aster-std/src/syscall/mod.rs +++ b/services/libs/aster-std/src/syscall/mod.rs @@ -65,6 +65,7 @@ use crate::syscall::symlink::{sys_symlink, sys_symlinkat}; use crate::syscall::sync::sys_sync; use crate::syscall::tgkill::sys_tgkill; use crate::syscall::time::sys_time; +use crate::syscall::truncate::{sys_ftruncate, sys_truncate}; use crate::syscall::umask::sys_umask; use crate::syscall::uname::sys_uname; use crate::syscall::unlink::{sys_unlink, sys_unlinkat}; @@ -200,6 +201,7 @@ mod symlink; mod sync; mod tgkill; mod time; +mod truncate; mod umask; mod uname; mod unlink; @@ -290,6 +292,8 @@ define_syscall_nums!( SYS_UNAME = 63, SYS_FCNTL = 72, SYS_FSYNC = 74, + SYS_TRUNCATE = 76, + SYS_FTRUNCATE = 77, SYS_GETCWD = 79, SYS_CHDIR = 80, SYS_FCHDIR = 81, @@ -468,6 +472,8 @@ pub fn syscall_dispatch( SYS_UNAME => syscall_handler!(1, sys_uname, args), SYS_FCNTL => syscall_handler!(3, sys_fcntl, args), SYS_FSYNC => syscall_handler!(1, sys_fsync, args), + SYS_TRUNCATE => syscall_handler!(2, sys_truncate, args), + SYS_FTRUNCATE => syscall_handler!(2, sys_ftruncate, args), SYS_GETCWD => syscall_handler!(2, sys_getcwd, args), SYS_CHDIR => syscall_handler!(1, sys_chdir, args), SYS_FCHDIR => syscall_handler!(1, sys_fchdir, args), diff --git a/services/libs/aster-std/src/syscall/truncate.rs b/services/libs/aster-std/src/syscall/truncate.rs new file mode 100644 index 000000000..49639099b --- /dev/null +++ b/services/libs/aster-std/src/syscall/truncate.rs @@ -0,0 +1,66 @@ +// SPDX-License-Identifier: MPL-2.0 + +use crate::fs::{ + file_table::FileDescripter, + fs_resolver::{FsPath, AT_FDCWD}, + utils::PATH_MAX, +}; +use crate::log_syscall_entry; +use crate::prelude::*; +use crate::process::ResourceType; +use crate::util::read_cstring_from_user; + +use super::SyscallReturn; +use super::{SYS_FTRUNCATE, SYS_TRUNCATE}; + +pub fn sys_ftruncate(fd: FileDescripter, len: isize) -> Result { + log_syscall_entry!(SYS_FTRUNCATE); + debug!("fd = {}, lentgh = {}", fd, len); + + check_length(len)?; + + let current = current!(); + let file_table = current.file_table().lock(); + let file = file_table.get_file(fd)?; + file.resize(len as usize)?; + Ok(SyscallReturn::Return(0)) +} + +pub fn sys_truncate(path_ptr: Vaddr, len: isize) -> Result { + log_syscall_entry!(SYS_TRUNCATE); + let path = read_cstring_from_user(path_ptr, PATH_MAX)?; + debug!("path = {:?}, length = {}", path, len); + + check_length(len)?; + + let current = current!(); + let dentry = { + let path = path.to_string_lossy(); + if path.is_empty() { + return_errno_with_message!(Errno::ENOENT, "path is empty"); + } + let fs_path = FsPath::new(AT_FDCWD, path.as_ref())?; + current.fs().read().lookup(&fs_path)? + }; + dentry.set_inode_size(len as usize)?; + Ok(SyscallReturn::Return(0)) +} + +#[inline] +fn check_length(len: isize) -> Result<()> { + if len < 0 { + return_errno_with_message!(Errno::EINVAL, "length is negative"); + } + + let max_file_size = { + let current = current!(); + let resource_limits = current.resource_limits().lock(); + resource_limits + .get_rlimit(ResourceType::RLIMIT_FSIZE) + .get_cur() as usize + }; + if len as usize > max_file_size { + return_errno_with_message!(Errno::EFBIG, "length is larger than the maximum file size"); + } + Ok(()) +}