diff options
Diffstat (limited to 'na/fs')
| -rw-r--r-- | na/fs/chardev.cc | 43 | ||||
| -rw-r--r-- | na/fs/dcache.cc | 287 | ||||
| -rw-r--r-- | na/fs/devfs.cc | 137 | ||||
| -rw-r--r-- | na/fs/fd.cc | 216 | ||||
| -rw-r--r-- | na/fs/file_table.cc | 164 | ||||
| -rw-r--r-- | na/fs/fs_struct.cc | 90 | ||||
| -rw-r--r-- | na/fs/initramfs.cc | 285 | ||||
| -rw-r--r-- | na/fs/inode.cc | 166 | ||||
| -rw-r--r-- | na/fs/module.mk | 17 | ||||
| -rw-r--r-- | na/fs/namei.cc | 250 | ||||
| -rw-r--r-- | na/fs/pipe.cc | 278 | ||||
| -rw-r--r-- | na/fs/ramfs/file.cc | 216 | ||||
| -rw-r--r-- | na/fs/ramfs/inode.cc | 164 | ||||
| -rw-r--r-- | na/fs/ramfs/super.cc | 154 | ||||
| -rw-r--r-- | na/fs/super.cc | 95 | ||||
| -rw-r--r-- | na/fs/vfs_init.cc | 78 |
16 files changed, 2640 insertions, 0 deletions
diff --git a/na/fs/chardev.cc b/na/fs/chardev.cc new file mode 100644 index 0000000..fa96276 --- /dev/null +++ b/na/fs/chardev.cc @@ -0,0 +1,43 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +/* + * chardev.cc — Character device registry + * + * Simple linked list of registered cdevs, searched by dev number. + */ + +#include <lume/chardev.h> +#include <lume/list.h> +#include <lume/klog.h> +#include <kernel/sync/spinlock.h> + +static list_node g_cdev_list; +static Spinlock g_cdev_lock; + +void cdev_init() +{ + list_init(&g_cdev_list); +} + +void cdev_register(cdev* dev) +{ + LockGuard guard(g_cdev_lock); + list_init(&dev->list); + list_add_tail(&dev->list, &g_cdev_list); + printk("[cdev] registered '%s' (%d,%d)\n", + dev->name, MAJOR(dev->dev), MINOR(dev->dev)); +} + +cdev* cdev_find(uint32 dev_nr) +{ + LockGuard guard(g_cdev_lock); + list_node* pos; + for (pos = g_cdev_list.prev; pos != &g_cdev_list; pos = pos->prev) { + cdev* cd = container_of(pos, cdev, list); + if (cd->dev == dev_nr) { + return cd; + } + } + return nullptr; +} diff --git a/na/fs/dcache.cc b/na/fs/dcache.cc new file mode 100644 index 0000000..94ab287 --- /dev/null +++ b/na/fs/dcache.cc @@ -0,0 +1,287 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +/* + * dcache.cc — Directory entry cache + * + * Provides dentry allocation, lookup, instantiation, and d_path. + */ + +#include <lume/dcache.h> +#include <lume/fs.h> +#include <lume/file.h> +#include <lume/slab.h> +#include <lume/errno.h> +#include <lume/fs_types.h> + +/* ================================================================== */ +/* Hash function */ +/* ================================================================== */ + +uint32 full_name_hash(const char* name, uint32 len) +{ + uint32 hash = 0; + for (uint32 i = 0; i < len; i++) + hash = hash * 31 + static_cast<uint8>(name[i]); + return hash; +} + +/* ================================================================== */ +/* d_set_type — set DCACHE_*_TYPE bits from inode mode */ +/* ================================================================== */ + +void d_set_type(dentry* de, uint32 i_mode) +{ + de->d_flags &= ~DCACHE_ENTRY_TYPE; + + if (S_ISDIR(i_mode)) + de->d_flags |= DCACHE_DIRECTORY_TYPE; + else if (S_ISLNK(i_mode)) + de->d_flags |= DCACHE_SYMLINK_TYPE; + else if (S_ISREG(i_mode)) + de->d_flags |= DCACHE_REGULAR_TYPE; + else + de->d_flags |= DCACHE_SPECIAL_TYPE; +} + +/* ================================================================== */ +/* d_alloc — allocate a dentry under a parent */ +/* ================================================================== */ + +static void dentry_init(dentry* de, dentry* parent, const qstr* name) +{ + de->d_flags = DCACHE_MISS_TYPE; /* negative until instantiated */ + de->d_parent = parent ? parent : de; + de->d_inode = nullptr; + de->d_op = nullptr; + de->d_sb = parent ? parent->d_sb : nullptr; + de->d_time = 0; + de->d_fsdata = nullptr; + de->d_lockref.store(1); + + list_init(&de->d_lru); + list_init(&de->d_child); + list_init(&de->d_children); + list_init(&de->d_alias); + + /* Copy name */ + if (name && name->name && name->len > 0) { + uint32 len = name->len; + if (len < DNAME_INLINE_LEN) { + for (uint32 i = 0; i < len; i++) + de->d_iname[i] = name->name[i]; + de->d_iname[len] = '\0'; + de->d_name.name = reinterpret_cast<const char*>(de->d_iname); + } else { + /* Heap-allocate for long names */ + char* buf = new char[len + 1]; + for (uint32 i = 0; i < len; i++) + buf[i] = name->name[i]; + buf[len] = '\0'; + de->d_name.name = buf; + } + de->d_name.len = len; + de->d_name.hash = name->hash ? name->hash : full_name_hash(name->name, len); + } else { + de->d_iname[0] = '\0'; + de->d_name.name = reinterpret_cast<const char*>(de->d_iname); + de->d_name.len = 0; + de->d_name.hash = 0; + } +} + +dentry* d_alloc(dentry* parent, const qstr* name) +{ + auto* de = new dentry{}; + if (!de) return nullptr; + + dentry_init(de, parent, name); + de->d_sb = parent->d_sb; + + /* Use sb default d_op if set */ + if (parent->d_sb->s_d_op) + de->d_op = parent->d_sb->s_d_op; + + /* Link into parent's children list */ + if (parent) { + LockGuard guard(parent->d_lock); + list_add_tail(&de->d_child, &parent->d_children); + dget(parent); /* child holds ref on parent */ + } + + return de; +} + +dentry* d_alloc_anon(super_block* sb) +{ + auto* de = new dentry{}; + if (!de) return nullptr; + + dentry_init(de, nullptr, nullptr); + de->d_sb = sb; + de->d_parent = de; /* root: parent is self */ + + return de; +} + +/* ================================================================== */ +/* d_instantiate — bind an inode to a dentry */ +/* ================================================================== */ + +void d_instantiate(dentry* de, inode* ip) +{ + if (!ip) return; + + LockGuard guard(de->d_lock); + de->d_inode = ip; + d_set_type(de, ip->i_mode); + + /* Add to inode's alias list */ + list_add_tail(&de->d_alias, &ip->i_dentry); +} + +/* ================================================================== */ +/* d_make_root — create root dentry for a superblock */ +/* ================================================================== */ + +dentry* d_make_root(inode* root_inode) +{ + if (!root_inode) return nullptr; + + dentry* de = d_alloc_anon(root_inode->i_sb); + if (!de) { + iput(root_inode); + return nullptr; + } + + d_instantiate(de, root_inode); + return de; +} + +/* ================================================================== */ +/* d_add — combined d_instantiate + d_rehash */ +/* ================================================================== */ + +void d_add(dentry* de, inode* ip) +{ + d_instantiate(de, ip); + /* d_rehash would add to dcache hash table; simplified for now */ +} + +/* ================================================================== */ +/* d_lookup — search children of parent for a name match */ +/* ================================================================== */ + +dentry* d_lookup(const dentry* parent, const qstr* name) +{ + LockGuard guard(const_cast<Spinlock&>(parent->d_lock)); + + list_node* pos; + list_for_each(pos, &parent->d_children) { + dentry* child = list_entry(pos, dentry, d_child); + + if (child->d_name.len != name->len) + continue; + + /* Compare names */ + const char* a = child->d_name.name; + const char* b = name->name; + uint32 len = name->len; + bool match = true; + for (uint32 i = 0; i < len; i++) { + if (a[i] != b[i]) { match = false; break; } + } + if (match) { + dget(child); + return child; + } + } + + return nullptr; +} + +/* ================================================================== */ +/* dget / dput — dentry reference counting */ +/* ================================================================== */ + +dentry* dget(dentry* de) +{ + if (de) + de->d_lockref.fetch_add(1); + return de; +} + +void dput(dentry* de) +{ + if (!de) return; + + int old = de->d_lockref.fetch_sub(1); + if (old > 1) return; + + /* Last reference — could free, but for ramfs we keep dentries alive. + * A real implementation would check d_op->d_delete and possibly + * add to LRU for later reclaim. For now, just leak (Phase 1). */ +} + +/* ================================================================== */ +/* d_drop / d_delete */ +/* ================================================================== */ + +void d_drop(dentry* de) +{ + /* Remove from parent's children list */ + LockGuard guard(de->d_lock); + if (!list_empty(&de->d_child)) + list_del_init(&de->d_child); +} + +void d_delete(dentry* de) +{ + d_drop(de); +} + +/* ================================================================== */ +/* d_path — build absolute pathname from a path */ +/* ================================================================== */ + +char* d_path(const path* p, char* buf, int buflen) +{ + if (!p || !p->de || buflen <= 0) + return nullptr; + + /* Build path by walking up d_parent chain, writing backwards */ + char* end = buf + buflen - 1; + *end = '\0'; + + dentry* de = p->de; + + if (d_is_root(de)) { + if (end <= buf) return nullptr; + *(--end) = '/'; + return end; + } + + while (!d_is_root(de)) { + uint32 len = de->d_name.len; + end -= len; + if (end <= buf) return nullptr; + + const char* name = de->d_name.name; + for (uint32 i = 0; i < len; i++) + end[i] = name[i]; + + *(--end) = '/'; + de = de->d_parent; + } + + return end; +} + +/* ================================================================== */ +/* dcache_init */ +/* ================================================================== */ + +void dcache_init() +{ + /* Future: init dcache hash table */ +} diff --git a/na/fs/devfs.cc b/na/fs/devfs.cc new file mode 100644 index 0000000..b4b2cf3 --- /dev/null +++ b/na/fs/devfs.cc @@ -0,0 +1,137 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +/* + * devfs.cc — /dev population and chardev-aware open + * + * Creates /dev as a subdirectory under the ramfs root, then + * populates device nodes. On open(), if the inode is S_IFCHR, + * we look up the cdev by i_rdev and use its fops. + */ + +#include <lume/fs.h> +#include <lume/dcache.h> +#include <lume/file.h> +#include <lume/chardev.h> +#include <lume/mount.h> +#include <lume/klog.h> +#include <lume/errno.h> +#include <lume/fs_types.h> + +/* ================================================================== */ +/* devfs_create_node — create a char device inode under parent */ +/* ================================================================== */ + +static int devfs_create_node(dentry* parent, const char* name, + uint32 major, uint32 minor) +{ + inode* dir_ip = parent->d_inode; + if (!dir_ip || !dir_ip->i_op || !dir_ip->i_op->mknod) { + return -EPERM; + } + + qstr q{}; + q.name = name; + q.len = 0; + for (const char* s = name; *s; s++) { + q.len++; + } + + dentry* de = d_alloc(parent, &q); + if (!de) { + return -ENOMEM; + } + + int err = dir_ip->i_op->mknod(dir_ip, de, S_IFCHR | 0666, + MKDEV(major, minor)); + if (err) { + return err; + } + + return 0; +} + +/* ================================================================== */ +/* devfs_open — chardev-aware open hook */ +/* ================================================================== */ + +/* + * Called from sys_openat when the inode is S_IFCHR. + * Replaces f_op with the chardev's file_operations. + */ +int devfs_chrdev_open(inode* ip, file* fp) +{ + if (!S_ISCHR(ip->i_mode)) { + return 0; + } + + cdev* cd = cdev_find(ip->i_rdev); + if (!cd || !cd->fops) { + return -ENXIO; /* No such device */ + } + + fp->f_op = cd->fops; + + /* Call the device's own open if it has one */ + if (fp->f_op->open) { + return fp->f_op->open(ip, fp); + } + + return 0; +} + +/* ================================================================== */ +/* devfs_init — create /dev and populate device nodes */ +/* ================================================================== */ + +void devfs_init() +{ + if (!g_root_dentry || !g_root_dentry->d_inode) { + printk(KERN_ERR "[devfs] no root filesystem!\n"); + return; + } + + /* Create /dev directory — or reuse if initramfs already created it. + * Linux: devtmpfs similarly handles pre-existing directories. */ + inode* root_ip = g_root_dentry->d_inode; + + qstr dev_name{}; + dev_name.name = "dev"; + dev_name.len = 3; + dev_name.hash = full_name_hash("dev", 3); + + dentry* dev_de = d_lookup(g_root_dentry, &dev_name); + if (dev_de) { + /* /dev already exists (from initramfs) — reuse it */ + printk("[devfs] reusing existing /dev directory\n"); + } else { + /* Create /dev from scratch */ + if (!root_ip->i_op || !root_ip->i_op->mkdir) { + printk(KERN_ERR "[devfs] root has no mkdir!\n"); + return; + } + + dev_de = d_alloc(g_root_dentry, &dev_name); + if (!dev_de) { + printk(KERN_ERR "[devfs] failed to alloc /dev dentry\n"); + return; + } + + int err = root_ip->i_op->mkdir(root_ip, dev_de, S_IFDIR | 0755); + if (err) { + printk(KERN_ERR "[devfs] mkdir /dev failed: %d\n", err); + return; + } + } + + /* Populate: /dev/console (5,1) */ + devfs_create_node(dev_de, "console", 5, 1); + + /* Populate: /dev/null (1,3) */ + devfs_create_node(dev_de, "null", 1, 3); + + /* Populate: /dev/zero (1,5) */ + devfs_create_node(dev_de, "zero", 1, 5); + + printk("[devfs] /dev populated (console, null, zero)\n"); +} diff --git a/na/fs/fd.cc b/na/fs/fd.cc new file mode 100644 index 0000000..5fc8ab0 --- /dev/null +++ b/na/fs/fd.cc @@ -0,0 +1,216 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +/* + * fd.cc — File descriptor table management + */ + +#include <lume/fdtable.h> +#include <lume/file.h> +#include <lume/slab.h> +#include <lume/errno.h> + +/* ================================================================== */ +/* Bitmap helpers */ +/* ================================================================== */ + +static inline bool bit_test(const uint64* bmap, int bit) { + return (bmap[bit / 64] >> (bit % 64)) & 1; +} +static inline void bit_set(uint64* bmap, int bit) { + bmap[bit / 64] |= (1ULL << (bit % 64)); +} +static inline void bit_clear(uint64* bmap, int bit) { + bmap[bit / 64] &= ~(1ULL << (bit % 64)); +} + +/* ================================================================== */ +/* alloc_files_struct */ +/* ================================================================== */ + +files_struct* alloc_files_struct() +{ + auto* files = new files_struct{}; + if (!files) return nullptr; + + files->count.store(1); + files->next_fd = 0; + files->max_fds = NR_OPEN_DEFAULT; + + for (int i = 0; i < NR_OPEN_DEFAULT; i++) + files->fd_array[i] = nullptr; + + int bmap_words = (NR_OPEN_DEFAULT + 63) / 64; + for (int i = 0; i < bmap_words; i++) { + files->close_on_exec_init[i] = 0; + files->open_fds_init[i] = 0; + } + + return files; +} + +/* ================================================================== */ +/* put_files_struct — drop reference, free on last */ +/* ================================================================== */ + +void put_files_struct(files_struct* files) +{ + if (!files) return; + + int old = files->count.fetch_sub(1); + if (old > 1) return; + + /* Close all open fds */ + for (int i = 0; i < files->max_fds; i++) { + if (files->fd_array[i]) { + fput(files->fd_array[i]); + files->fd_array[i] = nullptr; + } + } + + delete files; +} + +/* ================================================================== */ +/* dup_fd — duplicate entire fd table (for fork) */ +/* ================================================================== */ + +files_struct* dup_fd(files_struct* old_files) +{ + auto* new_files = alloc_files_struct(); + if (!new_files) return nullptr; + + LockGuard guard(old_files->file_lock); + + for (int i = 0; i < old_files->max_fds; i++) { + file* fp = old_files->fd_array[i]; + if (fp) { + get_file(fp); + new_files->fd_array[i] = fp; + } + } + + /* Copy bitmaps */ + int bmap_words = (NR_OPEN_DEFAULT + 63) / 64; + for (int i = 0; i < bmap_words; i++) { + new_files->open_fds_init[i] = old_files->open_fds_init[i]; + new_files->close_on_exec_init[i] = old_files->close_on_exec_init[i]; + } + + new_files->next_fd = old_files->next_fd; + + return new_files; +} + +/* ================================================================== */ +/* alloc_fd — find lowest free fd >= start */ +/* ================================================================== */ + +int alloc_fd(files_struct* files, unsigned int start) +{ + LockGuard guard(files->file_lock); + + for (int fd = static_cast<int>(start); fd < files->max_fds; fd++) { + if (!bit_test(files->open_fds_init, fd)) { + bit_set(files->open_fds_init, fd); + files->fd_array[fd] = nullptr; /* placeholder, fd_install sets real file */ + if (fd >= files->next_fd) + files->next_fd = fd + 1; + return fd; + } + } + + return -EMFILE; +} + +/* ================================================================== */ +/* fd_install — place a file* at a given fd */ +/* ================================================================== */ + +void fd_install(files_struct* files, int fd, file* fp) +{ + LockGuard guard(files->file_lock); + files->fd_array[fd] = fp; +} + +/* ================================================================== */ +/* fcheck — look up file by fd (no refcount bump) */ +/* ================================================================== */ + +file* fcheck(files_struct* files, int fd) +{ + if (fd < 0 || fd >= files->max_fds) return nullptr; + return files->fd_array[fd]; +} + +/* ================================================================== */ +/* close_fd */ +/* ================================================================== */ + +int close_fd(files_struct* files, int fd) +{ + file* fp; + { + LockGuard guard(files->file_lock); + if (fd < 0 || fd >= files->max_fds) return -EBADF; + + fp = files->fd_array[fd]; + if (!fp) return -EBADF; + + files->fd_array[fd] = nullptr; + bit_clear(files->open_fds_init, fd); + bit_clear(files->close_on_exec_init, fd); + } + + fput(fp); + return 0; +} + +/* ================================================================== */ +/* do_dup2 — duplicate oldfd to newfd */ +/* ================================================================== */ + +int do_dup2(files_struct* files, int oldfd, int newfd, int flags) +{ + if (oldfd == newfd) return -EINVAL; + + file* existing = nullptr; + { + LockGuard guard(files->file_lock); + + if (oldfd < 0 || oldfd >= files->max_fds) return -EBADF; + if (newfd < 0 || newfd >= files->max_fds) return -EBADF; + + file* old_fp = files->fd_array[oldfd]; + if (!old_fp) return -EBADF; + + existing = files->fd_array[newfd]; + + get_file(old_fp); + files->fd_array[newfd] = old_fp; + bit_set(files->open_fds_init, newfd); + + if (flags & O_CLOEXEC) + bit_set(files->close_on_exec_init, newfd); + else + bit_clear(files->close_on_exec_init, newfd); + } + + if (existing) + fput(existing); + + return newfd; +} + +/* ================================================================== */ +/* do_close_on_exec — close all fds with CLOEXEC set */ +/* ================================================================== */ + +void do_close_on_exec(files_struct* files) +{ + for (int fd = 0; fd < files->max_fds; fd++) { + if (bit_test(files->close_on_exec_init, fd)) { + close_fd(files, fd); + } + } +} diff --git a/na/fs/file_table.cc b/na/fs/file_table.cc new file mode 100644 index 0000000..06095c1 --- /dev/null +++ b/na/fs/file_table.cc @@ -0,0 +1,164 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +/* + * file_table.cc — Open file allocation and reference counting + */ + +#include <lume/file.h> +#include <lume/fs.h> +#include <lume/dcache.h> +#include <lume/slab.h> +#include <lume/errno.h> + +/* ================================================================== */ +/* alloc_empty_file */ +/* ================================================================== */ + +file* alloc_empty_file(int flags) +{ + auto* fp = new file{}; + if (!fp) return nullptr; + + fp->f_count.store(1); + fp->f_flags = flags; + fp->f_mode = 0; + fp->f_op = nullptr; + fp->f_mapping = nullptr; + fp->f_inode = nullptr; + fp->f_pos = 0; + fp->private_data = nullptr; + fp->f_path.mnt = nullptr; + fp->f_path.de = nullptr; + + /* Derive f_mode from O_* flags */ + int acc = flags & O_ACCMODE; + if (acc == O_RDONLY) + fp->f_mode = FMODE_READ | FMODE_CAN_READ; + else if (acc == O_WRONLY) + fp->f_mode = FMODE_WRITE | FMODE_CAN_WRITE; + else if (acc == O_RDWR) + fp->f_mode = FMODE_READ | FMODE_WRITE | FMODE_CAN_READ | FMODE_CAN_WRITE; + + fp->f_mode |= FMODE_LSEEK | FMODE_PREAD | FMODE_PWRITE; + + return fp; +} + +/* ================================================================== */ +/* get_file / fput */ +/* ================================================================== */ + +file* get_file(file* f) +{ + f->f_count.fetch_add(1); + return f; +} + +void fput(file* fp) +{ + if (!fp) return; + + int old = fp->f_count.fetch_sub(1); + if (old > 1) return; + + /* Last reference — release */ + if (fp->f_op && fp->f_op->release) + fp->f_op->release(fp->f_inode, fp); + + if (fp->f_path.de) + dput(fp->f_path.de); + + /* Future: mntput(fp->f_path.mnt) */ + + delete fp; +} + +/* ================================================================== */ +/* vfs_read / vfs_write / vfs_llseek / vfs_fstat */ +/* ================================================================== */ + +int64 vfs_read(file* fp, char* buf, uint64 count, int64* pos) +{ + if (!(fp->f_mode & FMODE_CAN_READ)) + return -EBADF; + if (!fp->f_op || !fp->f_op->read) + return -EINVAL; + + return fp->f_op->read(fp, buf, count, pos); +} + +int64 vfs_write(file* fp, const char* buf, uint64 count, int64* pos) +{ + if (!(fp->f_mode & FMODE_CAN_WRITE)) + return -EBADF; + if (!fp->f_op || !fp->f_op->write) + return -EINVAL; + + return fp->f_op->write(fp, buf, count, pos); +} + +int64 vfs_llseek(file* fp, int64 offset, int whence) +{ + if (fp->f_op && fp->f_op->llseek) + return fp->f_op->llseek(fp, offset, whence); + return generic_file_llseek(fp, offset, whence); +} + +int64 generic_file_llseek(file* fp, int64 offset, int whence) +{ + int64 new_pos; + switch (whence) { + case SEEK_SET: + new_pos = offset; + break; + case SEEK_CUR: + new_pos = fp->f_pos + offset; + break; + case SEEK_END: + new_pos = fp->f_inode->i_size + offset; + break; + default: + return -EINVAL; + } + + if (new_pos < 0) return -EINVAL; + fp->f_pos = new_pos; + return new_pos; +} + +int vfs_fstat(file* fp, kstat* stat) +{ + inode* ip = file_inode(fp); + if (!ip) return -EBADF; + + if (ip->i_op && ip->i_op->getattr) { + return ip->i_op->getattr(&fp->f_path, stat, 0, 0); + } + + generic_fillattr(ip, stat); + return 0; +} + +/* ================================================================== */ +/* path_get / path_put (simplified) */ +/* ================================================================== */ + +void path_get(const path* p) +{ + if (p->de) dget(p->de); + /* Future: mntget(p->mnt) */ +} + +void path_put(const path* p) +{ + if (p->de) dput(p->de); + /* Future: mntput(p->mnt) */ +} + +/* ================================================================== */ +/* mntget / mntput (stubs for Phase 1) */ +/* ================================================================== */ + +vfsmount* mntget(vfsmount* mnt) { return mnt; } +void mntput(vfsmount*) {} diff --git a/na/fs/fs_struct.cc b/na/fs/fs_struct.cc new file mode 100644 index 0000000..090819f --- /dev/null +++ b/na/fs/fs_struct.cc @@ -0,0 +1,90 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +/* + * fs_struct.cc — Per-task filesystem context (cwd, root) + */ + +#include <lume/fs_struct.h> +#include <lume/slab.h> + +/* ================================================================== */ +/* alloc_fs_struct */ +/* ================================================================== */ + +fs_struct* alloc_fs_struct() +{ + auto* fs = new fs_struct{}; + if (!fs) return nullptr; + + fs->count.store(1); + fs->umask = 022; + fs->root = {nullptr, nullptr}; + fs->pwd = {nullptr, nullptr}; + + return fs; +} + +/* ================================================================== */ +/* put_fs_struct */ +/* ================================================================== */ + +void put_fs_struct(fs_struct* fs) +{ + if (!fs) return; + + int old = fs->count.fetch_sub(1); + if (old > 1) return; + + path_put(&fs->root); + path_put(&fs->pwd); + delete fs; +} + +/* ================================================================== */ +/* copy_fs_struct — for fork without CLONE_FS */ +/* ================================================================== */ + +fs_struct* copy_fs_struct(const fs_struct* old_fs) +{ + auto* fs = alloc_fs_struct(); + if (!fs) return nullptr; + + fs->umask = old_fs->umask; + + fs->root = old_fs->root; + path_get(&fs->root); + + fs->pwd = old_fs->pwd; + path_get(&fs->pwd); + + return fs; +} + +/* ================================================================== */ +/* set_fs_root / set_fs_pwd */ +/* ================================================================== */ + +void set_fs_root(fs_struct* fs, const path* p) +{ + path old_root; + { + LockGuard guard(fs->lock); + old_root = fs->root; + fs->root = *p; + path_get(p); + } + path_put(&old_root); +} + +void set_fs_pwd(fs_struct* fs, const path* p) +{ + path old_pwd; + { + LockGuard guard(fs->lock); + old_pwd = fs->pwd; + fs->pwd = *p; + path_get(p); + } + path_put(&old_pwd); +} diff --git a/na/fs/initramfs.cc b/na/fs/initramfs.cc new file mode 100644 index 0000000..ba10a3e --- /dev/null +++ b/na/fs/initramfs.cc @@ -0,0 +1,285 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +/* + * initramfs.cc — Unpack embedded CPIO archive into ramfs + * + * The build system embeds a newc-format CPIO archive into the kernel + * ELF as the section .init.ramfs. The linker script exports + * __initramfs_start / __initramfs_end symbols. + * + * At boot, populate_rootfs() walks the CPIO entries and creates + * directories and files in the already-mounted ramfs root. + * + * Reference: Linux init/initramfs.c + * + * CPIO "newc" header format (110 bytes, ASCII hex fields): + * magic[6] "070701" + * ino[8] mode[8] uid[8] gid[8] nlink[8] mtime[8] + * filesize[8] devmajor[8] devminor[8] rdevmajor[8] rdevminor[8] + * namesize[8] check[8] + * filename (namesize bytes, NUL terminated, padded to 4-byte boundary) + * file data (filesize bytes, padded to 4-byte boundary) + */ + +#include <lume/klog.h> +#include <lume/fs.h> +#include <lume/dcache.h> +#include <lume/file.h> +#include <lume/errno.h> +#include <lume/fs_types.h> + +/* Linker-provided symbols for the embedded CPIO archive */ +extern "C" const char __initramfs_start[]; +extern "C" const char __initramfs_end[]; + +/* ramfs inode creation (from fs/ramfs/inode.cc) */ +extern inode* ramfs_get_inode(super_block* sb, const inode* dir, + uint32 mode, uint32 rdev); + +/* Global root dentry (from fs/vfs_init.cc) */ +extern dentry* g_root_dentry; + +/* ================================================================== */ +/* CPIO newc header parsing */ +/* ================================================================== */ + +struct cpio_header { + char magic[6]; + char ino[8]; + char mode[8]; + char uid[8]; + char gid[8]; + char nlink[8]; + char mtime[8]; + char filesize[8]; + char devmajor[8]; + char devminor[8]; + char rdevmajor[8]; + char rdevminor[8]; + char namesize[8]; + char check[8]; +}; + +static constexpr int kCpioHeaderSize = 110; + +static uint32 parse_hex8(const char* s) +{ + uint32 val = 0; + for (int i = 0; i < 8; i++) { + char c = s[i]; + uint32 nibble; + if (c >= '0' && c <= '9') nibble = c - '0'; + else if (c >= 'a' && c <= 'f') nibble = c - 'a' + 10; + else if (c >= 'A' && c <= 'F') nibble = c - 'A' + 10; + else return val; + val = (val << 4) | nibble; + } + return val; +} + +static inline uint32 align4(uint32 v) +{ + return (v + 3) & ~3U; +} + +/* ================================================================== */ +/* Path resolution helpers */ +/* ================================================================== */ + +static dentry* cpio_mkdir_p(dentry* root, const char* path, + const char** out_name) +{ + dentry* cur = root; + + while (*path == '/') path++; + + while (true) { + const char* slash = path; + while (*slash && *slash != '/') slash++; + + if (*slash == '\0') { + *out_name = path; + return cur; + } + + uint32 len = static_cast<uint32>(slash - path); + + qstr name; + name.name = path; + name.len = len; + name.hash = full_name_hash(path, len); + + dentry* child = d_lookup(cur, &name); + if (!child) { + child = d_alloc(cur, &name); + if (!child) return nullptr; + + inode* ip = ramfs_get_inode(cur->d_inode->i_sb, cur->d_inode, + S_IFDIR | 0755, 0); + if (!ip) return nullptr; + + d_instantiate(child, ip); + dget(child); + inc_nlink(cur->d_inode); + } + + cur = child; + path = slash + 1; + while (*path == '/') path++; + } +} + +/* ================================================================== */ +/* populate_rootfs — the main entry point */ +/* ================================================================== */ + +void populate_rootfs() +{ + const char* p = __initramfs_start; + const char* end = __initramfs_end; + uint64 archive_size = static_cast<uint64>(end - p); + + if (archive_size == 0) { + printk("[initramfs] no embedded archive found\n"); + return; + } + + printk("[initramfs] unpacking %llu bytes...\n", archive_size); + + int file_count = 0; + int dir_count = 0; + + while (p + kCpioHeaderSize <= end) { + auto* hdr = reinterpret_cast<const cpio_header*>(p); + + /* Verify magic */ + if (hdr->magic[0] != '0' || hdr->magic[1] != '7' || + hdr->magic[2] != '0' || hdr->magic[3] != '7' || + hdr->magic[4] != '0' || hdr->magic[5] != '1') { + printk("[initramfs] bad cpio magic at offset %llu\n", + static_cast<uint64>(p - __initramfs_start)); + break; + } + + uint32 mode = parse_hex8(hdr->mode); + uint32 filesize = parse_hex8(hdr->filesize); + uint32 namesize = parse_hex8(hdr->namesize); + + const char* name = p + kCpioHeaderSize; + uint32 name_padded = align4(kCpioHeaderSize + namesize); + const char* data = p + name_padded; + uint32 data_padded = align4(filesize); + + const char* next = p + name_padded + data_padded; + + /* Check for TRAILER!!! end-of-archive marker */ + if (namesize == 11 && name[0] == 'T' && name[1] == 'R' && + name[2] == 'A' && name[3] == 'I' && name[4] == 'L' && + name[5] == 'E' && name[6] == 'R' && name[7] == '!' && + name[8] == '!' && name[9] == '!') { + break; + } + + /* Skip "." entry */ + if (namesize == 2 && name[0] == '.' && name[1] == '\0') { + p = next; + continue; + } + + /* Resolve parent directory, creating intermediate dirs */ + const char* basename = nullptr; + dentry* parent = cpio_mkdir_p(g_root_dentry, name, &basename); + if (!parent || !basename || basename[0] == '\0') { + p = next; + continue; + } + + /* Build qstr for the final component */ + uint32 blen = 0; + while (basename[blen] && basename[blen] != '/') blen++; + + qstr bname; + bname.name = basename; + bname.len = blen; + bname.hash = full_name_hash(basename, blen); + + if (S_ISDIR(mode)) { + dentry* existing = d_lookup(parent, &bname); + if (!existing) { + dentry* de = d_alloc(parent, &bname); + if (de) { + inode* ip = ramfs_get_inode(parent->d_inode->i_sb, + parent->d_inode, + S_IFDIR | (mode & 0777), 0); + if (ip) { + d_instantiate(de, ip); + dget(de); + inc_nlink(parent->d_inode); + dir_count++; + } + } + } + } else if (S_ISREG(mode)) { + dentry* de = d_alloc(parent, &bname); + if (de) { + inode* ip = ramfs_get_inode(parent->d_inode->i_sb, + parent->d_inode, + S_IFREG | (mode & 0777), 0); + if (ip) { + d_instantiate(de, ip); + dget(de); + + /* Write file content via ramfs write path */ + if (filesize > 0 && ip->i_fop && ip->i_fop->write) { + file tmp_fp = {}; + tmp_fp.f_inode = ip; + tmp_fp.f_path.de = de; + tmp_fp.f_pos = 0; + tmp_fp.f_flags = O_WRONLY; + tmp_fp.f_count.store(1); + + int64 pos = 0; + ip->i_fop->write(&tmp_fp, data, + filesize, &pos); + } + + file_count++; + printk("[initramfs] %s (%u bytes, mode %03o)\n", + name, filesize, mode & 0777); + } + } + } else if (S_ISLNK(mode)) { + /* Symlink: file data is the target path (not NUL-terminated). + * Linux: init/initramfs.c do_symlink() */ + dentry* de = d_alloc(parent, &bname); + if (de) { + inode* ip = ramfs_get_inode(parent->d_inode->i_sb, + parent->d_inode, + S_IFLNK | 0777, 0); + if (ip) { + /* Store symlink target in inode. + * Allocate a kernel buffer and copy the target. */ + char* target = new char[filesize + 1]; + if (target) { + for (uint32 i = 0; i < filesize; i++) + target[i] = data[i]; + target[filesize] = '\0'; + ip->i_link = target; + } + + d_instantiate(de, ip); + dget(de); + file_count++; + printk("[initramfs] %s -> %s (symlink)\n", + name, target ? target : "?"); + } + } + } + + p = next; + } + + printk("[initramfs] done: %d files, %d dirs\n", + file_count, dir_count); +} diff --git a/na/fs/inode.cc b/na/fs/inode.cc new file mode 100644 index 0000000..1d3d006 --- /dev/null +++ b/na/fs/inode.cc @@ -0,0 +1,166 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +/* + * inode.cc — Inode allocation, lifecycle, and generic helpers + */ + +#include <lume/fs.h> +#include <lume/dcache.h> +#include <lume/slab.h> +#include <lume/errno.h> + +namespace { +lume::atomic<uint64> g_next_ino{1}; +} // namespace + +/* ================================================================== */ +/* new_inode — allocate a fresh inode on a superblock */ +/* ================================================================== */ + +inode* new_inode(super_block* sb) +{ + inode* ip = nullptr; + + if (sb->s_op && sb->s_op->alloc_inode) + ip = sb->s_op->alloc_inode(sb); + else + ip = new inode{}; + + if (!ip) return nullptr; + + /* Zero-init key fields */ + ip->i_sb = sb; + ip->i_ino = g_next_ino.fetch_add(1); + ip->i_mode = 0; + ip->i_flags = 0; + ip->i_uid = 0; + ip->i_gid = 0; + ip->i_nlink = 1; + ip->i_rdev = 0; + ip->i_size = 0; + ip->i_blocks = 0; + ip->i_bytes = 0; + ip->i_blkbits = sb->s_blocksize_bits; + ip->i_generation = 0; + ip->i_state = I_NEW; + ip->i_count.store(1); + ip->i_writecount.store(0); + + ip->i_atime_sec = ip->i_mtime_sec = ip->i_ctime_sec = 0; + ip->i_atime_nsec = ip->i_mtime_nsec = ip->i_ctime_nsec = 0; + + ip->i_op = nullptr; + ip->i_fop = nullptr; + ip->i_mapping = &ip->i_data; + ip->i_private = nullptr; + ip->i_pipe = nullptr; + + /* Init embedded address_space */ + ip->i_data.host = ip; + ip->i_data.a_ops = nullptr; + ip->i_data.nrpages = 0; + ip->i_data.flags = 0; + + /* Init lists */ + list_init(&ip->i_sb_list); + list_init(&ip->i_dentry); + + /* Add to superblock's inode list */ + { + LockGuard guard(sb->s_inode_list_lock); + list_add_tail(&ip->i_sb_list, &sb->s_inodes); + } + + return ip; +} + +/* ================================================================== */ +/* ihold / iput — reference counting */ +/* ================================================================== */ + +void ihold(inode* ip) +{ + ip->i_count.fetch_add(1); +} + +void iput(inode* ip) +{ + if (!ip) return; + + int old = ip->i_count.fetch_sub(1); + if (old > 1) return; + + /* Last reference dropped — evict */ + if (ip->i_sb->s_op && ip->i_sb->s_op->evict_inode) + ip->i_sb->s_op->evict_inode(ip); + + /* Remove from sb list */ + { + LockGuard guard(ip->i_sb->s_inode_list_lock); + list_del(&ip->i_sb_list); + } + + if (ip->i_sb->s_op && ip->i_sb->s_op->destroy_inode) + ip->i_sb->s_op->destroy_inode(ip); + else + delete ip; +} + +/* ================================================================== */ +/* nlink helpers */ +/* ================================================================== */ + +void set_nlink(inode* ip, uint32 nlink) { ip->i_nlink = nlink; } +void inc_nlink(inode* ip) { ip->i_nlink++; } +void drop_nlink(inode* ip) { ip->i_nlink--; } +void clear_nlink(inode* ip) { ip->i_nlink = 0; } + +/* ================================================================== */ +/* inode_init_owner */ +/* ================================================================== */ + +void inode_init_owner(inode* ip, const inode* dir, uint32 mode) +{ + ip->i_uid = 0; /* Phase 1: always root */ + ip->i_gid = 0; + ip->i_mode = mode; +} + +/* ================================================================== */ +/* generic_fillattr — fill kstat from inode (used by getattr) */ +/* ================================================================== */ + +void generic_fillattr(inode* ip, kstat* stat) +{ + stat->st_dev = ip->i_sb->s_dev; + stat->st_ino = ip->i_ino; + stat->st_mode = ip->i_mode; + stat->st_nlink = ip->i_nlink; + stat->st_uid = ip->i_uid; + stat->st_gid = ip->i_gid; + stat->st_rdev = ip->i_rdev; + stat->__pad = 0; + stat->st_size = ip->i_size; + stat->st_blksize = 1 << ip->i_blkbits; + stat->__pad2 = 0; + stat->st_blocks = ip->i_blocks; + stat->st_atime_sec = ip->i_atime_sec; + stat->st_atime_nsec = ip->i_atime_nsec; + stat->st_mtime_sec = ip->i_mtime_sec; + stat->st_mtime_nsec = ip->i_mtime_nsec; + stat->st_ctime_sec = ip->i_ctime_sec; + stat->st_ctime_nsec = ip->i_ctime_nsec; + stat->__unused[0] = 0; + stat->__unused[1] = 0; +} + +/* ================================================================== */ +/* mark_inode_dirty (stub) */ +/* ================================================================== */ + +void mark_inode_dirty(inode* ip) +{ + ip->i_state |= I_DIRTY_SYNC; + /* Phase 1: no writeback, ramfs is memory-only */ +} diff --git a/na/fs/module.mk b/na/fs/module.mk new file mode 100644 index 0000000..f1be157 --- /dev/null +++ b/na/fs/module.mk @@ -0,0 +1,17 @@ +# fs/module.mk — VFS subsystem build rules + +SRCS_CC += fs/super.cc +SRCS_CC += fs/inode.cc +SRCS_CC += fs/dcache.cc +SRCS_CC += fs/file_table.cc +SRCS_CC += fs/fd.cc +SRCS_CC += fs/fs_struct.cc +SRCS_CC += fs/namei.cc +SRCS_CC += fs/ramfs/inode.cc +SRCS_CC += fs/ramfs/file.cc +SRCS_CC += fs/ramfs/super.cc +SRCS_CC += fs/vfs_init.cc +SRCS_CC += fs/chardev.cc +SRCS_CC += fs/devfs.cc +SRCS_CC += fs/pipe.cc +SRCS_CC += fs/initramfs.cc diff --git a/na/fs/namei.cc b/na/fs/namei.cc new file mode 100644 index 0000000..9124ea9 --- /dev/null +++ b/na/fs/namei.cc @@ -0,0 +1,250 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +/* + * namei.cc — Pathname resolution (path lookup) + * + * Simplified Phase 1: no symlink, no RCU, no mount traversal. + */ + +#include <lume/namei.h> +#include <lume/dcache.h> +#include <lume/fs.h> +#include <lume/file.h> +#include <lume/fdtable.h> +#include <lume/fs_struct.h> +#include <lume/mount.h> +#include <lume/task.h> +#include <lume/errno.h> +#include <lume/fs_types.h> + +/* ================================================================== */ +/* Helpers */ +/* ================================================================== */ + +static const char* next_component(const char* path, qstr* result) +{ + while (*path == '/') path++; + if (*path == '\0') return nullptr; + + const char* start = path; + while (*path != '\0' && *path != '/') path++; + + uint32 len = static_cast<uint32>(path - start); + result->name = start; + result->len = len; + result->hash = full_name_hash(start, len); + + return path; +} + +static bool is_dot(const qstr* name) { + return name->len == 1 && name->name[0] == '.'; +} +static bool is_dotdot(const qstr* name) { + return name->len == 2 && name->name[0] == '.' && name->name[1] == '.'; +} + +/* ================================================================== */ +/* walk_component */ +/* ================================================================== */ + +static dentry* walk_component(dentry* parent, const qstr* name) +{ + if (!parent->d_inode || !S_ISDIR(parent->d_inode->i_mode)) + return reinterpret_cast<dentry*>(-ENOTDIR); + + if (is_dot(name)) + return dget(parent); + + if (is_dotdot(name)) + return dget(parent->d_parent); + + /* Try dcache */ + dentry* child = d_lookup(parent, name); + if (child) + return child; + + /* Cache miss — call filesystem lookup */ + inode* dir = parent->d_inode; + if (!dir->i_op || !dir->i_op->lookup) + return reinterpret_cast<dentry*>(-ENOENT); + + child = d_alloc(parent, name); + if (!child) + return reinterpret_cast<dentry*>(-ENOMEM); + + dentry* result = dir->i_op->lookup(dir, child, 0); + if (result) { + dput(child); + return result; + } + + /* If lookup didn't set an inode, this is a negative dentry + * (file doesn't exist). Remove from dcache and return error. */ + if (!child->d_inode) { + d_drop(child); + dput(child); + return reinterpret_cast<dentry*>(-ENOENT); + } + + return child; +} + +/* ================================================================== */ +/* filename_lookup */ +/* ================================================================== */ + +int filename_lookup(int dfd, const char* name, unsigned int flags, path* result) +{ + if (!name || !result) + return -EINVAL; + + TaskControlBlock* task = current_task(); + dentry* start; + + /* Determine starting dentry */ + if (name[0] == '/') { + start = task->fs->root.de; + if (!start) start = g_root_dentry; + name++; + } else if (dfd == AT_FDCWD) { + start = task->fs->pwd.de; + if (!start) start = g_root_dentry; + } else { + file* dir_fp = fcheck(task->files, dfd); + if (!dir_fp) return -EBADF; + start = dir_fp->f_path.de; + if (!start || !start->d_inode || !S_ISDIR(start->d_inode->i_mode)) + return -ENOTDIR; + } + + dentry* current_de = dget(start); + const char* p = name; + + while (p && *p) { + qstr component; + const char* next = next_component(p, &component); + if (!next && component.len == 0) + break; + + const char* after = next; + while (after && *after == '/') after++; + bool is_last = (!after || *after == '\0'); + + if (is_last && (flags & LOOKUP_PARENT)) { + result->de = current_de; + result->mnt = task->fs->root.mnt; + return 0; + } + + dentry* child = walk_component(current_de, &component); + + int64 err = reinterpret_cast<int64>(child); + if (err < 0 && err >= -4095) { + dput(current_de); + return static_cast<int>(err); + } + + /* ---- Symlink following (Linux: step_into → pick_link) ---- + * If the resolved component is a symlink, follow it. + * Linux limits this to MAXSYMLINKS (40); we use 8. */ + constexpr int kMaxSymlinks = 8; + int symlink_depth = 0; + while (child->d_inode && S_ISLNK(child->d_inode->i_mode) && + child->d_inode->i_link && symlink_depth < kMaxSymlinks) { + symlink_depth++; + const char* target = child->d_inode->i_link; + dput(child); + + /* Absolute symlink: restart from root. + * Relative symlink: resolve from current parent. */ + dentry* link_start; + if (target[0] == '/') { + link_start = dget(task->fs->root.de ? task->fs->root.de : g_root_dentry); + target++; + } else { + link_start = dget(current_de); + } + + /* Walk each component of the symlink target */ + dentry* link_cur = link_start; + const char* lp = target; + qstr lcomp; + while (lp && *lp) { + const char* lnext = next_component(lp, &lcomp); + if (!lnext && lcomp.len == 0) break; + + dentry* lchild = walk_component(link_cur, &lcomp); + int64 lerr = reinterpret_cast<int64>(lchild); + if (lerr < 0 && lerr >= -4095) { + dput(link_cur); + dput(current_de); + return static_cast<int>(lerr); + } + + dput(link_cur); + link_cur = lchild; + lp = lnext; + } + + child = link_cur; + } + + if (symlink_depth >= kMaxSymlinks) { + dput(child); + dput(current_de); + return -ELOOP; + } + + if (!is_last && child->d_inode && !S_ISDIR(child->d_inode->i_mode)) { + dput(child); + dput(current_de); + return -ENOTDIR; + } + + if (!is_last && !child->d_inode) { + dput(child); + dput(current_de); + return -ENOENT; + } + + dput(current_de); + current_de = child; + p = next; + } + + if ((flags & LOOKUP_DIRECTORY) && current_de->d_inode && + !S_ISDIR(current_de->d_inode->i_mode)) { + dput(current_de); + return -ENOTDIR; + } + + /* Negative dentry: the path was walked, but the final component + * does not exist (d_inode == NULL). Return -ENOENT. */ + if (!current_de->d_inode) { + dput(current_de); + return -ENOENT; + } + + result->de = current_de; + result->mnt = task->fs->root.mnt; + return 0; +} + +int kern_path(const char* name, unsigned int flags, path* result) +{ + return filename_lookup(AT_FDCWD, name, flags, result); +} + +dentry* lookup_one_len(const char* name, dentry* parent, int len) +{ + qstr q; + q.name = name; + q.len = static_cast<uint32>(len); + q.hash = full_name_hash(name, q.len); + + return walk_component(parent, &q); +} + +void namei_init() {} diff --git a/na/fs/pipe.cc b/na/fs/pipe.cc new file mode 100644 index 0000000..f7708f5 --- /dev/null +++ b/na/fs/pipe.cc @@ -0,0 +1,278 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +/* + * pipe.cc — Pipe implementation + * + * Implements unidirectional byte-stream pipes with blocking I/O. + * Each pipe has a 4KB circular buffer. Reads block when empty, + * writes block when full (unless O_NONBLOCK). + * + * When all writers close, read returns 0 (EOF). + * When all readers close, write returns -EPIPE. + */ + +#include <lume/pipe.h> +#include <lume/file.h> +#include <lume/fdtable.h> +#include <lume/errno.h> +#include <lume/slab.h> +#include <lume/fs_types.h> +#include <lume/klog.h> +#include <lume/sched.h> + +/* ================================================================== */ +/* pipe_inode_info lifecycle */ +/* ================================================================== */ + +pipe_inode_info* alloc_pipe_info() +{ + auto* pi = new pipe_inode_info{}; + if (!pi) return nullptr; + + pi->rd_pos = 0; + pi->wr_pos = 0; + pi->len = 0; + pi->rd_wait.init("pipe_rd"); + pi->wr_wait.init("pipe_wr"); + pi->readers.store(0); + pi->writers.store(0); + pi->refcount.store(1); + return pi; +} + +void free_pipe_info(pipe_inode_info* pipe) +{ + delete pipe; +} + +static void pipe_get(pipe_inode_info* pi) +{ + pi->refcount.fetch_add(1); +} + +static void pipe_put(pipe_inode_info* pi) +{ + if (pi->refcount.fetch_sub(1) == 1) { + free_pipe_info(pi); + } +} + +/* ================================================================== */ +/* pipe_read */ +/* ================================================================== */ + +static int64 pipe_read(file* fp, char* buf, uint64 count, int64* /*pos*/) +{ + pipe_inode_info* pi = static_cast<pipe_inode_info*>(fp->private_data); + if (!pi) return -EBADF; + + while (true) { + pi->lock.acquire(); + + if (pi->len > 0) { + /* Data available — copy out */ + int64 to_read = static_cast<int64>(count); + if (to_read > pi->len) to_read = pi->len; + + for (int64 i = 0; i < to_read; i++) { + buf[i] = pi->buf[pi->rd_pos]; + pi->rd_pos = (pi->rd_pos + 1) % PIPE_BUF_SIZE; + } + pi->len -= static_cast<int>(to_read); + + pi->lock.release(); + + /* Wake any blocked writers */ + pi->wr_wait.wake_all(); + return to_read; + } + + /* Buffer empty */ + if (pi->writers.load() == 0) { + /* No writers left — EOF */ + pi->lock.release(); + return 0; + } + + if (fp->f_flags & O_NONBLOCK) { + pi->lock.release(); + return -EAGAIN; + } + + pi->lock.release(); + + /* Sleep until data or writer closes */ + pi->rd_wait.sleep(); + } +} + +/* ================================================================== */ +/* pipe_write */ +/* ================================================================== */ + +static int64 pipe_write(file* fp, const char* buf, uint64 count, int64* /*pos*/) +{ + pipe_inode_info* pi = static_cast<pipe_inode_info*>(fp->private_data); + if (!pi) return -EBADF; + + if (pi->readers.load() == 0) { + return -EPIPE; /* Broken pipe */ + } + + int64 written = 0; + + while (static_cast<uint64>(written) < count) { + pi->lock.acquire(); + + if (pi->len < PIPE_BUF_SIZE) { + /* Space available — copy in */ + int avail = PIPE_BUF_SIZE - pi->len; + int64 remaining = static_cast<int64>(count) - written; + int to_write = avail < static_cast<int>(remaining) + ? avail + : static_cast<int>(remaining); + + for (int i = 0; i < to_write; i++) { + pi->buf[pi->wr_pos] = buf[written + i]; + pi->wr_pos = (pi->wr_pos + 1) % PIPE_BUF_SIZE; + } + pi->len += to_write; + written += to_write; + + pi->lock.release(); + + /* Wake any blocked readers */ + pi->rd_wait.wake_all(); + continue; + } + + /* Buffer full */ + if (pi->readers.load() == 0) { + pi->lock.release(); + return written > 0 ? written : -EPIPE; + } + + if (fp->f_flags & O_NONBLOCK) { + pi->lock.release(); + return written > 0 ? written : -EAGAIN; + } + + pi->lock.release(); + + /* Sleep until space or reader closes */ + pi->wr_wait.sleep(); + } + + return written; +} + +/* ================================================================== */ +/* pipe_release — called on last fput of a pipe fd */ +/* ================================================================== */ + +static int pipe_read_release(inode* /*ip*/, file* fp) +{ + pipe_inode_info* pi = static_cast<pipe_inode_info*>(fp->private_data); + if (pi) { + pi->readers.fetch_sub(1); + /* Wake writers so they see readers==0 and get EPIPE */ + pi->wr_wait.wake_all(); + pipe_put(pi); + } + return 0; +} + +static int pipe_write_release(inode* /*ip*/, file* fp) +{ + pipe_inode_info* pi = static_cast<pipe_inode_info*>(fp->private_data); + if (pi) { + pi->writers.fetch_sub(1); + /* Wake readers so they see writers==0 and get EOF */ + pi->rd_wait.wake_all(); + pipe_put(pi); + } + return 0; +} + +/* ================================================================== */ +/* file_operations tables */ +/* ================================================================== */ + +static file_operations pipe_read_fops; +static file_operations pipe_write_fops; +static bool pipe_fops_initialized = false; + +static void pipe_fops_init() +{ + if (pipe_fops_initialized) return; + + pipe_read_fops = {}; + pipe_read_fops.read = pipe_read; + pipe_read_fops.release = pipe_read_release; + + pipe_write_fops = {}; + pipe_write_fops.write = pipe_write; + pipe_write_fops.release = pipe_write_release; + + pipe_fops_initialized = true; +} + +/* ================================================================== */ +/* do_pipe2 — create a pipe, install two fds */ +/* ================================================================== */ + +int do_pipe2(files_struct* files, int fds[2], int flags) +{ + pipe_fops_init(); + + pipe_inode_info* pi = alloc_pipe_info(); + if (!pi) return -ENOMEM; + + /* Create read-end file */ + file* rd_fp = alloc_empty_file(O_RDONLY | (flags & O_NONBLOCK)); + if (!rd_fp) { + free_pipe_info(pi); + return -ENFILE; + } + rd_fp->f_op = &pipe_read_fops; + rd_fp->private_data = pi; + + /* Create write-end file */ + file* wr_fp = alloc_empty_file(O_WRONLY | (flags & O_NONBLOCK)); + if (!wr_fp) { + fput(rd_fp); + free_pipe_info(pi); + return -ENFILE; + } + wr_fp->f_op = &pipe_write_fops; + wr_fp->private_data = pi; + + /* Bump pipe refcount for the two files */ + pipe_get(pi); /* wr_fp gets second ref */ + pi->readers.store(1); + pi->writers.store(1); + + /* Allocate fds */ + int rd_fd = alloc_fd(files, 0); + if (rd_fd < 0) { + fput(rd_fp); + fput(wr_fp); + return rd_fd; + } + + int wr_fd = alloc_fd(files, 0); + if (wr_fd < 0) { + close_fd(files, rd_fd); + fput(wr_fp); + return wr_fd; + } + + fd_install(files, rd_fd, rd_fp); + fd_install(files, wr_fd, wr_fp); + + fds[0] = rd_fd; + fds[1] = wr_fd; + + return 0; +} diff --git a/na/fs/ramfs/file.cc b/na/fs/ramfs/file.cc new file mode 100644 index 0000000..6d9a070 --- /dev/null +++ b/na/fs/ramfs/file.cc @@ -0,0 +1,216 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +/* + * ramfs/file.cc — RamFS file operations (read/write via in-memory pages) + * + * Phase 1: simple flat buffer per-inode via i_private. + * Will migrate to page_cache integration later. + */ + +#include <lume/fs.h> +#include <lume/file.h> +#include <lume/dcache.h> +#include <lume/slab.h> +#include <lume/errno.h> +#include <lume/fs_types.h> + +/* + * Simplest possible in-memory file storage: + * i_private points to a dynamically grown buffer. + */ +struct ramfs_file_data { + char* buf; + uint64 capacity; +}; + +static ramfs_file_data* get_or_create_data(inode* ip) +{ + if (!ip->i_private) { + auto* data = new ramfs_file_data{}; + data->buf = nullptr; + data->capacity = 0; + ip->i_private = data; + } + return static_cast<ramfs_file_data*>(ip->i_private); +} + +static int ensure_capacity(ramfs_file_data* data, uint64 needed) +{ + if (needed <= data->capacity) return 0; + + /* Round up to page size */ + uint64 new_cap = (needed + 4095) & ~4095ULL; + char* new_buf = new char[new_cap]; + if (!new_buf) return -ENOMEM; + + /* Copy old data */ + if (data->buf && data->capacity > 0) { + for (uint64 i = 0; i < data->capacity; i++) + new_buf[i] = data->buf[i]; + } + /* Zero new region */ + for (uint64 i = data->capacity; i < new_cap; i++) + new_buf[i] = 0; + + delete[] data->buf; + data->buf = new_buf; + data->capacity = new_cap; + return 0; +} + +/* ================================================================== */ +/* ramfs_read */ +/* ================================================================== */ + +static int64 ramfs_read(file* fp, char* buf, uint64 count, int64* pos) +{ + inode* ip = file_inode(fp); + int64 offset = *pos; + + if (offset < 0) return -EINVAL; + if (offset >= ip->i_size) return 0; + + int64 avail = ip->i_size - offset; + if (static_cast<int64>(count) > avail) + count = static_cast<uint64>(avail); + + auto* data = static_cast<ramfs_file_data*>(ip->i_private); + if (!data || !data->buf) return 0; + + /* Copy to user buffer. + * TODO: use copy_to_user when called from syscall context */ + for (uint64 i = 0; i < count; i++) + buf[i] = data->buf[offset + i]; + + *pos += count; + return static_cast<int64>(count); +} + +/* ================================================================== */ +/* ramfs_write */ +/* ================================================================== */ + +static int64 ramfs_write(file* fp, const char* buf, uint64 count, int64* pos) +{ + inode* ip = file_inode(fp); + int64 offset = *pos; + + if (fp->f_flags & O_APPEND) + offset = ip->i_size; + + if (offset < 0) return -EINVAL; + + auto* data = get_or_create_data(ip); + uint64 end = static_cast<uint64>(offset) + count; + + int err = ensure_capacity(data, end); + if (err) return err; + + /* Copy from user buffer */ + for (uint64 i = 0; i < count; i++) + data->buf[offset + i] = buf[i]; + + if (static_cast<int64>(end) > ip->i_size) + ip->i_size = end; + + *pos = end; + mark_inode_dirty(ip); + return static_cast<int64>(count); +} + +/* ================================================================== */ +/* ramfs_dir_iterate — getdents64 */ +/* ================================================================== */ + +static int ramfs_dir_iterate(file* fp, dir_context* ctx) +{ + dentry* dir_de = file_dentry(fp); + inode* dir_ip = file_inode(fp); + int64 pos = ctx->pos; + int count = 0; + + /* pos 0: emit "." */ + if (pos == 0) { + if (!ctx->actor(ctx, ".", 1, pos, dir_ip->i_ino, DT_DIR)) + return 0; + ctx->pos = 1; + pos = 1; + count++; + } + + /* pos 1: emit ".." */ + if (pos == 1) { + uint64 parent_ino = dir_de->d_parent->d_inode + ? dir_de->d_parent->d_inode->i_ino : dir_ip->i_ino; + if (!ctx->actor(ctx, "..", 2, pos, parent_ino, DT_DIR)) + return 0; + ctx->pos = 2; + pos = 2; + count++; + } + + /* pos >= 2: iterate children */ + int64 child_idx = 2; + list_node* node; + list_for_each(node, &dir_de->d_children) { + dentry* child = list_entry(node, dentry, d_child); + + if (!child->d_inode) + continue; /* skip negative dentries */ + + if (child_idx >= pos) { + uint8 d_type = DT_UNKNOWN; + if (S_ISDIR(child->d_inode->i_mode)) d_type = DT_DIR; + else if (S_ISREG(child->d_inode->i_mode)) d_type = DT_REG; + else if (S_ISCHR(child->d_inode->i_mode)) d_type = DT_CHR; + else if (S_ISBLK(child->d_inode->i_mode)) d_type = DT_BLK; + else if (S_ISLNK(child->d_inode->i_mode)) d_type = DT_LNK; + else if (S_ISFIFO(child->d_inode->i_mode)) d_type = DT_FIFO; + + if (!ctx->actor(ctx, child->d_name.name, + static_cast<int>(child->d_name.len), + child_idx, child->d_inode->i_ino, d_type)) + return 0; + + ctx->pos = child_idx + 1; + count++; + } + child_idx++; + } + + return count; +} + +extern file_operations ramfs_file_operations; +extern file_operations ramfs_dir_operations; + +file_operations ramfs_file_operations; +file_operations ramfs_dir_operations; + +void ramfs_file_ops_init() +{ + ramfs_file_operations.llseek = nullptr; + ramfs_file_operations.read = ramfs_read; + ramfs_file_operations.write = ramfs_write; + ramfs_file_operations.iterate_shared = nullptr; + ramfs_file_operations.poll = nullptr; + ramfs_file_operations.unlocked_ioctl = nullptr; + ramfs_file_operations.mmap = nullptr; + ramfs_file_operations.open = nullptr; + ramfs_file_operations.flush = nullptr; + ramfs_file_operations.release = nullptr; + ramfs_file_operations.fsync = nullptr; + + ramfs_dir_operations.llseek = nullptr; + ramfs_dir_operations.read = nullptr; + ramfs_dir_operations.write = nullptr; + ramfs_dir_operations.iterate_shared = ramfs_dir_iterate; + ramfs_dir_operations.poll = nullptr; + ramfs_dir_operations.unlocked_ioctl = nullptr; + ramfs_dir_operations.mmap = nullptr; + ramfs_dir_operations.open = nullptr; + ramfs_dir_operations.flush = nullptr; + ramfs_dir_operations.release = nullptr; + ramfs_dir_operations.fsync = nullptr; +} diff --git a/na/fs/ramfs/inode.cc b/na/fs/ramfs/inode.cc new file mode 100644 index 0000000..c5e49bf --- /dev/null +++ b/na/fs/ramfs/inode.cc @@ -0,0 +1,164 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +/* + * ramfs/inode.cc — RamFS inode operations + * + * A simple in-memory filesystem (like Linux tmpfs without swap). + * All data lives in memory via the page cache. + */ + +#include <lume/fs.h> +#include <lume/dcache.h> +#include <lume/file.h> +#include <lume/slab.h> +#include <lume/errno.h> +#include <lume/fs_types.h> + +/* Forward declarations */ +extern file_operations ramfs_file_operations; +extern file_operations ramfs_dir_operations; +extern inode_operations ramfs_dir_inode_operations; + +/* ================================================================== */ +/* ramfs_get_inode — allocate + init an inode for ramfs */ +/* ================================================================== */ + +inode* ramfs_get_inode(super_block* sb, const inode* dir, uint32 mode, uint32 rdev) +{ + inode* ip = new_inode(sb); + if (!ip) return nullptr; + + inode_init_owner(ip, dir, mode); + /* TODO: set timestamps to current time when we have a time source */ + + switch (mode & S_IFMT) { + case S_IFREG: + ip->i_op = nullptr; /* regular files don't need inode_ops for Phase 1 */ + ip->i_fop = &ramfs_file_operations; + break; + case S_IFDIR: + ip->i_op = &ramfs_dir_inode_operations; + ip->i_fop = &ramfs_dir_operations; + inc_nlink(ip); /* directories start with nlink=2 (. and parent's link) */ + break; + case S_IFLNK: + /* Symlink: stub for Phase 1 */ + break; + default: + /* Character/block devices: i_rdev */ + ip->i_rdev = rdev; + break; + } + + return ip; +} + +/* ================================================================== */ +/* Directory inode operations */ +/* ================================================================== */ + +static dentry* ramfs_lookup(inode* dir, dentry* de, unsigned int flags) +{ + (void)dir; + (void)flags; + /* For ramfs, everything is in dcache. If we get here, it means + * d_lookup() already failed, so this is a negative dentry. + * Just return nullptr to let the caller see de->d_inode == NULL. */ + return nullptr; +} + +static int ramfs_create(inode* dir, dentry* de, uint32 mode, bool excl) +{ + (void)excl; + inode* ip = ramfs_get_inode(dir->i_sb, dir, mode | S_IFREG, 0); + if (!ip) return -ENOMEM; + + d_instantiate(de, ip); + dget(de); + dir->i_size += de->d_name.len; + mark_inode_dirty(dir); + return 0; +} + +static int ramfs_mkdir(inode* dir, dentry* de, uint32 mode) +{ + inode* ip = ramfs_get_inode(dir->i_sb, dir, mode | S_IFDIR, 0); + if (!ip) return -ENOMEM; + + d_instantiate(de, ip); + dget(de); + inc_nlink(dir); + dir->i_size += de->d_name.len; + mark_inode_dirty(dir); + return 0; +} + +static int ramfs_rmdir(inode* dir, dentry* de) +{ + /* Check if directory is empty */ + if (!list_empty(&de->d_children)) + return -ENOTEMPTY; + + drop_nlink(de->d_inode); /* remove . link */ + drop_nlink(de->d_inode); /* remove parent's link */ + drop_nlink(dir); + d_delete(de); + dir->i_size -= de->d_name.len; + mark_inode_dirty(dir); + return 0; +} + +static int ramfs_unlink(inode* dir, dentry* de) +{ + drop_nlink(de->d_inode); + d_delete(de); + dir->i_size -= de->d_name.len; + mark_inode_dirty(dir); + return 0; +} + +static int ramfs_link(dentry* old_de, inode* dir, dentry* new_de) +{ + inode* ip = old_de->d_inode; + if (!ip) return -ENOENT; + + inc_nlink(ip); + ihold(ip); + d_instantiate(new_de, ip); + dget(new_de); + dir->i_size += new_de->d_name.len; + mark_inode_dirty(dir); + return 0; +} + +static int ramfs_mknod(inode* dir, dentry* de, uint32 mode, uint32 rdev) +{ + inode* ip = ramfs_get_inode(dir->i_sb, dir, mode, rdev); + if (!ip) return -ENOMEM; + + d_instantiate(de, ip); + dget(de); + dir->i_size += de->d_name.len; + mark_inode_dirty(dir); + return 0; +} + +inode_operations ramfs_dir_inode_operations; + +void ramfs_dir_inode_ops_init() +{ + ramfs_dir_inode_operations.lookup = ramfs_lookup; + ramfs_dir_inode_operations.create = ramfs_create; + ramfs_dir_inode_operations.link = ramfs_link; + ramfs_dir_inode_operations.unlink = ramfs_unlink; + ramfs_dir_inode_operations.symlink = nullptr; + ramfs_dir_inode_operations.mkdir = ramfs_mkdir; + ramfs_dir_inode_operations.rmdir = ramfs_rmdir; + ramfs_dir_inode_operations.mknod = ramfs_mknod; + ramfs_dir_inode_operations.rename = nullptr; + ramfs_dir_inode_operations.setattr = nullptr; + ramfs_dir_inode_operations.getattr = nullptr; + ramfs_dir_inode_operations.get_link = nullptr; + ramfs_dir_inode_operations.permission = nullptr; +} diff --git a/na/fs/ramfs/super.cc b/na/fs/ramfs/super.cc new file mode 100644 index 0000000..6f929f2 --- /dev/null +++ b/na/fs/ramfs/super.cc @@ -0,0 +1,154 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +/* + * ramfs/super.cc — RamFS superblock: mount, fill_super, and init + */ + +#include <lume/fs.h> +#include <lume/dcache.h> +#include <lume/file.h> +#include <lume/mount.h> +#include <lume/slab.h> +#include <lume/kprintf.h> +#include <lume/errno.h> +#include <lume/fs_types.h> + +extern inode* ramfs_get_inode(super_block* sb, const inode* dir, + uint32 mode, uint32 rdev); + +/* Superblock allocation helper (defined in super.cc) */ +extern super_block* alloc_super(file_system_type* type); + +/* ================================================================== */ +/* ramfs super_operations */ +/* ================================================================== */ + +static inode* ramfs_alloc_inode(super_block* sb) +{ + return new inode{}; +} + +static void ramfs_destroy_inode(inode* ip) +{ + /* Free ramfs_file_data if any */ + if (ip->i_private) { + struct ramfs_file_data { + char* buf; + uint64 capacity; + }; + auto* data = static_cast<ramfs_file_data*>(ip->i_private); + delete[] data->buf; + delete data; + } + delete ip; +} + +static int ramfs_statfs(dentry* de, kstatfs* buf) +{ + /* Simple fake values */ + buf->f_type = 0x858458f6; /* RAMFS_MAGIC */ + buf->f_bsize = 4096; + buf->f_blocks = 0; + buf->f_bfree = 0; + buf->f_bavail = 0; + buf->f_files = 0; + buf->f_ffree = 0; + buf->f_namelen = 255; + buf->f_frsize = 4096; + buf->f_flags = 0; + return 0; +} + +static super_operations ramfs_sops; + +static void ramfs_sops_init() +{ + ramfs_sops.alloc_inode = ramfs_alloc_inode; + ramfs_sops.destroy_inode = ramfs_destroy_inode; + ramfs_sops.free_inode = nullptr; + ramfs_sops.dirty_inode = nullptr; + ramfs_sops.write_inode = nullptr; + ramfs_sops.drop_inode = nullptr; + ramfs_sops.evict_inode = nullptr; + ramfs_sops.put_super = nullptr; + ramfs_sops.sync_fs = nullptr; + ramfs_sops.statfs = ramfs_statfs; + ramfs_sops.umount_begin = nullptr; +} + +/* ================================================================== */ +/* ramfs_fill_super */ +/* ================================================================== */ + +static int ramfs_fill_super(super_block* sb, void* data, int silent) +{ + sb->s_op = &ramfs_sops; + sb->s_magic = 0x858458f6; + sb->s_blocksize = 4096; + sb->s_blocksize_bits = 12; + sb->s_maxbytes = 0x7FFFFFFFFFFFFFFFLL; + sb->s_time_gran = 1; + + /* Create root inode */ + inode* root_ip = ramfs_get_inode(sb, nullptr, S_IFDIR | 0755, 0); + if (!root_ip) return -ENOMEM; + + /* Create root dentry */ + sb->s_root = d_make_root(root_ip); + if (!sb->s_root) + return -ENOMEM; + + return 0; +} + +/* ================================================================== */ +/* ramfs_mount */ +/* ================================================================== */ + +static super_block* ramfs_mount(file_system_type* type, int flags, + const char* dev_name, void* data) +{ + super_block* sb = alloc_super(type); + if (!sb) return nullptr; + + int err = ramfs_fill_super(sb, data, flags & SB_SILENT); + if (err) { + delete sb; + return nullptr; + } + + return sb; +} + +static void ramfs_kill_sb(super_block* sb) +{ + /* Phase 1: no cleanup needed for ramfs */ + (void)sb; +} + +/* ================================================================== */ +/* Filesystem type registration */ +/* ================================================================== */ + +static file_system_type ramfs_fs_type; + +extern void ramfs_file_ops_init(); +extern void ramfs_dir_inode_ops_init(); + +void ramfs_init() +{ + /* Runtime init to avoid un-relocated absolute pointers in .data */ + ramfs_sops_init(); + ramfs_file_ops_init(); + ramfs_dir_inode_ops_init(); + + ramfs_fs_type.name = "ramfs"; + ramfs_fs_type.fs_flags = FS_NO_DEVICE; + ramfs_fs_type.mount = ramfs_mount; + ramfs_fs_type.kill_sb = ramfs_kill_sb; + list_init(&ramfs_fs_type.fs_list); + + register_filesystem(&ramfs_fs_type); +} + diff --git a/na/fs/super.cc b/na/fs/super.cc new file mode 100644 index 0000000..231b57c --- /dev/null +++ b/na/fs/super.cc @@ -0,0 +1,95 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +/* + * super.cc — Superblock lifecycle + filesystem type registry + */ + +#include <lume/fs.h> +#include <lume/dcache.h> +#include <lume/mount.h> +#include <lume/klog.h> +#include <lume/slab.h> +#include <lume/errno.h> + +/* Global root filesystem entry points */ +vfsmount* g_root_mnt = nullptr; +dentry* g_root_dentry = nullptr; + +/* ================================================================== */ +/* Filesystem type registry */ +/* ================================================================== */ + +namespace { + +list_head g_fs_types; +Spinlock g_fs_type_lock; + +} // namespace + +int register_filesystem(file_system_type* fs) +{ + LockGuard guard(g_fs_type_lock); + list_add_tail(&fs->fs_list, &g_fs_types); + printk("[vfs] registered filesystem: %s\n", fs->name); + return 0; +} + +file_system_type* get_fs_type(const char* name) +{ + LockGuard guard(g_fs_type_lock); + list_node* pos; + list_for_each(pos, &g_fs_types) { + auto* fs = list_entry(pos, file_system_type, fs_list); + /* Simple strcmp */ + const char* a = fs->name; + const char* b = name; + while (*a && *a == *b) { a++; b++; } + if (*a == '\0' && *b == '\0') + return fs; + } + return nullptr; +} + +/* ================================================================== */ +/* Superblock allocation */ +/* ================================================================== */ + +super_block* alloc_super(file_system_type* type) +{ + auto* sb = new super_block{}; + if (!sb) return nullptr; + + list_init(&sb->s_list); + list_init(&sb->s_inodes); + sb->s_type = type; + sb->s_op = nullptr; + sb->s_root = nullptr; + sb->s_fs_info = nullptr; + sb->s_blocksize = 4096; + sb->s_blocksize_bits = 12; + sb->s_maxbytes = 0x7FFFFFFFFFFFFFFFLL; + sb->s_time_gran = 1; + sb->s_time_min = 0; + sb->s_time_max = 0x7FFFFFFFFFFFFFFFLL; + sb->s_flags = 0; + sb->s_magic = 0; + sb->s_active.store(1); + sb->s_count = 1; + sb->s_max_links = 0; + sb->s_d_op = nullptr; + sb->s_mounts = nullptr; + sb->s_dev = 0; + for (int i = 0; i < 32; i++) sb->s_id[i] = 0; + + return sb; +} + +/* ================================================================== */ +/* Init */ +/* ================================================================== */ + +void super_init() +{ + list_init(&g_fs_types); +} diff --git a/na/fs/vfs_init.cc b/na/fs/vfs_init.cc new file mode 100644 index 0000000..ec56e88 --- /dev/null +++ b/na/fs/vfs_init.cc @@ -0,0 +1,78 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +/* + * vfs_init.cc — Bootstrap the VFS: mount ramfs root, set global root + * + * Called from kernel_main() after slab_init() and ramfs_init(). + * This is the moral equivalent of Linux's vfs_caches_init() + + * mnt_init() + init_rootfs(). + */ + +#include <lume/fs.h> +#include <lume/dcache.h> +#include <lume/file.h> +#include <lume/mount.h> +#include <lume/fdtable.h> +#include <lume/fs_struct.h> +#include <lume/klog.h> +#include <lume/kprintf.h> +#include <lume/task.h> +#include <lume/sched.h> + +/* ================================================================== */ +/* vfs_init — mount ramfs as root filesystem */ +/* ================================================================== */ + +/* + * ramfs_mount is called internally via the file_system_type::mount + * callback registered by ramfs_init(). We look it up by name. + */ +extern file_system_type* get_fs_type(const char* name); +extern super_block* alloc_super(file_system_type* type); + +void vfs_init() +{ + /* 1. Look up the ramfs filesystem type */ + file_system_type* ramfs_type = get_fs_type("ramfs"); + if (!ramfs_type || !ramfs_type->mount) { + printk(KERN_ERR "vfs_init: ramfs not registered!\n"); + return; + } + + /* 2. Mount ramfs */ + super_block* sb = ramfs_type->mount(ramfs_type, 0, "ramfs", nullptr); + if (!sb || !sb->s_root) { + printk(KERN_ERR "vfs_init: failed to mount ramfs!\n"); + return; + } + + /* 3. Set global root */ + g_root_dentry = sb->s_root; + g_root_mnt = nullptr; /* TODO: allocate a proper vfsmount when mount.cc lands */ + + dget(g_root_dentry); /* Hold a reference for the global root */ + + printk(KERN_INFO "[vfs] root filesystem mounted (ramfs)\n"); + + /* 4. Set up VFS state for the current (BSP idle) task, + * which will be inherited by all subsequently created tasks. */ + TaskControlBlock* bsp = current_task(); + if (bsp) { + if (!bsp->files) { + bsp->files = alloc_files_struct(); + } + if (!bsp->fs) { + bsp->fs = alloc_fs_struct(); + if (bsp->fs) { + bsp->fs->root.de = g_root_dentry; + bsp->fs->root.mnt = g_root_mnt; + bsp->fs->pwd.de = g_root_dentry; + bsp->fs->pwd.mnt = g_root_mnt; + dget(g_root_dentry); /* root ref */ + dget(g_root_dentry); /* pwd ref */ + } + } + printk(KERN_INFO "[vfs] BSP task VFS context initialized\n"); + } +} |
