32dfd83f92fc34ae1610084dc54164a9254bf53c
[catagits/fcgi2.git] / cgi-fcgi / cgi-fcgi.c
1 /* 
2  * cgifcgi.c --
3  *
4  *      CGI to FastCGI bridge
5  *
6  *
7  * Copyright (c) 1996 Open Market, Inc.
8  *
9  * See the file "LICENSE.TERMS" for information on usage and redistribution
10  * of this file, and for a DISCLAIMER OF ALL WARRANTIES.
11  *
12  */
13
14 #ifndef lint
15 static const char rcsid[] = "$Id: cgi-fcgi.c,v 1.1 1997/09/16 15:36:25 stanleyg Exp $";
16 #endif /* not lint */
17
18 #include <stdio.h>
19 #ifdef HAVE_UNISTD_H
20 #include <unistd.h>
21 #endif
22 #include <fcntl.h>
23 #include <assert.h>
24 #include <stdlib.h>
25 #include <string.h>
26 #include <errno.h>
27 #include <ctype.h>
28 #ifdef HAVE_NETDB_H
29 #include <netdb.h>
30 #endif
31 #ifdef HAVE_SYS_TIME_H
32 #include <sys/time.h>
33 #endif
34 #ifdef HAVE_SYS_PARAM_H
35 #include <sys/param.h>
36 #endif
37 #include "fcgimisc.h"
38 #include "fcgiapp.h"
39 #include "fcgiappmisc.h"
40 #include "fastcgi.h"
41 #include "fcgi_config.h"
42 #include "fcgios.h"
43
44 static int wsReadPending = 0;
45 static int wsWritePending = 0;
46 static int fcgiReadPending = 0;
47 static int fcgiWritePending = 0;
48
49 static void ScheduleIo(void);
50
51 \f
52 /*
53  * Simple buffer (not ring buffer) type, used by all event handlers.
54  */
55 #define BUFFLEN 8192
56 typedef struct {
57     char *next;
58     char *stop;
59     char buff[BUFFLEN];
60 } Buffer;
61
62 /*
63  *----------------------------------------------------------------------
64  *
65  * GetPtr --
66  *
67  *      Returns a count of the number of characters available
68  *      in the buffer (at most n) and advances past these
69  *      characters.  Stores a pointer to the first of these
70  *      characters in *ptr.
71  *
72  *----------------------------------------------------------------------
73  */
74
75 static int GetPtr(char **ptr, int n, Buffer *pBuf)
76 {
77     int result;
78     *ptr = pBuf->next;
79     result = min(n, pBuf->stop - pBuf->next);
80     pBuf->next += result;
81     return result;
82 }
83 \f
84 /*
85  *----------------------------------------------------------------------
86  *
87  * MakeHeader --
88  *
89  *      Constructs an FCGI_Header struct.
90  *
91  *----------------------------------------------------------------------
92  */
93 static FCGI_Header MakeHeader(
94         int type,
95         int requestId,
96         int contentLength,
97         int paddingLength)
98 {
99     FCGI_Header header;
100     ASSERT(contentLength >= 0 && contentLength <= FCGI_MAX_LENGTH);
101     ASSERT(paddingLength >= 0 && paddingLength <= 0xff);
102     header.version = FCGI_VERSION_1;
103     header.type             =  type;
104     header.requestIdB1      = (requestId      >> 8) & 0xff;
105     header.requestIdB0      = (requestId          ) & 0xff;
106     header.contentLengthB1  = (contentLength  >> 8) & 0xff;
107     header.contentLengthB0  = (contentLength      ) & 0xff;
108     header.paddingLength    =  paddingLength;
109     header.reserved         =  0;
110     return header;
111 }
112 \f
113 /*
114  *----------------------------------------------------------------------
115  *
116  * MakeBeginRequestBody --
117  *
118  *      Constructs an FCGI_BeginRequestBody record.
119  *
120  *----------------------------------------------------------------------
121  */
122 static FCGI_BeginRequestBody MakeBeginRequestBody(
123         int role,
124         int keepConnection)
125 {
126     FCGI_BeginRequestBody body;
127     ASSERT((role >> 16) == 0);
128     body.roleB1 = (role >>  8) & 0xff;
129     body.roleB0 = (role      ) & 0xff;
130     body.flags = (keepConnection) ? FCGI_KEEP_CONN : 0;
131     memset(body.reserved, 0, sizeof(body.reserved));
132     return body;
133 }
134
135 \f
136 static int bytesToRead;    /* number of bytes to read from Web Server */
137 static int appServerSock = -1;  /* Socket connected to FastCGI application,
138                                  * used by AppServerReadHandler and
139                                  * AppServerWriteHandler. */
140 static Buffer fromAS;      /* Bytes read from the FCGI application server. */
141 static FCGI_Header header; /* Header of the current record.  Is global
142                             * since read may return a partial header. */
143 static int headerLen = 0;  /* Number of valid bytes contained in header.
144                             * If headerLen < sizeof(header),
145                             * AppServerReadHandler is reading a record header;
146                             * otherwise it is reading bytes of record content
147                             * or padding. */
148 static int contentLen;     /* If headerLen == sizeof(header), contentLen
149                             * is the number of content bytes still to be
150                             * read. */
151 static int paddingLen;     /* If headerLen == sizeof(header), paddingLen
152                             * is the number of padding bytes still
153                             * to be read. */
154 static int requestId;      /* RequestId of the current request.
155                             * Set by main. */
156 static FCGI_EndRequestBody erBody;
157 static int readingEndRequestBody = FALSE;
158                            /* If readingEndRequestBody, erBody contains
159                             * partial content: contentLen more bytes need
160                             * to be read. */
161 static int exitStatus = 0;
162 static int exitStatusSet = FALSE;
163
164 static int stdinFds[3];
165
166 \f
167 /*
168  *----------------------------------------------------------------------
169  *
170  * FCGIexit --
171  *
172  *      FCGIexit provides a single point of exit.  It's main use is for
173  *      application debug when porting to other operating systems.
174  *
175  *----------------------------------------------------------------------
176  */
177 static void FCGIexit(int exitCode)
178 {
179     if(appServerSock != -1) {
180         OS_Close(appServerSock);
181         appServerSock = -1;
182     }
183     OS_LibShutdown();
184     exit(exitCode);
185 }
186
187 #undef exit
188 #define exit FCGIexit
189
190 \f
191 /*
192  *----------------------------------------------------------------------
193  *
194  * AppServerReadHandler --
195  *
196  *      Reads data from the FCGI application server and (blocking)
197  *      writes all of it to the Web server.  Exits the program upon
198  *      reading EOF from the FCGI application server.  Called only when
199  *      there's data ready to read from the application server.
200  *
201  *----------------------------------------------------------------------
202  */
203
204 static void AppServerReadHandler(ClientData clientData, int bytesRead)
205 {
206     int count, outFD;
207     char *ptr;
208
209     assert(fcgiReadPending == TRUE);
210     fcgiReadPending = FALSE;
211     count = bytesRead;
212
213     if(count <= 0) {
214         if(count < 0) {
215             exit(OS_Errno);
216         }
217         if(headerLen > 0 || paddingLen > 0) {
218             exit(FCGX_PROTOCOL_ERROR);
219         }
220         if(appServerSock != -1) {
221             OS_Close(appServerSock);
222             appServerSock = -1;
223         }
224         /*
225          * XXX: Shouldn't be here if exitStatusSet.
226          */
227         exit((exitStatusSet) ? exitStatus : FCGX_PROTOCOL_ERROR);
228     }
229     fromAS.stop = fromAS.next + count;
230     while(fromAS.next != fromAS.stop) {
231         /*
232          * fromAS is not empty.  What to do with the contents?
233          */
234         if(headerLen < sizeof(header)) {
235             /*
236              * First priority is to complete the header.
237              */
238             count = GetPtr(&ptr, sizeof(header) - headerLen, &fromAS);
239             assert(count > 0);
240             memcpy(&header + headerLen, ptr, count);
241             headerLen += count;
242             if(headerLen < sizeof(header)) {
243                 break;
244             }
245             if(header.version != FCGI_VERSION_1) {
246                 exit(FCGX_UNSUPPORTED_VERSION);
247             }
248             if((header.requestIdB1 << 8) + header.requestIdB0 != requestId) {
249                 exit(FCGX_PROTOCOL_ERROR);
250             }
251             contentLen = (header.contentLengthB1 << 8)
252                          + header.contentLengthB0;
253             paddingLen =  header.paddingLength;
254         } else {
255             /*
256              * Header is complete (possibly from previous call).  What now?
257              */
258             switch(header.type) {
259                 case FCGI_STDOUT:
260                 case FCGI_STDERR:
261                     /*
262                      * Write the buffered content to stdout or stderr.
263                      * Blocking writes are OK here; can't prevent a slow
264                      * client from tying up the app server without buffering
265                      * output in temporary files.
266                      */
267                     count = GetPtr(&ptr, contentLen, &fromAS);
268                     contentLen -= count;
269                     if(count > 0) {
270                         outFD = (header.type == FCGI_STDOUT) ?
271                                     STDOUT_FILENO : STDERR_FILENO;
272                         if(OS_Write(outFD, ptr, count) < 0) {
273                             exit(OS_Errno);
274                         }
275                     }
276                     break;
277                 case FCGI_END_REQUEST:
278                     if(!readingEndRequestBody) {
279                         if(contentLen != sizeof(erBody)) {
280                             exit(FCGX_PROTOCOL_ERROR);
281                         }
282                         readingEndRequestBody = TRUE;
283                     }
284                     count = GetPtr(&ptr, contentLen, &fromAS);
285                     if(count > 0) {
286                         memcpy(&erBody + sizeof(erBody) - contentLen,
287                                 ptr, count);
288                         contentLen -= count;
289                     }
290                     if(contentLen == 0) {
291                         if(erBody.protocolStatus != FCGI_REQUEST_COMPLETE) {
292                             /*
293                              * XXX: What to do with FCGI_OVERLOADED?
294                              */
295                             exit(FCGX_PROTOCOL_ERROR);
296                         }
297                         exitStatus = (erBody.appStatusB3 << 24)
298                                    + (erBody.appStatusB2 << 16)
299                                    + (erBody.appStatusB1 <<  8)
300                                    + (erBody.appStatusB0      );
301                         exitStatusSet = TRUE;
302                         readingEndRequestBody = FALSE;
303                     }
304                     break;
305                 case FCGI_GET_VALUES_RESULT:
306                     /* coming soon */
307                 case FCGI_UNKNOWN_TYPE:
308                     /* coming soon */
309                 default:
310                     exit(FCGX_PROTOCOL_ERROR);
311             }
312             if(contentLen == 0) {
313                 if(paddingLen > 0) {
314                     paddingLen -= GetPtr(&ptr, paddingLen, &fromAS);
315                 }
316                 /*
317                  * If we've processed all the data and skipped all the
318                  * padding, discard the header and look for the next one.
319                  */
320                 if(paddingLen == 0) {
321                     headerLen = 0;
322                 }
323             }
324         } /* headerLen >= sizeof(header) */
325     } /*while*/
326     ScheduleIo();
327 }
328 \f
329 static Buffer fromWS;   /* Buffer for data read from Web server
330                          * and written to FastCGI application. Used
331                          * by WebServerReadHandler and
332                          * AppServerWriteHandler. */
333 static int webServerReadHandlerEOF;
334                         /* TRUE iff WebServerReadHandler has read EOF from
335                          * the Web server. Used in main to prevent
336                          * rescheduling WebServerReadHandler. */
337
338 /*
339  *----------------------------------------------------------------------
340  *
341  * WebServerReadHandler --
342  *
343  *      Non-blocking reads data from the Web server into the fromWS
344  *      buffer.  Called only when fromWS is empty, no EOF has been
345  *      received from the Web server, and there's data available to read.
346  *
347  *----------------------------------------------------------------------
348  */
349
350 static void WebServerReadHandler(ClientData clientData, int bytesRead)
351 {
352     assert(fromWS.next == fromWS.stop);
353     assert(fromWS.next == &fromWS.buff[0]);
354     assert(wsReadPending == TRUE);
355     wsReadPending = FALSE;
356
357     if(bytesRead < 0) {
358         exit(OS_Errno);
359     }
360     *((FCGI_Header *) &fromWS.buff[0])
361             = MakeHeader(FCGI_STDIN, requestId, bytesRead, 0);
362     bytesToRead -= bytesRead;
363     fromWS.stop = &fromWS.buff[sizeof(FCGI_Header) + bytesRead];
364     webServerReadHandlerEOF = (bytesRead == 0);
365     ScheduleIo();
366 }
367 \f
368 /*
369  *----------------------------------------------------------------------
370  *
371  * AppServerWriteHandler --
372  *
373  *      Non-blocking writes data from the fromWS buffer to the FCGI
374  *      application server.  Called only when fromWS is non-empty
375  *      and the socket is ready to accept some data.
376  *
377  *----------------------------------------------------------------------
378  */
379
380 static void AppServerWriteHandler(ClientData clientData, int bytesWritten)
381 {
382     int length = fromWS.stop - fromWS.next;
383
384     assert(length > 0);
385     assert(fcgiWritePending == TRUE);
386
387     fcgiWritePending = FALSE;
388     if(bytesWritten < 0) {
389         exit(OS_Errno);
390     }
391     if((int)bytesWritten < length) {
392         fromWS.next += bytesWritten;
393     } else {
394         fromWS.stop = fromWS.next = &fromWS.buff[0];
395     }
396
397     ScheduleIo();
398 }      
399
400 \f
401 /*
402  * ScheduleIo --
403  *
404  *      This functions is responsible for scheduling all I/O to move
405  *      data between a web server and a FastCGI application.
406  *
407  * Results:
408  *      None.
409  *
410  * Side effects:
411  *      This routine will signal the ioEvent upon completion.
412  * 
413  */
414 static void ScheduleIo(void)
415 {
416     int length;
417
418     /*
419      * Move data between standard in and the FastCGI connection.
420      */
421     if(!fcgiWritePending && appServerSock != -1 &&
422        ((length = fromWS.stop - fromWS.next) != 0)) {
423         if(OS_AsyncWrite(appServerSock, 0, fromWS.next, length,
424                          AppServerWriteHandler,
425                          (ClientData)appServerSock) == -1) {
426             FCGIexit(OS_Errno);
427         } else {
428             fcgiWritePending = TRUE;
429         }
430     }
431
432     /*
433      * Schedule a read from the FastCGI application if there's not
434      * one pending and there's room in the buffer.
435      */
436     if(!fcgiReadPending && appServerSock != -1) {
437         fromAS.next = &fromAS.buff[0];
438
439         if(OS_AsyncRead(appServerSock, 0, fromAS.next, BUFFLEN, 
440                         AppServerReadHandler,
441                         (ClientData)appServerSock) == -1) {
442             FCGIexit(OS_Errno);
443         } else {
444             fcgiReadPending = TRUE;
445         }
446     }
447
448     /*
449      * Schedule a read from standard in if necessary.
450      */
451     if((bytesToRead > 0) && !webServerReadHandlerEOF && !wsReadPending &&
452        !fcgiWritePending &&
453        fromWS.next == &fromWS.buff[0]) {
454         if(OS_AsyncReadStdin(fromWS.next + sizeof(FCGI_Header),
455                              BUFFLEN - sizeof(FCGI_Header), 
456                              WebServerReadHandler, STDIN_FILENO)== -1) {
457             FCGIexit(OS_Errno);
458         } else {
459             wsReadPending = TRUE;
460         }
461     }
462 }
463
464 \f
465 /*
466  *----------------------------------------------------------------------
467  *
468  * FCGI_Start --
469  *
470  *      Starts nServers copies of FCGI application appPath, all
471  *      listening to a Unix Domain socket at bindPath.
472  *
473  *----------------------------------------------------------------------
474  */
475
476 static void FCGI_Start(char *bindPath, char *appPath, int nServers)
477 {
478     int listenFd, i;
479     int     tcp = FALSE;
480
481     if((listenFd = OS_CreateLocalIpcFd(bindPath)) == -1) {
482         exit(OS_Errno);
483     }
484     
485     if(access(appPath, X_OK) == -1) {
486         fprintf(stderr, "%s is not executable\n", appPath);
487         exit(1);
488     }
489
490     /*
491      * Create the server processes
492      */
493     for(i = 0; i < nServers; i++) {
494         if(OS_SpawnChild(appPath, listenFd) == -1) {
495             exit(OS_Errno);
496         }
497     }
498     OS_Close(listenFd);
499 }
500 \f
501 /*
502  *----------------------------------------------------------------------
503  *
504  * FCGIUtil_BuildNameValueHeader --
505  *
506  *      Builds a name-value pair header from the name length
507  *      and the value length.  Stores the header into *headerBuffPtr,
508  *      and stores the length of the header into *headerLenPtr.
509  *
510  * Side effects:
511  *      Stores header's length (at most 8) into *headerLenPtr,
512  *      and stores the header itself into
513  *      headerBuffPtr[0 .. *headerLenPtr - 1].
514  *
515  *----------------------------------------------------------------------
516  */
517 static buildNameValueHeaderCalls = 0; /* XXX: for testing */
518
519 static void FCGIUtil_BuildNameValueHeader(
520         int nameLen,
521         int valueLen,
522         unsigned char *headerBuffPtr,
523         int *headerLenPtr) {
524     unsigned char *startHeaderBuffPtr = headerBuffPtr;
525
526     ASSERT(nameLen >= 0);
527     if(nameLen < 0x80 && (buildNameValueHeaderCalls & 1) == 0) {
528         *headerBuffPtr++ = nameLen;
529     } else {
530         *headerBuffPtr++ = (nameLen >> 24) | 0x80;
531         *headerBuffPtr++ = (nameLen >> 16);
532         *headerBuffPtr++ = (nameLen >> 8);
533         *headerBuffPtr++ = nameLen;
534     }
535     ASSERT(valueLen >= 0);
536     if(valueLen < 0x80 && (buildNameValueHeaderCalls & 2) == 0) {
537         *headerBuffPtr++ = valueLen;
538     } else {
539         *headerBuffPtr++ = (valueLen >> 24) | 0x80;
540         *headerBuffPtr++ = (valueLen >> 16);
541         *headerBuffPtr++ = (valueLen >> 8);
542         *headerBuffPtr++ = valueLen;
543     }
544     *headerLenPtr = headerBuffPtr - startHeaderBuffPtr;
545     buildNameValueHeaderCalls++;
546 }
547 \f
548
549 #define MAXARGS 16
550 static int ParseArgs(int argc, char *argv[],
551         int *doBindPtr, int *doStartPtr,
552         char *connectPathPtr, char *appPathPtr, int *nServersPtr) {
553     int     i,
554             x,
555             err = 0,
556             ac;
557     char    *tp1,
558             *tp2,
559             *av[MAXARGS];
560     FILE    *fp;
561     char    line[BUFSIZ];
562
563     *doBindPtr = TRUE;
564     *doStartPtr = TRUE;
565     *connectPathPtr = '\0';
566     *appPathPtr = '\0';
567     *nServersPtr = 0;
568
569     for(i = 0; i < MAXARGS; i++)
570         av[i] = NULL;
571     for(i = 1; i < argc; i++) {
572         if(argv[i][0] == '-') {
573             if(!strcmp(argv[i], "-f")) {
574                 if(++i == argc) {
575                     fprintf(stderr,
576                             "Missing command file name after -f\n");
577                     return 1;
578                 }
579                 if((fp = fopen(argv[i], "r")) == NULL) {
580                     fprintf(stderr, "Cannot open command file %s\n", argv[i]);
581                     return 1;
582                 }
583                 ac = 1;
584                 while(fgets(line, BUFSIZ, fp)) {
585                     if(line[0] == '#') {
586                         continue;
587                     }
588                     if((tp1 = (char *) strrchr(line,'\n')) != NULL) {
589                         *tp1-- = 0;
590                         while(*tp1 == ' ' || *tp1 =='\t') {
591                             *tp1-- = 0;
592                         }
593                     } else {
594                         fprintf(stderr, "Line to long\n");
595                         return 1;
596                     }
597                     tp1 = line;
598                     while(tp1) {
599                         if((tp2 = strchr(tp1, ' ')) != NULL) {
600                             *tp2++ =  0;
601                         }
602                         if(ac >= MAXARGS) {
603                             fprintf(stderr,
604                                     "To many arguments, "
605                                     "%d is max from a file\n", MAXARGS);
606                                 exit(-1);
607                         }
608                         if((av[ac] = malloc(strlen(tp1)+1)) == NULL) {
609                             fprintf(stderr, "Cannot allocate %d bytes\n",
610                                     strlen(tp1)+1);
611                             exit(-1);
612                         }
613                         strcpy(av[ac++], tp1);
614                         tp1 = tp2;
615                     }
616                 }
617                 err = ParseArgs(ac, av, doBindPtr, doStartPtr,
618                         connectPathPtr, appPathPtr, nServersPtr);
619                 for(x = 1; x < ac; x++) {
620                     ASSERT(av[x] != NULL);
621                     free(av[x]);
622                 }
623                 return err;
624 #ifdef _WIN32
625             } else if (!strcmp(argv[i], "-jitcgi")) {
626                 DebugBreak();
627             } else if (!strcmp(argv[i], "-dbgfcgi")) {
628                 putenv("DEBUG_FCGI=TRUE");
629 #endif
630             } else if(!strcmp(argv[i], "-start")) {
631                 *doBindPtr = FALSE;
632             } else if(!strcmp(argv[i], "-bind")) {
633                 *doStartPtr = FALSE;
634             } else if(!strcmp(argv[i], "-connect")) {
635                 if(++i == argc) {
636                     fprintf(stderr,
637                             "Missing connection name after -connect\n");
638                     err++;
639                 } else {
640                     strcpy(connectPathPtr, argv[i]);
641                 }
642             } else {
643                 fprintf(stderr, "Unknown option %s\n", argv[i]);
644                 err++;
645             }
646         } else if(*appPathPtr == '\0') {
647             strcpy(appPathPtr, argv[i]);
648         } else if(isdigit(argv[i][0]) && *nServersPtr == 0) {
649             *nServersPtr = atoi(argv[i]);
650             if(*nServersPtr <= 0) {
651                 fprintf(stderr, "Number of servers must be greater than 0\n");
652                 err++;
653             }
654         } else {
655             fprintf(stderr, "Unknown argument %s\n", argv[i]);
656             err++;
657         }
658     }
659     if(*doStartPtr && *appPathPtr == 0) {
660         fprintf(stderr, "Missing application pathname\n");
661         err++;
662     }
663     if(*connectPathPtr == 0) {
664         fprintf(stderr, "Missing -connect <connName>\n");
665         err++;
666     } else if(strchr(connectPathPtr, ':')) {
667 /*
668  * XXX: Test to see if we can use IP connect locally...
669         This hack lets me test the ability to create a local process listening
670         to a TCP/IP port for connections and subsequently connect to the app
671         like we do for Unix domain and named pipes.
672         
673         if(*doStartPtr && *doBindPtr) {
674             fprintf(stderr,
675                     "<connName> of form hostName:portNumber "
676                     "requires -start or -bind\n");
677             err++;
678         }
679  */
680     }
681     if(*nServersPtr == 0) {
682         *nServersPtr = 1;
683     }
684     return err;
685 }
686 \f
687 void main(int argc, char **argv, char **envp)
688 {
689     int count;
690     FCGX_Stream *paramsStream;
691     int numFDs;
692     unsigned char headerBuff[8];
693     int headerLen, valueLen;
694     char *equalPtr;
695     FCGI_BeginRequestRecord beginRecord;
696     int doBind, doStart, nServers;
697     char appPath[MAXPATHLEN], bindPath[MAXPATHLEN];
698
699     if(ParseArgs(argc, argv, &doBind, &doStart,
700                    (char *) &bindPath, (char *) &appPath, &nServers)) {
701         fprintf(stderr,
702 "Usage:\n"
703 "    cgi-fcgi -f <cmdPath> , or\n"
704 "    cgi-fcgi -connect <connName> <appPath> [<nServers>] , or\n"
705 "    cgi-fcgi -start -connect <connName> <appPath> [<nServers>] , or\n"
706 "    cgi-fcgi -bind -connect <connName> ,\n"
707 "where <connName> is either the pathname of a UNIX domain socket\n"
708 "or (if -bind is given) a hostName:portNumber specification\n"
709 "or (if -start is given) a :portNumber specification (uses local host).\n");
710         exit(1);
711     }
712
713     if(OS_LibInit(stdinFds)) {
714         fprintf(stderr, "Error initializing OS library: %d\n", OS_Errno);
715         exit(0);
716     }
717
718     equalPtr = getenv("CONTENT_LENGTH");
719     if(equalPtr != NULL) {
720         bytesToRead = atoi(equalPtr);
721     } else {
722         bytesToRead = 0;
723     }
724     
725     if(doBind) {
726         appServerSock = OS_FcgiConnect(bindPath);
727     }
728     if(doStart && (!doBind || appServerSock < 0)) {
729         FCGI_Start(bindPath, appPath, nServers);
730         if(!doBind) {
731             exit(0);
732         } else {
733             appServerSock = OS_FcgiConnect(bindPath);
734         }
735     }
736     if(appServerSock < 0) {
737         fprintf(stderr, "Could not connect to %s\n", bindPath);
738         exit(OS_Errno);
739     }
740     /*
741      * Set an arbitrary non-null FCGI RequestId
742      */
743     requestId = 1;
744     /*
745      * XXX: Send FCGI_GET_VALUES
746      */
747
748     /*
749      * XXX: Receive FCGI_GET_VALUES_RESULT
750      */
751
752     /*
753      * Send FCGI_BEGIN_REQUEST (XXX: hack, separate write)
754      */
755     beginRecord.header = MakeHeader(FCGI_BEGIN_REQUEST, requestId,
756             sizeof(beginRecord.body), 0);
757     beginRecord.body = MakeBeginRequestBody(FCGI_RESPONDER, FALSE);
758     count = OS_Write(appServerSock, (char *)&beginRecord, sizeof(beginRecord));
759     if(count != sizeof(beginRecord)) {
760         exit(OS_Errno);
761     }
762     /*
763      * Send environment to the FCGI application server
764      */
765     paramsStream = CreateWriter(appServerSock, requestId, 8192, FCGI_PARAMS);
766     for( ; *envp != NULL; envp++) {
767         equalPtr = strchr(*envp, '=');
768         if(equalPtr  == NULL) {
769             exit(1000);
770         }
771         valueLen = strlen(equalPtr + 1);
772         FCGIUtil_BuildNameValueHeader(
773                 equalPtr - *envp,
774                 valueLen,
775                 &headerBuff[0],
776                 &headerLen);
777         if(FCGX_PutStr((char *) &headerBuff[0], headerLen, paramsStream) < 0
778                 || FCGX_PutStr(*envp, equalPtr - *envp, paramsStream) < 0
779                 || FCGX_PutStr(equalPtr + 1, valueLen, paramsStream) < 0) {
780             exit(FCGX_GetError(paramsStream));
781         }
782     }
783     FCGX_FClose(paramsStream);
784     FreeStream(&paramsStream);
785     /*
786      * Perform the event loop until AppServerReadHander sees FCGI_END_REQUEST
787      */
788     fromWS.stop = fromWS.next = &fromWS.buff[0];
789     webServerReadHandlerEOF = FALSE;
790     /*
791      * XXX: might want to use numFDs in the os library.
792      */
793     numFDs = max(appServerSock, STDIN_FILENO) + 1;
794     OS_SetFlags(appServerSock, O_NONBLOCK);
795
796     ScheduleIo();
797     while(!exitStatusSet) {
798         /*
799          * NULL = wait forever (or at least until there's something
800          *        to do.
801          */
802         OS_DoIo(NULL);
803     }
804     if(exitStatusSet) {
805         FCGIexit(exitStatus);
806     } else {
807         FCGIexit(999);
808     }
809 }