src/server/daemon/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 2013 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.
 */

#define _POSIX_PTHREAD_SEMANTIS

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <aio.h>
#include <cx/hash_map.h>

#include "../util/pool.h"
#include "netsite.h"
#include "acl.h"
#include "vfs.h"
#include "threadpools.h"
#include "event.h"

#define VFS_MALLOC(pool, size) pool ? pool_malloc(pool, size) : malloc(size)
#define VFS_FREE(pool, ptr) pool ? pool_free(pool, ptr) : free(ptr)

static CxMap *vfs_type_map;

static VFS sys_vfs = {
    sys_vfs_open,
    sys_vfs_stat,
    sys_vfs_fstat,
    sys_vfs_opendir,
    sys_vfs_fdopendir,
    sys_vfs_mkdir,
    sys_vfs_unlink,
    sys_vfs_rmdir,
    VFS_CHECKS_ACL,
    NULL
};

static VFS_IO sys_file_io = {
    sys_file_read,
    sys_file_write,
    sys_file_pread,
    sys_file_pwrite,
    sys_file_seek,
    sys_file_close,
    //sys_file_aioread,
    //sys_file_aiowrite,
    NULL,  // aioread
    NULL,  // aiowrite
    NULL   // getetag
};

static VFS_DIRIO sys_dir_io = {
    sys_dir_read,
    sys_dir_close
};

int vfs_init(void) {
    vfs_type_map = cxHashMapCreate(cxDefaultAllocator, CX_STORE_POINTERS, 16);
    if(!vfs_type_map) {
        return -1;
    }
    return 0;
}

int vfs_register_type(const char *name, vfs_init_func vfsInit, vfs_create_func vfsCreate) {
    WS_ASSERT(name);
    
    if(!vfs_type_map) {
        if(vfs_init()) {
            return 1;
        }
    }
    
    VfsType *vfsType = malloc(sizeof(VfsType));
    if(!vfsType) {
        return 1;
    }
    vfsType->init = vfsInit;
    vfsType->create = vfsCreate;
    
    return cxMapPut(vfs_type_map, cx_hash_key_str(name), vfsType);
}

VfsType* vfs_get_type(cxstring vfs_class) {
    return cxMapGet(vfs_type_map, cx_hash_key_bytes((const unsigned char*)vfs_class.ptr, vfs_class.length));
}

void* vfs_init_backend(ServerConfiguration *cfg, pool_handle_t *pool, VfsType *vfs_class, WSConfigNode *config, int *error) {
    *error = 0;
    if(vfs_class->init) {
        void *initData = vfs_class->init(cfg, pool, config);
        if(!initData) {
            *error = 1;
        }
        return initData;
    } else {
        return NULL;
    }
}

VFS* vfs_create(Session *sn, Request *rq, const char *vfs_class, pblock *pb, void *initData) {
    VfsType *vfsType = cxMapGet(vfs_type_map, cx_hash_key_str(vfs_class));
    if(!vfsType) {
        log_ereport(LOG_MISCONFIG, "vfs_create: unkown VFS type %s", vfs_class);
        return NULL;
    }
    
    return vfsType->create(sn, rq, pb, initData);
}

VFSContext* vfs_request_context(Session *sn, Request *rq) {
    WS_ASSERT(sn);
    WS_ASSERT(rq);
    
    VFSContext *ctx = pool_malloc(sn->pool, sizeof(VFSContext));
    if(!ctx) {
        return NULL;
    }
    ctx->sn = sn;
    ctx->rq = rq;
    ctx->vfs = rq->vfs ? rq->vfs : &sys_vfs;
    ctx->user = acllist_getuser(sn, rq, rq->acllist);
    ctx->acllist = rq->acllist;
    ctx->aclreqaccess = rq->aclreqaccess;
    ctx->pool = sn->pool;
    ctx->vfs_errno = 0;
    ctx->error_response_set = 0;
    return ctx;
}

SYS_FILE vfs_open(VFSContext *ctx, const char *path, int oflags) {
    WS_ASSERT(ctx);
    WS_ASSERT(path);
    
    uint32_t access_mask = ctx->aclreqaccess | acl_oflag2mask(oflags);
    
    // ctx->aclreqaccess should be the complete access mask
    uint32_t m = ctx->aclreqaccess; // save original access mask
    ctx->aclreqaccess = access_mask; // set mask for vfs->open call
    if((ctx->vfs->flags & VFS_CHECKS_ACL) != VFS_CHECKS_ACL) {
        // VFS does not evaluates the ACL itself, so we have to do it here
        SysACL sysacl;
        if(sys_acl_check(ctx, access_mask, &sysacl)) {
            return NULL;
        }
    }
    SYS_FILE file = ctx->vfs->open(ctx, path, oflags);
    ctx->aclreqaccess = m; // restore original access mask 
    if(!file && ctx) {
        sys_set_error_status(ctx);
    }
    return file;
}

