summaryrefslogtreecommitdiff
path: root/src/util/profile/prof_parse.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/util/profile/prof_parse.c')
-rw-r--r--src/util/profile/prof_parse.c668
1 files changed, 668 insertions, 0 deletions
diff --git a/src/util/profile/prof_parse.c b/src/util/profile/prof_parse.c
new file mode 100644
index 0000000000000..e7c1f65aa09f3
--- /dev/null
+++ b/src/util/profile/prof_parse.c
@@ -0,0 +1,668 @@
+/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+#include "prof_int.h"
+
+#include <sys/types.h>
+#include <stdio.h>
+#include <string.h>
+#ifdef HAVE_STDLIB_H
+#include <stdlib.h>
+#endif
+#include <errno.h>
+#include <ctype.h>
+#ifndef _WIN32
+#include <dirent.h>
+#endif
+
+#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 errcode_t parse_file(FILE *f, struct parse_state *state,
+ char **ret_modspec);
+
+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;
+
+ 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_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;
+}
+
+/* Open and parse an included profile file. */
+static errcode_t parse_include_file(const char *filename,
+ struct profile_node *root_section)
+{
+ FILE *fp;
+ errcode_t retval = 0;
+ struct parse_state state;
+
+ /* Create a new state so that fragments are syntactically independent but
+ * share a root section. */
+ state.state = STATE_INIT_COMMENT;
+ state.group_level = 0;
+ state.root_section = root_section;
+ state.current_section = NULL;
+
+ fp = fopen(filename, "r");
+ if (fp == NULL)
+ return PROF_FAIL_INCLUDE_FILE;
+ retval = parse_file(fp, &state, NULL);
+ fclose(fp);
+ return retval;
+}
+
+/* Return non-zero if filename contains only alphanumeric characters, dashes,
+ * and underscores, or if the filename ends in ".conf". */
+static int valid_name(const char *filename)
+{
+ const char *p;
+ size_t len = strlen(filename);
+
+ if (len >= 5 && !strcmp(filename + len - 5, ".conf"))
+ return 1;
+
+ for (p = filename; *p != '\0'; p++) {
+ if (!isalnum((unsigned char)*p) && *p != '-' && *p != '_')
+ return 0;
+ }
+ return 1;
+}
+
+/*
+ * Include files within dirname. Only files with names ending in ".conf", or
+ * consisting entirely of alphanumeric characters, dashes, and underscores are
+ * included. This restriction avoids including editor backup files, .rpmsave
+ * files, and the like.
+ */
+static errcode_t parse_include_dir(const char *dirname,
+ struct profile_node *root_section)
+{
+#ifdef _WIN32
+ char *wildcard = NULL, *pathname;
+ WIN32_FIND_DATA ffd;
+ HANDLE handle;
+ errcode_t retval = 0;
+
+ if (asprintf(&wildcard, "%s\\*", dirname) < 0)
+ return ENOMEM;
+
+ handle = FindFirstFile(wildcard, &ffd);
+ if (handle == INVALID_HANDLE_VALUE) {
+ retval = PROF_FAIL_INCLUDE_DIR;
+ goto cleanup;
+ }
+
+ do {
+ if (!valid_name(ffd.cFileName))
+ continue;
+ if (asprintf(&pathname, "%s\\%s", dirname, ffd.cFileName) < 0) {
+ retval = ENOMEM;
+ break;
+ }
+ retval = parse_include_file(pathname, root_section);
+ free(pathname);
+ if (retval)
+ break;
+ } while (FindNextFile(handle, &ffd) != 0);
+
+ FindClose(handle);
+
+cleanup:
+ free(wildcard);
+ return retval;
+
+#else /* not _WIN32 */
+
+ DIR *dir;
+ char *pathname;
+ errcode_t retval = 0;
+ struct dirent *ent;
+
+ dir = opendir(dirname);
+ if (dir == NULL)
+ return PROF_FAIL_INCLUDE_DIR;
+ while ((ent = readdir(dir)) != NULL) {
+ if (!valid_name(ent->d_name))
+ continue;
+ if (asprintf(&pathname, "%s/%s", dirname, ent->d_name) < 0) {
+ retval = ENOMEM;
+ break;
+ }
+ retval = parse_include_file(pathname, root_section);
+ free(pathname);
+ if (retval)
+ break;
+ }
+ closedir(dir);
+ return retval;
+#endif /* not _WIN32 */
+}
+
+static errcode_t parse_line(char *line, struct parse_state *state,
+ char **ret_modspec)
+{
+ char *cp;
+
+ if (strncmp(line, "include", 7) == 0 && isspace(line[7])) {
+ cp = skip_over_blanks(line + 7);
+ strip_line(cp);
+ return parse_include_file(cp, state->root_section);
+ }
+ if (strncmp(line, "includedir", 10) == 0 && isspace(line[10])) {
+ cp = skip_over_blanks(line + 10);
+ strip_line(cp);
+ return parse_include_dir(cp, state->root_section);
+ }
+ switch (state->state) {
+ case STATE_INIT_COMMENT:
+ if (strncmp(line, "module", 6) == 0 && isspace(line[6])) {
+ /*
+ * If we are expecting a module declaration, fill in *ret_modspec
+ * and return PROF_MODULE, which will cause parsing to abort and
+ * the module to be loaded instead. If we aren't expecting a
+ * module declaration, return PROF_MODULE without filling in
+ * *ret_modspec, which will be treated as an ordinary error.
+ */
+ if (ret_modspec) {
+ cp = skip_over_blanks(line + 6);
+ strip_line(cp);
+ *ret_modspec = strdup(cp);
+ if (!*ret_modspec)
+ return ENOMEM;
+ }
+ return PROF_MODULE;
+ }
+ if (line[0] != '[')
+ return 0;
+ state->state = STATE_STD_LINE;
+ 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;
+ }
+ return 0;
+}
+
+static errcode_t parse_file(FILE *f, struct parse_state *state,
+ char **ret_modspec)
+{
+#define BUF_SIZE 2048
+ char *bptr;
+ errcode_t retval;
+
+ bptr = malloc (BUF_SIZE);
+ if (!bptr)
+ return ENOMEM;
+
+ while (!feof(f)) {
+ if (fgets(bptr, BUF_SIZE, f) == NULL)
+ break;
+#ifndef PROFILE_SUPPORTS_FOREIGN_NEWLINES
+ retval = parse_line(bptr, state, ret_modspec);
+ if (retval) {
+ 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, ret_modspec);
+ if (retval) {
+ free (bptr);
+ return retval;
+ }
+
+ p = newp;
+ }
+ }
+#endif
+ }
+
+ free (bptr);
+ return 0;
+}
+
+errcode_t profile_parse_file(FILE *f, struct profile_node **root,
+ char **ret_modspec)
+{
+ struct parse_state state;
+ errcode_t retval;
+
+ *root = NULL;
+
+ /* Initialize parsing state with a new root node. */
+ state.state = STATE_INIT_COMMENT;
+ state.group_level = 0;
+ state.current_section = NULL;
+ retval = profile_create_node("(root)", 0, &state.root_section);
+ if (retval)
+ return retval;
+
+ retval = parse_file(f, &state, ret_modspec);
+ if (retval) {
+ profile_free_node(state.root_section);
+ return retval;
+ }
+ *root = state.root_section;
+ return 0;
+}
+
+errcode_t profile_process_directory(const char *dirname,
+ struct profile_node **root)
+{
+ errcode_t retval;
+ struct profile_node *node;
+
+ *root = NULL;
+ retval = profile_create_node("(root)", 0, &node);
+ if (retval)
+ return retval;
+ retval = parse_include_dir(dirname, node);
+ if (retval) {
+ profile_free_node(node);
+ return retval;
+ }
+ *root = node;
+ 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;
+}