]> arthur.barton.de Git - netatalk.git/blob - libatalk/iniparser/iniparser.c
Twisted loglevel and logtype
[netatalk.git] / libatalk / iniparser / iniparser.c
1 /*-------------------------------------------------------------------------*/
2 /**
3    @file    iniparser.c
4    @author  N. Devillard
5    @date    Sep 2007
6    @version 3.0
7    @brief   Parser for ini files.
8 */
9
10 /*---------------------------- Includes ------------------------------------*/
11 #include <ctype.h>
12
13 #include <atalk/iniparser.h>
14 #include <atalk/logger.h>
15
16 /*---------------------------- Defines -------------------------------------*/
17 #define ASCIILINESZ         (1024)
18 #define INI_INVALID_KEY     ((char*)-1)
19
20 /*---------------------------------------------------------------------------
21                         Private to this module
22  ---------------------------------------------------------------------------*/
23 /**
24  * This enum stores the status for each parsed line (internal use only).
25  */
26 typedef enum _line_status_ {
27     LINE_UNPROCESSED,
28     LINE_ERROR,
29     LINE_EMPTY,
30     LINE_COMMENT,
31     LINE_SECTION,
32     LINE_VALUE
33 } line_status ;
34
35 /*-------------------------------------------------------------------------*/
36 /**
37   @brief    Remove blanks at the beginning and the end of a string.
38   @param    s   String to parse.
39   @return   ptr to statically allocated string.
40
41   This function returns a pointer to a statically allocated string,
42   which is identical to the input string, except that all blank
43   characters at the end and the beg. of the string have been removed.
44   Do not free or modify the returned string! Since the returned string
45   is statically allocated, it will be modified at each function call
46   (not re-entrant).
47  */
48 /*--------------------------------------------------------------------------*/
49 static char * strstrip(char * s)
50 {
51     static char l[ASCIILINESZ+1];
52     char * last ;
53
54     if (s==NULL) return NULL ;
55
56     while (isspace((int)*s) && *s) s++;
57     memset(l, 0, ASCIILINESZ+1);
58     strcpy(l, s);
59     last = l + strlen(l);
60     while (last > l) {
61         if (!isspace((int)*(last-1)))
62             break ;
63         last -- ;
64     }
65     *last = (char)0;
66     return (char*)l ;
67 }
68
69 /*-------------------------------------------------------------------------*/
70 /**
71   @brief    Get number of sections in a dictionary
72   @param    d   Dictionary to examine
73   @return   int Number of sections found in dictionary
74
75   This function returns the number of sections found in a dictionary.
76   The test to recognize sections is done on the string stored in the
77   dictionary: a section name is given as "section" whereas a key is
78   stored as "section:key", thus the test looks for entries that do not
79   contain a colon.
80
81   This clearly fails in the case a section name contains a colon, but
82   this should simply be avoided.
83
84   This function returns -1 in case of error.
85  */
86 /*--------------------------------------------------------------------------*/
87 int iniparser_getnsec(const dictionary * d)
88 {
89     int i ;
90     int nsec ;
91
92     if (d==NULL) return -1 ;
93     nsec=0 ;
94     for (i=0 ; i<d->size ; i++) {
95         if (d->key[i]==NULL)
96             continue ;
97         if (strchr(d->key[i], ':')==NULL) {
98             nsec ++ ;
99         }
100     }
101     return nsec ;
102 }
103
104 /*-------------------------------------------------------------------------*/
105 /**
106   @brief    Get name for section n in a dictionary.
107   @param    d   Dictionary to examine
108   @param    n   Section number (from 0 to nsec-1).
109   @return   Pointer to char string
110
111   This function locates the n-th section in a dictionary and returns
112   its name as a pointer to a string statically allocated inside the
113   dictionary. Do not free or modify the returned string!
114
115   This function returns NULL in case of error.
116  */
117 /*--------------------------------------------------------------------------*/
118 const char * iniparser_getsecname(const dictionary * d, int n)
119 {
120     int i ;
121     int foundsec ;
122
123     if (d==NULL || n<0) return NULL ;
124     foundsec=0 ;
125     for (i=0 ; i<d->size ; i++) {
126         if (d->key[i]==NULL)
127             continue ;
128         if (strchr(d->key[i], ':')==NULL) {
129             foundsec++ ;
130             if (foundsec>n)
131                 break ;
132         }
133     }
134     if (foundsec<=n) {
135         return NULL ;
136     }
137     return d->key[i] ;
138 }
139
140 /*-------------------------------------------------------------------------*/
141 /**
142   @brief    Dump a dictionary to an opened file pointer.
143   @param    d   Dictionary to dump.
144   @param    f   Opened file pointer to dump to.
145   @return   void
146
147   This function prints out the contents of a dictionary, one element by
148   line, onto the provided file pointer. It is OK to specify @c stderr
149   or @c stdout as output files. This function is meant for debugging
150   purposes mostly.
151  */
152 /*--------------------------------------------------------------------------*/
153 void iniparser_dump(const dictionary * d, FILE * f)
154 {
155     int     i ;
156
157     if (d==NULL || f==NULL) return ;
158     for (i=0 ; i<d->size ; i++) {
159         if (d->key[i]==NULL)
160             continue ;
161         if (d->val[i]!=NULL) {
162             fprintf(f, "[%s]=[%s]\n", d->key[i], d->val[i]);
163         } else {
164             fprintf(f, "[%s]=UNDEF\n", d->key[i]);
165         }
166     }
167     return ;
168 }
169
170 /*-------------------------------------------------------------------------*/
171 /**
172   @brief    Save a dictionary to a loadable ini file
173   @param    d   Dictionary to dump
174   @param    f   Opened file pointer to dump to
175   @return   void
176
177   This function dumps a given dictionary into a loadable ini file.
178   It is Ok to specify @c stderr or @c stdout as output files.
179  */
180 /*--------------------------------------------------------------------------*/
181 void iniparser_dump_ini(const dictionary * d, FILE * f)
182 {
183     int     i, j ;
184     char    keym[ASCIILINESZ+1];
185     int     nsec ;
186     const char *  secname ;
187     int     seclen ;
188
189     if (d==NULL || f==NULL) return ;
190
191     nsec = iniparser_getnsec(d);
192     if (nsec<1) {
193         /* No section in file: dump all keys as they are */
194         for (i=0 ; i<d->size ; i++) {
195             if (d->key[i]==NULL)
196                 continue ;
197             fprintf(f, "%s = %s\n", d->key[i], d->val[i]);
198         }
199         return ;
200     }
201     for (i=0 ; i<nsec ; i++) {
202         secname = iniparser_getsecname(d, i) ;
203         seclen  = (int)strlen(secname);
204         fprintf(f, "\n[%s]\n", secname);
205         sprintf(keym, "%s:", secname);
206         for (j=0 ; j<d->size ; j++) {
207             if (d->key[j]==NULL)
208                 continue ;
209             if (!strncmp(d->key[j], keym, seclen+1)) {
210                 fprintf(f,
211                         "%-30s = %s\n",
212                         d->key[j]+seclen+1,
213                         d->val[j] ? d->val[j] : "");
214             }
215         }
216     }
217     fprintf(f, "\n");
218     return ;
219 }
220
221 /*-------------------------------------------------------------------------*/
222 /**
223   @brief    Get the string associated to a key
224   @param    d       Dictionary to search
225   @param    section Section to search
226   @param    key     Key string to look for
227   @param    def     Default value to return if key not found.
228   @return   pointer to statically allocated character string
229
230   This function queries a dictionary for a key. A key as read from an
231   ini file is given as "section:key". If the key cannot be found,
232   the pointer passed as 'def' is returned.
233   The returned char pointer is pointing to a string allocated in
234   the dictionary, do not free or modify it.
235  */
236 /*--------------------------------------------------------------------------*/
237 const char * iniparser_getstring(const dictionary * d, const char *section, const char * key, const char * def)
238 {
239     const char * sval ;
240
241     if (d==NULL || key==NULL)
242         return def ;
243
244     sval = dictionary_get(d, section, key, def);
245     return sval ;
246 }
247
248 /*-------------------------------------------------------------------------*/
249 /**
250   @brief    Get the string associated to a key
251   @param    d       Dictionary to search
252   @param    section Section to search
253   @param    key     Key string to look for
254   @param    def     Default value to return if key not found.
255   @return   pointer to statically allocated character string
256
257   This function queries a dictionary for a key. A key as read from an
258   ini file is given as "section:key". If the key cannot be found,
259   the pointer passed as 'def' is returned.
260   The returned char pointer a strdup'ed allocated string, so the caller must free it.
261  */
262 /*--------------------------------------------------------------------------*/
263 char * iniparser_getstrdup(const dictionary * d, const char *section, const char * key, const char * def)
264 {
265     const char * sval ;
266
267     if (d==NULL || key==NULL)
268         return NULL;
269
270     if ((sval = dictionary_get(d, section, key, def)))
271         return strdup(sval);
272     return NULL;
273 }
274
275 /*-------------------------------------------------------------------------*/
276 /**
277   @brief    Get the string associated to a key, convert to an int
278   @param    d        Dictionary to search
279   @param    section  Section to search
280   @param    key      Key string to look for
281   @param    notfound Value to return in case of error
282   @return   integer
283
284   This function queries a dictionary for a key. A key as read from an
285   ini file is given as "section:key". If the key cannot be found,
286   the notfound value is returned.
287
288   Supported values for integers include the usual C notation
289   so decimal, octal (starting with 0) and hexadecimal (starting with 0x)
290   are supported. Examples:
291
292   "42"      ->  42
293   "042"     ->  34 (octal -> decimal)
294   "0x42"    ->  66 (hexa  -> decimal)
295
296   Warning: the conversion may overflow in various ways. Conversion is
297   totally outsourced to strtol(), see the associated man page for overflow
298   handling.
299
300   Credits: Thanks to A. Becker for suggesting strtol()
301  */
302 /*--------------------------------------------------------------------------*/
303 int iniparser_getint(const dictionary * d, const char *section, const char * key, int notfound)
304 {
305     const char    *   str ;
306
307     str = iniparser_getstring(d, section, key, INI_INVALID_KEY);
308     if (str==INI_INVALID_KEY) return notfound ;
309     return (int)strtol(str, NULL, 0);
310 }
311
312 /*-------------------------------------------------------------------------*/
313 /**
314   @brief    Get the string associated to a key, convert to a double
315   @param    d        Dictionary to search
316   @param    section  Section to search
317   @param    key      Key string to look for
318   @param    notfound Value to return in case of error
319   @return   double
320
321   This function queries a dictionary for a key. A key as read from an
322   ini file is given as "section:key". If the key cannot be found,
323   the notfound value is returned.
324  */
325 /*--------------------------------------------------------------------------*/
326 double iniparser_getdouble(const dictionary * d, const char *section, const char * key, double notfound)
327 {
328     const char    *   str ;
329
330     str = iniparser_getstring(d, section, key, INI_INVALID_KEY);
331     if (str==INI_INVALID_KEY) return notfound ;
332     return atof(str);
333 }
334
335 /*-------------------------------------------------------------------------*/
336 /**
337   @brief    Get the string associated to a key, convert to a boolean
338   @param    d        Dictionary to search
339   @param    section  Section to search
340   @param    key      Key string to look for
341   @param    notfound Value to return in case of error
342   @return   integer
343
344   This function queries a dictionary for a key. A key as read from an
345   ini file is given as "section:key". If the key cannot be found,
346   the notfound value is returned.
347
348   A true boolean is found if one of the following is matched:
349
350   - A string starting with 'y'
351   - A string starting with 'Y'
352   - A string starting with 't'
353   - A string starting with 'T'
354   - A string starting with '1'
355
356   A false boolean is found if one of the following is matched:
357
358   - A string starting with 'n'
359   - A string starting with 'N'
360   - A string starting with 'f'
361   - A string starting with 'F'
362   - A string starting with '0'
363
364   The notfound value returned if no boolean is identified, does not
365   necessarily have to be 0 or 1.
366  */
367 /*--------------------------------------------------------------------------*/
368 int iniparser_getboolean(const dictionary * d, const char *section, const char * key, int notfound)
369 {
370     const char    *   c ;
371     int         ret ;
372
373     c = iniparser_getstring(d, section, key, INI_INVALID_KEY);
374     if (c==INI_INVALID_KEY) return notfound ;
375     if (c[0]=='y' || c[0]=='Y' || c[0]=='1' || c[0]=='t' || c[0]=='T') {
376         ret = 1 ;
377     } else if (c[0]=='n' || c[0]=='N' || c[0]=='0' || c[0]=='f' || c[0]=='F') {
378         ret = 0 ;
379     } else {
380         ret = notfound ;
381     }
382     return ret;
383 }
384
385 /*-------------------------------------------------------------------------*/
386 /**
387   @brief    Finds out if a given entry exists in a dictionary
388   @param    ini     Dictionary to search
389   @param    entry   Name of the entry to look for
390   @return   integer 1 if entry exists, 0 otherwise
391
392   Finds out if a given entry exists in the dictionary. Since sections
393   are stored as keys with NULL associated values, this is the only way
394   of querying for the presence of sections in a dictionary.
395  */
396 /*--------------------------------------------------------------------------*/
397 int iniparser_find_entry(const dictionary *ini, const char *entry)
398 {
399     int found=0 ;
400     if (iniparser_getstring(ini, entry, NULL, INI_INVALID_KEY)!=INI_INVALID_KEY) {
401         found = 1 ;
402     }
403     return found ;
404 }
405
406 /*-------------------------------------------------------------------------*/
407 /**
408   @brief    Set an entry in a dictionary.
409   @param    ini     Dictionary to modify.
410   @param    section Entry to modify (entry section)
411   @param    key     Entry to modify (entry key)
412   @param    val     New value to associate to the entry.
413   @return   int 0 if Ok, -1 otherwise.
414
415   If the given entry can be found in the dictionary, it is modified to
416   contain the provided value. If it cannot be found, -1 is returned.
417   It is Ok to set val to NULL.
418  */
419 /*--------------------------------------------------------------------------*/
420 int iniparser_set(dictionary * ini, char *section, char * key, char * val)
421 {
422     return dictionary_set(ini, section, key, val) ;
423 }
424
425 /*-------------------------------------------------------------------------*/
426 /**
427   @brief    Delete an entry in a dictionary
428   @param    ini     Dictionary to modify
429   @param    section Entry to delete (entry section)
430   @param    key     Entry to delete (entry key)
431   @return   void
432
433   If the given entry can be found, it is deleted from the dictionary.
434  */
435 /*--------------------------------------------------------------------------*/
436 void iniparser_unset(dictionary * ini, char *section, char * key)
437 {
438     dictionary_unset(ini, section, key);
439 }
440
441 /*-------------------------------------------------------------------------*/
442 /**
443   @brief    Load a single line from an INI file
444   @param    input_line  Input line, may be concatenated multi-line input
445   @param    section     Output space to store section
446   @param    key         Output space to store key
447   @param    value       Output space to store value
448   @return   line_status value
449  */
450 /*--------------------------------------------------------------------------*/
451 static line_status iniparser_line(
452     char * input_line,
453     char * section,
454     char * key,
455     char * value)
456 {
457     line_status sta ;
458     char        line[ASCIILINESZ+1];
459     int         len ;
460
461     strcpy(line, strstrip(input_line));
462     len = (int)strlen(line);
463
464     sta = LINE_UNPROCESSED ;
465     if (len<1) {
466         /* Empty line */
467         sta = LINE_EMPTY ;
468     } else if (line[0]=='#' || line[0]==';') {
469         /* Comment line */
470         sta = LINE_COMMENT ;
471     } else if (line[0]=='[' && line[len-1]==']') {
472         /* Section name */
473         sscanf(line, "[%[^]]", section);
474         strcpy(section, strstrip(section));
475         strcpy(section, section);
476         sta = LINE_SECTION ;
477     } else if (sscanf (line, "%[^=] = \"%[^\"]\"", key, value) == 2
478            ||  sscanf (line, "%[^=] = '%[^\']'",   key, value) == 2
479            ||  sscanf (line, "%[^=] = %[^;#]",     key, value) == 2) {
480         /* Usual key=value, with or without comments */
481         strcpy(key, strstrip(key));
482         strcpy(key, key);
483         strcpy(value, strstrip(value));
484         /*
485          * sscanf cannot handle '' or "" as empty values
486          * this is done here
487          */
488         if (!strcmp(value, "\"\"") || (!strcmp(value, "''"))) {
489             value[0]=0 ;
490         }
491         sta = LINE_VALUE ;
492     } else if (sscanf(line, "%[^=] = %[;#]", key, value)==2
493            ||  sscanf(line, "%[^=] %[=]", key, value) == 2) {
494         /*
495          * Special cases:
496          * key=
497          * key=;
498          * key=#
499          */
500         strcpy(key, strstrip(key));
501         strcpy(key, key);
502         value[0]=0 ;
503         sta = LINE_VALUE ;
504     } else {
505         /* Generate syntax error */
506         sta = LINE_ERROR ;
507     }
508     return sta ;
509 }
510
511 /*-------------------------------------------------------------------------*/
512 /**
513   @brief    Parse an ini file and return an allocated dictionary object
514   @param    ininame Name of the ini file to read.
515   @return   Pointer to newly allocated dictionary
516
517   This is the parser for ini files. This function is called, providing
518   the name of the file to be read. It returns a dictionary object that
519   should not be accessed directly, but through accessor functions
520   instead.
521
522   The returned dictionary must be freed using iniparser_freedict().
523  */
524 /*--------------------------------------------------------------------------*/
525 dictionary * iniparser_load(const char * ininame)
526 {
527     FILE *in, *include = NULL, *inifile;
528
529     char line    [ASCIILINESZ+1] ;
530     char section [ASCIILINESZ+1] ;
531     char key     [ASCIILINESZ+1] ;
532     char tmp     [ASCIILINESZ+1] ;
533     char val     [ASCIILINESZ+1] ;
534
535     int  last=0 ;
536     int  len ;
537     int  lineno=0 ;
538     int  errs=0;
539
540     dictionary * dict ;
541
542     if ((inifile=fopen(ininame, "r"))==NULL) {
543         LOG(log_error, logtype_default, "iniparser: cannot open \"%s\"", ininame);
544         return NULL ;
545     }
546
547     dict = dictionary_new(0) ;
548     if (!dict) {
549         fclose(inifile);
550         return NULL ;
551     }
552
553     memset(line,    0, ASCIILINESZ);
554     memset(section, 0, ASCIILINESZ);
555     memset(key,     0, ASCIILINESZ);
556     memset(val,     0, ASCIILINESZ);
557     last=0 ;
558
559     in = inifile;
560     while (1) {
561         if (fgets(line+last, ASCIILINESZ-last, in) == NULL) {
562             if (include) {
563                 fclose(include);
564                 include = NULL;
565                 in = inifile;
566                 continue;
567             }
568             break;
569         }
570         lineno++ ;
571         len = (int)strlen(line)-1;
572         if (len==0)
573             continue;
574         /* Safety check against buffer overflows */
575         if (line[len]!='\n') {
576             LOG(log_error, logtype_default, "iniparser: input line too long in \"%s\" (lineno: %d)",
577                 ininame, lineno);
578             dictionary_del(dict);
579             fclose(in);
580             return NULL ;
581         }
582         /* Get rid of \n and spaces at end of line */
583         while ((len>=0) &&
584                 ((line[len]=='\n') || (isspace(line[len])))) {
585             line[len]=0 ;
586             len-- ;
587         }
588         /* Detect multi-line */
589         if (line[len]=='\\') {
590             /* Multi-line value */
591             last=len ;
592             continue ;
593         } else {
594             last=0 ;
595         }
596         switch (iniparser_line(line, section, key, val)) {
597         case LINE_EMPTY:
598         case LINE_COMMENT:
599             break ;
600         case LINE_SECTION:
601             errs = dictionary_set(dict, section, NULL, NULL);
602             break ;
603         case LINE_VALUE:
604             if (strcmp(key, "include") == 0) {
605                 if ((include = fopen(val, "r")) == NULL) {
606                     LOG(log_error, logtype_default, "iniparser: cannot open \"%s\"", val);
607                     continue;
608                 }
609                 in = include;
610                 continue;
611             }
612             errs = dictionary_set(dict, section, key, val) ;
613             break ;
614         case LINE_ERROR:
615             LOG(log_error, logtype_default, "iniparser: syntax error in %s (lineno: %d): %s",
616                 ininame, lineno, line);
617             errs++ ;
618             break;
619         default:
620             break ;
621         }
622         memset(line, 0, ASCIILINESZ);
623         last=0;
624         if (errs<0) {
625             LOG(log_error, logtype_default, "iniparser: memory allocation failure");
626             break ;
627         }
628     }
629     if (errs) {
630         dictionary_del(dict);
631         dict = NULL ;
632     }
633     fclose(in);
634     return dict ;
635 }
636
637 /*-------------------------------------------------------------------------*/
638 /**
639   @brief    Free all memory associated to an ini dictionary
640   @param    d Dictionary to free
641   @return   void
642
643   Free all memory associated to an ini dictionary.
644   It is mandatory to call this function before the dictionary object
645   gets out of the current context.
646  */
647 /*--------------------------------------------------------------------------*/
648 void iniparser_freedict(dictionary * d)
649 {
650     dictionary_del(d);
651 }
652