diff --git a/kernel/filesystem/dentry.c b/kernel/filesystem/dentry.c index 278a4c92..f0f4dde3 100644 --- a/kernel/filesystem/dentry.c +++ b/kernel/filesystem/dentry.c @@ -303,21 +303,26 @@ static dentry_t *dentry_resolve_lastseg(dentry_t *parent, char *leaf, lastseg_re void dentry_attach(dentry_t *d, inode_t *inode) { - MOS_ASSERT(d->inode == NULL); + MOS_ASSERT_X(d->inode == NULL, "reattaching an inode to a dentry"); MOS_ASSERT(inode != NULL); + // MOS_ASSERT_X(d->refcount == 1, "dentry %p refcount %zu is not 1", (void *) d, d->refcount); - inode_ref(inode); + for (atomic_t i = 0; i < d->refcount; i++) + inode_ref(inode); // refcount the inode for each reference to the dentry + + inode_ref(inode); // refcount the inode for each reference to the dentry d->inode = inode; } void dentry_detach(dentry_t *d) { - MOS_ASSERT(d->inode != NULL); + if (d->inode == NULL) + return; // the caller should have the only reference to the dentry - MOS_ASSERT(d->refcount == 1); + // MOS_ASSERT(d->refcount == 1); // !! TODO: this assertion fails in vfs_unlinkat - inode_unref(d->inode); + (void) inode_unref(d->inode); // we don't care if the inode is freed or not d->inode = NULL; } diff --git a/kernel/filesystem/dentry_utils.c b/kernel/filesystem/dentry_utils.c index 798c4db8..9c83bf3c 100644 --- a/kernel/filesystem/dentry_utils.c +++ b/kernel/filesystem/dentry_utils.c @@ -16,8 +16,7 @@ dentry_t *dentry_ref(dentry_t *dentry) MOS_ASSERT(dentry); MOS_ASSERT(dentry->inode); // one cannot refcount a dentry without an inode dentry->refcount++; - if (dentry->inode) - inode_ref(dentry->inode); + inode_ref(dentry->inode); pr_dinfo2(dcache_ref, "dentry %p '%s' increased refcount to %zu", (void *) dentry, dentry_name(dentry), dentry->refcount); return dentry; } @@ -61,7 +60,13 @@ __nodiscard bool dentry_unref_one_norelease(dentry_t *dentry) dentry->refcount--; if (dentry->inode) - inode_unref(dentry->inode); + { + if (inode_unref(dentry->inode)) + { + pr_dinfo2(vfs, "inode %p has no more references, releasing", (void *) dentry->inode); + dentry->inode = NULL; + } + } pr_dinfo2(dcache_ref, "dentry %p '%s' decreased refcount to %zu", (void *) dentry, dentry_name(dentry), dentry->refcount); @@ -187,12 +192,31 @@ ssize_t dentry_path(dentry_t *dentry, dentry_t *root, char *buf, size_t size) if (current->name == NULL) current = dentry_root_get_mountpoint(current); - char *newpath = kmalloc(strlen(current->name) + 1 + strlen(path) + 1); - strcpy(newpath, current->name); - strcat(newpath, "/"); - strcat(newpath, path); - kfree(path); - path = newpath; + if (current == NULL) + { + // root for other fs trees + char *newpath = kmalloc(strlen(path) + 3); + strcpy(newpath, ":/"); + strcat(newpath, path); + kfree(path); + path = newpath; + + if (strlen(path) + 1 > size) + return -1; + + const size_t real_size = snprintf(buf, size, "%s", path); + kfree(path); + return real_size; + } + else + { + char *newpath = kmalloc(strlen(current->name) + 1 + strlen(path) + 1); + strcpy(newpath, current->name); + strcat(newpath, "/"); + strcat(newpath, path); + kfree(path); + path = newpath; + } } if (strlen(path) + 1 > size) diff --git a/kernel/filesystem/inode.c b/kernel/filesystem/inode.c index 80036cf9..e7fb7ab0 100644 --- a/kernel/filesystem/inode.c +++ b/kernel/filesystem/inode.c @@ -2,6 +2,7 @@ #include "mos/filesystem/inode.h" +#include "mos/filesystem/page_cache.h" #include "mos/filesystem/vfs_types.h" #include "mos/lib/sync/spinlock.h" #include "mos/mm/slab_autoinit.h" @@ -13,17 +14,6 @@ slab_t *inode_cache; SLAB_AUTOINIT("inode", inode_cache, inode_t); -static bool do_flush_and_drop_cache_page(const uintn key, void *value, void *data) -{ - MOS_UNUSED(key); - MOS_UNUSED(value); - MOS_UNUSED(data); - return true; - // phyframe_t *page = value; - // mmstat_dec1(MEM_PAGECACHE); - // pmm_free(page); -} - static bool vfs_generic_inode_drop(inode_t *inode) { MOS_UNUSED(inode); @@ -35,9 +25,12 @@ static bool inode_try_drop(inode_t *inode) { if (inode->refcount == 0 && inode->nlinks == 0) { + pr_dinfo2(vfs, "inode %p has 0 refcount and 0 nlinks, dropping", (void *) inode); + // drop the inode - inode_cache_t *icache = &inode->cache; - hashmap_foreach(&icache->pages, do_flush_and_drop_cache_page, NULL); + spinlock_acquire(&inode->cache.lock); + pagecache_flush_or_drop_all(&inode->cache, true); + spinlock_release(&inode->cache.lock); bool dropped = false; if (inode->superblock->ops && inode->superblock->ops->drop_inode) @@ -46,7 +39,7 @@ static bool inode_try_drop(inode_t *inode) dropped = vfs_generic_inode_drop(inode); if (!dropped) - pr_warn("inode %p has 0 refcount and 0 nlinks, but could not be dropped", (void *) inode); + pr_warn("inode %p has 0 refcount and 0 nlinks, but failed to be dropped", (void *) inode); return dropped; } @@ -96,8 +89,20 @@ bool inode_unlink(inode_t *dir, dentry_t *dentry) inode_t *inode = dentry->inode; MOS_ASSERT(dir && inode); MOS_ASSERT(inode->nlinks > 0); + inode->nlinks--; + bool ok = true; if (dir->ops->unlink) - dir->ops->unlink(dir, dentry); - return inode_try_drop(dentry->inode); + ok = dir->ops->unlink(dir, dentry); + + if (!ok) + { + inode->nlinks++; + return false; + } + + const bool dropped = inode_try_drop(dentry->inode); + MOS_ASSERT_X(!dropped, "inode %p was dropped accidentally, where dentry %p should be holding a reference", (void *) inode, (void *) dentry); + + return true; } diff --git a/kernel/filesystem/page_cache.c b/kernel/filesystem/page_cache.c index a669900c..203798c9 100644 --- a/kernel/filesystem/page_cache.c +++ b/kernel/filesystem/page_cache.c @@ -2,28 +2,102 @@ #include "mos/filesystem/page_cache.h" +#include "mos/filesystem/vfs_types.h" +#include "mos/filesystem/vfs_utils.h" #include "mos/lib/sync/spinlock.h" #include "mos/mm/mm.h" #include "mos/mm/mmstat.h" #include "mos/mm/physical/pmm.h" #include "mos/syslog/printk.h" +#include #include #include -phyframe_t *pagecache_get_page_for_read(inode_cache_t *cache, off_t pgoff) +struct _flush_and_drop_data +{ + inode_cache_t *icache; + bool should_drop_page; + long ret; +}; + +static bool do_flush_and_drop_cached_page(const uintn key, void *value, void *data) { - MOS_ASSERT(spinlock_is_locked(&cache->lock)); + // key = page number, value = phyframe_t *, data = _flush_and_drop_data * + struct _flush_and_drop_data *const fdd = data; + inode_cache_t *const icache = fdd->icache; + phyframe_t *const page = value; + off_t const pgoff = key; + const bool drop_page = fdd->should_drop_page; + + // !! TODO + // !! currently we have no reliable way to track if a page is dirty + // !! so we always flush it + // !! this causes performance issues, but it's simple and works for now + // if (!page->pagecache.dirty) + + spinlock_assert_locked(&icache->lock); + + long ret = 0; + if (icache->ops->flush_page) + ret = icache->ops->flush_page(icache, pgoff, page); + else + ret = simple_flush_page_discard_data(icache, pgoff, page); + + if (!IS_ERR_VALUE(ret) && drop_page) + { + // only when the page was successfully flushed + hashmap_remove(&icache->pages, pgoff); + mmstat_dec1(MEM_PAGECACHE); + pmm_unref_one(page); + } + + fdd->ret = ret; + return ret == 0; +} + +long pagecache_flush_or_drop(inode_cache_t *icache, off_t pgoff, size_t npages, bool drop_page) +{ + struct _flush_and_drop_data data = { .icache = icache, .should_drop_page = drop_page }; + spinlock_assert_locked(&icache->lock); + long ret = 0; + for (size_t i = 0; i < npages; i++) + { + phyframe_t *page = hashmap_get(&icache->pages, pgoff + i); + if (!page) + continue; + + do_flush_and_drop_cached_page(pgoff + i, page, &data); - void *p = hashmap_get(&cache->pages, pgoff); - if (p) - return p; + if (data.ret != 0) + { + ret = data.ret; + break; + } + } + return ret; +} + +long pagecache_flush_or_drop_all(inode_cache_t *icache, bool drop_page) +{ + spinlock_assert_locked(&icache->lock); + struct _flush_and_drop_data data = { .icache = icache, .should_drop_page = drop_page }; + hashmap_foreach(&icache->pages, do_flush_and_drop_cached_page, &data); + return data.ret; +} + +phyframe_t *pagecache_get_page_for_read(inode_cache_t *cache, off_t pgoff) +{ + spinlock_assert_locked(&cache->lock); + phyframe_t *page = hashmap_get(&cache->pages, pgoff); + if (page) + return page; // fast path if (!cache->ops) return ERR_PTR(-EIO); MOS_ASSERT_X(cache->ops && cache->ops->fill_cache, "no page cache ops for inode %p", (void *) cache->owner); - phyframe_t *page = cache->ops->fill_cache(cache, pgoff); + page = cache->ops->fill_cache(cache, pgoff); if (IS_ERR(page)) return page; mmstat_inc1(MEM_PAGECACHE); diff --git a/kernel/filesystem/userfs/userfs.c b/kernel/filesystem/userfs/userfs.c index 5b94fc86..1521bb4c 100644 --- a/kernel/filesystem/userfs/userfs.c +++ b/kernel/filesystem/userfs/userfs.c @@ -28,6 +28,7 @@ RPC_CLIENT_DEFINE_SIMPLECALL(fs_client, USERFS_IMPL_X) static const inode_ops_t userfs_iops; static const file_ops_t userfs_fops; static const inode_cache_ops_t userfs_inode_cache_ops; +static const superblock_ops_t userfs_sb_ops; inode_t *i_from_pbfull(const pb_inode_info *stat, superblock_t *sb, void *private) { @@ -69,19 +70,19 @@ pb_inode_info *i_to_pb_full(const inode_t *i, pb_inode_info *pbi) return pbi; } -pb_inode_ref *i_to_pb_ref(const inode_t *i, pb_inode_ref *pbi) +pb_inode_ref i_to_pb_ref(const inode_t *i) { - pbi->data = (ptr_t) i->private_data; - return pbi; + pb_inode_ref ref = { .data = (ptr_t) i->private_data }; // for userfs, private_data is the inode reference used by the server + return ref; } void userfs_ensure_connected(userfs_t *userfs) { - if (userfs->rpc_server) + if (likely(userfs->rpc_server)) return; userfs->rpc_server = rpc_client_create(userfs->rpc_server_name); - if (!userfs->rpc_server) + if (unlikely(!userfs->rpc_server)) { pr_warn("userfs_ensure_connected: failed to connect to %s", userfs->rpc_server_name); return; @@ -100,7 +101,7 @@ static void userfs_iop_iterate_dir(dentry_t *dentry, vfs_listdir_state_t *state, { userfs_t *userfs = container_of(dentry->superblock->fs, userfs_t, fs); mos_rpc_fs_readdir_request req = { 0 }; - i_to_pb_ref(dentry->inode, &req.i_ref); + req.i_ref = i_to_pb_ref(dentry->inode); mos_rpc_fs_readdir_response resp = { 0 }; userfs_ensure_connected(userfs); @@ -137,7 +138,7 @@ static bool userfs_iop_lookup(inode_t *dir, dentry_t *dentry) bool ret = false; userfs_t *userfs = container_of(dir->superblock->fs, userfs_t, fs); mos_rpc_fs_lookup_request req = { 0 }; - i_to_pb_ref(dir, &req.i_ref); + req.i_ref = i_to_pb_ref(dir); req.name = (char *) dentry_name(dentry); mos_rpc_fs_lookup_response resp = { 0 }; @@ -154,10 +155,7 @@ static bool userfs_iop_lookup(inode_t *dir, dentry_t *dentry) } if (!resp.result.success) - { - pr_dwarn(userfs, "userfs_iop_lookup: failed to lookup %s: %s", dentry_name(dentry), resp.result.error); - goto leave; - } + goto leave; // ENOENT is not a big deal inode_t *i = i_from_pbfull(&resp.i_info, dir->superblock, (void *) resp.i_ref.data); dentry_attach(dentry, i); @@ -192,17 +190,52 @@ static bool userfs_iop_mknode(inode_t *dir, dentry_t *dentry, file_type_t type, static bool userfs_iop_newfile(inode_t *dir, dentry_t *dentry, file_type_t type, file_perm_t perm) { - MOS_UNUSED(dir); - MOS_UNUSED(dentry); - MOS_UNUSED(type); - MOS_UNUSED(perm); - return false; + bool ret = false; + + mos_rpc_fs_create_file_request req; + req.i_ref = i_to_pb_ref(dir); + req.name = (char *) dentry_name(dentry); + req.type = type; + req.perm = perm; + + mos_rpc_fs_create_file_response resp = { 0 }; + + userfs_t *userfs = container_of(dir->superblock->fs, userfs_t, fs); + userfs_ensure_connected(userfs); + + const pf_point_t pp = profile_enter(); + const int result = fs_client_create_file(userfs->rpc_server, &req, &resp); + profile_leave(pp, "userfs.'%s'.create_file", userfs->rpc_server_name); + + if (result != RPC_RESULT_OK) + { + pr_warn("userfs_iop_newfile: failed to create file %s: %d", dentry_name(dentry), result); + goto bail_out; + } + + if (!resp.result.success) + { + pr_dwarn(userfs, "userfs_iop_newfile: failed to create file %s: %s", dentry_name(dentry), resp.result.error); + goto bail_out; + } + + inode_t *i = i_from_pbfull(&resp.i_info, dir->superblock, (void *) resp.i_ref.data); + dentry_attach(dentry, i); + dentry->superblock = i->superblock = dir->superblock; + i->ops = &userfs_iops; + i->cache.ops = &userfs_inode_cache_ops; + i->file_ops = &userfs_fops; + ret = true; + +bail_out: + pb_release(mos_rpc_fs_create_file_response_fields, &resp); + return ret; } static size_t userfs_iop_readlink(dentry_t *dentry, char *buffer, size_t buflen) { mos_rpc_fs_readlink_request req = { 0 }; - i_to_pb_ref(dentry->inode, &req.i_ref); + req.i_ref = i_to_pb_ref(dentry->inode); mos_rpc_fs_readlink_response resp = { 0 }; userfs_t *userfs = container_of(dentry->superblock->fs, userfs_t, fs); @@ -262,8 +295,35 @@ static bool userfs_iop_symlink(inode_t *dir, dentry_t *dentry, const char *symna static bool userfs_iop_unlink(inode_t *dir, dentry_t *dentry) { - MOS_UNUSED(dir); - MOS_UNUSED(dentry); + mos_rpc_fs_unlink_request req; + req.i_ref = i_to_pb_ref(dir); + req.dentry.inode_id = dentry->inode->ino; + req.dentry.name = (char *) dentry_name(dentry); + + mos_rpc_fs_unlink_response resp = { 0 }; + userfs_t *userfs = container_of(dir->superblock->fs, userfs_t, fs); + userfs_ensure_connected(userfs); + + const pf_point_t pp = profile_enter(); + const int result = fs_client_unlink(userfs->rpc_server, &req, &resp); + profile_leave(pp, "userfs.'%s'.unlink", userfs->rpc_server_name); + + if (result != RPC_RESULT_OK) + { + pr_warn("userfs_iop_unlink: failed to unlink %s: %d", dentry_name(dentry), result); + goto bail_out; + } + + if (!resp.result.success) + { + pr_dwarn(userfs, "userfs_iop_unlink: failed to unlink %s: %s", dentry_name(dentry), resp.result.error); + goto bail_out; + } + + return true; + +bail_out: + pb_release(mos_rpc_fs_unlink_response_fields, &resp); return false; } @@ -293,7 +353,6 @@ static const file_ops_t userfs_fops = { .open = userfs_fop_open, .read = vfs_generic_read, .write = vfs_generic_write, - .flush = NULL, .release = NULL, .seek = NULL, .mmap = NULL, @@ -305,7 +364,7 @@ static phyframe_t *userfs_inode_cache_fill_cache(inode_cache_t *cache, off_t pgo // get a page from the server userfs_t *userfs = container_of(cache->owner->superblock->fs, userfs_t, fs); mos_rpc_fs_getpage_request req = { 0 }; - i_to_pb_ref(cache->owner, &req.i_ref); + req.i_ref = i_to_pb_ref(cache->owner); req.pgoff = pgoff; mos_rpc_fs_getpage_response resp = { 0 }; @@ -317,13 +376,13 @@ static phyframe_t *userfs_inode_cache_fill_cache(inode_cache_t *cache, off_t pgo if (result != RPC_RESULT_OK) { - pr_warn("userfs_inode_cache_fill_cache: failed to getpage %s: %d", dentry_name(cache->owner->superblock->root), result); + pr_warn("userfs_inode_cache_fill_cache: failed to getpage: %d", result); goto bail_out; } if (!resp.result.success) { - pr_dwarn(userfs, "userfs_inode_cache_fill_cache: failed to getpage %s: %s", dentry_name(cache->owner->superblock->root), resp.result.error); + pr_dwarn(userfs, "userfs_inode_cache_fill_cache: failed to getpage: %s", resp.result.error); goto bail_out; } @@ -344,10 +403,85 @@ static phyframe_t *userfs_inode_cache_fill_cache(inode_cache_t *cache, off_t pgo return ERR_PTR(-EIO); } +long userfs_inode_cache_flush_page(inode_cache_t *cache, off_t pgoff, phyframe_t *page) +{ + long ret = 0; + userfs_t *userfs = container_of(cache->owner->superblock->fs, userfs_t, fs); + mos_rpc_fs_putpage_request req = { 0 }; + req.i_ref = i_to_pb_ref(cache->owner); + req.pgoff = pgoff; + req.data = kmalloc(PB_BYTES_ARRAY_T_ALLOCSIZE(MOS_PAGE_SIZE)); + req.data->size = MOS_PAGE_SIZE; + memcpy(req.data->bytes, (void *) phyframe_va(page), MOS_PAGE_SIZE); + + mos_rpc_fs_putpage_response resp = { 0 }; + userfs_ensure_connected(userfs); + + const pf_point_t pp = profile_enter(); + const int result = fs_client_putpage(userfs->rpc_server, &req, &resp); + profile_leave(pp, "userfs.'%s'.putpage", userfs->rpc_server_name); + + if (result != RPC_RESULT_OK) + { + pr_warn("userfs_inode_cache_flush_page: failed to putpage: %d", result); + ret = -EIO; + goto bail_out; + } + + if (!resp.result.success) + { + pr_dwarn(userfs, "userfs_inode_cache_flush_page: failed to putpage: %s", resp.result.error); + ret = -EIO; + goto bail_out; + } + + ret = 0; + +bail_out: + pb_release(mos_rpc_fs_putpage_request_fields, &req); + pb_release(mos_rpc_fs_putpage_response_fields, &resp); + return ret; +} + static const inode_cache_ops_t userfs_inode_cache_ops = { .fill_cache = userfs_inode_cache_fill_cache, - .page_write_begin = NULL, - .page_write_end = NULL, + .page_write_begin = simple_page_write_begin, + .page_write_end = simple_page_write_end, + .flush_page = userfs_inode_cache_flush_page, +}; + +long userfs_sync_inode(inode_t *inode) +{ + userfs_t *userfs = container_of(inode->superblock->fs, userfs_t, fs); + mos_rpc_fs_sync_inode_request req = { 0 }; + req.i_ref = i_to_pb_ref(inode); + req.i_info = *i_to_pb_full(inode, &req.i_info); + + mos_rpc_fs_sync_inode_response resp = { 0 }; + userfs_ensure_connected(userfs); + + const pf_point_t pp = profile_enter(); + const int result = fs_client_sync_inode(userfs->rpc_server, &req, &resp); + profile_leave(pp, "userfs.'%s'.sync_inode", userfs->rpc_server_name); + + if (result != RPC_RESULT_OK) + { + pr_warn("userfs_sync_inode: failed to sync inode %llu: %d", inode->ino, result); + return -EIO; + } + + if (!resp.result.success) + { + pr_dwarn(userfs, "userfs_sync_inode: failed to sync inode %llu: %s", inode->ino, resp.result.error); + return -EIO; + } + + return 0; +} + +static const superblock_ops_t userfs_sb_ops = { + .drop_inode = NULL, + .sync_inode = userfs_sync_inode, }; dentry_t *userfs_fsop_mount(filesystem_t *fs, const char *device, const char *options) @@ -382,6 +516,8 @@ dentry_t *userfs_fsop_mount(filesystem_t *fs, const char *device, const char *op } superblock_t *sb = kmalloc(superblock_cache); + sb->ops = &userfs_sb_ops; + inode_t *i = i_from_pbfull(&resp.root_info, sb, (void *) resp.root_ref.data); sb->fs = fs; diff --git a/kernel/filesystem/vfs.c b/kernel/filesystem/vfs.c index 160827c6..2c450c29 100644 --- a/kernel/filesystem/vfs.c +++ b/kernel/filesystem/vfs.c @@ -1,5 +1,7 @@ // SPDX-License-Identifier: GPL-3.0-or-later +#include "mos/assert.h" +#include "mos/device/timer.h" #include "mos/filesystem/inode.h" #include "mos/filesystem/mount.h" #include "mos/filesystem/page_cache.h" @@ -9,6 +11,7 @@ #include "mos/mm/mmstat.h" #include "mos/mm/physical/pmm.h" #include "mos/mm/slab_autoinit.h" +#include "mos/tasks/kthread.h" #include #include @@ -39,16 +42,52 @@ SLAB_AUTOINIT("superblock", superblock_cache, superblock_t); SLAB_AUTOINIT("mount", mount_cache, mount_t); SLAB_AUTOINIT("file", file_cache, file_t); +static long do_pagecache_flush(file_t *file, off_t pgoff, size_t npages) +{ + pr_dinfo2(vfs, "vfs: flushing page cache for file %pio", (void *) &file->io); + + spinlock_acquire(&file->dentry->inode->cache.lock); + long ret = 0; + if (pgoff == 0 && npages == (size_t) -1) + ret = pagecache_flush_or_drop_all(&file->dentry->inode->cache, false); + else + ret = pagecache_flush_or_drop(&file->dentry->inode->cache, pgoff, npages, false); + + spinlock_release(&file->dentry->inode->cache.lock); + return ret; +} + +static long do_sync_inode(file_t *file) +{ + const superblock_ops_t *ops = file->dentry->inode->superblock->ops; + if (ops && ops->sync_inode) + return ops->sync_inode(file->dentry->inode); + + return 0; +} + // BEGIN: filesystem's io_t operations static void vfs_io_ops_close(io_t *io) { file_t *file = container_of(io, file_t, io); - const file_ops_t *file_ops = file_get_ops(file); - if (file_ops && file_ops->flush) - file_ops->flush(file); - if (file_ops && file_ops->release) - file_ops->release(file); + if (io->type == IO_FILE && io->flags & IO_WRITABLE) // only flush if the file is writable + { + do_pagecache_flush(file, 0, (off_t) -1); + do_sync_inode(file); + } + dentry_unref(file->dentry); + + if (io->type == IO_FILE) + { + const file_ops_t *file_ops = file_get_ops(file); + if (file_ops) + { + if (file_ops->release) + file_ops->release(file); + } + } + kfree(file); } @@ -158,6 +197,8 @@ static vmfault_result_t vfs_fault_handler(vmap_t *vmap, ptr_t fault_addr, pagefa if (IS_ERR(pagecache_page)) return VMFAULT_CANNOT_HANDLE; + // ! mm subsystem has verified that this vmap can be written to, but in the page table it's marked as read-only + // * currently, only CoW pages have this property, we treat this as a CoW page if (info->is_present && info->is_write) { if (pagecache_page == info->faulting_page) @@ -165,7 +206,7 @@ static vmfault_result_t vfs_fault_handler(vmap_t *vmap, ptr_t fault_addr, pagefa else vmap_stat_dec(vmap, cow); // the faulting page is a COW page vmap_stat_inc(vmap, regular); - return mm_resolve_cow_fault(vmap, fault_addr, info); + return mm_resolve_cow_fault(vmap, fault_addr, info); // resolve by copying data page into prevate page } info->backing_page = pagecache_page; @@ -242,6 +283,22 @@ static const io_op_t dir_io_ops = { // END: filesystem's io_t operations +static void vfs_flusher_entry(void *arg) +{ + MOS_UNUSED(arg); + while (true) + { + timer_msleep(10 * 1000); + // pagecache_flush_all(); + } +} + +static void vfs_flusher_init(void) +{ + kthread_create(vfs_flusher_entry, NULL, "vfs_flusher"); +} +MOS_INIT(KTHREAD, vfs_flusher_init); + static void vfs_copy_stat(file_stat_t *statbuf, inode_t *inode) { statbuf->ino = inode->ino; @@ -759,12 +816,41 @@ long vfs_unlinkat(fd_t dirfd, const char *path) return -ENOTSUP; } - inode_unlink(parent_dir->inode, dentry); + if (!inode_unlink(parent_dir->inode, dentry)) + { + dentry_unref(dentry); + return -EIO; + } + + dentry_unref(dentry); // it won't release dentry because dentry->inode is still valid dentry_detach(dentry); - dentry_unref(dentry); + dentry_try_release(dentry); return 0; } +long vfs_fsync(io_t *io, bool sync_metadata, off_t start, off_t end) +{ + pr_dinfo2(vfs, "vfs_fsync(io=%p, sync_metadata=%d, start=%ld, end=%ld)", (void *) io, sync_metadata, start, end); + file_t *file = container_of(io, file_t, io); + + const off_t nbytes = end - start; + const off_t npages = ALIGN_UP_TO_PAGE(nbytes) / MOS_PAGE_SIZE; + const off_t pgoffset = start / MOS_PAGE_SIZE; + + long ret = do_pagecache_flush(file, pgoffset, npages); + if (ret < 0) + return ret; + + if (sync_metadata) + { + ret = do_sync_inode(file); + if (ret < 0) + return ret; + } + + return ret; +} + // ! sysfs support static bool vfs_sysfs_filesystems(sysfs_file_t *f) diff --git a/kernel/filesystem/vfs_utils.c b/kernel/filesystem/vfs_utils.c index f0696248..b8bbd685 100644 --- a/kernel/filesystem/vfs_utils.c +++ b/kernel/filesystem/vfs_utils.c @@ -83,6 +83,14 @@ void simple_page_write_end(inode_cache_t *icache, off_t offset, size_t size, phy icache->owner->size = offset + size; } +long simple_flush_page_discard_data(inode_cache_t *icache, off_t pgoff, phyframe_t *page) +{ + MOS_UNUSED(icache); + MOS_UNUSED(pgoff); + MOS_UNUSED(page); + return 0; +} + // read from the page cache, the size and offset are already validated to be in the file's bounds ssize_t vfs_generic_read(const file_t *file, void *buf, size_t size, off_t offset) { diff --git a/kernel/include/private/mos/assert.h b/kernel/include/private/mos/assert.h index ec38f795..a000ae8f 100644 --- a/kernel/include/private/mos/assert.h +++ b/kernel/include/private/mos/assert.h @@ -29,6 +29,8 @@ mos_warn(__VA_ARGS__); \ } while (0) +#define spinlock_assert_locked(lock) MOS_ASSERT(spinlock_is_locked(lock)) + __BEGIN_DECLS __printf(3, 4) void mos_kwarn(const char *func, u32 line, const char *fmt, ...); diff --git a/kernel/include/private/mos/filesystem/inode.h b/kernel/include/private/mos/filesystem/inode.h index 2f1c3849..af8e8fa1 100644 --- a/kernel/include/private/mos/filesystem/inode.h +++ b/kernel/include/private/mos/filesystem/inode.h @@ -6,13 +6,13 @@ void inode_ref(inode_t *inode); -bool inode_unref(inode_t *inode); +[[nodiscard]] bool inode_unref(inode_t *inode); /** * @brief Unlink a dentry from its parent inode * * @param dir The parent inode * @param dentry The dentry to unlink - * @return true if the inode was removed, false if there was other references/links to the inode + * @return true if the inode was successfully unlinked, false otherwise (but the inode may still be there if other references exist) */ bool inode_unlink(inode_t *dir, dentry_t *dentry); diff --git a/kernel/include/private/mos/filesystem/page_cache.h b/kernel/include/private/mos/filesystem/page_cache.h index 7ec9f75d..22acace2 100644 --- a/kernel/include/private/mos/filesystem/page_cache.h +++ b/kernel/include/private/mos/filesystem/page_cache.h @@ -26,3 +26,23 @@ phyframe_t *pagecache_get_page_for_write(inode_cache_t *cache, off_t pgoff); ssize_t vfs_read_pagecache(inode_cache_t *icache, void *buf, size_t size, off_t offset); ssize_t vfs_write_pagecache(inode_cache_t *icache, const void *buf, size_t total_size, off_t offset); + +/** + * @brief Flush or drop a range of pages from the page cache + * + * @param icache The inode cache + * @param pgoff The starting page offset + * @param npages The number of pages to flush or drop + * @param drop_page Whether to drop the pages, or just flush them + * @return long + */ +long pagecache_flush_or_drop(inode_cache_t *icache, off_t pgoff, size_t npages, bool drop_page); + +/** + * @brief Flush or drop all pages in the page cache + * + * @param icache The inode cache + * @param drop_page Whether to drop the pages, or just flush them + * @return long + */ +long pagecache_flush_or_drop_all(inode_cache_t *icache, bool drop_page); diff --git a/kernel/include/private/mos/filesystem/vfs.h b/kernel/include/private/mos/filesystem/vfs.h index 30019bed..c523f463 100644 --- a/kernel/include/private/mos/filesystem/vfs.h +++ b/kernel/include/private/mos/filesystem/vfs.h @@ -18,18 +18,22 @@ should_inline const file_ops_t *file_get_ops(file_t *file) { if (!file) - return NULL; + goto error; if (!file->dentry) - return NULL; + goto error; if (!file->dentry->inode) - return NULL; + goto error; if (!file->dentry->inode->file_ops) - return NULL; + goto error; return file->dentry->inode->file_ops; + +error: + pr_warn("no file_ops for file %p", (void *) file); + return NULL; } extern dentry_t *root_dentry; @@ -177,4 +181,15 @@ long vfs_fchmodat(fd_t fd, const char *path, int perm, int flags); */ long vfs_unlinkat(fd_t dirfd, const char *path); +/** + * @brief Synchronize a file with the filesystem + * + * @param io The io object of a file_t + * @param sync_metadata Whether to sync the metadata + * @param start The start of the range to sync + * @param end The end of the range to sync + * @return long 0 on success, or errno on failure + */ +long vfs_fsync(io_t *io, bool sync_metadata, off_t start, off_t end); + /** @} */ diff --git a/kernel/include/private/mos/filesystem/vfs_types.h b/kernel/include/private/mos/filesystem/vfs_types.h index a11e0178..0ac17caa 100644 --- a/kernel/include/private/mos/filesystem/vfs_types.h +++ b/kernel/include/private/mos/filesystem/vfs_types.h @@ -86,20 +86,19 @@ typedef struct typedef struct { - bool (*open)(inode_t *inode, file_t *file, bool created); - ssize_t (*read)(const file_t *file, void *buf, size_t size, off_t offset); - ssize_t (*write)(const file_t *file, const void *buf, size_t size, off_t offset); - int (*flush)(file_t *file); - void (*release)(file_t *file); - off_t (*seek)(file_t *file, off_t offset, io_seek_whence_t whence); - bool (*mmap)(file_t *file, vmap_t *vmap, off_t offset); - bool (*munmap)(file_t *file, vmap_t *vmap, bool *unmapped); + bool (*open)(inode_t *inode, file_t *file, bool created); ///< called when a file is opened, or created + ssize_t (*read)(const file_t *file, void *buf, size_t size, off_t offset); ///< read from the file + ssize_t (*write)(const file_t *file, const void *buf, size_t size, off_t offset); ///< write to the file + void (*release)(file_t *file); ///< called when the last reference to the file is dropped + off_t (*seek)(file_t *file, off_t offset, io_seek_whence_t whence); ///< seek to a new position in the file + bool (*mmap)(file_t *file, vmap_t *vmap, off_t offset); ///< map the file into memory + bool (*munmap)(file_t *file, vmap_t *vmap, bool *unmapped); ///< unmap the file from memory } file_ops_t; typedef struct { - /// The inode has zero links and the last reference to the file has been dropped - bool (*drop_inode)(inode_t *inode); + bool (*drop_inode)(inode_t *inode); ///< The inode has zero links and the last reference to the file has been dropped + long (*sync_inode)(inode_t *inode); ///< flush the inode to disk } superblock_ops_t; typedef struct _superblock @@ -132,12 +131,16 @@ typedef struct _inode_cache_ops { /** * @brief Read a page from the underlying storage, at file offset pgoff * MOS_PAGE_SIZE - * */ phyframe_t *(*fill_cache)(inode_cache_t *cache, off_t pgoff); bool (*page_write_begin)(inode_cache_t *cache, off_t file_offset, size_t inpage_size, phyframe_t **page_out, void **data); void (*page_write_end)(inode_cache_t *cache, off_t file_offset, size_t inpage_size, phyframe_t *page, void *data); + + /** + * @brief Flush a page to the underlying storage + */ + long (*flush_page)(inode_cache_t *cache, off_t pgoff, phyframe_t *page); } inode_cache_ops_t; typedef struct _inode_cache diff --git a/kernel/include/private/mos/filesystem/vfs_utils.h b/kernel/include/private/mos/filesystem/vfs_utils.h index f7bffa14..f5a17e89 100644 --- a/kernel/include/private/mos/filesystem/vfs_utils.h +++ b/kernel/include/private/mos/filesystem/vfs_utils.h @@ -28,6 +28,7 @@ int vfs_generic_close(const file_t *file); phyframe_t *simple_fill_cache(inode_cache_t *cache, off_t pgoff); bool simple_page_write_begin(inode_cache_t *icache, off_t offset, size_t size, phyframe_t **page, void **private); void simple_page_write_end(inode_cache_t *icache, off_t offset, size_t size, phyframe_t *page, void *private); +long simple_flush_page_discard_data(inode_cache_t *icache, off_t pgoff, phyframe_t *page); // ! simple in-memory directory iterator void vfs_generic_iterate_dir(const dentry_t *dir, vfs_listdir_state_t *state, dentry_iterator_op op); diff --git a/kernel/include/private/mos/mm/physical/pmm.h b/kernel/include/private/mos/mm/physical/pmm.h index a37d7567..c649ec4d 100644 --- a/kernel/include/private/mos/mm/physical/pmm.h +++ b/kernel/include/private/mos/mm/physical/pmm.h @@ -43,6 +43,12 @@ typedef struct phyframe { as_linked_list; // for use of freelist in the buddy allocator }; + + struct + { + // for page cache frames + bool dirty : 1; ///< 1 if the page is dirty + } pagecache; MOS_WARNING_POP }; @@ -51,7 +57,6 @@ typedef struct phyframe // number of times this frame is mapped, if this drops to 0, the frame is freed atomic_t allocated_refcount; }; - } phyframe_t; MOS_STATIC_ASSERT(sizeof(phyframe_t) == 32, "update phyframe_t struct size"); @@ -72,7 +77,7 @@ typedef enum extern phyframe_t *phyframes; // array of all physical frames -#define phyframe_pfn(frame) ((pfn_t) ((frame) -phyframes)) +#define phyframe_pfn(frame) ((pfn_t) ((frame) - phyframes)) #define pfn_phyframe(pfn) (&phyframes[(pfn)]) extern size_t pmm_total_frames, pmm_allocated_frames, pmm_reserved_frames; diff --git a/kernel/include/public/mos/proto/fs_server.h b/kernel/include/public/mos/proto/fs_server.h index 34b05763..965b737e 100644 --- a/kernel/include/public/mos/proto/fs_server.h +++ b/kernel/include/public/mos/proto/fs_server.h @@ -13,4 +13,8 @@ PB(xarg, 1, readdir, READDIR, mos_rpc_fs_readdir_request, mos_rpc_fs_readdir_response) \ PB(xarg, 2, lookup, LOOKUP, mos_rpc_fs_lookup_request, mos_rpc_fs_lookup_response) \ PB(xarg, 3, readlink, READLINK, mos_rpc_fs_readlink_request, mos_rpc_fs_readlink_response) \ - PB(xarg, 4, getpage, GETPAGE, mos_rpc_fs_getpage_request, mos_rpc_fs_getpage_response) + PB(xarg, 4, getpage, GETPAGE, mos_rpc_fs_getpage_request, mos_rpc_fs_getpage_response) \ + PB(xarg, 5, putpage, PUTPAGE, mos_rpc_fs_putpage_request, mos_rpc_fs_putpage_response) \ + PB(xarg, 6, create_file, CREATE_FILE, mos_rpc_fs_create_file_request, mos_rpc_fs_create_file_response) \ + PB(xarg, 7, sync_inode, SYNC_INODE, mos_rpc_fs_sync_inode_request, mos_rpc_fs_sync_inode_response) \ + PB(xarg, 8, unlink, UNLINK, mos_rpc_fs_unlink_request, mos_rpc_fs_unlink_response)\ diff --git a/kernel/io/io.c b/kernel/io/io.c index 18a5026c..459b0c38 100644 --- a/kernel/io/io.c +++ b/kernel/io/io.c @@ -306,7 +306,7 @@ bool io_munmap(io_t *io, vmap_t *vmap, bool *unmapped) void io_get_name(const io_t *io, char *buf, size_t size) { - if (!io_valid(io)) + if (io == NULL || io->ops == NULL || io->ops->get_name == NULL) { snprintf(buf, size, "", (void *) io); return; diff --git a/kernel/ksyscall.c b/kernel/ksyscall.c index 62208ee0..434aaeb0 100644 --- a/kernel/ksyscall.c +++ b/kernel/ksyscall.c @@ -671,3 +671,15 @@ DEFINE_SYSCALL(long, signal_mask_op)(int how, const sigset_t *set, sigset_t *old return 0; } + +DEFINE_SYSCALL(long, vfs_fsync)(fd_t fd, bool data_only) +{ + io_t *io = process_get_fd(current_process, fd); + if (!io) + return -EBADF; + + if (io->type != IO_FILE) + return -EBADF; + + return vfs_fsync(io, data_only, 0, (off_t) -1); +} diff --git a/kernel/ksyscalls.json b/kernel/ksyscalls.json index 71993c22..0897a595 100644 --- a/kernel/ksyscalls.json +++ b/kernel/ksyscalls.json @@ -499,6 +499,16 @@ "name": "signal_mask_op", "return": "long", "arguments": [ { "type": "int", "arg": "how" }, { "type": "const sigset_t *", "arg": "set" }, { "type": "sigset_t *", "arg": "oldset" } ] + }, + { + "number": 65, + "name": "vfs_fsync", + "return": "long", + "arguments": [ { "type": "fd_t", "arg": "fd" }, { "type": "bool", "arg": "data_only" } ], + "comments": [ + "Synchronize the file to disk.", + "If data_only is true, only the file data is synchronized, otherwise both data and metadata are synchronized." + ] } ] } diff --git a/proto/filesystem.proto b/proto/filesystem.proto index bfc4ddb1..e4b4a7cc 100644 --- a/proto/filesystem.proto +++ b/proto/filesystem.proto @@ -119,3 +119,52 @@ message mos_rpc_fs_getpage_response // we could use a page manager to reference a page and only pass a page uuid here bytes data = 2; } + +message mos_rpc_fs_putpage_request +{ + pb_inode_ref i_ref = 1; // the inode of the file + uint64 pgoff = 2; // the offset of the page, in number of pages + bytes data = 3; // the data of the page +} + +message mos_rpc_fs_putpage_response +{ + mos_rpc.result result = 1; +} + +message mos_rpc_fs_create_file_request +{ + pb_inode_ref i_ref = 1; // the inode of the parent directory + string name = 2; // the name of the file to create + int32 type = 3; // the type of the file to create + uint32 perm = 4; // the permissions of the file to create +} + +message mos_rpc_fs_create_file_response +{ + mos_rpc.result result = 1; + pb_inode_ref i_ref = 2; // the inode of the created file + pb_inode_info i_info = 3; +} + +message mos_rpc_fs_sync_inode_request +{ + pb_inode_ref i_ref = 1; // the inode to sync + pb_inode_info i_info = 2; // the inode info +} + +message mos_rpc_fs_sync_inode_response +{ + mos_rpc.result result = 1; +} + +message mos_rpc_fs_unlink_request +{ + pb_inode_ref i_ref = 1; // the inode of the parent directory + pb_dentry dentry = 2; // the dentry to unlink +} + +message mos_rpc_fs_unlink_response +{ + mos_rpc.result result = 1; +} diff --git a/userspace/drivers/blockdev-manager/blockdev_manager.cpp b/userspace/drivers/blockdev-manager/blockdev_manager.cpp index fa895c57..9954b0de 100644 --- a/userspace/drivers/blockdev-manager/blockdev_manager.cpp +++ b/userspace/drivers/blockdev-manager/blockdev_manager.cpp @@ -188,8 +188,53 @@ rpc_result_code_t BlockManager::read_block(rpc_context_t *ctx, read_block::reque rpc_result_code_t BlockManager::write_block(rpc_context_t *ctx, write_block::request *req, write_block::response *resp) { auto fdtable = get_data(ctx); - MOS_UNUSED(fdtable); - MOS_UNUSED(req); - MOS_UNUSED(resp); + + if (!fdtable->fd_to_device.contains(req->device.devid)) + { + std::cout << "Invalid device handle " << req->device.devid << std::endl; + resp->result.success = false; + resp->result.error = strdup("Invalid device handle"); + return RPC_RESULT_OK; + } + + auto &device = devices[fdtable->fd_to_device[req->device.devid]]; + if (req->n_boffset >= device.n_blocks) + { + std::cout << "Block offset " << req->n_boffset << " out of range" << std::endl; + resp->result.success = false; + resp->result.error = strdup("Block offset out of range"); + return RPC_RESULT_OK; + } + + switch (device.type) + { + case BlockInfo::BLOCKDEV_LAYER: + { + const auto info = std::get(device.info); + const auto server = get_layer_server(info.server_name); + + std::cout << "Writing to layer " << info.partid << " block " << req->n_boffset << std::endl; + + mos_rpc_blockdev_write_partition_block_request part_req = { + .device = { .devid = (u32) -1 }, + .partition = { .partid = info.partid }, + .data = req->data, + .n_boffset = req->n_boffset, + .n_blocks = req->n_blocks, + }; + + return server->write_partition_block(&part_req, resp); + } + + case BlockInfo::BLOCKDEV_DEVICE: + { + const auto servername = std::get(device.info).server_name; + const auto server = get_device_server(servername); + return server->write_block(req, resp); + } + + default: __builtin_unreachable(); + }; + return RPC_RESULT_OK; } diff --git a/userspace/drivers/blockdev-manager/blockdevfs.cpp b/userspace/drivers/blockdev-manager/blockdevfs.cpp index 66efb87b..7eab0b0b 100644 --- a/userspace/drivers/blockdev-manager/blockdevfs.cpp +++ b/userspace/drivers/blockdev-manager/blockdevfs.cpp @@ -133,20 +133,6 @@ rpc_result_code_t BlockdevFSServer::lookup(rpc_context_t *, mos_rpc_fs_lookup_re return RPC_RESULT_OK; } -rpc_result_code_t BlockdevFSServer::readlink(rpc_context_t *, mos_rpc_fs_readlink_request *, mos_rpc_fs_readlink_response *resp) -{ - resp->result.success = false; - resp->result.error = strdup("blockdevfs: no symlinks expected in blockdevfs"); - return RPC_RESULT_OK; -} - -rpc_result_code_t BlockdevFSServer::getpage(rpc_context_t *, mos_rpc_fs_getpage_request *, mos_rpc_fs_getpage_response *resp) -{ - resp->result.success = false; - resp->result.error = strdup("blockdevfs doesn't support reading or writing pages"); - return RPC_RESULT_OK; -} - static void *blockdevfs_worker(void *data) { MOS_UNUSED(data); diff --git a/userspace/drivers/blockdev-manager/blockdevfs.hpp b/userspace/drivers/blockdev-manager/blockdevfs.hpp index e0c83ae5..b52b2642 100644 --- a/userspace/drivers/blockdev-manager/blockdevfs.hpp +++ b/userspace/drivers/blockdev-manager/blockdevfs.hpp @@ -5,6 +5,7 @@ #include "proto/filesystem.pb.h" #include +#include #include #include #include @@ -27,6 +28,4 @@ class BlockdevFSServer : public IUserFSServer rpc_result_code_t mount(rpc_context_t *, mos_rpc_fs_mount_request *req, mos_rpc_fs_mount_response *resp) override; rpc_result_code_t readdir(rpc_context_t *, mos_rpc_fs_readdir_request *req, mos_rpc_fs_readdir_response *resp) override; rpc_result_code_t lookup(rpc_context_t *, mos_rpc_fs_lookup_request *req, mos_rpc_fs_lookup_response *resp) override; - rpc_result_code_t readlink(rpc_context_t *, mos_rpc_fs_readlink_request *, mos_rpc_fs_readlink_response *resp) override; - rpc_result_code_t getpage(rpc_context_t *, mos_rpc_fs_getpage_request *, mos_rpc_fs_getpage_response *resp) override; }; diff --git a/userspace/drivers/device-manager/dm_server.hpp b/userspace/drivers/device-manager/dm_server.hpp index 714cd2b2..f91e40f0 100644 --- a/userspace/drivers/device-manager/dm_server.hpp +++ b/userspace/drivers/device-manager/dm_server.hpp @@ -13,7 +13,7 @@ RPC_DECL_SERVER_INTERFACE_CLASS(IDeviceManager, DEVICE_MANAGER_RPCS_X); class DeviceManagerServer : public IDeviceManager { public: - DeviceManagerServer() : IDeviceManager(MOS_DEVICE_MANAGER_SERVICE_NAME){}; + DeviceManagerServer() : IDeviceManager(MOS_DEVICE_MANAGER_SERVICE_NAME) {}; virtual rpc_result_code_t register_device(rpc_context_t *context, s32 vendor, s32 devid, s32 location, s64 mmio_base) override; virtual rpc_result_code_t register_driver(rpc_context_t *context) override; }; diff --git a/userspace/drivers/fs/ext4/CMakeLists.txt b/userspace/drivers/fs/ext4/CMakeLists.txt index 6262c290..0b6b40fd 100644 --- a/userspace/drivers/fs/ext4/CMakeLists.txt +++ b/userspace/drivers/fs/ext4/CMakeLists.txt @@ -1,6 +1,8 @@ # SPDX-License-Identifier: GPL-3.0-or-later set(LIB_ONLY ON) + +add_definitions(-DCONFIG_USE_DEFAULT_CFG=1) add_subdirectory(lwext4) generate_nanopb_proto(PROTO_SRCS PROTO_HDRS @@ -18,7 +20,6 @@ add_executable(ext4fs target_link_libraries(ext4fs PRIVATE lwext4 blockdev-manager-lib) target_include_directories(ext4fs PRIVATE lwext4/include) -target_compile_definitions(ext4fs PRIVATE -DCONFIG_USE_DEFAULT_CFG=1) target_compile_options(ext4fs PRIVATE -Wno-pedantic) # lwext4 is not pedantic clean target_link_libraries(ext4fs PRIVATE diff --git a/userspace/drivers/fs/ext4/ext4fs.cpp b/userspace/drivers/fs/ext4/ext4fs.cpp index 3cfdb411..6f0c9b48 100644 --- a/userspace/drivers/fs/ext4/ext4fs.cpp +++ b/userspace/drivers/fs/ext4/ext4fs.cpp @@ -2,14 +2,29 @@ #include "ext4fs.hpp" +#include "ext4.h" +#include "ext4_blockdev.h" #include "ext4_dir.h" +#include "ext4_fs.h" #include "ext4_inode.h" +#include "ext4_types.h" +#include "proto/filesystem.pb.h" #include #include +#include #include +#include #include +// clang-format off +u64 inode_index_from_data(pb_inode_ref &ref) { return ref.data; } + +pb_inode_ref make_inode_ref(ext4_inode_ref &ref) { return { .data = ref.index }; } + +pb_inode_ref make_inode_ref(u64 index) { return { .data = index }; } +// clang-format on + static std::optional open_blockdev(const std::string &name) { open_device::request req{ .device_name = strdup(name.c_str()) }; @@ -63,7 +78,7 @@ static int blockdev_bread(struct ext4_blockdev *bdev, void *buf, uint64_t blk_id std::cerr << "Failed to read block" << std::endl; if (resp.result.error) std::cerr << "Error: " << resp.result.error << std::endl; - return -1; + return EIO; } assert(resp.data->size == 512 * blk_cnt); // 512 bytes per block (hardcoded) @@ -73,17 +88,50 @@ static int blockdev_bread(struct ext4_blockdev *bdev, void *buf, uint64_t blk_id pb_release(&mos_rpc_blockdev_read_block_request_msg, &req); pb_release(&mos_rpc_blockdev_read_block_response_msg, &resp); - return 0; + return EOK; } static int blockdev_bwrite(struct ext4_blockdev *bdev, const void *buf, uint64_t blk_id, uint32_t blk_cnt) { - MOS_UNUSED(bdev); - MOS_UNUSED(buf); - MOS_UNUSED(blk_id); - MOS_UNUSED(blk_cnt); - std::cout << "bwrite(" << blk_id << ", " << blk_cnt << ")" << std::endl; - return 0; + const auto state = static_cast(bdev->bdif->p_user); + + const auto data_size = 512 * blk_cnt; // 512 bytes per block (hardcoded) + + write_block::request req{ + .device = state->blockdev, + .data = (pb_bytes_array_t *) malloc(PB_BYTES_ARRAY_T_ALLOCSIZE(data_size)), + .n_boffset = blk_id, + .n_blocks = blk_cnt, + }; + + // std::cout << "Writing " << blk_cnt << " blocks at offset " << blk_id << std::endl; + // std::cout << " buffer: " << buf << std::endl; + // std::cout << " to: " << req.data << std::endl; + // std::cout << std::flush; + + req.data->size = data_size; + memcpy(req.data->bytes, buf, data_size); + + write_block::response resp; + + const auto result = blockdev_manager->write_block(&req, &resp); + + if (result != RPC_RESULT_OK || !resp.result.success) + { + std::cerr << "Failed to write block" << std::endl; + if (resp.result.error) + std::cerr << "Error: " << resp.result.error << std::endl; + + pb_release(&mos_rpc_blockdev_write_block_request_msg, &req); + pb_release(&mos_rpc_blockdev_write_block_response_msg, &resp); + return EIO; + } + + pb_release(&mos_rpc_blockdev_write_block_request_msg, &req); + pb_release(&mos_rpc_blockdev_write_block_response_msg, &resp); + + // std::cout << " ok" << std::endl; + return EOK; } Ext4UserFS::Ext4UserFS(const std::string &name) : IExt4Server(name) @@ -119,6 +167,18 @@ void Ext4UserFS::populate_pb_inode_info(pb_inode_info &info, ext4_sblock *sb, ex info.sgid = false; }; +void Ext4UserFS::save_inode_info(ext4_sblock *sb, ext4_inode *inode, const pb_inode_info &info) +{ + ext4_inode_set_size(inode, info.size); + ext4_inode_set_mode(sb, inode, info.perm); + ext4_inode_set_uid(inode, info.uid); + ext4_inode_set_gid(inode, info.gid); + ext4_inode_set_access_time(inode, info.accessed); + ext4_inode_set_modif_time(inode, info.modified); + ext4_inode_set_change_inode_time(inode, info.created); + ext4_inode_set_links_cnt(inode, info.nlinks); +} + rpc_result_code_t Ext4UserFS::mount(rpc_context_t *ctx, mos_rpc_fs_mount_request *req, mos_rpc_fs_mount_response *resp) { if (req->fs_name != "userfs.ext4"s) @@ -159,20 +219,29 @@ rpc_result_code_t Ext4UserFS::mount(rpc_context_t *ctx, mos_rpc_fs_mount_request ext4_device_register(&state->ext4_dev, "dev"); - if (ext4_mount("dev", "/", true)) + if (int retval = ext4_mount("dev", "/", false); retval != EOK) { resp->result.success = false; - resp->result.error = strdup("Failed to mount ext4 filesystem"); + resp->result.error = strdup(strerror(retval)); return RPC_RESULT_OK; } state->fs = state->ext4_dev.fs; + + if (state->fs->read_only) + { + resp->result.success = false; + resp->result.error = strdup("Filesystem is read-only"); + return RPC_RESULT_OK; + } + state->mp = ext4_get_mount("/"); - ext4_inode_ref *root = new ext4_inode_ref; - ext4_fs_get_inode_ref(state->fs, EXT4_ROOT_INO, root); - populate_pb_inode_info(resp->root_info, &state->fs->sb, root->inode, EXT4_ROOT_INO); - resp->root_ref.data = (ptr_t) root; + ext4_inode_ref root; + ext4_fs_get_inode_ref(state->fs, EXT4_ROOT_INO, &root); + populate_pb_inode_info(resp->root_info, &state->fs->sb, root.inode, EXT4_ROOT_INO); + ext4_fs_put_inode_ref(&root); + resp->root_ref = make_inode_ref(root); resp->result.success = true; resp->result.error = nullptr; @@ -183,13 +252,20 @@ rpc_result_code_t Ext4UserFS::readdir(rpc_context_t *ctx, mos_rpc_fs_readdir_req { auto state = get_data(ctx); - ext4_inode_ref *dir = (ext4_inode_ref *) req->i_ref.data; + ext4_inode_ref dir; + if (ext4_fs_get_inode_ref(state->fs, inode_index_from_data(req->i_ref), &dir) != EOK) + { + resp->result.success = false; + resp->result.error = strdup("Failed to get inode reference"); + return RPC_RESULT_OK; + } ext4_dir_iter iter; - if (ext4_dir_iterator_init(&iter, dir, 0) != EOK) + if (ext4_dir_iterator_init(&iter, &dir, 0) != EOK) { resp->result.success = false; resp->result.error = strdup("Failed to initialize directory iterator"); + ext4_fs_put_inode_ref(&dir); return RPC_RESULT_OK; } @@ -218,9 +294,12 @@ rpc_result_code_t Ext4UserFS::readdir(rpc_context_t *ctx, mos_rpc_fs_readdir_req { resp->result.success = false; resp->result.error = strdup("Failed to finalize directory iterator"); + ext4_fs_put_inode_ref(&dir); return RPC_RESULT_OK; } + ext4_fs_put_inode_ref(&dir); + resp->result.success = true; resp->result.error = nullptr; return RPC_RESULT_OK; @@ -229,26 +308,37 @@ rpc_result_code_t Ext4UserFS::readdir(rpc_context_t *ctx, mos_rpc_fs_readdir_req rpc_result_code_t Ext4UserFS::lookup(rpc_context_t *ctx, mos_rpc_fs_lookup_request *req, mos_rpc_fs_lookup_response *resp) { auto state = get_data(ctx); - auto inode_ref = (ext4_inode_ref *) req->i_ref.data; + + ext4_inode_ref parent_inode_ref; + if (ext4_fs_get_inode_ref(state->fs, inode_index_from_data(req->i_ref), &parent_inode_ref) != EOK) + { + resp->result.success = false; + resp->result.error = strdup("Failed to get inode reference"); + return RPC_RESULT_OK; + } ext4_dir_search_result result; - if (ext4_dir_find_entry(&result, inode_ref, req->name, strlen(req->name)) != EOK) + if (ext4_dir_find_entry(&result, &parent_inode_ref, req->name, strlen(req->name)) != EOK) { resp->result.success = false; resp->result.error = strdup("Failed to find directory entry"); return RPC_RESULT_OK; } - ext4_inode_ref *sub_inode = new ext4_inode_ref; - if (ext4_fs_get_inode_ref(state->fs, result.dentry->inode, sub_inode) != EOK) + ext4_inode_ref sub_inode; + if (ext4_fs_get_inode_ref(state->fs, result.dentry->inode, &sub_inode) != EOK) { + ext4_dir_destroy_result(&parent_inode_ref, &result); resp->result.success = false; resp->result.error = strdup("Failed to get inode reference"); return RPC_RESULT_OK; } - resp->i_ref.data = (ptr_t) sub_inode; - populate_pb_inode_info(resp->i_info, &state->fs->sb, sub_inode->inode, result.dentry->inode); + resp->i_ref = make_inode_ref(result.dentry->inode); + populate_pb_inode_info(resp->i_info, &state->fs->sb, sub_inode.inode, result.dentry->inode); + ext4_dir_destroy_result(&parent_inode_ref, &result); + ext4_fs_put_inode_ref(&sub_inode); + ext4_fs_put_inode_ref(&parent_inode_ref); resp->result.success = true; resp->result.error = nullptr; @@ -258,19 +348,27 @@ rpc_result_code_t Ext4UserFS::lookup(rpc_context_t *ctx, mos_rpc_fs_lookup_reque rpc_result_code_t Ext4UserFS::readlink(rpc_context_t *ctx, mos_rpc_fs_readlink_request *req, mos_rpc_fs_readlink_response *resp) { auto state = get_data(ctx); - auto inode_ref = (ext4_inode_ref *) req->i_ref.data; - size_t file_size = ext4_inode_get_size(&state->fs->sb, inode_ref->inode); + ext4_inode_ref inode_ref; + if (ext4_fs_get_inode_ref(state->fs, inode_index_from_data(req->i_ref), &inode_ref) != EOK) + { + resp->result.success = false; + resp->result.error = strdup("Failed to get inode reference"); + return RPC_RESULT_OK; + } + + size_t file_size = ext4_inode_get_size(&state->fs->sb, inode_ref.inode); if (file_size == 0) { resp->result.success = false; resp->result.error = strdup("File is empty"); + ext4_fs_put_inode_ref(&inode_ref); return RPC_RESULT_OK; } ext4_file file = { .mp = state->mp, - .inode = inode_ref->index, + .inode = inode_ref.index, .flags = O_RDONLY, .fsize = file_size, .fpos = 0, @@ -282,6 +380,7 @@ rpc_result_code_t Ext4UserFS::readlink(rpc_context_t *ctx, mos_rpc_fs_readlink_r { resp->result.success = false; resp->result.error = strdup("Failed to read file"); + ext4_fs_put_inode_ref(&inode_ref); return RPC_RESULT_OK; } @@ -290,44 +389,235 @@ rpc_result_code_t Ext4UserFS::readlink(rpc_context_t *ctx, mos_rpc_fs_readlink_r resp->result.success = true; resp->result.error = nullptr; + ext4_fs_put_inode_ref(&inode_ref); return RPC_RESULT_OK; } rpc_result_code_t Ext4UserFS::getpage(rpc_context_t *ctx, mos_rpc_fs_getpage_request *req, mos_rpc_fs_getpage_response *resp) { auto state = get_data(ctx); - auto inode_ref = (ext4_inode_ref *) req->i_ref.data; - - ext4_inode *inode = inode_ref->inode; - size_t file_size = ext4_inode_get_size(&state->fs->sb, inode); - if (file_size == 0) + ext4_inode_ref inode_ref; + if (ext4_fs_get_inode_ref(state->fs, inode_index_from_data(req->i_ref), &inode_ref) != EOK) { resp->result.success = false; - resp->result.error = strdup("File is empty"); + resp->result.error = strdup("Failed to get inode reference"); return RPC_RESULT_OK; } + ext4_inode *inode = inode_ref.inode; + const size_t file_size = ext4_inode_get_size(&state->fs->sb, inode); + ext4_file file = { .mp = state->mp, - .inode = inode_ref->index, + .inode = inode_ref.index, .flags = O_RDONLY, .fsize = file_size, .fpos = req->pgoff * MOS_PAGE_SIZE, }; - size_t read_size = std::min((size_t) MOS_PAGE_SIZE, file_size - file.fpos); + const size_t read_size = std::min((size_t) MOS_PAGE_SIZE, file_size - file.fpos); resp->data = (pb_bytes_array_t *) malloc(PB_BYTES_ARRAY_T_ALLOCSIZE(read_size)); - size_t read_cnt; + size_t read_cnt = 0; // should zero-initialize if read_size is zero if (ext4_fread(&file, resp->data->bytes, read_size, &read_cnt) != EOK) { resp->result.success = false; resp->result.error = strdup("Failed to read file"); + ext4_fs_put_inode_ref(&inode_ref); return RPC_RESULT_OK; } + assert(read_cnt <= MOS_PAGE_SIZE); resp->data->size = read_cnt; + resp->result.success = true; + resp->result.error = nullptr; + ext4_fs_put_inode_ref(&inode_ref); + return RPC_RESULT_OK; +} + +rpc_result_code_t Ext4UserFS::create_file(rpc_context_t *ctx, mos_rpc_fs_create_file_request *req, mos_rpc_fs_create_file_response *resp) +{ + auto state = get_data(ctx); + ext4_inode_ref inode_ref; + if (ext4_fs_get_inode_ref(state->fs, inode_index_from_data(req->i_ref), &inode_ref) != EOK) + { + resp->result.success = false; + resp->result.error = strdup("Failed to get inode reference"); + return RPC_RESULT_OK; + } + + const auto ext4_ftype = [&]() + { + switch ((file_type_t) req->type) + { + case FILE_TYPE_REGULAR: return EXT4_DE_REG_FILE; + case FILE_TYPE_DIRECTORY: return EXT4_DE_DIR; + case FILE_TYPE_SYMLINK: return EXT4_DE_SYMLINK; + case FILE_TYPE_CHAR_DEVICE: return EXT4_DE_CHRDEV; + case FILE_TYPE_BLOCK_DEVICE: return EXT4_DE_BLKDEV; + case FILE_TYPE_NAMED_PIPE: return EXT4_DE_FIFO; + case FILE_TYPE_SOCKET: return EXT4_DE_SOCK; + case FILE_TYPE_UNKNOWN: return EXT4_DE_UNKNOWN; + default: return EXT4_DE_UNKNOWN; + } + }(); + + ext4_inode_ref child_ref; + int ret = ext4_fs_alloc_inode(state->fs, &child_ref, (int) ext4_ftype); + if (ret != EOK) + { + resp->result.success = false; + resp->result.error = strdup("Failed to allocate inode"); + ext4_fs_put_inode_ref(&inode_ref); + return RPC_RESULT_OK; + } + + ext4_fs_inode_blocks_init(state->fs, &child_ref); + + ret = ext4_link(state->mp, &inode_ref, &child_ref, req->name, strlen(req->name), false); + if (ret != EOK) + { + /*Fail. Free new inode.*/ + ext4_fs_free_inode(&child_ref); + /*We do not want to write new inode. But block has to be released.*/ + child_ref.dirty = false; + ext4_fs_put_inode_ref(&child_ref); + ext4_fs_put_inode_ref(&inode_ref); + resp->result.success = false; + resp->result.error = strdup("Failed to link new inode"); + return RPC_RESULT_OK; + } + + resp->i_ref = make_inode_ref(child_ref); + populate_pb_inode_info(resp->i_info, &state->fs->sb, child_ref.inode, child_ref.index); + ext4_fs_put_inode_ref(&child_ref); + ext4_block_cache_flush(state->mp->bc.bdev); + ext4_fs_put_inode_ref(&inode_ref); + + resp->result.success = true; + resp->result.error = nullptr; + return RPC_RESULT_OK; +} + +rpc_result_code_t Ext4UserFS::putpage(rpc_context_t *ctx, mos_rpc_fs_putpage_request *req, mos_rpc_fs_putpage_response *resp) +{ + auto state = get_data(ctx); + ext4_inode_ref inode_ref; + if (ext4_fs_get_inode_ref(state->fs, inode_index_from_data(req->i_ref), &inode_ref) != EOK) + { + resp->result.success = false; + resp->result.error = strdup("Failed to get inode reference"); + return RPC_RESULT_OK; + } + + ext4_inode *inode = inode_ref.inode; + ext4_file file = { + .mp = state->mp, + .inode = inode_ref.index, + .flags = O_WRONLY, + .fsize = ext4_inode_get_size(&state->fs->sb, inode), + .fpos = 0, + }; + + ext4_fseek(&file, req->pgoff * MOS_PAGE_SIZE, SEEK_SET); + + const auto write_pos = req->pgoff * MOS_PAGE_SIZE; + + // if pos is beyond the file size, we need to extend the file + if (write_pos > file.fsize) + { + size_t pad_size = write_pos - file.fsize; + while (pad_size > 0) + { + char pad[512] = { 0 }; + size_t written = 0; + if (int err = ext4_fwrite(&file, pad, std::min(pad_size, sizeof(pad)), &written); err != EOK) + { + resp->result.success = false; + resp->result.error = strdup((std::string("Failed to pad file: ") + strerror(err)).data()); + ext4_fs_put_inode_ref(&inode_ref); + return RPC_RESULT_OK; + } + + pad_size -= written; + } + } + + size_t written = 0; + + if (int err = ext4_fwrite(&file, req->data->bytes, req->data->size, &written); err != EOK) + { + resp->result.success = false; + resp->result.error = strdup((std::string("Failed to write file: ") + strerror(err)).data()); + ext4_fs_put_inode_ref(&inode_ref); + return RPC_RESULT_OK; + } + + if (written != req->data->size) + { + resp->result.success = false; + resp->result.error = strdup("Failed to write all data"); + ext4_fs_put_inode_ref(&inode_ref); + return RPC_RESULT_OK; + } + + ext4_fs_put_inode_ref(&inode_ref); + resp->result.success = true; + resp->result.error = nullptr; + return RPC_RESULT_OK; +} + +rpc_result_code_t Ext4UserFS::sync_inode(rpc_context_t *ctx, mos_rpc_fs_sync_inode_request *req, mos_rpc_fs_sync_inode_response *resp) +{ + auto state = get_data(ctx); + + ext4_inode_ref inode_ref; + if (ext4_fs_get_inode_ref(state->fs, req->i_info.ino, &inode_ref) != EOK) + { + resp->result.success = false; + resp->result.error = strdup("Failed to get inode reference"); + return RPC_RESULT_OK; + } + + save_inode_info(&state->fs->sb, inode_ref.inode, req->i_info); + inode_ref.dirty = true; + ext4_fs_put_inode_ref(&inode_ref); + resp->result.success = true; + resp->result.error = nullptr; + return RPC_RESULT_OK; +} + +rpc_result_code_t Ext4UserFS::unlink(rpc_context_t *ctx, mos_rpc_fs_unlink_request *req, mos_rpc_fs_unlink_response *resp) +{ + auto state = get_data(ctx); + + ext4_inode_ref dir; + if (ext4_fs_get_inode_ref(state->fs, inode_index_from_data(req->i_ref), &dir) != EOK) + { + resp->result.success = false; + resp->result.error = strdup("Failed to get directory inode reference"); + return RPC_RESULT_OK; + } + + ext4_inode_ref child; + if (ext4_fs_get_inode_ref(state->fs, req->dentry.inode_id, &child) != EOK) + { + resp->result.success = false; + resp->result.error = strdup("Failed to get child inode reference"); + return RPC_RESULT_OK; + } + + if (ext4_unlink(state->mp, &dir, &child, req->dentry.name, strlen(req->dentry.name)) != EOK) + { + resp->result.success = false; + resp->result.error = strdup("Failed to unlink child"); + return RPC_RESULT_OK; + } + + ext4_fs_put_inode_ref(&child); + ext4_fs_put_inode_ref(&dir); + resp->result.success = true; resp->result.error = nullptr; return RPC_RESULT_OK; diff --git a/userspace/drivers/fs/ext4/ext4fs.hpp b/userspace/drivers/fs/ext4/ext4fs.hpp index 1fef5172..e2723c5f 100644 --- a/userspace/drivers/fs/ext4/ext4fs.hpp +++ b/userspace/drivers/fs/ext4/ext4fs.hpp @@ -9,6 +9,7 @@ #include "proto/filesystem.pb.h" #include +#include #include #include #include @@ -88,7 +89,8 @@ class Ext4UserFS : public IExt4Server } }; - void populate_pb_inode_info(pb_inode_info &info, ext4_sblock *sb, ext4_inode *inode, int ino); + static void populate_pb_inode_info(pb_inode_info &info, ext4_sblock *sb, ext4_inode *inode, int ino); + static void save_inode_info(ext4_sblock *sb, ext4_inode *inode, const pb_inode_info &info); private: virtual rpc_result_code_t mount(rpc_context_t *ctx, mos_rpc_fs_mount_request *req, mos_rpc_fs_mount_response *resp) override; @@ -100,4 +102,12 @@ class Ext4UserFS : public IExt4Server virtual rpc_result_code_t readlink(rpc_context_t *ctx, mos_rpc_fs_readlink_request *req, mos_rpc_fs_readlink_response *resp) override; virtual rpc_result_code_t getpage(rpc_context_t *ctx, mos_rpc_fs_getpage_request *req, mos_rpc_fs_getpage_response *resp) override; + + virtual rpc_result_code_t create_file(rpc_context_t *ctx, mos_rpc_fs_create_file_request *req, mos_rpc_fs_create_file_response *resp) override; + + virtual rpc_result_code_t putpage(rpc_context_t *ctx, mos_rpc_fs_putpage_request *req, mos_rpc_fs_putpage_response *resp) override; + + virtual rpc_result_code_t sync_inode(rpc_context_t *ctx, mos_rpc_fs_sync_inode_request *req, mos_rpc_fs_sync_inode_response *resp) override; + + virtual rpc_result_code_t unlink(rpc_context_t *ctx, mos_rpc_fs_unlink_request *req, mos_rpc_fs_unlink_response *resp) override; }; diff --git a/userspace/drivers/fs/ext4/lwext4 b/userspace/drivers/fs/ext4/lwext4 index b951fd2a..9a7cceac 160000 --- a/userspace/drivers/fs/ext4/lwext4 +++ b/userspace/drivers/fs/ext4/lwext4 @@ -1 +1 @@ -Subproject commit b951fd2a1282d953379b5a4a9f284c2a5f8a582f +Subproject commit 9a7cceacc001e2b9a7615a1dec3a2349256d7340 diff --git a/userspace/drivers/fs/ext4/main.cpp b/userspace/drivers/fs/ext4/main.cpp index a1cb2e4d..2106c501 100644 --- a/userspace/drivers/fs/ext4/main.cpp +++ b/userspace/drivers/fs/ext4/main.cpp @@ -4,6 +4,8 @@ #include +#define DEBUG 1 + std::unique_ptr userfs_manager; std::unique_ptr blockdev_manager; @@ -19,6 +21,10 @@ int main(int argc, char **) const auto server_name = "fs.ext4"s; +#if DEBUG + ext4_dmask_set(DEBUG_ALL); +#endif + mos_rpc_fs_register_request req{ .fs = { .name = strdup("ext4") }, .rpc_server_name = strdup(server_name.c_str()), diff --git a/userspace/initrd/assets/init.msh b/userspace/initrd/assets/init.msh index 7d2b6d26..44982b61 100644 --- a/userspace/initrd/assets/init.msh +++ b/userspace/initrd/assets/init.msh @@ -17,4 +17,6 @@ alias threads 'cat /sys/tasks/threads' alias self-test 'mossh -c /initrd/assets/self-test.msh' +alias setup 'source /initrd/assets/setup.msh' + cat /initrd/README.txt diff --git a/userspace/initrd/assets/setup.msh b/userspace/initrd/assets/setup.msh new file mode 100644 index 00000000..8f7ab2aa --- /dev/null +++ b/userspace/initrd/assets/setup.msh @@ -0,0 +1,7 @@ +echo "Setting up the data directory..." +mkdir /data + +echo "Mounting virtblk as /data as userfs.ext4..." +mount virtblk.00:04:00 /data userfs.ext4 + +ls /data diff --git a/userspace/programs/init/bootstrapper/cpiofs_server.c b/userspace/programs/init/bootstrapper/cpiofs_server.c index 0a5be076..a5b634c1 100644 --- a/userspace/programs/init/bootstrapper/cpiofs_server.c +++ b/userspace/programs/init/bootstrapper/cpiofs_server.c @@ -282,6 +282,34 @@ static rpc_result_code_t cpiofs_getpage(rpc_context_t *, mos_rpc_fs_getpage_requ return RPC_RESULT_OK; } +static rpc_result_code_t cpiofs_create_file(rpc_context_t *, mos_rpc_fs_create_file_request *, mos_rpc_fs_create_file_response *resp) +{ + resp->result.success = false; + resp->result.error = strdup("cpiofs: cannot create files"); + return RPC_RESULT_OK; +} + +static rpc_result_code_t cpiofs_putpage(rpc_context_t *, mos_rpc_fs_putpage_request *, mos_rpc_fs_putpage_response *resp) +{ + resp->result.success = false; + resp->result.error = strdup("cpiofs: cannot write to cpiofs"); + return RPC_RESULT_OK; +} + +static rpc_result_code_t cpiofs_sync_inode(rpc_context_t *, mos_rpc_fs_sync_inode_request *, mos_rpc_fs_sync_inode_response *resp) +{ + resp->result.success = false; + resp->result.error = strdup("cpiofs: cannot sync cpiofs"); + return RPC_RESULT_OK; +} + +static rpc_result_code_t cpiofs_unlink(rpc_context_t *, mos_rpc_fs_unlink_request *, mos_rpc_fs_unlink_response *resp) +{ + resp->result.success = false; + resp->result.error = strdup("cpiofs: cannot unlink from cpiofs"); + return RPC_RESULT_OK; +} + void init_start_cpiofs_server(fd_t notifier) { cpiofs = rpc_server_create(CPIOFS_RPC_SERVER_NAME, NULL);