diff --git a/sapi/cgi/cgi_main.c b/sapi/cgi/cgi_main.c index f649421bbb7..b4991517a2c 100644 --- a/sapi/cgi/cgi_main.c +++ b/sapi/cgi/cgi_main.c @@ -76,6 +76,41 @@ #include "php_getopt.h" +#ifdef PHP_FASTCGI +#include "fcgi_config.h" +#include "fcgiapp.h" +FCGX_Stream *in, *out, *err; +FCGX_ParamArray envp; + +/* Our original environment from when the FastCGI first started */ +char **orig_env; + +/* The environment given by the FastCGI */ +char **cgi_env; + +/* The manufactured environment, from merging the base environ with + * the parameters set by the per-connection environment + */ +char **merge_env; + +/** + * Number of child processes that will get created to service requests + */ +static int children = 8; + +/** + * Set to non-zero if we are the parent process + */ +static int parent = 1; + +/** + * Process group + */ +static pid_t pgroup; + + +#endif + #define PHP_MODE_STANDARD 1 #define PHP_MODE_HIGHLIGHT 2 #define PHP_MODE_INDENT 3 @@ -107,13 +142,20 @@ static inline size_t sapi_cgibin_single_write(const char *str, uint str_length) { #ifdef PHP_WRITE_STDOUT long ret; +#else + size_t ret; +#endif +#ifdef PHP_FASTCGI + if (!FCGX_IsCGI()) { + return FCGX_PutStr( str, str_length, out ); + } +#endif +#ifdef PHP_WRITE_STDOUT ret = write(STDOUT_FILENO, str, str_length); if (ret <= 0) return 0; return ret; #else - size_t ret; - ret = fwrite(str, 1, MIN(str_length, 16384), stdout); return ret; #endif @@ -141,6 +183,13 @@ static int sapi_cgibin_ub_write(const char *str, uint str_length TSRMLS_DC) static void sapi_cgibin_flush(void *server_context) { +#ifdef PHP_FASTCGI + if (!FCGX_IsCGI()) { + if( FCGX_FFlush( out ) == -1 ) { + php_handle_aborted_connection(); + } + } else +#endif if (fflush(stdout)==EOF) { php_handle_aborted_connection(); } @@ -159,10 +208,20 @@ static void sapi_cgi_send_header(sapi_header_struct *sapi_header, void *server_c static int sapi_cgi_read_post(char *buffer, uint count_bytes TSRMLS_DC) { uint read_bytes=0, tmp_read_bytes; +#ifdef PHP_FASTCGI + char *pos = buffer; +#endif count_bytes = MIN(count_bytes, (uint)SG(request_info).content_length-SG(read_post_bytes)); while (read_bytes < count_bytes) { - tmp_read_bytes = read(0, buffer+read_bytes, count_bytes-read_bytes); +#ifdef PHP_FASTCGI + if (!FCGX_IsCGI()) { + tmp_read_bytes = FCGX_GetStr( pos, count_bytes-read_bytes, in ); + pos += tmp_read_bytes; + } else +#endif + tmp_read_bytes = read(0, buffer+read_bytes, count_bytes-read_bytes); + if (tmp_read_bytes<=0) { break; } @@ -213,8 +272,12 @@ static int sapi_cgi_deactivate(TSRMLS_D) */ static sapi_module_struct cgi_sapi_module = { "cgi", /* name */ +#ifdef PHP_FASTCGI + "CGI/FastCGI", /* pretty name */ +#else "CGI", /* pretty name */ - +#endif + php_module_startup, /* startup */ php_module_shutdown_wrapper, /* shutdown */ @@ -405,6 +468,28 @@ int main(int argc, char *argv[]) void ***tsrm_ls; #endif +#ifdef PHP_FASTCGI + int env_size, cgi_env_size; + int max_requests = 500; + int requests = 0; + int fastcgi = !FCGX_IsCGI(); + + if (fastcgi) { + /* Calculate environment size */ + env_size = 0; + while( environ[ env_size ] ) { env_size++; } + /* Also include the final NULL pointer */ + env_size++; + + /* Allocate for our environment */ + orig_env = malloc( env_size * sizeof( char *)); + if( !orig_env ) { + perror( "Can't malloc environment" ); + exit( 1 ); + } + memcpy( orig_env, environ, env_size * sizeof( char *)); + } +#endif #ifdef HAVE_SIGNAL_H #if defined(SIGPIPE) && defined(SIG_IGN) @@ -446,7 +531,11 @@ int main(int argc, char *argv[]) } } - if (!cgi) { + if (!cgi +#ifdef PHP_FASTCGI + /* allow ini override for fastcgi */ +#endif + ) { while ((c=ap_php_getopt(argc, argv, OPTSTRING))!=-1) { switch (c) { case 'c': @@ -523,9 +612,102 @@ If you are running IIS, you may safely set cgi.force_redirect=0 in php.ini.\n\ } #endif /* FORCE_CGI_REDIRECT */ +#ifdef PHP_FASTCGI + /* How many times to run PHP scripts before dying */ + if( getenv( "PHP_FCGI_MAX_REQUESTS" )) { + max_requests = atoi( getenv( "PHP_FCGI_MAX_REQUESTS" )); + if( !max_requests ) { + fprintf( stderr, + "PHP_FCGI_MAX_REQUESTS is not valid\n" ); + exit( 1 ); + } + } + +#ifndef PHP_WIN32 + /* Pre-fork, if required */ + if( getenv( "PHP_FCGI_CHILDREN" )) { + children = atoi( getenv( "PHP_FCGI_CHILDREN" )); + if( !children ) { + fprintf( stderr, + "PHP_FCGI_CHILDREN is not valid\n" ); + exit( 1 ); + } + } + + if( children ) { + int running = 0; + int i; + pid_t pid; + + /* Create a process group for ourself & children */ + setsid(); + pgroup = getpgrp(); +#ifdef DEBUG_FASTCGI + fprintf( stderr, "Process group %d\n", pgroup ); +#endif + + /* Set up handler to kill children upon exit */ + act.sa_flags = 0; + act.sa_handler = fastcgi_cleanup; + if( sigaction( SIGTERM, &act, &old_term ) || + sigaction( SIGINT, &act, &old_int ) || + sigaction( SIGQUIT, &act, &old_quit )) { + perror( "Can't set signals" ); + exit( 1 ); + } + + while( parent ) { + do { +#ifdef DEBUG_FASTCGI + fprintf( stderr, "Forking, %d running\n", + running ); +#endif + pid = fork(); + switch( pid ) { + case 0: + /* One of the children. + * Make sure we don't go round the + * fork loop any more + */ + parent = 0; + + /* don't catch our signals */ + sigaction( SIGTERM, &old_term, 0 ); + sigaction( SIGQUIT, &old_quit, 0 ); + sigaction( SIGINT, &old_int, 0 ); + break; + case -1: + perror( "php (pre-forking)" ); + exit( 1 ); + break; + default: + /* Fine */ + running++; + break; + } + } while( parent && ( running < children )); + + if( parent ) { +#ifdef DEBUG_FASTCGI + fprintf( stderr, "Wait for kids, pid %d\n", + getpid() ); +#endif + wait( &status ); + running--; + } + } + } + +#endif /* WIN32 */ + +#endif zend_first_try { - if (!cgi) { + if (!cgi +#ifdef PHP_FASTCGI + && !fastcgi +#endif + ) { while ((c=ap_php_getopt(argc, argv, OPTSTRING))!=-1) { switch (c) { case '?': @@ -543,6 +725,27 @@ If you are running IIS, you may safely set cgi.force_redirect=0 in php.ini.\n\ ap_php_optarg = orig_optarg; } +#ifdef PHP_FASTCGI + /* start of FAST CGI loop */ + while (!fastcgi + || FCGX_Accept( &in, &out, &err, &cgi_env ) >= 0) { + + if (fastcgi) { + /* set up environment */ + cgi_env_size = 0; + while( cgi_env[ cgi_env_size ] ) { cgi_env_size++; } + merge_env = malloc( (env_size+cgi_env_size)*sizeof(char*) ); + if( !merge_env ) { + perror( "Can't malloc environment" ); + exit( 1 ); + } + memcpy( merge_env, orig_env, (env_size-1)*sizeof(char *) ); + memcpy( merge_env + env_size - 1, + cgi_env, (cgi_env_size+1)*sizeof(char *) ); + environ = merge_env; + } +#endif + init_request_info(TSRMLS_C); SG(server_context) = (void *) 1; /* avoid server_context==NULL checks */ @@ -550,7 +753,11 @@ If you are running IIS, you may safely set cgi.force_redirect=0 in php.ini.\n\ zend_llist_init(&global_vars, sizeof(char *), NULL, 0); - if (!cgi) { /* never execute the arguments if you are a CGI */ + if (!cgi +#ifdef PHP_FASTCGI + && !fastcgi +#endif + ) { /* never execute the arguments if you are a CGI */ if (SG(request_info).argv0) { free(SG(request_info).argv0); SG(request_info).argv0 = NULL; @@ -676,7 +883,11 @@ If you are running IIS, you may safely set cgi.force_redirect=0 in php.ini.\n\ CG(interactive) = interactive; - if (!cgi) { + if (!cgi +#ifdef PHP_FASTCGI + && !fastcgi +#endif + ) { if (!SG(request_info).query_string) { len = 0; if (script_file) { @@ -716,9 +927,18 @@ If you are running IIS, you may safely set cgi.force_redirect=0 in php.ini.\n\ SG(headers_sent) = 1; SG(request_info).no_headers = 1; } - file_handle.filename = "-"; - file_handle.type = ZEND_HANDLE_FP; - file_handle.handle.fp = stdin; +#ifdef PHP_FASTCGI + if (fastcgi) { + file_handle.type = ZEND_HANDLE_FILENAME; + file_handle.filename = SG(request_info).path_translated; + } else { +#endif + file_handle.filename = "-"; + file_handle.type = ZEND_HANDLE_FP; + file_handle.handle.fp = stdin; +#ifdef PHP_FASTCGI + } +#endif file_handle.opened_path = NULL; file_handle.free_filename = 0; @@ -726,7 +946,11 @@ If you are running IIS, you may safely set cgi.force_redirect=0 in php.ini.\n\ zend_llist_apply(&global_vars, (llist_apply_func_t) php_register_command_line_global_vars TSRMLS_CC); zend_llist_destroy(&global_vars); - if (!cgi) { + if (!cgi +#ifdef PHP_FASTCGI + && !fastcgi +#endif + ) { if (!SG(request_info).path_translated && argc > ap_php_optind) { SG(request_info).path_translated = estrdup(argv[ap_php_optind]); } @@ -835,10 +1059,28 @@ If you are running IIS, you may safely set cgi.force_redirect=0 in php.ini.\n\ STR_FREE(SG(request_info).path_translated); +#ifdef PHP_FASTCGI + if (!fastcgi) break; + /* only fastcgi will get here */ + + /* TODO: We should free our environment here, but + * some platforms are unhappy if they've altered our + * existing environment and we then free() the new + * environ pointer + */ + + requests++; + if( max_requests && ( requests == max_requests )) { + FCGX_Finish(); + break; + } + /* end of fastcgi loop */ + } +#endif + if (cgi_sapi_module.php_ini_path_override) { free(cgi_sapi_module.php_ini_path_override); } - } zend_catch { exit_status = 255; } zend_end_try(); diff --git a/sapi/cgi/libfcgi/LICENSE.TERMS b/sapi/cgi/libfcgi/LICENSE.TERMS new file mode 100644 index 00000000000..7e6bdfded70 --- /dev/null +++ b/sapi/cgi/libfcgi/LICENSE.TERMS @@ -0,0 +1,28 @@ +This FastCGI application library source and object code (the +"Software") and its documentation (the "Documentation") are +copyrighted by Open Market, Inc ("Open Market"). The following terms +apply to all files associated with the Software and Documentation +unless explicitly disclaimed in individual files. + +Open Market permits you to use, copy, modify, distribute, and license +this Software and the Documentation for any purpose, provided that +existing copyright notices are retained in all copies and that this +notice is included verbatim in any distributions. No written +agreement, license, or royalty fee is required for any of the +authorized uses. Modifications to this Software and Documentation may +be copyrighted by their authors and need not follow the licensing +terms described here. If modifications to this Software and +Documentation have new licensing terms, the new terms must be clearly +indicated on the first page of each file where they apply. + +OPEN MARKET MAKES NO EXPRESS OR IMPLIED WARRANTY WITH RESPECT TO THE +SOFTWARE OR THE DOCUMENTATION, INCLUDING WITHOUT LIMITATION ANY +WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. IN +NO EVENT SHALL OPEN MARKET BE LIABLE TO YOU OR ANY THIRD PARTY FOR ANY +DAMAGES ARISING FROM OR RELATING TO THIS SOFTWARE OR THE +DOCUMENTATION, INCLUDING, WITHOUT LIMITATION, ANY INDIRECT, SPECIAL OR +CONSEQUENTIAL DAMAGES OR SIMILAR DAMAGES, INCLUDING LOST PROFITS OR +LOST DATA, EVEN IF OPEN MARKET HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. THE SOFTWARE AND DOCUMENTATION ARE PROVIDED "AS IS". +OPEN MARKET HAS NO LIABILITY IN CONTRACT, TORT, NEGLIGENCE OR +OTHERWISE ARISING OUT OF THIS SOFTWARE OR THE DOCUMENTATION. diff --git a/sapi/cgi/libfcgi/fcgi_stdio.c b/sapi/cgi/libfcgi/fcgi_stdio.c new file mode 100644 index 00000000000..39a56315fd7 --- /dev/null +++ b/sapi/cgi/libfcgi/fcgi_stdio.c @@ -0,0 +1,801 @@ +/* + * fcgi_stdio.c -- + * + * FastCGI-stdio compatibility package + * + * + * Copyright (c) 1996 Open Market, Inc. + * + * See the file "LICENSE.TERMS" for information on usage and redistribution + * of this file, and for a DISCLAIMER OF ALL WARRANTIES. + * + */ + +#ifndef lint +static const char rcsid[] = "$Id$"; +#endif /* not lint */ + +#include /* for errno */ +#include /* for va_arg */ +#include /* for malloc */ +#include /* for strerror */ + +#include "fcgi_config.h" + +#ifdef HAVE_UNISTD_H +#include +#endif + +#ifdef _WIN32 +#define DLLAPI __declspec(dllexport) +#endif + +#include "fcgiapp.h" +#include "fcgios.h" +#include "fcgimisc.h" + +#define NO_FCGI_DEFINES +#include "fcgi_stdio.h" +#undef NO_FCGI_DEFINES + +#ifndef _WIN32 + +extern char **environ; + +#ifdef HAVE_FILENO_PROTO +#include +#else +extern int fileno(FILE *stream); +#endif + +extern FILE *fdopen(int fildes, const char *type); +extern FILE *popen(const char *command, const char *type); +extern int pclose(FILE *stream); + +#else /* _WIN32 */ + +#define popen _popen +#define pclose _pclose + +#endif /* _WIN32 */ + +FCGI_FILE _fcgi_sF[3]; + + +/* + *---------------------------------------------------------------------- + * + * FCGI_Accept -- + * + * Accepts a new request from the HTTP server and creates + * a conventional execution environment for the request. + * + * If the application was invoked as a FastCGI server, + * the first call to FCGI_Accept indicates that the application + * has completed its initialization and is ready to accept + * a request. Subsequent calls to FCGI_Accept indicate that + * the application has completed its processing of the + * current request and is ready to accept a new request. + * + * If the application was invoked as a CGI program, the first + * call to FCGI_Accept is essentially a no-op and the second + * call returns EOF (-1). + * + * Results: + * 0 for successful call, -1 for error (application should exit). + * + * Side effects: + * If the application was invoked as a FastCGI server, + * and this is not the first call to this procedure, + * FCGI_Accept first performs the equivalent of FCGI_Finish. + * + * On every call, FCGI_Accept accepts the new request and + * reads the FCGI_PARAMS stream into an environment array, + * i.e. a NULL-terminated array of strings of the form + * ``name=value''. It assigns a pointer to this array + * to the global variable environ, used by the standard + * library function getenv. It creates new FCGI_FILE *s + * representing input from the HTTP server, output to the HTTP + * server, and error output to the HTTP server, and assigns these + * new files to stdin, stdout, and stderr respectively. + * + * DO NOT mutate or retain pointers to environ or any values + * contained in it (e.g. to the result of calling getenv(3)), + * since these are freed by the next call to FCGI_Finish or + * FCGI_Accept. In particular do not use setenv(3) or putenv(3) + * in conjunction with FCGI_Accept. + * + *---------------------------------------------------------------------- + */ +static int acceptCalled = FALSE; +static int isCGI = FALSE; + +int FCGI_Accept(void) +{ + if(!acceptCalled) { + /* + * First call to FCGI_Accept. Is application running + * as FastCGI or as CGI? + */ + isCGI = FCGX_IsCGI(); + acceptCalled = TRUE; + atexit(&FCGI_Finish); + } else if(isCGI) { + /* + * Not first call to FCGI_Accept and running as CGI means + * application is done. + */ + return(EOF); + } + if(isCGI) { + FCGI_stdin->stdio_stream = stdin; + FCGI_stdin->fcgx_stream = NULL; + FCGI_stdout->stdio_stream = stdout; + FCGI_stdout->fcgx_stream = NULL; + FCGI_stderr->stdio_stream = stderr; + FCGI_stderr->fcgx_stream = NULL; + } else { + FCGX_Stream *in, *out, *error; + FCGX_ParamArray envp; + int acceptResult = FCGX_Accept(&in, &out, &error, &envp); + if(acceptResult < 0) { + return acceptResult; + } + FCGI_stdin->stdio_stream = NULL; + FCGI_stdin->fcgx_stream = in; + FCGI_stdout->stdio_stream = NULL; + FCGI_stdout->fcgx_stream = out; + FCGI_stderr->stdio_stream = NULL; + FCGI_stderr->fcgx_stream = error; + environ = envp; + } + return 0; +} + +/* + *---------------------------------------------------------------------- + * + * FCGI_Finish -- + * + * Finishes the current request from the HTTP server. + * + * Side effects: + * + * Flushes any buffered output to the HTTP server. Then frees + * all storage allocated by the previous call, including all + * storage reachable from the value of environ set by the previous + * call to FCGI_Accept. + * + * DO NOT use stdin, stdout, stderr, or environ between calling + * FCGI_Finish and calling FCGI_Accept. + * + * DO NOT mutate or retain pointers to environ or any values + * contained in it (e.g. to the result of calling getenv(3)), + * since these are freed by the next call to FCGI_Finish or + * FCGI_Accept. In particular do not use setenv(3) or putenv(3) + * in conjunction with FCGI_Accept. + * + *---------------------------------------------------------------------- + */ +void FCGI_Finish(void) +{ + if(!acceptCalled || isCGI) { + return; + } + FCGX_Finish(); + FCGI_stdin->fcgx_stream = NULL; + FCGI_stdout->fcgx_stream = NULL; + FCGI_stderr->fcgx_stream = NULL; + environ = NULL; +} + +/* + *---------------------------------------------------------------------- + * + * FCGI_StartFilterData -- + * + * + * The current request is for the filter role, and stdin is + * positioned at EOF of FCGI_STDIN. The call repositions + * stdin to the start of FCGI_DATA. + * If the preconditions are not met (e.g. FCGI_STDIN has not + * been read to EOF), the call sets the stream error code to + * FCGX_CALL_SEQ_ERROR. + * + * Results: + * 0 for a normal return, < 0 for error + * + *---------------------------------------------------------------------- + */ +int FCGI_StartFilterData(void) +{ + if(FCGI_stdin->stdio_stream) { + return -1; + } else { + return FCGX_StartFilterData(FCGI_stdin->fcgx_stream); + } +} + +/* + *---------------------------------------------------------------------- + * + * FCGI_SetExitStatus -- + * + * Sets the exit status for the current request. The exit status + * is the status code the request would have exited with, had + * the request been run as a CGI program. You can call + * FCGI_SetExitStatus several times during a request; the last call + * before the request ends (by calling FCGI_Accept) determines the + * value. + * + *---------------------------------------------------------------------- + */ +void FCGI_SetExitStatus(int status) +{ + if(FCGI_stdin->fcgx_stream) { + FCGX_SetExitStatus(status, FCGI_stdin->fcgx_stream); + } +} + +/* + *---------------------------------------------------------------------- + * + * FCGI_perror -- + * + * Wrapper for function defined in H&S Section 11.2 + * + *---------------------------------------------------------------------- + */ +void FCGI_perror(const char *str) +{ + FCGI_fputs(str, FCGI_stderr); + FCGI_fputs(": ", FCGI_stderr); + FCGI_fputs(strerror(OS_Errno), FCGI_stderr); + return; +} + +/* + *---------------------------------------------------------------------- + * + * FCGI_OpenFromFILE -- + * + * Constructs a new FCGI_FILE * from the FILE *stream. + * + * Results: + * NULL if stream == NULL or storage could not be allocated, + * otherwise the new FCGI_FILE *. + * + *---------------------------------------------------------------------- + */ +static FCGI_FILE *FCGI_OpenFromFILE(FILE *stream) +{ + FCGI_FILE *fp; + + if (stream == NULL) + return NULL; + + fp = (FCGI_FILE *) malloc(sizeof(FCGI_FILE)); + if (fp != NULL) + { + fp->stdio_stream = stream; + fp->fcgx_stream = NULL; + } + + return fp; +} + +/* + *---------------------------------------------------------------------- + * + * FCGI_fopen, FCGI_fclose, FCGI_fflush, FCGI_freopen -- + * + * Wrappers for functions defined in H&S Section 15.2 + * + *---------------------------------------------------------------------- + */ +FCGI_FILE *FCGI_fopen(const char *path, const char *mode) +{ + FILE * file = fopen(path, mode); + FCGI_FILE * fcgi_file = FCGI_OpenFromFILE(file); + + if (file && !fcgi_file) + fclose(file); + + return fcgi_file; +} + +int FCGI_fclose(FCGI_FILE *fp) +{ + int n = EOF; + if(fp->stdio_stream) { + n = fclose(fp->stdio_stream); + fp->stdio_stream = NULL; + } else if(fp->fcgx_stream) { + n = FCGX_FClose(fp->fcgx_stream); + fp->fcgx_stream = NULL; + } + if((fp != FCGI_stdin) && (fp != FCGI_stdout) && (fp != FCGI_stderr)) { + free(fp); + } + return n; +} + +int FCGI_fflush(FCGI_FILE *fp) +{ + if(fp == NULL) + return fflush(NULL); + if(fp->stdio_stream) + return fflush(fp->stdio_stream); + else if(fp->fcgx_stream) + return FCGX_FFlush(fp->fcgx_stream); + return EOF; +} + +FCGI_FILE *FCGI_freopen(const char *path, const char *mode, + FCGI_FILE *fp) +{ + if(fp->stdio_stream) { + if(freopen(path, mode, fp->stdio_stream) == NULL) + return NULL; + else + return fp; + } else if(fp->fcgx_stream) { + (void) FCGX_FClose(fp->fcgx_stream); + fp->stdio_stream = fopen(path, mode); + if(fp->stdio_stream == NULL) + return NULL; + else { + fp->fcgx_stream = NULL; + return fp; + } + } + return NULL; +} + +/* + *---------------------------------------------------------------------- + * + * FCGI_setvbuf, FCGI_setbuf -- + * + * Wrappers for functions defined in H&S Section 15.3 + * + *---------------------------------------------------------------------- + */ +int FCGI_setvbuf(FCGI_FILE *fp, char *buf, int bufmode, size_t size) +{ + if(fp->stdio_stream) + return setvbuf(fp->stdio_stream, buf, bufmode, size); + else { + return -1; + } +} + +void FCGI_setbuf(FCGI_FILE *fp, char *buf) +{ + if(fp->stdio_stream) + setbuf(fp->stdio_stream, buf); +} + +/* + *---------------------------------------------------------------------- + * + * FCGI_fseek, FCGI_ftell, FCGI_rewind, FCGI_fgetpos, FCGI_fsetpos -- + * + * Wrappers for functions defined in H&S Section 15.5 + * + *---------------------------------------------------------------------- + */ +int FCGI_fseek(FCGI_FILE *fp, long offset, int whence) +{ + if(fp->stdio_stream) + return fseek(fp->stdio_stream, offset, whence); + else { + OS_SetErrno(ESPIPE); + return -1; + } +} + +int FCGI_ftell(FCGI_FILE *fp) +{ + if(fp->stdio_stream) + return ftell(fp->stdio_stream); + else { + OS_SetErrno(ESPIPE); + return -1; + } +} + +void FCGI_rewind(FCGI_FILE *fp) +{ + if(fp->stdio_stream) + rewind(fp->stdio_stream); + else + OS_SetErrno(ESPIPE); +} + +#ifdef HAVE_FPOS +int FCGI_fgetpos(FCGI_FILE *fp, fpos_t *pos) +{ + if(fp->stdio_stream) + return fgetpos(fp->stdio_stream, pos); + else { + OS_SetErrno(ESPIPE); + return -1; + } +} + +int FCGI_fsetpos(FCGI_FILE *fp, const fpos_t *pos) +{ + if(fp->stdio_stream) + return fsetpos(fp->stdio_stream, pos); + else { + OS_SetErrno(ESPIPE); + return -1; + } +} +#endif + +/* + *---------------------------------------------------------------------- + * + * FCGI_fgetc, FCGI_getchar, FCGI_ungetc -- + * + * Wrappers for functions defined in H&S Section 15.6 + * + * XXX: getc and getchar are generally defined as macros + * for performance reasons + * + *---------------------------------------------------------------------- + */ +int FCGI_fgetc(FCGI_FILE *fp) +{ + if(fp->stdio_stream) + return fgetc(fp->stdio_stream); + else if(fp->fcgx_stream) + return FCGX_GetChar(fp->fcgx_stream); + return EOF; +} + +int FCGI_getchar(void) +{ + return FCGI_fgetc(FCGI_stdin); +} + +int FCGI_ungetc(int c, FCGI_FILE *fp) +{ + if(fp->stdio_stream) + return ungetc(c, fp->stdio_stream); + else if(fp->fcgx_stream) + return FCGX_UnGetChar(c, fp->fcgx_stream); + return EOF; +} + +/* + *---------------------------------------------------------------------- + * + * FCGI_fgets, FCGI_gets -- + * + * Wrappers for functions defined in H&S Section 15.7 + * + *---------------------------------------------------------------------- + */ +char *FCGI_fgets(char *str, int size, FCGI_FILE *fp) +{ + if(fp->stdio_stream) + return fgets(str, size, fp->stdio_stream); + else if(fp->fcgx_stream) + return FCGX_GetLine(str, size, fp->fcgx_stream); + return NULL; +} + +/* + * The gets() function reads characters from the standard input stream + * into the array pointed to by str until a newline character is read + * or an end-of-file condition is encountered. The newline character + * is discarded and the string is terminated with a null character. + */ +char *FCGI_gets(char *str) +{ + char *s; + int c; + + for (s = str; ((c = FCGI_getchar()) != '\n');) { + if(c == EOF) { + if(s == str) + return NULL; + else + break; + } else + *s++ = (char) c; + } + *s = 0; + return str; +} + +/* + *---------------------------------------------------------------------- + * + * Wrappers for functions defined in H&S Section 15.8 + * + * XXX: missing: fscanf, scanf + * + *---------------------------------------------------------------------- + */ + +/* + *---------------------------------------------------------------------- + * + * FCGI_fputc, FCGI_putchar -- + * + * Wrappers for functions defined in H&S Section 15.9 + * + * XXX: putc and putchar are generally defined as macros + * for performance reasons + * + *---------------------------------------------------------------------- + */ +int FCGI_fputc(int c, FCGI_FILE *fp) +{ + if(fp->stdio_stream) + return fputc(c, fp->stdio_stream); + else if(fp->fcgx_stream) + return FCGX_PutChar(c, fp->fcgx_stream); + else return EOF; +} + +int FCGI_putchar(int c) +{ + return FCGI_fputc(c, FCGI_stdout); +} + +/* + *---------------------------------------------------------------------- + * + * FCGI_fputs, FCGI_puts + * + * Wrappers for functions defined in H&S Section 15.10 + * + *---------------------------------------------------------------------- + */ +int FCGI_fputs(const char *str, FCGI_FILE *fp) +{ + if(fp->stdio_stream) + return fputs(str, fp->stdio_stream); + else if(fp->fcgx_stream) + return FCGX_PutS(str, fp->fcgx_stream); + return EOF; +} + +int FCGI_puts(const char *str) +{ + int n; + if(FCGI_stdout->stdio_stream) { + n = fputs(str, FCGI_stdout->stdio_stream); + if(n < 0) + return n; + else + return fputc('\n', FCGI_stdout->stdio_stream); + } else if(FCGI_stdout->fcgx_stream) { + n = FCGX_PutS(str, FCGI_stdout->fcgx_stream); + if(n < 0) + return n; + else + return FCGX_PutChar('\n', FCGI_stdout->fcgx_stream); + } + return EOF; +} + +/* + *---------------------------------------------------------------------- + * + * FCGI_fprintf, FCGI_printf -- + * + * Wrappers for functions defined in H&S Section 15.11 + * + *---------------------------------------------------------------------- + */ +int FCGI_fprintf(FCGI_FILE *fp, const char *format, ...) +{ + va_list ap; + int n = 0; + va_start(ap, format); + if(fp->stdio_stream) + n = vfprintf(fp->stdio_stream, format, ap); + else if(fp->fcgx_stream) + n = FCGX_VFPrintF(fp->fcgx_stream, format, ap); + va_end(ap); + return n; +} + +int FCGI_printf(const char *format, ...) +{ + va_list ap; + int n; + va_start(ap, format); + n = FCGI_vfprintf(FCGI_stdout, format, ap); + va_end(ap); + return n; +} + +/* + *---------------------------------------------------------------------- + * + * FCGI_vfprintf, FCGI_vprintf -- + * + * Wrappers for functions defined in H&S Section 15.12 + * + *---------------------------------------------------------------------- + */ +int FCGI_vfprintf(FCGI_FILE *fp, const char *format, va_list ap) +{ + if(fp->stdio_stream) + return vfprintf(fp->stdio_stream, format, ap); + else if(fp->fcgx_stream) + return FCGX_VFPrintF(fp->fcgx_stream, format, ap); + return EOF; +} + +int FCGI_vprintf(const char *format, va_list ap) +{ + if(FCGI_stdout->stdio_stream) + return vfprintf(FCGI_stdout->stdio_stream, format, ap); + else if(FCGI_stdout->fcgx_stream) + return FCGX_VFPrintF(FCGI_stdout->fcgx_stream, format, ap); + return EOF; +} + +/* + *---------------------------------------------------------------------- + * + * FCGI_fread, FCGI_fwrite -- + * + * Wrappers for functions defined in H&S Section 15.13 + * + *---------------------------------------------------------------------- + */ +size_t FCGI_fread(void *ptr, size_t size, size_t nmemb, FCGI_FILE *fp) +{ + int n; + if(fp->stdio_stream) + return fread(ptr, size, nmemb, fp->stdio_stream); + else if(fp->fcgx_stream) { + if((size * nmemb) == 0) { + return 0; + } + n = FCGX_GetStr((char *) ptr, size * nmemb, fp->fcgx_stream); + return (n/size); + } + return (size_t)EOF; +} + +size_t FCGI_fwrite(void *ptr, size_t size, size_t nmemb, FCGI_FILE *fp) +{ + int n; + if(fp->stdio_stream) + return fwrite(ptr, size, nmemb, fp->stdio_stream); + else if(fp->fcgx_stream) { + if((size * nmemb) == 0) { + return 0; + } + n = FCGX_PutStr((char *) ptr, size * nmemb, fp->fcgx_stream); + return (n/size); + } + return (size_t)EOF; +} + +/* + *---------------------------------------------------------------------- + * + * FCGI_feof, FCGI_ferror, FCGI_clearerr -- + * + * Wrappers for functions defined in H&S Section 15.14 + * + *---------------------------------------------------------------------- + */ +int FCGI_feof(FCGI_FILE *fp) +{ + if(fp->stdio_stream) { + return feof(fp->stdio_stream); + } else if (fp->fcgx_stream){ + return FCGX_HasSeenEOF(fp->fcgx_stream); + } + return -1; + +} + +int FCGI_ferror(FCGI_FILE *fp) +{ + if(fp->stdio_stream) { + return ferror(fp->stdio_stream); + } else if(fp->fcgx_stream) { + return FCGX_GetError(fp->fcgx_stream); + } + return -1; +} + +void FCGI_clearerr(FCGI_FILE *fp) +{ + if(fp->stdio_stream) { + clearerr(fp->stdio_stream); + } else if(fp->fcgx_stream) { + FCGX_ClearError(fp->fcgx_stream); + } + return; +} + +/* + *---------------------------------------------------------------------- + * + * FCGI_tmpfile -- + * + * Wrappers for function defined in H&S Section 15.16 + * + *---------------------------------------------------------------------- + */ +FCGI_FILE *FCGI_tmpfile(void) +{ + FILE * file = tmpfile(); + FCGI_FILE * fcgi_file = FCGI_OpenFromFILE(file); + + if (file && !fcgi_file) + fclose(file); + + return fcgi_file; +} + +/* + *---------------------------------------------------------------------- + * + * FCGI_fileno, FCGI_fdopen, FCGI_popen, FCGI_pclose -- + * + * Wrappers for POSIX, X/OPEN functions not in ISO C + * + *---------------------------------------------------------------------- + */ +int FCGI_fileno(FCGI_FILE *fp) +{ + if(fp->stdio_stream) + return fileno(fp->stdio_stream); + else + return -1; +} + +FCGI_FILE *FCGI_fdopen(int fd, const char *mode) +{ + FILE * file = fdopen(fd, mode); + FCGI_FILE * fcgi_file = FCGI_OpenFromFILE(file); + + if (file && !fcgi_file) + fclose(file); + + return fcgi_file; +} + +FCGI_FILE *FCGI_popen(const char *cmd, const char *type) +{ + FILE * file = popen(cmd, type); + FCGI_FILE * fcgi_file = FCGI_OpenFromFILE(file); + + if (file && !fcgi_file) + pclose(file); + + return fcgi_file; +} + +int FCGI_pclose(FCGI_FILE *fp) +{ + int n = EOF; + if (fp->stdio_stream) { + n = pclose(fp->stdio_stream); + fp->stdio_stream = NULL; + } else if(fp->fcgx_stream) { + /* + * The caller is deeply confused; don't free the storage. + */ + return EOF; + } + if((fp != FCGI_stdin) && (fp != FCGI_stdout) && (fp != FCGI_stderr)) { + free(fp); + } + return n; +} diff --git a/sapi/cgi/libfcgi/fcgiapp.c b/sapi/cgi/libfcgi/fcgiapp.c new file mode 100644 index 00000000000..d35b3d140d3 --- /dev/null +++ b/sapi/cgi/libfcgi/fcgiapp.c @@ -0,0 +1,2307 @@ +/* + * fcgiapp.c -- + * + * FastCGI application library: request-at-a-time + * + * + * Copyright (c) 1996 Open Market, Inc. + * + * See the file "LICENSE.TERMS" for information on usage and redistribution + * of this file, and for a DISCLAIMER OF ALL WARRANTIES. + * + */ +#ifndef lint +static const char rcsid[] = "$Id$"; +#endif /* not lint */ + +#include +#include +#include /* for fcntl */ +#include +#include /* for memchr() */ +#include +#include +#include +#include +#include + +#include "fcgi_config.h" + +#ifdef HAVE_SYS_SOCKET_H +#include /* for getpeername */ +#endif + +#ifdef HAVE_SYS_TIME_H +#include +#endif + +#ifdef HAVE_UNISTD_H +#include +#endif + +#ifdef HAVE_LIMITS_H +#include +#endif + +#ifdef _WIN32 +#define DLLAPI __declspec(dllexport) +#endif + +#include "fcgimisc.h" +#include "fastcgi.h" +#include "fcgios.h" +#include "fcgiapp.h" + +/* + * This is a workaround for one version of the HP C compiler + * (c89 on HP-UX 9.04, also Stratus FTX), which will dump core + * if given 'long double' for varargs. + */ +#ifdef HAVE_VA_ARG_LONG_DOUBLE_BUG +#define LONG_DOUBLE double +#else +#define LONG_DOUBLE long double +#endif + +/* + * Globals + */ +static int libInitialized = 0; +static int isFastCGI = -1; +static char *webServerAddressList = NULL; +static FCGX_Request the_request; +void _FCGX_FreeStream(FCGX_Stream **streamPtr, BOOL freeData); + +void FCGX_ShutdownPending(void) +{ + OS_ShutdownPending(); +} + +static void *Malloc(size_t size) +{ + void *result = malloc(size); + ASSERT(size == 0 || result != NULL); + return result; +} + +static char *StringCopy(char *str) +{ + int strLen = strlen(str); + char *newString = (char *)Malloc(strLen + 1); + memcpy(newString, str, strLen); + newString[strLen] = '\000'; + return newString; +} + + +/* + *---------------------------------------------------------------------- + * + * FCGX_GetChar -- + * + * Reads a byte from the input stream and returns it. + * + * Results: + * The byte, or EOF (-1) if the end of input has been reached. + * + *---------------------------------------------------------------------- + */ +int FCGX_GetChar(FCGX_Stream *stream) +{ + if(stream->rdNext != stream->stop) + return *stream->rdNext++; + if(stream->isClosed || !stream->isReader) + return EOF; + stream->fillBuffProc(stream); + stream->stopUnget = stream->rdNext; + if(stream->rdNext != stream->stop) + return *stream->rdNext++; + ASSERT(stream->isClosed); /* bug in fillBufProc if not */ + return EOF; +} + +/* + *---------------------------------------------------------------------- + * + * FCGX_GetStr -- + * + * Reads up to n consecutive bytes from the input stream + * into the character array str. Performs no interpretation + * of the input bytes. + * + * Results: + * Number of bytes read. If result is smaller than n, + * the end of input has been reached. + * + *---------------------------------------------------------------------- + */ +int FCGX_GetStr(char *str, int n, FCGX_Stream *stream) +{ + int m, bytesMoved; + + if(n <= 0) { + return 0; + } + /* + * Fast path: n bytes are already available + */ + if(n <= (stream->stop - stream->rdNext)) { + memcpy(str, stream->rdNext, n); + stream->rdNext += n; + return n; + } + /* + * General case: stream is closed or buffer fill procedure + * needs to be called + */ + bytesMoved = 0; + for (;;) { + if(stream->rdNext != stream->stop) { + m = min(n - bytesMoved, stream->stop - stream->rdNext); + memcpy(str, stream->rdNext, m); + bytesMoved += m; + stream->rdNext += m; + if(bytesMoved == n) + return bytesMoved; + str += m; + } + if(stream->isClosed || !stream->isReader) + return bytesMoved; + stream->fillBuffProc(stream); + stream->stopUnget = stream->rdNext; + } +} + +/* + *---------------------------------------------------------------------- + * + * FCGX_GetLine -- + * + * Reads up to n-1 consecutive bytes from the input stream + * into the character array str. Stops before n-1 bytes + * have been read if '\n' or EOF is read. The terminating '\n' + * is copied to str. After copying the last byte into str, + * stores a '\0' terminator. + * + * Results: + * NULL if EOF is the first thing read from the input stream, + * str otherwise. + * + *---------------------------------------------------------------------- + */ +char *FCGX_GetLine(char *str, int n, FCGX_Stream *stream) +{ + int c; + char *p = str; + + n--; + while (n > 0) { + c = FCGX_GetChar(stream); + if(c == EOF) { + if(p == str) + return NULL; + else + break; + } + *p++ = (char) c; + n--; + if(c == '\n') + break; + } + *p = '\0'; + return str; +} + +/* + *---------------------------------------------------------------------- + * + * FCGX_UnGetChar -- + * + * Pushes back the character c onto the input stream. One + * character of pushback is guaranteed once a character + * has been read. No pushback is possible for EOF. + * + * Results: + * Returns c if the pushback succeeded, EOF if not. + * + *---------------------------------------------------------------------- + */ +int FCGX_UnGetChar(int c, FCGX_Stream *stream) { + if(c == EOF + || stream->isClosed + || !stream->isReader + || stream->rdNext == stream->stopUnget) + return EOF; + --(stream->rdNext); + *stream->rdNext = (unsigned char) c; + return c; +} + +/* + *---------------------------------------------------------------------- + * + * FCGX_HasSeenEOF -- + * + * Returns EOF if end-of-file has been detected while reading + * from stream; otherwise returns 0. + * + * Note that FCGX_HasSeenEOF(s) may return 0, yet an immediately + * following FCGX_GetChar(s) may return EOF. This function, like + * the standard C stdio function feof, does not provide the + * ability to peek ahead. + * + * Results: + * EOF if end-of-file has been detected, 0 if not. + * + *---------------------------------------------------------------------- + */ +int FCGX_HasSeenEOF(FCGX_Stream *stream) { + return (stream->isClosed) ? EOF : 0; +} + +/* + *---------------------------------------------------------------------- + * + * FCGX_PutChar -- + * + * Writes a byte to the output stream. + * + * Results: + * The byte, or EOF (-1) if an error occurred. + * + *---------------------------------------------------------------------- + */ +int FCGX_PutChar(int c, FCGX_Stream *stream) +{ + if(stream->wrNext != stream->stop) + return (*stream->wrNext++ = (unsigned char) c); + if(stream->isClosed || stream->isReader) + return EOF; + stream->emptyBuffProc(stream, FALSE); + if(stream->wrNext != stream->stop) + return (*stream->wrNext++ = (unsigned char) c); + ASSERT(stream->isClosed); /* bug in emptyBuffProc if not */ + return EOF; +} + +/* + *---------------------------------------------------------------------- + * + * FCGX_PutStr -- + * + * Writes n consecutive bytes from the character array str + * into the output stream. Performs no interpretation + * of the output bytes. + * + * Results: + * Number of bytes written (n) for normal return, + * EOF (-1) if an error occurred. + * + *---------------------------------------------------------------------- + */ +int FCGX_PutStr(const char *str, int n, FCGX_Stream *stream) +{ + int m, bytesMoved; + + /* + * Fast path: room for n bytes in the buffer + */ + if(n <= (stream->stop - stream->wrNext)) { + memcpy(stream->wrNext, str, n); + stream->wrNext += n; + return n; + } + /* + * General case: stream is closed or buffer empty procedure + * needs to be called + */ + bytesMoved = 0; + for (;;) { + if(stream->wrNext != stream->stop) { + m = min(n - bytesMoved, stream->stop - stream->wrNext); + memcpy(stream->wrNext, str, m); + bytesMoved += m; + stream->wrNext += m; + if(bytesMoved == n) + return bytesMoved; + str += m; + } + if(stream->isClosed || stream->isReader) + return -1; + stream->emptyBuffProc(stream, FALSE); + } +} + +/* + *---------------------------------------------------------------------- + * + * FCGX_PutS -- + * + * Writes a character string to the output stream. + * + * Results: + * number of bytes written for normal return, + * EOF (-1) if an error occurred. + * + *---------------------------------------------------------------------- + */ +int FCGX_PutS(const char *str, FCGX_Stream *stream) +{ + return FCGX_PutStr(str, strlen(str), stream); +} + +/* + *---------------------------------------------------------------------- + * + * FCGX_FPrintF -- + * + * Performs output formatting and writes the results + * to the output stream. + * + * Results: + * number of bytes written for normal return, + * EOF (-1) if an error occurred. + * + *---------------------------------------------------------------------- + */ +int FCGX_FPrintF(FCGX_Stream *stream, const char *format, ...) +{ + int result; + va_list ap; + va_start(ap, format); + result = FCGX_VFPrintF(stream, format, ap); + va_end(ap); + return result; +} + +/* + *---------------------------------------------------------------------- + * + * FCGX_VFPrintF -- + * + * Performs output formatting and writes the results + * to the output stream. + * + * Results: + * number of bytes written for normal return, + * EOF (-1) if an error occurred. + * + *---------------------------------------------------------------------- + */ + +#define PRINTF_BUFFLEN 100 + /* + * More than sufficient space for all unmodified conversions + * except %s and %f. + */ +#define FMT_BUFFLEN 25 + /* + * Max size of a format specifier is 1 + 5 + 7 + 7 + 2 + 1 + slop + */ +static void CopyAndAdvance(char **destPtr, char **srcPtr, int n); + +int FCGX_VFPrintF(FCGX_Stream *stream, const char *format, va_list arg) +{ + char *f, *fStop, *percentPtr, *p, *fmtBuffPtr, *buffPtr; + int op, performedOp, sizeModifier, buffCount = 0, buffLen, specifierLength; + int fastPath, n, auxBuffLen = 0, buffReqd, minWidth, precision, exp; + char *auxBuffPtr = NULL; + int streamCount = 0; + char fmtBuff[FMT_BUFFLEN]; + char buff[PRINTF_BUFFLEN]; + + int intArg; + short shortArg; + long longArg; + unsigned unsignedArg; + unsigned long uLongArg; + unsigned short uShortArg; + char *charPtrArg = NULL; + void *voidPtrArg; + int *intPtrArg; + long *longPtrArg; + short *shortPtrArg; + double doubleArg = 0.0; + LONG_DOUBLE lDoubleArg = 0.0L; + + fmtBuff[0] = '%'; + f = (char *) format; + fStop = f + strlen(f); + while (f != fStop) { + percentPtr = (char *)memchr(f, '%', fStop - f); + if(percentPtr == NULL) percentPtr = fStop; + if(percentPtr != f) { + if(FCGX_PutStr(f, percentPtr - f, stream) < 0) + goto ErrorReturn; + streamCount += percentPtr - f; + f = percentPtr; + if(f == fStop) break; + } + fastPath = TRUE; + /* + * The following loop always executes either once or twice. + */ + for (;;) { + if(fastPath) { + /* + * Fast path: Scan optimistically, hoping that no flags, + * minimum field width, or precision are specified. + * Use the preallocated buffer, which is large enough + * for all fast path cases. If the conversion specifier + * is really more complex, run the loop a second time + * using the slow path. + * Note that fast path execution of %s bypasses the buffer + * and %f is not attempted on the fast path due to + * its large buffering requirements. + */ + op = *(percentPtr + 1); + switch(op) { + case 'l': + case 'L': + case 'h': + sizeModifier = op; + op = *(percentPtr + 2); + fmtBuff[1] = (char) sizeModifier; + fmtBuff[2] = (char) op; + fmtBuff[3] = '\0'; + specifierLength = 3; + break; + default: + sizeModifier = ' '; + fmtBuff[1] = (char) op; + fmtBuff[2] = '\0'; + specifierLength = 2; + break; + } + buffPtr = buff; + buffLen = PRINTF_BUFFLEN; + } else { + /* + * Slow path: Scan the conversion specifier and construct + * a new format string, compute an upper bound on the + * amount of buffering that sprintf will require, + * and allocate a larger buffer if necessary. + */ + p = percentPtr + 1; + fmtBuffPtr = &fmtBuff[1]; + /* + * Scan flags + */ + n = strspn(p, "-0+ #"); + if(n > 5) + goto ErrorReturn; + CopyAndAdvance(&fmtBuffPtr, &p, n); + /* + * Scan minimum field width + */ + n = strspn(p, "0123456789"); + if(n == 0) { + if(*p == '*') { + minWidth = va_arg(arg, int); + if(abs(minWidth) > 999999) + goto ErrorReturn; + /* + * The following use of strlen rather than the + * value returned from sprintf is because SUNOS4 + * returns a char * instead of an int count. + */ + sprintf(fmtBuffPtr, "%d", minWidth); + fmtBuffPtr += strlen(fmtBuffPtr); + p++; + } else { + minWidth = 0; + } + } else if(n <= 6) { + minWidth = strtol(p, NULL, 10); + CopyAndAdvance(&fmtBuffPtr, &p, n); + } else { + goto ErrorReturn; + } + /* + * Scan precision + */ + if(*p == '.') { + CopyAndAdvance(&fmtBuffPtr, &p, 1); + n = strspn(p, "0123456789"); + if(n == 0) { + if(*p == '*') { + precision = va_arg(arg, int); + if(precision < 0) precision = 0; + if(precision > 999999) + goto ErrorReturn; + /* + * The following use of strlen rather than the + * value returned from sprintf is because SUNOS4 + * returns a char * instead of an int count. + */ + sprintf(fmtBuffPtr, "%d", precision); + fmtBuffPtr += strlen(fmtBuffPtr); + p++; + } else { + precision = 0; + } + } else if(n <= 6) { + precision = strtol(p, NULL, 10); + CopyAndAdvance(&fmtBuffPtr, &p, n); + } else { + goto ErrorReturn; + } + } else { + precision = -1; + } + /* + * Scan size modifier and conversion operation + */ + switch(*p) { + case 'l': + case 'L': + case 'h': + sizeModifier = *p; + CopyAndAdvance(&fmtBuffPtr, &p, 1); + break; + default: + sizeModifier = ' '; + break; + } + op = *p; + CopyAndAdvance(&fmtBuffPtr, &p, 1); + ASSERT(fmtBuffPtr - fmtBuff < FMT_BUFFLEN); + *fmtBuffPtr = '\0'; + specifierLength = p - percentPtr; + /* + * Bound the required buffer size. For s and f + * conversions this requires examining the argument. + */ + switch(op) { + case 'd': + case 'i': + case 'u': + case 'o': + case 'x': + case 'X': + case 'c': + case 'p': + buffReqd = max(precision, 46); + break; + case 's': + charPtrArg = va_arg(arg, char *); + if (!charPtrArg) charPtrArg = "(null)"; + if(precision == -1) { + buffReqd = strlen(charPtrArg); + } else { + p = (char *)memchr(charPtrArg, '\0', precision); + buffReqd = + (p == NULL) ? precision : p - charPtrArg; + } + break; + case 'f': + switch(sizeModifier) { + case ' ': + doubleArg = va_arg(arg, double); + frexp(doubleArg, &exp); + break; + case 'L': + lDoubleArg = va_arg(arg, LONG_DOUBLE); + /* XXX Need to check for the presence of + * frexpl() and use it if available */ + frexp((double) lDoubleArg, &exp); + break; + default: + goto ErrorReturn; + } + if(precision == -1) precision = 6; + buffReqd = precision + 3 + ((exp > 0) ? exp/3 : 0); + break; + case 'e': + case 'E': + case 'g': + case 'G': + if(precision == -1) precision = 6; + buffReqd = precision + 8; + break; + case 'n': + case '%': + default: + goto ErrorReturn; + break; + } + buffReqd = max(buffReqd + 10, minWidth); + /* + * Allocate the buffer + */ + if(buffReqd <= PRINTF_BUFFLEN) { + buffPtr = buff; + buffLen = PRINTF_BUFFLEN; + } else { + if(auxBuffPtr == NULL || buffReqd > auxBuffLen) { + if(auxBuffPtr != NULL) free(auxBuffPtr); + auxBuffPtr = (char *)Malloc(buffReqd); + auxBuffLen = buffReqd; + if(auxBuffPtr == NULL) + goto ErrorReturn; + } + buffPtr = auxBuffPtr; + buffLen = auxBuffLen; + } + } + /* + * This giant switch statement requires the following variables + * to be set up: op, sizeModifier, arg, buffPtr, fmtBuff. + * When fastPath == FALSE and op == 's' or 'f', the argument + * has been read into charPtrArg, doubleArg, or lDoubleArg. + * The statement produces the boolean performedOp, TRUE iff + * the op/sizeModifier were executed and argument consumed; + * if performedOp, the characters written into buffPtr[] + * and the character count buffCount (== EOF meaning error). + * + * The switch cases are arranged in the same order as in the + * description of fprintf in section 15.11 of Harbison and Steele. + */ + performedOp = TRUE; + switch(op) { + case 'd': + case 'i': + switch(sizeModifier) { + case ' ': + intArg = va_arg(arg, int); + sprintf(buffPtr, fmtBuff, intArg); + buffCount = strlen(buffPtr); + break; + case 'l': + longArg = va_arg(arg, long); + sprintf(buffPtr, fmtBuff, longArg); + buffCount = strlen(buffPtr); + break; + case 'h': + shortArg = (short) va_arg(arg, int); + sprintf(buffPtr, fmtBuff, shortArg); + buffCount = strlen(buffPtr); + break; + default: + goto ErrorReturn; + } + break; + case 'u': + case 'o': + case 'x': + case 'X': + switch(sizeModifier) { + case ' ': + unsignedArg = va_arg(arg, unsigned); + sprintf(buffPtr, fmtBuff, unsignedArg); + buffCount = strlen(buffPtr); + break; + case 'l': + uLongArg = va_arg(arg, unsigned long); + sprintf(buffPtr, fmtBuff, uLongArg); + buffCount = strlen(buffPtr); + break; + case 'h': + uShortArg = (unsigned short) va_arg(arg, int); + sprintf(buffPtr, fmtBuff, uShortArg); + buffCount = strlen(buffPtr); + break; + default: + goto ErrorReturn; + } + break; + case 'c': + switch(sizeModifier) { + case ' ': + intArg = va_arg(arg, int); + sprintf(buffPtr, fmtBuff, intArg); + buffCount = strlen(buffPtr); + break; + case 'l': + /* + * XXX: Allowed by ISO C Amendment 1, but + * many platforms don't yet support wint_t + */ + goto ErrorReturn; + default: + goto ErrorReturn; + } + break; + case 's': + switch(sizeModifier) { + case ' ': + if(fastPath) { + buffPtr = va_arg(arg, char *); + buffCount = strlen(buffPtr); + buffLen = buffCount + 1; + } else { + sprintf(buffPtr, fmtBuff, charPtrArg); + buffCount = strlen(buffPtr); + } + break; + case 'l': + /* + * XXX: Don't know how to convert a sequence + * of wide characters into a byte stream, or + * even how to predict the buffering required. + */ + goto ErrorReturn; + default: + goto ErrorReturn; + } + break; + case 'p': + if(sizeModifier != ' ') + goto ErrorReturn; + voidPtrArg = va_arg(arg, void *); + sprintf(buffPtr, fmtBuff, voidPtrArg); + buffCount = strlen(buffPtr); + break; + case 'n': + switch(sizeModifier) { + case ' ': + intPtrArg = va_arg(arg, int *); + *intPtrArg = streamCount; + break; + case 'l': + longPtrArg = va_arg(arg, long *); + *longPtrArg = streamCount; + break; + case 'h': + shortPtrArg = (short *) va_arg(arg, short *); + *shortPtrArg = (short) streamCount; + break; + default: + goto ErrorReturn; + } + buffCount = 0; + break; + case 'f': + if(fastPath) { + performedOp = FALSE; + break; + } + switch(sizeModifier) { + case ' ': + sprintf(buffPtr, fmtBuff, doubleArg); + buffCount = strlen(buffPtr); + break; + case 'L': + sprintf(buffPtr, fmtBuff, lDoubleArg); + buffCount = strlen(buffPtr); + break; + default: + goto ErrorReturn; + } + break; + case 'e': + case 'E': + case 'g': + case 'G': + switch(sizeModifier) { + case ' ': + doubleArg = va_arg(arg, double); + sprintf(buffPtr, fmtBuff, doubleArg); + buffCount = strlen(buffPtr); + break; + case 'L': + lDoubleArg = va_arg(arg, LONG_DOUBLE); + sprintf(buffPtr, fmtBuff, lDoubleArg); + buffCount = strlen(buffPtr); + break; + default: + goto ErrorReturn; + } + break; + case '%': + if(sizeModifier != ' ') + goto ErrorReturn; + buff[0] = '%'; + buffCount = 1; + break; + case '\0': + goto ErrorReturn; + default: + performedOp = FALSE; + break; + } /* switch(op) */ + if(performedOp) break; + if(!fastPath) + goto ErrorReturn; + fastPath = FALSE; + } /* for (;;) */ + ASSERT(buffCount < buffLen); + if(buffCount > 0) { + if(FCGX_PutStr(buffPtr, buffCount, stream) < 0) + goto ErrorReturn; + streamCount += buffCount; + } else if(buffCount < 0) { + goto ErrorReturn; + } + f += specifierLength; + } /* while(f != fStop) */ + goto NormalReturn; + ErrorReturn: + streamCount = -1; + NormalReturn: + if(auxBuffPtr != NULL) free(auxBuffPtr); + return streamCount; +} + +/* + * Copy n characters from *srcPtr to *destPtr, then increment + * both *srcPtr and *destPtr by n. + */ +static void CopyAndAdvance(char **destPtr, char **srcPtr, int n) +{ + char *dest = *destPtr; + char *src = *srcPtr; + int i; + for (i = 0; i < n; i++) + *dest++ = *src++; + *destPtr = dest; + *srcPtr = src; +} + +/* + *---------------------------------------------------------------------- + * + * FCGX_FFlush -- + * + * Flushes any buffered output. + * + * Server-push is a legitimate application of FCGX_FFlush. + * Otherwise, FCGX_FFlush is not very useful, since FCGX_Accept + * does it implicitly. FCGX_FFlush may reduce performance + * by increasing the total number of operating system calls + * the application makes. + * + * Results: + * EOF (-1) if an error occurred. + * + *---------------------------------------------------------------------- + */ +int FCGX_FFlush(FCGX_Stream *stream) +{ + if(!stream || stream->isClosed || stream->isReader) + return 0; + stream->emptyBuffProc(stream, FALSE); + return (stream->isClosed) ? -1 : 0; +} + +/* + *---------------------------------------------------------------------- + * + * FCGX_FClose -- + * + * Performs FCGX_FFlush and closes the stream. + * + * This is not a very useful operation, since FCGX_Accept + * does it implicitly. Closing the out stream before the + * err stream results in an extra write if there's nothing + * in the err stream, and therefore reduces performance. + * + * Results: + * EOF (-1) if an error occurred. + * + *---------------------------------------------------------------------- + */ +int FCGX_FClose(FCGX_Stream *stream) +{ + if (stream == NULL) return 0; + + if(!stream->wasFCloseCalled) { + if(!stream->isReader) { + stream->emptyBuffProc(stream, TRUE); + } + stream->wasFCloseCalled = TRUE; + stream->isClosed = TRUE; + if(stream->isReader) { + stream->wrNext = stream->stop = stream->rdNext; + } else { + stream->rdNext = stream->stop = stream->wrNext; + } + } + return (stream->FCGI_errno == 0) ? 0 : EOF; +} + +/* + *---------------------------------------------------------------------- + * + * SetError -- + * + * An error has occurred; save the error code in the stream + * for diagnostic purposes and set the stream state so that + * reads return EOF and writes have no effect. + * + *---------------------------------------------------------------------- + */ +static void SetError(FCGX_Stream *stream, int FCGI_errno) +{ + /* + * Preserve only the first error. + */ + if(stream->FCGI_errno == 0) { + stream->FCGI_errno = FCGI_errno; + stream->isClosed = TRUE; + } +} + +/* + *---------------------------------------------------------------------- + * + * FCGX_GetError -- + * + * Return the stream error code. 0 means no error, > 0 + * is an errno(2) error, < 0 is an FCGX_errno error. + * + *---------------------------------------------------------------------- + */ +int FCGX_GetError(FCGX_Stream *stream) { + return stream->FCGI_errno; +} + +/* + *---------------------------------------------------------------------- + * + * FCGX_ClearError -- + * + * Clear the stream error code and end-of-file indication. + * + *---------------------------------------------------------------------- + */ +void FCGX_ClearError(FCGX_Stream *stream) { + stream->FCGI_errno = 0; + /* + * stream->isClosed = FALSE; + * XXX: should clear isClosed but work is needed to make it safe + * to do so. For example, if an application calls FClose, gets + * an I/O error on the write, calls ClearError and retries + * the FClose, FClose (really EmptyBuffProc) will write a second + * EOF record. If an application calls PutChar instead of FClose + * after the ClearError, the application will write more data. + * The stream's state must discriminate between various states + * of the stream that are now all lumped under isClosed. + */ +} + +/* + *====================================================================== + * Parameters + *====================================================================== + */ + +/* + * A vector of pointers representing the parameters received + * by a FastCGI application server, with the vector's length + * and last valid element so adding new parameters is efficient. + */ + +typedef struct Params { + FCGX_ParamArray vec; /* vector of strings */ + int length; /* number of string vec can hold */ + char **cur; /* current item in vec; *cur == NULL */ +} Params; +typedef Params *ParamsPtr; + +/* + *---------------------------------------------------------------------- + * + * NewParams -- + * + * Creates a new Params structure. + * + * Results: + * Pointer to the new structure. + * + *---------------------------------------------------------------------- + */ +static ParamsPtr NewParams(int length) +{ + ParamsPtr result; + result = (Params *)Malloc(sizeof(Params)); + result->vec = (char **)Malloc(length * sizeof(char *)); + result->length = length; + result->cur = result->vec; + *result->cur = NULL; + return result; +} + +/* + *---------------------------------------------------------------------- + * + * FreeParams -- + * + * Frees a Params structure and all the parameters it contains. + * + * Side effects: + * env becomes invalid. + * + *---------------------------------------------------------------------- + */ +static void FreeParams(ParamsPtr *paramsPtrPtr) +{ + ParamsPtr paramsPtr = *paramsPtrPtr; + char **p; + if(paramsPtr == NULL) { + return; + } + for (p = paramsPtr->vec; p < paramsPtr->cur; p++) { + free(*p); + } + free(paramsPtr->vec); + free(paramsPtr); + *paramsPtrPtr = NULL; +} + +/* + *---------------------------------------------------------------------- + * + * PutParam -- + * + * Add a name/value pair to a Params structure. + * + * Results: + * None. + * + * Side effects: + * Parameters structure updated. + * + *---------------------------------------------------------------------- + */ +static void PutParam(ParamsPtr paramsPtr, char *nameValue) +{ + int size; + + *paramsPtr->cur++ = nameValue; + size = paramsPtr->cur - paramsPtr->vec; + if(size >= paramsPtr->length) { + paramsPtr->length *= 2; + paramsPtr->vec = (FCGX_ParamArray)realloc(paramsPtr->vec, paramsPtr->length * sizeof(char *)); + paramsPtr->cur = paramsPtr->vec + size; + } + *paramsPtr->cur = NULL; +} + +/* + *---------------------------------------------------------------------- + * + * FCGX_GetParam -- obtain value of FCGI parameter in environment + * + * + * Results: + * Value bound to name, NULL if name not present in the + * environment envp. Caller must not mutate the result + * or retain it past the end of this request. + * + *---------------------------------------------------------------------- + */ +char *FCGX_GetParam(const char *name, FCGX_ParamArray envp) +{ + int len; + char **p; + + if (name == NULL || envp == NULL) return NULL; + + len = strlen(name); + + for (p = envp; *p; ++p) { + if((strncmp(name, *p, len) == 0) && ((*p)[len] == '=')) { + return *p+len+1; + } + } + return NULL; +} + +/* + *---------------------------------------------------------------------- + * + * Start of FastCGI-specific code + * + *---------------------------------------------------------------------- + */ + +/* + *---------------------------------------------------------------------- + * + * ReadParams -- + * + * Reads FastCGI name-value pairs from stream until EOF. Converts + * each pair to name=value format and adds it to Params structure. + * + *---------------------------------------------------------------------- + */ +static int ReadParams(Params *paramsPtr, FCGX_Stream *stream) +{ + int nameLen, valueLen; + unsigned char lenBuff[3]; + char *nameValue; + + while((nameLen = FCGX_GetChar(stream)) != EOF) { + /* + * Read name length (one or four bytes) and value length + * (one or four bytes) from stream. + */ + if((nameLen & 0x80) != 0) { + if(FCGX_GetStr((char *) &lenBuff[0], 3, stream) != 3) { + SetError(stream, FCGX_PARAMS_ERROR); + return -1; + } + nameLen = ((nameLen & 0x7f) << 24) + (lenBuff[0] << 16) + + (lenBuff[1] << 8) + lenBuff[2]; + } + if((valueLen = FCGX_GetChar(stream)) == EOF) { + SetError(stream, FCGX_PARAMS_ERROR); + return -1; + } + if((valueLen & 0x80) != 0) { + if(FCGX_GetStr((char *) &lenBuff[0], 3, stream) != 3) { + SetError(stream, FCGX_PARAMS_ERROR); + return -1; + } + valueLen = ((valueLen & 0x7f) << 24) + (lenBuff[0] << 16) + + (lenBuff[1] << 8) + lenBuff[2]; + } + /* + * nameLen and valueLen are now valid; read the name and value + * from stream and construct a standard environment entry. + */ + nameValue = (char *)Malloc(nameLen + valueLen + 2); + if(FCGX_GetStr(nameValue, nameLen, stream) != nameLen) { + SetError(stream, FCGX_PARAMS_ERROR); + free(nameValue); + return -1; + } + *(nameValue + nameLen) = '='; + if(FCGX_GetStr(nameValue + nameLen + 1, valueLen, stream) + != valueLen) { + SetError(stream, FCGX_PARAMS_ERROR); + free(nameValue); + return -1; + } + *(nameValue + nameLen + valueLen + 1) = '\0'; + PutParam(paramsPtr, nameValue); + } + return 0; +} + +/* + *---------------------------------------------------------------------- + * + * MakeHeader -- + * + * Constructs an FCGI_Header struct. + * + *---------------------------------------------------------------------- + */ +static FCGI_Header MakeHeader( + int type, + int requestId, + int contentLength, + int paddingLength) +{ + FCGI_Header header; + ASSERT(contentLength >= 0 && contentLength <= FCGI_MAX_LENGTH); + ASSERT(paddingLength >= 0 && paddingLength <= 0xff); + header.version = FCGI_VERSION_1; + header.type = (unsigned char) type; + header.requestIdB1 = (unsigned char) ((requestId >> 8) & 0xff); + header.requestIdB0 = (unsigned char) ((requestId ) & 0xff); + header.contentLengthB1 = (unsigned char) ((contentLength >> 8) & 0xff); + header.contentLengthB0 = (unsigned char) ((contentLength ) & 0xff); + header.paddingLength = (unsigned char) paddingLength; + header.reserved = 0; + return header; +} + +/* + *---------------------------------------------------------------------- + * + * MakeEndRequestBody -- + * + * Constructs an FCGI_EndRequestBody struct. + * + *---------------------------------------------------------------------- + */ +static FCGI_EndRequestBody MakeEndRequestBody( + int appStatus, + int protocolStatus) +{ + FCGI_EndRequestBody body; + body.appStatusB3 = (unsigned char) ((appStatus >> 24) & 0xff); + body.appStatusB2 = (unsigned char) ((appStatus >> 16) & 0xff); + body.appStatusB1 = (unsigned char) ((appStatus >> 8) & 0xff); + body.appStatusB0 = (unsigned char) ((appStatus ) & 0xff); + body.protocolStatus = (unsigned char) protocolStatus; + memset(body.reserved, 0, sizeof(body.reserved)); + return body; +} + +/* + *---------------------------------------------------------------------- + * + * MakeUnknownTypeBody -- + * + * Constructs an FCGI_MakeUnknownTypeBody struct. + * + *---------------------------------------------------------------------- + */ +static FCGI_UnknownTypeBody MakeUnknownTypeBody( + int type) +{ + FCGI_UnknownTypeBody body; + body.type = (unsigned char) type; + memset(body.reserved, 0, sizeof(body.reserved)); + return body; +} + +/* + *---------------------------------------------------------------------- + * + * AlignInt8 -- + * + * Returns the smallest integer greater than or equal to n + * that's a multiple of 8. + * + *---------------------------------------------------------------------- + */ +static int AlignInt8(unsigned n) { + return (n + 7) & (UINT_MAX - 7); +} + +/* + *---------------------------------------------------------------------- + * + * AlignPtr8 -- + * + * Returns the smallest pointer greater than or equal to p + * that's a multiple of 8. + * + *---------------------------------------------------------------------- + */ +static unsigned char *AlignPtr8(unsigned char *p) { + unsigned long u = (unsigned long) p; + u = ((u + 7) & (ULONG_MAX - 7)) - u; + return p + u; +} + + +/* + * State associated with a stream + */ +typedef struct FCGX_Stream_Data { + unsigned char *buff; /* buffer after alignment */ + int bufflen; /* number of bytes buff can store */ + unsigned char *mBuff; /* buffer as returned by Malloc */ + unsigned char *buffStop; /* reader: last valid byte + 1 of entire buffer. + * stop generally differs from buffStop for + * readers because of record structure. + * writer: buff + bufflen */ + int type; /* reader: FCGI_PARAMS or FCGI_STDIN + * writer: FCGI_STDOUT or FCGI_STDERR */ + int eorStop; /* reader: stop stream at end-of-record */ + int skip; /* reader: don't deliver content bytes */ + int contentLen; /* reader: bytes of unread content */ + int paddingLen; /* reader: bytes of unread padding */ + int isAnythingWritten; /* writer: data has been written to ipcFd */ + int rawWrite; /* writer: write data without stream headers */ + FCGX_Request *reqDataPtr; /* request data not specific to one stream */ +} FCGX_Stream_Data; + +/* + *---------------------------------------------------------------------- + * + * WriteCloseRecords -- + * + * Writes an EOF record for the stream content if necessary. + * If this is the last writer to close, writes an FCGI_END_REQUEST + * record. + * + *---------------------------------------------------------------------- + */ +static void WriteCloseRecords(struct FCGX_Stream *stream) +{ + FCGX_Stream_Data *data = (FCGX_Stream_Data *)stream->data; + /* + * Enter rawWrite mode so final records won't be encapsulated as + * stream data. + */ + data->rawWrite = TRUE; + /* + * Generate EOF for stream content if needed. + */ + if(!(data->type == FCGI_STDERR + && stream->wrNext == data->buff + && !data->isAnythingWritten)) { + FCGI_Header header; + header = MakeHeader(data->type, data->reqDataPtr->requestId, 0, 0); + FCGX_PutStr((char *) &header, sizeof(header), stream); + }; + /* + * Generate FCGI_END_REQUEST record if needed. + */ + if(data->reqDataPtr->nWriters == 1) { + FCGI_EndRequestRecord endRequestRecord; + endRequestRecord.header = MakeHeader(FCGI_END_REQUEST, + data->reqDataPtr->requestId, + sizeof(endRequestRecord.body), 0); + endRequestRecord.body = MakeEndRequestBody( + data->reqDataPtr->appStatus, FCGI_REQUEST_COMPLETE); + FCGX_PutStr((char *) &endRequestRecord, + sizeof(endRequestRecord), stream); + } + data->reqDataPtr->nWriters--; +} + + + +static int write_it_all(int fd, char *buf, int len) +{ + int wrote; + + while (len) { + wrote = OS_Write(fd, buf, len); + if (wrote < 0) + return wrote; + len -= wrote; + buf += wrote; + } + return len; +} + +/* + *---------------------------------------------------------------------- + * + * EmptyBuffProc -- + * + * Encapsulates any buffered stream content in a FastCGI + * record. Writes the data, making the buffer empty. + * + *---------------------------------------------------------------------- + */ +static void EmptyBuffProc(struct FCGX_Stream *stream, int doClose) +{ + FCGX_Stream_Data *data = (FCGX_Stream_Data *)stream->data; + int cLen, eLen; + /* + * If the buffer contains stream data, fill in the header. + * Pad the record to a multiple of 8 bytes in length. Padding + * can't overflow the buffer because the buffer is a multiple + * of 8 bytes in length. If the buffer contains no stream + * data, reclaim the space reserved for the header. + */ + if(!data->rawWrite) { + cLen = stream->wrNext - data->buff - sizeof(FCGI_Header); + if(cLen > 0) { + eLen = AlignInt8(cLen); + /* + * Giving the padding a well-defined value keeps Purify happy. + */ + memset(stream->wrNext, 0, eLen - cLen); + stream->wrNext += eLen - cLen; + *((FCGI_Header *) data->buff) + = MakeHeader(data->type, + data->reqDataPtr->requestId, cLen, eLen - cLen); + } else { + stream->wrNext = data->buff; + } + } + if(doClose) { + WriteCloseRecords(stream); + }; + if (stream->wrNext != data->buff) { + data->isAnythingWritten = TRUE; + if (write_it_all(data->reqDataPtr->ipcFd, (char *)data->buff, stream->wrNext - data->buff) < 0) { + SetError(stream, OS_Errno); + return; + } + stream->wrNext = data->buff; + } + /* + * The buffer is empty. + */ + if(!data->rawWrite) { + stream->wrNext += sizeof(FCGI_Header); + } +} + +/* + * Return codes for Process* functions + */ +#define STREAM_RECORD 0 +#define SKIP 1 +#define BEGIN_RECORD 2 +#define MGMT_RECORD 3 + +/* + *---------------------------------------------------------------------- + * + * ProcessManagementRecord -- + * + * Reads and responds to a management record. The only type of + * management record this library understands is FCGI_GET_VALUES. + * The only variables that this library's FCGI_GET_VALUES + * understands are FCGI_MAX_CONNS, FCGI_MAX_REQS, and FCGI_MPXS_CONNS. + * Ignore other FCGI_GET_VALUES variables; respond to other + * management records with a FCGI_UNKNOWN_TYPE record. + * + *---------------------------------------------------------------------- + */ +static int ProcessManagementRecord(int type, FCGX_Stream *stream) +{ + FCGX_Stream_Data *data = (FCGX_Stream_Data *)stream->data; + ParamsPtr paramsPtr = NewParams(3); + char **pPtr; + char response[64]; /* 64 = 8 + 3*(1+1+14+1)* + padding */ + char *responseP = &response[FCGI_HEADER_LEN]; + char *name, value = '\0'; + int len, paddedLen; + if(type == FCGI_GET_VALUES) { + ReadParams(paramsPtr, stream); + if((FCGX_GetError(stream) != 0) || (data->contentLen != 0)) { + FreeParams(¶msPtr); + return FCGX_PROTOCOL_ERROR; + } + for (pPtr = paramsPtr->vec; pPtr < paramsPtr->cur; pPtr++) { + name = *pPtr; + *(strchr(name, '=')) = '\0'; + if(strcmp(name, FCGI_MAX_CONNS) == 0) { + value = '1'; + } else if(strcmp(name, FCGI_MAX_REQS) == 0) { + value = '1'; + } else if(strcmp(name, FCGI_MPXS_CONNS) == 0) { + value = '0'; + } else { + name = NULL; + } + if(name != NULL) { + len = strlen(name); + sprintf(responseP, "%c%c%s%c", len, 1, name, value); + responseP += len + 3; + } + } + len = responseP - &response[FCGI_HEADER_LEN]; + paddedLen = AlignInt8(len); + *((FCGI_Header *) response) + = MakeHeader(FCGI_GET_VALUES_RESULT, FCGI_NULL_REQUEST_ID, + len, paddedLen - len); + FreeParams(¶msPtr); + } else { + paddedLen = len = sizeof(FCGI_UnknownTypeBody); + ((FCGI_UnknownTypeRecord *) response)->header + = MakeHeader(FCGI_UNKNOWN_TYPE, FCGI_NULL_REQUEST_ID, + len, 0); + ((FCGI_UnknownTypeRecord *) response)->body + = MakeUnknownTypeBody(type); + } + if (write_it_all(data->reqDataPtr->ipcFd, response, FCGI_HEADER_LEN + paddedLen) < 0) { + SetError(stream, OS_Errno); + return -1; + } + + return MGMT_RECORD; +} + +/* + *---------------------------------------------------------------------- + * + * ProcessBeginRecord -- + * + * Reads an FCGI_BEGIN_REQUEST record. + * + * Results: + * BEGIN_RECORD for normal return. FCGX_PROTOCOL_ERROR for + * protocol error. SKIP for attempt to multiplex + * connection. -1 for error from write (errno in stream). + * + * Side effects: + * In case of BEGIN_RECORD return, stores requestId, role, + * keepConnection values, and sets isBeginProcessed = TRUE. + * + *---------------------------------------------------------------------- + */ +static int ProcessBeginRecord(int requestId, FCGX_Stream *stream) +{ + FCGX_Stream_Data *data = (FCGX_Stream_Data *)stream->data; + FCGI_BeginRequestBody body; + if(requestId == 0 || data->contentLen != sizeof(body)) { + return FCGX_PROTOCOL_ERROR; + } + if(data->reqDataPtr->isBeginProcessed) { + /* + * The Web server is multiplexing the connection. This library + * doesn't know how to handle multiplexing, so respond with + * FCGI_END_REQUEST{protocolStatus = FCGI_CANT_MPX_CONN} + */ + FCGI_EndRequestRecord endRequestRecord; + endRequestRecord.header = MakeHeader(FCGI_END_REQUEST, + requestId, sizeof(endRequestRecord.body), 0); + endRequestRecord.body + = MakeEndRequestBody(0, FCGI_CANT_MPX_CONN); + if (write_it_all(data->reqDataPtr->ipcFd, (char *)&endRequestRecord, sizeof(endRequestRecord)) < 0) { + SetError(stream, OS_Errno); + return -1; + } + + return SKIP; + } + /* + * Accept this new request. Read the record body. + */ + data->reqDataPtr->requestId = requestId; + if(FCGX_GetStr((char *) &body, sizeof(body), stream) + != sizeof(body)) { + return FCGX_PROTOCOL_ERROR; + } + data->reqDataPtr->keepConnection = (body.flags & FCGI_KEEP_CONN); + data->reqDataPtr->role = (body.roleB1 << 8) + body.roleB0; + data->reqDataPtr->isBeginProcessed = TRUE; + return BEGIN_RECORD; +} + +/* + *---------------------------------------------------------------------- + * + * ProcessHeader -- + * + * Interprets FCGI_Header. Processes FCGI_BEGIN_REQUEST and + * management records here; extracts information from stream + * records (FCGI_PARAMS, FCGI_STDIN) into stream. + * + * Results: + * >= 0 for a normal return, < 0 for error + * + * Side effects: + * XXX: Many (more than there used to be). + * If !stream->isRequestIdSet, ProcessHeader initializes + * stream->requestId from header and sets stream->isRequestIdSet + * to TRUE. ProcessHeader also sets stream->contentLen to header's + * contentLength, and sets stream->paddingLen to the header's + * paddingLength. + * + *---------------------------------------------------------------------- + */ +static int ProcessHeader(FCGI_Header header, FCGX_Stream *stream) +{ + FCGX_Stream_Data *data = (FCGX_Stream_Data *)stream->data; + int requestId; + if(header.version != FCGI_VERSION_1) { + return FCGX_UNSUPPORTED_VERSION; + } + requestId = (header.requestIdB1 << 8) + + header.requestIdB0; + data->contentLen = (header.contentLengthB1 << 8) + + header.contentLengthB0; + data->paddingLen = header.paddingLength; + if(header.type == FCGI_BEGIN_REQUEST) { + return ProcessBeginRecord(requestId, stream); + } + if(requestId == FCGI_NULL_REQUEST_ID) { + return ProcessManagementRecord(header.type, stream); + } + if(requestId != data->reqDataPtr->requestId) { + return SKIP; + } + if(header.type != data->type) { + return FCGX_PROTOCOL_ERROR; + } + return STREAM_RECORD; +} + +/* + *---------------------------------------------------------------------- + * + * FillBuffProc -- + * + * Reads bytes from the ipcFd, supplies bytes to a stream client. + * + *---------------------------------------------------------------------- + */ +static void FillBuffProc(FCGX_Stream *stream) +{ + FCGX_Stream_Data *data = (FCGX_Stream_Data *)stream->data; + FCGI_Header header; + int headerLen = 0; + int status, count; + + for (;;) { + /* + * If data->buff is empty, do a read. + */ + if(stream->rdNext == data->buffStop) { + count = OS_Read(data->reqDataPtr->ipcFd, (char *)data->buff, + data->bufflen); + if(count <= 0) { + SetError(stream, (count == 0 ? FCGX_PROTOCOL_ERROR : OS_Errno)); + return; + } + stream->rdNext = data->buff; + data->buffStop = data->buff + count; + } + /* + * Now data->buff is not empty. If the current record contains + * more content bytes, deliver all that are present in data->buff. + */ + if(data->contentLen > 0) { + count = min(data->contentLen, data->buffStop - stream->rdNext); + data->contentLen -= count; + if(!data->skip) { + stream->wrNext = stream->stop = stream->rdNext + count; + return; + } else { + stream->rdNext += count; + if(data->contentLen > 0) { + continue; + } else { + data->skip = FALSE; + } + } + } + /* + * If the current record (whose content has been fully consumed by + * the client) was padded, skip over the padding bytes. + */ + if(data->paddingLen > 0) { + count = min(data->paddingLen, data->buffStop - stream->rdNext); + data->paddingLen -= count; + stream->rdNext += count; + if(data->paddingLen > 0) { + continue; + } + } + /* + * All done with the current record, including the padding. + * If we're in a recursive call from ProcessHeader, deliver EOF. + */ + if(data->eorStop) { + stream->stop = stream->rdNext; + stream->isClosed = TRUE; + return; + } + /* + * Fill header with bytes from the input buffer. + */ + count = min((int)sizeof(header) - headerLen, + data->buffStop - stream->rdNext); + memcpy(((char *)(&header)) + headerLen, stream->rdNext, count); + headerLen += count; + stream->rdNext += count; + if(headerLen < sizeof(header)) { + continue; + }; + headerLen = 0; + /* + * Interpret header. eorStop prevents ProcessHeader from reading + * past the end-of-record when using stream to read content. + */ + data->eorStop = TRUE; + stream->stop = stream->rdNext; + status = ProcessHeader(header, stream); + data->eorStop = FALSE; + stream->isClosed = FALSE; + switch(status) { + case STREAM_RECORD: + /* + * If this stream record header marked the end of stream + * data deliver EOF to the stream client, otherwise loop + * and deliver data. + * + * XXX: If this is final stream and + * stream->rdNext != data->buffStop, buffered + * data is next request (server pipelining)? + */ + if(data->contentLen == 0) { + stream->wrNext = stream->stop = stream->rdNext; + stream->isClosed = TRUE; + return; + } + break; + case SKIP: + data->skip = TRUE; + break; + case BEGIN_RECORD: + /* + * If this header marked the beginning of a new + * request, return role information to caller. + */ + return; + break; + case MGMT_RECORD: + break; + default: + ASSERT(status < 0); + SetError(stream, status); + return; + break; + } + } +} + +/* + *---------------------------------------------------------------------- + * + * NewStream -- + * + * Creates a stream to read or write from an open ipcFd. + * The stream performs reads/writes of up to bufflen bytes. + * + *---------------------------------------------------------------------- + */ +static FCGX_Stream *NewStream( + FCGX_Request *reqDataPtr, int bufflen, int isReader, int streamType) +{ + /* + * XXX: It would be a lot cleaner to have a NewStream that only + * knows about the type FCGX_Stream, with all other + * necessary data passed in. It appears that not just + * data and the two procs are needed for initializing stream, + * but also data->buff and data->buffStop. This has implications + * for procs that want to swap buffers, too. + */ + FCGX_Stream *stream = (FCGX_Stream *)Malloc(sizeof(FCGX_Stream)); + FCGX_Stream_Data *data = (FCGX_Stream_Data *)Malloc(sizeof(FCGX_Stream_Data)); + data->reqDataPtr = reqDataPtr; + bufflen = AlignInt8(min(max(bufflen, 32), FCGI_MAX_LENGTH + 1)); + data->bufflen = bufflen; + data->mBuff = (unsigned char *)Malloc(bufflen); + data->buff = AlignPtr8(data->mBuff); + if(data->buff != data->mBuff) { + data->bufflen -= 8; + } + if(isReader) { + data->buffStop = data->buff; + } else { + data->buffStop = data->buff + data->bufflen; + } + data->type = streamType; + data->eorStop = FALSE; + data->skip = FALSE; + data->contentLen = 0; + data->paddingLen = 0; + data->isAnythingWritten = FALSE; + data->rawWrite = FALSE; + + stream->data = data; + stream->isReader = isReader; + stream->isClosed = FALSE; + stream->wasFCloseCalled = FALSE; + stream->FCGI_errno = 0; + if(isReader) { + stream->fillBuffProc = FillBuffProc; + stream->emptyBuffProc = NULL; + stream->rdNext = data->buff; + stream->stop = stream->rdNext; + stream->stopUnget = data->buff; + stream->wrNext = stream->stop; + } else { + stream->fillBuffProc = NULL; + stream->emptyBuffProc = EmptyBuffProc; + stream->wrNext = data->buff + sizeof(FCGI_Header); + stream->stop = data->buffStop; + stream->stopUnget = NULL; + stream->rdNext = stream->stop; + } + return stream; +} + +/* + *---------------------------------------------------------------------- + * + * FCGX_FreeStream -- + * + * Frees all storage allocated when *streamPtr was created, + * and nulls out *streamPtr. + * + *---------------------------------------------------------------------- + */ +void FCGX_FreeStream(FCGX_Stream **streamPtr) +{ + _FCGX_FreeStream(streamPtr, TRUE); +} + +void _FCGX_FreeStream(FCGX_Stream **streamPtr, BOOL freeData) +{ + FCGX_Stream *stream = *streamPtr; + FCGX_Stream_Data *data; + if(stream == NULL) { + return; + } + data = (FCGX_Stream_Data *)stream->data; + if (freeData && data->reqDataPtr) free(data->reqDataPtr); + data->reqDataPtr = NULL; + free(data->mBuff); + free(data); + free(stream); + *streamPtr = NULL; +} + +/* + *---------------------------------------------------------------------- + * + * SetReaderType -- + * + * Re-initializes the stream to read data of the specified type. + * + *---------------------------------------------------------------------- + */ +static FCGX_Stream *SetReaderType(FCGX_Stream *stream, int streamType) +{ + FCGX_Stream_Data *data = (FCGX_Stream_Data *)stream->data; + ASSERT(stream->isReader); + data->type = streamType; + data->eorStop = FALSE; + data->skip = FALSE; + data->contentLen = 0; + data->paddingLen = 0; + stream->wrNext = stream->stop = stream->rdNext; + stream->isClosed = FALSE; + return stream; +} + +/* + *---------------------------------------------------------------------- + * + * NewReader -- + * + * Creates a stream to read streamType records for the given + * request. The stream performs OS reads of up to bufflen bytes. + * + *---------------------------------------------------------------------- + */ +static FCGX_Stream *NewReader(FCGX_Request *reqDataPtr, int bufflen, int streamType) +{ + return NewStream(reqDataPtr, bufflen, TRUE, streamType); +} + +/* + *---------------------------------------------------------------------- + * + * NewWriter -- + * + * Creates a stream to write streamType FastCGI records, using + * the ipcFd and RequestId contained in *reqDataPtr. + * The stream performs OS writes of up to bufflen bytes. + * + *---------------------------------------------------------------------- + */ +static FCGX_Stream *NewWriter(FCGX_Request *reqDataPtr, int bufflen, int streamType) +{ + return NewStream(reqDataPtr, bufflen, FALSE, streamType); +} + +/* + *---------------------------------------------------------------------- + * + * FCGX_CreateWriter -- + * + * Creates a stream to write streamType FastCGI records, using + * the given ipcFd and request Id. This function is provided + * for use by cgi-fcgi. In order to be defensive against misuse, + * this function leaks a little storage; cgi-fcgi doesn't care. + * + *---------------------------------------------------------------------- + */ +FCGX_Stream *FCGX_CreateWriter( + int ipcFd, + int requestId, + int bufflen, + int streamType) +{ + FCGX_Request *reqDataPtr = (FCGX_Request *)Malloc(sizeof(FCGX_Request)); + reqDataPtr->ipcFd = ipcFd; + reqDataPtr->requestId = requestId; + /* + * Suppress writing an FCGI_END_REQUEST record. + */ + reqDataPtr->nWriters = 2; + return NewWriter(reqDataPtr, bufflen, streamType); +} + +/* + *====================================================================== + * Control + *====================================================================== + */ + +/* + *---------------------------------------------------------------------- + * + * FCGX_IsCGI -- + * + * This routine determines if the process is running as a CGI or + * FastCGI process. The distinction is made by determining whether + * FCGI_LISTENSOCK_FILENO is a listener ipcFd or the end of a + * pipe (ie. standard in). + * + * Results: + * TRUE if the process is a CGI process, FALSE if FastCGI. + * + *---------------------------------------------------------------------- + */ +int FCGX_IsCGI(void) +{ + if (isFastCGI != -1) { + return !isFastCGI; + } + + if (!libInitialized) { + int rc = FCGX_Init(); + if (rc) { + /* exit() isn't great, but hey */ + //exit((rc < 0) ? rc : -rc); + return 0; + } + } + + isFastCGI = OS_IsFcgi(FCGI_LISTENSOCK_FILENO); + + return !isFastCGI; +} + +/* + *---------------------------------------------------------------------- + * + * FCGX_Finish -- + * + * Finishes the current request from the HTTP server. + * + * Side effects: + * + * Finishes the request accepted by (and frees any + * storage allocated by) the previous call to FCGX_Accept. + * + * DO NOT retain pointers to the envp array or any strings + * contained in it (e.g. to the result of calling FCGX_GetParam), + * since these will be freed by the next call to FCGX_Finish + * or FCGX_Accept. + * + *---------------------------------------------------------------------- + */ + +void FCGX_Finish(void) +{ + FCGX_Finish_r(&the_request); +} + +/* + *---------------------------------------------------------------------- + * + * FCGX_Finish_r -- + * + * Finishes the current request from the HTTP server. + * + * Side effects: + * + * Finishes the request accepted by (and frees any + * storage allocated by) the previous call to FCGX_Accept. + * + * DO NOT retain pointers to the envp array or any strings + * contained in it (e.g. to the result of calling FCGX_GetParam), + * since these will be freed by the next call to FCGX_Finish + * or FCGX_Accept. + * + *---------------------------------------------------------------------- + */ +void FCGX_Finish_r(FCGX_Request *reqDataPtr) +{ + int close; + + if (reqDataPtr == NULL) { + return; + } + + close = !reqDataPtr->keepConnection; + + /* This should probably use a 'status' member instead of 'in' */ + if (reqDataPtr->in) { + close |= FCGX_FClose(reqDataPtr->err); + close |= FCGX_FClose(reqDataPtr->out); + + close |= FCGX_GetError(reqDataPtr->in); + } + + FCGX_Free(reqDataPtr, close); +} + +void FCGX_Free(FCGX_Request * request, int close) +{ + if (request == NULL) + return; + + _FCGX_FreeStream(&request->in, FALSE); + _FCGX_FreeStream(&request->out, FALSE); + _FCGX_FreeStream(&request->err, FALSE); + FreeParams(&request->paramsPtr); + + if (close) { + OS_IpcClose(request->ipcFd); + request->ipcFd = -1; + } +} + +int FCGX_OpenSocket(const char *path, int backlog) +{ + int rc = OS_CreateLocalIpcFd(path, backlog, 1); + if (rc == FCGI_LISTENSOCK_FILENO && isFastCGI == 0) { + /* XXX probably need to call OS_LibInit() again for Win */ + isFastCGI = 1; + } + return rc; +} + +int FCGX_InitRequest(FCGX_Request *request, int sock, int flags) +{ + memset(request, 0, sizeof(FCGX_Request)); + + /* @@@ Should check that sock is open and listening */ + request->listen_sock = sock; + + /* @@@ Should validate against "known" flags */ + request->flags = flags; + + request->ipcFd = -1; + + return 0; +} + +/* + *---------------------------------------------------------------------- + * + * FCGX_Init -- + * + * Initilize the FCGX library. This is called by FCGX_Accept() + * but must be called by the user when using FCGX_Accept_r(). + * + * Results: + * 0 for successful call. + * + *---------------------------------------------------------------------- + */ +int FCGX_Init(void) +{ + char *p; + + if (libInitialized) { + return 0; + } + + FCGX_InitRequest(&the_request, FCGI_LISTENSOCK_FILENO, 0); + + if (OS_LibInit(NULL) == -1) { + return OS_Errno ? OS_Errno : -9997; + } + + p = getenv("FCGI_WEB_SERVER_ADDRS"); + webServerAddressList = p ? StringCopy(p) : NULL; + + libInitialized = 1; + return 0; +} + +/* + *---------------------------------------------------------------------- + * + * FCGX_Accept -- + * + * Accepts a new request from the HTTP server. + * + * Results: + * 0 for successful call, -1 for error. + * + * Side effects: + * + * Finishes the request accepted by (and frees any + * storage allocated by) the previous call to FCGX_Accept. + * Creates input, output, and error streams and + * assigns them to *in, *out, and *err respectively. + * Creates a parameters data structure to be accessed + * via getenv(3) (if assigned to environ) or by FCGX_GetParam + * and assigns it to *envp. + * + * DO NOT retain pointers to the envp array or any strings + * contained in it (e.g. to the result of calling FCGX_GetParam), + * since these will be freed by the next call to FCGX_Finish + * or FCGX_Accept. + * + *---------------------------------------------------------------------- + */ + +int FCGX_Accept( + FCGX_Stream **in, + FCGX_Stream **out, + FCGX_Stream **err, + FCGX_ParamArray *envp) +{ + int rc; + + if (! libInitialized) { + rc = FCGX_Init(); + if (rc) { + return rc; + } + } + + rc = FCGX_Accept_r(&the_request); + + *in = the_request.in; + *out = the_request.out; + *err = the_request.err; + *envp = the_request.envp; + + return rc; +} + +/* + *---------------------------------------------------------------------- + * + * FCGX_Accept_r -- + * + * Accepts a new request from the HTTP server. + * + * Results: + * 0 for successful call, -1 for error. + * + * Side effects: + * + * Finishes the request accepted by (and frees any + * storage allocated by) the previous call to FCGX_Accept. + * Creates input, output, and error streams and + * assigns them to *in, *out, and *err respectively. + * Creates a parameters data structure to be accessed + * via getenv(3) (if assigned to environ) or by FCGX_GetParam + * and assigns it to *envp. + * + * DO NOT retain pointers to the envp array or any strings + * contained in it (e.g. to the result of calling FCGX_GetParam), + * since these will be freed by the next call to FCGX_Finish + * or FCGX_Accept. + * + *---------------------------------------------------------------------- + */ +int FCGX_Accept_r(FCGX_Request *reqDataPtr) +{ + if (!libInitialized) { + return -9998; + } + + /* Finish the current request, if any. */ + FCGX_Finish_r(reqDataPtr); + + for (;;) { + /* + * If a connection isn't open, accept a new connection (blocking). + * If an OS error occurs in accepting the connection, + * return -1 to the caller, who should exit. + */ + if (reqDataPtr->ipcFd < 0) { + int fail_on_intr = reqDataPtr->flags & FCGI_FAIL_ACCEPT_ON_INTR; + + reqDataPtr->ipcFd = OS_Accept(reqDataPtr->listen_sock, fail_on_intr, webServerAddressList); + if (reqDataPtr->ipcFd < 0) { + return (errno > 0) ? (0 - errno) : -9999; + } + } + /* + * A connection is open. Read from the connection in order to + * get the request's role and environment. If protocol or other + * errors occur, close the connection and try again. + */ + reqDataPtr->isBeginProcessed = FALSE; + reqDataPtr->in = NewReader(reqDataPtr, 8192, 0); + FillBuffProc(reqDataPtr->in); + if(!reqDataPtr->isBeginProcessed) { + goto TryAgain; + } + { + char *roleStr; + switch(reqDataPtr->role) { + case FCGI_RESPONDER: + roleStr = "FCGI_ROLE=RESPONDER"; + break; + case FCGI_AUTHORIZER: + roleStr = "FCGI_ROLE=AUTHORIZER"; + break; + case FCGI_FILTER: + roleStr = "FCGI_ROLE=FILTER"; + break; + default: + goto TryAgain; + } + reqDataPtr->paramsPtr = NewParams(30); + PutParam(reqDataPtr->paramsPtr, StringCopy(roleStr)); + } + SetReaderType(reqDataPtr->in, FCGI_PARAMS); + if(ReadParams(reqDataPtr->paramsPtr, reqDataPtr->in) >= 0) { + /* + * Finished reading the environment. No errors occurred, so + * leave the connection-retry loop. + */ + break; + } + + /* + * Close the connection and try again. + */ +TryAgain: + FCGX_Free(reqDataPtr, 1); + + } /* for (;;) */ + /* + * Build the remaining data structures representing the new + * request and return successfully to the caller. + */ + SetReaderType(reqDataPtr->in, FCGI_STDIN); + reqDataPtr->out = NewWriter(reqDataPtr, 8192, FCGI_STDOUT); + reqDataPtr->err = NewWriter(reqDataPtr, 512, FCGI_STDERR); + reqDataPtr->nWriters = 2; + reqDataPtr->envp = reqDataPtr->paramsPtr->vec; + return 0; +} + +/* + *---------------------------------------------------------------------- + * + * FCGX_StartFilterData -- + * + * stream is an input stream for a FCGI_FILTER request. + * stream is positioned at EOF on FCGI_STDIN. + * Repositions stream to the start of FCGI_DATA. + * If the preconditions are not met (e.g. FCGI_STDIN has not + * been read to EOF) sets the stream error code to + * FCGX_CALL_SEQ_ERROR. + * + * Results: + * 0 for a normal return, < 0 for error + * + *---------------------------------------------------------------------- + */ + +int FCGX_StartFilterData(FCGX_Stream *stream) +{ + FCGX_Stream_Data *data = (FCGX_Stream_Data *)stream->data; + if(data->reqDataPtr->role != FCGI_FILTER + || !stream->isReader + || !stream->isClosed + || data->type != FCGI_STDIN) { + SetError(stream, FCGX_CALL_SEQ_ERROR); + return -1; + } + SetReaderType(stream, FCGI_DATA); + return 0; +} + +/* + *---------------------------------------------------------------------- + * + * FCGX_SetExitStatus -- + * + * Sets the exit status for stream's request. The exit status + * is the status code the request would have exited with, had + * the request been run as a CGI program. You can call + * SetExitStatus several times during a request; the last call + * before the request ends determines the value. + * + *---------------------------------------------------------------------- + */ + +void FCGX_SetExitStatus(int status, FCGX_Stream *stream) +{ + FCGX_Stream_Data *data = (FCGX_Stream_Data *)stream->data; + data->reqDataPtr->appStatus = status; +} + diff --git a/sapi/cgi/libfcgi/include/fastcgi.h b/sapi/cgi/libfcgi/include/fastcgi.h new file mode 100644 index 00000000000..36b4725ba1e --- /dev/null +++ b/sapi/cgi/libfcgi/include/fastcgi.h @@ -0,0 +1,136 @@ +/* + * fastcgi.h -- + * + * Defines for the FastCGI protocol. + * + * + * Copyright (c) 1995-1996 Open Market, Inc. + * + * See the file "LICENSE.TERMS" for information on usage and redistribution + * of this file, and for a DISCLAIMER OF ALL WARRANTIES. + * + * $Id$ + */ + +#ifndef _FASTCGI_H +#define _FASTCGI_H + +/* + * Listening socket file number + */ +#define FCGI_LISTENSOCK_FILENO 0 + +typedef struct { + unsigned char version; + unsigned char type; + unsigned char requestIdB1; + unsigned char requestIdB0; + unsigned char contentLengthB1; + unsigned char contentLengthB0; + unsigned char paddingLength; + unsigned char reserved; +} FCGI_Header; + +#define FCGI_MAX_LENGTH 0xffff + +/* + * Number of bytes in a FCGI_Header. Future versions of the protocol + * will not reduce this number. + */ +#define FCGI_HEADER_LEN 8 + +/* + * Value for version component of FCGI_Header + */ +#define FCGI_VERSION_1 1 + +/* + * Values for type component of FCGI_Header + */ +#define FCGI_BEGIN_REQUEST 1 +#define FCGI_ABORT_REQUEST 2 +#define FCGI_END_REQUEST 3 +#define FCGI_PARAMS 4 +#define FCGI_STDIN 5 +#define FCGI_STDOUT 6 +#define FCGI_STDERR 7 +#define FCGI_DATA 8 +#define FCGI_GET_VALUES 9 +#define FCGI_GET_VALUES_RESULT 10 +#define FCGI_UNKNOWN_TYPE 11 +#define FCGI_MAXTYPE (FCGI_UNKNOWN_TYPE) + +/* + * Value for requestId component of FCGI_Header + */ +#define FCGI_NULL_REQUEST_ID 0 + + +typedef struct { + unsigned char roleB1; + unsigned char roleB0; + unsigned char flags; + unsigned char reserved[5]; +} FCGI_BeginRequestBody; + +typedef struct { + FCGI_Header header; + FCGI_BeginRequestBody body; +} FCGI_BeginRequestRecord; + +/* + * Mask for flags component of FCGI_BeginRequestBody + */ +#define FCGI_KEEP_CONN 1 + +/* + * Values for role component of FCGI_BeginRequestBody + */ +#define FCGI_RESPONDER 1 +#define FCGI_AUTHORIZER 2 +#define FCGI_FILTER 3 + + +typedef struct { + unsigned char appStatusB3; + unsigned char appStatusB2; + unsigned char appStatusB1; + unsigned char appStatusB0; + unsigned char protocolStatus; + unsigned char reserved[3]; +} FCGI_EndRequestBody; + +typedef struct { + FCGI_Header header; + FCGI_EndRequestBody body; +} FCGI_EndRequestRecord; + +/* + * Values for protocolStatus component of FCGI_EndRequestBody + */ +#define FCGI_REQUEST_COMPLETE 0 +#define FCGI_CANT_MPX_CONN 1 +#define FCGI_OVERLOADED 2 +#define FCGI_UNKNOWN_ROLE 3 + + +/* + * Variable names for FCGI_GET_VALUES / FCGI_GET_VALUES_RESULT records + */ +#define FCGI_MAX_CONNS "FCGI_MAX_CONNS" +#define FCGI_MAX_REQS "FCGI_MAX_REQS" +#define FCGI_MPXS_CONNS "FCGI_MPXS_CONNS" + + +typedef struct { + unsigned char type; + unsigned char reserved[7]; +} FCGI_UnknownTypeBody; + +typedef struct { + FCGI_Header header; + FCGI_UnknownTypeBody body; +} FCGI_UnknownTypeRecord; + +#endif /* _FASTCGI_H */ + diff --git a/sapi/cgi/libfcgi/include/fcgi_config.h b/sapi/cgi/libfcgi/include/fcgi_config.h new file mode 100644 index 00000000000..81ff264d9f5 --- /dev/null +++ b/sapi/cgi/libfcgi/include/fcgi_config.h @@ -0,0 +1,111 @@ +/* fcgi_config.h. Generated automatically by configure. */ +/* fcgi_config.h.in. Generated automatically from configure.in by autoheader. */ + +/* Define if you have the header file. */ +#define HAVE_ARPA_INET_H 1 + +/* Define if you have the header file. */ +#define HAVE_DLFCN_H 1 + +/* Define if there's a fileno() prototype in stdio.h */ +#define HAVE_FILENO_PROTO 1 + +/* Define if the fpos_t typedef is in stdio.h */ +#define HAVE_FPOS 1 + +/* Define if you have the header file. */ +#define HAVE_INTTYPES_H 1 + +/* Define if you have the `dnet_stub' library (-ldnet_stub). */ +/* #undef HAVE_LIBDNET_STUB */ + +/* Define if you have the `ieee' library (-lieee). */ +/* #undef HAVE_LIBIEEE */ + +/* Define if you have the `nsl' library (-lnsl). */ +#define HAVE_LIBNSL 1 + +/* Define if you have the pthread library */ +#define HAVE_LIBPTHREAD 1 + +/* Define if you have the `resolv' library (-lresolv). */ +#define HAVE_LIBRESOLV 1 + +/* Define if you have the `socket' library (-lsocket). */ +#define HAVE_LIBSOCKET 1 + +/* Define if you have the header file. */ +#define HAVE_LIMITS_H 1 + +/* Define if you have the header file. */ +#define HAVE_MEMORY_H 1 + +/* Define if you have the header file. */ +/* #define HAVE_NETDB_H 1 */ + +/* Define if you have the header file. */ +#define HAVE_NETINET_IN_H 1 + +/* Define if sockaddr_un in sys/un.h contains a sun_len component */ +/* #undef HAVE_SOCKADDR_UN_SUN_LEN */ + +/* Define if the socklen_t typedef is in sys/socket.h */ +/* #undef HAVE_SOCKLEN */ + +/* Define if you have the header file. */ +/* #undef HAVE_STDINT_H */ + +/* Define if you have the header file. */ +#define HAVE_STDLIB_H 1 + +/* Define if you have the `strerror' function. */ +#define HAVE_STRERROR 1 + +/* Define if you have the header file. */ +#define HAVE_STRINGS_H 1 + +/* Define if you have the header file. */ +#define HAVE_STRING_H 1 + +/* Define if you have the header file. */ +/* #define HAVE_SYS_PARAM_H 1 */ + +/* Define if you have the header file. */ +/*#define HAVE_SYS_SOCKET_H 1*/ + +/* Define if you have the header file. */ +#define HAVE_SYS_STAT_H 1 + +/* Define if you have the header file. */ +/*#define HAVE_SYS_TIME_H 1*/ + +/* Define if you have the header file. */ +#define HAVE_SYS_TYPES_H 1 + +/* Define if you have the header file. */ +/*#define HAVE_UNISTD_H 1*/ + +/* Define if va_arg(arg, long double) crashes the compiler */ +/* #undef HAVE_VA_ARG_LONG_DOUBLE_BUG */ + +/* Name of package */ +#define PACKAGE "fcgi" + +/* Define if you have the ANSI C header files. */ +#define STDC_HEADERS 1 + +/* Define if cross-process locking is required by accept() */ +#define USE_LOCKING 1 + +/* Version number of package */ +#define VERSION "2.2.2" + +/* Define to empty if `const' does not conform to ANSI C. */ +/* #undef const */ + +/* Define as `__inline' if that's what the C compiler calls it, or to nothing + if it is not supported. */ +/* #undef inline */ + +/* Define to `int' if does not define. */ +#define ssize_t int \ No newline at end of file diff --git a/sapi/cgi/libfcgi/include/fcgi_config_x86.h b/sapi/cgi/libfcgi/include/fcgi_config_x86.h new file mode 100644 index 00000000000..f56b3af75d6 --- /dev/null +++ b/sapi/cgi/libfcgi/include/fcgi_config_x86.h @@ -0,0 +1,103 @@ +/* + * Default fcgi_config.h when building on WinNT (configure is not run). + */ + +/* Define if you have the header file. */ +#undef HAVE_ARPA_INET_H + +/* Define if there's a fileno() prototype in stdio.h */ +#undef HAVE_FILENO_PROTO + +/* Define if you have the header file. */ +#undef HAVE_INTTYPES_H + +/* Define if we have f{set,get}pos functions */ +#define HAVE_FPOS 1 + +/* Define if you have the `dnet_stub' library (-ldnet_stub). */ +#undef HAVE_LIBDNET_STUB + +/* Define if you have the `ieee' library (-lieee). */ +#undef HAVE_LIBIEEE + +/* Define if you have the `nsl' library (-lnsl). */ +#undef HAVE_LIBNSL + +/* Define if you have the pthread library */ +#undef HAVE_LIBPTHREAD + +/* Define if you have the `resolv' library (-lresolv). */ +#undef HAVE_LIBRESOLV + +/* Define if you have the `socket' library (-lsocket). */ +#undef HAVE_LIBSOCKET + +/* Define if you have the header file. */ +#define HAVE_LIMITS_H 1 + +/* Define if we need cross-process locking */ +#undef USE_LOCKING + +/* Define if you have the header file. */ +#undef HAVE_MEMORY_H + +/* Define if you have the header file. */ +#undef HAVE_NETDB_H + +/* Define if you have the header file. */ +#undef HAVE_NETINET_IN_H + +/* Define if sockaddr_un in sys/un.h contains a sun_len component */ +#undef HAVE_SOCKADDR_UN_SUN_LEN + +/* Define if the socklen_t typedef is in sys/socket.h */ +#undef HAVE_SOCKLEN + +/* Define if you have the header file. */ +#undef HAVE_STDINT_H + +/* Define if you have the header file. */ +#undef HAVE_STDLIB_H + +/* Define if you have the `strerror' function. */ +#define HAVE_STRERROR 1 + +/* Define if you have the header file. */ +#undef HAVE_STRINGS_H + +/* Define if you have the header file. */ +#undef HAVE_STRING_H + +/* Define if you have the header file. */ +#undef HAVE_SYS_PARAM_H + +/* Define if you have the header file. */ +#undef HAVE_SYS_SOCKET_H + +/* Define if you have the header file. */ +#undef HAVE_SYS_STAT_H + +/* Define if you have the header file. */ +#undef HAVE_SYS_TIME_H + +/* Define if you have the header file. */ +#undef HAVE_SYS_TYPES_H + +/* Define if you have the header file. */ +#undef HAVE_UNISTD_H + +/* Define if va_arg(arg, long double) crashes the compiler */ +#undef HAVE_VA_ARG_LONG_DOUBLE_BUG + +/* Define if you have the ANSI C header files. */ +#undef STDC_HEADERS + +/* Define to empty if `const' does not conform to ANSI C. */ +#undef const + +/* Define as `__inline' if that's what the C compiler calls it, or to nothing + if it is not supported. */ +#undef inline + +/* Define to `int' if does not define. */ +#undef ssize_t diff --git a/sapi/cgi/libfcgi/include/fcgi_stdio.h b/sapi/cgi/libfcgi/include/fcgi_stdio.h new file mode 100644 index 00000000000..518462b5916 --- /dev/null +++ b/sapi/cgi/libfcgi/include/fcgi_stdio.h @@ -0,0 +1,249 @@ +/* + * fcgi_stdio.h -- + * + * FastCGI-stdio compatibility package + * + * + * Copyright (c) 1996 Open Market, Inc. + * + * See the file "LICENSE.TERMS" for information on usage and redistribution + * of this file, and for a DISCLAIMER OF ALL WARRANTIES. + * + * $Id$ + */ + +#ifndef _FCGI_STDIO +#define _FCGI_STDIO 1 + +#include +#include +#include "fcgiapp.h" + +#if defined (c_plusplus) || defined (__cplusplus) +extern "C" { +#endif + +#ifndef DLLAPI +#ifdef _WIN32 +#if defined(_LIB) || defined(FCGI_STATIC) +#define DLLAPI +#else +#define DLLAPI __declspec(dllimport) +#endif +#else +#define DLLAPI +#endif +#endif + +/* + * Wrapper type for FILE + */ + +typedef struct { + FILE *stdio_stream; + FCGX_Stream *fcgx_stream; +} FCGI_FILE; + +/* + * The four new functions and two new macros + */ + +DLLAPI int FCGI_Accept(void); +DLLAPI void FCGI_Finish(void); +DLLAPI int FCGI_StartFilterData(void); +DLLAPI void FCGI_SetExitStatus(int status); + +#define FCGI_ToFILE(fcgi_file) (fcgi_file->stdio_stream) +#define FCGI_ToFcgiStream(fcgi_file) (fcgi_file->fcgx_stream) + +/* + * Wrapper stdin, stdout, and stderr variables, set up by FCGI_Accept() + */ + +DLLAPI extern FCGI_FILE _fcgi_sF[]; +#define FCGI_stdin (&_fcgi_sF[0]) +#define FCGI_stdout (&_fcgi_sF[1]) +#define FCGI_stderr (&_fcgi_sF[2]) + +/* + * Wrapper function prototypes, grouped according to sections + * of Harbison & Steele, "C: A Reference Manual," fourth edition, + * Prentice-Hall, 1995. + */ + +DLLAPI void FCGI_perror(const char *str); + +DLLAPI FCGI_FILE *FCGI_fopen(const char *path, const char *mode); +DLLAPI int FCGI_fclose(FCGI_FILE *fp); +DLLAPI int FCGI_fflush(FCGI_FILE *fp); +DLLAPI FCGI_FILE *FCGI_freopen(const char *path, const char *mode, FCGI_FILE *fp); + +DLLAPI int FCGI_setvbuf(FCGI_FILE *fp, char *buf, int bufmode, size_t size); +DLLAPI void FCGI_setbuf(FCGI_FILE *fp, char *buf); + +DLLAPI int FCGI_fseek(FCGI_FILE *fp, long offset, int whence); +DLLAPI int FCGI_ftell(FCGI_FILE *fp); +DLLAPI void FCGI_rewind(FCGI_FILE *fp); +#ifdef HAVE_FPOS +DLLAPI int FCGI_fgetpos(FCGI_FILE *fp, fpos_t *pos); +DLLAPI int FCGI_fsetpos(FCGI_FILE *fp, const fpos_t *pos); +#endif +DLLAPI int FCGI_fgetc(FCGI_FILE *fp); +DLLAPI int FCGI_getchar(void); +DLLAPI int FCGI_ungetc(int c, FCGI_FILE *fp); + +DLLAPI char *FCGI_fgets(char *str, int size, FCGI_FILE *fp); +DLLAPI char *FCGI_gets(char *str); + +/* + * Not yet implemented + * + * int FCGI_fscanf(FCGI_FILE *fp, const char *format, ...); + * int FCGI_scanf(const char *format, ...); + * + */ + +DLLAPI int FCGI_fputc(int c, FCGI_FILE *fp); +DLLAPI int FCGI_putchar(int c); + +DLLAPI int FCGI_fputs(const char *str, FCGI_FILE *fp); +DLLAPI int FCGI_puts(const char *str); + +DLLAPI int FCGI_fprintf(FCGI_FILE *fp, const char *format, ...); +DLLAPI int FCGI_printf(const char *format, ...); + +DLLAPI int FCGI_vfprintf(FCGI_FILE *fp, const char *format, va_list ap); +DLLAPI int FCGI_vprintf(const char *format, va_list ap); + +DLLAPI size_t FCGI_fread(void *ptr, size_t size, size_t nmemb, FCGI_FILE *fp); +DLLAPI size_t FCGI_fwrite(void *ptr, size_t size, size_t nmemb, FCGI_FILE *fp); + +DLLAPI int FCGI_feof(FCGI_FILE *fp); +DLLAPI int FCGI_ferror(FCGI_FILE *fp); +DLLAPI void FCGI_clearerr(FCGI_FILE *fp); + +DLLAPI FCGI_FILE *FCGI_tmpfile(void); + +DLLAPI int FCGI_fileno(FCGI_FILE *fp); +DLLAPI FCGI_FILE *FCGI_fdopen(int fd, const char *mode); +DLLAPI FCGI_FILE *FCGI_popen(const char *cmd, const char *type); +DLLAPI int FCGI_pclose(FCGI_FILE *); + +/* + * The remaining definitions are for application programs, + * not for fcgi_stdio.c + */ + +#ifndef NO_FCGI_DEFINES + +/* + * Replace standard types, variables, and functions with FastCGI wrappers. + * Use undef in case a macro is already defined. + */ + +#undef FILE +#define FILE FCGI_FILE + +#undef stdin +#define stdin FCGI_stdin +#undef stdout +#define stdout FCGI_stdout +#undef stderr +#define stderr FCGI_stderr + +#undef perror +#define perror FCGI_perror + +#undef fopen +#define fopen FCGI_fopen +#undef fclose +#define fclose FCGI_fclose +#undef fflush +#define fflush FCGI_fflush +#undef freopen +#define freopen FCGI_freopen + +#undef setvbuf +#define setvbuf FCGI_setvbuf +#undef setbuf +#define setbuf FCGI_setbuf + +#undef fseek +#define fseek FCGI_fseek +#undef ftell +#define ftell FCGI_ftell +#undef rewind +#define rewind FCGI_rewind +#undef fgetpos +#define fgetpos FCGI_fgetpos +#undef fsetpos +#define fsetpos FCGI_fsetpos + +#undef fgetc +#define fgetc FCGI_fgetc +#undef getc +#define getc FCGI_fgetc +#undef getchar +#define getchar FCGI_getchar +#undef ungetc +#define ungetc FCGI_ungetc + +#undef fgets +#define fgets FCGI_fgets +#undef gets +#define gets FCGI_gets + +#undef fputc +#define fputc FCGI_fputc +#undef putc +#define putc FCGI_fputc +#undef putchar +#define putchar FCGI_putchar + +#undef fputs +#define fputs FCGI_fputs +#undef puts +#define puts FCGI_puts + +#undef fprintf +#define fprintf FCGI_fprintf +#undef printf +#define printf FCGI_printf + +#undef vfprintf +#define vfprintf FCGI_vfprintf +#undef vprintf +#define vprintf FCGI_vprintf + +#undef fread +#define fread FCGI_fread +#undef fwrite +#define fwrite FCGI_fwrite + +#undef feof +#define feof FCGI_feof +#undef ferror +#define ferror FCGI_ferror +#undef clearerr +#define clearerr FCGI_clearerr + +#undef tmpfile +#define tmpfile FCGI_tmpfile + +#undef fileno +#define fileno FCGI_fileno +#undef fdopen +#define fdopen FCGI_fdopen +#undef popen +#define popen FCGI_popen +#undef pclose +#define pclose FCGI_pclose + +#endif /* NO_FCGI_DEFINES */ + +#if defined (__cplusplus) || defined (c_plusplus) +} /* terminate extern "C" { */ +#endif + +#endif /* _FCGI_STDIO */ + diff --git a/sapi/cgi/libfcgi/include/fcgiapp.h b/sapi/cgi/libfcgi/include/fcgiapp.h new file mode 100644 index 00000000000..394e2078f9a --- /dev/null +++ b/sapi/cgi/libfcgi/include/fcgiapp.h @@ -0,0 +1,626 @@ +/* + * fcgiapp.h -- + * + * Definitions for FastCGI application server programs + * + * + * Copyright (c) 1996 Open Market, Inc. + * + * See the file "LICENSE.TERMS" for information on usage and redistribution + * of this file, and for a DISCLAIMER OF ALL WARRANTIES. + * + * $Id$ + */ + +#ifndef _FCGIAPP_H +#define _FCGIAPP_H + +/* Hack to see if we are building TCL - TCL needs varargs not stdarg */ +#ifndef TCL_LIBRARY +#include +#else +#include +#endif + +#ifndef DLLAPI +#ifdef _WIN32 +#if defined(_LIB) || defined(FCGI_STATIC) +#define DLLAPI +#else +#define DLLAPI __declspec(dllimport) +#endif +#else +#define DLLAPI +#endif +#endif + +#if defined (c_plusplus) || defined (__cplusplus) +extern "C" { +#endif + +/* + * Error codes. Assigned to avoid conflict with EOF and errno(2). + */ +#define FCGX_UNSUPPORTED_VERSION -2 +#define FCGX_PROTOCOL_ERROR -3 +#define FCGX_PARAMS_ERROR -4 +#define FCGX_CALL_SEQ_ERROR -5 + +/* + * This structure defines the state of a FastCGI stream. + * Streams are modeled after the FILE type defined in stdio.h. + * (We wouldn't need our own if platform vendors provided a + * standard way to subclass theirs.) + * The state of a stream is private and should only be accessed + * by the procedures defined below. + */ +typedef struct FCGX_Stream { + unsigned char *rdNext; /* reader: first valid byte + * writer: equals stop */ + unsigned char *wrNext; /* writer: first free byte + * reader: equals stop */ + unsigned char *stop; /* reader: last valid byte + 1 + * writer: last free byte + 1 */ + unsigned char *stopUnget; /* reader: first byte of current buffer + * fragment, for ungetc + * writer: undefined */ + int isReader; + int isClosed; + int wasFCloseCalled; + int FCGI_errno; /* error status */ + void (*fillBuffProc) (struct FCGX_Stream *stream); + void (*emptyBuffProc) (struct FCGX_Stream *stream, int doClose); + void *data; +} FCGX_Stream; + +/* + * An environment (as defined by environ(7)): A NULL-terminated array + * of strings, each string having the form name=value. + */ +typedef char **FCGX_ParamArray; + +/* + * FCGX_Request Flags + * + * Setting FCGI_FAIL_ACCEPT_ON_INTR prevents FCGX_Accept() from + * restarting upon being interrupted. + */ +#define FCGI_FAIL_ACCEPT_ON_INTR 1 + +/* + * FCGX_Request -- State associated with a request. + * + * Its exposed for API simplicity, I expect parts of it to change! + */ +typedef struct FCGX_Request { + int requestId; /* valid if isBeginProcessed */ + int role; + FCGX_Stream *in; + FCGX_Stream *out; + FCGX_Stream *err; + char **envp; + + /* Don't use anything below here */ + + struct Params *paramsPtr; + int ipcFd; /* < 0 means no connection */ + int isBeginProcessed; /* FCGI_BEGIN_REQUEST seen */ + int keepConnection; /* don't close ipcFd at end of request */ + int appStatus; + int nWriters; /* number of open writers (0..2) */ + int flags; + int listen_sock; +} FCGX_Request; + + +/* + *====================================================================== + * Control + *====================================================================== + */ + +/* + *---------------------------------------------------------------------- + * + * FCGX_IsCGI -- + * + * Returns TRUE iff this process appears to be a CGI process + * rather than a FastCGI process. + * + *---------------------------------------------------------------------- + */ +DLLAPI int FCGX_IsCGI(void); + +/* + *---------------------------------------------------------------------- + * + * FCGX_Init -- + * + * Initialize the FCGX library. Call in multi-threaded apps + * before calling FCGX_Accept_r(). + * + * Returns 0 upon success. + * + *---------------------------------------------------------------------- + */ +DLLAPI int FCGX_Init(void); + +/* + *---------------------------------------------------------------------- + * + * FCGX_OpenSocket -- + * + * Create a FastCGI listen socket. + * + * path is the Unix domain socket (named pipe for WinNT), or a colon + * followed by a port number. e.g. "/tmp/fastcgi/mysocket", ":5000" + * + * backlog is the listen queue depth used in the listen() call. + * + * Returns the socket's file descriptor or -1 on error. + * + *---------------------------------------------------------------------- + */ +DLLAPI int FCGX_OpenSocket(const char *path, int backlog); + +/* + *---------------------------------------------------------------------- + * + * FCGX_InitRequest -- + * + * Initialize a FCGX_Request for use with FCGX_Accept_r(). + * + * sock is a file descriptor returned by FCGX_OpenSocket() or 0 (default). + * The only supported flag at this time is FCGI_FAIL_ON_INTR. + * + * Returns 0 upon success. + *---------------------------------------------------------------------- + */ +DLLAPI int FCGX_InitRequest(FCGX_Request *request, int sock, int flags); + +/* + *---------------------------------------------------------------------- + * + * FCGX_Accept_r -- + * + * Accept a new request (multi-thread safe). Be sure to call + * FCGX_Init() first. + * + * Results: + * 0 for successful call, -1 for error. + * + * Side effects: + * + * Finishes the request accepted by (and frees any + * storage allocated by) the previous call to FCGX_Accept. + * Creates input, output, and error streams and + * assigns them to *in, *out, and *err respectively. + * Creates a parameters data structure to be accessed + * via getenv(3) (if assigned to environ) or by FCGX_GetParam + * and assigns it to *envp. + * + * DO NOT retain pointers to the envp array or any strings + * contained in it (e.g. to the result of calling FCGX_GetParam), + * since these will be freed by the next call to FCGX_Finish + * or FCGX_Accept. + * + * DON'T use the FCGX_Request, its structure WILL change. + * + *---------------------------------------------------------------------- + */ +DLLAPI int FCGX_Accept_r(FCGX_Request *request); + +/* + *---------------------------------------------------------------------- + * + * FCGX_Finish_r -- + * + * Finish the request (multi-thread safe). + * + * Side effects: + * + * Finishes the request accepted by (and frees any + * storage allocated by) the previous call to FCGX_Accept. + * + * DO NOT retain pointers to the envp array or any strings + * contained in it (e.g. to the result of calling FCGX_GetParam), + * since these will be freed by the next call to FCGX_Finish + * or FCGX_Accept. + * + *---------------------------------------------------------------------- + */ +DLLAPI void FCGX_Finish_r(FCGX_Request *request); + +/* + *---------------------------------------------------------------------- + * + * FCGX_Free -- + * + * Free the memory and, if close is true, + * IPC FD associated with the request (multi-thread safe). + * + *---------------------------------------------------------------------- + */ +DLLAPI void FCGX_Free(FCGX_Request * request, int close); + +/* + *---------------------------------------------------------------------- + * + * FCGX_Accept -- + * + * Accept a new request (NOT multi-thread safe). + * + * Results: + * 0 for successful call, -1 for error. + * + * Side effects: + * + * Finishes the request accepted by (and frees any + * storage allocated by) the previous call to FCGX_Accept. + * Creates input, output, and error streams and + * assigns them to *in, *out, and *err respectively. + * Creates a parameters data structure to be accessed + * via getenv(3) (if assigned to environ) or by FCGX_GetParam + * and assigns it to *envp. + * + * DO NOT retain pointers to the envp array or any strings + * contained in it (e.g. to the result of calling FCGX_GetParam), + * since these will be freed by the next call to FCGX_Finish + * or FCGX_Accept. + * + *---------------------------------------------------------------------- + */ +DLLAPI int FCGX_Accept( + FCGX_Stream **in, + FCGX_Stream **out, + FCGX_Stream **err, + FCGX_ParamArray *envp); + +/* + *---------------------------------------------------------------------- + * + * FCGX_Finish -- + * + * Finish the current request (NOT multi-thread safe). + * + * Side effects: + * + * Finishes the request accepted by (and frees any + * storage allocated by) the previous call to FCGX_Accept. + * + * DO NOT retain pointers to the envp array or any strings + * contained in it (e.g. to the result of calling FCGX_GetParam), + * since these will be freed by the next call to FCGX_Finish + * or FCGX_Accept. + * + *---------------------------------------------------------------------- + */ +DLLAPI void FCGX_Finish(void); + +/* + *---------------------------------------------------------------------- + * + * FCGX_StartFilterData -- + * + * stream is an input stream for a FCGI_FILTER request. + * stream is positioned at EOF on FCGI_STDIN. + * Repositions stream to the start of FCGI_DATA. + * If the preconditions are not met (e.g. FCGI_STDIN has not + * been read to EOF) sets the stream error code to + * FCGX_CALL_SEQ_ERROR. + * + * Results: + * 0 for a normal return, < 0 for error + * + *---------------------------------------------------------------------- + */ +DLLAPI int FCGX_StartFilterData(FCGX_Stream *stream); + +/* + *---------------------------------------------------------------------- + * + * FCGX_SetExitStatus -- + * + * Sets the exit status for stream's request. The exit status + * is the status code the request would have exited with, had + * the request been run as a CGI program. You can call + * SetExitStatus several times during a request; the last call + * before the request ends determines the value. + * + *---------------------------------------------------------------------- + */ +DLLAPI void FCGX_SetExitStatus(int status, FCGX_Stream *stream); + +/* + *====================================================================== + * Parameters + *====================================================================== + */ + +/* + *---------------------------------------------------------------------- + * + * FCGX_GetParam -- obtain value of FCGI parameter in environment + * + * + * Results: + * Value bound to name, NULL if name not present in the + * environment envp. Caller must not mutate the result + * or retain it past the end of this request. + * + *---------------------------------------------------------------------- + */ +DLLAPI char *FCGX_GetParam(const char *name, FCGX_ParamArray envp); + +/* + *====================================================================== + * Readers + *====================================================================== + */ + +/* + *---------------------------------------------------------------------- + * + * FCGX_GetChar -- + * + * Reads a byte from the input stream and returns it. + * + * Results: + * The byte, or EOF (-1) if the end of input has been reached. + * + *---------------------------------------------------------------------- + */ +DLLAPI int FCGX_GetChar(FCGX_Stream *stream); + +/* + *---------------------------------------------------------------------- + * + * FCGX_UnGetChar -- + * + * Pushes back the character c onto the input stream. One + * character of pushback is guaranteed once a character + * has been read. No pushback is possible for EOF. + * + * Results: + * Returns c if the pushback succeeded, EOF if not. + * + *---------------------------------------------------------------------- + */ +DLLAPI int FCGX_UnGetChar(int c, FCGX_Stream *stream); + +/* + *---------------------------------------------------------------------- + * + * FCGX_GetStr -- + * + * Reads up to n consecutive bytes from the input stream + * into the character array str. Performs no interpretation + * of the input bytes. + * + * Results: + * Number of bytes read. If result is smaller than n, + * the end of input has been reached. + * + *---------------------------------------------------------------------- + */ +DLLAPI int FCGX_GetStr(char *str, int n, FCGX_Stream *stream); + +/* + *---------------------------------------------------------------------- + * + * FCGX_GetLine -- + * + * Reads up to n-1 consecutive bytes from the input stream + * into the character array str. Stops before n-1 bytes + * have been read if '\n' or EOF is read. The terminating '\n' + * is copied to str. After copying the last byte into str, + * stores a '\0' terminator. + * + * Results: + * NULL if EOF is the first thing read from the input stream, + * str otherwise. + * + *---------------------------------------------------------------------- + */ +DLLAPI char *FCGX_GetLine(char *str, int n, FCGX_Stream *stream); + +/* + *---------------------------------------------------------------------- + * + * FCGX_HasSeenEOF -- + * + * Returns EOF if end-of-file has been detected while reading + * from stream; otherwise returns 0. + * + * Note that FCGX_HasSeenEOF(s) may return 0, yet an immediately + * following FCGX_GetChar(s) may return EOF. This function, like + * the standard C stdio function feof, does not provide the + * ability to peek ahead. + * + * Results: + * EOF if end-of-file has been detected, 0 if not. + * + *---------------------------------------------------------------------- + */ + +DLLAPI int FCGX_HasSeenEOF(FCGX_Stream *stream); + +/* + *====================================================================== + * Writers + *====================================================================== + */ + +/* + *---------------------------------------------------------------------- + * + * FCGX_PutChar -- + * + * Writes a byte to the output stream. + * + * Results: + * The byte, or EOF (-1) if an error occurred. + * + *---------------------------------------------------------------------- + */ +DLLAPI int FCGX_PutChar(int c, FCGX_Stream *stream); + +/* + *---------------------------------------------------------------------- + * + * FCGX_PutStr -- + * + * Writes n consecutive bytes from the character array str + * into the output stream. Performs no interpretation + * of the output bytes. + * + * Results: + * Number of bytes written (n) for normal return, + * EOF (-1) if an error occurred. + * + *---------------------------------------------------------------------- + */ +DLLAPI int FCGX_PutStr(const char *str, int n, FCGX_Stream *stream); + +/* + *---------------------------------------------------------------------- + * + * FCGX_PutS -- + * + * Writes a null-terminated character string to the output stream. + * + * Results: + * number of bytes written for normal return, + * EOF (-1) if an error occurred. + * + *---------------------------------------------------------------------- + */ +DLLAPI int FCGX_PutS(const char *str, FCGX_Stream *stream); + +/* + *---------------------------------------------------------------------- + * + * FCGX_FPrintF, FCGX_VFPrintF -- + * + * Performs printf-style output formatting and writes the results + * to the output stream. + * + * Results: + * number of bytes written for normal return, + * EOF (-1) if an error occurred. + * + *---------------------------------------------------------------------- + */ +DLLAPI int FCGX_FPrintF(FCGX_Stream *stream, const char *format, ...); + +DLLAPI int FCGX_VFPrintF(FCGX_Stream *stream, const char *format, va_list arg); + +/* + *---------------------------------------------------------------------- + * + * FCGX_FFlush -- + * + * Flushes any buffered output. + * + * Server-push is a legitimate application of FCGX_FFlush. + * Otherwise, FCGX_FFlush is not very useful, since FCGX_Accept + * does it implicitly. Calling FCGX_FFlush in non-push applications + * results in extra writes and therefore reduces performance. + * + * Results: + * EOF (-1) if an error occurred. + * + *---------------------------------------------------------------------- + */ +DLLAPI int FCGX_FFlush(FCGX_Stream *stream); + +/* + *====================================================================== + * Both Readers and Writers + *====================================================================== + */ + +/* + *---------------------------------------------------------------------- + * + * FCGX_FClose -- + * + * Closes the stream. For writers, flushes any buffered + * output. + * + * Close is not a very useful operation since FCGX_Accept + * does it implicitly. Closing the out stream before the + * err stream results in an extra write if there's nothing + * in the err stream, and therefore reduces performance. + * + * Results: + * EOF (-1) if an error occurred. + * + *---------------------------------------------------------------------- + */ +DLLAPI int FCGX_FClose(FCGX_Stream *stream); + +/* + *---------------------------------------------------------------------- + * + * FCGX_GetError -- + * + * Return the stream error code. 0 means no error, > 0 + * is an errno(2) error, < 0 is an FastCGI error. + * + *---------------------------------------------------------------------- + */ +DLLAPI int FCGX_GetError(FCGX_Stream *stream); + +/* + *---------------------------------------------------------------------- + * + * FCGX_ClearError -- + * + * Clear the stream error code and end-of-file indication. + * + *---------------------------------------------------------------------- + */ +DLLAPI void FCGX_ClearError(FCGX_Stream *stream); + +/* + *---------------------------------------------------------------------- + * + * FCGX_CreateWriter -- + * + * Create a FCGX_Stream (used by cgi-fcgi). This shouldn't + * be needed by a FastCGI applictaion. + * + *---------------------------------------------------------------------- + */ +DLLAPI FCGX_Stream *FCGX_CreateWriter( + int socket, + int requestId, + int bufflen, + int streamType); + +/* + *---------------------------------------------------------------------- + * + * FCGX_FreeStream -- + * + * Free a FCGX_Stream (used by cgi-fcgi). This shouldn't + * be needed by a FastCGI applictaion. + * + *---------------------------------------------------------------------- + */ +DLLAPI void FCGX_FreeStream(FCGX_Stream **stream); + +/* ---------------------------------------------------------------------- + * + * Prevent the lib from accepting any new requests. Signal handler safe. + * + * ---------------------------------------------------------------------- + */ +DLLAPI void FCGX_ShutdownPending(void); + +#if defined (__cplusplus) || defined (c_plusplus) +} /* terminate extern "C" { */ +#endif + +#endif /* _FCGIAPP_H */ diff --git a/sapi/cgi/libfcgi/include/fcgiappmisc.h b/sapi/cgi/libfcgi/include/fcgiappmisc.h new file mode 100644 index 00000000000..db8651a44bf --- /dev/null +++ b/sapi/cgi/libfcgi/include/fcgiappmisc.h @@ -0,0 +1,50 @@ +/* + * fcgiappmisc.h -- + * + * Functions implemented by fcgiapp.h that aren't needed + * by normal applications, but may be useful to special + * applications. + * + * + * Copyright (c) 1996 Open Market, Inc. + * + * See the file "LICENSE.TERMS" for information on usage and redistribution + * of this file, and for a DISCLAIMER OF ALL WARRANTIES. + * + * $Id$ + */ + +#ifndef _FCGIAPPMISC_H +#define _FCGIAPPMISC_H + +#include "fcgiapp.h" /* for FCGX_Stream */ + +#if defined (c_plusplus) || defined (__cplusplus) +extern "C" { +#endif + +#ifdef _WIN32 +#ifndef DLLAPI +#ifdef FCGI_STATIC +#define DLLAPI +#else +#define DLLAPI __declspec(dllimport) +#endif +#endif +#else +#define DLLAPI +#endif + +DLLAPI FCGX_Stream *CreateWriter( + int socket, + int requestId, + int bufflen, + int streamType); + +DLLAPI void FreeStream(FCGX_Stream **stream); + +#if defined (__cplusplus) || defined (c_plusplus) +} /* terminate extern "C" { */ +#endif + +#endif /* _FCGIAPPMISC_H */ diff --git a/sapi/cgi/libfcgi/include/fcgimisc.h b/sapi/cgi/libfcgi/include/fcgimisc.h new file mode 100644 index 00000000000..20ee4a0cf03 --- /dev/null +++ b/sapi/cgi/libfcgi/include/fcgimisc.h @@ -0,0 +1,38 @@ +/* + * fcgimisc.h -- + * + * Miscellaneous definitions + * + * + * Copyright (c) 1996 Open Market, Inc. + * + * See the file "LICENSE.TERMS" for information on usage and redistribution + * of this file, and for a DISCLAIMER OF ALL WARRANTIES. + * + * $Id$ + */ + +#ifndef _FCGIMISC_H +#define _FCGIMISC_H + +#ifndef FALSE +#define FALSE (0) +#endif + +#ifndef TRUE +#define TRUE (1) +#endif + +#ifndef min +#define min(a,b) ((a) < (b) ? (a) : (b)) +#endif + +#ifndef max +#define max(a,b) ((a) > (b) ? (a) : (b)) +#endif + +#ifndef ASSERT +#define ASSERT(assertion) assert(assertion) +#endif + +#endif /* _FCGIMISC_H */ diff --git a/sapi/cgi/libfcgi/include/fcgio.h b/sapi/cgi/libfcgi/include/fcgio.h new file mode 100644 index 00000000000..865bff385bd --- /dev/null +++ b/sapi/cgi/libfcgi/include/fcgio.h @@ -0,0 +1,147 @@ +// +// Provides support for FastCGI via C++ iostreams. +// +// $Id$ +// +// This work is based on routines written by George Feinberg. They +// have been mostly re-written and extensively changed by +// Michael Richards. +// +// Rewritten again with bug fixes and numerous enhancements by +// Michael Shell. +// +// And rewritten again by Rob Saccoccio. +// +// Special Thanks to Dietmar Kuehl for his help and the numerous custom +// streambuf examples on his web site. +// +// Copyright (c) 2000 Tux the Linux Penguin +// Copyright (c) 2001 Rob Saccoccio and Chelsea Networks +// +// You are free to use this software without charge or royalty +// as long as this notice is not removed or altered, and recognition +// is given to the author(s) +// +// This code is offered as-is without any warranty either expressed or +// implied; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. If it breaks, you get to keep +// both halves. + +#ifndef FCGIO_H +#define FCGIO_H + +#include + +#include "fcgiapp.h" + +#ifndef DLLAPI +#ifdef _WIN32 +#define DLLAPI __declspec(dllimport) +#else +#define DLLAPI +#endif +#endif + +/* + * fcgi_streambuf + */ +class fcgi_streambuf : public streambuf +{ +public: + + // Note that if no buf is assigned (the default), iostream methods + // such as peek(), unget() and putback() will fail. If a buf is + // assigned, I/O is a bit less effecient and output streams will + // have to be flushed (or the streambuf destroyed) before the next + // call to "accept". + DLLAPI fcgi_streambuf(FCGX_Stream * fcgx, char * buf, int len); + + DLLAPI fcgi_streambuf(char * buf, int len); + + DLLAPI fcgi_streambuf(FCGX_Stream * fcgx = NULL); + + DLLAPI ~fcgi_streambuf(void); + + DLLAPI int attach(FCGX_Stream * fcgx); + +protected: + + // Consume the put area (if buffered) and c (if c is not EOF). + DLLAPI virtual int overflow(int); + + // Flush the put area (if buffered) and the FCGX buffer to the client. + DLLAPI virtual int sync(); + + // Remove and return the current character. + DLLAPI virtual int uflow(); + + // Fill the get area (if buffered) and return the current character. + DLLAPI virtual int underflow(); + + // Use a buffer. The only reasons that a buffer would be useful is + // to support the use of the unget()/putback() or seek() methods. Using + // a buffer will result in less efficient I/O. Note: the underlying + // FastCGI library (FCGX) maintains its own input and output buffers. + DLLAPI virtual streambuf * setbuf(char * buf, int len); + + DLLAPI virtual int xsgetn(char * s, int n); + DLLAPI virtual int xsputn(const char * s, int n); + +private: + + FCGX_Stream * fcgx; + + // buf is just handy to have around + char * buf; + + // this isn't kept by the base class + int bufsize; + + void init(FCGX_Stream * fcgx, char * buf, int bufsize); + + void reset(void); +}; + +/* + * fcgi_istream - deprecated + */ +class fcgi_istream : public istream +{ +public: + + // deprecated + DLLAPI fcgi_istream(FCGX_Stream * fcgx = NULL); + + // deprecated + DLLAPI ~fcgi_istream(void) {} + + // deprecated + DLLAPI virtual void attach(FCGX_Stream * fcgx); + +private: + + fcgi_streambuf fcgi_strmbuf; +}; + +/* + * fcgi_ostream - deprecated + */ +class fcgi_ostream : public ostream +{ +public: + + // deprecated + DLLAPI fcgi_ostream(FCGX_Stream * fcgx = NULL); + + // deprecated + DLLAPI ~fcgi_ostream(void) {} + + // deprecated + DLLAPI virtual void attach(FCGX_Stream *fcgx); + +private: + + fcgi_streambuf fcgi_strmbuf; +}; + +#endif /* FCGIO_H */ diff --git a/sapi/cgi/libfcgi/include/fcgios.h b/sapi/cgi/libfcgi/include/fcgios.h new file mode 100644 index 00000000000..bc62a185d55 --- /dev/null +++ b/sapi/cgi/libfcgi/include/fcgios.h @@ -0,0 +1,132 @@ +/* + * fcgios.h -- + * + * Description of file. + * + * + * Copyright (c) 1996 Open Market, Inc. + * All rights reserved. + * + * This file contains proprietary and confidential information and + * remains the unpublished property of Open Market, Inc. Use, + * disclosure, or reproduction is prohibited except as permitted by + * express written license agreement with Open Market, Inc. + * + * Bill Snapper + * snapper@openmarket.com + */ +#ifndef _FCGIOS_H +#define _FCGIOS_H + +#ifdef _WIN32 +#define WIN32_LEAN_AND_MEAN +#include +#include +#endif + +#include "fcgi_config.h" + +#ifdef HAVE_SYS_TIME_H +#include +#endif + +#if defined (c_plusplus) || defined (__cplusplus) +extern "C" { +#endif + +#ifdef _WIN32 +#define OS_Errno GetLastError() +#define OS_SetErrno(err) SetLastError(err) +#ifndef O_NONBLOCK +#define O_NONBLOCK 0x0004 /* no delay */ +#endif +#else /* !_WIN32 */ +#define OS_Errno errno +#define OS_SetErrno(err) errno = (err) +#endif /* !_WIN32 */ + +#ifndef DLLAPI +#ifdef _WIN32 +#if defined(_LIB) || defined(FCGI_STATIC) +#define DLLAPI +#else +#define DLLAPI __declspec(dllimport) +#endif +#else +#define DLLAPI +#endif +#endif + + +/* This is the initializer for a "struct timeval" used in a select() call + * right after a new request is accept()ed to determine readablity. Its + * a drop-dead timer. Its only used for AF_UNIX sockets (not TCP sockets). + * Its a workaround for a kernel bug in Linux 2.0.x and SCO Unixware. + * Making this as small as possible, yet remain reliable would be best. + * 2 seconds is very conservative. 0,0 is not reliable. The shorter the + * timeout, the faster request processing will recover. The longer the + * timeout, the more likely this application being "busy" will cause other + * requests to abort and cause more dead sockets that need this timeout. */ +#define READABLE_UNIX_FD_DROP_DEAD_TIMEVAL 2,0 + +#ifndef STDIN_FILENO +#define STDIN_FILENO 0 +#endif + +#ifndef STDOUT_FILENO +#define STDOUT_FILENO 1 +#endif + +#ifndef STDERR_FILENO +#define STDERR_FILENO 2 +#endif + +#ifndef MAXPATHLEN +#define MAXPATHLEN 1024 +#endif + +#ifndef X_OK +#define X_OK 0x01 +#endif + +#ifndef _CLIENTDATA +# if defined(__STDC__) || defined(__cplusplus) + typedef void *ClientData; +# else + typedef int *ClientData; +# endif /* __STDC__ */ +#define _CLIENTDATA +#endif +#define MUTEX_VARNAME "_FCGI_MUTEX_" +#define SHUTDOWN_EVENT_NAME "_FCGI_SHUTDOWN_EVENT_" + +typedef void (*OS_AsyncProc) (ClientData clientData, int len); + +DLLAPI int OS_LibInit(int stdioFds[3]); +DLLAPI void OS_LibShutdown(void); +DLLAPI int OS_CreateLocalIpcFd(const char *bindPath, int backlog, int bCreateMutex); +DLLAPI int OS_FcgiConnect(char *bindPath); +DLLAPI int OS_Read(int fd, char * buf, size_t len); +DLLAPI int OS_Write(int fd, char * buf, size_t len); +DLLAPI int OS_SpawnChild(char *execPath, int listenFd, PROCESS_INFORMATION *pInfo, char *env); +DLLAPI int OS_AsyncReadStdin(void *buf, int len, OS_AsyncProc procPtr, + ClientData clientData); +DLLAPI int OS_AsyncRead(int fd, int offset, void *buf, int len, + OS_AsyncProc procPtr, ClientData clientData); +DLLAPI int OS_AsyncWrite(int fd, int offset, void *buf, int len, + OS_AsyncProc procPtr, ClientData clientData); +DLLAPI int OS_Close(int fd); +DLLAPI int OS_CloseRead(int fd); +DLLAPI int OS_DoIo(struct timeval *tmo); +DLLAPI int OS_Accept(int listen_sock, int fail_on_intr, const char *webServerAddrs); +DLLAPI int OS_IpcClose(int ipcFd); +DLLAPI int OS_IsFcgi(int sock); +DLLAPI void OS_SetFlags(int fd, int flags); + +DLLAPI void OS_ShutdownPending(void); + +#if defined (__cplusplus) || defined (c_plusplus) +} /* terminate extern "C" { */ +#endif + +#endif /* _FCGIOS_H */ diff --git a/sapi/cgi/libfcgi/os_unix.c b/sapi/cgi/libfcgi/os_unix.c new file mode 100644 index 00000000000..cefa193c485 --- /dev/null +++ b/sapi/cgi/libfcgi/os_unix.c @@ -0,0 +1,1265 @@ +/* + * os_unix.c -- + * + * Description of file. + * + * + * Copyright (c) 1995 Open Market, Inc. + * All rights reserved. + * + * This file contains proprietary and confidential information and + * remains the unpublished property of Open Market, Inc. Use, + * disclosure, or reproduction is prohibited except as permitted by + * express written license agreement with Open Market, Inc. + * + * Bill Snapper + * snapper@openmarket.com + */ + +#ifndef lint +static const char rcsid[] = "$Id$"; +#endif /* not lint */ + +#include "fcgi_config.h" + +#include + +#ifdef HAVE_NETINET_IN_H +#include +#endif + +#include +#include +#include +#include /* for fcntl */ +#include +#include /* for memchr() */ +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef HAVE_NETDB_H +#include +#endif + +#ifdef HAVE_SYS_SOCKET_H +#include /* for getpeername */ +#endif + +#ifdef HAVE_UNISTD_H +#include +#endif + +#include "fastcgi.h" +#include "fcgimisc.h" +#include "fcgios.h" + +#ifndef INADDR_NONE +#define INADDR_NONE ((unsigned long) -1) +#endif + +/* + * This structure holds an entry for each oustanding async I/O operation. + */ +typedef struct { + OS_AsyncProc procPtr; /* callout completion procedure */ + ClientData clientData; /* caller private data */ + int fd; + int len; + int offset; + void *buf; + int inUse; +} AioInfo; + +/* + * Entries in the async I/O table are allocated 2 per file descriptor. + * + * Read Entry Index = fd * 2 + * Write Entry Index = (fd * 2) + 1 + */ +#define AIO_RD_IX(fd) (fd * 2) +#define AIO_WR_IX(fd) ((fd * 2) + 1) + +static int asyncIoInUse = FALSE; +static int asyncIoTableSize = 16; +static AioInfo *asyncIoTable = NULL; + +static int libInitialized = FALSE; + +static fd_set readFdSet; +static fd_set writeFdSet; + +static fd_set readFdSetPost; +static int numRdPosted = 0; +static fd_set writeFdSetPost; +static int numWrPosted = 0; +static int volatile maxFd = -1; + +static int shutdownPending = FALSE; +static int shutdownNow = FALSE; + +void OS_ShutdownPending() +{ + shutdownPending = TRUE; +} + +static void OS_Sigusr1Handler(int signo) +{ + OS_ShutdownPending(); +} + +static void OS_SigpipeHandler(int signo) +{ + ; +} + +static void installSignalHandler(int signo, const struct sigaction * act, int force) +{ + struct sigaction sa; + + sigaction(signo, NULL, &sa); + + if (force || sa.sa_handler == SIG_DFL) + { + sigaction(signo, act, NULL); + } +} + +static void OS_InstallSignalHandlers(int force) +{ + struct sigaction sa; + + sigemptyset(&sa.sa_mask); + sa.sa_flags = 0; + + sa.sa_handler = OS_SigpipeHandler; + installSignalHandler(SIGPIPE, &sa, force); + + sa.sa_handler = OS_Sigusr1Handler; + installSignalHandler(SIGUSR1, &sa, force); +} + +/* + *-------------------------------------------------------------- + * + * OS_LibInit -- + * + * Set up the OS library for use. + * + * NOTE: This function is really only needed for application + * asynchronous I/O. It will most likely change in the + * future to setup the multi-threaded environment. + * + * Results: + * Returns 0 if success, -1 if not. + * + * Side effects: + * Async I/O table allocated and initialized. + * + *-------------------------------------------------------------- + */ +int OS_LibInit(int stdioFds[3]) +{ + if(libInitialized) + return 0; + + asyncIoTable = (AioInfo *)malloc(asyncIoTableSize * sizeof(AioInfo)); + if(asyncIoTable == NULL) { + errno = ENOMEM; + return -1; + } + memset((char *) asyncIoTable, 0, + asyncIoTableSize * sizeof(AioInfo)); + + FD_ZERO(&readFdSet); + FD_ZERO(&writeFdSet); + FD_ZERO(&readFdSetPost); + FD_ZERO(&writeFdSetPost); + + OS_InstallSignalHandlers(FALSE); + + libInitialized = TRUE; + + return 0; +} + +/* + *-------------------------------------------------------------- + * + * OS_LibShutdown -- + * + * Shutdown the OS library. + * + * Results: + * None. + * + * Side effects: + * Memory freed, fds closed. + * + *-------------------------------------------------------------- + */ +void OS_LibShutdown() +{ + if(!libInitialized) + return; + + free(asyncIoTable); + asyncIoTable = NULL; + libInitialized = FALSE; + return; +} + +/* + *---------------------------------------------------------------------- + * + * OS_BuildSockAddrUn -- + * + * Using the pathname bindPath, fill in the sockaddr_un structure + * *servAddrPtr and the length of this structure *servAddrLen. + * + * The format of the sockaddr_un structure changed incompatibly in + * 4.3BSD Reno. Digital UNIX supports both formats, other systems + * support one or the other. + * + * Results: + * 0 for normal return, -1 for failure (bindPath too long). + * + *---------------------------------------------------------------------- + */ + +static int OS_BuildSockAddrUn(const char *bindPath, + struct sockaddr_un *servAddrPtr, + int *servAddrLen) +{ + int bindPathLen = strlen(bindPath); + +#ifdef HAVE_SOCKADDR_UN_SUN_LEN /* 4.3BSD Reno and later: BSDI, DEC */ + if(bindPathLen >= sizeof(servAddrPtr->sun_path)) { + return -1; + } +#else /* 4.3 BSD Tahoe: Solaris, HPUX, DEC, ... */ + if(bindPathLen > sizeof(servAddrPtr->sun_path)) { + return -1; + } +#endif + memset((char *) servAddrPtr, 0, sizeof(*servAddrPtr)); + servAddrPtr->sun_family = AF_UNIX; + memcpy(servAddrPtr->sun_path, bindPath, bindPathLen); +#ifdef HAVE_SOCKADDR_UN_SUN_LEN /* 4.3BSD Reno and later: BSDI, DEC */ + *servAddrLen = sizeof(servAddrPtr->sun_len) + + sizeof(servAddrPtr->sun_family) + + bindPathLen + 1; + servAddrPtr->sun_len = *servAddrLen; +#else /* 4.3 BSD Tahoe: Solaris, HPUX, DEC, ... */ + *servAddrLen = sizeof(servAddrPtr->sun_family) + bindPathLen; +#endif + return 0; +} +union SockAddrUnion { + struct sockaddr_un unixVariant; + struct sockaddr_in inetVariant; +}; + +/* + * OS_CreateLocalIpcFd -- + * + * This procedure is responsible for creating the listener socket + * on Unix for local process communication. It will create a + * domain socket or a TCP/IP socket bound to "localhost" and return + * a file descriptor to it to the caller. + * + * Results: + * Listener socket created. This call returns either a valid + * file descriptor or -1 on error. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ +int OS_CreateLocalIpcFd(const char *bindPath, int backlog) +{ + int listenSock, servLen; + union SockAddrUnion sa; + int tcp = FALSE; + unsigned long tcp_ia = 0; + char *tp; + short port = 0; + char host[MAXPATHLEN]; + + strcpy(host, bindPath); + if((tp = strchr(host, ':')) != 0) { + *tp++ = 0; + if((port = atoi(tp)) == 0) { + *--tp = ':'; + } else { + tcp = TRUE; + } + } + if(tcp) { + if (!*host || !strcmp(host,"*")) { + tcp_ia = htonl(INADDR_ANY); + } else { + tcp_ia = inet_addr(host); + if (tcp_ia == INADDR_NONE) { + struct hostent * hep; + hep = gethostbyname(host); + if ((!hep) || (hep->h_addrtype != AF_INET || !hep->h_addr_list[0])) { + fprintf(stderr, "Cannot resolve host name %s -- exiting!\n", host); + exit(1); + } + if (hep->h_addr_list[1]) { + fprintf(stderr, "Host %s has multiple addresses ---\n", host); + fprintf(stderr, "you must choose one explicitly!!!\n"); + exit(1); + } + tcp_ia = ((struct in_addr *) (hep->h_addr))->s_addr; + } + } + } + + if(tcp) { + listenSock = socket(AF_INET, SOCK_STREAM, 0); + if(listenSock >= 0) { + int flag = 1; + if(setsockopt(listenSock, SOL_SOCKET, SO_REUSEADDR, + (char *) &flag, sizeof(flag)) < 0) { + fprintf(stderr, "Can't set SO_REUSEADDR.\n"); + exit(1001); + } + } + } else { + listenSock = socket(AF_UNIX, SOCK_STREAM, 0); + } + if(listenSock < 0) { + return -1; + } + + /* + * Bind the listening socket. + */ + if(tcp) { + memset((char *) &sa.inetVariant, 0, sizeof(sa.inetVariant)); + sa.inetVariant.sin_family = AF_INET; + sa.inetVariant.sin_addr.s_addr = tcp_ia; + sa.inetVariant.sin_port = htons(port); + servLen = sizeof(sa.inetVariant); + } else { + unlink(bindPath); + if(OS_BuildSockAddrUn(bindPath, &sa.unixVariant, &servLen)) { + fprintf(stderr, "Listening socket's path name is too long.\n"); + exit(1000); + } + } + if(bind(listenSock, (struct sockaddr *) &sa.unixVariant, servLen) < 0 + || listen(listenSock, backlog) < 0) { + perror("bind/listen"); + exit(errno); + } + + return listenSock; +} + +/* + *---------------------------------------------------------------------- + * + * OS_FcgiConnect -- + * + * Create the socket and connect to the remote application if + * possible. + * + * This was lifted from the cgi-fcgi application and was abstracted + * out because Windows NT does not have a domain socket and must + * use a named pipe which has a different API altogether. + * + * Results: + * -1 if fail or a valid file descriptor if connection succeeds. + * + * Side effects: + * Remote connection established. + * + *---------------------------------------------------------------------- + */ +int OS_FcgiConnect(char *bindPath) +{ + union SockAddrUnion sa; + int servLen, resultSock; + int connectStatus; + char *tp; + char host[MAXPATHLEN]; + short port = 0; + int tcp = FALSE; + + strcpy(host, bindPath); + if((tp = strchr(host, ':')) != 0) { + *tp++ = 0; + if((port = atoi(tp)) == 0) { + *--tp = ':'; + } else { + tcp = TRUE; + } + } + if(tcp == TRUE) { + struct hostent *hp; + if((hp = gethostbyname((*host ? host : "localhost"))) == NULL) { + fprintf(stderr, "Unknown host: %s\n", bindPath); + exit(1000); + } + sa.inetVariant.sin_family = AF_INET; + memcpy(&sa.inetVariant.sin_addr, hp->h_addr, hp->h_length); + sa.inetVariant.sin_port = htons(port); + servLen = sizeof(sa.inetVariant); + resultSock = socket(AF_INET, SOCK_STREAM, 0); + } else { + if(OS_BuildSockAddrUn(bindPath, &sa.unixVariant, &servLen)) { + fprintf(stderr, "Listening socket's path name is too long.\n"); + exit(1000); + } + resultSock = socket(AF_UNIX, SOCK_STREAM, 0); + } + + ASSERT(resultSock >= 0); + connectStatus = connect(resultSock, (struct sockaddr *) &sa.unixVariant, + servLen); + if(connectStatus >= 0) { + return resultSock; + } else { + /* + * Most likely (errno == ENOENT || errno == ECONNREFUSED) + * and no FCGI application server is running. + */ + close(resultSock); + return -1; + } +} + +/* + *-------------------------------------------------------------- + * + * OS_Read -- + * + * Pass through to the unix read function. + * + * Results: + * Returns number of byes read, 0, or -1 failure: errno + * contains actual error. + * + * Side effects: + * None. + * + *-------------------------------------------------------------- + */ +int OS_Read(int fd, char * buf, size_t len) +{ + if (shutdownNow) return -1; + return(read(fd, buf, len)); +} + +/* + *-------------------------------------------------------------- + * + * OS_Write -- + * + * Pass through to unix write function. + * + * Results: + * Returns number of byes read, 0, or -1 failure: errno + * contains actual error. + * + * Side effects: + * none. + * + *-------------------------------------------------------------- + */ +int OS_Write(int fd, char * buf, size_t len) +{ + if (shutdownNow) return -1; + return(write(fd, buf, len)); +} + +/* + *---------------------------------------------------------------------- + * + * OS_SpawnChild -- + * + * Spawns a new FastCGI listener process. + * + * Results: + * 0 if success, -1 if error. + * + * Side effects: + * Child process spawned. + * + *---------------------------------------------------------------------- + */ +int OS_SpawnChild(char *appPath, int listenFd) +{ + int forkResult; + + forkResult = fork(); + if(forkResult < 0) { + exit(errno); + } + + if(forkResult == 0) { + /* + * Close STDIN unconditionally. It's used by the parent + * process for CGI communication. The FastCGI applciation + * will be replacing this with the FastCGI listenFd IF + * STDIN_FILENO is the same as FCGI_LISTENSOCK_FILENO + * (which it is on Unix). Regardless, STDIN, STDOUT, and + * STDERR will be closed as the FastCGI process uses a + * multiplexed socket in their place. + */ + close(STDIN_FILENO); + + /* + * If the listenFd is already the value of FCGI_LISTENSOCK_FILENO + * we're set. If not, change it so the child knows where to + * get the listen socket from. + */ + if(listenFd != FCGI_LISTENSOCK_FILENO) { + dup2(listenFd, FCGI_LISTENSOCK_FILENO); + close(listenFd); + } + + close(STDOUT_FILENO); + close(STDERR_FILENO); + + /* + * We're a child. Exec the application. + * + * XXX: entire environment passes through + */ + execl(appPath, appPath, NULL); + /* + * XXX: Can't do this as we've already closed STDERR!!! + * + * perror("exec"); + */ + exit(errno); + } + return 0; +} + +/* + *-------------------------------------------------------------- + * + * OS_AsyncReadStdin -- + * + * This initiates an asynchronous read on the standard + * input handle. + * + * The abstraction is necessary because Windows NT does not + * have a clean way of "select"ing a file descriptor for + * I/O. + * + * Results: + * -1 if error, 0 otherwise. + * + * Side effects: + * Asynchronous bit is set in the readfd variable and + * request is enqueued. + * + *-------------------------------------------------------------- + */ +int OS_AsyncReadStdin(void *buf, int len, OS_AsyncProc procPtr, + ClientData clientData) +{ + int index = AIO_RD_IX(STDIN_FILENO); + + asyncIoInUse = TRUE; + ASSERT(asyncIoTable[index].inUse == 0); + asyncIoTable[index].procPtr = procPtr; + asyncIoTable[index].clientData = clientData; + asyncIoTable[index].fd = STDIN_FILENO; + asyncIoTable[index].len = len; + asyncIoTable[index].offset = 0; + asyncIoTable[index].buf = buf; + asyncIoTable[index].inUse = 1; + FD_SET(STDIN_FILENO, &readFdSet); + if(STDIN_FILENO > maxFd) + maxFd = STDIN_FILENO; + return 0; +} + +static void GrowAsyncTable(void) +{ + int oldTableSize = asyncIoTableSize; + + asyncIoTableSize = asyncIoTableSize * 2; + asyncIoTable = (AioInfo *)realloc(asyncIoTable, asyncIoTableSize * sizeof(AioInfo)); + if(asyncIoTable == NULL) { + errno = ENOMEM; + exit(errno); + } + memset((char *) &asyncIoTable[oldTableSize], 0, + oldTableSize * sizeof(AioInfo)); + +} + +/* + *-------------------------------------------------------------- + * + * OS_AsyncRead -- + * + * This initiates an asynchronous read on the file + * handle which may be a socket or named pipe. + * + * We also must save the ProcPtr and ClientData, so later + * when the io completes, we know who to call. + * + * We don't look at any results here (the ReadFile may + * return data if it is cached) but do all completion + * processing in OS_Select when we get the io completion + * port done notifications. Then we call the callback. + * + * Results: + * -1 if error, 0 otherwise. + * + * Side effects: + * Asynchronous I/O operation is queued for completion. + * + *-------------------------------------------------------------- + */ +int OS_AsyncRead(int fd, int offset, void *buf, int len, + OS_AsyncProc procPtr, ClientData clientData) +{ + int index = AIO_RD_IX(fd); + + ASSERT(asyncIoTable != NULL); + asyncIoInUse = TRUE; + + if(fd > maxFd) + maxFd = fd; + + if(index >= asyncIoTableSize) { + GrowAsyncTable(); + } + + ASSERT(asyncIoTable[index].inUse == 0); + asyncIoTable[index].procPtr = procPtr; + asyncIoTable[index].clientData = clientData; + asyncIoTable[index].fd = fd; + asyncIoTable[index].len = len; + asyncIoTable[index].offset = offset; + asyncIoTable[index].buf = buf; + asyncIoTable[index].inUse = 1; + FD_SET(fd, &readFdSet); + return 0; +} + +/* + *-------------------------------------------------------------- + * + * OS_AsyncWrite -- + * + * This initiates an asynchronous write on the "fake" file + * descriptor (which may be a file, socket, or named pipe). + * We also must save the ProcPtr and ClientData, so later + * when the io completes, we know who to call. + * + * We don't look at any results here (the WriteFile generally + * completes immediately) but do all completion processing + * in OS_DoIo when we get the io completion port done + * notifications. Then we call the callback. + * + * Results: + * -1 if error, 0 otherwise. + * + * Side effects: + * Asynchronous I/O operation is queued for completion. + * + *-------------------------------------------------------------- + */ +int OS_AsyncWrite(int fd, int offset, void *buf, int len, + OS_AsyncProc procPtr, ClientData clientData) +{ + int index = AIO_WR_IX(fd); + + asyncIoInUse = TRUE; + + if(fd > maxFd) + maxFd = fd; + + if(index >= asyncIoTableSize) { + GrowAsyncTable(); + } + + ASSERT(asyncIoTable[index].inUse == 0); + asyncIoTable[index].procPtr = procPtr; + asyncIoTable[index].clientData = clientData; + asyncIoTable[index].fd = fd; + asyncIoTable[index].len = len; + asyncIoTable[index].offset = offset; + asyncIoTable[index].buf = buf; + asyncIoTable[index].inUse = 1; + FD_SET(fd, &writeFdSet); + return 0; +} + +/* + *-------------------------------------------------------------- + * + * OS_Close -- + * + * Closes the descriptor. This is a pass through to the + * Unix close. + * + * Results: + * 0 for success, -1 on failure + * + * Side effects: + * None. + * + *-------------------------------------------------------------- + */ +int OS_Close(int fd) +{ + if (fd == -1) + return 0; + + if (asyncIoInUse) { + int index = AIO_RD_IX(fd); + + FD_CLR(fd, &readFdSet); + FD_CLR(fd, &readFdSetPost); + if (asyncIoTable[index].inUse != 0) { + asyncIoTable[index].inUse = 0; + } + + FD_CLR(fd, &writeFdSet); + FD_CLR(fd, &writeFdSetPost); + index = AIO_WR_IX(fd); + if (asyncIoTable[index].inUse != 0) { + asyncIoTable[index].inUse = 0; + } + + if (maxFd == fd) { + maxFd--; + } + } + return close(fd); +} + +/* + *-------------------------------------------------------------- + * + * OS_CloseRead -- + * + * Cancel outstanding asynchronous reads and prevent subsequent + * reads from completing. + * + * Results: + * Socket or file is shutdown. Return values mimic Unix shutdown: + * 0 success, -1 failure + * + *-------------------------------------------------------------- + */ +int OS_CloseRead(int fd) +{ + if(asyncIoTable[AIO_RD_IX(fd)].inUse != 0) { + asyncIoTable[AIO_RD_IX(fd)].inUse = 0; + FD_CLR(fd, &readFdSet); + } + + return shutdown(fd, 0); +} + +/* + *-------------------------------------------------------------- + * + * OS_DoIo -- + * + * This function was formerly OS_Select. It's purpose is + * to pull I/O completion events off the queue and dispatch + * them to the appropriate place. + * + * Results: + * Returns 0. + * + * Side effects: + * Handlers are called. + * + *-------------------------------------------------------------- + */ +int OS_DoIo(struct timeval *tmo) +{ + int fd, len, selectStatus; + OS_AsyncProc procPtr; + ClientData clientData; + AioInfo *aioPtr; + fd_set readFdSetCpy; + fd_set writeFdSetCpy; + + asyncIoInUse = TRUE; + FD_ZERO(&readFdSetCpy); + FD_ZERO(&writeFdSetCpy); + + for(fd = 0; fd <= maxFd; fd++) { + if(FD_ISSET(fd, &readFdSet)) { + FD_SET(fd, &readFdSetCpy); + } + if(FD_ISSET(fd, &writeFdSet)) { + FD_SET(fd, &writeFdSetCpy); + } + } + + /* + * If there were no completed events from a prior call, see if there's + * any work to do. + */ + if(numRdPosted == 0 && numWrPosted == 0) { + selectStatus = select((maxFd+1), &readFdSetCpy, &writeFdSetCpy, + NULL, tmo); + if(selectStatus < 0) { + exit(errno); + } + + for(fd = 0; fd <= maxFd; fd++) { + /* + * Build up a list of completed events. We'll work off of + * this list as opposed to looping through the read and write + * fd sets since they can be affected by a callbacl routine. + */ + if(FD_ISSET(fd, &readFdSetCpy)) { + numRdPosted++; + FD_SET(fd, &readFdSetPost); + FD_CLR(fd, &readFdSet); + } + + if(FD_ISSET(fd, &writeFdSetCpy)) { + numWrPosted++; + FD_SET(fd, &writeFdSetPost); + FD_CLR(fd, &writeFdSet); + } + } + } + + if(numRdPosted == 0 && numWrPosted == 0) + return 0; + + for(fd = 0; fd <= maxFd; fd++) { + /* + * Do reads and dispatch callback. + */ + if(FD_ISSET(fd, &readFdSetPost) + && asyncIoTable[AIO_RD_IX(fd)].inUse) { + + numRdPosted--; + FD_CLR(fd, &readFdSetPost); + aioPtr = &asyncIoTable[AIO_RD_IX(fd)]; + + len = read(aioPtr->fd, aioPtr->buf, aioPtr->len); + + procPtr = aioPtr->procPtr; + aioPtr->procPtr = NULL; + clientData = aioPtr->clientData; + aioPtr->inUse = 0; + + (*procPtr)(clientData, len); + } + + /* + * Do writes and dispatch callback. + */ + if(FD_ISSET(fd, &writeFdSetPost) && + asyncIoTable[AIO_WR_IX(fd)].inUse) { + + numWrPosted--; + FD_CLR(fd, &writeFdSetPost); + aioPtr = &asyncIoTable[AIO_WR_IX(fd)]; + + len = write(aioPtr->fd, aioPtr->buf, aioPtr->len); + + procPtr = aioPtr->procPtr; + aioPtr->procPtr = NULL; + clientData = aioPtr->clientData; + aioPtr->inUse = 0; + (*procPtr)(clientData, len); + } + } + return 0; +} + +/* + * Not all systems have strdup(). + * @@@ autoconf should determine whether or not this is needed, but for now.. + */ +static char * str_dup(const char * str) +{ + char * sdup = (char *) malloc(strlen(str) + 1); + + if (sdup) + strcpy(sdup, str); + + return sdup; +} + +/* + *---------------------------------------------------------------------- + * + * ClientAddrOK -- + * + * Checks if a client address is in a list of allowed addresses + * + * Results: + * TRUE if address list is empty or client address is present + * in the list, FALSE otherwise. + * + *---------------------------------------------------------------------- + */ +static int ClientAddrOK(struct sockaddr_in *saPtr, const char *clientList) +{ + int result = FALSE; + char *clientListCopy, *cur, *next; + + if (clientList == NULL || *clientList == '\0') { + return TRUE; + } + + clientListCopy = str_dup(clientList); + + for (cur = clientListCopy; cur != NULL; cur = next) { + next = strchr(cur, ','); + if (next != NULL) { + *next++ = '\0'; + } + if (inet_addr(cur) == saPtr->sin_addr.s_addr) { + result = TRUE; + break; + } + } + + free(clientListCopy); + return result; +} + +/* + *---------------------------------------------------------------------- + * + * AcquireLock -- + * + * On platforms that implement concurrent calls to accept + * on a shared listening ipcFd, returns 0. On other platforms, + * acquires an exclusive lock across all processes sharing a + * listening ipcFd, blocking until the lock has been acquired. + * + * Results: + * 0 for successful call, -1 in case of system error (fatal). + * + * Side effects: + * This process now has the exclusive lock. + * + *---------------------------------------------------------------------- + */ +static int AcquireLock(int sock, int fail_on_intr) +{ +#ifdef USE_LOCKING + do { + struct flock lock; + lock.l_type = F_WRLCK; + lock.l_start = 0; + lock.l_whence = SEEK_SET; + lock.l_len = 0; + + if (fcntl(sock, F_SETLKW, &lock) != -1) + return 0; + } while (errno == EINTR + && ! fail_on_intr + && ! shutdownPending); + + return -1; + +#else + return 0; +#endif +} + +/* + *---------------------------------------------------------------------- + * + * ReleaseLock -- + * + * On platforms that implement concurrent calls to accept + * on a shared listening ipcFd, does nothing. On other platforms, + * releases an exclusive lock acquired by AcquireLock. + * + * Results: + * 0 for successful call, -1 in case of system error (fatal). + * + * Side effects: + * This process no longer holds the lock. + * + *---------------------------------------------------------------------- + */ +static int ReleaseLock(int sock) +{ +#ifdef USE_LOCKING + do { + struct flock lock; + lock.l_type = F_UNLCK; + lock.l_start = 0; + lock.l_whence = SEEK_SET; + lock.l_len = 0; + + if (fcntl(sock, F_SETLK, &lock) != -1) + return 0; + } while (errno == EINTR); + + return -1; + +#else + return 0; +#endif +} + +/********************************************************************** + * Determine if the errno resulting from a failed accept() warrants a + * retry or exit(). Based on Apache's http_main.c accept() handling + * and Stevens' Unix Network Programming Vol 1, 2nd Ed, para. 15.6. + */ +static int is_reasonable_accept_errno (const int error) +{ + switch (error) { +#ifdef EPROTO + /* EPROTO on certain older kernels really means ECONNABORTED, so + * we need to ignore it for them. See discussion in new-httpd + * archives nh.9701 search for EPROTO. Also see nh.9603, search + * for EPROTO: There is potentially a bug in Solaris 2.x x<6, and + * other boxes that implement tcp sockets in userland (i.e. on top of + * STREAMS). On these systems, EPROTO can actually result in a fatal + * loop. See PR#981 for example. It's hard to handle both uses of + * EPROTO. */ + case EPROTO: +#endif +#ifdef ECONNABORTED + case ECONNABORTED: +#endif + /* Linux generates the rest of these, other tcp stacks (i.e. + * bsd) tend to hide them behind getsockopt() interfaces. They + * occur when the net goes sour or the client disconnects after the + * three-way handshake has been done in the kernel but before + * userland has picked up the socket. */ +#ifdef ECONNRESET + case ECONNRESET: +#endif +#ifdef ETIMEDOUT + case ETIMEDOUT: +#endif +#ifdef EHOSTUNREACH + case EHOSTUNREACH: +#endif +#ifdef ENETUNREACH + case ENETUNREACH: +#endif + return 1; + + default: + return 0; + } +} + +/********************************************************************** + * This works around a problem on Linux 2.0.x and SCO Unixware (maybe + * others?). When a connect() is made to a Unix Domain socket, but its + * not accept()ed before the web server gets impatient and close()s, an + * accept() results in a valid file descriptor, but no data to read. + * This causes a block on the first read() - which never returns! + * + * Another approach to this is to write() to the socket to provoke a + * SIGPIPE, but this is a pain because of the FastCGI protocol, the fact + * that whatever is written has to be universally ignored by all FastCGI + * web servers, and a SIGPIPE handler has to be installed which returns + * (or SIGPIPE is ignored). + * + * READABLE_UNIX_FD_DROP_DEAD_TIMEVAL = 2,0 by default. + * + * Making it shorter is probably safe, but I'll leave that to you. Making + * it 0,0 doesn't work reliably. The shorter you can reliably make it, + * the faster your application will be able to recover (waiting 2 seconds + * may _cause_ the problem when there is a very high demand). At any rate, + * this is better than perma-blocking. + */ +static int is_af_unix_keeper(const int fd) +{ + struct timeval tval = { READABLE_UNIX_FD_DROP_DEAD_TIMEVAL }; + fd_set read_fds; + + FD_ZERO(&read_fds); + FD_SET(fd, &read_fds); + + return select(fd + 1, &read_fds, NULL, NULL, &tval) >= 0 && FD_ISSET(fd, &read_fds); +} + +/* + *---------------------------------------------------------------------- + * + * OS_Accept -- + * + * Accepts a new FastCGI connection. This routine knows whether + * we're dealing with TCP based sockets or NT Named Pipes for IPC. + * + * Results: + * -1 if the operation fails, otherwise this is a valid IPC fd. + * + * Side effects: + * New IPC connection is accepted. + * + *---------------------------------------------------------------------- + */ +int OS_Accept(int listen_sock, int fail_on_intr, const char *webServerAddrs) +{ + int socket = -1; + union { + struct sockaddr_un un; + struct sockaddr_in in; + } sa; + + for (;;) { + if (AcquireLock(listen_sock, fail_on_intr)) + return -1; + + for (;;) { + do { +#ifdef HAVE_SOCKLEN + socklen_t len = sizeof(sa); +#else + int len = sizeof(sa); +#endif + if (shutdownPending) break; + /* There's a window here */ + + socket = accept(listen_sock, (struct sockaddr *)&sa, &len); + } while (socket < 0 + && errno == EINTR + && ! fail_on_intr + && ! shutdownPending); + + if (socket < 0) { + if (shutdownPending || ! is_reasonable_accept_errno(errno)) { + int errnoSave = errno; + + ReleaseLock(listen_sock); + + if (! shutdownPending) { + errno = errnoSave; + } + + return (-1); + } + errno = 0; + } + else { /* socket >= 0 */ + int set = 1; + + if (sa.in.sin_family != AF_INET) + break; + +#ifdef TCP_NODELAY + /* No replies to outgoing data, so disable Nagle */ + setsockopt(socket, IPPROTO_TCP, TCP_NODELAY, (char *)&set, sizeof(set)); +#endif + + /* Check that the client IP address is approved */ + if (ClientAddrOK(&sa.in, webServerAddrs)) + break; + + close(socket); + } /* socket >= 0 */ + } /* for(;;) */ + + if (ReleaseLock(listen_sock)) + return (-1); + + if (sa.in.sin_family != AF_UNIX || is_af_unix_keeper(socket)) + break; + + close(socket); + } /* while(1) - lock */ + + return (socket); +} + +/* + *---------------------------------------------------------------------- + * + * OS_IpcClose + * + * OS IPC routine to close an IPC connection. + * + * Results: + * + * + * Side effects: + * IPC connection is closed. + * + *---------------------------------------------------------------------- + */ +int OS_IpcClose(int ipcFd) +{ + return OS_Close(ipcFd); +} + +/* + *---------------------------------------------------------------------- + * + * OS_IsFcgi -- + * + * Determines whether this process is a FastCGI process or not. + * + * Results: + * Returns 1 if FastCGI, 0 if not. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ +int OS_IsFcgi(int sock) +{ + union { + struct sockaddr_in in; + struct sockaddr_un un; + } sa; +#ifdef HAVE_SOCKLEN + socklen_t len = sizeof(sa); +#else + int len = sizeof(sa); +#endif + + errno = 0; + + if (getpeername(sock, (struct sockaddr *)&sa, &len) != 0 && errno == ENOTCONN) { + return TRUE; + } + else { + return FALSE; + } +} + +/* + *---------------------------------------------------------------------- + * + * OS_SetFlags -- + * + * Sets selected flag bits in an open file descriptor. + * + *---------------------------------------------------------------------- + */ +void OS_SetFlags(int fd, int flags) +{ + int val; + if((val = fcntl(fd, F_GETFL, 0)) < 0) { + exit(errno); + } + val |= flags; + if(fcntl(fd, F_SETFL, val) < 0) { + exit(errno); + } +} diff --git a/sapi/cgi/libfcgi/os_win32.c b/sapi/cgi/libfcgi/os_win32.c new file mode 100644 index 00000000000..b44b9fab731 --- /dev/null +++ b/sapi/cgi/libfcgi/os_win32.c @@ -0,0 +1,1914 @@ +/* + * os_win32.c -- + * + * + * Copyright (c) 1995 Open Market, Inc. + * All rights reserved. + * + * This file contains proprietary and confidential information and + * remains the unpublished property of Open Market, Inc. Use, + * disclosure, or reproduction is prohibited except as permitted by + * express written license agreement with Open Market, Inc. + * + * Bill Snapper + * snapper@openmarket.com + * + * (Special thanks to Karen and Bill. They made my job much easier and + * significantly more enjoyable.) + */ +#ifndef lint +static const char rcsid[] = "$Id$"; +#endif /* not lint */ + +#define WIN32_LEAN_AND_MEAN +#include +#include +#include +#include +#include +#include + +#define DLLAPI __declspec(dllexport) + +#include "fcgimisc.h" +#include "fcgios.h" + +#define WIN32_OPEN_MAX 128 /* XXX: Small hack */ + +/* + * millisecs to wait for a client connection before checking the + * shutdown flag (then go back to waiting for a connection, etc). + */ +#define ACCEPT_TIMEOUT 1000 + +#define LOCALHOST "localhost" + +static HANDLE hIoCompPort = INVALID_HANDLE_VALUE; +static HANDLE hStdinCompPort = INVALID_HANDLE_VALUE; +static HANDLE hStdinThread = INVALID_HANDLE_VALUE; + +static HANDLE stdioHandles[3] = {INVALID_HANDLE_VALUE, INVALID_HANDLE_VALUE, + INVALID_HANDLE_VALUE}; + +static HANDLE acceptMutex = INVALID_HANDLE_VALUE; + +static BOOLEAN shutdownPending = FALSE; +static BOOLEAN shutdownNow = FALSE; + +/* + * An enumeration of the file types + * supported by the FD_TABLE structure. + * + * XXX: Not all currently supported. This allows for future + * functionality. + */ +typedef enum { + FD_UNUSED, + FD_FILE_SYNC, + FD_FILE_ASYNC, + FD_SOCKET_SYNC, + FD_SOCKET_ASYNC, + FD_PIPE_SYNC, + FD_PIPE_ASYNC +} FILE_TYPE; + +typedef union { + HANDLE fileHandle; + SOCKET sock; + unsigned int value; +} DESCRIPTOR; + +/* + * Structure used to map file handle and socket handle + * values into values that can be used to create unix-like + * select bitmaps, read/write for both sockets/files. + */ +struct FD_TABLE { + DESCRIPTOR fid; + FILE_TYPE type; + char *path; + DWORD Errno; + unsigned long instance; + int status; + int offset; /* only valid for async file writes */ + LPDWORD offsetHighPtr; /* pointers to offset high and low words */ + LPDWORD offsetLowPtr; /* only valid for async file writes (logs) */ + HANDLE hMapMutex; /* mutex handle for multi-proc offset update */ + LPVOID ovList; /* List of associated OVERLAPPED_REQUESTs */ +}; + +/* + * XXX Note there is no dyanmic sizing of this table, so if the + * number of open file descriptors exceeds WIN32_OPEN_MAX the + * app will blow up. + */ +static struct FD_TABLE fdTable[WIN32_OPEN_MAX]; + +static CRITICAL_SECTION fdTableCritical; + +struct OVERLAPPED_REQUEST { + OVERLAPPED overlapped; + unsigned long instance; /* file instance (won't match after a close) */ + OS_AsyncProc procPtr; /* callback routine */ + ClientData clientData; /* callback argument */ + ClientData clientData1; /* additional clientData */ +}; +typedef struct OVERLAPPED_REQUEST *POVERLAPPED_REQUEST; + +static const char *bindPathPrefix = "\\\\.\\pipe\\FastCGI\\"; + +static FILE_TYPE listenType = FD_UNUSED; + +// XXX This should be a DESCRIPTOR +static HANDLE hListen = INVALID_HANDLE_VALUE; + +static OVERLAPPED listenOverlapped; +static BOOLEAN libInitialized = FALSE; + +/* + *-------------------------------------------------------------- + * + * Win32NewDescriptor -- + * + * Set up for I/O descriptor masquerading. + * + * Results: + * Returns "fake id" which masquerades as a UNIX-style "small + * non-negative integer" file/socket descriptor. + * Win32_* routine below will "do the right thing" based on the + * descriptor's actual type. -1 indicates failure. + * + * Side effects: + * Entry in fdTable is reserved to represent the socket/file. + * + *-------------------------------------------------------------- + */ +static int Win32NewDescriptor(FILE_TYPE type, int fd, int desiredFd) +{ + int index = -1; + + EnterCriticalSection(&fdTableCritical); + + /* + * If desiredFd is set, try to get this entry (this is used for + * mapping stdio handles). Otherwise try to get the fd entry. + * If this is not available, find a the first empty slot. . + */ + if (desiredFd >= 0 && desiredFd < WIN32_OPEN_MAX) + { + if (fdTable[desiredFd].type == FD_UNUSED) + { + index = desiredFd; + } + } + else if (fd > 0) + { + if (fd < WIN32_OPEN_MAX && fdTable[fd].type == FD_UNUSED) + { + index = fd; + } + else + { + int i; + + for (i = 1; i < WIN32_OPEN_MAX; ++i) + { + if (fdTable[i].type == FD_UNUSED) + { + index = i; + break; + } + } + } + } + + if (index != -1) + { + fdTable[index].fid.value = fd; + fdTable[index].type = type; + fdTable[index].path = NULL; + fdTable[index].Errno = NO_ERROR; + fdTable[index].status = 0; + fdTable[index].offset = -1; + fdTable[index].offsetHighPtr = fdTable[index].offsetLowPtr = NULL; + fdTable[index].hMapMutex = NULL; + fdTable[index].ovList = NULL; + } + + LeaveCriticalSection(&fdTableCritical); + return index; +} + +/* + *-------------------------------------------------------------- + * + * StdinThread-- + * + * This thread performs I/O on stadard input. It is needed + * because you can't guarantee that all applications will + * create standard input with sufficient access to perform + * asynchronous I/O. Since we don't want to block the app + * reading from stdin we make it look like it's using I/O + * completion ports to perform async I/O. + * + * Results: + * Data is read from stdin and posted to the io completion + * port. + * + * Side effects: + * None. + * + *-------------------------------------------------------------- + */ +static void StdinThread(LPDWORD startup){ + + int doIo = TRUE; + unsigned long fd; + unsigned long bytesRead; + POVERLAPPED_REQUEST pOv; + + // Touch the arg to prevent warning + startup = NULL; + + while(doIo) { + /* + * Block until a request to read from stdin comes in or a + * request to terminate the thread arrives (fd = -1). + */ + if (!GetQueuedCompletionStatus(hStdinCompPort, &bytesRead, &fd, + (LPOVERLAPPED *)&pOv, (DWORD)-1) && !pOv) { + doIo = 0; + break; + } + + ASSERT((fd == STDIN_FILENO) || (fd == -1)); + if(fd == -1) { + doIo = 0; + break; + } + ASSERT(pOv->clientData1 != NULL); + + if(ReadFile(stdioHandles[STDIN_FILENO], pOv->clientData1, bytesRead, + &bytesRead, NULL)) { + PostQueuedCompletionStatus(hIoCompPort, bytesRead, + STDIN_FILENO, (LPOVERLAPPED)pOv); + } else { + doIo = 0; + break; + } + } + + ExitThread(0); +} + +void OS_ShutdownPending(void) +{ + shutdownPending = TRUE; +} + +/* XXX Need a shutdown now event */ +static DWORD WINAPI ShutdownRequestThread(LPVOID arg) +{ + HANDLE shutdownEvent = (HANDLE) arg; + + if (WaitForSingleObject(shutdownEvent, INFINITE) == WAIT_FAILED) + { + // Assuming it will happen again, all we can do is exit the thread + return 1; + } + else + { + // "Simple reads and writes to properly-aligned 32-bit variables are atomic" + shutdownPending = TRUE; + + // Before an accept() is entered the shutdownPending flag is checked. + // If set, OS_Accept() will return -1. If not, it waits + // on a connection request for one second, checks the flag, & repeats. + // Only one process/thread is allowed to do this at time by + // wrapping the accept() with mutex. + return 0; + } +} + +/* + *-------------------------------------------------------------- + * + * OS_LibInit -- + * + * Set up the OS library for use. + * + * Results: + * Returns 0 if success, -1 if not. + * + * Side effects: + * Sockets initialized, pseudo file descriptors setup, etc. + * + *-------------------------------------------------------------- + */ +int OS_LibInit(int stdioFds[3]) +{ + WORD wVersion; + WSADATA wsaData; + int err; + int fakeFd; + DWORD threadId; + char *cLenPtr = NULL; + char *val = NULL; + + if(libInitialized) + return 0; + + InitializeCriticalSection(&fdTableCritical); + + /* + * Initialize windows sockets library. + */ + wVersion = MAKEWORD(2,0); + err = WSAStartup( wVersion, &wsaData ); + if (err) { + fprintf(stderr, "Error starting Windows Sockets. Error: %d", + WSAGetLastError()); + //exit(111); + return -1; + } + + /* + * Create the I/O completion port to be used for our I/O queue. + */ + if (hIoCompPort == INVALID_HANDLE_VALUE) { + hIoCompPort = CreateIoCompletionPort (INVALID_HANDLE_VALUE, NULL, + 0, 1); + if(hIoCompPort == INVALID_HANDLE_VALUE) { + printf("

