summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorStef Walter <stef@memberwebs.com>2006-01-25 04:07:04 +0000
committerStef Walter <stef@memberwebs.com>2006-01-25 04:07:04 +0000
commit7fcead4473bc224bc8c6a66e2db3b3ee87f751d4 (patch)
treed92c54aed884ed001f7901bbb4bef9623975df76
parent26a677b17880a14b9345270cb15b37d27d7d4797 (diff)
Configuration file parsing for rrdbotd.
-rw-r--r--src/Makefile.am5
-rw-r--r--src/common/hash.c53
-rw-r--r--src/common/hash.h30
-rw-r--r--src/config.c506
-rw-r--r--src/rrdbotd.c214
-rw-r--r--src/rrdbotd.h48
-rw-r--r--src/snmp-help.c111
7 files changed, 763 insertions, 204 deletions
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 <stdlib.h>
#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 <nielsen@memberwebs.com>
+ *
+ */
+
+#include "usuals.h"
+#include <errno.h>
+#include <unistd.h>
+#include <syslog.h>
+#include <dirent.h>
+
+#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 <unistd.h>
#include <stdarg.h>
#include <syslog.h>
-#include <dirent.h>
/* TODO: Abstract these headers away nicely */
#include <bsnmp/asn1.h>
@@ -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 <nielsen@memberwebs.com>
+ *
+ */
+
+#include "usuals.h"
+#include <errno.h>
+#include <syslog.h>
+
+#include <bsnmp/asn1.h>
+#include <bsnmp/snmp.h>
+
+#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));
+}