SYS_FILE vfs_openRO(VFSContext *ctx, const char *path) {
    return vfs_open(ctx, path, O_RDONLY);
}

SYS_FILE vfs_openWO(VFSContext *ctx, const char *path) {
    return vfs_open(ctx, path, O_WRONLY | O_CREAT);
}

SYS_FILE vfs_openRW(VFSContext *ctx, const char *path) {
    return vfs_open(ctx, path, O_RDONLY | O_WRONLY | O_CREAT);
}

int vfs_stat(VFSContext *ctx, const char *path, struct stat *buf) {
    WS_ASSERT(ctx);
    WS_ASSERT(path);
    
    uint32_t access_mask = ctx->aclreqaccess | ACL_READ_ATTRIBUTES;
    
    // ctx->aclreqaccess should be the complete access mask
    uint32_t m = ctx->aclreqaccess; // save original access mask
    ctx->aclreqaccess = access_mask; // set mask for vfs->open call
    if((ctx->vfs->flags & VFS_CHECKS_ACL) != VFS_CHECKS_ACL) {
        // VFS does not evaluates the ACL itself, so we have to do it here
        SysACL sysacl;
        if(sys_acl_check(ctx, access_mask, &sysacl)) {
            return -1;
        }
    }
    int ret = ctx->vfs->stat(ctx, path, buf);
    ctx->aclreqaccess = m; // restore original access mask
    if(ret && ctx) {
        sys_set_error_status(ctx);
    }
    return ret;
}

int vfs_fstat(VFSContext *ctx, SYS_FILE fd, struct stat *buf) {
    WS_ASSERT(ctx);
    WS_ASSERT(fd);
    WS_ASSERT(buf);
    
    int ret = ctx->vfs->fstat(ctx, fd, buf);
    if(ret && ctx) {
        sys_set_error_status(ctx);
    }
    return ret;
}

const char * vfs_getetag(SYS_FILE fd) {
    WS_ASSERT(fd);
    
    if(fd->io->opt_getetag) {
        return fd->io->opt_getetag(fd);
    }
    return NULL;
}

void vfs_close(SYS_FILE fd) {
    WS_ASSERT(fd);
    
    fd->io->close(fd);
    if(fd->ctx) {
        pool_free(fd->ctx->pool, fd);
    } else {
        free(fd);
    }
}

VFS_DIR vfs_opendir(VFSContext *ctx, const char *path) {
    WS_ASSERT(ctx);
    WS_ASSERT(path);
    
    uint32_t access_mask = ctx->aclreqaccess | ACL_LIST;
    
    // ctx->aclreqaccess should be the complete access mask
    uint32_t m = ctx->aclreqaccess; // save original access mask
    ctx->aclreqaccess = access_mask; // set mask for vfs->open call
    if((ctx->vfs->flags & VFS_CHECKS_ACL) != VFS_CHECKS_ACL) {
        // VFS does not evaluates the ACL itself, so we have to do it here
        SysACL sysacl;
        if(sys_acl_check(ctx, access_mask, &sysacl)) {
            return NULL;
        }
    }
    VFS_DIR dir = ctx->vfs->opendir(ctx, path);
    ctx->aclreqaccess = m; // restore original access mask
    if(!dir && ctx) {
        sys_set_error_status(ctx);
    }
    return dir;
}

VFS_DIR vfs_fdopendir(VFSContext *ctx, SYS_FILE fd) {
    WS_ASSERT(ctx);
    WS_ASSERT(path);
    
    uint32_t access_mask = ctx->aclreqaccess | ACL_LIST;
    
    // ctx->aclreqaccess should be the complete access mask
    uint32_t m = ctx->aclreqaccess; // save original access mask
    ctx->aclreqaccess = access_mask; // set mask for vfs->open call
    if((ctx->vfs->flags & VFS_CHECKS_ACL) != VFS_CHECKS_ACL) {
        // VFS does not evaluates the ACL itself, so we have to do it here
        SysACL sysacl;
        if(sys_acl_check(ctx, access_mask, &sysacl)) {
            return NULL;
        }
    }
    VFS_DIR dir = ctx->vfs->fdopendir(ctx, fd);
    ctx->aclreqaccess = m; // restore original access mask
    if(!dir && ctx) {
        sys_set_error_status(ctx);
    }
    return dir;
}

