From 0c5aa72fd3b7ec853f2c6646f3618d4c0232b644 Mon Sep 17 00:00:00 2001 From: AI Date: Mon, 23 Feb 2026 12:23:32 +0000 Subject: [PATCH] Add VFS subsystem and initrd filesystem driver (AI) VFS subsystem (vfs.c/h): - Mount table with up to 16 mount points, longest-prefix path matching. - File descriptor table (256 entries, fds 0-2 reserved for std streams). - Path resolution walks mount table then delegates to filesystem's finddir() for each path component. - Operations: open, close, read, write, seek, readdir, stat. - Each filesystem driver provides a vfs_fs_ops_t with callbacks. Initrd filesystem driver (initrd_fs.c/h): - Read-only VFS driver backed by the CPIO ramdisk. - Mounted at '/initrd' during boot. - Zero-copy reads: file data points directly into the CPIO archive memory, no allocation or copying needed. - Supports readdir (flat iteration) and finddir (name lookup). Bug fix: resolve_path was overwriting file-specific fs_data (set by finddir, e.g. pointer to CPIO file data) with the mount's fs_data (NULL). Fixed to preserve fs_data from finddir. Verified in QEMU: kernel reads /initrd/README via VFS and prints its contents. Ring 3 user process continues to work. --- README.md | 4 +- docs/vfs.md | 78 ++++++++++++ src/CMakeLists.txt | 2 + src/initrd_fs.c | 123 +++++++++++++++++++ src/initrd_fs.h | 20 +++ src/kernel.c | 23 ++++ src/vfs.c | 295 +++++++++++++++++++++++++++++++++++++++++++++ src/vfs.h | 197 ++++++++++++++++++++++++++++++ 8 files changed, 740 insertions(+), 2 deletions(-) create mode 100644 docs/vfs.md create mode 100644 src/initrd_fs.c create mode 100644 src/initrd_fs.h create mode 100644 src/vfs.c create mode 100644 src/vfs.h diff --git a/README.md b/README.md index 76033e7..715bf89 100644 --- a/README.md +++ b/README.md @@ -51,8 +51,8 @@ Once a task is completed, it should be checked off. - [x] Create a VGA driver. On startup, some memory statistics should be displayed, as well as boot progress. - [x] Create subsystem for loading new processes in Ring 3. - [x] Update the build script to generate a ramdisk containing any applications to run. This initial ramdisk is in CPIO format. -- [ ] Write a VFS subsystem. -- [ ] Write a VFS driver that provides the contents of the CPIO initial ramdisk to the VFS layer. +- [x] Write a VFS subsystem. +- [x] Write a VFS driver that provides the contents of the CPIO initial ramdisk to the VFS layer. - [ ] Create a `hello-world` app. It should print `Hello, World` to its own stdout. The kernel should route this to Qemu and to the VGA dispaly. Ensure this work. - [ ] Implement the fork system call. - [ ] Implement environment variables. Apps should be able to modify this, and it should be copied to new forks of an app. diff --git a/docs/vfs.md b/docs/vfs.md new file mode 100644 index 0000000..c6d6aa4 --- /dev/null +++ b/docs/vfs.md @@ -0,0 +1,78 @@ +# Virtual Filesystem (VFS) + +## Overview + +The VFS provides a unified interface for file and directory operations across +different filesystem implementations. Filesystem drivers register ops structs +and are mounted at specific paths. Path resolution finds the longest-matching +mount point and delegates to that filesystem. + +## Architecture + +``` + User/Kernel Code + │ + ▼ + vfs_open("/initrd/hello-world", 0) + │ + ▼ + VFS: find_mount("/initrd/hello-world") + │ → mount "/initrd", rel_path = "hello-world" + ▼ + resolve_path → initrd_finddir("hello-world") + │ + ▼ + vfs_read(fd, buf, size) + │ → initrd_read(node, offset, size, buf) + ▼ + Returns file data from CPIO archive +``` + +## Mount Points + +Filesystems are mounted at absolute paths. The VFS supports up to 16 +simultaneous mounts. Path resolution uses longest-prefix matching: + +``` +Mount: "/initrd" → handles /initrd/* +Mount: "/sys" → handles /sys/* +Mount: "/dev" → handles /dev/* +``` + +## File Operations + +| Function | Description | +|----------|-------------| +| `vfs_open(path, flags)` | Open a file, returns fd | +| `vfs_close(fd)` | Close a file descriptor | +| `vfs_read(fd, buf, size)` | Read bytes, advances offset | +| `vfs_write(fd, buf, size)` | Write bytes, advances offset | +| `vfs_seek(fd, offset, whence)` | Seek within file | +| `vfs_readdir(path, idx, out)` | Read directory entry | +| `vfs_stat(path, out)` | Get file info | + +## Filesystem Driver Interface + +Each filesystem provides a `vfs_fs_ops_t` struct: + +```c +typedef struct vfs_fs_ops { + int (*open)(vfs_node_t *node, uint32_t flags); + void (*close)(vfs_node_t *node); + int32_t (*read)(vfs_node_t *node, uint32_t offset, uint32_t size, void *buf); + int32_t (*write)(vfs_node_t *node, uint32_t offset, uint32_t size, const void *buf); + int (*readdir)(vfs_node_t *dir, uint32_t idx, vfs_dirent_t *out); + int (*finddir)(vfs_node_t *dir, const char *name, vfs_node_t *out); +} vfs_fs_ops_t; +``` + +## Initrd Filesystem Driver + +The initrd filesystem (`initrd_fs.c`) provides read-only access to the CPIO +ramdisk. It is automatically mounted at `/initrd` during boot. Files are +accessed via zero-copy reads directly from the CPIO archive in memory. + +## Files + +- `src/vfs.h` / `src/vfs.c` — VFS core: mount table, fd table, path resolution +- `src/initrd_fs.h` / `src/initrd_fs.c` — CPIO ramdisk VFS driver diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 3e23404..5b508fa 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -17,6 +17,8 @@ add_executable(kernel process.c syscall.c cpio.c + vfs.c + initrd_fs.c interrupts.S kernel.c ) diff --git a/src/initrd_fs.c b/src/initrd_fs.c new file mode 100644 index 0000000..9bff566 --- /dev/null +++ b/src/initrd_fs.c @@ -0,0 +1,123 @@ +/** + * @file initrd_fs.c + * @brief CPIO initial ramdisk VFS driver implementation. + * + * Provides a read-only VFS interface to the CPIO archive loaded at boot. + * Files are accessed directly from the archive memory (zero-copy reads). + */ + +#include "initrd_fs.h" +#include "vfs.h" +#include "cpio.h" +#include + +/* Debug print helpers defined in kernel.c */ +extern void offset_print(const char *str); +extern void print_hex(uint32_t val); + +/** + * Read from a file in the initrd. + * Data is read directly from the CPIO archive (memory-mapped). + */ +static int32_t initrd_read(vfs_node_t *node, uint32_t offset, + uint32_t size, void *buf) { + if (!node || !node->fs_data || !buf) return -1; + + /* fs_data points to the file's data within the CPIO archive */ + const uint8_t *data = (const uint8_t *)node->fs_data; + uint32_t file_size = node->size; + + if (offset >= file_size) return 0; + + uint32_t remaining = file_size - offset; + if (size > remaining) size = remaining; + + memcpy(buf, data + offset, size); + return (int32_t)size; +} + +/** + * Read a directory entry from the initrd root. + * The initrd is a flat archive — all files are at the root level. + */ +static int initrd_readdir(vfs_node_t *dir, uint32_t idx, vfs_dirent_t *out) { + (void)dir; + + uint32_t off = 0; + uint32_t current = 0; + cpio_entry_t entry; + + while (cpio_next(&off, &entry) == 0) { + /* Skip the "." directory entry if present */ + if (entry.name[0] == '.' && entry.name[1] == '\0') continue; + + /* Strip "./" prefix */ + const char *name = entry.name; + if (name[0] == '.' && name[1] == '/') name += 2; + + /* Skip empty names */ + if (*name == '\0') continue; + + if (current == idx) { + memset(out, 0, sizeof(vfs_dirent_t)); + strncpy(out->name, name, VFS_MAX_NAME - 1); + out->inode = current; + out->type = VFS_FILE; + return 0; + } + current++; + } + + return -1; /* No more entries */ +} + +/** + * Find a file by name within the initrd. + */ +static int initrd_finddir(vfs_node_t *dir, const char *name, vfs_node_t *out) { + (void)dir; + + cpio_entry_t entry; + if (cpio_find(name, &entry) != 0) { + return -1; + } + + memset(out, 0, sizeof(vfs_node_t)); + /* Strip "./" prefix for the node name */ + const char *display_name = entry.name; + if (display_name[0] == '.' && display_name[1] == '/') { + display_name += 2; + } + strncpy(out->name, display_name, VFS_MAX_NAME - 1); + out->type = VFS_FILE; + out->size = entry.datasize; + out->mode = entry.mode; + out->fs_data = (void *)entry.data; /* Direct pointer into CPIO archive */ + + return 0; +} + +/** Filesystem operations for the initrd. */ +static vfs_fs_ops_t initrd_ops = { + .open = NULL, /* No special open needed */ + .close = NULL, /* No special close needed */ + .read = initrd_read, + .write = NULL, /* Read-only */ + .readdir = initrd_readdir, + .finddir = initrd_finddir, +}; + +int init_initrd_fs(void) { + if (cpio_count() == 0) { + offset_print(" INITRD_FS: no files in ramdisk\n"); + } + + int ret = vfs_mount("/initrd", &initrd_ops, NULL); + if (ret != 0) { + offset_print(" INITRD_FS: failed to mount\n"); + return -1; + } + + offset_print(" INITRD_FS: mounted at /initrd\n"); + return 0; +} diff --git a/src/initrd_fs.h b/src/initrd_fs.h new file mode 100644 index 0000000..7e377c6 --- /dev/null +++ b/src/initrd_fs.h @@ -0,0 +1,20 @@ +/** + * @file initrd_fs.h + * @brief CPIO initial ramdisk VFS driver. + * + * Provides a read-only filesystem backed by the CPIO initial ramdisk. + * Mounted at "/initrd" to expose the contents of the ramdisk via the VFS. + */ + +#ifndef INITRD_FS_H +#define INITRD_FS_H + +/** + * Initialize the initrd filesystem driver and mount it at "/initrd". + * Must be called after init_vfs() and cpio_init(). + * + * @return 0 on success, -1 on failure. + */ +int init_initrd_fs(void); + +#endif /* INITRD_FS_H */ diff --git a/src/kernel.c b/src/kernel.c index 58b885c..8dcc5e3 100644 --- a/src/kernel.c +++ b/src/kernel.c @@ -14,6 +14,8 @@ #include "syscall.h" #include "process.h" #include "cpio.h" +#include "vfs.h" +#include "initrd_fs.h" void offset_print(const char *str) { @@ -101,6 +103,27 @@ void kernel_main(uint32_t magic, uint32_t addr) { offset_print("No initrd module found\n"); } + init_vfs(); + offset_print("VFS initialized\n"); + + if (initrd_start != 0) { + init_initrd_fs(); + offset_print("Initrd filesystem mounted\n"); + + /* Test VFS: read a file from the initrd */ + int fd = vfs_open("/initrd/README", 0); + if (fd >= 0) { + char buf[64]; + int32_t n = vfs_read(fd, buf, sizeof(buf) - 1); + if (n > 0) { + buf[n] = '\0'; + offset_print("VFS read /initrd/README: "); + offset_print(buf); + } + vfs_close(fd); + } + } + init_tss(); offset_print("TSS initialized\n"); diff --git a/src/vfs.c b/src/vfs.c new file mode 100644 index 0000000..8a60918 --- /dev/null +++ b/src/vfs.c @@ -0,0 +1,295 @@ +/** + * @file vfs.c + * @brief Virtual Filesystem (VFS) subsystem implementation. + * + * Manages mount points and routes file operations to the appropriate + * filesystem driver. Path resolution walks the mount table to find the + * longest-matching mount point, then delegates to that fs's operations. + */ + +#include "vfs.h" +#include + +/* Debug print helpers defined in kernel.c */ +extern void offset_print(const char *str); +extern void print_hex(uint32_t val); + +/** Mount table. */ +static vfs_mount_t mounts[VFS_MAX_MOUNTS]; + +/** Open file descriptor table. */ +static vfs_fd_t fd_table[VFS_MAX_OPEN_FILES]; + +/** + * Find the mount point that best matches a given path. + * Returns the index of the longest matching mount, or -1 if none. + * + * @param path The absolute path to resolve. + * @param rel_path Output: pointer within `path` past the mount prefix. + * @return Mount index, or -1. + */ +static int find_mount(const char *path, const char **rel_path) { + int best = -1; + uint32_t best_len = 0; + + for (int i = 0; i < VFS_MAX_MOUNTS; i++) { + if (!mounts[i].active) continue; + + uint32_t mlen = strlen(mounts[i].path); + + /* Check if path starts with this mount point */ + if (strncmp(path, mounts[i].path, mlen) != 0) continue; + + /* Must match at a directory boundary */ + if (mlen > 1 && path[mlen] != '\0' && path[mlen] != '/') continue; + + if (mlen > best_len) { + best = i; + best_len = mlen; + } + } + + if (best >= 0 && rel_path) { + const char *r = path + best_len; + /* Skip leading slash in relative path */ + while (*r == '/') r++; + *rel_path = r; + } + + return best; +} + +/** + * Resolve a path to a VFS node by finding the appropriate mount + * and asking the filesystem driver. + * + * @param path Full absolute path. + * @param out Output node. + * @return 0 on success, -1 on failure. + */ +static int resolve_path(const char *path, vfs_node_t *out) { + const char *rel_path = NULL; + int mount_idx = find_mount(path, &rel_path); + + if (mount_idx < 0) { + return -1; + } + + vfs_mount_t *mnt = &mounts[mount_idx]; + + /* If rel_path is empty, we're looking at the mount root */ + if (*rel_path == '\0') { + /* Return a directory node for the mount root */ + memset(out, 0, sizeof(vfs_node_t)); + strncpy(out->name, mnt->path, VFS_MAX_NAME - 1); + out->type = VFS_DIRECTORY; + out->ops = mnt->ops; + out->fs_data = mnt->fs_data; + out->mount_idx = mount_idx; + return 0; + } + + /* Walk path components through the filesystem */ + vfs_node_t current; + memset(¤t, 0, sizeof(vfs_node_t)); + current.type = VFS_DIRECTORY; + current.ops = mnt->ops; + current.fs_data = mnt->fs_data; + current.mount_idx = mount_idx; + + /* Parse path components */ + char component[VFS_MAX_NAME]; + const char *p = rel_path; + + while (*p) { + /* Skip slashes */ + while (*p == '/') p++; + if (*p == '\0') break; + + /* Extract component */ + const char *end = p; + while (*end && *end != '/') end++; + + uint32_t clen = (uint32_t)(end - p); + if (clen >= VFS_MAX_NAME) clen = VFS_MAX_NAME - 1; + memcpy(component, p, clen); + component[clen] = '\0'; + + /* Look up this component in the current directory */ + if (!current.ops || !current.ops->finddir) { + return -1; + } + + vfs_node_t child; + if (current.ops->finddir(¤t, component, &child) != 0) { + return -1; + } + + child.ops = mnt->ops; + /* Preserve fs_data set by finddir; only use mount fs_data if not set */ + if (!child.fs_data) { + child.fs_data = mnt->fs_data; + } + child.mount_idx = mount_idx; + current = child; + p = end; + } + + *out = current; + return 0; +} + +/** + * Find a free file descriptor slot. + * @return Index (>= 0), or -1 if all slots are used. + */ +static int alloc_fd(void) { + /* Skip fds 0,1,2 (stdin, stdout, stderr) for user processes */ + for (int i = 3; i < VFS_MAX_OPEN_FILES; i++) { + if (!fd_table[i].active) { + return i; + } + } + return -1; +} + +void init_vfs(void) { + memset(mounts, 0, sizeof(mounts)); + memset(fd_table, 0, sizeof(fd_table)); + offset_print(" VFS: initialized\n"); +} + +int vfs_mount(const char *path, vfs_fs_ops_t *ops, void *fs_data) { + /* Find a free mount slot */ + int slot = -1; + for (int i = 0; i < VFS_MAX_MOUNTS; i++) { + if (!mounts[i].active) { + slot = i; + break; + } + } + + if (slot < 0) { + offset_print(" VFS: no free mount slots\n"); + return -1; + } + + strncpy(mounts[slot].path, path, VFS_MAX_PATH - 1); + mounts[slot].ops = ops; + mounts[slot].fs_data = fs_data; + mounts[slot].active = 1; + + offset_print(" VFS: mounted at "); + offset_print(path); + offset_print("\n"); + + return 0; +} + +int vfs_open(const char *path, uint32_t flags) { + vfs_node_t node; + if (resolve_path(path, &node) != 0) { + return -1; + } + + int fd = alloc_fd(); + if (fd < 0) { + return -1; + } + + /* Call filesystem's open if available */ + if (node.ops && node.ops->open) { + if (node.ops->open(&node, flags) != 0) { + return -1; + } + } + + fd_table[fd].node = node; + fd_table[fd].offset = 0; + fd_table[fd].flags = flags; + fd_table[fd].active = 1; + + return fd; +} + +void vfs_close(int fd) { + if (fd < 0 || fd >= VFS_MAX_OPEN_FILES) return; + if (!fd_table[fd].active) return; + + vfs_fd_t *f = &fd_table[fd]; + if (f->node.ops && f->node.ops->close) { + f->node.ops->close(&f->node); + } + + f->active = 0; +} + +int32_t vfs_read(int fd, void *buf, uint32_t size) { + if (fd < 0 || fd >= VFS_MAX_OPEN_FILES) return -1; + if (!fd_table[fd].active) return -1; + + vfs_fd_t *f = &fd_table[fd]; + if (!f->node.ops || !f->node.ops->read) return -1; + + int32_t bytes = f->node.ops->read(&f->node, f->offset, size, buf); + if (bytes > 0) { + f->offset += (uint32_t)bytes; + } + return bytes; +} + +int32_t vfs_write(int fd, const void *buf, uint32_t size) { + if (fd < 0 || fd >= VFS_MAX_OPEN_FILES) return -1; + if (!fd_table[fd].active) return -1; + + vfs_fd_t *f = &fd_table[fd]; + if (!f->node.ops || !f->node.ops->write) return -1; + + int32_t bytes = f->node.ops->write(&f->node, f->offset, size, buf); + if (bytes > 0) { + f->offset += (uint32_t)bytes; + } + return bytes; +} + +int32_t vfs_seek(int fd, int32_t offset, int whence) { + if (fd < 0 || fd >= VFS_MAX_OPEN_FILES) return -1; + if (!fd_table[fd].active) return -1; + + vfs_fd_t *f = &fd_table[fd]; + int32_t new_offset; + + switch (whence) { + case VFS_SEEK_SET: + new_offset = offset; + break; + case VFS_SEEK_CUR: + new_offset = (int32_t)f->offset + offset; + break; + case VFS_SEEK_END: + new_offset = (int32_t)f->node.size + offset; + break; + default: + return -1; + } + + if (new_offset < 0) return -1; + f->offset = (uint32_t)new_offset; + return new_offset; +} + +int vfs_readdir(const char *path, uint32_t idx, vfs_dirent_t *out) { + vfs_node_t node; + if (resolve_path(path, &node) != 0) { + return -1; + } + + if (node.type != VFS_DIRECTORY) return -1; + if (!node.ops || !node.ops->readdir) return -1; + + return node.ops->readdir(&node, idx, out); +} + +int vfs_stat(const char *path, vfs_node_t *out) { + return resolve_path(path, out); +} diff --git a/src/vfs.h b/src/vfs.h new file mode 100644 index 0000000..39f57be --- /dev/null +++ b/src/vfs.h @@ -0,0 +1,197 @@ +/** + * @file vfs.h + * @brief Virtual Filesystem (VFS) subsystem. + * + * Provides a unified interface for file and directory operations across + * different filesystem implementations. Filesystem drivers register + * themselves and can be mounted at specific paths. + */ + +#ifndef VFS_H +#define VFS_H + +#include +#include + +/** Maximum number of open files across all processes. */ +#define VFS_MAX_OPEN_FILES 256 + +/** Maximum number of mounted filesystems. */ +#define VFS_MAX_MOUNTS 16 + +/** Maximum path length. */ +#define VFS_MAX_PATH 256 + +/** Maximum filename length. */ +#define VFS_MAX_NAME 128 + +/** File types. */ +#define VFS_FILE 0x01 +#define VFS_DIRECTORY 0x02 +#define VFS_CHARDEV 0x03 +#define VFS_BLOCKDEV 0x04 +#define VFS_SYMLINK 0x06 + +/** Seek origins. */ +#define VFS_SEEK_SET 0 +#define VFS_SEEK_CUR 1 +#define VFS_SEEK_END 2 + +/** Forward declarations. */ +struct vfs_node; +struct vfs_dirent; +struct vfs_fs_ops; + +/** + * Directory entry, returned by readdir. + */ +typedef struct vfs_dirent { + char name[VFS_MAX_NAME]; /**< Entry name. */ + uint32_t inode; /**< Inode number (fs-specific). */ + uint8_t type; /**< VFS_FILE, VFS_DIRECTORY, etc. */ +} vfs_dirent_t; + +/** + * VFS node representing a file or directory. + */ +typedef struct vfs_node { + char name[VFS_MAX_NAME]; /**< Node name. */ + uint8_t type; /**< VFS_FILE, VFS_DIRECTORY, etc. */ + uint32_t size; /**< File size in bytes. */ + uint32_t inode; /**< Inode number (fs-specific). */ + uint32_t mode; /**< Permissions/mode. */ + + /** Filesystem-specific operations. */ + struct vfs_fs_ops *ops; + + /** Opaque pointer for the filesystem driver. */ + void *fs_data; + + /** Mount index (which mount this node belongs to). */ + int mount_idx; +} vfs_node_t; + +/** + * Filesystem operations provided by each filesystem driver. + */ +typedef struct vfs_fs_ops { + /** Open a file. Returns 0 on success. */ + int (*open)(vfs_node_t *node, uint32_t flags); + + /** Close a file. */ + void (*close)(vfs_node_t *node); + + /** Read up to `size` bytes at `offset`. Returns bytes read, or -1. */ + int32_t (*read)(vfs_node_t *node, uint32_t offset, uint32_t size, void *buf); + + /** Write up to `size` bytes at `offset`. Returns bytes written, or -1. */ + int32_t (*write)(vfs_node_t *node, uint32_t offset, uint32_t size, const void *buf); + + /** Read directory entry at index `idx`. Returns 0 on success, -1 at end. */ + int (*readdir)(vfs_node_t *dir, uint32_t idx, vfs_dirent_t *out); + + /** Look up a child by name within a directory. Returns 0 on success. */ + int (*finddir)(vfs_node_t *dir, const char *name, vfs_node_t *out); +} vfs_fs_ops_t; + +/** + * Mount point entry. + */ +typedef struct vfs_mount { + char path[VFS_MAX_PATH]; /**< Mount path (e.g., "/initrd"). */ + vfs_fs_ops_t *ops; /**< Filesystem operations. */ + void *fs_data; /**< Filesystem-specific data. */ + int active; /**< 1 if mounted, 0 if free. */ +} vfs_mount_t; + +/** + * Open file descriptor. + */ +typedef struct vfs_fd { + vfs_node_t node; /**< The file node. */ + uint32_t offset; /**< Current read/write offset. */ + uint32_t flags; /**< Open flags. */ + int active; /**< 1 if in use, 0 if free. */ +} vfs_fd_t; + +/** + * Initialize the VFS subsystem. + */ +void init_vfs(void); + +/** + * Mount a filesystem at the given path. + * + * @param path Mount point path (e.g., "/initrd"). + * @param ops Filesystem operations. + * @param fs_data Filesystem-specific data pointer. + * @return 0 on success, -1 on failure. + */ +int vfs_mount(const char *path, vfs_fs_ops_t *ops, void *fs_data); + +/** + * Open a file by path. + * + * @param path Absolute path to the file. + * @param flags Open flags (currently unused). + * @return File descriptor (>= 0), or -1 on failure. + */ +int vfs_open(const char *path, uint32_t flags); + +/** + * Close an open file descriptor. + * + * @param fd File descriptor. + */ +void vfs_close(int fd); + +/** + * Read from an open file. + * + * @param fd File descriptor. + * @param buf Buffer to read into. + * @param size Maximum bytes to read. + * @return Bytes read, or -1 on error. + */ +int32_t vfs_read(int fd, void *buf, uint32_t size); + +/** + * Write to an open file. + * + * @param fd File descriptor. + * @param buf Buffer to write from. + * @param size Bytes to write. + * @return Bytes written, or -1 on error. + */ +int32_t vfs_write(int fd, const void *buf, uint32_t size); + +/** + * Seek within an open file. + * + * @param fd File descriptor. + * @param offset Offset to seek to. + * @param whence VFS_SEEK_SET, VFS_SEEK_CUR, or VFS_SEEK_END. + * @return New offset, or -1 on error. + */ +int32_t vfs_seek(int fd, int32_t offset, int whence); + +/** + * Read a directory entry. + * + * @param path Path to the directory. + * @param idx Entry index (0-based). + * @param out Output directory entry. + * @return 0 on success, -1 at end or on error. + */ +int vfs_readdir(const char *path, uint32_t idx, vfs_dirent_t *out); + +/** + * Stat a file (get its node info). + * + * @param path Path to the file. + * @param out Output node. + * @return 0 on success, -1 on failure. + */ +int vfs_stat(const char *path, vfs_node_t *out); + +#endif /* VFS_H */