diff options
Diffstat (limited to 'lib/support/profile.c')
-rw-r--r-- | lib/support/profile.c | 1910 |
1 files changed, 1910 insertions, 0 deletions
diff --git a/lib/support/profile.c b/lib/support/profile.c new file mode 100644 index 0000000..bdb14b1 --- /dev/null +++ b/lib/support/profile.c @@ -0,0 +1,1910 @@ +/* + * profile.c -- A simple configuration file parsing "library in a file" + * + * The profile library was originally written by Theodore Ts'o in 1995 + * for use in the MIT Kerberos v5 library. It has been + * modified/enhanced/bug-fixed over time by other members of the MIT + * Kerberos team. This version was originally taken from the Kerberos + * v5 distribution, version 1.4.2, and radically simplified for use in + * e2fsprogs. (Support for locking for multi-threaded operations, + * being able to modify and update the configuration file + * programmatically, and Mac/Windows portability have been removed. + * It has been folded into a single C source file to make it easier to + * fold into an application program.) + * + * Copyright (C) 2005, 2006 by Theodore Ts'o. + * + * %Begin-Header% + * This file may be redistributed under the terms of the GNU Public + * License. + * %End-Header% + * + * Copyright (C) 1985-2005 by the Massachusetts Institute of Technology. + * + * All rights reserved. + * + * Export of this software from the United States of America may require + * a specific license from the United States Government. It is the + * responsibility of any person or organization contemplating export to + * obtain such a license before exporting. + * + * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and + * distribute this software and its documentation for any purpose and + * without fee is hereby granted, provided that the above copyright + * notice appear in all copies and that both that copyright notice and + * this permission notice appear in supporting documentation, and that + * the name of M.I.T. not be used in advertising or publicity pertaining + * to distribution of the software without specific, written prior + * permission. Furthermore if you modify this software you must label + * your software as modified software and not distribute it in such a + * fashion that it might be confused with the original MIT software. + * M.I.T. makes no representations about the suitability of this software + * for any purpose. It is provided "as is" without express or implied + * warranty. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED + * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + */ + +#include "config.h" +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#endif +#include <stdio.h> +#ifdef HAVE_STDLIB_H +#include <stdlib.h> +#endif +#include <time.h> +#include <string.h> +#include <strings.h> +#include <errno.h> +#include <ctype.h> +#include <limits.h> +#include <stddef.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <dirent.h> +#ifdef HAVE_PWD_H +#include <pwd.h> +#endif + +#include <et/com_err.h> +#include "profile.h" +#include "prof_err.h" + +#undef STAT_ONCE_PER_SECOND +#undef HAVE_STAT + +/* + * prof_int.h + */ + +typedef long prf_magic_t; + +/* + * This is the structure which stores the profile information for a + * particular configuration file. + */ +struct _prf_file_t { + prf_magic_t magic; + char *filespec; +#ifdef STAT_ONCE_PER_SECOND + time_t last_stat; +#endif + time_t timestamp; /* time tree was last updated from file */ + int flags; /* r/w, dirty */ + int upd_serial; /* incremented when data changes */ + struct profile_node *root; + struct _prf_file_t *next; +}; + +typedef struct _prf_file_t *prf_file_t; + +/* + * The profile flags + */ +#define PROFILE_FILE_RW 0x0001 +#define PROFILE_FILE_DIRTY 0x0002 +#define PROFILE_FILE_NO_RELOAD 0x0004 + +/* + * This structure defines the high-level, user visible profile_t + * object, which is used as a handle by users who need to query some + * configuration file(s) + */ +struct _profile_t { + prf_magic_t magic; + prf_file_t first_file; +}; + +/* + * Used by the profile iterator in prof_get.c + */ +#define PROFILE_ITER_LIST_SECTION 0x0001 +#define PROFILE_ITER_SECTIONS_ONLY 0x0002 +#define PROFILE_ITER_RELATIONS_ONLY 0x0004 + +#define PROFILE_ITER_FINAL_SEEN 0x0100 + +/* + * Check if a filespec is last in a list (NULL on UNIX, invalid FSSpec on MacOS + */ + +#define PROFILE_LAST_FILESPEC(x) (((x) == NULL) || ((x)[0] == '\0')) + +struct profile_node { + errcode_t magic; + char *name; + char *value; + int group_level; + unsigned int final:1; /* Indicate don't search next file */ + unsigned int deleted:1; + struct profile_node *first_child; + struct profile_node *parent; + struct profile_node *next, *prev; +}; + +#define CHECK_MAGIC(node) \ + if ((node)->magic != PROF_MAGIC_NODE) \ + return PROF_MAGIC_NODE; + +/* profile parser declarations */ +struct parse_state { + int state; + int group_level; + int line_num; + struct profile_node *root_section; + struct profile_node *current_section; +}; + +static const char *default_filename = "<default>"; + +static profile_syntax_err_cb_t syntax_err_cb; + +static errcode_t parse_line(char *line, struct parse_state *state); + +#ifdef DEBUG_PROGRAM +static errcode_t profile_write_tree_file + (struct profile_node *root, FILE *dstfile); + +static errcode_t profile_write_tree_to_buffer + (struct profile_node *root, char **buf); +#endif + + +static void profile_free_node + (struct profile_node *relation); + +static errcode_t profile_create_node + (const char *name, const char *value, + struct profile_node **ret_node); + +#ifdef DEBUG_PROGRAM +static errcode_t profile_verify_node + (struct profile_node *node); +#endif + +static errcode_t profile_add_node + (struct profile_node *section, + const char *name, const char *value, + struct profile_node **ret_node); + +static errcode_t profile_find_node + (struct profile_node *section, + const char *name, const char *value, + int section_flag, void **state, + struct profile_node **node); + +static errcode_t profile_node_iterator + (void **iter_p, struct profile_node **ret_node, + char **ret_name, char **ret_value); + +static errcode_t profile_open_file + (const char * file, prf_file_t *ret_prof); + +static errcode_t profile_update_file + (prf_file_t prf); + +static void profile_free_file + (prf_file_t profile); + +static errcode_t profile_get_value(profile_t profile, const char *name, + const char *subname, const char *subsubname, + const char **ret_value); + + +/* + * prof_init.c --- routines that manipulate the user-visible profile_t + * object. + */ + +static int compstr(const void *m1, const void *m2) +{ + const char *s1 = *((const char * const *) m1); + const char *s2 = *((const char * const *) m2); + + return strcmp(s1, s2); +} + +static void free_list(char **list) +{ + char **cp; + + if (list == 0) + return; + + for (cp = list; *cp; cp++) + free(*cp); + free(list); +} + +static errcode_t get_dirlist(const char *dirname, char***ret_array) +{ + DIR *dir; + struct dirent *de; + struct stat st; + errcode_t retval; + char *fn, *cp; + char **array = 0, **new_array; + int max = 0, num = 0; + + dir = opendir(dirname); + if (!dir) + return errno; + + while ((de = readdir(dir)) != NULL) { + for (cp = de->d_name; *cp; cp++) { + if (!isalnum(*cp) && + (*cp != '-') && + (*cp != '_')) + break; + } + if (*cp) + continue; + fn = malloc(strlen(dirname) + strlen(de->d_name) + 2); + if (!fn) { + retval = ENOMEM; + goto errout; + } + sprintf(fn, "%s/%s", dirname, de->d_name); + if ((stat(fn, &st) < 0) || !S_ISREG(st.st_mode)) { + free(fn); + continue; + } + if (num >= max) { + max += 10; + new_array = realloc(array, sizeof(char *) * (max+1)); + if (!new_array) { + retval = ENOMEM; + free(fn); + goto errout; + } + array = new_array; + } + array[num++] = fn; + } + if (array) { + qsort(array, num, sizeof(char *), compstr); + array[num++] = 0; + } + *ret_array = array; + closedir(dir); + return 0; +errout: + if (array) + array[num] = 0; + closedir(dir); + free_list(array); + return retval; +} + +errcode_t +profile_init(const char * const *files, profile_t *ret_profile) +{ + const char * const *fs; + profile_t profile; + prf_file_t new_file, *last; + errcode_t retval = 0; + char **cpp, *cp, **array = 0; + + profile = malloc(sizeof(struct _profile_t)); + if (!profile) + return ENOMEM; + memset(profile, 0, sizeof(struct _profile_t)); + profile->magic = PROF_MAGIC_PROFILE; + last = &profile->first_file; + + /* if the filenames list is not specified return an empty profile */ + if ( files ) { + for (fs = files; !PROFILE_LAST_FILESPEC(*fs); fs++) { + if (array) + free_list(array); + array = NULL; + retval = get_dirlist(*fs, &array); + if (retval == 0) { + if (!array) + continue; + for (cpp = array; (cp = *cpp); cpp++) { + retval = profile_open_file(cp, &new_file); + if (retval == EACCES) + continue; + if (retval) + goto errout; + *last = new_file; + last = &new_file->next; + } + } else if ((retval != ENOTDIR) && + strcmp(*fs, default_filename)) + goto errout; + + retval = profile_open_file(*fs, &new_file); + /* if this file is missing, skip to the next */ + if (retval == ENOENT || retval == EACCES) { + continue; + } + if (retval) + goto errout; + *last = new_file; + last = &new_file->next; + } + /* + * If all the files were not found, return the appropriate error. + */ + if (!profile->first_file) { + retval = ENOENT; + goto errout; + } + } + + free_list(array); + *ret_profile = profile; + return 0; +errout: + free_list(array); + profile_release(profile); + return retval; +} + +void +profile_release(profile_t profile) +{ + prf_file_t p, next; + + if (!profile || profile->magic != PROF_MAGIC_PROFILE) + return; + + for (p = profile->first_file; p; p = next) { + next = p->next; + profile_free_file(p); + } + profile->magic = 0; + free(profile); +} + +/* + * This function sets the value of the pseudo file "<default>". If + * the file "<default>" had previously been passed to profile_init(), + * then def_string parameter will be parsed and used as the profile + * information for the "<default>" file. + */ +errcode_t profile_set_default(profile_t profile, const char *def_string) +{ + struct parse_state state; + prf_file_t prf; + errcode_t retval; + const char *in; + char *line, *p, *end; + int line_size, len; + + if (!def_string || !profile || profile->magic != PROF_MAGIC_PROFILE) + return PROF_MAGIC_PROFILE; + + for (prf = profile->first_file; prf; prf = prf->next) { + if (strcmp(prf->filespec, default_filename) == 0) + break; + } + if (!prf) + return 0; + + if (prf->root) { + profile_free_node(prf->root); + prf->root = 0; + } + + memset(&state, 0, sizeof(struct parse_state)); + retval = profile_create_node("(root)", 0, &state.root_section); + if (retval) + return retval; + + line = 0; + line_size = 0; + in = def_string; + while (*in) { + end = strchr(in, '\n'); + len = end ? (end - in) : (int) strlen(in); + if (len >= line_size) { + line_size = len+1; + p = realloc(line, line_size); + if (!p) { + retval = ENOMEM; + goto errout; + } + line = p; + } + memcpy(line, in, len); + line[len] = 0; + retval = parse_line(line, &state); + if (retval) { + errout: + if (syntax_err_cb) + (syntax_err_cb)(prf->filespec, retval, + state.line_num); + free(line); + if (prf->root) + profile_free_node(prf->root); + return retval; + } + if (!end) + break; + in = end+1; + } + prf->root = state.root_section; + free(line); + + return 0; +} + +/* + * prof_file.c ---- routines that manipulate an individual profile file. + */ + +errcode_t profile_open_file(const char * filespec, + prf_file_t *ret_prof) +{ + prf_file_t prf; + errcode_t retval; + char *home_env = 0; + unsigned int len; + char *expanded_filename; + + prf = malloc(sizeof(struct _prf_file_t)); + if (!prf) + return ENOMEM; + memset(prf, 0, sizeof(struct _prf_file_t)); + prf->magic = PROF_MAGIC_FILE; + + len = strlen(filespec)+1; + if (filespec[0] == '~' && filespec[1] == '/') { + home_env = getenv("HOME"); +#ifdef HAVE_PWD_H + if (home_env == NULL) { +#ifdef HAVE_GETWUID_R + struct passwd *pw, pwx; + uid_t uid; + char pwbuf[BUFSIZ]; + + uid = getuid(); + if (!getpwuid_r(uid, &pwx, pwbuf, sizeof(pwbuf), &pw) + && pw != NULL && pw->pw_dir[0] != 0) + home_env = pw->pw_dir; +#else + struct passwd *pw; + + pw = getpwuid(getuid()); + home_env = pw->pw_dir; +#endif + } +#endif + if (home_env) + len += strlen(home_env); + } + expanded_filename = malloc(len); + if (expanded_filename == 0) { + profile_free_file(prf); + return errno; + } + if (home_env) { + strcpy(expanded_filename, home_env); + strcat(expanded_filename, filespec+1); + } else + memcpy(expanded_filename, filespec, len); + + prf->filespec = expanded_filename; + + if (strcmp(prf->filespec, default_filename) != 0) { + retval = profile_update_file(prf); + if (retval) { + profile_free_file(prf); + return retval; + } + } + + *ret_prof = prf; + return 0; +} + +errcode_t profile_update_file(prf_file_t prf) +{ + errcode_t retval; +#ifdef HAVE_STAT + struct stat st; +#ifdef STAT_ONCE_PER_SECOND + time_t now; +#endif +#endif + FILE *f; + char buf[2048]; + struct parse_state state; + + if (prf->flags & PROFILE_FILE_NO_RELOAD) + return 0; + +#ifdef HAVE_STAT +#ifdef STAT_ONCE_PER_SECOND + now = time(0); + if (now == prf->last_stat && prf->root != NULL) { + return 0; + } +#endif + if (stat(prf->filespec, &st)) { + retval = errno; + return retval; + } +#ifdef STAT_ONCE_PER_SECOND + prf->last_stat = now; +#endif + if (st.st_mtime == prf->timestamp && prf->root != NULL) { + return 0; + } + if (prf->root) { + profile_free_node(prf->root); + prf->root = 0; + } +#else + /* + * If we don't have the stat() call, assume that our in-core + * memory image is correct. That is, we won't reread the + * profile file if it changes. + */ + if (prf->root) { + return 0; + } +#endif + memset(&state, 0, sizeof(struct parse_state)); + retval = profile_create_node("(root)", 0, &state.root_section); + if (retval) + return retval; + errno = 0; + f = fopen(prf->filespec, "r"); + if (f == NULL) { + retval = errno; + if (retval == 0) + retval = ENOENT; + return retval; + } + prf->upd_serial++; + while (!feof(f)) { + if (fgets(buf, sizeof(buf), f) == NULL) + break; + retval = parse_line(buf, &state); + if (retval) { + if (syntax_err_cb) + (syntax_err_cb)(prf->filespec, retval, + state.line_num); + fclose(f); + return retval; + } + } + prf->root = state.root_section; + + fclose(f); + +#ifdef HAVE_STAT + prf->timestamp = st.st_mtime; +#endif + return 0; +} + +void profile_free_file(prf_file_t prf) +{ + if (prf->root) + profile_free_node(prf->root); + free(prf->filespec); + free(prf); +} + +/* Begin the profile parser */ + +profile_syntax_err_cb_t profile_set_syntax_err_cb(profile_syntax_err_cb_t hook) +{ + profile_syntax_err_cb_t old; + + old = syntax_err_cb; + syntax_err_cb = hook; + return(old); +} + +#define STATE_INIT_COMMENT 0 +#define STATE_STD_LINE 1 +#define STATE_GET_OBRACE 2 + +static char *skip_over_blanks(char *cp) +{ + while (*cp && isspace((int) (*cp))) + cp++; + return cp; +} + +static int end_or_comment(char ch) +{ + return (ch == 0 || ch == '#' || ch == ';'); +} + +static char *skip_over_nonblanks(char *cp) +{ + while (!end_or_comment(*cp) && !isspace(*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_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; + + state->line_num++; + if (state->state == STATE_GET_OBRACE) { + cp = skip_over_blanks(line); + if (*cp != '{') + return PROF_MISSING_OBRACE; + state->state = STATE_STD_LINE; + return 0; + } + if (state->state == STATE_INIT_COMMENT) { + if (line[0] != '[') + return 0; + state->state = STATE_STD_LINE; + } + + if (*line == 0) + return 0; + strip_line(line); + cp = skip_over_blanks(line); + ch = *cp; + if (end_or_comment(ch)) + return 0; + if (ch == '[') { + if (state->group_level > 0) + return PROF_SECTION_NOTOP; + cp++; + cp = skip_over_blanks(cp); + p = strchr(cp, ']'); + if (p == NULL) + return PROF_SECTION_SYNTAX; + if (*cp == '"') { + cp++; + parse_quoted_string(cp); + } else { + *p-- = '\0'; + while (isspace(*p) && (p > cp)) + *p-- = '\0'; + if (*cp == 0) + return PROF_SECTION_SYNTAX; + } + retval = profile_find_node(state->root_section, cp, 0, 1, + &iter, &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 == '*') { + state->current_section->final = 1; + cp++; + } + /* + * Spaces or comments after ']' should not be fatal + */ + cp = skip_over_blanks(cp); + if (!end_or_comment(*cp)) + return PROF_SECTION_SYNTAX; + return 0; + } + if (ch == '}') { + if (state->group_level == 0) + return PROF_EXTRA_CBRACE; + if (*(cp+1) == '*') + state->current_section->final = 1; + state->current_section = state->current_section->parent; + 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'; + if (*tag == '"') { + tag++; + parse_quoted_string(tag); + } else { + /* Look for whitespace on left-hand side. */ + p = skip_over_nonblanks(tag); + if (*p) + *p++ = 0; + p = skip_over_blanks(p); + /* If we have more non-whitespace, it's an error. */ + if (*p) + return PROF_RELATION_SYNTAX; + } + + cp = skip_over_blanks(cp+1); + value = cp; + ch = value[0]; + if (ch == '"') { + value++; + parse_quoted_string(value); + } else if (end_or_comment(ch)) { + do_subsection++; + state->state = STATE_GET_OBRACE; + } else if (value[0] == '{') { + cp = skip_over_blanks(value+1); + ch = *cp; + if (end_or_comment(ch)) + do_subsection++; + else + return PROF_RELATION_SYNTAX; + } else { + cp = skip_over_nonblanks(value); + p = skip_over_blanks(cp); + ch = *p; + *cp = 0; + if (!end_or_comment(ch)) + return PROF_RELATION_SYNTAX; + } + 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) + state->current_section->final = 1; + state->group_level++; + return 0; + } + p = strchr(tag, '*'); + if (p) + *p = '\0'; + profile_add_node(state->current_section, tag, value, &node); + if (p) + node->final = 1; + return 0; +} + +#ifdef DEBUG_PROGRAM +/* + * Return TRUE if the string begins or ends with whitespace + */ +static int need_double_quotes(char *str) +{ + if (!str || !*str) + return 0; + if (isspace((int) (*str)) ||isspace((int) (*(str + strlen(str) - 1)))) + return 1; + if (strchr(str, '\n') || strchr(str, '\t') || strchr(str, '\b') || + strchr(str, ' ') || strchr(str, '#') || strchr(str, ';')) + 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); +} + +#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; + + iter = 0; + do { + retval = profile_find_node(root, 0, 0, 0, &iter, &p); + if (retval) + break; + for (i=0; i < level; i++) + cb("\t", data); + if (need_double_quotes(p->name)) + output_quoted_string(p->name, cb, data); + else + cb(p->name, data); + cb(" = ", data); + if (need_double_quotes(p->value)) + output_quoted_string(p->value, cb, data); + else + cb(p->value, data); + cb(EOL, data); + } while (iter != 0); + + iter = 0; + do { + retval = profile_find_node(root, 0, 0, 1, &iter, &p); + if (retval) + break; + if (level == 0) { /* [xxx] */ + cb("[", data); + if (need_double_quotes(p->name)) + output_quoted_string(p->name, cb, data); + else + cb(p->name, data); + cb("]", data); + cb(p->final ? "*" : "", 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); + if (need_double_quotes(p->name)) + output_quoted_string(p->name, cb, data); + else + cb(p->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(p->final ? "*" : "", 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; +} +#endif + +/* + * prof_tree.c --- these routines maintain the parse tree of the + * config file. + * + * All of the details of how the tree is stored is abstracted away in + * this file; all of the other profile routines build, access, and + * modify the tree via the accessor functions found in this file. + * + * Each node may represent either a relation or a section header. + * + * A section header must have its value field set to 0, and may a one + * or more child nodes, pointed to by first_child. + * + * A relation has as its value a pointer to allocated memory + * containing a string. Its first_child pointer must be null. + * + */ + +/* + * Free a node, and any children + */ +void profile_free_node(struct profile_node *node) +{ + struct profile_node *child, *next; + + if (node->magic != PROF_MAGIC_NODE) + return; + + free(node->name); + free(node->value); + + for (child=node->first_child; child; child = next) { + next = child->next; + profile_free_node(child); + } + node->magic = 0; + + free(node); +} + +#ifndef HAVE_STRDUP +#undef strdup +#define strdup MYstrdup +static char *MYstrdup (const char *s) +{ + size_t sz = strlen(s) + 1; + char *p = malloc(sz); + if (p != 0) + memcpy(p, s, sz); + return p; +} +#endif + +/* + * Create a node + */ +errcode_t profile_create_node(const char *name, const char *value, + struct profile_node **ret_node) +{ + struct profile_node *new; + + new = malloc(sizeof(struct profile_node)); + if (!new) + return ENOMEM; + memset(new, 0, sizeof(struct profile_node)); + new->magic = PROF_MAGIC_NODE; + new->name = strdup(name); + if (new->name == 0) { + profile_free_node(new); + return ENOMEM; + } + if (value) { + new->value = strdup(value); + if (new->value == 0) { + profile_free_node(new); + return ENOMEM; + } + } + + *ret_node = new; + return 0; +} + +/* + * This function verifies that all of the representation invariants of + * the profile are true. If not, we have a programming bug somewhere, + * probably in this file. + */ +#ifdef DEBUG_PROGRAM +errcode_t profile_verify_node(struct profile_node *node) +{ + struct profile_node *p, *last; + errcode_t retval; + + CHECK_MAGIC(node); + + if (node->value && node->first_child) + return PROF_SECTION_WITH_VALUE; + + last = 0; + for (p = node->first_child; p; last = p, p = p->next) { + if (p->prev != last) + return PROF_BAD_LINK_LIST; + if (last && (last->next != p)) + return PROF_BAD_LINK_LIST; + if (node->group_level+1 != p->group_level) + return PROF_BAD_GROUP_LVL; + if (p->parent != node) + return PROF_BAD_PARENT_PTR; + retval = profile_verify_node(p); + if (retval) + return retval; + } + return 0; +} +#endif + +/* + * Add a node to a particular section + */ +errcode_t profile_add_node(struct profile_node *section, const char *name, + const char *value, struct profile_node **ret_node) +{ + errcode_t retval; + struct profile_node *p, *last, *new; + + CHECK_MAGIC(section); + + if (section->value) + return PROF_ADD_NOT_SECTION; + + /* + * Find the place to insert the new node. We look for the + * place *after* the last match of the node name, since + * order matters. + */ + for (p=section->first_child, last = 0; p; last = p, p = p->next) { + int cmp; + cmp = strcmp(p->name, name); + if (cmp > 0) + break; + } + retval = profile_create_node(name, value, &new); + if (retval) + return retval; + new->group_level = section->group_level+1; + new->deleted = 0; + new->parent = section; + new->prev = last; + new->next = p; + if (p) + p->prev = new; + if (last) + last->next = new; + else + section->first_child = new; + if (ret_node) + *ret_node = new; + return 0; +} + +/* + * Iterate through the section, returning the nodes which match + * the given name. If name is NULL, then iterate through all the + * nodes in the section. If section_flag is non-zero, only return the + * section which matches the name; don't return relations. If value + * is non-NULL, then only return relations which match the requested + * value. (The value argument is ignored if section_flag is non-zero.) + * + * The first time this routine is called, the state pointer must be + * null. When this profile_find_node_relation() returns, if the state + * pointer is non-NULL, then this routine should be called again. + * (This won't happen if section_flag is non-zero, obviously.) + * + */ +errcode_t profile_find_node(struct profile_node *section, const char *name, + const char *value, int section_flag, void **state, + struct profile_node **node) +{ + struct profile_node *p; + + CHECK_MAGIC(section); + p = *state; + if (p) { + CHECK_MAGIC(p); + } else + p = section->first_child; + + for (; p; p = p->next) { + if (name && (strcmp(p->name, name))) + continue; + if (section_flag) { + if (p->value) + continue; + } else { + if (!p->value) + continue; + if (value && (strcmp(p->value, value))) + continue; + } + if (p->deleted) + continue; + /* A match! */ + if (node) + *node = p; + break; + } + if (p == 0) { + *state = 0; + return section_flag ? PROF_NO_SECTION : PROF_NO_RELATION; + } + /* + * OK, we've found one match; now let's try to find another + * one. This way, if we return a non-zero state pointer, + * there's guaranteed to be another match that's returned. + */ + for (p = p->next; p; p = p->next) { + if (name && (strcmp(p->name, name))) + continue; + if (section_flag) { + if (p->value) + continue; + } else { + if (!p->value) + continue; + if (value && (strcmp(p->value, value))) + continue; + } + /* A match! */ + break; + } + *state = p; + return 0; +} + +/* + * This is a general-purpose iterator for returning all nodes that + * match the specified name array. + */ +struct profile_iterator { + prf_magic_t magic; + profile_t profile; + int flags; + const char *const *names; + const char *name; + prf_file_t file; + int file_serial; + int done_idx; + struct profile_node *node; + int num; +}; + +errcode_t +profile_iterator_create(profile_t profile, const char *const *names, int flags, + void **ret_iter) +{ + struct profile_iterator *iter; + int done_idx = 0; + + if (profile == 0) + return PROF_NO_PROFILE; + if (profile->magic != PROF_MAGIC_PROFILE) + return PROF_MAGIC_PROFILE; + if (!names) + return PROF_BAD_NAMESET; + if (!(flags & PROFILE_ITER_LIST_SECTION)) { + if (!names[0]) + return PROF_BAD_NAMESET; + done_idx = 1; + } + + if ((iter = malloc(sizeof(struct profile_iterator))) == NULL) + return ENOMEM; + + iter->magic = PROF_MAGIC_ITERATOR; + iter->profile = profile; + iter->names = names; + iter->flags = flags; + iter->file = profile->first_file; + iter->done_idx = done_idx; + iter->node = 0; + iter->num = 0; + *ret_iter = iter; + return 0; +} + +void profile_iterator_free(void **iter_p) +{ + struct profile_iterator *iter; + + if (!iter_p) + return; + iter = *iter_p; + if (!iter || iter->magic != PROF_MAGIC_ITERATOR) + return; + free(iter); + *iter_p = 0; +} + +/* + * Note: the returned character strings in ret_name and ret_value + * points to the stored character string in the parse string. Before + * this string value is returned to a calling application + * (profile_node_iterator is not an exported interface), it should be + * strdup()'ed. + */ +errcode_t profile_node_iterator(void **iter_p, struct profile_node **ret_node, + char **ret_name, char **ret_value) +{ + struct profile_iterator *iter = *iter_p; + struct profile_node *section, *p; + const char *const *cpp; + errcode_t retval; + int skip_num = 0; + + if (!iter || iter->magic != PROF_MAGIC_ITERATOR) + return PROF_MAGIC_ITERATOR; + if (iter->file && iter->file->magic != PROF_MAGIC_FILE) + return PROF_MAGIC_FILE; + /* + * If the file has changed, then the node pointer is invalid, + * so we'll have search the file again looking for it. + */ + if (iter->node && (iter->file && + iter->file->upd_serial != iter->file_serial)) { + iter->flags &= ~PROFILE_ITER_FINAL_SEEN; + skip_num = iter->num; + iter->node = 0; + } + if (iter->node && iter->node->magic != PROF_MAGIC_NODE) { + return PROF_MAGIC_NODE; + } +get_new_file: + if (iter->node == 0) { + if (iter->file == NULL || + (iter->flags & PROFILE_ITER_FINAL_SEEN)) { + profile_iterator_free(iter_p); + if (ret_node) + *ret_node = 0; + if (ret_name) + *ret_name = 0; + if (ret_value) + *ret_value =0; + return 0; + } + if ((retval = profile_update_file(iter->file))) { + if (retval == ENOENT || retval == EACCES) { + /* XXX memory leak? */ + if (iter->file) + iter->file = iter->file->next; + skip_num = 0; + retval = 0; + goto get_new_file; + } else { + profile_iterator_free(iter_p); + return retval; + } + } + iter->file_serial = iter->file->upd_serial; + /* + * Find the section to list if we are a LIST_SECTION, + * or find the containing section if not. + */ + section = iter->file->root; + for (cpp = iter->names; cpp[iter->done_idx]; cpp++) { + for (p=section->first_child; p; p = p->next) { + if (!strcmp(p->name, *cpp) && !p->value) + break; + } + if (!p) { + section = 0; + break; + } + section = p; + if (p->final) + iter->flags |= PROFILE_ITER_FINAL_SEEN; + } + if (!section) { + if (iter->file) + iter->file = iter->file->next; + skip_num = 0; + goto get_new_file; + } + iter->name = *cpp; + iter->node = section->first_child; + } + /* + * OK, now we know iter->node is set up correctly. Let's do + * the search. + */ + for (p = iter->node; p; p = p->next) { + if (iter->name && strcmp(p->name, iter->name)) + continue; + if ((iter->flags & PROFILE_ITER_SECTIONS_ONLY) && + p->value) + continue; + if ((iter->flags & PROFILE_ITER_RELATIONS_ONLY) && + !p->value) + continue; + if (skip_num > 0) { + skip_num--; + continue; + } + if (p->deleted) + continue; + break; + } + iter->num++; + if (!p) { + if (iter->file) + iter->file = iter->file->next; + iter->node = 0; + skip_num = 0; + goto get_new_file; + } + if ((iter->node = p->next) == NULL) + if (iter->file) + iter->file = iter->file->next; + if (ret_node) + *ret_node = p; + if (ret_name) + *ret_name = p->name; + if (ret_value) + *ret_value = p->value; + return 0; +} + + +/* + * prof_get.c --- routines that expose the public interfaces for + * querying items from the profile. + * + */ + +/* + * This function only gets the first value from the file; it is a + * helper function for profile_get_string, profile_get_integer, etc. + */ +errcode_t profile_get_value(profile_t profile, const char *name, + const char *subname, const char *subsubname, + const char **ret_value) +{ + errcode_t retval; + void *state; + char *value; + const char *names[4]; + + names[0] = name; + names[1] = subname; + names[2] = subsubname; + names[3] = 0; + + if ((retval = profile_iterator_create(profile, names, + PROFILE_ITER_RELATIONS_ONLY, + &state))) + return retval; + + if ((retval = profile_node_iterator(&state, 0, 0, &value))) + goto cleanup; + + if (value) + *ret_value = value; + else + retval = PROF_NO_RELATION; + +cleanup: + profile_iterator_free(&state); + return retval; +} + +errcode_t +profile_get_string(profile_t profile, const char *name, const char *subname, + const char *subsubname, const char *def_val, + char **ret_string) +{ + const char *value; + errcode_t retval; + + if (profile) { + retval = profile_get_value(profile, name, subname, + subsubname, &value); + if (retval == PROF_NO_SECTION || retval == PROF_NO_RELATION) + value = def_val; + else if (retval) + return retval; + } else + value = def_val; + + if (value) { + *ret_string = malloc(strlen(value)+1); + if (*ret_string == 0) + return ENOMEM; + strcpy(*ret_string, value); + } else + *ret_string = 0; + return 0; +} + +errcode_t +profile_get_integer(profile_t profile, const char *name, const char *subname, + const char *subsubname, int def_val, int *ret_int) +{ + const char *value; + errcode_t retval; + char *end_value; + long ret_long; + + *ret_int = def_val; + if (profile == 0) + return 0; + + retval = profile_get_value(profile, name, subname, subsubname, &value); + if (retval == PROF_NO_SECTION || retval == PROF_NO_RELATION) { + *ret_int = def_val; + return 0; + } else if (retval) + return retval; + + if (value[0] == 0) + /* Empty string is no good. */ + return PROF_BAD_INTEGER; + errno = 0; + ret_long = strtol(value, &end_value, 0); + + /* Overflow or underflow. */ + if ((ret_long == LONG_MIN || ret_long == LONG_MAX) && errno != 0) + return PROF_BAD_INTEGER; + /* Value outside "int" range. */ + if ((long) (int) ret_long != ret_long) + return PROF_BAD_INTEGER; + /* Garbage in string. */ + if (end_value != value + strlen (value)) + return PROF_BAD_INTEGER; + + + *ret_int = ret_long; + return 0; +} + +errcode_t +profile_get_uint(profile_t profile, const char *name, const char *subname, + const char *subsubname, unsigned int def_val, + unsigned int *ret_int) +{ + const char *value; + errcode_t retval; + char *end_value; + unsigned long ret_long; + + *ret_int = def_val; + if (profile == 0) + return 0; + + retval = profile_get_value(profile, name, subname, subsubname, &value); + if (retval == PROF_NO_SECTION || retval == PROF_NO_RELATION) { + *ret_int = def_val; + return 0; + } else if (retval) + return retval; + + if (value[0] == 0) + /* Empty string is no good. */ + return PROF_BAD_INTEGER; + errno = 0; + ret_long = strtoul(value, &end_value, 0); + + /* Overflow or underflow. */ + if ((ret_long == ULONG_MAX) && errno != 0) + return PROF_BAD_INTEGER; + /* Value outside "int" range. */ + if ((unsigned long) (unsigned int) ret_long != ret_long) + return PROF_BAD_INTEGER; + /* Garbage in string. */ + if (end_value != value + strlen (value)) + return PROF_BAD_INTEGER; + + *ret_int = ret_long; + return 0; +} + +errcode_t +profile_get_double(profile_t profile, const char *name, const char *subname, + const char *subsubname, double def_val, double *ret_double) +{ + const char *value; + errcode_t retval; + char *end_value; + double double_val; + + *ret_double = def_val; + if (profile == 0) + return 0; + + retval = profile_get_value(profile, name, subname, subsubname, &value); + if (retval == PROF_NO_SECTION || retval == PROF_NO_RELATION) { + *ret_double = def_val; + return 0; + } else if (retval) + return retval; + + if (value[0] == 0) + /* Empty string is no good. */ + return PROF_BAD_INTEGER; + errno = 0; + double_val = strtod(value, &end_value); + + /* Overflow or underflow. */ + if (errno != 0) + return PROF_BAD_INTEGER; + /* Garbage in string. */ + if (end_value != value + strlen(value)) + return PROF_BAD_INTEGER; + + *ret_double = double_val; + return 0; +} + +static const char *const conf_yes[] = { + "y", "yes", "true", "t", "1", "on", + 0, +}; + +static const char *const conf_no[] = { + "n", "no", "false", "nil", "0", "off", + 0, +}; + +static errcode_t +profile_parse_boolean(const char *s, int *ret_boolean) +{ + const char *const *p; + + if (ret_boolean == NULL) + return PROF_EINVAL; + + for(p=conf_yes; *p; p++) { + if (!strcasecmp(*p,s)) { + *ret_boolean = 1; + return 0; + } + } + + for(p=conf_no; *p; p++) { + if (!strcasecmp(*p,s)) { + *ret_boolean = 0; + return 0; + } + } + + return PROF_BAD_BOOLEAN; +} + +errcode_t +profile_get_boolean(profile_t profile, const char *name, const char *subname, + const char *subsubname, int def_val, int *ret_boolean) +{ + const char *value; + errcode_t retval; + + if (profile == 0) { + *ret_boolean = def_val; + return 0; + } + + retval = profile_get_value(profile, name, subname, subsubname, &value); + if (retval == PROF_NO_SECTION || retval == PROF_NO_RELATION) { + *ret_boolean = def_val; + return 0; + } else if (retval) + return retval; + + return profile_parse_boolean (value, ret_boolean); +} + +errcode_t +profile_iterator(void **iter_p, char **ret_name, char **ret_value) +{ + char *name, *value; + errcode_t retval; + + retval = profile_node_iterator(iter_p, 0, &name, &value); + if (retval) + return retval; + + if (ret_name) { + if (name) { + *ret_name = malloc(strlen(name)+1); + if (!*ret_name) + return ENOMEM; + strcpy(*ret_name, name); + } else + *ret_name = 0; + } + if (ret_value) { + if (value) { + *ret_value = malloc(strlen(value)+1); + if (!*ret_value) { + if (ret_name) { + free(*ret_name); + *ret_name = 0; + } + return ENOMEM; + } + strcpy(*ret_value, value); + } else + *ret_value = 0; + } + return 0; +} + +#ifdef DEBUG_PROGRAM + +/* + * test_profile.c --- testing program for the profile routine + */ + +#include "argv_parse.h" +#include "profile_helpers.h" + +const char *program_name = "test_profile"; + +#define PRINT_VALUE 1 +#define PRINT_VALUES 2 + +static void do_cmd(profile_t profile, char **argv) +{ + errcode_t retval; + const char **names, *value; + char **values, **cpp; + char *cmd; + int print_status; + + cmd = *(argv); + names = (const char **) argv + 1; + print_status = 0; + retval = 0; + if (cmd == 0) + return; + if (!strcmp(cmd, "query")) { + retval = profile_get_values(profile, names, &values); + print_status = PRINT_VALUES; + } else if (!strcmp(cmd, "query1")) { + const char *name = 0; + const char *subname = 0; + const char *subsubname = 0; + + name = names[0]; + if (name) + subname = names[1]; + if (subname) + subsubname = names[2]; + if (subsubname && names[3]) { + fprintf(stderr, + "Only 3 levels are allowed with query1\n"); + retval = EINVAL; + } else + retval = profile_get_value(profile, name, subname, + subsubname, &value); + print_status = PRINT_VALUE; + } else if (!strcmp(cmd, "list_sections")) { + retval = profile_get_subsection_names(profile, names, + &values); + print_status = PRINT_VALUES; + } else if (!strcmp(cmd, "list_relations")) { + retval = profile_get_relation_names(profile, names, + &values); + print_status = PRINT_VALUES; + } else if (!strcmp(cmd, "dump")) { + retval = profile_write_tree_file + (profile->first_file->root, stdout); +#if 0 + } else if (!strcmp(cmd, "clear")) { + retval = profile_clear_relation(profile, names); + } else if (!strcmp(cmd, "update")) { + retval = profile_update_relation(profile, names+2, + *names, *(names+1)); +#endif + } else if (!strcmp(cmd, "verify")) { + retval = profile_verify_node + (profile->first_file->root); +#if 0 + } else if (!strcmp(cmd, "rename_section")) { + retval = profile_rename_section(profile, names+1, *names); + } else if (!strcmp(cmd, "add")) { + value = *names; + if (strcmp(value, "NULL") == 0) + value = NULL; + retval = profile_add_relation(profile, names+1, value); + } else if (!strcmp(cmd, "flush")) { + retval = profile_flush(profile); +#endif + } else { + printf("Invalid command.\n"); + } + if (retval) { + com_err(cmd, retval, ""); + print_status = 0; + } + switch (print_status) { + case PRINT_VALUE: + printf("%s\n", value); + break; + case PRINT_VALUES: + for (cpp = values; *cpp; cpp++) + printf("%s\n", *cpp); + profile_free_list(values); + break; + } +} + +static void do_batchmode(profile_t profile) +{ + int argc, ret; + char **argv; + char buf[256]; + + while (!feof(stdin)) { + if (fgets(buf, sizeof(buf), stdin) == NULL) + break; + printf(">%s", buf); + ret = argv_parse(buf, &argc, &argv); + if (ret != 0) { + printf("Argv_parse returned %d!\n", ret); + continue; + } + do_cmd(profile, argv); + printf("\n"); + argv_free(argv); + } + profile_release(profile); + exit(0); + +} + +void syntax_err_report(const char *filename, long err, int line_num) +{ + fprintf(stderr, "Syntax error in %s, line number %d: %s\n", + filename, line_num, error_message(err)); + exit(1); +} + +const char *default_str = "[foo]\n\tbar=quux\n\tsub = {\n\t\twin = true\n}\n"; + +int main(int argc, char **argv) +{ + profile_t profile; + long retval; + char *cmd; + + if (argc < 2) { + fprintf(stderr, "Usage: %s filename [cmd argset]\n", program_name); + exit(1); + } + + initialize_prof_error_table(); + + profile_set_syntax_err_cb(syntax_err_report); + + retval = profile_init_path(argv[1], &profile); + if (retval) { + com_err(program_name, retval, "while initializing profile"); + exit(1); + } + retval = profile_set_default(profile, default_str); + if (retval) { + com_err(program_name, retval, "while setting default"); + exit(1); + } + + cmd = *(argv+2); + if (!cmd || !strcmp(cmd, "batch")) + do_batchmode(profile); + else + do_cmd(profile, argv+2); + profile_release(profile); + + return 0; +} + +#endif |