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