From 1e9eba6d717ed3132db6b6c232e9d78e0f452568 Mon Sep 17 00:00:00 2001 From: Stef Walter Date: Sat, 5 Aug 2006 23:09:03 +0000 Subject: Finish the C implementation of rrdui-cgi * Add color support See #77 * Add variable substitutions for environment --- tools/rrdui-cgi.c | 848 +++++++++++++++++++++++++++++++++--------------------- 1 file changed, 523 insertions(+), 325 deletions(-) (limited to 'tools') diff --git a/tools/rrdui-cgi.c b/tools/rrdui-cgi.c index ad755a4..f5f526e 100644 --- a/tools/rrdui-cgi.c +++ b/tools/rrdui-cgi.c @@ -5,6 +5,7 @@ #define _GNU_SOURCE +#include #include #include #include @@ -14,24 +15,68 @@ #include #include #include +#include -#include "../common/config-parser.h" +#ifndef ASSERT +#define ASSERT assert +#endif + +#include "common/compat.h" +#include "common/config-parser.h" static char* conf_directory = NULL; static char* work_directory = NULL; +#define MAX_COLORS 30 +static char* all_colors[MAX_COLORS] = +{ + "#336699", + "#99CCFF", + "#999933", + "#666699", + "#CC9933", + "#006666", + "#3399FF", + "#993300", + "#CCCC99", + "#666666", + "#FFCC66", + "#6699CC", + "#663366", + "#9999CC", + "#CCCCCC", + "#669999", + "#CCCC66", + "#CC6600", + "#9999FF", + "#0066CC", + "#99CCCC", + "#999999", + "#FFCC00", + "#009999", + "#99CC33", + "#FF9900", + "#999966", + "#66CCCC", + "#339966", + "#CCCC33", +}; + +#define VARIABLE_CHARS "abcdefghijklmnopqrstuvwxyz_ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" + typedef struct _graph { - const char* name; - char* title; - int width; - int height; - int interval; - char* category; - const char** options; - const char** commands; - - struct _graph *next; + char rrd[MAXPATHLEN]; + const char* name; + char* title; + int width; + int height; + int interval; + char* category; + const char** options; + const char** commands; + + struct _graph *next; } graph; @@ -42,20 +87,20 @@ static graph* allgraphs = NULL; */ static void -usererror (const char* msg) +user_error (const char* msg) { - printf("Content-Type: text/plain\n\n"); - printf("%s\n", msg); + printf("Content-Type: text/plain\n\n"); + printf("%s\n", msg); - /* Custom error so webserver doesn't think it's a server error */ - exit(0); + /* Custom error so webserver doesn't think it's a server error */ + exit(0); } /* ----------------------------------------------------------------------------- * CONFIG PARSING */ -/* *argv should be freed with free() after use */ +/* *argvp should be freed with free() after use */ int parse_argv(const char* s, const char*** argvp) { @@ -85,11 +130,11 @@ parse_argv(const char* s, const char*** argvp) argv[argc] = buf; for (src = s; r == 0 && *src; src++) { - if (quote == *src) { - quote = '\0'; - } else if (quote != '\0') { + if (quote != '\0') { + if (quote == *src) + quote = '\0'; if (*src == '\\') { - src++; + *buf++ = *src++; if (!*src) { r = -1; errno = EINVAL; continue; @@ -114,16 +159,16 @@ parse_argv(const char* s, const char*** argvp) } } else { switch (*src) { - case '"': - case '\'': - quote = *src; - break; case '\\': - src++; + *buf++ = *src++; if (!*src) { r = -1; errno = EINVAL; continue; } + case '"': + case '\'': + quote = *src; + /* Fall through */ default: *buf++ = *src; break; @@ -131,152 +176,164 @@ parse_argv(const char* s, const char*** argvp) } } - /* Reallocate the whole thing as one block of memory */ + /* Reallocate the whole thing as one block of memory */ if (r == 0) { if (strlen(argv[argc])) argc++; - nb = (argc + 1) * sizeof(*argv); - - for (i = 0; i < argc; i++) { - if (argv[i] != NULL) - nb += strlen(argv[i]) + 1; - } - - dst = malloc (nb); - if (dst == NULL) { - free(argv); - errno = ENOMEM; - return -1; - } - - argv2 = (void *) dst; - dst += (argc + 1) * sizeof(*argv); - - for (i = 0; i < argc; i++) { - if (argv[i] == NULL) - continue; - argv2[i] = dst; - dst += strlen(strcpy(dst, argv[i])) + 1; - } - argv2[argc] = NULL; - - if (argvp) { - *argvp = argv2; - } else { - free(argv2); - argv2 = NULL; - } - - r = 0; + nb = (argc + 1) * sizeof(*argv); + + for (i = 0; i < argc; i++) { + if (argv[i] != NULL) + nb += strlen(argv[i]) + 1; + } + + dst = malloc (nb); + if (dst == NULL) { + free(argv); + errno = ENOMEM; + return -1; + } + + argv2 = (void *) dst; + dst += (argc + 1) * sizeof(*argv); + + for (i = 0; i < argc; i++) { + if (argv[i] == NULL) + continue; + argv2[i] = dst; + dst += strlen(strcpy(dst, argv[i])) + 1; + } + argv2[argc] = NULL; + + if (argvp) { + *argvp = argv2; + } else { + free(argv2); + argv2 = NULL; + } + + r = 0; } free(argv); return r; } -int cfg_value(const char* filename, const char* header, const char* name, - char* value, void* data) +/* Called for each value in the config files */ +int +cfg_value(const char* filename, const char* header, const char* name, + char* value, void* data) { - static graph* gr = NULL; - - /* called like this after ever file */ - if (!header) - { - /* add to the list */ - gr->next = allgraphs; - allgraphs = gr; - - gr = NULL; - return 0; - } - - if (gr == NULL) - { - gr = calloc(1, sizeof(graph)); - if (!gr) - err(1, "Out of memory"); - gr->name = filename; - gr->category = ""; - } - - if (strcmp(header, "general") == 0) - { - if (strcmp(name, "title") == 0) - gr->title = value; - else if (strcmp(name, "category") == 0) - gr->category = value; - } - else if (strcmp(header, "graph") == 0) - { - if (strcmp(name, "height") == 0) - gr->height = atoi(value); - else if (strcmp(name, "width") == 0) - gr->width = atoi(value); - else if (strcmp(name, "options") == 0) - { - if (parse_argv(value, &gr->options) != 0) - errx(2, "Invalid 'options' option"); - } - else if (strcmp(name, "commands") == 0) - { - if (parse_argv(value, &gr->commands) != 0) - errx(2, "Invalid 'commands' option"); - } - } - else if (strcmp(header, "poll") == 0) - { - if (strcmp(name, "interval") == 0) - gr->interval = atoi(value); - } - - return 0; + static graph* gr = NULL; + + /* called like this after ever file */ + if (!header) + { + /* add to the list */ + gr->next = allgraphs; + allgraphs = gr; + + gr = NULL; + return 0; + } + + if (gr == NULL) + { + gr = calloc(1, sizeof(graph)); + if (!gr) + err(1, "Out of memory"); + gr->name = filename; + gr->category = ""; + + /* The default RRD path */ + strlcpy(gr->rrd, work_directory, sizeof(gr->rrd)); + strlcat(gr->rrd, "/", sizeof(gr->rrd)); + strlcat(gr->rrd, filename, sizeof(gr->rrd)); + strlcat(gr->rrd, ".rrd", sizeof(gr->rrd)); + } + + if (strcmp(header, "general") == 0) + { + if (strcmp(name, "title") == 0) + gr->title = value; + else if (strcmp(name, "category") == 0) + gr->category = value; + else if (strcmp(name, "rrd") == 0) + strlcpy(gr->rrd, value, sizeof(gr->rrd)); + } + else if (strcmp(header, "graph") == 0) + { + if (strcmp(name, "height") == 0) + gr->height = atoi(value); + else if (strcmp(name, "width") == 0) + gr->width = atoi(value); + else if (strcmp(name, "options") == 0) + { + if (parse_argv(value, &gr->options) != 0) + errx (2, "Invalid 'options' option"); + } + else if (strcmp(name, "commands") == 0) + { + if (parse_argv(value, &gr->commands) != 0) + errx (2, "Invalid 'commands' option"); + } + } + else if (strcmp(header, "poll") == 0) + { + if (strcmp(name, "interval") == 0) + gr->interval = atoi(value); + } + + return 0; } -int cfg_error(const char* filename, const char* errmsg, void* data) +/* Called for each error in the config files */ +int +cfg_error(const char* filename, const char* errmsg, void* data) { - /* todo skip the rest of this file on error */ + /* TODO: skip the rest of this file on error */ warnx("%s", errmsg); return 0; } -void -freegraphs () +static void +free_graphs () { - graph* l; - graph* t; - - for (l = allgraphs; l != NULL; ) - { - t = l->next; - if (l->options) - free(l->options); - if (l->commands) - free(l->commands); - - free(l); - l = t; - } + graph* l; + graph* t; + + for (l = allgraphs; l != NULL; ) + { + t = l->next; + if (l->options) + free(l->options); + if (l->commands) + free(l->commands); + + free(l); + l = t; + } } -graph* -findgraphs (char* name) +static graph* +find_graphs (char* name) { - graph* l; + graph* l; - if (!name) - return NULL; + if (!name) + return NULL; - for (l = allgraphs; l != NULL; l = l->next) - { - if (!l->commands) - continue; + for (l = allgraphs; l != NULL; l = l->next) + { + if (!l->commands) + continue; - if (strcmp(l->name, name) == 0) - return l; - } + if (strcmp(l->name, name) == 0) + return l; + } - return NULL; + return NULL; } /* ----------------------------------------------------------------------------- @@ -284,168 +341,309 @@ findgraphs (char* name) */ static void -listgraphs () +list_graphs () { - graph* l; + graph* l; - printf("Content-Type: text/xml\n\n"); - printf("\n"); - printf("\n"); + printf("Content-Type: text/xml\n\n"); + printf("\n"); + printf("\n"); - for (l = allgraphs; l != NULL; l = l->next) - { - if (!l->commands) - continue; + for (l = allgraphs; l != NULL; l = l->next) + { + if (!l->commands) + continue; - printf(" name, l->category, l->title); + printf(" name, l->category, l->title); - if (l->width) - printf(" width=\"%d\"", l->width); + if (l->width) + printf(" width=\"%d\"", l->width); + if (l->height) + printf(" height=\"%d\"", l->height); + if (l->interval) + printf(" interval=\"%d\"", l->interval); + + printf(" />\n"); + } + printf("\n"); +} - if (l->height) - printf(" height=\"%d\"", l->height); +static void +add_text(char** result, char** pos, char** end, const char* text, int len) +{ + ASSERT(*result <= *pos); + ASSERT(*pos <= *end); + ASSERT(text); + + if(!len || !text) + return; + + if(len == -1) + len = strlen(text); + + /* Need more memory allocated */ + while(len >= *end - *pos) + { + int o = *end - *result; + int p = *pos - *result; + int x = o ? o * 2 : 128; + + *result = realloc(*result, x); + if(!*result) + err(1, "out of memory"); + + memset(*result + o, 0, x); + *end = *result + x; + *pos = *result + p; + } - if (l->interval) - printf(" interval=\"%d\"", l->interval); + /* Copy the nastiness in */ + memcpy(*pos, text, len); + *pos += len; - printf(" />\n"); - } - printf("\n"); + ASSERT(*pos <= *end); } -static int -runrrd(int argc, const char** argv) +static void +add_time(char** result, char** pos, char** end, time_t time) { - int x, y; - double min, max; - char** text; + char buf[128]; + strftime(buf, sizeof(buf), "%Y-%m-%d %H\\:%M", localtime(&time)); + buf[sizeof(buf) - 1] = 0; + add_text(result, pos, end, buf, -1); +} - optind = 0; - opterr = 0; +static char* +substitute_variables(graph* gr, time_t start, time_t end, const char* src) +{ + char* result = NULL; + char* bound = NULL; + char* p = NULL; + const char* next; + char* name; + int len, color; + + while(*src) + { + /* Next variable */ + next = strpbrk(src, "$\\"); + if(!next) + next = src + strlen(src); + + /* Copy all the text until that point */ + add_text(&result, &p, &bound, src, next - src); + src = next; + + if(!*src) + break; + + /* Handled escaped characters */ + if(*src == '\\') + { + add_text(&result, &p, &bound, src + 1, 1); + src += 2; + continue; + } + + /* We found a variable parse it out */ + if(*(++src) == '{') + { + ++src; + next = strchr(src, '}'); + + /* Invalid variables are empty */ + if(!next) + break; + + len = next - src; + next++; + } + else + { + /* A variable without braces */ + next = src + strspn(src, VARIABLE_CHARS); + len = next - src; + } + + name = calloc(len + 1, 1); + if(!name) + err(1, "out of memory"); + memcpy(name, src, len); + + /* Put in the variable text */ + if(strcmp(name, "START") == 0) + add_time(&result, &p, &bound, start); + + else if(strcmp(name, "END") == 0) + add_time(&result, &p, &bound, end); + + else if(strcmp(name, "RRD") == 0) + add_text(&result, &p, &bound, gr->rrd, -1); - return rrd_graph(argc, (char**)argv, &text, &x, &y, stdout, &min, &max); + else if(strncmp(name, "COLOR", 5) == 0) + { + color = atoi(src + 5); + if(color > 0 && color < MAX_COLORS) + add_text(&result, &p, &bound, all_colors[color], -1); + } + + /* Pull other environment variables from outside */ + else + add_text(&result, &p, &bound, getenv(name), -1); + + free(name); + src = next; + } + + return result; +} + +static int +run_rrd(int argc, const char** argv) +{ + int x, y; + double min, max; + char** text; + + optind = 0; + opterr = 0; + +//#ifdef TEST + { + int i; + for(i = 0; i < argc; i++) + fprintf(stderr, "%s\n", argv[i]); + } +//#endif + + return rrd_graph(argc, (char**)argv, &text, &x, &y, stdout, &min, &max); } static void -displaygraphs () +display_graphs () { - #define MAX_SIZE 1024 - const char* args[MAX_SIZE]; - int numargs = 0; - char* allocs[MAX_SIZE]; - int numalloc = 0; - char* name = NULL; - int width, height, start, end; - char* t; - const char** k; - char* q; - char *nm, *vl; - char* query = NULL; - graph* gr = NULL; - int r; - int i; - - width = height = start = end = 0; - memset(args, 0, sizeof(args)); - - args[numargs++] = "graph"; - args[numargs++] = "-"; - args[numargs++] = "--imgformat=PNG"; - args[numargs++] = "--rigid"; - - q = getenv("QUERY_STRING"); - - if (q) - { - query = q = strdup(q); - while (q[0] && (q[0] == '?' || isspace(q[0]))) - q++; - - while (q && q[0]) - { - nm = strsep(&q, "&"); - vl = strchr(nm, '='); - if (!vl) - continue; - vl[0] = 0; - vl++; - - if (strcmp(nm, "name") == 0) - name = vl; - else if (strcmp(nm, "width") == 0) - width = atoi(vl); - else if (strcmp(nm, "height") == 0) - height = atoi(vl); - else if (strcmp(nm, "start") == 0) - start = atoi(vl); - else if (strcmp(nm, "end") == 0) - end = atoi(vl); - else if (strcmp(nm, "color") == 0) - { - args[numargs++] = "--color"; - t = strchr(vl, ':'); - if (t != NULL) - *t = '#'; - args[numargs++] = vl; - } - } - } - - gr = findgraphs(name); - if (gr == NULL) - usererror("Invalid graph"); - - if (width || gr->width) - { - asprintf(&t, "--width=%d", width ? width : gr->width); - - args[numargs++] = allocs[numalloc++] = t; - } - - if (height || gr->height) - { - asprintf(&t, "--height=%d", height ? height : gr->height); - - args[numargs++] = allocs[numalloc++] = t; - } - - if (!end) - end = (int)time(NULL); - - asprintf(&t, "--end=%d", end); - args[numargs++] = allocs[numalloc++] = t; - - if (!start) - start = 86400; - - asprintf(&t, "--start=%d", start); - args[numargs++] = allocs[numalloc++] = t; - - asprintf(&t, "--title=%s", gr->title); - args[numargs++] = allocs[numalloc++] = t; - - if (gr->options) - { - for (k = gr->options; *k != NULL; k++) - args[numargs++] = *k; - } - - for (k = gr->commands; *k != NULL; k++) - args[numargs++] = *k; - - printf("Content-Type: image/png\n\n"); - -/* for (i = 0; i < numargs; i++) - fprintf(stderr, "%s\n", args[i]);*/ - - rrd_clear_error(); - r = runrrd(numargs, args); - if (r != 0) - errx(2, "could't create graph: %s", rrd_get_error()); - - for (i = 0; i < numalloc; i++) - free(allocs[i]); + #define MAX_SIZE 1024 + const char* args[MAX_SIZE]; + int numargs = 0; + char* allocs[MAX_SIZE]; + int numalloc = 0; + char* name = NULL; + int width, height, start, end; + char* t; + const char** k; + char* q; + char *nm, *vl; + char* query = NULL; + graph* gr = NULL; + int r; + int i; + + width = height = start = end = 0; + memset(args, 0, sizeof(args)); + + /* Arguments that are always present */ + args[numargs++] = "graph"; + args[numargs++] = "-"; + args[numargs++] = "--imgformat=PNG"; + args[numargs++] = "--rigid"; + + /* Dig out the arguments from the query string */ + q = getenv("QUERY_STRING"); + if (q) + { + query = q = strdup(q); + while (q[0] && (q[0] == '?' || isspace(q[0]))) + q++; + + while (q && q[0]) + { + nm = strsep(&q, "&"); + vl = strchr(nm, '='); + if (!vl) + continue; + vl[0] = 0; + vl++; + + if (strcmp(nm, "name") == 0) + name = vl; + else if (strcmp(nm, "width") == 0) + width = atoi(vl); + else if (strcmp(nm, "height") == 0) + height = atoi(vl); + else if (strcmp(nm, "start") == 0) + start = atoi(vl); + else if (strcmp(nm, "end") == 0) + end = atoi(vl); + else if (strcmp(nm, "color") == 0) + { + args[numargs++] = "--color"; + t = strchr(vl, ':'); + if (t != NULL) + *t = '#'; + args[numargs++] = vl; + } + } + } + + gr = find_graphs(name); + if (gr == NULL) + user_error("Invalid graph"); + + if (width || gr->width) + { + /* Substitute values from the graph where not specified */ + asprintf(&t, "--width=%d", width ? width : gr->width); + args[numargs++] = allocs[numalloc++] = t; + } + + if (height || gr->height) + { + /* Substitute values from the graph where not specified */ + asprintf(&t, "--height=%d", height ? height : gr->height); + args[numargs++] = allocs[numalloc++] = t; + } + + /* Figure out the start / end times */ + if (!end) + end = (int)time(NULL); + + asprintf(&t, "--end=%d", end); + args[numargs++] = allocs[numalloc++] = t; + + if (!start) + start = end - 86400; + + asprintf(&t, "--start=%d", start); + args[numargs++] = allocs[numalloc++] = t; + + asprintf(&t, "--title=%s", gr->title); + args[numargs++] = allocs[numalloc++] = t; + + /* Add in any other specified options */ + if (gr->options) + { + for (k = gr->options; *k != NULL; k++) + args[numargs++] = *k; + } + + /* And then the graph commands */ + for (k = gr->commands; *k != NULL; k++) + { + args[numargs++] = allocs[numalloc++] = + substitute_variables(gr, start, end, *k); + } + + /* Now make it happen */ + printf("Content-Type: image/png\n\n"); + + rrd_clear_error(); + r = run_rrd(numargs, args); + if (r != 0) + errx(2, "could't create graph: %s", rrd_get_error()); + + for (i = 0; i < numalloc; i++) + free(allocs[i]); } /* ----------------------------------------------------------------------------- @@ -453,54 +651,54 @@ displaygraphs () */ static int -pathstarts (const char* path, const char* method) +path_starts (const char* path, const char* method) { - int len = strlen(method); + int len = strlen(method); - if (strncmp(path, method, len) != 0) - return 0; + if (strncmp(path, method, len) != 0) + return 0; - if (path[len] == '/' || !path[len]) - return 1; + if (path[len] == '/' || !path[len]) + return 1; - return 0; + return 0; } int -main (int argc, char* argv[]) +main(int argc, char* argv[]) { - char* path; - - conf_directory = getenv("CONFDIR"); - work_directory = getenv("WORKDIR"); + char* path; - if (!conf_directory || !work_directory) - errx(2, "not setup properly. CONFDIR and WORKDIR env variables must be set"); + conf_directory = getenv("CONFDIR"); + work_directory = getenv("WORKDIR"); - /* - * We always have this as our current directory. - * It helps when constructing graphs and the like - */ - if (chdir(work_directory) < 0) - err(1, "Couldn't change to work directory: %s", work_directory); + if (!conf_directory || !work_directory) + errx(2, "not setup properly. CONFDIR and WORKDIR env variables must be set"); + /* + * We always have this as our current directory. + * It helps when constructing graphs and the like + */ + if (chdir(work_directory) < 0) + err(1, "Couldn't change to work directory: %s", work_directory); - path = getenv("PATH_INFO"); + /* Load up all the configuration files */ + cfg_parse_dir(conf_directory, NULL); - while (path && path[0] && - (isspace(path[0]) || path[0] == '/')) - path++; + path = getenv("PATH_INFO"); - cfg_parse_dir(conf_directory, NULL); + while (path && path[0] && + (isspace(path[0]) || path[0] == '/')) + path++; - if (!path || !path[0] || pathstarts(path, "list")) - listgraphs(); - else if (pathstarts(path, "graph")) - displaygraphs(); - else - usererror("Invalid request"); + /* See what kind of request we're talking about */ + if (!path || !path[0] || path_starts(path, "list")) + list_graphs(); + else if (path_starts(path, "graph")) + display_graphs(); + else + user_error("Invalid request"); - freegraphs(); - - return 0; + free_graphs(); + return 0; } -- cgit v1.2.3