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