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