root/src/modules/external.c

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

refs #102

  • 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 <math.h>
23 #ifndef MAXFLOAT
24 #include <float.h>
25 #define MAXFLOAT FLT_MAX
26 #endif
27
28 #include "noit_module.h"
29 #include "noit_check.h"
30 #include "noit_check_tools.h"
31 #include "utils/noit_log.h"
32 #include "utils/noit_security.h"
33 #include "external_proc.h"
34
35 struct check_info {
36   int64_t check_no;
37   u_int16_t argcnt;
38   u_int16_t *arglens;
39   char **args;
40   u_int16_t envcnt;
41   u_int16_t *envlens;
42   char **envs;
43   noit_check_t *check;
44   int exit_code;
45
46   int timedout;
47   char *output;
48   char *error;
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   current.status = ci->output;
126   noit_check_set_stats(self, check, &current);
127
128   /* If we didn't exit normally, or we core, or we have stderr to report...
129    * provide a full report.
130    */
131   if(WTERMSIG(ci->exit_code) != SIGQUIT ||
132      WCOREDUMP(ci->exit_code) ||
133      ci->error) {
134     char uuid_str[37];
135     uuid_unparse_lower(check->checkid, uuid_str);
136     noitL(data->nlerr, "external/%s: (sig:%d%s) [%s]\n", uuid_str,
137           WTERMSIG(ci->exit_code), WCOREDUMP(ci->exit_code)?", cored":"",
138           ci->error ? ci->error : "");
139   }
140 }
141 static int external_timeout(eventer_t e, int mask,
142                             void *closure, struct timeval *now) {
143   external_closure_t *ecl = (external_closure_t *)closure;
144   struct check_info *data;
145   if(!NOIT_CHECK_KILLED(ecl->check) && !NOIT_CHECK_DISABLED(ecl->check)) {
146     data = (struct check_info *)ecl->check->closure;
147     data->timedout = 1;
148     data->exit_code = 3;
149     external_log_results(ecl->self, ecl->check);
150     data->timeout_event = NULL;
151   }
152   ecl->check->flags &= ~NP_RUNNING;
153   free(ecl);
154   return 0;
155 }
156 static void check_info_clean(struct check_info *ci) {
157   int i;
158   for(i=0; i<ci->argcnt; i++)
159     if(ci->args[i]) free(ci->args[i]);
160   if(ci->arglens) free(ci->arglens);
161   if(ci->args) free(ci->args);
162   for(i=0; i<ci->envcnt; i++)
163     if(ci->envs[i]) free(ci->envs[i]);
164   if(ci->envlens) free(ci->envlens);
165   if(ci->envs) free(ci->envs);
166   memset(ci, 0, sizeof(*ci));
167 }
168 static int external_handler(eventer_t e, int mask,
169                             void *closure, struct timeval *now) {
170   noit_module_t *self = (noit_module_t *)closure;
171   external_data_t *data;
172
173   data = noit_module_get_userdata(self);
174   while(1) {
175     int inlen, expectlen;
176     noit_check_t *check;
177     struct check_info *ci;
178
179     if(!data->cr) {
180       struct external_response r;
181       struct msghdr msg;
182       struct iovec v[3];
183       memset(&r, 0, sizeof(r));
184       v[0].iov_base = &r.check_no;
185       v[0].iov_len = sizeof(r.check_no);
186       v[1].iov_base = &r.exit_code;
187       v[1].iov_len = sizeof(r.exit_code);
188       v[2].iov_base = &r.stdoutlen;
189       v[2].iov_len = sizeof(r.stdoutlen);
190       expectlen = v[0].iov_len + v[1].iov_len + v[2].iov_len;
191
192       /* Make this into a recv'ble message so we can PEEK */
193       memset(&msg, 0, sizeof(msg));
194       msg.msg_iov = v;
195       msg.msg_iovlen = 3;
196       inlen = recvmsg(e->fd, &msg, MSG_PEEK);
197       if(inlen == 0) goto widowed;
198       if((inlen == -1 && errno == EAGAIN) ||
199          (inlen > 0 && inlen < expectlen))
200         return EVENTER_READ | EVENTER_EXCEPTION;
201       if(inlen == -1)
202         noitL(noit_error, "recvmsg() failed: %s\n", strerror(errno));
203       assert(inlen == expectlen);
204       while(-1 == (inlen = recvmsg(e->fd, &msg, 0)) && errno == EINTR);
205       assert(inlen == expectlen);
206       data->cr = calloc(sizeof(*data->cr), 1);
207       memcpy(data->cr, &r, sizeof(r));
208       data->cr->stdoutbuff = malloc(data->cr->stdoutlen);
209     }
210     if(data->cr) {
211       while(data->cr->stdoutlen_sofar < data->cr->stdoutlen) {
212         while((inlen =
213                  read(e->fd,
214                       data->cr->stdoutbuff + data->cr->stdoutlen_sofar,
215                       data->cr->stdoutlen - data->cr->stdoutlen_sofar)) == -1 &&
216                errno == EINTR);
217         if(inlen == -1 && errno == EAGAIN)
218           return EVENTER_READ | EVENTER_EXCEPTION;
219         if(inlen == 0) goto widowed;
220         data->cr->stdoutlen_sofar += inlen;
221       }
222       assert(data->cr->stdoutbuff[data->cr->stdoutlen-1] == '\0');
223       if(!data->cr->stderrbuff) {
224         while((inlen = read(e->fd, &data->cr->stderrlen,
225                             sizeof(data->cr->stderrlen))) == -1 &&
226               errno == EINTR);
227         if(inlen == -1 && errno == EAGAIN)
228           return EVENTER_READ | EVENTER_EXCEPTION;
229         if(inlen == 0) goto widowed;
230         assert(inlen == sizeof(data->cr->stderrlen));
231         data->cr->stderrbuff = malloc(data->cr->stderrlen);
232       }
233       while(data->cr->stderrlen_sofar < data->cr->stderrlen) {
234         while((inlen =
235                  read(e->fd,
236                       data->cr->stderrbuff + data->cr->stderrlen_sofar,
237                       data->cr->stderrlen - data->cr->stderrlen_sofar)) == -1 &&
238                errno == EINTR);
239         if(inlen == -1 && errno == EAGAIN)
240           return EVENTER_READ | EVENTER_EXCEPTION;
241         if(inlen == 0) goto widowed;
242         data->cr->stderrlen_sofar += inlen;
243       }
244       assert(data->cr->stderrbuff[data->cr->stderrlen-1] == '\0');
245     }
246     assert(data->cr && data->cr->stdoutbuff && data->cr->stderrbuff);
247
248     gettimeofday(now, NULL); /* set it, as we care about accuracy */
249
250     /* Lookup data in check_no hash */
251     if(noit_hash_retrieve(&data->external_checks,
252                           (const char *)&data->cr->check_no,
253                           sizeof(data->cr->check_no),
254                           (void **)&ci) == 0)
255       ci = NULL;
256
257     /* We've seen it, it ain't coming again...
258      * remove it, we'll free it ourselves */
259     noit_hash_delete(&data->external_checks,
260                      (const char *)&data->cr->check_no,
261                      sizeof(data->cr->check_no), NULL, NULL);
262
263     /* If there is no timeout_event, the check must have completed.
264      * We have nothing to do. */
265     if(!ci || !ci->timeout_event) {
266       free(data->cr->stdoutbuff);
267       free(data->cr->stderrbuff);
268       free(data->cr);
269       data->cr = NULL;
270       continue;
271     }
272     ci->exit_code = data->cr->exit_code;
273     ci->output = data->cr->stdoutbuff;
274     ci->error = data->cr->stderrbuff;
275     free(data->cr);
276     data->cr = NULL;
277     check = ci->check;
278     external_log_results(self, check);
279     eventer_remove(ci->timeout_event);
280     free(ci->timeout_event->closure);
281     eventer_free(ci->timeout_event);
282     ci->timeout_event = NULL;
283     check->flags &= ~NP_RUNNING;
284   }
285   return EVENTER_READ;
286
287  widowed:
288   noitL(noit_error, "external module terminated, must restart.\n");
289   exit(1);
290 }
291
292 static int external_init(noit_module_t *self) {
293   external_data_t *data;
294
295   data = noit_module_get_userdata(self);
296   if(!data) data = malloc(sizeof(*data));
297   data->nlerr = noit_log_stream_find("error/external");
298   data->nldeb = noit_log_stream_find("debug/external");
299
300   data->jobq = calloc(1, sizeof(*data->jobq));
301   eventer_jobq_init(data->jobq, "external");
302   data->jobq->backq = eventer_default_backq();
303   eventer_jobq_increase_concurrency(data->jobq);
304
305   if(socketpair(AF_UNIX, SOCK_STREAM, 0, data->pipe_n2e) != 0 ||
306      socketpair(AF_UNIX, SOCK_STREAM, 0, data->pipe_e2n) != 0) {
307     noitL(noit_error, "external: pipe() failed: %s\n", strerror(errno));
308     return -1;
309   }
310
311   data->child = fork();
312   if(data->child == -1) {
313     /* No child, bail. */
314     noitL(noit_error, "external: fork() failed: %s\n", strerror(errno));
315     return -1;
316   }
317
318   /* parent must close the read side of n2e and the write side of e2n */
319   /* The child must do the opposite */
320   close(data->pipe_n2e[(data->child == 0) ? 1 : 0]);
321   close(data->pipe_e2n[(data->child == 0) ? 0 : 1]);
322
323   /* Now the parent must set its bits non-blocking, the child need not */
324   if(data->child != 0) {
325     long on = 1;
326     /* in the parent */
327     if(ioctl(data->pipe_e2n[0], FIONBIO, &on) == -1) {
328       close(data->pipe_n2e[1]);
329       close(data->pipe_e2n[0]);
330       noitL(noit_error,
331             "external: could not set pipe non-blocking: %s\n",
332             strerror(errno));
333       return -1;
334     }
335     eventer_t newe;
336     newe = eventer_alloc();
337     newe->fd = data->pipe_e2n[0];
338     newe->mask = EVENTER_READ | EVENTER_EXCEPTION;
339     newe->callback = external_handler;
340     newe->closure = self;
341     eventer_add(newe);
342   }
343   else {
344     const char *user = NULL, *group = NULL;
345     if(data->options) {
346       noit_hash_retrieve(data->options, "user", 4, (void **)&user);
347       noit_hash_retrieve(data->options, "group", 4, (void **)&group);
348     }
349     noit_security_usergroup(user, group);
350     exit(external_child(data));
351   }
352   noit_module_set_userdata(self, data);
353   return 0;
354 }
355
356 static void external_cleanup(noit_module_t *self, noit_check_t *check) {
357   struct check_info *ci = (struct check_info *)check->closure;
358   if(ci) {
359     if(ci->timeout_event) {
360       eventer_remove(ci->timeout_event);
361       free(ci->timeout_event->closure);
362       eventer_free(ci->timeout_event);
363       ci->timeout_event = NULL;
364     }
365   }
366 }
367 #define assert_write(fd, w, s) assert(write(fd, w, s) == s)
368 static int external_enqueue(eventer_t e, int mask, void *closure,
369                             struct timeval *now) {
370   external_closure_t *ecl = (external_closure_t *)closure;
371   struct check_info *ci = (struct check_info *)ecl->check->closure;
372   external_data_t *data;
373   int fd, i;
374
375   if(mask == EVENTER_ASYNCH_CLEANUP) {
376     e->mask = 0;
377     return 0;
378   }
379   if(!(mask & EVENTER_ASYNCH_WORK)) return 0;
380   data = noit_module_get_userdata(ecl->self);
381   fd = data->pipe_n2e[1];
382   assert_write(fd, &ci->check_no, sizeof(ci->check_no));
383   assert_write(fd, &ci->argcnt, sizeof(ci->argcnt));
384   assert_write(fd, ci->arglens, sizeof(*ci->arglens)*ci->argcnt);
385   for(i=0; i<ci->argcnt; i++)
386     assert_write(fd, ci->args[i], ci->arglens[i]);
387   assert_write(fd, &ci->envcnt, sizeof(ci->envcnt));
388   assert_write(fd, ci->envlens, sizeof(*ci->envlens)*ci->envcnt);
389   for(i=0; i<ci->envcnt; i++)
390     assert_write(fd, ci->envs[i], ci->envlens[i]);
391   return 0;
392 }
393 static int external_invoke(noit_module_t *self, noit_check_t *check) {
394   struct timeval when, p_int;
395   external_closure_t *ecl;
396   struct check_info *ci = (struct check_info *)check->closure;
397   eventer_t newe;
398   external_data_t *data;
399   noit_hash_table check_attrs_hash = NOIT_HASH_EMPTY;
400   int i, klen;
401   noit_hash_iter iter = NOIT_HASH_ITER_ZERO;
402   const char *name, *value;
403   char interp_fmt[4096], interp_buff[4096];
404
405   data = noit_module_get_userdata(self);
406
407   check->flags |= NP_RUNNING;
408   noitL(data->nldeb, "external_invoke(%p,%s)\n",
409         self, check->target);
410
411   /* remove a timeout if we still have one -- we should unless someone
412    * has set a lower timeout than the period.
413    */
414   if(ci->timeout_event) {
415     eventer_remove(ci->timeout_event);
416     free(ci->timeout_event->closure);
417     eventer_free(ci->timeout_event);
418     ci->timeout_event = NULL;
419   }
420
421   check_info_clean(ci);
422
423   gettimeofday(&when, NULL);
424   memcpy(&check->last_fire_time, &when, sizeof(when));
425
426   /* Setup all our check bits */
427   ci->check_no = noit_atomic_inc64(&data->check_no_seq);
428   ci->check = check;
429   noit_check_make_attrs(check, &check_attrs_hash);
430
431   /* Count the args */
432   i = 1;
433   while(1) {
434     char argname[10];
435     snprintf(argname, sizeof(argname), "arg%d", i);
436     if(noit_hash_retrieve(check->config, argname, strlen(argname),
437                           (void **)&value) == 0) break;
438     i++;
439   }
440   ci->argcnt = i + 1; /* path, arg0, (i-1 more args) */
441   ci->arglens = calloc(ci->argcnt, sizeof(*ci->arglens));
442   ci->args = calloc(ci->argcnt, sizeof(*ci->args));
443
444   /* Make the command */
445   if(noit_hash_retrieve(check->config, "command", strlen("command"),
446                         (void **)&value) == 0) {
447     value = "/bin/true";
448   }
449   ci->args[0] = strdup(value);
450   ci->arglens[0] = strlen(ci->args[0]) + 1;
451
452   i = 0;
453   while(1) {
454     char argname[10];
455     snprintf(argname, sizeof(argname), "arg%d", i);
456     if(noit_hash_retrieve(check->config, argname, strlen(argname),
457                           (void **)&value) == 0) {
458       if(i == 0) {
459         /* if we don't have arg0, make it last element of path */
460         char *cp = ci->args[0] + strlen(ci->args[0]);
461         while(cp > ci->args[0] && *(cp-1) != '/') cp--;
462         value = cp;
463       }
464       else break; /* if we don't have argn, we're done */
465     }
466     noit_check_interpolate(interp_buff, sizeof(interp_buff), value,
467                            &check_attrs_hash, check->config);
468     ci->args[i+1] = strdup(interp_buff);
469     ci->arglens[i+1] = strlen(ci->args[i+1]) + 1;
470     i++;
471   }
472
473   /* Make the environment */
474   memset(&iter, 0, sizeof(iter));
475   ci->envcnt = 0;
476   while(noit_hash_next(check->config, &iter, &name, &klen, (void **)&value))
477     if(!strncasecmp(name, "env_", 4))
478       ci->envcnt++;
479   memset(&iter, 0, sizeof(iter));
480   ci->envlens = calloc(ci->envcnt, sizeof(*ci->envlens));
481   ci->envs = calloc(ci->envcnt, sizeof(*ci->envs));
482   ci->envcnt = 0;
483   while(noit_hash_next(check->config, &iter, &name, &klen, (void **)&value))
484     if(!strncasecmp(name, "env_", 4)) {
485       snprintf(interp_fmt, sizeof(interp_fmt), "%s=%s", name+4, value);
486       noit_check_interpolate(interp_buff, sizeof(interp_buff), interp_fmt,
487                              &check_attrs_hash, check->config);
488       ci->envs[ci->envcnt] = strdup(interp_buff);
489       ci->envlens[ci->envcnt] = strlen(ci->envs[ci->envcnt]) + 1;
490       ci->envcnt++;
491     }
492
493   noit_hash_destroy(&check_attrs_hash, NULL, NULL);
494
495   noit_hash_store(&data->external_checks,
496                   (const char *)&ci->check_no, sizeof(ci->check_no),
497                   ci);
498
499   /* Setup a timeout */
500   newe = eventer_alloc();
501   newe->mask = EVENTER_TIMER;
502   gettimeofday(&when, NULL);
503   p_int.tv_sec = check->timeout / 1000;
504   p_int.tv_usec = (check->timeout % 1000) * 1000;
505   add_timeval(when, p_int, &newe->whence);
506   ecl = calloc(1, sizeof(*ecl));
507   ecl->self = self;
508   ecl->check = check;
509   newe->closure = ecl;
510   newe->callback = external_timeout;
511   eventer_add(newe);
512   ci->timeout_event = newe;
513
514   /* Setup push */
515   newe = eventer_alloc();
516   newe->mask = EVENTER_ASYNCH;
517   add_timeval(when, p_int, &newe->whence);
518   ecl = calloc(1, sizeof(*ecl));
519   ecl->self = self;
520   ecl->check = check;
521   newe->closure = ecl;
522   newe->callback = external_enqueue;
523   eventer_add(newe);
524
525   return 0;
526 }
527 static int external_initiate_check(noit_module_t *self, noit_check_t *check,
528                                     int once, noit_check_t *cause) {
529   if(!check->closure) check->closure = calloc(1, sizeof(struct check_info));
530   INITIATE_CHECK(external_invoke, self, check);
531   return 0;
532 }
533
534 static int external_onload(noit_image_t *self) {
535   eventer_name_callback("external/timeout", external_timeout);
536   eventer_name_callback("external/handler", external_handler);
537   return 0;
538 }
539
540 #include "external.xmlh"
541 noit_module_t external = {
542   {
543     NOIT_MODULE_MAGIC,
544     NOIT_MODULE_ABI_VERSION,
545     "external",
546     "checks via external programs",
547     external_xml_description,
548     external_onload
549   },
550   external_config,
551   external_init,
552   external_initiate_check,
553   external_cleanup
554 };
555
Note: See TracBrowser for help on using the browser.