libmultipath: use explicit 'config' argument for configuration file parsing
[multipath-tools/.git] / libmultipath / parser.c
1 /*
2  * Part:        Configuration file parser/reader. Place into the dynamic
3  *              data structure representation the conf file
4  *
5  * Version:     $Id: parser.c,v 1.0.3 2003/05/11 02:28:03 acassen Exp $
6  *
7  * Author:      Alexandre Cassen, <acassen@linux-vs.org>
8  *
9  *              This program is distributed in the hope that it will be useful,
10  *              but WITHOUT ANY WARRANTY; without even the implied warranty of
11  *              MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
12  *              See the GNU General Public License for more details.
13  *
14  *              This program is free software; you can redistribute it and/or
15  *              modify it under the terms of the GNU General Public License
16  *              as published by the Free Software Foundation; either version
17  *              2 of the License, or (at your option) any later version.
18  */
19
20 #include <syslog.h>
21 #include <errno.h>
22
23 #include "vector.h"
24 #include "config.h"
25 #include "parser.h"
26 #include "memory.h"
27 #include "debug.h"
28
29 /* local vars */
30 static int sublevel = 0;
31 static vector keywords = NULL;
32 static vector *keywords_addr = NULL;
33 static int line_nr;
34
35 void set_current_keywords (vector *k)
36 {
37         keywords_addr = k;
38         keywords = NULL;
39 }
40
41 int
42 keyword_alloc(vector keywords, char *string,
43               int (*handler) (struct config *, vector),
44               int (*print) (struct config *, char *, int, void *), int unique)
45 {
46         struct keyword *keyword;
47
48         keyword = (struct keyword *) MALLOC(sizeof (struct keyword));
49
50         if (!keyword)
51                 return 1;
52
53         if (!vector_alloc_slot(keywords)) {
54                 FREE(keyword);
55                 return 1;
56         }
57         keyword->string = string;
58         keyword->handler = handler;
59         keyword->print = print;
60         keyword->unique = unique;
61
62         vector_set_slot(keywords, keyword);
63
64         return 0;
65 }
66
67 int
68 install_keyword_root(char *string, int (*handler) (struct config *, vector))
69 {
70         int r = keyword_alloc(keywords, string, handler, NULL, 1);
71         if (!r)
72                 *keywords_addr = keywords;
73         return r;
74 }
75
76 void
77 install_sublevel(void)
78 {
79         sublevel++;
80 }
81
82 void
83 install_sublevel_end(void)
84 {
85         sublevel--;
86 }
87
88 int
89 _install_keyword(char *string, int (*handler) (struct config *, vector),
90                  int (*print) (struct config *, char *, int, void *), int unique)
91 {
92         int i = 0;
93         struct keyword *keyword;
94
95         /* fetch last keyword */
96         keyword = VECTOR_SLOT(keywords, VECTOR_SIZE(keywords) - 1);
97
98         /* position to last sub level */
99         for (i = 0; i < sublevel; i++)
100                 keyword =
101                     VECTOR_SLOT(keyword->sub, VECTOR_SIZE(keyword->sub) - 1);
102
103         /* First sub level allocation */
104         if (!keyword->sub)
105                 keyword->sub = vector_alloc();
106
107         if (!keyword->sub)
108                 return 1;
109
110         /* add new sub keyword */
111         return keyword_alloc(keyword->sub, string, handler, print, unique);
112 }
113
114 void
115 free_keywords(vector keywords)
116 {
117         struct keyword *keyword;
118         int i;
119
120         if (!keywords)
121                 return;
122
123         for (i = 0; i < VECTOR_SIZE(keywords); i++) {
124                 keyword = VECTOR_SLOT(keywords, i);
125                 if (keyword->sub)
126                         free_keywords(keyword->sub);
127                 FREE(keyword);
128         }
129         vector_free(keywords);
130 }
131
132 struct keyword *
133 find_keyword(vector v, char * name)
134 {
135         struct keyword *keyword;
136         int i;
137         int len;
138
139         if (!name || !keywords)
140                 return NULL;
141
142         if (!v)
143                 v = keywords;
144
145         len = strlen(name);
146
147         for (i = 0; i < VECTOR_SIZE(v); i++) {
148                 keyword = VECTOR_SLOT(v, i);
149                 if ((strlen(keyword->string) == len) &&
150                     !strcmp(keyword->string, name))
151                         return keyword;
152                 if (keyword->sub) {
153                         keyword = find_keyword(keyword->sub, name);
154                         if (keyword)
155                                 return keyword;
156                 }
157         }
158         return NULL;
159 }
160
161 int
162 snprint_keyword(char *buff, int len, char *fmt, struct keyword *kw, void *data)
163 {
164         int r;
165         int fwd = 0;
166         char *f = fmt;
167
168         if (!kw || !kw->print)
169                 return 0;
170
171         do {
172                 if (fwd == len || *f == '\0')
173                         break;
174                 if (*f != '%') {
175                         *(buff + fwd) = *f;
176                         fwd++;
177                         continue;
178                 }
179                 f++;
180                 switch(*f) {
181                 case 'k':
182                         fwd += snprintf(buff + fwd, len - fwd, "%s", kw->string);
183                         break;
184                 case 'v':
185                         r = kw->print(conf, buff + fwd, len - fwd, data);
186                         if (!r) { /* no output if no value */
187                                 buff = '\0';
188                                 return 0;
189                         }
190                         fwd += r;
191                         break;
192                 }
193                 if (fwd > len)
194                         fwd = len;
195         } while (*f++);
196         return fwd;
197 }
198
199 vector
200 alloc_strvec(char *string)
201 {
202         char *cp, *start, *token;
203         int strlen;
204         int in_string;
205         vector strvec;
206
207         if (!string)
208                 return NULL;
209
210         cp = string;
211
212         /* Skip white spaces */
213         while ((isspace((int) *cp) || !isascii((int) *cp)) && *cp != '\0')
214                 cp++;
215
216         /* Return if there is only white spaces */
217         if (*cp == '\0')
218                 return NULL;
219
220         /* Return if string begin with a comment */
221         if (*cp == '!' || *cp == '#')
222                 return NULL;
223
224         /* Create a vector and alloc each command piece */
225         strvec = vector_alloc();
226
227         if (!strvec)
228                 return NULL;
229
230         in_string = 0;
231         while (1) {
232                 if (!vector_alloc_slot(strvec))
233                         goto out;
234
235                 start = cp;
236                 if (*cp == '"') {
237                         cp++;
238                         token = MALLOC(2);
239
240                         if (!token)
241                                 goto out;
242
243                         *(token) = '"';
244                         *(token + 1) = '\0';
245                         if (in_string)
246                                 in_string = 0;
247                         else
248                                 in_string = 1;
249                 } else if (!in_string && (*cp == '{' || *cp == '}')) {
250                         token = MALLOC(2);
251
252                         if (!token)
253                                 goto out;
254
255                         *(token) = *cp;
256                         *(token + 1) = '\0';
257                         cp++;
258                 } else {
259                         while ((in_string ||
260                                 (!isspace((int) *cp) && isascii((int) *cp) &&
261                                  *cp != '!' && *cp != '#' && *cp != '{' &&
262                                  *cp != '}')) && *cp != '\0' && *cp != '"')
263                                 cp++;
264                         strlen = cp - start;
265                         token = MALLOC(strlen + 1);
266
267                         if (!token)
268                                 goto out;
269
270                         memcpy(token, start, strlen);
271                         *(token + strlen) = '\0';
272                 }
273                 vector_set_slot(strvec, token);
274
275                 while ((isspace((int) *cp) || !isascii((int) *cp))
276                        && *cp != '\0')
277                         cp++;
278                 if (*cp == '\0' || *cp == '!' || *cp == '#')
279                         return strvec;
280         }
281 out:
282         vector_free(strvec);
283         return NULL;
284 }
285
286 static int
287 read_line(FILE *stream, char *buf, int size)
288 {
289         char *p;
290
291         if (fgets(buf, size, stream) == NULL)
292                 return 0;
293         strtok_r(buf, "\n\r", &p);
294         return 1;
295 }
296
297 void *
298 set_value(vector strvec)
299 {
300         char *str = VECTOR_SLOT(strvec, 1);
301         size_t size;
302         int i = 0;
303         int len = 0;
304         char *alloc = NULL;
305         char *tmp;
306
307         if (!str) {
308                 condlog(0, "option '%s' missing value",
309                         (char *)VECTOR_SLOT(strvec, 0));
310                 return NULL;
311         }
312         size = strlen(str);
313         if (size == 0) {
314                 condlog(0, "option '%s' has empty value",
315                         (char *)VECTOR_SLOT(strvec, 0));
316                 return NULL;
317         }
318         if (*str != '"') {
319                 alloc = MALLOC(sizeof (char) * (size + 1));
320                 if (alloc)
321                         memcpy(alloc, str, size);
322                 else
323                         condlog(0, "can't allocate memeory for option '%s'",
324                                 (char *)VECTOR_SLOT(strvec, 0));
325                 return alloc;
326         }
327         /* Even empty quotes counts as a value (An empty string) */
328         alloc = (char *) MALLOC(sizeof (char));
329         if (!alloc) {
330                 condlog(0, "can't allocate memeory for option '%s'",
331                         (char *)VECTOR_SLOT(strvec, 0));
332                 return NULL;
333         }
334         for (i = 2; i < VECTOR_SIZE(strvec); i++) {
335                 str = VECTOR_SLOT(strvec, i);
336                 if (!str) {
337                         free(alloc);
338                         condlog(0, "parse error for option '%s'",
339                                 (char *)VECTOR_SLOT(strvec, 0));
340                         return NULL;
341                 }
342                 if (*str == '"')
343                         break;
344                 tmp = alloc;
345                 /* The first +1 is for the NULL byte. The rest are for the
346                  * spaces between words */
347                 len += strlen(str) + 1;
348                 alloc = REALLOC(alloc, sizeof (char) * len);
349                 if (!alloc) {
350                         FREE(tmp);
351                         condlog(0, "can't allocate memeory for option '%s'",
352                                 (char *)VECTOR_SLOT(strvec, 0));
353                         return NULL;
354                 }
355                 if (*alloc != '\0')
356                         strncat(alloc, " ", 1);
357                 strncat(alloc, str, strlen(str));
358         }
359         return alloc;
360 }
361
362 /* non-recursive configuration stream handler */
363 static int kw_level = 0;
364
365 int warn_on_duplicates(vector uniques, char *str, char *file)
366 {
367         char *tmp;
368         int i;
369
370         vector_foreach_slot(uniques, tmp, i) {
371                 if (!strcmp(str, tmp)) {
372                         condlog(1, "%s line %d, duplicate keyword: %s",
373                                 file, line_nr, str);
374                         return 0;
375                 }
376         }
377         tmp = strdup(str);
378         if (!tmp)
379                 return 1;
380         if (!vector_alloc_slot(uniques)) {
381                 free(tmp);
382                 return 1;
383         }
384         vector_set_slot(uniques, tmp);
385         return 0;
386 }
387
388 void free_uniques(vector uniques)
389 {
390         char *tmp;
391         int i;
392
393         vector_foreach_slot(uniques, tmp, i)
394                 free(tmp);
395         vector_free(uniques);
396 }
397
398 int
399 is_sublevel_keyword(char *str)
400 {
401         return (strcmp(str, "defaults") == 0 || strcmp(str, "blacklist") == 0 ||
402                 strcmp(str, "blacklist_exceptions") == 0 ||
403                 strcmp(str, "devices") == 0 || strcmp(str, "devices") == 0 ||
404                 strcmp(str, "device") == 0 || strcmp(str, "multipaths") == 0 ||
405                 strcmp(str, "multipath") == 0);
406 }
407
408 int
409 validate_config_strvec(vector strvec, char *file)
410 {
411         char *str;
412         int i;
413
414         str = VECTOR_SLOT(strvec, 0);
415         if (str == NULL) {
416                 condlog(0, "can't parse option on line %d of %s",
417                         line_nr, file);
418         return -1;
419         }
420         if (*str == '}') {
421                 if (VECTOR_SIZE(strvec) > 1)
422                         condlog(0, "ignoring extra data starting with '%s' on line %d of %s", (char *)VECTOR_SLOT(strvec, 1), line_nr, file);
423                 return 0;
424         }
425         if (*str == '{') {
426                 condlog(0, "invalid keyword '%s' on line %d of %s",
427                         str, line_nr, file);
428                 return -1;
429         }
430         if (is_sublevel_keyword(str)) {
431                 str = VECTOR_SLOT(strvec, 1);
432                 if (str == NULL)
433                         condlog(0, "missing '{' on line %d of %s",
434                                 line_nr, file);
435                 else if (*str != '{')
436                         condlog(0, "expecting '{' on line %d of %s. found '%s'",
437                                 line_nr, file, str);
438                 else if (VECTOR_SIZE(strvec) > 2)
439                         condlog(0, "ignoring extra data starting with '%s' on line %d of %s", (char *)VECTOR_SLOT(strvec, 2), line_nr, file);
440                 return 0;
441         }
442         str = VECTOR_SLOT(strvec, 1);
443         if (str == NULL) {
444                 condlog(0, "missing value for option '%s' on line %d of %s",
445                         (char *)VECTOR_SLOT(strvec, 0), line_nr, file);
446                 return -1;
447         }
448         if (*str != '"') {
449                 if (VECTOR_SIZE(strvec) > 2)
450                         condlog(0, "ignoring extra data starting with '%s' on line %d of %s", (char *)VECTOR_SLOT(strvec, 2), line_nr, file);
451                 return 0;
452         }
453         for (i = 2; i < VECTOR_SIZE(strvec); i++) {
454                 str = VECTOR_SLOT(strvec, i);
455                 if (str == NULL) {
456                         condlog(0, "can't parse value on line %d of %s",
457                                 line_nr, file);
458                         return -1;
459                 }
460                 if (*str == '"') {
461                         if (VECTOR_SIZE(strvec) > i + 1)
462                                 condlog(0, "ignoring extra data starting with '%s' on line %d of %s", (char *)VECTOR_SLOT(strvec, (i + 1)), line_nr, file);
463                         return 0;
464                 }
465         }
466         condlog(0, "missing closing quotes on line %d of %s",
467                 line_nr, file);
468         return 0;
469 }
470
471 static int
472 process_stream(struct config *conf, FILE *stream, vector keywords, char *file)
473 {
474         int i;
475         int r = 0, t;
476         struct keyword *keyword;
477         char *str;
478         char *buf;
479         vector strvec;
480         vector uniques;
481
482         uniques = vector_alloc();
483         if (!uniques)
484                 return 1;
485
486         buf = MALLOC(MAXBUF);
487
488         if (!buf) {
489                 vector_free(uniques);
490                 return 1;
491         }
492
493         while (read_line(stream, buf, MAXBUF)) {
494                 line_nr++;
495                 strvec = alloc_strvec(buf);
496                 if (!strvec)
497                         continue;
498
499                 if (validate_config_strvec(strvec, file) != 0) {
500                         free_strvec(strvec);
501                         continue;
502                 }
503
504                 str = VECTOR_SLOT(strvec, 0);
505
506                 if (!strcmp(str, EOB)) {
507                         if (kw_level > 0) {
508                                 free_strvec(strvec);
509                                 break;
510                         }
511                         condlog(0, "unmatched '%s' at line %d of %s",
512                                 EOB, line_nr, file);
513                 }
514
515                 for (i = 0; i < VECTOR_SIZE(keywords); i++) {
516                         keyword = VECTOR_SLOT(keywords, i);
517
518                         if (!strcmp(keyword->string, str)) {
519                                 if (keyword->unique &&
520                                     warn_on_duplicates(uniques, str, file)) {
521                                                 r = 1;
522                                                 free_strvec(strvec);
523                                                 goto out;
524                                 }
525                                 if (keyword->handler) {
526                                     t = (*keyword->handler) (conf, strvec);
527                                         r += t;
528                                         if (t)
529                                                 condlog(1, "multipath.conf +%d, parsing failed: %s",
530                                                         line_nr, buf);
531                                 }
532
533                                 if (keyword->sub) {
534                                         kw_level++;
535                                         r += process_stream(conf, stream,
536                                                             keyword->sub, file);
537                                         kw_level--;
538                                 }
539                                 break;
540                         }
541                 }
542                 if (i >= VECTOR_SIZE(keywords))
543                         condlog(1, "%s line %d, invalid keyword: %s",
544                                 file, line_nr, str);
545
546                 free_strvec(strvec);
547         }
548
549 out:
550         FREE(buf);
551         free_uniques(uniques);
552         return r;
553 }
554
555 int alloc_keywords(void)
556 {
557         if (!keywords)
558                 keywords = vector_alloc();
559
560         if (!keywords)
561                 return 1;
562
563         return 0;
564 }
565
566 /* Data initialization */
567 int
568 process_file(struct config *conf, char *file)
569 {
570         int r;
571         FILE *stream;
572
573         if (!keywords) {
574                 condlog(0, "No keywords alocated");
575                 return 1;
576         }
577         stream = fopen(file, "r");
578         if (!stream) {
579                 condlog(0, "couldn't open configuration file '%s': %s",
580                         file, strerror(errno));
581                 return 1;
582         }
583
584         /* Stream handling */
585         line_nr = 0;
586         r = process_stream(conf, stream, keywords, file);
587         fclose(stream);
588         //free_keywords(keywords);
589
590         return r;
591 }