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