UNIXworkcode

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 #include "conf.h" 32 #include "logging.h" 33 34 #include <stdio.h> 35 #include <stdlib.h> 36 #include <string.h> 37 #include <ctype.h> 38 #include <errno.h> 39 40 #include <cx/buffer.h> 41 #include <cx/utils.h> 42 43 ServerConfig* serverconfig_load(const char *file) { 44 CxMempool *mp = cxBasicMempoolCreate(512); 45 if(!mp) { 46 return NULL; 47 } 48 49 ConfigParser2 parser; 50 memset(&parser, 0, sizeof(ConfigParser2)); 51 parser.mp = mp; 52 parser.filename = file; 53 parser.allow_hierarchy = true; 54 parser.delim = ""; 55 ConfigNode *root = serverconfig_load_file(&parser, file); 56 if(!root) { 57 cxMempoolDestroy(mp); 58 return NULL; 59 } 60 61 ServerConfig *scfg = cxMalloc(mp->allocator, sizeof(ServerConfig)); 62 if(!scfg) { 63 cxMempoolDestroy(mp); 64 return NULL; 65 } 66 scfg->root = root; 67 scfg->mp = mp; 68 scfg->tab = cx_str("\t"); 69 70 return scfg; 71 } 72 73 ConfigNode* serverconfig_load_file(ConfigParser2 *parser, const char *file) { 74 FILE *in = fopen(file, "r"); 75 if(in == NULL) { 76 parser->error = CONFIG_PARSER_IO_ERROR; 77 parser->io_errno = errno; 78 return NULL; 79 } 80 81 // temporary buffer to store the file content 82 CxBuffer buf; 83 if(cxBufferInit(&buf, NULL, 16384, cxDefaultAllocator, CX_BUFFER_AUTO_EXTEND|CX_BUFFER_FREE_CONTENTS)) { 84 fclose(in); 85 parser->error = CONFIG_PARSER_OOM; 86 return NULL; 87 } 88 89 //ucx_stream_copy(in, buf, (read_func)fread, (write_func)ucx_buffer_write); 90 char readbuf[2048]; 91 size_t r; 92 while((r = fread(readbuf, 1, 2048, in)) > 0) { 93 cxBufferWrite(readbuf, 1, r, &buf); 94 } 95 fclose(in); 96 97 ConfigNode *root = serverconfig_parse(parser, cx_strn(buf.space, buf.size)); 98 cxBufferDestroy(&buf); 99 return root; 100 } 101 102 static int scfg_char_is_delim(ConfigParser2 *parser, char c) { 103 size_t len = strlen(parser->delim); 104 for(int i=0;i<len;i++) { 105 if(c == parser->delim[i]) { 106 return 1; 107 } 108 } 109 return 0; 110 } 111 112 static CFGToken get_next_token(ConfigParser2 *parser, cxstring content, int *pos) { 113 CFGToken token = { {NULL, 0}, CFG_NO_TOKEN }; 114 CFGTokenType type = CFG_TOKEN; 115 116 int start = *pos; 117 118 int token_begin = -1; 119 int token_end = content.length-1; 120 121 int quote = 0; 122 int comment = 0; 123 124 int i; 125 char prev = 0; 126 for(i=start;i<content.length;i++) { 127 char c = content.ptr[i]; 128 if(c == '\n') { 129 if(quote) { 130 *pos = i; 131 ws_cfg_log( 132 LOG_FAILURE, 133 "cfgparser: file %s:%d:%d: error: %s", 134 parser->filename, 135 parser->linenum, 136 parser->linepos+2, 137 "missing ''\"'' character before end of line"); 138 return token; // error 139 } else if(start == i) { 140 // single newline char token 141 type = CFG_TOKEN_NEWLINE; 142 token_begin = i; 143 token_end = i+1; 144 parser->linenum++; 145 parser->linepos = 0; 146 break; 147 } 148 149 token_end = i; 150 if(token_begin < 0) { 151 // only space/comment token 152 token_begin = start; 153 type = comment ? CFG_TOKEN_COMMENT : CFG_TOKEN_SPACE; 154 } 155 // make sure next run will return current newline char as token 156 i--; 157 break; 158 } else if(quote) { 159 if(c == '""' && prev != '\\') { 160 quote = 0; 161 } 162 } else if(comment) { 163 // ignore 164 if(c == '\n') { 165 comment = 0; 166 } 167 } else if(c == '#') { 168 comment = 1; 169 } else if(isspace(c)) { 170 if(token_begin >= 0) { 171 token_end = i; 172 break; 173 } 174 } else if(c == '""') { 175 quote = 1; 176 if(token_begin < 0) { 177 token_begin = i; 178 } 179 } else if(scfg_char_is_delim(parser, c)) { 180 if(token_begin >= 0) { 181 token_end = i; 182 i--; 183 break; 184 } else { 185 token_begin = i; 186 token_end = i+1; 187 break; 188 } 189 } else if(token_begin < 0) { 190 token_begin = i; 191 } 192 prev = c; 193 194 parser->linepos++; 195 } 196 197 *pos = i + 1; 198 199 if(token_begin < 0) { 200 return token; // error or EOF 201 } 202 203 token.type = type; 204 token.content = cx_strsubsl(content, token_begin, token_end - token_begin); 205 return token; 206 } 207 208 /* 209 static void test_print_config(ConfigNode *parent) { 210 UCX_FOREACH(elm, parent->children) { 211 ConfigNode *node = elm->data; 212 213 if(node->type == CONFIG_NODE_SPACE) { 214 printf("sp: %s", node->text_begin.ptr); 215 } else if(node->type == CONFIG_NODE_COMMENT) { 216 printf("cm: %s", node->text_begin.ptr); 217 } else if(node->type == CONFIG_NODE_OBJECT) { 218 printf("o{: %s : %s", node->name.ptr, node->text_begin.ptr); 219 test_print_config(node); 220 printf("o}: %s", node->text_end.ptr); 221 } else if(node->type == CONFIG_NODE_DIRECTIVE) { 222 printf("di: %s", node->text_begin.ptr); 223 } else { 224 printf("fk: %s", node->text_begin.ptr); 225 } 226 } 227 } 228 */ 229 230 static void config_arg_set_value(CxAllocator *a, ConfigParam *arg, CFGToken token) { 231 cxstring nv = (cxstring){NULL,0}; 232 WSBool quotes = token.content.ptr[0] == '\"'; 233 if(!quotes) { 234 nv = cx_strchr(token.content, '='); 235 } 236 if(!nv.ptr) { 237 if(quotes) { 238 // remove quote 239 token.content.ptr++; 240 token.content.length -= 2; 241 } 242 arg->value = cx_strdup_a(a, token.content); 243 } else { 244 intptr_t eq = (intptr_t)(nv.ptr - token.content.ptr); 245 cxstring name = token.content; 246 name.length = (size_t)eq; 247 248 cxstring value = nv; 249 value.ptr++; 250 value.length--; 251 if(value.length > 1 && value.ptr[0] == '""' && value.ptr[value.length-1] == '""') { 252 value.ptr++; 253 value.length -= 2; // remove quote 254 } 255 256 arg->name = cx_strdup_a(a, name); 257 arg->value = cx_strdup_a(a, value); 258 } 259 } 260 261 static int nodestack_prepend(CxAllocator *a, ConfigNodeStack **stack, ConfigNode *node) { 262 ConfigNodeStack *elm = cxMalloc(a, sizeof(ConfigNodeStack)); 263 if(!elm) return 1; 264 elm->node = node; 265 elm->next = NULL; 266 cx_linked_list_prepend((void**)stack, NULL, -1, offsetof(ConfigNodeStack, next), elm); 267 return 0; 268 } 269 270 ConfigNode* serverconfig_parse(ConfigParser2 *parser, cxstring content) { 271 CxMempool *mp = parser->mp; 272 CxAllocator *a = (CxAllocator*)mp->allocator; 273 274 parser->linenum = 1; 275 parser->linepos = 1; 276 277 if(!parser->delim) { 278 parser->delim = ""; 279 } 280 281 // PARSE: 282 // first non space/comment token is directive/object name 283 // following tokens are arguments 284 // newline starts new directive 285 // '{' converts directive to object and following directives will 286 // be placed into the object 287 int pos = 0; // needed for tokenizer 288 CFGToken token; 289 290 ConfigNode *root_obj = cxCalloc(a, 1, sizeof(ConfigNode)); 291 root_obj->type = CONFIG_NODE_OBJECT; 292 293 ConfigNodeStack *node_stack = cxMalloc(a, sizeof(ConfigNodeStack)); 294 node_stack->node = root_obj; 295 node_stack->next = NULL; 296 297 ConfigNode *current = cxCalloc(a, 1, sizeof(ConfigNode)); 298 current->type = CONFIG_NODE_SPACE; 299 ConfigNode *obj = NULL; 300 int obj_closed = 0; 301 302 int text_start = 0; 303 int err = 0; 304 while((token = get_next_token(parser, content, &pos)).type != CFG_NO_TOKEN) { 305 //printf("[%.*s]\n", (int)token.content.length, token.content.ptr); fflush(stdout); 306 307 switch(token.type) { 308 case CFG_NO_TOKEN: break; 309 case CFG_TOKEN_COMMENT: { 310 if(current->type == CONFIG_NODE_SPACE) { 311 current->type = CONFIG_NODE_COMMENT; 312 } 313 break; 314 } 315 case CFG_TOKEN_SPACE: break; 316 case CFG_TOKEN_NEWLINE: { 317 cxstring line = cx_strsubsl(content, text_start, pos - text_start); 318 text_start = pos; 319 320 cxmutstr line_cp = cx_strdup_a(a, line); 321 322 ConfigNode *parent = node_stack->node; 323 if(current->type == CONFIG_NODE_CLOSE_OBJECT) { 324 // this is a newline after a object is closed with '}' 325 // the line containing "}\n" should be added to the object 326 parent->text_end = line_cp; 327 // validate 328 if(parser->validateObjEnd && parser->validateObjEnd(parser, parent)) { 329 // validation failed 330 err = 1; 331 break; 332 } 333 // done with this object, remove it from the stack 334 ConfigNodeStack *remove_item = node_stack; 335 node_stack = node_stack->next; 336 cxFree(a, remove_item); 337 } else if(current->type == CONFIG_NODE_OPEN_OBJECT) { 338 // newline after a object is opened with '{' 339 // append '{' to the object text 340 cxmutstr new_textbegin = cx_strcat_a(a, 2, obj->text_begin, line_cp); 341 cxFree(a, obj->text_begin.ptr); 342 cxFree(a, line_cp.ptr); 343 obj->text_begin = new_textbegin; 344 } else { 345 // normal line containing a directive, space or comment 346 347 // add it to parent node 348 current->text_begin = line_cp; 349 CFG_NODE_ADD(&parent->children_begin, &parent->children_end, current); 350 351 // validate after CFG_NODE_ADD, because now current->prev is set 352 if(current->type == CONFIG_NODE_DIRECTIVE && 353 parser->validateDirective && 354 parser->validateDirective(parser, current)) 355 { 356 err = 1; 357 break; 358 } 359 } 360 361 // obj points to the previous node that started as a directive 362 // the type is set to CONFIG_NODE_OBECT if it was followed by 363 // a '{' character 364 if(obj && obj->type == CONFIG_NODE_OBJECT) { 365 // new object started 366 if(parser->validateObjBegin && parser->validateObjBegin(parser, obj)) { 367 // validation callback failed 368 err = 1; 369 break; 370 } 371 372 // add it to the stack 373 nodestack_prepend(a, &node_stack, obj); 374 obj = NULL; 375 } 376 377 current = cxCalloc(a, 1, sizeof(ConfigNode)); 378 current->type = CONFIG_NODE_SPACE; 379 380 obj_closed = 0; 381 break; 382 } 383 case CFG_TOKEN: { 384 // normal text token 385 // either a directive/obj name, parameter or { } 386 387 if(!cx_strcmp(token.content, cx_str("{"))) { 388 // check if the parser allows an object hierarchy 389 if(!parser->allow_hierarchy) { 390 parser->error = CONFIG_PARSER_SYNTAX_ERROR; 391 err = 1; 392 break; 393 } 394 395 // obj is pointing to the previous node that started 396 // a directive 397 if(!obj) { 398 err = 1; 399 break; 400 } 401 obj->type = CONFIG_NODE_OBJECT; 402 if(current != obj) { 403 current->type = CONFIG_NODE_OPEN_OBJECT; 404 } 405 } else if(!cx_strcmp(token.content, cx_str("}"))) { 406 // check if the parser allows an object hierarchy 407 if(!parser->allow_hierarchy) { 408 parser->error = CONFIG_PARSER_SYNTAX_ERROR; 409 err = 1; 410 break; 411 } 412 413 obj_closed = 1; // force newline before next directive 414 obj = NULL; 415 current->type = CONFIG_NODE_CLOSE_OBJECT; 416 } else { 417 if(obj_closed) { 418 err = 1; 419 break; 420 } 421 422 if(!current->name.ptr) { 423 // currently this could be a directive or object 424 current->name = cx_strdup_a(a, token.content); 425 current->type = CONFIG_NODE_DIRECTIVE; 426 obj = current; // potential object 427 } else { 428 // name already set, therefore this token must 429 // be a parameter 430 ConfigParam *arg = cxCalloc(a, 1, sizeof(ConfigParam)); 431 config_arg_set_value(a, arg, token); 432 CFG_PARAM_ADD(&current->args, NULL, arg); 433 } 434 } 435 break; 436 } 437 } 438 439 if(err) { 440 break; 441 } 442 } 443 444 if(pos < content.length || err) { 445 // content not fully parsed because of an error 446 return NULL; 447 } 448 449 //test_print_config(&root_obj); 450 return root_obj; 451 } 452 453 void serverconfig_free(ServerConfig *cfg) { 454 cxMempoolDestroy(cfg->mp); 455 } 456 457 ConfigNode* serverconfig_get_node(ConfigNode *parent, ConfigNodeType type, cxstring name) { 458 for(ConfigNode *node=parent->children_begin;node;node=node->next) { 459 if(node->type == type && !cx_strcasecmp(cx_strcast(node->name), name)) { 460 return node; 461 } 462 } 463 return NULL; 464 } 465 466 CxList* serverconfig_get_node_list(ConfigNode *parent, ConfigNodeType type, cxstring name) { 467 CxList *nodes = cxLinkedListCreate(cxDefaultAllocator, NULL, CX_STORE_POINTERS); 468 469 for(ConfigNode *node=parent->children_begin;node;node=node->next) { 470 if(node->type == type && !cx_strcasecmp(cx_strcast(node->name), name)) { 471 cxListAdd(nodes, node); 472 } 473 } 474 475 return nodes; 476 } 477 478 cxstring serverconfig_object_directive_value(ConfigNode *obj, cxstring name) { 479 ConfigNode *node = serverconfig_get_node(obj, CONFIG_NODE_DIRECTIVE, name); 480 if(node && CFG_NUM_PARAMS(node->args) == 1) { 481 ConfigParam *arg = node->args; 482 return (cxstring){ arg->value.ptr, arg->value.length }; 483 } 484 return (cxstring){ NULL, 0 }; 485 } 486 487 cxstring serverconfig_directive_get_arg(ConfigNode *directive, cxstring arg_name) { 488 cxstring ret = (cxstring){ NULL, 0 }; 489 ConfigParam *arg = directive->args; 490 while(arg) { 491 if(!cx_strcmp(arg_name, cx_strcast(arg->name))) { 492 ret = cx_strcast(arg->value); 493 break; 494 } 495 arg = arg->next; 496 } 497 498 return ret; 499 } 500 501 502 503 504 /* -------------------------- utility functions -------------------------- */ 505 506 int serverconfig_validate_directive_name( 507 ConfigNode *directive, 508 const char *names[], 509 size_t numnames, 510 size_t *nameindex) 511 { 512 for(size_t i=0;i<numnames;i++) { 513 if(!cx_strcmp(cx_strcast(directive->name), cx_str(names[i]))) { 514 *nameindex = i; 515 return 0; 516 } 517 } 518 return 1; 519 } 520 521 int serverconfig_check_param_names(ConfigNode *directive, ConfigParam **err) { 522 ConfigParam *arg = directive->args; 523 while(arg) { 524 if(arg->name.length == 0) { 525 *err = arg; 526 return 1; 527 } 528 arg = arg->next; 529 } 530 return 0; 531 } 532 533 ConfigNode* serverconfig_previous_dir_or_obj(ConfigNode *node) { 534 node = node->prev; 535 while(node) { 536 if(node->type == CONFIG_NODE_DIRECTIVE || node->type == CONFIG_NODE_OBJECT) { 537 return node; 538 } 539 node = node->prev; 540 } 541 return NULL; 542 } 543 544 size_t serverconfig_children_count(ConfigNode *node, ConfigNodeType type) { 545 size_t count = 0; 546 node = node->children_begin; 547 while(node) { 548 if(node->type == type) { 549 count++; 550 } 551 node = node->next; 552 } 553 return count; 554 } 555