/****************************************************************************** * * Project: MapServer * Purpose: Implementations for MapServer IO redirection capability. * Author: Frank Warmerdam, warmerdam@pobox.com * ****************************************************************************** * Copyright (c) 2004, Frank Warmerdam * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. ****************************************************************************** * * $Log: mapio.c,v $ * Revision 1.16 2006/08/22 23:07:52 hobu * cast to a const char* the first argument to strncasecmp * * Revision 1.15 2006/08/09 14:27:00 frank * fixed data length handling in strip content header * * Revision 1.14 2006/07/13 20:18:12 frank * added msIO_stripStdoutBufferContentType * * Revision 1.13 2006/07/06 21:35:29 frank * fixed statement order to be valid C * * Revision 1.12 2006/07/05 05:54:53 frank * implement per-thread io contexts * * Revision 1.11 2006/06/19 15:13:37 frank * add io context labelling, avoid depending on function pointer compares * * Revision 1.10 2006/05/22 19:20:59 frank * added some of the RFC 16 entry points * * Revision 1.9 2006/05/08 20:28:43 frank * force stdin into binary mode when reading from stdin on win32 (bug 1768) * * Revision 1.8 2005/07/25 20:56:00 frank * Undef msIO_fread() too. * * Revision 1.7 2005/07/22 17:26:11 frank * bug 1259: fixed POST support in fastcgi mode * * Revision 1.6 2004/11/04 21:13:02 frank * ensure we include io.h on win32 * * Revision 1.5 2004/11/04 21:06:09 frank * centralize 'stdout binary mode setting' for win32, add for gdal output * * Revision 1.4 2004/10/21 04:30:55 frank * Added standardized headers. Added MS_CVSID(). * * Revision 1.3 2004/10/08 19:11:39 frank * ensure msIO_vfprintf is undefed * * Revision 1.2 2004/10/05 04:23:10 sdlime * Added include for stdarg.h to mapio.c. * * Revision 1.1 2004/10/01 19:03:35 frank * New * */ #include #include "map.h" #include "mapthread.h" #ifdef _WIN32 #include #include #endif MS_CVSID("$Id: mapio.c,v 1.16 2006/08/22 23:07:52 hobu Exp $") static int is_msIO_initialized = MS_FALSE; typedef struct msIOContextGroup_t { msIOContext stdin_context; msIOContext stdout_context; msIOContext stderr_context; int thread_id; struct msIOContextGroup_t *next; } msIOContextGroup; static msIOContextGroup default_contexts; static msIOContextGroup *io_context_list = NULL; static void msIO_Initialize( void ); #ifdef msIO_printf # undef msIO_printf # undef msIO_fprintf # undef msIO_fwrite # undef msIO_fread # undef msIO_vfprintf #endif /************************************************************************/ /* msIO_Cleanup() */ /************************************************************************/ void msIO_Cleanup() { if( is_msIO_initialized ) { is_msIO_initialized = MS_FALSE; while( io_context_list != NULL ) { msIOContextGroup *last = io_context_list; io_context_list = io_context_list->next; free( last ); } } } /************************************************************************/ /* msIO_GetContextGroup() */ /************************************************************************/ static msIOContextGroup *msIO_GetContextGroup() { int nThreadId = msGetThreadId(); msIOContextGroup *prev = NULL, *group = io_context_list; if( group != NULL && group->thread_id == nThreadId ) return group; /* -------------------------------------------------------------------- */ /* Search for group for this thread */ /* -------------------------------------------------------------------- */ msAcquireLock( TLOCK_IOCONTEXT ); msIO_Initialize(); group = io_context_list; while( group != NULL && group->thread_id != nThreadId ) { prev = group; group = group->next; } /* -------------------------------------------------------------------- */ /* If we found it, make sure it is pushed to the front of the */ /* link for faster finding next time, and return it. */ /* -------------------------------------------------------------------- */ if( group != NULL ) { if( prev != NULL ) { prev->next = group->next; group->next = io_context_list; io_context_list = group; } msReleaseLock( TLOCK_IOCONTEXT ); return group; } /* -------------------------------------------------------------------- */ /* Create a new context group for this thread. */ /* -------------------------------------------------------------------- */ group = (msIOContextGroup *) calloc(sizeof(msIOContextGroup),1); group->stdin_context = default_contexts.stdin_context; group->stdout_context = default_contexts.stdout_context; group->stderr_context = default_contexts.stderr_context; group->thread_id = nThreadId; group->next = io_context_list; io_context_list = group; msReleaseLock( TLOCK_IOCONTEXT ); return group; } /************************************************************************/ /* msIO_getHandler() */ /************************************************************************/ msIOContext *msIO_getHandler( FILE * fp ) { int nThreadId = msGetThreadId(); msIOContextGroup *group = io_context_list; msIO_Initialize(); if( group == NULL || group->thread_id != nThreadId ) { group = msIO_GetContextGroup(); if( group == NULL ) return NULL; } if( fp == stdin || fp == NULL || strcmp((const char *)fp,"stdin") == 0 ) return &(group->stdin_context); else if( fp == stdout || strcmp((const char *)fp,"stdout") == 0 ) return &(group->stdout_context); else if( fp == stderr || strcmp((const char *)fp,"stderr") == 0 ) return &(group->stderr_context); else return NULL; } /************************************************************************/ /* msIO_installHandlers() */ /************************************************************************/ int msIO_installHandlers( msIOContext *stdin_context, msIOContext *stdout_context, msIOContext *stderr_context ) { msIOContextGroup *group; msIO_Initialize(); group = msIO_GetContextGroup(); if( stdin_context == NULL ) group->stdin_context = default_contexts.stdin_context; else if( stdin_context != &group->stdin_context ) group->stdin_context = *stdin_context; if( stdout_context == NULL ) group->stdout_context = default_contexts.stdout_context; else if( stdout_context != &group->stdout_context ) group->stdout_context = *stdout_context; if( stderr_context == NULL ) group->stderr_context = default_contexts.stderr_context; else if( stderr_context != &group->stderr_context ) group->stderr_context = *stderr_context; return MS_TRUE; } /************************************************************************/ /* msIO_contextRead() */ /************************************************************************/ int msIO_contextRead( msIOContext *context, void *data, int byteCount ) { if( context->write_channel == MS_TRUE ) return 0; else return context->readWriteFunc( context->cbData, data, byteCount ); } /************************************************************************/ /* msIO_contextWrite() */ /************************************************************************/ int msIO_contextWrite( msIOContext *context, const void *data, int byteCount ) { if( context->write_channel == MS_FALSE ) return 0; else return context->readWriteFunc( context->cbData, (void *) data, byteCount ); } /* ==================================================================== */ /* ==================================================================== */ /* Stdio-like cover functions. */ /* ==================================================================== */ /* ==================================================================== */ /************************************************************************/ /* msIO_printf() */ /************************************************************************/ int msIO_printf( const char *format, ... ) { va_list args; int return_val; msIOContext *context; char workBuf[8000]; va_start( args, format ); #if defined(HAVE_VSNPRINTF) return_val = vsnprintf( workBuf, sizeof(workBuf), format, args ); #else return_val = vsprintf( workBuf, format, args); #endif va_end( args ); if( return_val < 0 || return_val >= sizeof(workBuf) ) return -1; context = msIO_getHandler( stdout ); if( context == NULL ) return -1; return msIO_contextWrite( context, workBuf, return_val ); } /************************************************************************/ /* msIO_fprintf() */ /************************************************************************/ int msIO_fprintf( FILE *fp, const char *format, ... ) { va_list args; int return_val; msIOContext *context; char workBuf[8000]; va_start( args, format ); #if defined(HAVE_VSNPRINTF) return_val = vsnprintf( workBuf, sizeof(workBuf), format, args ); #else return_val = vsprintf( workBuf, format, args); #endif va_end( args ); if( return_val < 0 || return_val >= sizeof(workBuf) ) return -1; context = msIO_getHandler( fp ); if( context == NULL ) return fwrite( workBuf, 1, return_val, fp ); else return msIO_contextWrite( context, workBuf, return_val ); } /************************************************************************/ /* msIO_vfprintf() */ /************************************************************************/ int msIO_vfprintf( FILE *fp, const char *format, va_list ap ) { int return_val; msIOContext *context; char workBuf[8000]; #if defined(HAVE_VSNPRINTF) return_val = vsnprintf( workBuf, sizeof(workBuf), format, ap ); #else return_val = vsprintf( workBuf, format, ap ); #endif if( return_val < 0 || return_val >= sizeof(workBuf) ) return -1; context = msIO_getHandler( fp ); if( context == NULL ) return fwrite( workBuf, 1, return_val, fp ); else return msIO_contextWrite( context, workBuf, return_val ); } /************************************************************************/ /* msIO_fwrite() */ /************************************************************************/ int msIO_fwrite( const void *data, size_t size, size_t nmemb, FILE *fp ) { msIOContext *context; context = msIO_getHandler( fp ); if( context == NULL ) return fwrite( data, size, nmemb, fp ); else return msIO_contextWrite( context, data, size * nmemb ) / size; } /************************************************************************/ /* msIO_fread() */ /************************************************************************/ int msIO_fread( void *data, size_t size, size_t nmemb, FILE *fp ) { msIOContext *context; context = msIO_getHandler( fp ); if( context == NULL ) return fread( data, size, nmemb, fp ); else return msIO_contextRead( context, data, size * nmemb ) / size; } /* ==================================================================== */ /* ==================================================================== */ /* Internal default callbacks implementing stdio reading and */ /* writing. */ /* ==================================================================== */ /* ==================================================================== */ /************************************************************************/ /* msIO_stdioRead() */ /* */ /* This is the default implementation via stdio. */ /************************************************************************/ static int msIO_stdioRead( void *cbData, void *data, int byteCount ) { return fread( data, 1, byteCount, (FILE *) cbData ); } /************************************************************************/ /* msIO_stdioWrite() */ /* */ /* This is the default implementation via stdio. */ /************************************************************************/ static int msIO_stdioWrite( void *cbData, void *data, int byteCount ) { return fwrite( data, 1, byteCount, (FILE *) cbData ); } /************************************************************************/ /* msIO_Initialize() */ /************************************************************************/ static void msIO_Initialize( void ) { if( is_msIO_initialized == MS_TRUE ) return; default_contexts.stdin_context.label = "stdio"; default_contexts.stdin_context.write_channel = MS_FALSE; default_contexts.stdin_context.readWriteFunc = msIO_stdioRead; default_contexts.stdin_context.cbData = (void *) stdin; default_contexts.stdout_context.label = "stdio"; default_contexts.stdout_context.write_channel = MS_TRUE; default_contexts.stdout_context.readWriteFunc = msIO_stdioWrite; default_contexts.stdout_context.cbData = (void *) stdout; default_contexts.stderr_context.label = "stdio"; default_contexts.stderr_context.write_channel = MS_TRUE; default_contexts.stderr_context.readWriteFunc = msIO_stdioWrite; default_contexts.stderr_context.cbData = (void *) stderr; default_contexts.next = NULL; default_contexts.thread_id = 0; is_msIO_initialized = MS_TRUE; } /* ==================================================================== */ /* ==================================================================== */ /* Stuff related to having a gdIOCtx operate through the msIO */ /* layer. */ /* ==================================================================== */ /* ==================================================================== */ typedef struct { gdIOCtx gd_io_ctx; msIOContext *ms_io_ctx; } msIO_gdIOCtx; /************************************************************************/ /* msIO_gd_putC() */ /* */ /* A GD IO context callback to put a character, redirected */ /* through the msIO output context. */ /************************************************************************/ static void msIO_gd_putC( gdIOCtx *cbData, int out_char ) { msIO_gdIOCtx *merged_context = (msIO_gdIOCtx *) cbData; char out_char_as_char = (char) out_char; msIO_contextWrite( merged_context->ms_io_ctx, &out_char_as_char, 1 ); } /************************************************************************/ /* msIO_gd_putBuf() */ /* */ /* The GD IO context callback to put a buffer of data, */ /* redirected through the msIO output context. */ /************************************************************************/ static int msIO_gd_putBuf( gdIOCtx *cbData, const void *data, int byteCount ) { msIO_gdIOCtx *merged_context = (msIO_gdIOCtx *) cbData; return msIO_contextWrite( merged_context->ms_io_ctx, data, byteCount ); } /************************************************************************/ /* msIO_getGDIOCtx() */ /* */ /* The returned context should be freed with free() when no */ /* longer needed. */ /************************************************************************/ gdIOCtx *msIO_getGDIOCtx( FILE *fp ) { msIO_gdIOCtx *merged_context; msIOContext *context = msIO_getHandler( fp ); if( context == NULL ) return NULL; merged_context = (msIO_gdIOCtx *) calloc(1,sizeof(msIO_gdIOCtx)); merged_context->gd_io_ctx.putC = msIO_gd_putC; merged_context->gd_io_ctx.putBuf = msIO_gd_putBuf; merged_context->ms_io_ctx = context; return (gdIOCtx *) merged_context; } /* ==================================================================== */ /* ==================================================================== */ /* FastCGI output redirection functions. */ /* ==================================================================== */ /* ==================================================================== */ #ifdef USE_FASTCGI #define NO_FCGI_DEFINES #include "fcgi_stdio.h" /************************************************************************/ /* msIO_fcgiRead() */ /* */ /* This is the default implementation via stdio. */ /************************************************************************/ static int msIO_fcgiRead( void *cbData, void *data, int byteCount ) { return FCGI_fread( data, 1, byteCount, (FCGI_FILE *) cbData ); } /************************************************************************/ /* msIO_fcgiWrite() */ /* */ /* This is the default implementation via stdio. */ /************************************************************************/ static int msIO_fcgiWrite( void *cbData, void *data, int byteCount ) { return FCGI_fwrite( data, 1, byteCount, (FCGI_FILE *) cbData ); } /************************************************************************/ /* msIO_installFastCGIRedirect() */ /************************************************************************/ int msIO_installFastCGIRedirect() { msIOContext stdin_ctx, stdout_ctx, stderr_ctx; stdin_ctx.label = "fcgi"; stdin_ctx.write_channel = MS_FALSE; stdin_ctx.readWriteFunc = msIO_fcgiRead; stdin_ctx.cbData = (void *) FCGI_stdin; stdout_ctx.label = "fcgi"; stdout_ctx.write_channel = MS_TRUE; stdout_ctx.readWriteFunc = msIO_fcgiWrite; stdout_ctx.cbData = (void *) FCGI_stdout; stderr_ctx.label = "fcgi"; stderr_ctx.write_channel = MS_TRUE; stderr_ctx.readWriteFunc = msIO_fcgiWrite; stderr_ctx.cbData = (void *) FCGI_stderr; msIO_installHandlers( &stdin_ctx, &stdout_ctx, &stderr_ctx ); return MS_TRUE; } #endif /************************************************************************/ /* msIO_needBinaryStdout() */ /* */ /* This function is intended to ensure that stdout is in binary */ /* mode. */ /* */ /* But don't do it we are using FastCGI. We will take care of */ /* doing it in the libfcgi library in that case for the normal */ /* cgi case, and for the fastcgi case the _setmode() call */ /* causes a crash. */ /************************************************************************/ int msIO_needBinaryStdout() { #if defined(_WIN32) && !defined(USE_FASTCGI) if(_setmode( _fileno(stdout), _O_BINARY) == -1) { msSetError(MS_IOERR, "Unable to change stdout to binary mode.", "msIO_needBinaryStdout()" ); return(MS_FAILURE); } #endif return MS_SUCCESS; } /************************************************************************/ /* msIO_needBinaryStdin() */ /* */ /* This function is intended to ensure that stdin is in binary */ /* mode. */ /* */ /* But don't do it we are using FastCGI. We will take care of */ /* doing it in the libfcgi library in that case for the normal */ /* cgi case, and for the fastcgi case the _setmode() call */ /* causes a crash. */ /************************************************************************/ int msIO_needBinaryStdin() { #if defined(_WIN32) && !defined(USE_FASTCGI) if(_setmode( _fileno(stdin), _O_BINARY) == -1) { msSetError(MS_IOERR, "Unable to change stdin to binary mode.", "msIO_needBinaryStdin()" ); return(MS_FAILURE); } #endif return MS_SUCCESS; } /* ==================================================================== */ /* memory buffer io handling functions. */ /* ==================================================================== */ /************************************************************************/ /* msIO_resetHandlers() */ /************************************************************************/ void msIO_resetHandlers() { msIOContextGroup *group = msIO_GetContextGroup(); if( group == NULL ) return; if( strcmp(group->stdin_context.label,"buffer") == 0 ) { msIOBuffer *buf = (msIOBuffer *) group->stdin_context.cbData; if( buf->data != NULL ) free( buf->data ); free( buf ); } if( strcmp(group->stdout_context.label,"buffer") == 0 ) { msIOBuffer *buf = (msIOBuffer *) group->stdout_context.cbData; if( buf->data != NULL ) free( buf->data ); free( buf ); } if( strcmp(group->stderr_context.label,"buffer") == 0 ) { msIOBuffer *buf = (msIOBuffer *) group->stderr_context.cbData; if( buf->data != NULL ) free( buf->data ); free( buf ); } msIO_installHandlers( NULL, NULL, NULL ); } /************************************************************************/ /* msIO_installStdoutToBuffer() */ /************************************************************************/ void msIO_installStdoutToBuffer() { msIOContextGroup *group = msIO_GetContextGroup(); msIOContext context; context.label = "buffer"; context.write_channel = MS_TRUE; context.readWriteFunc = msIO_bufferWrite; context.cbData = calloc(sizeof(msIOBuffer),1); msIO_installHandlers( &group->stdin_context, &context, &group->stderr_context ); } /************************************************************************/ /* msIO_installStdinFromBuffer() */ /************************************************************************/ void msIO_installStdinFromBuffer() { msIOContextGroup *group = msIO_GetContextGroup(); msIOContext context; context.label = "buffer"; context.write_channel = MS_FALSE; context.readWriteFunc = msIO_bufferRead; context.cbData = calloc(sizeof(msIOBuffer),1); msIO_installHandlers( &context, &group->stdout_context, &group->stderr_context ); } /************************************************************************/ /* msIO_stripStdoutBufferContentType() */ /* */ /* Strip off content-type header from buffer, and return to */ /* caller. Returned string is the callers responsibility to */ /* call msFree() on to deallocate. This function will return */ /* NULL if there is no content-type header. */ /************************************************************************/ char *msIO_stripStdoutBufferContentType() { /* -------------------------------------------------------------------- */ /* Find stdout buffer. */ /* -------------------------------------------------------------------- */ msIOContext *ctx = msIO_getHandler( (FILE *) "stdout" ); msIOBuffer *buf; char *content_type = NULL; int end_of_ct, start_of_data; if( ctx == NULL || ctx->write_channel == MS_FALSE || strcmp(ctx->label,"buffer") != 0 ) { msSetError( MS_MISCERR, "Can't identify msIO buffer.", "msIO_stripStdoutBufferContentType" ); return NULL; } buf = (msIOBuffer *) ctx->cbData; /* -------------------------------------------------------------------- */ /* Return NULL if we don't have a content-type header. */ /* -------------------------------------------------------------------- */ if( buf->data_offset < 14 || strncasecmp((const char*) buf->data,"Content-type: ",14) != 0 ) return NULL; /* -------------------------------------------------------------------- */ /* Find newline marker at end of content type argument. */ /* -------------------------------------------------------------------- */ end_of_ct = 13; while( end_of_ct+1 < buf->data_offset && buf->data[end_of_ct+1] != 10 ) end_of_ct++; if( end_of_ct+1 == buf->data_offset ) { msSetError( MS_MISCERR, "Corrupt Content-type header.", "msIO_stripStdoutBufferContentType" ); return NULL; } /* -------------------------------------------------------------------- */ /* Continue on to the start of data ... skipping two newline */ /* markers. */ /* -------------------------------------------------------------------- */ start_of_data = end_of_ct+2; while( start_of_data < buf->data_offset && buf->data[start_of_data] != 10 ) start_of_data++; if( start_of_data == buf->data_offset ) { msSetError( MS_MISCERR, "Corrupt Content-type header.", "msIO_stripStdoutBufferContentType" ); return NULL; } start_of_data++; /* -------------------------------------------------------------------- */ /* Copy out content type. */ /* -------------------------------------------------------------------- */ content_type = (char *) malloc(end_of_ct-14+2); strncpy( content_type, (const char *) buf->data + 14, end_of_ct - 14 + 1); content_type[end_of_ct-14+1] = '\0'; /* -------------------------------------------------------------------- */ /* Move data to front of buffer, and reset length. */ /* -------------------------------------------------------------------- */ memmove( buf->data, buf->data+start_of_data, buf->data_offset - start_of_data ); buf->data[buf->data_offset - start_of_data] = '\0'; buf->data_offset -= start_of_data; return content_type; } /************************************************************************/ /* msIO_bufferWrite() */ /************************************************************************/ int msIO_bufferWrite( void *cbData, void *data, int byteCount ) { msIOBuffer *buf = (msIOBuffer *) cbData; /* ** Grow buffer if needed. */ if( buf->data_offset + byteCount > buf->data_len ) { buf->data_len = buf->data_len * 2 + byteCount + 100; if( buf->data == NULL ) buf->data = (unsigned char *) malloc(buf->data_len); else buf->data = (unsigned char *) realloc(buf->data, buf->data_len); if( buf->data == NULL ) { msSetError( MS_MEMERR, "Failed to allocate %d bytes for capture buffer.", "msIO_bufferWrite()", buf->data_len ); buf->data_len = 0; return 0; } } /* ** Copy result into buffer. */ memcpy( buf->data + buf->data_offset, data, byteCount ); buf->data_offset += byteCount; return byteCount; } /************************************************************************/ /* msIO_bufferRead() */ /************************************************************************/ int msIO_bufferRead( void *cbData, void *data, int byteCount ) { /* msIOBuffer *buf = (msIOBuffer *) cbData; */ /* not implemented yet. */ return 0; }