src/server/plugins/postgresql/config.c

changeset 385
a1f4cb076d2f
parent 376
61d481d3c2e4
child 415
d938228c382e
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/server/plugins/postgresql/config.c	Sat Sep 24 16:26:10 2022 +0200
@@ -0,0 +1,394 @@
+/*
+ * 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) {
+    UcxAllocator a = util_pool_allocator(pool);
+    
+    ConfigNode *pg = serverconfig_get_node(config, CONFIG_NODE_OBJECT, SC("Postgresql"));
+    if(!pg) {
+        log_ereport(LOG_MISCONFIG, "pg_init_repo: missing postgresql config object");
+        return NULL;
+    }
+    
+    scstr_t cfg_respool = serverconfig_directive_value(pg, SC("ResourcePool"));
+    scstr_t cfg_rootid = serverconfig_directive_value(pg, SC("RootId"));
+    scstr_t cfg_rootnode = serverconfig_directive_value(pg, SC("RootNode"));
+    scstr_t cfg_dav = serverconfig_directive_value(pg, SC("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 = sstrdup_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)
+{
+    UcxAllocator a = util_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 = ucx_map_new_a(&a, 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 = ucx_map_new(8);
+    parserData.tables = NULL;
+    
+    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 = ucx_list_size(parserData.tables);
+        repo->ntables = ntables;
+        repo->tables = pool_calloc(pool, ntables, sizeof(PgExtTable));
+        if(repo->tables) {
+            int i = 0;
+            UCX_FOREACH(elm, parserData.tables) {
+                PgExtTable *tab = elm->data;
+                repo->tables[i++] = *tab;
+            }
+        } else {
+            ret = 1;
+        }
+        
+    }
+    
+    // cleanup parser
+    ucx_list_free_content(parserData.tables, free);
+    ucx_list_free(parserData.tables);
+    ucx_map_free(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)
+{
+    UcxAllocator a = util_pool_allocator(pool);
+    
+    xmlNode *node = ext_node->children;
+    
+    const char *table = NULL;
+    UcxList *properties = NULL;
+    
+    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;
+                        }
+                        properties = ucx_list_append_a(&a, 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(ucx_map_cstr_get(ext->table_lookup, 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 = malloc(sizeof(PgExtTable));
+    if(!exttable) return 1;
+    exttable->table = tabname;
+    exttable->isused = 0; // not relevant in config
+    int tableindex = (int)ucx_list_size(ext->tables);
+    ext->tables = ucx_list_append(ext->tables, exttable);
+    
+    if(ucx_map_cstr_put(ext->table_lookup, table, table)) {
+        return 1;
+    }
+     
+    UCX_FOREACH(elm, properties) {
+        xmlNode *ps = elm->data;
+        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;
+        }
+        
+        UcxKey key = webdav_property_key(ext_col->ns, ext_col->name);
+        int err = ucx_map_put(repo->prop_ext, key, ext_col);
+        free((void*)key.data);
+        if(err) {
+            return 1;
+        }
+    }
+    
+    return 0;
+}

mercurial