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