]> code.citadel.org Git - citadel.git/blobdiff - citadel/sysdep.c
* Initial hack of worker-thread rearchitecture. Right now it is successfully
[citadel.git] / citadel / sysdep.c
index 9a05d83cf2bcec5815ce4208ac077ba1ed25245d..0f004f172645af4817b4d81d3ea38313f56a6b81 100644 (file)
@@ -14,6 +14,7 @@
  */
 
 
+#include "sysdep.h"
 #include <stdlib.h>
 #include <unistd.h>
 #include <stdio.h>
 #include <errno.h>
 #include <stdarg.h>
 #include <syslog.h>
+#include <grp.h>
+#ifdef __GNUC__
+#include <malloc.h>
+#endif
+#ifdef HAVE_PTHREAD_H
 #include <pthread.h>
+#endif
 #include "citadel.h"
 #include "server.h"
 #include "sysdep_decls.h"
 #include "snprintf.h"
 #endif
 
+#ifdef DEBUG_MEMORY_LEAKS
+struct TheHeap *heap = NULL;
+#endif
+
 pthread_mutex_t Critters[MAX_SEMAPHORES];      /* Things needing locking */
 pthread_key_t MyConKey;                                /* TSD key for MyContext() */
 
 int msock;                                     /* master listening socket */
-int verbosity = 3;                             /* Logging level */
+int verbosity = 9;                             /* Logging level */
 
 struct CitContext masterCC;
