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