[BACK]Return to cvsd.c CVS log [TXT][DIR] Up to [local] / src / usr.bin / cvs

File: [local] / src / usr.bin / cvs / Attic / cvsd.c (download)

Revision 1.14, Tue Dec 7 17:10:56 2004 UTC (19 years, 5 months ago) by tedu
Branch: MAIN
Changes since 1.13: +12 -25 lines

less whitespace, more pretty.  ok jfb

/*	$OpenBSD: cvsd.c,v 1.14 2004/12/07 17:10:56 tedu Exp $	*/
/*
 * Copyright (c) 2004 Jean-Francois Brousseau <jfb@openbsd.org>
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. The name of the author may not be used to endorse or promote products
 *    derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
 * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
 * THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL  DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include <sys/param.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <sys/uio.h>

#include <err.h>
#include <pwd.h>
#include <grp.h>
#include <poll.h>
#include <fcntl.h>
#include <dirent.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include <errno.h>
#include <string.h>
#include <sysexits.h>

#include "log.h"
#include "sock.h"
#include "cvs.h"
#include "cvsd.h"


static void  cvsd_parent_loop (void);
static void  cvsd_child_main  (void);
static int   cvsd_privdrop    (void);
static void  cvsd_report      (void);


extern char *__progname;


int    cvsd_fg = 0;
uid_t  cvsd_uid = -1;
gid_t  cvsd_gid = -1;

volatile sig_atomic_t cvsd_running = 1;
volatile sig_atomic_t cvsd_restart = 0;

static char  *cvsd_user = NULL;
static char  *cvsd_group = NULL;
static char  *cvsd_root = NULL;
static char  *cvsd_conffile = CVSD_CONF;
static char  *cvsd_moddir = NULL;
static int    cvsd_privfd = -1;


static TAILQ_HEAD(,cvsd_child) cvsd_children;
static volatile sig_atomic_t   cvsd_chnum = 0;
static volatile sig_atomic_t   cvsd_chmin = CVSD_CHILD_DEFMIN;
static volatile sig_atomic_t   cvsd_chmax = CVSD_CHILD_DEFMAX;
static volatile sig_atomic_t   cvsd_sigchld = 0;
static volatile sig_atomic_t   cvsd_siginfo = 0;


void   usage         (void);
void   cvsd_sighdlr  (int);
int    cvsd_msghdlr  (struct cvsd_child *, int);


/*
 * cvsd_sighdlr()
 *
 * Generic signal handler.
 */
void
cvsd_sighdlr(int signo)
{
	switch (signo) {
	case SIGHUP:
		cvsd_restart = 1;
		break;
	case SIGCHLD:
		cvsd_sigchld = 1;
		break;
	case SIGINT:
	case SIGTERM:
	case SIGQUIT:
		cvsd_running = 0;
		break;
	case SIGINFO:
		cvsd_siginfo = 1;
		break;
	}
}


/*
 * usage()
 *
 * Display program usage.
 */
void
usage(void)
{
	fprintf(stderr,
	    "Usage: %s [-dfhpv] [-c config] [-g group] [-r root] "
	    "[-s path] [-u user]\n"
	    "\t-c config\tUse <config> as the configuration file\n"
	    "\t-d\t\tStart the server in debugging mode (very verbose)\n"
	    "\t-f\t\tStay in foreground instead of becoming a daemon\n"
	    "\t-g group\tUse group <group> for privilege revocation\n"
	    "\t-h\t\tPrint the usage and exit\n"
	    "\t-p\t\tPerform repository sanity check on startup\n"
	    "\t-r root\t\tUse <root> as the root directory of the repository\n"
	    "\t-s path\t\tUse <path> as the path for the CVS server socket\n"
	    "\t-u user\tUse user <user> for privilege revocation\n"
	    "\t-v\t\tBe verbose\n",
	    __progname);
}


