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