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 33 static char *skip_over_blanks(char *cp) 34 { 35 while (*cp && isspace((int) (*cp))) 36 cp++; 37 return cp; 38 } 39 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 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 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. */ 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. */ 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 */ 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 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 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 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 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 */ 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 */ 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! */ 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 585 static void dump_profile_to_file_cb(const char *str, void *data) 586 { 587 fputs(str, data); 588 } 589 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 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 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 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