int vfs_readdir(VFS_DIR dir, VFS_ENTRY *entry) {
    WS_ASSERT(dir);
    WS_ASSERT(entry);
    
    return dir->io->readdir(dir, entry, 0);
}

int vfs_readdir_stat(VFS_DIR dir, VFS_ENTRY *entry) {
    WS_ASSERT(dir);
    WS_ASSERT(entry);
    
    return dir->io->readdir(dir, entry, 1);
}

void vfs_closedir(VFS_DIR dir) {
    WS_ASSERT(dir);
    
    dir->io->close(dir);
    if(dir->ctx) {
        VFS_FREE(dir->ctx->pool, dir);
    } else {
        free(dir);
    }
}

int vfs_mkdir(VFSContext *ctx, const char *path) {
    WS_ASSERT(ctx);
    WS_ASSERT(path);
    
    return vfs_path_op(ctx, path, ctx->vfs->mkdir, ACL_ADD_FILE);
}

int vfs_unlink(VFSContext *ctx, const char *path) {
    WS_ASSERT(ctx);
    WS_ASSERT(path);
    
    return vfs_path_op(ctx, path, ctx->vfs->unlink, ACL_DELETE);
}

int vfs_rmdir(VFSContext *ctx, const char *path) {
    WS_ASSERT(ctx);
    WS_ASSERT(path);
    
    return vfs_path_op(ctx, path, ctx->vfs->rmdir, ACL_DELETE);
}

// private
int vfs_path_op(VFSContext *ctx, const char *path, vfs_op_f op, uint32_t access) {  
    uint32_t access_mask = ctx->aclreqaccess;
    access_mask |= access;
    
    // ctx->aclreqaccess should be the complete access mask
    uint32_t m = ctx->aclreqaccess; // save original access mask
    ctx->aclreqaccess = access_mask; // set mask for vfs function call
    if((ctx->vfs->flags & VFS_CHECKS_ACL) != VFS_CHECKS_ACL) {
        // VFS does not evaluates the ACL itself, so we have to do it here
        SysACL sysacl;
        if(sys_acl_check(ctx, access_mask, &sysacl)) {
            return -1;
        }
    }
    int ret = op(ctx, path);
    ctx->aclreqaccess = m; // restore original access mask
    if(ret && ctx) {
        sys_set_error_status(ctx);
    }
    return ret;
}

/* system vfs implementation */

SYS_FILE sys_vfs_open(VFSContext *ctx, const char *path, int oflags) {
    uint32_t access_mask = ctx->aclreqaccess;
    pool_handle_t *pool = ctx->pool;
    
    // check ACLs
    SysACL sysacl;
    if(sys_acl_check(ctx, access_mask, &sysacl)) {
        return NULL;
    }
    
    if(sysacl.acl) {
        if(!fs_acl_check(&sysacl, ctx->user, path, access_mask)) {
            acl_set_error_status(ctx->sn, ctx->rq, sysacl.acl, ctx->user);
            return NULL;
        }
    }
    
    // open file
    mode_t mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH;
    int fd = open(path, oflags, mode);
    if(fd == -1) {
        if(ctx) {
            ctx->vfs_errno = errno;
            sys_set_error_status(ctx);
        }
        return NULL;
    }
    
    // if a file system acl is active, we set the owner for newly created files
    if(((oflags & O_CREAT) == O_CREAT) && sysacl.acl) {
        if(fchown(fd, sysacl.user_uid, sysacl.user_gid)) {
            perror("vfs_open: fchown");
            system_close(fd);
            return NULL;
        }
    }
    
    VFSFile *file = VFS_MALLOC(pool, sizeof(VFSFile));
    if(!file) {
        system_close(fd);
        return NULL;
    }
    file->ctx = ctx;
    file->data = NULL;
    file->fd = fd;
    file->io = &sys_file_io;
    return file;
}

int sys_vfs_stat(VFSContext *ctx, const char *path, struct stat *buf) {
    uint32_t access_mask = ctx->aclreqaccess;
    
    // check ACLs
    SysACL sysacl;
    if(sys_acl_check(ctx, access_mask, &sysacl)) {
        return -1;
    }
    
    if(sysacl.acl) {
        if(!fs_acl_check(&sysacl, ctx->user, path, access_mask)) {
            acl_set_error_status(ctx->sn, ctx->rq, sysacl.acl, ctx->user);
            return -1;
        }
    }
    
    // stat
    if(stat(path, buf)) {
        if(ctx) {
            ctx->vfs_errno = errno;
            sys_set_error_status(ctx);
        }
        return -1;
    }
    
    return 0;
}

