소스 검색

refactor: vfs

greatbridf 11 달 전
부모
커밋
da84d0d39b
9개의 변경된 파일514개의 추가작업 그리고 376개의 파일을 삭제
  1. 1 0
      CMakeLists.txt
  2. 1 0
      gblibc/include/errno.h
  3. 3 6
      gblibstdc++/include/functional
  4. 2 2
      include/fs/fat.hpp
  5. 24 5
      include/kernel/vfs.hpp
  6. 33 6
      src/fs/fat.cpp
  7. 21 8
      src/kernel/process.cpp
  8. 103 349
      src/kernel/vfs.cpp
  9. 326 0
      src/kernel/vfs/tmpfs.cc

+ 1 - 0
CMakeLists.txt

@@ -53,6 +53,7 @@ set(KERNEL_MAIN_SOURCES src/fs/fat.cpp
                         src/kernel/hw/timer.c
                         src/kernel/event/event.cpp
                         src/kernel/user/thread_local.cc
+                        src/kernel/vfs/tmpfs.cc
                         src/kernel/signal.cpp
                         src/types/elf.cpp
                         src/types/libstdcpp.cpp

+ 1 - 0
gblibc/include/errno.h

@@ -21,6 +21,7 @@ extern int* __errno_location(void);
 #define EACCES 13
 #define EFAULT 14
 #define EEXIST 17
+#define ENODEV 19
 #define ENOTDIR 20
 #define EISDIR 21
 #define EINVAL 22

+ 3 - 6
gblibstdc++/include/functional

@@ -87,8 +87,7 @@ namespace __inner {
     class _function : public _function_base<Ret, Args...> {
     private:
         using __enable = std::enable_if_t<
-            std::is_same_v<Ret,
-                decltype(std::declval<std::decay_t<FuncLike>>()(std::declval<Args>()...))>
+            std::is_invocable_r_v<Ret, std::decay_t<FuncLike>, Args...>
         >;
         FuncLike func;
 
@@ -179,10 +178,8 @@ public:
 
     template <typename FuncLike, std::enable_if_t
         <
-            std::is_same_v<
-                Ret,
-                decltype(std::declval<std::decay_t<FuncLike>>()(std::declval<Args>()...))
-            >
+            // TODO: check its behavior for nullptr
+            std::is_invocable_r_v<Ret, std::decay_t<FuncLike>, Args...>
             && (sizeof(std::decay_t<FuncLike>) <= STACK_ALLOCATED_SIZE - sizeof(void*))
             && !std::is_same_v<std::decay_t<FuncLike>, function>
         , bool> = true

+ 2 - 2
include/fs/fat.hpp

@@ -163,10 +163,10 @@ private:
 
 public:
     fat32(const fat32&) = delete;
-    explicit fat32(inode* _device);
+    explicit fat32(dev_t device);
     ~fat32();
 
-    virtual size_t inode_read(inode* file, char* buf, size_t buf_size, size_t offset, size_t n) override;
+    virtual size_t read(inode* file, char* buf, size_t buf_size, size_t offset, size_t n) override;
     virtual int inode_statx(dentry* ent, statx* st, unsigned int mask) override;
     virtual int inode_stat(dentry* ent, struct stat* st) override;
     virtual int inode_readdir(fs::inode* dir, size_t offset, const fs::vfs::filldir_func& callback) override;

+ 24 - 5
include/kernel/vfs.hpp

@@ -22,6 +22,7 @@
 #include <types/path.hpp>
 #include <types/lock.hpp>
 #include <types/types.h>
+#include <types/string.hpp>
 
 #define NODE_MAJOR(node) ((node) >> 16)
 #define NODE_MINOR(node) ((node) & 0xffffU)
@@ -186,16 +187,23 @@ public:
 
     int mount(dentry* mnt, vfs* new_fs);
 
-    virtual size_t inode_read(inode* file, char* buf, size_t buf_size, size_t offset, size_t n);
-    virtual size_t inode_write(inode* file, const char* buf, size_t offset, size_t n);
+    // directory operations
+
     virtual int inode_mkfile(dentry* dir, const char* filename, mode_t mode);
     virtual int inode_mknode(dentry* dir, const char* filename, mode_t mode, dev_t sn);
     virtual int inode_rmfile(dentry* dir, const char* filename);
     virtual int inode_mkdir(dentry* dir, const char* dirname, mode_t mode);
+
+    // metadata operation
+
     virtual int inode_statx(dentry* dent, statx* buf, unsigned int mask);
     virtual int inode_stat(dentry* dent, struct stat* stat);
-    virtual int inode_devid(inode* file, dev_t& out_dev);
 
+    // file operations
+
+    virtual size_t read(inode* file, char* buf, size_t buf_size, size_t offset, size_t n);
+    virtual size_t write(inode* file, const char* buf, size_t offset, size_t n);
+    virtual int dev_id(inode* file, dev_t& out_dev);
     virtual int truncate(inode* file, size_t size);
 
     // parameter 'length' in callback:
@@ -313,6 +321,19 @@ inline fs::vfs::dentry* fs_root;
 int register_block_device(dev_t node, blkdev_ops ops);
 int register_char_device(dev_t node, chrdev_ops ops);
 
+// return value: pointer to created vfs object
+// 1. dev_t: device number
+using create_fs_func_t = std::function<vfs*(dev_t)>;
+
+int register_fs(const char* name, create_fs_func_t);
+
+// in tmpfs.cc
+int register_tmpfs();
+
+// returns a pointer to the vfs object
+// vfs objects are managed by the kernel
+int create_fs(const char* name, dev_t device, vfs*& out_vfs);
+
 void partprobe();
 
 ssize_t block_device_read(dev_t node, char* buf, size_t buf_size, size_t offset, size_t n);
@@ -321,8 +342,6 @@ ssize_t block_device_write(dev_t node, const char* buf, size_t offset, size_t n)
 ssize_t char_device_read(dev_t node, char* buf, size_t buf_size, size_t n);
 ssize_t char_device_write(dev_t node, const char* buf, size_t n);
 
-vfs* register_fs(vfs* fs);
-
 size_t vfs_read(inode* file, char* buf, size_t buf_size, size_t offset, size_t n);
 size_t vfs_write(inode* file, const char* buf, size_t offset, size_t n);
 int vfs_mkfile(fs::vfs::dentry* dir, const char* filename, mode_t mode);

+ 33 - 6
src/fs/fat.cpp

@@ -3,6 +3,7 @@
 #include <fs/fat.hpp>
 #include <kernel/mem.h>
 #include <kernel/mm.hpp>
+#include <kernel/module.hpp>
 #include <kernel/vfs.hpp>
 #include <stdint.h>
 #include <stdio.h>
@@ -130,13 +131,10 @@ int fat32::inode_readdir(fs::inode* dir, size_t offset, const fs::vfs::filldir_f
     return nread;
 }
 
-fat32::fat32(inode* _device)
-    : device { 0 }
+fat32::fat32(dev_t _device)
+    : device { _device }
     , label { 0 }
 {
-    // TODO: error handling
-    assert(_device->fs->inode_devid(_device, device) == 0);
-
     auto* buf = new char[SECTOR_SIZE];
     _raw_read_sector(buf, 0);
 
@@ -188,7 +186,7 @@ fat32::~fat32()
     delete[]((char*)fat);
 }
 
-size_t fat32::inode_read(inode* file, char* buf, size_t buf_size, size_t offset, size_t n)
+size_t fat32::read(inode* file, char* buf, size_t buf_size, size_t offset, size_t n)
 {
     cluster_t next = cl(file);
     uint32_t cluster_size = SECTOR_SIZE * sectors_per_cluster;
@@ -302,4 +300,33 @@ int fat32::inode_stat(dentry* dent, struct stat* st)
     return GB_OK;
 }
 
+static fat32* create_fat32(dev_t device)
+{
+    return new fat32(device);
+}
+
+class fat32_module : public virtual kernel::module::module {
+public:
+    fat32_module() : module("fat32") { }
+    ~fat32_module()
+    {
+        // TODO: unregister filesystem
+    }
+
+    virtual int init() override
+    {
+        int ret = fs::register_fs("fat32", create_fat32);
+
+        if (ret != 0)
+            return kernel::module::MODULE_FAILED;
+
+        return kernel::module::MODULE_SUCCESS;
+    }
+};
+
 } // namespace fs::fat
+
+static kernel::module::module* fat32_module_init()
+{ return new fs::fat::fat32_module; }
+
+INTERNAL_MODULE(fat32_module_loader, fat32_module_init);

+ 21 - 8
src/kernel/process.cpp

@@ -540,14 +540,27 @@ void NORETURN _kernel_init(void)
         kmsg(buf);
     }
 
+
+    // mount fat32 /mnt directory
     // TODO: parse kernel parameters
-    auto* drive = fs::vfs_open(*fs::fs_root, "/dev/sda1");
-    assert(drive);
-    auto* _new_fs = fs::register_fs(new fs::fat::fat32(drive->ind));
-    auto* mnt = fs::vfs_open(*fs::fs_root, "/mnt");
-    assert(mnt);
-    int ret = fs::fs_root->ind->fs->mount(mnt, _new_fs);
-    assert(ret == GB_OK);
+    if (1) {
+        auto* drive = fs::vfs_open(*fs::fs_root, "/dev/sda1");
+        assert(drive);
+
+        dev_t drive_device;
+        int ret = drive->ind->fs->dev_id(drive->ind, drive_device);
+        assert(ret == 0);
+
+        fs::vfs* new_fs;
+        ret = fs::create_fs("fat32", drive_device, new_fs);
+        assert(ret == 0);
+
+        auto* mount_point = fs::vfs_open(*fs::fs_root, "/mnt");
+        assert(mount_point);
+
+        ret = fs::fs_root->ind->fs->mount(mount_point, new_fs);
+        assert(ret == GB_OK);
+    }
 
     current_process->attr.system = 0;
     current_thread->attr.system = 0;
@@ -566,7 +579,7 @@ void NORETURN _kernel_init(void)
         freeze();
     }
 
