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