int sys_vfs_fstat(VFSContext *ctx, SYS_FILE fd, struct stat *buf) {
    // stat
    if(fstat(fd->fd, buf)) {
        if(ctx) {
            ctx->vfs_errno = errno;
        }
        return -1;
    }
    
    return 0;
}

VFS_DIR sys_vfs_opendir(VFSContext *ctx, const char *path) {
    uint32_t access_mask = ctx->aclreqaccess;
    pool_handle_t *pool = ctx->pool;
    
    // check ACLs
    SysACL sysacl;
    if(sys_acl_check(ctx, access_mask, &sysacl)) {
        return NULL;
    }
    
    if(sysacl.acl) {
        if(!fs_acl_check(&sysacl, ctx->user, path, access_mask)) {
            acl_set_error_status(ctx->sn, ctx->rq, sysacl.acl, ctx->user);
            return NULL;
        }
    }
    
    // open directory
#ifdef BSD
    DIR *sys_dir = opendir(path);
    int dir_fd = sys_dir ? dirfd(sys_dir) : 0;
#else
    int dir_fd = open(path, O_RDONLY);
    if(dir_fd == -1) {
        if(ctx) {
            ctx->vfs_errno = errno;
            sys_set_error_status(ctx);
        }
        return NULL;
    }
    DIR *sys_dir = fdopendir(dir_fd);
#endif
    if(!sys_dir) {
        if(dir_fd > 0) {
            close(dir_fd);
        }
        if(ctx) {
            ctx->vfs_errno = errno;
            sys_set_error_status(ctx);
        }
        return NULL;
    }
    
    SysVFSDir *dir_data = VFS_MALLOC(pool, sizeof(SysVFSDir));
    if(!dir_data) {
        closedir(sys_dir);
        return NULL;
    }
    long maxfilelen = fpathconf(dir_fd, _PC_NAME_MAX);
    size_t entry_len = offsetof(struct dirent, d_name) + maxfilelen + 1;
    dir_data->cur = VFS_MALLOC(pool, entry_len);
    if(!dir_data->cur) {
        closedir(sys_dir);
        VFS_FREE(pool, dir_data);
        return NULL;
    }
    dir_data->dir = sys_dir;
    
    VFSDir *dir = VFS_MALLOC(pool, sizeof(VFSDir));
    if(!dir) {
        closedir(sys_dir);
        VFS_FREE(pool, dir_data->cur);
        VFS_FREE(pool, dir_data);
        return NULL;
    }
    dir->ctx = ctx;
    dir->data = dir_data;
    dir->fd = dir_fd;
    dir->io = &sys_dir_io;
    return dir;
}

VFS_DIR sys_vfs_fdopendir(VFSContext *ctx, SYS_FILE fd) {
    uint32_t access_mask = ctx->aclreqaccess;
    pool_handle_t *pool = ctx->pool;
    
    // check ACLs
    SysACL sysacl;
    if(sys_acl_check(ctx, access_mask, &sysacl)) {
        return NULL;
    }
    
    if(sysacl.acl) {
        if(!fs_acl_check_fd(&sysacl, ctx->user, fd->fd, access_mask)) {
            acl_set_error_status(ctx->sn, ctx->rq, sysacl.acl, ctx->user);
            return NULL;
        }
    }
    
    // open directory
    DIR *sys_dir = fdopendir(fd->fd);
    if(!sys_dir) {
        if(ctx) {
            ctx->vfs_errno = errno;
            sys_set_error_status(ctx);
        }
        return NULL;
    }
    
    SysVFSDir *dir_data = VFS_MALLOC(pool, sizeof(SysVFSDir));
    if(!dir_data) {
        closedir(sys_dir);
        return NULL;
    }
    long maxfilelen = fpathconf(fd->fd, _PC_NAME_MAX);
    size_t entry_len = offsetof(struct dirent, d_name) + maxfilelen + 1;
    dir_data->cur = VFS_MALLOC(pool, entry_len);
    if(!dir_data->cur) {
        closedir(sys_dir);
        VFS_FREE(pool, dir_data);
        return NULL;
    }
    dir_data->dir = sys_dir;
    
    VFSDir *dir = VFS_MALLOC(pool, sizeof(VFSDir));
    if(!dir) {
        closedir(sys_dir);
        VFS_FREE(pool, dir_data->cur);
        VFS_FREE(pool, dir_data);
        return NULL;
    }
    dir->ctx = ctx;
    dir->data = dir_data;
    dir->fd = fd->fd;
    dir->io = &sys_dir_io;
    return dir;
}

