/* $Id: child.c,v 1.8 2003-03-13 19:30:19 rjkaes Exp $
 *
 * Handles the creation/destruction of the various children required for
 * processing incoming connections.
 *
 * Copyright (C) 2000 Robert James Kaes (rjkaes@flarenet.com)
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the
 * Free Software Foundation; either version 2, or (at your option) any
 * later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 */

#include "tinyproxy.h"

#include "child.h"
#include "daemon.h"
#include "filter.h"
#include "heap.h"
#include "log.h"
#include "reqs.h"
#include "sock.h"
#include "utils.h"

static int listenfd;
static socklen_t addrlen;

/*
 * Stores the internal data needed for each child (connection)
 */
struct child_s {
	pid_t tid;
	unsigned int connects;
	enum { T_EMPTY, T_WAITING, T_CONNECTED } status;
};

/*
 * A pointer to an array of children. A certain number of children are
 * created when the program is started.
 */
static struct child_s *child_ptr;

static struct child_config_s {
	unsigned int maxclients, maxrequestsperchild;
	unsigned int maxspareservers, minspareservers, startservers;
} child_config;

static int* servers_waiting;	/* servers waiting for a connection */

/*
 * Lock/Unlock the "servers_waiting" variable so that two children cannot
 * modify it at the same time.
 */
#define SERVER_COUNT_LOCK()   _child_lock_wait()
#define SERVER_COUNT_UNLOCK() _child_lock_release()

/* START OF LOCKING SECTION */

/*
 * These variables are required for the locking mechanism.  Also included
 * are the "private" functions for locking/unlocking.
 */
static struct flock lock_it, unlock_it;
static int lock_fd = -1;

static void
_child_lock_init(void)
{
	char lock_file[] = "/tmp/tinyproxy.servers.lock.XXXXXX";

	lock_fd = mkstemp(lock_file);
	unlink(lock_file);

	lock_it.l_type = F_WRLCK;
	lock_it.l_whence = SEEK_SET;
	lock_it.l_start = 0;
	lock_it.l_len = 0;

	unlock_it.l_type = F_UNLCK;
	unlock_it.l_whence = SEEK_SET;
	unlock_it.l_start = 0;
	unlock_it.l_len = 0;
}

static void
_child_lock_wait(void)
{
	int rc;

	while ((rc = fcntl(lock_fd, F_SETLKW, &lock_it)) < 0) {
		if (errno == EINTR)
			continue;
		else
			return;
	}
}

static void
_child_lock_release(void)
{
	if (fcntl(lock_fd, F_SETLKW, &unlock_it) < 0)
		return;
}

/* END OF LOCKING SECTION */

#define SERVER_INC() do { \
    SERVER_COUNT_LOCK(); \
    ++(*servers_waiting); \
    DEBUG2("INC: servers_waiting: %d", *servers_waiting); \
    SERVER_COUNT_UNLOCK(); \
} while (0)

#define SERVER_DEC() do { \
    SERVER_COUNT_LOCK(); \
    --(*servers_waiting); \
    DEBUG2("DEC: servers_waiting: %d", *servers_waiting); \
    SERVER_COUNT_UNLOCK(); \
} while (0)

/*
 * Set the configuration values for the various child related settings.
 */
short int
child_configure(child_config_t type, unsigned int val)
{
	switch (type) {
	case CHILD_MAXCLIENTS:
		child_config.maxclients = val;
		break;
	case CHILD_MAXSPARESERVERS:
		child_config.maxspareservers = val;
		break;
	case CHILD_MINSPARESERVERS:
		child_config.minspareservers = val;
		break;
	case CHILD_STARTSERVERS:
		child_config.startservers = val;
		break;
	case CHILD_MAXREQUESTSPERCHILD:
		child_config.maxrequestsperchild = val;
		break;
	default:
		DEBUG2("Invalid type (%d)", type);
		return -1;
	}

	return 0;
}

/*
 * This is the main (per child) loop.
 */
static void
child_main(struct child_s* ptr)
{
	int connfd;
	struct sockaddr *cliaddr;
	socklen_t clilen;

	cliaddr = safemalloc(addrlen);
	if (!cliaddr) {
		log_message(LOG_CRIT,
			    "Could not allocate memory for child address.");
		exit(0);
	}

	ptr->connects = 0;

	while (!config.quit) {
		ptr->status = T_WAITING;

		clilen = addrlen;

		connfd = accept(listenfd, cliaddr, &clilen);

#ifndef NDEBUG
		/* Pause for 10 seconds to allow us to connect debugger */
		fprintf(stderr, "Process has accepted connection: %u\n", ptr->tid);
		sleep(10);
		fprintf(stderr, "Continuing process: %u\n", ptr->tid);
#endif

		/*
		 * Make sure no error occurred...
		 */
		if (connfd < 0) {
			log_message(LOG_ERR, "Accept returned an error (%s) ... retrying.", strerror(errno));
			continue;
		}

		ptr->status = T_CONNECTED;

		SERVER_DEC();

		handle_connection(connfd);

		if (child_config.maxrequestsperchild != 0) {
			ptr->connects++;

			DEBUG2("%u connections so far...", ptr->connects);

			if (ptr->connects == child_config.maxrequestsperchild - 1) {
				log_message(LOG_NOTICE,
					    "Child has reached MaxRequestsPerChild (%u). Killing child.",
					    ptr->connects);
				break;
			}
		}

		SERVER_COUNT_LOCK();
		if (*servers_waiting > child_config.maxspareservers) {
			/*
			 * There are too many spare children, kill ourself
			 * off.
			 */
			log_message(LOG_NOTICE,
				    "Waiting servers (%d) exceeds MaxSpareServers (%d). Killing child.",
				    *servers_waiting, child_config.maxspareservers);
			SERVER_COUNT_UNLOCK();

			break;
		} else {
			SERVER_COUNT_UNLOCK();
		}

		SERVER_INC();
	}

	ptr->status = T_EMPTY;

	safefree(cliaddr);
	exit(0);
}

