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