]> Projects (at) Tadryanom (dot) Me - AdrOS.git/commitdiff
vfs: implement cwd busy check via mount refcount
authorTulio A M Mendes <[email protected]>
Tue, 26 May 2026 03:08:21 +0000 (00:08 -0300)
committerTulio A M Mendes <[email protected]>
Wed, 3 Jun 2026 04:02:35 +0000 (01:02 -0300)
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
src/kernel/fs.c
src/kernel/scheduler.c
src/kernel/syscall.c

index d8bc90d38922cff2712c245486ef8c47c4c26589..bc130c275a7daf70b0f7afd8aa282983926924d8 100644 (file)
@@ -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);
index 658f94cd57ab20a789fa17cd038d3549c5718f07..e83d21fa87364f2cb248c60dc2d2700bb95dc64d 100644 (file)
@@ -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) {
index 1b66cec9156f0c9cc3655e59944687fb6de543ff..316f62a4f9c1472f510e885a2e4934b9ca27c604 100644 (file)
@@ -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;
index 953f402ec3b7226b226c0fab2fd884323c9d5833..51adc05d68417b0ed46da309ac376ef39cfcb84e 100644 (file)
@@ -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;
 }