OS_LibInit Failed CreateIoCompletionPort! ERROR: %d

\r\n\r\n", + GetLastError()); + return -1; + } + } + + /* + * If a shutdown event is in the env, save it (I don't see any to + * remove it from the environment out from under the application). + * Spawn a thread to wait on the shutdown request. + */ + val = getenv(SHUTDOWN_EVENT_NAME); + if (val != NULL) + { + HANDLE shutdownEvent = (HANDLE) atoi(val); + + if (! CreateThread(NULL, 0, ShutdownRequestThread, + shutdownEvent, 0, NULL)) + { + return -1; + } + } + + /* + * If an accept mutex is in the env, save it and remove it. + */ + val = getenv(MUTEX_VARNAME); + if (val != NULL) + { + acceptMutex = (HANDLE) atoi(val); + } + + + /* + * Determine if this library is being used to listen for FastCGI + * connections. This is communicated by STDIN containing a + * valid handle to a listener object. In this case, both the + * "stdout" and "stderr" handles will be INVALID (ie. closed) by + * the starting process. + * + * The trick is determining if this is a pipe or a socket... + * + * XXX: Add the async accept test to determine socket or handle to a + * pipe!!! + */ + if((GetStdHandle(STD_OUTPUT_HANDLE) == INVALID_HANDLE_VALUE) && + (GetStdHandle(STD_ERROR_HANDLE) == INVALID_HANDLE_VALUE) && + (GetStdHandle(STD_INPUT_HANDLE) != INVALID_HANDLE_VALUE) ) + { + DWORD pipeMode = PIPE_READMODE_BYTE | PIPE_WAIT; + HANDLE oldStdIn = GetStdHandle(STD_INPUT_HANDLE); + + // Move the handle to a "low" number + if (! DuplicateHandle(GetCurrentProcess(), oldStdIn, + GetCurrentProcess(), &hListen, + 0, TRUE, DUPLICATE_SAME_ACCESS)) + { + return -1; + } + + if (! SetStdHandle(STD_INPUT_HANDLE, hListen)) + { + return -1; + } + + CloseHandle(oldStdIn); + + /* + * Set the pipe handle state so that it operates in wait mode. + * + * NOTE: The listenFd is not mapped to a pseudo file descriptor + * as all work done on it is contained to the OS library. + * + * XXX: Initial assumption is that SetNamedPipeHandleState will + * fail if this is an IP socket... + */ + if (SetNamedPipeHandleState(hListen, &pipeMode, NULL, NULL)) + { + listenType = FD_PIPE_SYNC; + listenOverlapped.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); + } + else + { + listenType = FD_SOCKET_SYNC; + } + } + + /* + * If there are no stdioFds passed in, we're done. + */ + if(stdioFds == NULL) { + libInitialized = 1; + return 0; + } + + /* + * Setup standard input asynchronous I/O. There is actually a separate + * thread spawned for this purpose. The reason for this is that some + * web servers use anonymous pipes for the connection between itself + * and a CGI application. Anonymous pipes can't perform asynchronous + * I/O or use I/O completion ports. Therefore in order to present a + * consistent I/O dispatch model to an application we emulate I/O + * completion port behavior by having the standard input thread posting + * messages to the hIoCompPort which look like a complete overlapped + * I/O structure. This keeps the event dispatching simple from the + * application perspective. + */ + stdioHandles[STDIN_FILENO] = GetStdHandle(STD_INPUT_HANDLE); + + if(!SetHandleInformation(stdioHandles[STDIN_FILENO], + HANDLE_FLAG_INHERIT, 0)) { +/* + * XXX: Causes error when run from command line. Check KB + err = GetLastError(); + DebugBreak(); + exit(99); + */ + return -1; + } + + if ((fakeFd = Win32NewDescriptor(FD_PIPE_SYNC, + (int)stdioHandles[STDIN_FILENO], + STDIN_FILENO)) == -1) { + return -1; + } else { + /* + * Set stdin equal to our pseudo FD and create the I/O completion + * port to be used for async I/O. + */ + stdioFds[STDIN_FILENO] = fakeFd; + } + + /* + * Create the I/O completion port to be used for communicating with + * the thread doing I/O on standard in. This port will carry read + * and possibly thread termination requests to the StdinThread. + */ + if (hStdinCompPort == INVALID_HANDLE_VALUE) { + hStdinCompPort = CreateIoCompletionPort (INVALID_HANDLE_VALUE, NULL, + 0, 1); + if(hStdinCompPort == INVALID_HANDLE_VALUE) { + printf("

