/* -*- Mode: C -*-
* $Basename: waisserver.c $
* $Revision: 1.9.1.13.1.4 $
* Author : Harry Morris, morris@think.com
* Created On : 5.29.90
* Last Modified By: Ulrich Pfeifer
* Last Modified On: Mon May 5 10:44:49 1997
* Language : C
* Update Count : 1
* Status : Unknown, Use with caution!
*
* (C) Copyright 1997, Universität Dortmund, all rights reserved.
* (C) Copyright CNIDR (see ../doc/CNIDR/COPYRIGHT)
*/
/* $Format: "#define SERVER_DATE \"$Date$\""$ */
#define SERVER_DATE "Thu, 25 Feb 1999 09:46:46 +0100"
#ifndef lint
static char *PRCSid = "$Id: waisserver.c 1.9.1.13.1.4 Thu, 25 Feb 1999 09:46:46 +0100 goevert $";
#endif
#define INFO_DICT "INFO.dct"
#define LOCKFILE "/tmp/INFO.lock" /* while re-indexing INFO */
#define NAPTIME 1 /* seconds */
#define MAXNAPTIME 0 /* Don't wait, just go on. */
#include "server.h"
#include "sockets.h"
#include <sys/stat.h>
#include "cdialect.h"
#ifdef I_FCNTL
#include <fcntl.h>
#else /* not I_FCNTL */
#include <sys/fcntl.h>
#endif /* not I_FCNTL */
#ifdef SYSV
#define SIGCHLD SIGCLD
#endif
#include <signal.h>
#ifdef I_SYS_SELECT
#include <sys/select.h>
#endif
#include "cdialect.h"
#include "panic.h"
#include "ustubs.h"
#include "transprt.h"
#include "wmessage.h"
#include "ir.h"
#include "wprot.h"
#include "cutil.h"
#include "futil.h"
#include "irext.h"
#include "irsearch.h"
/* to create the INFO index */
#include "irtfiles.h"
#include "irfiles.h"
#include "irhash.h"
#include <errno.h>
#ifdef Mach
extern int errno;
#endif
static long bufferSize = BUFSZ; /* how much we are using
(we get one of these per process) */
/* Bitmap association table -- LEB/HSTX 8/5/92 */
#ifdef STELAR
#define LOOKUP_TABLE /archives/stelar/abs/wais/bitmap.table
char *bitmap_table = "LOOKUP_TABLE";
/* char *bitmap_table = "/archives/stelar/abs/wais/bitmap.table"; */
#endif /* STELAR */
/*---------------------------------------------------------------------------*/
#define TIMEOUT_LENGTH 3600 /* one hour timeout. */
#define IDLE_TIME "1 hour"
void
serve_client(in,out, index_directory)
FILE* in;
FILE* out;
char *index_directory;
{
char buf[BUFSZ]; /* contains the message and header */
char *bufPtr ; /* points at the begining of the z3950 */
long size; /* bytes in the z3950 message */
WAISMessage header; /* for storing the header */
long i;
long bytesLeft;
struct itimerval new, old;
long nextChar;
new.it_interval.tv_sec = 0;
new.it_interval.tv_usec = 0;
new.it_value.tv_sec = TIMEOUT_LENGTH;
new.it_value.tv_usec = 0;
bzero(buf, BUFSZ*sizeof(char)); /* avoid umr (up)/8/94 */
getitimer(ITIMER_REAL, &old);
while (TRUE)
{
/* try to read the header */
for (i = 0; i < HEADER_LENGTH; i++)
{
setitimer(ITIMER_REAL, &new, NULL);
nextChar = fgetc(in);
if (nextChar == EOF) /* my connection exited, so will I */
{
return;
}
else
buf[i] = (char)nextChar;
}
setitimer(ITIMER_REAL, &old, NULL);
/* parse the header */
readWAISPacketHeader(buf,&header);
/* make sure we have the right version.
If we dont, we dont know what to do. */
if (header.hdr_vers > HEADER_VERSION)
panic("Incompatable header versions (Current version: %c, supplied version: %c.",
HEADER_VERSION, header.hdr_vers) ;
/* determine the size of the z3950 message */
{
char length_array[11];
strncpy(length_array, header.msg_len, 10);
length_array[10] = '\0';
size = atol(length_array);
}
/* set bufPtr to start the z3950 message */
bufPtr = buf + HEADER_LENGTH ;
/* read the z3950 message */
for (i = 0; i < size ; i++) {
setitimer(ITIMER_REAL, &new, NULL);
buf[i + HEADER_LENGTH] = (char)fgetc(in) ;
}
fflush(in);
rewind(in);
/* decode the z3950 if necessary */
transportDecode((long)header.encoding,bufPtr,&size);
/* XXX handle compression options */
/* process it the z3950 */
bytesLeft = bufferSize;
size = interpret_buffer(bufPtr,size,bufPtr,bytesLeft,
&bufferSize,(long)header.hdr_vers,
index_directory);
/* re-encode the message if necessary */
transportCode((long)header.encoding,bufPtr,&size);
/* XXX handle compression options */
/* write the new header */
writeWAISPacketHeader(buf,size,
(long)header.msg_type,header.server,
(long)header.compression,(long)header.encoding,
(long)header.hdr_vers);
/* write the whole response to the output file */
for (i = 0; i < size + HEADER_LENGTH; i++)
fputc(buf[i],out) ;
fflush(out); /* flush any file buffers */
rewind(out); /* reset the file for read */
}
}
/*---------------------------------------------------------------------------*/
typedef struct sigcontext sigcontext_t;
static void
breakKey (s1,s2,s3,s4)
long s1;
long s2;
struct sigcontext *s3;
char *s4;
{
if(0 != finished_search_engine())
panic("unable to close search engine");
panic ("got a ^c");
}
/*---------------------------------------------------------------------------*/
void
childhandler(sig, code, scp, addr)
long sig, code;
struct sigcontext *scp;
char *addr;
{
wait(NULL); /* give the kid a decent burial */
signal (SIGCHLD, childhandler); /* Dave Judd - IRIX requires resetting signal */
}
/*---------------------------------------------------------------------------*/
void
alarmhandler(sig, code, scp, addr)
long sig, code;
struct sigcontext *scp;
char *addr;
{
waislog(WLOG_HIGH, WLOG_CLOSE,
"Server idle longer %s. Closing server and exiting.", IDLE_TIME);
if(0 != finished_search_engine())
panic("unable to close search engine");
exit(0);
}
/*---------------------------------------------------------------------------*/
Signal_t
seghandler(sig, code, scp, addr)
long sig, code;
struct sigcontext *scp;
char *addr;
{
waislog(WLOG_HIGH, WLOG_ERROR, "Segmentation violation.");
waislog(WLOG_HIGH, WLOG_CLOSE, "Bummer. Closing server and exiting.");
#ifdef DUMPCORE
abort();
#else
exit(-1);
#endif
}
/*---------------------------------------------------------------------------*/
void
bushandler(sig, code, scp, addr)
long sig, code;
struct sigcontext *scp;
char *addr;
{
waislog(WLOG_HIGH, WLOG_ERROR, "Bus error.");
waislog(WLOG_HIGH, WLOG_CLOSE, "Bummer. Closing server and exiting.");
#ifdef DUMPCORE
abort();
#else
exit(-1);
#endif
}
/*---------------------------------------------------------------------------*/
#include <pwd.h>
int finduid(name)
char *name;
{
struct passwd *pwent;
if ((pwent = getpwnam(name)) == NULL) {
return -1;
}
return(pwent->pw_uid);
}
static char *index_dir = NULL;
static time_t info_change_time;
static int indexing_needed = 0;
static char *info_dict = INFO_DICT;
extern int alphasort();
/* selecttion function for scandir()
* trigger on ".src" extension, regular file, and != "INFO.src"
* Indexing is needed if any of the .src files is younger than
* INFO.dct
*/
static int
srcfiles(e)
Direntry_t *e;
{
struct stat sb;
char *lastdot = strrchr(e->d_name,'.');
int candidate;
candidate = lastdot &&
(stat(merge_pathnames(e->d_name,index_dir), &sb) >= 0) &&
((sb.st_mode & S_IFMT) == S_IFREG) &&
!strcmp(lastdot,source_ext) &&
strcmp(e->d_name,info_dict); /* whew */
if (candidate) {
indexing_needed |= (sb.st_mtime > info_change_time);
return 1;
}
return 0;
}
/*---------------------------------------------------------------------------*/
#ifdef HAS_SYS_ERRLIST
extern int errno;
extern int sys_nerr;
#endif
Pid_t pgrp;
/* detach() was stolen right off of NCSA's httpd */
static void
detach() {
int x;
if((x = fork()) > 0)
exit(0);
else if(x == -1) {
waislog(WLOG_HIGH, WLOG_ERROR, "Error: fork(): %s",
Strerror(errno));
exit(1);
}
#ifndef NO_SETSID
if((pgrp=setsid()) == -1) {
waislog(WLOG_HIGH, WLOG_ERROR, "Error: setsid(): %s",
Strerror(errno));
exit(1);
}
#else
if((pgrp=setpgrp(getpid(),0)) == -1) {
waislog(WLOG_HIGH, WLOG_ERROR, "Error: setpgrp(): %s",
Strerror(errno));
exit(1);
}
#endif
}
/*---------------------------------------------------------------------------*/
#ifdef SET_LIMIT
#include <sys/resource.h>
#endif
#define INDEX_FORK
extern char *inet_ntoa ();
extern char *syslog_name;
void
main(argc,argv)
int argc;
char* argv[];
{ FILE *file;
long socket;
char *next_argument = next_arg(&argc, &argv), *command_name;
boolean use_stdio = TRUE; /* default is true */
/* char *wais_log_file_name = NULL; */ /* name of file for error output */
int child_proc; /* for the child process id */
char *uid_name = "root"; /* user id so setuid if root */
int uid = 0; /* if not specified, leave as root. */
int child;
long cm_mem_percent = 0; /* default */
struct stat statbuf;
Direntry_t **list;
int naptime = 0;
extern void filename_finish_header_function();
dataopsrec dataops;
int detach_flag = 0; /* if specified, fork and setsid() */
#ifdef CACHE_SYN
int cachesyn = 0;
#endif
#ifdef SET_LIMIT
struct rlimit rlp;
getrlimit(RLIMIT_CORE, &rlp);
rlp.rlim_cur = rlp.rlim_max;
setrlimit(RLIMIT_CORE, &rlp);
getrlimit(RLIMIT_DATA, &rlp);
rlp.rlim_cur = rlp.rlim_max;
setrlimit(RLIMIT_DATA, &rlp);
#endif
/* dgg -- must duplicate mods to irbuild.c, here is mini-build of INFO.src */
bzero(&dataops,sizeof(dataops));
dataops.separator_function= NULL;
dataops.header_function= NULL;
dataops.date_function= NULL;
dataops.finish_header_function= filename_finish_header_function;
dataops.type= "WSRC";
dataops.wordDelimiter= wordbreak_notalnum;
dataops.addseparatorwords= false;
dataops.extraheaderweight= true;
dataops.repeat_weight= 1;
dataops.minwordlen= 2;
stop_list_file("\0");
gDelimiters[0]= '\0';
wordDelimiter= wordbreak_notalnum;
/* dgg -- end new inits */
tcp_port = 210; /* tcp_port to use */
command_name = next_argument;
syslog_name = (syslog_name = (char *)rindex(command_name, '/')) ? (syslog_name + 1) : command_name;
host_name[0] = 0;
host_address[0] = 0;
server_name = s_malloc(255);
mygethostname(server_name, 255);
wais_pid = getpid();
if (!strcmp(command_name, "waisserver.d")) {
struct sockaddr_in source;
int sourcelen;
sourcelen = sizeof(struct sockaddr_in);
if (!getpeername(fileno(stdout),(struct sockaddr *)&source,&sourcelen)) {
struct hostent *peer = NULL;
#ifdef __DGUX__
peer = gethostbyaddr((char *)&source.sin_addr.s_addr, 4, AF_INET);
#else
peer = gethostbyaddr((char *)&source.sin_addr, 4, AF_INET);
#endif
if(peer != NULL) {
sprintf(host_name, "%s", peer->h_name);
sprintf(host_address, "%s",
#if defined(sparc) && defined(__GNUC__) && defined(INET_NTOA_WITH_POINTER)
inet_ntoa(&source.sin_addr)
#else
inet_ntoa(source.sin_addr)
#endif /* sparc */
);
}
}
else sprintf(host_address, "Error getting socket: %d, %s.", errno, Strerror(errno));
use_stdio = TRUE;
}
if (argc == 0){
#ifdef CACHE_SYN
fprintf(stderr,"Usage: %s [-p [port_number]] [-s] [-d directory] [-u user] [-cmmem number] [-cachesyn] [-detach] [-v]\n",
#else
fprintf(stderr,"Usage: %s [-p [port_number]] [-s] [-d directory] [-u user] [-cmmem number] [-detach] [-v]\n",
#endif
command_name);
fprintf(stderr," -p [port] listen to the port. If the port is supplied, then\n");
fprintf(stderr," that tcp_port number is used. If it is not supplied \n");
fprintf(stderr," then the Z39.50 port (210) is used.\n");
fprintf(stderr," -d directory: means to use the directory as the source of databases.\n");
fprintf(stderr," Defaults to the current directory.\n");
fprintf(stderr," -e [file]: set log output to file, or /dev/null if not specified.\n");
fprintf(stderr," -l log_level: set log level. 0 means log nothing,\n");
fprintf(stderr," 10 [the default] means log everything.\n");
fprintf(stderr," -s means listen to standard I/O for queries. This is the default\n");
fprintf(stderr," -u user: if started as root, setuid to user after startup.\n");
fprintf(stderr," -cmmem number: percentage of CM memory to use (CM code only).\n");
#ifdef CACHE_SYN
fprintf(stderr," -cachesyn: cache synonym table in shared memory (default is don't cache)\n");
#endif
fprintf(stderr," -detach: fork off a new process and make it the session leader.\n");
fprintf(stderr," (useful when starting from boot scripts)\n");
fprintf(stderr," -v prints the version.\n");
exit(1);
}
if(NULL == (next_argument = next_arg(&argc, &argv))){
fprintf(stderr,"No arguments specified\n");
exit(0);
}
while((next_argument != NULL) &&
('-' == next_argument[0])){
/* then we have an argument to process */
if (0 == strcmp("-p", next_argument)){
char *peek_argument = peek_arg(&argc, &argv);
use_stdio = FALSE;
if ((NULL != peek_argument) && /* if we are not out of args */
('-' != peek_argument[0])){ { /* and the next isn't an option... */
/* get the port number */
tcp_port = atoi(next_arg(&argc, &argv));
} /* end if (explicit tcp_port) */
}
} /* end if (-p) */
else if (0 == strcmp("-s", next_argument)){
use_stdio = TRUE;
} /* end if (-s) */
else if (0 == strcmp("-e", next_argument)) {
char *peek_argument = peek_arg(&argc, &argv);
wais_log_file_name = "/dev/null"; /* default to /dev/null */
if ((peek_argument != NULL) &&
('-' != peek_argument[0])) {
wais_log_file_name = next_arg(&argc, &argv);
} /* end if (explicit log file) */
} /* end if (-e) */
else if (0 == strcmp("-l", next_argument)) {
wais_log_level = atol(next_arg(&argc, &argv));
} /* end if (-l) */
else if (0 == strcmp("-d", next_argument)) {
index_dir = next_arg(&argc, &argv);
}
else if (0 == strcmp("-v", next_argument)) {
fprintf(stderr,"%s: %s, %s\n", command_name, VERSION_STRING, SERVER_DATE);
}
else if (0 == strcmp("-u", next_argument)) {
uid_name = next_arg(&argc, &argv);
if((uid = finduid(uid_name)) < 0)
panic("Couldn't find user %s.", uid_name);
}
else if(0 == strcmp("-cmmem", next_argument)){
if(NULL == (next_argument = next_arg(&argc, &argv)))
panic("Expected a number (1-100) for percentage of memory to use");
cm_mem_percent = atol(next_argument);
if(cm_mem_percent < 1)
panic("The -cmmem argument should not be less than 1 and less than 100");
if(cm_mem_percent > 100)
panic("Warning: The -cmmem parameter was %ld%%. It should be between 1-100.", cm_mem_percent);
}
#ifdef CACHE_SYN
else if (0 == strcmp("-cachesyn", next_argument)) {
cachesyn = 1;
}
#endif
else if (0 == strcmp("-detach", next_argument)) {
detach_flag = 1;
}
else{
panic("Don't recognize the %s option", next_argument);
}
next_argument = next_arg(&argc, &argv);
} /* end while (more arguments) */
if (use_stdio && wais_log_file_name == NULL)
wais_log_file_name = "/dev/null";
if (detach_flag)
detach();
if (wais_log_file_name == NULL)
waislogfile = stderr;
else waislogfile = NULL;
index_dir = index_dir ? index_dir : ".";
info_dict = s_strdup(merge_pathnames(info_dict,index_dir));
if(0 != init_search_engine(index_dir, false, true, cm_mem_percent,0,0))
panic("unable to initialize search engine");
/* remember timestamp on INFO.dct if rebuilding needed
* If it doesnt exist, it's assumed to be *very* old, to force
* re-indexing
*/
info_change_time = (stat(info_dict,&statbuf) == -1) ? 0 : statbuf.st_mtime;
/* compare with candidates */
if (scandir(index_dir, &list, srcfiles, alphasort) < 0) {
waislog(WLOG_HIGH, WLOG_ERROR,
"Error: reading directory %s, %s",
index_dir, Strerror(errno));
indexing_needed = FALSE;
}
/* ok. we know if we need indexing,
* and have all the filenames.
*/
if (info_change_time == 0) indexing_needed = TRUE;
if (indexing_needed) {
#ifndef DO_NOT_TELL_ABOUT_ME
extern int sayiamhere _AP(());
sayiamhere();
#endif
/* Time to re-index,
* aquire the lock
*/
waislog(WLOG_MEDIUM, WLOG_INDEX,
"re-indexing needed, info_change_time=%d",info_change_time);
if (open(LOCKFILE, O_WRONLY|O_CREAT|O_EXCL,0666) == -1) {
/* already locked by somebody else
* spin till she finishes
*/
while (!(stat(LOCKFILE,&statbuf) == -1)) {
sleep(NAPTIME);
naptime += NAPTIME;
waislog(WLOG_MEDIUM, WLOG_INFO,
"INFO locked, waiting since %d seconds", naptime);
if (naptime > MAXNAPTIME) {
waislog(WLOG_HIGH, WLOG_WARNING,
"Warning - lockfile %s won't go away after %d seconds, not reindexing.",
LOCKFILE, naptime);
break;
}
}
/* if lockfile went away, assume INFO.* build finished
* so just use it
*/
} else { /* we aquired the lock, so rebuild database */
#ifdef INDEX_FORK
if (!(child = fork())) {
#endif
database *db;
Direntry_t **s = list;
char filename[MAX_FILENAME_LEN], *dbname;
waislog(WLOG_MEDIUM, WLOG_INDEX,
"Creating INFO database, pid=%d",getpid());
dbname = s_strdup(merge_pathnames("INFO", index_dir));
#ifdef FIELDS /* tung, 3/94 */
db = openDatabase(dbname, true, false, false);
#else
db = openDatabase(dbname, true, false);
#endif
s_free(dbname);
init_add_word(db, 0, 100000L);
while (list && *s) { /* index it */
strncpy(filename, index_dir, MAX_FILENAME_LEN);
if(index_dir[strlen(index_dir) -1] != '/')
strncat(filename, "/", MAX_FILENAME_LEN);
strncat(filename, (*s)->d_name, MAX_FILENAME_LEN);
waislog(WLOG_MEDIUM, WLOG_INDEX,
"Indexing %s", filename);
index_text_file(filename, &dataops, db, true, false,
false, true
#ifdef FIELDS /* tung, 5/94 */
,-1
#endif
);
s++;
}
if (list)
freedir(list); /* array of filenames */
if(!probe_file(source_filename(filename, db)))
write_src_structure(source_filename(filename, db),
"INFO", "WSRC", NULL, 0L, true,
#ifdef FIELDS /* tung, 3/94 */
tcp_port, db);
#else
tcp_port);
#endif
finished_add_word(db);
build_catalog(db);
closeDatabase(db);
if (unlink(LOCKFILE))
panic("Indexer: cant unlink lockfile!\n");
waislog(WLOG_MEDIUM, WLOG_INDEX,
"Indexer pid=%d done", getpid());
#ifdef INDEX_FORK
exit(0); /* indexing child */
}
else if (child == -1) {
waislog(WLOG_HIGH, WLOG_ERROR,
"Unable to fork for indexer.");
exit(1);
}
/* wait for child process */
else while (wait(0) != child) ; /* do nothing */
#endif
}
}
if (use_stdio == TRUE) {
if(host_address[0] != 0){
waislog(WLOG_MEDIUM, WLOG_CONNECT,
"Accepted connection from: %s [%s], %s",
host_name, host_address, VERSION_STRING);
}
else{
waislog(WLOG_MEDIUM, WLOG_CONNECT,
"Couldn't determine peer connection. %s", VERSION_STRING);
}
if ( server_security(index_dir,NULL) != true ){
waislog(WLOG_HIGH, WLOG_INFO,"Closing down server");
exit(-1);
}
}
else{
waislog(WLOG_MEDIUM, WLOG_INFO, "Running server %s", VERSION_STRING);
}
signal(SIGINT, breakKey);
signal(SIGCHLD, childhandler); /* XXX dont really need this any more */
signal(SIGALRM, alarmhandler);
signal(SIGSEGV, seghandler);
signal(SIGBUS, bushandler);
if(use_stdio == FALSE)
{
if (tcp_port < 1024 && getuid() != 0) {
waislog(WLOG_HIGH, WLOG_ERROR,
"Error opening port %d: Must be superuser to use a port < 1024",
tcp_port);
exit(-1);
}
open_server(tcp_port,&socket,BUFSZ);
#ifdef SECURE_SERVER
/* if root, setuid to user specified id. */
if (uid > 0 && getuid() == 0) {
waislog(WLOG_MEDIUM, WLOG_INFO,
"Setting uid to %s.", uid_name);
if (wais_log_file_name && *wais_log_file_name &&
chown(wais_log_file_name,uid,getgid()) < 0)
waislog(WLOG_HIGH, WLOG_ERROR,
"Unable to chown log file to %s!", uid_name);
if ( 0 > setuid(uid)) {
waislog(WLOG_HIGH, WLOG_ERROR,
"Unable to setuid to %s! Exiting.", uid_name);
exit(-1);
}
}
#endif
#ifdef CACHE_SYN
if (cachesyn) {
/* create and attach master shared memory segment containing info
* on cached synonyms (see synonym.c) */
int shm_key = 1, shm_cnt = 0;
char *pcs = NULL;
t_cacheSyn *cs = NULL;
cacheSynId = -1;
while (cacheSynId == (-1)) {
cacheSynId = shmget (shm_key, MAX_SYN_CACHE * sizeof(t_cacheSyn),
0644 | IPC_CREAT | IPC_EXCL);
if (cacheSynId == (-1) && errno != EEXIST)
break;
shm_key++;
}
if (cacheSynId <= 0) {
cacheSynId = 0;
waislog (WLOG_HIGH, WLOG_WARNING,
"Warning: cannot get shared memory area for synonyms, caching disabled");
}
else {
/* get shared memory for and initialize t_cacheSyn structure */
if ((pcs = (char *) shmat (cacheSynId, 0, 0)) == ((char *)-1)) {
cacheSynId = 0;
waislog (WLOG_HIGH, WLOG_WARNING,
"Warning: cannot attatch shared memory segment for synonyms, caching disabled");
}
else {
for (shm_cnt = 0; shm_cnt < MAX_SYN_CACHE;
shm_cnt++, pcs += sizeof(t_cacheSyn)) {
cs = (t_cacheSyn *) pcs;
cs->id = 0;
(cs->synfile)[0] = '\0';
}
}
}
}
#endif
while (TRUE) { /* be a server for several connections */
accept_client_connection(socket,&file);
#ifdef QUERY_FORK
if ((child_proc = fork()) == 0) {
#endif
/* grandson handles this connection
* double-fork takes care of zombies
*/
#ifdef QUERY_FORK
if ((child_proc = fork()) == 0) {
#endif
wais_pid = getpid();
log_line = 0;
serve_client(file, file, index_dir);
/* but leaves server up */
close_client_connection(file);
close_server(socket);
/* just exits this child */
waislog(WLOG_MEDIUM, WLOG_CLOSE,
"Done handling client");
#ifdef QUERY_FORK
exit(0);
} else {
/* son: orphans the grandchild, so init picks up
* the exit status
*/
exit(0);
}
} else {
waislog(WLOG_MEDIUM, WLOG_INFO,
"Child PID = %d", child_proc);
close_client_connection(file); /* parent shouldn't keep the file */
}
#endif
}
}
else if(use_stdio == TRUE)
{ /* connections on stdio don't use child processes yet */
serve_client(stdin, stdout, index_dir);
waislog(WLOG_MEDIUM, WLOG_CLOSE,
"Done handling client");
/* close the whole thing */
if(0 != finished_search_engine())
panic("unable to close search engine");
exit(0);
}
}
/*---------------------------------------------------------------------------*/
syntax highlighted by Code2HTML, v. 0.9.1