#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,
NULL,
NULL,
NULL
};
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);
uint32_t m = ctx->aclreqaccess;
ctx->aclreqaccess = access_mask;
if((ctx->vfs->flags &
VFS_CHECKS_ACL) !=
VFS_CHECKS_ACL) {
SysACL sysacl;
if(sys_acl_check(ctx, access_mask, &sysacl)) {
return NULL;
}
}
SYS_FILE file = ctx->vfs->open(ctx, path, oflags);
ctx->aclreqaccess = m;
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;
uint32_t m = ctx->aclreqaccess;
ctx->aclreqaccess = access_mask;
if((ctx->vfs->flags &
VFS_CHECKS_ACL) !=
VFS_CHECKS_ACL) {
SysACL sysacl;
if(sys_acl_check(ctx, access_mask, &sysacl)) {
return -
1;
}
}
int ret = ctx->vfs->stat(ctx, path, buf);
ctx->aclreqaccess = m;
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;
uint32_t m = ctx->aclreqaccess;
ctx->aclreqaccess = access_mask;
if((ctx->vfs->flags &
VFS_CHECKS_ACL) !=
VFS_CHECKS_ACL) {
SysACL sysacl;
if(sys_acl_check(ctx, access_mask, &sysacl)) {
return NULL;
}
}
VFS_DIR dir = ctx->vfs->opendir(ctx, path);
ctx->aclreqaccess = m;
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;
uint32_t m = ctx->aclreqaccess;
ctx->aclreqaccess = access_mask;
if((ctx->vfs->flags &
VFS_CHECKS_ACL) !=
VFS_CHECKS_ACL) {
SysACL sysacl;
if(sys_acl_check(ctx, access_mask, &sysacl)) {
return NULL;
}
}
VFS_DIR dir = ctx->vfs->fdopendir(ctx, fd);
ctx->aclreqaccess = m;
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);
}
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;
uint32_t m = ctx->aclreqaccess;
ctx->aclreqaccess = access_mask;
if((ctx->vfs->flags &
VFS_CHECKS_ACL) !=
VFS_CHECKS_ACL) {
SysACL sysacl;
if(sys_acl_check(ctx, access_mask, &sysacl)) {
return -
1;
}
}
int ret = op(ctx, path);
ctx->aclreqaccess = m;
if(ret && ctx) {
sys_set_error_status(ctx);
}
return ret;
}
SYS_FILE sys_vfs_open(VFSContext *ctx,
const char *path,
int oflags) {
uint32_t access_mask = ctx->aclreqaccess;
pool_handle_t *pool = ctx->pool;
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;
}
}
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(((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;
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;
}
}
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) {
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;
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;
}
}
#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;
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;
}
}
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;
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;
}
}
if(op(ctx, path, &sysacl)) {
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) {
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)) {
}
}
}
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);
}
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;
}
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();
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);
}
}