浏览代码

feat(syscall): impl renameat

greatbridf 7 月之前
父节点
当前提交
7178806d53

+ 2 - 4
crates/pointers/src/lib.rs

@@ -42,11 +42,9 @@ impl<'a, T: ?Sized> BorrowedArc<'a, T> {
         }
     }
 
-    #[allow(dead_code)]
-    pub fn new(ptr: &'a *const T) -> Self {
-        assert!(!ptr.is_null());
+    pub fn new(ptr: &'a Arc<T>) -> Self {
         Self {
-            arc: ManuallyDrop::new(unsafe { Arc::from_raw(*ptr) }),
+            arc: ManuallyDrop::new(unsafe { core::mem::transmute_copy(ptr) }),
             _phantom: PhantomData,
         }
     }

+ 1 - 0
crates/posix_types/src/lib.rs

@@ -2,6 +2,7 @@
 
 pub mod constants;
 pub mod ctypes;
+pub mod namei;
 pub mod open;
 pub mod result;
 pub mod signal;

+ 11 - 0
crates/posix_types/src/namei.rs

@@ -0,0 +1,11 @@
+use bitflags::bitflags;
+
+bitflags! {
+    #[derive(Debug, Clone, Copy)]
+    pub struct RenameFlags: u32 {
+        /// Do not overwrite existing files
+        const RENAME_NOREPLACE = 0x1;
+        /// Exchange the names of two files
+        const RENAME_EXCHANGE = 0x2;
+    }
+}

+ 2 - 0
crates/posix_types/src/result.rs

@@ -1,5 +1,6 @@
 pub enum PosixError {
     EFAULT = 14,
+    EXDEV = 18,
     EINVAL = 22,
 }
 
@@ -7,6 +8,7 @@ impl From<PosixError> for u32 {
     fn from(error: PosixError) -> Self {
         match error {
             PosixError::EFAULT => 14,
+            PosixError::EXDEV => 18,
             PosixError::EINVAL => 22,
         }
     }

+ 1 - 1
src/fs/fat32.rs

@@ -296,7 +296,7 @@ impl Inode for DirInode {
         let entry = entries.find(|entry| {
             entry
                 .as_ref()
-                .map(|entry| &entry.filename == dentry.name())
+                .map(|entry| &entry.filename == &***dentry.name())
                 .unwrap_or(true)
         });
 

+ 1 - 5
src/fs/procfs.rs

@@ -139,11 +139,7 @@ impl Inode for DirInode {
             .entries
             .access(lock.prove())
             .iter()
-            .find_map(|(name, node)| {
-                name.as_ref()
-                    .eq(dentry.name().as_ref())
-                    .then(|| node.unwrap())
-            }))
+            .find_map(|(name, node)| (name == &***dentry.name()).then(|| node.unwrap())))
     }
 
     fn do_readdir(

+ 242 - 43
src/fs/tmpfs.rs

@@ -1,6 +1,7 @@
 use crate::io::Stream;
-use crate::kernel::constants::{EINVAL, EIO, EISDIR};
+use crate::kernel::constants::{EEXIST, EINVAL, EIO, EISDIR, ENOENT, ENOSYS, ENOTDIR};
 use crate::kernel::timer::Instant;
+use crate::kernel::vfs::inode::RenameData;
 use crate::{
     io::Buffer,
     kernel::constants::{S_IFBLK, S_IFCHR, S_IFDIR, S_IFLNK, S_IFREG},
@@ -17,7 +18,7 @@ use crate::{
 use alloc::sync::{Arc, Weak};
 use core::{ops::ControlFlow, sync::atomic::Ordering};
 use eonix_runtime::task::Task;
-use eonix_sync::{AsProof as _, AsProofMut as _, Locked, ProofMut};
+use eonix_sync::{AsProof as _, AsProofMut as _, Locked, Mutex, ProofMut};
 use itertools::Itertools;
 
 fn acquire(vfs: &Weak<dyn Vfs>) -> KResult<Arc<dyn Vfs>> {
@@ -78,16 +79,53 @@ impl DirectoryInode {
     }
 
     fn link(&self, name: Arc<[u8]>, file: &dyn Inode, dlock: ProofMut<'_, ()>) {
+        let now = Instant::now();
+
         // SAFETY: Only `unlink` will do something based on `nlink` count
         //         No need to synchronize here
         file.nlink.fetch_add(1, Ordering::Relaxed);
+        *self.ctime.lock() = now;
 
         // SAFETY: `rwsem` has done the synchronization
         self.size.fetch_add(1, Ordering::Relaxed);
-        *self.mtime.lock() = Instant::now();
+        *self.mtime.lock() = now;
 
         self.entries.access_mut(dlock).push((name, file.ino));
     }
+
+    fn do_unlink(
+        &self,
+        file: &Arc<dyn Inode>,
+        filename: &[u8],
+        entries: &mut Vec<(Arc<[u8]>, Ino)>,
+        now: Instant,
+        decrease_size: bool,
+        _dir_lock: ProofMut<()>,
+        _file_lock: ProofMut<()>,
+    ) -> KResult<()> {
+        // SAFETY: `file_lock` has done the synchronization
+        if file.mode.load(Ordering::Relaxed) & S_IFDIR != 0 {
+            return Err(EISDIR);
+        }
+
+        entries.retain(|(name, ino)| *ino != file.ino || name.as_ref() != filename);
+
+        if decrease_size {
+            // SAFETY: `dir_lock` has done the synchronization
+            self.size.fetch_sub(1, Ordering::Relaxed);
+        }
+
+        *self.mtime.lock() = now;
+
+        // The last reference to the inode is held by some dentry
+        // and will be released when the dentry is released
+
+        // SAFETY: `file_lock` has done the synchronization
+        file.nlink.fetch_sub(1, Ordering::Relaxed);
+        *file.ctime.lock() = now;
+
+        Ok(())
+    }
 }
 
 impl Inode for DirectoryInode {
@@ -116,7 +154,7 @@ impl Inode for DirectoryInode {
         let ino = vfs.assign_ino();
         let file = FileInode::new(ino, self.vfs.clone(), mode);
 
-        self.link(at.name().clone(), file.as_ref(), rwsem.prove_mut());
+        self.link(at.get_name(), file.as_ref(), rwsem.prove_mut());
         at.save_reg(file)
     }
 
@@ -138,7 +176,7 @@ impl Inode for DirectoryInode {
             dev,
         );
 
-        self.link(at.name().clone(), file.as_ref(), rwsem.prove_mut());
+        self.link(at.get_name(), file.as_ref(), rwsem.prove_mut());
         at.save_reg(file)
     }
 
@@ -151,7 +189,7 @@ impl Inode for DirectoryInode {
         let ino = vfs.assign_ino();
         let file = SymlinkInode::new(ino, self.vfs.clone(), target.into());
 
-        self.link(at.name().clone(), file.as_ref(), rwsem.prove_mut());
+        self.link(at.get_name(), file.as_ref(), rwsem.prove_mut());
         at.save_symlink(file)
     }
 
@@ -164,51 +202,32 @@ impl Inode for DirectoryInode {
         let ino = vfs.assign_ino();
         let newdir = DirectoryInode::new(ino, self.vfs.clone(), mode);
 
-        self.link(at.name().clone(), newdir.as_ref(), rwsem.prove_mut());
+        self.link(at.get_name(), newdir.as_ref(), rwsem.prove_mut());
         at.save_dir(newdir)
     }
 
     fn unlink(&self, at: &Arc<Dentry>) -> KResult<()> {
         let _vfs = acquire(&self.vfs)?;
 
-        let dlock = Task::block_on(self.rwsem.write());
+        let dir_lock = Task::block_on(self.rwsem.write());
 
         let file = at.get_inode()?;
-        let _flock = file.rwsem.write();
-
-        // SAFETY: `flock` has done the synchronization
-        if file.mode.load(Ordering::Relaxed) & S_IFDIR != 0 {
-            return Err(EISDIR);
-        }
-
-        let entries = self.entries.access_mut(dlock.prove_mut());
-        entries.retain(|(_, ino)| *ino != file.ino);
-
-        assert_eq!(
-            entries.len() as u64,
-            // SAFETY: `dlock` has done the synchronization
-            self.size.fetch_sub(1, Ordering::Relaxed) - 1
-        );
-
-        *self.mtime.lock() = Instant::now();
-
-        // SAFETY: `flock` has done the synchronization
-        let file_nlink = file.nlink.fetch_sub(1, Ordering::Relaxed) - 1;
-
-        if file_nlink == 0 {
-            // Remove the file inode from the inode cache
-            // The last reference to the inode is held by some dentry
-            // and will be released when the dentry is released
-            //
-            // TODO: Should we use some inode cache in tmpfs?
-            //
-            // vfs.icache.lock().retain(|ino, _| *ino != file.ino);
-        }
-
-        // Postpone the invalidation of the dentry and inode until the
-        // last reference to the dentry is released
-        //
-        // But we can remove it from the dentry cache immediately
+        let filename = at.get_name();
+        let file_lock = Task::block_on(file.rwsem.write());
+
+        let entries = self.entries.access_mut(dir_lock.prove_mut());
+
+        self.do_unlink(
+            &file,
+            &filename,
+            entries,
+            Instant::now(),
+            true,
+            dir_lock.prove_mut(),
+            file_lock.prove_mut(),
+        )?;
+
+        // Remove the dentry from the dentry cache immediately
         // so later lookup will fail with ENOENT
         dcache::d_remove(at);
 
@@ -227,6 +246,184 @@ impl Inode for DirectoryInode {
 
         Ok(())
     }
+
+    fn rename(&self, rename_data: RenameData) -> KResult<()> {
+        let RenameData {
+            old_dentry,
+            new_dentry,
+            new_parent,
+            is_exchange,
+            no_replace,
+            vfs,
+        } = rename_data;
+
+        if is_exchange {
+            println_warn!("TmpFs does not support exchange rename for now");
+            return Err(ENOSYS);
+        }
+
+        let vfs = vfs
+            .as_any()
+            .downcast_ref::<TmpFs>()
+            .expect("vfs must be a TmpFs");
+
+        let _rename_lock = Task::block_on(vfs.rename_lock.lock());
+
+        let old_file = old_dentry.get_inode()?;
+        let new_file = new_dentry.get_inode();
+
+        if no_replace && new_file.is_ok() {
+            return Err(EEXIST);
+        }
+
+        let same_parent = Arc::as_ptr(&new_parent) == &raw const *self;
+        if same_parent {
+            // Same directory rename
+            // Remove from old location and add to new location
+            let parent_lock = Task::block_on(self.rwsem.write());
+            let entries = self.entries.access_mut(parent_lock.prove_mut());
+
+            fn rename_old(
+                old_entry: &mut (Arc<[u8]>, Ino),
+                old_file: &Arc<dyn Inode + 'static>,
+                new_dentry: &Arc<Dentry>,
+                now: Instant,
+            ) {
+                let (name, _) = old_entry;
+                *name = new_dentry.get_name();
+                *old_file.ctime.lock() = now;
+            }
+
+            let old_ino = old_file.ino;
+            let new_ino = new_file.as_ref().ok().map(|f| f.ino);
+            let old_name = old_dentry.get_name();
+            let new_name = new_dentry.get_name();
+
+            // Find the old and new entries in the directory after we've locked the directory.
+            let indices =
+                entries
+                    .iter()
+                    .enumerate()
+                    .fold([None, None], |[old, new], (idx, (name, ino))| {
+                        if Some(*ino) == new_ino && *name == new_name {
+                            [old, Some(idx)]
+                        } else if *ino == old_ino && *name == old_name {
+                            [Some(idx), new]
+                        } else {
+                            [old, new]
+                        }
+                    });
+
+            let (old_entry_idx, new_entry_idx) = match indices {
+                [None, ..] => return Err(ENOENT),
+                [Some(old_idx), new_idx] => (old_idx, new_idx),
+            };
+
+            let now = Instant::now();
+
+            if let Some(new_idx) = new_entry_idx {
+                // Replace existing file (i.e. rename the old and unlink the new)
+                let new_file = new_file.unwrap();
+                let _new_file_lock = Task::block_on(new_file.rwsem.write());
+
+                // SAFETY: `new_file_lock` has done the synchronization
+                if new_file.mode.load(Ordering::Relaxed) & S_IFDIR != 0 {
+                    return Err(EISDIR);
+                } else {
+                    if old_file.mode.load(Ordering::Relaxed) & S_IFDIR != 0 {
+                        return Err(ENOTDIR);
+                    }
+                }
+
+                entries.remove(new_idx);
+
+                // SAFETY: `parent_lock` has done the synchronization
+                self.size.fetch_sub(1, Ordering::Relaxed);
+
+                // The last reference to the inode is held by some dentry
+                // and will be released when the dentry is released
+
+                // SAFETY: `new_file_lock` has done the synchronization
+                new_file.nlink.fetch_sub(1, Ordering::Relaxed);
+                *new_file.ctime.lock() = now;
+            }
+
+            rename_old(&mut entries[old_entry_idx], &old_file, new_dentry, now);
+            *self.mtime.lock() = now;
+        } else {
+            // Cross-directory rename - handle similar to same directory case
+
+            // Get new parent directory
+            let new_parent_inode = new_dentry.parent().get_inode()?;
+            assert!(new_parent_inode.is_dir());
+            let new_parent = (new_parent_inode.as_ref() as &dyn Any)
+                .downcast_ref::<DirectoryInode>()
+                .expect("new parent must be a DirectoryInode");
+
+            let old_parent_lock = Task::block_on(self.rwsem.write());
+            let new_parent_lock = Task::block_on(new_parent_inode.rwsem.write());
+
+            let old_ino = old_file.ino;
+            let new_ino = new_file.as_ref().ok().map(|f| f.ino);
+            let old_name = old_dentry.get_name();
+            let new_name = new_dentry.get_name();
+
+            // Find the old entry in the old directory
+            let old_entries = self.entries.access_mut(old_parent_lock.prove_mut());
+            let old_pos = old_entries
+                .iter()
+                .position(|(name, ino)| *ino == old_ino && *name == old_name)
+                .ok_or(ENOENT)?;
+
+            // Find the new entry in the new directory (if it exists)
+            let new_entries = new_parent.entries.access_mut(new_parent_lock.prove_mut());
+            let has_new = new_entries
+                .iter()
+                .position(|(name, ino)| Some(*ino) == new_ino && *name == new_name)
+                .is_some();
+
+            let now = Instant::now();
+
+            if has_new {
+                // Replace existing file (i.e. move the old and unlink the new)
+                let new_file = new_file.unwrap();
+                let new_file_lock = Task::block_on(new_file.rwsem.write());
+
+                if old_file.mode.load(Ordering::Relaxed) & S_IFDIR != 0
+                    && new_file.mode.load(Ordering::Relaxed) & S_IFDIR == 0
+                {
+                    return Err(ENOTDIR);
+                }
+
+                // Unlink the old file that was replaced
+                new_parent.do_unlink(
+                    &new_file,
+                    &new_name,
+                    new_entries,
+                    now,
+                    false,
+                    new_parent_lock.prove_mut(),
+                    new_file_lock.prove_mut(),
+                )?;
+            } else {
+                new_parent.size.fetch_add(1, Ordering::Relaxed);
+            }
+
+            // Remove from old directory
+            old_entries.remove(old_pos);
+
+            // Add new entry
+            new_entries.push((new_name, old_ino));
+
+            self.size.fetch_sub(1, Ordering::Relaxed);
+            *self.mtime.lock() = now;
+            *old_file.ctime.lock() = now;
+        }
+
+        Task::block_on(dcache::d_exchange(old_dentry, new_dentry));
+
+        Ok(())
+    }
 }
 
 define_struct_inode! {
@@ -365,6 +562,7 @@ impl_any!(TmpFs);
 struct TmpFs {
     next_ino: AtomicIno,
     readonly: bool,
+    rename_lock: Mutex<()>,
 }
 
 impl Vfs for TmpFs {
@@ -390,6 +588,7 @@ impl TmpFs {
         let tmpfs = Arc::new(Self {
             next_ino: AtomicIno::new(1),
             readonly,
+            rename_lock: Mutex::new(()),
         });
 
         let weak = Arc::downgrade(&tmpfs);

+ 35 - 0
src/kernel/syscall/file_rw.rs

@@ -23,6 +23,7 @@ use crate::{
 use alloc::sync::Arc;
 use eonix_runtime::task::Task;
 use posix_types::ctypes::{Long, PtrT};
+use posix_types::namei::RenameFlags;
 use posix_types::open::{AtFlags, OpenFlags};
 use posix_types::signal::SigSet;
 use posix_types::stat::{StatX, TimeSpec};
@@ -545,4 +546,38 @@ fn utimensat(
     Ok(())
 }
 
+#[eonix_macros::define_syscall(SYS_RENAMEAT2)]
+fn renameat2(
+    old_dirfd: FD,
+    old_pathname: *const u8,
+    new_dirfd: FD,
+    new_pathname: *const u8,
+    flags: u32,
+) -> KResult<()> {
+    let flags = RenameFlags::from_bits(flags).ok_or(EINVAL)?;
+
+    // The two flags RENAME_NOREPLACE and RENAME_EXCHANGE are mutually exclusive.
+    if flags.contains(RenameFlags::RENAME_NOREPLACE | RenameFlags::RENAME_EXCHANGE) {
+        Err(EINVAL)?;
+    }
+
+    let old_dentry = dentry_from(thread, old_dirfd, old_pathname, false)?;
+    let new_dentry = dentry_from(thread, new_dirfd, new_pathname, false)?;
+
+    old_dentry.rename(&new_dentry, flags)
+}
+
+#[cfg(target_arch = "x86_64")]
+#[eonix_macros::define_syscall(SYS_RENAME)]
+fn rename(old_pathname: *const u8, new_pathname: *const u8) -> KResult<()> {
+    sys_renameat2(
+        thread,
+        FD::AT_FDCWD,
+        old_pathname,
+        FD::AT_FDCWD,
+        new_pathname,
+        0,
+    )
+}
+
 pub fn keep_alive() {}

+ 3 - 6
src/kernel/syscall/procops.rs

@@ -80,11 +80,8 @@ fn getcwd(buffer: *mut u8, bufsize: usize) -> KResult<usize> {
     let mut user_buffer = UserBuffer::new(buffer, bufsize)?;
     let mut buffer = PageBuffer::new();
 
-    thread
-        .fs_context
-        .cwd
-        .lock()
-        .get_path(&thread.fs_context, &mut buffer)?;
+    let cwd = thread.fs_context.cwd.lock().clone();
+    cwd.get_path(&thread.fs_context, &mut buffer)?;
 
     user_buffer.fill(buffer.data())?.ok_or(ERANGE)?;
 
@@ -178,7 +175,7 @@ fn execve(exec: *const u8, argv: *const PtrT, envp: *const PtrT) -> KResult<Sysc
 
     thread.files.on_exec();
     thread.signal_list.clear_non_ignore();
-    thread.set_name(dentry.name().clone());
+    thread.set_name(dentry.get_name());
 
     let mut trap_ctx = thread.trap_ctx.borrow();
     trap_ctx.set_program_counter(load_info.entry_ip.addr());

+ 131 - 61
src/kernel/vfs/dentry.rs

@@ -1,7 +1,7 @@
 pub mod dcache;
 
 use super::{
-    inode::{Ino, Inode, Mode, WriteOffset},
+    inode::{Ino, Inode, Mode, RenameData, WriteOffset},
     s_isblk, s_ischr, s_isdir, s_isreg, DevId, FsContext,
 };
 use crate::{
@@ -10,21 +10,22 @@ use crate::{
     kernel::{block::BlockDevice, CharDevice},
     path::{Path, PathComponent},
     prelude::*,
-    rcu::{RCUNode, RCUPointer},
+    rcu::{RCUNode, RCUPointer, RCUReadGuard},
 };
 use crate::{
     io::Stream,
-    kernel::constants::{EEXIST, EINVAL, EISDIR, ELOOP, ENOENT, ENOTDIR, EPERM, ERANGE},
+    kernel::constants::{EEXIST, EINVAL, EIO, EISDIR, ELOOP, ENOENT, ENOTDIR, EPERM, ERANGE},
 };
-use alloc::sync::Arc;
+use alloc::sync::{Arc, Weak};
 use core::{
     fmt,
     hash::{BuildHasher, BuildHasherDefault, Hasher},
     ops::ControlFlow,
-    sync::atomic::{AtomicPtr, Ordering},
+    sync::atomic::{AtomicPtr, AtomicU64, Ordering},
 };
 use eonix_sync::LazyLock;
-use posix_types::{open::OpenFlags, stat::StatX};
+use pointers::BorrowedArc;
+use posix_types::{namei::RenameFlags, open::OpenFlags, result::PosixError, stat::StatX};
 
 struct DentryData {
     inode: Arc<dyn Inode>,
@@ -39,9 +40,9 @@ struct DentryData {
 /// the last reference is dropped.
 pub struct Dentry {
     // Const after insertion into dcache
-    parent: Arc<Dentry>,
-    name: Arc<[u8]>,
-    hash: u64,
+    parent: RCUPointer<Dentry>,
+    name: RCUPointer<Arc<[u8]>>,
+    hash: AtomicU64,
 
     // Used by the dentry cache
     prev: AtomicPtr<Dentry>,
@@ -51,27 +52,29 @@ pub struct Dentry {
     data: RCUPointer<DentryData>,
 }
 
-pub(super) static DROOT: LazyLock<Arc<Dentry>> = LazyLock::new(|| unsafe {
-    let mut dentry = Arc::new_uninit();
-    let parent = dentry.clone().assume_init();
-
-    Arc::get_mut_unchecked(&mut dentry).write(Dentry {
-        parent,
-        name: Arc::from("[root]".as_ref()),
-        hash: 0,
+pub(super) static DROOT: LazyLock<Arc<Dentry>> = 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(),
         data: RCUPointer::empty(),
     });
 
-    dentry.assume_init()
+    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))
-            .field("parent", &String::from_utf8_lossy(&self.parent.name))
+            .field("name", &String::from_utf8_lossy(&self.name()))
             .finish()
     }
 }
@@ -93,14 +96,24 @@ impl RCUNode<Dentry> for Dentry {
 }
 
 impl Dentry {
-    fn rehash(self: &Arc<Self>) -> u64 {
+    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<KernelHasher> = Default::default();
         let mut hasher = builder.build_hasher();
 
         hasher.write_usize(self.parent_addr() as usize);
-        hasher.write(self.name.as_ref());
+        hasher.write(&self.name());
+        let hash = hasher.finish();
 
-        hasher.finish()
+        self.hash.store(hash, Ordering::Relaxed);
     }
 
     fn find(self: &Arc<Self>, name: &[u8]) -> KResult<Arc<Self>> {
@@ -113,15 +126,25 @@ impl Dentry {
 
         match name {
             b"." => Ok(self.clone()),
-            b".." => Ok(self.parent.clone()),
+            b".." => Ok(self.parent().clone()),
             _ => {
                 let dentry = Dentry::create(self.clone(), name);
-                Ok(dcache::d_find_fast(&dentry).unwrap_or_else(|| {
-                    dcache::d_try_revalidate(&dentry);
-                    dcache::d_add(&dentry);
 
-                    dentry
-                }))
+                if let Some(found) = dcache::d_find_fast(&dentry) {
+                    unsafe {
+                        // SAFETY: This is safe because the dentry is never shared with
+                        //         others so we can drop them safely.
+                        let _ = dentry.name.swap(None);
+                        let _ = dentry.parent.swap(None);
+                    }
+
+                    return Ok(found);
+                }
+
+                dcache::d_try_revalidate(&dentry);
+                dcache::d_add(dentry.clone());
+
+                Ok(dentry)
             }
         }
     }
@@ -129,40 +152,44 @@ impl Dentry {
 
 impl Dentry {
     pub fn create(parent: Arc<Dentry>, name: &[u8]) -> Arc<Self> {
-        let mut val = Arc::new(Self {
-            parent,
-            name: Arc::from(name),
-            hash: 0,
+        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(),
             data: RCUPointer::empty(),
         });
-        let hash = val.rehash();
-        let val_mut = Arc::get_mut(&mut val).unwrap();
-        val_mut.hash = hash;
 
+        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: &Arc<Self>, other: &Arc<Self>) -> bool {
-        self.hash == other.hash
+    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
+            && &***self.name() == &***other.name()
     }
 
-    pub fn name(&self) -> &Arc<[u8]> {
-        &self.name
+    pub fn name(&self) -> RCUReadGuard<BorrowedArc<Arc<[u8]>>> {
+        self.name.load().expect("Dentry has no name")
     }
 
-    pub fn parent(&self) -> &Arc<Self> {
-        &self.parent
+    pub fn get_name(&self) -> Arc<[u8]> {
+        (***self.name()).clone()
+    }
+
+    pub fn parent<'a>(&self) -> RCUReadGuard<'a, BorrowedArc<Dentry>> {
+        self.parent.load().expect("Dentry has no parent")
     }
 
     pub fn parent_addr(&self) -> *const Self {
-        Arc::as_ptr(&self.parent)
+        self.parent
+            .load()
+            .map_or(core::ptr::null(), |parent| Arc::as_ptr(&parent))
     }
 
     fn save_data(&self, inode: Arc<dyn Inode>, flags: u64) -> KResult<()> {
@@ -251,7 +278,8 @@ impl Dentry {
                 data.inode.readlink(&mut buffer)?;
                 let path = Path::new(buffer.data())?;
 
-                let dentry = Self::open_recursive(context, &dentry.parent, path, true, nrecur + 1)?;
+                let dentry =
+                    Self::open_recursive(context, &dentry.parent(), path, true, nrecur + 1)?;
 
                 Self::resolve_directory(context, dentry, nrecur + 1)
             }
@@ -291,7 +319,8 @@ impl Dentry {
                 PathComponent::TrailingEmpty | PathComponent::Current => {} // pass
                 PathComponent::Parent => {
                     if !cwd.hash_eq(&context.fsroot) {
-                        cwd = Self::resolve_directory(context, cwd.parent.clone(), nrecur)?;
+                        let parent = cwd.parent().clone();
+                        cwd = Self::resolve_directory(context, parent, nrecur)?;
                     }
                     continue;
                 }
@@ -314,7 +343,8 @@ impl Dentry {
                     data.inode.readlink(&mut buffer)?;
                     let path = Path::new(buffer.data())?;
 
-                    cwd = Self::open_recursive(context, &cwd.parent, path, true, nrecur + 1)?;
+                    let parent = cwd.parent().clone();
+                    cwd = Self::open_recursive(context, &parent, path, true, nrecur + 1)?;
                 }
             }
         }
@@ -341,19 +371,26 @@ impl Dentry {
         context: &FsContext,
         buffer: &mut dyn Buffer,
     ) -> KResult<()> {
-        let mut dentry = self;
-        let root = &context.fsroot;
+        let locked_parent = self.parent();
 
-        let mut path = vec![];
+        let path = {
+            let mut path = vec![];
 
-        while Arc::as_ptr(dentry) != Arc::as_ptr(root) {
-            if path.len() > 32 {
-                return Err(ELOOP);
+            let mut parent = locked_parent.borrow();
+            let mut dentry = BorrowedArc::new(self);
+
+            while Arc::as_ptr(&dentry) != Arc::as_ptr(&context.fsroot) {
+                if path.len() > 32 {
+                    return Err(ELOOP);
+                }
+
+                path.push(dentry.name().clone());
+                dentry = parent;
+                parent = dentry.parent.load_protected(&locked_parent).unwrap();
             }
 
-            path.push(dentry.name().clone());
-            dentry = dentry.parent();
-        }
+            path
+        };
 
         buffer.fill(b"/")?.ok_or(ERANGE)?;
         for item in path.iter().rev().map(|name| name.as_ref()) {
@@ -403,14 +440,16 @@ impl Dentry {
     where
         F: FnMut(&[u8], Ino) -> KResult<ControlFlow<(), ()>>,
     {
-        self.get_inode()?.do_readdir(offset, &mut callback)
+        let dir = self.get_inode()?;
+        dir.do_readdir(offset, &mut callback)
     }
 
     pub fn mkdir(&self, mode: Mode) -> KResult<()> {
         if self.get_inode().is_ok() {
             Err(EEXIST)
         } else {
-            self.parent.get_inode().unwrap().mkdir(self, mode)
+            let dir = self.parent().get_inode()?;
+            dir.mkdir(self, mode)
         }
     }
 
@@ -426,7 +465,8 @@ impl Dentry {
         if self.get_inode().is_err() {
             Err(ENOENT)
         } else {
-            self.parent.get_inode().unwrap().unlink(self)
+            let dir = self.parent().get_inode()?;
+            dir.unlink(self)
         }
     }
 
@@ -434,7 +474,8 @@ impl Dentry {
         if self.get_inode().is_ok() {
             Err(EEXIST)
         } else {
-            self.parent.get_inode().unwrap().symlink(self, link)
+            let dir = self.parent().get_inode()?;
+            dir.symlink(self, link)
         }
     }
 
@@ -446,7 +487,8 @@ impl Dentry {
         if self.get_inode().is_ok() {
             Err(EEXIST)
         } else {
-            self.parent.get_inode().unwrap().mknod(self, mode, devid)
+            let dir = self.parent().get_inode()?;
+            dir.mknod(self, mode, devid)
         }
     }
 
@@ -457,4 +499,32 @@ impl Dentry {
     pub fn chown(&self, uid: u32, gid: u32) -> KResult<()> {
         self.get_inode()?.chown(uid, gid)
     }
+
+    pub fn rename(self: &Arc<Self>, new: &Arc<Self>, 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 !Weak::ptr_eq(&old_parent.vfs, &new_parent.vfs) {
+            Err(PosixError::EXDEV)?;
+        }
+
+        let vfs = old_parent.vfs.upgrade().ok_or(EIO)?;
+
+        let rename_data = RenameData {
+            old_dentry: self,
+            new_dentry: new,
+            new_parent,
+            vfs,
+            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)
+    }
 }

+ 39 - 10
src/kernel/vfs/dentry/dcache.rs

@@ -1,5 +1,6 @@
 use super::{Dentry, Inode};
 use crate::kernel::constants::ENOENT;
+use crate::rcu::RCUPointer;
 use crate::{
     kernel::vfs::{s_isdir, s_islnk},
     prelude::*,
@@ -7,28 +8,32 @@ use crate::{
 };
 use alloc::sync::Arc;
 use core::sync::atomic::Ordering;
+use eonix_runtime::task::Task;
+use eonix_sync::Mutex;
 
 const DCACHE_HASH_BITS: u32 = 8;
 
 static DCACHE: [RCUList<Dentry>; 1 << DCACHE_HASH_BITS] =
     [const { RCUList::new() }; 1 << DCACHE_HASH_BITS];
 
-pub fn d_hinted(hash: u64) -> &'static RCUList<Dentry> {
-    let hash = hash as usize & ((1 << DCACHE_HASH_BITS) - 1);
+static D_EXCHANGE_LOCK: Mutex<()> = Mutex::new(());
+
+pub fn d_hinted(dentry: &Dentry) -> &'static RCUList<Dentry> {
+    let hash = dentry.hash.load(Ordering::Relaxed) as usize & ((1 << DCACHE_HASH_BITS) - 1);
     &DCACHE[hash]
 }
 
-pub fn d_iter_for(hash: u64) -> RCUIterator<'static, Dentry> {
-    d_hinted(hash).iter()
+pub fn d_iter_for(dentry: &Dentry) -> RCUIterator<'static, Dentry> {
+    d_hinted(dentry).iter()
 }
 
 /// Add the dentry to the dcache
-pub fn d_add(dentry: &Arc<Dentry>) {
-    d_hinted(dentry.hash).insert(dentry.clone());
+pub fn d_add(dentry: Arc<Dentry>) {
+    d_hinted(&dentry).insert(dentry);
 }
 
-pub fn d_find_fast(dentry: &Arc<Dentry>) -> Option<Arc<Dentry>> {
-    d_iter_for(dentry.rehash())
+pub fn d_find_fast(dentry: &Dentry) -> Option<Arc<Dentry>> {
+    d_iter_for(dentry)
         .find(|cur| cur.hash_eq(dentry))
         .map(|dentry| dentry.clone())
 }
@@ -37,6 +42,8 @@ pub fn d_find_fast(dentry: &Arc<Dentry>) -> Option<Arc<Dentry>> {
 ///
 /// Silently fail without any side effects
 pub fn d_try_revalidate(dentry: &Arc<Dentry>) {
+    let _lock = Task::block_on(D_EXCHANGE_LOCK.lock());
+
     (|| -> KResult<()> {
         let parent = dentry.parent().get_inode()?;
         let inode = parent.lookup(dentry)?.ok_or(ENOENT)?;
@@ -59,10 +66,32 @@ pub fn d_save(dentry: &Arc<Dentry>, inode: Arc<dyn Inode>) -> KResult<()> {
 
 /// Replace the old dentry with the new one in the dcache
 pub fn d_replace(old: &Arc<Dentry>, new: Arc<Dentry>) {
-    d_hinted(old.hash).replace(old, new);
+    d_hinted(old).replace(old, new);
 }
 
 /// Remove the dentry from the dcache so that later d_find_fast will fail
 pub fn d_remove(dentry: &Arc<Dentry>) {
-    d_hinted(dentry.hash).remove(&dentry);
+    d_hinted(dentry).remove(&dentry);
+}
+
+pub async fn d_exchange(old: &Arc<Dentry>, new: &Arc<Dentry>) {
+    if Arc::ptr_eq(old, new) {
+        return;
+    }
+
+    let _lock = D_EXCHANGE_LOCK.lock().await;
+
+    d_remove(old);
+    d_remove(new);
+
+    unsafe {
+        RCUPointer::exchange(&old.parent, &new.parent);
+        RCUPointer::exchange(&old.name, &new.name);
+    }
+
+    old.rehash();
+    new.rehash();
+
+    d_add(old.clone());
+    d_add(new.clone());
 }

+ 15 - 1
src/kernel/vfs/inode.rs

@@ -15,6 +15,7 @@ use core::{
 };
 use eonix_runtime::task::Task;
 use eonix_sync::RwLock;
+use posix_types::namei::RenameFlags;
 use posix_types::stat::StatX;
 
 pub type Ino = u64;
@@ -83,8 +84,17 @@ pub enum WriteOffset<'end> {
     End(&'end mut usize),
 }
 
+pub struct RenameData<'a, 'b> {
+    pub old_dentry: &'a Arc<Dentry>,
+    pub new_dentry: &'b Arc<Dentry>,
+    pub new_parent: Arc<dyn Inode>,
+    pub vfs: Arc<dyn Vfs>,
+    pub is_exchange: bool,
+    pub no_replace: bool,
+}
+
 #[allow(unused_variables)]
-pub trait Inode: Send + Sync + InodeInner {
+pub trait Inode: Send + Sync + InodeInner + Any {
     fn is_dir(&self) -> bool {
         self.mode.load(Ordering::SeqCst) & S_IFDIR != 0
     }
@@ -133,6 +143,10 @@ pub trait Inode: Send + Sync + InodeInner {
         Err(if self.is_dir() { EISDIR } else { EPERM })
     }
 
+    fn rename(&self, rename_data: RenameData) -> KResult<()> {
+        Err(if !self.is_dir() { ENOTDIR } else { EPERM })
+    }
+
     fn do_readdir(
         &self,
         offset: usize,

+ 2 - 2
src/kernel/vfs/mount.rs

@@ -36,7 +36,7 @@ pub struct Mount {
 
 impl Mount {
     pub fn new(mp: &Dentry, vfs: Arc<dyn Vfs>, root_inode: Arc<dyn Inode>) -> KResult<Self> {
-        let root_dentry = Dentry::create(mp.parent().clone(), mp.name());
+        let root_dentry = Dentry::create(mp.parent().clone(), &mp.get_name());
         root_dentry.save_dir(root_inode)?;
 
         Ok(Self {
@@ -170,7 +170,7 @@ impl Dentry {
 
             let root_dentry = mount.root().clone();
 
-            dcache::d_add(&root_dentry);
+            dcache::d_add(root_dentry.clone());
 
             MOUNTS.lock().push((
                 DROOT.clone(),

+ 35 - 3
src/rcu.rs

@@ -17,14 +17,22 @@ pub struct RCUReadGuard<'data, T: 'data> {
 
 static GLOBAL_RCU_SEM: RwLock<()> = RwLock::new(());
 
-impl<'data, T: 'data> RCUReadGuard<'data, T> {
-    fn lock(value: T) -> Self {
+impl<'data, T> RCUReadGuard<'data, BorrowedArc<'data, T>> {
+    fn lock(value: BorrowedArc<'data, T>) -> Self {
         Self {
             value,
             _guard: Task::block_on(GLOBAL_RCU_SEM.read()),
             _phantom: PhantomData,
         }
     }
+
+    pub fn borrow(&self) -> BorrowedArc<'data, T> {
+        unsafe {
+            BorrowedArc::from_raw(NonNull::new_unchecked(
+                &raw const *self.value.borrow() as *mut T
+            ))
+        }
+    }
 }
 
 impl<'data, T: 'data> Deref for RCUReadGuard<'data, T> {
@@ -194,15 +202,26 @@ impl<T: core::fmt::Debug> core::fmt::Debug for RCUPointer<T> {
 }
 
 impl<T> RCUPointer<T> {
-    pub fn empty() -> Self {
+    pub const fn empty() -> Self {
         Self(AtomicPtr::new(core::ptr::null_mut()))
     }
 
+    pub fn new(value: Arc<T>) -> Self {
+        Self(AtomicPtr::new(Arc::into_raw(value) as *mut T))
+    }
+
     pub fn load<'lt>(&self) -> Option<RCUReadGuard<'lt, BorrowedArc<'lt, T>>> {
         NonNull::new(self.0.load(Ordering::Acquire))
             .map(|p| RCUReadGuard::lock(unsafe { BorrowedArc::from_raw(p) }))
     }
 
+    pub fn load_protected<'a, U: 'a>(
+        &self,
+        _guard: &RCUReadGuard<'a, U>,
+    ) -> Option<BorrowedArc<'a, T>> {
+        NonNull::new(self.0.load(Ordering::Acquire)).map(|p| unsafe { BorrowedArc::from_raw(p) })
+    }
+
     /// # Safety
     /// Caller must ensure no writers are updating the pointer.
     pub unsafe fn load_locked<'lt>(&self) -> Option<BorrowedArc<'lt, T>> {
@@ -224,6 +243,19 @@ impl<T> RCUPointer<T> {
             Some(unsafe { Arc::from_raw(old) })
         }
     }
+
+    /// Exchange the value of the pointers.
+    ///
+    /// # Safety
+    /// Presence of readers is acceptable.
+    /// But the caller must ensure that we are the only one **altering** the pointers.
+    pub unsafe fn exchange(old: &Self, new: &Self) {
+        let old_value = old.0.load(Ordering::Acquire);
+
+        let new_value = new.0.swap(old_value, Ordering::AcqRel);
+
+        old.0.store(new_value, Ordering::Release);
+    }
 }
 
 impl<T> Drop for RCUPointer<T> {