Sun, 19 Mar 2023 16:48:19 +0100
implement webdav xattr namespace lists
/* * 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 <cx/string.h> #include <cx/list.h> #include <cx/map.h> #include "../daemon/session.h" #include "testutils.h" #include "vfs.h" typedef struct TestVFS { CxMap *files; int count_unlink; int count_rmdir; } TestVFS; typedef struct TestVFSFile { VFSFile file; cxmutstr path; int isdir; CxBuffer content; } TestVFSFile; typedef struct TestVFSDir { VFSDir dir; TestVFSFile *file; CxIterator i; cxmutstr name; } TestVFSDir; /* dir io */ static const char* test_resource_name(char *url) { cxstring urlstr = cx_str(url); if(urlstr.ptr[urlstr.length-1] == '/') { urlstr.length--; } cxstring resname = cx_strrchr(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; cxmutstr prefix = cx_strcat(2, vfsdir->file->path, cx_str("/")); // not the most efficient file system implementation ever, but it is only // for testing // check every entry in vfs->files and return, if the parent path // matches the open directory TestVFSFile *file = NULL; cx_foreach(TestVFSFile *, entry, vfsdir->i) { if(file) break; cxmutstr file_path = cx_strcat( 2, prefix, cx_str(test_resource_name(entry->path.ptr))); file = cxMapGet(vfs->files, cx_hash_key(file_path.ptr, file_path.length)); free(file_path.ptr); } free(prefix.ptr); if(file) { vfsdir->name = cx_strdup_a( pool_allocator(dir->ctx->sn->pool), cx_str(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)cxBufferRead(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)cxBufferWrite(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; cxBufferSeek(&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; cxstring s_path = cx_str((char*)path); if(cx_strsuffix(s_path, cx_str("/"))) { s_path.length--; } file = cxMapGet(vfs->files, cx_hash_key_bytes((const unsigned char*)s_path.ptr, s_path.length)); 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 = cx_strdup_a(pool_allocator(ctx->sn->pool), s_path); file->file.io = &test_file_io; cxBufferInit(&file->content, pool_malloc(ctx->sn->pool, 2048), 2048, pool_allocator(ctx->sn->pool), 0); cxMapPut(vfs->files, cx_hash_key((void*)s_path.ptr, s_path.length), 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; cxstring s_path = cx_str((char*)path); if(cx_strsuffix(s_path, cx_str("/"))) { s_path.length--; } file = cxMapGet(vfs->files, cx_hash_key((void*)s_path.ptr, s_path.length)); 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; cxstring s_path = cx_str((char*)path); if(cx_strsuffix(s_path, cx_str("/"))) { s_path.length--; } file = cxMapGet(vfs->files, cx_hash_key((void*)s_path.ptr, s_path.length)); 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 = cxMapIteratorValues(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 = cxMapIteratorValues(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; CxHashKey path_key = cx_hash_key_str(path); TestVFSFile *file = cxMapGet(vfs->files, path_key); if(!file) { return 1; } if(file->isdir) { return 1; } (void)cxMapRemove(vfs->files, path_key); vfs->count_unlink++; return 0; } int testvfs_rmdir(VFSContext *ctx, const char *path) { TestVFS *vfs = ctx->vfs->instance; CxHashKey path_key = cx_hash_key_str(path); TestVFSFile *dir = cxMapGet(vfs->files, path_key); if(!dir) { ctx->vfs_errno = ENOENT; return 1; } if(!dir->isdir) { return 1; } CxIterator i = cxMapIteratorValues(vfs->files); cx_foreach(TestVFSFile *, f, i) { if(f->path.length > dir->path.length && cx_strprefix(cx_strcast(f->path), cx_strcast(dir->path))){ return 1; // dir not empty } } (void)cxMapRemove(vfs->files, path_key); 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 = cxHashMapCreate(pool_allocator(sn->pool), 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"); CxMap *files = cxHashMapCreate(cxDefaultAllocator, 8); VFSEntry entry; while(vfs_readdir(dir, &entry)) { cxMapPut(files, cx_hash_key_str(entry.name), dir); } UCX_TEST_ASSERT(files->size == 4, "wrong files count"); UCX_TEST_ASSERT(cxMapGet(files, cx_hash_key_str("file1")), "file1 missing"); UCX_TEST_ASSERT(cxMapGet(files, cx_hash_key_str("file2")), "file2 missing"); UCX_TEST_ASSERT(cxMapGet(files, cx_hash_key_str("file3")), "file3 missing"); UCX_TEST_ASSERT(cxMapGet(files, cx_hash_key_str("file4")), "file4 missing"); cxMapDestroy(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); }