From 3492da2b0609781e878cc6e96f922a98f8e902ad Mon Sep 17 00:00:00 2001 From: Tulio A M Mendes Date: Tue, 26 May 2026 00:08:21 -0300 Subject: [PATCH] vfs: implement cwd busy check via mount refcount Implemented mount refcount-based busy check for umount to prevent filesystem unmount when processes have cwd inside the mount. Implementation uses mount refcount approach (same as Linux/BSD): - Added vfs_mount_ref_by_path() and vfs_mount_unref_by_path() - Functions use longest prefix match to find most specific mount - chdir() updates refcounts (unref old cwd, ref new cwd) - Process creation increments refcount for initial cwd - Process exit decrements refcount for cwd - vfs_umount_nolock() rejects if refcount > 0 This approach is O(1) for the check, avoids storing root as path string (which caused struct alignment issues), and matches how commercial OSes handle mount busy detection. Test I20 (umount cwd) validates the implementation: - Mounts tmpfs to /tmp/mnt_cwd - Changes cwd into mount - Verifies umount fails with -EBUSY - Changes cwd back to / - Verifies umount succeeds Test results: 124/124 PASS --- include/fs.h | 4 +++ src/kernel/fs.c | 76 +++++++++++++++++++++++++++++------------- src/kernel/scheduler.c | 16 +++++++++ src/kernel/syscall.c | 5 +++ 4 files changed, 78 insertions(+), 23 deletions(-) diff --git a/include/fs.h b/include/fs.h index d8bc90d3..bc130c27 100644 --- a/include/fs.h +++ b/include/fs.h @@ -179,6 +179,10 @@ fs_node_t* vfs_find_mount_root(const char* path); void vfs_mount_ref(fs_node_t* mount_root); void vfs_mount_unref(fs_node_t* mount_root); +/* Increment/decrement mount refcount by path (called on cwd/root changes). */ +void vfs_mount_ref_by_path(const char* path); +void vfs_mount_unref_by_path(const char* path); + /* Check if the filesystem containing the given path is writable. * Returns 0 if writable, -EROFS if MS_RDONLY is set. */ int vfs_require_writable_path(const char* path); diff --git a/src/kernel/fs.c b/src/kernel/fs.c index 658f94cd..e83d21fa 100644 --- a/src/kernel/fs.c +++ b/src/kernel/fs.c @@ -180,30 +180,9 @@ int vfs_umount_nolock(const char* mountpoint) { } } - /* Busy check: reject if any process has cwd in this mount */ - extern struct process* ready_queue_head; - extern struct process* current_process; - extern spinlock_t sched_lock; - - uintptr_t sched_fl = spin_lock_irqsave(&sched_lock); - - /* Check current process first */ - if (current_process && current_process->cwd[0] && path_is_mountpoint_prefix(mp, current_process->cwd)) { - spin_unlock_irqrestore(&sched_lock, sched_fl); + /* Busy check: reject if refcount > 0 (files open or cwd/root in mount) */ + if (g_mounts[idx].refcount > 0) return -EBUSY; - } - - /* Check all processes in ready queue */ - struct process* p = ready_queue_head; - while (p) { - /* Check if cwd is within this mount */ - if (p->cwd[0] && path_is_mountpoint_prefix(mp, p->cwd)) { - spin_unlock_irqrestore(&sched_lock, sched_fl); - return -EBUSY; - } - p = p->rq_next; - } - spin_unlock_irqrestore(&sched_lock, sched_fl); /* Release the block device */ if (g_mounts[idx].bdev) { @@ -828,6 +807,57 @@ void vfs_mount_unref(fs_node_t* mount_root) { spin_unlock_irqrestore(&g_vfs_lock, fl); } +/* Increment the refcount on the mount that contains the given path. */ +void vfs_mount_ref_by_path(const char* path) { + if (!path || !path[0]) return; + + uintptr_t fl = spin_lock_irqsave(&g_vfs_lock); + int best_idx = -1; + size_t best_len = 0; + + /* Find the most specific mount (longest prefix match) */ + for (int i = 0; i < g_mount_count; i++) { + if (path_is_mountpoint_prefix(g_mounts[i].mountpoint, path)) { + size_t len = strlen(g_mounts[i].mountpoint); + if (len > best_len) { + best_len = len; + best_idx = i; + } + } + } + + if (best_idx >= 0) { + g_mounts[best_idx].refcount++; + } + spin_unlock_irqrestore(&g_vfs_lock, fl); +} + +/* Decrement the refcount on the mount that contains the given path. */ +void vfs_mount_unref_by_path(const char* path) { + if (!path || !path[0]) return; + + uintptr_t fl = spin_lock_irqsave(&g_vfs_lock); + int best_idx = -1; + size_t best_len = 0; + + /* Find the most specific mount (longest prefix match) */ + for (int i = 0; i < g_mount_count; i++) { + if (path_is_mountpoint_prefix(g_mounts[i].mountpoint, path)) { + size_t len = strlen(g_mounts[i].mountpoint); + if (len > best_len) { + best_len = len; + best_idx = i; + } + } + } + + if (best_idx >= 0) { + if (g_mounts[best_idx].refcount > 0) + g_mounts[best_idx].refcount--; + } + spin_unlock_irqrestore(&g_vfs_lock, fl); +} + /* Check if the filesystem containing the given path is writable. * Returns 0 if writable, -EROFS if MS_RDONLY is set. */ int vfs_require_writable_path(const char* path) { diff --git a/src/kernel/scheduler.c b/src/kernel/scheduler.c index 1b66cec9..316f62a4 100644 --- a/src/kernel/scheduler.c +++ b/src/kernel/scheduler.c @@ -591,6 +591,10 @@ void sched_assign_pid1(struct process* p) { void process_exit_notify(int status) { if (!current_process) return; + /* Decrement mount refcount for the exiting process's cwd */ + extern void vfs_mount_unref_by_path(const char* path); + vfs_mount_unref_by_path(current_process->cwd); + uintptr_t flags = spin_lock_irqsave(&sched_lock); current_process->exit_status = status; @@ -695,6 +699,10 @@ struct process* process_fork_create(uintptr_t child_as, const void* child_regs) } else { strcpy(proc->cwd, "/"); } + + /* Increment mount refcount for the new process's cwd */ + extern void vfs_mount_ref_by_path(const char* path); + vfs_mount_ref_by_path(proc->cwd); proc->has_user_regs = 1; memcpy(proc->user_regs, child_regs, ARCH_REGS_SIZE); @@ -981,6 +989,10 @@ void process_init(void) { kernel_proc->wait_result_status = 0; strcpy(kernel_proc->cwd, "/"); + + /* Increment mount refcount for kernel process's cwd */ + extern void vfs_mount_ref_by_path(const char* path); + vfs_mount_ref_by_path(kernel_proc->cwd); for (int i = 0; i < PROCESS_MAX_FILES; i++) { kernel_proc->files[i] = NULL; @@ -1047,6 +1059,10 @@ void sched_ap_init(uint32_t cpu) { idle->addr_space = kernel_as; idle->cpu_id = cpu; strcpy(idle->cwd, "/"); + + /* Increment mount refcount for idle process's cwd */ + extern void vfs_mount_ref_by_path(const char* path); + vfs_mount_ref_by_path(idle->cwd); for (int i = 0; i < PROCESS_MAX_MMAPS; i++) idle->mmaps[i].shmid = -1; idle->kernel_stack = (uint32_t*)kstack; diff --git a/src/kernel/syscall.c b/src/kernel/syscall.c index 953f402e..51adc05d 100644 --- a/src/kernel/syscall.c +++ b/src/kernel/syscall.c @@ -2644,6 +2644,11 @@ static int syscall_chdir_impl(const char* user_path) { fs_node_t* n = vfs_lookup(path); if (!n) return -ENOENT; if (n->flags != FS_DIRECTORY) return -ENOTDIR; + + /* Update mount refcounts */ + vfs_mount_unref_by_path(current_process->cwd); + vfs_mount_ref_by_path(path); + strcpy(current_process->cwd, path); return 0; } -- 2.43.0