src/server/config/serverconfig.c

branch
config
changeset 256
19259b6c5cf7
child 258
134279e804b6
equal deleted inserted replaced
255:b5d15a4a19f5 256:19259b6c5cf7
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 ServerConfig* serverconfig_parse(scstr_t content) {
159 UcxMempool *mp = ucx_mempool_new(512);
160 if(!mp) return NULL;
161 UcxAllocator *a = mp->allocator;
162
163 ServerConfig *config = ucx_mempool_malloc(mp, sizeof(ServerConfig));
164 if(!config) {
165 ucx_mempool_destroy(mp);
166 return NULL;
167 }
168 config->mp = mp;
169
170 // PARSE:
171 // first non space/comment token is directive/object name
172 // following tokens are arguments
173 // newline starts new directive
174 // '{' converts directive to object and following directives will
175 // be placed into the object
176 int pos = 0; // needed for tokenizer
177 CFGToken token;
178
179 ConfigNode *root_obj = ucx_mempool_calloc(mp, 1, sizeof(ConfigNode));
180 root_obj->type = CONFIG_NODE_OBJECT;
181
182 UcxList *node_stack = ucx_list_prepend(NULL, root_obj);
183
184 ConfigNode *current = ucx_mempool_calloc(mp, 1, sizeof(ConfigNode));
185 current->type = CONFIG_NODE_SPACE;
186 ConfigNode *obj = NULL;
187 int obj_closed = 0;
188
189 int text_start = 0;
190 int err = 0;
191 while((token = get_next_token(content, &pos)).type != CFG_NO_TOKEN) {
192 //printf("%s [%.*s]\n", token_type_str(token.type), (int)token.content.length, token.content.ptr);
193
194 switch(token.type) {
195 CFG_NO_TOKEN: break;
196 case CFG_TOKEN_COMMENT: {
197 if(current->type == CONFIG_NODE_SPACE) {
198 current->type = CONFIG_NODE_COMMENT;
199 }
200 break;
201 }
202 case CFG_TOKEN_SPACE: break;
203 case CFG_TOKEN_NEWLINE: {
204 scstr_t line = scstrsubsl(content, text_start, pos - text_start);
205 text_start = pos;
206
207 sstr_t line_cp = sstrdup_a(a, line);
208
209 ConfigNode *parent = node_stack->data;
210 if(current->type == CONFIG_NODE_CLOSE_OBJECT) {
211 parent->text_end = line_cp;
212 node_stack = ucx_list_remove_a(a, node_stack, node_stack);
213 } else if(current->type == CONFIG_NODE_OPEN_OBJECT) {
214 sstr_t new_textbegin = sstrcat_a(a, 2, obj->text_begin, line_cp);
215 alfree(a, obj->text_begin.ptr);
216 alfree(a, line_cp.ptr);
217 obj->text_begin = new_textbegin;
218 } else {
219 current->text_begin = line_cp;
220 ConfigNode *parent = node_stack->data;
221 parent->children = ucx_list_append_a(a, parent->children, current);
222 }
223
224 if(obj && obj->type == CONFIG_NODE_OBJECT) {
225 node_stack = ucx_list_prepend_a(a, node_stack, obj);
226 obj = NULL;
227 }
228
229 current = ucx_mempool_calloc(mp, 1, sizeof(ConfigNode));
230 current->type = CONFIG_NODE_SPACE;
231
232 obj_closed = 0;
233 break;
234 }
235 case CFG_TOKEN: {
236 if(!sstrcmp(token.content, S("{"))) {
237 if(!obj) {
238 err = 1;
239 break;
240 }
241 obj->type = CONFIG_NODE_OBJECT;
242 if(current != obj) {
243 current->type = CONFIG_NODE_OPEN_OBJECT;
244 }
245 } else if(!sstrcmp(token.content, S("}"))) {
246 obj_closed = 1; // force newline before next directive
247 obj = NULL;
248 current->type = CONFIG_NODE_CLOSE_OBJECT;
249 } else {
250 if(obj_closed) {
251 err = 1;
252 break;
253 }
254
255 if(!current->name.ptr) {
256 current->name = sstrdup_a(a, token.content);
257 current->type = CONFIG_NODE_DIRECTIVE;
258 obj = current;
259 } else {
260 ConfigArg *arg = ucx_mempool_calloc(mp, 1, sizeof(ConfigArg));
261 // TODO: add support for key/value
262 arg->value = sstrdup_a(a, token.content);
263 current->args = ucx_list_append_a(a, current->args, arg);
264 }
265 }
266 break;
267 }
268 }
269
270 if(err) {
271 break;
272 }
273 }
274
275 if(pos < content.length || err) {
276 // content not fully parsed because of an error
277 ucx_mempool_destroy(mp);
278 return NULL;
279 }
280
281 //test_print_config(&root_obj);
282 config->root = root_obj;
283 config->tab = sstrdup_a(a, SC("\t"));
284
285 return config;
286 }
287
288 void serverconfig_free(ServerConfig *cfg) {
289 ucx_mempool_destroy(cfg->mp);
290 }
291
292 ConfigNode* serverconfig_get_node(ConfigNode *parent, ConfigNodeType type, scstr_t name) {
293 UCX_FOREACH(elm, parent->children) {
294 ConfigNode *node = elm->data;
295 if(node->type == type && !sstrcmp(node->name, name)) {
296 return node;
297 }
298 }
299 return NULL;
300 }
301
302 UcxList* serverconfig_get_node_list(ConfigNode *parent, ConfigNodeType type, scstr_t name) {
303 UcxList *nodes = NULL;
304
305 UCX_FOREACH(elm, parent->children) {
306 ConfigNode *node = elm->data;
307 if(node->type == type && !sstrcmp(node->name, name)) {
308 nodes = ucx_list_append(nodes, node);
309 }
310 }
311
312 return nodes;
313 }
314
315 scstr_t serverconfig_directive_value(ConfigNode *obj, scstr_t name) {
316 ConfigNode *node = serverconfig_get_node(obj, CONFIG_NODE_DIRECTIVE, name);
317 if(node && ucx_list_size(node->args) == 1) {
318 ConfigArg *arg = node->args->data;
319 return SCSTR(arg->value);
320 }
321 return scstrn(NULL, 0);
322 }

mercurial