/* $Id: config.c,v 1.1 2001/02/18 21:00:41 steve Exp $ */ /*- * Copyright (c) 2001 Steve C. Woodford. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by Steve C. Woodford. * 4. The name of the author may not be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include #include #include #include #include #include #include "config.h" #define CONFIG_TOK_EOF 0 #define CONFIG_TOK_SEMICOLON 1 #define CONFIG_TOK_COMMA 2 #define CONFIG_TOK_STMT_BEGIN 3 #define CONFIG_TOK_STMT_END 4 #define CONFIG_TOK_REGULAR 5 #define CONFIG_TOK_STRING 6 struct config_stream { FILE *cs_fp; char *cs_name; char cs_tok[128]; int cs_tok_type; int cs_nested; int cs_in_string; int cs_line; }; int config_init(void **csp, const char *fn) { struct config_stream *cs; if ((cs = calloc(1, sizeof(*cs))) == NULL) return (-1); if ((cs->cs_name = strdup(fn)) == NULL) { (void) free(cs); return (-1); } if ((cs->cs_fp = fopen(fn, "r")) == NULL) { (void) free(cs->cs_name); (void) free(cs); return (-1); } *csp = (void *) cs; return (0); } void config_destroy(void *csp) { struct config_stream *cs = csp; (void) fclose(cs->cs_fp); (void) free(cs->cs_name); (void) free(cs); } static int config_nextchar(struct config_stream *cs) { int ch; if ((ch = getc(cs->cs_fp)) == '#' && cs->cs_in_string == 0) while ((ch = getc(cs->cs_fp)) != EOF && ch != '\n') ; if (ch == '\n') cs->cs_line++; return (ch); } static int copy_regular_token(struct config_stream *cs, char *p, int tl) { int ch, len = tl; while ((ch = config_nextchar(cs)) != EOF) { if (isspace(ch) || ch == '\n') break; if (ch == ';' || ch == ',' || ch == '{' || ch == '}' ) { (void) ungetc(ch, cs->cs_fp); break; } if (len-- == 0) return (0); *p++ = ch; } *p = '\0'; return (tl - len + 1); } static int copy_string_token(struct config_stream *cs, char *p, int tl) { int ch, len = tl; int quoted = 0; cs->cs_in_string = 1; while (cs->cs_in_string && (ch = config_nextchar(cs)) != EOF) { switch (ch) { case '\n': cs->cs_in_string = 0; return (0); case '"': if (!quoted) { cs->cs_in_string = 0; continue; } break; case '\\': if (!quoted) { quoted = 1; continue; } break; default: break; } if (len-- == 0) return (0); *p++ = ch; } *p = '\0'; return (tl - len); } static int config_next_token(struct config_stream *cs) { int ch, rv; char *p = cs->cs_tok; /* * Skip leading white space and blank lines */ while ((ch = config_nextchar(cs)) != EOF && (isspace(ch) || ch == '\n')) ; switch (ch) { case EOF: cs->cs_tok_type = CONFIG_TOK_EOF; rv = 1; break; case ';': cs->cs_tok_type = CONFIG_TOK_SEMICOLON; rv = 1; break; case ',': cs->cs_tok_type = CONFIG_TOK_COMMA; rv = 1; break; case '{': cs->cs_tok_type = CONFIG_TOK_STMT_BEGIN; rv = 1; break; case '}': cs->cs_tok_type = CONFIG_TOK_STMT_END; rv = 1; break; case '"': cs->cs_tok_type = CONFIG_TOK_STRING; rv = copy_string_token(cs, p, sizeof(cs->cs_tok)); break; default: *p++ = ch; cs->cs_tok_type = CONFIG_TOK_REGULAR; rv = copy_regular_token(cs, p, sizeof(cs->cs_tok) - 1); break; } return (rv); } const char * config_err(void *csp, const char *fmt, ...) { struct config_stream *cs = csp; static char errbuff[256]; va_list ap; size_t len; va_start(ap, fmt); sprintf(errbuff, "%s, line %d: ", cs->cs_name, cs->cs_line); len = strlen(errbuff); vsnprintf(&errbuff[len], sizeof(errbuff) - len, fmt, ap); len = strlen(errbuff); if (len < (sizeof(errbuff) - 1) && fmt[strlen(fmt) - 1] != '\n') { errbuff[len++] = '\n'; errbuff[len] = '\0'; } va_end(ap); return (errbuff); } static void config_argv_free(char **argv, int argc) { int i; for (i = 0; i < argc; i++) (void) free(argv[i]); (void) free(argv); } static int config_get_statement(struct config_stream *cs, char ***argvp, int *is_compound, const char **errstr) { char **argv; int argv_size = 4; int argc, rv; int lasttoktype; if (config_next_token(cs) == 0) { *errstr = config_err(cs, "Keyword too long"); return (-1); } if (cs->cs_tok_type == CONFIG_TOK_EOF) return (0); if (cs->cs_tok_type == CONFIG_TOK_STMT_END) { cs->cs_nested--; return (0); } if (cs->cs_tok_type != CONFIG_TOK_REGULAR) { *errstr = config_err(cs, "Keyword expected"); return (-1); } if ((argv = malloc(sizeof(char **) * argv_size)) == NULL) { *errstr = config_err(cs, "%s", strerror(errno)); return (-1); } if ((argv[0] = strdup(cs->cs_tok)) == NULL) { *errstr = config_err(cs, "%s", strerror(errno)); (void) free(argv); return (-1); } argc = 1; *is_compound = 0; lasttoktype = CONFIG_TOK_REGULAR; while ((rv = config_next_token(cs)) > 0) { switch (cs->cs_tok_type) { case CONFIG_TOK_STMT_END: *errstr = config_err(cs, "Syntax error"); config_argv_free(argv, argc); return (-1); case CONFIG_TOK_STMT_BEGIN: *is_compound = 1; #if 0 cs->cs_nested++; #endif /* FALLTHROUGH */ case CONFIG_TOK_EOF: case CONFIG_TOK_SEMICOLON: if (lasttoktype != CONFIG_TOK_REGULAR && lasttoktype != CONFIG_TOK_STRING) { *errstr = config_err(cs, "Syntax error"); config_argv_free(argv, argc); return (-1); } *argvp = argv; return (argc); case CONFIG_TOK_COMMA: if (argc == 1 || lasttoktype == CONFIG_TOK_COMMA) { *errstr = config_err(cs, "Syntax error"); config_argv_free(argv, argc); return (-1); } break; case CONFIG_TOK_STRING: case CONFIG_TOK_REGULAR: if (argc >= argv_size) { char **nargv; argv_size *= 2; nargv = realloc(argv, sizeof(char **) * argv_size); if (nargv == NULL) { *errstr = config_err(cs, "%s", strerror(errno)); config_argv_free(argv, argc); return (-1); } argv = nargv; } if ((argv[argc++] = strdup(cs->cs_tok)) == NULL) { *errstr = config_err(cs, "%s", strerror(errno)); config_argv_free(argv, argc); return (-1); } break; } lasttoktype = cs->cs_tok_type; } config_argv_free(argv, argc); if (rv == 0) *errstr = config_err(cs, "Parameter too long"); else *errstr = config_err(cs, "%s", strerror(errno)); return (-1); } const char * config_parse(void *csp, struct config_tokens *ct, void *arg) { struct config_stream *cs = csp; struct config_tokens *mt; const char *errstr; char **argv; int argc; int stmt; #if 0 int nested; nested = cs->cs_nested; #endif while ((argc = config_get_statement(cs, &argv, &stmt, &errstr)) > 0) { for (mt = ct; mt->ct_tok && strcmp(mt->ct_tok, argv[0]); mt++) ; if (mt->ct_tok == NULL) { config_argv_free(argv, argc); return (config_err(cs, "Invalid keyword")); } if ((mt->ct_argc & (CFG_ARGC_COMPOUNDABLE | CFG_ARGC_MUST_COMPOUND)) == 0 && stmt) { config_argv_free(argv, argc); return (config_err(cs,"Unexpected compound statement")); } if ((mt->ct_argc & CFG_ARGC_MUST_COMPOUND) != 0 && stmt == 0) { config_argv_free(argv, argc); return (config_err(cs,"Compound statement required")); } if ((mt->ct_argc & CFG_ARGC_ARGS_OPTIONAL) == 0 && CFG_ARGC(mt->ct_argc) != (argc - 1)) { config_argv_free(argv, argc); return (config_err(cs, "%d parameter%s required", CFG_ARGC(mt->ct_argc), (CFG_ARGC(mt->ct_argc) > 1) ? "s" : "")); } errstr = (mt->ct_func)(cs, argv, argc, stmt, arg); config_argv_free(argv, argc); if (errstr != NULL) return (errstr); } if (argc < 0) return (errstr); #if 0 if (cs->cs_nested < 0) return (config_err(cs, "Unmatched closing brace")); else if (cs->cs_nested != nested) return (config_err(cs, "Missing closing brace")); #endif return (NULL); } const char * config_boolean(void *csp, const char *arg, int *boolp) { struct config_stream *cs = csp; if (strcmp(arg, "1") == 0 || strcasecmp(arg, "true") == 0 || strcasecmp(arg, "yes") == 0) *boolp = 1; else if (strcmp(arg, "0") == 0 || strcasecmp(arg, "false") == 0 || strcasecmp(arg, "no") == 0) *boolp = 0; else return (config_err(cs, "Invalid boolean")); return (NULL); } const char * config_integer(void *csp, const char *arg, int *intp) { struct config_stream *cs = csp; const char *p = arg; int is_hex = 0, is_neg = 0; if (p[0] == '0' && (p[1] == 'x' || p[1] == 'X')) { p += 2; is_hex = 1; } else if (p[0] == '+') p++; else if (p[0] == '-') { p++; is_neg = 1; } do { if ((is_hex && !isxdigit(*p)) || (!is_hex && !isdigit(*p))) return (config_err(cs, "Invalid integer: %s", arg)); } while (*(++p) != '\0'); *intp = (int) strtol(arg, NULL, 0); if (is_neg) *intp = -(*intp); return (NULL); }