Add struct file_operations to fs.h with all VFS callback signatures.
Add const struct file_operations* f_ops to fs_node_t.
Update all VFS dispatch points (fs.c wrappers + syscall.c direct
dispatch for poll, readdir, ioctl, mmap) to check f_ops first,
then fall back to legacy per-node function pointers.
This enables incremental migration: filesystems can adopt f_ops
one at a time while legacy pointers continue to work.
20/20 smoke tests pass.
#define VFS_POLL_ERR 0x0008
#define VFS_POLL_HUP 0x0010
+struct fs_node; /* forward declaration for file_operations */
+
+/* Shared file operations table — filesystems define one static instance
+ * per node type (file, dir, device) and point every node's f_ops at it.
+ * During the migration period, the VFS checks f_ops first, then falls
+ * back to per-node function pointers (legacy). */
+struct file_operations {
+ uint32_t (*read)(struct fs_node* node, uint32_t offset, uint32_t size, uint8_t* buffer);
+ uint32_t (*write)(struct fs_node* node, uint32_t offset, uint32_t size, const uint8_t* buffer);
+ void (*open)(struct fs_node* node);
+ void (*close)(struct fs_node* node);
+ struct fs_node* (*finddir)(struct fs_node* node, const char* name);
+ int (*readdir)(struct fs_node* node, uint32_t* inout_index, void* buf, uint32_t buf_len);
+ int (*ioctl)(struct fs_node* node, uint32_t cmd, void* arg);
+ uintptr_t (*mmap)(struct fs_node* node, uintptr_t addr, uint32_t length, uint32_t prot, uint32_t offset);
+ int (*poll)(struct fs_node* node, int events);
+ int (*create)(struct fs_node* dir, const char* name, uint32_t flags, struct fs_node** out);
+ int (*mkdir)(struct fs_node* dir, const char* name);
+ int (*unlink)(struct fs_node* dir, const char* name);
+ int (*rmdir)(struct fs_node* dir, const char* name);
+ int (*rename)(struct fs_node* old_dir, const char* old_name,
+ struct fs_node* new_dir, const char* new_name);
+ int (*truncate)(struct fs_node* node, uint32_t length);
+ int (*link)(struct fs_node* dir, const char* name, struct fs_node* target);
+};
+
typedef struct fs_node {
char name[128];
uint32_t flags;
uint32_t gid;
uint32_t mode;
char symlink_target[128];
-
- // Function pointers for operations (Polymorphism in C)
+
+ const struct file_operations* f_ops;
+
+ // Legacy per-node function pointers (will be removed after migration)
uint32_t (*read)(struct fs_node* node, uint32_t offset, uint32_t size, uint8_t* buffer);
uint32_t (*write)(struct fs_node* node, uint32_t offset, uint32_t size, const uint8_t* buffer);
void (*open)(struct fs_node* node);
int (*ioctl)(struct fs_node* node, uint32_t cmd, void* arg);
uintptr_t (*mmap)(struct fs_node* node, uintptr_t addr, uint32_t length, uint32_t prot, uint32_t offset);
int (*poll)(struct fs_node* node, int events);
-
- // Directory mutation operations (called on the parent directory node)
int (*create)(struct fs_node* dir, const char* name, uint32_t flags, struct fs_node** out);
int (*mkdir)(struct fs_node* dir, const char* name);
int (*unlink)(struct fs_node* dir, const char* name);
}
uint32_t vfs_read(fs_node_t* node, uint32_t offset, uint32_t size, uint8_t* buffer) {
+ if (node->f_ops && node->f_ops->read)
+ return node->f_ops->read(node, offset, size, buffer);
if (node->read)
return node->read(node, offset, size, buffer);
return 0;
}
uint32_t vfs_write(fs_node_t* node, uint32_t offset, uint32_t size, const uint8_t* buffer) {
+ if (node->f_ops && node->f_ops->write)
+ return node->f_ops->write(node, offset, size, buffer);
if (node->write)
return node->write(node, offset, size, buffer);
return 0;
}
void vfs_open(fs_node_t* node) {
- if (node->open)
+ if (node->f_ops && node->f_ops->open)
+ node->f_ops->open(node);
+ else if (node->open)
node->open(node);
}
void vfs_close(fs_node_t* node) {
- if (node->close)
+ if (node->f_ops && node->f_ops->close)
+ node->f_ops->close(node);
+ else if (node->close)
node->close(node);
}
if (part[0] == 0) continue;
- if (!cur || !cur->finddir) return NULL;
- cur = cur->finddir(cur, part);
+ if (!cur) return NULL;
+ fs_node_t* (*fn_finddir)(fs_node_t*, const char*) = NULL;
+ if (cur->f_ops && cur->f_ops->finddir) fn_finddir = cur->f_ops->finddir;
+ else if (cur->finddir) fn_finddir = cur->finddir;
+ if (!fn_finddir) return NULL;
+ cur = fn_finddir(cur, part);
if (!cur) return NULL;
if (cur->flags == FS_SYMLINK && cur->symlink_target[0]) {
fs_node_t* parent = vfs_lookup_parent(path, name, sizeof(name));
if (!parent) return -ENOENT;
if (parent->flags != FS_DIRECTORY) return -ENOTDIR;
+ if (parent->f_ops && parent->f_ops->create)
+ return parent->f_ops->create(parent, name, flags, out);
if (!parent->create) return -ENOSYS;
return parent->create(parent, name, flags, out);
}
fs_node_t* parent = vfs_lookup_parent(path, name, sizeof(name));
if (!parent) return -ENOENT;
if (parent->flags != FS_DIRECTORY) return -ENOTDIR;
+ if (parent->f_ops && parent->f_ops->mkdir)
+ return parent->f_ops->mkdir(parent, name);
if (!parent->mkdir) return -ENOSYS;
return parent->mkdir(parent, name);
}
fs_node_t* parent = vfs_lookup_parent(path, name, sizeof(name));
if (!parent) return -ENOENT;
if (parent->flags != FS_DIRECTORY) return -ENOTDIR;
+ if (parent->f_ops && parent->f_ops->unlink)
+ return parent->f_ops->unlink(parent, name);
if (!parent->unlink) return -ENOSYS;
return parent->unlink(parent, name);
}
fs_node_t* parent = vfs_lookup_parent(path, name, sizeof(name));
if (!parent) return -ENOENT;
if (parent->flags != FS_DIRECTORY) return -ENOTDIR;
+ if (parent->f_ops && parent->f_ops->rmdir)
+ return parent->f_ops->rmdir(parent, name);
if (!parent->rmdir) return -ENOSYS;
return parent->rmdir(parent, name);
}
fs_node_t* old_parent = vfs_lookup_parent(old_path, old_name, sizeof(old_name));
fs_node_t* new_parent = vfs_lookup_parent(new_path, new_name, sizeof(new_name));
if (!old_parent || !new_parent) return -ENOENT;
+ if (old_parent->f_ops && old_parent->f_ops->rename)
+ return old_parent->f_ops->rename(old_parent, old_name, new_parent, new_name);
if (!old_parent->rename) return -ENOSYS;
return old_parent->rename(old_parent, old_name, new_parent, new_name);
}
fs_node_t* node = vfs_lookup(path);
if (!node) return -ENOENT;
if (node->flags != FS_FILE) return -EISDIR;
+ if (node->f_ops && node->f_ops->truncate)
+ return node->f_ops->truncate(node, length);
if (!node->truncate) return -ENOSYS;
return node->truncate(node, length);
}
fs_node_t* parent = vfs_lookup_parent(new_path, name, sizeof(name));
if (!parent) return -ENOENT;
if (parent->flags != FS_DIRECTORY) return -ENOTDIR;
+ if (parent->f_ops && parent->f_ops->link)
+ return parent->f_ops->link(parent, name, target);
if (!parent->link) return -ENOSYS;
return parent->link(parent, name, target);
}
fs_node_t* n = f->node;
- if (n->poll) {
+ int (*fn_poll)(fs_node_t*, int) = NULL;
+ if (n->f_ops && n->f_ops->poll) fn_poll = n->f_ops->poll;
+ else if (n->poll) fn_poll = n->poll;
+ if (fn_poll) {
int vfs_events = 0;
if (kfds[i].events & POLLIN) vfs_events |= VFS_POLL_IN;
if (kfds[i].events & POLLOUT) vfs_events |= VFS_POLL_OUT;
- int vfs_rev = n->poll(n, vfs_events);
+ int vfs_rev = fn_poll(n, vfs_events);
if (vfs_rev & VFS_POLL_IN) kfds[i].revents |= POLLIN;
if (vfs_rev & VFS_POLL_OUT) kfds[i].revents |= POLLOUT;
struct file* f = fd_get(fd);
if (!f || !f->node) return -EBADF;
if (f->node->flags != FS_DIRECTORY) return -ENOTDIR;
- if (!f->node->readdir) return -ENOSYS;
+ int (*fn_readdir)(struct fs_node*, uint32_t*, void*, uint32_t) = NULL;
+ if (f->node->f_ops && f->node->f_ops->readdir) fn_readdir = f->node->f_ops->readdir;
+ else if (f->node->readdir) fn_readdir = f->node->readdir;
+ if (!fn_readdir) return -ENOSYS;
uint8_t kbuf[256];
uint32_t klen = len;
if (klen > (uint32_t)sizeof(kbuf)) klen = (uint32_t)sizeof(kbuf);
uint32_t idx = f->offset;
- int rc = f->node->readdir(f->node, &idx, kbuf, klen);
+ int rc = fn_readdir(f->node, &idx, kbuf, klen);
if (rc < 0) return rc;
if (rc == 0) return 0;
if (!f || !f->node) return -EBADF;
int nonblock = (f->flags & O_NONBLOCK) ? 1 : 0;
- if (nonblock && f->node->poll) {
- int rev = f->node->poll(f->node, VFS_POLL_IN);
- if (!(rev & (VFS_POLL_IN | VFS_POLL_ERR | VFS_POLL_HUP)))
- return -EAGAIN;
+ {
+ int (*fn_poll)(fs_node_t*, int) = NULL;
+ if (f->node->f_ops && f->node->f_ops->poll) fn_poll = f->node->f_ops->poll;
+ else if (f->node->poll) fn_poll = f->node->poll;
+ if (nonblock && fn_poll) {
+ int rev = fn_poll(f->node, VFS_POLL_IN);
+ if (!(rev & (VFS_POLL_IN | VFS_POLL_ERR | VFS_POLL_HUP)))
+ return -EAGAIN;
+ }
}
if (f->node->flags == FS_CHARDEVICE) {
if (!f || !f->node) return -EBADF;
int nonblock = (f->flags & O_NONBLOCK) ? 1 : 0;
- if (nonblock && f->node->poll) {
- int rev = f->node->poll(f->node, VFS_POLL_OUT);
- if (!(rev & (VFS_POLL_OUT | VFS_POLL_ERR)))
- return -EAGAIN;
+ {
+ int (*fn_poll)(fs_node_t*, int) = NULL;
+ if (f->node->f_ops && f->node->f_ops->poll) fn_poll = f->node->f_ops->poll;
+ else if (f->node->poll) fn_poll = f->node->poll;
+ if (nonblock && fn_poll) {
+ int rev = fn_poll(f->node, VFS_POLL_OUT);
+ if (!(rev & (VFS_POLL_OUT | VFS_POLL_ERR)))
+ return -EAGAIN;
+ }
}
- if (!f->node->write) return -ESPIPE;
+ if (!(f->node->f_ops && f->node->f_ops->write) && !f->node->write) return -ESPIPE;
if (f->node->flags != FS_FILE && f->node->flags != FS_CHARDEVICE && f->node->flags != FS_SOCKET) return -ESPIPE;
if ((f->flags & O_APPEND) && (f->node->flags & FS_FILE)) {
if (!f || !f->node) return -EBADF;
fs_node_t* n = f->node;
+ if (n->f_ops && n->f_ops->ioctl) return n->f_ops->ioctl(n, cmd, user_arg);
if (n->ioctl) return n->ioctl(n, cmd, user_arg);
return -ENOTTY;
}
if (fd < 0) return (uintptr_t)-EBADF;
struct file* f = fd_get(fd);
if (!f || !f->node) return (uintptr_t)-EBADF;
- if (!f->node->mmap) return (uintptr_t)-ENOSYS;
+ if (!(f->node->f_ops && f->node->f_ops->mmap) && !f->node->mmap) return (uintptr_t)-ENOSYS;
mmap_node = f->node;
}
if (mmap_node) {
/* Device-backed mmap: delegate to the node's mmap callback */
- uintptr_t result = mmap_node->mmap(mmap_node, base, aligned_len, prot, offset);
+ uintptr_t (*fn_mmap)(fs_node_t*, uintptr_t, uint32_t, uint32_t, uint32_t) = NULL;
+ if (mmap_node->f_ops && mmap_node->f_ops->mmap) fn_mmap = mmap_node->f_ops->mmap;
+ else fn_mmap = mmap_node->mmap;
+ uintptr_t result = fn_mmap(mmap_node, base, aligned_len, prot, offset);
if (!result) return (uintptr_t)-ENOMEM;
base = result;
} else {