Exemple #1
0
Fichier : net.c Projet : op5/merlin
/*
 * 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;
}
Exemple #2
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;
}
Exemple #3
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;
}
Exemple #4
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;
}
Exemple #5
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;
}
Exemple #6
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;
}
Exemple #7
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;
}
Exemple #8
0
Fichier : net.c Projet : op5/merlin
/* 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;
}
Exemple #9
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;
}
Exemple #10
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;
}
Exemple #11
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;
}
Exemple #12
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;
}
Exemple #13
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;
	}
}
Exemple #14
0
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;
}
Exemple #15
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;
}
Exemple #16
0
Fichier : net.c Projet : op5/merlin
/*
 * 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;
}
Exemple #17
0
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;
}