5 Author: Johnny Mnemonic <johnny@themnemonic.org>
7 Copyright (C) 1997 - 2002 Pekka Riikonen
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; either version 2 of the License, or
12 (at your option) any later version.
14 This program is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 GNU General Public License for more details.
22 #include "silcincludes.h"
24 /* limit debug logging verbosity */
26 #define SILC_CONFIG_DEBUG(fmt) SILC_LOG_DEBUG(fmt)
28 #define SILC_CONFIG_DEBUG(fmt)
31 /* this is the option struct and currently it is only used internally to
32 * the module and other structs. */
33 typedef struct SilcConfigOptionStruct {
34 char *name; /* *lowercase* name of the option */
35 SilcConfigType type; /* important: the type of the returned value */
36 SilcConfigCallback cb; /* the value handler */
37 const SilcConfigTable *subtable; /* used if type is SILC_CONFIG_ARG_BLOCK */
38 void *context; /* context passed to the callback function */
39 struct SilcConfigOptionStruct *next;
42 /* unique for each config file (and included ones) */
43 struct SilcConfigFileObject {
44 char *filename; /* the original filename opened */
45 int level; /* parsing level, how many nested silc_config_main we have */
46 char *base; /* this is a fixed pointer to the base location */
47 char *p; /* the Parser poitner */
48 uint32 len; /* fixed length of the whole file */
49 uint32 line; /* current parsing line, strictly linked to p */
50 bool included; /* wether this file is main or included */
53 /* We need the entity to base our block-style parsing on */
54 struct SilcConfigEntityObject {
55 SilcConfigOption *opts; /* known options list */
56 SilcConfigFile *file; /* parsing file object */
59 /* access error descriptions only with silc_config_strerror() */
60 static char *errorstrs[] = {
61 "-OK", /* SILC_CONFIG_OK */
62 "-SILENT", /* SILC_CONFIG_ESILENT */
63 "Invalid syntax", /* SILC_CONFIG_EGENERIC */
64 "Internal error! Please report this bug", /* SILC_CONFIG_EINTERNAL */
65 "Can't open specified file", /* SILC_CONFIG_ECANTOPEN */
66 "Expected open-brace '{'", /* SILC_CONFIG_EOPENBRACE */
67 "Missing close-brace '}'", /* SILC_CONFIG_ECLOSEBRACE */
68 "Invalid data type", /* SILC_CONFIG_ETYPE */
69 "Unknown option", /* SILC_CONFIG_EBADOPTION */
70 "Invalid text", /* SILC_CONFIG_EINVALIDTEXT */
71 "Double option specification", /* SILC_CONFIG_EDOUBLE */
72 "Expected data but not found", /* SILC_CONFIG_EEXPECTED */
73 "Expected '='", /* SILC_CONFIG_EEXPECTEDEQUAL */
74 "Unexpected data", /* SILC_CONFIG_EUNEXPECTED */
75 "Missing mandatory fields", /* SILC_CONFIG_EMISSFIELDS */
76 "Missing ';'", /* SILC_CONFIG_EMISSCOLON */
79 /* return string describing SilcConfig's error code */
80 char *silc_config_strerror(int errnum)
82 if ((errnum < 0) || (errnum >= sizeof(errorstrs)/sizeof(*errorstrs)) ||
83 (errorstrs[errnum] == NULL)) {
84 char *defret = "-INVALIDERROR";
87 return errorstrs[errnum];
90 /* Begin of internal SilcConfig's text util functions */
92 /* Points the first non-space character */
93 static void my_trim_spaces(SilcConfigFile *file)
95 register char *r = file->p;
97 if (*r++ == '\n') file->line++;
100 /* Skips the current line until newline (lf or cr) */
101 static void my_skip_line(SilcConfigFile *file)
103 register char *r = file->p;
104 while (*r && (*r != '\n') && (*r != '\r')) r++;
105 file->p = (*r ? r + 1 : r);
108 /* Obtains a text token from the current position until first separator.
109 * a separator is any non alphanumeric character nor "_" or "-" */
110 static char *my_next_token(SilcConfigFile *file, char *to)
113 my_trim_spaces(file);
115 while (isalnum(*o) || (*o == '_') || (*o == '-'))
121 /* Obtains a string from the current position. The only difference from
122 * next_token() is that quoted-strings are also accepted */
123 static char *my_get_string(SilcConfigFile *file, char *to)
126 my_trim_spaces(file);
129 char *quot = strchr(++o, '"');
131 if (!quot) { /* XXX FIXME: gotta do something here */
132 printf("Bullshit, missing matching \"");
141 /* update stream pointer */
145 /* we don't need quote parsing, fall-back to token extractor */
146 my_next_token(file, to);
149 /* Skips all comment lines and spaces lines until first useful character */
150 static void my_skip_comments(SilcConfigFile *file)
153 my_trim_spaces(file);
154 if (*file->p != '#') return;
159 /* End of internal text functions
160 * Next section contains SilcConfig internal config utils */
162 /* find an option in the list by name and returns its pointer */
163 static SilcConfigOption *silc_config_find_option(SilcConfigEntity ent,
166 SilcConfigOption *tmp;
167 for (tmp = ent->opts; tmp; tmp = tmp->next) {
168 if (!strcasecmp(tmp->name, name))
174 static void *silc_config_marshall(SilcConfigType type, const char *val)
183 case SILC_CONFIG_ARG_TOGGLE:
184 if (!strcasecmp(val, "yes") || !strcasecmp(val, "true") ||
185 !strcasecmp(val, "on") || !strcasecmp(val, "1")) {
188 else if (!strcasecmp(val, "no") || !strcasecmp(val, "false") ||
189 !strcasecmp(val, "off") || !strcasecmp(val, "0")) {
194 pt = silc_calloc(1, sizeof(val_bool));
195 *(bool *)pt = (bool) val_bool;
197 case SILC_CONFIG_ARG_INT:
198 val_int = (int) strtol(val, &val_tmp, 0);
199 if (*val_tmp) /* error converting string */
201 pt = silc_calloc(1, sizeof(val_int));
202 *(int *)pt = val_int;
204 case SILC_CONFIG_ARG_SIZE:
205 val_size = (uint32) strtol(val, &val_tmp, 0);
207 return NULL; /* really wrong, there must be at least one digit */
208 /* Search for a designator */
209 switch (tolower(val_tmp[0])) {
210 case '\0': /* None */
212 case 'k': /* Kilobytes */
213 val_size *= (uint32) 1024;
215 case 'm': /* Megabytes */
216 val_size *= (uint32) (1024 * 1024);
219 val_size *= (uint32) (1024 * 1024 * 1024);
224 /* the string must die here */
227 pt = silc_calloc(1, sizeof(val_size));
228 *(uint32 *)pt = val_size;
230 case SILC_CONFIG_ARG_STR: /* the only difference between STR and STRE is */
231 if (!val[0]) /* that STR cannot be empty, while STRE can. */
233 case SILC_CONFIG_ARG_STRE:
234 pt = (void *) strdup(val);
236 /* following types are not supposed to have a return value */
237 case SILC_CONFIG_ARG_BLOCK:
238 case SILC_CONFIG_ARG_NONE:
247 /* End of internal functions */
250 /* Tries to open the config file and returns a valid SilcConfigFile object
251 * or NULL if failed */
253 SilcConfigFile *silc_config_open(char *configfile)
259 if (!(buffer = silc_file_readfile(configfile, &filelen)))
262 ret = silc_calloc(1, sizeof(*ret));
263 ret->filename = strdup(configfile);
264 ret->base = ret->p = buffer;
266 ret->line = 1; /* line count, start from first line */
270 /* Frees a file object */
272 void silc_config_close(SilcConfigFile *file)
275 /* XXX FIXME: this check could probably be removed later */
276 uint32 my_len = (uint32) (strchr(file->base, EOF) - file->base);
277 SILC_CONFIG_DEBUG(("file=0x%x name=\"%s\" level=%d line=%lu", (uint32) file,
278 file->filename, file->level, file->line));
279 if (my_len != file->len) {
280 fprintf(stderr, "FATAL ERROR: saved len and current len does not match!\n");
283 silc_free(file->filename);
284 memset(file->base, 'F', file->len);
285 silc_free(file->base);
286 memset(file, 'F', sizeof(*file));
291 /* initializes a SilcConfigEntity pointer allocation */
293 SilcConfigEntity silc_config_init(SilcConfigFile *file)
295 SilcConfigEntity ret;
300 SILC_CONFIG_DEBUG(("Allocating new config entity"));
301 ret = silc_calloc(1, sizeof(*ret));
306 /* Returns the original filename of the object file */
308 char *silc_config_get_filename(SilcConfigFile *file)
311 return file->filename;
315 /* Returns the current line that file parsing arrived at */
317 uint32 silc_config_get_line(SilcConfigFile *file)
324 /* Returns a pointer to the beginning of the requested line. If the line
325 * was not found, NULL is returned */
327 char *silc_config_read_line(SilcConfigFile *file, uint32 line)
333 if (!file || (line <= 0))
335 for (p = file->base; *p && (*p != EOF); p++) {
344 if ((endbuf = strchr(p, '\n'))) {
346 ret = silc_calloc(len, sizeof(*ret));
347 strncpy(ret, p, len);
351 ret = silc_calloc(strlen(p), sizeof(*ret));
357 /* Convenience function to read the current parsed line */
359 char *silc_config_read_current_line(SilcConfigFile *file)
361 return silc_config_read_line(file, file->line);
364 /* (Private) destroy a SilcConfigEntity */
366 static void silc_config_destroy(SilcConfigEntity ent)
368 SilcConfigOption *oldopt, *nextopt;
369 SILC_CONFIG_DEBUG(("Freeing config entity [ent=0x%x] [opts=0x%x]",
370 (uint32) ent, (uint32) ent->opts));
371 for (oldopt = ent->opts; oldopt; oldopt = nextopt) {
372 nextopt = oldopt->next;
373 memset(oldopt->name, 'F', strlen(oldopt->name) + 1);
374 silc_free(oldopt->name);
375 memset(oldopt, 'F', sizeof(*oldopt));
378 memset(ent, 'F', sizeof(*ent));
382 /* Registers a new option in the specified entity.
383 * Returns TRUE on success, FALSE if already registered. */
385 bool silc_config_register(SilcConfigEntity ent, const char *name,
386 SilcConfigType type, SilcConfigCallback cb,
387 const SilcConfigTable *subtable, void *context)
389 SilcConfigOption *newopt;
390 SILC_CONFIG_DEBUG(("Register new option=\"%s\" type=%u cb=0x%08x context=0x%08x",
391 name, type, (uint32) cb, (uint32) context));
393 /* if we are registering a block, make sure there is a specified sub-table */
394 if (!ent || !name || ((type == SILC_CONFIG_ARG_BLOCK) && !subtable))
397 /* don't register a reserved tag */
398 if (!strcasecmp(name, "include"))
401 /* check if an option was previously registered */
402 if (silc_config_find_option(ent, name)) {
403 SILC_LOG_DEBUG(("Error: Can't register \"%s\" twice.", name));
407 /* allocate and append the new option */
408 newopt = silc_calloc(1, sizeof(*newopt));
409 newopt->name = strdup(name);
412 newopt->subtable = subtable;
413 newopt->context = context;
415 /* append this option to the list */
419 SilcConfigOption *tmp;
420 for (tmp = ent->opts; tmp->next; tmp = tmp->next);
426 /* Register a new option table in the specified config entity */
428 bool silc_config_register_table(SilcConfigEntity ent,
429 const SilcConfigTable table[], void *context)
434 SILC_CONFIG_DEBUG(("Registering table"));
435 /* XXX FIXME: some potability checks needed - really? */
436 for (i = 0; table[i].name; i++) {
437 if (!silc_config_register(ent, table[i].name, table[i].type,
438 table[i].callback, table[i].subtable, context))
446 static int silc_config_main_internal(SilcConfigEntity ent)
448 SilcConfigFile *file = ent->file;
451 /* loop throught statements */
454 SilcConfigOption *thisopt;
456 /* makes it pointing to the next interesting char */
457 my_skip_comments(file);
459 if (**p == '\0' || **p == EOF) {
460 if (file->level > 1) /* cannot get eof in a sub-level! */
461 return SILC_CONFIG_EEXPECTED;
464 /* check if we completed this (sub) section (it doesn't matter if this
465 * is the main section) */
467 if (file->level < 2) /* can't be! must be at least one sub-block */
468 return SILC_CONFIG_EUNEXPECTED;
472 //SILC_LOG_HEXDUMP(("Preparing lookup at line=%lu", file->line), *p, 16);
474 /* obtain the keyword */
475 my_next_token(file, buf);
476 SILC_CONFIG_DEBUG(("Looking up keyword=\"%s\" [line=%lu]", buf, file->line));
478 /* handle special directive */
479 if (!strcasecmp(buf, "include")) {
481 SilcConfigFile *inc_file;
482 SilcConfigEntity inc_ent;
484 my_trim_spaces(file); /* prepare next char */
486 /* Now trying to include the specified file. The included file will
487 * be allowed to include sub-files but it will preserve the block-level
488 * of the including block. Note that the included file won't be allowed
489 * to raise the block level of the including block. */
491 my_get_string(file, buf); /* get the filename */
492 SILC_LOG_DEBUG(("Including file \"%s\"", buf));
493 /* before getting on, check if this row is REALLY complete */
495 return SILC_CONFIG_EMISSCOLON;
497 /* open the file and start the parsing */
498 inc_file = silc_config_open(buf);
499 if (!inc_file) /* does it point a valid filename? */
500 return SILC_CONFIG_ECANTOPEN;
501 inc_file->included = TRUE;
503 /* create a new entity and hack it to use the same options */
504 inc_ent = silc_config_init(inc_file);
505 inc_ent->opts = ent->opts;
506 ret = silc_config_main(inc_ent);
509 * If the included file returned an error, the application will probably
510 * want to output some kind of error message. Because of this, we can't
511 * destroy THIS file object. The hack works this way: The application
512 * expects to destroy the originally created object file, so we'll swap
513 * the original file with the included file. */
515 SilcConfigFile tmp_file;
516 SILC_CONFIG_DEBUG(("SWAPPING FILE OBJECTS"));
517 memcpy(&tmp_file, inc_file, sizeof(tmp_file));
518 memcpy(inc_file, file, sizeof(tmp_file));
519 silc_config_close(inc_file);
520 memcpy(file, &tmp_file, sizeof(tmp_file));
523 /* otherwise if no errors encoured, continue normally */
524 silc_config_close(inc_file);
525 continue; /* this one is handled */
528 /* we have a registered option (it can also be a sub-block) */
529 thisopt = silc_config_find_option(ent, buf);
531 return SILC_CONFIG_EBADOPTION;
533 my_trim_spaces(file); /* prepare next char */
535 /* option type is a block? */
536 if (thisopt->type == SILC_CONFIG_ARG_BLOCK) {
538 SilcConfigEntity sub_ent;
540 SILC_CONFIG_DEBUG(("Entering sub-block"));
542 return SILC_CONFIG_EOPENBRACE;
543 /* build the new entity for this sub-block */
544 sub_ent = silc_config_init(ent->file);
545 /* use the previous specified table to describe this block's options */
546 silc_config_register_table(sub_ent, thisopt->subtable, thisopt->context);
547 /* run this block! */
548 ret = silc_config_main(sub_ent);
549 SILC_CONFIG_DEBUG(("Returned from sub-block [ret=%d]", ret));
551 if (ret) /* now check the result */
554 /* now call block clean-up callback (if any) */
557 SILC_CONFIG_DEBUG(("Now calling clean-up callback"));
558 ret = thisopt->cb(thisopt->type, thisopt->name, file->line, NULL,
561 SILC_CONFIG_DEBUG(("Callback refused the value [ret=%d]", ret));
565 /* Do we want ';' to be mandatory after close brace? */
567 return SILC_CONFIG_EMISSCOLON;
569 else if (thisopt->type == SILC_CONFIG_ARG_NONE) {
570 /* before getting on, check if this row is REALLY complete */
572 return SILC_CONFIG_EMISSCOLON;
573 SILC_CONFIG_DEBUG(("Triggering callback for none"));
575 thisopt->cb(thisopt->type, thisopt->name, file->line,
576 NULL, thisopt->context);
584 return SILC_CONFIG_EEXPECTEDEQUAL;
586 my_get_string(file, buf); /* get the option argument */
587 SILC_CONFIG_DEBUG(("With argument=\"%s\"", buf));
589 /* before getting on, check if this row is REALLY complete */
591 return SILC_CONFIG_EMISSCOLON;
593 /* convert the option argument to the right format */
594 pt = silc_config_marshall(thisopt->type, buf);
596 return SILC_CONFIG_EINVALIDTEXT;
598 ret = thisopt->cb(thisopt->type, thisopt->name, file->line,
599 pt, thisopt->context);
601 SILC_CONFIG_DEBUG(("Callback refused the value [ret=%d]", ret));
613 return SILC_CONFIG_OK;
618 int silc_config_main(SilcConfigEntity ent)
620 SilcConfigFile *file = ent->file;
623 /* don't silently accept a NULL entity */
625 ret = SILC_CONFIG_EGENERIC;
629 /* call the real main and store the result */
631 SILC_CONFIG_DEBUG(("[Lev=%d] Entering config parsing core", file->level));
632 ret = silc_config_main_internal(ent);
633 SILC_CONFIG_DEBUG(("[Lev=%d] Quitting main [ret=%d]", file->level, ret));
634 if (!file->level) /* when swap happens, we could close a file twice */
638 /* If this file was included don't destroy the options set because it is
639 * the same of the including block. Although if this entity is in a
640 * sub-block created inside the included file, this options set must be
643 if ((file->level != 0) || (file->included != TRUE))
644 silc_config_destroy(ent);