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