int sys_vfs_mkdir(VFSContext *ctx, const char *path) {
    return sys_path_op(ctx, path, sys_mkdir);
}

int sys_vfs_unlink(VFSContext *ctx, const char *path) {
    return sys_path_op(ctx, path, sys_unlink);
}

int sys_vfs_rmdir(VFSContext *ctx, const char *path) {
    return sys_path_op(ctx, path, sys_rmdir);
}


int sys_path_op(VFSContext *ctx, const char *path, sys_op_f op) {
    uint32_t access_mask = ctx->aclreqaccess;
    
    // check ACLs
    SysACL sysacl;
    if(sys_acl_check(ctx, access_mask, &sysacl)) {
        return -1;
    }
    
    if(sysacl.acl) {
        if(!fs_acl_check(&sysacl, ctx->user, path, access_mask)) {
            acl_set_error_status(ctx->sn, ctx->rq, sysacl.acl, ctx->user);
            return -1;
        }
    }
    
    // do path operation
    if(op(ctx, path, &sysacl)) {
        // error
        ctx->vfs_errno = errno;
        sys_set_error_status(ctx);
        return -1;
    }
    
    return 0;
}

int sys_acl_check(VFSContext *ctx, uint32_t access_mask, SysACL *sysacl) {
    if(sysacl) {
        sysacl->acl = NULL;
    }
    if(!ctx) {
        return 0;
    }
    
    ACLListHandle *acllist = ctx->acllist;
    if(acllist) {
        ACLList *acl = acl_evallist(
                acllist,
                ctx->user,
                access_mask,
                &sysacl->acl);
        
        if(acl) {
            acl_set_error_status(ctx->sn, ctx->rq, acl, ctx->user);
            return 1;
        }
    }
    
    return 0;
}

void sys_set_error_status(VFSContext *ctx) {
    if(ctx->sn && ctx->rq && !ctx->error_response_set) {
        int status = util_errno2status(ctx->vfs_errno);
        protocol_status(ctx->sn, ctx->rq, status, NULL);
        ctx->error_response_set = TRUE;
    }
}

ssize_t sys_file_read(SYS_FILE fd, void *buf, size_t nbyte) {
    return read(fd->fd, buf, nbyte);
}

ssize_t sys_file_write(SYS_FILE fd, const void *buf, size_t nbyte) {
    return write(fd->fd, buf, nbyte);
}

ssize_t sys_file_pread(SYS_FILE fd, void *buf, size_t nbyte, off_t offset) {
    return pread(fd->fd, buf, nbyte, offset);
}

ssize_t sys_file_pwrite(SYS_FILE fd, const void *buf, size_t nbyte, off_t offset) {
    return pwrite(fd->fd, buf, nbyte, offset);
}

off_t sys_file_seek(SYS_FILE fd, off_t offset, int whence) {
    return lseek(fd->fd, offset, whence);
}

void sys_file_close(SYS_FILE fd) {
    system_close(fd->fd);
}

int sys_file_aioread(aiocb_s *aiocb) {
    WS_ASSERT(aiocb->buf);
    WS_ASSERT(aiocb->nbytes > 0);
    return ev_aioread(aiocb->filedes->fd, aiocb);
}

int sys_file_aiowrite(aiocb_s *aiocb) {
    WS_ASSERT(aiocb->buf);
    WS_ASSERT(aiocb->nbytes > 0);
    return ev_aiowrite(aiocb->filedes->fd, aiocb);
}


int sys_dir_read(VFS_DIR dir, VFS_ENTRY *entry, int getstat) {
    SysVFSDir *dirdata = dir->data;
    struct dirent *result = NULL;
    int s = readdir_r(dirdata->dir, dirdata->cur, &result);
    if(!s && result) {
        char *name = result->d_name;
        if(!strcmp(name, ".") || !strcmp(name, "..")) {
            return sys_dir_read(dir, entry, getstat);
        } else {
            entry->name = name;
            if(getstat) {
                // TODO: check ACLs again for new path
                entry->stat_errno = 0;
                if(fstatat(dir->fd, result->d_name, &entry->stat, 0)) {
                    entry->stat_errno = errno;
                }
                entry->stat_extra = NULL;
            }     
            return 1;
        }
    } else {
        return 0;
    }
}

