1 /* 2 * Copyright 2008 Sun Microsystems, Inc. All rights reserved. 3 * Use is subject to license terms. 4 */ 5 6 7 #include "prof_int.h" 8 9 #include <stdio.h> 10 #include <string.h> 11 #ifdef HAVE_STDLIB_H 12 #include <stdlib.h> 13 #endif 14 #include <errno.h> 15 #include <ctype.h> 16 17 #define SECTION_SEP_CHAR '/' 18 19 #define STATE_INIT_COMMENT 1 20 #define STATE_STD_LINE 2 21 #define STATE_GET_OBRACE 3 22 23 struct parse_state { 24 int state; 25 int group_level; 26 struct profile_node *root_section; 27 struct profile_node *current_section; 28 }; 29 30 static char *skip_over_blanks(char *cp) 31 { 32 while (*cp && isspace((int) (*cp))) 33 cp++; 34 return cp; 35 } 36 37 static void strip_line(char *line) 38 { 39 char *p = line + strlen(line); 40 while (p > line && (p[-1] == '\n' || p[-1] == '\r')) 41 *p-- = 0; 42 } 43 44 static void parse_quoted_string(char *str) 45 { 46 char *to, *from; 47 48 to = from = str; 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_init_state(struct parse_state *state) 75 { 76 state->state = STATE_INIT_COMMENT; 77 state->group_level = 0; 78 79 return profile_create_node("(root)", 0, &state->root_section); 80 } 81 82 static errcode_t parse_std_line(char *line, struct parse_state *state) 83 { 84 char *cp, ch, *tag, *value; 85 char *p; 86 errcode_t retval; 87 struct profile_node *node; 88 int do_subsection = 0; 89 void *iter = 0; 90 91 if (*line == 0) 92 return 0; 93 cp = skip_over_blanks(line); 94 if (cp[0] == ';' || cp[0] == '#') 95 return 0; 96 strip_line(cp); 97 ch = *cp; 98 if (ch == 0) 99 return 0; 100 if (ch == '[') { 101 if (state->group_level > 0) 102 return PROF_SECTION_NOTOP; 103 cp++; 104 p = strchr(cp, ']'); 105 if (p == NULL) 106 return PROF_SECTION_SYNTAX; 107 *p = '\0'; 108 retval = profile_find_node_subsection(state->root_section, 109 cp, &iter, 0, 110 &state->current_section); 111 if (retval == PROF_NO_SECTION) { 112 retval = profile_add_node(state->root_section, 113 cp, 0, 114 &state->current_section); 115 if (retval) 116 return retval; 117 } else if (retval) 118 return retval; 119 120 /* 121 * Finish off the rest of the line. 122 */ 123 cp = p+1; 124 if (*cp == '*') { 125 profile_make_node_final(state->current_section); 126 cp++; 127 } 128 /* 129 * A space after ']' should not be fatal 130 */ 131 cp = skip_over_blanks(cp); 132 if (*cp) 133 return PROF_SECTION_SYNTAX; 134 return 0; 135 } 136 if (ch == '}') { 137 if (state->group_level == 0) 138 return PROF_EXTRA_CBRACE; 139 if (*(cp+1) == '*') 140 profile_make_node_final(state->current_section); 141 retval = profile_get_node_parent(state->current_section, 142 &state->current_section); 143 if (retval) 144 return retval; 145 state->group_level--; 146 return 0; 147 } 148 /* 149 * Parse the relations 150 */ 151 tag = cp; 152 cp = strchr(cp, '='); 153 if (!cp) 154 return PROF_RELATION_SYNTAX; 155 if (cp == tag) 156 return PROF_RELATION_SYNTAX; 157 *cp = '\0'; 158 p = tag; 159 /* Look for whitespace on left-hand side. */ 160 while (p < cp && !isspace((int)*p)) 161 p++; 162 if (p < cp) { 163 /* Found some sort of whitespace. */ 164 *p++ = 0; 165 /* If we have more non-whitespace, it's an error. */ 166 while (p < cp) { 167 if (!isspace((int)*p)) 168 return PROF_RELATION_SYNTAX; 169 p++; 170 } 171 } 172 cp = skip_over_blanks(cp+1); 173 value = cp; 174 if (value[0] == '"') { 175 value++; 176 parse_quoted_string(value); 177 } else if (value[0] == 0) { 178 do_subsection++; 179 state->state = STATE_GET_OBRACE; 180 } else if (value[0] == '{' && *(skip_over_blanks(value+1)) == 0) 181 do_subsection++; 182 else { 183 cp = value + strlen(value) - 1; 184 while ((cp > value) && isspace((int) (*cp))) 185 *cp-- = 0; 186 } 187 if (do_subsection) { 188 p = strchr(tag, '*'); 189 if (p) 190 *p = '\0'; 191 retval = profile_add_node(state->current_section, 192 tag, 0, &state->current_section); 193 if (retval) 194 return retval; 195 if (p) 196 profile_make_node_final(state->current_section); 197 state->group_level++; 198 return 0; 199 } 200 p = strchr(tag, '*'); 201 if (p) 202 *p = '\0'; 203 profile_add_node(state->current_section, tag, value, &node); 204 if (p) 205 profile_make_node_final(node); 206 return 0; 207 } 208 209 static errcode_t parse_line(char *line, struct parse_state *state) 210 { 211 char *cp; 212 213 switch (state->state) { 214 case STATE_INIT_COMMENT: 215 if (line[0] != '[') 216 return 0; 217 state->state = STATE_STD_LINE; 218 /*FALLTHRU*/ 219 case STATE_STD_LINE: 220 return parse_std_line(line, state); 221 case STATE_GET_OBRACE: 222 cp = skip_over_blanks(line); 223 if (*cp != '{') 224 return PROF_MISSING_OBRACE; 225 state->state = STATE_STD_LINE; 226 /*FALLTHRU*/ 227 } 228 return 0; 229 } 230 231 errcode_t profile_parse_file(FILE *f, struct profile_node **root) 232 { 233 #define BUF_SIZE 2048 234 char *bptr; 235 errcode_t retval; 236 struct parse_state state; 237 238 bptr = malloc (BUF_SIZE); 239 if (!bptr) 240 return ENOMEM; 241 242 retval = parse_init_state(&state); 243 if (retval) { 244 free (bptr); 245 return retval; 246 } 247 while (!feof(f)) { 248 if (fgets(bptr, BUF_SIZE, f) == NULL) 249 break; 250 #ifndef PROFILE_SUPPORTS_FOREIGN_NEWLINES 251 retval = parse_line(bptr, &state); 252 if (retval) { 253 /* Solaris Kerberos: check if an unconfigured file */ 254 if (strstr(bptr, "___")) 255 retval = PROF_NO_PROFILE; 256 free (bptr); 257 return retval; 258 } 259 #else 260 { 261 char *p, *end; 262 263 if (strlen(bptr) >= BUF_SIZE - 1) { 264 /* The string may have foreign newlines and 265 gotten chopped off on a non-newline 266 boundary. Seek backwards to the last known 267 newline. */ 268 long offset; 269 char *c = bptr + strlen (bptr); 270 for (offset = 0; offset > -BUF_SIZE; offset--) { 271 if (*c == '\r' || *c == '\n') { 272 *c = '\0'; 273 fseek (f, offset, SEEK_CUR); 274 break; 275 } 276 c--; 277 } 278 } 279 280 /* First change all newlines to \n */ 281 for (p = bptr; *p != '\0'; p++) { 282 if (*p == '\r') 283 *p = '\n'; 284 } 285 /* Then parse all lines */ 286 p = bptr; 287 end = bptr + strlen (bptr); 288 while (p < end) { 289 char* newline; 290 char* newp; 291 292 newline = strchr (p, '\n'); 293 if (newline != NULL) 294 *newline = '\0'; 295 296 /* parse_line modifies contents of p */ 297 newp = p + strlen (p) + 1; 298 retval = parse_line (p, &state); 299 if (retval) { 300 free (bptr); 301 return retval; 302 } 303 304 p = newp; 305 } 306 } 307 #endif 308 } 309 *root = state.root_section; 310 311 free (bptr); 312 return 0; 313 } 314 315 /* 316 * Return TRUE if the string begins or ends with whitespace 317 */ 318 static int need_double_quotes(char *str) 319 { 320 if (!str) 321 return 0; 322 if (str[0] == '\0') 323 return 1; 324 if (isspace((int) (*str)) ||isspace((int) (*(str + strlen(str) - 1)))) 325 return 1; 326 if (strchr(str, '\n') || strchr(str, '\t') || strchr(str, '\b')) 327 return 1; 328 return 0; 329 } 330 331 /* 332 * Output a string with double quotes, doing appropriate backquoting 333 * of characters as necessary. 334 */ 335 static void output_quoted_string(char *str, void (*cb)(const char *,void *), 336 void *data) 337 { 338 char ch; 339 char buf[2]; 340 341 cb("\"", data); 342 if (!str) { 343 cb("\"", data); 344 return; 345 } 346 buf[1] = 0; 347 while ((ch = *str++)) { 348 switch (ch) { 349 case '\\': 350 cb("\\\\", data); 351 break; 352 case '\n': 353 cb("\\n", data); 354 break; 355 case '\t': 356 cb("\\t", data); 357 break; 358 case '\b': 359 cb("\\b", data); 360 break; 361 default: 362 /* This would be a lot faster if we scanned 363 forward for the next "interesting" 364 character. */ 365 buf[0] = ch; 366 cb(buf, data); 367 break; 368 } 369 } 370 cb("\"", data); 371 } 372 373 374 375 #if defined(_WIN32) 376 #define EOL "\r\n" 377 #endif 378 379 #ifndef EOL 380 #define EOL "\n" 381 #endif 382 383 /* Errors should be returned, not ignored! */ 384 static void dump_profile(struct profile_node *root, int level, 385 void (*cb)(const char *, void *), void *data) 386 { 387 int i; 388 struct profile_node *p; 389 void *iter; 390 long retval; 391 char *name, *value; 392 393 iter = 0; 394 do { 395 retval = profile_find_node_relation(root, 0, &iter, 396 &name, &value); 397 if (retval) 398 break; 399 for (i=0; i < level; i++) 400 cb("\t", data); 401 if (need_double_quotes(value)) { 402 cb(name, data); 403 cb(" = ", data); 404 output_quoted_string(value, cb, data); 405 cb(EOL, data); 406 } else { 407 cb(name, data); 408 cb(" = ", data); 409 cb(value, data); 410 cb(EOL, data); 411 } 412 } while (iter != 0); 413 414 iter = 0; 415 do { 416 retval = profile_find_node_subsection(root, 0, &iter, 417 &name, &p); 418 if (retval) 419 break; 420 if (level == 0) { /* [xxx] */ 421 cb("[", data); 422 cb(name, data); 423 cb("]", data); 424 cb(profile_is_node_final(p) ? "*" : "", data); 425 cb(EOL, data); 426 dump_profile(p, level+1, cb, data); 427 cb(EOL, data); 428 } else { /* xxx = { ... } */ 429 for (i=0; i < level; i++) 430 cb("\t", data); 431 cb(name, data); 432 cb(" = {", data); 433 cb(EOL, data); 434 dump_profile(p, level+1, cb, data); 435 for (i=0; i < level; i++) 436 cb("\t", data); 437 cb("}", data); 438 cb(profile_is_node_final(p) ? "*" : "", data); 439 cb(EOL, data); 440 } 441 } while (iter != 0); 442 } 443 444 static void dump_profile_to_file_cb(const char *str, void *data) 445 { 446 fputs(str, data); 447 } 448 449 errcode_t profile_write_tree_file(struct profile_node *root, FILE *dstfile) 450 { 451 dump_profile(root, 0, dump_profile_to_file_cb, dstfile); 452 return 0; 453 } 454 455 struct prof_buf { 456 char *base; 457 size_t cur, max; 458 int err; 459 }; 460 461 static void add_data_to_buffer(struct prof_buf *b, const void *d, size_t len) 462 { 463 if (b->err) 464 return; 465 if (b->max - b->cur < len) { 466 size_t newsize; 467 char *newptr; 468 469 newsize = b->max + (b->max >> 1) + len + 1024; 470 newptr = realloc(b->base, newsize); 471 if (newptr == NULL) { 472 b->err = 1; 473 return; 474 } 475 b->base = newptr; 476 b->max = newsize; 477 } 478 memcpy(b->base + b->cur, d, len); 479 b->cur += len; /* ignore overflow */ 480 } 481 482 static void dump_profile_to_buffer_cb(const char *str, void *data) 483 { 484 add_data_to_buffer((struct prof_buf *)data, str, strlen(str)); 485 } 486 487 errcode_t profile_write_tree_to_buffer(struct profile_node *root, 488 char **buf) 489 { 490 struct prof_buf prof_buf = { 0, 0, 0, 0 }; 491 492 dump_profile(root, 0, dump_profile_to_buffer_cb, &prof_buf); 493 if (prof_buf.err) { 494 *buf = NULL; 495 return ENOMEM; 496 } 497 add_data_to_buffer(&prof_buf, "", 1); /* append nul */ 498 if (prof_buf.max - prof_buf.cur > (prof_buf.max >> 3)) { 499 char *newptr = realloc(prof_buf.base, prof_buf.cur); 500 if (newptr) 501 prof_buf.base = newptr; 502 } 503 *buf = prof_buf.base; 504 return 0; 505 } 506