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