void sys_dir_close(VFS_DIR dir) {
    SysVFSDir *dirdata = dir->data;
    closedir(dirdata->dir);
    
    pool_handle_t *pool = dir->ctx->pool;
    if(pool) {
        pool_free(pool, dirdata->cur);
        pool_free(pool, dirdata);
        pool_free(pool, dir);
    } else {
        free(dirdata->cur);
        free(dirdata);
        free(dir);
    }
}

int sys_mkdir(VFSContext *ctx, const char *path, SysACL *sysacl) {
    mode_t mode = S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH;
    int ret = mkdir(path, mode);
    if(ret == 0) {
        if(sysacl->user_uid != -1) {
            if(chown(path, sysacl->user_uid, sysacl->user_gid)) {
                // TODO: error
            }
        }
    }
    return ret;
}

int sys_unlink(VFSContext *ctx, const char *path, SysACL *sysacl) {
    return unlink(path);
}

int sys_rmdir(VFSContext *ctx, const char *path, SysACL *sysacl) {
    return rmdir(path);
}

/* public file api */

NSAPI_PUBLIC int system_fread(SYS_FILE fd, void *buf, int nbyte) {
    return fd->io->read(fd, buf, nbyte);
}

NSAPI_PUBLIC int system_fwrite(SYS_FILE fd, const void *buf, int nbyte) {
    return fd->io->write(fd, buf, nbyte);
}

NSAPI_PUBLIC int system_pread(SYS_FILE fd, void *buf, int nbyte, off_t offset) {
    return fd->io->pread(fd, buf, nbyte, offset);
}

NSAPI_PUBLIC int system_pwrite(SYS_FILE fd, const void *buf, int nbyte, off_t offset) {
    return fd->io->pwrite(fd, buf, nbyte, offset);
}

NSAPI_PUBLIC off_t system_lseek(SYS_FILE fd, off_t offset, int whence) {
    return fd->io->seek(fd, offset, whence);
}

NSAPI_PUBLIC int system_fclose(SYS_FILE fd) {
    vfs_close(fd);
    return 0;
}

NSAPI_PUBLIC int vfs_is_sys(VFS *vfs) {
    if(!vfs) return 1;
    if(vfs == &sys_vfs) return 1;
    return 0;
}

// AIO API

NSAPI_PUBLIC int system_aio_read(aiocb_s *aiocb) {
    if(!aiocb->event || !aiocb->evhandler) {
        return -1;
    }
   
    SYS_FILE file = aiocb->filedes;
    aiocb->event->object = (intptr_t)aiocb;
    if(file->io->opt_aioread) {
        return file->io->opt_aioread(aiocb);
    } else {
        vfs_queue_aio(aiocb, VFS_AIO_READ);
        return 0;
    }
}

NSAPI_PUBLIC int system_aio_write(aiocb_s *aiocb) {
    if(!aiocb->event || !aiocb->evhandler) {
        return -1;
    }
    
    SYS_FILE file = aiocb->filedes;
    aiocb->event->object = (intptr_t)aiocb;
    if(file->io->opt_aiowrite) {
        return file->io->opt_aiowrite(aiocb);
    } else {
        vfs_queue_aio(aiocb, VFS_AIO_WRITE);
        return 0;
    }
}

static void* vfs_aio_read(aiocb_s *aiocb) {
    int result = system_pread(aiocb->filedes, aiocb->buf, aiocb->nbytes, aiocb->offset);
    aiocb->result = result;
    if(result < 0) {
        aiocb->result_errno = errno;
    }
    event_send(aiocb->evhandler, aiocb->event);
    return NULL;
}

static void* vfs_aio_write(aiocb_s *aiocb) {
    int result = system_pwrite(aiocb->filedes, aiocb->buf, aiocb->nbytes, aiocb->offset);
    aiocb->result = result;
    if(result < 0) {
        aiocb->result_errno = errno;
    }
    event_send(aiocb->evhandler, aiocb->event);
    return NULL;
}

void vfs_queue_aio(aiocb_s *aiocb, VFSAioOp op) {
    threadpool_t *pool = get_default_iopool(); // TODO: use specific IOPool
    if(op == VFS_AIO_READ) {
        threadpool_run(pool, (job_callback_f)vfs_aio_read, aiocb);
    } else if(VFS_AIO_WRITE) {
        threadpool_run(pool, (job_callback_f)vfs_aio_write, aiocb);
    }
}

mercurial