From 30861c8c16edb7ed6dc7081d4c93608d83fa7c67 Mon Sep 17 00:00:00 2001 From: Tulio A M Mendes Date: Mon, 25 May 2026 22:51:06 -0300 Subject: [PATCH] vfs: fix MS_REMOUNT, mount validation, blockdev locking, and resource leaks High-severity fixes: - MS_REMOUNT: pass full flags to VFS, store flags & ~MS_REMOUNT in remount branch - Reject mount replacement with -EBUSY (except with MS_REMOUNT) - Fix init_mount_fs leak: call kill_sb on failure after fst->mount - Add MS_RDONLY check in SYSCALL_FTRUNCATE via f->mount_root Medium-severity fixes: - Centralize read-only check in vfs_link with vfs_require_writable_path - Validate mountpoint in init_mount_fs and kconsole mount (must exist and be directory) - Add spinlock to g_blockdevs with irqsave/irqrestore protection - Remove const from blockdev_claim/release API (block_device_t* instead of const*) Files modified: - src/kernel/syscall.c: MS_REMOUNT fix, ftruncate readonly check - src/kernel/fs.c: mount replacement rejection, vfs_link readonly check, bdev const removal - src/kernel/init.c: mountpoint validation, init_mount_fs leak fix, blockdev_init_lock call - src/kernel/kconsole.c: mountpoint validation - include/blockdev.h: spinlock include, const removal from API - src/kernel/blockdev.c: spinlock implementation, const removal from functions - include/fs.h: const removal from bdev, mount function signatures - include/ext2.h: const removal from bdev, mount signature - include/fat.h: const removal from bdev, mount signature - src/kernel/ext2.c: const removal from mount signature - src/kernel/fat.c: const removal from mount signature - src/drivers/virtio_blk.c: const removal from ops functions - include/kernel/init.h: const removal from init_mount_fs signature Test results: - Smoke test: 119/119 PASS - cppcheck: style-level warnings only (no errors) --- include/blockdev.h | 20 ++++++++----- include/ext2.h | 4 +-- include/fat.h | 4 +-- include/fs.h | 8 ++--- include/kernel/init.h | 2 +- src/drivers/virtio_blk.c | 4 +-- src/kernel/blockdev.c | 64 +++++++++++++++++++++++++++------------- src/kernel/ext2.c | 2 +- src/kernel/fat.c | 2 +- src/kernel/fs.c | 34 +++++++-------------- src/kernel/init.c | 24 +++++++++++++-- src/kernel/kconsole.c | 14 ++++++++- src/kernel/syscall.c | 11 +++++-- 13 files changed, 122 insertions(+), 71 deletions(-) diff --git a/include/blockdev.h b/include/blockdev.h index f5fdd86e..edf38f5e 100644 --- a/include/blockdev.h +++ b/include/blockdev.h @@ -13,6 +13,7 @@ #include #include #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, @@ -23,8 +24,8 @@ 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 { @@ -40,25 +41,25 @@ 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); } @@ -66,4 +67,7 @@ static inline int blockdev_write(const block_device_t* dev, uint32_t lba, const /* 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 diff --git a/include/ext2.h b/include/ext2.h index 592a1e13..04098a99 100644 --- a/include/ext2.h +++ b/include/ext2.h @@ -18,7 +18,7 @@ struct ext2_group_desc; /* 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; @@ -35,7 +35,7 @@ struct ext2_mount { /* 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); diff --git a/include/fat.h b/include/fat.h index ca774a00..5465a13f 100644 --- a/include/fat.h +++ b/include/fat.h @@ -22,7 +22,7 @@ enum fat_type { /* 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; @@ -42,7 +42,7 @@ struct fat_mount { /* 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); diff --git a/include/fs.h b/include/fs.h index d4238b1b..d8bc90d3 100644 --- a/include/fs.h +++ b/include/fs.h @@ -39,7 +39,7 @@ struct vfs_fs_type; /* forward declaration */ /* 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) */ @@ -55,7 +55,7 @@ typedef struct vfs_mount_result { 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; @@ -145,7 +145,7 @@ int vfs_link(const char* old_path, const char* new_path); 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); @@ -155,7 +155,7 @@ 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); diff --git a/include/kernel/init.h b/include/kernel/init.h index 31416f8e..bbc1bcaa 100644 --- a/include/kernel/init.h +++ b/include/kernel/init.h @@ -23,6 +23,6 @@ int init_start(const struct boot_info* bi); * 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 diff --git a/src/drivers/virtio_blk.c b/src/drivers/virtio_blk.c index d93ead42..06757c54 100644 --- a/src/drivers/virtio_blk.c +++ b/src/drivers/virtio_blk.c @@ -301,12 +301,12 @@ uint64_t virtio_blk_capacity(void) { } /* ---- 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); } diff --git a/src/kernel/blockdev.c b/src/kernel/blockdev.c index bdc18709..5eae1a51 100644 --- a/src/kernel/blockdev.c +++ b/src/kernel/blockdev.c @@ -16,63 +16,87 @@ 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); } diff --git a/src/kernel/ext2.c b/src/kernel/ext2.c index 05b2a54a..677ce5d5 100644 --- a/src/kernel/ext2.c +++ b/src/kernel/ext2.c @@ -1427,7 +1427,7 @@ static int ext2_link_impl(struct fs_node* dir, const char* name, struct fs_node* /* ---- 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) { diff --git a/src/kernel/fat.c b/src/kernel/fat.c index ebf7afba..8e5991c7 100644 --- a/src/kernel/fat.c +++ b/src/kernel/fat.c @@ -1133,7 +1133,7 @@ static int fat_truncate_impl(struct fs_node* node, uint32_t length) { /* ---- 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) { diff --git a/src/kernel/fs.c b/src/kernel/fs.c index 9268659c..e75db37a 100644 --- a/src/kernel/fs.c +++ b/src/kernel/fs.c @@ -34,7 +34,7 @@ struct vfs_mount { 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; }; @@ -85,7 +85,7 @@ static void normalize_mountpoint(const char* in, char* out, size_t out_sz) { 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)); @@ -95,28 +95,11 @@ int vfs_mount_nolock_full(const char* mountpoint, fs_node_t* root, /* 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; } } } @@ -153,7 +136,7 @@ int vfs_mount_nolock(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) { uintptr_t fl = spin_lock_irqsave(&g_vfs_lock); int ret = vfs_mount_nolock_full(mountpoint, root, fstype, source, flags, bdev, sb); @@ -702,6 +685,11 @@ int vfs_check_permission(fs_node_t* node, int want) { 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; diff --git a/src/kernel/init.c b/src/kernel/init.c index 9195de01..57448bbf 100644 --- a/src/kernel/init.c +++ b/src/kernel/init.c @@ -43,7 +43,18 @@ /* ---- 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); @@ -86,6 +97,12 @@ int init_mount_fs(const char* fstype, const block_device_t* bdev, uint32_t lba, 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; } @@ -245,6 +262,7 @@ int init_start(const struct boot_info* bi) { 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 @@ -260,7 +278,7 @@ int init_start(const struct boot_info* bi) { 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 }; @@ -280,7 +298,7 @@ int init_start(const struct boot_info* bi) { } } 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++) { diff --git a/src/kernel/kconsole.c b/src/kernel/kconsole.c index 8058f73c..01af2357 100644 --- a/src/kernel/kconsole.c +++ b/src/kernel/kconsole.c @@ -350,12 +350,24 @@ static void kconsole_mount(const char* args) { 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; diff --git a/src/kernel/syscall.c b/src/kernel/syscall.c index dd789aab..953f402e 100644 --- a/src/kernel/syscall.c +++ b/src/kernel/syscall.c @@ -4445,6 +4445,11 @@ static void posix_ext_syscall_dispatch(struct registers* regs, uint32_t syscall_ 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; @@ -5174,7 +5179,7 @@ static void extended_syscall_dispatch(struct registers* regs, uint32_t syscall_n /* 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; } @@ -5208,8 +5213,8 @@ static void extended_syscall_dispatch(struct registers* regs, uint32_t syscall_n /* 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); -- 2.43.0