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