int
main(int argc, char **argv)
{
	u_int i;
	int ret, checkrepo;
	struct passwd *pwd;
	struct group *grp;

	checkrepo = 0;
	cvsd_set(CVSD_SET_USER, CVSD_USER);
	cvsd_set(CVSD_SET_GROUP, CVSD_GROUP);

	if (cvs_log_init(LD_STD|LD_SYSLOG, LF_PID) < 0)
		err(1, "failed to initialize logging mechanism");

	while ((ret = getopt(argc, argv, "a:c:dfhpr:s:v")) != -1) {
		switch (ret) {
		case 'c':
			cvsd_conffile = optarg;
			break;
		case 'd':
			cvs_log_filter(LP_FILTER_UNSET, LP_DEBUG);
			cvs_log_filter(LP_FILTER_UNSET, LP_INFO);
			break;
		case 'f':
			cvsd_fg = 1;
			break;
		case 'g':
			cvsd_set(CVSD_SET_GROUP, optarg);
			break;
		case 'h':
			usage();
			exit(0);
			/* NOTREACHED */
			break;
		case 'p':
			checkrepo = 1;
			break;
		case 'r':
			cvsd_root = optarg;
			break;
		case 's':
			cvsd_sock_path = optarg;
			break;
		case 'u':
			cvsd_set(CVSD_SET_USER, optarg);
			break;
		case 'v':
			cvs_log_filter(LP_FILTER_UNSET, LP_INFO);
			break;
		default:
			usage();
			exit(EX_USAGE);
		}
	}

	argc -= optind;
	argv += optind;

	if (cvs_conf_read(cvsd_conffile) < 0)
		errx(1, "error parsing configuration file `%s'", cvsd_conffile);

	if (cvsd_root == NULL)
		errx(1, "no CVS root directory specified");

	if (argc > 0)
		errx(EX_USAGE, "unrecognized trailing arguments");

	TAILQ_INIT(&cvsd_children);

	pwd = getpwnam(cvsd_user);
	if (pwd == NULL)
		err(EX_NOUSER, "failed to get user `%s'", cvsd_user);

	grp = getgrnam(cvsd_group);
	if (grp == NULL)
		err(EX_NOUSER, "failed to get group `%s'", cvsd_group);

	cvsd_uid = pwd->pw_uid;
	cvsd_gid = grp->gr_gid;

	signal(SIGHUP, cvsd_sighdlr);
	signal(SIGINT, cvsd_sighdlr);
	signal(SIGQUIT, cvsd_sighdlr);
	signal(SIGTERM, cvsd_sighdlr);
	signal(SIGCHLD, cvsd_sighdlr);
	signal(SIGPIPE, SIG_IGN);

	if (!cvsd_fg && daemon(0, 0) == -1) {
		cvs_log(LP_ERRNO, "failed to become a daemon");
		exit(EX_OSERR);
	}

	if (cvsd_sock_open() < 0) {
		exit(1);
	}

	if (setegid(cvsd_gid) == -1) {
		cvs_log(LP_ERRNO, "failed to drop group privileges");
		exit(EX_OSERR);
	}
	if (seteuid(cvsd_uid) == -1) {
		cvs_log(LP_ERRNO, "failed to drop user privileges");
		exit(EX_OSERR);
	}

	if (checkrepo && cvsd_checkperms("/") != 0) {
		cvs_log(LP_ERR,
		    "exiting due to permission errors on repository");
		exit(1);
	}

	/* spawn the initial pool of children */
	for (i = 0; i < (u_int)cvsd_chmin; i++)
		if (cvsd_child_fork(NULL) < 0)
			exit(EX_OSERR);

	signal(SIGINFO, cvsd_sighdlr);
	cvsd_parent_loop();

	cvs_log(LP_NOTICE, "shutting down");
	cvs_log_cleanup();

	cvsd_sock_close();

	return (0);
}


/*
 * cvsd_privdrop()
 *
 * Drop privileges.
 */
int
cvsd_privdrop(void)
{
	cvs_log(LP_INFO, "dropping privileges to %s[%d]:%s[%d]",
	    cvsd_user, cvsd_uid, cvsd_group, cvsd_gid);
	if (setgid(cvsd_gid) == -1) {
		cvs_log(LP_ERRNO, "failed to drop group privileges to %s",
		    CVSD_GROUP);
		return (-1);
	}

	if (setuid(cvsd_uid) == -1) {
		cvs_log(LP_ERRNO, "failed to drop user privileges to %s",
		    CVSD_USER);
		return (-1);
	}

	return (0);
}


/*
 * cvsd_checkperms()
 *
 * Check permissions on the CVS repository and log warnings for any
 * weird of loose permissions.
 * Returns the number of warnings on success, or -1 on failure.
 */