-
+int rescan[2];                                 /* The Rescan Pipe */
 
 /*
  * lprintf()  ...   Write logging information
  */
 void lprintf(int loglevel, const char *format, ...) {   
         va_list arg_ptr;
-       char buf[256];
+       char buf[512];
   
         va_start(arg_ptr, format);   
         vsprintf(buf, format, arg_ptr);   
@@ -81,6 +92,117 @@ void lprintf(int loglevel, const char *format, ...) {
        }   
 
 
+
+#ifdef DEBUG_MEMORY_LEAKS
+void *tracked_malloc(size_t tsize, char *tfile, int tline) {
+       void *ptr;
+       struct TheHeap *hptr;
+
+       ptr = malloc(tsize);
+       if (ptr == NULL) {
+               lprintf(3, "DANGER!  mallok(%d) at %s:%d failed!\n",
+                       tsize, tfile, tline);
+               return(NULL);
+       }
+
+       hptr = (struct TheHeap *) malloc(sizeof(struct TheHeap));
+       strcpy(hptr->h_file, tfile);
+       hptr->h_line = tline;
+       hptr->next = heap;
+       hptr->h_ptr = ptr;
+       heap = hptr;
+       return ptr;
+       }
+
+char *tracked_strdup(const char *orig, char *tfile, int tline) {
+       char *s;
+
+       s = tracked_malloc( (strlen(orig)+1), tfile, tline);
+       if (s == NULL) return NULL;
+
+       strcpy(s, orig);
+       return s;
+}
+
+void tracked_free(void *ptr) {
+       struct TheHeap *hptr, *freeme;
+
+       if (heap->h_ptr == ptr) {
+               hptr = heap->next;
+               free(heap);
+               heap = hptr;
+               }
+       else {
+               for (hptr=heap; hptr->next!=NULL; hptr=hptr->next) {
+                       if (hptr->next->h_ptr == ptr) {
+                               freeme = hptr->next;
+                               hptr->next = hptr->next->next;
+                               free(freeme);
+                               }
+                       }
+               }
+
+       free(ptr);
+       }
+
+void *tracked_realloc(void *ptr, size_t size) {
+       void *newptr;
+       struct TheHeap *hptr;
+       
+       newptr = realloc(ptr, size);
+
+       for (hptr=heap; hptr!=NULL; hptr=hptr->next) {
+               if (hptr->h_ptr == ptr) hptr->h_ptr = newptr;
+               }
+
+       return newptr;
+       }
+
+
+void dump_tracked() {
+       struct TheHeap *hptr;
+
+       cprintf("%d Here's what's allocated...\n", LISTING_FOLLOWS);    
+       for (hptr=heap; hptr!=NULL; hptr=hptr->next) {
+               cprintf("%20s %5d\n",
+                       hptr->h_file, hptr->h_line);
+               }
+#ifdef __GNUC__
+        malloc_stats();
+#endif
+
+       cprintf("000\n");
+       }
+#endif
+
+static pthread_t main_thread_id;
+
+#ifndef HAVE_PTHREAD_CANCEL
+/*
+ * signal handler to fake thread cancellation; only required on BSDI as far
+ * as I know.
+ */
+static RETSIGTYPE cancel_thread(int signum) {
+       pthread_exit(NULL);
+       }
+#endif
+
+/*
+ * we used to use master_cleanup() as a signal handler to shut down the server.
+ * however, master_cleanup() and the functions it calls do some things that
+ * aren't such a good idea to do from a signal handler: acquiring mutexes,
+ * playing with signal masks on BSDI systems, etc. so instead we install the
+ * following signal handler to set a global variable to inform the main loop
+ * that it's time to call master_cleanup() and exit.
+ */
+
+static volatile int time_to_die = 0;
+
+static RETSIGTYPE signal_cleanup(int signum) {
+       time_to_die = 1;
+       }
+
+
 /*
  * Some initialization stuff...
  */
@@ -103,12 +225,17 @@ void init_sysdep(void) {
 
        /*
         * The action for unexpected signals and exceptions should be to
-        * call master_cleanup() to gracefully shut down the server.
+        * call signal_cleanup() to gracefully shut down the server.
         */
-       signal(SIGINT, (void(*)(int))master_cleanup);
-       signal(SIGQUIT, (void(*)(int))master_cleanup);
-       signal(SIGHUP, (void(*)(int))master_cleanup);
-       signal(SIGTERM, (void(*)(int))master_cleanup);
+       signal(SIGINT, signal_cleanup);
+       signal(SIGQUIT, signal_cleanup);
+       signal(SIGHUP, signal_cleanup);
+       signal(SIGTERM, signal_cleanup);
+       signal(SIGPIPE, SIG_IGN);
+       main_thread_id = pthread_self();
+#ifndef HAVE_PTHREAD_CANCEL /* fake it - only BSDI afaik */
+       signal(SIGUSR1, cancel_thread);
+#endif
        }
 
 
@@ -117,38 +244,16 @@ void init_sysdep(void) {
  */
 void begin_critical_section(int which_one)
 {
-       int oldval;
-
-       /* lprintf(8, "begin_critical_section(%d)\n", which_one); */
-
-       /* Don't get interrupted during the critical section */
-       pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED, &oldval);
-
-       /* Obtain a semaphore */
        pthread_mutex_lock(&Critters[which_one]);
-
-       }
+}
 
 /*
  * Release a semaphore lock to end a critical section.
  */
 void end_critical_section(int which_one)
 {
-       int oldval;
-
-       /* lprintf(8, "  end_critical_section(%d)\n", which_one); */
-
-       /* Let go of the semaphore */
        pthread_mutex_unlock(&Critters[which_one]);
-
-       /* If a cancel was sent during the critical section, do it now.
-        * Then re-enable thread cancellation.
-        */
-       pthread_testcancel();
-       pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, &oldval);
-       pthread_testcancel();
-
-       }
+}
 
 
 
@@ -198,26 +303,6 @@ int ig_tcp_server(int port_number, int queue_len)
        }
 
 
