5 Author: Giovanni Giacobbi <giovanni@giacobbi.net>
7 Copyright (C) 2002 - 2006 Giovanni Giacobbi
9 This program is free software; you can redistribute it and/or modify
10 it under the terms of the GNU General Public License as published by
11 the Free Software Foundation; version 2 of the License.
13 This program is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 GNU General Public License for more details.
23 /* limit debug logging verbosity */
25 #define SILC_CONFIG_DEBUG(fmt) SILC_LOG_DEBUG(fmt)
27 #define SILC_CONFIG_DEBUG(fmt)
32 /* this is the option struct and currently it is only used internally to
33 * the module and other structs. */
34 typedef struct SilcConfigOptionStruct {
35 char *name; /* *lowercase* name of the option */
36 SilcConfigType type; /* important: the type of the returned value */
37 SilcConfigCallback cb; /* the value handler */
38 const SilcConfigTable *subtable; /* used if type is SILC_CONFIG_ARG_BLOCK */
39 void *context; /* context passed to the callback function */
40 struct SilcConfigOptionStruct *next;
43 /* unique for each config file (and included ones) */
44 struct SilcConfigFileObject {
45 char *filename; /* the original filename opened */
46 int level; /* parsing level, how many nested
47 silc_config_main we have */
48 unsigned char *base; /* this is a fixed pointer to the base location */
49 unsigned char *p; /* the Parser poitner */
50 SilcUInt32 len; /* fixed length of the whole file */
51 SilcUInt32 line; /* current parsing line, strictly linked to p */
52 SilcBool included; /* wether this file is main or included */
55 /* We need the entity to base our block-style parsing on */
56 struct SilcConfigEntityObject {
57 SilcConfigOption *opts; /* known options list */
58 SilcConfigFile *file; /* parsing file object */
61 /* access error descriptions only with silc_config_strerror() */
62 static char *errorstrs[] = {
63 "-OK", /* SILC_CONFIG_OK */
64 "-SILENT", /* SILC_CONFIG_ESILENT */
65 "-PRINTLINE", /* SILC_CONFIG_EPRINTLINE */
66 "Invalid syntax", /* SILC_CONFIG_EGENERIC */
67 "Internal error! Please report this bug", /* SILC_CONFIG_EINTERNAL */
68 "Can't open specified file", /* SILC_CONFIG_ECANTOPEN */
69 "Expected open-brace '{'", /* SILC_CONFIG_EOPENBRACE */
70 "Missing close-brace '}'", /* SILC_CONFIG_ECLOSEBRACE */
71 "Invalid data type", /* SILC_CONFIG_ETYPE */
72 "Unknown option", /* SILC_CONFIG_EBADOPTION */
73 "Invalid text", /* SILC_CONFIG_EINVALIDTEXT */
74 "Double option specification", /* SILC_CONFIG_EDOUBLE */
75 "Expected data but not found", /* SILC_CONFIG_EEXPECTED */
76 "Expected '='", /* SILC_CONFIG_EEXPECTEDEQUAL */
77 "Unexpected data", /* SILC_CONFIG_EUNEXPECTED */
78 "Missing mandatory fields", /* SILC_CONFIG_EMISSFIELDS */
79 "Missing ';'", /* SILC_CONFIG_EMISSCOLON */
82 /* return string describing SilcConfig's error code */
83 char *silc_config_strerror(int errnum)
85 if ((errnum < 0) || (errnum >= sizeof(errorstrs)/sizeof(*errorstrs)) ||
86 (errorstrs[errnum] == NULL)) {
87 char *defret = "-INVALIDERROR";
90 return errorstrs[errnum];
93 /* Begin of internal SilcConfig's text util functions */
95 /* Points the first non-space character */
96 static void my_trim_spaces(SilcConfigFile *file)
98 unsigned char *r = file->p;
99 while ((*r != '\0' && *r != (unsigned char)EOF) && isspace((int)*r))
100 if (*r++ == '\n') file->line++;
104 /* Skips the current line until newline (lf or cr) */
105 static void my_skip_line(SilcConfigFile *file)
107 unsigned char *r = file->p;
108 while ((*r != '\0' && *r != (unsigned char)EOF) &&
109 (*r != '\n') && (*r != '\r')) r++;
110 file->p = ((*r != '\0' && *r != (unsigned char)EOF) ? r + 1 : r);
114 /* Obtains a text token from the current position until first separator.
115 * a separator is any non alphanumeric character nor "_" or "-" */
116 static unsigned char *my_next_token(SilcConfigFile *file, char *to)
118 unsigned int count = 0;
121 my_trim_spaces(file);
123 while ((isalnum((int)*o) || (*o == '_') || (*o == '-')) &&
133 /* Obtains a string from the current position. The only difference from
134 * next_token() is that quoted-strings are also accepted */
135 static char *my_get_string(SilcConfigFile *file, char *to)
139 my_trim_spaces(file);
142 unsigned int count = 0;
143 unsigned char *d = to;
144 while (count < BUF_SIZE) {
155 if (count >= BUF_SIZE) { /* XXX FIXME: gotta do something here */
156 fprintf(stderr, "Bullshit, missing matching \"");
160 /* update stream pointer */
163 /* we don't need quote parsing, fall-back to token extractor */
164 my_next_token(file, to);
169 /* Skips all comment lines and spaces lines until first useful character */
170 static void my_skip_comments(SilcConfigFile *file)
173 my_trim_spaces(file);
174 if (*file->p != '#') return;
179 /* End of internal text functions
180 * Next section contains SilcConfig internal config utils */
182 /* find an option in the list by name and returns its pointer */
183 static SilcConfigOption *silc_config_find_option(SilcConfigEntity ent,
186 SilcConfigOption *tmp;
187 for (tmp = ent->opts; tmp; tmp = tmp->next) {
188 if (!strcasecmp(tmp->name, name))
194 /* Converts a string in the type specified. returns a dynamically
195 * allocated pointer. */
196 static void *silc_config_marshall(SilcConfigType type, const char *val)
200 SilcBool val_boolean;
205 case SILC_CONFIG_ARG_TOGGLE:
206 if (!strcasecmp(val, "yes") || !strcasecmp(val, "true") ||
207 !strcasecmp(val, "on") || !strcasecmp(val, "1")) {
210 else if (!strcasecmp(val, "no") || !strcasecmp(val, "false") ||
211 !strcasecmp(val, "off") || !strcasecmp(val, "0")) {
216 pt = silc_calloc(1, sizeof(val_boolean));
217 *(SilcBool *)pt = (SilcBool) val_boolean;
219 case SILC_CONFIG_ARG_INT:
220 val_int = (int) strtol(val, &val_tmp, 0);
221 if (*val_tmp) /* error converting string */
223 pt = silc_calloc(1, sizeof(val_int));
224 *(int *)pt = val_int;
226 case SILC_CONFIG_ARG_SIZE:
227 val_size = (SilcUInt32) strtol(val, &val_tmp, 0);
229 return NULL; /* really wrong, there must be at least one digit */
230 /* Search for a designator */
231 switch (tolower((int)val_tmp[0])) {
232 case '\0': /* None */
234 case 'k': /* Kilobytes */
235 val_size *= (SilcUInt32) 1024;
237 case 'm': /* Megabytes */
238 val_size *= (SilcUInt32) (1024 * 1024);
241 val_size *= (SilcUInt32) (1024 * 1024 * 1024);
246 /* the string must die here */
249 pt = silc_calloc(1, sizeof(val_size));
250 *(SilcUInt32 *)pt = val_size;
252 case SILC_CONFIG_ARG_STR: /* the only difference between STR and STRE is */
253 if (!val[0]) /* that STR cannot be empty, while STRE can. */
255 case SILC_CONFIG_ARG_STRE:
256 pt = (void *) strdup(val);
258 /* following types are not supposed to have a return value */
259 case SILC_CONFIG_ARG_BLOCK:
260 case SILC_CONFIG_ARG_NONE:
269 /* End of internal functions */
272 /* Tries to open the config file and returns a valid SilcConfigFile object
273 * or NULL if failed */
275 SilcConfigFile *silc_config_open(const char *configfile)
281 if (!(buffer = silc_file_readfile(configfile, &filelen)))
284 ret = silc_calloc(1, sizeof(*ret));
285 ret->filename = strdup(configfile);
286 ret->base = ret->p = (unsigned char *)buffer;
288 ret->line = 1; /* line count, start from first line */
292 /* Frees a file object */
294 void silc_config_close(SilcConfigFile *file)
297 silc_free(file->filename);
298 memset(file->base, 'F', file->len);
299 silc_free(file->base);
300 memset(file, 'F', sizeof(*file));
305 /* initializes a SilcConfigEntity pointer allocation */
307 SilcConfigEntity silc_config_init(SilcConfigFile *file)
309 SilcConfigEntity ret;
314 SILC_CONFIG_DEBUG(("Allocating new config entity"));
315 ret = silc_calloc(1, sizeof(*ret));
320 /* Returns the original filename of the object file */
322 char *silc_config_get_filename(SilcConfigFile *file)
325 return file->filename;
329 /* Returns the current line that file parsing arrived at */
331 SilcUInt32 silc_config_get_line(SilcConfigFile *file)
338 /* Returns a pointer to the beginning of the requested line. If the line
339 * was not found, NULL is returned */
341 char *silc_config_read_line(SilcConfigFile *file, SilcUInt32 line)
343 unsigned char *p, *ret = NULL, *endbuf;
346 if (!file || (line <= 0))
348 for (p = file->base; *p && (*p != (unsigned char)EOF); p++) {
357 if ((endbuf = strchr(p, '\n'))) {
360 ret = silc_memdup(p, len);
362 ret = silc_memdup(p, strlen(p));
367 /* Convenience function to read the current parsed line */
369 char *silc_config_read_current_line(SilcConfigFile *file)
371 return silc_config_read_line(file, file->line);
374 /* (Private) destroy a SilcConfigEntity */
376 static void silc_config_destroy(SilcConfigEntity ent, SilcBool destroy_opts)
378 SilcConfigOption *oldopt, *nextopt;
379 SILC_CONFIG_DEBUG(("Freeing config entity [ent=0x%x] [opts=0x%x]",
380 (SilcUInt32) ent, (SilcUInt32) ent->opts));
382 /* if she wants to preserve options just free the object struct */
386 for (oldopt = ent->opts; oldopt; oldopt = nextopt) {
387 nextopt = oldopt->next;
388 memset(oldopt->name, 'F', strlen(oldopt->name) + 1);
389 silc_free(oldopt->name);
390 memset(oldopt, 'F', sizeof(*oldopt));
395 memset(ent, 'F', sizeof(*ent));
399 /* Registers a new option in the specified entity.
400 * Returns TRUE on success, FALSE if already registered. */
402 SilcBool silc_config_register(SilcConfigEntity ent, const char *name,
403 SilcConfigType type, SilcConfigCallback cb,
404 const SilcConfigTable *subtable, void *context)
406 SilcConfigOption *newopt;
407 SILC_CONFIG_DEBUG(("Register new option=\"%s\" "
408 "type=%u cb=0x%08x context=0x%08x",
409 name, type, (SilcUInt32) cb, (SilcUInt32) context));
411 /* if we are registering a block, make sure there is a specified sub-table */
412 if (!ent || !name || ((type == SILC_CONFIG_ARG_BLOCK) && !subtable))
415 /* don't register a reserved tag */
416 if (!strcasecmp(name, "include"))
419 /* check if an option was previously registered */
420 if (silc_config_find_option(ent, name)) {
421 SILC_LOG_DEBUG(("Error: Can't register \"%s\" twice.", name));
425 /* allocate and append the new option */
426 newopt = silc_calloc(1, sizeof(*newopt));
427 newopt->name = strdup(name);
430 newopt->subtable = subtable;
431 newopt->context = context;
433 /* append this option to the list */
437 SilcConfigOption *tmp;
438 for (tmp = ent->opts; tmp->next; tmp = tmp->next);
444 /* Register a new option table in the specified config entity */
446 SilcBool silc_config_register_table(SilcConfigEntity ent,
447 const SilcConfigTable table[], void *context)
452 SILC_CONFIG_DEBUG(("Registering table"));
453 /* XXX FIXME: some potability checks needed - really? */
454 for (i = 0; table[i].name; i++) {
455 if (!silc_config_register(ent, table[i].name, table[i].type,
456 table[i].callback, table[i].subtable, context))
464 static int silc_config_main_internal(SilcConfigEntity ent)
466 SilcConfigFile *file = ent->file;
467 unsigned char **p = &file->p;
469 /* loop throught statements */
472 SilcConfigOption *thisopt;
474 /* makes it pointing to the next interesting char */
475 my_skip_comments(file);
477 if (**p == '\0' || **p == (unsigned char)EOF) {
478 if (file->level > 1) /* cannot get eof in a sub-level! */
479 return SILC_CONFIG_EEXPECTED;
482 /* check if we completed this (sub) section (it doesn't matter if this
483 * is the main section) */
485 if (file->level < 2) /* can't be! must be at least one sub-block */
486 return SILC_CONFIG_EUNEXPECTED;
490 //SILC_LOG_HEXDUMP(("Preparing lookup at line=%lu", file->line), *p, 16);
492 /* obtain the keyword */
493 my_next_token(file, buf);
494 SILC_CONFIG_DEBUG(("Looking up keyword=\"%s\" [line=%lu]",
497 /* handle special directive */
498 if (!strcasecmp(buf, "include")) {
500 SilcConfigFile *inc_file;
501 SilcConfigEntity inc_ent;
503 my_trim_spaces(file); /* prepare next char */
505 /* Now trying to include the specified file. The included file will
506 * be allowed to include sub-files but it will preserve the block-level
507 * of the including block. Note that the included file won't be allowed
508 * to raise the block level of the including block. */
510 my_get_string(file, buf); /* get the filename */
511 SILC_LOG_DEBUG(("Including file \"%s\"", buf));
512 /* before getting on, check if this row is REALLY complete */
514 return SILC_CONFIG_EMISSCOLON;
516 /* open the file and start the parsing */
517 inc_file = silc_config_open(buf);
518 if (!inc_file) /* does it point a valid filename? */
519 return SILC_CONFIG_ECANTOPEN;
520 inc_file->included = TRUE;
522 /* create a new entity and hack it to use the same options */
523 inc_ent = silc_config_init(inc_file);
524 inc_ent->opts = ent->opts;
525 ret = silc_config_main(inc_ent);
528 * If the included file returned an error, the application will probably
529 * want to output some kind of error message. Because of this, we can't
530 * destroy THIS file object. The hack works this way: The application
531 * expects to destroy the originally created object file, so we'll swap
532 * the original file with the included file. */
534 SilcConfigFile tmp_file;
535 SILC_CONFIG_DEBUG(("SWAPPING FILE OBJECTS"));
536 memcpy(&tmp_file, inc_file, sizeof(tmp_file));
537 memcpy(inc_file, file, sizeof(tmp_file));
538 silc_config_close(inc_file);
539 memcpy(file, &tmp_file, sizeof(tmp_file));
542 /* otherwise if no errors encoured, continue normally */
543 silc_config_close(inc_file);
544 continue; /* this one is handled */
547 /* we have a registered option (it can also be a sub-block) */
548 thisopt = silc_config_find_option(ent, buf);
550 return SILC_CONFIG_EBADOPTION;
552 my_trim_spaces(file); /* prepare next char */
554 /* option type is a block? */
555 if (thisopt->type == SILC_CONFIG_ARG_BLOCK) {
557 SilcConfigEntity sub_ent;
559 SILC_CONFIG_DEBUG(("Entering sub-block"));
561 return SILC_CONFIG_EOPENBRACE;
562 /* build the new entity for this sub-block */
563 sub_ent = silc_config_init(ent->file);
564 /* use the previous specified table to describe this block's options */
565 silc_config_register_table(sub_ent, thisopt->subtable, thisopt->context);
566 /* run this block! */
567 ret = silc_config_main(sub_ent);
568 SILC_CONFIG_DEBUG(("Returned from sub-block [ret=%d]", ret));
570 if (ret) /* now check the result */
573 /* now call block clean-up callback (if any) */
576 SILC_CONFIG_DEBUG(("Now calling clean-up callback"));
577 ret = thisopt->cb(thisopt->type, thisopt->name, file->line, NULL,
580 SILC_CONFIG_DEBUG(("Callback refused the value [ret=%d]", ret));
584 /* Do we want ';' to be mandatory after close brace? */
586 return SILC_CONFIG_EMISSCOLON;
588 else if (thisopt->type == SILC_CONFIG_ARG_NONE) {
589 /* before getting on, check if this row is REALLY complete */
591 return SILC_CONFIG_EMISSCOLON;
592 SILC_CONFIG_DEBUG(("Triggering callback for none"));
594 thisopt->cb(thisopt->type, thisopt->name, file->line,
595 NULL, thisopt->context);
600 int ret = 0; /* very important in case of no cb */
603 return SILC_CONFIG_EEXPECTEDEQUAL;
605 my_get_string(file, buf); /* get the option argument */
606 SILC_CONFIG_DEBUG(("With argument=\"%s\"", buf));
608 /* before getting on, check if this row is REALLY complete */
610 return SILC_CONFIG_EMISSCOLON;
612 /* convert the option argument to the right format */
613 pt = silc_config_marshall(thisopt->type, buf);
615 return SILC_CONFIG_EINVALIDTEXT;
617 ret = thisopt->cb(thisopt->type, thisopt->name, file->line,
618 pt, thisopt->context);
620 /* since we have to free "pt" both on failure and on success, we
621 assume that ret == 0 if we didn't actually call any cb. */
624 SILC_CONFIG_DEBUG(("Callback refused the value [ret=%d]", ret));
634 return SILC_CONFIG_OK;
639 int silc_config_main(SilcConfigEntity ent)
641 SilcConfigFile *file = ent->file;
644 /* don't silently accept a NULL entity */
646 ret = SILC_CONFIG_EGENERIC;
650 /* call the real main and store the result */
652 SILC_CONFIG_DEBUG(("[Lev=%d] Entering config parsing core", file->level));
653 ret = silc_config_main_internal(ent);
654 SILC_CONFIG_DEBUG(("[Lev=%d] Quitting main [ret=%d]", file->level, ret));
655 if (!file->level) /* when swap happens, we could close a file twice */
659 /* If this file was included don't destroy the options set because it is
660 * the same of the including block. Although if this entity is in a
661 * sub-block created inside the included file, this options set must be
664 if ((file->level != 0) || (file->included != TRUE))
665 silc_config_destroy(ent, TRUE);
667 silc_config_destroy(ent, FALSE);