#include <stdint.h>
#include <stddef.h>
#include "errno.h"
+#include "spinlock.h"
/* Generic block device interface — abstracts ATA/virtio-blk/etc.
* Filesystems (fat, ext2) use this instead of calling ATA directly,
struct block_device;
struct block_device_ops {
- int (*read)(const struct block_device* dev, uint32_t lba, void* buf);
- int (*write)(const struct block_device* dev, uint32_t lba, const void* buf);
+ int (*read)(struct block_device* dev, uint32_t lba, void* buf);
+ int (*write)(struct block_device* dev, uint32_t lba, const void* buf);
};
typedef struct block_device {
int blockdev_register(const block_device_t* dev);
/* Look up a block device by name (e.g. "hda"). Returns pointer or NULL. */
-const block_device_t* blockdev_find(const char* name);
+block_device_t* blockdev_find(const char* name);
/* Look up a block device by drive_id. Returns pointer or NULL. */
-const block_device_t* blockdev_by_id(int drive_id);
+block_device_t* blockdev_by_id(int drive_id);
/* Increment block device refcount (called when filesystem mounts). */
-void blockdev_claim(const block_device_t* dev);
+void blockdev_claim(block_device_t* dev);
/* Decrement block device refcount (called when filesystem unmounts). */
-void blockdev_release(const block_device_t* dev);
+void blockdev_release(block_device_t* dev);
/* Convenience: read one sector from a block device. */
-static inline int blockdev_read(const block_device_t* dev, uint32_t lba, void* buf) {
+static inline int blockdev_read(block_device_t* dev, uint32_t lba, void* buf) {
if (!dev || !dev->ops || !dev->ops->read) return -ENODEV;
return dev->ops->read(dev, lba, buf);
}
/* Convenience: write one sector to a block device. */
-static inline int blockdev_write(const block_device_t* dev, uint32_t lba, const void* buf) {
+static inline int blockdev_write(block_device_t* dev, uint32_t lba, const void* buf) {
if (!dev || !dev->ops || !dev->ops->write) return -ENODEV;
return dev->ops->write(dev, lba, buf);
}
/* Register all detected ATA drives as block devices. */
void blockdev_register_ata(void);
+/* Initialize the blockdev lock (must be called before any blockdev operations). */
+void blockdev_init_lock(void);
+
#endif
/* Per-mount filesystem state */
struct ext2_mount {
- const block_device_t* bdev;
+ block_device_t* bdev;
uint32_t part_lba; /* partition start LBA */
uint32_t block_size; /* bytes per block (1024, 2048, or 4096) */
uint32_t sectors_per_block;
/* Mount an ext2 filesystem on the given block device starting at the given
* LBA offset. Returns a mount result with root node and superblock, or {NULL, NULL} on failure. */
-vfs_mount_result_t ext2_mount(const block_device_t* bdev, uint32_t partition_lba);
+vfs_mount_result_t ext2_mount(block_device_t* bdev, uint32_t partition_lba);
/* Unmount an ext2 filesystem and free its resources */
void ext2_umount(struct ext2_mount* em);
/* Per-mount filesystem state */
struct fat_mount {
- const block_device_t* bdev;
+ block_device_t* bdev;
uint32_t part_lba;
uint16_t bytes_per_sector;
uint8_t sectors_per_cluster;
/* Mount a FAT12/16/32 filesystem on the given block device starting at
* the given LBA offset. Auto-detects FAT type from BPB.
* Returns a mount result with root node and superblock, or {NULL, NULL} on failure. */
-vfs_mount_result_t fat_mount(const block_device_t* bdev, uint32_t partition_lba);
+vfs_mount_result_t fat_mount(block_device_t* bdev, uint32_t partition_lba);
/* Unmount a FAT filesystem and free its resources */
void fat_umount(struct fat_mount* fm);
/* VFS superblock — per-mount filesystem metadata */
typedef struct vfs_superblock {
const struct vfs_fs_type* fstype; /* Filesystem type */
- const block_device_t* bdev; /* Block device (NULL for virtual FS) */
+ block_device_t* bdev; /* Block device (NULL for virtual FS) */
uint32_t lba; /* Partition start LBA (0 for whole disk) */
void* private_data; /* Filesystem-specific data (e.g. fat_mount, ext2_mount) */
struct fs_node* root; /* VFS root node (for cleanup on umount) */
typedef struct vfs_fs_type {
const char* name; /* e.g. "fat", "ext2" */
uint32_t flags; /* FS_NEEDS_BDEV, etc. */
- vfs_mount_result_t (*mount)(const block_device_t* bdev, uint32_t lba); /* Mount function */
+ vfs_mount_result_t (*mount)(block_device_t* bdev, uint32_t lba); /* Mount function */
void (*kill_sb)(vfs_superblock_t* sb); /* Unmount/cleanup function */
} vfs_fs_type_t;
int vfs_mount(const char* mountpoint, fs_node_t* root);
int vfs_mount_full(const char* mountpoint, fs_node_t* root,
const char* fstype, const char* source,
- unsigned long flags, const block_device_t* bdev,
+ unsigned long flags, block_device_t* bdev,
vfs_superblock_t* sb);
int vfs_umount(const char* mountpoint);
int vfs_mount_nolock(const char* mountpoint, fs_node_t* root);
int vfs_mount_nolock_full(const char* mountpoint, fs_node_t* root,
const char* fstype, const char* source,
- unsigned long flags, const block_device_t* bdev,
+ unsigned long flags, block_device_t* bdev,
vfs_superblock_t* sb);
int vfs_umount_nolock(const char* mountpoint);
* mountpoint: e.g. "/disk", "/fat", "/ext2"
* flags: mount flags (MS_RDONLY, etc.) — stored in VFS mount table
* Returns 0 on success, negative errno on failure. */
-int init_mount_fs(const char* fstype, const struct block_device* bdev, uint32_t lba, const char* mountpoint, unsigned long flags);
+int init_mount_fs(const char* fstype, struct block_device* bdev, uint32_t lba, const char* mountpoint, unsigned long flags);
#endif
}
/* ---- Block device operations ---- */
-static int vblk_bdev_read(const block_device_t* dev, uint32_t lba, void* buf) {
+static int vblk_bdev_read(block_device_t* dev, uint32_t lba, void* buf) {
(void)dev;
return virtio_blk_read((uint64_t)lba, buf, 1);
}
-static int vblk_bdev_write(const block_device_t* dev, uint32_t lba, const void* buf) {
+static int vblk_bdev_write(block_device_t* dev, uint32_t lba, const void* buf) {
(void)dev;
return virtio_blk_write((uint64_t)lba, buf, 1);
}
static block_device_t g_blockdevs[BLOCKDEV_MAX];
static int g_blockdev_count = 0;
+static spinlock_t g_blockdev_lock;
+
+void blockdev_init_lock(void) {
+ spinlock_init(&g_blockdev_lock);
+}
int blockdev_register(const block_device_t* dev) {
if (!dev) return -EINVAL;
- if (g_blockdev_count >= BLOCKDEV_MAX) return -ENOSPC;
+
+ uintptr_t flags = spin_lock_irqsave(&g_blockdev_lock);
+
+ if (g_blockdev_count >= BLOCKDEV_MAX) {
+ spin_unlock_irqrestore(&g_blockdev_lock, flags);
+ return -ENOSPC;
+ }
/* Check for duplicate name */
for (int i = 0; i < g_blockdev_count; i++) {
if (strcmp(g_blockdevs[i].name, dev->name) == 0) {
/* Update existing entry */
g_blockdevs[i] = *dev;
+ spin_unlock_irqrestore(&g_blockdev_lock, flags);
return 0;
}
}
g_blockdevs[g_blockdev_count++] = *dev;
+ spin_unlock_irqrestore(&g_blockdev_lock, flags);
return 0;
}
-const block_device_t* blockdev_find(const char* name) {
+block_device_t* blockdev_find(const char* name) {
if (!name) return NULL;
+
+ uintptr_t flags = spin_lock_irqsave(&g_blockdev_lock);
+ block_device_t* result = NULL;
for (int i = 0; i < g_blockdev_count; i++) {
- if (strcmp(g_blockdevs[i].name, name) == 0)
- return &g_blockdevs[i];
+ if (strcmp(g_blockdevs[i].name, name) == 0) {
+ result = &g_blockdevs[i];
+ break;
+ }
}
- return NULL;
+ spin_unlock_irqrestore(&g_blockdev_lock, flags);
+ return result;
}
-const block_device_t* blockdev_by_id(int drive_id) {
+block_device_t* blockdev_by_id(int drive_id) {
+ uintptr_t flags = spin_lock_irqsave(&g_blockdev_lock);
+ block_device_t* result = NULL;
for (int i = 0; i < g_blockdev_count; i++) {
- if (g_blockdevs[i].drive_id == drive_id)
- return &g_blockdevs[i];
+ if (g_blockdevs[i].drive_id == drive_id) {
+ result = &g_blockdevs[i];
+ break;
+ }
}
- return NULL;
+ spin_unlock_irqrestore(&g_blockdev_lock, flags);
+ return result;
}
-void blockdev_claim(const block_device_t* dev) {
+void blockdev_claim(block_device_t* dev) {
if (!dev) return;
- /* Cast away const since we need to modify refcount */
- block_device_t* mutable_dev = (block_device_t*)dev;
- mutable_dev->refcount++;
+ uintptr_t flags = spin_lock_irqsave(&g_blockdev_lock);
+ dev->refcount++;
+ spin_unlock_irqrestore(&g_blockdev_lock, flags);
}
-void blockdev_release(const block_device_t* dev) {
+void blockdev_release(block_device_t* dev) {
if (!dev) return;
- /* Cast away const since we need to modify refcount */
- block_device_t* mutable_dev = (block_device_t*)dev;
- if (mutable_dev->refcount > 0)
- mutable_dev->refcount--;
+ uintptr_t flags = spin_lock_irqsave(&g_blockdev_lock);
+ if (dev->refcount > 0)
+ dev->refcount--;
+ spin_unlock_irqrestore(&g_blockdev_lock, flags);
}
/* ---- ATA block device ops ---- */
-static int ata_bd_read(const block_device_t* dev, uint32_t lba, void* buf) {
+static int ata_bd_read(block_device_t* dev, uint32_t lba, void* buf) {
return ata_pio_read28(dev->drive_id, lba, (uint8_t*)buf);
}
-static int ata_bd_write(const block_device_t* dev, uint32_t lba, const void* buf) {
+static int ata_bd_write(block_device_t* dev, uint32_t lba, const void* buf) {
return ata_pio_write28(dev->drive_id, lba, (const uint8_t*)buf);
}
/* ---- Mount ---- */
-vfs_mount_result_t ext2_mount(const block_device_t* bdev, uint32_t partition_lba) {
+vfs_mount_result_t ext2_mount(block_device_t* bdev, uint32_t partition_lba) {
vfs_mount_result_t result = {NULL, NULL};
if (!bdev) {
/* ---- Mount ---- */
-vfs_mount_result_t fat_mount(const block_device_t* bdev, uint32_t partition_lba) {
+vfs_mount_result_t fat_mount(block_device_t* bdev, uint32_t partition_lba) {
vfs_mount_result_t result = {NULL, NULL};
if (!bdev) {
char source[64]; /* e.g. "/dev/hda", "none", "initrd" */
unsigned long flags; /* MS_RDONLY, MS_NOSUID, etc. */
int refcount; /* number of open files on this mount */
- const block_device_t* bdev; /* block device (NULL for virtual FS) */
+ block_device_t* bdev; /* block device (NULL for virtual FS) */
vfs_superblock_t* sb; /* superblock (NULL for virtual FS) */
fs_node_t* root;
};
int vfs_mount_nolock_full(const char* mountpoint, fs_node_t* root,
const char* fstype, const char* source,
- unsigned long flags, const block_device_t* bdev,
+ unsigned long flags, block_device_t* bdev,
vfs_superblock_t* sb) {
char mp[128];
normalize_mountpoint(mountpoint, mp, sizeof(mp));
/* Mount already exists at this mountpoint */
if (flags & MS_REMOUNT) {
/* Remount: update flags only (bdev, sb, root, source, fstype preserved) */
- g_mounts[i].flags = flags;
+ g_mounts[i].flags = flags & ~MS_REMOUNT;
return 0;
} else {
- /* New mount attempt on existing mountpoint - reject if active */
- if (g_mounts[i].refcount > 0) {
- return -EBUSY;
- }
- /* Allow replacement if mount is idle (refcount == 0) */
- /* This is the old behavior for backward compatibility */
- if (root) g_mounts[i].root = root;
- if (fstype) {
- strncpy(g_mounts[i].fstype, fstype, sizeof(g_mounts[i].fstype) - 1);
- g_mounts[i].fstype[sizeof(g_mounts[i].fstype) - 1] = '\0';
- }
- if (source) {
- strncpy(g_mounts[i].source, source, sizeof(g_mounts[i].source) - 1);
- g_mounts[i].source[sizeof(g_mounts[i].source) - 1] = '\0';
- }
- g_mounts[i].flags = flags;
- g_mounts[i].bdev = bdev;
- g_mounts[i].sb = sb;
- return 0;
+ /* Reject new mount on existing mountpoint - use MS_REMOUNT to change flags */
+ return -EBUSY;
}
}
}
int vfs_mount_full(const char* mountpoint, fs_node_t* root,
const char* fstype, const char* source,
- unsigned long flags, const block_device_t* bdev,
+ unsigned long flags, block_device_t* bdev,
vfs_superblock_t* sb) {
uintptr_t fl = spin_lock_irqsave(&g_vfs_lock);
int ret = vfs_mount_nolock_full(mountpoint, root, fstype, source, flags, bdev, sb);
int vfs_link(const char* old_path, const char* new_path) {
if (!old_path || !new_path) return -EINVAL;
+
+ /* Check if new_path is on a read-only mount */
+ int rc = vfs_require_writable_path(new_path);
+ if (rc < 0) return rc;
+
fs_node_t* target = vfs_lookup(old_path);
if (!target) return -ENOENT;
if (target->flags != FS_FILE) return -EPERM;
/* ---- Mount helper: used by fstab parser and kconsole 'mount' command ---- */
-int init_mount_fs(const char* fstype, const block_device_t* bdev, uint32_t lba, const char* mountpoint, unsigned long flags) {
+int init_mount_fs(const char* fstype, block_device_t* bdev, uint32_t lba, const char* mountpoint, unsigned long flags) {
+ /* Validate mountpoint exists and is a directory */
+ fs_node_t* mp_node = vfs_lookup(mountpoint);
+ if (!mp_node) {
+ kprintf("[MOUNT] Mountpoint does not exist: %s\n", mountpoint);
+ return -ENOENT;
+ }
+ if (!(mp_node->flags & FS_DIRECTORY)) {
+ kprintf("[MOUNT] Mountpoint is not a directory: %s\n", mountpoint);
+ return -ENOTDIR;
+ }
+
const vfs_fs_type_t* fst = vfs_fs_type_find(fstype);
if (!fst) {
kprintf("[MOUNT] Unknown filesystem type: %s\n", fstype);
if (bdev) {
blockdev_release(bdev);
}
+ /* Cleanup filesystem state if mount registration failed */
+ if (mres.sb && mres.sb->fstype && mres.sb->fstype->kill_sb) {
+ mres.sb->fstype->kill_sb(mres.sb);
+ } else if (mres.root) {
+ kfree(mres.root);
+ }
return rc;
}
ata_register_devfs();
/* Register ATA drives as generic block devices (used by fat/ext2) */
+ blockdev_init_lock();
blockdev_register_ata();
/* If root= is specified on the kernel command line, mount that device
const char* devname = root_dev;
if (strncmp(root_dev, "/dev/", 5) == 0)
devname = root_dev + 5;
- const block_device_t* bdev = blockdev_find(devname);
+ block_device_t* bdev = blockdev_find(devname);
if (bdev) {
/* Auto-detect: try ext2, then fat (non-destructive probes). */
static const char* fstypes[] = { "ext2", "fat", NULL };
}
} else {
/* No root= on cmdline — try to auto-mount primary master if present */
- const block_device_t* bdev = blockdev_find("hda");
+ block_device_t* bdev = blockdev_find("hda");
if (bdev) {
static const char* fstypes[] = { "ext2", "fat", NULL };
for (int i = 0; fstypes[i]; i++) {
return;
}
+ /* Validate mountpoint exists and is a directory */
+ extern fs_node_t* vfs_lookup(const char* path);
+ fs_node_t* mp_node = vfs_lookup(mountpoint);
+ if (!mp_node) {
+ kprintf("mount: mountpoint does not exist: %s\n", mountpoint);
+ return;
+ }
+ if (!(mp_node->flags & FS_DIRECTORY)) {
+ kprintf("mount: mountpoint is not a directory: %s\n", mountpoint);
+ return;
+ }
+
/* Resolve device to block device */
const char* devname = device;
if (strncmp(device, "/dev/", 5) == 0) {
devname = device + 5;
}
- const block_device_t* bdev = blockdev_find(devname);
+ block_device_t* bdev = blockdev_find(devname);
if (!bdev) {
kprintf("mount: unknown device: %s\n", device);
return;
if (!f || !f->node) { sc_ret(regs) = (uint32_t)-EBADF; return; }
if ((f->flags & 3U) == 0U) { sc_ret(regs) = (uint32_t)-EBADF; return; } /* O_RDONLY */
if (!(f->node->flags & FS_FILE)) { sc_ret(regs) = (uint32_t)-EINVAL; return; }
+ /* Check if mount is read-only */
+ if (f->mount_root && (vfs_node_mount_flags(f->mount_root) & MS_RDONLY)) {
+ sc_ret(regs) = (uint32_t)-EROFS;
+ return;
+ }
/* A14: use vfs_truncate_node to call backend truncate when available */
int rc = vfs_truncate_node(f->node, length);
sc_ret(regs) = (uint32_t)rc;
/* MS_REMOUNT: update flags on existing mount */
if (mount_flags & MS_REMOUNT) {
- sc_ret(regs) = (uint32_t)vfs_mount_full(kmp, NULL, NULL, NULL, mount_flags & ~MS_REMOUNT, NULL, NULL);
+ sc_ret(regs) = (uint32_t)vfs_mount_full(kmp, NULL, NULL, NULL, mount_flags, NULL, NULL);
return;
}
/* Disk-based: parse /dev/hdX -> block device */
const char* devname = kdev;
if (strncmp(devname, "/dev/", 5) == 0) devname += 5;
- extern const block_device_t* blockdev_find(const char* name);
- const block_device_t* bdev = blockdev_find(devname);
+ extern block_device_t* blockdev_find(const char* name);
+ block_device_t* bdev = blockdev_find(devname);
if (!bdev) { sc_ret(regs) = (uint32_t)-ENODEV; return; }
extern int init_mount_fs(const char* fstype, const block_device_t* bdev, uint32_t lba, const char* mountpoint, unsigned long flags);