/* * This gets called when a connect() attempt has become writable. * It's entirely possible that the node we're trying to connect * to has connected to us while we were waiting for them, in * which case we need to figure out which of the two connections * we're supposed to use. */ static int conn_writable(int sd, int events, void *node_) { merlin_node *node = (merlin_node *)node_; int result; int sel_sd; /* unregister so we don't peg one cpu at 100% */ ldebug("CONN: In conn_writable(): node=%s; sd=%d; node->conn_sock=%d", node->name, sd, node->conn_sock); iobroker_unregister(nagios_iobs, sd); if (node->sock < 0) { /* no inbound connection accept()'ed yet */ node->sock = sd; node->conn_sock = -1; if (!net_is_connected(node)) { node_disconnect(node, "Connection attempt failed: %s", strerror(errno)); close(sd); return 0; } iobroker_register(nagios_iobs, sd, node, net_input); node_set_state(node, STATE_NEGOTIATING, "Connect completed successfully. Negotiating protocol"); return 0; } sel_sd = net_negotiate_socket(node, node->conn_sock, node->sock); if (sel_sd < 0) { node_disconnect(node, "Failed to negotiate socket"); return 0; } if (sel_sd == node->conn_sock) { iobroker_close(nagios_iobs, node->sock); } else if (sel_sd == node->sock) { iobroker_close(nagios_iobs, node->conn_sock); } node->sock = sel_sd; node->conn_sock = -1; node_set_state(node, STATE_NEGOTIATING, "polled for writability"); /* now re-register for input */ ldebug("IOB: registering %s(%d) for input events", node->name, node->sock); result = iobroker_register(nagios_iobs, node->sock, node, net_input); if (result < 0) { lerr("IOB: Failed to register %s(%d) for input events: %s", node->name, node->sock, iobroker_strerror(result)); } return 0; }
static int connected_handler(int fd, int events, void *arg) { int *counter = (int *)arg; int i; i = *counter; if (events == IOBROKER_POLLIN) { char buf[1024]; int len = read(fd, buf, sizeof(buf)); buf[len] = 0; test(len == (int)strlen(msg[i]), "len match for message %d", i); test(!memcmp(buf, msg[i], len), "data match for message %d", i); } i++; if (i < 0 || i >= (int)ARRAY_SIZE(msg)) { fprintf(stderr, "i = %d in connected_handler(). What's up with that?\n", i); return 0; } if (!msg[i]) { iobroker_close(iobs, fd); return 0; } if (write(fd, msg[i], strlen(msg[i])) < 0) t_fail("write returned failure: %s", strerror(errno)); *counter = i; return 0; }
static int nerd_deinit(void) { unsigned int i; if(host_parent_path_cache) { for(i = 0; i < num_objects.hosts; i++) { my_free(host_parent_path_cache[i]); } my_free(host_parent_path_cache); } for(i = 0; i < num_channels; i++) { struct nerd_channel *chan = channels[i]; objectlist *list, *next; for(list = chan->subscriptions; list; list = next) { struct nerd_subscription *subscr = (struct nerd_subscription *)list->object_ptr; iobroker_close(nagios_iobs, subscr->sd); next = list->next; free(list); free(subscr); } chan->subscriptions = NULL; my_free(chan); } my_free(channels); num_channels = 0; alloc_channels = 0; return 0; }
static int send_command(int sd, int events, void *discard) { char buf[8192]; int ret; simple_worker *wp; struct kvvec *kvv; ret = read(sd, buf, sizeof(buf)); if (ret == 0) { iobroker_close(iobs, sd); return 0; } if (ret < 0) { printf("main: Failed to read() from fd %d: %s", sd, strerror(errno)); } /* this happens when we're reading from stdin */ buf[--ret] = 0; kvv = kvvec_create(5); wp = wps[wp_index++ % NWPS]; kvvec_addkv(kvv, "job_id", (char *)mkstr("%d", wp->job_index++)); kvvec_addkv_wlen(kvv, "command", sizeof("command") - 1, buf, ret); kvvec_addkv(kvv, "timeout", (char *)mkstr("%d", 10)); printf("Sending kvvec with %d pairs to worker %d\n", kvv->kv_pairs, wp->pid); worker_send_kvvec(wp->sd, kvv); kvvec_destroy(kvv, 0); return 0; }
/* removes a subscriber entirely and closes its socket */ int nerd_cancel_subscriber(int sd) { unsigned int i; for(i = 0; i < num_channels; i++) { cancel_channel_subscription(channels[i], sd); } iobroker_close(nagios_iobs, sd); return 0; }
int wproc_destroy(worker_process *wp, int flags) { int i = 0, destroyed = 0, force = 0, sd, self; if (!wp) return 0; force = !!(flags & WPROC_FORCE); self = getpid(); /* master retains workers through restarts */ if (self == nagios_pid && !force) return 0; /* free all memory when either forcing or a worker called us */ iocache_destroy(wp->ioc); wp->ioc = NULL; if (wp->jobs) { for (i = 0; i < wp->max_jobs; i++) { if (!wp->jobs[i]) continue; destroy_job(wp, wp->jobs[i]); /* we can (often) break out early */ if (++destroyed >= wp->jobs_running) break; } /* this triggers a double-free() for some reason */ /* free(wp->jobs); */ wp->jobs = NULL; } sd = wp->sd; free(wp); /* workers must never control other workers, so they return early */ if (self != nagios_pid) return 0; /* kill(0, SIGKILL) equals suicide, so we avoid it */ if (wp->pid) { kill(wp->pid, SIGKILL); } iobroker_close(nagios_iobs, sd); /* reap our possibly lost children */ while (waitpid(-1, &i, WNOHANG) > 0) ; /* do nothing */ return 0; }
int main(int argc, char **argv) { int listen_fd, flags, sockopt = 1; struct sockaddr_in sain; int error; const char *err_msg; t_set_colors(0); t_start("iobroker ipc test"); error = iobroker_get_max_fds(NULL); ok_int(error, IOBROKER_ENOSET, "test errors when passing null"); err_msg = iobroker_strerror(error); test(err_msg && !strcmp(err_msg, iobroker_errors[(~error) + 1].string), "iobroker_strerror() returns the right string"); iobs = iobroker_create(); error = iobroker_get_max_fds(iobs); test(iobs && error >= 0, "max fd's for real iobroker set must be > 0"); listen_fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); flags = fcntl(listen_fd, F_GETFD); flags |= FD_CLOEXEC; fcntl(listen_fd, F_SETFD, flags); (void)setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &sockopt, sizeof(sockopt)); memset(&sain, 0, sizeof(sain)); sain.sin_port = ntohs(9123); sain.sin_family = AF_INET; bind(listen_fd, (struct sockaddr *)&sain, sizeof(sain)); listen(listen_fd, 128); iobroker_register(iobs, listen_fd, iobs, listen_handler); if (argc == 1) conn_spam(&sain); for (;;) { iobroker_poll(iobs, -1); if (iobroker_get_num_fds(iobs) <= 1) { break; } } iobroker_close(iobs, listen_fd); iobroker_destroy(iobs, 0); t_end(); return 0; }
/* close all sockets and release the memory used by * static global vars for networking purposes */ int net_deinit(void) { uint i; for (i = 0; i < num_nodes; i++) { node_disconnect(node_table[i], "Deinitializing networking"); } iobroker_close(nagios_iobs, net_sock); close(net_sock); net_sock = -1; return 0; }
static int echo_service(int fd, int events, void *arg) { char buf[1024]; int len; len = read(fd, buf, sizeof(buf)); if (len < 0) { perror("read"); iobroker_close(iobs, fd); ok_int(iobroker_is_registered(iobs, fd), 0, "Closing must deregister"); return 0; } /* zero read means we're disconnected */ if (!len) { iobroker_close(iobs, fd); ok_int(iobroker_is_registered(iobs, fd), 0, "Closing must deregister"); return 0; } if (write(fd, buf, len) < 0) t_fail("write returned failure: %s", strerror(errno)); return 0; }
int qh_init(const char *path) { int result, old_umask; if (qh_listen_sock >= 0) iobroker_close(nagios_iobs, qh_listen_sock); if (!path) { nm_log(NSLOG_RUNTIME_ERROR, "qh: query_socket is NULL. What voodoo is this?\n"); return ERROR; } old_umask = umask(0117); errno = 0; qh_listen_sock = nsock_unix(path, NSOCK_TCP | NSOCK_UNLINK); umask(old_umask); if (qh_listen_sock < 0) { nm_log(NSLOG_RUNTIME_ERROR, "qh: Failed to init socket '%s'. %s: %s\n", path, nsock_strerror(qh_listen_sock), strerror(errno)); return ERROR; } /* plugins shouldn't have this socket */ (void)fcntl(qh_listen_sock, F_SETFD, FD_CLOEXEC); /* most likely overkill, but it's small, so... */ qh_table = g_hash_table_new_full(g_str_hash, g_str_equal, free, (GDestroyNotify) qh_remove); errno = 0; result = iobroker_register(nagios_iobs, qh_listen_sock, NULL, qh_registration_input); if (result < 0) { g_hash_table_destroy(qh_table); close(qh_listen_sock); nm_log(NSLOG_RUNTIME_ERROR, "qh: Failed to register socket with io broker: %s\n", iobroker_strerror(result)); return ERROR; } nm_log(NSLOG_INFO_MESSAGE, "qh: Socket '%s' successfully initialized\n", path); /* now register our the in-core handlers */ qh_register_handler("command", "Naemon external commands interface", 0, qh_command); qh_register_handler("echo", "The Echo Service - What You Put Is What You Get", 0, qh_echo); qh_register_handler("help", "Help for the query handler", 0, qh_help); return 0; }
static int wproc_destroy(struct wproc_worker *wp, int flags) { int i = 0, force = 0, self; if (!wp) return 0; force = !!(flags & WPROC_FORCE); self = getpid(); /* master retains workers through restarts */ if (self == nagios_pid && !force) return 0; /* free all memory when either forcing or a worker called us */ iocache_destroy(wp->ioc); wp->ioc = NULL; my_free(wp->name); fanout_destroy(wp->jobs, fo_destroy_job); wp->jobs = NULL; /* workers must never control other workers, so they return early */ if (self != nagios_pid) return 0; /* kill(0, SIGKILL) equals suicide, so we avoid it */ if (wp->pid) { kill(wp->pid, SIGKILL); } iobroker_close(nagios_iobs, wp->sd); /* reap our possibly lost children */ while (waitpid(-1, &i, WNOHANG) > 0) ; /* do nothing */ free(wp); return 0; }
static int receive_command(int sd, int events, void *discard) { int ioc_ret; char *buf; unsigned long size; if (!ioc) { ioc = iocache_create(512 * 1024); } ioc_ret = iocache_read(ioc, sd); /* master closed the connection, so we exit */ if (ioc_ret == 0) { iobroker_close(iobs, sd); exit_worker(); } if (ioc_ret < 0) { /* XXX: handle this somehow */ } #if 0 /* debug-volley */ buf = iocache_use_size(ioc, ioc_ret); write(master_sd, buf, ioc_ret); return 0; #endif /* * now loop over all inbound messages in the iocache. * Since KV_TERMINATOR is a nul-byte, they're separated by 3 nuls */ while ((buf = iocache_use_delim(ioc, MSG_DELIM, MSG_DELIM_LEN_RECV, &size))) { struct kvvec *kvv; /* we must copy vars here, as we preserve them for the response */ kvv = buf2kvvec(buf, (unsigned int)size, KV_SEP, PAIR_SEP, KVVEC_COPY); if (kvv) spawn_job(kvv); } return 0; }
static void gather_output(child_process *cp, iobuf *io) { iobuf *other_io; other_io = io == &cp->outstd ? &cp->outerr : &cp->outstd; for (;;) { char buf[4096]; int rd; rd = read(io->fd, buf, sizeof(buf)); if (rd < 0) { if (errno == EINTR) continue; /* XXX: handle the error somehow */ check_completion(cp, WNOHANG); } if (rd) { /* we read some data */ io->buf = realloc(io->buf, rd + io->len + 1); memcpy(&io->buf[io->len], buf, rd); io->len += rd; io->buf[io->len] = '\0'; } else { iobroker_close(iobs, io->fd); io->fd = -1; if (other_io->fd < 0) { check_completion(cp, 0); } else { check_completion(cp, WNOHANG); } } break; } }
int finish_job(child_process *cp, int reason) { static struct kvvec resp = KVVEC_INITIALIZER; struct rusage *ru = &cp->ei->rusage; int i, ret; /* get rid of still open filedescriptors */ if (cp->outstd.fd != -1) { gather_output(cp, &cp->outstd, 1); iobroker_close(iobs, cp->outstd.fd); } if (cp->outerr.fd != -1) { gather_output(cp, &cp->outerr, 1); iobroker_close(iobs, cp->outerr.fd); } /* Make sure network-supplied data doesn't contain nul bytes */ strip_nul_bytes(cp->outstd); strip_nul_bytes(cp->outerr); /* how many key/value pairs do we need? */ if (kvvec_init(&resp, 12 + cp->request->kv_pairs) == NULL) { /* what the hell do we do now? */ exit_worker(1, "Failed to init response key/value vector"); } gettimeofday(&cp->ei->stop, NULL); if (running_jobs != squeue_size(sq)) { wlog("running_jobs(%d) != squeue_size(sq) (%d)\n", running_jobs, squeue_size(sq)); wlog("started: %d; running: %d; finished: %d\n", started, running_jobs, started - running_jobs); } cp->ei->runtime = tv_delta_f(&cp->ei->start, &cp->ei->stop); /* * Now build the return message. * First comes the request, minus environment variables */ for (i = 0; i < cp->request->kv_pairs; i++) { struct key_value *kv = &cp->request->kv[i]; /* skip environment macros */ if (kv->key_len == 3 && !strcmp(kv->key, "env")) { continue; } kvvec_addkv_wlen(&resp, kv->key, kv->key_len, kv->value, kv->value_len); } kvvec_addkv(&resp, "wait_status", mkstr("%d", cp->ret)); kvvec_add_tv(&resp, "start", cp->ei->start); kvvec_add_tv(&resp, "stop", cp->ei->stop); kvvec_addkv(&resp, "runtime", mkstr("%f", cp->ei->runtime)); if (!reason) { /* child exited nicely (or with a signal, so check wait_status) */ kvvec_addkv(&resp, "exited_ok", "1"); kvvec_add_tv(&resp, "ru_utime", ru->ru_utime); kvvec_add_tv(&resp, "ru_stime", ru->ru_stime); kvvec_add_long(&resp, "ru_minflt", ru->ru_minflt); kvvec_add_long(&resp, "ru_majflt", ru->ru_majflt); kvvec_add_long(&resp, "ru_inblock", ru->ru_inblock); kvvec_add_long(&resp, "ru_oublock", ru->ru_oublock); } else { /* some error happened */ kvvec_addkv(&resp, "exited_ok", "0"); kvvec_addkv(&resp, "error_code", mkstr("%d", reason)); } kvvec_addkv_wlen(&resp, "outerr", 6, cp->outerr.buf, cp->outerr.len); kvvec_addkv_wlen(&resp, "outstd", 6, cp->outstd.buf, cp->outstd.len); ret = worker_send_kvvec(master_sd, &resp); if (ret < 0 && errno == EPIPE) exit_worker(1, "Failed to send kvvec struct to master"); return 0; }
static int qh_input(int sd, int events, void *bq_) { nm_bufferqueue *bq = (nm_bufferqueue *)bq_; int result; size_t len; unsigned int query_len = 0; char *buf, *space; struct query_handler *qh; char *handler = NULL, *query = NULL; result = nm_bufferqueue_read(bq, sd); /* disconnect? */ if (result == 0 || (result < 0 && errno == EPIPE)) { nm_bufferqueue_destroy(bq); iobroker_close(nagios_iobs, sd); qh_running--; return 0; } /* * A request looks like this: '[@|#]<qh>[<SP>][<query>]\0'. * That is, optional '#' (oneshot) or '@' (keepalive), * followed by the name of a registered handler, followed by * an optional space and an optional query. If the handler * has no "default" handler, a query is required or an error * will be thrown. */ /* Use data up to the first nul byte */ nm_bufferqueue_unshift_to_delim(bq, "\0", 1, &len, (void **)&buf); if (!buf) return 0; /* Identify handler part and any magic query bytes */ if (*buf == '@' || *buf == '#') { handler = buf + 1; } else { handler = buf; } /* Locate query (if any) */ if ((space = strchr(buf, ' '))) { *space = 0; query = space + 1; query_len = len - ((unsigned long)query - (unsigned long)buf); } else { query = ""; query_len = 0; } /* locate the handler */ if (!(qh = qh_find_handler(handler))) { /* not found. that's a 404 */ nsock_printf(sd, "404: %s: No such handler", handler); nm_free(buf); iobroker_close(nagios_iobs, sd); nm_bufferqueue_destroy(bq); return 0; } /* strip trailing newlines */ while (query_len > 0 && (query[query_len - 1] == 0 || query[query_len - 1] == '\n')) query[--query_len] = 0; /* now pass the query to the handler */ if ((result = qh->handler(sd, query, query_len)) >= 100) { nsock_printf_nul(sd, "%d: %s", result, qh_strerror(result)); } if (result >= 300 || *buf != '@') { /* error code or one-shot query */ nm_free(buf); iobroker_close(nagios_iobs, sd); nm_bufferqueue_destroy(bq); return 0; } nm_free(buf); /* check for magic handler codes */ switch (result) { case QH_CLOSE: /* oneshot handler */ case -1: /* general error */ iobroker_close(nagios_iobs, sd); /* fallthrough */ case QH_TAKEOVER: /* handler takes over */ case 101: /* switch protocol (takeover + message) */ nm_bufferqueue_destroy(bq); break; } return 0; }
/* * Negotiate which socket to use for communication when the remote * host has accepted a connection attempt from us while we have * accepted one from the remote host. We must make sure both ends * agree on one socket to use. */ static int net_negotiate_socket(merlin_node *node, int con, int lis) { struct sockaddr_in lissain, consain; socklen_t slen = sizeof(struct sockaddr_in); linfo("negotiate: Choosing socket for %s %s (%d or %d)", node_type(node), node->name, con, lis); if (con < 0) return lis; if (lis < 0) return con; /* we prefer the socket with the lowest ip-address */ if (getsockname(lis, (struct sockaddr *)&lissain, &slen) < 0) { lerr("negotiate: getsockname(%d, ...) failed: %s", lis, strerror(errno)); return con; } if (getpeername(con, (struct sockaddr *)&consain, &slen) < 0) { lerr("negotiate: getpeername(%d, ...) failed: %s", con, strerror(errno)); return lis; } ldebug("negotiate: lis(%d): %s:%d", lis, inet_ntoa(lissain.sin_addr), ntohs(lissain.sin_port)); ldebug("negotiate: con(%d): %s:%d", con, inet_ntoa(consain.sin_addr), ntohs(consain.sin_port)); if (lissain.sin_addr.s_addr > consain.sin_addr.s_addr) { ldebug("negotiate: con has lowest ip. using that"); return con; } if (consain.sin_addr.s_addr > lissain.sin_addr.s_addr) { ldebug("negotiate: lis has lowest ip. using that"); return lis; } /* * this will happen if multiple merlin instances run * on the same server, such as when we're testing * things. In that case, let the portnumber decide * the tiebreak */ if (lissain.sin_port > consain.sin_port) { ldebug("negotiate: con has lowest port. using that"); return con; } if (consain.sin_port > lissain.sin_port) { ldebug("negotiate: lis has lowest port. using that"); return lis; } ldebug("negotiate: con and lis are equal. killing both"); node->last_conn_attempt_logged = 0; node_disconnect(node, "socket negotiation failed"); iobroker_close(nagios_iobs, lis); node->sock = -1; return -1; }
static int finish_job(child_process *cp, int reason) { static struct kvvec resp = KVVEC_INITIALIZER; struct rusage *ru = &cp->rusage; int i, ret; /* how many key/value pairs do we need? */ if (kvvec_init(&resp, 12 + cp->request->kv_pairs) == NULL) { /* what the hell do we do now? */ exit_worker(); } gettimeofday(&cp->stop, NULL); if (running_jobs != squeue_size(sq)) { wlog("running_jobs(%d) != squeue_size(sq) (%d)\n", running_jobs, squeue_size(sq)); wlog("started: %d; running: %d; finished: %d\n", started, running_jobs, started - running_jobs); } /* * we must remove the job's timeout ticker, * or we'll end up accessing an already free()'d * pointer, or the pointer to a different child. */ squeue_remove(sq, cp->sq_event); /* get rid of still open filedescriptors */ if (cp->outstd.fd != -1) iobroker_close(iobs, cp->outstd.fd); if (cp->outerr.fd != -1) iobroker_close(iobs, cp->outerr.fd); cp->runtime = tv_delta_f(&cp->start, &cp->stop); /* * Now build the return message. * First comes the request, minus environment variables */ for (i = 0; i < cp->request->kv_pairs; i++) { struct key_value *kv = &cp->request->kv[i]; /* skip environment macros */ if (kv->key_len == 3 && !strcmp(kv->key, "env")) { continue; } kvvec_addkv_wlen(&resp, kv->key, kv->key_len, kv->value, kv->value_len); } kvvec_addkv(&resp, "wait_status", (char *)mkstr("%d", cp->ret)); kvvec_addkv_wlen(&resp, "outstd", 6, cp->outstd.buf, cp->outstd.len); kvvec_addkv_wlen(&resp, "outerr", 6, cp->outerr.buf, cp->outerr.len); kvvec_add_tv(&resp, "start", cp->start); kvvec_add_tv(&resp, "stop", cp->stop); kvvec_addkv(&resp, "runtime", (char *)mkstr("%f", cp->runtime)); if (!reason) { /* child exited nicely */ kvvec_addkv(&resp, "exited_ok", "1"); kvvec_add_tv(&resp, "ru_utime", ru->ru_utime); kvvec_add_tv(&resp, "ru_stime", ru->ru_stime); kvvec_add_long(&resp, "ru_minflt", ru->ru_minflt); kvvec_add_long(&resp, "ru_majflt", ru->ru_majflt); kvvec_add_long(&resp, "ru_nswap", ru->ru_nswap); kvvec_add_long(&resp, "ru_inblock", ru->ru_inblock); kvvec_add_long(&resp, "ru_oublock", ru->ru_oublock); kvvec_add_long(&resp, "ru_nsignals", ru->ru_nsignals); } else { /* some error happened */ kvvec_addkv(&resp, "exited_ok", "0"); kvvec_addkv(&resp, "error_code", (char *)mkstr("%d", reason)); } ret = send_kvvec(master_sd, &resp); if (ret < 0 && errno == EPIPE) exit_worker(); running_jobs--; if (cp->outstd.buf) { free(cp->outstd.buf); cp->outstd.buf = NULL; } if (cp->outerr.buf) { free(cp->outerr.buf); cp->outerr.buf = NULL; } kvvec_destroy(cp->request, KVVEC_FREE_ALL); free(cp->cmd); free(cp); return 0; }