X-Git-Url: http://git.silcnet.org/gitweb/?p=silc.git;a=blobdiff_plain;f=lib%2Fsilcutil%2Fsilcconfig.c;fp=lib%2Fsilcutil%2Fsilcconfig.c;h=188ffa2b66a46e171b703db2a92162281b537d72;hp=19ae0b975b8ce0569f2c4dfc2951e7fd5f3108f1;hb=d47a87b03b846e2333ef57b2c0d81f1644992964;hpb=e3654ab77286898065796f3aba10ab9d22446190 diff --git a/lib/silcutil/silcconfig.c b/lib/silcutil/silcconfig.c index 19ae0b97..188ffa2b 100644 --- a/lib/silcutil/silcconfig.c +++ b/lib/silcutil/silcconfig.c @@ -2,15 +2,15 @@ silcconfig.c - Author: Pekka Riikonen + Author: Johnny Mnemonic - Copyright (C) 1997 - 2000 Pekka Riikonen + Copyright (C) 1997 - 2002 Pekka Riikonen This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. - + This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the @@ -21,66 +21,614 @@ #include "silcincludes.h" -/* Opens and reads a configuration file to a buffer. The read data is - returned to the ret_buffer argument. */ +/* limit debug logging verbosity */ +#if 0 +#define SILC_CONFIG_DEBUG(fmt) SILC_LOG_DEBUG(fmt) +#else +#define SILC_CONFIG_DEBUG(fmt) +#endif + +/* this is the option struct and currently it is only used internally to + * the module and other structs. */ +typedef struct SilcConfigOptionStruct { + char *name; /* *lowercase* name of the option */ + SilcConfigType type; /* important: the type of the returned value */ + SilcConfigCallback cb; /* the value handler */ + const SilcConfigTable *subtable; /* used if type is SILC_CONFIG_ARG_BLOCK */ + void *context; /* context passed to the callback function */ + struct SilcConfigOptionStruct *next; +} SilcConfigOption; + +/* unique for each config file (and included ones) */ +struct SilcConfigFileObject { + char *filename; /* the original filename opened */ + int level; /* parsing level, how many nested silc_config_main we have */ + char *base; /* this is a fixed pointer to the base location */ + char *p; /* the Parser poitner */ + uint32 len; /* fixed length of the whole file */ + uint32 line; /* current parsing line, strictly linked to p */ + bool included; /* wether this file is main or included */ +}; + +/* We need the entity to base our block-style parsing on */ +struct SilcConfigEntityObject { + SilcConfigOption *opts; /* known options list */ + SilcConfigFile *file; /* parsing file object */ +}; + +/* access error descriptions only with silc_config_strerror() */ +static char *errorstrs[] = { + "-OK", /* SILC_CONFIG_OK */ + "-SILENT", /* SILC_CONFIG_ESILENT */ + "Invalid syntax", /* SILC_CONFIG_EGENERIC */ + "Internal error! Please report this bug", /* SILC_CONFIG_EINTERNAL */ + "Can't open specified file", /* SILC_CONFIG_ECANTOPEN */ + "Expected open-brace '{'", /* SILC_CONFIG_EOPENBRACE */ + "Missing close-brace '}'", /* SILC_CONFIG_ECLOSEBRACE */ + "Invalid data type", /* SILC_CONFIG_ETYPE */ + "Unknown option", /* SILC_CONFIG_EBADOPTION */ + "Invalid text", /* SILC_CONFIG_EINVALIDTEXT */ + "Double option specification", /* SILC_CONFIG_EDOUBLE */ + "Expected data but not found", /* SILC_CONFIG_EEXPECTED */ + "Expected '='", /* SILC_CONFIG_EEXPECTEDEQUAL */ + "Unexpected data", /* SILC_CONFIG_EUNEXPECTED */ + "Missing needed fields", /* SILC_CONFIG_EMISSFIELDS */ + "Missing ';'", /* SILC_CONFIG_EMISSCOLON */ +}; + +/* return string describing SilcConfig's error code */ +char *silc_config_strerror(int errnum) +{ + if ((errnum < 0) || (errnum >= sizeof(errorstrs)/sizeof(*errorstrs)) || + (errorstrs[errnum] == NULL)) { + char *defret = "-INVALIDERROR"; + return defret; + } + return errorstrs[errnum]; +} + +/* Begin of internal SilcConfig's text util functions */ + +/* Points the first non-space character */ +static void my_trim_spaces(SilcConfigFile *file) +{ + register char *r = file->p; + while (isspace(*r)) + if (*r++ == '\n') file->line++; + file->p = r; +} +/* Skips the current line until newline (lf or cr) */ +static void my_skip_line(SilcConfigFile *file) +{ + register char *r = file->p; + while (*r && (*r != '\n') && (*r != '\r')) r++; + file->p = (*r ? r + 1 : r); + file->line++; +} +/* Obtains a text token from the current position until first separator. + * a separator is any non alphanumeric character nor "_" or "-" */ +static char *my_next_token(SilcConfigFile *file, char *to) +{ + register char *o; + my_trim_spaces(file); + o = file->p; + while (isalnum(*o) || (*o == '_') || (*o == '-')) + *to++ = *o++; + *to = '\0'; + file->p = o; + return to; +} +/* Obtains a string from the current position. The only difference from + * next_token() is that quoted-strings are also accepted */ +static char *my_get_string(SilcConfigFile *file, char *to) +{ + char *o; + my_trim_spaces(file); + o = file->p; + if (*o == '"') { + char *quot = strchr(++o, '"'); + int len = quot - o; + if (!quot) { /* XXX FIXME: gotta do something here */ + printf("Bullshit, missing matching \""); + exit(1); + } + if (len <= 0) + *to = '\0'; + else { + strncpy(to, o, len); + to[len] = '\0'; + } + /* update stream pointer */ + file->p = quot + 1; + return to; + } + /* we don't need quote parsing, fall-back to token extractor */ + my_next_token(file, to); + return to; +}; +/* Skips all comment lines and spaces lines until first useful character */ +static void my_skip_comments(SilcConfigFile *file) +{ + while (1) { + my_trim_spaces(file); + if (*file->p != '#') return; + my_skip_line(file); + } +} + +/* End of internal text functions + * Next section contains SilcConfig internal config utils */ -void silc_config_open(char *filename, SilcBuffer *ret_buffer) +/* find an option in the list by name and returns its pointer */ +static SilcConfigOption *silc_config_find_option(SilcConfigEntity ent, + const char *name) +{ + SilcConfigOption *tmp; + for (tmp = ent->opts; tmp; tmp = tmp->next) { + if (!strcasecmp(tmp->name, name)) + return tmp; + } + return NULL; +} +/* ... */ +static void *silc_config_marshall(SilcConfigType type, const char *val) +{ + void *pt; + int val_int; + bool val_bool; + char *val_tmp; + uint32 val_size; + + switch (type) { + case SILC_CONFIG_ARG_TOGGLE: + if (!strcasecmp(val, "yes") || !strcasecmp(val, "true") || + !strcasecmp(val, "on") || !strcasecmp(val, "1")) { + val_bool = TRUE; + } + else if (!strcasecmp(val, "no") || !strcasecmp(val, "false") || + !strcasecmp(val, "off") || !strcasecmp(val, "0")) { + val_bool = FALSE; + } + else + return NULL; + pt = silc_calloc(1, sizeof(val_bool)); + *(bool *)pt = (bool) val_bool; + return pt; + case SILC_CONFIG_ARG_INT: + val_int = (int) strtol(val, &val_tmp, 0); + if (*val_tmp) /* error converting string */ + return NULL; + pt = silc_calloc(1, sizeof(val_int)); + *(int *)pt = val_int; + return pt; + case SILC_CONFIG_ARG_SIZE: + val_size = (uint32) strtol(val, &val_tmp, 0); + if (val == val_tmp) + return NULL; /* really wrong, there must be at least one digit */ + /* Search for a designator */ + switch (tolower(val_tmp[0])) { + case '\0': /* None */ + break; + case 'k': /* Kilobytes */ + val_size *= (uint32) 1024; + break; + case 'm': /* Megabytes */ + val_size *= (uint32) (1024 * 1024); + break; + case 'g': + val_size *= (uint32) (1024 * 1024 * 1024); + break; + default: + return NULL; + } + /* the string must die here */ + if (val_tmp[1]) + return NULL; + pt = silc_calloc(1, sizeof(val_size)); + *(uint32 *)pt = val_size; + return pt; + case SILC_CONFIG_ARG_STR: /* the only difference between STR and STRE is */ + if (!val[0]) /* that STR cannot be empty, while STRE can. */ + return NULL; + case SILC_CONFIG_ARG_STRE: + pt = (void *) strdup(val); + return pt; + /* following types are not supposed to have a return value */ + case SILC_CONFIG_ARG_BLOCK: + case SILC_CONFIG_ARG_NONE: + return NULL; + default: + return NULL; + } + + return NULL; +} + +/* End of internal functions */ + + +/* Tries to open the config file and returns a valid SilcConfigFile object + * or NULL if failed */ + +SilcConfigFile *silc_config_open(char *configfile) { char *buffer; uint32 filelen; + SilcConfigFile *ret; - buffer = silc_file_readfile(filename, &filelen); - if (buffer == NULL) - return; + if (!(buffer = silc_file_readfile(configfile, &filelen))) + return NULL; - /* Buffer don't have EOF, but we'll need it. */ - buffer[filelen] = EOF; + ret = (SilcConfigFile *) silc_calloc(1, sizeof(*ret)); + ret->filename = strdup(configfile); + ret->base = ret->p = buffer; + ret->len = filelen; + ret->line = 1; /* line count, start from first line */ + return ret; +} - *ret_buffer = silc_buffer_alloc(filelen + 1); - silc_buffer_pull_tail(*ret_buffer, filelen + 1); - silc_buffer_put(*ret_buffer, buffer, filelen + 1); +/* Frees a file object */ - SILC_LOG_DEBUG(("Config file `%s' opened", filename)); +void silc_config_close(SilcConfigFile *file) +{ + if (file) { + /* XXX FIXME: this check could probably be removed later */ + uint32 my_len = (uint32) (strchr(file->base, EOF) - file->base); + SILC_CONFIG_DEBUG(("file=0x%x name=\"%s\" level=%d line=%lu", (uint32) file, + file->filename, file->level, file->line)); + if (my_len != file->len) { + fprintf(stderr, "FATAL ERROR: saved len and current len does not match!\n"); + abort(); + } + silc_free(file->filename); + memset(file->base, 'F', file->len); + silc_free(file->base); + memset(file, 'F', sizeof(*file)); + silc_free(file); + } } -/* Returns next token from a buffer to the dest argument. Returns the - length of the token. This is used to take tokens from a configuration - line. */ +/* initializes a SilcConfigEntity pointer allocation */ + +SilcConfigEntity silc_config_init(SilcConfigFile *file) +{ + SilcConfigEntity ret; + if (!file) + return NULL; + SILC_CONFIG_DEBUG(("Allocating new config entity")); + ret = (SilcConfigEntity) silc_calloc(1, sizeof(*ret)); + ret->file = file; + return ret; +}; + +/* Returns the original filename of the object file */ -int silc_config_get_token(SilcBuffer buffer, char **dest) +char *silc_config_get_filename(SilcConfigFile *file) { + if (file) + return file->filename; + return NULL; +} + +/* Returns the current line that file parsing arrived at */ + +uint32 silc_config_get_line(SilcConfigFile *file) +{ + if (file) + return file->line; + return 0; +} + +/* Returns a pointer to the beginning of the requested line. If the line + * was not found, NULL is returned */ + +char *silc_config_read_line(SilcConfigFile *file, uint32 line) +{ + register char *p; int len; + char *ret, *endbuf; - if (strchr(buffer->data, ':')) { - len = strcspn(buffer->data, ":"); - if (len) { - *dest = silc_calloc(len + 1, sizeof(char)); - memcpy(*dest, buffer->data, len); - } - silc_buffer_pull(buffer, len + 1); - return len; + if (!file || (line <= 0)) + return NULL; + for (p = file->base; *p && (*p != EOF); p++) { + if (line <= 1) + goto found; + if (*p == '\n') + line--; } + return NULL; - return -1; + found: + if ((endbuf = strchr(p, '\n'))) { + len = endbuf - p; + ret = silc_calloc(len, sizeof(*ret)); + strncpy(ret, p, len); + ret[len] = '\0'; + } + else { + ret = silc_calloc(strlen(p), sizeof(*ret)); + strcpy(ret, p); + } + return ret; } -/* Returns number of tokens in a buffer. */ +/* Convenience function to read the current parsed line */ -int silc_config_check_num_token(SilcBuffer buffer) +char *silc_config_read_current_line(SilcConfigFile *file) { - int len, len2, num; + return silc_config_read_line(file, file->line); +} + +/* (Private) destroy a SilcConfigEntity */ - if (strchr(buffer->data, ':')) { - len = 0; - num = 0; - while (strchr(buffer->data + len, ':')) { - num++; - len2 = strcspn(buffer->data + len, ":") + 1; - len += len2; +static void silc_config_destroy(SilcConfigEntity ent) +{ + SilcConfigOption *oldopt, *nextopt; + SILC_CONFIG_DEBUG(("Freeing config entity [ent=0x%x] [opts=0x%x]", + (uint32) ent, (uint32) ent->opts)); + for (oldopt = ent->opts; oldopt; oldopt = nextopt) { + nextopt = oldopt->next; + memset(oldopt->name, 'F', strlen(oldopt->name) + 1); + silc_free(oldopt->name); + memset(oldopt, 'F', sizeof(*oldopt)); + silc_free(oldopt); + } + memset(ent, 'F', sizeof(*ent)); + silc_free(ent); +} + +/* Registers a new option in the specified entity */ + +void silc_config_register(SilcConfigEntity ent, const char *name, + SilcConfigType type, SilcConfigCallback cb, + const SilcConfigTable *subtable, void *context) +{ + SilcConfigOption *newopt; + SILC_CONFIG_DEBUG(("Register new option=\"%s\" type=%u cb=0x%08x context=0x%08x", + name, type, (uint32) cb, (uint32) context)); + + if (!ent || !name) + return; + /* if we are registering a block, make sure there is a specified sub-table */ + if ((type == SILC_CONFIG_ARG_BLOCK) && !subtable) + return; + /* refuse special tag */ + if (!strcasecmp(name, "include")) + return; + if (silc_config_find_option(ent, name)) { + fprintf(stderr, "Internal Error: Option double registered\n"); + abort(); + } + + /* allocate and append the new option */ + newopt = (SilcConfigOption *) silc_calloc(1, sizeof(*newopt)); + newopt->name = strdup(name); + newopt->type = type; + newopt->cb = cb; + newopt->subtable = subtable; + newopt->context = context; + + if (!ent->opts) + ent->opts = newopt; + else { + SilcConfigOption *tmp; + for (tmp = ent->opts; tmp->next; tmp = tmp->next); + tmp->next = newopt; + } +} + +/* Register a new option table in the specified config entity */ + +void silc_config_register_table(SilcConfigEntity ent, + const SilcConfigTable table[], void *context) +{ + int i; + if (!ent || !table) return; + SILC_CONFIG_DEBUG(("Registering table")); + /* FIXME: some potability checks needed */ + for (i = 0; table[i].name; i++) { + silc_config_register(ent, table[i].name, table[i].type, + table[i].callback, table[i].subtable, context); + } +} + +/* ... */ + +static int silc_config_main_internal(SilcConfigEntity ent) +{ + SilcConfigFile *file = ent->file; + char **p = &file->p; + + /* loop throught statements */ + while (1) { + char buf[255]; + SilcConfigOption *thisopt; + + /* makes it pointing to the next interesting char */ + my_skip_comments(file); + /* got eof? */ + if (**p == '\0' || **p == EOF) { + if (file->level > 1) /* cannot get eof in a sub-level! */ + return SILC_CONFIG_EEXPECTED; + goto finish; + } + /* check if we completed this (sub) section (it doesn't matter if this + * is the main section) */ + if (**p == '}') { + if (file->level < 2) /* can't be! must be at least one sub-block */ + return SILC_CONFIG_EUNEXPECTED; + (*p)++; + goto finish; + } + //SILC_LOG_HEXDUMP(("Preparing lookup at line=%lu", file->line), *p, 16); + + /* obtain the keyword */ + my_next_token(file, buf); + SILC_CONFIG_DEBUG(("Looking up keyword=\"%s\" [line=%lu]", buf, file->line)); + + /* handle special directive */ + if (!strcasecmp(buf, "include")) { + int ret; + SilcConfigFile *inc_file; + SilcConfigEntity inc_ent; + + my_trim_spaces(file); /* prepare next char */ + + /* Now trying to include the specified file. The included file will + * be allowed to include sub-files but it will preserve the block-level + * of the including block. Note that the included file won't be allowed + * to raise the block level of the including block. */ + + my_get_string(file, buf); /* get the filename */ + SILC_LOG_DEBUG(("Including file \"%s\"", buf)); + /* before getting on, check if this row is REALLY complete */ + if (*(*p)++ != ';') + return SILC_CONFIG_EMISSCOLON; + + /* open the file and start the parsing */ + inc_file = silc_config_open(buf); + if (!inc_file) /* does it point a valid filename? */ + return SILC_CONFIG_ECANTOPEN; + inc_file->included = TRUE; + + /* create a new entity and hack it to use the same options */ + inc_ent = silc_config_init(inc_file); + inc_ent->opts = ent->opts; + ret = silc_config_main(inc_ent); + + /* Cleanup. + * If the included file returned an error, the application will probably + * want to output some kind of error message. Because of this, we can't + * destroy THIS file object. The hack works this way: The application + * expects to destroy the originally created object file, so we'll swap + * the original file with the included file. */ + if (ret) { + SilcConfigFile tmp_file; + SILC_CONFIG_DEBUG(("SWAPPING FILE OBJECTS")); + memcpy(&tmp_file, inc_file, sizeof(tmp_file)); + memcpy(inc_file, file, sizeof(tmp_file)); + silc_config_close(inc_file); + memcpy(file, &tmp_file, sizeof(tmp_file)); + return ret; + } + /* otherwise if no errors encoured, continue normally */ + silc_config_close(inc_file); + continue; /* this one is handled */ } - return num; + /* we have a registered option (it can also be a sub-block) */ + thisopt = silc_config_find_option(ent, buf); + if (!thisopt) + return SILC_CONFIG_EBADOPTION; + + my_trim_spaces(file); /* prepare next char */ + + /* option type is a block? */ + if (thisopt->type == SILC_CONFIG_ARG_BLOCK) { + int ret; + SilcConfigEntity sub_ent; + + SILC_CONFIG_DEBUG(("Entering sub-block")); + if (*(*p)++ != '{') + return SILC_CONFIG_EOPENBRACE; + /* build the new entity for this sub-block */ + sub_ent = silc_config_init(ent->file); + /* use the previous specified table to describe this block's options */ + silc_config_register_table(sub_ent, thisopt->subtable, thisopt->context); + /* run this block! */ + ret = silc_config_main(sub_ent); + SILC_CONFIG_DEBUG(("Returned from sub-block [ret=%d]", ret)); + + if (ret) /* now check the result */ + return ret; + + /* now call block clean-up callback (if any) */ + if (thisopt->cb) { + SILC_CONFIG_DEBUG(("Now calling clean-up callback (if any)")); + thisopt->cb(thisopt->type, thisopt->name, file->line, + NULL, thisopt->context); + } + /* Do we want ';' to be mandatory after close brace? */ + if (*(*p)++ != ';') + return SILC_CONFIG_EMISSCOLON; + } + else if (thisopt->type == SILC_CONFIG_ARG_NONE) { + /* before getting on, check if this row is REALLY complete */ + if (*(*p)++ != ';') + return SILC_CONFIG_EMISSCOLON; + SILC_CONFIG_DEBUG(("Triggering callback for none")); + if (thisopt->cb) { + thisopt->cb(thisopt->type, thisopt->name, file->line, + NULL, thisopt->context); + } + } + else { + void *pt; + int ret; + + if (*(*p)++ != '=') + return SILC_CONFIG_EEXPECTEDEQUAL; + + my_get_string(file, buf); /* get the option argument */ + SILC_CONFIG_DEBUG(("With argument=\"%s\"", buf)); + + /* before getting on, check if this row is REALLY complete */ + if (*(*p)++ != ';') + return SILC_CONFIG_EMISSCOLON; + + /* convert the option argument to the right format */ + pt = silc_config_marshall(thisopt->type, buf); + if (!pt) + return SILC_CONFIG_EINVALIDTEXT; + if (thisopt->cb) { + ret = thisopt->cb(thisopt->type, thisopt->name, file->line, + pt, thisopt->context); + if (ret) { + SILC_CONFIG_DEBUG(("Callback refused the value [ret=%d]", ret)); + return ret; + } + } + silc_free(pt); + } + continue; + + finish: + break; } - return 0; + return SILC_CONFIG_OK; +} + +/* ... */ + +int silc_config_main(SilcConfigEntity ent) +{ + SilcConfigFile *file = ent->file; + int ret; + + /* don't silently accept a NULL entity */ + if (!ent) { + ret = SILC_CONFIG_EGENERIC; + goto main_cleanup; + } + + /* call the real main and store the result */ + file->level++; + SILC_CONFIG_DEBUG(("[Lev=%d] Entering config parsing core", file->level)); + ret = silc_config_main_internal(ent); + SILC_CONFIG_DEBUG(("[Lev=%d] Quitting main [ret=%d]", file->level, ret)); + if (!file->level) /* when swap happens, we could close a file twice */ + goto main_end; + file->level--; + + /* If this file was included don't destroy the options set because it is + * the same of the including block. Although if this entity is in a + * sub-block created inside the included file, this options set must be + * destroyed. */ + main_cleanup: + if ((file->level != 0) || (file->included != TRUE)) + silc_config_destroy(ent); + + main_end: + return ret; }