src/server/test/vfs.c

Wed, 27 Nov 2024 23:00:07 +0100

author
Olaf Wintermann <olaf.wintermann@gmail.com>
date
Wed, 27 Nov 2024 23:00:07 +0100
changeset 563
6ca97c99173e
parent 490
d218607f5a7e
permissions
-rw-r--r--

add TODO to use a future ucx feature

/*
 * 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;
    }
    
    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
        }
    }
    
    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), CX_STORE_POINTERS, 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, CX_STORE_POINTERS, 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);
}

mercurial