summaryrefslogtreecommitdiffhomepage
path: root/src/child.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/child.c')
-rw-r--r--src/child.c503
1 files changed, 115 insertions, 388 deletions
diff --git a/src/child.c b/src/child.c
index 60f8ead..06a977e 100644
--- a/src/child.c
+++ b/src/child.c
@@ -31,176 +31,66 @@
#include "sock.h"
#include "utils.h"
#include "conf.h"
+#include "sblist.h"
+#include <pthread.h>
static vector_t listen_fds;
-/*
- * 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;
+union sockaddr_union {
+ struct sockaddr_in v4;
+ struct sockaddr_in6 v6;
};
-/*
- * 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 */
+struct client {
+ union sockaddr_union addr;
+ int fd;
+};
-/*
- * 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;
+struct child {
+ pthread_t thread;
+ struct client client;
+ volatile int done;
+};
-static void _child_lock_init (void)
+static void* child_thread(void* data)
{
- 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;
+ struct child *c = data;
+ handle_connection (c->client.fd);
+ c->done = 1;
+ return NULL;
}
-static void _child_lock_wait (void)
-{
- int rc;
+static sblist *childs;
- while ((rc = fcntl (lock_fd, F_SETLKW, &lock_it)) < 0) {
- if (errno == EINTR)
- continue;
- else
- return;
- }
-}
-
-static void _child_lock_release (void)
+static void collect_threads(void)
{
- if (fcntl (lock_fd, F_SETLKW, &unlock_it) < 0)
- return;
+ size_t i;
+ for (i = 0; i < sblist_getsize(childs); ) {
+ struct child *c = *((struct child**)sblist_get(childs, i));
+ if (c->done) {
+ pthread_join(c->thread, 0);
+ sblist_delete(childs, i);
+ safefree(c);
+ } else i++;
+ }
}
-/* 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.
+ * This is the main loop accepting new connections.
*/
-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;
-}
-
-/**
- * child signal handler for sighup
- */
-static void child_sighup_handler (int sig)
-{
- if (sig == SIGHUP) {
- /*
- * Ignore the return value of reload_config for now.
- * This should actually be handled somehow...
- */
- reload_config ();
-
-#ifdef FILTER_ENABLE
- filter_reload ();
-#endif /* FILTER_ENABLE */
- }
-}
-
-/*
- * This is the main (per child) loop.
- */
-static void child_main (struct child_s *ptr)
+void child_main_loop (void)
{
int connfd;
- struct sockaddr *cliaddr;
- socklen_t clilen;
+ union sockaddr_union cliaddr_storage;
+ struct sockaddr *cliaddr = (void*) &cliaddr_storage;
+ socklen_t clilen = sizeof(cliaddr_storage);
fd_set rfds;
- int maxfd = 0;
ssize_t i;
- int ret;
+ int ret, listenfd, maxfd, was_full = 0;
+ pthread_attr_t *attrp, attr;
+ struct child *child;
- cliaddr = (struct sockaddr *)
- safemalloc (sizeof(struct sockaddr_storage));
- if (!cliaddr) {
- log_message (LOG_CRIT,
- "Could not allocate memory for child address.");
- exit (0);
- }
-
- ptr->connects = 0;
- srand(time(NULL));
+ childs = sblist_new(sizeof (struct child*), config.maxclients);
/*
* We have to wait for connections on multiple fds,
@@ -208,7 +98,37 @@ static void child_main (struct child_s *ptr)
*/
while (!config.quit) {
- int listenfd = -1;
+ collect_threads();
+
+ if (sblist_getsize(childs) >= config.maxclients) {
+ if (!was_full)
+ log_message (LOG_NOTICE,
+ "Maximum number of connections reached. "
+ "Refusing new connections.");
+ was_full = 1;
+ usleep(16);
+ continue;
+ }
+
+ was_full = 0;
+ listenfd = -1;
+ maxfd = 0;
+
+ /* Handle log rotation if it was requested */
+ if (received_sighup) {
+ /*
+ * Ignore the return value of reload_config for now.
+ * This should actually be handled somehow...
+ */
+ reload_config ();
+
+#ifdef FILTER_ENABLE
+ filter_reload ();
+#endif /* FILTER_ENABLE */
+
+ received_sighup = FALSE;
+ }
+
FD_ZERO(&rfds);
@@ -220,17 +140,13 @@ static void child_main (struct child_s *ptr)
log_message(LOG_ERR, "Failed to set the listening "
"socket %d to non-blocking: %s",
fd, strerror(errno));
- exit(1);
+ continue;
}
FD_SET(*fd, &rfds);
maxfd = max(maxfd, *fd);
}
- ptr->status = T_WAITING;
-
- clilen = sizeof(struct sockaddr_storage);
-
ret = select(maxfd + 1, &rfds, NULL, NULL, NULL);
if (ret == -1) {
if (errno == EINTR) {
@@ -238,7 +154,7 @@ static void child_main (struct child_s *ptr)
}
log_message (LOG_ERR, "error calling select: %s",
strerror(errno));
- exit(1);
+ continue;
} else if (ret == 0) {
log_message (LOG_WARNING, "Strange: select returned 0 "
"but we did not specify a timeout...");
@@ -269,7 +185,7 @@ static void child_main (struct child_s *ptr)
log_message(LOG_ERR, "Failed to set listening "
"socket %d to blocking for accept: %s",
listenfd, strerror(errno));
- exit(1);
+ continue;
}
/*
@@ -279,21 +195,6 @@ static void child_main (struct child_s *ptr)
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...
@@ -305,224 +206,37 @@ static void child_main (struct child_s *ptr)
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 ();
+ child = safemalloc(sizeof(struct child));
+ if (!child) {
+oom:
+ close(connfd);
+ log_message (LOG_CRIT,
+ "Could not allocate memory for child.");
+ usleep(16); /* prevent 100% CPU usage in OOM situation */
+ continue;
}
- 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, child_sighup_handler);
-
- 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;
- }
+ child->done = 0;
- 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 ();
+ if (!sblist_add(childs, &child)) {
+ free(child);
+ goto oom;
}
- }
-
- log_message (LOG_INFO, "Finished creating all children.");
- return 0;
-}
+ child->client.fd = connfd;
+ memcpy(&child->client.addr, &cliaddr_storage, sizeof(cliaddr_storage));
-/*
- * 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 ();
+ attrp = 0;
+ if (pthread_attr_init(&attr) == 0) {
+ attrp = &attr;
+ pthread_attr_setstacksize(attrp, 256*1024);
}
- sleep (5);
-
- /* Handle log rotation if it was requested */
- if (received_sighup) {
- /*
- * Ignore the return value of reload_config for now.
- * This should actually be handled somehow...
- */
- reload_config ();
-
-#ifdef FILTER_ENABLE
- filter_reload ();
-#endif /* FILTER_ENABLE */
-
- /* propagate filter reload to all children */
- child_kill_children (SIGHUP);
-
- received_sighup = FALSE;
- }
+ if (pthread_create(&child->thread, attrp, child_thread, child) != 0) {
+ sblist_delete(childs, sblist_getsize(childs) -1);
+ free(child);
+ goto oom;
+ }
}
}
@@ -531,12 +245,25 @@ void child_main_loop (void)
*/
void child_kill_children (int sig)
{
- unsigned int i;
-
- for (i = 0; i != child_config.maxclients; i++) {
- if (child_ptr[i].status != T_EMPTY)
- kill (child_ptr[i].tid, sig);
- }
+ size_t i;
+
+ if (sig != SIGTERM) return;
+
+ for (i = 0; i < sblist_getsize(childs); i++) {
+ struct child *c = *((struct child**)sblist_get(childs, i));
+ if (!c->done) {
+ /* interrupt blocking operations.
+ this should cause the threads to shutdown orderly. */
+ close(c->client.fd);
+ }
+ }
+ usleep(16);
+ collect_threads();
+ if (sblist_getsize(childs) != 0)
+ log_message (LOG_CRIT,
+ "child_kill_children: %zu threads still alive!",
+ sblist_getsize(childs)
+ );
}