Clean up a redundant assignment in libprofile
[krb5.git] / src / util / profile / prof_parse.c
1 /* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2 #include "prof_int.h"
3
4 #include <sys/types.h>
5 #include <stdio.h>
6 #include <string.h>
7 #ifdef HAVE_STDLIB_H
8 #include <stdlib.h>
9 #endif
10 #include <errno.h>
11 #include <ctype.h>
12 #ifndef _WIN32
13 #include <dirent.h>
14 #endif
15
16 #define SECTION_SEP_CHAR '/'
17
18 #define STATE_INIT_COMMENT      1
19 #define STATE_STD_LINE          2
20 #define STATE_GET_OBRACE        3
21
22 struct parse_state {
23     int     state;
24     int     group_level;
25     struct profile_node *root_section;
26     struct profile_node *current_section;
27 };
28
29 static errcode_t parse_file(FILE *f, struct parse_state *state,
30                             char **ret_modspec);
31
32 static char *skip_over_blanks(char *cp)
33 {
34     while (*cp && isspace((int) (*cp)))
35         cp++;
36     return cp;
37 }
38
39 static void strip_line(char *line)
40 {
41     char *p = line + strlen(line);
42     while (p > line && (p[-1] == '\n' || p[-1] == '\r'))
43         *--p = 0;
44 }
45
46 static void parse_quoted_string(char *str)
47 {
48     char *to, *from;
49
50     for (to = from = str; *from && *from != '"'; to++, from++) {
51         if (*from == '\\') {
52             from++;
53             switch (*from) {
54             case 'n':
55                 *to = '\n';
56                 break;
57             case 't':
58                 *to = '\t';
59                 break;
60             case 'b':
61                 *to = '\b';
62                 break;
63             default:
64                 *to = *from;
65             }
66             continue;
67         }
68         *to = *from;
69     }
70     *to = '\0';
71 }
72
73
74 static errcode_t parse_std_line(char *line, struct parse_state *state)
75 {
76     char    *cp, ch, *tag, *value;
77     char    *p;
78     errcode_t retval;
79     struct profile_node     *node;
80     int do_subsection = 0;
81     void *iter = 0;
82
83     if (*line == 0)
84         return 0;
85     cp = skip_over_blanks(line);
86     if (cp[0] == ';' || cp[0] == '#')
87         return 0;
88     strip_line(cp);
89     ch = *cp;
90     if (ch == 0)
91         return 0;
92     if (ch == '[') {
93         if (state->group_level > 0)
94             return PROF_SECTION_NOTOP;
95         cp++;
96         p = strchr(cp, ']');
97         if (p == NULL)
98             return PROF_SECTION_SYNTAX;
99         *p = '\0';
100         retval = profile_find_node_subsection(state->root_section,
101                                               cp, &iter, 0,
102                                               &state->current_section);
103         if (retval == PROF_NO_SECTION) {
104             retval = profile_add_node(state->root_section,
105                                       cp, 0,
106                                       &state->current_section);
107             if (retval)
108                 return retval;
109         } else if (retval)
110             return retval;
111
112         /*
113          * Finish off the rest of the line.
114          */
115         cp = p+1;
116         if (*cp == '*') {
117             profile_make_node_final(state->current_section);
118             cp++;
119         }
120         /*
121          * A space after ']' should not be fatal
122          */
123         cp = skip_over_blanks(cp);
124         if (*cp)
125             return PROF_SECTION_SYNTAX;
126         return 0;
127     }
128     if (ch == '}') {
129         if (state->group_level == 0)
130             return PROF_EXTRA_CBRACE;
131         if (*(cp+1) == '*')
132             profile_make_node_final(state->current_section);
133         retval = profile_get_node_parent(state->current_section,
134                                          &state->current_section);
135         if (retval)
136             return retval;
137         state->group_level--;
138         return 0;
139     }
140     /*
141      * Parse the relations
142      */
143     tag = cp;
144     cp = strchr(cp, '=');
145     if (!cp)
146         return PROF_RELATION_SYNTAX;
147     if (cp == tag)
148         return PROF_RELATION_SYNTAX;
149     *cp = '\0';
150     p = tag;
151     /* Look for whitespace on left-hand side.  */
152     while (p < cp && !isspace((int)*p))
153         p++;
154     if (p < cp) {
155         /* Found some sort of whitespace.  */
156         *p++ = 0;
157         /* If we have more non-whitespace, it's an error.  */
158         while (p < cp) {
159             if (!isspace((int)*p))
160                 return PROF_RELATION_SYNTAX;
161             p++;
162         }
163     }
164     cp = skip_over_blanks(cp+1);
165     value = cp;
166     if (value[0] == '"') {
167         value++;
168         parse_quoted_string(value);
169     } else if (value[0] == 0) {
170         do_subsection++;
171         state->state = STATE_GET_OBRACE;
172     } else if (value[0] == '{' && *(skip_over_blanks(value+1)) == 0)
173         do_subsection++;
174     else {
175         cp = value + strlen(value) - 1;
176         while ((cp > value) && isspace((int) (*cp)))
177             *cp-- = 0;
178     }
179     if (do_subsection) {
180         p = strchr(tag, '*');
181         if (p)
182             *p = '\0';
183         retval = profile_add_node(state->current_section,
184                                   tag, 0, &state->current_section);
185         if (retval)
186             return retval;
187         if (p)
188             profile_make_node_final(state->current_section);
189         state->group_level++;
190         return 0;
191     }
192     p = strchr(tag, '*');
193     if (p)
194         *p = '\0';
195     profile_add_node(state->current_section, tag, value, &node);
196     if (p)
197         profile_make_node_final(node);
198     return 0;
199 }
200
201 /* Open and parse an included profile file. */
202 static errcode_t parse_include_file(char *filename, struct parse_state *state)
203 {
204     FILE    *fp;
205     errcode_t retval = 0;
206     struct parse_state incstate;
207
208     /* Create a new state so that fragments are syntactically independent,
209      * sharing the root section with the existing state. */
210     incstate.state = STATE_INIT_COMMENT;
211     incstate.group_level = 0;
212     incstate.root_section = state->root_section;
213     incstate.current_section = NULL;
214
215     fp = fopen(filename, "r");
216     if (fp == NULL)
217         return PROF_FAIL_INCLUDE_FILE;
218     retval = parse_file(fp, &incstate, NULL);
219     fclose(fp);
220     return retval;
221 }
222
223 /* Return non-zero if filename contains only alphanumeric characters, dashes,
224  * and underscores. */
225 static int valid_name(const char *filename)
226 {
227     const char *p;
228
229     for (p = filename; *p != '\0'; p++) {
230         if (!isalnum((unsigned char)*p) && *p != '-' && *p != '_')
231             return 0;
232     }
233     return 1;
234 }
235
236 /*
237  * Include files within dirname.  Only files with names consisting entirely of
238  * alphanumeric chracters, dashes, and underscores are included, in order to
239  * avoid including editor backup files, .rpmsave files, and the like.
240  */
241 static errcode_t parse_include_dir(char *dirname, struct parse_state *state)
242 {
243 #ifdef _WIN32
244     char *wildcard = NULL, *pathname;
245     WIN32_FIND_DATA ffd;
246     HANDLE handle;
247     errcode_t retval = 0;
248
249     if (asprintf(&wildcard, "%s\\*", dirname) < 0)
250         return ENOMEM;
251
252     handle = FindFirstFile(wildcard, &ffd);
253     if (handle == INVALID_HANDLE_VALUE) {
254         retval = PROF_FAIL_INCLUDE_DIR;
255         goto cleanup;
256     }
257
258     do {
259         if (!valid_name(ffd.cFileName))
260             continue;
261         if (asprintf(&pathname, "%s\\%s", dirname, ffd.cFileName) < 0) {
262             retval = ENOMEM;
263             break;
264         }
265         retval = parse_include_file(pathname, state);
266         free(pathname);
267         if (retval)
268             break;
269     } while (FindNextFile(handle, &ffd) != 0);
270
271     FindClose(handle);
272
273 cleanup:
274     free(wildcard);
275     return retval;
276
277 #else /* not _WIN32 */
278
279     DIR     *dir;
280     char    *pathname;
281     errcode_t retval = 0;
282     struct dirent *ent;
283
284     dir = opendir(dirname);
285     if (dir == NULL)
286         return PROF_FAIL_INCLUDE_DIR;
287     while ((ent = readdir(dir)) != NULL) {
288         if (!valid_name(ent->d_name))
289             continue;
290         if (asprintf(&pathname, "%s/%s", dirname, ent->d_name) < 0) {
291             retval = ENOMEM;
292             break;
293         }
294         retval = parse_include_file(pathname, state);
295         free(pathname);
296         if (retval)
297             break;
298     }
299     closedir(dir);
300     return retval;
301 #endif /* not _WIN32 */
302 }
303
304 static errcode_t parse_line(char *line, struct parse_state *state,
305                             char **ret_modspec)
306 {
307     char    *cp;
308
309     if (strncmp(line, "include", 7) == 0 && isspace(line[7])) {
310         cp = skip_over_blanks(line + 7);
311         strip_line(cp);
312         return parse_include_file(cp, state);
313     }
314     if (strncmp(line, "includedir", 10) == 0 && isspace(line[10])) {
315         cp = skip_over_blanks(line + 10);
316         strip_line(cp);
317         return parse_include_dir(cp, state);
318     }
319     switch (state->state) {
320     case STATE_INIT_COMMENT:
321         if (strncmp(line, "module", 6) == 0 && isspace(line[6])) {
322             /*
323              * If we are expecting a module declaration, fill in *ret_modspec
324              * and return PROF_MODULE, which will cause parsing to abort and
325              * the module to be loaded instead.  If we aren't expecting a
326              * module declaration, return PROF_MODULE without filling in
327              * *ret_modspec, which will be treated as an ordinary error.
328              */
329             if (ret_modspec) {
330                 cp = skip_over_blanks(line + 6);
331                 strip_line(cp);
332                 *ret_modspec = strdup(cp);
333                 if (!*ret_modspec)
334                     return ENOMEM;
335             }
336             return PROF_MODULE;
337         }
338         if (line[0] != '[')
339             return 0;
340         state->state = STATE_STD_LINE;
341     case STATE_STD_LINE:
342         return parse_std_line(line, state);
343     case STATE_GET_OBRACE:
344         cp = skip_over_blanks(line);
345         if (*cp != '{')
346             return PROF_MISSING_OBRACE;
347         state->state = STATE_STD_LINE;
348     }
349     return 0;
350 }
351
352 static errcode_t parse_file(FILE *f, struct parse_state *state,
353                             char **ret_modspec)
354 {
355 #define BUF_SIZE        2048
356     char *bptr;
357     errcode_t retval;
358
359     bptr = malloc (BUF_SIZE);
360     if (!bptr)
361         return ENOMEM;
362
363     while (!feof(f)) {
364         if (fgets(bptr, BUF_SIZE, f) == NULL)
365             break;
366 #ifndef PROFILE_SUPPORTS_FOREIGN_NEWLINES
367         retval = parse_line(bptr, state, ret_modspec);
368         if (retval) {
369             free (bptr);
370             return retval;
371         }
372 #else
373         {
374             char *p, *end;
375
376             if (strlen(bptr) >= BUF_SIZE - 1) {
377                 /* The string may have foreign newlines and
378                    gotten chopped off on a non-newline
379                    boundary.  Seek backwards to the last known
380                    newline.  */
381                 long offset;
382                 char *c = bptr + strlen (bptr);
383                 for (offset = 0; offset > -BUF_SIZE; offset--) {
384                     if (*c == '\r' || *c == '\n') {
385                         *c = '\0';
386                         fseek (f, offset, SEEK_CUR);
387                         break;
388                     }
389                     c--;
390                 }
391             }
392
393             /* First change all newlines to \n */
394             for (p = bptr; *p != '\0'; p++) {
395                 if (*p == '\r')
396                     *p = '\n';
397             }
398             /* Then parse all lines */
399             p = bptr;
400             end = bptr + strlen (bptr);
401             while (p < end) {
402                 char* newline;
403                 char* newp;
404
405                 newline = strchr (p, '\n');
406                 if (newline != NULL)
407                     *newline = '\0';
408
409                 /* parse_line modifies contents of p */
410                 newp = p + strlen (p) + 1;
411                 retval = parse_line (p, state, ret_modspec);
412                 if (retval) {
413                     free (bptr);
414                     return retval;
415                 }
416
417                 p = newp;
418             }
419         }
420 #endif
421     }
422
423     free (bptr);
424     return 0;
425 }
426
427 errcode_t profile_parse_file(FILE *f, struct profile_node **root,
428                              char **ret_modspec)
429 {
430     struct parse_state state;
431     errcode_t retval;
432
433     *root = NULL;
434
435     /* Initialize parsing state with a new root node. */
436     state.state = STATE_INIT_COMMENT;
437     state.group_level = 0;
438     state.current_section = NULL;
439     retval = profile_create_node("(root)", 0, &state.root_section);
440     if (retval)
441         return retval;
442
443     retval = parse_file(f, &state, ret_modspec);
444     if (retval) {
445         profile_free_node(state.root_section);
446         return retval;
447     }
448     *root = state.root_section;
449     return 0;
450 }
451
452 /*
453  * Return TRUE if the string begins or ends with whitespace
454  */
455 static int need_double_quotes(char *str)
456 {
457     if (!str)
458         return 0;
459     if (str[0] == '\0')
460         return 1;
461     if (isspace((int) (*str)) ||isspace((int) (*(str + strlen(str) - 1))))
462         return 1;
463     if (strchr(str, '\n') || strchr(str, '\t') || strchr(str, '\b'))
464         return 1;
465     return 0;
466 }
467
468 /*
469  * Output a string with double quotes, doing appropriate backquoting
470  * of characters as necessary.
471  */
472 static void output_quoted_string(char *str, void (*cb)(const char *,void *),
473                                  void *data)
474 {
475     char    ch;
476     char buf[2];
477
478     cb("\"", data);
479     if (!str) {
480         cb("\"", data);
481         return;
482     }
483     buf[1] = 0;
484     while ((ch = *str++)) {
485         switch (ch) {
486         case '\\':
487             cb("\\\\", data);
488             break;
489         case '\n':
490             cb("\\n", data);
491             break;
492         case '\t':
493             cb("\\t", data);
494             break;
495         case '\b':
496             cb("\\b", data);
497             break;
498         default:
499             /* This would be a lot faster if we scanned
500                forward for the next "interesting"
501                character.  */
502             buf[0] = ch;
503             cb(buf, data);
504             break;
505         }
506     }
507     cb("\"", data);
508 }
509
510
511
512 #if defined(_WIN32)
513 #define EOL "\r\n"
514 #endif
515
516 #ifndef EOL
517 #define EOL "\n"
518 #endif
519
520 /* Errors should be returned, not ignored!  */
521 static void dump_profile(struct profile_node *root, int level,
522                          void (*cb)(const char *, void *), void *data)
523 {
524     int i;
525     struct profile_node *p;
526     void *iter;
527     long retval;
528     char *name, *value;
529
530     iter = 0;
531     do {
532         retval = profile_find_node_relation(root, 0, &iter,
533                                             &name, &value);
534         if (retval)
535             break;
536         for (i=0; i < level; i++)
537             cb("\t", data);
538         if (need_double_quotes(value)) {
539             cb(name, data);
540             cb(" = ", data);
541             output_quoted_string(value, cb, data);
542             cb(EOL, data);
543         } else {
544             cb(name, data);
545             cb(" = ", data);
546             cb(value, data);
547             cb(EOL, data);
548         }
549     } while (iter != 0);
550
551     iter = 0;
552     do {
553         retval = profile_find_node_subsection(root, 0, &iter,
554                                               &name, &p);
555         if (retval)
556             break;
557         if (level == 0) { /* [xxx] */
558             cb("[", data);
559             cb(name, data);
560             cb("]", data);
561             cb(profile_is_node_final(p) ? "*" : "", data);
562             cb(EOL, data);
563             dump_profile(p, level+1, cb, data);
564             cb(EOL, data);
565         } else {        /* xxx = { ... } */
566             for (i=0; i < level; i++)
567                 cb("\t", data);
568             cb(name, data);
569             cb(" = {", data);
570             cb(EOL, data);
571             dump_profile(p, level+1, cb, data);
572             for (i=0; i < level; i++)
573                 cb("\t", data);
574             cb("}", data);
575             cb(profile_is_node_final(p) ? "*" : "", data);
576             cb(EOL, data);
577         }
578     } while (iter != 0);
579 }
580
581 static void dump_profile_to_file_cb(const char *str, void *data)
582 {
583     fputs(str, data);
584 }
585
586 errcode_t profile_write_tree_file(struct profile_node *root, FILE *dstfile)
587 {
588     dump_profile(root, 0, dump_profile_to_file_cb, dstfile);
589     return 0;
590 }
591
592 struct prof_buf {
593     char *base;
594     size_t cur, max;
595     int err;
596 };
597
598 static void add_data_to_buffer(struct prof_buf *b, const void *d, size_t len)
599 {
600     if (b->err)
601         return;
602     if (b->max - b->cur < len) {
603         size_t newsize;
604         char *newptr;
605
606         newsize = b->max + (b->max >> 1) + len + 1024;
607         newptr = realloc(b->base, newsize);
608         if (newptr == NULL) {
609             b->err = 1;
610             return;
611         }
612         b->base = newptr;
613         b->max = newsize;
614     }
615     memcpy(b->base + b->cur, d, len);
616     b->cur += len;          /* ignore overflow */
617 }
618
619 static void dump_profile_to_buffer_cb(const char *str, void *data)
620 {
621     add_data_to_buffer((struct prof_buf *)data, str, strlen(str));
622 }
623
624 errcode_t profile_write_tree_to_buffer(struct profile_node *root,
625                                        char **buf)
626 {
627     struct prof_buf prof_buf = { 0, 0, 0, 0 };
628
629     dump_profile(root, 0, dump_profile_to_buffer_cb, &prof_buf);
630     if (prof_buf.err) {
631         *buf = NULL;
632         return ENOMEM;
633     }
634     add_data_to_buffer(&prof_buf, "", 1); /* append nul */
635     if (prof_buf.max - prof_buf.cur > (prof_buf.max >> 3)) {
636         char *newptr = realloc(prof_buf.base, prof_buf.cur);
637         if (newptr)
638             prof_buf.base = newptr;
639     }
640     *buf = prof_buf.base;
641     return 0;
642 }