/* =========================================================================== * * PUBLIC DOMAIN NOTICE * National Center for Biotechnology Information * * This software/database is a "United States Government Work" under the * terms of the United States Copyright Act. It was written as part of * the author's official duties as a United States Government employee and * thus cannot be copyrighted. This software/database is freely available * to the public for use. The National Library of Medicine and the U.S. * Government have not placed any restriction on its use or reproduction. * * Although all reasonable efforts have been taken to ensure the accuracy * and reliability of the software and data, the NLM and the U.S. * Government do not and cannot warrant the performance or results that * may be obtained by using this software or data. The NLM and the U.S. * Government disclaim all warranties, express or implied, including * warranties of performance, merchantability or fitness for any particular * purpose. * * Please cite the author in any work or product based on this material. * * ===========================================================================*/ /* * Blast server daemon. * * This daemon expects to be launched by inetd * * Author: W. Gish * Date: Jan. 1990 */ /* 2-28-91 The motd is displayed even when a bad database name is requested. 2-28-91 The number of bytes of output is monitored and the job killed if this number exceeds OUTPUT_MAX. 7-15-91 Fixed bug in semaphore queues, which had not been properly initialized 7-22-91 Added better logging of cpu time and real time */ #include #include #include #include #if defined(OS_UNIX_SYSV) && defined(sun) #define STRERROR(errno) strerror(errno) #define STRSIGNAL(signo) strsignal(signo) #else extern char const *sys_siglist[]; #define STRERROR(errno) sys_errlist[errno] #define STRSIGNAL(signo) sys_siglist[signo] #endif #include #include #include #include #include #include #include #include #include #ifdef SYSV_STREAMS_AVAIL #include #endif #include #include #include #include #include "blastsrv.h" #define PROJECT_ID 6 /* project ID for ftok */ /* Resource limits */ #define RUN_MAX 4 /* Max. total number of concurrent running requests */ #define QUEUE_MAX (RUN_MAX*5) /* Max. total number of waiting requests */ #define NETGRP_RUN_MAX 2 /* Max. number of requests running from one netgrp */ /*#define NETGRP_QUEUE_MAX 5 /* Max. number of queued requests from one netgrp */ #define NETGRP_QUEUE_MAX 20 /* Max. number of queued requests from one netgrp */ #define CORE_MAX (10*KBYTE) /* max. core dump size (megabytes) */ #define CPUTIME_MAX (1000*1000) /* seconds */ #define DATA_MAX (384*KBYTE) /* megabytes */ #define RSS_MAX (196*KBYTE) /* megabytes */ #define OUTPUT_MAX (256*KBYTE) /* megabytes */ #define TIMEOUT_DEFAULT 300 /* timeout (seconds) on socket reads and writes */ /*#define ALARM_DEFAULT 120 /* max. wall clock time (minutes) for entire job */ #define ALARM_DEFAULT 2880 /* max. wall clock time (minutes) for entire job */ #define PROC_WAIT 600 /* max. time (seconds) to wait for blast prog. start */ unsigned long cputime_max = CPUTIME_MAX; unsigned long core_max = CORE_MAX*KBYTE; unsigned long data_max = DATA_MAX*KBYTE; unsigned long rss_max = RSS_MAX*KBYTE; unsigned long output_max = OUTPUT_MAX*KBYTE; int timeout_sec = TIMEOUT_DEFAULT; unsigned int alarm_sec = ALARM_DEFAULT*60; unsigned int alarm_poll = 60; unsigned int proc_wait = PROC_WAIT; unsigned int querylen_max = QUERYLEN_MAX; sigset_t blockset; void mailmsg PROTO((char *)); #define STOPFILE "stop" #define HOSTSFILE "hosts" #define BATCHFILE "batch" #define EXPORTSFILE "exports" #define COLLABORATORS "collaborators" #define TOKSTR " \t\n\r" #define CL_WAIT 1 #define CL_QUEUE 2 char *module; char *hostname; CharPtr mailsubject; /* message for mailing to blastmgr, default = none */ char localhost[128], shorthost[128]; char workingdir[FILENAME_MAX+1]; char tempdir[FILENAME_MAX+1]; char cmdline[512]; char title[20*KBYTE]; char queryfile[FILENAME_MAX+1] = "blastXXXXXX"; char theuser[33] = "unknown"; long querylen; int nargs; char *args[50] = { "unassigned", "unassigned", "unassigned" }; char *theProgram; char PathProgram[512]; FILE *logfp; int logfd = -1; void close_child(), reap_child(), echobytes(); void sighandler(), sigchild(); void sigurg(), sigio(), sigterm(); int sigalarm PROTO((Nlm_VoidPtr)), sigalarm_poll PROTO((Nlm_VoidPtr)); AlarmBlkPtr qalarm = NULL; /* periodic alarm while waiting in queues */ AlarmBlkPtr tmpalarm = NULL; long tvdiff PROTO((struct timeval *tf, struct timeval *t0)); int pid, child, childpid; long bytecount; Boolean open_client_connection = TRUE; int errcode; char *date0 = "Day Mon Dy Hr:Mn:Sc Year"; char uptime[100]; char *hostaddr = "255.255.255.255"; char batch = FALSE; char repeatuser = FALSE; int blastd_errno; int mask = 022, niceval = 2; #ifndef DEFAULT_WD #define DEFAULT_WD "/home/blast/blastnet" #endif char *wd = DEFAULT_WD; /* Default working directory */ char *homedir; char *hostsfile = HOSTSFILE; char *stopfile = STOPFILE; char *exportsfile = EXPORTSFILE; char *collaborators = COLLABORATORS; char svcdomain[FILENAME_MAX] = "unassigned"; int run_max = RUN_MAX; int queue_max = QUEUE_MAX; int netgrp_run_max = NETGRP_RUN_MAX; int netgrp_queue_max = NETGRP_QUEUE_MAX; int ck_master_queue=TRUE; /* true ==> check master queue */ int ck_netgrp_queue=TRUE; /* true ==> check the subnet queue */ int extra_nice=2; /* extra nice value for unfriendly programs */ char *asterisk = ""; char errmsg[4096]; int p[2] = {-1, -1}; /* pipe */ int status; /* child's process status */ int exitcode; /* child's exit status */ #define FD_MAX 256 #define SOCK_BUFLEN 4096 char *sockbufs[FD_MAX]; size_t sockbuflens[FD_MAX], sockbufpop[FD_MAX]; struct timeval t0, t1, t2, t3, tf; #ifdef _SC_CLK_TCK static long clk_tck; #else #define clk_tck CLK_TCK #endif unsigned long bytes_queued, bytes_queued_total, bytes_queued_max; enum blastd_errors { err_none=0, /* no error */ err_output, /* too much output produced, exceeding OUTPUT_MAX */ err_oobmsg, /* killed by an out-of-band message */ err_killed, /* killed by a signal */ err_shutdown, /* early shutdown by client close of socket */ err_cputime, /* per-process cpu time limit reached */ err_alarm, /* connection timed out */ err_baddata, /* unexpected data coming from client */ err_chdir = 30, /* chdir() system call failed */ err_peername, /* getpeername() system call failed */ err_fork, /* fork() system call failed */ err_perm, /* permission denied to the host, based on its IP address */ err_down, /* service is down */ err_dbdown, /* database is down or unavailable */ err_logfile, /* unable to open log file */ err_username, /* missing user name */ err_cmdline, /* missing command line */ err_nullargs, /* no command line arguments at all */ err_argsmin, /* too few command line arguments */ err_argsmax, /* too many command line arguments */ err_mem, /* insufficient memory */ err_dbaccess, /* access denied to database */ err_queue, /* too many requests queued */ err_exports, /* database does not exist or is not being exported */ err_progname, /* bad program name */ err_seqname, /* missing sequence name */ err_qfile, /* unable to create query file */ err_qlen, /* query sequence is too long */ err_whoami, /* call to sys_whoami() failed */ err_pipe, /* pipe() system call failed */ err_uidlen, /* userid has illegal length */ err_pwnam, /* getpwnam() system call failed */ err_prognotfound, /* application program not found! */ err_semget, /* semget() system call failed */ err_semacc, /* permissions on semaphore denied */ err_select, /* select() error */ err_write, /* write() error */ err_read, /* read() error */ err_titlelen, /* name of query sequence is too long */ err_blast, /* no response from blast application */ err_nbuf, /* error in the nbuf functions */ err_max /* max error value */ }; char *getpeerhostname PROTO((char *addr,size_t len,int type)); void fatalf VPROTO((enum blastd_errors err, char *format, ...)); void logmsg VPROTO((char *format, ...)); int procread PROTO((int fd, char *buf, int buflen)); int procselect PROTO((int fd)); int clientprintf VPROTO((char *format, ...)); int clientwrite PROTO((char *buf, unsigned nbyte, int flags)); int clientflush PROTO((void)); int clientclose PROTO((void)); int cputc PROTO((int ch)); char *clientgets PROTO((char *buf, int buflen)); char *sockgets PROTO((char *buf, int buflen, FILE *fp)); int sockread PROTO((int fd, char *buf, unsigned nbyte)); int sockwrite PROTO((int fd, char *buf, unsigned nbyte, int wait)); int sockselectr PROTO((int fd)); int sockselectw PROTO((int fd)); int sockopen PROTO((int fd)); int sockclose PROTO((int fd)); char *gethome PROTO((char *)); void NbufSave PROTO((char *buf, unsigned size)); void NbufTruncate PROTO((void)); void NbufSkip PROTO((unsigned nbyte)); void NbufWrite PROTO((int flags)); struct sockaddr_in sockin; main(argc, argv) int argc; char **argv; { struct passwd *pwp; struct linger linger; int i, c, flags, errflg = 0; int forkattempts = 0; struct rlimit rl; static int on = 1; gettimeofday(&t0, NULL); Nlm_MemCpy((char *)&t1, (char *)&t0, sizeof(t1)); Nlm_MemCpy((char *)&t2, (char *)&t0, sizeof(t2)); Nlm_MemCpy((char *)&t3, (char *)&t0, sizeof(t3)); Nlm_MemCpy((char *)&tf, (char *)&t0, sizeof(tf)); #ifdef _SC_CLK_TCK if (clk_tck == 0) clk_tck = sysconf(_SC_CLK_TCK); #endif module = misc_basename(argv[0], NULL); if (fopen("/dev/tty", "r") != NULL) usage(); sigemptyset(&blockset); sigaddset(&blockset, SIGURG); sigaddset(&blockset, SIGCLD); pid = getpid(); /* Setup to receive signal upon arrival of out-of-band (OOB) messages */ /* #ifdef SIGURG signal(SIGURG, sigurg); fcntl(FD_STDIN, F_SETOWN, pid); #endif */ #if 0 /* set FD_STDIN for non-blocking i/o */ fcntl(FD_STDIN, F_SETFL, fcntl(FD_STDIN, F_GETFL)|FNDELAY); #endif /* set FD_STDOUT for non-blocking i/o */ fcntl(FD_STDOUT, F_SETFL, fcntl(FD_STDOUT, F_GETFL)|FNDELAY); /* Options: -W working directory for temporary files, log file, etc. -C directory containing configuration files (hosts, stop, exports) -n nicevalue -m umask */ while ((c = getopt(argc, argv, "d:s:c:o:R:Q:r:q:W:w:n:m:t:a:e:")) != -1) { switch (c) { case 'W': case 'w': homedir = wd = optarg; break; case 'n': niceval = atoi(optarg); break; case 'm': /* umask */ if (*optarg == '0') sscanf(optarg, "%o", &mask); else mask = atoi(optarg); break; case 'd': /* data_max */ if (sscanf(optarg, "%lu", &data_max) != 1 || data_max < 2000) data_max = DATA_MAX; data_max *= KBYTE; break; case 's': /* max. RSS */ if (sscanf(optarg, "%lu", &rss_max) != 1 || rss_max < 2000) rss_max = RSS_MAX; rss_max *= KBYTE; break; case 'c': /* max. cputime (total for all processors) */ if (sscanf(optarg, "%lu", &cputime_max) != 1) cputime_max = CPUTIME_MAX; break; case 'o': /* max. volume of output */ if (sscanf(optarg, "%lu", &output_max) != 1 || output_max < 300) output_max = OUTPUT_MAX; output_max *= KBYTE; break; case 'r': /* netgrp_run_max */ if (sscanf(optarg, "%d", &netgrp_run_max) != 1 || netgrp_run_max <= 0) netgrp_run_max = NETGRP_RUN_MAX; break; case 'q': /* netgrp_queue_max */ if (sscanf(optarg, "%d", &netgrp_queue_max) != 1 || netgrp_queue_max < 0) netgrp_queue_max = NETGRP_QUEUE_MAX; break; case 'R': /* run_max */ if (sscanf(optarg, "%d", &run_max) != 1 || run_max <= 0) run_max = RUN_MAX; break; case 'Q': /* queue_max */ if (sscanf(optarg, "%d", &queue_max) != 1 || queue_max < 0) queue_max = QUEUE_MAX; break; case 't': if (sscanf(optarg, "%d", &timeout_sec) != 1 || timeout_sec <= 30) timeout_sec = TIMEOUT_DEFAULT; break; case 'a': if (sscanf(optarg, "%d", &alarm_sec) != 1 || alarm_sec <= 5) alarm_sec = ALARM_DEFAULT; alarm_sec *= 60; /* convert to seconds */ break; case 'e': /* set an environment variable */ putenv(optarg); break; case '?': ++errflg; default: break; } } if (errflg > 0) usage(); signal(SIGHUP, sighandler); signal(SIGINT, sighandler); signal(SIGTERM, sighandler); signal(SIGPIPE, SIG_IGN); #ifdef SIGXCPU signal(SIGXCPU, sighandler); #endif #ifdef SIGXFSZ signal(SIGXFSZ, sighandler); #endif #ifdef SIGBUS signal(SIGBUS, sighandler); #endif #ifdef SIGSEGV signal(SIGSEGV, sighandler); #endif #ifdef SIGSYS signal(SIGSYS, sighandler); #endif sockopen(FD_STDIN); umask(mask); if (chdir(wd) == -1) { if (homedir == NULL) { homedir = gethome(NULL); if (homedir == NULL || chdir(homedir) == -1) fatalf(err_chdir, "couldn't chdir to blastnet home directory"); } } else fatalf(err_chdir, "couldn't chdir to the \"%s\" working directory", wd); AlarmSet(&tmpalarm, 300, sigalarm, "Sorry, it took too long to receive your request."); gethostname(localhost, sizeof(localhost)-1); strcpy(shorthost, localhost); { char *cp = strchr(shorthost, '.'); if (cp != NULL) *cp = NULLB; } i = strlen(localhost); localhost[0] = TOUPPER(localhost[0]); i = sizeof(sockin); if (getpeername(FD_STDIN, (struct sockaddr *)&sockin, &i) == -1) fatalf(err_peername, "couldn't getpeername, errno=%d", errno); /* Convert IP address into an ASCII null-terminated string */ hostname = hostaddr = str_dup(inet_ntoa(sockin.sin_addr)); if (hostaddr == NULL) fatalf(err_mem, "Out of memory!"); settempdir(); createlog(BLASTLOG); getbatchcmd(); getusername(); #ifdef VALIDATEADDRESS validate_user(); #endif /* VALIDATEADDRESS */ readcmdline(); parsecmdline(); if (nargs < 2) fatalf(err_argsmin, "too few command line arguments"); nargs = MAX(3, nargs); args[2] = queryfile; /* substitute the local query filename */ args[nargs] = NULL; ckprivileges(); readtitle(); readquery(); AlarmClr(tmpalarm); tmpalarm = NULL; ckdown(); ckdbdown(); ckmotd(); ckrepeatuser(); ckpriority(); ckqueue(inet_network(hostaddr)); gettimeofday(&t1, NULL); if (pipe(p) < 0) fatalf(err_pipe, "couldn't open pipe"); clientflush(); if (niceval < 0) (void) nice(niceval); #ifdef SIGCLD signal(SIGCLD, sigchild); #endif Refork: switch (childpid = child = fork()) { case -1: if (++forkattempts > 10) fatalf(err_fork, "fork: %s", STRERROR(errno)); switch (errno) { case EAGAIN: case ENOMEM: perror("fork"); sleep(15); goto Refork; /* loop until SIGALRM received */ default: fatalf(err_fork, "fork: %s", STRERROR(errno)); } case 0: alarm(alarm_sec); signal(SIGALRM, SIG_DFL); #ifdef SIGPIPE signal(SIGPIPE, SIG_DFL); #endif #ifdef SIGURG signal(SIGURG, SIG_IGN); #endif #ifdef RLIMIT_CORE /* Limit core dumps */ getrlimit(RLIMIT_CORE, &rl); rl.rlim_max = rl.rlim_cur = core_max; setrlimit(RLIMIT_CORE, &rl); #endif #ifdef RLIMIT_CPU /* Limit execution time (per process) */ getrlimit(RLIMIT_CPU, &rl); rl.rlim_max = rl.rlim_cur = cputime_max; setrlimit(RLIMIT_CPU, &rl); #endif #ifdef RLIMIT_DATA /* Limit total data segment size */ getrlimit(RLIMIT_DATA, &rl); rl.rlim_max = rl.rlim_cur = data_max; setrlimit(RLIMIT_DATA, &rl); #endif #ifdef RLIMIT_STACK /* Limit total stack size */ getrlimit(RLIMIT_STACK, &rl); rl.rlim_max = rl.rlim_cur = data_max/4; setrlimit(RLIMIT_STACK, &rl); #endif #ifdef RLIMIT_RSS /* Limit resident set size */ getrlimit(RLIMIT_RSS, &rl); rl.rlim_max = rl.rlim_cur = rss_max; setrlimit(RLIMIT_RSS, &rl); #endif close(FD_STDIN); /* no standard input for BLAST applications */ dup2(p[1], FD_STDOUT); dup2(p[1], FD_STDERR); close(p[0]); /* Reading side of the pipe is unused */ close(p[1]); /* Writing side has been dup-ed to stdout and stderr */ niceval += extra_nice; niceval = MIN(niceval, 19); niceval = MAX(niceval, -10); niceval -= nice(0); if (niceval > 0) nice(niceval); execvp(PathProgram, args); /* wait and try again... */ sleep(15); execvp(PathProgram, args); printf("fork error: %s: %s", PathProgram, STRERROR(errno)); fflush(stdout); close(FD_STDOUT); close(FD_STDERR); _exit(127); default: break; } close(p[1]); /* Writing side of the pipe is unused */ #ifdef RLIMIT_DATA /* Limit the data segment in accordance with the maximum amount of output that the blast job is permitted to produce. */ getrlimit(RLIMIT_DATA, &rl); rl.rlim_cur = output_max+(3*KBYTE*KBYTE); setrlimit(RLIMIT_DATA, &rl); #endif #ifdef RLIMIT_CPU getrlimit(RLIMIT_CPU, &rl); rl.rlim_cur = 30; setrlimit(RLIMIT_CPU, &rl); #endif #ifdef O_NONBLOCK /* Set for non-blocking I/O on a pipe */ fcntl(p[0], F_SETFL, fcntl(p[0], F_GETFL) | O_NONBLOCK); #endif /* !O_NONBLOCK */ echobytes(); leave_queue(); NbufWrite(CL_WAIT); if (errmsg[0] != NULLB) fatalf((errcode != 0 ? errcode : err_blast), "%s", errmsg); clientprintf("h_name); else hostname = str_dup(hostaddr); return hostname; } #ifdef VALIDATEADDRESS validate_user() { char *hostname; char *findmatch PROTO((char *,char *,char *)); if (findmatch(stopfile, NULL, NULL) != NULL) { logmsg("client is listed in the stop file"); dodown(); } /* Get hostname of peer */ hostname = getpeerhostname((char *)&sockin.sin_addr, sizeof(sockin.sin_addr), AF_INET); if (findmatch(hostsfile, NULL, svcdomain) == NULL) { if (batch) { dodown(); } sleep(5); clientprintf("\r\nYour computer is not listed in the access control list \r\n"); clientprintf("for WashU GSC's Experimental BLAST Network Service. \r\n"); clientprintf("Please contact a local system administrator who is knowledgeable \r\n"); clientprintf("about how to register additional computers with GSC, \r\n"); clientprintf("or if your institution has not yet submitted a request \r\n"); clientprintf("to use the service, send a simple message of inquiry to: \r\n"); clientprintf("blast-help@blast.wustl.edu \r\n\r\n"); sleep(10); if (strcmp(hostname,hostaddr) != 0) fatalf(err_perm, "permission denied to ``%s'' (IP address %s)", hostname, hostaddr); else fatalf(err_perm, "permission denied to IP address %s", hostaddr); } } #endif /* VALIDATEADDRESS */ char * findmatch(fname, retcode, domain) char *fname; char *retcode; char *domain; { FILE *fp; register char *bufp, *cp; int toklen; char buf[FILENAME_MAX+256]; sprintf(buf, "etc/%s", fname); fp = fopen(buf, "r"); if (fp != NULL) { while (fgets(bufp = buf, sizeof(buf)-1, fp) != NULL) { if ((cp = strchr(bufp, '#')) != NULL) *cp = NULLB; while ((cp = strtok(bufp, TOKSTR)) != NULL) { bufp = NULL; /* for subsequent calls to strtok() */ if (*cp == NULLB) break; if (isdigit(*cp)) { /* IP address or IP subnet */ toklen = strlen(cp); if (cp[toklen-1] == '.') { /* IP subnet? */ if (strncmp(hostaddr, cp, toklen) == 0) { (void) fclose(fp); if (domain != NULL) strcpy(domain, strtok(buf, TOKSTR)); return cp; } } else if (strcmp(hostaddr, cp) == 0) { (void) fclose(fp); if (domain != NULL) strcpy(domain, strtok(buf, TOKSTR)); return cp; } } else if (strcasecmp(hostname, cp) == 0) { (void) fclose(fp); if (domain != NULL) strcpy(domain, strtok(buf, TOKSTR)); return cp; } } } (void) fclose(fp); } return retcode; } int isdown() { struct stat sbuf; char fname[FILENAME_MAX+1]; sprintf(fname, "private/%s/down", shorthost); if (stat(fname, &sbuf) != -1) return TRUE; return FALSE; } ckdown() { if (isdown()) dodown(); return 0; } dodown() { char fname[FILENAME_MAX+1]; char buf[512]; FILE *fp; int i, j; sprintf(fname, "private/%s/down", shorthost); clientprintf(">DOWN\r\n"); clientflush(); clientprintf( "\r\nThe BLAST service on %s is down", localhost); fp = fopen(fname, "r"); if (fp != NULL) { clientprintf("...\r\n"); while (fgets(buf, sizeof buf, fp) != NULL) { i = buf[j = strlen(buf) - 1]; if (i == '\n') buf[j] = '\r', buf[j+1] = '\n'; buf[j+2] = '\0'; clientprintf("%s", buf); } fclose(fp); } clientprintf("\r\n"); clientflush(); clientclose(); fatalf(err_down, "service is down"); } ckdbdown() { struct stat sbuf; char fname[FILENAME_MAX]; char buf[512]; FILE *fp; int i, j; sprintf(fname, "private/%s/%s.down", shorthost, args[1]); if (stat(fname, &sbuf) == -1) return; clientprintf(">DOWN\r\n"); clientflush(); ckmotd(); clientprintf("The database ``%s'' is currently unavailable...\r\n", args[1]); fp = fopen(fname, "r"); if (fp != NULL) { while (fgets(buf, sizeof buf, fp) != NULL) { i = buf[j = strlen(buf) - 1]; if (i == '\n') buf[j] = '\r', buf[j+1] = '\n'; buf[j+2] = '\0'; clientprintf("%s", buf); } fclose(fp); } clientflush(); clientclose(); fatalf(err_dbdown, "database is down"); } ckmotd() { struct stat sbuf; char fname[FILENAME_MAX+1]; char buf[FILENAME_MAX+1]; FILE *fp; int i; static char once; if (once) return; once = 1; get_uptime(); clientprintf("Genome Sequencing Center (GSC)\n"); clientprintf("Washington University School of Medicine, St. Louis, MO\n\n"); clientprintf("Experimental GENINFO(R) BLAST Network Service (%s)\n\n", localhost); clientprintf("%s, %s\n\n", date0, uptime); logmsg("uptime %s", uptime); if (!batch) { clientprintf(" If results of this search are reported or published, please mention that\n"); clientprintf(" the computation was performed at the GSC using the BLAST network service.\n"); clientprintf(" Problems with the service should be reported to a local system administrator.\n\n"); /* Display host-specific message */ sprintf(fname, "etc/motd.%s", shorthost); cat_file(fname); } /* Display a general message */ cat_file("etc/motd"); return 0; } int cat_file(fname) CharPtr fname; { FILE *fp; int nbytes; char buf[512]; fp = fopen(fname, "r"); if (fp != NULL) { while ((nbytes = fread(buf, 1, sizeof(buf), fp)) > 0) if (clientwrite(buf, nbytes, CL_QUEUE) < 0) break; (void) fclose(fp); } return 0; } typedef struct _nbuf { struct _nbuf *next; unsigned len, _len; char *data; char _data[1]; } nbuf, *nbufptr; nbufptr Head, Current; void NbufSave(buf, size) char *buf; unsigned size; { nbufptr nbp; if (size == 0) return; #if 1 sigprocmask(SIG_BLOCK, &blockset, NULL); #endif nbp = (nbufptr)malloc(sizeof(*nbp)+size-1); if (nbp == NULL) return; nbp->next = NULL; Nlm_MemCpy(nbp->data = nbp->_data, buf, nbp->_len = nbp->len = size); if (Current == NULL) Head = nbp; else Current->next = nbp; Current = nbp; bytes_queued_total += size; bytes_queued += size; bytes_queued_max = MAX(bytes_queued_max, bytes_queued); if (bytecount + bytes_queued + size > output_max) NbufTruncate(); #if 1 sigprocmask(SIG_UNBLOCK, &blockset, NULL); #endif return; } void NbufNext() { nbufptr nbp; if ((nbp = Head) == NULL) return; #if 1 sigprocmask(SIG_BLOCK, &blockset, NULL); #endif bytes_queued -= Head->_len; Head = Head->next; if (Head == NULL) Current = NULL; free(nbp); #if 1 sigprocmask(SIG_UNBLOCK, &blockset, NULL); #endif return; } void NbufSkip(nbyte) unsigned nbyte; { if (Head == NULL) return; if (nbyte > Head->len) fatalf(err_nbuf, "NbufSkip: nbyte > Head->len, %u %u", nbyte, Head->len); if ((Head->len -= nbyte) == 0) { (void) NbufNext(); } else Head->data += nbyte; } void NbufWrite(flags) int flags; { int rc; while (open_client_connection && Head != NULL) { if ((rc = sockwrite(FD_STDOUT, Head->data, Head->len, flags&CL_WAIT)) == -1) { clientclose(); fatalf(err_write, "early shutdown of socket by client"); } if (rc <= 0) break; NbufSkip(rc); } return; } void NbufTruncate() { static char calledbefore = 0; long sum; nbufptr nbp; if (calledbefore) return; calledbefore = 1; sum = -8192; nbp = Head; while (nbp != NULL && (sum += nbp->len) + bytecount < output_max) nbp = nbp->next; if (nbp != NULL) nbp->next = NULL; sprintf(errmsg, "Too much output produced; the limit is %lu kilobytes", (unsigned long)output_max/KBYTE); errcode = err_output; close_child(); return; } void echobytes() { nbufptr nbp; int rc, wrc; char buf[4*KBYTE]; gettimeofday(&t2, NULL); while ((rc = procread(p[0], buf, sizeof(buf))) > 0) { clientwrite(buf, rc, CL_QUEUE); } close_child(); gettimeofday(&t3, NULL); return; } void close_child() { if (p[0] >= 0) (void) close(p[0]); p[0] = -1; } long tvdiff(tf, t0) struct timeval *tf, *t0; { long usec, sec; usec = tf->tv_usec - t0->tv_usec; sec = tf->tv_sec - t0->tv_sec; if (usec < 0) { --sec; usec = 1000000 + usec; } return sec + (usec+500000)/1000000; } createlog(logfile) char *logfile; { char fname[FILENAME_MAX+1]; char *cp; char buf[100]; struct tm *tp; int val; static int firstcall = 1; if (firstcall != 1) return; firstcall = 0; settempdir(); sprintf(fname, "%s/%s", tempdir, logfile); logfp = fopen(fname, "a+"); if (logfp != NULL) { logfd = fileno(logfp); /* Set the close-on-exec flag for the log file descriptor */ if ((val = fcntl(logfd, F_GETFD, 0)) != -1) { val |= FD_CLOEXEC; fcntl(logfd, F_SETFD, val); } } #if 0 else fatalf(err_logfile, "Sorry, unable to open log file"); #endif tp = localtime((time_t *)&t0.tv_sec); if (tp != NULL && strftime(buf, sizeof(buf)-1, "%a %h %d %X %Z %Y", tp) > 0) { cp = buf; if (cp[8] == '0') strcpy(cp+8, cp+9); } else cp = "time unknown"; rm_whitespace2(cp); date0 = str_dup(cp); logmsg("begins %s %s", date0, hostaddr); } endlog() { char *cp; struct tms t; long usecs, ssecs, tsecs; char buf[1024]; int i; static char recurse; if (logfp == NULL || recurse) { return; } recurse = 1; reap_child(); if (times(&t) != -1) { usecs = t.tms_utime + t.tms_cutime; ssecs = t.tms_stime + t.tms_cstime; tsecs = usecs + ssecs; } else tsecs = usecs = ssecs = -1; cp = ctime((time_t *)&tf.tv_sec); if (cp == NULL) cp = "at time unknown"; else cp[strlen(cp)-1] = NULLB; args[0][MIN((int)strlen(args[0]), 20)] = NULLB; args[1][MIN((int)strlen(args[1]), 20)] = NULLB; if (status == 2) logmsg("Killed by a %s signal", STRSIGNAL(exitcode)); logmsg("done: %s %s %lu", theuser, hostaddr, (unsigned long)time(NULL)); sprintf(buf, "summary: #Date %s #IP %s #Dom %s #User %s #Exec %s #Db %s #Qlen %d #BQ %lu #BQM %lu #Out %d #Wt %ld #Rt %ld #Ct %ld #Cpu %d.%du %d.%ds %d.%dt #Exit %d %d", date0, hostaddr, svcdomain, theuser, args[0], args[1], querylen, bytes_queued_total, bytes_queued_max, bytecount, tvdiff(&t1, &t0), /* queue wait time */ tvdiff(&t3, &t2), /* run time of blast process */ tvdiff(&tf, &t0), /* connect time */ usecs/clk_tck, ((usecs%clk_tck)*100 + 5)/(10*clk_tck), ssecs/clk_tck, ((ssecs%clk_tck)*100 + 5)/(10*clk_tck), tsecs/clk_tck, ((tsecs%clk_tck)*100 + 5)/(10*clk_tck), status, exitcode ); if (blastd_errno == err_none) logmsg("%s", buf); else logmsg("%s #Err %d", buf, blastd_errno); recurse = 0; ckuseroffsystem(); } void #ifndef VAR_ARGS logmsg(char *format, ...) #else logmsg(format, va_alist) char *format; va_dcl #endif { va_list args; static int notfirstline; char buf[8192]; if (logfp == NULL || ferror(logfp) != 0) { return; } #ifndef VAR_ARGS va_start(args, format); #else va_start(args); #endif vsprintf(buf, format, args); va_end(args); if (!notfirstline) /* a little extra space for easy reading by humans */ fprintf(logfp, "\n%d: %s\n", pid, buf); else fprintf(logfp, "%d: %s\n", pid, buf); notfirstline = 1; fflush(logfp); } getbatchcmd() { int i; if (sockgets(cmdline, sizeof(cmdline)-1, stdin) == NULL) fatalf(err_username, "null input"); i = strlen(cmdline); if (i == 0 || strcmp(cmdline, ">BATCH\r\n") != 0) return; logmsg("batch"); cmdline[0] = NULLB; /* signal to getusername() that fgets must be called */ batch = TRUE; netgrp_run_max = 2; netgrp_queue_max = 100; alarm_sec = 4*3600; alarm_poll = 3600; proc_wait = 600; niceval += 1; ck_netgrp_queue = TRUE; ck_master_queue = FALSE; if (cputime_max < 100000) cputime_max = 5*cputime_max; #ifdef VALIDATEADDRESS hostsfile = BATCHFILE; #endif } getusername() { int i; char *cp0, *cp; if (cmdline[0] == NULLB && sockgets(cmdline, sizeof(cmdline)-1, stdin) == NULL) fatalf(err_username, "null input"); i = strlen(cmdline); if (i == 0 || strncmp(cmdline, ">USER ", 6) != 0) return; cmdline[--i] = NULLB; cmdline[0] = NULLB; /* signal to readcmdline() that fgets must be called */ cmdline[MIN(sizeof(theuser) + sizeof(">USER"),sizeof(cmdline))] = NULLB; cp = cmdline + sizeof(">USER"); while (*cp != NULLB && isspace(*cp)) ++cp; i = strlen(cp); while (i > 0) { if (cp[i-1] == '\r' || cp[i-1] == '\n' || isspace(cp[i-1])) cp[--i] = NULLB; else break; } buffilter(cp); buffilter2(cp); strncpy(theuser, cp, sizeof(theuser)-1); logmsg("user: %s", theuser); } readcmdline() { int i; char *cp, buf[100]; if (cmdline[0] == NULLB && sockgets(cmdline, sizeof(cmdline) -1, stdin) == NULL) fatalf(err_cmdline, "null input."); i = strlen(cmdline); if (i > 0) cmdline[--i] = NULLB; if (i > 0 && (cmdline[--i] == '\r' || cmdline[i] == '\n')) cmdline[i] = NULLB; buffilter(cmdline); if (i < DIM(buf)) logmsg("command: %s", cmdline); else { Nlm_MemCpy(buf, cmdline, DIM(buf)-1); buf[DIM(buf)-1] = NULLB; logmsg("command: %s...", buf); } } parsecmdline() { register CharPtr cp, cp2; cp = cp2 = cmdline; while (*cp != NULLB) { if (isspace(*cp)) { ++cp; continue; } args[nargs++] = cp2; while (*cp != NULLB && !isspace(*cp)) { if (*cp == '\\') /* an "escaped" character follows */ ++cp; *cp2++ = *cp++; } if (*cp++ == NULLB) break; *cp2++ = NULLB; } *cp2++ = NULLB; if (nargs == 0) fatalf(err_nullargs, "no command was specified. Probable cause: buggy client software; contact your system manager about obtaining an update!"); if (nargs < 3) fatalf(err_cmdline, "incomplete command line. Probable cause: buggy client software; contact your system manager about obtaining an update!"); args[nargs] = NULL; if (strcmp(args[2], "-") == 0) args[2] = "stdin"; ckprogname(args[0]); if (wd != NULL && *wd != NULLB) sprintf(PathProgram, "netbin/%s", args[0]); else sprintf(PathProgram, "%s/netbin/%s", gethome(NULL), args[0]); if (batch) strcat(PathProgram, ".batch"); ckexecutable(PathProgram); } parsecmdline_orig() { register char *cp; int len; nargs = 0; cp = strtok(cmdline, TOKSTR); while (cp != NULL) { if (nargs > DIM(args)) fatalf(err_argsmax, "too many command line arguments"); len = strlen(cp); args[nargs] = cp; cp = strtok(NULL, TOKSTR); args[nargs++][len] = NULLB; } if (nargs == 0) fatalf(err_nullargs, "no command was specified. Probable cause: a buggy client program; obtain an update!"); if (nargs < 3) fatalf(err_cmdline, "incomplete command line. Probable cause: a buggy client program; obtain an update!"); args[nargs] = NULL; if (strcmp(args[2], "-") == 0) args[2] = "stdin"; ckprogname(args[0]); if (wd != NULL && *wd != NULLB) sprintf(PathProgram, "netbin/%s", args[0]); else sprintf(PathProgram, "%s/netbin/%s", gethome(NULL), args[0]); ckexecutable(PathProgram); } ckprivileges() { FILE *fp; char buf[1024], *bp; char fname[FILENAME_MAX+1]; register char *cp, ch; int toklen; Boolean goodhost = TRUE; Boolean dbislisted = FALSE; Boolean gets_access = TRUE; if (args[1] != NULL) str_tolower(args[1]); /* first try to open a program-specific exports file, e.g. for TBLASTX */ sprintf(fname, "etc/%s.%s", exportsfile, args[0]); fp = fopen(fname, "r"); if (fp == NULL) { sprintf(fname, "etc/%s", exportsfile); fp = fopen(fname, "r"); if (fp == NULL) return; } while (fgets(buf, sizeof(buf)-1, fp) != NULL) { if ((cp = strchr(buf, '#')) != NULL) if (cp == buf) continue; else *cp = NULLB; if ((cp = strtok(buf, TOKSTR)) != NULL) { /* Does this line start with the chosen database name? */ if (*cp != '*' && strcmp(cp, args[1]) != 0) continue; if (*cp != '*') dbislisted = TRUE; while ((cp = strtok(NULL, TOKSTR)) != NULL) { if ((ch = *cp) == '#' || ch == NULLB) break; goodhost = FALSE; if (ch == '-') { gets_access = FALSE; ch = *++cp; } else { gets_access = TRUE; if (ch == '+') ch = *++cp; } if (isdigit(ch)) { /* Compare IP addresses or subnets */ toklen = strlen(cp); if (cp[toklen-1] == '.') { if (strncmp(hostaddr, cp, toklen) == 0) { goodhost = TRUE; break; } } else if (strcmp(hostaddr, cp) == 0) { goodhost = TRUE; break; } } else /* Compare hostnames */ if (strcasecmp(hostname, cp) == 0) { goodhost = TRUE; break; } } if (goodhost) { fclose(fp); if (gets_access) return; /* Permission granted! */ } } } if (dbislisted) fatalf(err_dbaccess, "access denied from your host to database ``%s''", args[1]); else { ckmotd(); fatalf(err_exports, "a database named ``%s'' does not exist on this server or is not being exported for use with the %s program.", args[1], args[0]); } } ckprogname(progname) register char *progname; { register char **cp; cp = validprogs; while (*cp != NULL) { if (strncasecmp(*cp, progname, strlen(*cp)) == 0) { theProgram = *cp; break; } ++cp; } if (*cp == NULL) fatalf(err_progname, "invalid network application program: \"%s\"", progname); } /* Get title/name of sequence */ readtitle() { int i; title[0] = NULLB; if (sockgets(title, sizeof title, stdin) == NULL) fatalf(err_seqname, "missing sequence name"); i = strlen(title) - 1; if (title[i] != '\n') fatalf(err_titlelen, "Name of query sequence is too long (> %d letters)", sizeof(title)-2); if (i > 51) { title[50] = NULLB; strcat(title, "..."); } while (i >= 0 && isspace(title[i])) title[i--] = NULLB; buffilter(title); logmsg("query: %s", title); } readquery() { FILE *fp; static char goodletter[1< querylen_max) fatalf(err_qlen, "Query sequence is too long (> %d letters)", querylen_max); if (inclen > 0) { if (fwrite(buf, inclen, 1, fp) != 1) fatalf(err_qfile, "unable to create query file"); fputc('\n', fp); } } if (ferror(fp) != 0) fatalf(err_qfile, "unable to create query file"); fclose(fp); logmsg("querylen: %d", querylen = len); } void #ifndef VAR_ARGS fatalf(enum blastd_errors err, char *format, ...) #else fatalf(err, format, va_alist) enum blastd_errors err; char *format; va_dcl #endif { va_list args; char buf[1024]; char errbuf[4096]; CharPtr cp; int i; static int once; if (once > 0) return; once = 1; close_child(); leave_queue(); rm_queue(); blastd_errno = err; #ifndef VAR_ARGS va_start(args, format); #else va_start(args); #endif vsprintf(errbuf, format, args); va_end(args); clientprintf("\nFATAL: %s: %s\nEXIT CODE 1\n= sizeof(last_uname)-1) fatalf(err_uidlen, "userid has illegal length: \"%s\"", uname); if (!strcmp(uname, last_uname)) return last_home; /* * Rewind the passwd file */ setpwent(); if ((pwp = getpwnam(uname)) == NULL) fatalf(err_pwnam, "who is %s?", uname); strcpy(last_uname, uname); if (last_home != NULL) free(last_home); last_home = str_dup(pwp->pw_dir); if (last_home == NULL) fatalf(err_mem, "out of memory"); return last_home; } int ckexecutable(progname) char *progname; { struct stat sb; if (progname == NULL || progname == NULLB || !strcmp(progname, "-")) fatalf(err_prognotfound, "application program not found"); if (stat(progname, &sb) == 0) { if ((sb.st_mode & S_IFDIR) != 0 || (sb.st_mode & S_IFREG) == 0 || access(progname, X_OK) != 0) goto Sorry; return; } Sorry: fatalf(err_prognotfound, "Sorry, application program not found on server: %s", misc_basename(progname, NULL)); } ckpriority() { #if 1 char fname[FILENAME_MAX+1]; struct stat sb; double space; /* total size of search space (query X database) */ Boolean aadb = FALSE, aaq = FALSE, ntdb = FALSE, ntq = FALSE; if (strcmp(theProgram, "tblastn") == 0 || strcmp(theProgram, "blastn") == 0) ntdb = TRUE, space = 140.e6; else aadb = TRUE, space = 25.e6; if (strcmp(theProgram, "blastp") == 0 || strcmp(theProgram, "tblastn") == 0 || strcmp(theProgram, "blast3") == 0) aaq = TRUE; else ntq = TRUE; if (args[1] != NULL && args[1][0] != NULLB) { sprintf(fname, "/usr/ncbi/db/blast/%s.%s", args[1], (ntdb ? "csq" : "bsq") ); if (stat(fname, &sb) == 0) { space = sb.st_size; if (ntdb) space *= 4; } } space *= querylen; if ((aadb && space > 30.e9) || (ntdb && space > 1.4e12)) extra_nice += 1; if ((aaq && querylen > 2000) || (ntq && querylen > 50000)) extra_nice += 1; if (strcmp(theProgram, "tblastn") == 0) { if (space > 750.e6) extra_nice += 1; if (space > 70.e9) extra_nice += 1; } if (strcmp(theProgram, "blastx") == 0) { extra_nice += 1; if (space > 12.5e9 || querylen > 500) extra_nice += 1; } if (strcmp(theProgram, "blastn") == 0) { if (space < 30.e9 && !batch) ck_netgrp_queue = 0; } if (strcmp(theProgram, "blastp") == 0) { if (space < 300.e6 && !batch) ck_netgrp_queue = 0; if (space > 11.e9) extra_nice += 1; if (space > 25.e9) extra_nice += 1; } if (strcmp(theProgram, "blast3") == 0) { extra_nice += 1; if (space > 5.e9) extra_nice += 1; if (space > 12.5e9) extra_nice += 1; } if (batch) extra_nice = MIN(extra_nice, 3); extra_nice = MAX(extra_nice, 0); #endif } #include #include #if (defined(OS_UNIX_SYSV) && defined(sun)) || defined(OS_UNIX_ULTRIX) || defined(OS_UNIX_OSF1) union semun { int val; struct semid_ds *buf; ushort *array; }; #endif /* ckqueue--put user on a waiting list if too many requests from their subnet or too many requests overall are in progress. If too many requests are waiting from their subnet or overall, the user gets bumped off with a DOWN message. */ key_t master_key, netgrp_key, user_key; int master_semid, netgrp_semid = -2, user_semid = -1; int ckqueue(addr) unsigned int addr; { register int i; int rc; union semun sm; struct sembuf sops; int semval, semncnt; if (batch) addr = 0xffffffff; master_key = (PROJECT_ID<<24); if (repeatuser) master_key += 0x01000000; netgrp_key = (addr>>8) | master_key; if (ck_netgrp_queue) { GetNetgroup: netgrp_semid = semget(netgrp_key, 1, 0); if (netgrp_semid == -1) { switch (errno) { case EACCES: fatalf(err_semacc, "Access to service is denied due to semaphore block"); default: break; } netgrp_semid = semget(netgrp_key, 1, IPC_EXCL|IPC_CREAT|0662); if (netgrp_semid == -1) switch (errno) { case EINTR: case EEXIST: goto GetNetgroup; case EACCES: fatalf(err_semacc, "Access to service is denied due to semaphore block"); case ENOENT: /* this shouldn't occur */ fatalf(err_semacc, "weird semaphore error, errno = %d", errno); case ENOSPC: logmsg("ENOSPC error on netgrp semaphore"); default: netgrp_semid = -2; break; } else { /* Set size of netgroup queue to netgrp_run_max */ semval = sm.val = netgrp_run_max; while ((rc = semctl(netgrp_semid, 0, SETVAL, sm)) == -1 && errno == EINTR) ; if (rc == -1) switch (errno) { case EINVAL: netgrp_semid = -1; goto GetNetgroup; default: netgrp_semid = -2; goto Master; } } } if (netgrp_semid >= 0) { semval = semctl(netgrp_semid, 0, GETVAL, sm); semncnt = semctl(netgrp_semid, 0, GETNCNT, sm); if ((semval == -1 || semncnt == -1) && errno == EINVAL) { netgrp_semid = -1; semval = semncnt = -1; goto GetNetgroup; } /* Will user have to wait? */ if (semncnt > 0 || semval <= 0) { if (!batch && semncnt+1 > netgrp_queue_max) { clientprintf(">DOWN\r\n"); clientflush(); clientprintf("\r\nToo many users on your Internet subnet are currently using or waiting for service.\r\n"); clientclose(); fatalf(err_queue, "netgrp_queue_max=%d reached", netgrp_queue_max); } } if (!batch && semval <= 0) { logmsg("%d users waiting in subnet queue", semncnt+1); if (semncnt == 0) clientprintf("\nWaiting for 1 of %d users on your Internet subnet to finish.\n", netgrp_run_max); else clientprintf("\nWaiting for %d of %d users on your Internet subnet to finish.\n", semncnt+1, netgrp_run_max+semncnt); clientflush(); } AlarmEvery(&qalarm, alarm_poll, sigalarm_poll, NULL); sops.sem_num = 0; sops.sem_op = -1; sops.sem_flg = SEM_UNDO; while ((rc = semop(netgrp_semid, &sops, 1)) == -1 && errno == EINTR) ; if (rc == -1) switch (errno) { case EIDRM: case EINVAL: netgrp_semid = -1; AlarmClr(qalarm); qalarm = NULL; goto GetNetgroup; default: netgrp_semid = -2; break; } AlarmClr(qalarm); qalarm = NULL; gettimeofday(&t1, NULL); if (!batch && semval == 0) { clientprintf("Continuing...\n"); clientflush(); logmsg("continuing %d subnet wait", tvdiff(&t1, &t0)); } } } /* ck_netgrp_queue */ Master: if (ck_master_queue) { GetMaster: master_semid = semget(master_key, 1, 0); if (master_semid == -1) { switch (errno) { case ENOENT: break; default: master_semid = -2; return 1; } while ((master_semid = semget(master_key, 1, IPC_EXCL|IPC_CREAT|0662)) == -1 && errno == EINTR) ; if (master_semid == -1) switch (errno) { case EEXIST: goto GetMaster; default: master_semid = -2; break; } if (master_semid > -1) { /* Make semaphore available to maximum of run_max processes at a time */ sm.val = run_max; while ((rc = semctl(master_semid, 0, SETVAL, sm)) == -1 && errno == EINTR) ; if (rc == -1) switch (errno) { case EIDRM: case EINVAL: master_semid = -1; goto GetMaster; default: master_semid = -2; break; } } } if (master_semid > -1) { semval = semctl(master_semid, 0, GETVAL, sm); semncnt = semctl(master_semid, 0, GETNCNT, sm); if ((semval == -1 || semncnt == -1) && errno == EINVAL) { master_semid = -1; semval = semncnt = -1; goto GetMaster; } if (semncnt > 0 || semval == 0) { if (semncnt+1 > queue_max) { clientprintf(">DOWN\r\n"); clientflush(); clientprintf("Too many BLAST jobs are currently running or waiting in the queue.\r\n"); clientclose(); fatalf(err_queue, "queue_max=%d exceeded", queue_max); } } if (semval == 0) { if (semncnt == 0) clientprintf("\nWaiting for 1 of %d other BLAST requests on the system to finish.\n", run_max); else clientprintf("\nWaiting for %d of %d other BLAST requests on the system to finish.\n", semncnt+1, run_max+semncnt); clientflush(); logmsg("request placed on total system queue"); } AlarmEvery(&qalarm, alarm_poll, sigalarm_poll, NULL); sops.sem_num = 0; sops.sem_op = -1; sops.sem_flg = SEM_UNDO; while ((rc = semop(master_semid, &sops, 1)) == -1 && errno == EINTR) ; if (rc == -1) switch (errno) { case EIDRM: case EINVAL: master_semid = -1; AlarmClr(qalarm); qalarm = NULL; goto GetMaster; default: master_semid = -2; break; } AlarmClr(qalarm); qalarm = NULL; gettimeofday(&t1, NULL); if (semval == 0) { clientprintf("Continuing...\n"); clientflush(); logmsg("continuing %d master wait", tvdiff(&t1, &t0)); } } } /* ck_master_queue */ return 0; } int leave_queue() { union semun sm; struct sembuf sops; static int once; if (once > 0) return 0; once = 1; sops.sem_num = 0; sops.sem_op = 1; sops.sem_flg = SEM_UNDO; if (netgrp_semid >= 0) semop(netgrp_semid, &sops, 1); sops.sem_num = 0; sops.sem_op = 1; sops.sem_flg = SEM_UNDO; if (master_semid >= 0) semop(master_semid, &sops, 1); return 0; } /* rm_queue -- obtain exclusive access to and then remove a semaphore */ int rm_queue() { union semun sm; int rc, semid, semncnt, semval; critical_begin(); semid = netgrp_semid; netgrp_semid = -2; critical_end(); if (semid == -1) { logmsg("rm_queue: my semaphore disappeared!"); return 0; } if (semid < 0) { return 0; } semncnt = semctl(semid, 0, GETNCNT, sm); if (semncnt == -1) return -1; if (semncnt > 0) { logmsg("%d users were waiting in netgrp queue", semncnt); return 0; } semval = semctl(semid, 0, GETVAL, sm); /* If this is not the last running user, don't remove the semaphore */ if (semval < netgrp_run_max) return 0; rc = semctl(semid, 0, IPC_RMID); if (rc == -1) logmsg("semctl(IPC_RMID) failed"); return rc; } get_uptime() { struct stat sb; char *strstr(); char *cp, *cp2; int i; FILE *pp; if (uptime[0] != NULLB) return 0; if (stat(cp = "/usr/bsd/uptime", &sb) == -1) if (stat(cp = "/usr/ucb/uptime", &sb) == -1) return 0; pp = popen(cp, "r"); if (pp == NULL) return 0; cp = fgets(uptime, sizeof(uptime)-1, pp); pclose(pp); if (cp == NULL) return 0; cp = strchr(uptime, '\n'); if (cp != NULL) *cp = NULLB; rm_whitespace2(uptime); cp = strchr(uptime, 'u'); if (cp != NULL) { *cp = toupper(*cp); strcpy(uptime, cp); } if ((cp = strstr(uptime, " average")) != NULL) strcpy(cp, cp+sizeof(" average")-1); if ((cp = strstr(uptime, "users,")) != NULL) { cp2 = cp+sizeof("users,")-2; while (cp > uptime && *--cp != ',') ; strcpy(cp, cp2); } return 0; } /* rm_whitespace2 -- convert consecutive blanks to a single blank */ rm_whitespace2(s) char *s; { char *t = s, ch; while ((ch = *s++) != NULLB) { *t++ = ch; if (isspace(ch)) { do { ch = *s++; } while (ch != NULLB && isspace(ch)); if (ch != NULLB) *t++ = ch; } } *t = NULLB; } int sigalarm(vp) VoidPtr vp; { fatalf(err_alarm, "%s", vp); return 0; } int sigalarm_poll(vp) VoidPtr vp; { clientprintf("Still waiting\n"); clientflush(); return 0; } #ifdef SIGURG void sigurg() { unsigned char ctlbyte = 255, waste[128]; signal(SIGURG, SIG_IGN); /* Read the out-of-band data */ while (recv(FD_STDIN, (char *)&ctlbyte, sizeof ctlbyte, MSG_OOB) < 0) { switch (errno) { case EAGAIN: #if EAGAIN != EWOULDBLOCK case EWOULDBLOCK: #endif /* The urgent data is in-line and has not arrived yet */ READSOCKET(FD_STDIN, waste, sizeof(waste)); continue; case EINTR: continue; default: goto EndWhile; } } EndWhile: clientclose(); fatalf(err_oobmsg, "Terminated by OOB message = %d", ctlbyte); } #endif void reap_child() { int i, chld, s, code; int block_code = 0; if (child <= 0) return; chld = child; child = -1; do { while ((i = waitpid(-chld, &s, WNOHANG)) != -1 || (i = waitpid(chld, &s, WNOHANG)) != -1) { errno = 0; if (i == 0) { /* some child(ren) still survive */ kill(-chld, SIGKILL); kill(chld, SIGKILL); sleep(1); continue; } if (block_code) continue; if (WIFEXITED(s)) { code = WEXITSTATUS(s); status = 1; if (exitcode == 0) exitcode = code; continue; } if (WIFSTOPPED(s)) { kill(-chld, SIGKILL); kill(i, SIGKILL); if (i == chld) logmsg("child was stopped, requiring that it be KILLed"); else logmsg("descendant of child was stopped, requiring that it be KILLed"); continue; } if (WIFSIGNALED(s)) { code = WTERMSIG(s); status = 2; exitcode = code; switch (code) { case SIGKILL: case SIGTERM: continue; case SIGALRM: block_code = 1; mailsubject = "BLAST REQUEST TOOK TOO LONG TO COMPLETE"; continue; case SIGBUS: case SIGSEGV: block_code = 1; logmsg("%s, child=%d", STRSIGNAL(code), chld); mailsubject = "SEVERE BLAST APPLICATION PROBLEM"; strcpy(errmsg, STRSIGNAL(code)); continue; #ifdef SIGXCPU case SIGXCPU: block_code = 1; mailsubject = "BLAST SERVER CPU TIME LIMIT REACHED"; logmsg("cpu time limit reached"); if (errmsg[0] == NULLB) sprintf(errmsg, "cpu time limit (%lu seconds total for all processors) reached", (unsigned long)cputime_max); continue; #endif #ifdef SIGXFSZ case SIGXFSZ: block_code = 1; if (errmsg[0] == NULLB) sprintf(errmsg, "Too much output produced; the limit is %lu kilobytes", (unsigned long)output_max/KBYTE); continue; #endif default: block_code = 1; if (errmsg[0] == NULLB) sprintf(errmsg, "BLAST application was killed by a %s signal.", STRSIGNAL(code)); continue; } } status = 3; /* should never see this */ } } while (errno == EINTR); if (status == 2) switch (exitcode) { case SIGALRM: if (alarm_sec < 300) fatalf(err_alarm, "Sorry, the job took longer than %u seconds to complete.", alarm_sec); else fatalf(err_alarm, "Sorry, the job took longer than %u minutes to complete.", alarm_sec/60); break; case SIGTERM: case SIGKILL: fatalf(err_killed, "job was killed by a %s signal (perhaps the system is going down)", STRSIGNAL(exitcode)); break; default: break; } gettimeofday(&t3, NULL); } void mailmsg(msg) char *msg; { FILE *pp; char cmd[10*KBYTE]; sprintf(cmd, "/usr/sbin/Mail -s \"%s\" blastmgr > /dev/null 2>&1", msg); pp = popen(cmd, "w"); if (pp == NULL) return; fprintf(pp, "%s\n", msg); fprintf(pp, "blastd pid = %d\n", pid); fprintf(pp, "user = \"%s\", host = %s\n", theuser, hostname); pclose(pp); return; } buffilter(buf) char *buf; { register char ch; while ((ch = *buf) != NULLB) { if (!isascii(ch) || !isprint(ch) || ch == '/') *buf = '%'; ++buf; } } buffilter2(buf) char *buf; { register char ch; while ((ch = *buf) != NULLB) { if (ch == '#') *buf = '%'; ++buf; } } #ifndef S_ISREG #define S_ISREG(f) ((f)&S_IFREG) #endif #ifndef S_ISDIR #define S_ISDIR(f) (((f) & S_IFMT) == S_IFDIR) #endif int settempdir() { struct stat sbuf; if (tempdir[0] != NULLB) return 0; sprintf(tempdir, "private/%s", shorthost); if (lstat(tempdir, &sbuf) == -1 || !S_ISDIR(sbuf.st_mode) || ckperms(&sbuf)) { strcpy(tempdir, "/usr/tmp"); if (stat(tempdir, &sbuf) == -1 || !S_ISDIR(sbuf.st_mode) || ckperms(&sbuf)) { logmsg("unable to find a suitable temp directory"); clientprintf("Unable to find a suitable temporary working directory\n"); dodown(); } } return 0; } int ckperms(sbp) struct stat *sbp; { if (sbp->st_uid == getuid()) { if (sbp->st_mode & (2<<6)) /* user write permission */ return 0; return 1; } if (sbp->st_gid == getgid()) { if (sbp->st_mode & (2<<3)) /* group write permission */ return 0; return 1; } if (sbp->st_mode & 2) /* other write permission */ return 0; return 1; } int procread(fd, buf, buflen) int fd; char *buf; int buflen; { int rc; while ((rc = read(fd, buf, buflen)) <= 0) { if (rc == 0) return -1; switch (errno) { case EINTR: continue; case EAGAIN: #if EAGAIN != EWOULDBLOCK case EWOULDBLOCK: #endif if (procselect(fd) == 0) continue; return -1; case EFAULT: case EBADF: case EINVAL: default: return -1; } } return rc; } int procselect(fd) /* return when fd is readable */ int fd; { int rc, arg, nfds; fd_set rfds, wfds, *wfdp, xfds; struct timeval to; /* setup to detect a clear channel for writing output to the client */ if (Head != NULL) { /* Data has been queued for writing to the client */ FD_ZERO(&wfds); FD_SET(FD_STDOUT, &wfds); wfdp = &wfds; } else wfdp = NULL; /* setup to detect unexpected data coming from client */ FD_ZERO(&rfds); FD_SET(fd, &rfds); FD_SET(FD_STDIN, &rfds); /* setup to detect out-of-band messages */ FD_ZERO(&xfds); FD_SET(FD_STDIN, &xfds); FD_SET(FD_STDOUT, &xfds); to.tv_usec = 0; to.tv_sec = proc_wait; nfds = MAX(fd+1, FD_STDOUT+1); while ((rc = select(nfds, &rfds, wfdp, &xfds, &to)) == -1) { switch (errno) { case EINTR: continue; case EBADF: return -1; default: sprintf(errmsg, "select error: %s", STRERROR(errno)); return -1; } } if (rc == 0) { sprintf(errmsg, "No response from blast process for %d seconds.", proc_wait); return -1; } if (FD_ISSET(FD_STDIN, &xfds) || FD_ISSET(FD_STDOUT, &xfds)) fatalf(err_oobmsg, "client killed session with out-of-band message"); if (wfdp != NULL && FD_ISSET(FD_STDOUT, &wfds)) NbufWrite(0); /* no waiting and no queueing */ if (FD_ISSET(fd, &rfds)) return 0; /* some output is available from the blast application */ if (FD_ISSET(FD_STDIN, &rfds)) { char buf[32]; /* Unexpected read activity or close of socket by client */ errno = 0; while ((rc = read(FD_STDIN, buf, sizeof(buf)-1)) == -1) switch (errno) { case EINTR: continue; case EBADF: goto Done; default: goto Done; } Done: if (rc <= 0) fatalf(err_shutdown, "EOF on client connection"); buf[rc] = NULLB; fatalf(err_baddata, "unexpected data from client: %s", buf); } return 0; } int clientflush() { if (open_client_connection) { NbufWrite(CL_WAIT); return 0; } return -1; } int clientclose() { if (open_client_connection) { open_client_connection = FALSE; close(FD_STDIN); close(FD_STDOUT); sockclose(FD_STDIN); sockclose(FD_STDOUT); gettimeofday(&tf, NULL); return 0; } return -1; } int clientwrite(buf, nbyte, flags) char *buf; unsigned nbyte; int flags; { int rc; if (!open_client_connection) return -1; if (Head != NULL) { NbufSave(buf, nbyte); NbufWrite(flags&CL_WAIT); return nbyte; } if ((rc = sockwrite(FD_STDOUT, buf, nbyte, flags&CL_WAIT)) == -1) { clientclose(); fatalf(err_write, "early shutdown of socket by client"); } if ((flags&CL_QUEUE) && rc < nbyte) { NbufSave(buf+rc, nbyte-rc); } return rc; } #ifndef VAR_ARGS clientprintf(char *format, ...) #else clientprintf(format, va_alist) char *format; va_dcl #endif { va_list args; char buf[4096]; #ifndef VAR_ARGS va_start(args, format); #else va_start(args); #endif vsprintf(buf, format, args); va_end(args); return clientwrite(buf, strlen(buf), CL_QUEUE); } int cputc(ch) int ch; { char c = (char)ch; return clientwrite(&c, 1, CL_WAIT); } int sockwrite(fd, buf, nbyte, dowait) int fd; char *buf; unsigned nbyte; int dowait; { unsigned nbyte_orig = nbyte; int rc; while (nbyte > 0) { while ((rc = WRITESOCKET(fd, buf, nbyte)) <= 0) { if (rc == 0) return -1; switch (errno) { case EINTR: continue; case EAGAIN: #if EAGAIN != EWOULDBLOCK case EWOULDBLOCK: #endif if (!dowait) return nbyte_orig - nbyte; if (sockselectw(fd) == 0) continue; return -1; case EPIPE: return -1; case EBADF: return -1; case ETIMEDOUT: case EFAULT: case EFBIG: case ERANGE: case EINVAL: case ECONNRESET: case ECONNABORTED: case ENOBUFS: case ESHUTDOWN: case ETOOMANYREFS: case ENETUNREACH: case EHOSTUNREACH: case ENETDOWN: case EHOSTDOWN: fatalf(err_write, "write: %s", STRERROR(errno)); default: logmsg("unanticipated errno (%d) from write, fd=%d", errno, fd); return -1; } } buf += rc; nbyte -= rc; if ((bytecount += rc) > output_max) { NbufTruncate(); return nbyte_orig; } } return nbyte_orig; } int sockselectw(fd) int fd; { fd_set wfds, xfds; struct timeval to; int cnt; FD_ZERO(&wfds); FD_SET(fd, &wfds); Nlm_MemCpy((char *)&xfds, (char *)&wfds, sizeof(xfds)); to.tv_usec = 0; to.tv_sec = timeout_sec; while ((cnt = select(fd+1, NULL, &wfds, &xfds, &to)) == -1) { switch (errno) { case EINTR: continue; case EBADF: return -1; case EFAULT: case EINVAL: fatalf(err_select, "select: %s", STRERROR(errno)); default: fatalf(err_select, "select: unanticipated errno (%d)", errno); } } if (cnt == 0) { logmsg("Connection timed out"); return -1; } if (FD_ISSET(fd, &wfds)) return 0; return -1; } char * clientgets(buf, buflen) char *buf; int buflen; { if (!open_client_connection) return NULL; if ((buf = sockgets(buf, buflen, stdin)) == NULL) { clientclose(); fatalf(err_read, "read: premature EOF"); } return buf; } char * sockgets(buf, buflen, fp) char *buf; int buflen; FILE *fp; { char *buf0 = buf, ch; int fd, rc; if ((fd = fileno(fp)) == -1) return NULL; if (buflen < 1) return buf; rc = 0; while (buflen-- > 1 && (rc = sockread(fd, buf, 1)) == 0) { if (*buf++ == '\n') break; } *buf = NULLB; if (rc != 0) return NULL; return buf0; } int sockopen(fd) int fd; { if (fd >= 0 && fd < FD_MAX) { if (sockbufs[fd] == NULL) sockbufs[fd] = malloc(SOCK_BUFLEN); sockbufpop[fd] = sockbuflens[fd] = 0; } return 0; } int sockclose(fd) int fd; { if (fd >= 0 && fd < FD_MAX && sockbufs[fd] != NULL) { free(sockbufs[fd]); sockbufs[fd] = NULL; sockbufpop[fd] = sockbuflens[fd] = 0; } return 0; } int sockpopulatebuf(fd) int fd; { int rc; if (fd >= 0 && fd < FD_MAX && sockbufs[fd] != NULL) { if (sockbufpop[fd] > sockbuflens[fd]) return 0; sockbufpop[fd] = 0; while ((rc = READSOCKET(fd, sockbufs[fd], SOCK_BUFLEN)) <= 0) { if (rc == 0) return 0; switch (errno) { case EAGAIN: #if EAGAIN != EWOULDBLOCK case EWOULDBLOCK: #endif return 0; case EINTR: continue; default: return -1; } } sockbufpop[fd] = rc; sockbuflens[fd] = 0; return 0; } return -1; } int sockreadbuf(fd, buf, buflen) int fd; CharPtr buf; size_t buflen; { if (fd >= 0 && fd < FD_MAX) { if (sockbuflens[fd] >= sockbufpop[fd]) return 0; buflen = MIN(sockbufpop[fd] - sockbuflens[fd], buflen); Nlm_MemCpy(buf, sockbufs[fd] + sockbuflens[fd], buflen); sockbuflens[fd] += buflen; return buflen; } return -1; } int sockread(fd, buf, nbyte) int fd; char *buf; unsigned nbyte; { int rc; while (nbyte > 0) { if (sockpopulatebuf(fd) == 0) { rc = sockreadbuf(fd, buf, nbyte); if (rc > 0) { buf += rc; nbyte -= rc; continue; } } while ((rc = READSOCKET(fd, buf, nbyte)) <= 0) { if (rc == 0) return -1; switch (errno) { case EINTR: continue; case EAGAIN: #if EAGAIN != EWOULDBLOCK case EWOULDBLOCK: #endif if (sockselectr(fd) == 0) continue; return -1; case EPIPE: logmsg("premature shutdown of socket by client"); return -1; case EBADF: return -1; case ETIMEDOUT: case EFAULT: case EFBIG: case ERANGE: case EINVAL: case ECONNRESET: case ECONNABORTED: case ENOBUFS: case ESHUTDOWN: case ETOOMANYREFS: case ENETUNREACH: case EHOSTUNREACH: case ENETDOWN: case EHOSTDOWN: #ifdef EBADMSG case EBADMSG: #endif fatalf(err_read, "read: %s", STRERROR(errno)); default: logmsg("unanticipated errno (%d) from read, fd=%d", errno, fd); return -1; } } buf += rc; nbyte -= rc; } return 0; } int sockselectr(fd) int fd; { fd_set rfds, xfds; struct timeval to; int cnt; FD_ZERO(&rfds); FD_SET(fd, &rfds); Nlm_MemCpy((char *)&xfds, (char *)&rfds, sizeof(xfds)); to.tv_usec = 0; to.tv_sec = timeout_sec; while ((cnt = select(fd+1, &rfds, NULL, &xfds, &to)) == -1) { switch (errno) { case EINTR: continue; case EBADF: return -1; case EFAULT: case EINVAL: fatalf(err_select, "select: %s", STRERROR(errno)); default: fatalf(err_select, "select: unanticipated errno (%d)", errno); } } if (cnt == 0) { logmsg("Connection timed out"); return -1; } if (FD_ISSET(fd, &rfds)) return 0; return -1; } /* See if user was on the system within the last REPEAT_TIME seconds */ #define REPEAT_TIME (2*60+10) #define REPEAT_SEEK (3*KBYTE) long getatime PROTO((char *, char *)); int ckrepeatuser() { char user[1024]; char ipaddr[1024]; long time_min, time_current, timestamp; off_t seekpoint, tmpoff; int ch, iter = 0; if (batch) return 0; if (ckuseronsystem()) goto Repeatuser; if (logfp == NULL) return 0; time_current = time(NULL); if (time_current == -1) return 0; time_min = time_current - REPEAT_TIME; seekpoint = REPEAT_SEEK; ipaddr[0] = user[0] = NULLB; do { if (fseek(logfp, -seekpoint, SEEK_END) != 0) return 0; /* go to the beginning of a line */ while ((ch = getc(logfp)) != EOF && ch != '\n') ; if (ch == EOF) return 0; timestamp = getatime(ipaddr, user); /* Go backwards until we're at least REPEAT_TIME back in time */ seekpoint += REPEAT_SEEK; if (++iter > 10) return 0; } while (timestamp >= time_min); for (; timestamp != 0 && timestamp <= time_current; timestamp = getatime(ipaddr, user)) { tmpoff = ftell(logfp); if (fseek(logfp, tmpoff, SEEK_SET) != 0) break; if (timestamp >= time_min && strcmp(ipaddr, hostaddr) == 0 && strcmp(theuser, user) == 0) goto Repeatuser; } return 0; Repeatuser: niceval += 1; if (findmatch(collaborators, NULL, NULL) != NULL) { logmsg("collaborator: %s %s %lu", theuser, hostaddr, (unsigned long)time(NULL) ); return 0; } repeatuser = TRUE; logmsg("repeat: %s %s %lu", theuser, hostaddr, (unsigned long)time(NULL) ); run_max = MAX(run_max - 2, 1); queue_max = MAX(queue_max*2, 20); netgrp_run_max = 1; netgrp_queue_max = 20; niceval += 2; ck_netgrp_queue = TRUE; ck_master_queue = TRUE; return 0; } /* getatime() -- search log file for the last access time of user@ipaddr */ long getatime(ipaddr, user) CharPtr ipaddr, user; { char buf[1024], *cp; int i, apid; long timestamp; while (fgets(buf, sizeof buf, logfp) != NULL) { cp = strtok(buf, " \n"); if (cp == NULL || *cp == NULLB) continue; if (sscanf(cp, "%d", &apid) != 1) continue; cp = strtok(NULL, " \n"); if (cp == NULL) continue; /* Parse the 'done:' line */ if (*cp == 'd' && strcmp(cp, "done:") == 0) { if (apid == pid) /* found ourselves */ return 0; *user = NULLB; *ipaddr = NULLB; cp = strtok(NULL, " \n"); if (cp == NULL) continue; strcpy(user, cp); cp = strtok(NULL, " \n"); if (cp == NULL) continue; strcpy(ipaddr, cp); cp = strtok(NULL, " \n"); if (cp == NULL) continue; if (sscanf(cp, "%lu", ×tamp) != 1) continue; return timestamp; } } return 0; } #define USER_ID 0x08 char userfname[FILENAME_MAX+1]; int userfd = -1; /* Return non-zero if the user seems to be on the system */ int ckuseronsystem() { char buf[1024]; union semun sm; struct sembuf sops; int len; int semval; int rc, retcode = 0; critical_begin(); settempdir(); sprintf(userfname, "%s/%s@%s", tempdir, theuser, hostaddr); UserKey: user_semid = -1; user_key = ftok(userfname, USER_ID); if (user_key == -1) { userfd = open(userfname, O_WRONLY|O_SYNC|O_CREAT|O_EXCL, 0660); if (userfd == -1) switch (errno) { case EEXIST: goto UserKey; default: retcode = 1; goto Return; } goto UserKey; } if (userfd >= 0) { sprintf(buf, "IP address: 0x%08lx\n", (unsigned long)inet_network(hostaddr)); len = strlen(buf); while (write(userfd, buf, len) == -1 && errno == EINTR) ; sprintf(buf, "This file's key: 0x%08lx\n", (unsigned long)user_key); len = strlen(buf); while (write(userfd, buf, len) == -1 && errno == EINTR) ; close(userfd); userfd = -1; goto UserKey; } user_semid = semget(user_key, 1, 0); if (user_semid == -1) { switch (errno) { case ENOENT: user_semid = semget(user_key, 1, IPC_EXCL|IPC_CREAT|0662); if (user_semid == -1) switch (errno) { case EEXIST: goto UserKey; default: goto Return; } sm.val = 0; if (semctl(user_semid, 0, SETVAL, sm) == -1) switch (errno) { case EINVAL: goto UserKey; default: user_semid = -1; goto Return; } break; case ENOSPC: default: goto Return; } } sops.sem_num = 0; sops.sem_op = 1; sops.sem_flg = SEM_UNDO; while ((rc = semop(user_semid, &sops, 1)) == -1 && errno == EINTR) ; if (rc == -1) switch (errno) { case EIDRM: case EINVAL: goto UserKey; default: break; } semval = semctl(user_semid, 0, GETVAL, sm); retcode = (semval > 1); Return: critical_end(); return retcode; } int ckuseroffsystem() { union semun sm; struct sembuf sops; int semval; int retcode = 0; critical_begin(); if (user_semid < 0) goto Return; semval = semctl(user_semid, 0, GETVAL, sm); if (semval < 2) { /* remove the semaphore and unlink the userfile */ semctl(user_semid, 0, IPC_RMID); user_semid = -1; if (userfname[0] != NULLB) unlink(userfname); } Return: critical_end(); return retcode; } int usage() { printf("\nThis is the blast network service daemon. It is normally invoked by inetd,\n"); printf("as described in /etc/inetd.conf or /usr/etc/inetd.conf.\n\n"); printf("Valid command line options for specification in inetd.conf are:\n"); printf("\t-W dirname\tWorking directory\n"); printf("\t-C dirname\tConfiguration file directory\n"); printf("\t-m umask\tUmask for file creation (octal notation)\n"); printf("\t-n niceval\tNice value for child\n"); printf("\t-R runmax\tMaximum total runnable jobs for all netgroups\n"); printf("\t-r grpmax\tMaximum runnable jobs per netgroup\n"); printf("\t-Q quemax\tMaximum total requests to queue for all netgroups\n"); printf("\t-q grpmax\tMaximum requests to queue per netgroup\n"); printf("\t-d dataseg\tMaximum data segment (in kbytes) for child\n"); printf("\t-s rsseg\tMaximum RSS (in kbytes) for child\n"); printf("\t-c coremax\tMaximum core dump size (in kbytes) for child\n"); printf("\t-f filemax\tMax. amount of output (in kbytes) for child\n"); printf("\t-t timeout\tTimeout (seconds) for each read/write\n"); printf("\t-a alarm\tMaximum wall clock time (minutes) for a connection\n"); exit(1); /*NOTREACHED*/ }