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
23struct parse_state {
24	int	state;
25	int	group_level;
26	struct profile_node *root_section;
27	struct profile_node *current_section;
28};
29
30static char *skip_over_blanks(char *cp)
31{
32	while (*cp && isspace((int) (*cp)))
33		cp++;
34	return cp;
35}
36
37static 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
44static 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
74static 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
82static 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
209static 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
231errcode_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 */
318static 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 */
335static 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!  */
384static 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
444static void dump_profile_to_file_cb(const char *str, void *data)
445{
446	fputs(str, data);
447}
448
449errcode_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
455struct prof_buf {
456	char *base;
457	size_t cur, max;
458	int err;
459};
460
461static 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
482static 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
487errcode_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