int
cvsd_checkperms(const char *path)
{
	int fd, nbwarn, ret;
	mode_t fmode;
	long base;
	void *dp, *endp;
	char buf[1024], spath[MAXPATHLEN];
	struct stat st;
	struct dirent *dep;

	nbwarn = 0;

	cvs_log(LP_DEBUG, "checking permissions on `%s'", path);

	if (stat(path, &st) == -1) {
		cvs_log(LP_ERRNO, "failed to stat `%s'", path);
		return (-1);
	}

	if (S_ISDIR(st.st_mode))
		fmode = CVSD_DPERM;
	else
		fmode = CVSD_FPERM;

	if (st.st_uid != cvsd_uid) {
		cvs_log(LP_WARN, "owner of `%s' is not %s", path, CVSD_USER);
		nbwarn++;
	}

	if (st.st_gid != cvsd_gid) {
		cvs_log(LP_WARN, "group of `%s' is not %s", path, CVSD_GROUP);
		nbwarn++;
	}

	if (st.st_mode & S_IWGRP) {
		cvs_log(LP_WARN, "file `%s' is group-writable", path,
		    fmode);
		nbwarn++;
	}

	if (st.st_mode & S_IWOTH) {
		cvs_log(LP_WARN, "file `%s' is world-writable", path,
		    fmode);
		nbwarn++;
	}

	if (S_ISDIR(st.st_mode)) {
		fd = open(path, O_RDONLY, 0);
		if (fd == -1) {
			cvs_log(LP_ERRNO, "failed to open directory `%s'",
			    path);
			return (nbwarn);
		}
		/* recurse */
		ret = getdirentries(fd, buf, sizeof(buf), &base);
		if (ret == -1) {
			cvs_log(LP_ERRNO,
			    "failed to get directory entries for `%s'", path);
			(void)close(fd);
			return (nbwarn);
		}

		dp = buf;
		endp = buf + ret;

		while (dp < endp) {
			dep = (struct dirent *)dp;
			dp += dep->d_reclen;

			if ((dep->d_namlen == 1) && (dep->d_name[0] == '.'))
				continue;
			if ((dep->d_namlen == 2) && (dep->d_name[0] == '.') &&
			    (dep->d_name[1] == '.'))
				continue;

			/* skip the CVSROOT directory */
			if (strcmp(dep->d_name, CVS_PATH_ROOT) == 0)
				continue;

			snprintf(spath, sizeof(spath), "%s/%s", path,
			    dep->d_name);
			ret = cvsd_checkperms(spath);
			if (ret == -1)
				nbwarn++;
			else
				nbwarn += ret;
		}
		(void)close(fd);
	}


	return (nbwarn);
}


/*
 * cvsd_child_fork()
 *
 * Fork a child process which chroots to the CVS repository's root directory.
 * If the <chpp> argument is not NULL, a reference to the newly created child
 * structure will be returned.
 * On success, returns 0 in the child process context, 1 in the parent's
 * context, or -1 on failure.
 */
int
cvsd_child_fork(struct cvsd_child **chpp)
{
	int svec[2];
	pid_t pid;
	struct cvsd_child *chp;

	if (cvsd_chnum == cvsd_chmax) {
		cvs_log(LP_WARN, "child pool reached limit of processes");
		return (-1);
	}

	if (socketpair(AF_LOCAL, SOCK_STREAM, PF_UNSPEC, svec) == -1) {
		cvs_log(LP_ERRNO, "failed to create socket pair");
		return (-1);
	}

	/*
	 * We need to temporarily regain original privileges in order for the
	 * child to chroot().
	 */
	if (seteuid(0) == -1) {
		cvs_log(LP_ERRNO, "failed to regain privileges");
		return (-1);
	}

	pid = fork();
	if (pid == -1) {
		cvs_log(LP_ERRNO, "failed to fork child");
		(void)close(svec[0]);
		(void)close(svec[1]);
		return (-1);
	}

	if (pid == 0) {
		cvsd_privfd = svec[1];
		(void)close(svec[0]);

		cvsd_child_main();
		/* NOTREACHED */
	}

	cvs_log(LP_INFO, "spawning child %d", pid);

	if (seteuid(cvsd_uid) == -1) {
		cvs_log(LP_ERRNO, "failed to redrop privs");
		return (-1);
	}

	chp = (struct cvsd_child *)malloc(sizeof(*chp));
	if (chp == NULL) {
		cvs_log(LP_ERRNO, "failed to allocate child data");
		return (-1);
	}

	chp->ch_pid = pid;
	chp->ch_sock = svec[0];
	chp->ch_state = CVSD_ST_IDLE;

	signal(SIGCHLD, SIG_IGN);
	TAILQ_INSERT_TAIL(&cvsd_children, chp, ch_list);
	cvsd_chnum++;
	signal(SIGCHLD, cvsd_sighdlr);

	if (chpp != NULL)
		*chpp = chp;
	(void)close(svec[1]);
	return (1);
}


