Update documents.
[oweals/tinc.git] / cfg / cfg.c
1 /*
2     cfg.c -- cfguration code
3
4     Copyright (C) 1998 Robert van der Meulen
5                   1998-2004 Ivo Timmermans <ivo@tinc-vpn.org>
6                   2000-2004 Guus Sliepen <guus@tinc-vpn.org>
7                   2000 Cris van Pelt <tribbel@arise.dhs.org>
8
9     This program is free software; you can redistribute it and/or modify
10     it under the terms of the GNU General Public License as published by
11     the Free Software Foundation; either version 2 of the License, or
12     (at your option) any later version.
13
14     This program is distributed in the hope that it will be useful,
15     but WITHOUT ANY WARRANTY; without even the implied warranty of
16     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17     GNU General Public License for more details.
18
19     You should have received a copy of the GNU General Public License
20     along with this program; if not, write to the Free Software
21     Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
22
23     $Id$
24 */
25
26 #include "system.h"
27
28 #include "cfg/cfg.h"
29 #include "support/avl.h"
30 #include "support/xalloc.h"
31
32 static int cfg_compare(const cfg_t *a, const cfg_t *b) {
33         return strcasecmp(a->variable, b->variable) ?: (a->line - b->line) ?: strcmp(a->file, b->file);
34 }
35
36 avl_tree_t *cfg_tree_new(void) {
37         return avl_tree_new((avl_compare_t)cfg_compare, (avl_action_t)cfg_free);
38 }
39
40 void cfg_tree_del(avl_tree_t *cfgs) {
41         avl_tree_del(cfgs);
42 }
43
44 cfg_t *cfg_new(void) {
45         cfg_t *cfg;
46
47         return clear(new(cfg));
48 }
49
50 void cfg_free(cfg_t *cfg) {
51         replace(cfg->variable, NULL);
52         replace(cfg->value, NULL);
53         replace(cfg->file, NULL);
54         free(cfg);
55 }
56
57 void cfg_add(avl_tree_t *cfgs, cfg_t *cfg) {
58         avl_add(cfgs, cfg);
59 }
60
61 cfg_t *cfg_get(const avl_tree_t *cfgs, char *variable) {
62         cfg_t search, *cfg;
63
64         search.variable = variable;
65         search.file = "";
66         search.line = 0;
67
68         cfg = avl_get_closest_greater(cfgs, &search);
69
70         if(!cfg || strcasecmp(cfg->variable, variable))
71                 return NULL;
72
73         return cfg;
74 }
75
76 cfg_t *cfg_get_next(const avl_tree_t *cfgs, const cfg_t *cfg) {
77         avl_node_t *avl;
78         cfg_t *next;
79
80         avl = avl_get_node(cfgs, cfg);
81
82         if(avl && avl->next) {
83                 next = avl->next->data;
84
85                 if(!strcasecmp(next->variable, cfg->variable))
86                         return next;
87         }
88
89         return NULL;
90 }
91
92 bool cfg_bool(const cfg_t *cfg, const bool def, bool *result) {
93         if(!cfg) {
94                 *result = def;
95                 return true;
96         }
97
98         if(!strcasecmp(cfg->value, "yes")) {
99                 *result = true;
100                 return true;
101         } else if(!strcasecmp(cfg->value, "no")) {
102                 *result = false;
103                 return true;
104         }
105
106         logger(LOG_ERR, _("cfg: \"yes\" or \"no\" expected for configuration variable %s in %s line %d"),
107                         cfg->variable, cfg->file, cfg->line);
108
109         return false;
110 }
111
112 bool cfg_int(const cfg_t *cfg, const int def, int *result) {
113         if(!cfg) {
114                 *result = def;
115                 return true;
116         }
117
118         if(sscanf(cfg->value, "%d", result) == 1)
119                 return true;
120
121         logger(LOG_ERR, _("cfg: integer expected for configuration variable %s in %s line %d"),
122                    cfg->variable, cfg->file, cfg->line);
123
124         return false;
125 }
126
127 bool cfg_string(const cfg_t *cfg, const char *def, char **result) {
128         if(!cfg) {
129                 *result = def ? xstrdup(def) : NULL;
130                 return true;
131         }
132
133         *result = xstrdup(cfg->value);
134
135         return true;
136 }
137
138 bool cfg_choice(const cfg_t *cfg, const cfg_choice_t *choice, const int def, int *result) {
139         int i;
140         
141         if(!cfg) {
142                 *result = def;
143                 return true;
144         }
145
146         for(i = 0; choice[i].key; i++) {
147                 if(!strcasecmp(cfg->variable, choice[i].key)) {
148                         *result = choice[i].value;
149                         return true;
150                 }
151         }
152
153         logger(LOG_ERR, _("cfg: invalid choice for configuration variable %s in %s line %d"), 
154                         cfg->variable, cfg->file, cfg->line);
155
156         return false;
157 }       
158
159 bool cfg_period(const cfg_t *cfg, const int def, int *result) {
160         char unit;
161         
162         if(!cfg) {
163                 *result = def;
164                 return true;
165         }
166
167         if(sscanf(cfg->value, "%d%c", result, &unit) == 2) {
168                 switch(unit) {
169                         case 's':
170                                 break;
171                         case 'm':
172                                 *result *= 60;
173                                 break;
174                         case 'h':
175                                 *result *= 60 * 60;
176                                 break;
177                         case 'd':
178                                 *result *= 60 * 60 * 24;
179                                 break;
180                         case 'W':
181                                 *result *= 60 * 60 * 24 * 7;
182                                 break;
183                         case 'M':
184                                 *result *= 60 * 60 * 24 * 30;
185                                 break;
186                         case 'Y':
187                                 *result *= 60 * 60 * 24 * 365;
188                                 break;
189                         default:
190                                 logger(LOG_ERR, _("cfg: invalid period for configuration variable %s in %s line %d"),
191                                                 cfg->variable, cfg->file, cfg->line);
192                                 return false;
193                 }
194                 return true;
195         }
196
197         if(sscanf(cfg->value, "%d", result) == 1)
198                 return true;
199
200         logger(LOG_ERR, _("cfg: period expected for configuration variable %s in %s line %d"),
201                         cfg->variable, cfg->file, cfg->line);
202
203         return false;
204 }
205
206 static char *readline(FILE *fp, char **buf, size_t *buflen) {
207         char *newline = NULL;
208         char *p;
209         char *line;                                     /* The array that contains everything that has been read so far */
210         char *idx;                                      /* Read into this pointer, which points to an offset within line */
211         size_t size, newsize;                           /* The size of the current array pointed to by line */
212         size_t maxlen;                                  /* Maximum number of characters that may be read with fgets.  This is newsize - oldsize. */
213
214         if(feof(fp))
215                 return NULL;
216
217         if(buf && buflen) {
218                 size = *buflen;
219                 line = *buf;
220         } else {
221                 dim(line, size = 100);
222         }
223
224         maxlen = size;
225         idx = line;
226         *idx = 0;
227
228         for(;;) {
229                 errno = 0;
230                 p = fgets(idx, maxlen, fp);
231
232                 if(!p) {
233                         if(feof(fp))
234                                 break;
235
236                         logger(LOG_ERR, _("cfg: error while reading: %s"), strerror(errno));
237                         free(line);
238                         return NULL;
239                 }
240
241                 newline = strchr(p, '\n');
242
243                 if(!newline) {
244                         idx = &line[size - 1];
245                         maxlen = size + 1;
246                         redim(line, size *= 2);
247                 } else {
248                         *newline = '\0';
249                         break;
250                 }
251         }
252
253         if(buf && buflen) {
254                 *buflen = size;
255                 *buf = line;
256         }
257
258         return line;
259 }
260
261 bool cfg_read_file(avl_tree_t *cfgs, const char *fname) {
262         FILE *fp;
263         char *buffer, *line;
264         char *variable, *value;
265         int lineno = 0;
266         int len;
267         bool result = false;
268         bool ignore = false;
269         cfg_t *cfg;
270         size_t bufsize;
271
272         fp = fopen(fname, "r");
273
274         if(!fp) {
275                 logger(LOG_ERR, _("cfg: error opening %s: %s"), fname, strerror(errno));
276                 return false;
277         }
278
279         dim(buffer, bufsize = 100);
280
281         for(;;) {
282                 line = readline(fp, &buffer, &bufsize);
283
284                 if(!line)
285                         break;
286
287                 if(feof(fp)) {
288                         result = true;
289                         break;
290                 }
291
292                 lineno++;
293
294                 if(!*line || *line == '#')
295                         continue;
296
297                 if(ignore) {
298                         if(!strncmp(line, "-----END", 8))
299                                 ignore = false;
300                         continue;
301                 }
302                 
303                 if(!strncmp(line, "-----BEGIN", 10)) {
304                         ignore = true;
305                         continue;
306                 }
307
308                 variable = value = line;
309
310                 len = strcspn(value, "\t =");
311                 value += len;
312                 value += strspn(value, "\t ");
313                 if(*value == '=') {
314                         value++;
315                         value += strspn(value, "\t ");
316                 }
317                 variable[len] = '\0';
318
319                 if(!*value) {
320                         logger(LOG_ERR, _("cfg: no value for variable %s on line %d while reading cfg file %s"),
321                                         variable, lineno, fname);
322                         break;
323                 }
324
325                 cfg = cfg_new();
326                 replace(cfg->variable, variable);
327                 replace(cfg->value, value);
328                 replace(cfg->file, fname);
329                 cfg->line = lineno;
330
331                 cfg_add(cfgs, cfg);
332         }
333
334         free(buffer);
335         fclose(fp);
336
337         return result;
338 }