/* $Id$ */

/*
 * Copyright (c) 2006 Nicholas Marriott <nicholas.marriott@gmail.com>
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
 * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
 * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

#include <sys/types.h>
#include <sys/socket.h>
#include <sys/wait.h>

#include <unistd.h>

#include "fdm.h"

void	child_sighandler(int);

void
child_sighandler(int sig)
{
	switch (sig) {
#ifdef SIGINFO
	case SIGINFO:
#endif
	case SIGUSR1:
		sigusr1 = 1;
		break;
	case SIGCHLD:
		break;
	case SIGTERM:
		cleanup_purge();
		_exit(1);
	}
}

int
child_fork(void)
{
	pid_t		 pid;
	struct sigaction act;

	switch (pid = fork()) {
	case -1:
		fatal("fork failed");
	case 0:
		cleanup_flush();

		sigemptyset(&act.sa_mask);
#ifdef SIGINFO
		sigaddset(&act.sa_mask, SIGINFO);
#endif
		sigaddset(&act.sa_mask, SIGUSR1);
		sigaddset(&act.sa_mask, SIGINT);
		sigaddset(&act.sa_mask, SIGTERM);
		sigaddset(&act.sa_mask, SIGCHLD);
		act.sa_flags = SA_RESTART;

		act.sa_handler = SIG_IGN;
		if (sigaction(SIGINT, &act, NULL) < 0)
			fatal("sigaction failed");

		act.sa_handler = child_sighandler;
#ifdef SIGINFO
		if (sigaction(SIGINFO, &act, NULL) < 0)
			fatal("sigaction failed");
#endif
		if (sigaction(SIGUSR1, &act, NULL) < 0)
			fatal("sigaction failed");
		if (sigaction(SIGTERM, &act, NULL) < 0)
			fatal("sigaction failed");
		if (sigaction(SIGCHLD, &act, NULL) < 0)
			fatal("sigaction failed");

		return (0);
	default:
		return (pid);
	}
}

__dead void
child_exit(int status)
{
	cleanup_check();
	_exit(status);
}

struct child *
child_start(struct children *children, uid_t uid, gid_t gid,
    int (*start)(struct child *, struct io *),
    int (*msg)(struct child *, struct msg *, struct msgbuf *),
    void *data, struct child *parent)
{
	struct child	*child, *childp;
	int		 fds[2], n;
	u_int		 i;
	struct io	*io;

	if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, fds) != 0)
		fatal("socketpair failed");

	child = xcalloc(1, sizeof *child);
	child->io = io_create(fds[0], NULL, IO_CRLF);
	child->data = data;
	child->msg = msg;
	child->parent = parent;

	if ((child->pid = child_fork()) == 0) {
		for (i = 0; i < ARRAY_LENGTH(children); i++) {
			childp = ARRAY_ITEM(children, i);
			if (childp->io != NULL) {
				io_close(childp->io);
				io_free(childp->io);
			}
		}
		io_close(child->io);
		io_free(child->io);

		if (geteuid() == 0)
			dropto(uid, gid);

		io = io_create(fds[1], NULL, IO_LF);
		n = start(child, io);
		io_close(io);
		io_free(io);

		child_exit(n);
	}
	close(fds[1]);

	ARRAY_ADD(children, child);
	return (child);
}
