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