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