1 /*----------------------------------------------------------------------------*/
2 /* Xymon overview webpage generator tool. */
3 /* */
4 /* This file contains code to load the current Xymon status data. */
5 /* */
6 /* Copyright (C) 2002-2011 Henrik Storner <henrik@storner.dk> */
7 /* */
8 /* This program is released under the GNU General Public License (GPL), */
9 /* version 2. See the file "COPYING" for details. */
10 /* */
11 /*----------------------------------------------------------------------------*/
12
13 static char rcsid[] = "$Id: loaddata.c 7904 2016-02-19 19:29:58Z jccleaver $";
14
15 #include <limits.h>
16 #include <stdio.h>
17 #include <string.h>
18 #include <stdlib.h>
19 #include <ctype.h>
20 #include <sys/types.h>
21 #include <sys/stat.h>
22 #include <dirent.h>
23 #include <unistd.h>
24 #include <fcntl.h>
25 #include <errno.h>
26
27 #include "xymongen.h"
28 #include "util.h"
29 #include "loadlayout.h"
30 #include "loaddata.h"
31
32 int statuscount = 0;
33
34 char *ignorecolumns = NULL; /* Columns that will be ignored totally */
35 char *dialupskin = NULL; /* XYMONSKIN used for dialup tests */
36 char *reverseskin = NULL; /* XYMONSKIN used for reverse tests */
37 time_t recentgif_limit = 86400; /* Limit for recent-gifs display, in seconds */
38
39 xymongen_col_t null_column = { "", NULL }; /* Null column */
40
41 char *purplelogfn = NULL;
42 static FILE *purplelog = NULL;
43 int colorcount[COL_COUNT] = { 0, };
44 int colorcount_noprop[COL_COUNT] = { 0, };
45
46 static time_t oldestentry;
47
48
49 typedef struct compact_t {
50 char *compactname;
51 int color;
52 time_t fileage;
53 char *members;
54 } compact_t;
55
56
57
58 typedef struct logdata_t {
59 /* hostname|testname|color|testflags|lastchange|logtime|validtime|acktime|disabletime|sender|cookie|1st line of message */
60 char *hostname;
61 char *testname;
62 int color;
63 char *testflags;
64 time_t lastchange;
65 time_t logtime;
66 time_t validtime;
67 time_t acktime;
68 time_t disabletime;
69 char *sender;
70 int cookie;
71 char *msg;
72 } logdata_t;
73
parse_testflags(char * l)74 char *parse_testflags(char *l)
75 {
76 char *result = NULL;
77 char *flagstart = strstr(l, "[flags:");
78
79 if (flagstart) {
80 char *flagend;
81
82 flagstart += 7;
83 flagend = strchr(flagstart, ']');
84
85 if (flagend) {
86 *flagend = '\0';
87 result = strdup(flagstart);
88 *flagend = ']';
89 }
90 }
91
92 return result;
93 }
94
testflag_set(entry_t * e,char flag)95 int testflag_set(entry_t *e, char flag)
96 {
97 if (e->testflags)
98 return (strchr(e->testflags, flag) != NULL);
99 else
100 return 0;
101 }
102
103
unwantedcolumn(char * hostname,char * testname)104 int unwantedcolumn(char *hostname, char *testname)
105 {
106 void *hinfo;
107 char *nc, *tok;
108 int result = 0;
109
110 hinfo = hostinfo(hostname);
111 if (!hinfo) return 1;
112
113 nc = xmh_item(hinfo, XMH_NOCOLUMNS);
114 if (!nc) return 0;
115
116 nc = strdup(nc);
117 tok = strtok(nc, ",");
118 while (tok && (result == 0)) {
119 if (strcmp(tok, testname) == 0) result = 1;
120 tok = strtok(NULL, ",");
121 }
122
123 return result;
124 }
125
126
init_state(char * filename,logdata_t * log)127 state_t *init_state(char *filename, logdata_t *log)
128 {
129 FILE *fd = NULL;
130 char *p;
131 char *hostname = NULL;
132 char *testname = NULL;
133 char *testnameidx;
134 state_t *newstate;
135 char fullfn[PATH_MAX];
136 host_t *host;
137 struct stat log_st;
138 time_t now = getcurrenttime(NULL);
139 time_t histentry_start;
140 int propagating, isacked;
141
142 dbgprintf("init_state(%s, %d, ...)\n", textornull(filename));
143
144 /* Ignore summary files and dot-files (this catches "." and ".." also) */
145 if ( (strncmp(filename, "summary.", 8) == 0) || (filename[0] == '.')) {
146 return NULL;
147 }
148
149 if (reportstart || snapshot) {
150 /* Don't do reports for info- and trends-columns */
151 p = strrchr(filename, '.');
152 if (p == NULL) return NULL;
153 p++;
154
155 if (strcmp(p, xgetenv("INFOCOLUMN")) == 0) return NULL;
156 if (strcmp(p, xgetenv("TRENDSCOLUMN")) == 0) return NULL;
157 if (strcmp(p, xgetenv("CLIENTCOLUMN")) == 0) return NULL;
158
159 /*
160 * When doing reports, we are scanning the XYMONHISTDIR directory. It may
161 * contain files that are named as a host only (no test-name).
162 * Skip those.
163 */
164 if (find_host(filename)) return NULL;
165 }
166
167 if (!reportstart && !snapshot) {
168 if (log->hostname) hostname = strdup(log->hostname);
169 if (log->testname) testname = strdup(log->testname);
170 }
171 else {
172 sprintf(fullfn, "%s/%s", xgetenv("XYMONHISTDIR"), filename);
173
174 /* Check that we can access this file */
175 if (stat(fullfn, &log_st) == -1) {
176 errprintf("Error searching for '%s' history file: %s\n", filename, strerror(errno));
177 return NULL;
178 }
179 if (!S_ISREG(log_st.st_mode)) {
180 errprintf("Weird file '%s' skipped\n", fullfn);
181 return NULL;
182 }
183
184 if ((fd = fopen(fullfn, "r")) == NULL) {
185 errprintf("Unable to read file '%s', skipped: %s\n", fullfn, strerror(errno));
186 return NULL;
187 }
188
189 /* Pick out host- and test-name */
190 hostname = strdup(filename);
191 p = strrchr(hostname, '.');
192
193 /* Skip files that have no '.' in filename */
194 if (p) {
195 /* Pick out the testname ... */
196 *p = '\0'; p++;
197 testname = strdup(p);
198
199 /* ... and change hostname back into normal form */
200 for (p=hostname; (*p); p++) {
201 if (*p == ',') *p='.';
202 }
203 }
204 else {
205 xfree(hostname);
206 fclose(fd);
207 return NULL;
208 }
209 }
210
211 /* Must do these first to get the propagation value for the statistics */
212 host = find_host(hostname);
213 isacked = (log->acktime > now);
214 propagating = checkpropagation(host, testname, log->color, isacked);
215
216 /* Count all of the real columns */
217 if ( (strcmp(testname, xgetenv("INFOCOLUMN")) != 0) && (strcmp(testname, xgetenv("TRENDSCOLUMN")) != 0) ) {
218 statuscount++;
219 switch (log->color) {
220 case COL_RED:
221 case COL_YELLOW:
222 if (propagating) colorcount[log->color] += 1;
223 else colorcount_noprop[log->color] += 1;
224 break;
225
226 default:
227 colorcount[log->color] += 1;
228 break;
229 }
230 }
231
232 testnameidx = (char *)malloc(strlen(testname) + 3);
233 sprintf(testnameidx, ",%s,", testname);
234 if (unwantedcolumn(hostname, testname) || (ignorecolumns && strstr(ignorecolumns, testnameidx))) {
235 xfree(hostname);
236 xfree(testname);
237 xfree(testnameidx);
238 if (fd) fclose(fd);
239 return NULL; /* Ignore this type of test */
240 }
241 xfree(testnameidx);
242
243 newstate = (state_t *) calloc(1, sizeof(state_t));
244 newstate->entry = (entry_t *) calloc(1, sizeof(entry_t));
245 newstate->next = NULL;
246
247 newstate->entry->column = find_or_create_column(testname, 1);
248 newstate->entry->color = -1;
249 strcpy(newstate->entry->age, "");
250 newstate->entry->oldage = 0;
251 newstate->entry->propagate = 1;
252 newstate->entry->testflags = NULL;
253 newstate->entry->skin = NULL;
254 newstate->entry->repinfo = NULL;
255 newstate->entry->causes = NULL;
256 newstate->entry->histlogname = NULL;
257 newstate->entry->shorttext = NULL;
258
259 if (host) {
260 newstate->entry->alert = checkalert(host->alerts, testname);
261
262 /* If no WAP's specified, default all tests to be on WAP page */
263 newstate->entry->onwap = (host->waps ? checkalert(host->waps, testname) : 1);
264 }
265 else {
266 dbgprintf(" hostname %s not found\n", hostname);
267 newstate->entry->alert = newstate->entry->onwap = 0;
268 }
269
270 newstate->entry->sumurl = NULL;
271
272 if (reportstart) {
273 /* Determine "color" for this test from the historical data */
274 newstate->entry->repinfo = (reportinfo_t *) calloc(1, sizeof(reportinfo_t));
275 newstate->entry->color = parse_historyfile(fd, newstate->entry->repinfo,
276 (dynamicreport ? NULL: hostname), (dynamicreport ? NULL : testname),
277 reportstart, reportend, 0,
278 (host ? host->reportwarnlevel : reportwarnlevel),
279 reportgreenlevel,
280 (host ? host->reportwarnstops : reportwarnstops),
281 (host ? host->reporttime : NULL));
282 newstate->entry->causes = (dynamicreport ? NULL : save_replogs());
283 }
284 else if (snapshot) {
285 time_t fileage;
286
287 newstate->entry->color = history_color(fd, snapshot, &histentry_start, &newstate->entry->histlogname);
288 fileage = snapshot - histentry_start;
289
290 newstate->entry->oldage = (fileage >= recentgif_limit);
291 newstate->entry->fileage = fileage;
292 strcpy(newstate->entry->age, agestring(fileage));
293 }
294 else {
295 time_t fileage = (now - log->lastchange);
296
297 newstate->entry->color = log->color;
298 newstate->entry->testflags = strdup(log->testflags ? log->testflags : "");
299 if (testflag_set(newstate->entry, 'D')) newstate->entry->skin = dialupskin;
300 if (testflag_set(newstate->entry, 'R')) newstate->entry->skin = reverseskin;
301 newstate->entry->shorttext = strdup(log->msg);
302 newstate->entry->acked = isacked;
303
304 newstate->entry->oldage = (fileage >= recentgif_limit);
305 newstate->entry->fileage = (log->lastchange ? fileage : -1);
306 if (log->lastchange == 0)
307 strcpy(newstate->entry->age, "");
308 else
309 strcpy(newstate->entry->age, agestring(fileage));
310 }
311
312 if (purplelog && (newstate->entry->color == COL_PURPLE)) {
313 fprintf(purplelog, "%s %s%s\n",
314 hostname, testname, (host ? " (expired)" : " (unknown host)"));
315 }
316
317 newstate->entry->propagate = propagating;
318
319 dbgprintf("init_state: hostname=%s, testname=%s, color=%d, acked=%d, age=%s, oldage=%d, propagate=%d, alert=%d\n",
320 textornull(hostname), textornull(testname),
321 newstate->entry->color, newstate->entry->acked,
322 textornull(newstate->entry->age), newstate->entry->oldage,
323 newstate->entry->propagate, newstate->entry->alert);
324
325 if (host) {
326 hostlist_t *l;
327
328 /* Add this state entry to the host's list of state entries. */
329 newstate->entry->next = host->entries;
330 host->entries = newstate->entry;
331
332 /* There may be multiple host entries, if a host is
333 * listed in several locations in hosts.cfg (for display purposes).
334 * This is handled by updating ALL of the cloned host records.
335 * Bug reported by Bluejay Adametz of Fuji.
336 */
337
338 /* Cannot use "find_host()" here, as we need the hostlink record, not the host record */
339 l = find_hostlist(hostname);
340
341 /* Walk through the clone-list and set the "entries" for all hosts */
342 for (l=l->clones; (l); l = l->clones) l->hostentry->entries = host->entries;
343 }
344 else {
345 /* No host for this test - must be missing from hosts.cfg */
346 newstate->entry->next = NULL;
347 }
348
349 xfree(hostname);
350 xfree(testname);
351 if (fd) fclose(fd);
352
353 return newstate;
354 }
355
init_displaysummary(char * fn,logdata_t * log)356 dispsummary_t *init_displaysummary(char *fn, logdata_t *log)
357 {
358 char l[MAX_LINE_LEN];
359 dispsummary_t *newsum = NULL;
360 time_t now = getcurrenttime(NULL);
361
362 dbgprintf("init_displaysummary(%s)\n", textornull(fn));
363
364 if (log->validtime < now) return NULL;
365 strcpy(l, log->msg);
366
367 if (strlen(l)) {
368 char *p;
369 char *color = (char *) malloc(strlen(l));
370
371 newsum = (dispsummary_t *) calloc(1, sizeof(dispsummary_t));
372 newsum->url = (char *) malloc(strlen(l));
373
374 if (sscanf(l, "%s %s", color, newsum->url) == 2) {
375 char *rowcol;
376 newsum->color = parse_color(color);
377
378 rowcol = (char *) malloc(strlen(fn) + 1);
379 strcpy(rowcol, fn+8);
380 p = strrchr(rowcol, '.');
381 if (p) *p = ' ';
382
383 newsum->column = (char *) malloc(strlen(rowcol)+1);
384 newsum->row = (char *) malloc(strlen(rowcol)+1);
385 sscanf(rowcol, "%s %s", newsum->row, newsum->column);
386 newsum->next = NULL;
387
388 xfree(rowcol);
389 }
390 else {
391 xfree(newsum->url);
392 xfree(newsum);
393 newsum = NULL;
394 }
395
396 xfree(color);
397 }
398
399 return newsum;
400 }
401
402
generate_compactitems(state_t ** topstate)403 void generate_compactitems(state_t **topstate)
404 {
405 void *xmh;
406 compact_t **complist = NULL;
407 int complistsz = 0;
408 hostlist_t *h;
409 entry_t *e;
410 char *compacted;
411 char *tok1, *savep1, *savep2;
412 compact_t *itm;
413 int i;
414 state_t *newstate;
415 time_t now = getcurrenttime(NULL);
416
417 for (h = hostlistBegin(); (h); h = hostlistNext()) {
418 xmh = hostinfo(h->hostentry->hostname);
419 compacted = xmh_item(xmh, XMH_COMPACT);
420 if (!compacted) continue;
421
422 tok1 = strtok_r(compacted, ",", &savep1);
423 while (tok1) {
424 char *members;
425
426 itm = (compact_t *)calloc(1, sizeof(compact_t));
427 itm->compactname = strdup(strtok_r(tok1, "=", &savep2));
428 members = strtok_r(NULL, "\n", &savep2);
429 itm->members = (char *)malloc(3 + (members ? strlen(members) : 0) );
430 sprintf(itm->members, "|%s|", (members ? members : "") );
431
432 if (complistsz == 0) {
433 complist = (compact_t **)calloc(2, sizeof(compact_t *));
434 }
435 else {
436 complist = (compact_t **)realloc(complist, (complistsz+2)*sizeof(compact_t *));
437 }
438
439 complist[complistsz++] = itm;
440 complist[complistsz] = NULL;
441
442 tok1 = strtok_r(NULL, ",", &savep1);
443 }
444
445 for (e = h->hostentry->entries; (e); e = e->next) {
446 for (i = 0; (i < complistsz); i++) {
447 if (wantedcolumn(e->column->name, complist[i]->members)) {
448 e->compacted = 1;
449 if (e->color > complist[i]->color) complist[i]->color = e->color;
450 if (e->fileage > complist[i]->fileage) complist[i]->fileage = e->fileage;
451 }
452 }
453 }
454
455 for (i = 0; (i < complistsz); i++) {
456 logdata_t log;
457 char fn[PATH_MAX];
458
459 memset(&log, 0, sizeof(log));
460 sprintf(fn, "%s.%s", commafy(h->hostentry->hostname), complist[i]->compactname);
461 log.hostname = h->hostentry->hostname;
462 log.testname = complist[i]->compactname;
463 log.color = complist[i]->color;
464 log.testflags = "";
465 log.lastchange = now - complist[i]->fileage;
466 log.logtime = getcurrenttime(NULL);
467 log.validtime = log.logtime + 300;
468 log.sender = "";
469 log.msg = "";
470 newstate = init_state(fn, &log);
471 if (newstate) {
472 newstate->next = *topstate;
473 *topstate = newstate;
474 }
475 }
476 }
477 }
478
479
load_state(dispsummary_t ** sumhead)480 state_t *load_state(dispsummary_t **sumhead)
481 {
482 int xymondresult;
483 char fn[PATH_MAX];
484 state_t *newstate, *topstate;
485 dispsummary_t *newsum, *topsum;
486 char *board = NULL;
487 char *nextline;
488 int done;
489 logdata_t log;
490 sendreturn_t *sres;
491
492 dbgprintf("load_state()\n");
493
494 sres = newsendreturnbuf(1, NULL);
495
496 if (!reportstart && !snapshot) {
497 char *dumpfn = getenv("BOARDDUMP");
498 char *filter = getenv("BOARDFILTER");
499
500 if (dumpfn) {
501 /* Debugging - read data from a dump file */
502 struct stat st;
503 FILE *fd;
504
505 xymondresult = XYMONSEND_ETIMEOUT;
506 if (stat(dumpfn, &st) == 0) {
507 fd = fopen(dumpfn, "r");
508 if (fd) {
509 board = (char *)malloc(st.st_size + 1); *board = '\0';
510 if (fread(board, 1, st.st_size, fd)) {
511 fclose(fd);
512 xymondresult = XYMONSEND_OK;
513 }
514 }
515 }
516 }
517 else {
518 char *bcmd;
519
520 bcmd = (char *)malloc(1024 + (filter ? strlen(filter) : 0));
521 sprintf(bcmd, "xymondboard fields=hostname,testname,color,flags,lastchange,logtime,validtime,acktime,disabletime,sender,cookie,line1,acklist %s", (filter ? filter: ""));
522 xymondresult = sendmessage(bcmd, NULL, XYMON_TIMEOUT, sres);
523 board = getsendreturnstr(sres, 1);
524 xfree(bcmd);
525 }
526 }
527 else {
528 xymondresult = sendmessage("xymondboard fields=hostname,testname", NULL, XYMON_TIMEOUT, sres);
529 board = getsendreturnstr(sres, 1);
530 }
531
532 freesendreturnbuf(sres);
533
534 if ((xymondresult != XYMONSEND_OK) || (board == NULL) || (*board == '\0')) {
535 errprintf("xymond status-board not available, code %d\n", xymondresult);
536 return NULL;
537 }
538
539 if (reportstart || snapshot) {
540 oldestentry = getcurrenttime(NULL);
541 purplelog = NULL;
542 purplelogfn = NULL;
543 }
544 else {
545 if (purplelogfn) {
546 purplelog = fopen(purplelogfn, "w");
547 if (purplelog == NULL) errprintf("Cannot open purplelog file %s\n", purplelogfn);
548 else fprintf(purplelog, "Stale (purple) logfiles as of %s\n\n", timestamp);
549 }
550 }
551
552 topstate = NULL;
553 topsum = NULL;
554
555 done = 0; nextline = board;
556 while (!done) {
557 char *bol = nextline;
558 char *onelog, *acklist;
559 char *p;
560 int i;
561
562 nextline = strchr(nextline, '\n');
563 if (nextline) { *nextline = '\0'; nextline++; } else done = 1;
564
565 if (strlen(bol) == 0) {
566 done = 1;
567 continue;
568 }
569
570 memset(&log, 0, sizeof(log));
571 onelog = strdup(bol);
572 acklist = NULL;
573 p = gettok(onelog, "|"); i = 0;
574 while (p) {
575 switch (i) {
576 /* hostname|testname|color|testflags|lastchange|logtime|validtime|acktime|disabletime|sender|cookie|1st line of message|acklist */
577 case 0: log.hostname = p; break;
578 case 1: log.testname = p; break;
579 case 2: log.color = parse_color(p); break;
580 case 3: log.testflags = p; break;
581 case 4: log.lastchange = atoi(p); break;
582 case 5: log.logtime = atoi(p); break;
583 case 6: log.validtime = atoi(p); break;
584 case 7: log.acktime = atoi(p); break;
585 case 8: log.disabletime = atoi(p); break;
586 case 9: log.sender = p; break;
587 case 10: log.cookie = atoi(p); break;
588 case 11: log.msg = p; break;
589 case 12: acklist = p; break;
590 }
591
592 p = gettok(NULL, "|");
593 i++;
594 }
595 if (!log.hostname || !log.testname) {
596 errprintf("Found incomplete or corrupt log line (%s|%s); skipping\n", textornull(log.hostname), textornull(log.testname) );
597 xfree(onelog);
598 continue;
599 }
600 if (!log.msg) log.msg = "";
601 sprintf(fn, "%s.%s", commafy(log.hostname), log.testname);
602
603 /* Get the data */
604 if (strncmp(fn, "summary.", 8) == 0) {
605 if (!reportstart && !snapshot) {
606 newsum = init_displaysummary(fn, &log);
607 if (newsum) {
608 newsum->next = topsum;
609 topsum = newsum;
610 }
611 }
612 }
613 else {
614 if (acklist && *acklist) {
615 /*
616 * It's been acked. acklist looks like
617 * 1149489234:1149510834:1:henrik:Joe promised to take care of this right after lunch\n
618 * The "\n" is the delimiter between multiple acks.
619 */
620 char *tok;
621
622 tok = strtok(acklist, ":");
623 if (tok) tok = strtok(NULL, ":");
624 if (tok) log.acktime = atol(tok);
625 }
626 newstate = init_state(fn, &log);
627 if (newstate) {
628 newstate->next = topstate;
629 topstate = newstate;
630 if (reportstart && (newstate->entry->repinfo->reportstart < oldestentry)) {
631 oldestentry = newstate->entry->repinfo->reportstart;
632 }
633 }
634 }
635 xfree(onelog);
636 }
637
638 generate_compactitems(&topstate);
639
640 if (reportstart) sethostenv_report(oldestentry, reportend, reportwarnlevel, reportgreenlevel);
641 if (purplelog) fclose(purplelog);
642
643 *sumhead = topsum;
644 return topstate;
645 }
646
647