]> git.ozlabs.org Git - ccan/blob - ccan/ciniparser/ciniparser.c
Merge remote-tracking branch 'origin/pr/48'
[ccan] / ccan / ciniparser / ciniparser.c
1 /* Copyright (c) 2000-2007 by Nicolas Devillard.
2  * Copyright (x) 2009 by Tim Post <tinkertim@gmail.com>
3  * MIT License
4  *
5  * Permission is hereby granted, free of charge, to any person obtaining a
6  * copy of this software and associated documentation files (the "Software"),
7  * to deal in the Software without restriction, including without limitation
8  * the rights to use, copy, modify, merge, publish, distribute, sublicense,
9  * and/or sell copies of the Software, and to permit persons to whom the
10  * Software is furnished to do so, subject to the following conditions:
11  *
12  * The above copyright notice and this permission notice shall be included in
13  * all copies or substantial portions of the Software.
14  *
15  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
21  * DEALINGS IN THE SOFTWARE.
22  */
23
24 /** @addtogroup ciniparser
25  * @{
26  */
27 /**
28  *  @file ciniparser.c
29  *  @author N. Devillard
30  *  @date Sep 2007
31  *  @version 3.0
32  *  @brief Parser for ini files.
33  */
34
35 #include <ctype.h>
36 #include <ccan/ciniparser/ciniparser.h>
37
38 #define ASCIILINESZ      (1024)
39 #define INI_INVALID_KEY  ((char*) NULL)
40
41 /**
42  * This enum stores the status for each parsed line (internal use only).
43  */
44 typedef enum _line_status_ {
45         LINE_UNPROCESSED,
46         LINE_ERROR,
47         LINE_EMPTY,
48         LINE_COMMENT,
49         LINE_SECTION,
50         LINE_VALUE
51 } line_status;
52
53
54 /**
55  * @brief Convert a string to lowercase.
56  * @param s String to convert.
57  * @return ptr to statically allocated string.
58  *
59  * This function returns a pointer to a statically allocated string
60  * containing a lowercased version of the input string. Do not free
61  * or modify the returned string! Since the returned string is statically
62  * allocated, it will be modified at each function call (not re-entrant).
63  */
64 static char *strlwc(const char *s)
65 {
66         static char l[ASCIILINESZ+1];
67         int i;
68
69         if (s == NULL)
70                 return NULL;
71
72         for (i = 0; s[i] && i < ASCIILINESZ; i++)
73                 l[i] = tolower(s[i]);
74         l[i] = '\0';
75         return l;
76 }
77
78 /**
79  * @brief Remove blanks at the beginning and the end of a string.
80  * @param s String to parse.
81  * @return ptr to statically allocated string.
82  *
83  * This function returns a pointer to a statically allocated string,
84  * which is identical to the input string, except that all blank
85  * characters at the end and the beg. of the string have been removed.
86  * Do not free or modify the returned string! Since the returned string
87  * is statically allocated, it will be modified at each function call
88  * (not re-entrant).
89  */
90 static char *strstrip(const char *s)
91 {
92         static char l[ASCIILINESZ+1];
93         unsigned int i, numspc;
94
95         if (s == NULL)
96                 return NULL;
97
98         while (isspace(*s))
99                 s++;
100
101         for (i = numspc = 0; s[i] && i < ASCIILINESZ; i++) {
102                 l[i] = s[i];
103                 if (isspace(l[i]))
104                         numspc++;
105                 else
106                         numspc = 0;
107         }
108         l[i - numspc] = '\0';
109         return l;
110 }
111
112 /**
113  * @brief Load a single line from an INI file
114  * @param input_line Input line, may be concatenated multi-line input
115  * @param section Output space to store section
116  * @param key Output space to store key
117  * @param value Output space to store value
118  * @return line_status value
119  */
120 static
121 line_status ciniparser_line(char *input_line, char *section,
122         char *key, char *value)
123 {
124         line_status sta;
125         char line[ASCIILINESZ+1];
126         int      len;
127
128         strcpy(line, strstrip(input_line));
129         len = (int) strlen(line);
130
131         if (len < 1) {
132                 /* Empty line */
133                 sta = LINE_EMPTY;
134         } else if (line[0] == '#') {
135                 /* Comment line */
136                 sta = LINE_COMMENT;
137         } else if (line[0] == '[' && line[len-1] == ']') {
138                 /* Section name */
139                 sscanf(line, "[%[^]]", section);
140                 strcpy(section, strstrip(section));
141                 strcpy(section, strlwc(section));
142                 sta = LINE_SECTION;
143         } else if (sscanf (line, "%[^=] = \"%[^\"]\"", key, value) == 2
144                    ||  sscanf (line, "%[^=] = '%[^\']'", key, value) == 2
145                    ||  sscanf (line, "%[^=] = %[^;#]", key, value) == 2) {
146                 /* Usual key=value, with or without comments */
147                 strcpy(key, strstrip(key));
148                 strcpy(key, strlwc(key));
149                 strcpy(value, strstrip(value));
150                 /*
151                  * sscanf cannot handle '' or "" as empty values
152                  * this is done here
153                  */
154                 if (!strcmp(value, "\"\"") || (!strcmp(value, "''"))) {
155                         value[0] = 0;
156                 }
157                 sta = LINE_VALUE;
158         } else if (sscanf(line, "%[^=] = %[;#]", key, value) == 2
159                 ||  sscanf(line, "%[^=] %[=]", key, value) == 2) {
160                 /*
161                  * Special cases:
162                  * key=
163                  * key=;
164                  * key=#
165                  */
166                 strcpy(key, strstrip(key));
167                 strcpy(key, strlwc(key));
168                 value[0] = 0;
169                 sta = LINE_VALUE;
170         } else {
171                 /* Generate syntax error */
172                 sta = LINE_ERROR;
173         }
174         return sta;
175 }
176
177 /* The remaining public functions are documented in ciniparser.h */
178
179 int ciniparser_getnsec(dictionary *d)
180 {
181         int i;
182         int nsec;
183
184         if (d == NULL)
185                 return -1;
186
187         nsec = 0;
188         for (i = 0; i < d->size; i++) {
189                 if (d->key[i] == NULL)
190                         continue;
191                 if (strchr(d->key[i], ':') == NULL) {
192                         nsec ++;
193                 }
194         }
195
196         return nsec;
197 }
198
199 char *ciniparser_getsecname(dictionary *d, int n)
200 {
201         int i;
202         int foundsec;
203
204         if (d == NULL || n < 0)
205                 return NULL;
206
207         if (n == 0)
208                 n ++;
209
210         foundsec = 0;
211
212         for (i = 0; i < d->size; i++) {
213                 if (d->key[i] == NULL)
214                         continue;
215                 if (! strchr(d->key[i], ':')) {
216                         foundsec++;
217                         if (foundsec >= n)
218                                 break;
219                 }
220         }
221
222         if (foundsec == n) {
223                 return d->key[i];
224         }
225
226         return (char *) NULL;
227 }
228
229 void ciniparser_dump(dictionary *d, FILE *f)
230 {
231         int i;
232
233         if (d == NULL || f == NULL)
234                 return;
235
236         for (i = 0; i < d->size; i++) {
237                 if (d->key[i] == NULL)
238                         continue;
239                 if (d->val[i] != NULL) {
240                         fprintf(f, "[%s]=[%s]\n", d->key[i], d->val[i]);
241                 } else {
242                         fprintf(f, "[%s]=UNDEF\n", d->key[i]);
243                 }
244         }
245
246         return;
247 }
248
249 void ciniparser_dump_ini(dictionary *d, FILE *f)
250 {
251         int i, j;
252         char keym[ASCIILINESZ+1];
253         int nsec;
254         char *secname;
255         int seclen;
256
257         if (d == NULL || f == NULL)
258                 return;
259
260         memset(keym, 0, ASCIILINESZ + 1);
261
262         nsec = ciniparser_getnsec(d);
263         if (nsec < 1) {
264                 /* No section in file: dump all keys as they are */
265                 for (i = 0; i < d->size; i++) {
266                         if (d->key[i] == NULL)
267                                 continue;
268                         fprintf(f, "%s = %s\n", d->key[i], d->val[i]);
269                 }
270                 return;
271         }
272
273         for (i = 0; i < nsec; i++) {
274                 secname = ciniparser_getsecname(d, i);
275                 seclen  = (int)strlen(secname);
276                 fprintf(f, "\n[%s]\n", secname);
277                 snprintf(keym, ASCIILINESZ + 1, "%s:", secname);
278                 for (j = 0; j < d->size; j++) {
279                         if (d->key[j] == NULL)
280                                 continue;
281                         if (!strncmp(d->key[j], keym, seclen+1)) {
282                                 fprintf(f, "%-30s = %s\n",
283                                         d->key[j]+seclen+1,
284                                         d->val[j] ? d->val[j] : "");
285                         }
286                 }
287         }
288         fprintf(f, "\n");
289
290         return;
291 }
292
293 char *ciniparser_getstring(dictionary *d, const char *key, char *def)
294 {
295         char *lc_key;
296         char *sval;
297
298         if (d == NULL || key == NULL)
299                 return def;
300
301         lc_key = strlwc(key);
302         sval = dictionary_get(d, lc_key, def);
303
304         return sval;
305 }
306
307 int ciniparser_getint(dictionary *d, const char *key, int notfound)
308 {
309         char *str;
310
311         str = ciniparser_getstring(d, key, INI_INVALID_KEY);
312
313         if (str == INI_INVALID_KEY)
314                 return notfound;
315
316         return (int) strtol(str, NULL, 10);
317 }
318
319 double ciniparser_getdouble(dictionary *d, const char *key, double notfound)
320 {
321         char *str;
322
323         str = ciniparser_getstring(d, key, INI_INVALID_KEY);
324
325         if (str == INI_INVALID_KEY)
326                 return notfound;
327
328         return atof(str);
329 }
330
331 int ciniparser_getboolean(dictionary *d, const char *key, int notfound)
332 {
333         char *c;
334         int ret;
335
336         c = ciniparser_getstring(d, key, INI_INVALID_KEY);
337         if (c == INI_INVALID_KEY)
338                 return notfound;
339
340         switch(c[0]) {
341         case 'y': case 'Y': case '1': case 't': case 'T':
342                 ret = 1;
343                 break;
344         case 'n': case 'N': case '0': case 'f': case 'F':
345                 ret = 0;
346                 break;
347         default:
348                 ret = notfound;
349                 break;
350         }
351
352         return ret;
353 }
354
355 int ciniparser_find_entry(dictionary *ini, char *entry)
356 {
357         int found = 0;
358
359         if (ciniparser_getstring(ini, entry, INI_INVALID_KEY) != INI_INVALID_KEY) {
360                 found = 1;
361         }
362
363         return found;
364 }
365
366 int ciniparser_set(dictionary *d, char *entry, char *val)
367 {
368         return dictionary_set(d, strlwc(entry), val);
369 }
370
371 void ciniparser_unset(dictionary *ini, char *entry)
372 {
373         dictionary_unset(ini, strlwc(entry));
374 }
375
376 dictionary *ciniparser_load(const char *ininame)
377 {
378         FILE *in;
379         char line[ASCIILINESZ+1];
380         char section[ASCIILINESZ+1];
381         char key[ASCIILINESZ+1];
382         char tmp[ASCIILINESZ+1];
383         char val[ASCIILINESZ+1];
384         int  last = 0, len, lineno = 0, errs = 0;
385         dictionary *dict;
386
387         if ((in = fopen(ininame, "r")) == NULL) {
388                 fprintf(stderr, "ciniparser: cannot open %s\n", ininame);
389                 return NULL;
390         }
391
392         dict = dictionary_new(0);
393         if (!dict) {
394                 fclose(in);
395                 return NULL;
396         }
397
398         memset(line, 0, ASCIILINESZ + 1);
399         memset(section, 0, ASCIILINESZ + 1);
400         memset(key, 0, ASCIILINESZ + 1);
401         memset(val, 0, ASCIILINESZ + 1);
402         last = 0;
403
404         while (fgets(line+last, ASCIILINESZ-last, in)!=NULL) {
405                 lineno++;
406                 len = (int) strlen(line)-1;
407                 /* Safety check against buffer overflows */
408                 if (line[len] != '\n') {
409                         fprintf(stderr,
410                                         "ciniparser: input line too long in %s (%d)\n",
411                                         ininame,
412                                         lineno);
413                         dictionary_del(dict);
414                         fclose(in);
415                         return NULL;
416                 }
417
418                 /* Get rid of \n and spaces at end of line */
419                 while ((len >= 0) &&
420                                 ((line[len] == '\n') || (isspace(line[len])))) {
421                         line[len] = 0;
422                         len--;
423                 }
424
425                 /* Detect multi-line */
426                 if (len >= 0 && line[len] == '\\') {
427                         /* Multi-line value */
428                         last = len;
429                         continue;
430                 }
431
432                 switch (ciniparser_line(line, section, key, val)) {
433                 case LINE_EMPTY:
434                 case LINE_COMMENT:
435                         break;
436
437                 case LINE_SECTION:
438                         errs = dictionary_set(dict, section, NULL);
439                         break;
440
441                 case LINE_VALUE:
442                         snprintf(tmp, ASCIILINESZ + 1, "%s:%s", section, key);
443                         errs = dictionary_set(dict, tmp, val);
444                         break;
445
446                 case LINE_ERROR:
447                         fprintf(stderr, "ciniparser: syntax error in %s (%d):\n",
448                                         ininame, lineno);
449                         fprintf(stderr, "-> %s\n", line);
450                         errs++;
451                         break;
452
453                 default:
454                         break;
455                 }
456                 memset(line, 0, ASCIILINESZ);
457                 last = 0;
458                 if (errs < 0) {
459                         fprintf(stderr, "ciniparser: memory allocation failure\n");
460                         break;
461                 }
462         }
463
464         if (errs) {
465                 dictionary_del(dict);
466                 dict = NULL;
467         }
468
469         fclose(in);
470
471         return dict;
472 }
473
474 void ciniparser_freedict(dictionary *d)
475 {
476         dictionary_del(d);
477 }
478
479 /** @}
480  */