From 7fcead4473bc224bc8c6a66e2db3b3ee87f751d4 Mon Sep 17 00:00:00 2001 From: Stef Walter Date: Wed, 25 Jan 2006 04:07:04 +0000 Subject: Configuration file parsing for rrdbotd. --- src/Makefile.am | 5 +- src/common/hash.c | 53 +++--- src/common/hash.h | 30 +++- src/config.c | 506 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/rrdbotd.c | 214 +++++------------------ src/rrdbotd.h | 48 +++++- src/snmp-help.c | 111 ++++++++++++ 7 files changed, 763 insertions(+), 204 deletions(-) create mode 100644 src/config.c create mode 100644 src/snmp-help.c diff --git a/src/Makefile.am b/src/Makefile.am index 7aa9605..9f460fb 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -2,7 +2,8 @@ SUBDIRS = bsnmp sbin_PROGRAMS = rrdbotd -rrdbotd_SOURCES = rrdbotd.c rrdbotd.h snmpclient.c snmpclient.h usuals.h \ - common/stringx.h common/stringx.c +rrdbotd_SOURCES = rrdbotd.c rrdbotd.h config.c snmp-help.c \ + snmpclient.c snmpclient.h \ + usuals.h common/stringx.h common/stringx.c common/hash.h common/hash.c rrdbotd_CFLAGS = -I${top_srcdir}/src/common/ -I${top_srcdir} -Ibsnmp rrdbotd_LDADD = $(top_builddir)/src/bsnmp/libbsnmp-custom.a diff --git a/src/common/hash.c b/src/common/hash.c index eb48c88..789e85c 100644 --- a/src/common/hash.c +++ b/src/common/hash.c @@ -55,7 +55,7 @@ #include #include "hash.h" -#define KEY_DATA(he) (void*)(((unsigned char*)(he)) + sizeof(*(he))) +#define KEY_DATA(he) ((he)->key) /* * The internal form of a hash table. @@ -72,6 +72,8 @@ struct hsh_entry_t { hsh_entry_t* next; unsigned int hash; + const void* key; + size_t klen; const void* val; }; @@ -103,25 +105,24 @@ struct hsh_t hsh_index_t iterator; /* For hsh_first(...) */ unsigned int count; unsigned int max; - unsigned int klen; }; #define INITIAL_MAX 15 /* tunable == 2^n - 1 */ #define int_malloc malloc +#define int_calloc calloc #define int_free free - /* * Hash creation functions. */ static hsh_entry_t** alloc_array(hsh_t* ht, unsigned int max) { - return int_calloc(sizeof(*(ht->array)) * (max + 1)); + return (hsh_entry_t**)int_calloc(sizeof(*(ht->array)), (max + 1)); } -hsh_t* hsh_create(size_t klen) +hsh_t* hsh_create() { hsh_t* ht = int_malloc(sizeof(hsh_t)); if(ht) @@ -129,7 +130,6 @@ hsh_t* hsh_create(size_t klen) ht->count = 0; ht->max = INITIAL_MAX; ht->array = alloc_array(ht, ht->max); - ht->klen = klen; if(!ht->array) { int_free(ht); @@ -181,10 +181,12 @@ hsh_index_t* hsh_first(hsh_t* ht) return hsh_next(hi); } -void* hsh_this(hsh_index_t* hi, const void** key) +void* hsh_this(hsh_index_t* hi, const void** key, size_t* klen) { if(key) *key = KEY_DATA(hi->ths); + if(klen) + *klen = hi->ths->klen; return (void*)hi->ths->val; } @@ -229,7 +231,7 @@ static int expand_array(hsh_t* ht) * that hash entries can be removed. */ -static hsh_entry_t** find_entry(hsh_t* ht, const void* key, const void* val) +static hsh_entry_t** find_entry(hsh_t* ht, const void* key, size_t klen, const void* val) { hsh_entry_t** hep; hsh_entry_t* he; @@ -237,8 +239,6 @@ static hsh_entry_t** find_entry(hsh_t* ht, const void* key, const void* val) unsigned int hash; size_t i; - size_t klen = ht->klen; - /* * This is the popular `times 33' hash algorithm which is used by * perl and also appears in Berkeley DB. This is one of the best @@ -278,6 +278,14 @@ static hsh_entry_t** find_entry(hsh_t* ht, const void* key, const void* val) */ hash = 0; + if(klen == HSH_KEY_STRING) + { + for(p = key; *p; p++) + hash = hash * 33 + *p; + + klen = p - (const unsigned char *)key; + } + else { for(p = key, i = klen; i; i--, p++) hash = hash * 33 + *p; @@ -288,6 +296,7 @@ static hsh_entry_t** find_entry(hsh_t* ht, const void* key, const void* val) he; hep = &he->next, he = *hep) { if(he->hash == hash && + he->klen == klen && memcmp(KEY_DATA(he), key, klen) == 0) break; } @@ -296,15 +305,17 @@ static hsh_entry_t** find_entry(hsh_t* ht, const void* key, const void* val) return hep; /* add a new entry for non-NULL val */ - he = int_malloc(sizeof(*he) + klen); + he = int_malloc(sizeof(*he)); + if(he) { - /* Key data points past end of entry */ - memcpy(KEY_DATA(he), key, klen); + /* Key points to external data */ + he->key = key; + he->klen = klen; he->next = NULL; he->hash = hash; - he->val = val; + he->val = val; *hep = he; ht->count++; @@ -313,18 +324,19 @@ static hsh_entry_t** find_entry(hsh_t* ht, const void* key, const void* val) return hep; } -void* hsh_get(hsh_t* ht, const void *key) +void* hsh_get(hsh_t* ht, const void *key, size_t klen) { - hsh_entry_t** he = find_entry(ht, key, NULL); + hsh_entry_t** he = find_entry(ht, key, klen, NULL); + if(he && *he) return (void*)((*he)->val); else return NULL; } -int hsh_set(hsh_t* ht, const void* key, void* val) +int hsh_set(hsh_t* ht, const void* key, size_t klen, void* val) { - hsh_entry_t** hep = find_entry(ht, key, val); + hsh_entry_t** hep = find_entry(ht, key, klen, val); if(hep && *hep) { @@ -344,9 +356,9 @@ int hsh_set(hsh_t* ht, const void* key, void* val) return 0; } -void* hsh_rem(hsh_t* ht, const void* key) +void* hsh_rem(hsh_t* ht, const void* key, size_t klen) { - hsh_entry_t** hep = find_entry(ht, key, NULL); + hsh_entry_t** hep = find_entry(ht, key, klen, NULL); void* val = NULL; if(hep && *hep) @@ -365,3 +377,4 @@ unsigned int hsh_count(hsh_t* ht) { return ht->count; } + diff --git a/src/common/hash.h b/src/common/hash.h index 7e2cb66..df34a7a 100644 --- a/src/common/hash.h +++ b/src/common/hash.h @@ -54,6 +54,14 @@ #ifndef __HSH_H__ #define __HSH_H__ +/* + * OPTIONAL FEATURES + * + * Features to define. You need to build both this file and + * the corresponding hash.c file with whatever options you set here. + * These affect the method signatures, so see the sections below + * for the actual options + */ /* * ARGUMENT DOCUMENTATION @@ -63,6 +71,7 @@ * klen: The length of the key * val: Pointer to the value * hi: A hashtable iterator + * stamp: A unix timestamp */ @@ -76,16 +85,15 @@ typedef struct hsh_t hsh_t; /* Abstract type for scanning hash tables. */ typedef struct hsh_index_t hsh_index_t; - -/* ---------------------------------------------------------------------------------- - * FUNCS +/* ----------------------------------------------------------------------------- + * MAIN */ /* * hsh_create : Create a hash table * - returns an allocated hashtable */ -hsh_t* hsh_create(size_t klen); +hsh_t* hsh_create(); /* * hsh_free : Free a hash table @@ -102,19 +110,19 @@ unsigned int hsh_count(hsh_t* ht); * hsh_get: Retrieves a value from the hash table * - returns the value of the entry */ -void* hsh_get(hsh_t* ht, const void* key); +void* hsh_get(hsh_t* ht, const void* key, size_t klen); /* * hsh_set: Set a value in the hash table * - returns 1 if the entry was added properly */ -int hsh_set(hsh_t* ht, const void* key, void* val); +int hsh_set(hsh_t* ht, const void* key, size_t klen, void* val); /* * hsh_rem: Remove a value from the hash table * - returns the value of the removed entry */ -void* hsh_rem(hsh_t* ht, const void* key); +void* hsh_rem(hsh_t* ht, const void* key, size_t klen); /* * hsh_first: Start enumerating through the hash table @@ -132,6 +140,12 @@ hsh_index_t* hsh_next(hsh_index_t* hi); * hsh_this: While enumerating get current value * - returns the value that the iterator currently points to */ -void* hsh_this(hsh_index_t* hi, const void** key); +void* hsh_this(hsh_index_t* hi, const void** key, size_t* klen); + +/* + * This can be passed as 'klen' in any of the above functions to indicate + * a string-valued key, and have hash compute the length automatically. + */ +#define HSH_KEY_STRING (-1) #endif /* __HSH_H__ */ diff --git a/src/config.c b/src/config.c new file mode 100644 index 0000000..066203f --- /dev/null +++ b/src/config.c @@ -0,0 +1,506 @@ +/* + * Copyright (c) 2005, Nate Nielsen + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * * 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. + * * The names of contributors to this software may not be + * used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "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 + * COPYRIGHT OWNER OR CONTRIBUTORS 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. + * + * + * CONTRIBUTORS + * Nate Nielsen + * + */ + +#include "usuals.h" +#include +#include +#include +#include + +#include "stringx.h" +#include "rrdbotd.h" + +/* TODO: Put file names in all the parsing error messages */ + +/* + * These routines parse the configuration files and setup the in memory + * data structures. They're mostly run before becoming a daemon, and just + * exit on error. + */ + +typedef struct _config_ctx +{ + const char* confname; + char* configmem; + uint32_t interval; + rb_item* items; +} +config_ctx; + +/* ----------------------------------------------------------------------------- + * STRINGS + */ + +#define CONFIG_POLL "poll" +#define CONFIG_INTERVAL "interval" +#define CONFIG_FIELD "field." +#define CONFIG_SNMP "snmp" + +#define FIELD_VALID "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_-0123456789." + +/* ----------------------------------------------------------------------------- + * CONFIG LOADING + */ + +static void +config_done(config_ctx* ctx) +{ + char key[MAXPATHLEN]; + rb_item* it; + rb_poller* poll; + char *t; + + /* No polling specified */ + if(!ctx->items) + return; + + /* Make sure we found an interval */ + if(ctx->interval == 0) + errx(2, "no interval specified in config file: %s", ctx->confname); + + /* And a nice key for lookups */ + snprintf(key, sizeof(key), "%d:%s/%s.rrd", ctx->interval, g_state.rrddir, ctx->confname); + key[sizeof(key) - 1] = 0; + + /* See if we have one of these pollers already */ + poll = (rb_poller*)hsh_get(g_state.poll_by_key, key, -1); + if(!poll) + { + poll = (rb_poller*)calloc(1, sizeof(*poll)); + + if(!poll || !hsh_set(g_state.poll_by_key, key, -1, poll)) + errx(1, "out of memory"); + + strcpy(poll->key, key); + t = strchr(poll->key, ':'); + ASSERT(t); + poll->rrdname = t + 1; + + poll->interval = ctx->interval; + + /* Add it to the main lists */ + poll->next = g_state.polls; + g_state.polls = poll; + } + + /* Get the last item and add to the list */ + for(it = ctx->items; it->next; it = it->next) + it->poller = poll; + it->next->poller = poll; + + /* Add the items to this poller */ + it->next = poll->items; + poll->items = ctx->items; + + /* + * This remains allocated for the life of the program as + * All the configuration strings are in this memory. + * This allows all the users of these strings not to worry + * about reallocating or freeing them + */ + rb_atexit(free, ctx->configmem); + ctx->configmem = NULL; + + /* Clear current config and get ready for next */ + ctx->items = NULL; + ctx->interval = 0; + + +} + +static void +parse_uri(char *uri, char** scheme, char** host, + char** user, char** path, config_ctx* ctx) +{ + /* Copy only for error messages as we mess with original */ + char* copy = strdup(uri); + char* t; + + *host = NULL; + *path = NULL; + *user = NULL; + + *scheme = strsep(&uri, ":"); + if(uri == NULL) + errx(2, "invalid poll uri (scheme invalid): %s", copy); + + if((uri[0] != '/' && uri[1] != '/')) + errx(2, "invalid poll uri (scheme invalid): %s", copy); + + uri += 2; + *host = strsep(&uri, "/"); + if(*host[0]) + { + /* Parse the user name out from the host */ + t = strchr(*host, '@'); + if(t) + { + *t = 0; + *user = *host; + *host = t + 1; + } + } + + if(!*host[0]) + errx(2, "invalid poll uri (no hostname found): %s", copy); + + if(!uri || !uri[0] || !uri[1]) + errx(2, "invalid poll uri (no pathname found): %s", copy); + + *path = uri; + + /* This copy only for error messages */ + free(copy); +} + +static rb_item* +parse_item(const char* field, char* uri, config_ctx *ctx) +{ + rb_item *ritem; + rb_host *rhost; + + char* host; + char* user; + char* scheme; + char* path; + + /* Parse the SNMP URI */ + parse_uri(uri, &scheme, &host, &user, &path, ctx); + ASSERT(scheme && host && path); + + /* Currently we only support SNMP pollers */ + if(strcmp(scheme, CONFIG_SNMP) != 0) + errx(2, "invalid poll scheme: %s", scheme); + + /* See if we can find an associated host */ + rhost = (rb_host*)hsh_get(g_state.host_by_name, host, -1); + if(!rhost) + { + /* Make a new one if necessary */ + rhost = (rb_host*)calloc(1, sizeof(*rhost)); + + if(!rhost || !hsh_set(g_state.host_by_name, host, -1, rhost)) + errx(1, "out of memory"); + + rhost->name = host; + + /* And add it to the list */ + rhost->next = g_state.hosts; + g_state.hosts = rhost; + } + + /* Make a new item */ + ritem = calloc(1, sizeof(*ritem)); + if(!ritem) + errx(1, "out of memory"); + + ritem->rrdfield = field; + ritem->community = user ? user : "public"; + ritem->host = rhost; + ritem->poller = NULL; /* Set later in config_done */ + + /* And parse the OID */ + if(rb_parse_mib(path, &(ritem->value)) == -1) + errx(2, "invalid OID: %s", path + 1); + + /* And add it to the list */ + ritem->next = ctx->items; + ctx->items = ritem; +} + +static void +config_value(const char* header, const char* name, char* value, + config_ctx* ctx) +{ + if(strcmp(header, "poll") != 0) + return; + + if(strcmp(name, CONFIG_INTERVAL) == 0) + { + char* t; + int i; + + if(ctx->interval > 0) + errx(2, CONFIG_INTERVAL " specified twice: %s", value); + + i = strtol(value, &t, 10); + if(i < 1) + err(2, CONFIG_INTERVAL " must be a positive number: %s", value); + + ctx->interval = (uint32_t)i; + } + + /* If it starts with "field." */ + if(strncmp(name, CONFIG_FIELD, KL(CONFIG_FIELD)) == 0) + { + rb_poller* poll; + const char* field; + const char* t; + + /* Check the name */ + field = name + KL(CONFIG_FIELD); + t = field + strspn(field, FIELD_VALID); + if(*t) + err(2, "the '%s' field name must only contain characters, digits, underscore and dash", field); + + /* Parse out the field */ + parse_item(field, value, ctx); + } +} + +/* ----------------------------------------------------------------------------- + * CONFIG FILES + */ + +static char* +read_config_file(const char* configfile) +{ + char* config = NULL; + FILE* f = NULL; + long len; + + ASSERT(configfile); + + f = fopen(configfile, "r"); + if(f == NULL) + err(1, "couldn't open config file: %s", configfile); + + /* Figure out size */ + if(fseek(f, 0, SEEK_END) == -1 || (len = ftell(f)) == -1 || fseek(f, 0, SEEK_SET) == -1) + err(1, "couldn't seek config file: %s", configfile); + + if((config = (char*)malloc(len + 2)) == NULL) + errx(1, "out of memory"); + + /* And read in one block */ + if(fread(config, 1, len, f) != len) + err(1, "couldn't read config file: %s", configfile); + + fclose(f); + + /* Null terminate the data */ + config[len] = '\n'; + config[len + 1] = 0; + + /* Remove nasty dos line endings */ + remove_cr(config); + + rb_messagex(LOG_DEBUG, "read config file: %s", configfile); + return config; +} + +static void +parse_config_file(const char* configfile, config_ctx *ctx) +{ + char* name = NULL; + char* value = NULL; + char* config; + char* next; + char* header; + char* p; + char* t; + int pos; + + config = read_config_file(configfile); + ctx->configmem = config; + next = config; + + /* Go through lines and process them */ + while((t = strchr(next, '\n')) != NULL) + { + *t = 0; + p = next; /* Do this before cleaning below */ + next = t + 1; + + t = trim_start(p); + + /* Continuation line (had spaces at start) */ + if(p < t && *t) + { + if(!value) + errx(2, "invalid continuation in config: %s", p); + + /* Calculate the end of the current value */ + t = value + strlen(value); + ASSERT(t < p); + + /* Continuations are separated by spaces */ + *t = ' '; + t++; + + continue; + } + + // No continuation hand off value if necessary + if(name && value) + { + rb_messagex(LOG_DEBUG, "parsed configuration value: [%s] %s = %s", header, name, value); + config_value(header, name, value, ctx); + } + + name = NULL; + value = NULL; + + /* Empty lines / comments at start / comments without continuation */ + if(!*t || *p == '#') + continue; + + /* A header */ + if(*p == '[') + { + t = p + strcspn(p, "]"); + if(!*t || t == p + 1) + errx(2, "invalid config header: %s", p); + + *t = 0; + header = trim_space(p + 1); + continue; + } + + /* Look for the break between name = value on the same line */ + t = p + strcspn(p, ":="); + if(!*t) + errx(2, "invalid config line: %s", p); + + /* Null terminate and split value part */ + *t = 0; + t++; + + name = trim_space(p); + value = trim_space(t); + } + + if(name && value) + { + rb_messagex(LOG_DEBUG, "parsed configuration value: [%s] %s = %s", header, name, value); + config_value(header, name, value, ctx); + } + + config_done(ctx); + + /* If nobody claimed this memory then we don't need to keep it around */ + if(ctx->configmem) + free(ctx->configmem); + ctx->configmem = NULL; +} + +void +rb_config_parse() +{ + char configfile[MAXPATHLEN]; + struct dirent* dire; + config_ctx ctx; + DIR* dir; + + /* Setup the hash tables properly */ + g_state.poll_by_key = hsh_create(); + g_state.host_by_name = hsh_create(); + + dir = opendir(g_state.confdir); + if(!dir) + err("couldn't read config directory: %s", g_state.confdir); + + while((dire = readdir(dir)) != NULL) + { + if(dire->d_type != DT_REG && dire->d_type != DT_LNK) + continue; + + /* Build a happy path name */ + snprintf(configfile, MAXPATHLEN, "%s/%s", g_state.confdir, dire->d_name); + configfile[MAXPATHLEN - 1] = 0; + + memset(&ctx, 0, sizeof(ctx)); + ctx.confname = dire->d_name; + + parse_config_file(configfile, &ctx); + } + + closedir(dir); +} + +/* ----------------------------------------------------------------------------- + * FREEING MEMORY + */ + +static void +free_items(rb_item* item) +{ + rb_item* next; + for(; item; item = next) + { + next = item->next; + free(item); + } +} + +static void +free_hosts(rb_host* host) +{ + rb_host* next; + for(; host; host = next) + { + next = host->next; + free(host); + } +} + +static void +free_pollers(rb_poller* poll) +{ + rb_poller* next; + for(; poll; poll = next) + { + free_items(poll->items); + + next = poll->next; + free(poll); + } + +} + +void +rb_config_free() +{ + hsh_free(g_state.poll_by_key); + hsh_free(g_state.host_by_name); + + free_hosts(g_state.hosts); + + /* Note that rb_item's are owned by pollers */ + free_pollers(g_state.polls); +} diff --git a/src/rrdbotd.c b/src/rrdbotd.c index 064c603..bee836e 100644 --- a/src/rrdbotd.c +++ b/src/rrdbotd.c @@ -41,7 +41,6 @@ #include #include #include -#include /* TODO: Abstract these headers away nicely */ #include @@ -57,6 +56,9 @@ * GLOBALS */ +/* The one main state object */ +rb_state g_state; + /* TODO: These should be set from the command line */ static int daemonized = 0; static int debug_level = 7; @@ -92,189 +94,53 @@ test_bsnmpd() } /* ----------------------------------------------------------------------------- - * CONFIG FILES + * CLEANUP */ -static char* -read_config_file(const char* configfile) +typedef struct _exit_stack { - char* config = NULL; - FILE* f = NULL; - long len; - - ASSERT(configfile); - - f = fopen(configfile, "r"); - if(f == NULL) - err(1, "couldn't open config file: %s", configfile); - - /* Figure out size */ - if(fseek(f, 0, SEEK_END) == -1 || (len = ftell(f)) == -1 || fseek(f, 0, SEEK_SET) == -1) - err(1, "couldn't seek config file: %s", configfile); - - if((config = (char*)malloc(len + 2)) == NULL) - errx(1, "out of memory"); - - /* And read in one block */ - if(fread(config, 1, len, f) != len) - err(1, "couldn't read config file: %s", configfile); + voidfunc func; + void* data; - fclose(f); - - /* Null terminate the data */ - config[len] = '\n'; - config[len + 1] = 0; - - /* Remove nasty dos line endings */ - remove_cr(config); - - rb_messagex(LOG_DEBUG, "read config file: %s", configfile); - return config; + /* We have a list of these beauties */ + struct _exit_stack* next; } +exit_stack; + +/* Our exit stack */ +static exit_stack* atexits = NULL; +static int atexit_registered = 0; static void -parse_config_file(const char* configfile) +atexit_do_stack(void) { - char* name = NULL; - char* value = NULL; - char* config; - char* next; - char* header; - char* p; - char* t; - int pos; - - config = read_config_file(configfile); - next = config; - - /* Go through lines and process them */ - while((t = strchr(next, '\n')) != NULL) + exit_stack* next; + for(; atexits; atexits = next) { - *t = 0; - p = next; /* Do this before cleaning below */ - next = t + 1; - - t = trim_start(p); - - /* Continuation line (had spaces at start) */ - if(p < t && *t) - { - if(!value) - errx(2, "invalid continuation in config: %s", p); - - /* Calculate the end of the current value */ - t = value + strlen(value); - ASSERT(t < p); - - /* Continuations are separated by spaces */ - *t = ' '; - t++; - - continue; - } - - // No continuation hand off value if necessary - if(name && value) - { - rb_messagex(LOG_DEBUG, "parsed configuration value: [%s] %s = %s", header, name, value); - // TODO: Hand off pairs here - } - - name = NULL; - value = NULL; - - /* Empty lines / comments at start / comments without continuation */ - if(!*t || *p == '#') - continue; - - /* A header */ - if(*p == '[') - { - t = p + strcspn(p, "]"); - if(!*t || t == p + 1) - errx(2, "invalid config header: %s", p); - - *t = 0; - header = trim_space(p + 1); - continue; - } - - /* Look for the break between name = value on the same line */ - t = p + strcspn(p, ":="); - if(!*t) - errx(2, "invalid config line: %s", p); - - /* Null terminate and split value part */ - *t = 0; - t++; - - name = trim_space(p); - value = trim_space(t); - } - - if(name && value) - { - rb_messagex(LOG_DEBUG, "parsed configuration value: %s = %s", name, value); - // TODO: Hand off pairs here + next = atexits->next; + (atexits->func)(atexits->data); + free(atexits); } - - // TODO: Eventually no freeing - free(config); } -static void -parse_config_dir(const char* confdir) +void +rb_atexit(voidfunc func, void* data) { - char olddir[MAXPATHLEN]; - char configfile[MAXPATHLEN]; - DIR* top; - struct dirent* tope; - DIR* dir; - struct dirent* dire; - - /* Get the current directory */ - if(!getcwd(olddir, MAXPATHLEN)) - *olddir = 0; - - if(chdir(confdir) == -1 || - (top = opendir(".")) == NULL) - err("couldn't read config directory: %s", confdir); - - while((tope = readdir(top)) != NULL) - { - /* Only go through the category directories */ - if(!(tope->d_type & DT_DIR)) - continue; - - /* None of these dumb dots */ - if(strcmp(tope->d_name, ".") == 0 || - strcmp(tope->d_name, "..") == 0) - continue; + exit_stack* ae; - dir = opendir(tope->d_name); - if(!dir) - err("couldn't read config directory: %s/%s", confdir, tope->d_name); + ASSERT(func); - while((dire = readdir(dir)) != NULL) - { - if(dire->d_type != DT_REG && dire->d_type != DT_LNK) - continue; - - /* Build a happy path name */ - snprintf(configfile, MAXPATHLEN, "%s/%s/%s", confdir, tope->d_name, dire->d_name); - configfile[MAXPATHLEN - 1] = 0; - - parse_config_file(configfile); - } + ae = (exit_stack*)calloc(1, sizeof(exit_stack)); + if(ae) + { + ae->func = func; + ae->data = data; + ae->next = atexits; + atexits = ae; - closedir(dir); + if(!atexit_registered) + atexit(atexit_do_stack); } - - closedir(top); - - /* And put it back nicely */ - if(*olddir) - chdir(olddir); } /* ----------------------------------------------------------------------------- @@ -348,6 +214,13 @@ main(int argc, char* argv[]) int daemonize; char ch; + /* Initialize the state stuff */ + memset(&g_state, 0, sizeof(g_state)); + + /* TODO: These should come from configure, and from arguments */ + g_state.rrddir = "/data/projects/rrdui/work"; + g_state.confdir = "/data/projects/rrdui/conf"; + /* Parse the arguments nicely */ while((ch = getopt(argc, argv, "v")) != -1) { @@ -371,7 +244,12 @@ main(int argc, char* argv[]) argc -= optind; argv += optind; - parse_config_dir("/data/projects/rrdui/conf"); + rb_config_parse(); + + printf("a test\n"); + + rb_config_free(); + return 0; } diff --git a/src/rrdbotd.h b/src/rrdbotd.h index 61aef40..9c4f2b4 100644 --- a/src/rrdbotd.h +++ b/src/rrdbotd.h @@ -39,14 +39,24 @@ #ifndef __RRDBOTD_H__ #define __RRDBOTD_H__ +#include "asn1.h" #include "snmp.h" #include "sock_any.h" #include "hash.h" +/* ----------------------------------------------------------------------------- + * DATA + */ + struct _rb_item; struct _rb_poller; struct _rb_host; +/* + * Note that all the members are either in the config memory + * or inline. This helps us keep memory management simple. + */ + typedef struct _rb_item { /* Specific to this item */ @@ -57,13 +67,15 @@ typedef struct _rb_item /* Pointers to related */ const struct _rb_poller* poller; const struct _rb_host* host; + + /* Next in list of items */ + struct _rb_item* next; } rb_item; - typedef struct _rb_host { - const char* host; + const char* name; /* Host resolving and book keeping */ struct sockaddr_any address; @@ -75,13 +87,17 @@ typedef struct _rb_host } rb_host; - typedef struct _rb_poller { + /* The hash key is interval-rrdname */ + char key[MAXPATHLEN]; + + /* This points into the memory above */ const char* rrdname; + uint32_t interval; - /* The things to poll */ + /* The things to poll. rb_poller owns this list */ rb_item* items; /* Book keeping */ @@ -92,9 +108,12 @@ typedef struct _rb_poller } rb_poller; - typedef struct _rb_state { + /* Settings from command line */ + const char* confdir; + const char* rrddir; + /* All the pollers/hosts */ rb_poller* polls; rb_host* hosts; @@ -105,13 +124,30 @@ typedef struct _rb_state } rb_state; +/* One global rb_state with all the main settings */ +extern rb_state g_state; /* ----------------------------------------------------------------------------- - * LOGGING + * UTILITIES (rrdbotd.c) */ void rb_messagex (int level, const char* msg, ...); void rb_message (int level, const char* msg, ...); +typedef void (*voidfunc)(void*); +void rb_atexit (voidfunc func, void* data); + +/* ----------------------------------------------------------------------------- + * CONFIG (config.c) + */ + +void rb_config_parse(); +void rb_config_free(); + +/* ----------------------------------------------------------------------------- + * SNMP HELPERS (snmp-help.c) + */ + +int rb_parse_mib(const char* oid, struct snmp_value* value); #endif /* __RRDBOTD_H__ */ diff --git a/src/snmp-help.c b/src/snmp-help.c new file mode 100644 index 0000000..cf13d40 --- /dev/null +++ b/src/snmp-help.c @@ -0,0 +1,111 @@ +/* + * Copyright (c) 2005, Nate Nielsen + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * * 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. + * * The names of contributors to this software may not be + * used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "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 + * COPYRIGHT OWNER OR CONTRIBUTORS 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. + * + * + * CONTRIBUTORS + * Nate Nielsen + * + */ + +#include "usuals.h" +#include +#include + +#include +#include + +#include "stringx.h" +#include "rrdbotd.h" + +static int +parse_numeric_mib(const char* mib, struct asn_oid* oid) +{ + int ret = 0; + unsigned int sub; + char* next; + char* t; + char* copy; + char* src; + + memset(oid, 0, sizeof(*oid)); + + copy = src = strdup(mib); + if(!src) + return -1; + + while(src && *src) + { + next = strchr(src, '.'); + if(next) + *next = 0; + + sub = strtoul(src, &t, 10); + + /* Too many parts */ + if(oid->len > ASN_MAXOIDLEN) + ret = -1; + + /* An invalid number */ + if(*t) + ret = -1; + + /* Make sure this is a valid part */ + if((oid->len == 0 && sub < 1) || sub < 0 || sub >= ASN_MAXID) + ret -1; + + if(ret < 0) + break; + + oid->subs[oid->len] = sub; + oid->len++; + + src = next ? next + 1 : NULL; + } + + free(copy); + return ret; +} + +int +rb_parse_mib(const char* mib, struct snmp_value* value) +{ + /* + * TODO: Eventually we'll have code here to parse symbolic + * names, and initialize snmp_value to the right type and + * all that jazz. For now we just have numeric OID support. + */ + + value->syntax = SNMP_SYNTAX_NULL; + memset(&(value->v), 0, sizeof(value->v)); + + return parse_numeric_mib(mib, &(value->var)); +} -- cgit v1.2.3