/*
 * cvsd_child_reap()
 *
 * Wait for a child's status and perform the proper actions depending on it.
 * If the child has exited or has been terminated by a signal, it will be
 * removed from the list and new children will be created until the pool has
 * at least <cvsd_chmin> children in it.
 * Returns 0 on success, or -1 on failure.
 */
int
cvsd_child_reap(void)
{
	pid_t pid;
	int status;
	struct cvsd_child *ch;

	pid = wait(&status);
	if (pid == -1) {
		cvs_log(LP_ERRNO, "failed to wait for child");
		return (-1);
	}

	TAILQ_FOREACH(ch, &cvsd_children, ch_list) {
		if (ch->ch_pid == pid) {
			if (WIFEXITED(status)) {
				cvs_log(LP_WARN,
				    "child %d exited with status %d",
				    pid, WEXITSTATUS(status));
			} else if (WIFSIGNALED(status)) {
				cvs_log(LP_WARN,
				    "child %d terminated with signal %d",
				    pid, WTERMSIG(status));
			} else {
				cvs_log(LP_ERR, "HOLY SHIT!");
			}

			signal(SIGCHLD, SIG_IGN);
			TAILQ_REMOVE(&cvsd_children, ch, ch_list);
			cvsd_chnum--;
			signal(SIGCHLD, cvsd_sighdlr);

			break;
		}
	}

	while (cvsd_chnum < cvsd_chmin)
		cvsd_child_fork(NULL);

	return (0);
}


/*
 * cvsd_child_get()
 *
 * Find a child process in idle state and return a pointer to the child's
 * structure.  If there are no available child processes, a new one will be
 * created unless the number of children has attained the maximum, in which
 * case NULL is returned.
 */
struct cvsd_child*
cvsd_child_get(void)
{
	struct cvsd_child *chp;

	TAILQ_FOREACH(chp, &cvsd_children, ch_list)
		if (chp->ch_state == CVSD_ST_IDLE)
			return (chp);

	/* no available child, attempt to fork a new one */
	chp = NULL;
	if ((cvsd_chnum < cvsd_chmax) && (cvsd_child_fork(&chp) < 0))
		return (NULL);

	return (chp);
}



/*
 * cvsd_parent_loop()
 *
 * Main loop of the parent cvsd process, which listens on its end of the
 * local socket for requests from the cvs(1) program and on any outstanding
 * messages from the children.
 */
static void
cvsd_parent_loop(void)
{
	int cfd, timeout, ret;
	nfds_t nfds, i;
	struct pollfd *pfd;
	struct cvsd_child *chp;

	nfds = 0;
	timeout = INFTIM;
	pfd = NULL;

	for (;;) {
		if (!cvsd_running)
			break;

		if (cvsd_restart) {
			/* restart server */
		}

		if (cvsd_sigchld) {
			cvsd_sigchld = 0;
			cvsd_child_reap();
		}
		if (cvsd_siginfo) {
			cvsd_siginfo = 0;
			cvsd_report();
		}

		nfds = cvsd_chnum + 1;
		pfd = (struct pollfd *)realloc(pfd,
		    nfds * sizeof(struct pollfd));
		if (pfd == NULL) {
			cvs_log(LP_ERRNO, "failed to reallocate polling data");
			return;
		}

		pfd[0].fd = cvsd_sock;
		pfd[0].events = POLLIN;
		pfd[0].revents = 0;
		i = 1;
		TAILQ_FOREACH(chp, &cvsd_children, ch_list) {
			pfd[i].fd = chp->ch_sock;
			pfd[i].events = POLLIN;
			pfd[i].revents = 0;
			i++;

			if (i == nfds)   /* just a precaution */
				break;
		}

		ret = poll(pfd, nfds, timeout);
		if (ret == -1) {
			if (errno == EINTR)
				continue;
			cvs_log(LP_ERRNO, "poll error");
			break;
		}

		if (pfd[0].revents & (POLLERR|POLLNVAL)) {
			cvs_log(LP_ERR, "poll error on request socket");
		} else if (pfd[0].revents & POLLIN) {
			uid_t uid;
			gid_t gid;

			cfd = cvsd_sock_accept(pfd[0].fd);
			if (cfd == -1)
			chp = cvsd_child_get();
			if (chp == NULL) {
				cvs_log(LP_ALERT,
				    "request queue not implemented");
				break;
			}

			if (getpeereid(cfd, &uid, &gid) < 0)
				err(1, "failed to get UID");
			if (cvsd_sendmsg(chp->ch_sock, CVSD_MSG_PASSFD,
			    &cfd, sizeof(cfd)) < 0)
				break;

			/* mark the child as busy */
			chp->ch_state = CVSD_ST_BUSY;
		}

		chp = TAILQ_FIRST(&cvsd_children);
		for (i = 1; i < nfds; i++) {
			if (pfd[i].revents & (POLLERR|POLLNVAL)) {
				cvs_log(LP_ERR,
				    "poll error on child socket (PID %d)",
				    chp->ch_pid);
			} else if (pfd[i].revents & POLLIN)
				cvsd_msghdlr(chp, pfd[i].fd);

			chp = TAILQ_NEXT(chp, ch_list);
		}

	}

	/* broadcast a shutdown message to children */
	TAILQ_FOREACH(chp, &cvsd_children, ch_list) {
		(void)cvsd_sendmsg(chp->ch_sock, CVSD_MSG_SHUTDOWN, NULL, 0);
	}
}


