Newer
Older
if(host!=NULL && host->h_name!=NULL)
host_name=host->h_name;
else
host_name=session.host_ip;
SAFECOPY(session.host_name,host_name);
if(!(startup->options&BBS_OPT_NO_HOST_LOOKUP)) {
lprintf(LOG_INFO,"%04d Hostname: %s", session.socket, host_name);
for(i=0;host!=NULL && host->h_aliases!=NULL
&& host->h_aliases[i]!=NULL;i++)
lprintf(LOG_INFO,"%04d HostAlias: %s", session.socket, host->h_aliases[i]);
if(trashcan(&scfg,host_name,"host")) {
close_socket(session.socket);
session.socket=INVALID_SOCKET;
sem_wait(&session.output_thread_terminated);
RingBufDispose(&session.outbuf);
lprintf(LOG_NOTICE,"%04d !CLIENT BLOCKED in host.can: %s", session.socket, host_name);
session_threads--;
/* host_ip wasn't defined in http_session_thread */
if(trashcan(&scfg,session.host_ip,"ip")) {
close_socket(session.socket);
session.socket=INVALID_SOCKET;
sem_wait(&session.output_thread_terminated);
RingBufDispose(&session.outbuf);
lprintf(LOG_NOTICE,"%04d !CLIENT BLOCKED in ip.can: %s", session.socket, session.host_ip);
thread_down();
session_threads--;
return;
}
active_clients++;
update_clients();
SAFECOPY(session.username,unknown);
SAFECOPY(session.client.addr,session.host_ip);
SAFECOPY(session.client.host,session.host_name);
session.client.port=ntohs(session.addr.sin_port);
session.client.time=time(NULL);
session.client.protocol="HTTP";
session.client.user=session.username;
session.client.size=sizeof(session.client);
client_on(session.socket, &session.client, /* update existing client record? */FALSE);
session.last_user_num=-1;
session.last_js_user_num=-1;
session.logon_time=0;
session.subscan=(subscan_t*)malloc(sizeof(subscan_t)*scfg.total_subs);
while(!session.finished && server_socket!=INVALID_SOCKET) {
init_error=FALSE;
memset(&(session.req), 0, sizeof(session.req));
loop_count=0;
session.req.ld=NULL;
if(startup->options&WEB_OPT_HTTP_LOGGING) {
if((session.req.ld=(struct log_data*)malloc(sizeof(struct log_data)))==NULL)
lprintf(LOG_ERR,"%04d Cannot allocate memory for log data!",session.socket);
}
if(session.req.ld!=NULL) {
memset(session.req.ld,0,sizeof(struct log_data));
session.req.ld->hostname=strdup(session.host_name);
}
while((redirp==NULL || session.req.send_location >= MOVED_TEMP)
&& !session.finished && !session.req.finished
&& server_socket!=INVALID_SOCKET) {
SAFECOPY(session.req.status,"200 OK");
session.req.send_location=NO_LOCATION;
if(session.req.headers==NULL) {
if((session.req.headers=strListInit())==NULL) {
lprintf(LOG_ERR,"%04d !ERROR allocating memory for header list",session.socket);
init_error=TRUE;
}
if(session.req.cgi_env==NULL) {
if((session.req.cgi_env=strListInit())==NULL) {
lprintf(LOG_ERR,"%04d !ERROR allocating memory for CGI environment list",session.socket);
init_error=TRUE;
}
}
if(session.req.dynamic_heads==NULL) {
if((session.req.dynamic_heads=strListInit())==NULL) {
lprintf(LOG_ERR,"%04d !ERROR allocating memory for dynamic header list",session.socket);
init_error=TRUE;
}
if(get_req(&session,redirp)) {
if(init_error) {
send_error(&session, error_500);
}
/* At this point, if redirp is non-NULL then the headers have already been parsed */
if((session.http_ver<HTTP_1_0)||redirp!=NULL||parse_headers(&session)) {
if(check_request(&session)) {
if(session.req.send_location < MOVED_TEMP || session.req.virtual_path[0]!='/' || loop_count++ >= MAX_REDIR_LOOPS) {
if(read_post_data(&session))
respond(&session);
}
safe_snprintf(redir_req,sizeof(redir_req),"%s %s%s%s",methods[session.req.method]
,session.req.virtual_path,session.http_ver<HTTP_1_0?"":" ",http_vers[session.http_ver]);
lprintf(LOG_DEBUG,"%04d Internal Redirect to: %s",socket,redir_req);
redirp=redir_req;
}
}
close_request(&session);
http_logoff(&session,socket,__LINE__);
if(session.js_cx!=NULL) {
lprintf(LOG_INFO,"%04d JavaScript: Destroying context",socket);
JS_DestroyContext(session.js_cx); /* Free Context */
session.js_cx=NULL;
}
#ifndef ONE_JS_RUNTIME
if(session.js_runtime!=NULL) {
lprintf(LOG_INFO,"%04d JavaScript: Destroying runtime",socket);
JS_DestroyRuntime(session.js_runtime);
#endif
#ifdef _WIN32
if(startup->hangup_sound[0] && !(startup->options&BBS_OPT_MUTE))
PlaySound(startup->hangup_sound, NULL, SND_ASYNC|SND_FILENAME);
#endif
session.socket=INVALID_SOCKET;
sem_wait(&session.output_thread_terminated);
sem_destroy(&session.output_thread_terminated);
RingBufDispose(&session.outbuf);
active_clients--;
update_clients();
client_off(socket);
session_threads--;
thread_down();
if(startup->index_file_name==NULL || startup->cgi_ext==NULL)
lprintf(LOG_DEBUG,"%04d !!! ALL YOUR BASE ARE BELONG TO US !!!", socket);
lprintf(LOG_INFO,"%04d Session thread terminated (%u clients, %u threads remain, %lu served)"
,socket, active_clients, thread_count, served);
}
void DLLCALL web_terminate(void)
{
lprintf(LOG_INFO,"%04d Web Server terminate",server_socket);
terminate_server=TRUE;
}
static void cleanup(int code)
{
free_cfg(&scfg);
while(session_threads)
SLEEP(1);
listFree(&log_list);
mime_types=iniFreeNamedStringList(mime_types);
cgi_handlers=iniFreeNamedStringList(cgi_handlers);
xjs_handlers=iniFreeNamedStringList(xjs_handlers);
semfile_list_free(&recycle_semfiles);
semfile_list_free(&shutdown_semfiles);
if(server_socket!=INVALID_SOCKET) {
close_socket(server_socket);
server_socket=INVALID_SOCKET;
}
update_clients();
#ifdef _WINSOCKAPI_
if(WSAInitialized && WSACleanup()!=0)
lprintf(LOG_ERR,"0000 !WSACleanup ERROR %d",ERROR_VALUE);
#endif
thread_down();
status("Down");
if(terminate_server || code)
lprintf(LOG_INFO,"#### Web Server thread terminated (%u threads remain, %lu clients served)"
,thread_count, served);
if(startup!=NULL && startup->terminated!=NULL)
startup->terminated(startup->cbdata,code);
}
const char* DLLCALL web_ver(void)
{
static char ver[256];
char compiler[32];
DESCRIBE_COMPILER(compiler);
sscanf("$Revision$", "%*s %s", revision);
sprintf(ver,"%s %s%s "
"Compiled %s %s with %s"
,server_name
,revision
#ifdef _DEBUG
," Debug"
#else
,""
#endif
,__DATE__, __TIME__, compiler);
return(ver);
}
void http_logging_thread(void* arg)
{
char base[MAX_PATH+1];
char filename[MAX_PATH+1];
char newfilename[MAX_PATH+1];
FILE* logfile=NULL;
http_logging_thread_running=TRUE;
terminate_http_logging_thread=FALSE;
SAFECOPY(base,arg);
if(!base[0])
SAFEPRINTF(base,"%slogs/http-",scfg.logs_dir);
filename[0]=0;
newfilename[0]=0;
thread_up(TRUE /* setuid */);
lprintf(LOG_DEBUG,"%04d http logging thread started", server_socket);
for(;!terminate_http_logging_thread;) {
struct log_data *ld;
char sizestr[100];
listSemWait(&log_list);
if(terminate_http_logging_thread)
break;
lprintf(LOG_ERR,"%04d http logging thread received NULL linked list log entry"
,server_socket);
continue;
}
SAFECOPY(newfilename,base);
if(startup->options&WEB_OPT_VIRTUAL_HOSTS && ld->vhost!=NULL) {
strcat(newfilename,ld->vhost);
if(ld->vhost[0])
strcat(newfilename,"-");
}
strftime(strchr(newfilename,0),15,"%Y-%m-%d.log",&ld->completed);
if(strcmp(newfilename,filename)) {
fclose(logfile);
SAFECOPY(filename,newfilename);
logfile=fopen(filename,"ab");
lprintf(LOG_INFO,"%04d http logfile is now: %s",server_socket,filename);
}
if(logfile!=NULL) {
if(ld->status) {
sprintf(sizestr,"%d",ld->size);
strftime(timestr,sizeof(timestr),"%d/%b/%Y:%H:%M:%S %z",&ld->completed);
while(lock(fileno(logfile),0,1) && !terminate_http_logging_thread) {
SLEEP(10);
}
fprintf(logfile,"%s %s %s [%s] \"%s\" %d %s \"%s\" \"%s\"\n"
,ld->hostname?(ld->hostname[0]?ld->hostname:"-"):"-"
,ld->ident?(ld->ident[0]?ld->ident:"-"):"-"
,ld->user?(ld->user[0]?ld->user:"-"):"-"
,timestr
,ld->request?(ld->request[0]?ld->request:"-"):"-"
,ld->status
,ld->size?sizestr:"-"
,ld->referrer?(ld->referrer[0]?ld->referrer:"-"):"-"
,ld->agent?(ld->agent[0]?ld->agent:"-"):"-");
fflush(logfile);
unlock(fileno(logfile),0,1);
}
else {
logfile=fopen(filename,"ab");
lprintf(LOG_ERR,"%04d http logfile %s was not open!",server_socket,filename);
}
FREE_AND_NULL(ld->hostname);
FREE_AND_NULL(ld->ident);
FREE_AND_NULL(ld->user);
FREE_AND_NULL(ld->request);
FREE_AND_NULL(ld->referrer);
FREE_AND_NULL(ld->agent);
}
fclose(logfile);
thread_down();
lprintf(LOG_DEBUG,"%04d http logging thread terminated",server_socket);
http_logging_thread_running=FALSE;
}
void DLLCALL web_server(void* arg)
{
int i;
int result;
time_t start;
WORD host_port;
char host_ip[32];
char path[MAX_PATH+1];
char logstr[256];
SOCKADDR_IN server_addr={0};
SOCKADDR_IN client_addr;
socklen_t client_addr_len;
SOCKET client_socket;
SOCKET high_socket_set;
fd_set socket_set;
time_t t;
time_t initialized=0;
char compiler[32];
http_session_t * session;
struct timeval tv;
#ifdef ONE_JS_RUNTIME
JSRuntime* js_runtime;
#endif
if(startup==NULL) {
sbbs_beep(100,500);
fprintf(stderr, "No startup structure passed!\n");
return;
}
if(startup->size!=sizeof(web_startup_t)) { /* verify size */
sbbs_beep(100,500);
sbbs_beep(300,500);
sbbs_beep(100,500);
fprintf(stderr, "Invalid startup structure!\n");
return;
}
#ifdef _THREAD_SUID_BROKEN
startup->seteuid(TRUE);
#endif
/* Setup intelligent defaults */
if(startup->port==0) startup->port=IPPORT_HTTP;
if(startup->root_dir[0]==0) SAFECOPY(startup->root_dir,WEB_DEFAULT_ROOT_DIR);
if(startup->error_dir[0]==0) SAFECOPY(startup->error_dir,WEB_DEFAULT_ERROR_DIR);
if(startup->cgi_dir[0]==0) SAFECOPY(startup->cgi_dir,WEB_DEFAULT_CGI_DIR);
if(startup->default_cgi_content[0]==0) SAFECOPY(startup->default_cgi_content,WEB_DEFAULT_CGI_CONTENT);
if(startup->max_inactivity==0) startup->max_inactivity=120; /* seconds */
if(startup->max_cgi_inactivity==0) startup->max_cgi_inactivity=120; /* seconds */
if(startup->sem_chk_freq==0) startup->sem_chk_freq=2; /* seconds */
if(startup->js.max_bytes==0) startup->js.max_bytes=JAVASCRIPT_MAX_BYTES;
if(startup->js.cx_stack==0) startup->js.cx_stack=JAVASCRIPT_CONTEXT_STACK;
if(startup->ssjs_ext[0]==0) SAFECOPY(startup->ssjs_ext,".ssjs");
if(startup->js_ext[0]==0) SAFECOPY(startup->js_ext,".bbs");
ZERO_VAR(js_server_props);
SAFEPRINTF2(js_server_props.version,"%s %s",server_name,revision);
js_server_props.version_detail=web_ver();
js_server_props.clients=&active_clients;
js_server_props.options=&startup->options;
js_server_props.interface_addr=&startup->interface_addr;
uptime=0;
served=0;
startup->recycle_now=FALSE;
startup->shutdown_now=FALSE;
terminate_server=FALSE;
do {
thread_up(FALSE /* setuid */);
status("Initializing");
/* Copy html directories */
SAFECOPY(root_dir,startup->root_dir);
SAFECOPY(error_dir,startup->error_dir);
SAFECOPY(cgi_dir,startup->cgi_dir);
if(startup->temp_dir[0])
SAFECOPY(temp_dir,startup->temp_dir);
else
SAFECOPY(temp_dir,"../temp");
/* Change to absolute path */
prep_dir(startup->ctrl_dir, root_dir, sizeof(root_dir));
prep_dir(startup->ctrl_dir, temp_dir, sizeof(temp_dir));
prep_dir(root_dir, error_dir, sizeof(error_dir));
prep_dir(root_dir, cgi_dir, sizeof(cgi_dir));
/* Trim off trailing slash/backslash */
if(IS_PATH_DELIM(*(p=lastchar(root_dir)))) *p=0;
memset(&scfg, 0, sizeof(scfg));
lprintf(LOG_INFO,"%s Revision %s%s"
,revision
#ifdef _DEBUG
," Debug"
#else
,""
#endif
);
DESCRIBE_COMPILER(compiler);
lprintf(LOG_INFO,"Compiled %s %s with %s", __DATE__, __TIME__, compiler);
if(!winsock_startup()) {
cleanup(1);
return;
}
t=time(NULL);
lprintf(LOG_INFO,"Initializing on %.24s with options: %lx"
,CTIME_R(&t,logstr),startup->options);
if(chdir(startup->ctrl_dir)!=0)
lprintf(LOG_ERR,"!ERROR %d changing directory to: %s", errno, startup->ctrl_dir);
/* Initial configuration and load from CNF files */
SAFECOPY(scfg.ctrl_dir,startup->ctrl_dir);
lprintf(LOG_INFO,"Loading configuration files from %s", scfg.ctrl_dir);
scfg.size=sizeof(scfg);
SAFECOPY(logstr,UNKNOWN_LOAD_ERROR);
if(!load_cfg(&scfg, NULL, TRUE, logstr)) {
lprintf(LOG_ERR,"!ERROR %s",logstr);
lprintf(LOG_ERR,"!FAILED to load configuration files");
cleanup(1);
return;
}
scfg_reloaded=TRUE;
lprintf(LOG_DEBUG,"Temporary file directory: %s", temp_dir);
MKDIR(temp_dir);
if(!isdir(temp_dir)) {
lprintf(LOG_ERR,"!Invalid temp directory: %s", temp_dir);
cleanup(1);
return;
}
lprintf(LOG_DEBUG,"Root directory: %s", root_dir);
lprintf(LOG_DEBUG,"Error directory: %s", error_dir);
lprintf(LOG_DEBUG,"CGI directory: %s", cgi_dir);
mime_types=read_ini_list("mime_types.ini",NULL /* root section */,"MIME types"
,mime_types);
cgi_handlers=read_ini_list("web_handler.ini","CGI","CGI content handlers"
,cgi_handlers);
xjs_handlers=read_ini_list("web_handler.ini","JavaScript","JavaScript content handlers"
,xjs_handlers);
/* Don't do this for *each* CGI request, just once here during [re]init */
iniFileName(cgi_env_ini,sizeof(cgi_env_ini),scfg.ctrl_dir,"cgi_env.ini");
if(startup->host_name[0]==0)
SAFECOPY(startup->host_name,scfg.sys_inetaddr);
if(!(scfg.sys_misc&SM_LOCAL_TZ) && !(startup->options&BBS_OPT_LOCAL_TIMEZONE)) {
if(putenv("TZ=UTC0"))
lprintf(LOG_WARNING,"!putenv() FAILED");
tzset();
}
if(uptime==0)
uptime=time(NULL); /* this must be done *after* setting the timezone */
active_clients=0;
update_clients();
/* open a socket and wait for a client */
server_socket = open_socket(SOCK_STREAM);
if(server_socket == INVALID_SOCKET) {
lprintf(LOG_ERR,"!ERROR %d creating HTTP socket", ERROR_VALUE);
cleanup(1);
return;
}
/*
* i=1;
* if(setsockopt(server_socket, IPPROTO_TCP, TCP_NOPUSH, &i, sizeof(i)))
* lprintf("Cannot set TCP_NOPUSH socket option");
*/
lprintf(LOG_INFO,"%04d Web Server socket opened",server_socket);
/*****************************/
/* Listen for incoming calls */
/*****************************/
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_addr.s_addr = htonl(startup->interface_addr);
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(startup->port);
if(startup->seteuid!=NULL)
startup->seteuid(FALSE);
result = retry_bind(server_socket,(struct sockaddr *)&server_addr,sizeof(server_addr)
,startup->bind_retry_count,startup->bind_retry_delay,"Web Server",lprintf);
if(startup->seteuid!=NULL)
startup->seteuid(TRUE);
if(result != 0) {
lprintf(LOG_NOTICE,"%s",BIND_FAILURE_HELP);
cleanup(1);
return;
}
result = listen(server_socket, 64);
if(result != 0) {
lprintf(LOG_ERR,"%04d !ERROR %d (%d) listening on socket"
,server_socket, result, ERROR_VALUE);
cleanup(1);
return;
}
lprintf(LOG_INFO,"%04d Web Server listening on port %d"
,server_socket, startup->port);
status("Listening");
lprintf(LOG_INFO,"%04d Web Server thread started", server_socket);
listInit(&log_list,/* flags */ LINK_LIST_MUTEX|LINK_LIST_SEMAPHORE);
if(startup->options&WEB_OPT_HTTP_LOGGING) {
/********************/
/* Start log thread */
/********************/
_beginthread(http_logging_thread, 0, startup->logfile_base);
}
#ifdef ONE_JS_RUNTIME
if(js_runtime == NULL) {
lprintf(LOG_INFO,"%04d JavaScript: Creating runtime: %lu bytes"
,server_socket,startup->js.max_bytes);
if((js_runtime=JS_NewRuntime(startup->js.max_bytes))==NULL) {
lprintf(LOG_ERR,"%04d !ERROR creating JavaScript runtime",server_socket);
/* Sleep 15 seconds then try again */
/* ToDo: Something better should be used here. */
SLEEP(15000);
continue;
}
}
#endif
/* Setup recycle/shutdown semaphore file lists */
shutdown_semfiles=semfile_list_init(scfg.ctrl_dir,"shutdown","web");
recycle_semfiles=semfile_list_init(scfg.ctrl_dir,"recycle","web");
SAFEPRINTF(path,"%swebsrvr.rec",scfg.ctrl_dir); /* legacy */
semfile_list_add(&recycle_semfiles,path);
if(!initialized) {
semfile_list_check(&initialized,recycle_semfiles);
semfile_list_check(&initialized,shutdown_semfiles);
/* signal caller that we've started up successfully */
if(startup->started!=NULL)
startup->started(startup->cbdata);
while(server_socket!=INVALID_SOCKET && !terminate_server) {
/* check for re-cycle/shutdown semaphores */
if(active_clients==0) {
if(!(startup->options&BBS_OPT_NO_RECYCLE)) {
if((p=semfile_list_check(&initialized,recycle_semfiles))!=NULL) {
lprintf(LOG_INFO,"%04d Recycle semaphore file (%s) detected"
,server_socket,p);
break;
}
#if 0 /* unused */
if(startup->recycle_sem!=NULL && sem_trywait(&startup->recycle_sem)==0)
startup->recycle_now=TRUE;
#endif
if(startup->recycle_now==TRUE) {
lprintf(LOG_INFO,"%04d Recycle semaphore signaled",server_socket);
startup->recycle_now=FALSE;
break;
}
if(((p=semfile_list_check(&initialized,shutdown_semfiles))!=NULL
&& lprintf(LOG_INFO,"%04d Shutdown semaphore file (%s) detected"
,server_socket,p))
|| (startup->shutdown_now==TRUE
&& lprintf(LOG_INFO,"%04d Shutdown semaphore signaled"
,server_socket))) {
startup->shutdown_now=FALSE;
terminate_server=TRUE;
/* now wait for connection */
FD_ZERO(&socket_set);
FD_SET(server_socket,&socket_set);
high_socket_set=server_socket+1;
tv.tv_sec=startup->sem_chk_freq;
tv.tv_usec=0;
if((i=select(high_socket_set,&socket_set,NULL,NULL,&tv))<1) {
continue;
if(ERROR_VALUE==EINTR)
lprintf(LOG_INFO,"Web Server listening interrupted");
else if(ERROR_VALUE == ENOTSOCK)
lprintf(LOG_INFO,"Web Server socket closed");
lprintf(LOG_WARNING,"!ERROR %d selecting socket",ERROR_VALUE);
continue;
if(server_socket==INVALID_SOCKET) /* terminated */
break;
client_addr_len = sizeof(client_addr);
if(server_socket!=INVALID_SOCKET
&& FD_ISSET(server_socket,&socket_set)) {
client_socket = accept(server_socket, (struct sockaddr *)&client_addr
,&client_addr_len);
lprintf(LOG_NOTICE,"!NO SOCKETS set by select");
continue;
}
if(client_socket == INVALID_SOCKET) {
lprintf(LOG_WARNING,"!ERROR %d accepting connection", ERROR_VALUE);
#ifdef _WIN32
if(WSAGetLastError()==WSAENOBUFS) /* recycle (re-init WinSock) on this error */
break;
#endif
if(startup->socket_open!=NULL)
startup->socket_open(startup->cbdata,TRUE);
SAFECOPY(host_ip,inet_ntoa(client_addr.sin_addr));
if(trashcan(&scfg,host_ip,"ip-silent")) {
close_socket(client_socket);
continue;
}
if(startup->max_clients && active_clients>=startup->max_clients) {
,client_socket, startup->max_clients);
mswait(3000);
close_socket(client_socket);
continue;
}
host_port=ntohs(client_addr.sin_port);
lprintf(LOG_INFO,"%04d HTTP connection accepted from: %s port %u"
,host_ip, host_port);
if((session=malloc(sizeof(http_session_t)))==NULL) {
lprintf(LOG_CRIT,"%04d !ERROR allocating %u bytes of memory for http_session_t"
,client_socket, sizeof(http_session_t));
mswait(3000);
close_socket(client_socket);
continue;
}
memset(session, 0, sizeof(http_session_t));
SAFECOPY(session->host_ip,host_ip);
session->addr=client_addr;
session->socket=client_socket;
session->js_branch.auto_terminate=TRUE;
session->js_branch.terminated=&terminate_server;
session->js_branch.limit=startup->js.branch_limit;
session->js_branch.gc_interval=startup->js.gc_interval;
session->js_branch.yield_interval=startup->js.yield_interval;
#ifdef ONE_JS_RUNTIME
session->js_runtime=js_runtime;
#endif
_beginthread(http_session_thread, 0, session);
served++;
/* Wait for active clients to terminate */
if(active_clients) {
lprintf(LOG_DEBUG,"%04d Waiting for %d active clients to disconnect..."
,server_socket, active_clients);
start=time(NULL);
while(active_clients) {
if(time(NULL)-start>startup->max_inactivity) {
lprintf(LOG_WARNING,"%04d !TIMEOUT waiting for %d active clients"
,server_socket, active_clients);
break;
}
mswait(100);
}
}
if(http_logging_thread_running) {
terminate_http_logging_thread=TRUE;
listSemPost(&log_list);
mswait(100);
}
if(http_logging_thread_running) {
lprintf(LOG_DEBUG,"%04d Waiting for HTTP logging thread to terminate..."
,server_socket);
start=time(NULL);
while(http_logging_thread_running) {
if(time(NULL)-start>TIMEOUT_THREAD_WAIT) {
lprintf(LOG_WARNING,"%04d !TIMEOUT waiting for HTTP logging thread to "
"terminate", server_socket);
break;
}
mswait(100);
}
}
#ifdef ONE_JS_RUNTIME
lprintf(LOG_INFO,"%04d JavaScript: Destroying runtime",server_socket);
JS_DestroyRuntime(js_runtime);
js_runtime=NULL;
}
#endif
if(!terminate_server) {
lprintf(LOG_INFO,"Recycling server...");
if(startup->recycle!=NULL)
startup->recycle(startup->cbdata);
} while(!terminate_server);