OS_LibInit Failed CreateIoCompletionPort: STDIN! ERROR: %d

\r\n\r\n", + GetLastError()); + return -1; + } + } + + /* + * Create the thread that will read stdin if the CONTENT_LENGTH + * is non-zero. + */ + if((cLenPtr = getenv("CONTENT_LENGTH")) != NULL && + atoi(cLenPtr) > 0) { + hStdinThread = CreateThread(NULL, 8192, + (LPTHREAD_START_ROUTINE)&StdinThread, + NULL, 0, &threadId); + if (hStdinThread == NULL) { + printf("

OS_LibInit Failed to create STDIN thread! ERROR: %d

\r\n\r\n", + GetLastError()); + return -1; + } + } + + /* + * STDOUT will be used synchronously. + * + * XXX: May want to convert this so that it could be used for OVERLAPPED + * I/O later. If so, model it after the Stdin I/O as stdout is + * also incapable of async I/O on some servers. + */ + stdioHandles[STDOUT_FILENO] = GetStdHandle(STD_OUTPUT_HANDLE); + if(!SetHandleInformation(stdioHandles[STDOUT_FILENO], + HANDLE_FLAG_INHERIT, FALSE)) { + DebugBreak(); + //exit(99); + return -1; + } + + if ((fakeFd = Win32NewDescriptor(FD_PIPE_SYNC, + (int)stdioHandles[STDOUT_FILENO], + STDOUT_FILENO)) == -1) { + return -1; + } else { + /* + * Set stdout equal to our pseudo FD + */ + stdioFds[STDOUT_FILENO] = fakeFd; + } + + stdioHandles[STDERR_FILENO] = GetStdHandle(STD_ERROR_HANDLE); + if(!SetHandleInformation(stdioHandles[STDERR_FILENO], + HANDLE_FLAG_INHERIT, FALSE)) { + DebugBreak(); + //exit(99); + return -1; + } + if ((fakeFd = Win32NewDescriptor(FD_PIPE_SYNC, + (int)stdioHandles[STDERR_FILENO], + STDERR_FILENO)) == -1) { + return -1; + } else { + /* + * Set stderr equal to our pseudo FD + */ + stdioFds[STDERR_FILENO] = fakeFd; + } + + return 0; +} + +/* + *-------------------------------------------------------------- + * + * OS_LibShutdown -- + * + * Shutdown the OS library. + * + * Results: + * None. + * + * Side effects: + * Memory freed, handles closed. + * + *-------------------------------------------------------------- + */ +void OS_LibShutdown() +{ + + if (hIoCompPort != INVALID_HANDLE_VALUE) + { + CloseHandle(hIoCompPort); + hIoCompPort = INVALID_HANDLE_VALUE; + } + + if (hStdinCompPort != INVALID_HANDLE_VALUE) + { + CloseHandle(hStdinCompPort); + hStdinCompPort = INVALID_HANDLE_VALUE; + } + + if (acceptMutex != INVALID_HANDLE_VALUE) + { + ReleaseMutex(acceptMutex); + CloseHandle(acceptMutex); + } + + /* we only want to do this if we're not a web server */ + if (stdioHandles[0] != INVALID_HANDLE_VALUE) { + DisconnectNamedPipe(hListen); + CancelIo(hListen); + } + + DeleteCriticalSection(&fdTableCritical); + WSACleanup(); +} + +/* + *-------------------------------------------------------------- + * + * Win32FreeDescriptor -- + * + * Free I/O descriptor entry in fdTable. + * + * Results: + * Frees I/O descriptor entry in fdTable. + * + * Side effects: + * None. + * + *-------------------------------------------------------------- + */ +static void Win32FreeDescriptor(int fd) +{ + /* Catch it if fd is a bogus value */ + ASSERT((fd >= 0) && (fd < WIN32_OPEN_MAX)); + + EnterCriticalSection(&fdTableCritical); + + if (fdTable[fd].type != FD_UNUSED) + { + switch (fdTable[fd].type) + { + case FD_FILE_SYNC: + case FD_FILE_ASYNC: + + /* Free file path string */ + ASSERT(fdTable[fd].path != NULL); + + free(fdTable[fd].path); + fdTable[fd].path = NULL; + break; + case FD_PIPE_ASYNC: + CloseHandle((HANDLE)fdTable[fd].fid.value); + break; + default: + break; + } + + ASSERT(fdTable[fd].path == NULL); + + fdTable[fd].type = FD_UNUSED; + fdTable[fd].path = NULL; + fdTable[fd].Errno = NO_ERROR; + fdTable[fd].offsetHighPtr = fdTable[fd].offsetLowPtr = NULL; + + if (fdTable[fd].hMapMutex != NULL) + { + CloseHandle(fdTable[fd].hMapMutex); + fdTable[fd].hMapMutex = NULL; + } + } + + LeaveCriticalSection(&fdTableCritical); + + return; +} + +static short getPort(const char * bindPath) +{ + short port = 0; + char * p = strchr(bindPath, ':'); + + if (p && *++p) + { + char buf[6]; + + strncpy(buf, p, 6); + buf[5] = '\0'; + + port = (short) atoi(buf); + } + + return port; +} + +/* + * OS_CreateLocalIpcFd -- + * + * This procedure is responsible for creating the listener pipe + * on Windows NT for local process communication. It will create a + * named pipe and return a file descriptor to it to the caller. + * + * Results: + * Listener pipe created. This call returns either a valid + * pseudo file descriptor or -1 on error. + * + * Side effects: + * Listener pipe and IPC address are stored in the FCGI info + * structure. + * 'errno' will set on errors (-1 is returned). + * + *---------------------------------------------------------------------- + */ +int OS_CreateLocalIpcFd(const char *bindPath, int backlog, int bCreateMutex) +{ + int pseudoFd = -1; + short port = getPort(bindPath); + HANDLE mutex = INVALID_HANDLE_VALUE; + char mutexEnvString[100]; + + if (mutex == NULL) + { + return -2; + } + + if (bCreateMutex) { + mutex = CreateMutex(NULL, FALSE, NULL); + if (! SetHandleInformation(mutex, HANDLE_FLAG_INHERIT, TRUE)) + { + CloseHandle(mutex); + return -3; + } + // This is a nail for listening to more than one port.. + // This should really be handled by the caller. + _snprintf(mutexEnvString, sizeof(mutexEnvString)-1, MUTEX_VARNAME "=%d", (int) mutex); + putenv(mutexEnvString); + } + + // There's nothing to be gained (at the moment) by a shutdown Event + + if (port && *bindPath != ':' && strncmp(bindPath, LOCALHOST, strlen(LOCALHOST))) + { + fprintf(stderr, "To start a service on a TCP port can not " + "specify a host name.\n" + "You should either use \"localhost:\" or " + " just use \":.\"\n"); + //exit(1); + if (bCreateMutex) CloseHandle(mutexEnvString); + return -1; + } + + listenType = (port) ? FD_SOCKET_SYNC : FD_PIPE_ASYNC; + + if (port) + { + SOCKET listenSock; + struct sockaddr_in sockAddr; + int sockLen = sizeof(sockAddr); + + memset(&sockAddr, 0, sizeof(sockAddr)); + sockAddr.sin_family = AF_INET; + sockAddr.sin_addr.s_addr = htonl(INADDR_ANY); + sockAddr.sin_port = htons(port); + + listenSock = socket(AF_INET, SOCK_STREAM, 0); + if (listenSock == INVALID_SOCKET) + { + if (bCreateMutex)CloseHandle(mutexEnvString); + return -4; + } + + if (bind(listenSock, (struct sockaddr *) &sockAddr, sockLen) ) + { + if (bCreateMutex)CloseHandle(mutexEnvString); + return -12; + } + + if (listen(listenSock, backlog)) + { + if (bCreateMutex)CloseHandle(mutexEnvString); + return -5; + } + + pseudoFd = Win32NewDescriptor(listenType, listenSock, -1); + + if (pseudoFd == -1) + { + if (bCreateMutex)CloseHandle(mutexEnvString); + closesocket(listenSock); + return -6; + } + + hListen = (HANDLE) listenSock; + } + else + { + HANDLE hListenPipe = INVALID_HANDLE_VALUE; + char *pipePath = malloc(strlen(bindPathPrefix) + strlen(bindPath) + 1); + + if (! pipePath) + { + if (bCreateMutex)CloseHandle(mutexEnvString); + return -7; + } + + strcpy(pipePath, bindPathPrefix); + strcat(pipePath, bindPath); + + hListenPipe = CreateNamedPipe(pipePath, + PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED, + PIPE_TYPE_BYTE | PIPE_WAIT | PIPE_READMODE_BYTE, + PIPE_UNLIMITED_INSTANCES, + 4096, 4096, 0, NULL); + + free(pipePath); + + if (hListenPipe == INVALID_HANDLE_VALUE) + { + if (bCreateMutex)CloseHandle(mutexEnvString); + return -8; + } + + if (! SetHandleInformation(hListenPipe, HANDLE_FLAG_INHERIT, TRUE)) + { + if (bCreateMutex)CloseHandle(mutexEnvString); + return -9; + } + + pseudoFd = Win32NewDescriptor(listenType, (int) hListenPipe, -1); + + if (pseudoFd == -1) + { + if (bCreateMutex)CloseHandle(mutexEnvString); + CloseHandle(hListenPipe); + return -10; + } + + hListen = (HANDLE) hListenPipe; + } + + return pseudoFd; +} + +/* + *---------------------------------------------------------------------- + * + * OS_FcgiConnect -- + * + * Create the pipe pathname connect to the remote application if + * possible. + * + * Results: + * -1 if fail or a valid handle if connection succeeds. + * + * Side effects: + * Remote connection established. + * + *---------------------------------------------------------------------- + */ +int OS_FcgiConnect(char *bindPath) +{ + short port = getPort(bindPath); + int pseudoFd = -1; + + if (port) + { + struct hostent *hp; + char *host = NULL; + struct sockaddr_in sockAddr; + int sockLen = sizeof(sockAddr); + SOCKET sock; + + if (*bindPath != ':') + { + char * p = strchr(bindPath, ':'); + int len = p - bindPath + 1; + + host = malloc(len); + strncpy(host, bindPath, len); + host[len] = '\0'; + } + + hp = gethostbyname(host ? host : LOCALHOST); + + if (host) + { + free(host); + } + + if (hp == NULL) + { + fprintf(stderr, "Unknown host: %s\n", bindPath); + return -1; + } + + memset(&sockAddr, 0, sizeof(sockAddr)); + sockAddr.sin_family = AF_INET; + memcpy(&sockAddr.sin_addr, hp->h_addr, hp->h_length); + sockAddr.sin_port = htons(port); + + sock = socket(AF_INET, SOCK_STREAM, 0); + if (sock == INVALID_SOCKET) + { + return -1; + } + + if (! connect(sock, (struct sockaddr *) &sockAddr, sockLen)) + { + closesocket(sock); + return -1; + } + + pseudoFd = Win32NewDescriptor(FD_SOCKET_SYNC, sock, -1); + if (pseudoFd == -1) + { + closesocket(sock); + return -1; + } + } + else + { + char *pipePath = malloc(strlen(bindPathPrefix) + strlen(bindPath) + 1); + HANDLE hPipe; + + if (! pipePath) + { + return -1; + } + + strcpy(pipePath, bindPathPrefix); + strcat(pipePath, bindPath); + + hPipe = CreateFile(pipePath, + GENERIC_WRITE | GENERIC_READ, + FILE_SHARE_READ | FILE_SHARE_WRITE, + NULL, + OPEN_EXISTING, + FILE_FLAG_OVERLAPPED, + NULL); + + free(pipePath); + + if( hPipe == INVALID_HANDLE_VALUE || hPipe == 0) + { + return -1; + } + + pseudoFd = Win32NewDescriptor(FD_PIPE_ASYNC, (int) hPipe, -1); + + if (pseudoFd == -1) + { + CloseHandle(hPipe); + return -1; + } + + /* + * Set stdin equal to our pseudo FD and create the I/O completion + * port to be used for async I/O. + */ + if (! CreateIoCompletionPort(hPipe, hIoCompPort, pseudoFd, 1)) + { + Win32FreeDescriptor(pseudoFd); + CloseHandle(hPipe); + return -1; + } + } + + return pseudoFd; +} + +/* + *-------------------------------------------------------------- + * + * OS_Read -- + * + * Pass through to the appropriate NT read function. + * + * Results: + * Returns number of byes read. Mimics unix read:. + * n bytes read, 0 or -1 failure: errno contains actual error + * + * Side effects: + * None. + * + *-------------------------------------------------------------- + */ +int OS_Read(int fd, char * buf, size_t len) +{ + DWORD bytesRead; + int ret = -1; + + ASSERT((fd >= 0) && (fd < WIN32_OPEN_MAX)); + + if (shutdownNow) return -1; + + switch (fdTable[fd].type) + { + case FD_FILE_SYNC: + case FD_FILE_ASYNC: + case FD_PIPE_SYNC: + case FD_PIPE_ASYNC: + + if (ReadFile(fdTable[fd].fid.fileHandle, buf, len, &bytesRead, NULL)) + { + ret = bytesRead; + } + else + { + fdTable[fd].Errno = GetLastError(); + } + + break; + + case FD_SOCKET_SYNC: + case FD_SOCKET_ASYNC: + + ret = recv(fdTable[fd].fid.sock, buf, len, 0); + if (ret == SOCKET_ERROR) + { + fdTable[fd].Errno = WSAGetLastError(); + ret = -1; + } + + break; + + default: + + ASSERT(0); + } + + return ret; +} + +/* + *-------------------------------------------------------------- + * + * OS_Write -- + * + * Perform a synchronous OS write. + * + * Results: + * Returns number of bytes written. Mimics unix write: + * n bytes written, 0 or -1 failure (??? couldn't find man page). + * + * Side effects: + * none. + * + *-------------------------------------------------------------- + */ +int OS_Write(int fd, char * buf, size_t len) +{ + DWORD bytesWritten; + int ret = -1; + + ASSERT(fd >= 0 && fd < WIN32_OPEN_MAX); + + if (shutdownNow) return -1; + + switch (fdTable[fd].type) + { + case FD_FILE_SYNC: + case FD_FILE_ASYNC: + case FD_PIPE_SYNC: + case FD_PIPE_ASYNC: + + if (WriteFile(fdTable[fd].fid.fileHandle, buf, len, &bytesWritten, NULL)) + { + ret = bytesWritten; + } + else + { + fdTable[fd].Errno = GetLastError(); + } + + break; + + case FD_SOCKET_SYNC: + case FD_SOCKET_ASYNC: + + ret = send(fdTable[fd].fid.sock, buf, len, 0); + if (ret == SOCKET_ERROR) + { + fdTable[fd].Errno = WSAGetLastError(); + ret = -1; + } + + break; + + default: + + ASSERT(0); + } + + return ret; +} + +/* + *---------------------------------------------------------------------- + * + * OS_SpawnChild -- + * + * Spawns a new server listener process, and stores the information + * relating to the child in the supplied record. A wait handler is + * registered on the child's completion. This involves creating + * a process on NT and preparing a command line with the required + * state (currently a -childproc flag and the server socket to use + * for accepting connections). + * + * Results: + * 0 if success, -1 if error. + * + * Side effects: + * Child process spawned. + * + *---------------------------------------------------------------------- + */ +int OS_SpawnChild(char *execPath, int listenFd, PROCESS_INFORMATION *pInfo, char *env) +{ + STARTUPINFO StartupInfo; + BOOL success; + + memset((void *)&StartupInfo, 0, sizeof(STARTUPINFO)); + StartupInfo.cb = sizeof (STARTUPINFO); + StartupInfo.lpReserved = NULL; + StartupInfo.lpReserved2 = NULL; + StartupInfo.cbReserved2 = 0; + StartupInfo.lpDesktop = NULL; + StartupInfo.wShowWindow = SW_HIDE; + /* + * FastCGI on NT will set the listener pipe HANDLE in the stdin of + * the new process. The fact that there is a stdin and NULL handles + * for stdout and stderr tells the FastCGI process that this is a + * FastCGI process and not a CGI process. + */ + StartupInfo.dwFlags = STARTF_USESTDHANDLES|STARTF_USESHOWWINDOW; + /* + * XXX: Do I have to dup the handle before spawning the process or is + * it sufficient to use the handle as it's reference counted + * by NT anyway? + */ + StartupInfo.hStdInput = fdTable[listenFd].fid.fileHandle; + StartupInfo.hStdOutput = INVALID_HANDLE_VALUE; + StartupInfo.hStdError = INVALID_HANDLE_VALUE; + + /* + * Make the listener socket inheritable. + */ + success = SetHandleInformation(StartupInfo.hStdInput, HANDLE_FLAG_INHERIT, + TRUE); + if(!success) { + //exit(99); + return -1; + } + + /* + * XXX: Might want to apply some specific security attributes to the + * processes. + */ + success = CreateProcess(execPath, /* LPCSTR address of module name */ + NULL, /* LPCSTR address of command line */ + NULL, /* Process security attributes */ + NULL, /* Thread security attributes */ + TRUE, /* Inheritable Handes inherited. */ + 0, /* DWORD creation flags */ + env, /* Use parent environment block */ + NULL, /* Address of current directory name */ + &StartupInfo, /* Address of STARTUPINFO */ + pInfo); /* Address of PROCESS_INFORMATION */ + if(success) { + return 0; + } else { + return -1; + } +} + +/* + *-------------------------------------------------------------- + * + * OS_AsyncReadStdin -- + * + * This initiates an asynchronous read on the standard + * input handle. This handle is not guaranteed to be + * capable of performing asynchronous I/O so we send a + * message to the StdinThread to do the synchronous read. + * + * Results: + * -1 if error, 0 otherwise. + * + * Side effects: + * Asynchronous message is queued to the StdinThread and an + * overlapped structure is allocated/initialized. + * + *-------------------------------------------------------------- + */ +int OS_AsyncReadStdin(void *buf, int len, OS_AsyncProc procPtr, + ClientData clientData) +{ + POVERLAPPED_REQUEST pOv; + + ASSERT(fdTable[STDIN_FILENO].type != FD_UNUSED); + + pOv = (POVERLAPPED_REQUEST)malloc(sizeof(struct OVERLAPPED_REQUEST)); + ASSERT(pOv); + memset((void *)pOv, 0, sizeof(struct OVERLAPPED_REQUEST)); + pOv->clientData1 = (ClientData)buf; + pOv->instance = fdTable[STDIN_FILENO].instance; + pOv->procPtr = procPtr; + pOv->clientData = clientData; + + PostQueuedCompletionStatus(hStdinCompPort, len, STDIN_FILENO, + (LPOVERLAPPED)pOv); + return 0; +} + +/* + *-------------------------------------------------------------- + * + * OS_AsyncRead -- + * + * This initiates an asynchronous read on the file + * handle which may be a socket or named pipe. + * + * We also must save the ProcPtr and ClientData, so later + * when the io completes, we know who to call. + * + * We don't look at any results here (the ReadFile may + * return data if it is cached) but do all completion + * processing in OS_Select when we get the io completion + * port done notifications. Then we call the callback. + * + * Results: + * -1 if error, 0 otherwise. + * + * Side effects: + * Asynchronous I/O operation is queued for completion. + * + *-------------------------------------------------------------- + */ +int OS_AsyncRead(int fd, int offset, void *buf, int len, + OS_AsyncProc procPtr, ClientData clientData) +{ + DWORD bytesRead; + POVERLAPPED_REQUEST pOv; + + /* + * Catch any bogus fd values + */ + ASSERT((fd >= 0) && (fd < WIN32_OPEN_MAX)); + /* + * Confirm that this is an async fd + */ + ASSERT(fdTable[fd].type != FD_UNUSED); + ASSERT(fdTable[fd].type != FD_FILE_SYNC); + ASSERT(fdTable[fd].type != FD_PIPE_SYNC); + ASSERT(fdTable[fd].type != FD_SOCKET_SYNC); + + pOv = (POVERLAPPED_REQUEST)malloc(sizeof(struct OVERLAPPED_REQUEST)); + ASSERT(pOv); + memset((void *)pOv, 0, sizeof(struct OVERLAPPED_REQUEST)); + /* + * Only file offsets should be non-zero, but make sure. + */ + if (fdTable[fd].type == FD_FILE_ASYNC) + if (fdTable[fd].offset >= 0) + pOv->overlapped.Offset = fdTable[fd].offset; + else + pOv->overlapped.Offset = offset; + pOv->instance = fdTable[fd].instance; + pOv->procPtr = procPtr; + pOv->clientData = clientData; + bytesRead = fd; + /* + * ReadFile returns: TRUE success, FALSE failure + */ + if (!ReadFile(fdTable[fd].fid.fileHandle, buf, len, &bytesRead, + (LPOVERLAPPED)pOv)) { + fdTable[fd].Errno = GetLastError(); + if(fdTable[fd].Errno == ERROR_NO_DATA || + fdTable[fd].Errno == ERROR_PIPE_NOT_CONNECTED) { + PostQueuedCompletionStatus(hIoCompPort, 0, fd, (LPOVERLAPPED)pOv); + return 0; + } + if(fdTable[fd].Errno != ERROR_IO_PENDING) { + PostQueuedCompletionStatus(hIoCompPort, 0, fd, (LPOVERLAPPED)pOv); + return -1; + } + fdTable[fd].Errno = 0; + } + return 0; +} + +/* + *-------------------------------------------------------------- + * + * OS_AsyncWrite -- + * + * This initiates an asynchronous write on the "fake" file + * descriptor (which may be a file, socket, or named pipe). + * We also must save the ProcPtr and ClientData, so later + * when the io completes, we know who to call. + * + * We don't look at any results here (the WriteFile generally + * completes immediately) but do all completion processing + * in OS_DoIo when we get the io completion port done + * notifications. Then we call the callback. + * + * Results: + * -1 if error, 0 otherwise. + * + * Side effects: + * Asynchronous I/O operation is queued for completion. + * + *-------------------------------------------------------------- + */ +int OS_AsyncWrite(int fd, int offset, void *buf, int len, + OS_AsyncProc procPtr, ClientData clientData) +{ + DWORD bytesWritten; + POVERLAPPED_REQUEST pOv; + + /* + * Catch any bogus fd values + */ + ASSERT((fd >= 0) && (fd < WIN32_OPEN_MAX)); + /* + * Confirm that this is an async fd + */ + ASSERT(fdTable[fd].type != FD_UNUSED); + ASSERT(fdTable[fd].type != FD_FILE_SYNC); + ASSERT(fdTable[fd].type != FD_PIPE_SYNC); + ASSERT(fdTable[fd].type != FD_SOCKET_SYNC); + + pOv = (POVERLAPPED_REQUEST)malloc(sizeof(struct OVERLAPPED_REQUEST)); + ASSERT(pOv); + memset((void *)pOv, 0, sizeof(struct OVERLAPPED_REQUEST)); + /* + * Only file offsets should be non-zero, but make sure. + */ + if (fdTable[fd].type == FD_FILE_ASYNC) + /* + * Only file opened via OS_AsyncWrite with + * O_APPEND will have an offset != -1. + */ + if (fdTable[fd].offset >= 0) + /* + * If the descriptor has a memory mapped file + * handle, take the offsets from there. + */ + if (fdTable[fd].hMapMutex != NULL) { + /* + * Wait infinitely; this *should* not cause problems. + */ + WaitForSingleObject(fdTable[fd].hMapMutex, INFINITE); + + /* + * Retrieve the shared offset values. + */ + pOv->overlapped.OffsetHigh = *(fdTable[fd].offsetHighPtr); + pOv->overlapped.Offset = *(fdTable[fd].offsetLowPtr); + + /* + * Update the shared offset values for the next write + */ + *(fdTable[fd].offsetHighPtr) += 0; /* XXX How do I handle overflow */ + *(fdTable[fd].offsetLowPtr) += len; + + ReleaseMutex(fdTable[fd].hMapMutex); + } else + pOv->overlapped.Offset = fdTable[fd].offset; + else + pOv->overlapped.Offset = offset; + pOv->instance = fdTable[fd].instance; + pOv->procPtr = procPtr; + pOv->clientData = clientData; + bytesWritten = fd; + /* + * WriteFile returns: TRUE success, FALSE failure + */ + if (!WriteFile(fdTable[fd].fid.fileHandle, buf, len, &bytesWritten, + (LPOVERLAPPED)pOv)) { + fdTable[fd].Errno = GetLastError(); + if(fdTable[fd].Errno != ERROR_IO_PENDING) { + PostQueuedCompletionStatus(hIoCompPort, 0, fd, (LPOVERLAPPED)pOv); + return -1; + } + fdTable[fd].Errno = 0; + } + if (fdTable[fd].offset >= 0) + fdTable[fd].offset += len; + return 0; +} + +/* + *-------------------------------------------------------------- + * + * OS_Close -- + * + * Closes the descriptor with routine appropriate for + * descriptor's type. + * + * Results: + * Socket or file is closed. Return values mimic Unix close: + * 0 success, -1 failure + * + * Side effects: + * Entry in fdTable is marked as free. + * + *-------------------------------------------------------------- + */ +int OS_Close(int fd) +{ + int ret = 0; + + /* + * Catch it if fd is a bogus value + */ + ASSERT((fd >= 0) && (fd < WIN32_OPEN_MAX)); + ASSERT(fdTable[fd].type != FD_UNUSED); + + switch (fdTable[fd].type) { + case FD_PIPE_SYNC: + case FD_PIPE_ASYNC: + case FD_FILE_SYNC: + case FD_FILE_ASYNC: + break; + + case FD_SOCKET_SYNC: + case FD_SOCKET_ASYNC: + /* + * Closing a socket that has an async read outstanding causes a + * tcp reset and possible data loss. The shutdown call seems to + * prevent this. + */ + shutdown(fdTable[fd].fid.sock, 2); + /* + * closesocket returns: 0 success, SOCKET_ERROR failure + */ + if (closesocket(fdTable[fd].fid.sock) == SOCKET_ERROR) + ret = -1; + break; + default: + return -1; /* fake failure */ + } + + Win32FreeDescriptor(fd); + return ret; +} + +/* + *-------------------------------------------------------------- + * + * OS_CloseRead -- + * + * Cancel outstanding asynchronous reads and prevent subsequent + * reads from completing. + * + * Results: + * Socket or file is shutdown. Return values mimic Unix shutdown: + * 0 success, -1 failure + * + *-------------------------------------------------------------- + */ +int OS_CloseRead(int fd) +{ + int ret = 0; + + /* + * Catch it if fd is a bogus value + */ + ASSERT((fd >= 0) && (fd < WIN32_OPEN_MAX)); + ASSERT(fdTable[fd].type == FD_SOCKET_ASYNC + || fdTable[fd].type == FD_SOCKET_SYNC); + + if (shutdown(fdTable[fd].fid.sock,0) == SOCKET_ERROR) + ret = -1; + return ret; +} + +/* + *-------------------------------------------------------------- + * + * OS_DoIo -- + * + * This function was formerly OS_Select. It's purpose is + * to pull I/O completion events off the queue and dispatch + * them to the appropriate place. + * + * Results: + * Returns 0. + * + * Side effects: + * Handlers are called. + * + *-------------------------------------------------------------- + */ +int OS_DoIo(struct timeval *tmo) +{ + unsigned long fd; + unsigned long bytes; + POVERLAPPED_REQUEST pOv; + struct timeb tb; + int ms; + int ms_last; + int err; + + /* XXX + * We can loop in here, but not too long, as wait handlers + * must run. + * For cgi stdin, apparently select returns when io completion + * ports don't, so don't wait the full timeout. + */ + if(tmo) + ms = (tmo->tv_sec*1000 + tmo->tv_usec/1000) / 2; + else + ms = 1000; + ftime(&tb); + ms_last = tb.time*1000 + tb.millitm; + while (ms >= 0) { + if(tmo && (ms = tmo->tv_sec*1000 + tmo->tv_usec/1000)> 100) + ms = 100; + if (!GetQueuedCompletionStatus(hIoCompPort, &bytes, &fd, + (LPOVERLAPPED *)&pOv, ms) && !pOv) { + err = WSAGetLastError(); + return 0; /* timeout */ + } + + ASSERT((fd >= 0) && (fd < WIN32_OPEN_MAX)); + /* call callback if descriptor still valid */ + ASSERT(pOv); + if(pOv->instance == fdTable[fd].instance) + (*pOv->procPtr)(pOv->clientData, bytes); + free(pOv); + + ftime(&tb); + ms -= (tb.time*1000 + tb.millitm - ms_last); + ms_last = tb.time*1000 + tb.millitm; + } + return 0; +} + +static int isAddrOK(struct sockaddr_in * inet_sockaddr, const char * okAddrs) +{ + static const char *token = " ,;:\t"; + char *ipaddr; + char *p; + + if (okAddrs == NULL) return TRUE; + + ipaddr = inet_ntoa(inet_sockaddr->sin_addr); + p = strstr(okAddrs, ipaddr); + + if (p == NULL) return FALSE; + + if (p == okAddrs) + { + p += strlen(ipaddr); + return (strchr(token, *p) != NULL); + } + + if (strchr(token, *--p) != NULL) + { + p += strlen(ipaddr) + 1; + return (strchr(token, *p) != NULL); + } + + return FALSE; +} + +#ifndef NO_WSAACEPT +static int CALLBACK isAddrOKCallback(LPWSABUF lpCallerId, + LPWSABUF dc0, + LPQOS dc1, + LPQOS dc2, + LPWSABUF dc3, + LPWSABUF dc4, + GROUP *dc5, + DWORD data) +{ + struct sockaddr_in *sockaddr = (struct sockaddr_in *) lpCallerId->buf; + + // Touch the args to avoid warnings + dc0 = NULL; dc1 = NULL; dc2 = NULL; dc3 = NULL; dc4 = NULL; dc5 = NULL; + + if ((void *) data == NULL) return CF_ACCEPT; + + if (sockaddr->sin_family != AF_INET) return CF_ACCEPT; + + return isAddrOK(sockaddr, (const char *) data) ? CF_ACCEPT : CF_REJECT; +} +#endif + +static printLastError(const char * text) +{ + LPVOID buf; + + FormatMessage( + FORMAT_MESSAGE_ALLOCATE_BUFFER | + FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, + GetLastError(), + 0, + (LPTSTR) &buf, + 0, + NULL + ); + + fprintf(stderr, "%s: %s\n", text, (LPCTSTR) buf); + LocalFree(buf); +} + +static int acceptNamedPipe() +{ + int ipcFd = -1; + + if (! ConnectNamedPipe(hListen, &listenOverlapped)) + { + switch (GetLastError()) + { + case ERROR_PIPE_CONNECTED: + + // A client connected after CreateNamedPipe but + // before ConnectNamedPipe. Its a good connection. + + break; + + case ERROR_IO_PENDING: + + // Wait for a connection to complete. + + while (WaitForSingleObject(listenOverlapped.hEvent, + ACCEPT_TIMEOUT) == WAIT_TIMEOUT) + { + if (shutdownPending) + { + OS_LibShutdown(); + return -1; + } + } + + break; + + case ERROR_PIPE_LISTENING: + + // The pipe handle is in nonblocking mode. + + case ERROR_NO_DATA: + + // The previous client closed its handle (and we failed + // to call DisconnectNamedPipe) + + default: + + printLastError("unexpected ConnectNamedPipe() error"); + } + } + + ipcFd = Win32NewDescriptor(FD_PIPE_SYNC, (int) hListen, -1); + if (ipcFd == -1) + { + DisconnectNamedPipe(hListen); + } + + return ipcFd; +} + +static int acceptSocket(const char *webServerAddrs) +{ + SOCKET hSock; + int ipcFd = -1; + + for (;;) + { + struct sockaddr sockaddr; + int sockaddrLen = sizeof(sockaddr); + + for (;;) + { + const struct timeval timeout = {1, 0}; + fd_set readfds; + + FD_ZERO(&readfds); + FD_SET((unsigned int) hListen, &readfds); + + if (select(0, &readfds, NULL, NULL, &timeout) == 0) + { + if (shutdownPending) + { + OS_LibShutdown(); + return -1; + } + } + else + { + break; + } + } + +#if NO_WSAACEPT + hSock = accept((SOCKET) hListen, &sockaddr, &sockaddrLen); + + if (hSock == INVALID_SOCKET) + { + break; + } + + if (isAddrOK((struct sockaddr_in *) &sockaddr, webServerAddrs)) + { + break; + } + + closesocket(hSock); +#else + hSock = WSAAccept((unsigned int) hListen, + &sockaddr, + &sockaddrLen, + isAddrOKCallback, + (DWORD) webServerAddrs); + + if (hSock != INVALID_SOCKET) + { + break; + } + + if (WSAGetLastError() != WSAECONNREFUSED) + { + break; + } +#endif + } + + if (hSock == INVALID_SOCKET) + { + /* Use FormatMessage() */ + fprintf(stderr, "accept()/WSAAccept() failed: %d", WSAGetLastError()); + return -1; + } + + ipcFd = Win32NewDescriptor(FD_SOCKET_SYNC, hSock, -1); + if (ipcFd == -1) + { + closesocket(hSock); + } + + return ipcFd; +} + +/* + *---------------------------------------------------------------------- + * + * OS_Accept -- + * + * Accepts a new FastCGI connection. This routine knows whether + * we're dealing with TCP based sockets or NT Named Pipes for IPC. + * + * fail_on_intr is ignored in the Win lib. + * + * Results: + * -1 if the operation fails, otherwise this is a valid IPC fd. + * + *---------------------------------------------------------------------- + */ +int OS_Accept(int listen_sock, int fail_on_intr, const char *webServerAddrs) +{ + int ipcFd = -1; + + // Touch args to prevent warnings + listen_sock = 0; fail_on_intr = 0; + + // @todo Muliple listen sockets and sockets other than 0 are not + // supported due to the use of globals. + + if (shutdownPending) + { + OS_LibShutdown(); + return -1; + } + + // The mutex is to keep other processes (and threads, when supported) + // from going into the accept cycle. The accept cycle needs to + // periodically break out to check the state of the shutdown flag + // and there's no point to having more than one thread do that. + + if (acceptMutex != INVALID_HANDLE_VALUE) + { + if (WaitForSingleObject(acceptMutex, INFINITE) == WAIT_FAILED) + { + printLastError("WaitForSingleObject() failed"); + return -1; + } + } + + if (shutdownPending) + { + OS_LibShutdown(); + } + else if (listenType == FD_PIPE_SYNC) + { + ipcFd = acceptNamedPipe(); + } + else if (listenType == FD_SOCKET_SYNC) + { + ipcFd = acceptSocket(webServerAddrs); + } + else + { + fprintf(stderr, "unknown listenType (%d)\n", listenType); + } + + if (acceptMutex != INVALID_HANDLE_VALUE) + { + ReleaseMutex(acceptMutex); + } + + return ipcFd; +} + +/* + *---------------------------------------------------------------------- + * + * OS_IpcClose + * + * OS IPC routine to close an IPC connection. + * + * Results: + * + * + * Side effects: + * IPC connection is closed. + * + *---------------------------------------------------------------------- + */ +int OS_IpcClose(int ipcFd) +{ + if (ipcFd == -1) + return 0; + + /* + * Catch it if fd is a bogus value + */ + ASSERT((ipcFd >= 0) && (ipcFd < WIN32_OPEN_MAX)); + ASSERT(fdTable[ipcFd].type != FD_UNUSED); + + switch(listenType) { + + case FD_PIPE_SYNC: + /* + * Make sure that the client (ie. a Web Server in this case) has + * read all data from the pipe before we disconnect. + */ + if(!FlushFileBuffers(fdTable[ipcFd].fid.fileHandle)) + return -1; + if(DisconnectNamedPipe(fdTable[ipcFd].fid.fileHandle)) { + OS_Close(ipcFd); + return 0; + } else { + return -1; + } + break; + + case FD_SOCKET_SYNC: + OS_Close(ipcFd); + return 0; + break; + + case FD_UNUSED: + default: + //exit(106); + return -1; + break; + } + return -1; +} + +/* + *---------------------------------------------------------------------- + * + * OS_IsFcgi -- + * + * Determines whether this process is a FastCGI process or not. + * + * Results: + * Returns 1 if FastCGI, 0 if not. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ +int OS_IsFcgi(int sock) +{ + // Touch args to prevent warnings + sock = 0; + + /* XXX This is broken for sock */ + + return (listenType != FD_UNUSED); +} + +/* + *---------------------------------------------------------------------- + * + * OS_SetFlags -- + * + * Sets selected flag bits in an open file descriptor. Currently + * this is only to put a SOCKET into non-blocking mode. + * + *---------------------------------------------------------------------- + */ +void OS_SetFlags(int fd, int flags) +{ + unsigned long pLong = 1L; + int err; + + if (fdTable[fd].type == FD_SOCKET_SYNC && flags == O_NONBLOCK) { + if (ioctlsocket(fdTable[fd].fid.sock, FIONBIO, &pLong) == + SOCKET_ERROR) { + //exit(WSAGetLastError()); + SetLastError(WSAGetLastError()); + return; + } + if (!CreateIoCompletionPort((HANDLE)fdTable[fd].fid.sock, + hIoCompPort, fd, 1)) { + //err = GetLastError(); + //exit(err); + return; + } + + fdTable[fd].type = FD_SOCKET_ASYNC; + } + return; +} + diff --git a/sapi/cgi/libfcgi/strerror.c b/sapi/cgi/libfcgi/strerror.c new file mode 100644 index 00000000000..fac7c8eda2d --- /dev/null +++ b/sapi/cgi/libfcgi/strerror.c @@ -0,0 +1,91 @@ +/* + * The terms in the file "LICENSE.TERMS" do not apply to this file. + * See terms below. + * + * Copyright (c) 1988 Regents of the University of California. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#if defined(LIBC_SCCS) && !defined(lint) +/*static char *sccsid = "from: @(#)strerror.c 5.6 (Berkeley) 5/4/91";*/ +static char *rcsid = "$Id$"; +#endif /* LIBC_SCCS and not lint */ + +#include "fcgi_config.h" + +#if ! defined (HAVE_STRERROR) +#include + +/* + * Since perror() is not allowed to change the contents of strerror()'s + * static buffer, both functions supply their own buffers to the + * internal function __strerror(). + */ + +char * +__strerror(int num, char *buf) +{ +#define UPREFIX "Unknown error: " + extern char *sys_errlist[]; + extern int sys_nerr; + register unsigned int errnum; + register char *p, *t; + char tmp[40]; + + errnum = num; /* convert to unsigned */ + if (errnum < sys_nerr) + return(sys_errlist[errnum]); + + /* Do this by hand, so we don't include stdio(3). */ + t = tmp; + do { + *t++ = "0123456789"[errnum % 10]; + } while (errnum /= 10); + + strcpy (buf, UPREFIX); + for (p = buf + sizeof(UPREFIX) -1;;) { + *p++ = *--t; + if (t <= tmp) + break; + } + + return buf; +} + + +char * +strerror(int num) +{ + static char buf[40]; /* 64-bit number + slop */ + return __strerror(num, buf); +} + +#endif