root/src/modules/external.c

Revision 6210da7ee0e2ed143d71a8e00b709f16e71059f8, 18.6 kB (checked in by Theo Schlossnagle <jesus@omniti.com>, 5 years ago)

various changes to avoid dereferencing type-punned pointers and breaking strict-aliasing rules, refs #34

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