// SPDX-License-Identifier: MPL-2.0 use core::{ sync::atomic::{AtomicU64, Ordering}, time::Duration, }; use align_ext::AlignExt; use aster_block::bio::BioWaiter; use aster_util::slot_vec::SlotVec; use device_id::DeviceId; use hashbrown::HashMap; use ostd::{ mm::{HasSize, io_util::HasVmReaderWriter}, sync::{PreemptDisabled, RwLockWriteGuard}, }; use super::{memfd::MemfdInode, xattr::RamXattr, *}; use crate::{ device, fs::{ device::DeviceType, inode_handle::FileIo, notify::FsEventPublisher, path::{is_dot, is_dot_or_dotdot, is_dotdot}, pipe::NamedPipe, registry::{FsProperties, FsType}, utils::{ AccessMode, CStr256, CachePage, DirentVisitor, Extension, FallocMode, FileSystem, FsEventSubscriberStats, FsFlags, Inode, InodeIo, InodeMode, InodeType, Metadata, MknodType, PageCache, PageCacheBackend, Permission, StatusFlags, SuperBlock, SymbolicLink, XattrName, XattrNamespace, XattrSetFlags, mkmod, }, }, prelude::*, process::{Gid, Uid}, time::clocks::RealTimeCoarseClock, vm::vmo::Vmo, }; /// A volatile file system whose data and metadata exists only in memory. pub struct RamFs { /// The super block sb: SuperBlock, /// Root inode root: Arc, /// An inode allocator inode_allocator: AtomicU64, /// FS event subscriber stats for this file system fs_event_subscriber_stats: FsEventSubscriberStats, } impl RamFs { pub fn new() -> Arc { Arc::new_cyclic(|weak_fs| Self { sb: SuperBlock::new(RAMFS_MAGIC, BLOCK_SIZE, NAME_MAX), root: Arc::new_cyclic(|weak_root| RamInode { inner: Inner::new_dir(weak_root.clone(), weak_root.clone()), metadata: SpinLock::new(InodeMeta::new_dir( mkmod!(a+rx, u+w), Uid::new_root(), Gid::new_root(), )), ino: ROOT_INO, typ: InodeType::Dir, this: weak_root.clone(), fs: weak_fs.clone(), extension: Extension::new(), xattr: RamXattr::new(), fs_event_publisher: FsEventPublisher::new(), }), inode_allocator: AtomicU64::new(ROOT_INO + 1), fs_event_subscriber_stats: FsEventSubscriberStats::new(), }) } fn alloc_id(&self) -> u64 { self.inode_allocator.fetch_add(1, Ordering::SeqCst) } } impl FileSystem for RamFs { fn name(&self) -> &'static str { "ramfs" } fn sync(&self) -> Result<()> { // do nothing Ok(()) } fn root_inode(&self) -> Arc { self.root.clone() } fn sb(&self) -> SuperBlock { self.sb.clone() } fn fs_event_subscriber_stats(&self) -> &FsEventSubscriberStats { &self.fs_event_subscriber_stats } } /// An inode of `RamFs`. pub(super) struct RamInode { /// Inode inner specifics inner: Inner, /// Inode metadata metadata: SpinLock, /// Inode number ino: u64, /// Type of the inode typ: InodeType, /// Reference to self this: Weak, /// Reference to fs fs: Weak, /// Extensions extension: Extension, /// FS event publisher fs_event_publisher: FsEventPublisher, /// Extended attributes xattr: RamXattr, } /// Inode inner specifics. enum Inner { Dir(RwLock), File(PageCache), SymLink(SpinLock), BlockDevice(u64), CharDevice(u64), Socket, NamedPipe(NamedPipe), } impl Inner { pub(self) fn new_dir(this: Weak, parent: Weak) -> Self { Self::Dir(RwLock::new(DirEntry::new(this, parent))) } pub(self) fn new_file(this: Weak) -> Self { Self::File(PageCache::new(this).unwrap()) } pub(self) fn new_symlink() -> Self { Self::SymLink(SpinLock::new(String::from(""))) } pub(self) fn new_block_device(dev_id: u64) -> Self { Self::BlockDevice(dev_id) } pub(self) fn new_char_device(dev_id: u64) -> Self { Self::CharDevice(dev_id) } pub(self) fn new_socket() -> Self { Self::Socket } pub(self) fn new_named_pipe() -> Self { Self::NamedPipe(NamedPipe::new()) } pub(self) fn new_file_in_memfd(this: Weak) -> Self { Self::File(PageCache::new(this).unwrap()) } fn as_direntry(&self) -> Option<&RwLock> { match self { Self::Dir(dir_entry) => Some(dir_entry), _ => None, } } fn as_file(&self) -> Option<&PageCache> { match self { Self::File(page_cache) => Some(page_cache), _ => None, } } fn as_symlink(&self) -> Option<&SpinLock> { match self { Self::SymLink(link) => Some(link), _ => None, } } fn device_id(&self) -> Option { match self { Self::BlockDevice(dev_id) | Self::CharDevice(dev_id) => Some(*dev_id), _ => None, } } fn device_type(&self) -> Option { match self { Self::BlockDevice(_) => Some(DeviceType::Block), Self::CharDevice(_) => Some(DeviceType::Char), _ => None, } } fn open( &self, access_mode: AccessMode, status_flags: StatusFlags, ) -> Option>> { match self { Self::BlockDevice(device_id) | Self::CharDevice(device_id) => { let Some(device_id) = DeviceId::from_encoded_u64(*device_id) else { return Some(Err(Error::with_message( Errno::ENODEV, "the device ID is invalid", ))); }; let device_type = self.device_type().unwrap(); let Some(device) = device::lookup(device_type, device_id) else { return Some(Err(Error::with_message( Errno::ENODEV, "the required device ID does not exist", ))); }; Some(device.open()) } Self::NamedPipe(pipe) => Some(pipe.open(access_mode, status_flags)), _ => None, } } } /// Inode metadata. #[derive(Debug, Clone, Copy)] struct InodeMeta { size: usize, blocks: usize, atime: Duration, mtime: Duration, ctime: Duration, mode: InodeMode, nlinks: usize, uid: Uid, gid: Gid, } impl InodeMeta { pub fn new(mode: InodeMode, uid: Uid, gid: Gid) -> Self { let now = now(); Self { size: 0, blocks: 0, atime: now, mtime: now, ctime: now, mode, nlinks: 1, uid, gid, } } pub fn new_dir(mode: InodeMode, uid: Uid, gid: Gid) -> Self { let now = now(); Self { size: NUM_SPECIAL_ENTRIES, blocks: 1, atime: now, mtime: now, ctime: now, mode, nlinks: NUM_SPECIAL_ENTRIES, uid, gid, } } pub fn resize(&mut self, new_size: usize) { self.size = new_size; self.blocks = new_size.align_up(BLOCK_SIZE) / BLOCK_SIZE; } pub fn inc_size(&mut self) { self.size += 1; self.blocks = self.size.align_up(BLOCK_SIZE) / BLOCK_SIZE; } pub fn dec_size(&mut self) { debug_assert!(self.size > 0); self.size -= 1; self.blocks = self.size.align_up(BLOCK_SIZE) / BLOCK_SIZE; } pub fn set_atime(&mut self, time: Duration) { self.atime = time; } pub fn set_mtime(&mut self, time: Duration) { self.mtime = time; } pub fn set_ctime(&mut self, time: Duration) { self.ctime = time; } pub fn inc_nlinks(&mut self) { self.nlinks += 1; } pub fn dec_nlinks(&mut self) { debug_assert!(self.nlinks > 0); self.nlinks -= 1; } } /// Represents a directory entry within a `RamInode`. struct DirEntry { children: SlotVec<(CStr256, Arc)>, idx_map: HashMap, // Used to accelerate indexing in `children` this: Weak, parent: Weak, } // Every directory has two special entries: "." and "..". const NUM_SPECIAL_ENTRIES: usize = 2; impl DirEntry { fn new(this: Weak, parent: Weak) -> Self { Self { children: SlotVec::new(), idx_map: HashMap::new(), this, parent, } } fn set_parent(&mut self, parent: Weak) { self.parent = parent; } fn contains_entry(&self, name: &str) -> bool { if is_dot_or_dotdot(name) { true } else { self.idx_map.contains_key(name.as_bytes()) } } fn get_entry(&self, name: &str) -> Option<(usize, Arc)> { if is_dot(name) { Some((0, self.this.upgrade().unwrap())) } else if is_dotdot(name) { Some((1, self.parent.upgrade().unwrap())) } else { let idx = *self.idx_map.get(name.as_bytes())?; let target_inode = self .children .get(idx) .map(|(name_cstr256, inode)| { debug_assert_eq!(name, name_cstr256.as_str().unwrap()); inode.clone() }) .unwrap(); Some((idx + NUM_SPECIAL_ENTRIES, target_inode)) } } fn append_entry(&mut self, name: &str, inode: Arc) -> usize { let name = CStr256::from(name); let idx = self.children.put((name, inode)); self.idx_map.insert(name, idx); idx } fn remove_entry(&mut self, idx: usize) -> Option<(CStr256, Arc)> { assert!(idx >= NUM_SPECIAL_ENTRIES); let removed = self.children.remove(idx - NUM_SPECIAL_ENTRIES)?; self.idx_map.remove(&removed.0); Some(removed) } fn substitute_entry( &mut self, idx: usize, new_entry: (CStr256, Arc), ) -> Option<(CStr256, Arc)> { assert!(idx >= NUM_SPECIAL_ENTRIES); let new_name = new_entry.0; let idx_children = idx - NUM_SPECIAL_ENTRIES; let substitute = self.children.put_at(idx_children, new_entry)?; let removed = self.idx_map.remove(&substitute.0); debug_assert_eq!(removed.unwrap(), idx_children); self.idx_map.insert(new_name, idx_children); Some(substitute) } fn visit_entry(&self, idx: usize, visitor: &mut dyn DirentVisitor) -> Result { let try_visit = |idx: &mut usize, visitor: &mut dyn DirentVisitor| -> Result<()> { // Read the two special entries("." and ".."). if *idx == 0 { let this_inode = self.this.upgrade().unwrap(); visitor.visit(".", this_inode.ino, this_inode.typ, *idx)?; *idx += 1; } if *idx == 1 { let parent_inode = self.parent.upgrade().unwrap(); visitor.visit("..", parent_inode.ino, parent_inode.typ, *idx)?; *idx += 1; } // Read the normal child entries. let start_idx = *idx; for (offset_children, (name, child)) in self .children .idxes_and_items() .skip_while(|(offset, _)| offset + NUM_SPECIAL_ENTRIES < start_idx) { let offset = offset_children + NUM_SPECIAL_ENTRIES; visitor.visit(name.as_str().unwrap(), child.ino, child.typ, offset)?; *idx = offset + 1; } Ok(()) }; let mut iterate_idx = idx; match try_visit(&mut iterate_idx, visitor) { Err(e) if idx == iterate_idx => Err(e), _ => Ok(iterate_idx - idx), } } fn is_empty_children(&self) -> bool { self.children.is_empty() } } impl RamInode { fn new_dir( fs: &Arc, mode: InodeMode, uid: Uid, gid: Gid, parent: &Weak, ) -> Arc { Arc::new_cyclic(|weak_self| RamInode { inner: Inner::new_dir(weak_self.clone(), parent.clone()), metadata: SpinLock::new(InodeMeta::new_dir(mode, uid, gid)), ino: fs.alloc_id(), typ: InodeType::Dir, this: weak_self.clone(), fs: Arc::downgrade(fs), extension: Extension::new(), fs_event_publisher: FsEventPublisher::new(), xattr: RamXattr::new(), }) } fn new_file(fs: &Arc, mode: InodeMode, uid: Uid, gid: Gid) -> Arc { Arc::new_cyclic(|weak_self| RamInode { inner: Inner::new_file(weak_self.clone()), metadata: SpinLock::new(InodeMeta::new(mode, uid, gid)), ino: fs.alloc_id(), typ: InodeType::File, this: weak_self.clone(), fs: Arc::downgrade(fs), extension: Extension::new(), fs_event_publisher: FsEventPublisher::new(), xattr: RamXattr::new(), }) } /// Creates a `RamInode` that is detached from any `RamFs`, and resides in a `MemfdInode`. pub(super) fn new_file_detached_in_memfd( weak_self: &Weak, mode: InodeMode, uid: Uid, gid: Gid, ) -> Self { Self { inner: Inner::new_file_in_memfd(weak_self.clone()), metadata: SpinLock::new(InodeMeta::new(mode, uid, gid)), ino: weak_self.as_ptr() as u64, typ: InodeType::File, this: Weak::new(), fs: Weak::new(), extension: Extension::new(), fs_event_publisher: FsEventPublisher::new(), xattr: RamXattr::new(), } } fn new_symlink(fs: &Arc, mode: InodeMode, uid: Uid, gid: Gid) -> Arc { Arc::new_cyclic(|weak_self| RamInode { inner: Inner::new_symlink(), metadata: SpinLock::new(InodeMeta::new(mode, uid, gid)), ino: fs.alloc_id(), typ: InodeType::SymLink, this: weak_self.clone(), fs: Arc::downgrade(fs), extension: Extension::new(), fs_event_publisher: FsEventPublisher::new(), xattr: RamXattr::new(), }) } fn new_device( fs: &Arc, mode: InodeMode, uid: Uid, gid: Gid, dev_type: DeviceType, dev_id: u64, ) -> Arc { let inner = match dev_type { DeviceType::Block => Inner::new_block_device(dev_id), DeviceType::Char => Inner::new_char_device(dev_id), }; Arc::new_cyclic(|weak_self| RamInode { inner, metadata: SpinLock::new(InodeMeta::new(mode, uid, gid)), ino: fs.alloc_id(), typ: dev_type.into(), this: weak_self.clone(), fs: Arc::downgrade(fs), extension: Extension::new(), fs_event_publisher: FsEventPublisher::new(), xattr: RamXattr::new(), }) } fn new_socket(fs: &Arc, mode: InodeMode, uid: Uid, gid: Gid) -> Arc { Arc::new_cyclic(|weak_self| RamInode { inner: Inner::new_socket(), metadata: SpinLock::new(InodeMeta::new(mode, uid, gid)), ino: fs.alloc_id(), typ: InodeType::Socket, this: weak_self.clone(), fs: Arc::downgrade(fs), extension: Extension::new(), fs_event_publisher: FsEventPublisher::new(), xattr: RamXattr::new(), }) } fn new_named_pipe(fs: &Arc, mode: InodeMode, uid: Uid, gid: Gid) -> Arc { Arc::new_cyclic(|weak_self| RamInode { inner: Inner::new_named_pipe(), metadata: SpinLock::new(InodeMeta::new(mode, uid, gid)), ino: fs.alloc_id(), typ: InodeType::NamedPipe, this: weak_self.clone(), fs: Arc::downgrade(fs), extension: Extension::new(), fs_event_publisher: FsEventPublisher::new(), xattr: RamXattr::new(), }) } fn find(&self, name: &str) -> Result> { if self.typ != InodeType::Dir { return_errno_with_message!(Errno::ENOTDIR, "self is not dir"); } let (_, inode) = self .inner .as_direntry() .unwrap() .read() .get_entry(name) .ok_or(Error::new(Errno::ENOENT))?; Ok(inode) } } impl PageCacheBackend for RamInode { fn read_page_async(&self, _idx: usize, frame: &CachePage) -> Result { // Initially, any block/page in a RamFs inode contains all zeros frame.writer().fill_zeros(frame.size()); Ok(BioWaiter::new()) } fn write_page_async(&self, _idx: usize, _frame: &CachePage) -> Result { // do nothing Ok(BioWaiter::new()) } fn npages(&self) -> usize { self.metadata.lock().blocks } } impl InodeIo for RamInode { fn read_at( &self, offset: usize, writer: &mut VmWriter, _status_flags: StatusFlags, ) -> Result { let read_len = match &self.inner { Inner::File(page_cache) => { let (offset, read_len) = { let file_size = self.size(); let start = file_size.min(offset); let end = file_size.min(offset + writer.avail()); (start, end - start) }; page_cache.pages().read(offset, writer)?; read_len } _ => return_errno_with_message!(Errno::EISDIR, "read is not supported"), }; if self.typ == InodeType::File { self.set_atime(now()); } Ok(read_len) } fn write_at( &self, offset: usize, reader: &mut VmReader, _status_flags: StatusFlags, ) -> Result { let written_len = match self.typ { InodeType::File => { let page_cache = self.inner.as_file().unwrap(); let file_size = self.size(); let write_len = reader.remain(); let new_size = offset + write_len; let should_expand_size = new_size > file_size; let new_size_aligned = new_size.align_up(BLOCK_SIZE); if should_expand_size { page_cache.resize(new_size_aligned)?; } page_cache.pages().write(offset, reader)?; let now = now(); let mut inode_meta = self.metadata.lock(); inode_meta.set_mtime(now); inode_meta.set_ctime(now); if should_expand_size { inode_meta.size = new_size; inode_meta.blocks = new_size_aligned / BLOCK_SIZE; } write_len } _ => return_errno_with_message!(Errno::EISDIR, "write is not supported"), }; Ok(written_len) } } impl Inode for RamInode { fn page_cache(&self) -> Option> { self.inner .as_file() .map(|page_cache| page_cache.pages().clone()) } fn size(&self) -> usize { self.metadata.lock().size } fn resize(&self, new_size: usize) -> Result<()> { if self.typ == InodeType::Dir { return_errno_with_message!(Errno::EISDIR, "the inode is a directory"); } if self.typ != InodeType::File { return_errno_with_message!(Errno::EINVAL, "the inode is not a regular file"); } let file_size = self.size(); if file_size == new_size { return Ok(()); } let page_cache = self.inner.as_file().unwrap(); page_cache.resize(new_size)?; let now = now(); let mut inode_meta = self.metadata.lock(); inode_meta.set_mtime(now); inode_meta.set_ctime(now); inode_meta.resize(new_size); Ok(()) } fn atime(&self) -> Duration { self.metadata.lock().atime } fn set_atime(&self, time: Duration) { self.metadata.lock().set_atime(time); } fn mtime(&self) -> Duration { self.metadata.lock().mtime } fn set_mtime(&self, time: Duration) { self.metadata.lock().set_mtime(time); } fn ctime(&self) -> Duration { self.metadata.lock().ctime } fn set_ctime(&self, time: Duration) { self.metadata.lock().set_ctime(time); } fn ino(&self) -> u64 { self.ino } fn type_(&self) -> InodeType { self.typ } fn mode(&self) -> Result { Ok(self.metadata.lock().mode) } fn set_mode(&self, mode: InodeMode) -> Result<()> { let mut inode_meta = self.metadata.lock(); inode_meta.mode = mode; inode_meta.set_ctime(now()); Ok(()) } fn owner(&self) -> Result { Ok(self.metadata.lock().uid) } fn set_owner(&self, uid: Uid) -> Result<()> { let mut inode_meta = self.metadata.lock(); inode_meta.uid = uid; inode_meta.set_ctime(now()); Ok(()) } fn group(&self) -> Result { Ok(self.metadata.lock().gid) } fn set_group(&self, gid: Gid) -> Result<()> { let mut inode_meta = self.metadata.lock(); inode_meta.gid = gid; inode_meta.set_ctime(now()); Ok(()) } fn mknod(&self, name: &str, mode: InodeMode, type_: MknodType) -> Result> { if name.len() > NAME_MAX { return_errno!(Errno::ENAMETOOLONG); } if self.typ != InodeType::Dir { return_errno_with_message!(Errno::ENOTDIR, "self is not dir"); } let self_dir = self.inner.as_direntry().unwrap().upread(); if self_dir.contains_entry(name) { return_errno_with_message!(Errno::EEXIST, "entry exists"); } let new_inode = match type_ { MknodType::CharDevice(dev_id) | MknodType::BlockDevice(dev_id) => { let dev_type = type_.device_type().unwrap(); RamInode::new_device( &self.fs.upgrade().unwrap(), mode, Uid::new_root(), Gid::new_root(), dev_type, dev_id, ) } MknodType::NamedPipe => RamInode::new_named_pipe( &self.fs.upgrade().unwrap(), mode, Uid::new_root(), Gid::new_root(), ), }; let mut self_dir = self_dir.upgrade(); self_dir.append_entry(name, new_inode.clone()); drop(self_dir); self.metadata.lock().inc_size(); Ok(new_inode) } fn open( &self, access_mode: AccessMode, status_flags: StatusFlags, ) -> Option>> { self.inner.open(access_mode, status_flags) } fn create(&self, name: &str, type_: InodeType, mode: InodeMode) -> Result> { if name.len() > NAME_MAX { return_errno!(Errno::ENAMETOOLONG); } if self.typ != InodeType::Dir { return_errno_with_message!(Errno::ENOTDIR, "self is not dir"); } let self_dir = self.inner.as_direntry().unwrap().upread(); if self_dir.contains_entry(name) { return_errno_with_message!(Errno::EEXIST, "entry exists"); } let fs = self.fs.upgrade().unwrap(); let new_inode = match type_ { InodeType::File => RamInode::new_file(&fs, mode, Uid::new_root(), Gid::new_root()), InodeType::SymLink => { RamInode::new_symlink(&fs, mode, Uid::new_root(), Gid::new_root()) } InodeType::Socket => RamInode::new_socket(&fs, mode, Uid::new_root(), Gid::new_root()), InodeType::Dir => { RamInode::new_dir(&fs, mode, Uid::new_root(), Gid::new_root(), &self.this) } _ => { panic!("unsupported inode type"); } }; let mut self_dir = self_dir.upgrade(); self_dir.append_entry(name, new_inode.clone()); drop(self_dir); let now = now(); let mut inode_meta = self.metadata.lock(); inode_meta.set_mtime(now); inode_meta.set_ctime(now); inode_meta.inc_size(); if type_ == InodeType::Dir { inode_meta.inc_nlinks(); } Ok(new_inode) } fn readdir_at(&self, offset: usize, visitor: &mut dyn DirentVisitor) -> Result { if self.typ != InodeType::Dir { return_errno_with_message!(Errno::ENOTDIR, "self is not dir"); } let cnt = self .inner .as_direntry() .unwrap() .read() .visit_entry(offset, visitor)?; self.set_atime(now()); Ok(cnt) } fn link(&self, old: &Arc, name: &str) -> Result<()> { if !Arc::ptr_eq(&self.fs(), &old.fs()) { return_errno_with_message!(Errno::EXDEV, "not same fs"); } if self.typ != InodeType::Dir { return_errno_with_message!(Errno::ENOTDIR, "self is not dir"); } let old = old .downcast_ref::() .ok_or(Error::new(Errno::EXDEV))?; if old.typ == InodeType::Dir { return_errno_with_message!(Errno::EPERM, "old is a dir"); } let mut self_dir = self.inner.as_direntry().unwrap().write(); if self_dir.contains_entry(name) { return_errno_with_message!(Errno::EEXIST, "entry exist"); } self_dir.append_entry(name, old.this.upgrade().unwrap()); drop(self_dir); let now = now(); let mut self_meta = self.metadata.lock(); self_meta.set_mtime(now); self_meta.set_ctime(now); self_meta.inc_size(); drop(self_meta); let mut old_meta = old.metadata.lock(); old_meta.inc_nlinks(); old_meta.set_ctime(now); Ok(()) } fn unlink(&self, name: &str) -> Result<()> { if is_dot_or_dotdot(name) { return_errno_with_message!(Errno::EISDIR, "unlink . or .."); } let target = self.find(name)?; if target.typ == InodeType::Dir { return_errno_with_message!(Errno::EISDIR, "unlink on dir"); } // When we got the lock, the dir may have been modified by another thread let mut self_dir = self.inner.as_direntry().unwrap().write(); let (idx, new_target) = self_dir.get_entry(name).ok_or(Error::new(Errno::ENOENT))?; if !Arc::ptr_eq(&new_target, &target) { return_errno!(Errno::ENOENT); } self_dir.remove_entry(idx); drop(self_dir); let now = now(); let mut self_meta = self.metadata.lock(); self_meta.dec_size(); self_meta.set_mtime(now); self_meta.set_ctime(now); drop(self_meta); let mut target_meta = target.metadata.lock(); target_meta.dec_nlinks(); target_meta.set_ctime(now); Ok(()) } fn rmdir(&self, name: &str) -> Result<()> { if is_dot(name) { return_errno_with_message!(Errno::EINVAL, "rmdir on ."); } if is_dotdot(name) { return_errno_with_message!(Errno::ENOTEMPTY, "rmdir on .."); } let target = self.find(name)?; if target.typ != InodeType::Dir { return_errno_with_message!(Errno::ENOTDIR, "rmdir on not dir"); } // When we got the lock, the dir may have been modified by another thread let (mut self_dir, target_dir) = write_lock_two_direntries_by_ino( (self.ino, self.inner.as_direntry().unwrap()), (target.ino, target.inner.as_direntry().unwrap()), ); if !target_dir.is_empty_children() { return_errno_with_message!(Errno::ENOTEMPTY, "dir not empty"); } let (idx, new_target) = self_dir.get_entry(name).ok_or(Error::new(Errno::ENOENT))?; if !Arc::ptr_eq(&new_target, &target) { return_errno!(Errno::ENOENT); } self_dir.remove_entry(idx); drop(self_dir); drop(target_dir); let now = now(); let mut self_meta = self.metadata.lock(); self_meta.dec_size(); self_meta.dec_nlinks(); self_meta.set_mtime(now); self_meta.set_ctime(now); drop(self_meta); let mut target_meta = target.metadata.lock(); target_meta.dec_nlinks(); target_meta.dec_nlinks(); Ok(()) } fn lookup(&self, name: &str) -> Result> { let inode = self.find(name)?; Ok(inode as _) } fn rename(&self, old_name: &str, target: &Arc, new_name: &str) -> Result<()> { if is_dot_or_dotdot(old_name) { return_errno_with_message!(Errno::EISDIR, "old_name is . or .."); } if is_dot_or_dotdot(new_name) { return_errno_with_message!(Errno::EISDIR, "new_name is . or .."); } let target = target .downcast_ref::() .ok_or(Error::new(Errno::EXDEV))?; if !Arc::ptr_eq(&self.fs(), &target.fs()) { return_errno_with_message!(Errno::EXDEV, "not same fs"); } if self.typ != InodeType::Dir { return_errno_with_message!(Errno::ENOTDIR, "self is not dir"); } if target.typ != InodeType::Dir { return_errno_with_message!(Errno::ENOTDIR, "target is not dir"); } // Perform necessary checks to ensure that `dst_inode` can be replaced by `src_inode`. let check_replace_inode = |src_inode: &Arc, dst_inode: &Arc| -> Result<()> { if src_inode.ino == dst_inode.ino { return Ok(()); } match (src_inode.typ, dst_inode.typ) { (InodeType::Dir, InodeType::Dir) => { if !dst_inode .inner .as_direntry() .unwrap() .read() .is_empty_children() { return_errno_with_message!(Errno::ENOTEMPTY, "dir not empty"); } } (InodeType::Dir, _) => { return_errno_with_message!(Errno::ENOTDIR, "old is not dir"); } (_, InodeType::Dir) => { return_errno_with_message!(Errno::EISDIR, "new is dir"); } _ => {} } Ok(()) }; // Rename in the same directory if self.ino == target.ino { let mut self_dir = self.inner.as_direntry().unwrap().write(); let (src_idx, src_inode) = self_dir .get_entry(old_name) .ok_or(Error::new(Errno::ENOENT))?; let is_dir = src_inode.typ == InodeType::Dir; if let Some((dst_idx, dst_inode)) = self_dir.get_entry(new_name) { check_replace_inode(&src_inode, &dst_inode)?; self_dir.remove_entry(dst_idx); self_dir.substitute_entry(src_idx, (CStr256::from(new_name), src_inode.clone())); drop(self_dir); let now = now(); let mut self_meta = self.metadata.lock(); self_meta.dec_size(); if is_dir { self_meta.dec_nlinks(); } self_meta.set_mtime(now); self_meta.set_ctime(now); drop(self_meta); src_inode.set_ctime(now); dst_inode.set_ctime(now); } else { self_dir.substitute_entry(src_idx, (CStr256::from(new_name), src_inode.clone())); drop(self_dir); let now = now(); let mut self_meta = self.metadata.lock(); self_meta.set_mtime(now); self_meta.set_ctime(now); drop(self_meta); src_inode.set_ctime(now); } } // Or rename across different directories else { let (mut self_dir, mut target_dir) = write_lock_two_direntries_by_ino( (self.ino, self.inner.as_direntry().unwrap()), (target.ino, target.inner.as_direntry().unwrap()), ); let self_inode_arc = self.this.upgrade().unwrap(); let target_inode_arc = target.this.upgrade().unwrap(); let (src_idx, src_inode) = self_dir .get_entry(old_name) .ok_or(Error::new(Errno::ENOENT))?; // Avoid renaming a directory to a subdirectory of itself if Arc::ptr_eq(&src_inode, &target_inode_arc) { return_errno!(Errno::EINVAL); } let is_dir = src_inode.typ == InodeType::Dir; if let Some((dst_idx, dst_inode)) = target_dir.get_entry(new_name) { // Avoid renaming a subdirectory to a directory. if Arc::ptr_eq(&self_inode_arc, &dst_inode) { return_errno!(Errno::ENOTEMPTY); } check_replace_inode(&src_inode, &dst_inode)?; self_dir.remove_entry(src_idx); target_dir.remove_entry(dst_idx); target_dir.append_entry(new_name, src_inode.clone()); drop(self_dir); drop(target_dir); let now = now(); let mut self_meta = self.metadata.lock(); self_meta.dec_size(); if is_dir { self_meta.dec_nlinks(); } self_meta.set_mtime(now); self_meta.set_ctime(now); drop(self_meta); let mut target_meta = target.metadata.lock(); target_meta.set_mtime(now); target_meta.set_ctime(now); drop(target_meta); dst_inode.set_ctime(now); src_inode.set_ctime(now); } else { self_dir.remove_entry(src_idx); target_dir.append_entry(new_name, src_inode.clone()); drop(self_dir); drop(target_dir); let now = now(); let mut self_meta = self.metadata.lock(); self_meta.dec_size(); if is_dir { self_meta.dec_nlinks(); } self_meta.set_mtime(now); self_meta.set_ctime(now); drop(self_meta); let mut target_meta = target.metadata.lock(); target_meta.inc_size(); if is_dir { target_meta.inc_nlinks(); } target_meta.set_mtime(now); target_meta.set_ctime(now); drop(target_meta); src_inode.set_ctime(now); } if is_dir { src_inode .inner .as_direntry() .unwrap() .write() .set_parent(target.this.clone()); } } Ok(()) } fn read_link(&self) -> Result { if self.typ != InodeType::SymLink { return_errno_with_message!(Errno::EINVAL, "self is not symlink"); } let link = self.inner.as_symlink().unwrap().lock(); Ok(SymbolicLink::Plain(link.clone())) } fn write_link(&self, target: &str) -> Result<()> { if self.typ != InodeType::SymLink { return_errno_with_message!(Errno::EINVAL, "self is not symlink"); } let mut link = self.inner.as_symlink().unwrap().lock(); *link = String::from(target); drop(link); // Symlink's metadata.blocks should be 0, so just set the size. self.metadata.lock().size = target.len(); Ok(()) } fn metadata(&self) -> Metadata { let rdev = self.inner.device_id().unwrap_or(0); let inode_metadata = self.metadata.lock(); Metadata { dev: 0, ino: self.ino as _, size: inode_metadata.size, blk_size: BLOCK_SIZE, blocks: inode_metadata.blocks, atime: inode_metadata.atime, mtime: inode_metadata.mtime, ctime: inode_metadata.ctime, type_: self.typ, mode: inode_metadata.mode, nlinks: inode_metadata.nlinks, uid: inode_metadata.uid, gid: inode_metadata.gid, rdev, } } fn fs(&self) -> Arc { Weak::upgrade(&self.fs).unwrap() } fn fallocate(&self, mode: FallocMode, offset: usize, len: usize) -> Result<()> { // The support for flags is consistent with Linux match mode { FallocMode::Allocate => { let new_size = offset + len; if new_size > self.size() { self.resize(new_size)?; } Ok(()) } FallocMode::AllocateKeepSize => { // Do nothing Ok(()) } FallocMode::PunchHoleKeepSize => { let file_size = self.size(); if offset >= file_size { return Ok(()); } let range = offset..file_size.min(offset + len); // TODO: Think of a more light-weight approach self.inner.as_file().unwrap().fill_zeros(range) } _ => { return_errno_with_message!( Errno::EOPNOTSUPP, "fallocate with the specified flags is not supported" ); } } } fn extension(&self) -> Option<&Extension> { Some(&self.extension) } fn fs_event_publisher(&self) -> &FsEventPublisher { &self.fs_event_publisher } fn set_xattr( &self, name: XattrName, value_reader: &mut VmReader, flags: XattrSetFlags, ) -> Result<()> { RamXattr::check_file_type_for_xattr(self.typ)?; self.check_permission(Permission::MAY_WRITE)?; self.xattr.set(name, value_reader, flags) } fn get_xattr(&self, name: XattrName, value_writer: &mut VmWriter) -> Result { RamXattr::check_file_type_for_xattr(self.typ) .map_err(|_| Error::with_message(Errno::ENODATA, "no available xattrs"))?; self.check_permission(Permission::MAY_READ)?; self.xattr.get(name, value_writer) } fn list_xattr(&self, namespace: XattrNamespace, list_writer: &mut VmWriter) -> Result { if RamXattr::check_file_type_for_xattr(self.typ).is_err() { return Ok(0); } if self.check_permission(Permission::MAY_ACCESS).is_err() { return Ok(0); } self.xattr.list(namespace, list_writer) } fn remove_xattr(&self, name: XattrName) -> Result<()> { RamXattr::check_file_type_for_xattr(self.typ)?; self.check_permission(Permission::MAY_WRITE)?; self.xattr.remove(name) } } fn write_lock_two_direntries_by_ino<'a>( this: (u64, &'a RwLock), other: (u64, &'a RwLock), ) -> ( RwLockWriteGuard<'a, DirEntry, PreemptDisabled>, RwLockWriteGuard<'a, DirEntry, PreemptDisabled>, ) { if this.0 < other.0 { let this = this.1.write(); let other = other.1.write(); (this, other) } else { let other = other.1.write(); let this = this.1.write(); (this, other) } } fn now() -> Duration { RealTimeCoarseClock::get().read_time() } pub(super) struct RamFsType; impl FsType for RamFsType { fn name(&self) -> &'static str { "ramfs" } fn properties(&self) -> FsProperties { FsProperties::empty() } fn create( &self, _flags: FsFlags, _args: Option, _disk: Option>, ) -> Result> { Ok(RamFs::new()) } fn sysnode(&self) -> Option> { None } }