src/server/plugins/postgresql/config.c

changeset 385
a1f4cb076d2f
parent 376
61d481d3c2e4
child 415
d938228c382e
equal deleted inserted replaced
210:21274e5950af 385:a1f4cb076d2f
1 /*
2 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
3 *
4 * Copyright 2022 Olaf Wintermann. All rights reserved.
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions are met:
8 *
9 * 1. Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 *
12 * 2. Redistributions in binary form must reproduce the above copyright
13 * notice, this list of conditions and the following disclaimer in the
14 * documentation and/or other materials provided with the distribution.
15 *
16 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
17 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
20 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
21 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
22 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
23 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
24 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
25 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
26 * POSSIBILITY OF SUCH DAMAGE.
27 */
28
29 #include "config.h"
30
31 #include "../../util/util.h"
32
33 #include <libxml/tree.h>
34
35 #define xstreq(a,b) xmlStrEqual(BAD_CAST a, BAD_CAST b)
36
37 static int pg_load_ext_dav_config(
38 ServerConfiguration *cfg,
39 pool_handle_t *pool,
40 PgRepository *repo,
41 const char *file);
42
43
44 static const char *sql_get_repository_root = "select resource_id from Resource where parent_id is NULL and nodename = $1 ;";
45
46
47 // Uses a PGconn to lookup the resource id of the specified root node
48 // if the lookup succeeds, the resource id is written to rootid
49 // in case of an error, an error message will be logged
50 // returns: 0 success, 1 error
51 int pg_lookup_root(ResourceData *res, const char *rootnode, int64_t *rootid) {
52 PGconn *connection = res->data;
53
54 PGresult *result = PQexecParams(
55 connection,
56 sql_get_repository_root,
57 1, // number of parameters
58 NULL,
59 &rootnode, // parameter value
60 NULL,
61 NULL,
62 0); // 0: result in text format
63
64 if(!result) {
65 log_ereport(LOG_FAILURE, "pg: root lookup failed: %s", PQerrorMessage(connection));
66 return 1;
67 }
68
69 int ret = 0;
70
71 int nrows = PQntuples(result);
72 if(nrows == 1) {
73 char *resource_id_str = PQgetvalue(result, 0, 0);
74 if(resource_id_str) {
75 if(!util_strtoint(resource_id_str, rootid)) {
76 log_ereport(LOG_FAILURE, "pg: unexpected result for column resource_id", rootnode);
77 ret = 1;
78 }
79 }
80 } else {
81 log_ereport(LOG_FAILURE, "pg: cannot find root resource '%s'", rootnode);
82 ret = 1;
83 }
84 PQclear(result);
85
86 return ret;
87 }
88
89 PgRepository* pg_init_repo(ServerConfiguration *cfg, pool_handle_t *pool, WSConfigNode *config) {
90 UcxAllocator a = util_pool_allocator(pool);
91
92 ConfigNode *pg = serverconfig_get_node(config, CONFIG_NODE_OBJECT, SC("Postgresql"));
93 if(!pg) {
94 log_ereport(LOG_MISCONFIG, "pg_init_repo: missing postgresql config object");
95 return NULL;
96 }
97
98 scstr_t cfg_respool = serverconfig_directive_value(pg, SC("ResourcePool"));
99 scstr_t cfg_rootid = serverconfig_directive_value(pg, SC("RootId"));
100 scstr_t cfg_rootnode = serverconfig_directive_value(pg, SC("RootNode"));
101 scstr_t cfg_dav = serverconfig_directive_value(pg, SC("PGDavConfig"));
102
103 // minimum requirement is a resource pool
104 if(cfg_respool.length == 0) {
105 return NULL;
106 }
107
108 // check rootid
109 int64_t root_id = 1;
110 if(cfg_rootid.length > 0) {
111 if(!util_strtoint(cfg_rootid.ptr, &root_id)) {
112 log_ereport(LOG_MISCONFIG, "pg_init_repo: RootId parameter is not an integer: %s", cfg_rootid.ptr);
113 return NULL;
114 }
115 }
116
117 // warn if RootId and RootNode are specified
118 if(cfg_rootid.length > 0 && cfg_rootnode.length > 0) {
119 log_ereport(LOG_WARN, "log_init_repo: RootId and RootNode specified, RootNode ignored");
120 } else if(cfg_rootnode.length > 0) {
121 // check root node
122
123 // resolve rootnode
124 // therefore we first need to get a connection from the resourcepool
125 ResourceData *res = resourcepool_cfg_lookup(cfg, cfg_respool.ptr, 0);
126 if(!res) {
127 log_ereport(LOG_MISCONFIG, "pg_init_repo: resource lookup failed");
128 return NULL;
129 }
130 // do lookup, if successful, root_id will be set
131 int lookup_err = pg_lookup_root(res, cfg_rootnode.ptr, &root_id);
132 // we don't need the connection anymore
133 resourcepool_free(NULL, NULL, res);
134 if(lookup_err) {
135 // no logging required, pg_lookup_root will log the error
136 return NULL;
137 }
138 }
139
140 PgRepository *repo = pool_malloc(pool, sizeof(PgRepository));
141 ZERO(repo, sizeof(PgRepository));
142
143 repo->resourcepool = sstrdup_a(&a, cfg_respool);
144 repo->root_resource_id = root_id;
145
146 // check for extended pg dav config
147 if(cfg_dav.length > 0) {
148 // load extended config from config file
149 char *cfg_file_path = cfg_config_file_path(cfg_dav.ptr);
150 if(pg_load_ext_dav_config(cfg, pool, repo, cfg_file_path)) {
151 // error
152 repo = NULL; // no need to cleanup because everything is from the pool
153 }
154 free(cfg_file_path);
155 }
156
157 return repo;
158 }
159
160 static int pg_ext_get_config(
161 ServerConfiguration *cfg,
162 pool_handle_t *pool,
163 PgRepository *repo,
164 const char *file_path,
165 xmlNode *root);
166 static int pg_ext_get_extension(
167 PgExtParser *ext,
168 pool_handle_t *pool,
169 PgRepository *repo,
170 const char *file_path,
171 xmlNode *ext_node);
172
173 static const char* pg_util_xml_get_text(const xmlNode *elm) {
174 xmlNode *node = elm->children;
175 while(node) {
176 if(node->type == XML_TEXT_NODE) {
177 return (const char*)node->content;
178 }
179 node = node->next;
180 }
181 return NULL;
182 }
183
184 // load additional postgresql webdav config from the specified xml file
185 static int pg_load_ext_dav_config(
186 ServerConfiguration *cfg,
187 pool_handle_t *pool,
188 PgRepository *repo,
189 const char *file_path)
190 {
191 UcxAllocator a = util_pool_allocator(pool);
192
193 // check if the file exists and can be accessed
194 struct stat s;
195 if(stat(file_path, &s)) {
196 if(errno == ENOENT) {
197 log_ereport(LOG_FAILURE, "pg: config file %s not found", file_path);
198 } else {
199 log_ereport(LOG_FAILURE, "pg: cannot access config file %s", file_path);
200 }
201
202 return 1;
203 }
204
205 // prepare PgRepository
206 repo->prop_ext = ucx_map_new_a(&a, 8);
207 if(!repo->prop_ext) {
208 log_ereport(LOG_FAILURE, "pg: cannot load config file: OOM");
209 return 1;
210 }
211
212 // load xml document
213 xmlDoc *doc = xmlReadFile(file_path, NULL, 0);
214 if(!doc) {
215 log_ereport(LOG_FAILURE, "pg: cannot load config file %s", file_path);
216 return 1;
217 }
218
219 // the root element must be <repository>
220 int ret = 0;
221 xmlNode *xml_root = xmlDocGetRootElement(doc);
222 if(xstreq(xml_root->name, "repository")) {
223 // parse config
224 ret = pg_ext_get_config(cfg, pool, repo, file_path, xml_root);
225 } else {
226 log_ereport(LOG_MISCONFIG, "pg: config %s: root element <repository> expected", file_path);
227 ret = 1;
228 }
229 xmlFreeDoc(doc);
230
231 return ret;
232 }
233
234
235
236
237 static int pg_ext_get_config(
238 ServerConfiguration *cfg,
239 pool_handle_t *pool,
240 PgRepository *repo,
241 const char *file_path,
242 xmlNode *root)
243 {
244 xmlNode *node = root->children;
245 int ret = 0;
246
247 PgExtParser parserData;
248 parserData.table_lookup = ucx_map_new(8);
249 parserData.tables = NULL;
250
251 while(node && !ret) {
252 // currently, the only possible config element is <extension>
253 if(node->type == XML_ELEMENT_NODE) {
254 if(xstreq(node->name, "extension")) {
255 ret = pg_ext_get_extension(&parserData, pool, repo, file_path, node);
256 }
257 }
258 node = node->next;
259 }
260
261 // convert parserData
262 if(!ret) {
263 size_t ntables = ucx_list_size(parserData.tables);
264 repo->ntables = ntables;
265 repo->tables = pool_calloc(pool, ntables, sizeof(PgExtTable));
266 if(repo->tables) {
267 int i = 0;
268 UCX_FOREACH(elm, parserData.tables) {
269 PgExtTable *tab = elm->data;
270 repo->tables[i++] = *tab;
271 }
272 } else {
273 ret = 1;
274 }
275
276 }
277
278 // cleanup parser
279 ucx_list_free_content(parserData.tables, free);
280 ucx_list_free(parserData.tables);
281 ucx_map_free(parserData.table_lookup);
282
283 return ret;
284 }
285
286 static int pg_ext_get_extension(
287 PgExtParser *ext,
288 pool_handle_t *pool,
289 PgRepository *repo,
290 const char *file_path,
291 xmlNode *ext_node)
292 {
293 UcxAllocator a = util_pool_allocator(pool);
294
295 xmlNode *node = ext_node->children;
296
297 const char *table = NULL;
298 UcxList *properties = NULL;
299
300 while(node) {
301 if(node->type == XML_ELEMENT_NODE) {
302 if(xstreq(node->name, "table")) {
303 const char *value = pg_util_xml_get_text(node);
304 if(!value) {
305 log_ereport(LOG_MISCONFIG, "pg: config %s: table: missing value", file_path, table);
306 return 1;
307 } else if(table) {
308 log_ereport(LOG_MISCONFIG, "pg: config %s: table %s already set", file_path, table);
309 return 1;
310 }
311 table = value;
312 } else if(xstreq(node->name, "properties")) {
313 // add all child elements to the properties list
314 xmlNode *ps = node->children;
315 while(ps) {
316 if(ps->type == XML_ELEMENT_NODE) {
317 // validate
318 // required: namespace, value
319 if(!ps->ns || !ps->ns->href) {
320 log_ereport(LOG_MISCONFIG, "pg: config %s: property %s: missing namespace", file_path, ps->name);
321 return 1;
322 }
323 const char *value = pg_util_xml_get_text(ps);
324 if(!value) {
325 log_ereport(LOG_MISCONFIG, "pg: config %s: no column specified for property %s", file_path, ps->name);
326 return 1;
327 }
328 properties = ucx_list_append_a(&a, properties, ps);
329 }
330 ps = ps->next;
331 }
332 }
333 }
334 node = node->next;
335 }
336
337 // check if anything is missing
338 if(!table) {
339 log_ereport(LOG_MISCONFIG, "pg: config %s: missing table value for extension", file_path);
340 return 1;
341 }
342 if(!properties) {
343 log_ereport(LOG_MISCONFIG, "pg: config %s: no properties configured for extension", file_path);
344 return 1;
345 }
346
347 // check if the table was already specified
348 if(ucx_map_cstr_get(ext->table_lookup, table)) {
349 log_ereport(LOG_MISCONFIG, "pg: config %s: extension table %s not unique", file_path, table);
350 return 1;
351 }
352 // mark table as used
353 // tabname will be used later, so ist must be allocated in the pool
354 char *tabname = pool_strdup(pool, table);
355 if(!tabname) return 1;
356
357 // exttable is only used temporarily
358 PgExtTable *exttable = malloc(sizeof(PgExtTable));
359 if(!exttable) return 1;
360 exttable->table = tabname;
361 exttable->isused = 0; // not relevant in config
362 int tableindex = (int)ucx_list_size(ext->tables);
363 ext->tables = ucx_list_append(ext->tables, exttable);
364
365 if(ucx_map_cstr_put(ext->table_lookup, table, table)) {
366 return 1;
367 }
368
369 UCX_FOREACH(elm, properties) {
370 xmlNode *ps = elm->data;
371 const char *value = pg_util_xml_get_text(ps);
372
373 PgPropertyStoreExt *ext_col = pool_malloc(pool, sizeof(PgPropertyStoreExt));
374 if(!ext_col) {
375 return 1;
376 }
377 ext_col->tableindex = tableindex;
378 ext_col->ns = pool_strdup(pool, (const char*)ps->ns->href);
379 ext_col->name = pool_strdup(pool, (const char*)ps->name);
380 ext_col->column = pool_strdup(pool, (const char*)value);
381 if(!ext_col->ns || !ext_col->name || !ext_col->column) {
382 return 1;
383 }
384
385 UcxKey key = webdav_property_key(ext_col->ns, ext_col->name);
386 int err = ucx_map_put(repo->prop_ext, key, ext_col);
387 free((void*)key.data);
388 if(err) {
389 return 1;
390 }
391 }
392
393 return 0;
394 }

mercurial