Created SILC Runtime Toolkit git repository Part II.
[runtime.git] / lib / silcutil / silcconfig.c
1 /*
2
3   silcconfig.c
4
5   Author: Giovanni Giacobbi <giovanni@giacobbi.net>
6
7   Copyright (C) 2002 - 2008 Giovanni Giacobbi
8
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.
12
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.
17
18 */
19
20 #include "silcruntime.h"
21
22 /* limit debug logging verbosity */
23 #if 0
24 #define SILC_CONFIG_DEBUG(fmt) SILC_LOG_DEBUG(fmt)
25 #else
26 #define SILC_CONFIG_DEBUG(fmt)
27 #endif
28
29 /* this is the option struct and currently it is only used internally to
30  * the module and other structs. */
31 typedef struct SilcConfigOptionStruct {
32   char *name;                   /* *lowercase* name of the option */
33   SilcConfigType type;          /* important: the type of the returned value */
34   SilcConfigCallback cb;        /* the value handler */
35   const SilcConfigTable *subtable; /* used if type is SILC_CONFIG_ARG_BLOCK */
36   void *context;                /* context passed to the callback function */
37   struct SilcConfigOptionStruct *next;
38 } SilcConfigOption;
39
40 /* unique for each config file (and included ones) */
41 struct SilcConfigFileObject {
42   char *filename;       /* the original filename opened */
43   int level;            /* parsing level, how many nested
44                            silc_config_main we have */
45   char *base;           /* this is a fixed pointer to the base location */
46   char *p;              /* the Parser poitner */
47   SilcUInt32 len;       /* fixed length of the whole file */
48   SilcUInt32 line;      /* current parsing line, strictly linked to p */
49   SilcBool included;    /* wether this file is main or included */
50 };
51
52 /* We need the entity to base our block-style parsing on */
53 struct SilcConfigEntityObject {
54   SilcConfigOption *opts;       /* known options list */
55   SilcConfigFile *file;         /* parsing file object */
56 };
57
58 /* access error descriptions only with silc_config_strerror() */
59 static char *errorstrs[] = {
60   "-OK",                                      /* SILC_CONFIG_OK */
61   "-SILENT",                                  /* SILC_CONFIG_ESILENT */
62   "-PRINTLINE",                               /* SILC_CONFIG_EPRINTLINE */
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 */
77 };
78
79 /* return string describing SilcConfig's error code */
80 char *silc_config_strerror(int errnum)
81 {
82   if ((errnum < 0) || (errnum >= sizeof(errorstrs)/sizeof(*errorstrs)) ||
83     (errorstrs[errnum] == NULL)) {
84     char *defret = "-INVALIDERROR";
85     return defret;
86   }
87   return errorstrs[errnum];
88 }
89
90 /* Begin of internal SilcConfig's text util functions */
91
92 /* Points the first non-space character */
93 static void my_trim_spaces(SilcConfigFile *file)
94 {
95   register char *r = file->p;
96   while ((*r != '\0' && *r != EOF) && isspace((int)*r))
97     if (*r++ == '\n') file->line++;
98   file->p = r;
99 }
100
101 /* Skips the current line until newline (lf or cr) */
102 static void my_skip_line(SilcConfigFile *file)
103 {
104   register char *r = file->p;
105   while ((*r != '\0' && *r != EOF) && (*r != '\n') && (*r != '\r')) r++;
106   file->p = ((*r != '\0' && *r != EOF) ? r + 1 : r);
107   file->line++;
108 }
109
110 /* Obtains a text token from the current position until first separator.
111  * a separator is any non alphanumeric character nor "_" or "-" */
112 static char *my_next_token(SilcConfigFile *file, char *to)
113 {
114   register char *o;
115   my_trim_spaces(file);
116   o = file->p;
117   while (isalnum((int)*o) || (*o == '_') || (*o == '-'))
118     *to++ = *o++;
119   *to = '\0';
120   file->p = o;
121   return to;
122 }
123
124 /* Obtains a string from the current position. The only difference from
125  * next_token() is that quoted-strings are also accepted */
126 static char *my_get_string(SilcConfigFile *file, char *to)
127 {
128   char *o;
129   my_trim_spaces(file);
130   o = file->p;
131   if (*o == '"') {
132     char *quot = strchr(++o, '"');
133     int len = quot - o;
134     if (!quot) { /* XXX FIXME: gotta do something here */
135       printf("Bullshit, missing matching \"");
136       exit(1);
137     }
138     if (len <= 0)
139       *to = '\0';
140     else {
141       strncpy(to, o, len);
142       to[len] = '\0';
143     }
144     /* update stream pointer */
145     file->p = quot + 1;
146     return to;
147   }
148   /* we don't need quote parsing, fall-back to token extractor */
149   my_next_token(file, to);
150   return to;
151 }
152
153 /* Skips all comment lines and spaces lines until first useful character */
154 static void my_skip_comments(SilcConfigFile *file)
155 {
156   while (1) {
157     my_trim_spaces(file);
158     if (*file->p != '#') return;
159     my_skip_line(file);
160   }
161 }
162
163 /* End of internal text functions
164  * Next section contains SilcConfig internal config utils */
165
166 /* find an option in the list by name and returns its pointer */
167 static SilcConfigOption *silc_config_find_option(SilcConfigEntity ent,
168         const char *name)
169 {
170   SilcConfigOption *tmp;
171   for (tmp = ent->opts; tmp; tmp = tmp->next) {
172     if (!strcasecmp(tmp->name, name))
173       return tmp;
174   }
175   return NULL;
176 }
177
178 /* Converts a string in the type specified. returns a dynamically
179  * allocated pointer. */
180 static void *silc_config_marshall(SilcConfigType type, const char *val)
181 {
182   void *pt;
183   int val_int;
184   SilcBool val_boolean;
185   char *val_tmp;
186   SilcUInt32 val_size;
187
188   switch (type) {
189     case SILC_CONFIG_ARG_TOGGLE:
190       if (!strcasecmp(val, "yes") || !strcasecmp(val, "true") ||
191                 !strcasecmp(val, "on") || !strcasecmp(val, "1")) {
192         val_boolean = TRUE;
193       }
194       else if (!strcasecmp(val, "no") || !strcasecmp(val, "false") ||
195                 !strcasecmp(val, "off") || !strcasecmp(val, "0")) {
196         val_boolean = FALSE;
197       }
198       else
199         return NULL;
200       pt = silc_calloc(1, sizeof(val_boolean));
201       *(SilcBool *)pt = (SilcBool) val_boolean;
202       return pt;
203     case SILC_CONFIG_ARG_INT:
204       val_int = (int) strtol(val, &val_tmp, 0);
205       if (*val_tmp) /* error converting string */
206         return NULL;
207       pt = silc_calloc(1, sizeof(val_int));
208       *(int *)pt = val_int;
209       return pt;
210     case SILC_CONFIG_ARG_SIZE:
211       val_size = (SilcUInt32) strtol(val, &val_tmp, 0);
212       if (val == val_tmp)
213         return NULL; /* really wrong, there must be at least one digit */
214       /* Search for a designator */
215       switch (tolower((int)val_tmp[0])) {
216         case '\0': /* None */
217           break;
218         case 'k': /* Kilobytes */
219           val_size *= (SilcUInt32) 1024;
220           break;
221         case 'm': /* Megabytes */
222           val_size *= (SilcUInt32) (1024 * 1024);
223           break;
224         case 'g':
225           val_size *= (SilcUInt32) (1024 * 1024 * 1024);
226           break;
227         default:
228           return NULL;
229       }
230       /* the string must die here */
231       if (val_tmp[1])
232         return NULL;
233       pt = silc_calloc(1, sizeof(val_size));
234       *(SilcUInt32 *)pt = val_size;
235       return pt;
236     case SILC_CONFIG_ARG_STR: /* the only difference between STR and STRE is */
237       if (!val[0])            /* that STR cannot be empty, while STRE can.  */
238         return NULL;
239     case SILC_CONFIG_ARG_STRE:
240       pt = (void *) strdup(val);
241       return pt;
242     /* following types are not supposed to have a return value */
243     case SILC_CONFIG_ARG_BLOCK:
244     case SILC_CONFIG_ARG_NONE:
245       return NULL;
246     default:
247       return NULL;
248   }
249
250   return NULL;
251 }
252
253 /* End of internal functions */
254
255
256 /* Tries to open the config file and returns a valid SilcConfigFile object
257  * or NULL if failed */
258
259 SilcConfigFile *silc_config_open(const char *configfile)
260 {
261   char *buffer;
262   SilcUInt32 filelen;
263   SilcConfigFile *ret;
264
265   if (!(buffer = silc_file_readfile(configfile, &filelen, NULL)))
266     return NULL;
267
268   ret = silc_calloc(1, sizeof(*ret));
269   ret->filename = strdup(configfile);
270   ret->base = ret->p = buffer;
271   ret->len = filelen;
272   ret->line = 1; /* line count, start from first line */
273   return ret;
274 }
275
276 /* Frees a file object */
277
278 void silc_config_close(SilcConfigFile *file)
279 {
280   if (file) {
281     silc_free(file->filename);
282     memset(file->base, 'F', file->len);
283     silc_free(file->base);
284     memset(file, 'F', sizeof(*file));
285     silc_free(file);
286   }
287 }
288
289 /* initializes a SilcConfigEntity pointer allocation */
290
291 SilcConfigEntity silc_config_init(SilcConfigFile *file)
292 {
293   SilcConfigEntity ret;
294
295   if (!file)
296     return NULL;
297
298   SILC_CONFIG_DEBUG(("Allocating new config entity"));
299   ret = silc_calloc(1, sizeof(*ret));
300   ret->file = file;
301   return ret;
302 }
303
304 /* Returns the original filename of the object file */
305
306 char *silc_config_get_filename(SilcConfigFile *file)
307 {
308   if (file)
309     return file->filename;
310   return NULL;
311 }
312
313 /* Returns the current line that file parsing arrived at */
314
315 SilcUInt32 silc_config_get_line(SilcConfigFile *file)
316 {
317   if (file)
318     return file->line;
319   return 0;
320 }
321
322 /* Returns a pointer to the beginning of the requested line.  If the line
323  * was not found, NULL is returned */
324
325 char *silc_config_read_line(SilcConfigFile *file, SilcUInt32 line)
326 {
327   register char *p;
328   int len;
329   char *ret = NULL, *endbuf;
330
331   if (!file || (line <= 0))
332     return NULL;
333   for (p = file->base; *p && (*p != EOF); p++) {
334     if (line <= 1)
335       goto found;
336     if (*p == '\n')
337       line--;
338   }
339   return NULL;
340
341  found:
342   if ((endbuf = strchr(p, '\n'))) {
343     len = endbuf - p;
344     if (len > 0)
345       ret = silc_memdup(p, len);
346   } else {
347     ret = silc_memdup(p, strlen(p));
348   }
349   return ret;
350 }
351
352 /* Convenience function to read the current parsed line */
353
354 char *silc_config_read_current_line(SilcConfigFile *file)
355 {
356   return silc_config_read_line(file, file->line);
357 }
358
359 /* (Private) destroy a SilcConfigEntity */
360
361 static void silc_config_destroy(SilcConfigEntity ent, SilcBool destroy_opts)
362 {
363   SilcConfigOption *oldopt, *nextopt;
364   SILC_CONFIG_DEBUG(("Freeing config entity [ent=0x%x] [opts=0x%x]",
365                         (SilcUInt32) ent, (SilcUInt32) ent->opts));
366
367   /* if she wants to preserve options just free the object struct */
368   if (!destroy_opts)
369     goto skip_sect;
370
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));
376     silc_free(oldopt);
377   }
378
379  skip_sect:
380   memset(ent, 'F', sizeof(*ent));
381   silc_free(ent);
382 }
383
384 /* Registers a new option in the specified entity.
385  * Returns TRUE on success, FALSE if already registered. */
386
387 SilcBool silc_config_register(SilcConfigEntity ent, const char *name,
388                           SilcConfigType type, SilcConfigCallback cb,
389                           const SilcConfigTable *subtable, void *context)
390 {
391   SilcConfigOption *newopt;
392   SILC_CONFIG_DEBUG(("Register new option=\"%s\" "
393                      "type=%u cb=0x%08x context=0x%08x",
394                      name, type, (SilcUInt32) cb, (SilcUInt32) context));
395
396   /* if we are registering a block, make sure there is a specified sub-table */
397   if (!ent || !name || ((type == SILC_CONFIG_ARG_BLOCK) && !subtable))
398     return FALSE;
399
400   /* don't register a reserved tag */
401   if (!strcasecmp(name, "include"))
402     return FALSE;
403
404   /* check if an option was previously registered */
405   if (silc_config_find_option(ent, name)) {
406     SILC_LOG_DEBUG(("Error: Can't register \"%s\" twice.", name));
407     return FALSE;
408   }
409
410   /* allocate and append the new option */
411   newopt = silc_calloc(1, sizeof(*newopt));
412   newopt->name = strdup(name);
413   newopt->type = type;
414   newopt->cb = cb;
415   newopt->subtable = subtable;
416   newopt->context = context;
417
418   /* append this option to the list */
419   if (!ent->opts)
420     ent->opts = newopt;
421   else {
422     SilcConfigOption *tmp;
423     for (tmp = ent->opts; tmp->next; tmp = tmp->next);
424     tmp->next = newopt;
425   }
426   return TRUE;
427 }
428
429 /* Register a new option table in the specified config entity */
430
431 SilcBool silc_config_register_table(SilcConfigEntity ent,
432                                 const SilcConfigTable table[], void *context)
433 {
434   int i;
435   if (!ent || !table)
436     return FALSE;
437   SILC_CONFIG_DEBUG(("Registering table"));
438   /* XXX FIXME: some potability checks needed - really? */
439   for (i = 0; table[i].name; i++) {
440     if (!silc_config_register(ent, table[i].name, table[i].type,
441                               table[i].callback, table[i].subtable, context))
442       return FALSE;
443   }
444   return TRUE;
445 }
446
447 /* ... */
448
449 static int silc_config_main_internal(SilcConfigEntity ent)
450 {
451   SilcConfigFile *file = ent->file;
452   char **p = &file->p;
453
454   /* loop throught statements */
455   while (1) {
456     char buf[255];
457     SilcConfigOption *thisopt;
458
459     /* makes it pointing to the next interesting char */
460     my_skip_comments(file);
461     /* got eof? */
462     if (**p == '\0' || **p == EOF) {
463       if (file->level > 1) /* cannot get eof in a sub-level! */
464         return SILC_CONFIG_EEXPECTED;
465       goto finish;
466     }
467     /* check if we completed this (sub) section (it doesn't matter if this
468      * is the main section) */
469     if (**p == '}') {
470       if (file->level < 2) /* can't be! must be at least one sub-block */
471         return SILC_CONFIG_EUNEXPECTED;
472       (*p)++;
473       goto finish;
474     }
475     //SILC_LOG_HEXDUMP(("Preparing lookup at line=%lu", file->line), *p, 16);
476
477     /* obtain the keyword */
478     my_next_token(file, buf);
479     SILC_CONFIG_DEBUG(("Looking up keyword=\"%s\" [line=%lu]",
480                        buf, file->line));
481
482     /* handle special directive */
483     if (!strcasecmp(buf, "include")) {
484       int ret;
485       SilcConfigFile *inc_file;
486       SilcConfigEntity inc_ent;
487
488       my_trim_spaces(file); /* prepare next char */
489
490       /* Now trying to include the specified file.  The included file will
491        * be allowed to include sub-files but it will preserve the block-level
492        * of the including block. Note that the included file won't be allowed
493        * to raise the block level of the including block. */
494
495       my_get_string(file, buf); /* get the filename */
496       SILC_LOG_DEBUG(("Including file \"%s\"", buf));
497       /* before getting on, check if this row is REALLY complete */
498       if (*(*p)++ != ';')
499         return SILC_CONFIG_EMISSCOLON;
500
501       /* open the file and start the parsing */
502       inc_file = silc_config_open(buf);
503       if (!inc_file) /* does it point a valid filename? */
504         return SILC_CONFIG_ECANTOPEN;
505       inc_file->included = TRUE;
506
507       /* create a new entity and hack it to use the same options */
508       inc_ent = silc_config_init(inc_file);
509       inc_ent->opts = ent->opts;
510       ret = silc_config_main(inc_ent);
511
512       /* Cleanup.
513        * If the included file returned an error, the application will probably
514        * want to output some kind of error message. Because of this, we can't
515        * destroy THIS file object. The hack works this way: The application
516        * expects to destroy the originally created object file, so we'll swap
517        * the original file with the included file. */
518       if (ret) {
519         SilcConfigFile tmp_file;
520         SILC_CONFIG_DEBUG(("SWAPPING FILE OBJECTS"));
521         memcpy(&tmp_file, inc_file, sizeof(tmp_file));
522         memcpy(inc_file, file, sizeof(tmp_file));
523         silc_config_close(inc_file);
524         memcpy(file, &tmp_file, sizeof(tmp_file));
525         return ret;
526       }
527       /* otherwise if no errors encoured, continue normally */
528       silc_config_close(inc_file);
529       continue; /* this one is handled */
530     }
531
532     /* we have a registered option (it can also be a sub-block) */
533     thisopt = silc_config_find_option(ent, buf);
534     if (!thisopt)
535       return SILC_CONFIG_EBADOPTION;
536
537     my_trim_spaces(file); /* prepare next char */
538
539     /* option type is a block? */
540     if (thisopt->type == SILC_CONFIG_ARG_BLOCK) {
541       int ret;
542       SilcConfigEntity sub_ent;
543
544       SILC_CONFIG_DEBUG(("Entering sub-block"));
545       if (*(*p)++ != '{')
546         return SILC_CONFIG_EOPENBRACE;
547       /* build the new entity for this sub-block */
548       sub_ent = silc_config_init(ent->file);
549       /* use the previous specified table to describe this block's options */
550       silc_config_register_table(sub_ent, thisopt->subtable, thisopt->context);
551       /* run this block! */
552       ret = silc_config_main(sub_ent);
553       SILC_CONFIG_DEBUG(("Returned from sub-block [ret=%d]", ret));
554
555       if (ret) /* now check the result */
556         return ret;
557
558       /* now call block clean-up callback (if any) */
559       if (thisopt->cb) {
560         int ret;
561         SILC_CONFIG_DEBUG(("Now calling clean-up callback"));
562         ret = thisopt->cb(thisopt->type, thisopt->name, file->line, NULL,
563                           thisopt->context);
564         if (ret) {
565           SILC_CONFIG_DEBUG(("Callback refused the value [ret=%d]", ret));
566           return ret;
567         }
568       }
569       /* Do we want ';' to be mandatory after close brace? */
570       if (*(*p)++ != ';')
571         return SILC_CONFIG_EMISSCOLON;
572     }
573     else if (thisopt->type == SILC_CONFIG_ARG_NONE) {
574       /* before getting on, check if this row is REALLY complete */
575       if (*(*p)++ != ';')
576         return SILC_CONFIG_EMISSCOLON;
577       SILC_CONFIG_DEBUG(("Triggering callback for none"));
578       if (thisopt->cb) {
579         thisopt->cb(thisopt->type, thisopt->name, file->line,
580                     NULL, thisopt->context);
581       }
582     }
583     else {
584       void *pt;
585       int ret = 0;      /* very important in case of no cb */
586
587       if (*(*p)++ != '=')
588         return SILC_CONFIG_EEXPECTEDEQUAL;
589
590       my_get_string(file, buf); /* get the option argument */
591       SILC_CONFIG_DEBUG(("With argument=\"%s\"", buf));
592
593       /* before getting on, check if this row is REALLY complete */
594       if (*(*p)++ != ';')
595         return SILC_CONFIG_EMISSCOLON;
596
597       /* convert the option argument to the right format */
598       pt = silc_config_marshall(thisopt->type, buf);
599       if (!pt)
600         return SILC_CONFIG_EINVALIDTEXT;
601       if (thisopt->cb)
602         ret = thisopt->cb(thisopt->type, thisopt->name, file->line,
603                           pt, thisopt->context);
604
605       /* since we have to free "pt" both on failure and on success, we
606          assume that ret == 0 if we didn't actually call any cb. */
607       silc_free(pt);
608       if (ret) {
609         SILC_CONFIG_DEBUG(("Callback refused the value [ret=%d]", ret));
610         return ret;
611       }
612     }
613     continue;
614
615  finish:
616     break;
617   }
618
619   return SILC_CONFIG_OK;
620 }
621
622 /* ... */
623
624 int silc_config_main(SilcConfigEntity ent)
625 {
626   SilcConfigFile *file = ent->file;
627   int ret;
628
629   /* don't silently accept a NULL entity */
630   if (!ent) {
631     ret = SILC_CONFIG_EGENERIC;
632     goto main_cleanup;
633   }
634
635   /* call the real main and store the result */
636   file->level++;
637   SILC_CONFIG_DEBUG(("[Lev=%d] Entering config parsing core", file->level));
638   ret = silc_config_main_internal(ent);
639   SILC_CONFIG_DEBUG(("[Lev=%d] Quitting main [ret=%d]", file->level, ret));
640   if (!file->level) /* when swap happens, we could close a file twice */
641     goto main_end;
642   file->level--;
643
644   /* If this file was included don't destroy the options set because it is
645    * the same of the including block. Although if this entity is in a
646    * sub-block created inside the included file, this options set must be
647    * destroyed. */
648  main_cleanup:
649   if ((file->level != 0) || (file->included != TRUE))
650     silc_config_destroy(ent, TRUE);
651   else
652     silc_config_destroy(ent, FALSE);
653
654  main_end:
655   return ret;
656 }