summaryrefslogtreecommitdiffhomepage
path: root/src/child.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/child.c')
-rw-r--r--src/child.c573
1 files changed, 142 insertions, 431 deletions
diff --git a/src/child.c b/src/child.c
index effb2ae..985357d 100644
--- a/src/child.c
+++ b/src/child.c
@@ -31,247 +31,137 @@
#include "sock.h"
#include "utils.h"
#include "conf.h"
+#include "sblist.h"
+#include "loop.h"
+#include "conns.h"
+#include "mypoll.h"
+#include <pthread.h>
-static vector_t listen_fds;
+static sblist* 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;
+struct client {
+ union sockaddr_union addr;
};
-/*
- * 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;
- }
-}
+struct child {
+ pthread_t thread;
+ struct client client;
+ struct conn_s conn;
+ volatile int done;
+};
-static void _child_lock_release (void)
+static void* child_thread(void* data)
{
- if (fcntl (lock_fd, F_SETLKW, &unlock_it) < 0)
- return;
+ struct child *c = data;
+ handle_connection (&c->conn, &c->client.addr);
+ c->done = 1;
+ return NULL;
}
-/* 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;
-}
+static sblist *childs;
-/**
- * child signal handler for sighup
- */
-static void child_sighup_handler (int sig)
+static void collect_threads(void)
{
- 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 */
- }
+ 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++;
+ }
}
/*
- * This is the main (per child) loop.
+ * This is the main loop accepting new connections.
*/
-static void child_main (struct child_s *ptr)
+void child_main_loop (void)
{
int connfd;
- struct sockaddr *cliaddr;
- socklen_t clilen;
- fd_set rfds;
- int maxfd = 0;
+ union sockaddr_union cliaddr_storage;
+ struct sockaddr *cliaddr = (void*) &cliaddr_storage;
+ socklen_t clilen = sizeof(cliaddr_storage);
+ int nfds = sblist_getsize(listen_fds);
+ pollfd_struct *fds = safecalloc(nfds, sizeof *fds);
ssize_t i;
- int ret;
+ int ret, listenfd, 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);
- }
+ childs = sblist_new(sizeof (struct child*), config->maxclients);
- ptr->connects = 0;
- srand(time(NULL));
+ for (i = 0; i < nfds; i++) {
+ int *fd = sblist_get(listen_fds, i);
+ fds[i].fd = *fd;
+ fds[i].events |= MYPOLL_READ;
+ }
/*
* We have to wait for connections on multiple fds,
- * so use select.
+ * so use select/poll/whatever.
*/
+ while (!config->quit) {
- FD_ZERO(&rfds);
-
- for (i = 0; i < vector_length(listen_fds); i++) {
- int *fd = (int *) vector_getentry(listen_fds, i, NULL);
+ collect_threads();
- ret = socket_nonblocking(*fd);
- if (ret != 0) {
- log_message(LOG_ERR, "Failed to set the listening "
- "socket %d to non-blocking: %s",
- fd, strerror(errno));
- exit(1);
+ if (sblist_getsize(childs) >= config->maxclients) {
+ if (!was_full)
+ log_message (LOG_WARNING,
+ "Maximum number of connections reached. "
+ "Refusing new connections.");
+ was_full = 1;
+ usleep(16);
+ continue;
}
- FD_SET(*fd, &rfds);
- maxfd = max(maxfd, *fd);
- }
+ was_full = 0;
+ listenfd = -1;
- while (!config.quit) {
- int listenfd = -1;
+ /* Handle log rotation if it was requested */
+ if (received_sighup) {
- ptr->status = T_WAITING;
+ reload_config (1);
- clilen = sizeof(struct sockaddr_storage);
+#ifdef FILTER_ENABLE
+ filter_reload ();
+#endif /* FILTER_ENABLE */
+
+ received_sighup = FALSE;
+ }
+
+ ret = mypoll(fds, nfds, -1);
- ret = select(maxfd + 1, &rfds, NULL, NULL, NULL);
if (ret == -1) {
if (errno == EINTR) {
continue;
}
- log_message (LOG_ERR, "error calling select: %s",
+ log_message (LOG_ERR, "error calling " SELECT_OR_POLL ": %s",
strerror(errno));
- exit(1);
+ continue;
} else if (ret == 0) {
- log_message (LOG_WARNING, "Strange: select returned 0 "
+ log_message (LOG_WARNING, "Strange: " SELECT_OR_POLL " returned 0 "
"but we did not specify a timeout...");
continue;
}
- for (i = 0; i < vector_length(listen_fds); i++) {
- int *fd = (int *) vector_getentry(listen_fds, i, NULL);
-
- if (FD_ISSET(*fd, &rfds)) {
+ for (i = 0; i < nfds; i++) {
+ if (fds[i].revents & MYPOLL_READ) {
/*
* only accept the connection on the first
* fd that we find readable. - fair?
*/
- listenfd = *fd;
+ listenfd = fds[i].fd;
break;
}
}
if (listenfd == -1) {
log_message(LOG_WARNING, "Strange: None of our listen "
- "fds was readable after select");
+ "fds was readable after " SELECT_OR_POLL);
continue;
}
- ret = socket_blocking(listenfd);
- if (ret != 0) {
- log_message(LOG_ERR, "Failed to set listening "
- "socket %d to blocking for accept: %s",
- listenfd, strerror(errno));
- exit(1);
- }
-
/*
* We have a socket that is readable.
* Continue handling this connection.
@@ -279,21 +169,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,225 +180,41 @@ 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 = safecalloc(1, 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;
- }
-
- 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]);
+ child->done = 0;
- 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.");
+ conn_struct_init(&child->conn);
+ child->conn.client_fd = connfd;
- return 0;
-}
+ 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;
+ }
}
+ safefree(fds);
}
/*
@@ -531,27 +222,48 @@ 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, tries = 0;
+
+ if (sig != SIGTERM) return;
+ log_message (LOG_INFO,
+ "trying to bring down %zu threads...",
+ sblist_getsize(childs)
+ );
+
+
+again:
+ for (i = 0; i < sblist_getsize(childs); i++) {
+ struct child *c = *((struct child**)sblist_get(childs, i));
+ if (!c->done) pthread_kill(c->thread, SIGCHLD);
+ }
+ usleep(8192);
+ collect_threads();
+ if (sblist_getsize(childs) != 0)
+ if(tries++ < 8) goto again;
+ if (sblist_getsize(childs) != 0)
+ log_message (LOG_CRIT,
+ "child_kill_children: %zu threads still alive!",
+ sblist_getsize(childs)
+ );
}
+void child_free_children(void) {
+ sblist_free(childs);
+ childs = 0;
+}
/**
* Listen on the various configured interfaces
*/
-int child_listening_sockets(vector_t listen_addrs, uint16_t port)
+int child_listening_sockets(sblist *listen_addrs, uint16_t port)
{
int ret;
- ssize_t i;
+ size_t i;
assert (port > 0);
if (listen_fds == NULL) {
- listen_fds = vector_create();
+ listen_fds = sblist_new(sizeof(int), 16);
if (listen_fds == NULL) {
log_message (LOG_ERR, "Could not create the list "
"of listening fds");
@@ -559,8 +271,7 @@ int child_listening_sockets(vector_t listen_addrs, uint16_t port)
}
}
- if ((listen_addrs == NULL) ||
- (vector_length(listen_addrs) == 0))
+ if (!listen_addrs || !sblist_getsize(listen_addrs))
{
/*
* no Listen directive:
@@ -570,17 +281,17 @@ int child_listening_sockets(vector_t listen_addrs, uint16_t port)
return ret;
}
- for (i = 0; i < vector_length(listen_addrs); i++) {
- const char *addr;
+ for (i = 0; i < sblist_getsize(listen_addrs); i++) {
+ char **addr;
- addr = (char *)vector_getentry(listen_addrs, i, NULL);
- if (addr == NULL) {
+ addr = sblist_get(listen_addrs, i);
+ if (!addr || !*addr) {
log_message(LOG_WARNING,
"got NULL from listen_addrs - skipping");
continue;
}
- ret = listen_sock(addr, port, listen_fds);
+ ret = listen_sock(*addr, port, listen_fds);
if (ret != 0) {
return ret;
}
@@ -591,14 +302,14 @@ int child_listening_sockets(vector_t listen_addrs, uint16_t port)
void child_close_sock (void)
{
- ssize_t i;
+ size_t i;
- for (i = 0; i < vector_length(listen_fds); i++) {
- int *fd = (int *) vector_getentry(listen_fds, i, NULL);
+ for (i = 0; i < sblist_getsize(listen_fds); i++) {
+ int *fd = sblist_get(listen_fds, i);
close (*fd);
}
- vector_delete(listen_fds);
+ sblist_free(listen_fds);
listen_fds = NULL;
}