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 
skip_over_blanks(char * cp)30 static char *skip_over_blanks(char *cp)
31 {
32 	while (*cp && isspace((int) (*cp)))
33 		cp++;
34 	return cp;
35 }
36 
strip_line(char * line)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 
parse_quoted_string(char * str)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 
parse_init_state(struct parse_state * state)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 
parse_std_line(char * line,struct parse_state * state)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 
parse_line(char * line,struct parse_state * state)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 
profile_parse_file(FILE * f,struct profile_node ** root)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  */
need_double_quotes(char * str)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  */
output_quoted_string(char * str,void (* cb)(const char *,void *),void * data)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!  */
dump_profile(struct profile_node * root,int level,void (* cb)(const char *,void *),void * data)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 
dump_profile_to_file_cb(const char * str,void * data)444 static void dump_profile_to_file_cb(const char *str, void *data)
445 {
446 	fputs(str, data);
447 }
448 
profile_write_tree_file(struct profile_node * root,FILE * dstfile)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 
add_data_to_buffer(struct prof_buf * b,const void * d,size_t len)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 
dump_profile_to_buffer_cb(const char * str,void * data)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 
profile_write_tree_to_buffer(struct profile_node * root,char ** buf)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