Wed, 27 Nov 2024 23:00:07 +0100
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); } }