--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/server/test/vfs.c Sat Sep 24 16:26:10 2022 +0200 @@ -0,0 +1,558 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2019 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <errno.h> + +#include <ucx/string.h> +#include <ucx/list.h> +#include <ucx/map.h> + +#include "../daemon/session.h" + +#include "testutils.h" + +#include "vfs.h" + +typedef struct TestVFS { + UcxMap *files; + int count_unlink; + int count_rmdir; +} TestVFS; + +typedef struct TestVFSFile { + VFSFile file; + sstr_t path; + int isdir; + UcxBuffer *content; +} TestVFSFile; + +typedef struct TestVFSDir { + VFSDir dir; + TestVFSFile *file; + UcxMapIterator i; + sstr_t name; +} TestVFSDir; + +/* dir io */ + +static char* test_resource_name(char *url) { + sstr_t urlstr = sstr(url); + if(urlstr.ptr[urlstr.length-1] == '/') { + urlstr.length--; + } + sstr_t resname = sstrrchr(urlstr, '/'); + if(resname.length > 1) { + return resname.ptr+1; + } else { + return url; + } +} + +int testvfs_readdir(VFS_DIR dir, VFS_ENTRY *entry, int getstat) { + TestVFS *vfs = dir->ctx->vfs->instance; + TestVFSDir *vfsdir = (TestVFSDir*)dir; + + sstr_t prefix = sstrcat(2, vfsdir->file->path, S("/")); + + TestVFSFile *file = NULL; + UCX_MAP_FOREACH(key, file, vfsdir->i) { + sstr_t file_path = sstrcat( + 2, + prefix, + sstr(test_resource_name(file->path.ptr))); + void *m = ucx_map_get(vfs->files, ucx_key(file_path.ptr, file_path.length)); + // don't ask why alfree and not free() + alfree(ucx_default_allocator(), file_path.ptr); + if(m) { + break; + } else { + file = NULL; + } + } + free(prefix.ptr); + + if(file) { + vfsdir->name = sstrdup_a( + session_get_allocator(dir->ctx->sn), + sstr(test_resource_name(file->path.ptr))); + ZERO(entry, sizeof(VFS_ENTRY)); + entry->name = vfsdir->name.ptr; + + if(getstat) { + ZERO(&entry->stat, sizeof(struct stat)); + if(file->isdir) { + entry->stat.st_mode = S_IFDIR; + } + } + + return 1; + } else { + return 0; + } +} + +void testvfs_dir_close(VFS_DIR dir) { + TestVFSDir *testdir = (TestVFSDir*)dir; + pool_free(testdir->dir.ctx->sn->pool, dir); + +} + + ssize_t testvfs_read(SYS_FILE fd, void *buf, size_t nbyte) { + TestVFSFile *file = (TestVFSFile*)fd; + return (ssize_t)ucx_buffer_read(buf, 1, nbyte, file->content); + } + + ssize_t testvfs_write(SYS_FILE fd, const void *buf, size_t nbyte) { + TestVFSFile *file = (TestVFSFile*)fd; + return (ssize_t)ucx_buffer_write(buf, 1, nbyte, file->content); + } + + ssize_t testvfs_pread(SYS_FILE fd, void *buf, size_t nbyte, off_t offset) { + TestVFSFile *file = (TestVFSFile*)fd; + file->content->pos = (size_t)offset; + return testvfs_read(fd, buf, nbyte); + } + + ssize_t testvfs_pwrite(SYS_FILE fd, const void *buf, size_t nbyte, off_t offset) { + TestVFSFile *file = (TestVFSFile*)fd; + file->content->pos = (size_t)offset; + return testvfs_write(fd, buf, nbyte); + } + + off_t testvfs_seek(SYS_FILE fd, off_t offset, int whence) { + TestVFSFile *file = (TestVFSFile*)fd; + ucx_buffer_seek(file->content, offset, whence); + return (off_t)file->content->pos; + } + + void testvfs_close(SYS_FILE fd) { + TestVFSFile *file = (TestVFSFile*)fd; + file->content->pos = 0; + } + +VFS_IO test_file_io = { + testvfs_read, + testvfs_write, + testvfs_pread, + testvfs_pwrite, + testvfs_seek, + testvfs_close, + NULL, /* aio_read */ + NULL /* aio_write */ +}; + +VFS_DIRIO test_dir_io = { + testvfs_readdir, + testvfs_dir_close +}; + + +/* vfs funcs */ + +SYS_FILE testvfs_open(VFSContext *ctx, const char *path, int oflags) { + TestVFS *vfs = ctx->vfs->instance; + TestVFSFile *file = NULL; + + sstr_t s_path = sstr((char*)path); + if(sstrsuffix(s_path, S("/"))) { + s_path.length--; + } + + file = ucx_map_sstr_get(vfs->files, s_path); + if(!file) { + if((oflags & O_CREAT) == O_CREAT) { + file = pool_malloc(ctx->sn->pool, sizeof(TestVFSFile)); + ZERO(file, sizeof(TestVFSFile)); + file->file.ctx = ctx; + file->path = sstrdup_a(session_get_allocator(ctx->sn), s_path); + file->file.io = &test_file_io; + + file->content = pool_calloc(ctx->sn->pool, 1, sizeof(UcxBuffer)); + file->content->capacity = 2048; + file->content->space = pool_malloc(ctx->sn->pool, file->content->capacity); + file->content->flags = 0; + file->content->pos = 0; + file->content->size = 0; + + ucx_map_sstr_put(vfs->files, s_path, file); + } else { + ctx->vfs_errno = ENOENT; + } + } + + return (SYS_FILE)file; +} + +int testvfs_stat(VFSContext *ctx, const char *path, struct stat *buf) { + TestVFS *vfs = ctx->vfs->instance; + TestVFSFile *file = NULL; + + sstr_t s_path = sstr((char*)path); + if(sstrsuffix(s_path, S("/"))) { + s_path.length--; + } + + file = ucx_map_sstr_get(vfs->files, s_path); + if(!file) { + ctx->vfs_errno = ENOENT; + return 1; + } + + ZERO(buf, sizeof(struct stat)); + if(file->isdir) { + buf->st_mode = S_IFDIR; + } + + return 0; +} + +int testvfs_fstat(VFSContext *ctx, SYS_FILE fd, struct stat *buf) { + return 0; +} + +VFS_DIR testvfs_opendir(VFSContext *ctx, const char *path) { + TestVFS *vfs = ctx->vfs->instance; + TestVFSFile *file = NULL; + + sstr_t s_path = sstr((char*)path); + if(sstrsuffix(s_path, S("/"))) { + s_path.length--; + } + + file = ucx_map_sstr_get(vfs->files, s_path); + if(!file) { + ctx->vfs_errno = ENOENT; + return NULL; + } + + if(!file->isdir) { + return NULL; + } + + TestVFSDir *dir = pool_malloc(ctx->sn->pool, sizeof(TestVFSDir)); + ZERO(dir, sizeof(TestVFSDir)); + dir->file = file; + dir->i = ucx_map_iterator(vfs->files); + + dir->dir.ctx = ctx; + dir->dir.io = &test_dir_io; + + return (VFS_DIR)dir; +} + +VFS_DIR testvfs_fdopendir(VFSContext *ctx, SYS_FILE fd) { + TestVFS *vfs = ctx->vfs->instance; + TestVFSFile *file = (TestVFSFile*)fd; + if(!file->isdir) { + return NULL; + } + + TestVFSDir *dir = pool_malloc(ctx->sn->pool, sizeof(TestVFSDir)); + ZERO(dir, sizeof(TestVFSDir)); + dir->file = file; + dir->i = ucx_map_iterator(vfs->files); + + dir->dir.ctx = ctx; + dir->dir.io = &test_dir_io; + + return (VFS_DIR)dir; +} + +int testvfs_mkdir(VFSContext *ctx, const char *path) { + SYS_FILE fd = testvfs_open(ctx, path, O_CREAT); + if(!fd) { + return 1; + } + + TestVFSFile *file = (TestVFSFile*)fd; + file->isdir = 1; + + return 0; +} + +int testvfs_unlink(VFSContext *ctx, const char *path) { + TestVFS *vfs = ctx->vfs->instance; + TestVFSFile *file = ucx_map_cstr_get(vfs->files, path); + if(!file) { + return 1; + } + + if(file->isdir) { + return 1; + } + + ucx_map_cstr_remove(vfs->files, path); + vfs->count_unlink++; + return 0; +} + +int testvfs_rmdir(VFSContext *ctx, const char *path) { + TestVFS *vfs = ctx->vfs->instance; + TestVFSFile *dir = ucx_map_cstr_get(vfs->files, path); + if(!dir) { + ctx->vfs_errno = ENOENT; + return 1; + } + + if(!dir->isdir) { + return 1; + } + + UcxMapIterator i = ucx_map_iterator(vfs->files); + TestVFSFile *f; + UCX_MAP_FOREACH(key, f, i) { + if(f->path.length > dir->path.length && sstrprefix(f->path, dir->path)){ + return 1; // dir not empty + } + } + + ucx_map_cstr_remove(vfs->files, path); + vfs->count_rmdir++; + return 0; +} + +static VFS testVFSClass = { + testvfs_open, + testvfs_stat, + testvfs_fstat, + testvfs_opendir, + testvfs_fdopendir, + testvfs_mkdir, + testvfs_unlink, + testvfs_rmdir, + 0, + NULL +}; + + +VFS* testvfs_create(Session *sn) { + TestVFS *vfs = pool_malloc(sn->pool, sizeof(TestVFS)); + vfs->count_unlink = 0; + vfs->count_rmdir = 0; + vfs->files = ucx_map_new_a(session_get_allocator(sn), 64); + + testVFSClass.instance = vfs; + return &testVFSClass; +} + + +/* ------------------------------------------------------------------------- */ +// +// VFS Tests +// +/* ------------------------------------------------------------------------- */ + +UCX_TEST(test_vfs_open) { + Session *sn = testutil_session(); + Request *rq = testutil_request(sn->pool, "PUT", "/"); + rq->vfs = testvfs_create(sn); + + VFSContext *vfs = vfs_request_context(sn, rq); + + UCX_TEST_BEGIN; + + UCX_TEST_ASSERT(vfs, "vfs is NULL"); + + SYS_FILE f1 = vfs_open(vfs, "/file1", O_CREAT); + UCX_TEST_ASSERT(f1, "f1 not opened"); + + SYS_FILE f2 = vfs_open(vfs, "/file1", 0); + UCX_TEST_ASSERT(f2, "f2 not opened"); + + UCX_TEST_END; + + testutil_destroy_session(sn); +} + +UCX_TEST(test_vfs_mkdir) { + Session *sn = testutil_session(); + Request *rq = testutil_request(sn->pool, "PUT", "/"); + rq->vfs = testvfs_create(sn); + + VFSContext *vfs = vfs_request_context(sn, rq); + + UCX_TEST_BEGIN; + + int err = vfs_mkdir(vfs, "/dir"); + UCX_TEST_ASSERT(err == 0, "error not 0"); + + SYS_FILE fd = vfs_open(vfs, "/dir", 0); + UCX_TEST_ASSERT(fd, "no fd"); + + UCX_TEST_END; + + testutil_destroy_session(sn); +} + +UCX_TEST(test_vfs_opendir) { + Session *sn = testutil_session(); + Request *rq = testutil_request(sn->pool, "PUT", "/"); + rq->vfs = testvfs_create(sn); + + VFSContext *vfs = vfs_request_context(sn, rq); + + UCX_TEST_BEGIN; + + int err = vfs_mkdir(vfs, "/dir"); + UCX_TEST_ASSERT(err == 0, "error not 0"); + + VFSDir *dir = vfs_opendir(vfs, "/dir"); + UCX_TEST_ASSERT(dir, "no dir"); + + UCX_TEST_END; + + testutil_destroy_session(sn); +} + +UCX_TEST(test_vfs_readdir) { + Session *sn = testutil_session(); + Request *rq = testutil_request(sn->pool, "PUT", "/"); + rq->vfs = testvfs_create(sn); + + VFSContext *vfs = vfs_request_context(sn, rq); + + UCX_TEST_BEGIN; + + int err = vfs_mkdir(vfs, "/dir"); + UCX_TEST_ASSERT(err == 0, "error not 0"); + + // add some test file to /dir + UCX_TEST_ASSERT(vfs_open(vfs, "/dir/file1", O_CREAT), "creation of file1 failed"); + UCX_TEST_ASSERT(vfs_open(vfs, "/dir/file2", O_CREAT), "creation of file2 failed"); + UCX_TEST_ASSERT(vfs_open(vfs, "/dir/file3", O_CREAT), "creation of file3 failed"); + UCX_TEST_ASSERT(vfs_open(vfs, "/dir/file4", O_CREAT), "creation of file4 failed"); + + VFSDir *dir = vfs_opendir(vfs, "/dir"); + UCX_TEST_ASSERT(dir, "dir not opened"); + + UcxMap *files = ucx_map_new(8); + + VFSEntry entry; + while(vfs_readdir(dir, &entry)) { + ucx_map_cstr_put(files, entry.name, dir); + } + + UCX_TEST_ASSERT(files->count == 4, "wrong files count"); + UCX_TEST_ASSERT(ucx_map_cstr_get(files, "file1"), "file1 missing"); + UCX_TEST_ASSERT(ucx_map_cstr_get(files, "file2"), "file2 missing"); + UCX_TEST_ASSERT(ucx_map_cstr_get(files, "file3"), "file3 missing"); + UCX_TEST_ASSERT(ucx_map_cstr_get(files, "file4"), "file4 missing"); + + ucx_map_free(files); + + UCX_TEST_END; + + testutil_destroy_session(sn); +} + +UCX_TEST(test_vfs_unlink) { + Session *sn = testutil_session(); + Request *rq = testutil_request(sn->pool, "PUT", "/"); + rq->vfs = testvfs_create(sn); + + VFSContext *vfs = vfs_request_context(sn, rq); + + UCX_TEST_BEGIN; + // prepare test + int err; + err = vfs_mkdir(vfs, "/dir1"); + UCX_TEST_ASSERT(err == 0, "mkdir 1: error not 0"); + err = vfs_mkdir(vfs, "/dir2"); + UCX_TEST_ASSERT(err == 0, "mkdir 1: error not 0"); + + SYS_FILE f1 = vfs_open(vfs, "/file1", O_CREAT); + UCX_TEST_ASSERT(f1, "f1 not opened"); + + SYS_FILE f2 = vfs_open(vfs, "/file2", O_CREAT); + UCX_TEST_ASSERT(f1, "f2 not opened"); + + SYS_FILE f3 = vfs_open(vfs, "/dir1/file3", O_CREAT); + UCX_TEST_ASSERT(f1, "f3 not opened"); + + // test unlink + err = vfs_unlink(vfs, "/file1"); + UCX_TEST_ASSERT(err == 0, "unlink /file1 failed"); + err = vfs_unlink(vfs, "/dir1/file3"); + UCX_TEST_ASSERT(err == 0, "unlink /dir1/file3 failed"); + + err = vfs_unlink(vfs, "/filex"); + UCX_TEST_ASSERT(err != 0, "unlink /filex should fail"); + + // check if files were removed + SYS_FILE o1 = vfs_open(vfs, "/file1", O_RDONLY); + UCX_TEST_ASSERT(o1 == NULL, "/file1 not deleted"); + SYS_FILE o3 = vfs_open(vfs, "/dir1/file3", O_RDONLY); + UCX_TEST_ASSERT(o1 == NULL, "/dir1/file3 not deleted"); + + // file2 should still be there + SYS_FILE o2 = vfs_open(vfs, "/file2", O_RDONLY); + UCX_TEST_ASSERT(o2, "/file2 deleted"); + + // check if dir unlink fails + err = vfs_unlink(vfs, "/dir1"); + UCX_TEST_ASSERT(err != 0, "unlink dir1 should fail"); + + UCX_TEST_END; + + testutil_destroy_session(sn); +} + +UCX_TEST(test_vfs_rmdir) { + Session *sn = testutil_session(); + Request *rq = testutil_request(sn->pool, "PUT", "/"); + rq->vfs = testvfs_create(sn); + + VFSContext *vfs = vfs_request_context(sn, rq); + + UCX_TEST_BEGIN; + // prepare test + int err; + err = vfs_mkdir(vfs, "/dir1"); + UCX_TEST_ASSERT(err == 0, "mkdir 1: error not 0"); + err = vfs_mkdir(vfs, "/dir2"); + UCX_TEST_ASSERT(err == 0, "mkdir 1: error not 0"); + + SYS_FILE f1 = vfs_open(vfs, "/dir1/file1", O_CREAT); + UCX_TEST_ASSERT(f1, "f1 not opened"); + + err = vfs_rmdir(vfs, "/dir1"); + UCX_TEST_ASSERT(err != 0, "rmdir /dir1 should fail"); + err = vfs_rmdir(vfs, "/dir2"); + UCX_TEST_ASSERT(err == 0, "rmdir /dir2 failed"); + + err = vfs_unlink(vfs, "/dir1/file1"); + UCX_TEST_ASSERT(err == 0, "unlink failed"); + err = vfs_rmdir(vfs, "/dir1"); + UCX_TEST_ASSERT(err == 0, "rmdir /dir1 (2) failed"); + + UCX_TEST_END; + + testutil_destroy_session(sn); +}