db_updater: Put parentheses back
[merlin.git] / logutils.c
blob02403efb87826bb7a17aca676b17dcbda269a7af
1 #include "shared.h"
2 #include "logutils.h"
4 /* stubs required for linking */
5 int ipc_grok_var(char *var, char *val) { return 0; }
7 /* global variables used in all log-handling apps */
8 char **strv = NULL;
9 int num_nfile = 0;
10 static int nfile_alloc = 0;
11 struct naglog_file *nfile;
12 int debug_level = 0;
13 struct naglog_file *cur_file; /* the file we're currently importing */
14 uint line_no = 0;
15 uint num_unhandled = 0;
16 uint warnings = 0;
17 static dkhash_table *interesting_hosts, *interesting_services;
19 #define host_code(S) { 0, #S, sizeof(#S) - 1, HOST_##S }
20 static struct string_code host_state[] = {
21 host_code(UP),
22 host_code(DOWN),
23 host_code(UNREACHABLE),
26 #define service_code(S) { 0, #S, sizeof(#S) - 1, SERVICE_##S }
27 static struct string_code service_state[] = {
28 service_code(OK),
29 service_code(WARNING),
30 service_code(CRITICAL),
31 service_code(UNKNOWN),
34 #define notification_code(S) { 0, #S, sizeof(#S) - 1, NOTIFICATION_##S }
35 static struct string_code notification_reason[] = {
36 notification_code(ACKNOWLEDGEMENT),
37 notification_code(FLAPPINGSTART),
38 notification_code(DOWNTIMESTART),
39 notification_code(FLAPPINGSTOP),
40 notification_code(FLAPPINGDISABLED),
41 notification_code(DOWNTIMESTART),
42 notification_code(DOWNTIMEEND),
43 notification_code(DOWNTIMECANCELLED),
44 notification_code(CUSTOM),
47 /*** general utility functions ***/
48 void __attribute__((__noreturn__)) lp_crash(const char *fmt, ...)
50 va_list ap;
52 va_start(ap, fmt);
53 vfprintf(stderr, fmt, ap);
54 va_end(ap);
55 fputc('\n', stderr);
57 if (cur_file) {
58 fprintf(stderr, "crash() called when parsing line %u in %s\n",
59 line_no, cur_file->path);
62 exit(1);
65 void pdebug(int lvl, const char *fmt, ...)
67 va_list ap;
69 if (debug_level < lvl)
70 return;
72 va_start(ap, fmt);
73 vprintf(fmt, ap);
74 va_end(ap);
75 if (fmt[strlen(fmt) - 1] != '\n')
76 putchar('\n');
79 void warn(const char *fmt, ...)
81 va_list ap;
83 warnings++;
85 if (!debug_level)
86 return;
88 printf("WARNING: ");
89 va_start(ap, fmt);
90 vprintf(fmt, ap);
91 va_end(ap);
92 putchar('\n');
96 int parse_service_state_gently(const char *str)
98 uint i;
100 for (i = 0; i < ARRAY_SIZE(service_state); i++) {
101 if (!strcmp(str, service_state[i].str))
102 return service_state[i].code;
105 return -1;
108 int parse_service_state(const char *str)
110 int ret = parse_service_state_gently(str);
112 if (ret < 0)
113 lp_crash("bad value for service state: '%s'", str);
115 return ret;
118 int parse_host_state_gently(const char *str)
120 uint i;
122 for (i = 0; i < ARRAY_SIZE(host_state); i++) {
123 if (!strcmp(str, host_state[i].str))
124 return host_state[i].code;
127 return -1;
130 int parse_host_state(const char *str)
132 int ret = parse_host_state_gently(str);
134 if (ret < 0)
135 lp_crash("bad value for host state: '%s'", str);
137 return ret;
140 int parse_notification_reason(const char *str)
142 uint i;
144 for (i = 0; i < ARRAY_SIZE(notification_reason); i++) {
145 if (!prefixcmp(str, notification_reason[i].str))
146 return notification_reason[i].code;
149 return NOTIFICATION_NORMAL;
152 int soft_hard(const char *str)
154 if (!strcmp(str, "HARD"))
155 return HARD_STATE;
157 if (!strcmp(str, "SOFT"))
158 return SOFT_STATE;
159 lp_crash("wtf kind of value is '%s' to determine 'soft' or 'hard' from?", str);
162 static int print_string(void *data)
164 const char *str = data;
166 printf("%p: %s\n", str, str);
167 return 0;
171 * prints the objects we consider interesting
173 void print_interesting_objects(void)
175 if (interesting_hosts) {
176 printf("\nInteresting hosts:\n");
177 dkhash_walk_data(interesting_hosts, print_string);
179 if (interesting_services) {
180 printf("\nInteresting services:\n");
181 dkhash_walk_data(interesting_services, print_string);
183 if (interesting_hosts || interesting_services)
184 putchar('\n');
187 /* marks one object as 'interesting' */
188 int add_interesting_object(const char *orig_str)
190 char *semi_colon, *str;
192 str = strdup(orig_str);
193 semi_colon = strchr(str, ';');
194 if (!semi_colon) {
195 if (!interesting_hosts)
196 interesting_hosts = dkhash_create(512);
197 if (!interesting_hosts)
198 crash("Failed to initialize hash table for interesting hosts");
199 dkhash_insert(interesting_hosts, str, NULL, strdup(orig_str));
200 } else {
201 if (!interesting_services)
202 interesting_services = dkhash_create(512);
203 if (!interesting_services)
204 lp_crash("Failed to initialize hash table for interesting services");
205 *semi_colon++ = 0;
206 dkhash_insert(interesting_services, str, semi_colon, strdup(orig_str));
209 return 0;
212 int is_interesting_host(const char *host)
214 if (interesting_hosts)
215 return !!dkhash_get(interesting_hosts, host, NULL);
217 return 1;
220 int is_interesting_service(const char *host, const char *service)
222 /* fall back to checking if host is interesting */
223 if (!service || !interesting_services)
224 return is_interesting_host(host);
226 return !!dkhash_get(interesting_services, host, service);
229 struct unhandled_event {
230 char *file;
231 char *line;
232 unsigned line_no;
233 unsigned long repeated;
234 struct unhandled_event *next;
237 static struct unhandled_event *event_list;
239 * This is a fairly toothless function, since we can encounter
240 * pretty much any kind of message in the logfiles. In order to
241 * make sure we don't miss anything important though, we stash
242 * the messages and print them at the end if we're debugging.
244 void handle_unknown_event(const char *line)
246 struct unhandled_event *event;
247 static struct unhandled_event *last = NULL;
249 num_unhandled++;
251 if (last && !strncmp(&line[14], &last->line[14], 20)) {
252 last->repeated++;
253 return;
256 /* add to top of list. we'll print in reverse order */
257 if (last) {
258 /* add to "top" of list. we'll print in reverse order */
259 last->next = event_list;
260 event_list = last;
261 last = NULL;
264 if (!(event = calloc(1, sizeof(*event))) || !(event->line = strdup(line))) {
265 lp_crash("Failed to allocate memory for unhandled event [%s]\n", line);
266 return;
269 event->line_no = line_no;
270 event->file = cur_file->path;
271 last = event;
274 void print_unhandled_events(void)
276 struct unhandled_event *event;
277 uint x = 1;
279 if (!num_unhandled)
280 return;
283 * add the fake closing event so we get the last
284 * real event added to the list. The fake one won't
285 * get added though, so we needn't bother with it.
287 handle_unknown_event("Fake unhandled event");
290 * fake message bumps counter, so we decrease it here
291 * to get an accurate count
293 num_unhandled--;
295 printf("\n%u Unhandled events encountered:\n" \
296 "------------------------------", num_unhandled);
298 for (x = 1; num_unhandled > (x * 10); x *= 10)
299 putchar('-');
301 putchar('\n');
302 for (event = event_list; event; event = event->next) {
303 printf("%s:%d:\n%s\n", event->file, event->line_no, event->line);
304 if (event->repeated) {
305 printf(" #### Similar events repeated %lu times\n", event->repeated);
307 puts("----");
311 int vectorize_string(char *str, int nvecs)
313 char *p;
314 int i = 0;
316 strv[i++] = str;
317 for (p = str; *p && i < nvecs; p++) {
318 if (*p == ';') {
319 *p = 0;
320 strv[i++] = p+1;
324 return i;
328 * This takes care of lines that have been field-separated at
329 * semi-colons and passes it to the function above.
331 char *devectorize_string(char **ary, int nvecs)
333 int i;
335 for (i = 1; i < nvecs; i++) {
336 /* the char before the first char in the string
337 * in our array is the one where we replaced a
338 * semi-colon with a nul char, so the math here
339 * is actually correct.
341 ary[i][-1] = ';';
344 return *ary;
347 struct string_code *
348 get_string_code(struct string_code *codes, const char *str, uint len)
350 int i;
352 for (i = 0; codes[i].str; i++)
353 if (codes[i].len == len && !memcmp(str, codes[i].str, len))
354 return &codes[i];
356 return NULL;
359 int is_interesting(const char *ptr)
361 if (!prefixcmp(ptr, "Auto-save of retention data"))
362 return 0;
363 if (!prefixcmp(ptr, "Event broker module"))
364 return 0;
365 if (!prefixcmp(ptr, "You do not have permission"))
366 return 0;
367 if (!prefixcmp(ptr, "Local time is"))
368 return 0;
370 return 1;
373 int is_start_event(const char *ptr)
375 if (!prefixcmp(ptr, "Finished daemonizing..."))
376 return 1;
377 if (!prefixcmp(ptr, "Caught SIGHUP"))
378 return 1;
379 if (strstr(ptr, "starting..."))
380 return 1;
382 return 0;
385 int is_stop_event(const char *ptr)
387 if (!prefixcmp(ptr, "PROGRAM_RESTART"))
388 return 1;
389 if (!prefixcmp(ptr, "Caught SIGTERM"))
390 return 1;
391 if (!prefixcmp(ptr, "Successfully shutdown..."))
392 return 1;
393 if (!prefixcmp(ptr, "Bailing out"))
394 return 1;
395 if (!prefixcmp(ptr, "Lockfile"))
396 return 1;
397 if (strstr(ptr, "shutting down..."))
398 return 1;
400 return 0;
403 int strtotimet(const char *str, time_t *val)
405 char *endp;
407 *val = strtoul(str, &endp, 10);
408 if (endp == str) {
409 warn("strtotimet(): %s is not a valid time_t\n", str);
410 return -1;
413 return 0;
417 * Returns an increasing numeric value for a nagios logfile
418 * For a file with a name such as:
419 * nagios-12-01-2002-00.log
420 * it will return
421 * 2002120100
423 #define NUM_PARTS 4
424 uint path_cmp_number(char *path)
426 uint ret, len;
427 char *dash = NULL;
428 int i;
429 unsigned long part[NUM_PARTS];
431 dash = strrchr(path, '/');
432 if (!dash)
433 dash = path;
434 else
435 dash++;
438 * we special-case nagios.log as always being the
439 * last file to be parsed. It has to be, since it's
440 * the currently active logfile
442 if (!strcmp(dash, "nagios.log"))
443 return 1 << ((8 * sizeof(ret)) - 1);
445 len = strlen(dash);
446 if (len < 18 || strcmp(&dash[len - 4], ".log"))
447 return 0;
449 for (i = 0; i < NUM_PARTS; i++) {
450 char *endp;
452 dash = strchr(dash, '-');
453 if (!dash)
454 return 0;
456 dash++;
457 part[i] = strtoul(dash, &endp, 10);
458 if (!part[i] && dash == endp)
459 return 0;
460 if (!endp)
461 return 0;
462 dash = endp;
464 if (part[0] < 1 || part[0] > 12)
465 return 0;
466 if (part[1] < 1 || part[1] > 31)
467 return 0;
468 if (!part[2])
469 return 0;
470 ret = part[2] * 1000000;
471 ret += part[0] * 10000;
472 ret += part[1] * 100;
473 ret += part[3];
475 return ret;
478 void first_log_time(struct naglog_file *nf)
480 int fd;
481 uint i = 0;
482 char buf[1024];
483 struct stat st;
485 if (!(fd = open(nf->path, O_RDONLY)))
486 lp_crash("Failed to open %s: %s", nf->path, strerror(errno));
489 * since we're looking at every file in here anyway,
490 * we also determine the size of them so we can do an
491 * arena allocation large enough to fit the largest
492 * file + an added newline later
494 if (fstat(fd, &st) < 0)
495 lp_crash("Failed to stat %s: %s", nf->path, strerror(errno));
497 nf->size = st.st_size;
499 if (read(fd, buf, sizeof(buf)) < min((int)sizeof(buf), st.st_size))
500 lp_crash("Incomplete read of %s", nf->path);
502 buf[sizeof(buf) - 1] = 0;
503 /* skip empty lines at top of file */
504 while (i < sizeof(buf) - 12 && (buf[i] == '\n' || buf[i] == '\r'))
505 i++;
507 if (strtotimet(buf + i + 1, &nf->first))
508 lp_crash("'%s' has no timestamp for us to parse", buf);
510 nf->cmp = path_cmp_number(nf->path);
511 close(fd);
514 static void filesort_mismatch(const struct naglog_file *a, const struct naglog_file *b)
516 printf("filesort mismatch:\n");
517 printf(" %s:\n cmp: %d\n first: %lu\n", a->path, a->cmp, a->first);
518 printf(" %s:\n cmp: %d\n first: %lu\n", b->path, b->cmp, b->first);
519 lp_crash("%s and %s have same 'first' and 'cmp'? Bizarre...", a->path, b->path);
523 * sort function for nagios logfiles. Sorts based on
524 * first logged timestamp and then on filename, ascendingly
526 int nfile_cmp(const void *p1, const void *p2)
528 const struct naglog_file *a = p1;
529 const struct naglog_file *b = p2;
531 if (a->first > b->first)
532 return 1;
533 if (b->first > a->first)
534 return -1;
536 if (a->cmp > b->cmp)
537 return 1;
538 if (b->cmp > a->cmp)
539 return -1;
541 filesort_mismatch(a, b);
542 return 0;
545 /* same as above, but sorts in reverse order */
546 int nfile_rev_cmp(const void *p1, const void *p2)
548 const struct naglog_file *a = p1;
549 const struct naglog_file *b = p2;
551 if (a->first < b->first)
552 return 1;
553 if (b->first < a->first)
554 return -1;
556 if (a->cmp < b->cmp)
557 return 1;
558 if (b->cmp < a->cmp)
559 return -1;
561 filesort_mismatch(a, b);
562 return 0;
566 #ifndef PATH_MAX
567 # define PATH_MAX 4096
568 #endif
569 /* recurse into a log-archive path and find all logfiles there */
570 static int add_naglog_directory(const char *dir)
572 char path[PATH_MAX];
573 DIR *dirp;
574 struct dirent *de;
575 uint dlen = strlen(dir);
577 dirp = opendir(dir);
578 if (!dirp)
579 crash("Failed to opendir(%s): %s\n", dir, strerror(errno));
581 memcpy(path, dir, dlen);
582 path[dlen++] = '/';
583 while ((de = readdir(dirp))) {
584 unsigned int name_len;
585 path[dlen] = 0;
586 if (prefixcmp(de->d_name, "nagios"))
587 continue;
588 name_len = strlen(de->d_name);
589 if (strcmp(&de->d_name[name_len - 4], ".log"))
590 continue;
592 /* build some sort of path to the file */
593 memcpy(&path[dlen], de->d_name, name_len);
594 path[dlen + name_len] = 0;
595 add_naglog_path(path);
597 closedir(dirp);
598 return 0;
601 /* Handles both files and directories */
602 int add_naglog_path(char *path)
604 struct stat st;
605 int i;
607 /* make sure we never add duplicate files */
608 for (i = 0; i < num_nfile; i++) {
609 if (!strcmp(nfile[i].path, path))
610 return -1;
613 if (stat(path, &st) < 0) {
614 lp_crash("Failed to stat '%s': %s", path, strerror(errno));
616 if (S_ISDIR(st.st_mode)) {
617 return add_naglog_directory(path);
620 if (num_nfile >= nfile_alloc - 1) {
621 nfile_alloc += 20;
622 nfile = realloc(nfile, nfile_alloc * sizeof(*nfile));
625 nfile[num_nfile].path = strdup(path);
626 first_log_time(&nfile[num_nfile]);
627 num_nfile++;
629 return 0;