-    ret = types::elf::elf32_load(&d);
+    int ret = types::elf::elf32_load(&d);
     assert(ret == GB_OK);
 
     asm volatile(

+ 103 - 349
src/kernel/vfs.cpp

@@ -21,12 +21,9 @@
 #include <types/path.hpp>
 #include <types/string.hpp>
 
-struct tmpfs_file_entry {
-    size_t ino;
-    char filename[128];
-};
+using fs::vfs;
 
-fs::vfs::dentry::dentry(dentry* _parent, inode* _ind, name_type _name)
+vfs::dentry::dentry(dentry* _parent, inode* _ind, name_type _name)
     : parent(_parent) , ind(_ind) , flags { } , name(_name)
 {
     // the dentry is filesystem root or _ind MUST be non null
@@ -38,13 +35,13 @@ fs::vfs::dentry::dentry(dentry* _parent, inode* _ind, name_type _name)
     }
 }
 
-fs::vfs::dentry* fs::vfs::dentry::append(inode* ind, name_type name)
+vfs::dentry* vfs::dentry::append(inode* ind, name_type name)
 {
     auto& ent = children->emplace_back(this, ind, name);
     idx_children->emplace(ent.name, &ent);
     return &ent;
 }
-fs::vfs::dentry* fs::vfs::dentry::find(const name_type& name)
+vfs::dentry* vfs::dentry::find(const name_type& name)
 {
     if (!flags.dir)
         return nullptr;
@@ -67,14 +64,14 @@ fs::vfs::dentry* fs::vfs::dentry::find(const name_type& name)
 
     return iter->second;
 }
