root/src/modules/external.c

Revision 84fddd139d4c8f36cc7a25cac7a848e2e9a8e342, 18.6 kB (checked in by Theo Schlossnagle <jesus@omniti.com>, 6 years ago)

basic extraction, refs #103... might need to be more sophisticated

  • Property mode set to 100644
Line 
1 /*
2  * Copyright (c) 2007, OmniTI Computer Consulting, Inc.
3  * All rights reserved.
4  */
5
6 #include "noit_defines.h"
7
8 #include <stdio.h>
9 #include <unistd.h>
10 #include <netdb.h>
11 #include <errno.h>
12 #include <fcntl.h>
13 #include <assert.h>
14 #include <sys/ioctl.h>
15 #include <sys/uio.h>
16 #ifdef HAVE_SYS_FILIO_H
17 #include <sys/filio.h>
18 #endif
19 #ifdef HAVE_NETINET_IN_SYSTM_H
20 #include <netinet/in_systm.h>
21 #endif
22 #include <pcre.h>
23
24 #include "noit_module.h"
25 #include "noit_check.h"
26 #include "noit_check_tools.h"
27 #include "utils/noit_log.h"
28 #include "utils/noit_security.h"
29 #include "external_proc.h"
30
31 struct check_info {
32   int64_t check_no;
33   u_int16_t argcnt;
34   u_int16_t *arglens;
35   char **args;
36   u_int16_t envcnt;
37   u_int16_t *envlens;
38   char **envs;
39   noit_check_t *check;
40   int exit_code;
41
42   int timedout;
43   char *output;
44   char *error;
45   pcre *matcher;
46   eventer_t timeout_event;
47 };
48
49 typedef struct external_closure {
50   noit_module_t *self;
51   noit_check_t *check;
52 } external_closure_t;
53
54 /* Protocol:
55  *   noit 2 ext:
56  *     int64(check_no)
57  *     uint16(0) -> cancel .end
58
59  *     int64(check_no)
60  *     uint16(argcnt) [argcnt > 0]
61  *     uint16(arglen) x argcnt  (arglen includes \0)
62  *     string of sum(arglen)
63  *     uint16(envcnt)
64  *     uint16(envlen) x envcnt  (envlen includes \0)
65  *     string of sum(envlen) -> execve .end
66  *
67  *   ext 2 noit:
68  *     int64(check_no)
69  *     int32(exitcode) [0 -> good, {1,2} -> bad, 3 -> unknown]
70  *     uint16(outlen) (includes \0)
71  *     string of outlen
72  *     uint16(errlen) (includes \0)
73  *     string of errlen -> complete .end
74  */
75
76 static int external_config(noit_module_t *self, noit_hash_table *options) {
77   external_data_t *data;
78   data = noit_module_get_userdata(self);
79   if(data) {
80     if(data->options) {
81       noit_hash_destroy(data->options, free, free);
82       free(data->options);
83     }
84   }
85   else
86     data = calloc(1, sizeof(*data));
87   data->options = options;
88   if(!data->options) data->options = calloc(1, sizeof(*data->options));
89   noit_module_set_userdata(self, data);
90   return 1;
91 }
92
93 static void external_log_results(noit_module_t *self, noit_check_t *check) {
94   external_data_t *data;
95   struct check_info *ci;
96   stats_t current;
97   struct timeval duration;
98
99   noit_check_stats_clear(&current);
100
101   data = noit_module_get_userdata(self);
102   ci = (struct check_info *)check->closure;
103
104   noitL(data->nldeb, "external(%s) (timeout: %d, exit: %x)\n",
105         check->target, ci->timedout, ci->exit_code);
106
107   gettimeofday(&current.whence, NULL);
108   sub_timeval(current.whence, check->last_fire_time, &duration);
109   current.duration = duration.tv_sec * 1000 + duration.tv_usec / 1000;
110   if(ci->timedout) {
111     current.available = NP_UNAVAILABLE;
112     current.state = NP_BAD;
113   }
114   else if(WEXITSTATUS(ci->exit_code) == 3) {
115     current.available = NP_UNKNOWN;
116     current.state = NP_UNKNOWN;
117   }
118   else {
119     current.available = NP_AVAILABLE;
120     current.state = (WEXITSTATUS(ci->exit_code) == 0) ? NP_GOOD : NP_BAD;
121   }
122
123   /* Hack the output into metrics */
124   if(ci->output && ci->matcher) {
125     int rc, len, startoffset = 0;
126     int ovector[30];
127     len = strlen(ci->output);
128     noitL(data->nldeb, "going to match output at %d/%d\n", startoffset, len);
129     while((rc = pcre_exec(ci->matcher, NULL, ci->output, len, startoffset, 0,
130                           ovector, sizeof(ovector)/sizeof(*ovector))) > 0) {
131       char metric[128];
132       char value[128];
133       startoffset = ovector[1];
134       noitL(data->nldeb, "matched at offset %d\n", rc);
135       if(pcre_copy_named_substring(ci->matcher, ci->output, ovector, rc,
136                                    "key", metric, sizeof(metric)) > 0 &&
137          pcre_copy_named_substring(ci->matcher, ci->output, ovector, rc,
138                                    "value", value, sizeof(value)) > 0) {
139         /* We're able to extract something... */
140         noit_stats_set_metric(&current, metric, METRIC_GUESS, value);
141       }
142       noitL(data->nldeb, "going to match output at %d/%d\n", startoffset, len);
143     }
144     noitL(data->nldeb, "match failed.... %d\n", rc);
145   }
146
147   current.status = ci->output;
148   noit_check_set_stats(self, check, &current);
149
150   /* If we didn't exit normally, or we core, or we have stderr to report...
151    * provide a full report.
152    */
153   if((WTERMSIG(ci->exit_code) != SIGQUIT && WTERMSIG(ci->exit_code) != 0) ||
154      WCOREDUMP(ci->exit_code) ||
155      (ci->error && *ci->error)) {
156     char uuid_str[37];
157     uuid_unparse_lower(check->checkid, uuid_str);
158     noitL(data->nlerr, "external/%s: (sig:%d%s) [%s]\n", uuid_str,
159           WTERMSIG(ci->exit_code), WCOREDUMP(ci->exit_code)?", cored":"",
160           ci->error ? ci->error : "");
161   }
162 }
163 static int external_timeout(eventer_t e, int mask,
164                             void *closure, struct timeval *now) {
165   external_closure_t *ecl = (external_closure_t *)closure;
166   struct check_info *data;
167   if(!NOIT_CHECK_KILLED(ecl->check) && !NOIT_CHECK_DISABLED(ecl->check)) {
168     data = (struct check_info *)ecl->check->closure;
169     data->timedout = 1;
170     data->exit_code = 3;
171     external_log_results(ecl->self, ecl->check);
172     data->timeout_event = NULL;
173   }
174   ecl->check->flags &= ~NP_RUNNING;
175   free(ecl);
176   return 0;
177 }
178 static void check_info_clean(struct check_info *ci) {
179   int i;
180   for(i=0; i<ci->argcnt; i++)
181     if(ci->args[i]) free(ci->args[i]);
182   if(ci->arglens) free(ci->arglens);
183   if(ci->args) free(ci->args);
184   for(i=0; i<ci->envcnt; i++)
185     if(ci->envs[i]) free(ci->envs[i]);
186   if(ci->envlens) free(ci->envlens);
187   if(ci->envs) free(ci->envs);
188   if(ci->matcher) pcre_free(ci->matcher);
189   memset(ci, 0, sizeof(*ci));
190 }
191 static int external_handler(eventer_t e, int mask,
192                             void *closure, struct timeval *now) {
193   noit_module_t *self = (noit_module_t *)closure;
194   external_data_t *data;
195
196   data = noit_module_get_userdata(self);
197   while(1) {
198     int inlen, expectlen;
199     noit_check_t *check;
200     struct check_info *ci;
201
202     if(!data->cr) {
203       struct external_response r;
204       struct msghdr msg;
205       struct iovec v[3];
206       memset(&r, 0, sizeof(r));
207       v[0].iov_base = &r.check_no;
208       v[0].iov_len = sizeof(r.check_no);
209       v[1].iov_base = &r.exit_code;
210       v[1].iov_len = sizeof(r.exit_code);
211       v[2].iov_base = &r.stdoutlen;
212       v[2].iov_len = sizeof(r.stdoutlen);
213       expectlen = v[0].iov_len + v[1].iov_len + v[2].iov_len;
214
215       /* Make this into a recv'ble message so we can PEEK */
216       memset(&msg, 0, sizeof(msg));
217       msg.msg_iov = v;
218       msg.msg_iovlen = 3;
219       inlen = recvmsg(e->fd, &msg, MSG_PEEK);
220       if(inlen == 0) goto widowed;
221       if((inlen == -1 && errno == EAGAIN) ||
222          (inlen > 0 && inlen < expectlen))
223         return EVENTER_READ | EVENTER_EXCEPTION;
224       if(inlen == -1)
225         noitL(noit_error, "recvmsg() failed: %s\n", strerror(errno));
226       assert(inlen == expectlen);
227       while(-1 == (inlen = recvmsg(e->fd, &msg, 0)) && errno == EINTR);
228       assert(inlen == expectlen);
229       data->cr = calloc(sizeof(*data->cr), 1);
230       memcpy(data->cr, &r, sizeof(r));
231       data->cr->stdoutbuff = malloc(data->cr->stdoutlen);
232     }
233     if(data->cr) {
234       while(data->cr->stdoutlen_sofar < data->cr->stdoutlen) {
235         while((inlen =
236                  read(e->fd,
237                       data->cr->stdoutbuff + data->cr->stdoutlen_sofar,
238                       data->cr->stdoutlen - data->cr->stdoutlen_sofar)) == -1 &&
239                errno == EINTR);
240         if(inlen == -1 && errno == EAGAIN)
241           return EVENTER_READ | EVENTER_EXCEPTION;
242         if(inlen == 0) goto widowed;
243         data->cr->stdoutlen_sofar += inlen;
244       }
245       assert(data->cr->stdoutbuff[data->cr->stdoutlen-1] == '\0');
246       if(!data->cr->stderrbuff) {
247         while((inlen = read(e->fd, &data->cr->stderrlen,
248                             sizeof(data->cr->stderrlen))) == -1 &&
249               errno == EINTR);
250         if(inlen == -1 && errno == EAGAIN)
251           return EVENTER_READ | EVENTER_EXCEPTION;
252         if(inlen == 0) goto widowed;
253         assert(inlen == sizeof(data->cr->stderrlen));
254         data->cr->stderrbuff = malloc(data->cr->stderrlen);
255       }
256       while(data->cr->stderrlen_sofar < data->cr->stderrlen) {
257         while((inlen =
258                  read(e->fd,
259                       data->cr->stderrbuff + data->cr->stderrlen_sofar,
260                       data->cr->stderrlen - data->cr->stderrlen_sofar)) == -1 &&
261                errno == EINTR);
262         if(inlen == -1 && errno == EAGAIN)
263           return EVENTER_READ | EVENTER_EXCEPTION;
264         if(inlen == 0) goto widowed;
265         data->cr->stderrlen_sofar += inlen;
266       }
267       assert(data->cr->stderrbuff[data->cr->stderrlen-1] == '\0');
268     }
269     assert(data->cr && data->cr->stdoutbuff && data->cr->stderrbuff);
270
271     gettimeofday(now, NULL); /* set it, as we care about accuracy */
272
273     /* Lookup data in check_no hash */
274     if(noit_hash_retrieve(&data->external_checks,
275                           (const char *)&data->cr->check_no,
276                           sizeof(data->cr->check_no),
277                           (void **)&ci) == 0)
278       ci = NULL;
279
280     /* We've seen it, it ain't coming again...
281      * remove it, we'll free it ourselves */
282     noit_hash_delete(&data->external_checks,
283                      (const char *)&data->cr->check_no,
284                      sizeof(data->cr->check_no), NULL, NULL);
285
286     /* If there is no timeout_event, the check must have completed.
287      * We have nothing to do. */
288     if(!ci || !ci->timeout_event) {
289       free(data->cr->stdoutbuff);
290       free(data->cr->stderrbuff);
291       free(data->cr);
292       data->cr = NULL;
293       continue;
294     }
295     ci->exit_code = data->cr->exit_code;
296     ci->output = data->cr->stdoutbuff;
297     ci->error = data->cr->stderrbuff;
298     free(data->cr);
299     data->cr = NULL;
300     check = ci->check;
301     external_log_results(self, check);
302     eventer_remove(ci->timeout_event);
303     free(ci->timeout_event->closure);
304     eventer_free(ci->timeout_event);
305     ci->timeout_event = NULL;
306     check->flags &= ~NP_RUNNING;
307   }
308   return EVENTER_READ;
309
310  widowed:
311   noitL(noit_error, "external module terminated, must restart.\n");
312   exit(1);
313 }
314
315 static int external_init(noit_module_t *self) {
316   external_data_t *data;
317
318   data = noit_module_get_userdata(self);
319   if(!data) data = malloc(sizeof(*data));
320   data->nlerr = noit_log_stream_find("error/external");
321   data->nldeb = noit_log_stream_find("debug/external");
322
323   data->jobq = calloc(1, sizeof(*data->jobq));
324   eventer_jobq_init(data->jobq, "external");
325   data->jobq->backq = eventer_default_backq();
326   eventer_jobq_increase_concurrency(data->jobq);
327
328   if(socketpair(AF_UNIX, SOCK_STREAM, 0, data->pipe_n2e) != 0 ||
329      socketpair(AF_UNIX, SOCK_STREAM, 0, data->pipe_e2n) != 0) {
330     noitL(noit_error, "external: pipe() failed: %s\n", strerror(errno));
331     return -1;
332   }
333
334   data->child = fork();
335   if(data->child == -1) {
336     /* No child, bail. */
337     noitL(noit_error, "external: fork() failed: %s\n", strerror(errno));
338     return -1;
339   }
340
341   /* parent must close the read side of n2e and the write side of e2n */
342   /* The child must do the opposite */
343   close(data->pipe_n2e[(data->child == 0) ? 1 : 0]);
344   close(data->pipe_e2n[(data->child == 0) ? 0 : 1]);
345
346   /* Now the parent must set its bits non-blocking, the child need not */
347   if(data->child != 0) {
348     long on = 1;
349     /* in the parent */
350     if(ioctl(data->pipe_e2n[0], FIONBIO, &on) == -1) {
351       close(data->pipe_n2e[1]);
352       close(data->pipe_e2n[0]);
353       noitL(noit_error,
354             "external: could not set pipe non-blocking: %s\n",
355             strerror(errno));
356       return -1;
357     }
358     eventer_t newe;
359     newe = eventer_alloc();
360     newe->fd = data->pipe_e2n[0];
361     newe->mask = EVENTER_READ | EVENTER_EXCEPTION;
362     newe->callback = external_handler;
363     newe->closure = self;
364     eventer_add(newe);
365   }
366   else {
367     const char *user = NULL, *group = NULL;
368     if(data->options) {
369       noit_hash_retrieve(data->options, "user", 4, (void **)&user);
370       noit_hash_retrieve(data->options, "group", 4, (void **)&group);
371     }
372     noit_security_usergroup(user, group);
373     exit(external_child(data));
374   }
375   noit_module_set_userdata(self, data);
376   return 0;
377 }
378
379 static void external_cleanup(noit_module_t *self, noit_check_t *check) {
380   struct check_info *ci = (struct check_info *)check->closure;
381   if(ci) {
382     if(ci->timeout_event) {
383       eventer_remove(ci->timeout_event);
384       free(ci->timeout_event->closure);
385       eventer_free(ci->timeout_event);
386       ci->timeout_event = NULL;
387     }
388   }
389 }
390 #define assert_write(fd, w, s) assert(write(fd, w, s) == s)
391 static int external_enqueue(eventer_t e, int mask, void *closure,
392                             struct timeval *now) {
393   external_closure_t *ecl = (external_closure_t *)closure;
394   struct check_info *ci = (struct check_info *)ecl->check->closure;
395   external_data_t *data;
396   int fd, i;
397
398   if(mask == EVENTER_ASYNCH_CLEANUP) {
399     e->mask = 0;
400     return 0;
401   }
402   if(!(mask & EVENTER_ASYNCH_WORK)) return 0;
403   data = noit_module_get_userdata(ecl->self);
404   fd = data->pipe_n2e[1];
405   assert_write(fd, &ci->check_no, sizeof(ci->check_no));
406   assert_write(fd, &ci->argcnt, sizeof(ci->argcnt));
407   assert_write(fd, ci->arglens, sizeof(*ci->arglens)*ci->argcnt);
408   for(i=0; i<ci->argcnt; i++)
409     assert_write(fd, ci->args[i], ci->arglens[i]);
410   assert_write(fd, &ci->envcnt, sizeof(ci->envcnt));
411   assert_write(fd, ci->envlens, sizeof(*ci->envlens)*ci->envcnt);
412   for(i=0; i<ci->envcnt; i++)
413     assert_write(fd, ci->envs[i], ci->envlens[i]);
414   return 0;
415 }
416 static int external_invoke(noit_module_t *self, noit_check_t *check) {
417   struct timeval when, p_int;
418   external_closure_t *ecl;
419   struct check_info *ci = (struct check_info *)check->closure;
420   eventer_t newe;
421   external_data_t *data;
422   noit_hash_table check_attrs_hash = NOIT_HASH_EMPTY;
423   int i, klen;
424   noit_hash_iter iter = NOIT_HASH_ITER_ZERO;
425   const char *name, *value;
426   char interp_fmt[4096], interp_buff[4096];
427
428   data = noit_module_get_userdata(self);
429
430   check->flags |= NP_RUNNING;
431   noitL(data->nldeb, "external_invoke(%p,%s)\n",
432         self, check->target);
433
434   /* remove a timeout if we still have one -- we should unless someone
435    * has set a lower timeout than the period.
436    */
437   if(ci->timeout_event) {
438     eventer_remove(ci->timeout_event);
439     free(ci->timeout_event->closure);
440     eventer_free(ci->timeout_event);
441     ci->timeout_event = NULL;
442   }
443
444   check_info_clean(ci);
445
446   gettimeofday(&when, NULL);
447   memcpy(&check->last_fire_time, &when, sizeof(when));
448
449   /* Setup all our check bits */
450   ci->check_no = noit_atomic_inc64(&data->check_no_seq);
451   ci->check = check;
452   /* We might want to extract metrics */
453   if(noit_hash_retrieve(check->config,
454                         "output_extract", strlen("output_extract"),
455                         (void **)&value) != 0) {
456     const char *error;
457     int erroffset;
458     ci->matcher = pcre_compile(value, 0, &error, &erroffset, NULL);
459     if(!ci->matcher) {
460       noitL(data->nlerr, "external pcre /%s/ failed @ %d: %s\n",
461             value, erroffset, error);
462     }
463   }
464
465   noit_check_make_attrs(check, &check_attrs_hash);
466
467   /* Count the args */
468   i = 1;
469   while(1) {
470     char argname[10];
471     snprintf(argname, sizeof(argname), "arg%d", i);
472     if(noit_hash_retrieve(check->config, argname, strlen(argname),
473                           (void **)&value) == 0) break;
474     i++;
475   }
476   ci->argcnt = i + 1; /* path, arg0, (i-1 more args) */
477   ci->arglens = calloc(ci->argcnt, sizeof(*ci->arglens));
478   ci->args = calloc(ci->argcnt, sizeof(*ci->args));
479
480   /* Make the command */
481   if(noit_hash_retrieve(check->config, "command", strlen("command"),
482                         (void **)&value) == 0) {
483     value = "/bin/true";
484   }
485   ci->args[0] = strdup(value);
486   ci->arglens[0] = strlen(ci->args[0]) + 1;
487
488   i = 0;
489   while(1) {
490     char argname[10];
491     snprintf(argname, sizeof(argname), "arg%d", i);
492     if(noit_hash_retrieve(check->config, argname, strlen(argname),
493                           (void **)&value) == 0) {
494       if(i == 0) {
495         /* if we don't have arg0, make it last element of path */
496         char *cp = ci->args[0] + strlen(ci->args[0]);
497         while(cp > ci->args[0] && *(cp-1) != '/') cp--;
498         value = cp;
499       }
500       else break; /* if we don't have argn, we're done */
501     }
502     noit_check_interpolate(interp_buff, sizeof(interp_buff), value,
503                            &check_attrs_hash, check->config);
504     ci->args[i+1] = strdup(interp_buff);
505     ci->arglens[i+1] = strlen(ci->args[i+1]) + 1;
506     i++;
507   }
508
509   /* Make the environment */
510   memset(&iter, 0, sizeof(iter));
511   ci->envcnt = 0;
512   while(noit_hash_next(check->config, &iter, &name, &klen, (void **)&value))
513     if(!strncasecmp(name, "env_", 4))
514       ci->envcnt++;
515   memset(&iter, 0, sizeof(iter));
516   ci->envlens = calloc(ci->envcnt, sizeof(*ci->envlens));
517   ci->envs = calloc(ci->envcnt, sizeof(*ci->envs));
518   ci->envcnt = 0;
519   while(noit_hash_next(check->config, &iter, &name, &klen, (void **)&value))
520     if(!strncasecmp(name, "env_", 4)) {
521       snprintf(interp_fmt, sizeof(interp_fmt), "%s=%s", name+4, value);
522       noit_check_interpolate(interp_buff, sizeof(interp_buff), interp_fmt,
523                              &check_attrs_hash, check->config);
524       ci->envs[ci->envcnt] = strdup(interp_buff);
525       ci->envlens[ci->envcnt] = strlen(ci->envs[ci->envcnt]) + 1;
526       ci->envcnt++;
527     }
528
529   noit_hash_destroy(&check_attrs_hash, NULL, NULL);
530
531   noit_hash_store(&data->external_checks,
532                   (const char *)&ci->check_no, sizeof(ci->check_no),
533                   ci);
534
535   /* Setup a timeout */
536   newe = eventer_alloc();
537   newe->mask = EVENTER_TIMER;
538   gettimeofday(&when, NULL);
539   p_int.tv_sec = check->timeout / 1000;
540   p_int.tv_usec = (check->timeout % 1000) * 1000;
541   add_timeval(when, p_int, &newe->whence);
542   ecl = calloc(1, sizeof(*ecl));
543   ecl->self = self;
544   ecl->check = check;
545   newe->closure = ecl;
546   newe->callback = external_timeout;
547   eventer_add(newe);
548   ci->timeout_event = newe;
549
550   /* Setup push */
551   newe = eventer_alloc();
552   newe->mask = EVENTER_ASYNCH;
553   add_timeval(when, p_int, &newe->whence);
554   ecl = calloc(1, sizeof(*ecl));
555   ecl->self = self;
556   ecl->check = check;
557   newe->closure = ecl;
558   newe->callback = external_enqueue;
559   eventer_add(newe);
560
561   return 0;
562 }
563 static int external_initiate_check(noit_module_t *self, noit_check_t *check,
564                                     int once, noit_check_t *cause) {
565   if(!check->closure) check->closure = calloc(1, sizeof(struct check_info));
566   INITIATE_CHECK(external_invoke, self, check);
567   return 0;
568 }
569
570 static int external_onload(noit_image_t *self) {
571   eventer_name_callback("external/timeout", external_timeout);
572   eventer_name_callback("external/handler", external_handler);
573   return 0;
574 }
575
576 #include "external.xmlh"
577 noit_module_t external = {
578   {
579     NOIT_MODULE_MAGIC,
580     NOIT_MODULE_ABI_VERSION,
581     "external",
582     "checks via external programs",
583     external_xml_description,
584     external_onload
585   },
586   external_config,
587   external_init,
588   external_initiate_check,
589   external_cleanup
590 };
591
Note: See TracBrowser for help on using the browser.