/* (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 "../common/config-parser.h" static char* conf_directory = NULL; static char* work_directory = NULL; 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; } graph; static graph* allgraphs = NULL; /* ----------------------------------------------------------------------------- * ERRORS */ static void usererror (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 */ /* *argv 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 == *src) { quote = '\0'; } else if (quote != '\0') { if (*src == '\\') { 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 '"': case '\'': quote = *src; break; case '\\': src++; if (!*src) { r = -1; errno = EINVAL; continue; } 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; } 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; } 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; } void freegraphs () { 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) { 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 listgraphs () { 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 int runrrd(int argc, const char** argv) { int x, y; double min, max; char** text; optind = 0; opterr = 0; return rrd_graph(argc, (char**)argv, &text, &x, &y, stdout, &min, &max); } static void displaygraphs () { #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]); } /* ----------------------------------------------------------------------------- * MAIN */ static int pathstarts (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); path = getenv("PATH_INFO"); while (path && path[0] && (isspace(path[0]) || path[0] == '/')) path++; cfg_parse_dir(conf_directory, NULL); if (!path || !path[0] || pathstarts(path, "list")) listgraphs(); else if (pathstarts(path, "graph")) displaygraphs(); else usererror("Invalid request"); freegraphs(); return 0; }