/*
 * cvsd_child_main()
 *
 */
static void
cvsd_child_main(void)
{
	int ret, timeout;
	u_int mtype;
	size_t mlen;
	char mbuf[CVSD_MSG_MAXLEN];
	struct pollfd pfd[1];
	struct cvsd_sess *sessp;

	cvs_log(LP_INFO, "changing root to %s", cvsd_root);
	if (chroot(cvsd_root) == -1) {
		cvs_log(LP_ERRNO, "failed to chroot to `%s'", cvsd_root);
		exit(EX_OSERR);
	}
	(void)chdir("/");

	if (cvsd_privdrop() < 0)
		exit(EX_OSERR);

	setproctitle("%s [child %d]", __progname, getpid());

	pfd[0].fd = cvsd_privfd;
	pfd[0].events = POLLIN;
	timeout = INFTIM;
	sessp = NULL;

	while (cvsd_running) {
		ret = poll(pfd, 1, timeout);
		if (ret == -1) {
			if (errno == EINTR)
				continue;
			cvs_log(LP_ERRNO, "poll error");
			break;
		} else if (ret == 0)
			continue;

		if (pfd[0].revents & (POLLERR|POLLNVAL)) {
			cvs_log(LP_ERR, "poll error");
			break;
		}

		mlen = sizeof(mbuf);
		ret = cvsd_recvmsg(pfd[0].fd, &mtype, mbuf, &mlen);
		if (ret == -1) {
			continue;
		} else if (ret == 0)
			break;

		switch (mtype) {
		case CVSD_MSG_PASSFD:
			sessp = cvsd_sess_alloc(*(int *)mbuf);
			break;
		case CVSD_MSG_SHUTDOWN:
			cvsd_running = 0;
			break;
		default:
			cvs_log(LP_ERR,
			    "unexpected message type %u from parent", mtype);
			break;
		}

	}

	exit(0);
}


/*
 * cvsd_msghdlr()
 *
 * Handler for messages received from child processes.
 * Returns 0 on success, or -1 on failure.
 */
int
cvsd_msghdlr(struct cvsd_child *child, int fd)
{
	uid_t uid;
	ssize_t ret;
	char rbuf[CVSD_MSG_MAXLEN];
	struct group *gr;
	struct passwd *pw;
	struct iovec iov[2];
	struct cvsd_msg msg;

	ret = read(fd, &msg, sizeof(msg));
	if (ret == -1) {
		cvs_log(LP_ERRNO, "failed to read CVS message");
		return (-1);
	} else if (ret == 0) {
		cvs_log(LP_WARN, "child closed socket pair");
		return (0);
	}

	if (msg.cm_len > 0) {
		ret = read(fd, rbuf, msg.cm_len);
		if (ret != (ssize_t)msg.cm_len) {
			cvs_log(LP_ERR, "failed to read entire msg");
			return (-1);
		}
	}

	/* setup the I/O vector for the reply */
	iov[0].iov_base = &msg;
	iov[0].iov_len = sizeof(msg);

	msg.cm_type = CVSD_MSG_ERROR;
	msg.cm_len = 0;

	switch (msg.cm_type) {
	case CVSD_MSG_GETUID:
		rbuf[ret] = '\0';
		cvs_log(LP_INFO, "getting UID for `%s'", rbuf);

		pw = getpwnam(rbuf);
		if (pw != NULL) {
			msg.cm_type = CVSD_MSG_UID;
			msg.cm_len = sizeof(uid_t);
			iov[1].iov_len = msg.cm_len;
			iov[1].iov_base = &(pw->pw_uid);
		}
		break;
	case CVSD_MSG_GETUNAME:
		memcpy(&uid, rbuf, sizeof(uid));
		cvs_log(LP_INFO, "getting username for UID %u", uid);
		pw = getpwuid(uid);
		if (pw != NULL) {
			msg.cm_type = CVSD_MSG_UNAME;
			msg.cm_len = strlen(pw->pw_name);
			iov[1].iov_len = msg.cm_len;
			iov[1].iov_base = pw->pw_name;
		}
		break;
	case CVSD_MSG_GETGID:
		rbuf[ret] = '\0';
		cvs_log(LP_INFO, "getting GID for `%s'", rbuf);

		gr = getgrnam(rbuf);
		if (gr != NULL) {
			msg.cm_type = CVSD_MSG_GID;
			msg.cm_len = sizeof(gid_t);
			iov[1].iov_len = msg.cm_len;
			iov[1].iov_base = &(gr->gr_gid);
		}
		break;
	case CVSD_MSG_SETIDLE:
		child->ch_state = CVSD_ST_IDLE;
		break;
	default:
		cvs_log(LP_ERR, "unknown command type %u", msg.cm_type);
		return (-1);
	}

	ret = writev(fd, iov, 2);

	return (ret);
}


