Home | History | Annotate | Download | only in profile
      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