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