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