pub mod dcache; mod walk; use core::{ cell::UnsafeCell, fmt, hash::{BuildHasher, BuildHasherDefault, Hasher}, sync::atomic::{AtomicPtr, AtomicU64, AtomicU8, Ordering}, }; use alloc::sync::Arc; use arcref::AsArcRef; use eonix_sync::LazyLock; use pointers::BorrowedArc; use posix_types::{namei::RenameFlags, open::OpenFlags, result::PosixError, stat::StatX}; use crate::{ hash::KernelHasher, io::Buffer, io::Stream, kernel::constants::{EEXIST, EINVAL, EISDIR, ELOOP, ENOENT, EPERM, ERANGE}, kernel::{block::BlockDevice, CharDevice}, path::Path, prelude::*, rcu::{rcu_read_lock, RCUNode, RCUPointer, RCUReadGuard}, }; use super::{ inode::{Ino, Inode, InodeUse, RenameData, WriteOffset}, types::{DeviceId, Format, Mode, Permission}, FsContext, }; const D_INVALID: u8 = 0; const D_REGULAR: u8 = 1; const D_DIRECTORY: u8 = 2; const D_SYMLINK: u8 = 3; #[derive(Debug, PartialEq, Eq)] enum DentryKind { Regular = D_REGULAR as isize, Directory = D_DIRECTORY as isize, Symlink = D_SYMLINK as isize, } /// The [`Inode`] associated with a [`Dentry`]. /// /// We could assign an inode to a negative dentry exactly once when the dentry /// is invalid and we create a file or directory on it, or the dentry is brought /// to the dcache by [lookup()]. /// /// This guarantees that as long as we acquire a non-invalid from [`Self::kind`], /// we are synced with the writer and can safely read the [`Self::inode`] field /// without reading torn data. /// /// [lookup()]: crate::kernel::vfs::inode::InodeDirOps::lookup struct AssociatedInode { kind: UnsafeCell>, inode: UnsafeCell>>, } /// # Safety /// /// We wrap `Dentry` in `Arc` to ensure that the `Dentry` is not dropped while it is still in use. /// /// Since a `Dentry` is created and marked as live(some data is saved to it), it keeps alive until /// the last reference is dropped. pub struct Dentry { // Const after insertion into dcache parent: RCUPointer, name: RCUPointer>, hash: AtomicU64, // Used by the dentry cache prev: AtomicPtr, next: AtomicPtr, inode: AssociatedInode, } pub(super) static DROOT: LazyLock> = LazyLock::new(|| { let root = Arc::new(Dentry { parent: RCUPointer::empty(), name: RCUPointer::new(Arc::new(Arc::from(&b"[root]"[..]))), hash: AtomicU64::new(0), prev: AtomicPtr::default(), next: AtomicPtr::default(), inode: AssociatedInode::new(), }); unsafe { root.parent.swap(Some(root.clone())); } root.rehash(); root }); impl fmt::Debug for Dentry { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("Dentry") .field("name", &String::from_utf8_lossy(&self.name())) .finish() } } impl RCUNode for Dentry { fn rcu_prev(&self) -> &AtomicPtr { &self.prev } fn rcu_next(&self) -> &AtomicPtr { &self.next } } impl Dentry { fn is_hashed(&self) -> bool { self.prev.load(Ordering::Relaxed) != core::ptr::null_mut() } fn rehash(&self) { assert!( !self.is_hashed(), "`rehash()` called on some already hashed dentry" ); let builder: BuildHasherDefault = Default::default(); let mut hasher = builder.build_hasher(); hasher.write_usize(self.parent_addr() as usize); hasher.write(&self.name()); let hash = hasher.finish(); self.hash.store(hash, Ordering::Relaxed); } } impl Dentry { pub fn create(parent: Arc, name: &[u8]) -> Arc { // TODO!!!: don't acquire our parent's refcount here... let val = Arc::new(Self { parent: RCUPointer::new(parent), name: RCUPointer::new(Arc::new(Arc::from(name))), hash: AtomicU64::new(0), prev: AtomicPtr::default(), next: AtomicPtr::default(), inode: AssociatedInode::new(), }); val.rehash(); val } /// Check the equality of two denties inside the same dentry cache hash group /// where `other` is identified by `hash`, `parent` and `name` /// fn hash_eq(&self, other: &Self) -> bool { self.hash.load(Ordering::Relaxed) == other.hash.load(Ordering::Relaxed) && self.parent_addr() == other.parent_addr() && &***self.name() == &***other.name() } pub fn name(&self) -> RCUReadGuard>> { self.name.load().expect("Dentry has no name") } pub fn get_name(&self) -> Arc<[u8]> { (***self.name()).clone() } pub fn parent<'a>(&self) -> RCUReadGuard<'a, BorrowedArc> { self.parent.load().expect("Dentry has no parent") } pub fn parent_addr(&self) -> *const Self { self.parent .load() .map_or(core::ptr::null(), |parent| Arc::as_ptr(&parent)) } pub fn fill(&self, file: InodeUse) { self.inode.store(file); } pub fn inode(&self) -> Option> { self.inode.load().map(|(_, inode)| inode.clone()) } pub fn get_inode(&self) -> KResult> { self.inode().ok_or(ENOENT) } pub fn is_directory(&self) -> bool { self.inode .load() .map_or(false, |(kind, _)| kind == DentryKind::Directory) } pub fn is_valid(&self) -> bool { self.inode.load().is_some() } pub async fn open_check(self: &Arc, flags: OpenFlags, perm: Permission) -> KResult<()> { match self.inode.load() { Some(_) => { if flags.contains(OpenFlags::O_CREAT | OpenFlags::O_EXCL) { Err(EEXIST) } else { Ok(()) } } None => { if !flags.contains(OpenFlags::O_CREAT) { return Err(ENOENT); } let parent = self.parent().get_inode()?; parent.create(self, perm).await } } } } impl Dentry { pub async fn open( context: &FsContext, path: &Path, follow_symlinks: bool, ) -> KResult> { let cwd = context.cwd.lock().clone(); Self::open_at(context, &cwd, path, follow_symlinks).await } pub async fn open_at( context: &FsContext, at: &Arc, path: &Path, follow_symlinks: bool, ) -> KResult> { let mut found = context.start_recursive_walk(at, path).await?; if !follow_symlinks { return Ok(found); } loop { match found.inode.load() { Some((DentryKind::Symlink, inode)) => { found = context.follow_symlink(found.aref(), inode, 0).await?; } _ => return Ok(found), } } } pub fn get_path(self: &Arc, context: &FsContext, buffer: &mut dyn Buffer) -> KResult<()> { let rcu_read = rcu_read_lock(); let mut path = vec![]; let mut current = self.aref(); let mut parent = self.parent.dereference(&rcu_read).unwrap(); while !current.ptr_eq_arc(&context.fsroot) { if path.len() > 32 { return Err(ELOOP); } path.push(current.name.dereference(&rcu_read).unwrap()); current = parent; parent = current.parent.dereference(&rcu_read).unwrap(); } buffer.fill(b"/")?.ok_or(ERANGE)?; for item in path.iter().rev().map(|name| name.as_ref()) { buffer.fill(item)?.ok_or(ERANGE)?; buffer.fill(b"/")?.ok_or(ERANGE)?; } buffer.fill(&[0])?.ok_or(ERANGE)?; Ok(()) } } impl Dentry { pub async fn read(&self, buffer: &mut dyn Buffer, offset: usize) -> KResult { let inode = self.get_inode()?; // Safety: Changing mode alone will have no effect on the file's contents match inode.format() { Format::DIR => Err(EISDIR), Format::REG => inode.read(buffer, offset).await, Format::BLK => { let device = BlockDevice::get(inode.devid()?)?; Ok(device.read_some(offset, buffer).await?.allow_partial()) } Format::CHR => { let device = CharDevice::get(inode.devid()?).ok_or(EPERM)?; device.read(buffer) } _ => Err(EINVAL), } } pub async fn write(&self, stream: &mut dyn Stream, offset: WriteOffset<'_>) -> KResult { let inode = self.get_inode()?; // Safety: Changing mode alone will have no effect on the file's contents match inode.format() { Format::DIR => Err(EISDIR), Format::REG => inode.write(stream, offset).await, Format::BLK => Err(EINVAL), // TODO Format::CHR => CharDevice::get(inode.devid()?).ok_or(EPERM)?.write(stream), _ => Err(EINVAL), } } pub async fn readdir(&self, offset: usize, mut for_each_entry: F) -> KResult> where F: FnMut(&[u8], Ino) -> KResult + Send, { let dir = self.get_inode()?; dir.readdir(offset, &mut for_each_entry).await } pub async fn mkdir(&self, perm: Permission) -> KResult<()> { if self.get_inode().is_ok() { Err(EEXIST) } else { let dir = self.parent().get_inode()?; dir.mkdir(self, perm).await } } pub fn statx(&self, stat: &mut StatX, mask: u32) -> KResult<()> { self.get_inode()?.statx(stat, mask) } pub async fn truncate(&self, size: usize) -> KResult<()> { self.get_inode()?.truncate(size).await } pub async fn unlink(self: &Arc) -> KResult<()> { if self.get_inode().is_err() { Err(ENOENT) } else { let dir = self.parent().get_inode()?; dir.unlink(self).await } } pub async fn symlink(self: &Arc, link: &[u8]) -> KResult<()> { if self.get_inode().is_ok() { Err(EEXIST) } else { let dir = self.parent().get_inode()?; dir.symlink(self, link).await } } pub async fn readlink(&self, buffer: &mut dyn Buffer) -> KResult { self.get_inode()?.readlink(buffer).await } pub async fn mknod(&self, mode: Mode, devid: DeviceId) -> KResult<()> { if self.get_inode().is_ok() { Err(EEXIST) } else { let dir = self.parent().get_inode()?; dir.mknod(self, mode, devid).await } } pub async fn chmod(&self, mode: Mode) -> KResult<()> { self.get_inode()?.chmod(mode).await } pub async fn chown(&self, uid: u32, gid: u32) -> KResult<()> { self.get_inode()?.chown(uid, gid).await } pub async fn rename(self: &Arc, new: &Arc, flags: RenameFlags) -> KResult<()> { if Arc::ptr_eq(self, new) { return Ok(()); } let old_parent = self.parent().get_inode()?; let new_parent = new.parent().get_inode()?; // If the two dentries are not in the same filesystem, return EXDEV. if old_parent.sbref().eq(&new_parent.sbref()) { Err(PosixError::EXDEV)?; } let rename_data = RenameData { old_dentry: self, new_dentry: new, new_parent, is_exchange: flags.contains(RenameFlags::RENAME_EXCHANGE), no_replace: flags.contains(RenameFlags::RENAME_NOREPLACE), }; // Delegate to the parent directory's rename implementation old_parent.rename(rename_data).await } } impl DentryKind { fn into_raw(self) -> u8 { unsafe { core::mem::transmute(self) } } fn from_raw(raw: u8) -> Option { unsafe { core::mem::transmute(raw) } } fn as_atomic(me: &UnsafeCell>) -> &AtomicU8 { unsafe { AtomicU8::from_ptr(me.get().cast()) } } fn atomic_acq(me: &UnsafeCell>) -> Option { Self::from_raw(Self::as_atomic(me).load(Ordering::Acquire)) } fn atomic_swap_acqrel(me: &UnsafeCell>, kind: Option) -> Option { Self::from_raw(Self::as_atomic(me).swap(kind.map_or(0, Self::into_raw), Ordering::AcqRel)) } } impl AssociatedInode { fn new() -> Self { Self { inode: UnsafeCell::new(None), kind: UnsafeCell::new(None), } } fn store(&self, inode: InodeUse) { let kind = match inode.format() { Format::REG | Format::BLK | Format::CHR => DentryKind::Regular, Format::DIR => DentryKind::Directory, Format::LNK => DentryKind::Symlink, }; unsafe { // SAFETY: We should be the first and only one to store the inode as // is checked below. All other readers reading non-invalid // kind will see the fully written inode. self.inode.get().write(Some(inode)); } assert_eq!( DentryKind::atomic_swap_acqrel(&self.kind, Some(kind)), None, "Dentry can only be stored once." ); } fn kind(&self) -> Option { DentryKind::atomic_acq(&self.kind) } fn load(&self) -> Option<(DentryKind, &InodeUse)> { self.kind().map(|kind| unsafe { let inode = (&*self.inode.get()) .as_ref() .expect("Dentry with non-invalid kind has no inode"); (kind, inode) }) } } unsafe impl Send for AssociatedInode {} unsafe impl Sync for AssociatedInode {}