diff options
Diffstat (limited to 'src/child.c')
-rw-r--r-- | src/child.c | 503 |
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) + ); } |