/*
 * Fork a child "child" (or in our case a process) and then start up the
 * child_main() function.
 */
static int
child_make(struct child_s* ptr)
{
	pid_t pid;
       
	if ((pid = fork()) > 0)
		return pid;	/* parent */

	/*
	 * Reset the SIGNALS so that the child can be reaped.
	 */
	set_signal_handler(SIGCHLD, SIG_DFL);
	set_signal_handler(SIGTERM, SIG_DFL);
	set_signal_handler(SIGHUP, SIG_DFL);

	child_main(ptr); /* never returns */
	return -1;
}

/*
 * Create a pool of children to handle incoming connections
 */
short int
child_pool_create(void)
{
	unsigned int i;

	/*
	 * Make sure the number of MaxClients is not zero, since this
	 * variable determines the size of the array created for children
	 * later on.
	 */
	if (child_config.maxclients == 0) {
		log_message(LOG_ERR,
			    "child_pool_create: \"MaxClients\" must be greater than zero.");
		return -1;
	}
	if (child_config.startservers == 0) {
		log_message(LOG_ERR,
			    "child_pool_create: \"StartServers\" must be greater than zero.");
		return -1;
	}

	child_ptr = calloc_shared_memory(child_config.maxclients,
					 sizeof(struct child_s));
	if (!child_ptr) {
		log_message(LOG_ERR, "Could not allocate memory for children.");
		return -1;
	}

	servers_waiting = malloc_shared_memory(sizeof(int));
	if (servers_waiting == MAP_FAILED) {
		log_message(LOG_ERR, "Could not allocate memory for child counting.");
		return -1;
	}
	*servers_waiting = 0;

	/*
	 * Create a "locking" file for use around the servers_waiting
	 * variable.
	 */
	_child_lock_init();

	if (child_config.startservers > child_config.maxclients) {
		log_message(LOG_WARNING,
			    "Can not start more than \"MaxClients\" servers. Starting %u servers instead.",
			    child_config.maxclients);
		child_config.startservers = child_config.maxclients;
	}

	for (i = 0; i < child_config.maxclients; i++) {
		child_ptr[i].status = T_EMPTY;
		child_ptr[i].connects = 0;
	}

	for (i = 0; i < child_config.startservers; i++) {
		DEBUG2("Trying to create child %d of %d", i + 1, child_config.startservers);
		child_ptr[i].status = T_WAITING;
		child_ptr[i].tid = child_make(&child_ptr[i]);

		if (child_ptr[i].tid < 0) {
			log_message(LOG_WARNING,
				    "Could not create child number %d of %d",
				    i, child_config.startservers);
			return -1;
		} else {
			log_message(LOG_INFO,
				    "Creating child number %d of %d ...",
				    i + 1, child_config.startservers);

			SERVER_INC();
		}
	}

	log_message(LOG_INFO, "Finished creating all children.");

	return 0;
}

/*
 * Keep the proper number of servers running. This is the birth of the
 * servers. It monitors this at least once a second.
 */
void
child_main_loop(void)
{
	int i;

	while (1) {
		if (config.quit)
			return;

		/* If there are not enough spare servers, create more */
		SERVER_COUNT_LOCK();
		if (*servers_waiting < child_config.minspareservers) {
			log_message(LOG_NOTICE,
				    "Waiting servers (%d) is less than MinSpareServers (%d). Creating new child.",
				    *servers_waiting, child_config.minspareservers);

			SERVER_COUNT_UNLOCK();

			for (i = 0; i < child_config.maxclients; i++) {
				if (child_ptr[i].status == T_EMPTY) {
					child_ptr[i].status = T_WAITING;
					child_ptr[i].tid = child_make(&child_ptr[i]);
					if (child_ptr[i].tid < 0) {
						log_message(LOG_NOTICE,
							    "Could not create child");

						child_ptr[i].status = T_EMPTY;
						break;
					}

					SERVER_INC();

					break;
				}
			}
		} else {
			SERVER_COUNT_UNLOCK();
		}

		sleep(5);

		/* Handle log rotation if it was requested */
		if (received_sighup) {
			truncate_log_file();

#ifdef FILTER_ENABLE
			if (config.filter) {
				filter_destroy();
				filter_init();
			}
	log_message(LOG_NOTICE, "Re-reading filter file.");
#endif				/* FILTER_ENABLE */

			received_sighup = FALSE;
		}
	}
}

/*
 * Go through all the non-empty children and cancel them.
 */
void
child_kill_children(void)
{
	int i;
	
	for (i = 0; i < child_config.maxclients; i++) {
		if (child_ptr[i].status != T_EMPTY)
			kill(child_ptr[i].tid, SIGTERM);
	}
}

int
child_listening_sock(uint16_t port)
{
	listenfd = listen_sock(port, &addrlen);
	return listenfd;
}

void
child_close_sock(void)
{
	close(listenfd);
}