diff --git a/kernel/src/fs/fs_resolver.rs b/kernel/src/fs/fs_resolver.rs index e59d519a3..e1bf7543c 100644 --- a/kernel/src/fs/fs_resolver.rs +++ b/kernel/src/fs/fs_resolver.rs @@ -12,7 +12,6 @@ use super::{ use crate::{ fs::{ path::MountNamespace, - ramfs::memfd::MemfdInode, utils::{Inode, SymbolicLink}, }, prelude::*, @@ -407,13 +406,9 @@ impl PathOrInode { pub fn display_name(&self) -> String { match self { PathOrInode::Path(path) => path.abs_path(), - PathOrInode::Inode(inode) => { + PathOrInode::Inode(_) => { // FIXME: Add pseudo dentries to store the correct name. - if let Some(memfd_inode) = inode.downcast_ref::() { - memfd_inode.name().to_string() - } else { - String::from("[pseudo inode]") - } + String::from("[pseudo inode]") } } } diff --git a/kernel/src/fs/inode_handle.rs b/kernel/src/fs/inode_handle.rs index 77c7d6993..cf8d5d878 100644 --- a/kernel/src/fs/inode_handle.rs +++ b/kernel/src/fs/inode_handle.rs @@ -84,6 +84,10 @@ impl InodeHandle { *offset } + pub(super) fn rights(&self) -> Rights { + self.rights + } + fn inode_io_and_is_offset_aware(&self) -> (&dyn InodeIo, bool) { if let Some(ref file_io) = self.file_io { let is_offset_aware = file_io.is_offset_aware(); @@ -347,7 +351,11 @@ impl FileLike for InodeHandle { return_errno_with_message!(Errno::EINVAL, "the file is not opened writable"); } - do_resize_util(self.path.inode().as_ref(), self.status_flags(), new_size) + if self.status_flags().contains(StatusFlags::O_APPEND) { + // FIXME: It's allowed to `ftruncate` an append-only file on Linux. + return_errno_with_message!(Errno::EPERM, "can not resize append-only file"); + } + self.path.inode().resize(new_size) } fn status_flags(&self) -> StatusFlags { @@ -394,13 +402,41 @@ impl FileLike for InodeHandle { return_errno_with_message!(Errno::EBADF, "the file is not opened writable"); } - do_fallocate_util( - self.path.inode().as_ref(), - self.status_flags(), - mode, - offset, - len, - ) + let inode = self.path.inode().as_ref(); + let inode_type = inode.type_(); + + // TODO: `fallocate` on pipe files also fails with `ESPIPE`. + if inode_type == InodeType::NamedPipe { + return_errno_with_message!(Errno::ESPIPE, "the inode is a FIFO file"); + } + if !(inode_type == InodeType::File || inode_type == InodeType::Dir) { + return_errno_with_message!( + Errno::ENODEV, + "the inode is not a regular file or a directory" + ); + } + + let status_flags = self.status_flags(); + if status_flags.contains(StatusFlags::O_APPEND) + && (mode == FallocMode::PunchHoleKeepSize + || mode == FallocMode::CollapseRange + || mode == FallocMode::InsertRange) + { + return_errno_with_message!( + Errno::EPERM, + "the flags do not work on the append-only file" + ); + } + if status_flags.contains(StatusFlags::O_DIRECT) + || status_flags.contains(StatusFlags::O_PATH) + { + return_errno_with_message!( + Errno::EBADF, + "currently fallocate file with O_DIRECT or O_PATH is not supported" + ); + } + + inode.fallocate(mode, offset, len) } fn inode(&self) -> &Arc { @@ -477,11 +513,7 @@ pub trait FileIo: Pollable + InodeIo + Send + Sync + 'static { } } -pub(super) fn do_seek_util( - offset: &Mutex, - pos: SeekFrom, - end: Option, -) -> Result { +fn do_seek_util(offset: &Mutex, pos: SeekFrom, end: Option) -> Result { let mut offset = offset.lock(); let new_offset = match pos { @@ -508,54 +540,3 @@ pub(super) fn do_seek_util( *offset = new_offset; Ok(new_offset) } - -pub(super) fn do_fallocate_util( - inode: &dyn Inode, - status_flags: StatusFlags, - mode: FallocMode, - offset: usize, - len: usize, -) -> Result<()> { - let inode_type = inode.type_(); - // TODO: `fallocate` on pipe files also fails with `ESPIPE`. - if inode_type == InodeType::NamedPipe { - return_errno_with_message!(Errno::ESPIPE, "the inode is a FIFO file"); - } - if !(inode_type == InodeType::File || inode_type == InodeType::Dir) { - return_errno_with_message!( - Errno::ENODEV, - "the inode is not a regular file or a directory" - ); - } - - if status_flags.contains(StatusFlags::O_APPEND) - && (mode == FallocMode::PunchHoleKeepSize - || mode == FallocMode::CollapseRange - || mode == FallocMode::InsertRange) - { - return_errno_with_message!( - Errno::EPERM, - "the flags do not work on the append-only file" - ); - } - if status_flags.contains(StatusFlags::O_DIRECT) || status_flags.contains(StatusFlags::O_PATH) { - return_errno_with_message!( - Errno::EBADF, - "currently fallocate file with O_DIRECT or O_PATH is not supported" - ); - } - - inode.fallocate(mode, offset, len) -} - -pub(super) fn do_resize_util( - inode: &dyn Inode, - status_flags: StatusFlags, - new_size: usize, -) -> Result<()> { - if status_flags.contains(StatusFlags::O_APPEND) { - // FIXME: It's allowed to `ftruncate` an append-only file on Linux. - return_errno_with_message!(Errno::EPERM, "can not resize append-only file"); - } - inode.resize(new_size) -} diff --git a/kernel/src/fs/path/mod.rs b/kernel/src/fs/path/mod.rs index 2eadef748..da4fe164e 100644 --- a/kernel/src/fs/path/mod.rs +++ b/kernel/src/fs/path/mod.rs @@ -113,17 +113,38 @@ impl Path { /// /// Returns an `InodeHandle` on success. pub fn open(&self, open_args: OpenArgs) -> Result { - let inode = self.inode(); - check_open_util(inode.as_ref(), &open_args)?; + let inode = self.inode().as_ref(); + let inode_type = inode.type_(); + let creation_flags = &open_args.creation_flags; + let status_flags = &open_args.status_flags; - if inode.type_().is_regular_file() - && open_args.creation_flags.contains(CreationFlags::O_TRUNC) - && !open_args.status_flags.contains(StatusFlags::O_PATH) + if inode_type == InodeType::SymLink + && creation_flags.contains(CreationFlags::O_NOFOLLOW) + && !status_flags.contains(StatusFlags::O_PATH) + { + return_errno_with_message!(Errno::ELOOP, "the file is a symlink"); + } + + if creation_flags.contains(CreationFlags::O_CREAT) + && creation_flags.contains(CreationFlags::O_EXCL) + { + return_errno_with_message!(Errno::EEXIST, "the file already exists"); + } + if creation_flags.contains(CreationFlags::O_DIRECTORY) && inode_type != InodeType::Dir { + return_errno_with_message!( + Errno::ENOTDIR, + "O_DIRECTORY is specified but the file is not a directory" + ); + } + + if inode_type.is_regular_file() + && creation_flags.contains(CreationFlags::O_TRUNC) + && !status_flags.contains(StatusFlags::O_PATH) { self.resize(0)?; } - InodeHandle::new(self.clone(), open_args.access_mode, open_args.status_flags) + InodeHandle::new(self.clone(), open_args.access_mode, *status_flags) } /// Gets the absolute path. @@ -242,33 +263,6 @@ impl Path { } } -/// Checks if the given `Inode` can be opened with the given `OpenArgs`. -pub(super) fn check_open_util(inode: &dyn Inode, open_args: &OpenArgs) -> Result<()> { - let inode_type = inode.type_(); - let creation_flags = &open_args.creation_flags; - - if inode_type == InodeType::SymLink - && creation_flags.contains(CreationFlags::O_NOFOLLOW) - && !open_args.status_flags.contains(StatusFlags::O_PATH) - { - return_errno_with_message!(Errno::ELOOP, "the file is a symlink"); - } - - if creation_flags.contains(CreationFlags::O_CREAT) - && creation_flags.contains(CreationFlags::O_EXCL) - { - return_errno_with_message!(Errno::EEXIST, "the file already exists"); - } - if creation_flags.contains(CreationFlags::O_DIRECTORY) && inode_type != InodeType::Dir { - return_errno_with_message!( - Errno::ENOTDIR, - "O_DIRECTORY is specified but the file is not a directory" - ); - } - - Ok(()) -} - impl Path { /// Mounts a filesystem at the current path. /// diff --git a/kernel/src/fs/ramfs/memfd.rs b/kernel/src/fs/ramfs/memfd.rs index 947180936..675bd25a1 100644 --- a/kernel/src/fs/ramfs/memfd.rs +++ b/kernel/src/fs/ramfs/memfd.rs @@ -3,11 +3,7 @@ //! Memfd Implementation. use alloc::format; -use core::{ - fmt::Display, - sync::atomic::{AtomicU32, Ordering}, - time::Duration, -}; +use core::time::Duration; use align_ext::AlignExt; use aster_block::bio::BioWaiter; @@ -17,25 +13,18 @@ use spin::Once; use super::fs::RamInode; use crate::{ - events::IoEvents, fs::{ - file_handle::{FileLike, Mappable}, - file_table::FdFlags, - inode_handle::{do_fallocate_util, do_resize_util, do_seek_util}, - path::{Mount, RESERVED_MOUNT_ID, check_open_util}, + inode_handle::InodeHandle, + path::{Mount, Path}, tmpfs::TmpFs, utils::{ - AccessMode, CachePage, CreationFlags, Extension, FallocMode, FileSystem, Inode, - InodeIo, InodeMode, InodeType, Metadata, OpenArgs, PageCacheBackend, SeekFrom, - StatusFlags, XattrName, XattrNamespace, XattrSetFlags, mkmod, + AccessMode, CachePage, Extension, FallocMode, FileSystem, Inode, InodeIo, InodeMode, + InodeType, Metadata, PageCacheBackend, StatusFlags, XattrName, XattrNamespace, + XattrSetFlags, mkmod, }, }, prelude::*, - process::{ - Gid, Uid, - signal::{PollHandle, Pollable}, - }, - util::ioctl::RawIoctl, + process::{Gid, Uid}, vm::{perms::VmPerms, vmo::Vmo}, }; @@ -97,7 +86,7 @@ impl MemfdInode { Ok(()) } - pub fn name(&self) -> &str { + pub(self) fn name(&self) -> &str { &self.name } } @@ -235,53 +224,31 @@ impl Inode for MemfdInode { } } -struct MemfdTmpFs { - _private: (), +pub trait MemfdInodeHandle: Sized { + fn new_memfd(name: String, memfd_flags: MemfdFlags) -> Result; + fn add_seals(&self, new_seals: FileSeals) -> Result<()>; + fn get_seals(&self) -> Result; } -impl MemfdTmpFs { - // Reference: - fn singleton() -> &'static Arc { - static MEMFD_TMPFS: Once> = Once::new(); - - MEMFD_TMPFS.call_once(TmpFs::new) - } - - fn mount_node() -> &'static Arc { - static MEMFD_TMPFS_MOUNT: Once> = Once::new(); - - MEMFD_TMPFS_MOUNT.call_once(|| Mount::new_pseudo(Self::singleton().clone())) - } -} - -pub struct MemfdFile { - memfd_inode: Arc, - offset: Mutex, - status_flags: AtomicU32, - rights: Rights, -} - -impl MemfdFile { - pub fn new(name: &str, memfd_flags: MemfdFlags) -> Result { +impl MemfdInodeHandle for InodeHandle { + fn new_memfd(name: String, memfd_flags: MemfdFlags) -> Result { if name.len() > MAX_MEMFD_NAME_LEN { - return_errno_with_message!(Errno::EINVAL, "MemfdManager: `name` is too long."); + return_errno_with_message!(Errno::EINVAL, "the memfd name is too long"); } - let name = format!("/memfd:{}", name); - - let (allow_sealing, executable) = if memfd_flags.contains(MemfdFlags::MFD_NOEXEC_SEAL) { - (true, false) - } else { - (memfd_flags.contains(MemfdFlags::MFD_ALLOW_SEALING), true) - }; - - let mode = if executable { - mkmod!(a+rwx) - } else { - mkmod!(a+rw) - }; - let memfd_inode = Arc::new_cyclic(|weak_self| { + let (allow_sealing, executable) = if memfd_flags.contains(MemfdFlags::MFD_NOEXEC_SEAL) { + (true, false) + } else { + (memfd_flags.contains(MemfdFlags::MFD_ALLOW_SEALING), true) + }; + + let mode = if executable { + mkmod!(a+rwx) + } else { + mkmod!(a+rw) + }; + let ram_inode = RamInode::new_file_detached_in_memfd( weak_self, mode, @@ -304,216 +271,68 @@ impl MemfdFile { } }); - Ok(Self { - memfd_inode, - offset: Mutex::new(0), - status_flags: AtomicU32::new(0), - rights: Rights::READ | Rights::WRITE, - }) + let path = MemfdTmpFs::new_path(memfd_inode); + + InodeHandle::new_unchecked_access(path, AccessMode::O_RDWR, StatusFlags::empty()) } - pub fn open(inode: Arc, open_args: OpenArgs) -> Result { - let inode: Arc = inode; - let status_flags = open_args.status_flags; - let access_mode = open_args.access_mode; - - if !status_flags.contains(StatusFlags::O_PATH) { - inode.check_permission(access_mode.into())?; - } - check_open_util(inode.as_ref(), &open_args)?; - - if open_args.creation_flags.contains(CreationFlags::O_TRUNC) - && !status_flags.contains(StatusFlags::O_PATH) - { - inode.resize(0)?; - } - - let rights = if status_flags.contains(StatusFlags::O_PATH) { - Rights::empty() - } else { - access_mode.into() - }; - - Ok(Self { - memfd_inode: inode, - offset: Mutex::new(0), - status_flags: AtomicU32::new(open_args.status_flags.bits()), - rights, - }) - } - - pub fn add_seals(&self, new_seals: FileSeals) -> Result<()> { - if self.rights.is_empty() { + fn add_seals(&self, new_seals: FileSeals) -> Result<()> { + let rights = self.rights(); + if rights.is_empty() { return_errno_with_message!(Errno::EBADF, "the file is opened as a path"); } - if !self.rights.contains(Rights::WRITE) { + if !rights.contains(Rights::WRITE) { return_errno_with_message!(Errno::EPERM, "the file is not opened writable"); } - self.memfd_inode().add_seals(new_seals) + memfd_inode_or_err(self)?.add_seals(new_seals) } - pub fn get_seals(&self) -> Result { - if self.rights.is_empty() { + fn get_seals(&self) -> Result { + let rights = self.rights(); + if rights.is_empty() { return_errno_with_message!(Errno::EBADF, "the file is opened as a path"); } - Ok(self.memfd_inode().get_seals()) - } - - fn memfd_inode(&self) -> &MemfdInode { - self.memfd_inode.downcast_ref::().unwrap() + Ok(memfd_inode_or_err(self)?.get_seals()) } } -impl Pollable for MemfdFile { - fn poll(&self, mask: IoEvents, _poller: Option<&mut PollHandle>) -> IoEvents { - let events = IoEvents::IN | IoEvents::OUT; - events & mask - } -} - -impl FileLike for MemfdFile { - fn read(&self, writer: &mut VmWriter) -> Result { - let mut offset = self.offset.lock(); - - let len = self.read_at(*offset, writer)?; - *offset += len; - - Ok(len) - } - - fn read_at(&self, offset: usize, writer: &mut VmWriter) -> Result { - if !self.rights.contains(Rights::READ) { - return_errno_with_message!(Errno::EBADF, "the file is not opened readable"); - } - - self.memfd_inode - .read_at(offset, writer, self.status_flags()) - } - - fn write(&self, reader: &mut VmReader) -> Result { - let mut offset = self.offset.lock(); - - if self.status_flags().contains(StatusFlags::O_APPEND) { - // FIXME: `O_APPEND` should ensure that new content is appended even if another process - // is writing to the file concurrently. - *offset = self.memfd_inode.size(); - } - - let len = self.write_at(*offset, reader)?; - *offset += len; - - Ok(len) - } - - fn write_at(&self, mut offset: usize, reader: &mut VmReader) -> Result { - if !self.rights.contains(Rights::WRITE) { - return_errno_with_message!(Errno::EBADF, "the file is not opened writable"); - } - - let status_flags = self.status_flags(); - if status_flags.contains(StatusFlags::O_APPEND) { - // If the file has the `O_APPEND` flag, the offset is ignored. - // FIXME: `O_APPEND` should ensure that new content is appended even if another process - // is writing to the file concurrently. - offset = self.memfd_inode.size(); - } - self.memfd_inode.write_at(offset, reader, status_flags) - } - - fn resize(&self, new_size: usize) -> Result<()> { - if self.rights.is_empty() { - return_errno_with_message!(Errno::EBADF, "the file is opened as a path"); - } - if !self.rights.contains(Rights::WRITE) { - return_errno_with_message!(Errno::EINVAL, "the file is not opened writable"); - } - - do_resize_util(self.memfd_inode.as_ref(), self.status_flags(), new_size) - } - - fn status_flags(&self) -> StatusFlags { - let bits = self.status_flags.load(Ordering::Relaxed); - StatusFlags::from_bits(bits).unwrap() - } - - fn set_status_flags(&self, new_status_flags: StatusFlags) -> Result<()> { - self.status_flags - .store(new_status_flags.bits(), Ordering::Relaxed); - Ok(()) - } - - fn access_mode(&self) -> AccessMode { - self.rights.into() - } - - fn seek(&self, pos: SeekFrom) -> Result { - if self.rights.is_empty() { - return_errno_with_message!(Errno::EBADF, "the file is opened as a path"); - } - - do_seek_util(&self.offset, pos, Some(self.memfd_inode.size())) - } - - fn fallocate(&self, mode: FallocMode, offset: usize, len: usize) -> Result<()> { - if !self.rights.contains(Rights::WRITE) { - return_errno_with_message!(Errno::EBADF, "the file is not opened writable"); - } - - do_fallocate_util( - self.memfd_inode.as_ref(), - self.status_flags(), - mode, - offset, - len, - ) - } - - fn mappable(&self) -> Result { - if self.rights.is_empty() { - return_errno_with_message!(Errno::EBADF, "the file is opened as a path"); - } - - Ok(Mappable::Inode(self.memfd_inode.clone())) - } - - fn ioctl(&self, _raw_ioctl: RawIoctl) -> Result { - if self.rights.is_empty() { - return_errno_with_message!(Errno::EBADF, "the file is opened as a path"); - } - - return_errno_with_message!(Errno::ENOTTY, "ioctl is not supported"); - } - - fn inode(&self) -> &Arc { - &self.memfd_inode - } - - fn dump_proc_fdinfo(self: Arc, fd_flags: FdFlags) -> Box { - struct FdInfo { - inner: Arc, - fd_flags: FdFlags, - } - - impl Display for FdInfo { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - let mut flags = self.inner.status_flags().bits() | self.inner.access_mode() as u32; - if self.fd_flags.contains(FdFlags::CLOEXEC) { - flags |= CreationFlags::O_CLOEXEC.bits(); - } - - writeln!(f, "pos:\t{}", *self.inner.offset.lock())?; - writeln!(f, "flags:\t0{:o}", flags)?; - writeln!(f, "mnt_id:\t{}", RESERVED_MOUNT_ID)?; - writeln!(f, "ino:\t{}", self.inner.inode().ino()) - } - } - - Box::new(FdInfo { - inner: self, - fd_flags, +fn memfd_inode_or_err(file: &InodeHandle) -> Result<&MemfdInode> { + file.path() + .inode() + .downcast_ref::() + .ok_or_else(|| { + Error::with_message( + Errno::EINVAL, + "file seals can only be applied to memfd files", + ) }) +} + +struct MemfdTmpFs { + _private: (), +} + +impl MemfdTmpFs { + // Reference: + pub(self) fn singleton() -> &'static Arc { + static MEMFD_TMPFS: Once> = Once::new(); + + MEMFD_TMPFS.call_once(TmpFs::new) + } + + pub(self) fn new_path(memfd_inode: Arc) -> Path { + Path::new_pseudo(Self::mount_node().clone(), memfd_inode, |inode| { + let memfd_inode = inode.downcast_ref::().unwrap(); + format!("/memfd:{}", memfd_inode.name()) + }) + } + + fn mount_node() -> &'static Arc { + static MEMFD_TMPFS_MOUNT: Once> = Once::new(); + + MEMFD_TMPFS_MOUNT.call_once(|| Mount::new_pseudo(Self::singleton().clone())) } } diff --git a/kernel/src/syscall/fcntl.rs b/kernel/src/syscall/fcntl.rs index 75cc929bf..f15d8dfd2 100644 --- a/kernel/src/syscall/fcntl.rs +++ b/kernel/src/syscall/fcntl.rs @@ -7,7 +7,7 @@ use crate::{ fs::{ file_handle::FileLike, file_table::{FdFlags, FileDesc, WithFileTable, get_file_fast}, - ramfs::memfd::{FileSeals, MemfdFile}, + ramfs::memfd::{FileSeals, MemfdInodeHandle}, utils::{FileRange, OFFSET_MAX, RangeLockItem, RangeLockType, StatusFlags}, }, prelude::*, @@ -165,14 +165,8 @@ fn handle_addseal(fd: FileDesc, arg: u64, ctx: &Context) -> Result().ok_or_else(|| { - Error::with_message( - Errno::EINVAL, - "file seals can only be applied to memfd files", - ) - })?; - memfd_file.add_seals(new_seals)?; + file.as_inode_handle_or_err()?.add_seals(new_seals)?; Ok(SyscallReturn::Return(0)) } @@ -180,14 +174,8 @@ fn handle_addseal(fd: FileDesc, arg: u64, ctx: &Context) -> Result Result { let mut file_table = ctx.thread_local.borrow_file_table_mut(); let file = get_file_fast!(&mut file_table, fd); - let memfd_file = file.downcast_ref::().ok_or_else(|| { - Error::with_message( - Errno::EINVAL, - "file seals can only be applied to memfd files", - ) - })?; - let file_seals = memfd_file.get_seals()?; + let file_seals = file.as_inode_handle_or_err()?.get_seals()?; Ok(SyscallReturn::Return(file_seals.bits() as _)) } diff --git a/kernel/src/syscall/memfd_create.rs b/kernel/src/syscall/memfd_create.rs index 9ec75e215..1487905ce 100644 --- a/kernel/src/syscall/memfd_create.rs +++ b/kernel/src/syscall/memfd_create.rs @@ -4,7 +4,8 @@ use super::SyscallReturn; use crate::{ fs::{ file_table::FdFlags, - ramfs::memfd::{MAX_MEMFD_NAME_LEN, MemfdFile, MemfdFlags}, + inode_handle::InodeHandle, + ramfs::memfd::{MAX_MEMFD_NAME_LEN, MemfdFlags, MemfdInodeHandle}, }, prelude::*, }; @@ -40,7 +41,7 @@ pub fn sys_memfd_create(name_addr: Vaddr, flags: u32, ctx: &Context) -> Result match target { PathOrInode::Path(path) => Arc::new(path.open(open_args)?), PathOrInode::Inode(inode) => { - if let Ok(memfd_inode) = Arc::downcast::(inode.clone()) { - Arc::new(MemfdFile::open(memfd_inode, open_args)?) - } else if let Ok(pipe_inode) = Arc::downcast::(inode) { + if let Ok(pipe_inode) = Arc::downcast::(inode) { Arc::new(AnonPipeFile::open( pipe_inode, open_args.access_mode, diff --git a/test/src/syscall/gvisor/blocklists/memfd_test b/test/src/syscall/gvisor/blocklists/memfd_test deleted file mode 100644 index 696e74694..000000000 --- a/test/src/syscall/gvisor/blocklists/memfd_test +++ /dev/null @@ -1 +0,0 @@ -MemfdTest.Name \ No newline at end of file