/*
* Copyright 2008 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
#include "prof_int.h"
#include <stdio.h>
#include <string.h>
#ifdef HAVE_STDLIB_H
#include <stdlib.h>
#endif
#include <errno.h>
#include <ctype.h>
#define SECTION_SEP_CHAR '/'
#define STATE_INIT_COMMENT 1
#define STATE_STD_LINE 2
#define STATE_GET_OBRACE 3
struct parse_state {
int state;
int group_level;
struct profile_node *root_section;
struct profile_node *current_section;
};
static char *skip_over_blanks(char *cp)
{
while (*cp && isspace((int) (*cp)))
cp++;
return cp;
}
static void strip_line(char *line)
{
char *p = line + strlen(line);
while (p > line && (p[-1] == '\n' || p[-1] == '\r'))
*p-- = 0;
}
static void parse_quoted_string(char *str)
{
char *to, *from;
to = from = str;
for (to = from = str; *from && *from != '"'; to++, from++) {
if (*from == '\\') {
from++;
switch (*from) {
case 'n':
*to = '\n';
break;
case 't':
*to = '\t';
break;
case 'b':
*to = '\b';
break;
default:
*to = *from;
}
continue;
}
*to = *from;
}
*to = '\0';
}
static errcode_t parse_init_state(struct parse_state *state)
{
state->state = STATE_INIT_COMMENT;
state->group_level = 0;
return profile_create_node("(root)", 0, &state->root_section);
}
static errcode_t parse_std_line(char *line, struct parse_state *state)
{
char *cp, ch, *tag, *value;
char *p;
errcode_t retval;
struct profile_node *node;
int do_subsection = 0;
void *iter = 0;
if (*line == 0)
return 0;
cp = skip_over_blanks(line);
if (cp[0] == ';' || cp[0] == '#')
return 0;
strip_line(cp);
ch = *cp;
if (ch == 0)
return 0;
if (ch == '[') {
if (state->group_level > 0)
return PROF_SECTION_NOTOP;
cp++;
p = strchr(cp, ']');
if (p == NULL)
return PROF_SECTION_SYNTAX;
*p = '\0';
retval = profile_find_node_subsection(state->root_section,
cp, &iter, 0,
&state->current_section);
if (retval == PROF_NO_SECTION) {
retval = profile_add_node(state->root_section,
cp, 0,
&state->current_section);
if (retval)
return retval;
} else if (retval)
return retval;
/*
* Finish off the rest of the line.
*/
cp = p+1;
if (*cp == '*') {
profile_make_node_final(state->current_section);
cp++;
}
/*
* A space after ']' should not be fatal
*/
cp = skip_over_blanks(cp);
if (*cp)
return PROF_SECTION_SYNTAX;
return 0;
}
if (ch == '}') {
if (state->group_level == 0)
return PROF_EXTRA_CBRACE;
if (*(cp+1) == '*')
profile_make_node_final(state->current_section);
retval = profile_get_node_parent(state->current_section,
&state->current_section);
if (retval)
return retval;
state->group_level--;
return 0;
}
/*
* Parse the relations
*/
tag = cp;
cp = strchr(cp, '=');
if (!cp)
return PROF_RELATION_SYNTAX;
if (cp == tag)
return PROF_RELATION_SYNTAX;
*cp = '\0';
p = tag;
/* Look for whitespace on left-hand side. */
while (p < cp && !isspace((int)*p))
p++;
if (p < cp) {
/* Found some sort of whitespace. */
*p++ = 0;
/* If we have more non-whitespace, it's an error. */
while (p < cp) {
if (!isspace((int)*p))
return PROF_RELATION_SYNTAX;
p++;
}
}
cp = skip_over_blanks(cp+1);
value = cp;
if (value[0] == '"') {
value++;
parse_quoted_string(value);
} else if (value[0] == 0) {
do_subsection++;
state->state = STATE_GET_OBRACE;
} else if (value[0] == '{' && *(skip_over_blanks(value+1)) == 0)
do_subsection++;
else {
cp = value + strlen(value) - 1;
while ((cp > value) && isspace((int) (*cp)))
*cp-- = 0;
}
if (do_subsection) {
p = strchr(tag, '*');
if (p)
*p = '\0';
retval = profile_add_node(state->current_section,
tag, 0, &state->current_section);
if (retval)
return retval;
if (p)
profile_make_node_final(state->current_section);
state->group_level++;
return 0;
}
p = strchr(tag, '*');
if (p)
*p = '\0';
profile_add_node(state->current_section, tag, value, &node);
if (p)
profile_make_node_final(node);
return 0;
}
static errcode_t parse_line(char *line, struct parse_state *state)
{
char *cp;
switch (state->state) {
case STATE_INIT_COMMENT:
if (line[0] != '[')
return 0;
state->state = STATE_STD_LINE;
/*FALLTHRU*/
case STATE_STD_LINE:
return parse_std_line(line, state);
case STATE_GET_OBRACE:
cp = skip_over_blanks(line);
if (*cp != '{')
return PROF_MISSING_OBRACE;
state->state = STATE_STD_LINE;
/*FALLTHRU*/
}
return 0;
}
errcode_t profile_parse_file(FILE *f, struct profile_node **root)
{
#define BUF_SIZE 2048
char *bptr;
errcode_t retval;
struct parse_state state;
bptr = malloc (BUF_SIZE);
if (!bptr)
return ENOMEM;
retval = parse_init_state(&state);
if (retval) {
free (bptr);
return retval;
}
while (!feof(f)) {
if (fgets(bptr, BUF_SIZE, f) == NULL)
break;
#ifndef PROFILE_SUPPORTS_FOREIGN_NEWLINES
retval = parse_line(bptr, &state);
if (retval) {
/* Solaris Kerberos: check if an unconfigured file */
if (strstr(bptr, "___"))
retval = PROF_NO_PROFILE;
free (bptr);
return retval;
}
#else
{
char *p, *end;
if (strlen(bptr) >= BUF_SIZE - 1) {
/* The string may have foreign newlines and
gotten chopped off on a non-newline
boundary. Seek backwards to the last known
newline. */
long offset;
char *c = bptr + strlen (bptr);
for (offset = 0; offset > -BUF_SIZE; offset--) {
if (*c == '\r' || *c == '\n') {
*c = '\0';
fseek (f, offset, SEEK_CUR);
break;
}
c--;
}
}
/* First change all newlines to \n */
for (p = bptr; *p != '\0'; p++) {
if (*p == '\r')
*p = '\n';
}
/* Then parse all lines */
p = bptr;
end = bptr + strlen (bptr);
while (p < end) {
char* newline;
char* newp;
newline = strchr (p, '\n');
if (newline != NULL)
*newline = '\0';
/* parse_line modifies contents of p */
newp = p + strlen (p) + 1;
retval = parse_line (p, &state);
if (retval) {
free (bptr);
return retval;
}
p = newp;
}
}
#endif
}
*root = state.root_section;
free (bptr);
return 0;
}
/*
* Return TRUE if the string begins or ends with whitespace
*/
static int need_double_quotes(char *str)
{
if (!str)
return 0;
if (str[0] == '\0')
return 1;
if (isspace((int) (*str)) ||isspace((int) (*(str + strlen(str) - 1))))
return 1;
if (strchr(str, '\n') || strchr(str, '\t') || strchr(str, '\b'))
return 1;
return 0;
}
/*
* Output a string with double quotes, doing appropriate backquoting
* of characters as necessary.
*/
static void output_quoted_string(char *str, void (*cb)(const char *,void *),
void *data)
{
char ch;
char buf[2];
cb("\"", data);
if (!str) {
cb("\"", data);
return;
}
buf[1] = 0;
while ((ch = *str++)) {
switch (ch) {
case '\\':
cb("\\\\", data);
break;
case '\n':
cb("\\n", data);
break;
case '\t':
cb("\\t", data);
break;
case '\b':
cb("\\b", data);
break;
default:
/* This would be a lot faster if we scanned
forward for the next "interesting"
character. */
buf[0] = ch;
cb(buf, data);
break;
}
}
cb("\"", data);
}
#if defined(_WIN32)
#define EOL "\r\n"
#endif
#ifndef EOL
#define EOL "\n"
#endif
/* Errors should be returned, not ignored! */
static void dump_profile(struct profile_node *root, int level,
void (*cb)(const char *, void *), void *data)
{
int i;
struct profile_node *p;
void *iter;
long retval;
char *name, *value;
iter = 0;
do {
retval = profile_find_node_relation(root, 0, &iter,
&name, &value);
if (retval)
break;
for (i=0; i < level; i++)
cb("\t", data);
if (need_double_quotes(value)) {
cb(name, data);
cb(" = ", data);
output_quoted_string(value, cb, data);
cb(EOL, data);
} else {
cb(name, data);
cb(" = ", data);
cb(value, data);
cb(EOL, data);
}
} while (iter != 0);
iter = 0;
do {
retval = profile_find_node_subsection(root, 0, &iter,
&name, &p);
if (retval)
break;
if (level == 0) { /* [xxx] */
cb("[", data);
cb(name, data);
cb("]", data);
cb(profile_is_node_final(p) ? "*" : "", data);
cb(EOL, data);
dump_profile(p, level+1, cb, data);
cb(EOL, data);
} else { /* xxx = { ... } */
for (i=0; i < level; i++)
cb("\t", data);
cb(name, data);
cb(" = {", data);
cb(EOL, data);
dump_profile(p, level+1, cb, data);
for (i=0; i < level; i++)
cb("\t", data);
cb("}", data);
cb(profile_is_node_final(p) ? "*" : "", data);
cb(EOL, data);
}
} while (iter != 0);
}
static void dump_profile_to_file_cb(const char *str, void *data)
{
fputs(str, data);
}
errcode_t profile_write_tree_file(struct profile_node *root, FILE *dstfile)
{
dump_profile(root, 0, dump_profile_to_file_cb, dstfile);
return 0;
}
struct prof_buf {
char *base;
size_t cur, max;
int err;
};
static void add_data_to_buffer(struct prof_buf *b, const void *d, size_t len)
{
if (b->err)
return;
if (b->max - b->cur < len) {
size_t newsize;
char *newptr;
newsize = b->max + (b->max >> 1) + len + 1024;
newptr = realloc(b->base, newsize);
if (newptr == NULL) {
b->err = 1;
return;
}
b->base = newptr;
b->max = newsize;
}
memcpy(b->base + b->cur, d, len);
b->cur += len; /* ignore overflow */
}
static void dump_profile_to_buffer_cb(const char *str, void *data)
{
add_data_to_buffer((struct prof_buf *)data, str, strlen(str));
}
errcode_t profile_write_tree_to_buffer(struct profile_node *root,
char **buf)
{
struct prof_buf prof_buf = { 0, 0, 0, 0 };
dump_profile(root, 0, dump_profile_to_buffer_cb, &prof_buf);
if (prof_buf.err) {
*buf = NULL;
return ENOMEM;
}
add_data_to_buffer(&prof_buf, "", 1); /* append nul */
if (prof_buf.max - prof_buf.cur > (prof_buf.max >> 3)) {
char *newptr = realloc(prof_buf.base, prof_buf.cur);
if (newptr)
prof_buf.base = newptr;
}
*buf = prof_buf.base;
return 0;
}