1 /* vi:ai:et:ts=8 sw=2
2  */
3 /*
4  * wzdftpd - a modular and cool ftp server
5  * Copyright (C) 2002-2004  Pierre Chifflier
6  *
7  * This program is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License
9  * as published by the Free Software Foundation; either version 2
10  * of the License, or (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, write to the Free Software
19  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
20  *
21  * As a special exemption, Pierre Chifflier
22  * and other respective copyright holders give permission to link this program
23  * with OpenSSL, and distribute the resulting executable, without including
24  * the source code for OpenSSL in the source distribution.
25  */
26 /** \file wzd_configfile.c
27  * \brief Simple config file parser (.ini like)
28  *
29  * \note Implementation was greatly inspired from gkeyfile.c, from the glib sources.
30  */
31 
32 #include "wzd_all.h"
33 
34 #ifndef WZD_USE_PCH
35 
36 #include <stdio.h>
37 #include <stdlib.h>
38 #include <string.h>
39 #include <fcntl.h>
40 #include <sys/stat.h>
41 #include <errno.h>
42 
43 #ifdef HAVE_UNISTD_H
44 # include <unistd.h>
45 #endif
46 
47 #include <fcntl.h> /* O_RDONLY */
48 
49 #include <ctype.h> /* isspace */
50 
51 #include "wzd_structs.h"
52 #include "wzd_log.h"
53 
54 #include "wzd_string.h"
55 #include "wzd_utf8.h"
56 #include "wzd_configfile.h"
57 
58 #include "wzd_debug.h"
59 
60 #endif /* WZD_USE_PCH */
61 
62 
63 #include "libwzd-base/dlist.h"
64 
65 
66 
67 #define VALUE_LIST_SEPARATOR    ","
68 
69 typedef struct _wzd_configfile_group_t wzd_configfile_group_t;
70 
71 typedef struct _wzd_configfile_keyvalue_t wzd_configfile_keyvalue_t;
72 
73 struct _wzd_configfile_t {
74   List * groups;
75   wzd_string_t * parse_buffer;
76 
77   wzd_configfile_group_t * current_group;
78 
79   unsigned long flags;
80 };
81 
82 struct _wzd_configfile_group_t {
83   char * name;
84 
85   wzd_configfile_keyvalue_t * comment; /* comment at top */
86 
87   DList * values;
88 };
89 
90 struct _wzd_configfile_keyvalue_t {
91   char * key; /* NULL for comments */
92   char * value;
93 };
94 
95 static void _configfile_group_init(wzd_configfile_group_t * group);
96 static void _configfile_group_free(wzd_configfile_group_t * group);
97 static wzd_configfile_keyvalue_t * _configfile_keyvalue_calloc(void);
98 static void _configfile_keyvalue_free(wzd_configfile_keyvalue_t * kv);
99 
100 static void config_init(wzd_configfile_t * config);
101 static void config_clear(wzd_configfile_t * config);
102 
103 static int _config_cmp_keyvalue(const char *k1, const wzd_configfile_keyvalue_t *k2);
104 static int _config_cmp_groupname(const char *k1, const wzd_configfile_group_t *k2);
105 
106 static int config_line_is_comment(const char * line);
107 static int config_line_is_group(const char * line);
108 static int config_line_is_keyvalue(const char * line);
109 static int config_parse_data(wzd_configfile_t * config, const char * data, size_t length);
110 static int config_parse_flush_buffer(wzd_configfile_t * config);
111 static int config_parse_line(wzd_configfile_t * config, const char * line, size_t length);
112 static int config_parse_comment(wzd_configfile_t * config, const char * line, size_t length);
113 static int config_parse_group(wzd_configfile_t * config, const char * line, size_t length);
114 static int config_parse_keyvalue(wzd_configfile_t * config, const char * line, size_t length);
115 
116 static wzd_configfile_group_t * config_lookup_group(const wzd_configfile_t * config, const char *groupname);
117 static wzd_configfile_keyvalue_t * config_lookup_keyvalue(const wzd_configfile_t * config, wzd_configfile_group_t * group, const char * key);
118 
119 static int config_add_key(wzd_configfile_t * config, wzd_configfile_group_t * group, const char * key, const char * value);
120 static int config_add_group(wzd_configfile_t * config, const char * groupname);
121 
122 static int config_set_key_comment(wzd_configfile_t * config, const char * groupname, const char * key, const char * comment);
123 static int config_set_group_comment(wzd_configfile_t * config, const char * groupname, const char * comment);
124 static int config_set_top_comment(wzd_configfile_t * config, const char * comment);
125 
126 
127 
128 /** \brief Creates a new empty wzd_configfile_t object.
129  */
config_new(void)130 wzd_configfile_t * config_new(void)
131 {
132   wzd_configfile_t * filenew;
133 
134   filenew = wzd_malloc(sizeof(wzd_configfile_t));
135   config_init(filenew);
136 
137   return filenew;
138 }
139 
140 /** \brief Frees a wzd_configfile_t
141  */
config_free(wzd_configfile_t * file)142 void config_free(wzd_configfile_t * file)
143 {
144   if (!file) return;
145 
146   config_clear(file);
147   wzd_free(file);
148 }
149 
150 /** \brief Returns the list of groups contained in \a file
151  */
config_get_groups(const wzd_configfile_t * file)152 wzd_string_t ** config_get_groups(const wzd_configfile_t * file)
153 {
154   wzd_string_t ** array;
155   ListElmt * elmnt;
156   wzd_configfile_group_t * group = NULL;
157   int i = 0;
158 
159   if (!file) return NULL;
160 
161   array = wzd_malloc(sizeof(*array) * (list_size(file->groups)+1));
162 
163   for (elmnt = list_head(file->groups); elmnt; elmnt = list_next(elmnt)) {
164     group = list_data(elmnt);
165     array[i++] = STR(group->name);
166   }
167 
168   array[i] = NULL;
169 
170   return array;
171 }
172 
173 /** \brief Returns the list of keys contained in \a group
174  */
config_get_keys(const wzd_configfile_t * file,const char * groupname,int * errcode)175 wzd_string_t ** config_get_keys(const wzd_configfile_t * file, const char * groupname, int * errcode)
176 {
177   wzd_string_t ** array;
178   DListElmt * elmnt;
179   wzd_configfile_group_t * group;
180   wzd_configfile_keyvalue_t * kv;
181   int i = 0;
182 
183   if (!file || !groupname) return 0;
184 
185   group = config_lookup_group(file,groupname);
186   if (!group) {
187     if (errcode) *errcode = CF_ERROR_GROUP_NOT_FOUND;
188     return NULL;
189   }
190 
191   array = wzd_malloc(sizeof(*array) * (dlist_size(group->values)+1));
192 
193   for (elmnt = dlist_head(group->values); elmnt; elmnt = dlist_next(elmnt)) {
194     kv = list_data(elmnt);
195     array[i++] = STR(kv->key);
196   }
197 
198   array[i] = NULL;
199 
200   return array;
201 }
202 
203 /** \brief Looks whether the config file has the group \a groupname.
204  * \return 1 if \a groupname is part of \a file
205  */
config_has_group(wzd_configfile_t * file,const char * groupname)206 int config_has_group(wzd_configfile_t * file, const char * groupname)
207 {
208   if (!file || !groupname) return 0;
209 
210   return config_lookup_group(file,groupname) != NULL;
211 }
212 
213 /** \brief Looks whether the config file has the key \a key in the group \a groupname.
214  * \return 1 if \a key is part of \a groupname
215  */
config_has_key(wzd_configfile_t * file,const char * groupname,const char * key)216 int config_has_key(wzd_configfile_t * file, const char * groupname, const char * key)
217 {
218   wzd_configfile_group_t * group;
219   wzd_configfile_keyvalue_t * kv;
220 
221   if (!file || !groupname || !key) return 0;
222 
223   group = config_lookup_group(file,groupname);
224   if (!group) return 0;
225 
226   kv = config_lookup_keyvalue(file,group,key);
227 
228   return kv != NULL;
229 }
230 
231 /** \brief Returns the value associated with \a key under \a groupname.
232  * \return the value, or NULL if the key is not found
233  * The returned value is a pointer to the object, it must not be freed.
234  */
config_get_value(const wzd_configfile_t * file,const char * groupname,const char * key)235 char * config_get_value(const wzd_configfile_t * file, const char * groupname, const char * key)
236 {
237   wzd_configfile_group_t * group;
238   wzd_configfile_keyvalue_t * kv;
239 
240   if (!file || !groupname || !key) return NULL;
241 
242   group = config_lookup_group(file,groupname);
243   if (!group) return NULL;
244 
245   kv = config_lookup_keyvalue(file,group,key);
246   if (!kv) return NULL;
247 
248   return kv->value;
249 }
250 
251 /** \brief Associates a new value with \a key under \a groupname.
252  *
253  * If \a key cannot be found then it is created. If \a groupname cannot be found then it is
254  * created.
255  */
config_set_value(wzd_configfile_t * file,const char * groupname,const char * key,const char * value)256 int config_set_value(wzd_configfile_t * file, const char * groupname, const char * key, const char * value)
257 {
258   wzd_configfile_group_t * group;
259   wzd_configfile_keyvalue_t * kv;
260 
261   if (!file || !groupname || !key || !value) return CF_ERROR_INVALID_ARGS;
262 
263   group = config_lookup_group(file,groupname);
264   if (!group) {
265     if (config_add_group(file, groupname)) return CF_ERROR_GROUP_NOT_FOUND;
266     group = config_lookup_group(file,groupname);
267     if (!group) return CF_ERROR_GROUP_NOT_FOUND;
268   }
269 
270   kv = config_lookup_keyvalue(file,group,key);
271   if (!kv) {
272     return config_add_key(file,group,key,value);
273   } else {
274     wzd_free(kv->value);
275     kv->value = wzd_strdup(value);
276   }
277 
278   return CF_OK;
279 }
280 
281 /** \brief Returns the value associated with \a key under \a groupname as a string.
282  * \return the value, else \a errcode is set to nonzero.
283  */
config_get_string(const wzd_configfile_t * file,const char * groupname,const char * key,int * errcode)284 wzd_string_t * config_get_string(const wzd_configfile_t * file, const char * groupname, const char * key, int * errcode)
285 {
286   char * value;
287   wzd_string_t * str_value = NULL;
288 
289   if (!file || !groupname || !key) return NULL;
290   if (errcode) *errcode = CF_OK;
291 
292   value = config_get_value(file,groupname,key);
293   if (!value) {
294     if (errcode) *errcode = CF_ERROR_NOT_FOUND;
295     return NULL;
296   }
297 
298 #ifdef HAVE_UTF8
299   if (!utf8_valid(value,strlen(value))) {
300     if (errcode) *errcode = CF_ERROR_INVALID_ENCODING;
301     return NULL;
302   }
303 #endif
304 
305   str_value = STR(value);
306 
307   return str_value;
308 }
309 
310 /** \brief Associates a new string value with \a key under \a groupname.
311  *
312  * If \a key cannot be found then it is created.
313  */
config_set_string(wzd_configfile_t * file,const char * groupname,const char * key,wzd_string_t * value)314 int config_set_string(wzd_configfile_t * file, const char * groupname, const char * key, wzd_string_t * value)
315 {
316   if (!file || !groupname || !key) return CF_ERROR_INVALID_ARGS;
317 
318   return config_set_value(file, groupname, key, str_tochar(value));
319 }
320 
321 /** \brief Returns the value associated with \a key under \a groupname as a string.
322  * \return a NULL-terminated string array,, or NULL and set \a errcode to nonzero.
323  * The array should be freed using str_deallocate_array()
324  */
config_get_string_list(wzd_configfile_t * file,const char * groupname,const char * key,int * errcode)325 wzd_string_t ** config_get_string_list(wzd_configfile_t * file, const char * groupname, const char * key, int * errcode)
326 {
327   char * value;
328   wzd_string_t * str_value;
329   wzd_string_t ** array = NULL;
330   unsigned int i;
331 
332   if (!file || !groupname || !key) return NULL;
333   if (errcode) *errcode = CF_OK;
334 
335   value = config_get_value(file,groupname,key);
336   if (!value) {
337     if (errcode) *errcode = CF_ERROR_NOT_FOUND;
338     return NULL;
339   }
340 
341 #ifdef HAVE_UTF8
342   if (!utf8_valid(value,strlen(value))) {
343     if (errcode) *errcode = CF_ERROR_INVALID_ENCODING;
344     return NULL;
345   }
346 #endif
347 
348   str_value = STR(value);
349 
350   array = str_split(str_value,VALUE_LIST_SEPARATOR,0);
351 
352   str_deallocate(str_value);
353 
354   /* remove leading spaces */
355   if (array) {
356     for (i=0; array[i]; i++) {
357       str_trim_left(array[i]);
358     }
359   }
360 
361   return array;
362 }
363 
364 /** \brief Associates a list of string values with \a key under \a groupname.
365  *
366  * If \a key cannot be found then it is created.
367  */
config_set_string_list(wzd_configfile_t * file,const char * groupname,const char * key,wzd_string_t ** value,size_t length)368 int config_set_string_list(wzd_configfile_t * file, const char * groupname, const char * key, wzd_string_t ** value, size_t length)
369 {
370   wzd_string_t * str;
371   size_t i;
372   int ret;
373 
374   if (!file || !groupname || !key) return CF_ERROR_INVALID_ARGS;
375 
376   str = str_allocate();
377   for (i=0; value[i] != NULL && i < length; i++) {
378     str_append(str, str_tochar(value[i]));
379     str_append(str, VALUE_LIST_SEPARATOR);
380   }
381   /* removes the last separator */
382   str_erase(str, str_length(str)-strlen(VALUE_LIST_SEPARATOR), -1);
383 
384   ret = config_set_value(file, groupname, key, str_tochar(str));
385 
386   str_deallocate(str);
387 
388   return ret;
389 }
390 
391 /** \brief Returns the value associated with \a key under \a groupname as a boolean.
392  * \return the value, else \a errcode is set to nonzero.
393  */
config_get_boolean(wzd_configfile_t * file,const char * groupname,const char * key,int * errcode)394 int config_get_boolean(wzd_configfile_t * file, const char * groupname, const char * key, int * errcode)
395 {
396   char * value;
397 
398   if (errcode) *errcode = CF_OK;
399 
400   value = config_get_value(file,groupname,key);
401   if (!value) {
402     if (errcode) *errcode = CF_ERROR_NOT_FOUND;
403     return -1;
404   }
405 
406   if (strcmp(value,"true")==0  || strcmp(value,"1")==0 || strcmp(value,"yes")==0) return 1;
407   if (strcmp(value,"false")==0 || strcmp(value,"0")==0 || strcmp(value,"no")==0 ) return 0;
408 
409   if (errcode) *errcode = CF_ERROR_PARSE;
410 
411   return -1;
412 }
413 
414 /** \brief Associates a new boolean value with \a key under \a groupname.
415  *
416  * If \a key cannot be found then it is created.
417  */
config_set_boolean(wzd_configfile_t * file,const char * groupname,const char * key,int value)418 int config_set_boolean(wzd_configfile_t * file, const char * groupname, const char * key, int value)
419 {
420   char * result;
421 
422   if (!file || !groupname || !key) return CF_ERROR_INVALID_ARGS;
423 
424   result = (value) ? "true" : "false";
425 
426   return config_set_value(file, groupname, key, result);
427 }
428 
429 /** \brief Returns the value associated with \a key under \a groupname as an integer.
430  * \return the value, else \a errcode is set to nonzero.
431  */
config_get_integer(wzd_configfile_t * file,const char * groupname,const char * key,int * errcode)432 int config_get_integer(wzd_configfile_t * file, const char * groupname, const char * key, int * errcode)
433 {
434   char * value;
435   char * end;
436   long longv;
437   int intv;
438 
439   if (errcode) *errcode = CF_OK;
440 
441   value = config_get_value(file,groupname,key);
442   if (!value) {
443     if (errcode) *errcode = CF_ERROR_NOT_FOUND;
444     return -1;
445   }
446 
447   longv = strtoul(value,&end,10);
448   if (*value == '\0' || *end != '\0') {
449     if (errcode) *errcode = CF_ERROR_PARSE;
450     return -1;
451   }
452 
453   intv = longv;
454   if (intv != longv || errno == ERANGE) {
455     if (errcode) *errcode = CF_ERROR_PARSE;
456     return -1;
457   }
458 
459   return intv;
460 }
461 
462 /** \brief Associates a new integer value with \a key under \a groupname.
463  *
464  * If \a key cannot be found then it is created.
465  */
config_set_integer(wzd_configfile_t * file,const char * groupname,const char * key,int value)466 int config_set_integer(wzd_configfile_t * file, const char * groupname, const char * key, int value)
467 {
468   wzd_string_t * str;
469   int ret;
470 
471   if (!file || !groupname || !key) return CF_ERROR_INVALID_ARGS;
472 
473   str = str_allocate();
474   str_sprintf(str,"%d",value);
475 
476   ret = config_set_value(file, groupname, key, str_tochar(str));
477 
478   str_deallocate(str);
479 
480   return ret;
481 }
482 
483 /** \brief Places a comment above \a key from \a groupname.
484  *
485  * If \a key is NULL then \a comment will be written above \a groupname.
486  * If both \a key and \a groupname are NULL, then \a comment will be written
487  * above the first group in the file.
488  */
config_set_comment(wzd_configfile_t * file,const char * groupname,const char * key,const char * comment)489 int config_set_comment(wzd_configfile_t * file, const char * groupname, const char * key, const char * comment)
490 {
491   if (!file) return CF_ERROR_INVALID_ARGS;
492 
493   if (groupname && key)
494     return config_set_key_comment(file, groupname, key, comment);
495   else if (groupname)
496     return config_set_group_comment(file, groupname, comment);
497   else
498     return config_set_top_comment(file, comment);
499 }
500 
501 /** \brief Removes a comment above \a key from \a groupname
502  *
503  * If \a key is \a NULL then the comment above \a groupname is removed.
504  * If both \a key and \a groupname are \a NULL, then the comment before the
505  * first group is removed.
506  */
config_remove_comment(wzd_configfile_t * file,const char * groupname,const char * key)507 int config_remove_comment(wzd_configfile_t * file, const char * groupname, const char * key)
508 {
509   if (!file) return CF_ERROR_INVALID_ARGS;
510 
511   if (groupname && key)
512     return config_set_key_comment(file, groupname, key, NULL);
513   else if (groupname)
514     return config_set_group_comment(file, groupname, NULL);
515   else
516     return config_set_top_comment(file, NULL);
517 }
518 
519 /** \brief Removes a \a key in \a groupname from the key file.
520  */
config_remove_key(wzd_configfile_t * file,const char * groupname,const char * key)521 int config_remove_key(wzd_configfile_t * file, const char * groupname, const char * key)
522 {
523   wzd_configfile_group_t * group;
524   wzd_configfile_keyvalue_t * kv;
525   DListElmt * element;
526 
527   if (!file || !groupname || !key) return CF_ERROR_INVALID_ARGS;
528 
529   group = config_lookup_group(file,groupname);
530   if (!group) {
531     return CF_ERROR_GROUP_NOT_FOUND;
532   }
533 
534   /* find the key the comments are supposed to be associated with */
535   element = dlist_lookup_node(group->values,(void*)key);
536   if (!element) {
537     return CF_ERROR_NOT_FOUND;
538   } else {
539     dlist_remove(group->values,element,(void**)&kv);
540     _configfile_keyvalue_free(kv);
541   }
542 
543   return CF_OK;
544 }
545 
546 /** \brief Removes a \a groupname (and all associated keys and comments) from the key file.
547  */
config_remove_group(wzd_configfile_t * file,const char * groupname)548 int config_remove_group(wzd_configfile_t * file, const char * groupname)
549 {
550   ListElmt * element;
551   wzd_configfile_group_t * group;
552 
553   if (!file || !groupname) return CF_ERROR_INVALID_ARGS;
554 
555   element = list_lookup_node(file->groups, (void*)groupname);
556   if (!element) return CF_ERROR_GROUP_NOT_FOUND;
557 
558   if (list_data(element) == file->current_group) {
559     if (list_head(file->groups) != element)
560       file->current_group = list_data(list_head(file->groups));
561     else
562       file->current_group = NULL;
563   }
564 
565   list_remove(file->groups, element, (void**)&group);
566 
567   if (group) {
568     _configfile_group_free(group);
569   }
570 
571   return CF_OK;
572 }
573 
574 /** Loads a key file from disk into an empty wzd_configfile_t structure.
575  *
576  * If the object cannot be created then the return value is non-zero.
577  */
config_load_from_file(wzd_configfile_t * config,const char * file,unsigned long flags)578 int config_load_from_file (wzd_configfile_t * config, const char * file, unsigned long flags)
579 {
580   int fd;
581   int ret;
582 
583   if (!config || !file) return CF_ERROR_INVALID_ARGS;
584 
585   fd = open(file, O_RDONLY, 0);
586   if (fd < 0)
587     return CF_ERROR_FILE;
588 
589   ret = config_load_from_fd(config, fd, flags);
590 
591   close(fd);
592   return ret;
593 }
594 
595 /** Loads a key file from an opened file descriptor into an empty
596  * wzd_configfile_t structure.
597  *
598  * If the object cannot be created then the return value is non-zero.
599  */
config_load_from_fd(wzd_configfile_t * config,int fd,unsigned long flags)600 int config_load_from_fd (wzd_configfile_t * config, int fd, unsigned long flags)
601 {
602   char read_buf[4096];
603   int bytes_read;
604   int ret;
605   struct stat stat_buf;
606 
607   if (!config || fd < 0) return CF_ERROR_INVALID_ARGS;
608 
609   if (fstat(fd, &stat_buf) < 0)
610     return CF_ERROR_FILE;
611 
612   if (!S_ISREG(stat_buf.st_mode))
613     return CF_ERROR_FILE;
614 
615   if (stat_buf.st_size == 0)
616     return CF_ERROR_PARSE;
617 
618   config->flags = flags;
619 
620   do {
621     bytes_read = read(fd, read_buf, sizeof(read_buf));
622 
623     if (bytes_read == 0) /* EOF */
624       break;
625 
626     if (bytes_read < 0) {
627       if (errno == EINTR || errno == EAGAIN)
628         continue;
629 
630       return CF_ERROR_PARSE;
631     }
632 
633     /** \bug FIXME what happens if the last line is truncated ! */
634     ret = config_parse_data(config, read_buf, bytes_read);
635   } while (ret == CF_OK);
636 
637   if (ret != CF_OK)
638     return ret;
639 
640   config_parse_flush_buffer (config);
641 
642   return ret;
643 }
644 
645 /** Loads a key file from memory into an empty wzd_configfile_t structure.
646  *
647  * If the object cannot be created then the return value is non-zero.
648  */
config_load_from_data(wzd_configfile_t * config,const char * data,size_t length,unsigned long flags)649 int config_load_from_data (wzd_configfile_t * config, const char * data, size_t length, unsigned long flags)
650 {
651   int ret;
652 
653   if (!config) return CF_ERROR_INVALID_ARGS;
654   if (!data) return CF_ERROR_INVALID_ARGS;
655   if (length == 0) return CF_ERROR_INVALID_ARGS;
656 
657   if (length == (size_t)-1)
658     length = strlen (data);
659 
660 #if 0
661   if (config->approximate_size > 0)
662     {
663       config_clear (config);
664       config_init (config);
665     }
666 #endif
667   config->flags = flags;
668 
669   ret = config_parse_data (config, data, length);
670 
671   if (ret) return ret;
672 
673   config_parse_flush_buffer (config);
674 
675   return ret;
676 }
677 
678 /** outputs \a config as a wzd_string_t.
679  */
config_to_data(wzd_configfile_t * config,size_t * length)680 wzd_string_t * config_to_data (wzd_configfile_t * config, size_t * length)
681 {
682   wzd_string_t * data_string;
683   ListElmt * elmnt;
684   DListElmt * el;
685   wzd_configfile_group_t * group;
686   wzd_configfile_keyvalue_t * kv;
687 
688   if (!config) return NULL;
689 
690   data_string = str_allocate();
691 
692   for (elmnt = list_head(config->groups); elmnt; elmnt = list_next(elmnt)) {
693     group = list_data(elmnt);
694 
695     if (group->comment != NULL)
696       str_append_printf (data_string, "%s\n", group->comment->value);
697     if (group->name != NULL)
698       str_append_printf (data_string, "[%s]\n", group->name);
699 
700     if (!group->values) continue;
701 
702     for (el = dlist_head (group->values); el != NULL; el = dlist_next(el))
703     {
704       kv = dlist_data(el);
705 
706       if (kv->key != NULL)
707         str_append_printf (data_string, "%s = %s\n", kv->key, kv->value);
708       else
709         str_append_printf (data_string, "%s\n", kv->value);
710     }
711   }
712 
713   if (length)
714     *length = str_length(data_string);
715 
716   return data_string;
717 }
718 
719 /***************** static functions *****************/
720 
_configfile_group_init(wzd_configfile_group_t * group)721 static void _configfile_group_init(wzd_configfile_group_t * group)
722 {
723   WZD_ASSERT_VOID(group != NULL);
724   group->name = NULL;
725   group->comment = NULL;
726   group->values = wzd_malloc(sizeof(List));
727   dlist_init(group->values,(void (*)(void*))_configfile_keyvalue_free);
728   group->values->test = (int (*)(const void*,const void*))_config_cmp_keyvalue;
729 }
730 
_configfile_group_free(wzd_configfile_group_t * group)731 static void _configfile_group_free(wzd_configfile_group_t * group)
732 {
733   WZD_ASSERT_VOID(group != NULL);
734   wzd_free(group->name);
735   if (group->comment) _configfile_keyvalue_free(group->comment);
736   dlist_destroy(group->values);
737   wzd_free(group->values);
738   wzd_free(group);
739 }
740 
_configfile_keyvalue_calloc(void)741 static wzd_configfile_keyvalue_t * _configfile_keyvalue_calloc(void)
742 {
743   wzd_configfile_keyvalue_t * kv;
744 
745   kv = wzd_malloc(sizeof(*kv));
746   WZD_ASSERT_RETURN(kv != NULL, NULL);
747   kv->key = NULL;
748   kv->value = NULL;
749 
750   return kv;
751 }
752 
_configfile_keyvalue_free(wzd_configfile_keyvalue_t * kv)753 static void _configfile_keyvalue_free(wzd_configfile_keyvalue_t * kv)
754 {
755   WZD_ASSERT_VOID(kv != NULL);
756   wzd_free(kv->key);
757   wzd_free(kv->value);
758   wzd_free(kv);
759 }
760 
config_init(wzd_configfile_t * config)761 static void config_init(wzd_configfile_t * config)
762 {
763   wzd_configfile_group_t * group;
764 
765   if (!config) return;
766   config->groups = wzd_malloc(sizeof(List));
767   list_init(config->groups,(void (*)(void *))_configfile_group_free);
768   group = wzd_malloc(sizeof(wzd_configfile_group_t));
769   _configfile_group_init(group);
770   list_ins_next(config->groups,NULL,group);
771   config->groups->test = (int (*)(const void*,const void*))_config_cmp_groupname;
772   config->parse_buffer = str_allocate();
773   config->current_group = group;
774   config->flags = CF_FILE_NONE;
775 }
776 
config_clear(wzd_configfile_t * config)777 static void config_clear(wzd_configfile_t * config)
778 {
779   if (!config) return;
780   list_destroy(config->groups);
781   wzd_free(config->groups);
782   str_deallocate(config->parse_buffer);
783 }
784 
_config_cmp_keyvalue(const char * k1,const wzd_configfile_keyvalue_t * k2)785 static int _config_cmp_keyvalue(const char *k1, const wzd_configfile_keyvalue_t *k2)
786 {
787   WZD_ASSERT(k2 != NULL);
788   if (k1 == NULL || k2->key == NULL) return (!(k1 == k2->key));
789 
790   return strcmp(k1,k2->key);
791 }
792 
_config_cmp_groupname(const char * k1,const wzd_configfile_group_t * k2)793 static int _config_cmp_groupname(const char *k1, const wzd_configfile_group_t *k2)
794 {
795   WZD_ASSERT(k2 != NULL);
796   if (k1 == NULL || k2->name == NULL) return (!(k1 == k2->name));
797 
798   return strcmp(k1,k2->name);
799 }
800 
config_line_is_comment(const char * line)801 static int config_line_is_comment(const char * line)
802 {
803   return (*line == '#' || *line == '\0' || *line == '\n');
804 }
805 
config_line_is_group(const char * line)806 static int config_line_is_group(const char * line)
807 {
808   const char * p;
809 
810   p = line;
811   if (*p != '[') return 0;
812 
813   while (*p && *p != ']')
814     p++;
815 
816   if (!*p) return 0;
817 
818   return 1;
819 }
820 
config_line_is_keyvalue(const char * line)821 static int config_line_is_keyvalue(const char * line)
822 {
823   const char * p;
824 
825   p = strchr(line,'=');
826   if (!p) return 0;
827 
828   /* key must be non-empty */
829   if (*p == line[0])
830     return 0;
831 
832   return 1;
833 }
834 
config_parse_data(wzd_configfile_t * config,const char * data,size_t length)835 static int config_parse_data(wzd_configfile_t * config, const char * data, size_t length)
836 {
837   size_t i;
838   int ret;
839 
840   if (!config || !data) return CF_ERROR_INVALID_ARGS;
841 
842   for (i = 0; i < length; i++) {
843     if (data[i] == '\n')
844     {
845       if (i > 0 && str_length(config->parse_buffer) > 0 &&
846           str_tochar(config->parse_buffer)[str_length(config->parse_buffer) - 1] == '\r')
847         str_erase (config->parse_buffer, str_length(config->parse_buffer) - 1, 1);
848 
849       /* if the line is ended with a \ then delete the last char and continue with next line
850        */
851       if (i > 0 && str_length(config->parse_buffer) > 0 &&
852           str_tochar(config->parse_buffer)[str_length(config->parse_buffer) - 1] == '\\') {
853         str_erase (config->parse_buffer, str_length(config->parse_buffer) - 1, 1);
854         continue;
855       }
856 
857       /* When a newline is encountered flush the parse buffer so that the
858        * line can be parsed.  Note that completely blank lines won't show
859        * up in the parse buffer, so they get parsed directly.
860        */
861       if (str_length(config->parse_buffer) > 0)
862         ret = config_parse_flush_buffer (config);
863       else
864         ret = config_parse_comment (config, "", 1);
865 
866       if (ret) return ret; /* propagate error */
867     } else
868       str_append_c (config->parse_buffer, data[i]);
869   }
870 
871   return CF_OK;
872 }
873 
config_parse_flush_buffer(wzd_configfile_t * config)874 static int config_parse_flush_buffer(wzd_configfile_t * config)
875 {
876   int ret;
877 
878   if (!config) return CF_ERROR_INVALID_ARGS;
879 
880 #if DEBUG
881   if ((config->flags & CF_FILE_DEBUG)) {
882     out_err(LEVEL_INFO,"flushing buffer : [ %s ]\n",str_tochar(config->parse_buffer));
883   }
884 #endif
885 
886   if (str_length(config->parse_buffer) > 0) {
887     ret = config_parse_line (config, str_tochar(config->parse_buffer), str_length(config->parse_buffer));
888     str_erase (config->parse_buffer, 0, -1);
889 
890 #if DEBUG
891   if ((config->flags & CF_FILE_DEBUG)) {
892     if (ret) {
893       out_err(LEVEL_INFO,"ERROR: config_parse_line returned %d !\n",ret);
894     }
895   }
896 #endif
897 
898     if (ret) return ret;
899   }
900 
901   return CF_OK;
902 }
903 
config_parse_line(wzd_configfile_t * config,const char * line,size_t length)904 static int config_parse_line(wzd_configfile_t * config, const char * line, size_t length)
905 {
906   const char * line_start;
907   int ret;
908 
909   if (!config || !line) return CF_ERROR_INVALID_ARGS;
910 
911   line_start = line;
912   while (isspace(*line_start)) line_start++;
913 
914   if (config_line_is_comment(line_start))
915     ret = config_parse_comment(config,line,length);
916   else if (config_line_is_group(line_start))
917     ret = config_parse_group(config,line,length - (line_start - line));
918   else if (config_line_is_keyvalue(line_start))
919     ret = config_parse_keyvalue(config,line,length - (line_start - line));
920   else
921     return CF_ERROR_PARSE;
922 
923   if (ret) return ret; /* propagate error */
924 
925   return 0;
926 }
927 
config_parse_comment(wzd_configfile_t * config,const char * line,size_t length)928 static int config_parse_comment(wzd_configfile_t * config, const char * line, size_t length)
929 {
930   wzd_configfile_keyvalue_t * kv;
931 
932   if (!config || !line) return CF_ERROR_INVALID_ARGS;
933   if (!config->current_group) return CF_ERROR_NO_CURRENT_GROUP;
934 
935   kv = _configfile_keyvalue_calloc();
936   kv->value = wzd_strndup(line,length);
937 
938   dlist_ins_next(config->current_group->values,dlist_tail(config->current_group->values),kv);
939 
940   return CF_OK;
941 }
942 
config_parse_group(wzd_configfile_t * config,const char * line,size_t length)943 static int config_parse_group(wzd_configfile_t * config, const char * line, size_t length)
944 {
945   char * groupname;
946   const char *group_name_start, *group_name_end;
947 
948   if (!config || !line) return CF_ERROR_INVALID_ARGS;
949 
950   /* advance past opening '[' */
951   group_name_start = line + 1;
952   group_name_end = line + length - 1;
953 
954   while (*group_name_end != ']')
955     group_name_end--;
956 
957   groupname = wzd_strndup(group_name_start,group_name_end - group_name_start);
958   config_add_group(config,groupname);
959   wzd_free(groupname);
960 
961   return CF_OK;
962 }
963 
config_parse_keyvalue(wzd_configfile_t * config,const char * line,size_t length)964 static int config_parse_keyvalue(wzd_configfile_t * config, const char * line, size_t length)
965 {
966   char *key, *value, *key_end, *value_start;
967   size_t key_len, value_len;
968   int ret;
969 
970   if (!config || !line) return CF_ERROR_INVALID_ARGS;
971   if (!config->current_group || !config->current_group->name) return CF_ERROR_NO_CURRENT_GROUP;
972 
973   key_end = value_start = strchr (line, '=');
974   if (key_end == NULL) return CF_ERROR_PARSE;
975 
976   key_end--;
977   value_start++;
978 
979   /* Pull the key name from the line (chomping trailing whitespace) */
980   while (isspace (*key_end))
981     key_end--;
982 
983   key_len = key_end - line + 2;
984   if (key_len > length) return CF_ERROR_PARSE;
985 
986   key = wzd_strndup (line, key_len - 1);
987 
988   /* Pull the value from the line (chugging leading whitespace)
989    */
990   while (isspace (*value_start))
991     value_start++;
992 
993   value_len = line + length - value_start;
994   while (value_len > 0 && isspace(value_start[value_len-1]))
995     value_len--;
996 
997   value = wzd_strndup (value_start, value_len);
998 
999 /*  if (config->start_group == NULL) return CF_ERROR_PARSE; */
1000 
1001   ret = config_add_key(config,config->current_group,key,value);
1002 
1003   wzd_free(key);
1004   wzd_free(value);
1005 
1006   return ret;
1007 }
1008 
config_lookup_group(const wzd_configfile_t * config,const char * groupname)1009 static wzd_configfile_group_t * config_lookup_group(const wzd_configfile_t * config, const char *groupname)
1010 {
1011   ListElmt * elmnt;
1012   wzd_configfile_group_t * group = NULL;
1013 
1014   if (!config || !groupname) return NULL;
1015 
1016   for (elmnt = list_head(config->groups); elmnt; elmnt = list_next(elmnt)) {
1017     group = list_data(elmnt);
1018     if (group && group->name && strcmp(group->name,groupname)==0) break;
1019     group = NULL;
1020   }
1021 
1022   return group;
1023 }
1024 
config_lookup_keyvalue(const wzd_configfile_t * config,wzd_configfile_group_t * group,const char * key)1025 static wzd_configfile_keyvalue_t * config_lookup_keyvalue(const wzd_configfile_t * config, wzd_configfile_group_t * group, const char * key)
1026 {
1027   DListElmt * elmnt;
1028   wzd_configfile_keyvalue_t * kv = NULL;
1029 
1030   if (!config || !group || !key) return NULL;
1031 
1032   /** \todo this should be replaced by a direct lookup in a hash table */
1033   for (elmnt = dlist_head(group->values); elmnt; elmnt = dlist_next(elmnt)) {
1034     kv = dlist_data(elmnt);
1035     if (kv && kv->key && strcmp(kv->key,key)==0) break;
1036     kv = NULL;
1037   }
1038 
1039   return kv;
1040 }
1041 
config_add_key(wzd_configfile_t * config,wzd_configfile_group_t * group,const char * key,const char * value)1042 static int config_add_key(wzd_configfile_t * config, wzd_configfile_group_t * group, const char * key, const char * value)
1043 {
1044   wzd_configfile_keyvalue_t * kv;
1045 
1046   if (!config || !group) return CF_ERROR_INVALID_ARGS;
1047 
1048   if ( (kv = config_lookup_keyvalue(config, group, key)) != NULL)
1049   {
1050     if (config->flags & CF_FILE_MERGE_MULTIPLE) {
1051       wzd_string_t * str;
1052 
1053       str = STR(kv->value);
1054       str_append_printf(str, "%s %s", VALUE_LIST_SEPARATOR, value);
1055       wzd_free(kv->value);
1056       kv->value = wzd_strdup( str_tochar(str) );
1057       str_deallocate(str);
1058 
1059       return CF_OK;
1060     }
1061 #if DEBUG
1062     out_err(LEVEL_HIGH,"*** key collision *** %s/%s: old [%s] / new [%s]\n",
1063         group->name, key, kv->value, value);
1064 #endif
1065     return CF_ERROR_KEY_ALREADY_EXISTS;
1066   }
1067 
1068   kv = _configfile_keyvalue_calloc();
1069   kv->key = wzd_strdup(key);
1070   kv->value = wzd_strdup(value);
1071 
1072   dlist_ins_next(group->values,dlist_tail(group->values),kv);
1073 
1074   return CF_OK;
1075 }
1076 
config_add_group(wzd_configfile_t * config,const char * groupname)1077 static int config_add_group(wzd_configfile_t * config, const char * groupname)
1078 {
1079   wzd_configfile_group_t * group;
1080 
1081   if (!config) return CF_ERROR_INVALID_ARGS;
1082 
1083   group = wzd_malloc(sizeof(wzd_configfile_group_t));
1084   _configfile_group_init(group);
1085   group->name = wzd_strdup(groupname);
1086   list_ins_next(config->groups,list_tail(config->groups),group);
1087 
1088   config->current_group = group;
1089 
1090   return CF_OK;
1091 }
1092 
config_set_key_comment(wzd_configfile_t * config,const char * groupname,const char * key,const char * comment)1093 static int config_set_key_comment(wzd_configfile_t * config, const char * groupname, const char * key, const char * comment)
1094 {
1095   wzd_configfile_group_t * group;
1096   wzd_configfile_keyvalue_t * kv;
1097   DListElmt * element, * tmp, * current_node;
1098 
1099   if (!config || !groupname) return CF_ERROR_INVALID_ARGS;
1100 
1101   group = config_lookup_group(config,groupname);
1102   if (!group) return CF_ERROR_GROUP_NOT_FOUND;
1103 
1104   /* find the key the comments are supposed to be associated with */
1105   element = dlist_lookup_node(group->values,(void*)key);
1106   if (!element) return CF_ERROR_NOT_FOUND;
1107 
1108   /* free existing comments for that key */
1109   tmp = element->prev;
1110   while (tmp) {
1111     kv = dlist_data(tmp);
1112 
1113     if (kv->key) break;
1114 
1115     current_node = tmp;
1116     tmp = tmp->prev;
1117     dlist_remove(group->values,current_node,(void**)&kv);
1118     _configfile_keyvalue_free(kv);
1119   }
1120 
1121   if (comment == NULL) return CF_OK;
1122 
1123   /* add our comment */
1124   kv = _configfile_keyvalue_calloc();
1125   kv->value = wzd_strdup(comment);
1126 
1127   dlist_ins_prev(group->values,element,kv);
1128 
1129 
1130   return CF_ERROR_PARSE;
1131 }
1132 
config_set_group_comment(wzd_configfile_t * config,const char * groupname,const char * comment)1133 static int config_set_group_comment(wzd_configfile_t * config, const char * groupname, const char * comment)
1134 {
1135   wzd_configfile_group_t * group;
1136 
1137   if (!config || !groupname) return CF_ERROR_INVALID_ARGS;
1138 
1139   group = config_lookup_group(config,groupname);
1140   if (!group) return CF_ERROR_GROUP_NOT_FOUND;
1141 
1142   /* remove any existing comment */
1143   if (group->comment) {
1144     _configfile_keyvalue_free(group->comment);
1145     group->comment = NULL;
1146   }
1147 
1148   if (!comment) return CF_OK;
1149 
1150   if (config_line_is_comment(comment)) {
1151     group->comment = wzd_malloc(sizeof(wzd_configfile_group_t));
1152     group->comment->key = NULL;
1153     group->comment->value = wzd_strdup(comment);
1154 
1155     return CF_OK;
1156   }
1157 
1158   return CF_ERROR_PARSE;
1159 }
1160 
config_set_top_comment(wzd_configfile_t * config,const char * comment)1161 static int config_set_top_comment(wzd_configfile_t * config, const char * comment)
1162 {
1163   ListElmt * elmnt;
1164   wzd_configfile_group_t * group;
1165   wzd_configfile_keyvalue_t * kv;
1166 
1167   if (!config->groups) return CF_ERROR_INVALID_ARGS;
1168 
1169   elmnt = list_head(config->groups);
1170   group = list_data(elmnt);
1171   /* the last group is for comments only */
1172   if (!group || group->name) return CF_ERROR_INVALID_ARGS;
1173 
1174   WZD_ASSERT(group->values != NULL);
1175 
1176   while (dlist_size(group->values)>0) {
1177     dlist_remove(group->values,dlist_tail(group->values),(void**)&kv);
1178     _configfile_keyvalue_free(kv);
1179   }
1180 
1181   if (!comment)
1182     return CF_OK;
1183 
1184   if (config_line_is_comment(comment)) {
1185     kv = _configfile_keyvalue_calloc();
1186     kv->value = wzd_strdup(comment);
1187 
1188     dlist_ins_next(group->values,NULL,kv);
1189 
1190     return CF_OK;
1191   }
1192 
1193   return CF_ERROR_PARSE;
1194 }
1195 
1196