tinyproxy/src/child.c
2009-08-07 03:42:53 +05:30

489 lines
12 KiB
C

/* tinyproxy - A fast light-weight HTTP proxy
* Copyright (C) 2000 Robert James Kaes <rjkaes@users.sourceforge.net>
*
* 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 of the License, 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.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
/* Handles the creation/destruction of the various children required for
* processing incoming connections.
*/
#include "main.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)
*/
enum child_status_t
{ T_EMPTY, T_WAITING, T_CONNECTED };
struct child_s
{
pid_t tid;
unsigned int connects;
enum child_status_t 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 unsigned 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";
/* Only allow u+rw bits. This may be required for some versions
* of glibc so that mkstemp() doesn't make us vulnerable.
*/
umask (0177);
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(); \
assert(*servers_waiting > 0); \
--(*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 = (struct sockaddr *)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
/*
* Enable the TINYPROXY_DEBUG environment variable if you
* want to use the GDB debugger.
*/
if (getenv ("TINYPROXY_DEBUG"))
{
/* Pause for 10 seconds to allow us to connect debugger */
fprintf (stderr,
"Process has accepted connection: %ld\n",
(long int) ptr->tid);
sleep (10);
fprintf (stderr, "Continuing process: %ld\n", (long int) 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);
ptr->connects++;
if (child_config.maxrequestsperchild != 0)
{
DEBUG2 ("%u connections so far...", ptr->connects);
if (ptr->connects == child_config.maxrequestsperchild)
{
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 pid_t
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 = (struct child_s *)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 = (unsigned int *)malloc_shared_memory (sizeof (unsigned 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)
{
unsigned 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)
{
unsigned 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);
}