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
skip_over_blanks(char * cp)32 static char *skip_over_blanks(char *cp)
33 {
34 while (*cp && isspace((int) (*cp)))
35 cp++;
36 return cp;
37 }
38
strip_line(char * line)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
parse_quoted_string(char * str)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 == '\\' && *(from + 1) != '\0') {
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
parse_std_line(char * line,struct parse_state * state)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. */
parse_include_file(const char * filename,struct profile_node * root_section)202 static errcode_t parse_include_file(const char *filename,
203 struct profile_node *root_section)
204 {
205 FILE *fp;
206 errcode_t retval = 0;
207 struct parse_state state;
208
209 /* Create a new state so that fragments are syntactically independent but
210 * share a root section. */
211 state.state = STATE_INIT_COMMENT;
212 state.group_level = 0;
213 state.root_section = root_section;
214 state.current_section = NULL;
215
216 fp = fopen(filename, "r");
217 if (fp == NULL)
218 return PROF_FAIL_INCLUDE_FILE;
219 retval = parse_file(fp, &state, NULL);
220 fclose(fp);
221 return retval;
222 }
223
224 /* Return non-zero if filename contains only alphanumeric characters, dashes,
225 * and underscores, or if the filename ends in ".conf" and is not a dotfile. */
valid_name(const char * filename)226 static int valid_name(const char *filename)
227 {
228 const char *p;
229 size_t len = strlen(filename);
230
231 /* Ignore dotfiles, which might be editor or filesystem artifacts. */
232 if (*filename == '.')
233 return 0;
234
235 if (len >= 5 && !strcmp(filename + len - 5, ".conf"))
236 return 1;
237
238 for (p = filename; *p != '\0'; p++) {
239 if (!isalnum((unsigned char)*p) && *p != '-' && *p != '_')
240 return 0;
241 }
242 return 1;
243 }
244
245 /*
246 * Include files within dirname. Only files with names ending in ".conf", or
247 * consisting entirely of alphanumeric characters, dashes, and underscores are
248 * included. This restriction avoids including editor backup files, .rpmsave
249 * files, and the like. Files are processed in alphanumeric order.
250 */
parse_include_dir(const char * dirname,struct profile_node * root_section)251 static errcode_t parse_include_dir(const char *dirname,
252 struct profile_node *root_section)
253 {
254 errcode_t retval = 0;
255 char **fnames, *pathname;
256 int i;
257
258 if (k5_dir_filenames(dirname, &fnames) != 0)
259 return PROF_FAIL_INCLUDE_DIR;
260
261 for (i = 0; fnames != NULL && fnames[i] != NULL; i++) {
262 if (!valid_name(fnames[i]))
263 continue;
264 if (asprintf(&pathname, "%s/%s", dirname, fnames[i]) < 0) {
265 retval = ENOMEM;
266 break;
267 }
268 retval = parse_include_file(pathname, root_section);
269 free(pathname);
270 if (retval)
271 break;
272 }
273 k5_free_filenames(fnames);
274 return retval;
275 }
276
parse_line(char * line,struct parse_state * state,char ** ret_modspec)277 static errcode_t parse_line(char *line, struct parse_state *state,
278 char **ret_modspec)
279 {
280 char *cp;
281
282 if (strncmp(line, "include", 7) == 0 && isspace(line[7])) {
283 cp = skip_over_blanks(line + 7);
284 strip_line(cp);
285 return parse_include_file(cp, state->root_section);
286 }
287 if (strncmp(line, "includedir", 10) == 0 && isspace(line[10])) {
288 cp = skip_over_blanks(line + 10);
289 strip_line(cp);
290 return parse_include_dir(cp, state->root_section);
291 }
292 switch (state->state) {
293 case STATE_INIT_COMMENT:
294 if (strncmp(line, "module", 6) == 0 && isspace(line[6])) {
295 /*
296 * If we are expecting a module declaration, fill in *ret_modspec
297 * and return PROF_MODULE, which will cause parsing to abort and
298 * the module to be loaded instead. If we aren't expecting a
299 * module declaration, return PROF_MODULE without filling in
300 * *ret_modspec, which will be treated as an ordinary error.
301 */
302 if (ret_modspec) {
303 cp = skip_over_blanks(line + 6);
304 strip_line(cp);
305 *ret_modspec = strdup(cp);
306 if (!*ret_modspec)
307 return ENOMEM;
308 }
309 return PROF_MODULE;
310 }
311 if (line[0] != '[')
312 return 0;
313 state->state = STATE_STD_LINE;
314 case STATE_STD_LINE:
315 return parse_std_line(line, state);
316 case STATE_GET_OBRACE:
317 cp = skip_over_blanks(line);
318 if (*cp != '{')
319 return PROF_MISSING_OBRACE;
320 state->state = STATE_STD_LINE;
321 }
322 return 0;
323 }
324
parse_file(FILE * f,struct parse_state * state,char ** ret_modspec)325 static errcode_t parse_file(FILE *f, struct parse_state *state,
326 char **ret_modspec)
327 {
328 #define BUF_SIZE 2048
329 char *bptr;
330 errcode_t retval;
331
332 bptr = malloc (BUF_SIZE);
333 if (!bptr)
334 return ENOMEM;
335
336 while (!feof(f)) {
337 if (fgets(bptr, BUF_SIZE, f) == NULL)
338 break;
339 #ifndef PROFILE_SUPPORTS_FOREIGN_NEWLINES
340 retval = parse_line(bptr, state, ret_modspec);
341 if (retval) {
342 free (bptr);
343 return retval;
344 }
345 #else
346 {
347 char *p, *end;
348
349 if (strlen(bptr) >= BUF_SIZE - 1) {
350 /* The string may have foreign newlines and
351 gotten chopped off on a non-newline
352 boundary. Seek backwards to the last known
353 newline. */
354 long offset;
355 char *c = bptr + strlen (bptr);
356 for (offset = 0; offset > -BUF_SIZE; offset--) {
357 if (*c == '\r' || *c == '\n') {
358 *c = '\0';
359 fseek (f, offset, SEEK_CUR);
360 break;
361 }
362 c--;
363 }
364 }
365
366 /* First change all newlines to \n */
367 for (p = bptr; *p != '\0'; p++) {
368 if (*p == '\r')
369 *p = '\n';
370 }
371 /* Then parse all lines */
372 p = bptr;
373 end = bptr + strlen (bptr);
374 while (p < end) {
375 char* newline;
376 char* newp;
377
378 newline = strchr (p, '\n');
379 if (newline != NULL)
380 *newline = '\0';
381
382 /* parse_line modifies contents of p */
383 newp = p + strlen (p) + 1;
384 retval = parse_line (p, state, ret_modspec);
385 if (retval) {
386 free (bptr);
387 return retval;
388 }
389
390 p = newp;
391 }
392 }
393 #endif
394 }
395
396 free (bptr);
397 return 0;
398 }
399
profile_parse_file(FILE * f,struct profile_node ** root,char ** ret_modspec)400 errcode_t profile_parse_file(FILE *f, struct profile_node **root,
401 char **ret_modspec)
402 {
403 struct parse_state state;
404 errcode_t retval;
405
406 *root = NULL;
407
408 /* Initialize parsing state with a new root node. */
409 state.state = STATE_INIT_COMMENT;
410 state.group_level = 0;
411 state.current_section = NULL;
412 retval = profile_create_node("(root)", 0, &state.root_section);
413 if (retval)
414 return retval;
415
416 retval = parse_file(f, &state, ret_modspec);
417 if (retval) {
418 profile_free_node(state.root_section);
419 return retval;
420 }
421 *root = state.root_section;
422 return 0;
423 }
424
profile_process_directory(const char * dirname,struct profile_node ** root)425 errcode_t profile_process_directory(const char *dirname,
426 struct profile_node **root)
427 {
428 errcode_t retval;
429 struct profile_node *node;
430
431 *root = NULL;
432 retval = profile_create_node("(root)", 0, &node);
433 if (retval)
434 return retval;
435 retval = parse_include_dir(dirname, node);
436 if (retval) {
437 profile_free_node(node);
438 return retval;
439 }
440 *root = node;
441 return 0;
442 }
443
444 /*
445 * Return TRUE if the string begins or ends with whitespace
446 */
need_double_quotes(char * str)447 static int need_double_quotes(char *str)
448 {
449 if (!str)
450 return 0;
451 if (str[0] == '\0')
452 return 1;
453 if (isspace((int) (*str)) ||isspace((int) (*(str + strlen(str) - 1))))
454 return 1;
455 if (strchr(str, '\n') || strchr(str, '\t') || strchr(str, '\b'))
456 return 1;
457 return 0;
458 }
459
460 /*
461 * Output a string with double quotes, doing appropriate backquoting
462 * of characters as necessary.
463 */
output_quoted_string(char * str,void (* cb)(const char *,void *),void * data)464 static void output_quoted_string(char *str, void (*cb)(const char *,void *),
465 void *data)
466 {
467 char ch;
468 char buf[2];
469
470 cb("\"", data);
471 if (!str) {
472 cb("\"", data);
473 return;
474 }
475 buf[1] = 0;
476 while ((ch = *str++)) {
477 switch (ch) {
478 case '\\':
479 cb("\\\\", data);
480 break;
481 case '\n':
482 cb("\\n", data);
483 break;
484 case '\t':
485 cb("\\t", data);
486 break;
487 case '\b':
488 cb("\\b", data);
489 break;
490 default:
491 /* This would be a lot faster if we scanned
492 forward for the next "interesting"
493 character. */
494 buf[0] = ch;
495 cb(buf, data);
496 break;
497 }
498 }
499 cb("\"", data);
500 }
501
502
503
504 #if defined(_WIN32)
505 #define EOL "\r\n"
506 #endif
507
508 #ifndef EOL
509 #define EOL "\n"
510 #endif
511
512 /* Errors should be returned, not ignored! */
dump_profile(struct profile_node * root,int level,void (* cb)(const char *,void *),void * data)513 static void dump_profile(struct profile_node *root, int level,
514 void (*cb)(const char *, void *), void *data)
515 {
516 int i;
517 struct profile_node *p;
518 void *iter;
519 long retval;
520 char *name, *value;
521
522 iter = 0;
523 do {
524 retval = profile_find_node_relation(root, 0, &iter,
525 &name, &value);
526 if (retval)
527 break;
528 for (i=0; i < level; i++)
529 cb("\t", data);
530 if (need_double_quotes(value)) {
531 cb(name, data);
532 cb(" = ", data);
533 output_quoted_string(value, cb, data);
534 cb(EOL, data);
535 } else {
536 cb(name, data);
537 cb(" = ", data);
538 cb(value, data);
539 cb(EOL, data);
540 }
541 } while (iter != 0);
542
543 iter = 0;
544 do {
545 retval = profile_find_node_subsection(root, 0, &iter,
546 &name, &p);
547 if (retval)
548 break;
549 if (level == 0) { /* [xxx] */
550 cb("[", data);
551 cb(name, data);
552 cb("]", data);
553 cb(profile_is_node_final(p) ? "*" : "", data);
554 cb(EOL, data);
555 dump_profile(p, level+1, cb, data);
556 cb(EOL, data);
557 } else { /* xxx = { ... } */
558 for (i=0; i < level; i++)
559 cb("\t", data);
560 cb(name, data);
561 cb(" = {", data);
562 cb(EOL, data);
563 dump_profile(p, level+1, cb, data);
564 for (i=0; i < level; i++)
565 cb("\t", data);
566 cb("}", data);
567 cb(profile_is_node_final(p) ? "*" : "", data);
568 cb(EOL, data);
569 }
570 } while (iter != 0);
571 }
572
dump_profile_to_file_cb(const char * str,void * data)573 static void dump_profile_to_file_cb(const char *str, void *data)
574 {
575 fputs(str, data);
576 }
577
profile_write_tree_file(struct profile_node * root,FILE * dstfile)578 errcode_t profile_write_tree_file(struct profile_node *root, FILE *dstfile)
579 {
580 dump_profile(root, 0, dump_profile_to_file_cb, dstfile);
581 return 0;
582 }
583
584 struct prof_buf {
585 char *base;
586 size_t cur, max;
587 int err;
588 };
589
add_data_to_buffer(struct prof_buf * b,const void * d,size_t len)590 static void add_data_to_buffer(struct prof_buf *b, const void *d, size_t len)
591 {
592 if (b->err)
593 return;
594 if (b->max - b->cur < len) {
595 size_t newsize;
596 char *newptr;
597
598 newsize = b->max + (b->max >> 1) + len + 1024;
599 newptr = realloc(b->base, newsize);
600 if (newptr == NULL) {
601 b->err = 1;
602 return;
603 }
604 b->base = newptr;
605 b->max = newsize;
606 }
607 memcpy(b->base + b->cur, d, len);
608 b->cur += len; /* ignore overflow */
609 }
610
dump_profile_to_buffer_cb(const char * str,void * data)611 static void dump_profile_to_buffer_cb(const char *str, void *data)
612 {
613 add_data_to_buffer((struct prof_buf *)data, str, strlen(str));
614 }
615
profile_write_tree_to_buffer(struct profile_node * root,char ** buf)616 errcode_t profile_write_tree_to_buffer(struct profile_node *root,
617 char **buf)
618 {
619 struct prof_buf prof_buf = { 0, 0, 0, 0 };
620
621 dump_profile(root, 0, dump_profile_to_buffer_cb, &prof_buf);
622 if (prof_buf.err) {
623 *buf = NULL;
624 return ENOMEM;
625 }
626 add_data_to_buffer(&prof_buf, "", 1); /* append nul */
627 if (prof_buf.max - prof_buf.cur > (prof_buf.max >> 3)) {
628 char *newptr = realloc(prof_buf.base, prof_buf.cur);
629 if (newptr)
630 prof_buf.base = newptr;
631 }
632 *buf = prof_buf.base;
633 return 0;
634 }
635