libmultipath: config parser: Allow '"' in strings
[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 vector
190 alloc_strvec(char *string)
191 {
192         char *cp, *start, *token;
193         int strlen;
194         int in_string;
195         vector strvec;
196
197         if (!string)
198                 return NULL;
199
200         cp = string;
201
202         /* Skip white spaces */
203         while ((isspace((int) *cp) || !isascii((int) *cp)) && *cp != '\0')
204                 cp++;
205
206         /* Return if there is only white spaces */
207         if (*cp == '\0')
208                 return NULL;
209
210         /* Return if string begin with a comment */
211         if (*cp == '!' || *cp == '#')
212                 return NULL;
213
214         /* Create a vector and alloc each command piece */
215         strvec = vector_alloc();
216
217         if (!strvec)
218                 return NULL;
219
220         in_string = 0;
221         while (1) {
222                 int two_quotes = 0;
223
224                 if (!vector_alloc_slot(strvec))
225                         goto out;
226
227                 start = cp;
228                 if (*cp == '"' && !(in_string && *(cp + 1) == '"')) {
229                         cp++;
230                         token = MALLOC(2);
231
232                         if (!token)
233                                 goto out;
234
235                         *(token) = '"';
236                         *(token + 1) = '\0';
237                         if (in_string)
238                                 in_string = 0;
239                         else
240                                 in_string = 1;
241                 } else if (!in_string && (*cp == '{' || *cp == '}')) {
242                         token = MALLOC(2);
243
244                         if (!token)
245                                 goto out;
246
247                         *(token) = *cp;
248                         *(token + 1) = '\0';
249                         cp++;
250                 } else {
251
252                 move_on:
253                         while ((in_string ||
254                                 (!isspace((int) *cp) && isascii((int) *cp) &&
255                                  *cp != '!' && *cp != '#' && *cp != '{' &&
256                                  *cp != '}')) && *cp != '\0' && *cp != '"')
257                                 cp++;
258
259                         /* Two consecutive double quotes - don't end string */
260                         if (in_string && *cp == '"') {
261                                 if (*(cp + 1) == '"') {
262                                         two_quotes = 1;
263                                         cp += 2;
264                                         goto move_on;
265                                 }
266                         }
267
268                         strlen = cp - start;
269                         token = MALLOC(strlen + 1);
270
271                         if (!token)
272                                 goto out;
273
274                         memcpy(token, start, strlen);
275                         *(token + strlen) = '\0';
276
277                         /* Replace "" by " */
278                         if (two_quotes) {
279                                 char *qq = strstr(token, "\"\"");
280                                 while (qq != NULL) {
281                                         memmove(qq + 1, qq + 2,
282                                                 strlen + 1 - (qq + 2 - token));
283                                         qq = strstr(qq + 1, "\"\"");
284                                 }
285                         }
286                 }
287                 vector_set_slot(strvec, token);
288
289                 while ((!in_string &&
290                         (isspace((int) *cp) || !isascii((int) *cp)))
291                        && *cp != '\0')
292                         cp++;
293                 if (*cp == '\0' || *cp == '!' || *cp == '#')
294                         return strvec;
295         }
296 out:
297         vector_free(strvec);
298         return NULL;
299 }
300
301 static int
302 read_line(FILE *stream, char *buf, int size)
303 {
304         char *p;
305
306         if (fgets(buf, size, stream) == NULL)
307                 return 0;
308         strtok_r(buf, "\n\r", &p);
309         return 1;
310 }
311
312 void *
313 set_value(vector strvec)
314 {
315         char *str = VECTOR_SLOT(strvec, 1);
316         size_t size;
317         int i = 0;
318         int len = 0;
319         char *alloc = NULL;
320         char *tmp;
321
322         if (!str) {
323                 condlog(0, "option '%s' missing value",
324                         (char *)VECTOR_SLOT(strvec, 0));
325                 return NULL;
326         }
327         size = strlen(str);
328         if (size == 0) {
329                 condlog(0, "option '%s' has empty value",
330                         (char *)VECTOR_SLOT(strvec, 0));
331                 return NULL;
332         }
333         if (*str != '"') {
334                 alloc = MALLOC(sizeof (char) * (size + 1));
335                 if (alloc)
336                         memcpy(alloc, str, size);
337                 else
338                         condlog(0, "can't allocate memeory for option '%s'",
339                                 (char *)VECTOR_SLOT(strvec, 0));
340                 return alloc;
341         }
342         /* Even empty quotes counts as a value (An empty string) */
343         alloc = (char *) MALLOC(sizeof (char));
344         if (!alloc) {
345                 condlog(0, "can't allocate memeory for option '%s'",
346                         (char *)VECTOR_SLOT(strvec, 0));
347                 return NULL;
348         }
349         for (i = 2; i < VECTOR_SIZE(strvec); i++) {
350                 str = VECTOR_SLOT(strvec, i);
351                 if (!str) {
352                         free(alloc);
353                         condlog(0, "parse error for option '%s'",
354                                 (char *)VECTOR_SLOT(strvec, 0));
355                         return NULL;
356                 }
357                 if (*str == '"')
358                         break;
359                 tmp = alloc;
360                 /* The first +1 is for the NULL byte. The rest are for the
361                  * spaces between words */
362                 len += strlen(str) + 1;
363                 alloc = REALLOC(alloc, sizeof (char) * len);
364                 if (!alloc) {
365                         FREE(tmp);
366                         condlog(0, "can't allocate memeory for option '%s'",
367                                 (char *)VECTOR_SLOT(strvec, 0));
368                         return NULL;
369                 }
370                 if (*alloc != '\0')
371                         strncat(alloc, " ", 1);
372                 strncat(alloc, str, strlen(str));
373         }
374         return alloc;
375 }
376
377 /* non-recursive configuration stream handler */
378 static int kw_level = 0;
379
380 int warn_on_duplicates(vector uniques, char *str, char *file)
381 {
382         char *tmp;
383         int i;
384
385         vector_foreach_slot(uniques, tmp, i) {
386                 if (!strcmp(str, tmp)) {
387                         condlog(1, "%s line %d, duplicate keyword: %s",
388                                 file, line_nr, str);
389                         return 0;
390                 }
391         }
392         tmp = strdup(str);
393         if (!tmp)
394                 return 1;
395         if (!vector_alloc_slot(uniques)) {
396                 free(tmp);
397                 return 1;
398         }
399         vector_set_slot(uniques, tmp);
400         return 0;
401 }
402
403 void free_uniques(vector uniques)
404 {
405         char *tmp;
406         int i;
407
408         vector_foreach_slot(uniques, tmp, i)
409                 free(tmp);
410         vector_free(uniques);
411 }
412
413 int
414 is_sublevel_keyword(char *str)
415 {
416         return (strcmp(str, "defaults") == 0 || strcmp(str, "blacklist") == 0 ||
417                 strcmp(str, "blacklist_exceptions") == 0 ||
418                 strcmp(str, "devices") == 0 || strcmp(str, "devices") == 0 ||
419                 strcmp(str, "device") == 0 || strcmp(str, "multipaths") == 0 ||
420                 strcmp(str, "multipath") == 0);
421 }
422
423 int
424 validate_config_strvec(vector strvec, char *file)
425 {
426         char *str;
427         int i;
428
429         str = VECTOR_SLOT(strvec, 0);
430         if (str == NULL) {
431                 condlog(0, "can't parse option on line %d of %s",
432                         line_nr, file);
433         return -1;
434         }
435         if (*str == '}') {
436                 if (VECTOR_SIZE(strvec) > 1)
437                         condlog(0, "ignoring extra data starting with '%s' on line %d of %s", (char *)VECTOR_SLOT(strvec, 1), line_nr, file);
438                 return 0;
439         }
440         if (*str == '{') {
441                 condlog(0, "invalid keyword '%s' on line %d of %s",
442                         str, line_nr, file);
443                 return -1;
444         }
445         if (is_sublevel_keyword(str)) {
446                 str = VECTOR_SLOT(strvec, 1);
447                 if (str == NULL)
448                         condlog(0, "missing '{' on line %d of %s",
449                                 line_nr, file);
450                 else if (*str != '{')
451                         condlog(0, "expecting '{' on line %d of %s. found '%s'",
452                                 line_nr, file, str);
453                 else if (VECTOR_SIZE(strvec) > 2)
454                         condlog(0, "ignoring extra data starting with '%s' on line %d of %s", (char *)VECTOR_SLOT(strvec, 2), line_nr, file);
455                 return 0;
456         }
457         str = VECTOR_SLOT(strvec, 1);
458         if (str == NULL) {
459                 condlog(0, "missing value for option '%s' on line %d of %s",
460                         (char *)VECTOR_SLOT(strvec, 0), line_nr, file);
461                 return -1;
462         }
463         if (*str != '"') {
464                 if (VECTOR_SIZE(strvec) > 2)
465                         condlog(0, "ignoring extra data starting with '%s' on line %d of %s", (char *)VECTOR_SLOT(strvec, 2), line_nr, file);
466                 return 0;
467         }
468         for (i = 2; i < VECTOR_SIZE(strvec); i++) {
469                 str = VECTOR_SLOT(strvec, i);
470                 if (str == NULL) {
471                         condlog(0, "can't parse value on line %d of %s",
472                                 line_nr, file);
473                         return -1;
474                 }
475                 if (*str == '"') {
476                         if (VECTOR_SIZE(strvec) > i + 1)
477                                 condlog(0, "ignoring extra data starting with '%s' on line %d of %s", (char *)VECTOR_SLOT(strvec, (i + 1)), line_nr, file);
478                         return 0;
479                 }
480         }
481         condlog(0, "missing closing quotes on line %d of %s",
482                 line_nr, file);
483         return 0;
484 }
485
486 static int
487 process_stream(struct config *conf, FILE *stream, vector keywords, char *file)
488 {
489         int i;
490         int r = 0, t;
491         struct keyword *keyword;
492         char *str;
493         char *buf;
494         vector strvec;
495         vector uniques;
496
497         uniques = vector_alloc();
498         if (!uniques)
499                 return 1;
500
501         buf = MALLOC(MAXBUF);
502
503         if (!buf) {
504                 vector_free(uniques);
505                 return 1;
506         }
507
508         while (read_line(stream, buf, MAXBUF)) {
509                 line_nr++;
510                 strvec = alloc_strvec(buf);
511                 if (!strvec)
512                         continue;
513
514                 if (validate_config_strvec(strvec, file) != 0) {
515                         free_strvec(strvec);
516                         continue;
517                 }
518
519                 str = VECTOR_SLOT(strvec, 0);
520
521                 if (!strcmp(str, EOB)) {
522                         if (kw_level > 0) {
523                                 free_strvec(strvec);
524                                 break;
525                         }
526                         condlog(0, "unmatched '%s' at line %d of %s",
527                                 EOB, line_nr, file);
528                 }
529
530                 for (i = 0; i < VECTOR_SIZE(keywords); i++) {
531                         keyword = VECTOR_SLOT(keywords, i);
532
533                         if (!strcmp(keyword->string, str)) {
534                                 if (keyword->unique &&
535                                     warn_on_duplicates(uniques, str, file)) {
536                                                 r = 1;
537                                                 free_strvec(strvec);
538                                                 goto out;
539                                 }
540                                 if (keyword->handler) {
541                                     t = (*keyword->handler) (conf, strvec);
542                                         r += t;
543                                         if (t)
544                                                 condlog(1, "multipath.conf +%d, parsing failed: %s",
545                                                         line_nr, buf);
546                                 }
547
548                                 if (keyword->sub) {
549                                         kw_level++;
550                                         r += process_stream(conf, stream,
551                                                             keyword->sub, file);
552                                         kw_level--;
553                                 }
554                                 break;
555                         }
556                 }
557                 if (i >= VECTOR_SIZE(keywords))
558                         condlog(1, "%s line %d, invalid keyword: %s",
559                                 file, line_nr, str);
560
561                 free_strvec(strvec);
562         }
563
564 out:
565         FREE(buf);
566         free_uniques(uniques);
567         return r;
568 }
569
570 /* Data initialization */
571 int
572 process_file(struct config *conf, char *file)
573 {
574         int r;
575         FILE *stream;
576
577         if (!conf->keywords) {
578                 condlog(0, "No keywords alocated");
579                 return 1;
580         }
581         stream = fopen(file, "r");
582         if (!stream) {
583                 condlog(0, "couldn't open configuration file '%s': %s",
584                         file, strerror(errno));
585                 return 1;
586         }
587
588         /* Stream handling */
589         line_nr = 0;
590         r = process_stream(conf, stream, conf->keywords, file);
591         fclose(stream);
592         //free_keywords(keywords);
593
594         return r;
595 }