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 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 "config.h" #include "../../util/util.h" #include <libxml/tree.h> #define xstreq(a,b) xmlStrEqual(BAD_CAST a, BAD_CAST b) static int pg_load_ext_dav_config( ServerConfiguration *cfg, pool_handle_t *pool, PgRepository *repo, const char *file); static const char *sql_get_repository_root = "select resource_id from Resource where parent_id is NULL and nodename = $1 ;"; // Uses a PGconn to lookup the resource id of the specified root node // if the lookup succeeds, the resource id is written to rootid // in case of an error, an error message will be logged // returns: 0 success, 1 error int pg_lookup_root(ResourceData *res, const char *rootnode, int64_t *rootid) { PGconn *connection = res->data; PGresult *result = PQexecParams( connection, sql_get_repository_root, 1, // number of parameters NULL, &rootnode, // parameter value NULL, NULL, 0); // 0: result in text format if(!result) { log_ereport(LOG_FAILURE, "pg: root lookup failed: %s", PQerrorMessage(connection)); return 1; } int ret = 0; int nrows = PQntuples(result); if(nrows == 1) { char *resource_id_str = PQgetvalue(result, 0, 0); if(resource_id_str) { if(!util_strtoint(resource_id_str, rootid)) { log_ereport(LOG_FAILURE, "pg: unexpected result for column resource_id", rootnode); ret = 1; } } } else { log_ereport(LOG_FAILURE, "pg: cannot find root resource '%s'", rootnode); ret = 1; } PQclear(result); return ret; } PgRepository* pg_init_repo(ServerConfiguration *cfg, pool_handle_t *pool, WSConfigNode *config) { CxAllocator *a = pool_allocator(pool); ConfigNode *pg = serverconfig_get_node(config, CONFIG_NODE_OBJECT, cx_str("Postgresql")); if(!pg) { log_ereport(LOG_MISCONFIG, "pg_init_repo: missing postgresql config object"); return NULL; } cxstring cfg_respool = serverconfig_object_directive_value(pg, cx_str("ResourcePool")); cxstring cfg_rootid = serverconfig_object_directive_value(pg, cx_str("RootId")); cxstring cfg_rootnode = serverconfig_object_directive_value(pg, cx_str("RootNode")); cxstring cfg_dav = serverconfig_object_directive_value(pg, cx_str("PGDavConfig")); // minimum requirement is a resource pool if(cfg_respool.length == 0) { return NULL; } // check rootid int64_t root_id = 1; if(cfg_rootid.length > 0) { if(!util_strtoint(cfg_rootid.ptr, &root_id)) { log_ereport(LOG_MISCONFIG, "pg_init_repo: RootId parameter is not an integer: %s", cfg_rootid.ptr); return NULL; } } // warn if RootId and RootNode are specified if(cfg_rootid.length > 0 && cfg_rootnode.length > 0) { log_ereport(LOG_WARN, "log_init_repo: RootId and RootNode specified, RootNode ignored"); } else if(cfg_rootnode.length > 0) { // check root node // resolve rootnode // therefore we first need to get a connection from the resourcepool ResourceData *res = resourcepool_cfg_lookup(cfg, cfg_respool.ptr, 0); if(!res) { log_ereport(LOG_MISCONFIG, "pg_init_repo: resource lookup failed"); return NULL; } // do lookup, if successful, root_id will be set int lookup_err = pg_lookup_root(res, cfg_rootnode.ptr, &root_id); // we don't need the connection anymore resourcepool_free(NULL, NULL, res); if(lookup_err) { // no logging required, pg_lookup_root will log the error return NULL; } } PgRepository *repo = pool_malloc(pool, sizeof(PgRepository)); ZERO(repo, sizeof(PgRepository)); repo->resourcepool = cx_strdup_a(a, cfg_respool); repo->root_resource_id = root_id; // check for extended pg dav config if(cfg_dav.length > 0) { // load extended config from config file char *cfg_file_path = cfg_config_file_path(cfg_dav.ptr); if(pg_load_ext_dav_config(cfg, pool, repo, cfg_file_path)) { // error repo = NULL; // no need to cleanup because everything is from the pool } free(cfg_file_path); } return repo; } static int pg_ext_get_config( ServerConfiguration *cfg, pool_handle_t *pool, PgRepository *repo, const char *file_path, xmlNode *root); static int pg_ext_get_extension( PgExtParser *ext, pool_handle_t *pool, PgRepository *repo, const char *file_path, xmlNode *ext_node); static const char* pg_util_xml_get_text(const xmlNode *elm) { xmlNode *node = elm->children; while(node) { if(node->type == XML_TEXT_NODE) { return (const char*)node->content; } node = node->next; } return NULL; } // load additional postgresql webdav config from the specified xml file static int pg_load_ext_dav_config( ServerConfiguration *cfg, pool_handle_t *pool, PgRepository *repo, const char *file_path) { CxAllocator *a = pool_allocator(pool); // check if the file exists and can be accessed struct stat s; if(stat(file_path, &s)) { if(errno == ENOENT) { log_ereport(LOG_FAILURE, "pg: config file %s not found", file_path); } else { log_ereport(LOG_FAILURE, "pg: cannot access config file %s", file_path); } return 1; } // prepare PgRepository repo->prop_ext = cxHashMapCreate(a, CX_STORE_POINTERS, 8); if(!repo->prop_ext) { log_ereport(LOG_FAILURE, "pg: cannot load config file: OOM"); return 1; } // load xml document xmlDoc *doc = xmlReadFile(file_path, NULL, 0); if(!doc) { log_ereport(LOG_FAILURE, "pg: cannot load config file %s", file_path); return 1; } // the root element must be <repository> int ret = 0; xmlNode *xml_root = xmlDocGetRootElement(doc); if(xstreq(xml_root->name, "repository")) { // parse config ret = pg_ext_get_config(cfg, pool, repo, file_path, xml_root); } else { log_ereport(LOG_MISCONFIG, "pg: config %s: root element <repository> expected", file_path); ret = 1; } xmlFreeDoc(doc); return ret; } static int pg_ext_get_config( ServerConfiguration *cfg, pool_handle_t *pool, PgRepository *repo, const char *file_path, xmlNode *root) { xmlNode *node = root->children; int ret = 0; PgExtParser parserData; parserData.table_lookup = cxHashMapCreate(cxDefaultAllocator, CX_STORE_POINTERS, 8); parserData.tables = cxLinkedListCreate(cxDefaultAllocator, NULL, sizeof(PgExtTable)); while(node && !ret) { // currently, the only possible config element is <extension> if(node->type == XML_ELEMENT_NODE) { if(xstreq(node->name, "extension")) { ret = pg_ext_get_extension(&parserData, pool, repo, file_path, node); } } node = node->next; } // convert parserData if(!ret) { size_t ntables = parserData.tables->size; repo->ntables = ntables; repo->tables = pool_calloc(pool, ntables, sizeof(PgExtTable)); if(repo->tables) { int i = 0; CxIterator iter = cxListIterator(parserData.tables); cx_foreach(PgExtTable *, tab, iter) { repo->tables[i++] = *tab; } } else { ret = 1; } } // cleanup parser cxListDestroy(parserData.tables); cxMapDestroy(parserData.table_lookup); return ret; } static int pg_ext_get_extension( PgExtParser *ext, pool_handle_t *pool, PgRepository *repo, const char *file_path, xmlNode *ext_node) { CxAllocator *a = pool_allocator(pool); xmlNode *node = ext_node->children; const char *table = NULL; CxList *properties = cxLinkedListCreate(a, NULL, CX_STORE_POINTERS); while(node) { if(node->type == XML_ELEMENT_NODE) { if(xstreq(node->name, "table")) { const char *value = pg_util_xml_get_text(node); if(!value) { log_ereport(LOG_MISCONFIG, "pg: config %s: table: missing value", file_path, table); return 1; } else if(table) { log_ereport(LOG_MISCONFIG, "pg: config %s: table %s already set", file_path, table); return 1; } table = value; } else if(xstreq(node->name, "properties")) { // add all child elements to the properties list xmlNode *ps = node->children; while(ps) { if(ps->type == XML_ELEMENT_NODE) { // validate // required: namespace, value if(!ps->ns || !ps->ns->href) { log_ereport(LOG_MISCONFIG, "pg: config %s: property %s: missing namespace", file_path, ps->name); return 1; } const char *value = pg_util_xml_get_text(ps); if(!value) { log_ereport(LOG_MISCONFIG, "pg: config %s: no column specified for property %s", file_path, ps->name); return 1; } cxListAdd(properties, ps); } ps = ps->next; } } } node = node->next; } // check if anything is missing if(!table) { log_ereport(LOG_MISCONFIG, "pg: config %s: missing table value for extension", file_path); return 1; } if(!properties) { log_ereport(LOG_MISCONFIG, "pg: config %s: no properties configured for extension", file_path); return 1; } // check if the table was already specified if(cxMapGet(ext->table_lookup, cx_hash_key_str(table))) { log_ereport(LOG_MISCONFIG, "pg: config %s: extension table %s not unique", file_path, table); return 1; } // mark table as used // tabname will be used later, so ist must be allocated in the pool char *tabname = pool_strdup(pool, table); if(!tabname) return 1; // exttable is only used temporarily PgExtTable exttable; exttable.table = tabname; exttable.isused = 0; // not relevant in config int tableindex = (int)ext->tables->size; cxListAdd(ext->tables, &exttable); if(cxMapPut(ext->table_lookup, cx_hash_key_str(table), (void*)table)) { return 1; } CxIterator iter = cxListIterator(properties); cx_foreach(xmlNode *, ps, iter) { const char *value = pg_util_xml_get_text(ps); PgPropertyStoreExt *ext_col = pool_malloc(pool, sizeof(PgPropertyStoreExt)); if(!ext_col) { return 1; } ext_col->tableindex = tableindex; ext_col->ns = pool_strdup(pool, (const char*)ps->ns->href); ext_col->name = pool_strdup(pool, (const char*)ps->name); ext_col->column = pool_strdup(pool, (const char*)value); if(!ext_col->ns || !ext_col->name || !ext_col->column) { return 1; } CxHashKey key = webdav_property_key(ext_col->ns, ext_col->name); int err = cxMapPut(repo->prop_ext, key, ext_col); free((void*)key.data); if(err) { return 1; } } return 0; }