-/*
- * Return a pointer to a thread's own CitContext structure (old)
- * NOTE: this version of MyContext() is commented out because it is no longer
- * in use.  It was written before I discovered TSD keys.  This
- * version pounds through the context list until it finds the one matching
- * the currently running thread.  It remains here, commented out, in case it
- * is needed for future ports to threading libraries which have the equivalent
- * of pthread_self() but not pthread_key_create() and its ilk.
- *
- * struct CitContext *MyContext() {
- *     struct CitContext *ptr;
- *     THREAD me;
- *
- *     me = pthread_self();
- *     for (ptr=ContextList; ptr!=NULL; ptr=ptr->next) {
- *             if (ptr->mythread == me) return(ptr);
- *             }
- *     return(NULL);
- *     }
- */
 
 /*
  * Return a pointer to a thread's own CitContext structure (new)
@@ -236,14 +321,19 @@ struct CitContext *MyContext(void) {
 struct CitContext *CreateNewContext(void) {
        struct CitContext *me;
 
-       lprintf(9, "CreateNewContext: calling malloc()\n");
-       me = (struct CitContext *) malloc(sizeof(struct CitContext));
+       me = (struct CitContext *) mallok(sizeof(struct CitContext));
        if (me == NULL) {
                lprintf(1, "citserver: can't allocate memory!!\n");
-               pthread_exit(NULL);
+               return NULL;
                }
        memset(me, 0, sizeof(struct CitContext));
 
+       /* The new context will be created already in the CON_EXECUTING state
+        * in order to prevent another thread from grabbing it while it's
+        * being set up.
+        */
+       me->state = CON_EXECUTING;
+
        begin_critical_section(S_SESSION_TABLE);
        me->next = ContextList;
        ContextList = me;
@@ -251,34 +341,17 @@ struct CitContext *CreateNewContext(void) {
        return(me);
        }
 
