src/server/config/serverconfig.c

changeset 385
a1f4cb076d2f
parent 367
1592224f6059
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 2020 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
30 #include "serverconfig.h"
31
32 #include <stdio.h>
33 #include <stdlib.h>
34 #include <string.h>
35 #include <ctype.h>
36
37 #include <ucx/buffer.h>
38 #include <ucx/utils.h>
39
40 ServerConfig* serverconfig_load(const char *file) {
41 FILE *in = fopen(file, "r");
42 if(in == NULL) {
43 return NULL;
44 }
45
46 UcxBuffer *buf = ucx_buffer_new(NULL, 4096, UCX_BUFFER_AUTOEXTEND);
47 if(!buf) {
48 fclose(in);
49 return NULL;
50 }
51
52 ucx_stream_copy(in, buf, (read_func)fread, (write_func)ucx_buffer_write);
53 fclose(in);
54
55 ServerConfig *scfg = serverconfig_parse(scstrn(buf->space, buf->size));
56
57 ucx_buffer_free(buf);
58 return scfg;
59 }
60
61
62 static CFGToken get_next_token(scstr_t content, int *pos) {
63 CFGToken token = { {NULL, 0}, CFG_NO_TOKEN };
64 CFGTokenType type = CFG_TOKEN;
65
66 int start = *pos;
67
68 int token_begin = -1;
69 int token_end = content.length-1;
70
71 int quote = 0;
72 int comment = 0;
73
74 int i;
75 char prev = 0;
76 for(i=start;i<content.length;i++) {
77 char c = content.ptr[i];
78 if(c == '\n') {
79 if(quote) {
80 *pos = i;
81 return token; // error
82 } else if(start == i) {
83 // single newline char token
84 type = CFG_TOKEN_NEWLINE;
85 token_begin = i;
86 token_end = i+1;
87 break;
88 }
89
90 token_end = i;
91 if(token_begin < 0) {
92 // only space/comment token
93 token_begin = start;
94 type = comment ? CFG_TOKEN_COMMENT : CFG_TOKEN_SPACE;
95 }
96 // make sure next run will return current newline char as token
97 i--;
98 break;
99 } else if(quote) {
100 if(c == '"' && prev != '\\') {
101 quote = 0;
102 }
103 } else if(comment) {
104 // ignore
105 if(c == '\n') {
106 comment = 0;
107 }
108 } else if(c == '#') {
109 comment = 1;
110 } else if(isspace(c)) {
111 if(token_begin >= 0) {
112 token_end = i;
113 break;
114 }
115 } else if(c == '"') {
116 quote = 1;
117 if(token_begin < 0) {
118 token_begin = i;
119 }
120 } else if(token_begin < 0) {
121 token_begin = i;
122 }
123 prev = c;
124 }
125
126 *pos = i + 1;
127
128 if(token_begin < 0) {
129 return token; // error
130 }
131
132 token.type = type;
133 token.content = scstrsubsl(content, token_begin, token_end - token_begin);
134 return token;
135 }
136
137 /*
138 static void test_print_config(ConfigNode *parent) {
139 UCX_FOREACH(elm, parent->children) {
140 ConfigNode *node = elm->data;
141
142 if(node->type == CONFIG_NODE_SPACE) {
143 printf("sp: %s", node->text_begin.ptr);
144 } else if(node->type == CONFIG_NODE_COMMENT) {
145 printf("cm: %s", node->text_begin.ptr);
146 } else if(node->type == CONFIG_NODE_OBJECT) {
147 printf("o{: %s : %s", node->name.ptr, node->text_begin.ptr);
148 test_print_config(node);
149 printf("o}: %s", node->text_end.ptr);
150 } else if(node->type == CONFIG_NODE_DIRECTIVE) {
151 printf("di: %s", node->text_begin.ptr);
152 } else {
153 printf("fk: %s", node->text_begin.ptr);
154 }
155 }
156 }
157 */
158
159 static void config_arg_set_value(UcxAllocator *a, ConfigArg *arg, CFGToken token) {
160 scstr_t nv = scstrchr(token.content, '=');
161 if(!nv.ptr) {
162 arg->value = sstrdup_a(a, token.content);
163 } else {
164 intptr_t eq = (intptr_t)(nv.ptr - token.content.ptr);
165 scstr_t name = token.content;
166 name.length = (size_t)eq;
167
168 scstr_t value = nv;
169 value.ptr++;
170 value.length--;
171 if(value.length > 1 && value.ptr[0] == '"' && value.ptr[value.length-1] == '"') {
172 value.ptr++;
173 value.length -= 2; // remove quote
174 }
175
176 arg->name = sstrdup_a(a, name);
177 arg->value = sstrdup_a(a, value);
178 }
179 }
180
181 ServerConfig* serverconfig_parse(scstr_t content) {
182 UcxMempool *mp = ucx_mempool_new(512);
183 if(!mp) return NULL;
184 UcxAllocator *a = mp->allocator;
185
186 ServerConfig *config = ucx_mempool_malloc(mp, sizeof(ServerConfig));
187 if(!config) {
188 ucx_mempool_destroy(mp);
189 return NULL;
190 }
191 config->mp = mp;
192
193 // PARSE:
194 // first non space/comment token is directive/object name
195 // following tokens are arguments
196 // newline starts new directive
197 // '{' converts directive to object and following directives will
198 // be placed into the object
199 int pos = 0; // needed for tokenizer
200 CFGToken token;
201
202 ConfigNode *root_obj = ucx_mempool_calloc(mp, 1, sizeof(ConfigNode));
203 root_obj->type = CONFIG_NODE_OBJECT;
204
205 UcxList *node_stack = ucx_list_prepend(NULL, root_obj);
206
207 ConfigNode *current = ucx_mempool_calloc(mp, 1, sizeof(ConfigNode));
208 current->type = CONFIG_NODE_SPACE;
209 ConfigNode *obj = NULL;
210 int obj_closed = 0;
211
212 int text_start = 0;
213 int err = 0;
214 while((token = get_next_token(content, &pos)).type != CFG_NO_TOKEN) {
215 //printf("%s [%.*s]\n", token_type_str(token.type), (int)token.content.length, token.content.ptr);
216
217 switch(token.type) {
218 case CFG_NO_TOKEN: break;
219 case CFG_TOKEN_COMMENT: {
220 if(current->type == CONFIG_NODE_SPACE) {
221 current->type = CONFIG_NODE_COMMENT;
222 }
223 break;
224 }
225 case CFG_TOKEN_SPACE: break;
226 case CFG_TOKEN_NEWLINE: {
227 scstr_t line = scstrsubsl(content, text_start, pos - text_start);
228 text_start = pos;
229
230 sstr_t line_cp = sstrdup_a(a, line);
231
232 ConfigNode *parent = node_stack->data;
233 if(current->type == CONFIG_NODE_CLOSE_OBJECT) {
234 parent->text_end = line_cp;
235 node_stack = ucx_list_remove_a(a, node_stack, node_stack);
236 } else if(current->type == CONFIG_NODE_OPEN_OBJECT) {
237 sstr_t new_textbegin = sstrcat_a(a, 2, obj->text_begin, line_cp);
238 alfree(a, obj->text_begin.ptr);
239 alfree(a, line_cp.ptr);
240 obj->text_begin = new_textbegin;
241 } else {
242 current->text_begin = line_cp;
243 ConfigNode *parent = node_stack->data;
244 parent->children = ucx_list_append_a(a, parent->children, current);
245 }
246
247 if(obj && obj->type == CONFIG_NODE_OBJECT) {
248 node_stack = ucx_list_prepend_a(a, node_stack, obj);
249 obj = NULL;
250 }
251
252 current = ucx_mempool_calloc(mp, 1, sizeof(ConfigNode));
253 current->type = CONFIG_NODE_SPACE;
254
255 obj_closed = 0;
256 break;
257 }
258 case CFG_TOKEN: {
259 if(!sstrcmp(token.content, S("{"))) {
260 if(!obj) {
261 err = 1;
262 break;
263 }
264 obj->type = CONFIG_NODE_OBJECT;
265 if(current != obj) {
266 current->type = CONFIG_NODE_OPEN_OBJECT;
267 }
268 } else if(!sstrcmp(token.content, S("}"))) {
269 obj_closed = 1; // force newline before next directive
270 obj = NULL;
271 current->type = CONFIG_NODE_CLOSE_OBJECT;
272 } else {
273 if(obj_closed) {
274 err = 1;
275 break;
276 }
277
278 if(!current->name.ptr) {
279 current->name = sstrdup_a(a, token.content);
280 current->type = CONFIG_NODE_DIRECTIVE;
281 obj = current;
282 } else {
283 ConfigArg *arg = ucx_mempool_calloc(mp, 1, sizeof(ConfigArg));
284 config_arg_set_value(a, arg, token);
285 current->args = ucx_list_append_a(a, current->args, arg);
286 }
287 }
288 break;
289 }
290 }
291
292 if(err) {
293 break;
294 }
295 }
296
297 if(pos < content.length || err) {
298 // content not fully parsed because of an error
299 ucx_mempool_destroy(mp);
300 return NULL;
301 }
302
303 //test_print_config(&root_obj);
304 config->root = root_obj;
305 config->tab = sstrdup_a(a, SC("\t"));
306
307 return config;
308 }
309
310 void serverconfig_free(ServerConfig *cfg) {
311 ucx_mempool_destroy(cfg->mp);
312 }
313
314 ConfigNode* serverconfig_get_node(ConfigNode *parent, ConfigNodeType type, scstr_t name) {
315 UCX_FOREACH(elm, parent->children) {
316 ConfigNode *node = elm->data;
317 if(node->type == type && !sstrcasecmp(node->name, name)) {
318 return node;
319 }
320 }
321 return NULL;
322 }
323
324 UcxList* serverconfig_get_node_list(ConfigNode *parent, ConfigNodeType type, scstr_t name) {
325 UcxList *nodes = NULL;
326
327 UCX_FOREACH(elm, parent->children) {
328 ConfigNode *node = elm->data;
329 if(node->type == type && !sstrcasecmp(node->name, name)) {
330 nodes = ucx_list_append(nodes, node);
331 }
332 }
333
334 return nodes;
335 }
336
337 scstr_t serverconfig_directive_value(ConfigNode *obj, scstr_t name) {
338 ConfigNode *node = serverconfig_get_node(obj, CONFIG_NODE_DIRECTIVE, name);
339 if(node && ucx_list_size(node->args) == 1) {
340 ConfigArg *arg = node->args->data;
341 return SCSTR(arg->value);
342 }
343 return scstrn(NULL, 0);
344 }
345
346 sstr_t serverconfig_arg_name_value(UcxAllocator *a, scstr_t str, scstr_t *name) {
347 int valstart = 0;
348 for(int i=0;i<str.length;i++) {
349 if(str.ptr[i] == '=') {
350 if(name) {
351 name->ptr = str.ptr;
352 name->length = i;
353 }
354 valstart = i + 1;
355 break;
356 }
357 }
358
359 sstr_t ret;
360 return ret;
361 }

mercurial