qLibc
qaconf.c
Go to the documentation of this file.
1/******************************************************************************
2 * qLibc
3 *
4 * Copyright (c) 2010-2026 Seungyoung Kim.
5 * All rights reserved.
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions are met:
9 *
10 * 1. Redistributions of source code must retain the above copyright notice,
11 * this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright notice,
13 * this list of conditions and the following disclaimer in the documentation
14 * 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 OWNER 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 * @file qaconf.c Apache-style configuration file parser.
31 *
32 * Apache-style Configuration is a configuration file syntax and format
33 * originally introduced by Apache HTTPd project. This format is powerful,
34 * flexible and human friendly. Even though this code gets distributed
35 * as a part of qLibc project, the code is written not to have any external
36 * dependencies to make this single file stands alone for better portability.
37 * It is purely written from the ground up and dedicated to the public
38 * by Seungyoung Kim.
39 *
40 * Sample Apache-style Configuration Syntax:
41 * @code
42 * # Lines that begin with the hash character "#" are considered comments.
43 * Listen 53
44 * Protocols UDP TCP
45 * IPSEC On
46 *
47 * <Domain "qdecoder.org">
48 * TTL 86400
49 * MX 10 mail.qdecoder.org
50 *
51 * <Host mail>
52 * IPv4 192.168.10.1
53 * TXT "US Rack-13D-18 \"San Jose's\""
54 * </Host>
55 *
56 * <Host www>
57 * IPv4 192.168.10.2
58 * TXT 'KR Rack-48H-31 "Seoul\'s"'
59 * TTL 3600
60 * </Host>
61 * </Domain>
62 *
63 * <Domain "ringfs.org">
64 * <Host www>
65 * CNAME www.qdecoder.org
66 * </Host>
67 * </Domain>
68 * @endcode
69 *
70 * @code
71 * // THIS EXAMPLE CODE CAN BE FOUND IN EXAMPLES DIRECTORY.
72 *
73 * // Define scope.
74 * // QAC_SCOPE_ALL and QAC_SCOPE_ROOT are predefined.
75 * // Custom scope should be defined from 2(1 << 1).
76 * // Note) These values are ORed(bit operation), so the number should be
77 * // 2(1<<1), 4(1<<2), 6(1<<3), 8(1<<4), ...
78 * enum {
79 * OPT_SECTION_ALL = QAC_SECTION_ALL, // pre-defined
80 * OPT_SECTION_ROOT = QAC_SECTION_ROOT, // pre-defined
81 * OPT_SECTION_DOMAIN = (1 << 1), // user-defined section
82 * OPT_SECTION_HOST = (1 << 2), // user-defined section
83 * };
84 *
85 * // Define callback proto-types.
86 * static QAC_CB(confcb_debug);
87 *
88 * // Define options and callbacks.
89 * static qaconf_option_t options[] = {
90 * {"Listen", QAC_TAKE_INT, confcb_debug, 0, OPT_SECTION_ALL},
91 * {"Protocols", QAC_TAKEALL, confcb_debug, 0, OPT_SECTION_ROOT},
92 * {"IPSEC", QAC_TAKE_BOOL, confcb_debug, 0, OPT_SECTION_ROOT},
93 * {"Domain", QAC_TAKE_STR, confcb_debug, OPT_SECTION_DOMAIN, OPT_SECTION_ROOT},
94 * { "TTL", QAC_TAKE_INT, confcb_debug, 0, OPT_SECTION_DOMAIN | OPT_SECTION_HOST},
95 * { "MX", QAC_TAKE2 | QAC_A1_INT, confcb_debug, 0, OPT_SECTION_DOMAIN},
96 * { "Host", QAC_TAKE_STR, confcb_debug, OPT_SECTION_HOST, OPT_SECTION_DOMAIN},
97 * { "IPv4", QAC_TAKE_STR, confcb_debug, 0, OPT_SECTION_HOST},
98 * { "TXT", QAC_TAKE_STR, confcb_debug, 0, OPT_SECTION_HOST},
99 * { "CNAME", QAC_TAKE_STR, confcb_debug, 0, OPT_SECTION_HOST},
100 * QAC_OPTION_END
101 * };
102 *
103 * int user_main(void)
104 * {
105 * // Create a userdata structure.
106 * struct MyConf myconf;
107 *
108 * // Initialize and create a qaconf object.
109 * qaconf_t *conf = qaconf();
110 * if (conf == NULL) {
111 * printf("Failed to open '" CONF_PATH "'.\n");
112 * return -1;
113 * }
114 *
115 * // Register options.
116 * conf->addoptions(conf, options);
117 *
118 * // Set callback userdata
119 * // This is a userdata which will be provided on callback
120 * conf->setuserdata(conf, &myconf);
121 *
122 * // Run parser.
123 * int count = conf->parse(conf, CONF_PATH, QAC_CASEINSENSITIVE);
124 * if (count < 0) {
125 * printf("Error: %s\n", conf->errmsg(conf));
126 * } else {
127 * printf("Successfully loaded.\n");
128 * }
129 *
130 * // Verify userdata structure.
131 * if (conf->errmsg(conf) == NULL) { // another way to check parsing error.
132 * // codes here
133 * }
134 *
135 * // Release resources.
136 * conf->free(conf);
137 * }
138 *
139 * static QAC_CB(confcb_debug)
140 * {
141 * int i;
142 * for (i = 0; i < data->level; i++) {
143 * printf (" ");
144 * }
145 *
146 * // Print option name
147 * if (data->otype == QAC_OTYPE_SECTIONOPEN) {
148 * printf("<%s>", data->argv[0]);
149 * } else if (data->otype == QAC_OTYPE_SECTIONCLOSE) {
150 * printf("</%s>", data->argv[0]);
151 * } else { // This is QAC_OTYPE_OPTION type.
152 * printf("%s", data->argv[0]);
153 * }
154 *
155 * // Print parent names
156 * qaconf_cbdata_t *parent;
157 * for (parent = data->parent; parent != NULL; parent = parent->parent) {
158 * printf(" ::%s(%s)", parent->argv[0], parent->argv[1]);
159 * }
160 *
161 * // Print option arguments
162 * for (i = 1; i < data->argc; i++) {
163 * printf(" [%d:%s]", i, data->argv[i]);
164 * }
165 * printf("\n");
166 *
167 * // Return OK
168 * return NULL;
169 * }
170 * @endcode
171 *
172 * @code
173 * [Output]
174 * Listen [1:53]
175 * Protocols [1:UDP] [2:TCP]
176 * IPSEC [1:1]
177 * <Domain> [1:qdecoder.org]
178 * TTL ::Domain(qdecoder.org) [1:86400]
179 * MX ::Domain(qdecoder.org) [1:10] [2:mail.qdecoder.org]
180 * <Host> ::Domain(qdecoder.org) [1:mail]
181 * IPv4 ::Host(mail) ::Domain(qdecoder.org) [1:192.168.10.1]
182 * TXT ::Host(mail) ::Domain(qdecoder.org) [1:US Rack-13D-18 "San Jose's"]
183 * </Host> ::Domain(qdecoder.org) [1:mail]
184 * <Host> ::Domain(qdecoder.org) [1:www]
185 * IPv4 ::Host(www) ::Domain(qdecoder.org) [1:192.168.10.2]
186 * TXT ::Host(www) ::Domain(qdecoder.org) [1:KR Rack-48H-31 "Seoul's"]
187 * TTL ::Host(www) ::Domain(qdecoder.org) [1:3600]
188 * </Host> ::Domain(qdecoder.org) [1:www]
189 * </Domain> [1:qdecoder.org]
190 * <Domain> [1:ringfs.org]
191 * <Host> ::Domain(ringfs.org) [1:www]
192 * CNAME ::Host(www) ::Domain(ringfs.org) [1:www.qdecoder.org]
193 * </Host> ::Domain(ringfs.org) [1:www]
194 * </Domain> [1:ringfs.org]
195 * Successfully loaded.
196 * @endcode
197 */
198
199#ifndef DISABLE_QACONF
200
201#include <stdio.h>
202#include <stdlib.h>
203#include <stdbool.h>
204#include <stdarg.h>
205#include <string.h>
206#include <assert.h>
207#include <errno.h>
208#include "qinternal.h"
209#include "utilities/qstring.h"
210#include "extensions/qaconf.h"
211
212#ifndef _DOXYGEN_SKIP
213#define MAX_LINESIZE (1024*4)
214
215/* internal functions */
216static int addoptions(qaconf_t *qaconf, const qaconf_option_t *options);
217static void setdefhandler(qaconf_t *qaconf, qaconf_cb_t *callback);
218static void setuserdata(qaconf_t *qaconf, const void *userdata);
219static int parse(qaconf_t *qaconf, const char *filepath, uint8_t flags);
220static const char *errmsg(qaconf_t *qaconf);
221static void reseterror(qaconf_t *qaconf);
222static void free_(qaconf_t *qaconf);
223
224static int _parse_inline(qaconf_t *qaconf, FILE *fp, uint8_t flags,
225 enum qaconf_section sectionid,
226 qaconf_cbdata_t *cbdata_parent);
227static void _seterrmsg(qaconf_t *qaconf, const char *format, ...);
228static void _free_cbdata(qaconf_cbdata_t *cbdata);
229static int _is_str_number(const char *s);
230static int _is_str_bool(const char *s);
231#endif
232
233/**
234 * Create a new configuration object.
235 *
236 * @return pointer to new qaconf_t object.
237 *
238 * @code
239 * qaconf_t *conf = qaconf();
240 * if (conf == NULL) {
241 * // Insufficient memory.
242 * }
243 * @endcode
244 */
245qaconf_t *qaconf(void) {
246 // Malloc qaconf_t structure
247 qaconf_t *qaconf = (qaconf_t *) malloc(sizeof(qaconf_t));
248 if (qaconf == NULL)
249 return NULL;
250
251 // Initialize the structure
252 memset((void *) (qaconf), '\0', sizeof(qaconf_t));
253
254 // member methods
255 qaconf->addoptions = addoptions;
256 qaconf->setdefhandler = setdefhandler;
257 qaconf->setuserdata = setuserdata;
258 qaconf->parse = parse;
259 qaconf->errmsg = errmsg;
260 qaconf->reseterror = reseterror;
261 qaconf->free = free_;
262
263 return qaconf;
264}
265
266/**
267 * qaconf_t->addoptions(): Register option directives.
268 *
269 * @param qaconf qaconf_t object.
270 * @param options array pointer of qaconf_option_t.
271 *
272 * @return a number of options registered(added).
273 *
274 * @code
275 * qaconf_option_t options[] = {
276 * {"Listen", QAC_TAKE_INT, confcb_debug, 0, OPT_SECTION_ALL},
277 * {"Protocols", QAC_TAKEALL, confcb_debug, 0, OPT_SECTION_ROOT},
278 * {"IPSEC", QAC_TAKE_BOOL, confcb_debug, 0, OPT_SECTION_ROOT},
279 * {"Domain", QAC_TAKE_STR, confcb_debug, OPT_SECTION_DOMAIN, OPT_SECTION_ROOT},
280 * { "TTL", QAC_TAKE_INT, confcb_debug, 0, OPT_SECTION_DOMAIN | OPT_SECTION_HOST},
281 * { "MX", QAC_TAKE2 | QAC_A1_INT, confcb_debug, 0, OPT_SECTION_DOMAIN},
282 * { "Host", QAC_TAKE_STR, confcb_debug, OPT_SECTION_HOST, OPT_SECTION_DOMAIN},
283 * { "IPv4", QAC_TAKE_STR, confcb_debug, 0, OPT_SECTION_HOST},
284 * { "TXT", QAC_TAKE_STR, confcb_debug, 0, OPT_SECTION_HOST},
285 * { "CNAME", QAC_TAKE_STR, confcb_debug, 0, OPT_SECTION_HOST},
286 * QAC_OPTION_END
287 * };
288 *
289 * // Register options.
290 * qaconf_t *conf = qaconf();
291 * conf->addoptions(conf, options);
292 * (...codes goes...)
293 * @endcode
294 *
295 * It takes an array of options as provided in the sample codes.
296 * Each option consists of 5 parameters as below
297 *
298 * @code
299 * 1st) Option Name : A option directive name.
300 * 2nd) Arguments : A number of arguments this option takes and their types.
301 * 3rd) Callback : A function pointer for callback.
302 * 4th) Section ID : Section ID if this option is a section like <Option>.
303 * Otherwise 0 for regular option.
304 * 5th) Sections : ORed section IDs where this option can be specified.
305 * @endcode
306 *
307 * Example:
308 *
309 * @code
310 * {"TTL", QAC_TAKE_INT, confcb_debug, 0, OPT_SECTION_DOMAIN | OPT_SECTION_HOST}
311 * 1st) Option name is "TTL"
312 * 2nd) It takes 1 argument and its type must be integer.
313 * 3rd) Callback function, confcb_debug, will be called.
314 * 4th) This is a regular option and does not have section id.
315 * 5th) This option can be specified in OPT_SECTION_DOMAIN and OPT_SECTION_HOST.
316 * @endcode
317 *
318 * OPTION NAME field:
319 *
320 * Option name is a unique string. Even when an option is a section type like
321 * <option>, only the name part without brackets needs to be specified.
322 *
323 * ARGUMENT field:
324 *
325 * This field provides argument checking at the parser level, so the user's
326 * callback routine can stay simple. It checks the number of arguments this
327 * option can take and their argument types.
328 *
329 * There are 4 argument types, as shown below.
330 * And first 5 arguments can be checked individually with different types.
331 *
332 * @code
333 * STR type : any type
334 * INT type : integer type. ex) 23, -12, 0
335 * FLOAT type : integer + floating point type. ex) 1.32, -32.5, 23, -12, 0
336 * BOOL type : bool type ex) 1/0, true/false, on/off, yes/no
337 * @endcode
338 *
339 * When a BOOL type is specified, the argument passed to the callback will be
340 * replaced with "1" or "0" for convenience. For example, if "On" is specified
341 * as an argument and BOOL type checking is enabled, the actual argument passed
342 * to the callback will be "1". So we can simply determine it like
343 * "bool enabled = atoi(data->argv[1])".
344 *
345 * If the original input argument needs to be passed to the callback, specify
346 * STR type.
347 *
348 * Here are some examples of how to specify the "Arguments" field.
349 *
350 * @code
351 * An option takes 1 argument.
352 * QAC_TAKE_STR <= String(any) type
353 * QAC_TAKE_INT <= Integer type
354 * QAC_TAKE_FLOAT <= Float type
355 * QAC_TAKE_BOOL <= Bool type
356 *
357 * QAC_TAKE1 <= Equivalent to QAC_TAKE_STR
358 * QAC_TAKE1 | QAC_A1_BOOL <= Equivalent to QAC_TAKE_BOOL
359 *
360 * An option takes 2 arguments, bool and float.
361 * QAC_TAKE2 | QAC_A1_BOOL | QAC_A2_FLOAT
362 *
363 * An option takes any number of arguments in any type.
364 * QAC_TAKEALL
365 *
366 * An option takes any number of arguments but 1st one must be bool and
367 * 2nd one must be integer and rest of them must be float.
368 * QAC_TAKEALL | QAC_A1_BOOL | QAC_A2_INT | QAC_AA_FLOAT
369 * @endcode
370 *
371 * CALLBACK field:
372 *
373 * User defined callback function. We provide a macro, QAC_CB, for function
374 * proto type. Always use QAC_CB macro.
375 *
376 * @code
377 * QAC_CB(sample_cb) {
378 * (...codes...)
379 * }
380 *
381 * is equivalent to
382 *
383 * char *sample_cb(qaconf_cbdata_t *data, void *userdata) {
384 * (...codes...)
385 * }
386 * @endcode
387 *
388 * Callback function will be called with 2 arguments. One is callback data and
389 * the other one is userdata. Userdata is the data pointer set by setuserdata().
390 *
391 * Here is data structure. Arguments belong to the option can be accessed via
392 * argv variables like data->argv[1]. argv[0] is for the option name.
393 *
394 * @code
395 * struct qaconf_cbdata_s {
396 * enum qaconf_otype otype; // option type
397 * uint64_t section; // current section where this option is located
398 * uint64_t sections; // ORed all parent's sectionid(s) including current sections
399 * uint8_t level; // number of parents(level), root level is 0
400 * qaconf_cbdata_t *parent; // upper parent link
401 *
402 * int argc; // number arguments. always equal or greater than 1.
403 * char **argv; // argument pointers. argv[0] is option name.
404 * }
405 * @endcode
406 *
407 * SECTION ID field:
408 *
409 * If an option is a section like <Option>, a section ID can be assigned.
410 * This section ID can be used to limit some other option directives so they
411 * are located only inside that section. This is optional. If you do not need
412 * to check directory scope, you can simply specify 0 here.
413 *
414 * There are 2 predefined section IDs: QAC_SECTION_ALL and QAC_SECTION_ROOT.
415 * User-defined section IDs should start from `1 << 1`, as shown below.
416 *
417 * @code
418 * enum {
419 * OPT_SECTION_ALL = QAC_SECTION_ALL, // pre-defined
420 * OPT_SECTION_ROOT = QAC_SECTION_ROOT, // pre-defined
421 * OPT_SECTION_DOMAIN = (1 << 1), // user-defined section
422 * OPT_SECTION_HOST = (1 << 2), // user-defined section
423 * };
424 * @endcode
425 *
426 * Please note that these section IDs are ORed together. The values should be
427 * assigned as bit flags, such as 2 (`1 << 1`), 4 (`1 << 2`), 8 (`1 << 3`), ...
428 *
429 * SECTION IDS field:
430 *
431 * This field is to limit the scope where an option is allowed to be specified.
432 * Multiple section IDs can be ORed.
433 *
434 * QAC_SECTION_ALL means an option can appear anywhere.
435 *
436 * QAC_SECTION_ROOT means an option can appear only at the top level, not
437 * inside any section.
438 */
439static int addoptions(qaconf_t *qaconf, const qaconf_option_t *options) {
440 if (qaconf == NULL || options == NULL) {
441 _seterrmsg(qaconf, "Invalid parameters.");
442 return -1;
443 }
444
445 // Count a number of options
446 uint32_t numopts;
447 for (numopts = 0; options[numopts].name != NULL; numopts++)
448 ;
449 if (numopts == 0)
450 return 0;
451
452 // Realloc
453 size_t newsize = sizeof(qaconf_option_t) * (qaconf->numoptions + numopts);
454 qaconf->options = (qaconf_option_t *) realloc(qaconf->options, newsize);
455 memcpy(&qaconf->options[qaconf->numoptions], options,
456 sizeof(qaconf_option_t) * numopts);
457 qaconf->numoptions += numopts;
458
459 return numopts;
460}
461
462/**
463 * Set default callback function.
464 *
465 * Default callback function will be called for unregistered option directives.
466 * QAC_IGNOREUNKNOWN flag will be ignored when default callback has set.
467 *
468 * @param qaconf qaconf_t object.
469 * @param callback callback function pointer
470 */
471static void setdefhandler(qaconf_t *qaconf, qaconf_cb_t *callback) {
472 qaconf->defcb = callback;
473}
474
475/**
476 * qaconf_t->setuserdata(): Set userdata which will be provided on callback.
477 *
478 * @param qaconf qaconf_t object.
479 * @param userdata pointer to userdata.
480 *
481 * @code
482 * // Define an example userdata
483 * struct MyConf {
484 * int sample;
485 * };
486 *
487 * int user_main(void) {
488 * struct MyConf myconf;
489 *
490 * (...codes...)
491 *
492 * // Set callback userdata.
493 * conf->setuserdata(conf, &myconf);
494 * (...codes...)
495 * }
496 *
497 * QAC_CB(confcb_callback_func) {
498 * (...codes...)
499 * // Type casting userdata for convenient use.
500 * struct MyConf *myconf = (struct MyConf *)userdata;
501 * myconf->sample++;
502 * (...codes...)
503 * return NULL;
504 * }
505 * @endcode
506 */
507static void setuserdata(qaconf_t *qaconf, const void *userdata) {
508 qaconf->userdata = (void *) userdata;
509}
510
511/**
512 * qaconf_t->parse(): Run parser.
513 *
514 * @param qaconf qaconf_t object.
515 * @param filepath configuration file path.
516 * @param flags parser options. (0 for default)
517 *
518 * @return A number of option directives parsed. -1 will be returned in case of
519 * error.
520 *
521 * Here is a list of flags. Multiple flags can be ORed.
522 *
523 * QAC_CASEINSENSITIVE: Option name is case-insensitive.
524 *
525 * QAC_IGNOREUNKNOWN : Ignore unknown option directives.
526 * This flag will be ignored if setdefhandler() has set.
527 *
528 * @code
529 * int c;
530 * c = conf->parse(conf, "sm1.conf", 0);
531 * c = conf->parse(conf, "sm2.conf", QAC_CASEINSENSITIVE);
532 * c = conf->parse(conf, "sm3.conf", QAC_CASEINSENSITIVE | QAC_IGNOREUNKNOWN);
533 * @endcode
534 */
535static int parse(qaconf_t *qaconf, const char *filepath, uint8_t flags) {
536 // Open file
537 FILE *fp = fopen(filepath, "r");
538 if (fp == NULL) {
539 _seterrmsg(qaconf, "Failed to open file '%s'.", filepath);
540 return -1;
541 }
542
543 // Set info
544 if (qaconf->filepath != NULL)
545 free(qaconf->filepath);
546 qaconf->filepath = strdup(filepath);
547 qaconf->lineno = 0;
548
549 // Parse
550 int optcount = _parse_inline(qaconf, fp, flags, QAC_SECTION_ROOT, NULL);
551
552 // Clean up
553 fclose(fp);
554
555 return optcount;
556}
557
558/**
559 * qaconf_t->errmsg(): Get last error message.
560 *
561 * @param qaconf qaconf_t object.
562 *
563 * @return A const pointer of error message string.
564 *
565 * @code
566 * int c = conf->parse(conf, "sample.conf", 0);
567 * if (c < 0) {
568 * // ERROR
569 * printf("%s\n", conf->errmsg(conf));
570 * }
571 * @endcode
572 */
573static const char *errmsg(qaconf_t *qaconf) {
574 return (const char*) qaconf->errstr;
575}
576
577/**
578 * qaconf_t->reseterror(): Clear error message.
579 *
580 * @param qaconf qaconf_t object.
581 *
582 * @code
583 * conf->reseterror(conf);
584 * conf->parse(conf, "sample.conf", 0);
585 * if (conf->errmsg(conf) != NULL) {
586 * // ERROR
587 * }
588 * @endcode
589 */
590static void reseterror(qaconf_t *qaconf) {
591 if (qaconf->errstr != NULL) {
592 free(qaconf->errstr);
593 qaconf->errstr = NULL;
594 }
595}
596
597/**
598 * qaconf_t->free(): Release resources.
599 *
600 * @param qaconf qaconf_t object.
601 *
602 * @code
603 * conf->free(conf);
604 * @endcode
605 */
606static void free_(qaconf_t *qaconf) {
607 if (qaconf->filepath != NULL)
608 free(qaconf->filepath);
609 if (qaconf->errstr != NULL)
610 free(qaconf->errstr);
611 if (qaconf->options != NULL)
612 free(qaconf->options);
613 free(qaconf);
614}
615
616#ifndef _DOXYGEN_SKIP
617
618#define ARGV_INIT_SIZE (4)
619#define ARGV_INCR_STEP (8)
620#define MAX_TYPECHECK (5)
621static int _parse_inline(qaconf_t *qaconf, FILE *fp, uint8_t flags,
622 enum qaconf_section sectionid,
623 qaconf_cbdata_t *cbdata_parent) {
624 // Assign compare function.
625 int (*cmpfunc)(const char *, const char *) = strcmp;
626 if (flags & QAC_CASEINSENSITIVE)
627 cmpfunc = strcasecmp;
628
629 char buf[MAX_LINESIZE];
630 bool doneloop = false;
631 bool exception = false;
632 int optcount = 0; // number of option entries processed.
633 int newsectionid = 0; // temporary store
634 void *freethis = NULL; // userdata to free
635 while (doneloop == false && exception == false) {
636
637#define EXITLOOP(fmt, args...) do { \
638 _seterrmsg(qaconf, "%s:%d " fmt, \
639 qaconf->filepath, qaconf->lineno, ##args); \
640 exception = true; \
641 goto exitloop; \
642} while (0);
643
644 if (fgets(buf, MAX_LINESIZE, fp) == NULL) {
645 // Check if section was opened and never closed
646 if (cbdata_parent != NULL) {
647 EXITLOOP("<%s> section was not closed.", cbdata_parent->argv[0]);
648 }
649 break;
650 }
651
652 // Increase line number counter
653 qaconf->lineno++;
654
655 // Trim white spaces
656 qstrtrim(buf);
657
658 // Skip blank lines and comments.
659 if (IS_EMPTY_STR(buf) || *buf == '#') {
660 continue;
661 }
662
663 DEBUG("%s (line=%d)", buf, qaconf->lineno);
664
665 // Create a callback data
666 qaconf_cbdata_t *cbdata = (qaconf_cbdata_t*) malloc(
667 sizeof(qaconf_cbdata_t));
668 ASSERT(cbdata != NULL);
669 memset(cbdata, '\0', sizeof(qaconf_cbdata_t));
670 if (cbdata_parent != NULL) {
671 cbdata->section = sectionid;
672 cbdata->sections = cbdata_parent->sections | sectionid;
673 cbdata->level = cbdata_parent->level + 1;
674 cbdata->parent = cbdata_parent;
675 } else {
676 cbdata->section = sectionid;
677 cbdata->sections = sectionid;
678 cbdata->level = 0;
679 cbdata->parent = NULL;
680 }
681
682 // Escape section option
683 char *sp = buf;
684 if (*sp == '<') {
685 if (ENDING_CHAR(sp) != '>') {
686 EXITLOOP("Missing closing bracket. - '%s'.", buf);
687 }
688
689 sp++;
690 if (*sp == '/') {
691 cbdata->otype = QAC_OTYPE_SECTIONCLOSE;
692 sp++;
693 } else {
694 cbdata->otype = QAC_OTYPE_SECTIONOPEN;
695 }
696
697 // Remove trailing bracket
698 ENDING_CHAR(sp) = '\0';
699 } else {
700 cbdata->otype = QAC_OTYPE_OPTION;
701 }
702
703 // Brackets have been removed at this point
704 // Copy data into cbdata buffer.
705 cbdata->data = strdup(sp);
706 ASSERT(cbdata->data != NULL);
707
708 // Parse and tokenize.
709 int argvsize = 0;
710 char *wp1, *wp2;
711 bool doneparsing = false;
712 for (wp1 = (char *) cbdata->data; doneparsing == false; wp1 = wp2) {
713 // Allocate/Realloc argv array
714 if (argvsize == cbdata->argc) {
715 argvsize += (argvsize == 0) ? ARGV_INIT_SIZE : ARGV_INCR_STEP;
716 cbdata->argv = (char**) realloc((void *) cbdata->argv,
717 sizeof(char*) * argvsize);
718 ASSERT(cbdata->argv != NULL);
719 }
720
721 // Skip whitespaces
722 for (; (*wp1 == ' ' || *wp1 == '\t'); wp1++)
723 ;
724
725 // Quote handling
726 int qtmark = 0; // 1 for singlequotation, 2 for doublequotation
727 if (*wp1 == '\'') {
728 qtmark = 1;
729 wp1++;
730 } else if (*wp1 == '"') {
731 qtmark = 2;
732 wp1++;
733 }
734
735 // Parse a word
736 for (wp2 = wp1;; wp2++) {
737 if (*wp2 == '\0') {
738 doneparsing = true;
739 break;
740 } else if (*wp2 == '\'') {
741 if (qtmark == 1) {
742 qtmark = 0;
743 break;
744 }
745 } else if (*wp2 == '"') {
746 if (qtmark == 2) {
747 qtmark = 0;
748 break;
749 }
750 } else if (*wp2 == '\\') {
751 if (qtmark > 0) {
752 size_t wordlen = wp2 - wp1;
753 if (wordlen > 0)
754 memmove(wp1 + 1, wp1, wordlen);
755 wp1++;
756 wp2++;
757 }
758 } else if (*wp2 == ' ' || *wp2 == '\t') {
759 if (qtmark == 0)
760 break;
761 }
762 }
763 *wp2 = '\0';
764 wp2++;
765
766 // Check that quotations are paired.
767 if (qtmark > 0) {
768 EXITLOOP("Quotation marks were not closed properly.");
769 }
770
771 // Store an argument
772 cbdata->argv[cbdata->argc] = wp1;
773 cbdata->argc++;
774 DEBUG(" argv[%d]=%s", cbdata->argc - 1, wp1);
775
776 // For quoted strings, this can happen.
777 if (*wp2 == '\0') {
778 doneparsing = true;
779 }
780 }
781
782 // Check mismatch sectionclose
783 if (cbdata->otype == QAC_OTYPE_SECTIONCLOSE) {
784 if (cbdata_parent == NULL
785 || cmpfunc(cbdata->argv[0], cbdata_parent->argv[0])) {
786 EXITLOOP("Trying to close <%s> section that wasn't opened.",
787 cbdata->argv[0]);
788 }
789 }
790
791 // Find matching option
792 bool optfound = false;
793 int i;
794 for (i = 0; optfound == false && i < qaconf->numoptions; i++) {
795 qaconf_option_t *option = &qaconf->options[i];
796
797 if (!cmpfunc(cbdata->argv[0], option->name)) {
798 // Check sections
799 if ((cbdata->otype != QAC_OTYPE_SECTIONCLOSE)
800 && (option->sections != QAC_SECTION_ALL)
801 && (option->sections & sectionid) == 0) {
802 EXITLOOP("Option '%s' is in wrong section.", option->name);
803 }
804
805 // Check argument types
806 if (cbdata->otype != QAC_OTYPE_SECTIONCLOSE) {
807 // Check number of arguments
808 int numtake = option->take & QAC_TAKEALL;
809 if (numtake != QAC_TAKEALL
810 && numtake != (cbdata->argc - 1)) {
811 EXITLOOP("'%s' option takes %d arguments.",
812 option->name, numtake);
813 }
814
815 // Check argument types
816 int deftype; // 0:str, 1:int, 2:float, 3:bool
817 if (option->take & QAC_AA_INT)
818 deftype = 1;
819 else if (option->take & QAC_AA_FLOAT)
820 deftype = 2;
821 else if (option->take & QAC_AA_BOOL)
822 deftype = 3;
823 else
824 deftype = 0;
825
826 int j;
827 for (j = 1; j < cbdata->argc && j <= MAX_TYPECHECK; j++) {
828 int argtype;
829 if (option->take & (QAC_A1_INT << (j - 1)))
830 argtype = 1;
831 else if (option->take & (QAC_A1_FLOAT << (j - 1)))
832 argtype = 2;
833 else if (option->take & (QAC_A1_BOOL << (j - 1)))
834 argtype = 3;
835 else
836 argtype = deftype;
837
838 if (argtype == 1) {
839 // integer type
840 if (_is_str_number(cbdata->argv[j]) != 1) {
841 EXITLOOP(
842 "%dth argument of '%s' must be integer type.",
843 j, option->name);
844 }
845 } else if (argtype == 2) {
846 // floating point type
847 if (_is_str_number(cbdata->argv[j]) == 0) {
848 EXITLOOP(
849 "%dth argument of '%s' must be floating-point type.",
850 j, option->name);
851 }
852 } else if (argtype == 3) {
853 // bool type
854 if (_is_str_bool(cbdata->argv[j]) != 0) {
855 // Change argument to "1".
856 strcpy(cbdata->argv[j], "1");
857 } else {
858 EXITLOOP(
859 "%dth argument of '%s' must be bool type.",
860 j, option->name);
861 }
862 }
863 }
864 }
865
866 // Callback
867 //DEBUG("Callback %s", option->name);
868 qaconf_cb_t *usercb = option->cb;
869 if (usercb == NULL)
870 usercb = qaconf->defcb;
871 if (usercb != NULL) {
872 char *cberrmsg = NULL;
873
874 if (cbdata->otype != QAC_OTYPE_SECTIONCLOSE) {
875 // Normal option and sectionopen
876 cberrmsg = usercb(cbdata, qaconf->userdata);
877 } else {
878 // QAC_OTYPE_SECTIONCLOSE
879
880 // Change otype
881 ASSERT(cbdata_parent != NULL);
882 enum qaconf_otype orig_otype = cbdata_parent->otype;
883 cbdata_parent->otype = QAC_OTYPE_SECTIONCLOSE;
884
885 // Callback
886 cberrmsg = usercb(cbdata_parent, qaconf->userdata);
887
888 // Restore type
889 cbdata_parent->otype = orig_otype;
890 }
891
892 // Error handling
893 if (cberrmsg != NULL) {
894 freethis = cberrmsg;
895 EXITLOOP("%s", cberrmsg);
896 }
897 }
898
899 if (cbdata->otype == QAC_OTYPE_SECTIONOPEN) {
900 // Store it for later
901 newsectionid = option->sectionid;
902 }
903
904 // Set found flag
905 optfound = true;
906 }
907 }
908
909 // If not found.
910 if (optfound == false) {
911 if (qaconf->defcb != NULL) {
912 qaconf->defcb(cbdata, qaconf->userdata);
913 } else if ((flags & QAC_IGNOREUNKNOWN) == 0) {
914 EXITLOOP("Unregistered option '%s'.", cbdata->argv[0]);
915 }
916 }
917
918 // Section handling
919 if (cbdata->otype == QAC_OTYPE_SECTIONOPEN) {
920 // Enter recursive call
921 DEBUG("Entering next level %d.", cbdata->level+1);
922 int optcount2 = _parse_inline(qaconf, fp, flags, newsectionid,
923 cbdata);
924 if (optcount2 >= 0) {
925 optcount += optcount2;
926 } else {
927 exception = true;
928 }DEBUG("Returned to previous level %d.", cbdata->level);
929 } else if (cbdata->otype == QAC_OTYPE_SECTIONCLOSE) {
930 // Leave recursive call
931 doneloop = true;
932 }
933
934 exitloop:
935 // Release resources
936 if (freethis != NULL) {
937 free(freethis);
938 }
939
940 if (cbdata != NULL) {
941 _free_cbdata(cbdata);
942 cbdata = NULL;
943 }
944
945 if (exception == true) {
946 break;
947 }
948
949 // Go up and down
950 // if (otype
951
952 // Increase process counter
953 optcount++;
954 }
955
956 return (exception == false) ? optcount : -1;
957}
958
959static void _seterrmsg(qaconf_t *qaconf, const char *format, ...) {
960 if (qaconf->errstr != NULL)
961 free(qaconf->errstr);
962 DYNAMIC_VSPRINTF(qaconf->errstr, format);
963}
964
965static void _free_cbdata(qaconf_cbdata_t *cbdata) {
966 if (cbdata->argv != NULL)
967 free(cbdata->argv);
968 if (cbdata->data != NULL)
969 free(cbdata->data);
970 free(cbdata);
971}
972
973// return 2 for floating point .
974// return 1 for integer
975// return 0 for non number
976static int _is_str_number(const char *s) {
977 char *op = (char *) s;
978 if (*op == '-') {
979 op++;
980 }
981
982 char *cp, *dp;
983 for (cp = op, dp = NULL; *cp != '\0'; cp++) {
984 if ('0' <= *cp && *cp <= '9') {
985 continue;
986 }
987
988 if (*cp == '.') {
989 if (cp == op)
990 return 0; // dot can't be at the beginning.
991 if (dp != NULL)
992 return 0; // dot can't be appeared more than once.
993 dp = cp;
994 continue;
995 }
996
997 return 0;
998 }
999
1000 if (cp == op) {
1001 return 0; // empty string
1002 }
1003
1004 if (dp != NULL) {
1005 if (dp + 1 == cp)
1006 return 0; // dot can't be at the end.
1007 return 2; // float point
1008 }
1009
1010 // integer
1011 return 1;
1012}
1013
1014static int _is_str_bool(const char *s) {
1015 if (!strcasecmp(s, "true"))
1016 return 1;
1017 else if (!strcasecmp(s, "on"))
1018 return 1;
1019 else if (!strcasecmp(s, "yes"))
1020 return 1;
1021 else if (!strcasecmp(s, "1"))
1022 return 1;
1023 return 0;
1024}
1025
1026#endif /* _DOXYGEN_SKIP */
1027
1028#endif /* DISABLE_QACONF */
1029
static int parse(qaconf_t *qaconf, const char *filepath, uint8_t flags)
qaconf_t->parse(): Run parser.
Definition qaconf.c:535
static void setdefhandler(qaconf_t *qaconf, qaconf_cb_t *callback)
Set default callback function.
Definition qaconf.c:471
static int addoptions(qaconf_t *qaconf, const qaconf_option_t *options)
qaconf_t->addoptions(): Register option directives.
Definition qaconf.c:439
qaconf_t * qaconf(void)
Create a new configuration object.
Definition qaconf.c:245
static void setuserdata(qaconf_t *qaconf, const void *userdata)
qaconf_t->setuserdata(): Set userdata which will be provided on callback.
Definition qaconf.c:507
static void reseterror(qaconf_t *qaconf)
qaconf_t->reseterror(): Clear error message.
Definition qaconf.c:590
static void free_(qaconf_t *qaconf)
qaconf_t->free(): Release resources.
Definition qaconf.c:606
static const char * errmsg(qaconf_t *qaconf)
qaconf_t->errmsg(): Get last error message.
Definition qaconf.c:573
char * qstrtrim(char *str)
Remove whitespace, including CR and LF, from both ends of a string.
Definition qstring.c:55