Files
zinnia/fs/mount.c
T
2026-10-25 18:35:03 +01:00

227 lines
6.1 KiB
C

// SPDX-License-Identifier: ISC
#include <zinnia/alloc.h>
#include <zinnia/assert.h>
#include <zinnia/print.h>
#include <zinnia/spin.h>
#include <zinnia/vfs.h>
#include <string.h>
#include "internal.h"
// All known and registered file systems.
static struct spinlock fs_table_lock;
static vec(const struct vfs_filesystem*) fs_table;
// Super blocks of all mounted file systems. Each holds a reference.
static struct spinlock supers_lock;
static vec(struct vfs_super_block*) mounted_supers;
// The global root.
static struct vfs_path vfs_root;
static bool vfs_root_set;
void vfs_set_root(struct vfs_path root) {
assert(!vfs_root_set, "The VFS root can only be set once");
vfs_root = vfs_path_clone(root);
vfs_root_set = true;
}
struct vfs_path vfs_get_root() {
assert(vfs_root_set, "The VFS root hasn't been set yet");
return vfs_path_clone(vfs_root);
}
errno_t vfs_register_filesystem(const struct vfs_filesystem* fs) {
errno_t status = 0;
spin_lock(&fs_table_lock);
vec_foreach(&fs_table, i) {
if (!strcmp(vec_elem_get(&fs_table, i)->name, fs->name)) {
status = EEXIST;
goto out;
}
}
status = vec_push(&fs_table, fs);
out:
spin_unlock(&fs_table_lock);
if (!status)
log("vfs: Registered new file system \"%s\"\n", fs->name);
return status;
}
// Returns a new reference to the super block of `mount`, if it has one.
static struct vfs_super_block* mount_get_super(struct vfs_mount* mount) {
struct vfs_node* root = vfs_entry_get_inode(mount->root);
if (!root)
return nullptr;
struct vfs_super_block* sb = root->sb;
if (sb)
vfs_super_block_ref(sb);
vfs_node_unref(root);
return sb;
}
errno_t vfs_mount(const char* fs_name, enum mount_flags flags, void* arg, struct vfs_mount** out) {
const struct vfs_filesystem* fs = nullptr;
spin_lock(&fs_table_lock);
vec_foreach(&fs_table, i) {
if (!strcmp(vec_elem_get(&fs_table, i)->name, fs_name)) {
fs = vec_elem_get(&fs_table, i);
break;
}
}
spin_unlock(&fs_table_lock);
if (!fs) {
log_warn("Attempted to mount unknown file system type \"%s\"\n", fs_name);
return ENODEV;
}
struct vfs_mount* mount;
errno_t status = fs->mount(fs, flags, arg, &mount);
if (status)
return status;
struct vfs_super_block* sb = mount_get_super(mount);
if (sb) {
bool known = false;
spin_lock(&supers_lock);
vec_foreach(&mounted_supers, i) {
if (vec_elem_get(&mounted_supers, i) == sb) {
known = true;
break;
}
}
// The list takes over our reference.
if (!known && vec_push(&mounted_supers, sb) == 0)
sb = nullptr;
spin_unlock(&supers_lock);
if (sb)
vfs_super_block_unref(sb);
}
*out = mount;
return 0;
}
errno_t vfs_unmount(struct vfs_mount* mount) {
struct vfs_super_block* sb = mount_get_super(mount);
if (!sb)
return 0;
spin_lock(&supers_lock);
vec_foreach(&mounted_supers, i) {
if (vec_elem_get(&mounted_supers, i) == sb) {
vec_swap_remove(&mounted_supers, i);
vfs_super_block_unref(sb);
break;
}
}
spin_unlock(&supers_lock);
errno_t status = sb->ops && sb->ops->sync ? sb->ops->sync(sb) : 0;
vfs_super_block_unref(sb);
return status;
}
errno_t vfs_sync_all() {
// Take a snapshot so we don't call into file systems while holding a spinlock.
spin_lock(&supers_lock);
size_t count = vec_length(&mounted_supers);
struct vfs_super_block** supers = count ? mem_alloc(count * sizeof(*supers), ALLOC_NOZERO) : nullptr;
if (count && !supers) {
spin_unlock(&supers_lock);
return ENOMEM;
}
for (size_t i = 0; i < count; i++) {
supers[i] = vec_elem_get(&mounted_supers, i);
vfs_super_block_ref(supers[i]);
}
spin_unlock(&supers_lock);
errno_t result = 0;
for (size_t i = 0; i < count; i++) {
struct vfs_super_block* sb = supers[i];
if (sb->ops && sb->ops->sync) {
errno_t status = sb->ops->sync(sb);
if (status)
result = status;
}
vfs_super_block_unref(sb);
}
mem_free(supers);
return result;
}
errno_t vfs_mount_new(enum mount_flags flags, struct vfs_entry* root, struct vfs_mount** out) {
struct vfs_mount* mount = mem_alloc(sizeof(*mount), 0);
if (!mount)
return ENOMEM;
refcount_init(&mount->rc);
mount->flags = flags;
mount->root = root;
vfs_entry_ref(root);
mount->has_mount_point = false;
*out = mount;
return 0;
}
errno_t vfs_path_mount(struct vfs_path at, struct vfs_mount* mount) {
if (mount->has_mount_point)
return EBUSY;
mutex_lock(&at.entry->lock);
errno_t status = vec_push(&at.entry->mounts, mount);
mutex_unlock(&at.entry->lock);
if (status)
return status;
vfs_mount_ref(mount);
mount->mount_point = vfs_path_clone(at);
mount->has_mount_point = true;
return 0;
}
errno_t vfs_path_unmount(struct vfs_path path) {
struct vfs_mount* mount = path.mount;
if (path.entry != mount->root || !mount->has_mount_point)
return EINVAL;
vfs_entry_prune(mount->root);
mutex_lock(&mount->root->lock);
const bool busy = !tailq_empty(&mount->root->children);
mutex_unlock(&mount->root->lock);
if (busy)
return EBUSY;
struct vfs_entry* point = mount->mount_point.entry;
bool found = false;
mutex_lock(&point->lock);
vec_foreach(&point->mounts, i) {
if (vec_elem_get(&point->mounts, i) == mount) {
vec_remove(&point->mounts, i);
found = true;
break;
}
}
mutex_unlock(&point->lock);
if (!found)
return EINVAL;
struct vfs_path mount_point = mount->mount_point;
mount->mount_point = (struct vfs_path){};
mount->has_mount_point = false;
vfs_path_drop(mount_point);
errno_t status = vfs_unmount(mount);
vfs_mount_unref(mount);
return status;
}