/*
* $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
#include <limits.h>
#include <netinet/in.h>
+#include <arpa/inet.h>
#include <netdb.h>
#include <sys/un.h>
#include <string.h>
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);
-/* This is synchronized below; it helps implement round robin mode */
-extern struct CitContext* next_session;
/*
* lprintf() ... Write logging information
* log data sent through this function. BE CAREFUL!
*/
void lprintf(enum LogLevel loglevel, const char *format, ...) {
- va_list arg_ptr;
+ va_list arg_ptr;
char buf[SIZ];
- va_start(arg_ptr, format);
- vsnprintf(buf, sizeof(buf), format, arg_ptr);
- va_end(arg_ptr);
+ va_start(arg_ptr, format);
+ vsnprintf(buf, sizeof(buf), format, arg_ptr);
+ va_end(arg_ptr);
if (syslog_facility >= 0) {
if (loglevel <= verbosity) {
}
else 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);
+ localtime_r(&unixtime, &tim);
/*
* Log provides millisecond accuracy. If you need
* microsecond accuracy and your OS supports it, change
/* Millisecond display */
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,
+ 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);
#endif
/* Microsecond display */
fprintf(stderr,
"%04d/%02d/%02d %2d:%02d:%02d.%06ld [%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,
+ 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, buf);
} else {
#if 0
/* Millisecond display */
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);
+ 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);
#endif
/* Microsecond display */
fprintf(stderr,
"%04d/%02d/%02d %2d:%02d:%02d.%06ld %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, buf);
+ tim.tm_year + 1900, tim.tm_mon + 1,
+ tim.tm_mday, tim.tm_hour, tim.tm_min,
+ tim.tm_sec, (long)tv.tv_usec, buf);
}
fflush(stderr);
}
* a TCP port. The server shuts down if the bind fails.
*
*/
-int ig_tcp_server(int port_number, int queue_len)
+int ig_tcp_server(char *ip_addr, int port_number, int queue_len)
{
struct sockaddr_in sin;
int s, i;
memset(&sin, 0, sizeof(sin));
sin.sin_family = AF_INET;
- sin.sin_addr.s_addr = INADDR_ANY;
sin.sin_port = htons((u_short)port_number);
+ if (ip_addr == NULL) {
+ sin.sin_addr.s_addr = INADDR_ANY;
+ }
+ else {
+ sin.sin_addr.s_addr = inet_addr(ip_addr);
+ }
+
+ if (sin.sin_addr.s_addr == INADDR_NONE) {
+ sin.sin_addr.s_addr = INADDR_ANY;
+ }
s = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
* called this function. If there's no such binding (for example, if it's
* called by the housekeeper thread) then a generic 'master' CC is returned.
*
- * It's inlined because it's used *VERY* frequently.
+ * This function is used *VERY* frequently and must be kept small.
*/
-INLINE struct CitContext *MyContext(void) {
- return ((pthread_getspecific(MyConKey) == NULL)
- ? &masterCC
- : (struct CitContext *) pthread_getspecific(MyConKey)
+struct CitContext *MyContext(void) {
+
+ register struct CitContext *c;
+
+ return ((c = (struct CitContext *) pthread_getspecific(MyConKey),
+ c == NULL) ? &masterCC : c
);
}
/*
* buffer_output() ... tell client_write to buffer all output until
- * instructed to dump it all out later
+ * instructed to dump it all out later
*/
void buffer_output(void) {
if (CC->buffering == 0) {
}
/*
- * unbuffer_output() ... dump out all that output we've been buffering.
+ * flush_output() ... dump out all that output we've been buffering.
+ */
+void flush_output(void) {
+ if (CC->buffering == 1) {
+ client_write(CC->output_buffer, CC->buffer_len);
+ CC->buffer_len = 0;
+ }
+}
+
+/*
+ * unbuffer_output() ... stop buffering output.
*/
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);
+ CC->buffer_len = 0;
free(CC->output_buffer);
CC->output_buffer = NULL;
- CC->buffer_len = 0;
}
}
/*
* 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[SIZ];
- 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);
if ((ret = pthread_attr_setstacksize(&attr, 128 * 1024))) {
lprintf(CTDL_EMERG, "pthread_attr_setstacksize: %s\n", strerror(ret));
time_to_die = -1;
+ pthread_attr_destroy(&attr);
return;
}
n->next = worker_list;
worker_list = n;
+ pthread_attr_destroy(&attr);
}
-/*
- * 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);
-}
/*
*/
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++;
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();
- force_purge = 0;
+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) {
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;
+
+ /* 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().
+ */
- do_select:
- if (!time_to_die)
+ /* 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 (errno == EBADF) {
lprintf(CTDL_NOTICE, "select() failed: (%s)\n",
strerror(errno));
- goto SETUP_FD;
+ goto do_select;
}
if (errno != EINTR) {
lprintf(CTDL_EMERG, "Exiting (%s)\n", strerror(errno));
strerror(errno));
}
else {
- lprintf(CTDL_NOTICE,
+ lprintf(CTDL_DEBUG,
"New client socket %d\n",
ssock);
con->client_socket = ssock;
con->h_command_function =
serviceptr->h_command_function;
+ con->h_async_function =
+ serviceptr->h_async_function;
/* Determine whether local socket */
if (serviceptr->sockpath != NULL)
serviceptr->h_greeting_function();
become_session(NULL);
con->state = CON_IDLE;
- goto SETUP_FD;
+ goto do_select;
}
}
}
- if (time_to_die) {
- end_critical_section(S_I_WANNA_SELECT);
- break;
- }
-
- /* If the rescan pipe went active, someone is telling this
- * thread that the &readfds needs to be refreshed with more
- * current data.
- */
- 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);
- /*
- * We start where we left off. If we get to the end
- * we'll start from the beginning again, then give up
- * if we still don't find anything. This ensures
- * that all contexts get a more-or-less equal chance
- * to run. And yes, I did add a goto to the code. -IO
- */
-find_session: if (next_session == NULL)
- next_session = ContextList;
- for (ptr = next_session;
- ( (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;
- next_session = bind_me->next;
- } else if (next_session == ContextList) {
- next_session = NULL;
- }
- if (bind_me == NULL && next_session != NULL) {
- next_session = NULL;
- goto find_session;
- }
+ }
+ 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();
- force_purge = CC->kill_me;
- become_session(NULL);
- bind_me->state = CON_IDLE;
- 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(force_purge);
do_housekeeping();
check_sched_shutdown();
}
/* If control reaches this point, the server is shutting down */
- --num_threads;
- return NULL;
+ return(NULL);
}