/* (C) 1998-2002 Red Hat, Inc. -- Licensing details are in the COPYING file accompanying popt source distributions, available from ftp://ftp.rpm.org/pub/rpm/dist. */ #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #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 { 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; static graph* allgraphs = NULL; /* ----------------------------------------------------------------------------- * ERRORS */ static void user_error (const char* 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); } /* ----------------------------------------------------------------------------- * CONFIG PARSING */ /* *argvp should be freed with free() after use */ int parse_argv(const char* s, const char*** argvp) { #define GROW_DELTA 5 const char* src; char quote = '\0'; int alloced = GROW_DELTA; const char** argv = NULL; const char** argv2; const char** a; size_t nb; char* dst; int argc = 0; int buflen; char* buf; int i, r = 0; argv = malloc (sizeof (*argv) * alloced); if (!argv) { errno = ENOMEM; return -1; } buflen = strlen (s) + 1; buf = memset (alloca (buflen), 0, buflen); argv[argc] = buf; for (src = s; r == 0 && *src; src++) { if (quote != '\0') { if (quote == *src) quote = '\0'; if (*src == '\\') { *buf++ = *src++; if (!*src) { r = -1; errno = EINVAL; continue; } if (*src != quote) *buf++ = '\\'; } *buf++ = *src; } else if (isspace(*src)) { if (*argv[argc] != '\0') { buf++, argc++; if (argc == alloced) { alloced += GROW_DELTA; a = realloc (argv, sizeof(*argv) * alloced); if (!a) { r = -1; errno = ENOMEM; continue; } argv = a; } argv[argc] = buf; } } else { switch (*src) { case '\\': *buf++ = *src++; if (!*src) { r = -1; errno = EINVAL; continue; } case '"': case '\'': quote = *src; /* Fall through */ default: *buf++ = *src; break; } } } /* 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; } free(argv); return r; } /* 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 = ""; /* 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; } /* 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 */ warnx("%s", errmsg); return 0; } 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; } } static graph* find_graphs (char* name) { graph* l; if (!name) return NULL; for (l = allgraphs; l != NULL; l = l->next) { if (!l->commands) continue; if (strcmp(l->name, name) == 0) return l; } return NULL; } /* ----------------------------------------------------------------------------- * METHODS */ static void list_graphs () { graph* l; printf("Content-Type: text/xml\n\n"); printf("\n"); printf("\n"); for (l = allgraphs; l != NULL; l = l->next) { if (!l->commands) continue; printf(" name, l->category, l->title); 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"); } static void add_text(char** result, char** pos, char** end, const char* text, int len) { ASSERT(*result <= *pos); ASSERT(*pos <= *end); 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; } /* Copy the nastiness in */ memcpy(*pos, text, len); *pos += len; ASSERT(*pos <= *end); } static void add_time(char** result, char** pos, char** end, time_t time) { 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); } 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); 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], -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 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)); /* 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]); } /* ----------------------------------------------------------------------------- * MAIN */ static int path_starts (const char* path, const char* method) { int len = strlen(method); if (strncmp(path, method, len) != 0) return 0; if (path[len] == '/' || !path[len]) return 1; return 0; } int main(int argc, char* argv[]) { char* path; conf_directory = getenv("CONFDIR"); work_directory = getenv("WORKDIR"); 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); /* Load up all the configuration files */ cfg_parse_dir(conf_directory, NULL); path = getenv("PATH_INFO"); while (path && path[0] && (isspace(path[0]) || path[0] == '/')) path++; /* 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"); free_graphs(); return 0; }