-fs::vfs::dentry* fs::vfs::dentry::replace(dentry* val)
+vfs::dentry* vfs::dentry::replace(dentry* val)
 {
     // TODO: prevent the dirent to be swapped out of memory
     parent->idx_children->find(this->name)->second = val;
     return this;
 }
 
-void fs::vfs::dentry::remove(const name_type& name)
+void vfs::dentry::remove(const name_type& name)
 {
     for (auto iter = children->begin(); iter != children->end(); ++iter) {
         if (iter->name != name)
@@ -86,12 +83,12 @@ void fs::vfs::dentry::remove(const name_type& name)
     idx_children->remove(name);
 }
 
-fs::vfs::vfs()
+vfs::vfs()
     : _root { nullptr, nullptr, "" }
 {
 }
 
-void fs::vfs::dentry::path(
+void vfs::dentry::path(
     const dentry& root, types::path &out_dst) const
 {
     const dentry* dents[32];
@@ -109,7 +106,7 @@ void fs::vfs::dentry::path(
         out_dst.append(dents[i]->name.c_str());
 }
 
-fs::inode* fs::vfs::cache_inode(size_t size, ino_t ino,
+fs::inode* vfs::cache_inode(size_t size, ino_t ino,
     mode_t mode, uid_t uid, gid_t gid)
 {
     auto [ iter, inserted ] =
@@ -117,12 +114,12 @@ fs::inode* fs::vfs::cache_inode(size_t size, ino_t ino,
     return &iter->second;
 }
 
-void fs::vfs::free_inode(ino_t ino)
+void vfs::free_inode(ino_t ino)
 {
     assert(_inodes.erase(ino) == 1);
 }
 
-fs::inode* fs::vfs::get_inode(ino_t ino)
+fs::inode* vfs::get_inode(ino_t ino)
 {
     auto iter = _inodes.find(ino);
     // TODO: load inode from disk if not found
@@ -131,12 +128,13 @@ fs::inode* fs::vfs::get_inode(ino_t ino)
     else
         return nullptr;
 }
-void fs::vfs::register_root_node(inode* root)
+
+void vfs::register_root_node(inode* root)
 {
     if (!_root.ind)
         _root.ind = root;
 }
-int fs::vfs::load_dentry(dentry* ent)
+int vfs::load_dentry(dentry* ent)
 {
     auto* ind = ent->ind;
 
@@ -163,7 +161,7 @@ int fs::vfs::load_dentry(dentry* ent)
 
     return GB_OK;
 }
-int fs::vfs::mount(dentry* mnt, vfs* new_fs)
+int vfs::mount(dentry* mnt, vfs* new_fs)
 {
     if (!mnt->flags.dir) {
         errno = ENOTDIR;
@@ -180,329 +178,60 @@ int fs::vfs::mount(dentry* mnt, vfs* new_fs)
 
     return GB_OK;
 }
-size_t fs::vfs::inode_read(inode*, char*, size_t, size_t, size_t)
-{ return -EINVAL; }
-size_t fs::vfs::inode_write(inode*, const char*, size_t, size_t)
-{ return -EINVAL; }
-int fs::vfs::inode_mkfile(dentry*, const char*, mode_t)
-{ return -EINVAL; }
-int fs::vfs::inode_mknode(dentry*, const char*, mode_t, dev_t)
-{ return -EINVAL; }
-int fs::vfs::inode_rmfile(dentry*, const char*)
-{ return -EINVAL; }
-int fs::vfs::inode_mkdir(dentry*, const char*, mode_t)
-{ return -EINVAL; }
-int fs::vfs::inode_statx(dentry*, statx*, unsigned int)
-{ return -EINVAL; }
-int fs::vfs::inode_stat(dentry*, struct stat*)
-{ return -EINVAL; }
-int fs::vfs::inode_devid(fs::inode*, dev_t&)
-{ return -EINVAL; }
-int fs::vfs::truncate(inode*, size_t)
-{ return -EINVAL; }
-
-class tmpfs : public virtual fs::vfs {
-private:
-    using fe_t = tmpfs_file_entry;
-    using vfe_t = std::vector<fe_t>;
-    using fdata_t = std::vector<char>;
-
-private:
-    std::map<ino_t, void*> inode_data;
-    ino_t _next_ino;
-
-private:
-    ino_t _assign_ino(void)
-    {
-        return _next_ino++;
-    }
-
-    static constexpr vfe_t* as_vfe(void* data)
-    {
-        return static_cast<vfe_t*>(data);
-    }
-    static constexpr fdata_t* as_fdata(void* data)
-    {
-        return static_cast<fdata_t*>(data);
-    }
-    static constexpr ptr_t as_val(void* data)
-    {
-        return std::bit_cast<ptr_t>(data);
-    }
-    inline void* _getdata(ino_t ino) const
-    {
-        return inode_data.find(ino)->second;
-    }
-    inline ino_t _savedata(void* data)
-    {
-        ino_t ino = _assign_ino();
-        inode_data.insert(std::make_pair(ino, data));
-        return ino;
-    }
-    inline ino_t _savedata(ptr_t data)
-    {
-        return _savedata((void*)data);
-    }
-
-protected:
-    inline vfe_t* mk_fe_vector() { return new vfe_t{}; }
-    inline fdata_t* mk_data_vector() { return new fdata_t{}; }
-
-    void mklink(fs::inode* dir, fs::inode* inode, const char* filename)
-    {
-        auto* fes = as_vfe(_getdata(dir->ino));
-        fes->emplace_back(fe_t {
-            .ino = inode->ino,
-            .filename = {} });
-        dir->size += sizeof(fe_t);
-
-        auto& emplaced = fes->back();
-
-        strncpy(emplaced.filename, filename, sizeof(emplaced.filename));
-        emplaced.filename[sizeof(emplaced.filename) - 1] = 0;
-
-        ++inode->nlink;
-    }
-
-    virtual int inode_readdir(fs::inode* dir, size_t offset, const fs::vfs::filldir_func& filldir) override
-    {
-        if (!S_ISDIR(dir->mode)) {
-            return -1;
-        }
-
-        auto& entries = *as_vfe(_getdata(dir->ino));
-        size_t off = offset / sizeof(fe_t);
-
-        size_t nread = 0;
-
-        for (; (off + 1) <= entries.size(); ++off, nread += sizeof(fe_t)) {
-            const auto& entry = entries[off];
-            auto* ind = get_inode(entry.ino);
-
-            // inode mode filetype is compatible with user dentry filetype
-            auto ret = filldir(entry.filename, 0, entry.ino, ind->mode & S_IFMT);
-            if (ret != GB_OK)
-                break;
-        }
-
-        return nread;
-    }
-
-public:
-    explicit tmpfs(void)
-        : _next_ino(1)
-    {
-        auto& in = *cache_inode(0, _savedata(mk_fe_vector()), S_IFDIR | 0777, 0, 0);
-
-        mklink(&in, &in, ".");
-        mklink(&in, &in, "..");
-
-        register_root_node(&in);
-    }
-
-    virtual int inode_mkfile(dentry* dir, const char* filename, mode_t mode) override
-    {
-        if (!dir->flags.dir)
-            return -ENOTDIR;
-
-        auto& file = *cache_inode(0, _savedata(mk_data_vector()), S_IFREG | mode, 0, 0);
-        mklink(dir->ind, &file, filename);
-
-        if (dir->flags.present)
-            dir->append(get_inode(file.ino), filename);
-
-        return GB_OK;
-    }
-
-    virtual int inode_mknode(dentry* dir, const char* filename, mode_t mode, dev_t dev) override
-    {
-        if (!dir->flags.dir)
-            return -ENOTDIR;
-
-        if (!S_ISBLK(mode) && !S_ISCHR(mode))
-            return -EINVAL;
-
-        auto& node = *cache_inode(0, _savedata(dev), mode, 0, 0);
-        mklink(dir->ind, &node, filename);
-
-        if (dir->flags.present)
-            dir->append(get_inode(node.ino), filename);
-
-        return GB_OK;
-    }
-
-    virtual int inode_mkdir(dentry* dir, const char* dirname, mode_t mode) override
-    {
-        if (!dir->flags.dir)
-            return -ENOTDIR;
-
-        auto new_dir = cache_inode(0, _savedata(mk_fe_vector()), S_IFDIR | (mode & 0777), 0, 0);
-        mklink(new_dir, new_dir, ".");
-
-        mklink(dir->ind, new_dir, dirname);
-        mklink(new_dir, dir->ind, "..");
-
-        if (dir->flags.present)
-            dir->append(new_dir, dirname);
-
-        return GB_OK;
-    }
-
-    virtual size_t inode_read(fs::inode* file, char* buf, size_t buf_size, size_t offset, size_t n) override
-    {
-        if (!S_ISREG(file->mode))
-            return 0;
-
-        auto* data = as_fdata(_getdata(file->ino));
-        size_t fsize = data->size();
-
-        if (offset + n > fsize)
-            n = fsize - offset;
-
-        if (buf_size < n) {
-            n = buf_size;
-        }
-
-        memcpy(buf, data->data() + offset, n);
-
-        return n;
-    }
-
-    virtual size_t inode_write(fs::inode* file, const char* buf, size_t offset, size_t n) override
-    {
-        if (!S_ISREG(file->mode))
-            return 0;
-
-        auto* data = as_fdata(_getdata(file->ino));
-
-        if (data->size() < offset + n)
-            data->resize(offset+n);
-        memcpy(data->data() + offset, buf, n);
-
-        file->size = data->size();
-
-        return n;
-    }
-
-    virtual int inode_statx(dentry* dent, statx* st, unsigned int mask) override
-    {
-        auto* ind = dent->ind;
-        const mode_t mode = ind->mode;
 
-        st->stx_mask = 0;
+// default behavior is to
+// return -EINVAL to show that the operation
+// is not supported by the fs
 
-        if (mask & STATX_NLINK) {
-            st->stx_nlink = ind->nlink;
-            st->stx_mask |= STATX_NLINK;
-        }
-
-        // TODO: set modification time
-        if (mask & STATX_MTIME) {
-            st->stx_mtime = {};
-            st->stx_mask |= STATX_MTIME;
-        }
-
-        if (mask & STATX_SIZE) {
-            st->stx_size = ind->size;
-            st->stx_mask |= STATX_SIZE;
-        }
-
-        st->stx_mode = 0;
-        if (mask & STATX_MODE) {
-            st->stx_mode |= ind->mode & ~S_IFMT;
-            st->stx_mask |= STATX_MODE;
-        }
-
-        if (mask & STATX_TYPE) {
-            st->stx_mode |= ind->mode & S_IFMT;
-            if (S_ISBLK(mode) || S_ISCHR(mode)) {
-                auto nd = (dev_t)as_val(_getdata(ind->ino));
-                st->stx_rdev_major = NODE_MAJOR(nd);
-                st->stx_rdev_minor = NODE_MINOR(nd);
-            }
-            st->stx_mask |= STATX_TYPE;
-        }
-
-        if (mask & STATX_INO) {
-            st->stx_ino = ind->ino;
-            st->stx_mask |= STATX_INO;
-        }
-
-        if (mask & STATX_BLOCKS) {
-            st->stx_blocks = align_up<9>(ind->size) / 512;
-            st->stx_blksize = 4096;
-            st->stx_mask |= STATX_BLOCKS;
-        }
-
-        if (mask & STATX_UID) {
-            st->stx_uid = ind->uid;
-            st->stx_mask |= STATX_UID;
-        }
-
-        if (mask & STATX_GID) {
-            st->stx_gid = ind->gid;
-            st->stx_mask |= STATX_GID;
-        }
-
-        return GB_OK;
-    }
-
-    virtual int inode_rmfile(dentry* dir, const char* filename) override
-    {
-        if (!dir->flags.dir)
-            return -ENOTDIR;
-
-        auto* vfe = as_vfe(_getdata(dir->ind->ino));
-        assert(vfe);
-
-        auto* dent = dir->find(filename);
-        if (!dent)
-            return -ENOENT;
-
-        for (auto iter = vfe->begin(); iter != vfe->end(); ) {
-            if (iter->ino != dent->ind->ino) {
-                ++iter;
-                continue;
-            }
+size_t vfs::read(inode*, char*, size_t, size_t, size_t)
+{
+    return -EINVAL;
+}
 
-            if (S_ISREG(dent->ind->mode)) {
-                // since we do not allow hard links in tmpfs, there is no need
-                // to check references, we remove the file data directly
-                auto* filedata = as_fdata(_getdata(iter->ino));
-                assert(filedata);
+size_t vfs::write(inode*, const char*, size_t, size_t)
+{
+    return -EINVAL;
+}
 
-                delete filedata;
-            }
+int vfs::inode_mkfile(dentry*, const char*, mode_t)
+{
+    return -EINVAL;
+}
 
-            free_inode(iter->ino);
-            dir->remove(filename);
+int vfs::inode_mknode(dentry*, const char*, mode_t, dev_t)
+{
+    return -EINVAL;
+}
 
-            vfe->erase(iter);
+int vfs::inode_rmfile(dentry*, const char*)
+{
+    return -EINVAL;
+}
 
-            return 0;
-        }
+int vfs::inode_mkdir(dentry*, const char*, mode_t)
+{
+    return -EINVAL;
+}
 
-        kmsg("[tmpfs] warning: file entry not found in vfe\n");
-        return -EIO;
-    }
+int vfs::inode_statx(dentry*, statx*, unsigned int)
+{
+    return -EINVAL;
+}
 
-    virtual int inode_devid(fs::inode* file, dev_t& out_dev) override
-    {
-        out_dev = as_val(_getdata(file->ino));
-        return 0;
-    }
+int vfs::inode_stat(dentry*, struct stat*)
+{
+    return -EINVAL;
+}
 
-    virtual int truncate(fs::inode* file, size_t size) override
-    {
-        if (!S_ISREG(file->mode))
-            return -EINVAL;
+int vfs::dev_id(inode*, dev_t&)
+{
+    return -EINVAL;
+}
 
-        auto* data = as_fdata(_getdata(file->ino));
-        data->resize(size);
-        file->size = size;
-        return GB_OK;
-    }
-};
+int vfs::truncate(inode*, size_t)
+{
+    return -EINVAL;
+}
 
 fs::regular_file::regular_file(vfs::dentry* parent,
     file_flags flags, size_t cursor, inode* ind)
@@ -671,11 +400,11 @@ size_t fs::vfs_read(fs::inode* file, char* buf, size_t buf_size, size_t offset,
     }
 
     if (S_ISREG(file->mode))
-        return file->fs->inode_read(file, buf, buf_size, offset, n);
+        return file->fs->read(file, buf, buf_size, offset, n);
 
     if (S_ISBLK(file->mode) || S_ISCHR(file->mode)) {
         dev_t dev;
-        if (file->fs->inode_devid(file, dev) != 0) {
+        if (file->fs->dev_id(file, dev) != 0) {
             errno = EINVAL;
             return -1U;
         }
@@ -705,11 +434,11 @@ size_t fs::vfs_write(fs::inode* file, const char* buf, size_t offset, size_t n)
     }
 
     if (S_ISREG(file->mode))
-        return file->fs->inode_write(file, buf, offset, n);
+        return file->fs->write(file, buf, offset, n);
 
     if (S_ISBLK(file->mode) || S_ISCHR(file->mode)) {
         dev_t dev;
-        if (file->fs->inode_devid(file, dev) != 0) {
+        if (file->fs->dev_id(file, dev) != 0) {
             errno = EINVAL;
             return -1U;
         }
@@ -731,26 +460,26 @@ size_t fs::vfs_write(fs::inode* file, const char* buf, size_t offset, size_t n)
     errno = EINVAL;
     return -1U;
 }
-int fs::vfs_mkfile(fs::vfs::dentry* dir, const char* filename, mode_t mode)
+int fs::vfs_mkfile(vfs::dentry* dir, const char* filename, mode_t mode)
 {
     return dir->ind->fs->inode_mkfile(dir, filename, mode);
 }
-int fs::vfs_mknode(fs::vfs::dentry* dir, const char* filename, mode_t mode, dev_t dev)
+int fs::vfs_mknode(vfs::dentry* dir, const char* filename, mode_t mode, dev_t dev)
 {
     return dir->ind->fs->inode_mknode(dir, filename, mode, dev);
 }
-int fs::vfs_rmfile(fs::vfs::dentry* dir, const char* filename)
+int fs::vfs_rmfile(vfs::dentry* dir, const char* filename)
 {
     return dir->ind->fs->inode_rmfile(dir, filename);
 }
-int fs::vfs_mkdir(fs::vfs::dentry* dir, const char* dirname, mode_t mode)
+int fs::vfs_mkdir(vfs::dentry* dir, const char* dirname, mode_t mode)
 {
     return dir->ind->fs->inode_mkdir(dir, dirname, mode);
 }
 
-fs::vfs::dentry* fs::vfs_open(fs::vfs::dentry& root, const types::path& path)
+vfs::dentry* fs::vfs_open(vfs::dentry& root, const types::path& path)
 {
-    fs::vfs::dentry* cur = &root;
+    vfs::dentry* cur = &root;
 
     for (const auto& item : path) {
         if (item.empty())
@@ -763,7 +492,7 @@ fs::vfs::dentry* fs::vfs_open(fs::vfs::dentry& root, const types::path& path)
     return cur;
 }
 
-int fs::vfs_stat(fs::vfs::dentry* ent, statx* stat, unsigned int mask)
+int fs::vfs_stat(vfs::dentry* ent, statx* stat, unsigned int mask)
 {
     return ent->ind->fs->inode_statx(ent, stat, mask);
 }
@@ -773,7 +502,7 @@ int fs::vfs_truncate(inode *file, size_t size)
     return file->fs->truncate(file, size);
 }
 
-static std::list<fs::vfs*, types::memory::ident_allocator<fs::vfs*>> fs_es;
+static std::list<fs::vfs*> fs_es;
 
 int fs::register_block_device(dev_t node, fs::blkdev_ops ops)
 {
@@ -795,6 +524,32 @@ int fs::register_char_device(dev_t node, fs::chrdev_ops ops)
     return 0;
 }
 
+static std::list<std::pair<types::string<>, fs::create_fs_func_t>> fs_list;
+
+int fs::register_fs(const char* name, fs::create_fs_func_t func)
+{
+    fs_list.push_back({ {name}, func });
+
+    return 0;
+}
+
+int fs::create_fs(const char* name, dev_t device, vfs*& out_vfs)
+{
+    for (const auto& [ fsname, func ] : fs_list) {
+        if (fsname != name)
+            continue;
+
+        vfs* created_vfs = func(device);
+        fs_es.emplace_back(created_vfs);
+
+        out_vfs = created_vfs;
+
+        return 0;
+    }
+
+    return -ENODEV;
+}
+
 // MBR partition table, used by partprobe()
 
 struct PACKED mbr_part_entry {
@@ -922,12 +677,6 @@ ssize_t fs::char_device_write(dev_t node, const char* buf, size_t n)
     return iter->second.write(buf, n);
 }
 
-fs::vfs* fs::register_fs(vfs* fs)
-{
-    fs_es.push_back(fs);
-    return fs;
-}
-
 ssize_t b_null_read(char* buf, size_t buf_size, size_t n)
 {
     if (n >= buf_size)
@@ -1053,8 +802,13 @@ void init_vfs(void)
     // TODO: add interface to bind console device to other devices
     register_char_device(make_device(2, 0), { console_read, console_write });
 
-    auto* rootfs = new tmpfs;
-    fs_es.push_back(rootfs);
+    // register tmpfs
+    fs::register_tmpfs();
+
+    vfs* rootfs;
+    int ret = create_fs("tmpfs", make_device(0, 0), rootfs);
+
+    assert(ret == 0);
     fs_root = rootfs->root();
 
     vfs_mkdir(fs_root, "dev", 0755);

+ 326 - 0
src/kernel/vfs/tmpfs.cc

@@ -0,0 +1,326 @@
+#include <kernel/vfs.hpp>
+#include <kernel/mm.hpp>
+#include <kernel/log.hpp>
+
+#include <vector>
+#include <map>
+
+using fs::vfs, fs::inode;
+
+struct tmpfs_file_entry {
+    size_t ino;
+    char filename[128];
+};
+
+class tmpfs : public virtual vfs {
+private:
+    using fe_t = tmpfs_file_entry;
+    using vfe_t = std::vector<fe_t>;
+    using fdata_t = std::vector<char>;
+
+private:
+    std::map<ino_t, void*> inode_data;
+    ino_t _next_ino;
+
+private:
+    ino_t _assign_ino(void)
+    {
+        return _next_ino++;
+    }
+
+    static constexpr vfe_t* as_vfe(void* data)
+    {
+        return static_cast<vfe_t*>(data);
+    }
+    static constexpr fdata_t* as_fdata(void* data)
+    {
+        return static_cast<fdata_t*>(data);
+    }
+    static constexpr ptr_t as_val(void* data)
+    {
+        return std::bit_cast<ptr_t>(data);
+    }
+    inline void* _getdata(ino_t ino) const
+    {
+        return inode_data.find(ino)->second;
+    }
+    inline ino_t _savedata(void* data)
+    {
+        ino_t ino = _assign_ino();
+        inode_data.insert(std::make_pair(ino, data));
+        return ino;
+    }
+    inline ino_t _savedata(ptr_t data)
+    {
+        return _savedata((void*)data);
+    }
+
+protected:
+    inline vfe_t* mk_fe_vector() { return new vfe_t{}; }
+    inline fdata_t* mk_data_vector() { return new fdata_t{}; }
+
+    void mklink(inode* dir, inode* inode, const char* filename)
+    {
+        auto* fes = as_vfe(_getdata(dir->ino));
+        fes->emplace_back(fe_t {
+            .ino = inode->ino,
+            .filename = {} });
+        dir->size += sizeof(fe_t);
+
+        auto& emplaced = fes->back();
+
+        strncpy(emplaced.filename, filename, sizeof(emplaced.filename));
+        emplaced.filename[sizeof(emplaced.filename) - 1] = 0;
+
+        ++inode->nlink;
+    }
+
+    virtual int inode_readdir(inode* dir, size_t offset, const fs::vfs::filldir_func& filldir) override
+    {
+        if (!S_ISDIR(dir->mode)) {
+            return -1;
+        }
+
+        auto& entries = *as_vfe(_getdata(dir->ino));
+        size_t off = offset / sizeof(fe_t);
+
+        size_t nread = 0;
+
+        for (; (off + 1) <= entries.size(); ++off, nread += sizeof(fe_t)) {
+            const auto& entry = entries[off];
+            auto* ind = get_inode(entry.ino);
+
+            // inode mode filetype is compatible with user dentry filetype
+            auto ret = filldir(entry.filename, 0, entry.ino, ind->mode & S_IFMT);
+            if (ret != GB_OK)
+                break;
+        }
+
+        return nread;
+    }
+
+public:
+    explicit tmpfs(void)
+        : _next_ino(1)
+    {
+        auto& in = *cache_inode(0, _savedata(mk_fe_vector()), S_IFDIR | 0777, 0, 0);
+
+        mklink(&in, &in, ".");
+        mklink(&in, &in, "..");
+
+        register_root_node(&in);
+    }
+    virtual size_t read(inode* file, char* buf, size_t buf_size, size_t offset, size_t n) override
+    {
+        if (!S_ISREG(file->mode))
+            return 0;
+
+        auto* data = as_fdata(_getdata(file->ino));
+        size_t fsize = data->size();
+
+        if (offset + n > fsize)
+            n = fsize - offset;
+
+        if (buf_size < n) {
+            n = buf_size;
+        }
+
+        memcpy(buf, data->data() + offset, n);
+
+        return n;
+    }
+
+    virtual size_t write(inode* file, const char* buf, size_t offset, size_t n) override
+    {
+        if (!S_ISREG(file->mode))
+            return 0;
+
+        auto* data = as_fdata(_getdata(file->ino));
+
+        if (data->size() < offset + n)
+            data->resize(offset+n);
+        memcpy(data->data() + offset, buf, n);
+
+        file->size = data->size();
+
+        return n;
+    }
+
+    virtual int inode_mkfile(dentry* dir, const char* filename, mode_t mode) override
+    {
+        if (!dir->flags.dir)
+            return -ENOTDIR;
+
+        auto& file = *cache_inode(0, _savedata(mk_data_vector()), S_IFREG | mode, 0, 0);
+        mklink(dir->ind, &file, filename);
+
+        if (dir->flags.present)
+            dir->append(get_inode(file.ino), filename);
+
+        return GB_OK;
+    }
+
+    virtual int inode_mknode(dentry* dir, const char* filename, mode_t mode, dev_t dev) override
+    {
+        if (!dir->flags.dir)
+            return -ENOTDIR;
+
+        if (!S_ISBLK(mode) && !S_ISCHR(mode))
+            return -EINVAL;
+
+        auto& node = *cache_inode(0, _savedata(dev), mode, 0, 0);
+        mklink(dir->ind, &node, filename);
+
+        if (dir->flags.present)
+            dir->append(get_inode(node.ino), filename);
+
+        return GB_OK;
+    }
+
+    virtual int inode_mkdir(dentry* dir, const char* dirname, mode_t mode) override
+    {
+        if (!dir->flags.dir)
+            return -ENOTDIR;
+
+        auto new_dir = cache_inode(0, _savedata(mk_fe_vector()), S_IFDIR | (mode & 0777), 0, 0);
+        mklink(new_dir, new_dir, ".");
+
+        mklink(dir->ind, new_dir, dirname);
+        mklink(new_dir, dir->ind, "..");
+
+        if (dir->flags.present)
+            dir->append(new_dir, dirname);
+
+        return GB_OK;
+    }
+
+    virtual int inode_statx(dentry* dent, statx* st, unsigned int mask) override
+    {
+        auto* ind = dent->ind;
+        const mode_t mode = ind->mode;
+
+        st->stx_mask = 0;
+
+        if (mask & STATX_NLINK) {
+            st->stx_nlink = ind->nlink;
+            st->stx_mask |= STATX_NLINK;
+        }
+
+        // TODO: set modification time
+        if (mask & STATX_MTIME) {
+            st->stx_mtime = {};
+            st->stx_mask |= STATX_MTIME;
+        }
+
+        if (mask & STATX_SIZE) {
+            st->stx_size = ind->size;
+            st->stx_mask |= STATX_SIZE;
+        }
+
+        st->stx_mode = 0;
+        if (mask & STATX_MODE) {
+            st->stx_mode |= ind->mode & ~S_IFMT;
+            st->stx_mask |= STATX_MODE;
+        }
+
+        if (mask & STATX_TYPE) {
+            st->stx_mode |= ind->mode & S_IFMT;
+            if (S_ISBLK(mode) || S_ISCHR(mode)) {
+                auto nd = (dev_t)as_val(_getdata(ind->ino));
+                st->stx_rdev_major = NODE_MAJOR(nd);
+                st->stx_rdev_minor = NODE_MINOR(nd);
+            }
+            st->stx_mask |= STATX_TYPE;
+        }
+
+        if (mask & STATX_INO) {
+            st->stx_ino = ind->ino;
+            st->stx_mask |= STATX_INO;
+        }
+
+        if (mask & STATX_BLOCKS) {
+            st->stx_blocks = align_up<9>(ind->size) / 512;
+            st->stx_blksize = 4096;
+            st->stx_mask |= STATX_BLOCKS;
+        }
+
+        if (mask & STATX_UID) {
+            st->stx_uid = ind->uid;
+            st->stx_mask |= STATX_UID;
+        }
+
+        if (mask & STATX_GID) {
+            st->stx_gid = ind->gid;
+            st->stx_mask |= STATX_GID;
+        }
+
+        return GB_OK;
+    }
+
+    virtual int inode_rmfile(dentry* dir, const char* filename) override
+    {
+        if (!dir->flags.dir)
+            return -ENOTDIR;
+
+        auto* vfe = as_vfe(_getdata(dir->ind->ino));
+        assert(vfe);
+
+        auto* dent = dir->find(filename);
+        if (!dent)
+            return -ENOENT;
+
+        for (auto iter = vfe->begin(); iter != vfe->end(); ) {
+            if (iter->ino != dent->ind->ino) {
+                ++iter;
+                continue;
+            }
+
+            if (S_ISREG(dent->ind->mode)) {
+                // since we do not allow hard links in tmpfs, there is no need
+                // to check references, we remove the file data directly
+                auto* filedata = as_fdata(_getdata(iter->ino));
+                assert(filedata);
+
+                delete filedata;
+            }
+
+            free_inode(iter->ino);
+            dir->remove(filename);
+
+            vfe->erase(iter);
+
+            return 0;
+        }
+
+        kmsg("[tmpfs] warning: file entry not found in vfe\n");
+        return -EIO;
+    }
+
+    virtual int dev_id(inode* file, dev_t& out_dev) override
+    {
+        out_dev = as_val(_getdata(file->ino));
+        return 0;
+    }
+
+    virtual int truncate(inode* file, size_t size) override
+    {
+        if (!S_ISREG(file->mode))
+            return -EINVAL;
+
+        auto* data = as_fdata(_getdata(file->ino));
+        data->resize(size);
+        file->size = size;
+        return GB_OK;
+    }
+};
+
+static tmpfs* create_tmpfs(dev_t)
+{
+    return new tmpfs;
+}
+
+int fs::register_tmpfs()
+{
+    fs::register_fs("tmpfs", {create_tmpfs});
+    return 0;
+}