| 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(¤t); |
|---|
| 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(¤t.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(¤t, 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, ¤t); |
|---|
| 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 |
|
|---|