Mon, 31 Jan 2022 17:18:12 +0100
add basic postgresql vfs directory functions
/* * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * * Copyright 2022 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 "vfs.h" #include <inttypes.h> #include "../../util/util.h" static VFS pg_vfs_class = { pg_vfs_open, pg_vfs_stat, pg_vfs_fstat, pg_vfs_opendir, pg_vfs_fdopendir, pg_vfs_mkdir, pg_vfs_unlink, pg_vfs_rmdir }; static VFS_IO pg_vfs_io_class = { pg_vfs_io_read, pg_vfs_io_write, pg_vfs_io_pread, pg_vfs_io_pwrite, pg_vfs_io_seek, pg_vfs_io_close, NULL, // no pg aio implementation yet NULL }; static VFS_DIRIO pg_vfs_dirio_class = { pg_vfs_dirio_readdir, pg_vfs_dirio_close }; /* * SQL Queries */ // Resolves a path into resource_id and parent_id // params: $1: path string static const char *sql_resolve_path = "with recursive resolvepath as (\n\ select\n\ resource_id,\n\ parent_id,\n\ iscollection,\n\ regexp_split_to_array($1, '/') as pathelm,\n\ '' as fullpath,\n\ 1 as pathdepth\n\ from Resource\n\ where parent_id is null\n\ union\n\ select\n\ r.resource_id,\n\ r.parent_id,\n\ r.iscollection,\n\ p.pathelm,\n\ p.fullpath || '/' || r.nodename,\n\ p.pathdepth + 1\n\ from Resource r\n\ inner join resolvepath p on r.parent_id = p.resource_id\n\ where p.pathelm[p.pathdepth+1] = r.nodename\n\ )\n\ select resource_id, parent_id, fullpath, iscollection from resolvepath\n\ where fullpath = $1 ;"; // Same as sql_resolve_path, but it returns the root collection // params: $1: path string (should be '/') static const char *sql_get_root = "select resource_id, parent_id, $1 as fullpath, true as iscollection from Resource where parent_id is null;"; // Get all children of a specific collection // params: $1: parent resource_id static const char *sql_get_children = "select resource_id, nodename, iscollection from Resource where parent_id = $1;"; VFS* pg_vfs_create(Session *sn, Request *rq, pblock *pb) { // resourcepool is required char *resource_pool = pblock_findval("resourcepool", pb); if(!resource_pool) { log_ereport(LOG_MISCONFIG, "pg_vfs_create: missing resourcepool parameter"); return NULL; } // get the resource first (most likely to fail due to misconfig) ResourceData *resdata = resourcepool_lookup(sn, rq, resource_pool, 0); if(!resdata) { log_ereport(LOG_MISCONFIG, "postgresql vfs: resource pool %s not found", resource_pool); return NULL; } // resdata will be freed automatically when the request is finished // Create a new VFS object and a separate instance object // VFS contains fptrs that can be copied from pg_vfs_class // instance contains request specific data (PGconn) VFS *vfs = pool_malloc(sn->pool, sizeof(VFS)); if(!vfs) { return NULL; } PgVFS *vfs_priv = pool_malloc(sn->pool, sizeof(PgVFS)); if(!vfs_priv) { pool_free(sn->pool, vfs); return NULL; } vfs_priv->connection = resdata->data; vfs_priv->pg_resource = resdata; memcpy(vfs, &pg_vfs_class, sizeof(VFS)); vfs->flags = 0; vfs->instance = vfs_priv; return vfs; } int pg_resolve_path(VFSContext *ctx, const char *path, int64_t *parent_id, int64_t *resource_id, const char **resource_name, WSBool *iscollection) { // basic path validation if(!path) return 1; size_t pathlen = strlen(path); if(pathlen == 0) return 1; if(path[0] != '/') { return 1; } // get last node of path *resource_name = util_resource_name(path); VFS *vfs = ctx->vfs; PgVFS *pg = vfs->instance; const char *sql = pathlen == 1 ? sql_get_root : sql_resolve_path; PGresult *result = PQexecParams( pg->connection, sql, 1, // number of parameters NULL, &path, // parameter value NULL, NULL, 0); // 0: result in text format if(!result) return 1; int ret = 1; int nfields = PQnfields(result); int nrows = PQntuples(result); if(nrows == 1 && nfields == 4) { char *resource_id_str = PQgetvalue(result, 0, 0); char *parent_id_str = PQgetvalue(result, 0, 1); char *iscol = PQgetvalue(result, 0, 3); if(resource_id_str && parent_id_str) { if(util_strtoint(resource_id_str, resource_id)) { ret = 0; // success } // optionally get parent_id util_strtoint(parent_id_str, parent_id); } if(iscol) { *iscollection = iscol[0] == 't' ? TRUE : FALSE; } } else { ctx->vfs_errno = ENOENT; } PQclear(result); return ret; } /* -------------------------- VFS functions -------------------------- */ SYS_FILE pg_vfs_open(VFSContext *ctx, const char *path, int oflags) { const char *resname; int64_t resource_id, parent_id; resource_id = -1; parent_id = -1; WSBool iscollection; if(pg_resolve_path(ctx, path, &parent_id, &resource_id, &resname, &iscollection)) { return NULL; } VFSFile *file = pool_malloc(ctx->pool, sizeof(VFSFile)); if(!file) { return NULL; } PgFile *pgfile = pool_malloc(ctx->pool, sizeof(PgFile)); if(!pgfile) { pool_free(ctx->pool, file); return NULL; } pgfile->iscollection = iscollection; pgfile->resource_id = resource_id; pgfile->parent_id = parent_id; file->ctx = ctx; file->io = iscollection ? NULL : &pg_vfs_io_class; file->fd = -1; file->data = pgfile; return file; } int pg_vfs_stat(VFSContext *ctx, const char *path, struct stat *buf) { } int pg_vfs_fstat(VFSContext *ctx, SYS_FILE fd, struct stat *buf) { } VFS_DIR pg_vfs_opendir(VFSContext *ctx, const char *path) { VFSFile *file = pg_vfs_open(ctx, path, O_RDONLY); if(!file) return NULL; return pg_vfs_fdopendir(ctx, file); } VFS_DIR pg_vfs_fdopendir(VFSContext *ctx, SYS_FILE fd) { PgFile *pg = fd->data; if(!pg->iscollection) { ctx->vfs_errno = ENOTDIR; return NULL; } VFSDir *dir = pool_malloc(ctx->pool, sizeof(VFSDir)); if(!dir) { fd->io->close(fd); ctx->vfs_errno = ENOMEM; return NULL; } PgDir *pgdir = pool_malloc(ctx->pool, sizeof(PgDir)); if(!pgdir) { fd->io->close(fd); pool_free(ctx->pool, dir); ctx->vfs_errno = ENOMEM; return NULL; } memset(pgdir, 0, sizeof(PgDir)); pgdir->file = fd; dir->ctx = ctx; dir->io = &pg_vfs_dirio_class; dir->data = pgdir; dir->fd = -1; return dir; } int pg_vfs_mkdir(VFSContext *ctx, const char *path) { } int pg_vfs_unlink(VFSContext *ctx, const char *path) { } int pg_vfs_rmdir(VFSContext *Ctx, const char *path) { } /* -------------------------- VFS_IO functions -------------------------- */ ssize_t pg_vfs_io_read(SYS_FILE fd, void *buf, size_t nbyte) { } ssize_t pg_vfs_io_write(SYS_FILE fd, const void *buf, size_t nbyte) { } ssize_t pg_vfs_io_pread(SYS_FILE fd, void *buf, size_t nbyte, off_t offset) { } ssize_t pg_vfs_io_pwrite(SYS_FILE fd, const void *buf, size_t nbyte, off_t offset) { } off_t pg_vfs_io_seek(SYS_FILE fd, off_t offset, int whence) { } void pg_vfs_io_close(SYS_FILE fd) { } /* -------------------------- VFS_DIRIO functions -------------------------- */ static int load_dir(VFSDir *dir, PgDir *pg) { VFS *vfs = dir->ctx->vfs; PgVFS *pgvfs = vfs->instance; PgFile *pgfd = pg->file->data; PgDir *pgdir = dir->data; char resid_param[32]; snprintf(resid_param, 32, "%" PRId64, pgfd->resource_id); const char *param = resid_param; PGresult *result = PQexecParams( pgvfs->connection, sql_get_children, 1, // number of parameters NULL, ¶m, // param: parent resource_id NULL, NULL, 0); // 0: result in text format if(!result) return 1; pgdir->result = result; pgdir->nrows = PQntuples(result); return 0; } int pg_vfs_dirio_readdir(VFS_DIR dir, VFS_ENTRY *entry, int getstat) { PgDir *pg = dir->data; if(!pg->result) { if(load_dir(dir, pg)) { return 0; } } if(pg->row >= pg->nrows) { return 0; // EOF } entry->name = PQgetvalue(pg->result, pg->row, 1); // TODO: stat pg->row++; return 1; } void pg_vfs_dirio_close(VFS_DIR dir) { }