root/tags/0.10.0/cfg.c

Revision 547, 20.1 kB (checked in by reinelt, 4 years ago)

[lcd4linux @ 2005-05-08 04:32:43 by reinelt]
CodingStyle? added and applied

Line 
1/* $Id: cfg.c,v 1.47 2005/05/08 04:32:43 reinelt Exp $^
2 *
3 * config file stuff
4 *
5 * Copyright (C) 1999, 2000 Michael Reinelt <reinelt@eunet.at>
6 * Copyright (C) 2004 The LCD4Linux Team <lcd4linux-devel@users.sourceforge.net>
7 *
8 * This file is part of LCD4Linux.
9 *
10 * LCD4Linux is free software; you can redistribute it and/or modify
11 * it under the terms of the GNU General Public License as published by
12 * the Free Software Foundation; either version 2, or (at your option)
13 * any later version.
14 *
15 * LCD4Linux is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18 * GNU General Public License for more details.
19 *
20 * You should have received a copy of the GNU General Public License
21 * along with this program; if not, write to the Free Software
22 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
23 *
24 *
25 * $Log: cfg.c,v $
26 * Revision 1.47  2005/05/08 04:32:43  reinelt
27 * CodingStyle added and applied
28 *
29 * Revision 1.46  2005/05/02 05:15:46  reinelt
30 * make busy-flag checking configurable for LCD-Linux driver
31 *
32 * Revision 1.45  2005/01/18 06:30:21  reinelt
33 * added (C) to all copyright statements
34 *
35 * Revision 1.44  2005/01/17 06:29:24  reinelt
36 * added software-controlled backlight support to HD44780
37 *
38 * Revision 1.43  2004/11/29 04:42:06  reinelt
39 * removed the 99999 msec limit on widget update time (thanks to Petri Damsten)
40 *
41 * Revision 1.42  2004/06/26 12:04:59  reinelt
42 *
43 * uh-oh... the last CVS log message messed up things a lot...
44 *
45 * Revision 1.41  2004/06/26 09:27:20  reinelt
46 *
47 * added '-W' to CFLAGS
48 * changed all C++ comments to C ones
49 * cleaned up a lot of signed/unsigned mistakes
50 *
51 * Revision 1.40  2004/06/20 10:09:52  reinelt
52 *
53 * 'const'ified the whole source
54 *
55 * Revision 1.39  2004/03/11 06:39:58  reinelt
56 * big patch from Martin:
57 * - reuse filehandles
58 * - memory leaks fixed
59 * - earlier busy-flag checking with HD44780
60 * - reuse memory for strings in RESULT and hash
61 * - netdev_fast to wavid time-consuming regex
62 *
63 * Revision 1.38  2004/03/08 16:26:26  reinelt
64 * re-introduced \nnn (octal) characters in strings
65 * text widgets can have a 'update' speed of 0 which means 'never'
66 * (may be used for static content)
67 *
68 * Revision 1.37  2004/03/06 20:31:16  reinelt
69 * Complete rewrite of the evaluator to get rid of the code
70 * from mark Morley (because of license issues).
71 * The new Evaluator does a pre-compile of expressions, and
72 * stores them in trees. Therefore it should be reasonable faster...
73 *
74 * Revision 1.36  2004/03/03 03:47:04  reinelt
75 * big patch from Martin Hejl:
76 * - use qprintf() where appropriate
77 * - save CPU cycles on gettimeofday()
78 * - add quit() functions to free allocated memory
79 * - fixed lots of memory leaks
80 *
81 * Revision 1.35  2004/03/01 04:29:51  reinelt
82 * cfg_number() returns -1 on error, 0 if value not found (but default val used),
83 *  and 1 if value was used from the configuration.
84 * HD44780 driver adopted to new cfg_number()
85 * Crystalfontz 631 driver nearly finished
86 *
87 * Revision 1.34  2004/02/18 06:39:20  reinelt
88 * T6963 driver for graphic displays finished
89 *
90 * Revision 1.33  2004/02/01 18:08:50  reinelt
91 * removed strtok() from layout processing (took me hours to find this bug)
92 * further strtok() removind should be done!
93 *
94 * Revision 1.32  2004/01/30 20:57:55  reinelt
95 * HD44780 patch from Martin Hejl
96 * dmalloc integrated
97 *
98 * Revision 1.31  2004/01/29 04:40:02  reinelt
99 * every .c file includes "config.h" now
100 *
101 * Revision 1.30  2004/01/22 07:57:45  reinelt
102 * several bugs fixed where segfaulting on layout>display
103 * Crystalfontz driver optimized, 632 display already works
104 *
105 * Revision 1.29  2004/01/18 06:54:08  reinelt
106 * bug in expr.c fixed (thanks to Xavier)
107 * some progress with /proc/stat parsing
108 *
109 * Revision 1.28  2004/01/16 05:04:53  reinelt
110 * started plugin proc_stat which should parse /proc/stat
111 * which again is a paint in the a**
112 * thinking over implementation methods of delta functions
113 * (CPU load, ...)
114 *
115 * Revision 1.27  2004/01/14 11:33:00  reinelt
116 * new plugin 'uname' which does what it's called
117 * text widget nearly finished
118 * first results displayed on MatrixOrbital
119 *
120 * Revision 1.26  2004/01/11 18:26:02  reinelt
121 * further widget and layout processing
122 *
123 * Revision 1.25  2004/01/11 09:26:15  reinelt
124 * layout starts to exist...
125 *
126 * Revision 1.24  2004/01/10 20:22:33  reinelt
127 * added new function 'cfg_list()' (not finished yet)
128 * added layout.c (will replace processor.c someday)
129 * added widget_text.c (will be the first and most important widget)
130 * modified lcd4linux.c so that old-style configs should work, too
131 *
132 * Revision 1.23  2004/01/09 04:16:06  reinelt
133 * added 'section' argument to cfg_get(), but NULLed it on all calls by now.
134 *
135 * Revision 1.22  2004/01/08 06:00:28  reinelt
136 * allowed '.' in key names
137 * allowed empty section keys (not only "section anything {", but "anything {")
138 *
139 * Revision 1.21  2004/01/08 05:28:12  reinelt
140 * Luk Claes added to AUTHORS
141 * cfg: section handling ('{}') added
142 *
143 * Revision 1.20  2004/01/07 10:15:41  reinelt
144 * small glitch in evaluator fixed
145 * made config table sorted and access with bsearch(),
146 * which should be much faster
147 *
148 * Revision 1.19  2003/12/19 05:35:14  reinelt
149 * renamed 'client' to 'plugin'
150 *
151 * Revision 1.18  2003/10/11 06:01:52  reinelt
152 *
153 * renamed expression.{c,h} to client.{c,h}
154 * added config file client
155 * new functions 'AddNumericVariable()' and 'AddStringVariable()'
156 * new parameter '-i' for interactive mode
157 *
158 * Revision 1.17  2003/10/05 17:58:50  reinelt
159 * libtool junk; copyright messages cleaned up
160 *
161 * Revision 1.16  2003/09/09 06:54:43  reinelt
162 * new function 'cfg_number()'
163 *
164 * Revision 1.15  2003/08/24 05:17:58  reinelt
165 * liblcd4linux patch from Patrick Schemitz
166 *
167 * Revision 1.14  2003/08/14 03:47:40  reinelt
168 * remove PID file if driver initialisation fails
169 *
170 * Revision 1.13  2003/02/22 07:53:10  reinelt
171 * cfg_get(key,defval)
172 *
173 * Revision 1.12  2001/03/09 12:14:24  reinelt
174 *
175 * minor cleanups
176 *
177 * Revision 1.11  2001/03/08 15:25:38  ltoetsch
178 * improved exec
179 *
180 * Revision 1.10  2001/03/07 18:10:21  ltoetsch
181 * added e(x)ec commands
182 *
183 * Revision 1.9  2000/08/10 09:44:09  reinelt
184 *
185 * new debugging scheme: error(), info(), debug()
186 * uses syslog if in daemon mode
187 *
188 * Revision 1.8  2000/07/31 06:46:35  reinelt
189 *
190 * eliminated some compiler warnings with glibc
191 *
192 * Revision 1.7  2000/04/15 11:13:54  reinelt
193 *
194 * added '-d' (debugging) switch
195 * added several debugging messages
196 * removed config entry 'Delay' for HD44780 driver
197 * delay loop for HD44780 will be calibrated automatically
198 *
199 * Revision 1.6  2000/04/03 04:46:38  reinelt
200 *
201 * added '-c key=val' option
202 *
203 * Revision 1.5  2000/03/28 07:22:15  reinelt
204 *
205 * version 0.95 released
206 * X11 driver up and running
207 * minor bugs fixed
208 *
209 * Revision 1.4  2000/03/26 20:00:44  reinelt
210 *
211 * README.Raster added
212 *
213 * Revision 1.3  2000/03/26 19:03:52  reinelt
214 *
215 * more Pixmap renaming
216 * quoting of '#' in config file
217 *
218 * Revision 1.2  2000/03/10 17:36:02  reinelt
219 *
220 * first unstable but running release
221 *
222 * Revision 1.1  2000/03/10 11:40:47  reinelt
223 * *** empty log message ***
224 *
225 * Revision 1.3  2000/03/07 11:01:34  reinelt
226 *
227 * system.c cleanup
228 *
229 * Revision 1.2  2000/03/06 06:04:06  reinelt
230 *
231 * minor cleanups
232 *
233 */
234
235/*
236 * exported functions:
237 *
238 * cfg_init (source)
239 *   read configuration from source
240 *   returns  0 if successful
241 *   returns -1 in case of an error
242 *
243 * cfg_source (void)
244 *   returns the file the configuration was read from
245 *
246 * cfg_cmd (arg)
247 *   allows us to overwrite entries in the
248 *   config-file from the command line.
249 *   arg is 'key=value'
250 *   cfg_cmd can be called _before_ cfg_read()
251 *   returns 0 if ok, -1 if arg cannot be parsed
252 *
253 * cfg_list (section)
254 *   returns a list of all keys in the specified section
255 *   This list was allocated be cfg_list() and must be
256 *   freed by the caller!
257 *
258 * cfg_get_raw (section, key, defval)
259 *   return the a value for a given key in a given section
260 *   or <defval> if key does not exist. Does NOT evaluate
261 *   the expression. Therefore used to get the expression
262 *   itself!
263 *
264 * cfg_get (section, key, defval)
265 *   return the a value for a given key in a given section
266 *   or <defval> if key does not exist. The specified
267 *   value in the config is treated as a expression and
268 *   is evaluated!
269 *
270 * cfg_number (section, key, defval, min, int max, *value)
271 *   return the a value for a given key in a given section
272 *   convert it into a number with syntax checking
273 *   check if its in a given range. As it uses cfg_get()
274 *   internally, the evaluator is used here, too.
275 *
276 */
277
278
279#include "config.h"
280
281#include <stdlib.h>
282#include <stdio.h>
283#include <string.h>
284#include <ctype.h>
285#include <errno.h>
286
287#include <unistd.h>
288#include <sys/stat.h>
289
290#include "debug.h"
291#include "evaluator.h"
292#include "cfg.h"
293
294#ifdef WITH_DMALLOC
295#include <dmalloc.h>
296#endif
297
298typedef struct {
299    char *key;
300    char *val;
301    int lock;
302} ENTRY;
303
304
305static char *Config_File = NULL;
306static ENTRY *Config = NULL;
307static int nConfig = 0;
308
309
310/* bsearch compare function for config entries */
311static int c_lookup(const void *a, const void *b)
312{
313    char *key = (char *) a;
314    ENTRY *entry = (ENTRY *) b;
315
316    return strcasecmp(key, entry->key);
317}
318
319
320/* qsort compare function for variables */
321static int c_sort(const void *a, const void *b)
322{
323    ENTRY *ea = (ENTRY *) a;
324    ENTRY *eb = (ENTRY *) b;
325
326    return strcasecmp(ea->key, eb->key);
327}
328
329
330/* remove leading and trailing whitespace */
331static char *strip(char *s, const int strip_comments)
332{
333    char *p;
334
335    while (isblank(*s))
336  s++;
337    for (p = s; *p; p++) {
338  if (*p == '"')
339      do
340    p++;
341      while (*p && *p != '\n' && *p != '"');
342  if (*p == '\'')
343      do
344    p++;
345      while (*p && *p != '\n' && *p != '\'');
346  if (*p == '\n' || (strip_comments && *p == '#' && (p == s || *(p - 1) != '\\'))) {
347      *p = '\0';
348      break;
349  }
350    }
351    for (p--; p > s && isblank(*p); p--)
352  *p = '\0';
353    return s;
354}
355
356
357/* unquote a string */
358static char *dequote(char *string)
359{
360    int quote = 0;
361    char *s = string;
362    char *p = string;
363
364    do {
365  if (*s == '\'') {
366      quote = !quote;
367      *p++ = *s;
368  } else if (quote && *s == '\\') {
369      s++;
370      if (*s >= '0' && *s <= '7') {
371    int n;
372    unsigned int c = 0;
373    sscanf(s, "%3o%n", &c, &n);
374    if (c == 0 || c > 255) {
375        error("WARNING: illegal '\\' in <%s>", string);
376    } else {
377        *p++ = c;
378        s += n - 1;
379    }
380      } else {
381    *p++ = *s;
382      }
383  } else {
384      *p++ = *s;
385  }
386    } while (*s++);
387
388    return string;
389}
390
391
392/* which if a string contains only valid chars */
393/* i.e. start with a char and contains chars and nums */
394static int validchars(const char *string)
395{
396    const char *c;
397
398    for (c = string; *c; c++) {
399  /* first and following chars */
400  if ((*c >= 'A' && *c <= 'Z') || (*c >= 'a' && *c <= 'z') || (*c == '_'))
401      continue;
402  /* only following chars */
403  if ((c > string) && ((*c >= '0' && *c <= '9') || (*c == '.') || (*c == '-')))
404      continue;
405  return 0;
406    }
407    return 1;
408}
409
410
411static void cfg_add(const char *section, const char *key, const char *val, const int lock)
412{
413    char *buffer;
414    ENTRY *entry;
415
416    /* allocate buffer  */
417    buffer = malloc(strlen(section) + strlen(key) + 2);
418    *buffer = '\0';
419
420    /* prepare section.key */
421    if (section != NULL && *section != '\0') {
422  strcpy(buffer, section);
423  strcat(buffer, ".");
424    }
425    strcat(buffer, key);
426
427    /* does the key already exist? */
428    entry = bsearch(buffer, Config, nConfig, sizeof(ENTRY), c_lookup);
429
430    if (entry != NULL) {
431  if (entry->lock > lock)
432      return;
433  debug("Warning: key <%s>: value <%s> overwritten with <%s>", buffer, entry->val, val);
434  free(buffer);
435  if (entry->val)
436      free(entry->val);
437  entry->val = dequote(strdup(val));
438  return;
439    }
440
441    nConfig++;
442    Config = realloc(Config, nConfig * sizeof(ENTRY));
443    Config[nConfig - 1].key = buffer;
444    Config[nConfig - 1].val = dequote(strdup(val));
445    Config[nConfig - 1].lock = lock;
446
447    qsort(Config, nConfig, sizeof(ENTRY), c_sort);
448
449}
450
451
452int cfg_cmd(const char *arg)
453{
454    char *key, *val;
455    char buffer[256];
456
457    strncpy(buffer, arg, sizeof(buffer));
458    key = strip(buffer, 0);
459    for (val = key; *val; val++) {
460  if (*val == '=') {
461      *val++ = '\0';
462      break;
463  }
464    }
465    if (*key == '\0' || *val == '\0')
466  return -1;
467    if (!validchars(key))
468  return -1;
469    cfg_add("", key, val, 1);
470    return 0;
471}
472
473
474char *cfg_list(const char *section)
475{
476    int i, len;
477    char *key, *list;
478
479    /* calculate key length */
480    len = strlen(section) + 1;
481
482    /* prepare search key */
483    key = malloc(len + 1);
484    strcpy(key, section);
485    strcat(key, ".");
486
487    /* start with empty string */
488    list = malloc(1);
489    *list = '\0';
490
491    /* search matching entries */
492    for (i = 0; i < nConfig; i++) {
493  if (strncasecmp(Config[i].key, key, len) == 0) {
494      list = realloc(list, strlen(list) + strlen(Config[i].key) - len + 2);
495      if (*list != '\0')
496    strcat(list, "|");
497      strcat(list, Config[i].key + len);
498  }
499    }
500
501    free(key);
502    return list;
503}
504
505
506static char *cfg_lookup(const char *section, const char *key)
507{
508    int len;
509    char *buffer;
510    ENTRY *entry;
511
512    /* calculate key length */
513    len = strlen(key) + 1;
514    if (section != NULL)
515  len += strlen(section) + 1;
516
517    /* allocate buffer  */
518    buffer = malloc(len);
519    *buffer = '\0';
520
521    /* prepare section:key */
522    if (section != NULL && *section != '\0') {
523  strcpy(buffer, section);
524  strcat(buffer, ".");
525    }
526    strcat(buffer, key);
527
528    /* search entry */
529    entry = bsearch(buffer, Config, nConfig, sizeof(ENTRY), c_lookup);
530
531    /* free buffer again */
532    free(buffer);
533
534    if (entry != NULL)
535  return entry->val;
536
537    return NULL;
538}
539
540
541char *cfg_get_raw(const char *section, const char *key, const char *defval)
542{
543    char *val = cfg_lookup(section, key);
544
545    if (val != NULL)
546  return val;
547    return (char *) defval;
548}
549
550
551char *cfg_get(const char *section, const char *key, const char *defval)
552{
553    char *expression;
554    char *retval;
555    void *tree = NULL;
556    RESULT result = { 0, 0, 0, NULL };
557
558    expression = cfg_lookup(section, key);
559
560    if (expression != NULL) {
561  if (*expression == '\0')
562      return "";
563  if (Compile(expression, &tree) == 0 && Eval(tree, &result) == 0) {
564      retval = strdup(R2S(&result));
565      DelTree(tree);
566      DelResult(&result);
567      return (retval);
568  }
569  DelTree(tree);
570  DelResult(&result);
571    }
572    if (defval)
573  return strdup(defval);
574    return NULL;
575}
576
577
578int cfg_number(const char *section, const char *key, const int defval, const int min, const int max, int *value)
579{
580    char *expression;
581    void *tree = NULL;
582    RESULT result = { 0, 0, 0, NULL };
583
584    /* start with default value */
585    /* in case of an (uncatched) error, you have the */
586    /* default value set, which may be handy... */
587    *value = defval;
588
589    expression = cfg_get_raw(section, key, NULL);
590    if (expression == NULL || *expression == '\0') {
591  return 0;
592    }
593
594    if (Compile(expression, &tree) != 0) {
595  DelTree(tree);
596  return -1;
597    }
598    if (Eval(tree, &result) != 0) {
599  DelTree(tree);
600  DelResult(&result);
601  return -1;
602    }
603    *value = R2N(&result);
604    DelTree(tree);
605    DelResult(&result);
606
607    if (*value < min) {
608  error("bad '%s' value '%d' in %s, minimum is %d", key, *value, cfg_source(), min);
609  *value = min;
610  return -1;
611    }
612
613    if (max > min && max != -1 && *value > max) {
614  error("bad '%s' value '%d' in %s, maximum is %d", key, *value, cfg_source(), max);
615  *value = max;
616  return -1;
617    }
618
619    return 1;
620}
621
622
623static int cfg_check_source(const char *file)
624{
625    /* as passwords and commands are stored in the config file,
626     * we will check that:
627     * - file is a normal file (or /dev/null)
628     * - file owner is owner of program
629     * - file is not accessible by group
630     * - file is not accessible by other
631     */
632
633    struct stat stbuf;
634    uid_t uid, gid;
635    int error;
636
637    uid = geteuid();
638    gid = getegid();
639
640    if (stat(file, &stbuf) == -1) {
641  error("stat(%s) failed: %s", file, strerror(errno));
642  return -1;
643    }
644    if (S_ISCHR(stbuf.st_mode) && strcmp(file, "/dev/null") == 0)
645  return 0;
646
647    error = 0;
648    if (!S_ISREG(stbuf.st_mode)) {
649  error("security error: '%s' is not a regular file", file);
650  error = -1;
651    }
652    if (stbuf.st_uid != uid || stbuf.st_gid != gid) {
653  error("security error: owner and/or group of '%s' don't match", file);
654  error = -1;
655    }
656    if (stbuf.st_mode & S_IRWXG || stbuf.st_mode & S_IRWXO) {
657  error("security error: group or other have access to '%s'", file);
658  error = -1;
659    }
660    return error;
661}
662
663
664static int cfg_read(const char *file)
665{
666    FILE *stream;
667    char buffer[256];
668    char section[256];
669    char *line, *key, *val, *end;
670    int section_open, section_close;
671    int error, lineno;
672
673    stream = fopen(file, "r");
674    if (stream == NULL) {
675  error("open(%s) failed: %s", file, strerror(errno));
676  return -1;
677    }
678
679    /* start with empty section */
680    strcpy(section, "");
681
682    error = 0;
683    lineno = 0;
684    while ((line = fgets(buffer, 256, stream)) != NULL) {
685
686  /* increment line number */
687  lineno++;
688
689  /* skip empty lines */
690  if (*(line = strip(line, 1)) == '\0')
691      continue;
692
693  /* reset section flags */
694  section_open = 0;
695  section_close = 0;
696
697  /* key is first word */
698  key = line;
699
700  /* search first blank between key and value */
701  for (val = line; *val; val++) {
702      if (isblank(*val)) {
703    *val++ = '\0';
704    break;
705      }
706  }
707
708  /* strip value */
709  val = strip(val, 1);
710
711  /* search end of value */
712  if (*val)
713      for (end = val; *(end + 1); end++);
714  else
715      end = val;
716
717  /* if last char is '{', a section has been opened */
718  if (*end == '{') {
719      section_open = 1;
720      *end = '\0';
721      val = strip(val, 0);
722  }
723
724  /* provess "value" in double-quotes */
725  if (*val == '"' && *end == '"') {
726      *end = '\0';
727      val++;
728  }
729
730  /* if key is '}', a section has been closed */
731  if (strcmp(key, "}") == 0) {
732      section_close = 1;
733      *key = '\0';
734  }
735
736  /* sanity check: '}' should be the only char in a line */
737  if (section_close && (section_open || *val != '\0')) {
738      error("error in config file '%s' line %d: garbage after '}'", file, lineno);
739      error = 1;
740      break;
741  }
742
743  /* check key for valid chars */
744  if (!validchars(key)) {
745      error("error in config file '%s' line %d: key '%s' is invalid", file, lineno, key);
746      error = 1;
747      break;
748  }
749
750  /* on section-open, check value for valid chars */
751  if (section_open && !validchars(val)) {
752      error("error in config file '%s' line %d: section '%s' is invalid", file, lineno, val);
753      error = 1;
754      break;
755  }
756
757  /* on section-open, append new section name */
758  if (section_open) {
759      /* is the section[] array big enough? */
760      if (strlen(section) + strlen(key) + 3 > sizeof(section)) {
761    error("error in config file '%s' line %d: section buffer overflow", file, lineno);
762    error = 1;
763    break;
764      }
765      if (*section != '\0')
766    strcat(section, ".");
767      strcat(section, key);
768      if (*val != '\0') {
769    strcat(section, ":");
770    strcat(section, val);
771      }
772      continue;
773  }
774
775  /* on section-close, remove last section name */
776  if (section_close) {
777      /* sanity check: section already empty? */
778      if (*section == '\0') {
779    error("error in config file '%s' line %d: unmatched closing brace", file, lineno);
780    error = 1;
781    break;
782      }
783
784      end = strrchr(section, '.');
785      if (end == NULL)
786    *section = '\0';
787      else
788    *end = '\0';
789      continue;
790  }
791
792  /* finally: add key */
793  cfg_add(section, key, val, 0);
794
795    }
796
797    /* sanity check: are the braces balanced? */
798    if (!error && *section != '\0') {
799  error("error in config file '%s' line %d: unbalanced braces", file, lineno);
800  error = 1;
801    }
802
803    fclose(stream);
804
805    return -error;
806}
807
808
809int cfg_init(const char *file)
810{
811    if (cfg_check_source(file) == -1) {
812  return -1;
813    }
814
815    if (cfg_read(file) < 0)
816  return -1;
817
818    if (Config_File)
819  free(Config_File);
820    Config_File = strdup(file);
821
822    return 0;
823}
824
825
826char *cfg_source(void)
827{
828    if (Config_File)
829  return Config_File;
830    else
831  return "";
832}
833
834
835int cfg_exit(void)
836{
837    int i;
838    for (i = 0; i < nConfig; i++) {
839  if (Config[i].key)
840      free(Config[i].key);
841  if (Config[i].val)
842      free(Config[i].val);
843    }
844
845    if (Config) {
846  free(Config);
847  Config = NULL;
848    }
849
850    if (Config_File) {
851  free(Config_File);
852  Config_File = NULL;
853    }
854
855    return 0;
856}
Note: See TracBrowser for help on using the browser.