/*
 * cvsd_set()
 *
 * Generic interface to set some of the parameters of the cvs server.
 * When a string is set using cvsd_set(), the original string is copied into
 * a new buffer.
 * Returns 0 on success, or -1 on failure.
 */
int
cvsd_set(int what, ...)
{
	char *str;
	int error = 0;
	va_list vap;

	str = NULL;

	va_start(vap, what);

	if ((what == CVSD_SET_ROOT) || (what == CVSD_SET_SOCK) ||
	    (what == CVSD_SET_USER) || (what == CVSD_SET_GROUP) ||
	    (what == CVSD_SET_MODDIR)) {
		str = strdup(va_arg(vap, char *));
		if (str == NULL) {
			cvs_log(LP_ERRNO, "failed to set string");
			va_end(vap);
			return (-1);
		}
	}

	switch (what) {
	case CVSD_SET_ROOT:
		if (cvsd_root != NULL)
			free(cvsd_root);
		cvsd_root = str;
		break;
	case CVSD_SET_SOCK:
		if (cvsd_sock_path != NULL)
			free(cvsd_sock_path);
		cvsd_sock_path = str;
		error = cvsd_sock_open();
		break;
	case CVSD_SET_USER:
		if (cvsd_user != NULL)
			free(cvsd_user);
		cvsd_user = str;
		break;
	case CVSD_SET_GROUP:
		if (cvsd_group != NULL)
			free(cvsd_group);
		cvsd_group = str;
		break;
	case CVSD_SET_MODDIR:
		if (cvsd_moddir != NULL)
			free(cvsd_moddir);
		cvsd_moddir = str;
		break;
	case CVSD_SET_CHMIN:
		cvsd_chmin = va_arg(vap, int);
		/* we should increase the number of children accordingly */
		break;
	case CVSD_SET_CHMAX:
		cvsd_chmax = va_arg(vap, int);
		/* we should decrease the number of children accordingly */
		break;
	case CVSD_SET_ADDR:
		/* this is more like an add than a set */
		break;
	default:
		cvs_log(LP_ERR, "invalid field to set");
		error = -1;
		break;
	}

	va_end(vap);

	return (error);
}


/*
 * cvsd_report()
 */
static void
cvsd_report(void)
{
	u_int nb_idle, nb_busy, nb_unknown;
	struct cvsd_child *ch;

	nb_idle = 0;
	nb_busy = 0;
	nb_unknown = 0;

	signal(SIGCHLD, SIG_IGN);
	TAILQ_FOREACH(ch, &cvsd_children, ch_list) {
		if (ch->ch_state == CVSD_ST_IDLE)
			nb_idle++;
		else if (ch->ch_state == CVSD_ST_BUSY)
			nb_busy++;
		else if (ch->ch_state == CVSD_ST_UNKNOWN)
			nb_unknown++;
	}

	cvs_log(LP_WARN, "%u children, %u idle, %u busy, %u unknown",
	    cvsd_chnum, nb_idle, nb_busy, nb_unknown);

	TAILQ_FOREACH(ch, &cvsd_children, ch_list)
		cvs_log(LP_WARN, "");
	signal(SIGCHLD, cvsd_sighdlr);
}