-/*
- * Add a thread's thread ID to the context
- */
-void InitMyContext(struct CitContext *con)
-{
-       int oldval;
-
-       con->mythread = pthread_self();
-       pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, &oldval);
-       pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, &oldval);
-       if (pthread_setspecific(MyConKey, (void *)con) != 0) {
-               lprintf(1, "ERROR!  pthread_setspecific() failed: %s\n",
-                       strerror(errno));
-               }
-       }
-
 /*
  * Remove a context from the context list.
  */
 void RemoveContext(struct CitContext *con)
 {
-       struct CitContext *ptr;
+       struct CitContext *ptr = NULL;
+       struct CitContext *ToFree = NULL;
 
        lprintf(7, "Starting RemoveContext()\n");
-       lprintf(9, "Session count before RemoveContext is %d\n",
-               session_count());
        if (con==NULL) {
-               lprintf(7, "WARNING: RemoveContext() called with null!\n");
+               lprintf(5, "WARNING: RemoveContext() called with NULL!\n");
                return;
                }
 
@@ -287,28 +360,33 @@ void RemoveContext(struct CitContext *con)
         * so do not call it from within this loop.
         */
        begin_critical_section(S_SESSION_TABLE);
-       lprintf(7, "Closing socket %d\n", con->client_socket);
-       close(con->client_socket);
 
-       lprintf(9, "Dereferencing session context\n");
-       if (ContextList==con) {
+       if (ContextList == con) {
+               ToFree = ContextList;
                ContextList = ContextList->next;
                }
        else {
                for (ptr = ContextList; ptr != NULL; ptr = ptr->next) {
                        if (ptr->next == con) {
-                               ptr->next = con->next;
+                               ToFree = ptr->next;
+                               ptr->next = ptr->next->next;
                                }
                        }
                }
 
-       lprintf(9, "Freeing session context...\n");     
-       free(con);
-       lprintf(9, "...done.\n");
+
        end_critical_section(S_SESSION_TABLE);
 
-       lprintf(9, "Session count after RemoveContext is %d\n",
-               session_count());
+       lprintf(7, "Closing socket %d\n", ToFree->client_socket);
+       close(ToFree->client_socket);
+
+        /* Tell the housekeeping thread to check to see if this is the time
+         * to initiate a scheduled shutdown event.
+         */
+        enter_housekeeping_cmd("SCHED_SHUTDOWN");
+
+       /* Free up the memory used by this context */
+       phree(ToFree);
 
        lprintf(7, "Done with RemoveContext\n");
        }
@@ -322,15 +400,12 @@ int session_count(void) {
        struct CitContext *ptr;
        int TheCount = 0;
 
-       lprintf(9, "session_count() starting\n");
        begin_critical_section(S_SESSION_TABLE);
        for (ptr = ContextList; ptr != NULL; ptr = ptr->next) {
                ++TheCount;
-               lprintf(9, "Counted session %3d (%d)\n", ptr->cs_pid, TheCount);
                }
        end_critical_section(S_SESSION_TABLE);
 
-       lprintf(9, "session_count() finishing\n");
        return(TheCount);
        }
 
@@ -390,11 +465,12 @@ int client_read_to(char *buf, int bytes, int timeout)
        while(len<bytes) {
                FD_ZERO(&rfds);
                FD_SET(CC->client_socket, &rfds);
-               tv.tv_sec = timeout;
+               tv.tv_sec = 1;
                tv.tv_usec = 0;
 
                retval = select( (CC->client_socket)+1, 
                                        &rfds, NULL, NULL, &tv);
+
                if (FD_ISSET(CC->client_socket, &rfds) == 0) {
                        return(0);
                        }
@@ -462,17 +538,6 @@ void sysdep_master_cleanup(void) {
        close(msock);
        }
 
-/*
- * Cleanup routine to be called when one thread is shutting down.
- */
-void cleanup(int exit_code)
-{
-       /* Terminate the thread.
-        * Its cleanup handler will call cleanup_stuff()
-        */
-       lprintf(7, "Calling pthread_exit()\n");
-       pthread_exit(NULL);
-       }
 
 /*
  * Terminate another session.
@@ -480,24 +545,16 @@ void cleanup(int exit_code)
 void kill_session(int session_to_kill) {
        struct CitContext *ptr;
 
-       /* FIX ... do a lock-discover-unlock-kill sequence here. */
+       begin_critical_section(S_SESSION_TABLE);
        for (ptr = ContextList; ptr != NULL; ptr = ptr->next) {
                if (ptr->cs_pid == session_to_kill) {
-                       pthread_cancel(ptr->mythread);
+                       ptr->state = CON_DYING;
                        }
                }
+       end_critical_section(S_SESSION_TABLE);
        }
 
 
-/*
- * The system-dependent wrapper around the main context loop.
- */
-void *sd_context_loop(struct CitContext *con) {
-       pthread_cleanup_push(*cleanup_stuff, NULL);
-       context_loop(con);
-       pthread_cleanup_pop(0);
-       return NULL;
-       }
 
 
 /*
@@ -615,16 +672,13 @@ int convert_login(char NameToConvert[]) {
  */
 int main(int argc, char **argv)
 {
-       struct sockaddr_in fsin;        /* Data for master socket */
-       int alen;                       /* Data for master socket */
-       int ssock;                      /* Descriptor for master socket */
-       THREAD SessThread;              /* Thread descriptor */
+       THREAD HousekeepingThread;      /* Thread descriptor */
         pthread_attr_t attr;           /* Thread attributes */
-       struct CitContext *con;         /* Temporary context pointer */
        char tracefile[128];            /* Name of file to log traces to */
        int a, i;                       /* General-purpose variables */
-       char convbuf[128];
-       char modpath[128];
+       struct passwd *pw;
+       int drop_root_perms = 1;
+       char *moddir;
         
        /* specify default port name and trace file */
        strcpy(tracefile, "");
@@ -648,30 +702,43 @@ int main(int argc, char **argv)
 
                /* -x specifies the desired logging level */
                else if (!strncmp(argv[a], "-x", 2)) {
-                       strcpy(convbuf, argv[a]);
-                       verbosity = atoi(&convbuf[2]);
+                       verbosity = atoi(&argv[a][2]);
                        }
 
                else if (!strncmp(argv[a], "-h", 2)) {
-                       strcpy(convbuf, argv[a]);
-                       strcpy(bbs_home_directory, &convbuf[2]);
+                       safestrncpy(bbs_home_directory, &argv[a][2],
+                                   sizeof bbs_home_directory);
                        home_specified = 1;
                        }
 
+               else if (!strncmp(argv[a], "-f", 2)) {
+                       do_defrag = 1;
+                       }
+
+               /* -r tells the server not to drop root permissions. don't use
+                * this unless you know what you're doing. this should be
+                * removed in the next release if it proves unnecessary. */
+               else if (!strcmp(argv[a], "-r"))
+                       drop_root_perms = 0;
+
                /* any other parameter makes it crash and burn */
                else {
-                       lprintf(1, "citserver: usage: ");
-                       lprintf(1, "citserver [-tTraceFile]");
-                       lprintf(1, " [-d] [-xLogLevel] [-hHomeDir]\n");
+                       lprintf(1,      "citserver: usage: "
+                                       "citserver [-tTraceFile] [-d] [-f]"
+                                       " [-xLogLevel] [-hHomeDir]\n");
                        exit(1);
                        }
 
                }
 
        /* Tell 'em who's in da house */
-       lprintf(1, "Multithreaded message server for %s\n", CITADEL);
-       lprintf(1, "Copyright (C) 1987-1998 by Art Cancro.  ");
-       lprintf(1, "All rights reserved.\n\n");
+       lprintf(1,
+"\nMultithreaded message server for Citadel/UX\n"
+"Copyright (C) 1987-1999 by the Citadel/UX development team.\n"
+"Citadel/UX is free software, covered by the GNU General Public License, and\n"
+"you are welcome to change it and/or distribute copies of it under certain\n"
+"conditions.  There is absolutely no warranty for this software.  Please\n"
+"read the 'COPYING.txt' file for details.\n\n");
 
        /* Initialize... */
        init_sysdep();
@@ -680,14 +747,6 @@ int main(int argc, char **argv)
        lprintf(7, "Loading citadel.config\n");
        get_config();
 
-        lprintf(7, "Initializing loadable modules\n");
-        snprintf(modpath, 128, "%s/modules", BBSDIR);
-        DLoader_Init(modpath);
-        lprintf(9, "Modules done initializing.\n");
-
-       /* Do non system dependent startup functions */
-       master_startup();
-
        /*
         * Bind the server to our favourite port.
         * There is no need to check for errors, because ig_tcp_server()
@@ -698,62 +757,247 @@ int main(int argc, char **argv)
        lprintf(7, "Listening on socket %d\n", msock);
 
        /*
-        * Now that we've bound the socket, change to the BBS user id
-       lprintf(7, "Changing uid to %d\n", BBSUID);
-       if (setuid(BBSUID) != 0) {
-               lprintf(3, "setuid() failed: %s", strerror(errno));
+        * Now that we've bound the socket, change to the BBS user id and its
+        * corresponding group ids
+        */
+       if (drop_root_perms) {
+               if ((pw = getpwuid(BBSUID)) == NULL)
+                       lprintf(1, "WARNING: getpwuid(%d): %s\n"
+                                  "Group IDs will be incorrect.\n", BBSUID,
+                               strerror(errno));
+               else {
+                       initgroups(pw->pw_name, pw->pw_gid);
+                       if (setgid(pw->pw_gid))
+                               lprintf(3, "setgid(%d): %s\n", pw->pw_gid,
+                                       strerror(errno));
+                       }
+               lprintf(7, "Changing uid to %d\n", BBSUID);
+               if (setuid(BBSUID) != 0) {
+                       lprintf(3, "setuid() failed: %s\n", strerror(errno));
+                       }
                }
+
+       /*
+        * Do non system dependent startup functions.
         */
+       master_startup();
 
-       /* 
-        * Endless loop.  Listen on the master socket.  When a connection
-        * comes in, create a socket, a context, and a thread.
-        */     
-       while (1) {
-               ssock = accept(msock, (struct sockaddr *)&fsin, &alen);
-               if (ssock < 0) {
-                       lprintf(2, "citserver: accept() failed: %s\n",
-                               strerror(errno));
+       /*
+        * Load any server-side modules (plugins) available here.
+        */
+       lprintf(7, "Initializing loadable modules\n");
+       if ((moddir = malloc(strlen(bbs_home_directory) + 9)) != NULL) {
+               sprintf(moddir, "%s/modules", bbs_home_directory);
+               DLoader_Init(moddir);
+               free(moddir);
+               }
+
+       lprintf(7, "Starting housekeeper thread\n");
+       pthread_attr_init(&attr);
+               pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
+       if (pthread_create(&HousekeepingThread, &attr,
+          (void* (*)(void*)) housekeeping_loop, NULL) != 0) {
+               lprintf(1, "Can't create housekeeping thead: %s\n",
+                       strerror(errno));
+       }
+
+
+       /*
+        * The rescan pipe exists so that worker threads can be woken up and
+        * told to re-scan the context list for fd's to listen on.  This is
+        * necessary, for example, when a context is about to go idle and needs
+        * to get back on that list.
+        */
+       if (pipe(rescan)) {
+               lprintf(1, "Can't create rescan pipe!\n");
+               exit(errno);
+       }
+
+       /*
+        * Now create a bunch of worker threads.
+        */
+       for (i=0; i<(NUM_WORKER_THREADS-1); ++i) {
+               pthread_attr_init(&attr);
+                       pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
+               if (pthread_create(&HousekeepingThread, &attr,
+                  (void* (*)(void*)) worker_thread, NULL) != 0) {
+                       lprintf(1, "Can't create worker thead: %s\n",
+                       strerror(errno));
+               }
+       }
+
+       /* Now this thread can become a worker as well. */
+       worker_thread();
+
+       return(0);
+}
+
+
+
+
+
+
+
+/* 
+ * This loop just keeps going and going and going...
+ */    
+void worker_thread(void) {
+       int i;
+       char junk;
+       int numselect = 0;
+       int highest;
+       struct CitContext *ptr;
+       struct CitContext *bind_me = NULL;
+       fd_set readfds;
+       int retval;
+       struct CitContext *con= NULL;   /* Temporary context pointer */
+       struct sockaddr_in fsin;        /* Data for master socket */
+       int alen;                       /* Data for master socket */
+       int ssock;                      /* Descriptor for master socket */
+
+       while (!time_to_die) {
+
+               /* 
+                * In a stupid environment, we would have all idle threads
+                * calling select() and then they'd all wake up at once.  We
+                * solve this problem by putting the select() in a critical
+                * section, so only one thread has the opportunity to wake
+                * up.  If we wake up on the master socket, create a new
+                * session context; otherwise, just bind the thread to the
+                * context we want and go on our merry way.
+                */
+
+               begin_critical_section(S_I_WANNA_SELECT);
+               lprintf(9, "Worker thread %2d woke up\n", getpid());
+
+SETUP_FD:
+               FD_ZERO(&readfds);
+               FD_SET(msock, &readfds);
+               highest = msock;
+               FD_SET(rescan[0], &readfds);
+               if (rescan[0] > highest) highest = rescan[0];
+               numselect = 2;
+
+               begin_critical_section(S_SESSION_TABLE);
+               for (ptr = ContextList; ptr != NULL; ptr = ptr->next) {
+                       if (ptr->state == CON_IDLE) {
+                               FD_SET(ptr->client_socket, &readfds);
+                               if (ptr->client_socket > highest)
+                                       highest = ptr->client_socket;
+                               ++numselect;
                        }
-               else {
-                       lprintf(7, "citserver: Client socket %d\n", ssock);
-                       lprintf(9, "creating context\n");
-                       con = CreateNewContext();
-                       con->client_socket = ssock;
-
-                       /* Set the SO_REUSEADDR socket option */
-                       lprintf(9, "setting socket options\n");
-                       i = 1;
-                       setsockopt(ssock, SOL_SOCKET, SO_REUSEADDR,
-                               &i, sizeof(i));
-
-                       /* set attributes for the new thread */
-                       lprintf(9, "setting thread attributes\n");
-                       pthread_attr_init(&attr);
-                       pthread_attr_setdetachstate(&attr,
-                               PTHREAD_CREATE_DETACHED);
-
-                       /* now create the thread */
-                       lprintf(9, "creating thread\n");
-                       if (pthread_create(&SessThread, &attr,
-                                          (void* (*)(void*)) sd_context_loop,
-                                          con)
-                           != 0) {
-                               lprintf(1,
-                                       "citserver: can't create thread: %s\n",
+               }
+               end_critical_section(S_SESSION_TABLE);
+
+               lprintf(9, "Thread %2d can wake up on %d different fd's\n",
+                       getpid(), numselect);
+               retval = select(highest + 1, &readfds, NULL, NULL, NULL);
+               lprintf(9, "select() returned %d\n", retval);
+
+               /* Now figure out who made this select() unblock.
+                * First, check for an error or exit condition.
+                */
+               if (retval < 0) {
+                       end_critical_section(S_I_WANNA_SELECT);
+                       lprintf(9, "Exiting (%s)\n", errno);
+                       time_to_die = 1;
+               }
+
+               /* Next, check to see if it's a new client connecting
+                * on the master socket.
+                */
+               else if (FD_ISSET(msock, &readfds)) {
+                       lprintf(9, "It's the master socket!\n");
+                       alen = sizeof fsin;
+                       ssock = accept(msock, (struct sockaddr *)&fsin, &alen);
+                       if (ssock < 0) {
+                               lprintf(2, "citserver: accept() failed: %s\n",
                                        strerror(errno));
+                       }
+                       else {
+                               lprintf(7, "citserver: New client socket %d\n",
+                                       ssock);
+
+                               /* New context will be created already set up
+                                * in the CON_EXECUTING state.
+                                */
+                               con = CreateNewContext();
+
+                               /* Assign our new socket number to it. */
+                               con->client_socket = ssock;
+       
+                               /* Set the SO_REUSEADDR socket option */
+                               i = 1;
+                               setsockopt(ssock, SOL_SOCKET, SO_REUSEADDR,
+                                       &i, sizeof(i));
+
+                               pthread_setspecific(MyConKey, (void *)con);
+                               begin_session(con);
+                               /* do_command_loop(); */
+                               pthread_setspecific(MyConKey, (void *)NULL);
+                               con->state = CON_IDLE;
+                               goto SETUP_FD;
+                       }
+               }
+
+               /* If the rescan pipe went active, someone is telling this
+                * thread that the &readfds needs to be refreshed with more
+                * current data.
+                */
+               else if (FD_ISSET(rescan[0], &readfds)) {
+                       lprintf(9, "rescanning\n");
+                       read(rescan[0], &junk, 1);
+                       goto SETUP_FD;
+               }
+
+               /* It must be a client socket.  Find a context that has data
+                * waiting on its socket *and* is in the CON_IDLE state.
+                */
+               else {
+                       bind_me = NULL;
+                       begin_critical_section(S_SESSION_TABLE);
+                       for (ptr = ContextList;
+                           ( (ptr != NULL) && (bind_me == NULL) );
+                           ptr = ptr->next) {
+                               if ( (FD_ISSET(ptr->client_socket, &readfds))
+                                  && (ptr->state == CON_IDLE) ) {
+                                       bind_me = con;
                                }
+                       }
+                       if (bind_me != NULL) {
+                               /* Found one.  Stake a claim to it before
+                                * letting anyone else touch the context list.
+                                */
+                               bind_me->state = CON_EXECUTING;
+                       }
 
-                       /* detach the thread 
-                        * (defunct -- now done at thread creation time)
-                        * if (pthread_detach(&SessThread) != 0) {
-                        *      lprintf(1,
-                        *              "citserver: can't detach thread: %s\n",
-                        *              strerror(errno));
-                        *      }
-                        */
-                       lprintf(9, "done!\n");
+                       end_critical_section(S_SESSION_TABLE);
+                       end_critical_section(S_I_WANNA_SELECT);
+
+                       /* We're bound to a session, now do *one* command */
+                       if (bind_me != NULL) {
+                               lprintf(9, "Binding thread to session %d\n",
+                                       bind_me->client_socket);
+                               pthread_setspecific(MyConKey, (void *)bind_me);
+                               do_command_loop();
+                               pthread_setspecific(MyConKey, (void *)NULL);
+                               if (bind_me->state == CON_DYING) {
+                                       RemoveContext(bind_me);
+                               } 
+                               else {
+                                       bind_me->state = CON_IDLE;
+                               }
+                               write(rescan[1], &junk, 1);
                        }
+                       else {
+                               lprintf(9, "Thread %02d found nothing to do!\n",
+                                       getpid());
+                       }
+
                }
+
        }
 
+       pthread_exit(NULL);
+}
+