X-Git-Url: https://code.citadel.org/?a=blobdiff_plain;f=citadel%2Fsysdep.c;h=1413675ea9d07b4bf46745a7249763c7c2eb3fb6;hb=a2a06e5d6767d0e45eeccf4032153764e8a5520f;hp=061b9fee7937d751bf09f7cb758645ab9e7bfc35;hpb=2e5bfef2c1708874507a51c9469b2c181b5523f4;p=citadel.git diff --git a/citadel/sysdep.c b/citadel/sysdep.c index 061b9fee7..1413675ea 100644 --- a/citadel/sysdep.c +++ b/citadel/sysdep.c @@ -1,7 +1,7 @@ /* * $Id$ * - * Citadel/UX "system dependent" stuff. + * Citadel "system dependent" stuff. * See copyright.txt for copyright information. * * Here's where we (hopefully) have most parts of the Citadel server that @@ -12,10 +12,6 @@ * */ -#ifdef DLL_EXPORT -#define IN_LIBCIT -#endif - #include "sysdep.h" #include #include @@ -27,6 +23,7 @@ #include #include #include +#include #include #if TIME_WITH_SYS_TIME @@ -41,7 +38,10 @@ #endif #include +#include #include +#include +#include #include #include #include @@ -54,7 +54,6 @@ #endif #include "citadel.h" #include "server.h" -#include "serv_extensions.h" #include "sysdep_decls.h" #include "citserver.h" #include "support.h" @@ -62,7 +61,7 @@ #include "database.h" #include "housekeeping.h" #include "tools.h" -#include "serv_crypto.h" +#include "modules/crypto/serv_crypto.h" /* Needed for init_ssl, client_write_ssl, client_read_ssl, destruct_ssl */ #ifdef HAVE_SYS_SELECT_H #include @@ -72,202 +71,123 @@ #include "snprintf.h" #endif + #ifdef DEBUG_MEMORY_LEAKS -struct TheHeap *heap = NULL; +struct igheap { + struct igheap *next; + char file[32]; + int line; + void *block; +}; + +struct igheap *igheap = NULL; #endif + pthread_mutex_t Critters[MAX_SEMAPHORES]; /* Things needing locking */ pthread_key_t MyConKey; /* TSD key for MyContext() */ int verbosity = DEFAULT_VERBOSITY; /* Logging level */ struct CitContext masterCC; -int rescan[2]; /* The Rescan Pipe */ time_t last_purge = 0; /* Last dead session purge */ static int num_threads = 0; /* Current number of threads */ int num_sessions = 0; /* Current number of sessions */ -fd_set masterfds; /* Master sockets etc. */ -int masterhighest; - -pthread_t initial_thread; /* tid for main() thread */ - -int syslog_facility = (-1); +int syslog_facility = LOG_DAEMON; +int enable_syslog = 0; +void DestroyWorkerList(void); /* * lprintf() ... Write logging information - * - * Note: the variable "buf" below needs to be large enough to handle any - * log data sent through this function. BE CAREFUL! */ void lprintf(enum LogLevel loglevel, const char *format, ...) { - va_list arg_ptr; - char buf[SIZ]; - - va_start(arg_ptr, format); - vsnprintf(buf, sizeof(buf), format, arg_ptr); - va_end(arg_ptr); - - if (syslog_facility >= 0) { - if (loglevel <= verbosity) { - /* Hackery -IO */ - if (CC && CC->cs_pid) { - memmove(buf + 6, buf, sizeof(buf) - 6); - snprintf(buf, 6, "[%3d]", CC->cs_pid); - buf[5] = ' '; - } - syslog(loglevel, buf); - } + va_list arg_ptr; + + if (enable_syslog) { + va_start(arg_ptr, format); + vsyslog((syslog_facility | loglevel), format, arg_ptr); + va_end(arg_ptr); } - else if (loglevel <= verbosity) { + + /* stderr output code */ + if (enable_syslog || running_as_daemon) return; + + /* if we run in forground and syslog is disabled, log to terminal */ + if (loglevel <= verbosity) { struct timeval tv; - struct tm *tim; + struct tm tim; time_t unixtime; gettimeofday(&tv, NULL); /* Promote to time_t; types differ on some OSes (like darwin) */ unixtime = tv.tv_sec; - tim = localtime(&unixtime); - /* - * Log provides millisecond accuracy. If you need - * microsecond accuracy and your OS supports it, change - * %03ld to %06ld and remove " / 1000" after tv.tv_usec. - */ - if (CC && CC->cs_pid) { + localtime_r(&unixtime, &tim); + if (CC->cs_pid != 0) { fprintf(stderr, - "%04d/%02d/%02d %2d:%02d:%02d.%03ld [%3d] %s", - tim->tm_year + 1900, tim->tm_mon + 1, - tim->tm_mday, tim->tm_hour, tim->tm_min, - tim->tm_sec, (long)tv.tv_usec / 1000, - CC->cs_pid, buf); + "%04d/%02d/%02d %2d:%02d:%02d.%06ld [%3d] ", + tim.tm_year + 1900, tim.tm_mon + 1, + tim.tm_mday, tim.tm_hour, tim.tm_min, + tim.tm_sec, (long)tv.tv_usec, + CC->cs_pid); } else { fprintf(stderr, - "%04d/%02d/%02d %2d:%02d:%02d.%03ld %s", - tim->tm_year + 1900, tim->tm_mon + 1, - tim->tm_mday, tim->tm_hour, tim->tm_min, - tim->tm_sec, (long)tv.tv_usec / 1000, buf); + "%04d/%02d/%02d %2d:%02d:%02d.%06ld ", + tim.tm_year + 1900, tim.tm_mon + 1, + tim.tm_mday, tim.tm_hour, tim.tm_min, + tim.tm_sec, (long)tv.tv_usec); } + va_start(arg_ptr, format); + vfprintf(stderr, format, arg_ptr); + va_end(arg_ptr); fflush(stderr); } - - PerformLogHooks(loglevel, buf); } -#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(CTDL_ALERT, "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 - - /* - * 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. + * Signal handler to shut down the server. */ volatile int time_to_die = 0; +volatile int shutdown_and_halt = 0; +volatile int restart_server = 0; +volatile int running_as_daemon = 0; static RETSIGTYPE signal_cleanup(int signum) { + lprintf(CTDL_DEBUG, "Caught signal %d; shutting down.\n", signum); time_to_die = 1; + master_cleanup(signum); } - /* * Some initialization stuff... */ void init_sysdep(void) { - int a; + int i; + sigset_t set; + + /* Avoid vulnerabilities related to FD_SETSIZE if we can. */ +#ifdef FD_SETSIZE +#ifdef RLIMIT_NOFILE + struct rlimit rl; + getrlimit(RLIMIT_NOFILE, &rl); + rl.rlim_cur = FD_SETSIZE; + rl.rlim_max = FD_SETSIZE; + setrlimit(RLIMIT_NOFILE, &rl); +#endif +#endif + /* If we've got OpenSSL, we're going to use it. */ #ifdef HAVE_OPENSSL init_ssl(); #endif /* Set up a bunch of semaphores to be used for critical sections */ - for (a=0; astate = CON_EXECUTING; - /* * Generate a unique session number and insert this context into * the list. */ begin_critical_section(S_SESSION_TABLE); - - if (ContextList == NULL) { - ContextList = me; - me->cs_pid = 1; - me->next = NULL; - } - - else if (ContextList->cs_pid > 1) { - me->next = ContextList; - ContextList = me; - me->cs_pid = 1; + me->cs_pid = ++next_pid; + me->prev = NULL; + me->next = ContextList; + ContextList = me; + if (me->next != NULL) { + me->next->prev = me; } - - else { - for (ptr = ContextList; ptr != NULL; ptr = ptr->next) { - if (ptr->next == NULL) { - ptr->next = me; - me->cs_pid = ptr->cs_pid + 1; - me->next = NULL; - goto DONE; - } - else if (ptr->next->cs_pid > (ptr->cs_pid+1)) { - me->next = ptr->next; - ptr->next = me; - me->cs_pid = ptr->cs_pid + 1; - goto DONE; - } - } - } - -DONE: ++num_sessions; + ++num_sessions; end_critical_section(S_SESSION_TABLE); return(me); } /* - * buffer_output() ... tell client_write to buffer all output until - * instructed to dump it all out later + * The following functions implement output buffering. If the kernel supplies + * native TCP buffering (Linux & *BSD), use that; otherwise, emulate it with + * user-space buffering. */ +#ifndef HAVE_DARWIN +#ifdef TCP_CORK +# define HAVE_TCP_BUFFERING +#else +# ifdef TCP_NOPUSH +# define HAVE_TCP_BUFFERING +# define TCP_CORK TCP_NOPUSH +# endif +#endif /* TCP_CORK */ +#endif /* HAVE_DARWIN */ + +#ifdef HAVE_TCP_BUFFERING +static unsigned on = 1, off = 0; +void buffer_output(void) { + struct CitContext *ctx = MyContext(); + setsockopt(ctx->client_socket, IPPROTO_TCP, TCP_CORK, &on, 4); + ctx->buffering = 1; +} + +void unbuffer_output(void) { + struct CitContext *ctx = MyContext(); + setsockopt(ctx->client_socket, IPPROTO_TCP, TCP_CORK, &off, 4); + ctx->buffering = 0; +} + +void flush_output(void) { + struct CitContext *ctx = MyContext(); + setsockopt(ctx->client_socket, IPPROTO_TCP, TCP_CORK, &off, 4); + setsockopt(ctx->client_socket, IPPROTO_TCP, TCP_CORK, &on, 4); +} +#elif HAVE_DARWIN +/* Stub functions for Darwin/OS X where TCP buffering isn't liked at all */ +void buffer_output(void) { +CC->buffering = 0; +} +void unbuffer_output(void) { +CC->buffering = 0; +} +void flush_output(void) { +} +#else void buffer_output(void) { if (CC->buffering == 0) { CC->buffering = 1; CC->buffer_len = 0; - CC->output_buffer = mallok(SIZ); + CC->output_buffer = malloc(SIZ); + } +} + +void flush_output(void) { + if (CC->buffering == 1) { + client_write(CC->output_buffer, CC->buffer_len); + CC->buffer_len = 0; } } -/* - * unbuffer_output() ... dump out all that output we've been buffering. - */ void unbuffer_output(void) { if (CC->buffering == 1) { CC->buffering = 0; + /* We don't call flush_output because we can't. */ client_write(CC->output_buffer, CC->buffer_len); - phree(CC->output_buffer); - CC->output_buffer = NULL; CC->buffer_len = 0; + free(CC->output_buffer); + CC->output_buffer = NULL; } } +#endif @@ -531,29 +553,32 @@ void client_write(char *buf, int nbytes) { int bytes_written = 0; int retval; - int sock; +#ifndef HAVE_TCP_BUFFERING int old_buffer_len = 0; +#endif - if (CC->redirect_fp != NULL) { - fwrite(buf, nbytes, 1, CC->redirect_fp); + if (CC->redirect_buffer != NULL) { + if ((CC->redirect_len + nbytes + 2) >= CC->redirect_alloc) { + CC->redirect_alloc = (CC->redirect_alloc * 2) + nbytes; + CC->redirect_buffer = realloc(CC->redirect_buffer, + CC->redirect_alloc); + } + memcpy(&CC->redirect_buffer[CC->redirect_len], buf, nbytes); + CC->redirect_len += nbytes; + CC->redirect_buffer[CC->redirect_len] = 0; return; } - if (CC->redirect_sock > 0) { - sock = CC->redirect_sock; /* and continue below... */ - } - else { - sock = CC->client_socket; - } - +#ifndef HAVE_TCP_BUFFERING /* If we're buffering for later, do that now. */ if (CC->buffering) { old_buffer_len = CC->buffer_len; CC->buffer_len += nbytes; - CC->output_buffer = reallok(CC->output_buffer, CC->buffer_len); + CC->output_buffer = realloc(CC->output_buffer, CC->buffer_len); memcpy(&CC->output_buffer[old_buffer_len], buf, nbytes); return; } +#endif /* Ok, at this point we're not buffering. Go ahead and write. */ @@ -565,12 +590,14 @@ void client_write(char *buf, int nbytes) #endif while (bytes_written < nbytes) { - retval = write(sock, &buf[bytes_written], + retval = write(CC->client_socket, &buf[bytes_written], nbytes - bytes_written); if (retval < 1) { - lprintf(CTDL_ERR, "client_write() failed: %s\n", - strerror(errno)); - if (sock == CC->client_socket) CC->kill_me = 1; + lprintf(CTDL_ERR, + "client_write(%d bytes) failed: %s (%d)\n", + nbytes - bytes_written, + strerror(errno), errno); + CC->kill_me = 1; return; } bytes_written = bytes_written + retval; @@ -580,15 +607,15 @@ void client_write(char *buf, int nbytes) /* * cprintf() ... Send formatted printable data to the client. It is - * implemented in terms of client_write() but remains in - * sysdep.c in case we port to somewhere without va_args... + * implemented in terms of client_write() but remains in + * sysdep.c in case we port to somewhere without va_args... */ void cprintf(const char *format, ...) { - va_list arg_ptr; - char buf[SIZ]; + va_list arg_ptr; + char buf[1024]; - va_start(arg_ptr, format); - if (vsnprintf(buf, sizeof buf, format, arg_ptr) == -1) + va_start(arg_ptr, format); + if (vsnprintf(buf, sizeof buf, format, arg_ptr) == -1) buf[sizeof buf - 2] = '\n'; client_write(buf, strlen(buf)); va_end(arg_ptr); @@ -631,8 +658,7 @@ int client_read_to(char *buf, int bytes, int timeout) rlen = read(CC->client_socket, &buf[len], bytes-len); if (rlen<1) { - lprintf(CTDL_ERR, "client_read() failed: %s\n", - strerror(errno)); + /* The socket has been disconnected! */ CC->kill_me = 1; return(-1); } @@ -653,11 +679,11 @@ INLINE int client_read(char *buf, int bytes) /* - * client_gets() ... Get a LF-terminated line of text from the client. + * client_getln() ... Get a LF-terminated line of text from the client. * (This is implemented in terms of client_read() and could be * justifiably moved out of sysdep.c) */ -int client_gets(char *buf) +int client_getln(char *buf, int bufsize) { int i, retval; @@ -665,22 +691,23 @@ int client_gets(char *buf) */ for (i = 0;;i++) { retval = client_read(&buf[i], 1); - if (retval != 1 || buf[i] == '\n' || i == (SIZ-1)) + if (retval != 1 || buf[i] == '\n' || i == (bufsize-1)) break; } /* If we got a long line, discard characters until the newline. */ - if (i == (SIZ-1)) + if (i == (bufsize-1)) while (buf[i] != '\n' && retval == 1) retval = client_read(&buf[i], 1); - /* Strip the trailing newline and any trailing nonprintables (cr's) + /* Strip the trailing LF, and the trailing CR if present. */ buf[i] = 0; - while ((strlen(buf)>0)&&(!isprint(buf[strlen(buf)-1]))) + while ( (!IsEmptyStr(buf)) && ((buf[strlen(buf)-1]==10) || (buf[strlen(buf)-1] == 13)) ) { buf[strlen(buf)-1] = 0; - if (retval < 0) strcpy(buf, "000"); + } + if (retval < 0) safestrncpy(buf, "000", bufsize); return(retval); } @@ -692,6 +719,7 @@ int client_gets(char *buf) void sysdep_master_cleanup(void) { struct ServiceFunctionHook *serviceptr; +///// DestroyWorkerList(); /* * close all protocol master sockets */ @@ -713,9 +741,25 @@ void sysdep_master_cleanup(void) { unlink(serviceptr->sockpath); } } +#ifdef HAVE_OPENSSL + destruct_ssl(); +#endif + serv_calendar_destroy(); + CtdlDestroyProtoHooks(); + CtdlDestroyDeleteHooks(); + CtdlDestroyXmsgHooks(); + CtdlDestroyNetprocHooks(); + CtdlDestroyUserHooks(); + CtdlDestroyMessageHook(); + CtdlDestroyCleanupHooks(); + CtdlDestroyFixedOutputHooks(); + CtdlDestroySessionHooks(); + CtdlDestroyServiceHook(); } + + /* * Terminate another session. * (This could justifiably be moved out of sysdep.c because it @@ -733,22 +777,99 @@ void kill_session(int session_to_kill) { end_critical_section(S_SESSION_TABLE); } - +pid_t current_child; +void graceful_shutdown(int signum) { + kill(current_child, signum); + unlink(file_pid_file); + exit(0); +} /* - * Start running as a daemon. Only close stdio if do_close_stdio is set. + * Start running as a daemon. */ -void start_daemon(int do_close_stdio) { - if (do_close_stdio) { - /* close(0); */ - close(1); - close(2); +void start_daemon(int unused) { + int status = 0; + pid_t child = 0; + FILE *fp; + int do_restart = 0; + + current_child = 0; + + /* Close stdin/stdout/stderr and replace them with /dev/null. + * We don't just call close() because we don't want these fd's + * to be reused for other files. + */ + chdir(ctdl_run_dir); + + child = fork(); + if (child != 0) { + exit(0); } - signal(SIGHUP,SIG_IGN); - signal(SIGINT,SIG_IGN); - signal(SIGQUIT,SIG_IGN); - if (fork()!=0) exit(0); + + signal(SIGHUP, SIG_IGN); + signal(SIGINT, SIG_IGN); + signal(SIGQUIT, SIG_IGN); + + setsid(); + umask(0); + freopen("/dev/null", "r", stdin); + freopen("/dev/null", "w", stdout); + freopen("/dev/null", "w", stderr); + + do { + current_child = fork(); + + signal(SIGTERM, graceful_shutdown); + + if (current_child < 0) { + perror("fork"); + exit(errno); + } + + else if (current_child == 0) { + return; /* continue starting citadel. */ + } + + else { + fp = fopen(file_pid_file, "w"); + if (fp != NULL) { + fprintf(fp, ""F_PID_T"\n", child); + fclose(fp); + } + waitpid(current_child, &status, 0); + } + + do_restart = 0; + + /* Did the main process exit with an actual exit code? */ + if (WIFEXITED(status)) { + + /* Exit code 0 means the watcher should exit */ + if (WEXITSTATUS(status) == 0) { + do_restart = 0; + } + + /* Exit code 101-109 means the watcher should exit */ + else if ( (WEXITSTATUS(status) >= 101) && (WEXITSTATUS(status) <= 109) ) { + do_restart = 0; + } + + /* Any other exit code means we should restart. */ + else { + do_restart = 1; + } + } + + /* Any other type of termination (signals, etc.) should also restart. */ + else { + do_restart = 1; + } + + } while (do_restart); + + unlink(file_pid_file); + exit(WEXITSTATUS(status)); } @@ -786,7 +907,7 @@ void create_worker(void) { struct worker_node *n; pthread_attr_t attr; - n = mallok(sizeof(struct worker_node)); + n = malloc(sizeof(struct worker_node)); if (n == NULL) { lprintf(CTDL_EMERG, "can't allocate worker_node, exiting\n"); time_to_die = -1; @@ -799,11 +920,15 @@ void create_worker(void) { return; } - /* we seem to need something bigger than FreeBSD's default 64k stack */ - - if ((ret = pthread_attr_setstacksize(&attr, 128 * 1024))) { - lprintf(CTDL_EMERG, "pthread_attr_setstacksize: %s\n", strerror(ret)); + /* Our per-thread stacks need to be bigger than the default size, + * otherwise the MIME parser crashes on FreeBSD, and the IMAP service + * crashes on 64-bit Linux. + */ + if ((ret = pthread_attr_setstacksize(&attr, THREADSTACKSIZE))) { + lprintf(CTDL_EMERG, "pthread_attr_setstacksize: %s\n", + strerror(ret)); time_to_die = -1; + pthread_attr_destroy(&attr); return; } @@ -816,6 +941,83 @@ void create_worker(void) { n->next = worker_list; worker_list = n; + pthread_attr_destroy(&attr); +} + +void DestroyWorkerList(void) +{ + struct CitContext *ptr; /* general-purpose utility pointer */ + struct CitContext *rem = NULL; /* list of sessions to be destroyed */ + + begin_critical_section(S_SESSION_TABLE); + ptr = ContextList; + while (ptr != NULL){ + /* Remove the session from the active list */ + rem = ptr->next; + --num_sessions; + + lprintf(CTDL_DEBUG, "Purging session %d\n", rem->cs_pid); + end_critical_section(S_SESSION_TABLE); + RemoveContext(ptr); + begin_critical_section(S_SESSION_TABLE); + free (ptr); + ptr = rem; + } + end_critical_section(S_SESSION_TABLE); + + struct worker_node *cur, *p; + cur = worker_list; + while (cur != NULL) + { + p = cur->next; + free (cur); + cur = p; + } + worker_list = NULL; +} + +/* + * Create the maintenance threads and begin their operation. + */ +void create_maintenance_threads(void) { + int ret; + pthread_attr_t attr; + + if ((ret = pthread_attr_init(&attr))) { + lprintf(CTDL_EMERG, "pthread_attr_init: %s\n", strerror(ret)); + time_to_die = -1; + return; + } + + /* Our per-thread stacks need to be bigger than the default size, + * otherwise the MIME parser crashes on FreeBSD, and the IMAP service + * crashes on 64-bit Linux. + */ + if ((ret = pthread_attr_setstacksize(&attr, THREADSTACKSIZE))) { + lprintf(CTDL_EMERG, "pthread_attr_setstacksize: %s\n", + strerror(ret)); + time_to_die = -1; + pthread_attr_destroy(&attr); + return; + } + + struct MaintenanceThreadHook *fcn; + + lprintf(CTDL_DEBUG, "Performing startup of maintenance thread hooks\n"); + + for (fcn = MaintenanceThreadHookTable; fcn != NULL; fcn = fcn->next) { + if ((ret = pthread_create(&(fcn->MaintenanceThread_tid), &attr, fcn->fcn_ptr, NULL) != 0)) { + lprintf(CTDL_ALERT, "Can't create thread: %s\n", strerror(ret)); + } + else + { + lprintf(CTDL_NOTICE, "Spawned a new maintenance thread \"%s\" (%ld). \n", fcn->name, + fcn->MaintenanceThread_tid); + } + } + + + pthread_attr_destroy(&attr); } @@ -824,101 +1026,74 @@ void create_worker(void) { * Purge all sessions which have the 'kill_me' flag set. * This function has code to prevent it from running more than once every * few seconds, because running it after every single unbind would waste a lot - * of CPU time and keep the context list locked too much. + * of CPU time and keep the context list locked too much. To force it to run + * anyway, set "force" to nonzero. * - * After that's done, we raise or lower the size of the worker thread pool + * + * After that's done, we raise the size of the worker thread pool * if such an action is appropriate. */ -void dead_session_purge(void) { - struct CitContext *ptr, *rem; - struct worker_node **node, *tmp; - pthread_t self; +void dead_session_purge(int force) { + struct CitContext *ptr; /* general-purpose utility pointer */ + struct CitContext *rem = NULL; /* list of sessions to be destroyed */ - if ( (time(NULL) - last_purge) < 5 ) return; /* Too soon, go away */ + if (force == 0) { + if ( (time(NULL) - last_purge) < 5 ) { + return; /* Too soon, go away */ + } + } time(&last_purge); - do { - rem = NULL; - begin_critical_section(S_SESSION_TABLE); - for (ptr = ContextList; ptr != NULL; ptr = ptr->next) { - if ( (ptr->state == CON_IDLE) && (ptr->kill_me) ) { - rem = ptr; + begin_critical_section(S_SESSION_TABLE); + for (ptr = ContextList; ptr != NULL; ptr = ptr->next) { + if ( (ptr->state == CON_IDLE) && (ptr->kill_me) ) { + + /* Remove the session from the active list */ + if (ptr->prev) { + ptr->prev->next = ptr->next; + } + else { + ContextList = ptr->next; + } + if (ptr->next) { + ptr->next->prev = ptr->prev; } - } - end_critical_section(S_SESSION_TABLE); - /* RemoveContext() enters its own S_SESSION_TABLE critical - * section, so we have to do it like this. - */ - if (rem != NULL) { - lprintf(CTDL_DEBUG, "Purging session %d\n", rem->cs_pid); - RemoveContext(rem); - } + --num_sessions; - } while (rem != NULL); + /* And put it on our to-be-destroyed list */ + ptr->next = rem; + rem = ptr; + } + } + end_critical_section(S_SESSION_TABLE); - /* Raise or lower the size of the worker thread pool if such - * an action is appropriate. + /* Now that we no longer have the session list locked, we can take + * our time and destroy any sessions on the to-be-killed list, which + * is allocated privately on this thread's stack. */ + while (rem != NULL) { + lprintf(CTDL_DEBUG, "Purging session %d\n", rem->cs_pid); + RemoveContext(rem); + ptr = rem; + rem = rem->next; + free(ptr); + } - self = pthread_self(); - + /* Raise the size of the worker thread pool if necessary. */ if ( (num_sessions > num_threads) && (num_threads < config.c_max_workers) ) { begin_critical_section(S_WORKER_LIST); create_worker(); end_critical_section(S_WORKER_LIST); } - - /* don't let the initial thread die since it's responsible for - waiting for all the other threads to terminate. */ - else if ( (num_sessions < num_threads) - && (num_threads > config.c_min_workers) - && (self != initial_thread) ) { - cdb_free_tsd(); - begin_critical_section(S_WORKER_LIST); - --num_threads; - - /* we're exiting before server shutdown... unlink ourself from - the worker list and detach our thread to avoid memory leaks - */ - - for (node = &worker_list; *node != NULL; node = &(*node)->next) - if ((*node)->tid == self) { - tmp = *node; - *node = (*node)->next; - phree(tmp); - break; - } - - pthread_detach(self); - end_critical_section(S_WORKER_LIST); - pthread_exit(NULL); - } - } -/* - * Redirect a session's output to a file or socket. - * This function may be called with a file handle *or* a socket (but not - * both). Call with neither to return output to its normal client socket. - */ -void CtdlRedirectOutput(FILE *fp, int sock) { - - if (fp != NULL) CC->redirect_fp = fp; - else CC->redirect_fp = NULL; - - if (sock > 0) CC->redirect_sock = sock; - else CC->redirect_sock = (-1); - -} - - /* * masterCC is the context we use when not attached to a session. This * function initializes it. @@ -931,37 +1106,7 @@ void InitializeMasterCC(void) { -/* - * Set up a fd_set containing all the master sockets to which we - * always listen. It's computationally less expensive to just copy - * this to a local fd_set when starting a new select() and then add - * the client sockets than it is to initialize a new one and then - * figure out what to put there. - */ -void init_master_fdset(void) { - struct ServiceFunctionHook *serviceptr; - int m; - - lprintf(CTDL_DEBUG, "Initializing master fdset\n"); - FD_ZERO(&masterfds); - masterhighest = 0; - - lprintf(CTDL_DEBUG, "Will listen on rescan pipe %d\n", rescan[0]); - FD_SET(rescan[0], &masterfds); - if (rescan[0] > masterhighest) masterhighest = rescan[0]; - - for (serviceptr = ServiceHookTable; serviceptr != NULL; - serviceptr = serviceptr->next ) { - m = serviceptr->msock; - lprintf(CTDL_DEBUG, "Will listen on master socket %d\n", m); - FD_SET(m, &masterfds); - if (m > masterhighest) { - masterhighest = m; - } - } - lprintf(CTDL_DEBUG, "masterhighest = %d\n", masterhighest); -} /* @@ -978,16 +1123,17 @@ INLINE void become_session(struct CitContext *which_con) { */ void *worker_thread(void *arg) { int i; - char junk; int highest; struct CitContext *ptr; struct CitContext *bind_me = NULL; fd_set readfds; - int retval; + int retval = 0; struct CitContext *con= NULL; /* Temporary context pointer */ struct ServiceFunctionHook *serviceptr; int ssock; /* Descriptor for client socket */ struct timeval tv; + int force_purge = 0; + int m; num_threads++; @@ -995,26 +1141,17 @@ void *worker_thread(void *arg) { while (!time_to_die) { - /* - * A naive implementation would have all idle threads - * calling select() and then they'd all wake up at once - * (known in computer science as the "thundering herd" - * problem). 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 a master - * socket, create a new session context; otherwise, just - * bind the thread to the context we want and go on our - * merry way. - */ - /* make doubly sure we're not holding any stale db handles * which might cause a deadlock. */ cdb_check_handles(); +do_select: force_purge = 0; + bind_me = NULL; /* Which session shall we handle? */ + + /* Initialize the fdset. */ + FD_ZERO(&readfds); + highest = 0; - begin_critical_section(S_I_WANNA_SELECT); -SETUP_FD: memcpy(&readfds, &masterfds, sizeof masterfds); - highest = masterhighest; begin_critical_section(S_SESSION_TABLE); for (ptr = ContextList; ptr != NULL; ptr = ptr->next) { if (ptr->state == CON_IDLE) { @@ -1022,24 +1159,49 @@ SETUP_FD: memcpy(&readfds, &masterfds, sizeof masterfds); if (ptr->client_socket > highest) highest = ptr->client_socket; } + if ((bind_me == NULL) && (ptr->state == CON_READY)) { + bind_me = ptr; + ptr->state = CON_EXECUTING; + } } end_critical_section(S_SESSION_TABLE); - tv.tv_sec = 1; /* wake up every second if no input */ - tv.tv_usec = 0; + if (bind_me) { + goto SKIP_SELECT; + } - do_select: - if (!time_to_die) + /* If we got this far, it means that there are no sessions + * which a previous thread marked for attention, so we go + * ahead and get ready to select(). + */ + + /* First, add the various master sockets to the fdset. */ + for (serviceptr = ServiceHookTable; serviceptr != NULL; + serviceptr = serviceptr->next ) { + m = serviceptr->msock; + FD_SET(m, &readfds); + if (m > highest) { + highest = m; + } + } + + if (!time_to_die) { + tv.tv_sec = 1; /* wake up every second if no input */ + tv.tv_usec = 0; retval = select(highest + 1, &readfds, NULL, NULL, &tv); - else { - end_critical_section(S_I_WANNA_SELECT); - break; } + if (time_to_die) return(NULL); + /* Now figure out who made this select() unblock. * First, check for an error or exit condition. */ if (retval < 0) { + if (errno == EBADF) { + lprintf(CTDL_NOTICE, "select() failed: (%s)\n", + strerror(errno)); + goto do_select; + } if (errno != EINTR) { lprintf(CTDL_EMERG, "Exiting (%s)\n", strerror(errno)); time_to_die = 1; @@ -1055,27 +1217,34 @@ SETUP_FD: memcpy(&readfds, &masterfds, sizeof masterfds); if (FD_ISSET(serviceptr->msock, &readfds)) { ssock = accept(serviceptr->msock, NULL, 0); - if (ssock < 0) { - lprintf(CTDL_CRIT, - "citserver: accept(): %s\n", - strerror(errno)); - } - else { - lprintf(CTDL_NOTICE, + if (ssock >= 0) { + lprintf(CTDL_DEBUG, "New client socket %d\n", ssock); + /* The master socket is non-blocking but the client + * sockets need to be blocking, otherwise certain + * operations barf on FreeBSD. Not a fatal error. + */ + if (fcntl(ssock, F_SETFL, 0) < 0) { + lprintf(CTDL_EMERG, + "citserver: Can't set socket to blocking: %s\n", + strerror(errno)); + } + /* New context will be created already - * set up in the CON_EXECUTING state. - */ + * set up in the CON_EXECUTING state. + */ con = CreateNewContext(); - /* Assign new socket number to it. */ + /* Assign our new socket number to it. */ con->client_socket = ssock; con->h_command_function = serviceptr->h_command_function; + con->h_async_function = + serviceptr->h_async_function; - /* Determine whether local socket */ + /* Determine whether it's a local socket */ if (serviceptr->sockpath != NULL) con->is_local_socket = 1; @@ -1090,70 +1259,64 @@ SETUP_FD: memcpy(&readfds, &masterfds, sizeof masterfds); serviceptr->h_greeting_function(); become_session(NULL); con->state = CON_IDLE; - goto SETUP_FD; + goto do_select; } } } - /* If the rescan pipe went active, someone is telling this - * thread that the &readfds needs to be refreshed with more - * current data. - */ - if (time_to_die) { - end_critical_section(S_I_WANNA_SELECT); - break; - } - - if (FD_ISSET(rescan[0], &readfds)) { - 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. + * waiting on its socket *and* is in the CON_IDLE state. Any + * active sockets other than our chosen one are marked as + * CON_READY so the next thread that comes around can just bind + * to one without having to select() again. */ - 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 = ptr; + begin_critical_section(S_SESSION_TABLE); + for (ptr = ContextList; ptr != NULL; ptr = ptr->next) { + if ( (FD_ISSET(ptr->client_socket, &readfds)) + && (ptr->state != CON_EXECUTING) ) { + ptr->input_waiting = 1; + if (!bind_me) { + bind_me = ptr; /* I choose you! */ + bind_me->state = CON_EXECUTING; + } + else { + ptr->state = CON_READY; } } - if (bind_me != NULL) { - /* Found one. Stake a claim to it before - * letting anyone else touch the context list. - */ - bind_me->state = CON_EXECUTING; - } + } + end_critical_section(S_SESSION_TABLE); - end_critical_section(S_SESSION_TABLE); - end_critical_section(S_I_WANNA_SELECT); +SKIP_SELECT: + /* We're bound to a session */ + if (bind_me != NULL) { + become_session(bind_me); - /* We're bound to a session, now do *one* command */ - if (bind_me != NULL) { - become_session(bind_me); + /* If the client has sent a command, execute it. */ + if (CC->input_waiting) { CC->h_command_function(); - become_session(NULL); - bind_me->state = CON_IDLE; - if (bind_me->kill_me == 1) { - RemoveContext(bind_me); - } - write(rescan[1], &junk, 1); + CC->input_waiting = 0; } + /* If there are asynchronous messages waiting and the + * client supports it, do those now */ + if ((CC->is_async) && (CC->async_waiting) + && (CC->h_async_function != NULL)) { + CC->h_async_function(); + CC->async_waiting = 0; + } + + force_purge = CC->kill_me; + become_session(NULL); + bind_me->state = CON_IDLE; } - dead_session_purge(); + + dead_session_purge(force_purge); do_housekeeping(); check_sched_shutdown(); } - + if (con != NULL) free (con);//// TODO: could this harm other threads? /* If control reaches this point, the server is shutting down */ - --num_threads; - return NULL; + return(NULL); } @@ -1195,6 +1358,115 @@ int SyslogFacility(char *name) if(!strcasecmp(name, facTbl[i].name)) return facTbl[i].facility; } - return -1; + enable_syslog = 0; + return LOG_DAEMON; +} + + +/********** MEM CHEQQER ***********/ + +#ifdef DEBUG_MEMORY_LEAKS + +#undef malloc +#undef realloc +#undef strdup +#undef free + +void *tracked_malloc(size_t size, char *file, int line) { + struct igheap *thisheap; + void *block; + + block = malloc(size); + if (block == NULL) return(block); + + thisheap = malloc(sizeof(struct igheap)); + if (thisheap == NULL) { + free(block); + return(NULL); + } + + thisheap->block = block; + strcpy(thisheap->file, file); + thisheap->line = line; + + begin_critical_section(S_DEBUGMEMLEAKS); + thisheap->next = igheap; + igheap = thisheap; + end_critical_section(S_DEBUGMEMLEAKS); + + return(block); +} + + +void *tracked_realloc(void *ptr, size_t size, char *file, int line) { + struct igheap *thisheap; + void *block; + + block = realloc(ptr, size); + if (block == NULL) return(block); + + thisheap = malloc(sizeof(struct igheap)); + if (thisheap == NULL) { + free(block); + return(NULL); + } + + thisheap->block = block; + strcpy(thisheap->file, file); + thisheap->line = line; + + begin_critical_section(S_DEBUGMEMLEAKS); + thisheap->next = igheap; + igheap = thisheap; + end_critical_section(S_DEBUGMEMLEAKS); + + return(block); +} + + + +void tracked_free(void *ptr) { + struct igheap *thisheap; + struct igheap *trash; + + free(ptr); + + if (igheap == NULL) return; + begin_critical_section(S_DEBUGMEMLEAKS); + for (thisheap = igheap; thisheap != NULL; thisheap = thisheap->next) { + if (thisheap->next != NULL) { + if (thisheap->next->block == ptr) { + trash = thisheap->next; + thisheap->next = thisheap->next->next; + free(trash); + } + } + } + if (igheap->block == ptr) { + trash = igheap; + igheap = igheap->next; + free(trash); + } + end_critical_section(S_DEBUGMEMLEAKS); +} + +char *tracked_strdup(const char *s, char *file, int line) { + char *ptr; + + if (s == NULL) return(NULL); + ptr = tracked_malloc(strlen(s) + 1, file, line); + if (ptr == NULL) return(NULL); + strncpy(ptr, s, strlen(s)); + return(ptr); +} + +void dump_heap(void) { + struct igheap *thisheap; + + for (thisheap = igheap; thisheap != NULL; thisheap = thisheap->next) { + lprintf(CTDL_CRIT, "UNFREED: %30s : %d\n", + thisheap->file, thisheap->line); + } } +#endif /* DEBUG_MEMORY_LEAKS */