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 == '\\' && *(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 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(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. */ 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 */ 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 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 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 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 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 */ 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 */ 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! */ 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 573 static void dump_profile_to_file_cb(const char *str, void *data) 574 { 575 fputs(str, data); 576 } 577 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 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 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 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