1 /*----------------------------------------------------------------------------*/
2 /* Xymon RRD graph generator. */
3 /* */
4 /* This is a CGI script for generating graphs from the data stored in the */
5 /* RRD databases. */
6 /* */
7 /* Copyright (C) 2004-2011 Henrik Storner <henrik@hswn.dk> */
8 /* */
9 /* This program is released under the GNU General Public License (GPL), */
10 /* version 2. See the file "COPYING" for details. */
11 /* */
12 /*----------------------------------------------------------------------------*/
13
14 static char rcsid[] = "$Id: showgraph.c 8076 2019-08-12 19:23:00Z jccleaver $";
15
16 #include <limits.h>
17 #include <stdio.h>
18 #include <string.h>
19 #include <unistd.h>
20 #include <stdlib.h>
21 #include <ctype.h>
22 #include <sys/types.h>
23 #include <sys/stat.h>
24 #include <dirent.h>
25 #include <errno.h>
26 #include <sys/socket.h>
27 #include <sys/un.h>
28 #include <fcntl.h>
29
30 #include <pcre.h>
31 #include <rrd.h>
32
33 #include "libxymon.h"
34
35 #define HOUR_GRAPH "e-48h"
36 #define DAY_GRAPH "e-12d"
37 #define WEEK_GRAPH "e-48d"
38 #define MONTH_GRAPH "e-576d"
39
40 /* RRDtool 1.0.x handles graphs with no DS definitions just fine. 1.2.x does not. */
41 #ifdef RRDTOOL12
42 #ifndef HIDE_EMPTYGRAPH
43 #define HIDE_EMPTYGRAPH 1
44 #endif
45 #endif
46
47 #ifdef HIDE_EMPTYGRAPH
48 unsigned char blankimg[] = "\x89\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\x00\x00\x01\x00\x00\x00\x01\x08\x06\x00\x00\x00\x1f\x15\xc4\x89\x00\x00\x00\x04\x67\x41\x4d\x41\x00\x00\xb1\x8f\x0b\xfc\x61\x05\x00\x00\x00\x06\x62\x4b\x47\x44\x00\xff\x00\xff\x00\xff\xa0\xbd\xa7\x93\x00\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0b\x12\x00\x00\x0b\x12\x01\xd2\xdd\x7e\xfc\x00\x00\x00\x07\x74\x49\x4d\x45\x07\xd1\x01\x14\x12\x21\x14\x7e\x4a\x3a\xd2\x00\x00\x00\x0d\x49\x44\x41\x54\x78\xda\x63\x60\x60\x60\x60\x00\x00\x00\x05\x00\x01\x7a\xa8\x57\x50\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82";
49 #endif
50
51
52 char *hostname = NULL;
53 char **hostlist = NULL;
54 int hostlistsize = 0;
55 char *displayname = NULL;
56 char *service = NULL;
57 char *period = NULL;
58 time_t persecs = 0;
59 char *gtype = NULL;
60 char *glegend = NULL;
61 enum {ACT_MENU, ACT_SELZOOM, ACT_VIEW} action = ACT_VIEW;
62 time_t graphstart = 0;
63 time_t graphend = 0;
64 double upperlimit = 0.0;
65 int haveupperlimit = 0;
66 double lowerlimit = 0.0;
67 int havelowerlimit = 0;
68 int graphwidth = 0;
69 int graphheight = 0;
70 int ignorestalerrds = 0;
71 int bgcolor = COL_GREEN;
72
73 int coloridx = 0;
74 char *colorlist[] = {
75 "0000FF", "FF0000", "00CC00", "FF00FF",
76 "555555", "880000", "000088", "008800",
77 "008888", "888888", "880088", "FFFF00",
78 "888800", "00FFFF", "00FF00", "AA8800",
79 "AAAAAA", "DD8833", "DDCC33", "8888FF",
80 "5555AA", "B428D3", "FF5555", "DDDDDD",
81 "AAFFAA", "AAFFFF", "FFAAFF", "FFAA55",
82 "55AAFF", "AA55FF",
83 NULL
84 };
85
86 typedef struct gdef_t {
87 char *name;
88 char *fnpat;
89 char *exfnpat;
90 char *title;
91 char *yaxis;
92 char *graphopts;
93 int novzoom;
94 char **defs;
95 struct gdef_t *next;
96 } gdef_t;
97 gdef_t *gdefs = NULL;
98
99 typedef struct rrddb_t {
100 char *key;
101 char *rrdfn;
102 char *rrdparam;
103 } rrddb_t;
104
105 rrddb_t *rrddbs = NULL;
106 int rrddbcount = 0;
107 int rrddbsize = 0;
108 int rrdidx = 0;
109 int paramlen = 0;
110 int firstidx = -1;
111 int idxcount = -1;
112 int lastidx = 0;
113
errormsg(char * msg)114 void errormsg(char *msg)
115 {
116 printf("Content-type: %s\n\n", xgetenv("HTMLCONTENTTYPE"));
117 printf("<html><head><title>Invalid request</title></head>\n");
118 printf("<body>%s</body></html>\n", msg);
119 exit(1);
120 }
121
request_cacheflush(char * hostname)122 void request_cacheflush(char *hostname)
123 {
124 /* Build a cache-flush request, and send it to all of the $XYMONTMP/rrdctl.* sockets */
125 SBUF_DEFINE(req);
126 char *bufp;
127 int bytesleft;
128 DIR *dir;
129 struct dirent *d;
130 int ctlsocket = -1;
131
132 ctlsocket = socket(AF_UNIX, SOCK_DGRAM, 0);
133 if (ctlsocket == -1) {
134 errprintf("Cannot get socket: %s\n", strerror(errno));
135 return;
136 }
137 fcntl(ctlsocket, F_SETFL, O_NONBLOCK);
138
139 dir = opendir(xgetenv("XYMONTMP"));
140 if (!dir) {
141 errprintf("Cannot access $XYMONTMP directory: %s\n", strerror(errno));
142 return;
143 }
144
145 SBUF_MALLOC(req, strlen(hostname)+3);
146 snprintf(req, req_buflen, "/%s/", hostname);
147
148 while ((d = readdir(dir)) != NULL) {
149 if (strncmp(d->d_name, "rrdctl.", 7) == 0) {
150 struct sockaddr_un myaddr;
151 socklen_t myaddrsz = 0;
152 int n, sendfailed = 0;
153 SBUF_DEFINE(fnam);
154
155 memset(&myaddr, 0, sizeof(myaddr));
156 myaddr.sun_family = AF_UNIX;
157
158 SBUF_MALLOC(fnam, strlen(xgetenv("XYMONTMP"))+ strlen(d->d_name) + 2);
159 snprintf(fnam, fnam_buflen, "%s/%s", xgetenv("XYMONTMP"), d->d_name);
160 if (strlen(fnam) > sizeof(myaddr.sun_path)) {
161 errprintf("rrdctl files located in XYMONTMP with too long pathname - max %d characters\n", sizeof(myaddr.sun_path));
162 return;
163 }
164 strncpy(myaddr.sun_path, fnam, sizeof(myaddr.sun_path));
165 xfree(fnam);
166
167 myaddrsz = sizeof(myaddr);
168 bufp = req; bytesleft = strlen(req);
169 do {
170 n = sendto(ctlsocket, bufp, bytesleft, 0, (struct sockaddr *)&myaddr, myaddrsz);
171 if (n == -1) {
172 if (errno == EDESTADDRREQ) {
173 /* Probably a left-over rrdctl file, ignore it */
174 }
175 else if (errno == EAGAIN) {
176 /* Harmless */
177 }
178 else {
179 errprintf("Sendto failed: %s\n", strerror(errno));
180 }
181
182 sendfailed = 1;
183 }
184 else {
185 bytesleft -= n;
186 bufp += n;
187 }
188 } while ((!sendfailed) && (bytesleft > 0));
189 }
190 }
191 closedir(dir);
192 xfree(req);
193
194 /*
195 * Sleep 0.3 secs to allow the cache flush to happen.
196 * Note: It isn't guaranteed to happen in this time, but
197 * there's a good chance that it will.
198 */
199 usleep(300000);
200 }
201
202
parse_query(void)203 void parse_query(void)
204 {
205 cgidata_t *cgidata = NULL, *cwalk;
206 char *stp = NULL;
207
208 cgidata = cgi_request();
209
210 cwalk = cgidata;
211 while (cwalk) {
212 if (strcmp(cwalk->name, "host") == 0) {
213 char *hnames = strdup(cwalk->value);
214
215 hostname = strtok_r(cwalk->value, ",", &stp);
216 while (hostname) {
217 if (hostlist == NULL) {
218 hostlistsize = 1;
219 hostlist = (char **)malloc(sizeof(char *));
220 hostlist[0] = strdup(hostname);
221 }
222 else {
223 hostlistsize++;
224 hostlist = (char **)realloc(hostlist, (hostlistsize * sizeof(char *)));
225 hostlist[hostlistsize-1] = strdup(hostname);
226 }
227
228 hostname = strtok_r(NULL, ",", &stp);
229 }
230
231 xfree(hnames);
232 if (hostlist) hostname = hostlist[0];
233 }
234 else if (strcmp(cwalk->name, "service") == 0) {
235 service = strdup(cwalk->value);
236 }
237 else if (strcmp(cwalk->name, "disp") == 0) {
238 displayname = strdup(cwalk->value);
239 }
240 else if (strcmp(cwalk->name, "graph") == 0) {
241 if (strcmp(cwalk->value, "hourly") == 0) {
242 period = HOUR_GRAPH;
243 persecs = 48*60*60;
244 gtype = strdup(cwalk->value);
245 glegend = "Last 48 Hours";
246 }
247 else if (strcmp(cwalk->value, "daily") == 0) {
248 period = DAY_GRAPH;
249 persecs = 12*24*60*60;
250 gtype = strdup(cwalk->value);
251 glegend = "Last 12 Days";
252 }
253 else if (strcmp(cwalk->value, "weekly") == 0) {
254 period = WEEK_GRAPH;
255 persecs = 48*24*60*60;
256 gtype = strdup(cwalk->value);
257 glegend = "Last 48 Days";
258 }
259 else if (strcmp(cwalk->value, "monthly") == 0) {
260 period = MONTH_GRAPH;
261 persecs = 576*24*60*60;
262 gtype = strdup(cwalk->value);
263 glegend = "Last 576 Days";
264 }
265 else if (strcmp(cwalk->value, "custom") == 0) {
266 period = NULL;
267 persecs = 0;
268 gtype = strdup(cwalk->value);
269 glegend = "";
270 }
271 }
272 else if (strcmp(cwalk->name, "first") == 0) {
273 firstidx = atoi(cwalk->value) - 1;
274 }
275 else if (strcmp(cwalk->name, "count") == 0) {
276 idxcount = atoi(cwalk->value);
277 lastidx = firstidx + idxcount - 1;
278 }
279 else if (strcmp(cwalk->name, "action") == 0) {
280 if (cwalk->value) {
281 if (strcmp(cwalk->value, "menu") == 0) action = ACT_MENU;
282 else if (strcmp(cwalk->value, "selzoom") == 0) action = ACT_SELZOOM;
283 else if (strcmp(cwalk->value, "view") == 0) action = ACT_VIEW;
284 }
285 }
286 else if (strcmp(cwalk->name, "graph_start") == 0) {
287 if (cwalk->value) graphstart = atoi(cwalk->value);
288 }
289 else if (strcmp(cwalk->name, "graph_end") == 0) {
290 if (cwalk->value) graphend = atoi(cwalk->value);
291 }
292 else if (strcmp(cwalk->name, "upper") == 0) {
293 if (cwalk->value) { upperlimit = atof(cwalk->value); haveupperlimit = 1; }
294 }
295 else if (strcmp(cwalk->name, "lower") == 0) {
296 if (cwalk->value) { lowerlimit = atof(cwalk->value); havelowerlimit = 1; }
297 }
298 else if (strcmp(cwalk->name, "graph_width") == 0) {
299 if (cwalk->value) graphwidth = atoi(cwalk->value);
300 }
301 else if (strcmp(cwalk->name, "graph_height") == 0) {
302 if (cwalk->value) graphheight = atoi(cwalk->value);
303 }
304 else if (strcmp(cwalk->name, "nostale") == 0) {
305 ignorestalerrds = 1;
306 }
307 else if (strcmp(cwalk->name, "color") == 0) {
308 int color = parse_color(cwalk->value);
309 if (color != -1) bgcolor = color;
310 }
311
312 cwalk = cwalk->next;
313 }
314
315 if (hostlistsize == 1) {
316 xfree(hostlist); hostlist = NULL;
317 }
318 else {
319 displayname = hostname = strdup("");
320 }
321
322 if ((hostname == NULL) || (service == NULL)) errormsg("Invalid request - no host or service");
323 if (displayname == NULL) displayname = hostname;
324 if (graphstart && graphend) {
325 char t1[15], t2[15];
326
327 persecs = (graphend - graphstart);
328
329 strftime(t1, sizeof(t1), "%d/%b/%Y", localtime(&graphstart));
330 strftime(t2, sizeof(t2), "%d/%b/%Y", localtime(&graphend));
331 glegend = (char *)malloc(40);
332 snprintf(glegend, 40, "%s - %s", t1, t2);
333 }
334 }
335
336
load_gdefs(char * fn)337 void load_gdefs(char *fn)
338 {
339 FILE *fd;
340 strbuffer_t *inbuf;
341 char *p;
342 gdef_t *newitem = NULL;
343 char **alldefs = NULL;
344 int alldefcount = 0, alldefidx = 0;
345
346 inbuf = newstrbuffer(0);
347 fd = stackfopen(fn, "r", NULL);
348 if (fd == NULL) errormsg("Cannot load graph definitions");
349 while (stackfgets(inbuf, NULL)) {
350 p = strchr(STRBUF(inbuf), '\n'); if (p) *p = '\0';
351 p = STRBUF(inbuf); p += strspn(p, " \t");
352 if ((strlen(p) == 0) || (*p == '#')) continue;
353
354 if (*p == '[') {
355 char *delim;
356
357 if (newitem) {
358 /* Save the current one, and start on the next item */
359 alldefs[alldefidx] = NULL;
360 newitem->defs = alldefs;
361 newitem->next = gdefs;
362 gdefs = newitem;
363 }
364 newitem = calloc(1, sizeof(gdef_t));
365 delim = strchr(p, ']'); if (delim) *delim = '\0';
366 newitem->name = strdup(p+1);
367 alldefcount = 10;
368 alldefs = (char **)malloc((alldefcount+1) * sizeof(char *));
369 alldefidx = 0;
370 }
371 else if (strncasecmp(p, "FNPATTERN", 9) == 0) {
372 p += 9; p += strspn(p, " \t");
373 newitem->fnpat = strdup(p);
374 }
375 else if (strncasecmp(p, "EXFNPATTERN", 11) == 0) {
376 p += 11; p += strspn(p, " \t");
377 newitem->exfnpat = strdup(p);
378 }
379 else if (strncasecmp(p, "TITLE", 5) == 0) {
380 p += 5; p += strspn(p, " \t");
381 newitem->title = strdup(p);
382 }
383 else if (strncasecmp(p, "YAXIS", 5) == 0) {
384 p += 5; p += strspn(p, " \t");
385 newitem->yaxis = strdup(p);
386 }
387 else if (strncasecmp(p, "NOVZOOM", 7) == 0) {
388 newitem->novzoom = 1;
389 }
390 else if (strncasecmp(p, "GRAPHOPTIONS", 12) == 0) {
391 p += 12; p += strspn(p, " \t");
392 newitem->graphopts = strdup(p);
393 }
394 else if (haveupperlimit && (strncmp(p, "-u ", 3) == 0)) {
395 continue;
396 }
397 else if (haveupperlimit && (strncmp(p, "-upper ", 7) == 0)) {
398 continue;
399 }
400 else if (havelowerlimit && (strncmp(p, "-l ", 3) == 0)) {
401 continue;
402 }
403 else if (havelowerlimit && (strncmp(p, "-lower ", 7) == 0)) {
404 continue;
405 }
406 else {
407 if (alldefidx == alldefcount) {
408 /* Must expand alldefs */
409 alldefcount += 5;
410 alldefs = (char **)realloc(alldefs, (alldefcount+1) * sizeof(char *));
411 }
412 alldefs[alldefidx++] = strdup(p);
413 }
414 }
415
416 /* Pick up the last item */
417 if (newitem) {
418 /* Save the current one, and start on the next item */
419 alldefs[alldefidx] = NULL;
420 newitem->defs = alldefs;
421 newitem->next = gdefs;
422 gdefs = newitem;
423 }
424
425 stackfclose(fd);
426 freestrbuffer(inbuf);
427 }
428
lookup_meta(char * keybuf,char * rrdfn)429 char *lookup_meta(char *keybuf, char *rrdfn)
430 {
431 FILE *fd;
432 SBUF_DEFINE(metafn);
433 char *p;
434 int servicelen = strlen(service);
435 int keylen = strlen(keybuf);
436 int found;
437 static char buf[1024]; /* Must be static since it is returned to caller */
438
439 SBUF_MALLOC(metafn, PATH_MAX);
440
441 p = strrchr(rrdfn, '/');
442 if (!p) {
443 strncpy(metafn, "rrd.meta", metafn_buflen);
444 }
445 else {
446 metafn = (char *)malloc(strlen(rrdfn) + 10);
447 *p = '\0';
448 snprintf(metafn, metafn_buflen, "%s/rrd.meta", rrdfn);
449 *p = '/';
450 }
451 fd = fopen(metafn, "r");
452 xfree(metafn);
453
454 if (!fd) return NULL;
455
456 /* Find the first line that has our key and then whitespace */
457 found = 0;
458 while (!found && fgets(buf, sizeof(buf), fd)) {
459 found = ( (strncmp(buf, service, servicelen) == 0) &&
460 (*(buf+servicelen) == ':') &&
461 (strncmp(buf+servicelen+1, keybuf, keylen) == 0) &&
462 isspace(*(buf+servicelen+1+keylen)) );
463 }
464 fclose(fd);
465
466 if (found) {
467 char *eoln, *val;
468
469 val = buf + servicelen + 1 + keylen;
470 val += strspn(val, " \t");
471
472 eoln = strchr(val, '\n');
473 if (eoln) *eoln = '\0';
474
475 if (strlen(val) > 0) return val;
476 }
477
478 return NULL;
479 }
480
colon_escape(char * buf)481 char *colon_escape(char *buf)
482 {
483 /* Change all colons to "\:" */
484 static char *result = NULL;
485 int count = 0;
486 char *p, *inp, *outp;
487
488 p = buf; while ((p = strchr(p, ':')) != NULL) { count++; p++; }
489 if (count == 0) return buf;
490
491 if (result) xfree(result);
492 result = (char *) malloc(strlen(buf) + count + 1); /* Add one backslash per colon */
493 *result = '\0';
494
495 inp = buf; outp = result;
496 while (*inp) {
497 p = strchr(inp, ':');
498 if (p == NULL) {
499 strcat(outp, inp);
500 inp += strlen(inp);
501 outp += strlen(outp);
502 }
503 else {
504 *p = '\0';
505 strcat(outp, inp); strcat(outp, "\\:");
506 *p = ':';
507 inp = p+1;
508 outp = outp + strlen(outp);
509 }
510 }
511
512 *outp = '\0';
513 return result;
514 }
515
expand_tokens(char * tpl)516 char *expand_tokens(char *tpl)
517 {
518 static strbuffer_t *result = NULL;
519 char *inp, *p;
520
521 if (strchr(tpl, '@') == NULL) return tpl;
522
523 if (!result) result = newstrbuffer(2048); else clearstrbuffer(result);
524
525 inp = tpl;
526 while (*inp) {
527 p = strchr(inp, '@');
528 if (p == NULL) {
529 addtobuffer(result, inp);
530 inp += strlen(inp);
531 continue;
532 }
533
534 *p = '\0';
535 if (strlen(inp)) {
536 addtobuffer(result, inp);
537 inp = p;
538 }
539 *p = '@';
540
541 if (strncmp(inp, "@RRDFN@", 7) == 0) {
542 addtobuffer(result, colon_escape(rrddbs[rrdidx].rrdfn));
543 inp += 7;
544 }
545 else if (strncmp(inp, "@RRDPARAM@", 10) == 0) {
546 /*
547 * We do a colon-escape first, then change all commas to slashes as
548 * this is a common mangling used by multiple backends (disk, http, iostat...)
549 */
550 if (rrddbs[rrdidx].rrdparam) {
551 char *val, *p;
552 int vallen;
553 SBUF_DEFINE(resultstr);
554
555 val = colon_escape(rrddbs[rrdidx].rrdparam);
556 p = val; while ((p = strchr(p, ',')) != NULL) *p = '/';
557
558 /* rrdparam strings may be very long. */
559 if (strlen(val) > 100) *(val+100) = '\0';
560
561 /*
562 * "paramlen" holds the longest string of the any of the matching files' rrdparam.
563 * However, because this goes through colon_escape(), the actual string length
564 * passed to librrd functions may be longer (since ":" must be escaped as "\:").
565 */
566 vallen = strlen(val);
567 if (vallen < paramlen) vallen = paramlen;
568
569 SBUF_MALLOC(resultstr, vallen + 1);
570 snprintf(resultstr, resultstr_buflen, "%-*s", paramlen, val);
571 addtobuffer(result, resultstr);
572 xfree(resultstr);
573 }
574 inp += 10;
575 }
576 else if (strncmp(inp, "@RRDMETA@", 9) == 0) {
577 /*
578 * We do a colon-escape first, then change all commas to slashes as
579 * this is a common mangling used by multiple backends (disk, http, iostat...)
580 */
581 if (rrddbs[rrdidx].rrdparam) {
582 char *val, *p, *metaval;
583
584 val = colon_escape(rrddbs[rrdidx].rrdparam);
585 p = val; while ((p = strchr(p, ',')) != NULL) *p = '/';
586
587 metaval = lookup_meta(val, rrddbs[rrdidx].rrdfn);
588 if (metaval) addtobuffer(result, metaval);
589 }
590 inp += 9;
591 }
592 else if (strncmp(inp, "@RRDIDX@", 8) == 0) {
593 char numstr[10];
594
595 snprintf(numstr, sizeof(numstr), "%d", rrdidx);
596 addtobuffer(result, numstr);
597 inp += 8;
598 }
599 else if (strncmp(inp, "@STACKIT@", 9) == 0) {
600 /* Contributed by Gildas Le Nadan <gn1@sanger.ac.uk> */
601
602 /* the STACK behavior changed between rrdtool 1.0.x
603 * and 1.2.x, hence the ifdef:
604 * - in 1.0.x, you replace the graph type (AREA|LINE)
605 * for the graph you want to stack with the STACK
606 * keyword
607 * - in 1.2.x, you add the STACK keyword at the end
608 * of the definition
609 *
610 * Please note that in both cases the first entry
611 * mustn't contain the keyword STACK at all, so
612 * we need a different treatment for the first rrdidx
613 *
614 * examples of graphs.cfg entries:
615 *
616 * - rrdtool 1.0.x
617 * @STACKIT@:la@RRDIDX@#@COLOR@:@RRDPARAM@
618 *
619 * - rrdtool 1.2.x
620 * AREA::la@RRDIDX@#@COLOR@:@RRDPARAM@:@STACKIT@
621 */
622 char numstr[10];
623
624 if (rrdidx == 0) {
625 #ifdef RRDTOOL12
626 strncpy(numstr, "", sizeof(numstr));
627 #else
628 snprintf(numstr, sizeof(numstr), "AREA");
629 #endif
630 }
631 else {
632 snprintf(numstr, sizeof(numstr), "STACK");
633 }
634 addtobuffer(result, numstr);
635 inp += 9;
636 }
637 else if (strncmp(inp, "@SERVICE@", 9) == 0) {
638 addtobuffer(result, service);
639 inp += 9;
640 }
641 else if (strncmp(inp, "@COLOR@", 7) == 0) {
642 addtobuffer(result, colorlist[coloridx]);
643 inp += 7;
644 coloridx++; if (colorlist[coloridx] == NULL) coloridx = 0;
645 }
646 else {
647 addtobuffer(result, "@");
648 inp += 1;
649 }
650 }
651
652 return STRBUF(result);
653 }
654
rrd_name_compare(const void * v1,const void * v2)655 int rrd_name_compare(const void *v1, const void *v2)
656 {
657 rrddb_t *r1 = (rrddb_t *)v1;
658 rrddb_t *r2 = (rrddb_t *)v2;
659 char *endptr;
660 long numkey1, numkey2;
661 int key1isnumber, key2isnumber;
662
663 /* See if the keys are all numeric; if yes, then do a numeric sort */
664 numkey1 = strtol(r1->key, &endptr, 10); key1isnumber = (*endptr == '\0');
665 numkey2 = strtol(r2->key, &endptr, 10); key2isnumber = (*endptr == '\0');
666
667 if (key1isnumber && key2isnumber) {
668 if (numkey1 < numkey2) return -1;
669 else if (numkey1 > numkey2) return 1;
670 else return 0;
671 }
672
673 return strcmp(r1->key, r2->key);
674 }
675
graph_link(FILE * output,char * uri,char * grtype,time_t seconds)676 void graph_link(FILE *output, char *uri, char *grtype, time_t seconds)
677 {
678 time_t gstart, gend;
679 char *grtype_s;
680
681 fprintf(output, "<tr>\n");
682 grtype_s = htmlquoted(grtype);
683
684 switch (action) {
685 case ACT_MENU:
686 fprintf(output, " <td align=\"left\"><img src=\"%s&action=view&graph=%s\" alt=\"%s graph\"></td>\n",
687 uri, grtype_s, grtype_s);
688 fprintf(output, " <td align=\"left\" valign=\"top\"> <a href=\"%s&graph=%s&action=selzoom&color=%s\"> <img src=\"%s/zoom.%s\" border=0 alt=\"Zoom graph\" style='padding: 3px'> </a> </td>\n",
689 uri, grtype_s, colorname(bgcolor), xgetenv("XYMONSKIN"), xgetenv("IMAGEFILETYPE"));
690 break;
691
692 case ACT_SELZOOM:
693 if (graphend == 0) gend = getcurrenttime(NULL); else gend = graphend;
694 if (graphstart == 0) gstart = gend - persecs; else gstart = graphstart;
695
696 fprintf(output, " <td align=\"left\"><img id='zoomGraphImage' src=\"%s&graph=%s&action=view&graph_start=%u&graph_end=%u&graph_height=%d&graph_width=%d&",
697 uri, grtype_s, (int) gstart, (int) gend, graphheight, graphwidth);
698 if (haveupperlimit) fprintf(output, "&upper=%f", upperlimit);
699 if (havelowerlimit) fprintf(output, "&lower=%f", lowerlimit);
700 fprintf(output, "\" alt=\"Zoom source image\"></td>\n");
701 break;
702
703 case ACT_VIEW:
704 break;
705 }
706
707 fprintf(output, "</tr>\n");
708 }
709
build_selfURI(void)710 char *build_selfURI(void)
711 {
712 strbuffer_t *result = newstrbuffer(2048);
713 char numbuf[40];
714
715 addtobuffer(result, xgetenv("SCRIPT_NAME"));
716
717 addtobuffer(result, "?host=");
718 if (hostlist) {
719 int i;
720
721 addtobuffer(result, urlencode(hostlist[0]));
722 for (i = 1; (i < hostlistsize); i++) {
723 addtobuffer(result, ",");
724 addtobuffer(result, urlencode(hostlist[i]));
725 }
726 }
727 else {
728 addtobuffer(result, urlencode(hostname));
729 }
730
731 addtobuffer(result, "&color="); addtobuffer(result, colorname(bgcolor));
732 if (service) {
733 addtobuffer(result, "&service=");
734 addtobuffer(result, urlencode(service));
735 }
736 if (haveupperlimit) {
737 snprintf(numbuf, sizeof(numbuf)-1, "%f", upperlimit);
738 addtobuffer(result, "&upper=");
739 addtobuffer(result, urlencode(numbuf));
740 }
741 if (graphheight) {
742 snprintf(numbuf, sizeof(numbuf)-1, "%d", graphheight);
743 addtobuffer(result, "&graph_height=");
744 addtobuffer(result, urlencode(numbuf));
745 }
746 if (graphwidth) {
747 snprintf(numbuf, sizeof(numbuf)-1, "%d", graphwidth);
748 addtobuffer(result, "&graph_width=");
749 addtobuffer(result, urlencode(numbuf));
750 }
751
752 if (displayname && (displayname != hostname)) {
753 addtobuffer(result, "&disp=");
754 addtobuffer(result, urlencode(displayname));
755 }
756
757 if (firstidx != -1) {
758 snprintf(numbuf, sizeof(numbuf)-1, "&first=%d", firstidx+1);
759 addtobuffer(result, numbuf);
760 }
761 if (idxcount != -1) {
762 snprintf(numbuf, sizeof(numbuf)-1, "&count=%d", idxcount);
763 addtobuffer(result, numbuf);
764 }
765 if (ignorestalerrds) addtobuffer(result, "&nostale");
766
767 return STRBUF(result);
768 }
769
770
build_menu_page(char * selfURI,int backsecs)771 void build_menu_page(char *selfURI, int backsecs)
772 {
773 /* This is special-handled, because we just want to generate an HTML link page */
774 fprintf(stdout, "Content-type: %s\n\n", xgetenv("HTMLCONTENTTYPE"));
775 sethostenv(displayname, "", service, colorname(bgcolor), hostname);
776 sethostenv_backsecs(backsecs);
777
778 headfoot(stdout, "graphs", "", "header", bgcolor);
779
780 fprintf(stdout, "<table align=\"center\" summary=\"Graphs\">\n");
781
782 graph_link(stdout, selfURI, "hourly", 48*60*60);
783 graph_link(stdout, selfURI, "daily", 12*24*60*60);
784 graph_link(stdout, selfURI, "weekly", 48*24*60*60);
785 graph_link(stdout, selfURI, "monthly", 576*24*60*60);
786
787 fprintf(stdout, "</table>\n");
788
789 headfoot(stdout, "graphs", "", "footer", bgcolor);
790 }
791
792
generate_graph(char * gdeffn,char * rrddir,char * graphfn)793 void generate_graph(char *gdeffn, char *rrddir, char *graphfn)
794 {
795 gdef_t *gdef = NULL, *gdefuser = NULL;
796 int wantsingle = 0;
797 DIR *dir;
798 time_t now = getcurrenttime(NULL);
799
800 int argi, pcount;
801
802 /* Options for rrd_graph() */
803 int rrdargcount;
804 char **rrdargs = NULL; /* The full argv[] table of string pointers to arguments */
805 char heightopt[30]; /* -h HEIGHT */
806 char widthopt[30]; /* -w WIDTH */
807 char upperopt[30]; /* -u MAX */
808 char loweropt[30]; /* -l MIN */
809 char startopt[30]; /* -s STARTTIME */
810 char endopt[30]; /* -e ENDTIME */
811 char graphtitle[1024]; /* --title TEXT */
812 char timestamp[50]; /* COMMENT with timestamp graph was generated */
813
814 /* Return variables from rrd_graph() */
815 int result;
816 char **calcpr = NULL;
817 int xsize, ysize;
818 double ymin, ymax;
819
820 char *useroptval = NULL;
821 char **useropts = NULL;
822 int useroptcount = 0, useroptidx;
823
824 /* Find the graphs.cfg file and load it */
825 if (gdeffn == NULL) {
826 char fnam[PATH_MAX];
827 snprintf(fnam, sizeof(fnam), "%s/etc/graphs.cfg", xgetenv("XYMONHOME"));
828 gdeffn = strdup(fnam);
829 }
830 load_gdefs(gdeffn);
831
832
833 /* Determine the real service name. It might be a multi-service graph */
834 if (strchr(service, ':') || strchr(service, '.')) {
835 /*
836 * service is "tcp:foo" - so use the "tcp" graph definition, but for a
837 * single service (as if service was set to just "foo").
838 */
839 char *delim = service + strcspn(service, ":.");
840 char *realservice;
841
842 *delim = '\0';
843 realservice = strdup(delim+1);
844
845 /* The requested gdef only acts as a fall-back solution so don't set gdef here. */
846 for (gdefuser = gdefs; (gdefuser && strcmp(service, gdefuser->name)); gdefuser = gdefuser->next) ;
847 strcpy(service, realservice);
848 wantsingle = 1;
849
850 xfree(realservice);
851 }
852
853 /*
854 * Lookup which RRD file corresponds to the service-name, and how we handle this graph.
855 * We first lookup the service name in the graph definition list.
856 * If that fails, then we try mapping it via the servicename -> RRD map.
857 */
858 for (gdef = gdefs; (gdef && strcmp(service, gdef->name)); gdef = gdef->next) ;
859 if (gdef == NULL) {
860 if (gdefuser) {
861 gdef = gdefuser;
862 }
863 else {
864 xymonrrd_t *ldef = find_xymon_rrd(service, NULL);
865 if (ldef) {
866 for (gdef = gdefs; (gdef && strcmp(ldef->xymonrrdname, gdef->name)); gdef = gdef->next) ;
867 wantsingle = 1;
868 }
869 }
870 }
871 if (gdef == NULL) errormsg("Unknown graph requested");
872 if (hostlist && (gdef->fnpat == NULL)) {
873 SBUF_DEFINE(multiname);
874
875 SBUF_MALLOC(multiname, strlen(gdef->name) + 7);
876 snprintf(multiname, multiname_buflen, "%s-multi", gdef->name);
877 for (gdef = gdefs; (gdef && strcmp(multiname, gdef->name)); gdef = gdef->next) ;
878 if (gdef == NULL) errormsg("Unknown multi-graph requested");
879 xfree(multiname);
880 }
881
882
883 /*
884 * If we're here only to collect the min/max values for the graph but it doesn't
885 * allow vertical zoom, then there's no reason to waste anymore time.
886 */
887 if ((action == ACT_SELZOOM) && gdef->novzoom) {
888 haveupperlimit = havelowerlimit = 0;
889 return;
890 }
891
892 /* Determine the directory with the host RRD files, and go there. */
893 if (rrddir == NULL) {
894 char dnam[PATH_MAX];
895
896 if (hostlist) snprintf(dnam, sizeof(dnam), "%s", xgetenv("XYMONRRDS"));
897 else snprintf(dnam, sizeof(dnam), "%s/%s", xgetenv("XYMONRRDS"), hostname);
898
899 rrddir = strdup(dnam);
900 }
901 if (chdir(rrddir)) errormsg("Cannot access RRD directory");
902
903 /* Request an RRD cache flush from the xymond_rrd update daemon */
904 if (hostlist) {
905 int i;
906 for (i=0; (i < hostlistsize); i++) request_cacheflush(hostlist[i]);
907 }
908 else if (hostname) request_cacheflush(hostname);
909
910 /* What RRD files do we have matching this request? */
911 if (hostlist || (gdef->fnpat == NULL)) {
912 /*
913 * No pattern, just a single file. It doesnt matter if it exists, because
914 * these types of graphs usually have a hard-coded value for the RRD filename
915 * in the graph definition.
916 */
917 rrddbcount = rrddbsize = (hostlist ? hostlistsize : 1);
918 rrddbs = (rrddb_t *)malloc((rrddbsize + 1) * sizeof(rrddb_t));
919
920 if (!hostlist) {
921 size_t buflen = strlen(gdef->name) + strlen(".rrd") + 1;
922
923 rrddbs[0].key = strdup(service);
924 rrddbs[0].rrdfn = (char *)malloc(buflen);
925 snprintf(rrddbs[0].rrdfn, buflen, "%s.rrd", gdef->name);
926 rrddbs[0].rrdparam = NULL;
927 }
928 else {
929 int i, maxlen;
930 char paramfmt[20];
931
932 for (i=0, maxlen=0; (i < hostlistsize); i++) {
933 if (strlen(hostlist[i]) > maxlen) maxlen = strlen(hostlist[i]);
934 }
935 snprintf(paramfmt, sizeof(paramfmt), "%%-%ds", maxlen+1);
936
937 for (i=0; (i < hostlistsize); i++) {
938 size_t buflen;
939
940 rrddbs[i].key = strdup(service);
941 buflen = strlen(hostlist[i]) + strlen(gdef->fnpat) + 2;
942 rrddbs[i].rrdfn = (char *)malloc(buflen);
943 snprintf(rrddbs[i].rrdfn, buflen, "%s/%s", hostlist[i], gdef->fnpat);
944
945 buflen = maxlen + 2;
946 rrddbs[i].rrdparam = (char *)malloc(buflen);
947 snprintf(rrddbs[i].rrdparam, buflen, paramfmt, hostlist[i]);
948 }
949 }
950 }
951 else {
952 struct dirent *d;
953 pcre *pat, *expat = NULL;
954 const char *errmsg;
955 int errofs, result;
956 int ovector[30];
957 struct stat st;
958 time_t now = getcurrenttime(NULL);
959
960 /* Scan the directory to see what RRD files are there that match */
961 dir = opendir("."); if (dir == NULL) errormsg("Unexpected error while accessing RRD directory");
962
963 /* Setup the pattern to match filenames against */
964 pat = pcre_compile(gdef->fnpat, PCRE_CASELESS, &errmsg, &errofs, NULL);
965 if (!pat) {
966 char msg[8192];
967
968 snprintf(msg, sizeof(msg), "graphs.cfg error, PCRE pattern %s invalid: %s, offset %d\n",
969 htmlquoted(gdef->fnpat), errmsg, errofs);
970 errormsg(msg);
971 }
972 if (gdef->exfnpat) {
973 expat = pcre_compile(gdef->exfnpat, PCRE_CASELESS, &errmsg, &errofs, NULL);
974 if (!expat) {
975 char msg[8192];
976
977 snprintf(msg, sizeof(msg),
978 "graphs.cfg error, PCRE pattern %s invalid: %s, offset %d\n",
979 htmlquoted(gdef->exfnpat), errmsg, errofs);
980 errormsg(msg);
981 }
982 }
983
984 /* Allocate an initial filename table */
985 rrddbsize = 5;
986 rrddbs = (rrddb_t *) malloc((rrddbsize+1) * sizeof(rrddb_t));
987
988 while ((d = readdir(dir)) != NULL) {
989 char *ext;
990 char param[PATH_MAX];
991
992 /* Ignore dot-files and files with names shorter than ".rrd" */
993 if (*(d->d_name) == '.') continue;
994 ext = d->d_name + strlen(d->d_name) - strlen(".rrd");
995 if ((ext <= d->d_name) || (strcmp(ext, ".rrd") != 0)) continue;
996
997 /* First check the exclude pattern. */
998 if (expat) {
999 result = pcre_exec(expat, NULL, d->d_name, strlen(d->d_name), 0, 0,
1000 ovector, (sizeof(ovector)/sizeof(int)));
1001 if (result >= 0) continue;
1002 }
1003
1004 /* Then see if the include pattern matches. */
1005 result = pcre_exec(pat, NULL, d->d_name, strlen(d->d_name), 0, 0,
1006 ovector, (sizeof(ovector)/sizeof(int)));
1007 if (result < 0) continue;
1008
1009 if (wantsingle) {
1010 /* "Single" graph, i.e. a graph for a service normally included in a bundle (tcp) */
1011 if (strstr(d->d_name, service) == NULL) continue;
1012 }
1013
1014 /*
1015 * Has it been updated recently (within the past 24 hours) ?
1016 * We don't want old graphs to mess up multi-displays.
1017 */
1018 if (ignorestalerrds && (stat(d->d_name, &st) == 0) && ((now - st.st_mtime) > 86400)) {
1019 continue;
1020 }
1021
1022 /* We have a matching file! */
1023 rrddbs[rrddbcount].rrdfn = strdup(d->d_name);
1024 if (pcre_copy_substring(d->d_name, ovector, result, 1, param, sizeof(param)) > 0) {
1025 /*
1026 * This is ugly, but I cannot find a pretty way of un-mangling
1027 * the disk- and http-data that has been molested by the back-end.
1028 */
1029 if ((strcmp(param, ",root") == 0) &&
1030 ((strncmp(gdef->name, "disk", 4) == 0) || (strncmp(gdef->name, "inode", 5) == 0)) ) {
1031 rrddbs[rrddbcount].rrdparam = strdup(",");
1032 }
1033 else if ((strcmp(gdef->name, "http") == 0) && (strncmp(param, "http", 4) != 0)) {
1034 size_t buflen = strlen("http://")+strlen(param)+1;
1035 rrddbs[rrddbcount].rrdparam = (char *)malloc(buflen);
1036 snprintf(rrddbs[rrddbcount].rrdparam, buflen, "http://%s", param);
1037 }
1038 else {
1039 rrddbs[rrddbcount].rrdparam = strdup(param);
1040 }
1041
1042 if (strlen(rrddbs[rrddbcount].rrdparam) > paramlen) {
1043 /*
1044 * "paramlen" holds the longest string of the any of the matching files' rrdparam.
1045 */
1046 paramlen = strlen(rrddbs[rrddbcount].rrdparam);
1047 }
1048
1049 rrddbs[rrddbcount].key = strdup(rrddbs[rrddbcount].rrdparam);
1050 }
1051 else {
1052 rrddbs[rrddbcount].key = strdup(d->d_name);
1053 rrddbs[rrddbcount].rrdparam = NULL;
1054 }
1055
1056 rrddbcount++;
1057 if (rrddbcount == rrddbsize) {
1058 rrddbsize += 5;
1059 rrddbs = (rrddb_t *)realloc(rrddbs, (rrddbsize+1) * sizeof(rrddb_t));
1060 }
1061 }
1062 pcre_free(pat);
1063 if (expat) pcre_free(expat);
1064 closedir(dir);
1065 }
1066 rrddbs[rrddbcount].key = rrddbs[rrddbcount].rrdfn = rrddbs[rrddbcount].rrdparam = NULL;
1067
1068 /* Sort them so the display looks prettier */
1069 qsort(&rrddbs[0], rrddbcount, sizeof(rrddb_t), rrd_name_compare);
1070
1071 /* Setup the title */
1072 if (!gdef->title) gdef->title = strdup("");
1073 if (strncmp(gdef->title, "exec:", 5) == 0) {
1074 char *pcmd;
1075 int i, pcmdlen = 7;
1076 FILE *pfd;
1077 char *p;
1078 char *param_str = "%s \"%s\" %s \"%s\"";
1079
1080 pcmdlen += (strlen(gdef->title+5) + strlen(displayname) + strlen(service) + strlen(glegend));
1081 for (i=0; (i<rrddbcount); i++) pcmdlen += (strlen(rrddbs[i].rrdfn) + 3);
1082
1083 p = pcmd = (char *)malloc(pcmdlen+1);
1084 p += snprintf(p, pcmdlen+1, param_str, gdef->title+5, displayname, service, glegend);
1085 for (i=0; (i<rrddbcount); i++) {
1086 if ((firstidx == -1) || ((i >= firstidx) && (i <= lastidx))) {
1087 p += snprintf(p, (pcmdlen - (p - pcmd) + 1), " \"%s\"", rrddbs[i].rrdfn);
1088 }
1089 }
1090 pfd = popen(pcmd, "r");
1091 if (pfd) {
1092 if (fgets(graphtitle, sizeof(graphtitle), pfd) == NULL) *graphtitle = '\0';
1093 pclose(pfd);
1094 }
1095
1096 /* Drop any newline at end of the title */
1097 p = strchr(graphtitle, '\n'); if (p) *p = '\0';
1098 }
1099 else {
1100 snprintf(graphtitle, sizeof(graphtitle), "%s %s %s", displayname, gdef->title, glegend);
1101 }
1102
1103 snprintf(heightopt, sizeof(heightopt), "-h%d", graphheight);
1104 snprintf(widthopt, sizeof(widthopt), "-w%d", graphwidth);
1105
1106 /*
1107 * Grab user-provided additional rrd_graph options from RRDGRAPHOPTS
1108 */
1109 useroptcount = 0;
1110 useroptval = gdef->graphopts;
1111 if (!useroptval) useroptval = getenv("RRDGRAPHOPTS");
1112 if (useroptval) {
1113 char *tok;
1114
1115 useropts = (char **)calloc(1, sizeof(char *));
1116 useroptval = strdup(useroptval);
1117 tok = strtok(useroptval, " ");
1118 while (tok) {
1119 useroptcount++;
1120 useropts = (char **)realloc(useropts, (useroptcount+1)*sizeof(char *));
1121 useropts[useroptcount-1] = tok;
1122 useropts[useroptcount] = NULL;
1123 tok = strtok(NULL, " ");
1124 }
1125 }
1126
1127 /*
1128 * Setup the arguments for calling rrd_graph.
1129 * There's up to 16 standard arguments, plus the
1130 * graph-specific ones (which may be repeated if
1131 * there are multiple RRD-files to handle).
1132 */
1133 for (pcount = 0; (gdef->defs[pcount]); pcount++) ;
1134 rrdargs = (char **) calloc(16 + pcount*rrddbcount + useroptcount + 1, sizeof(char *));
1135
1136
1137 argi = 0;
1138 rrdargs[argi++] = "rrdgraph";
1139 rrdargs[argi++] = (action == ACT_VIEW) ? graphfn : "/dev/null";
1140 rrdargs[argi++] = "--title";
1141 rrdargs[argi++] = graphtitle;
1142 rrdargs[argi++] = widthopt;
1143 rrdargs[argi++] = heightopt;
1144 rrdargs[argi++] = "-v";
1145 rrdargs[argi++] = gdef->yaxis;
1146 rrdargs[argi++] = "-a";
1147 rrdargs[argi++] = "PNG";
1148
1149 if (haveupperlimit) {
1150 snprintf(upperopt, sizeof(upperopt), "-u %f", upperlimit);
1151 rrdargs[argi++] = upperopt;
1152 }
1153 if (havelowerlimit) {
1154 snprintf(loweropt, sizeof(loweropt), "-l %f", lowerlimit);
1155 rrdargs[argi++] = loweropt;
1156 }
1157 if (haveupperlimit || havelowerlimit) rrdargs[argi++] = "--rigid";
1158
1159 if (graphstart) snprintf(startopt, sizeof(startopt), "-s %u", (unsigned int) graphstart);
1160 else snprintf(startopt, sizeof(startopt), "-s %s", period);
1161 rrdargs[argi++] = startopt;
1162
1163 if (graphend) {
1164 snprintf(endopt, sizeof(endopt), "-e %u", (unsigned int) graphend);
1165 rrdargs[argi++] = endopt;
1166 }
1167
1168 for (useroptidx=0; (useroptidx < useroptcount); useroptidx++) {
1169 rrdargs[argi++] = useropts[useroptidx];
1170 }
1171
1172 for (rrdidx=0; (rrdidx < rrddbcount); rrdidx++) {
1173 if ((firstidx == -1) || ((rrdidx >= firstidx) && (rrdidx <= lastidx))) {
1174 int i;
1175 for (i=0; (gdef->defs[i]); i++) {
1176 rrdargs[argi++] = strdup(expand_tokens(gdef->defs[i]));
1177 }
1178 }
1179 }
1180
1181 #ifdef RRDTOOL12
1182 strftime(timestamp, sizeof(timestamp), "COMMENT:Updated\\: %d-%b-%Y %H\\:%M\\:%S", localtime(&now));
1183 #else
1184 strftime(timestamp, sizeof(timestamp), "COMMENT:Updated: %d-%b-%Y %H:%M:%S", localtime(&now));
1185 #endif
1186 rrdargs[argi++] = strdup(timestamp);
1187
1188
1189 rrdargcount = argi; rrdargs[argi++] = NULL;
1190
1191
1192 if (debug) { for (argi=0; (argi < rrdargcount); argi++) dbgprintf("%s\n", rrdargs[argi]); }
1193
1194 /* If sending to stdout, print the HTTP header first. */
1195 if ((action == ACT_VIEW) && (strcmp(graphfn, "-") == 0)) {
1196 time_t expiretime = now + 300;
1197 char expirehdr[100];
1198
1199 printf("Content-type: image/png\n");
1200 strftime(expirehdr, sizeof(expirehdr), "Expires: %a, %d %b %Y %H:%M:%S GMT", gmtime(&expiretime));
1201 printf("%s\n", expirehdr);
1202 printf("\n");
1203
1204 #ifdef HIDE_EMPTYGRAPH
1205 /* It works, but we still get the "zoom" magnifying glass which looks odd */
1206 if (rrddbcount == 0) {
1207 /* No graph */
1208 fwrite(blankimg, 1, sizeof(blankimg), stdout);
1209 return;
1210 }
1211 #endif
1212 }
1213
1214 /* All set - generate the graph */
1215 rrd_clear_error();
1216
1217 #ifdef RRDTOOL12
1218 result = rrd_graph(rrdargcount, rrdargs, &calcpr, &xsize, &ysize, NULL, &ymin, &ymax);
1219
1220 /*
1221 * If we have neither the upper- nor lower-limits of the graph, AND we allow vertical
1222 * zooming of this graph, then save the upper/lower limit values and flag that we have
1223 * them. The values are then used for the zoom URL we construct later on.
1224 */
1225 if (!haveupperlimit && !havelowerlimit) {
1226 upperlimit = ymax; haveupperlimit = 1;
1227 lowerlimit = ymin; havelowerlimit = 1;
1228 }
1229 #else
1230 result = rrd_graph(rrdargcount, rrdargs, &calcpr, &xsize, &ysize);
1231 #endif
1232
1233 /* Was it OK ? */
1234 if (rrd_test_error() || (result != 0)) {
1235 if (calcpr) {
1236 int i;
1237 for (i=0; (calcpr[i]); i++) xfree(calcpr[i]);
1238 calcpr = NULL;
1239 }
1240
1241 errormsg(rrd_get_error());
1242 }
1243
1244 if (useroptval) xfree(useroptval);
1245 if (useropts) xfree(useropts);
1246 }
1247
generate_zoompage(char * selfURI)1248 void generate_zoompage(char *selfURI)
1249 {
1250 fprintf(stdout, "Content-type: %s\n\n", xgetenv("HTMLCONTENTTYPE"));
1251 sethostenv(displayname, "", service, colorname(bgcolor), hostname);
1252 headfoot(stdout, "graphs", "", "header", bgcolor);
1253
1254
1255 fprintf(stdout, " <div id='zoomBox' style='position:absolute; overflow:none; left:0px; top:0px; width:0px; height:0px; visibility:visible; background:red; filter:alpha(opacity=50); -moz-opacity:0.5; opacity:0.5; -khtml-opacity:0.5'></div>\n");
1256 fprintf(stdout, " <div id='zoomSensitiveZone' style='position:absolute; overflow:none; left:0px; top:0px; width:0px; height:0px; visibility:visible; cursor:crosshair; background:blue; filter:alpha(opacity=0); opacity:0; -moz-opacity:0; -khtml-opacity:0'></div>\n");
1257
1258 fprintf(stdout, "<table align=\"center\" summary=\"Graphs\">\n");
1259 graph_link(stdout, selfURI, gtype, 0);
1260 fprintf(stdout, "</table>\n");
1261
1262 {
1263 char zoomjsfn[PATH_MAX];
1264 struct stat st;
1265
1266 snprintf(zoomjsfn, sizeof(zoomjsfn), "%s/web/zoom.js", xgetenv("XYMONHOME"));
1267 if (stat(zoomjsfn, &st) == 0) {
1268 FILE *fd;
1269 char *buf;
1270 size_t n;
1271 char *zoomrightoffsetmarker = "var cZoomBoxRightOffset = -";
1272 char *zoomrightoffsetp;
1273
1274 fd = fopen(zoomjsfn, "r");
1275 if (fd) {
1276 buf = (char *)malloc(st.st_size+1);
1277 n = fread(buf, 1, st.st_size, fd);
1278 fclose(fd);
1279
1280 #ifdef RRDTOOL12
1281 zoomrightoffsetp = strstr(buf, zoomrightoffsetmarker);
1282 if (zoomrightoffsetp) {
1283 zoomrightoffsetp += strlen(zoomrightoffsetmarker);
1284 memcpy(zoomrightoffsetp, "30", 2);
1285 }
1286 #endif
1287
1288 fwrite(buf, 1, n, stdout);
1289 }
1290 }
1291 }
1292
1293
1294 headfoot(stdout, "graphs", "", "footer", bgcolor);
1295 }
1296
1297
main(int argc,char * argv[])1298 int main(int argc, char *argv[])
1299 {
1300 /* Command line settings */
1301 int argi;
1302 char *envarea = NULL;
1303 char *rrddir = NULL; /* RRD files top-level directory */
1304 char *gdeffn = NULL; /* graphs.cfg file */
1305 char *graphfn = "-"; /* Output filename, default is stdout */
1306
1307 char *selfURI;
1308
1309 /* Setup defaults */
1310 graphwidth = atoi(xgetenv("RRDWIDTH"));
1311 graphheight = atoi(xgetenv("RRDHEIGHT"));
1312
1313 /* See what we want to do - i.e. get hostname, service and graph-type */
1314 parse_query();
1315
1316 /* Handle any command-line args */
1317 for (argi=1; (argi < argc); argi++) {
1318 if (strcmp(argv[argi], "--debug") == 0) {
1319 debug = 1;
1320 }
1321 else if (argnmatch(argv[argi], "--env=")) {
1322 char *p = strchr(argv[argi], '=');
1323 loadenv(p+1, envarea);
1324 }
1325 else if (argnmatch(argv[argi], "--area=")) {
1326 char *p = strchr(argv[argi], '=');
1327 envarea = strdup(p+1);
1328 }
1329 else if (argnmatch(argv[argi], "--rrddir=")) {
1330 char *p = strchr(argv[argi], '=');
1331 rrddir = strdup(p+1);
1332 }
1333 else if (argnmatch(argv[argi], "--config=")) {
1334 char *p = strchr(argv[argi], '=');
1335 gdeffn = strdup(p+1);
1336 }
1337 else if (strcmp(argv[argi], "--save=") == 0) {
1338 char *p = strchr(argv[argi], '=');
1339 graphfn = strdup(p+1);
1340 }
1341 }
1342
1343 redirect_cgilog("showgraph");
1344
1345 selfURI = build_selfURI();
1346
1347 if (action == ACT_MENU) {
1348 build_menu_page(selfURI, graphend-graphstart);
1349 return 0;
1350 }
1351
1352 if ((action == ACT_VIEW) || !(haveupperlimit && havelowerlimit)) {
1353 generate_graph(gdeffn, rrddir, graphfn);
1354 }
1355
1356 if (action == ACT_SELZOOM) {
1357 generate_zoompage(selfURI);
1358 }
1359
1360 return 0;
1361 }
1362
1363