/* .+ .identifier : $Id: chf_init.c,v 2.2 2001/01/25 14:05:23 cibrario Exp $ .context : CHF, Condition Handling Facility .title : $RCSfile: chf_init.c,v $, condition generation .kind : C source .author : Ivan Cibrario B. .site : CSTV-CNR .creation : 3-May-1996 .keywords : * .description : This module implements the CHF initialization function ChfInit() .include : Chf.h .notes : $Log: chf_init.c,v $ Revision 2.2 2001/01/25 14:05:23 cibrario Added partial Win32 support (Windows CE only). Revision 2.1 2000/05/26 15:30:42 cibrario - Renamed static context chf_context to _chf_context; it is now in actual use only when mt support is not enabled, otherwise it is used only as a prototype to prime the per-thread contexts on demand - Conditional definition of mt support static variables (context_mutex to access the static master context, fputs_mutex to ensure the atomicity of DefaultHandler() while it is printing a sequence of grouped condition messages) and functions (DestroyContext() to destroy a per-thread context when the owning thread terminates) - Deeply revised ChfInit() and ChfExit() to implement multithreading support - Implemented function _ChfGetContext() to return a pointer to the per-thread Chf context, creating them on-demand. Revision 1.6 1997/01/15 13:37:19 cibrario The Chf condition handlers now have a third argument: the private handler context pointer; the code has been updated accordingly. Revision 1.4 1996/09/25 13:29:01 cibrario Added static char[] variable rcs_lib_id; it contains the value of the CHF_LIBRARY_ID macro. The header Chf.h sets that macro to its RCS Id, that is also the Id of the whole Chf library. Revision 1.1 1996/05/28 12:54:58 cibrario Initial revision .- */ #ifndef lint static char rcs_id[] = "$Id: chf_init.c,v 2.2 2001/01/25 14:05:23 cibrario Exp $"; #endif #include #include #ifndef _WIN32 # include #endif #include #include #ifdef _WIN32 # include # include #endif #include "Chf.h" #include "ChfPriv.h" /* ------------------------------------------------------------------------- Global and static variables ------------------------------------------------------------------------- */ /* Chf Library Id */ #ifndef lint static ChfChar rcs_lib_id[] = CHF_LIBRARY_ID; #endif /* CHF context */ ChfContext _chf_context; /* Message separator and severity names for ChfBuildMessage() */ static const ChfChar separator[] = CHF_MESSAGE_SEPARATOR; static const ChfChar* severity_name[] = CHF_SEVERITY_NAMES; /* ------------------------------------------------------------------------- Multithreading support ------------------------------------------------------------------------- */ #ifdef _REENTRANT # include /* Mutex to access chf_context during initialization and exit; mutex to puts condition messages on stderr (DefaultHandler) */ static pthread_mutex_t context_mutex = PTHREAD_MUTEX_INITIALIZER; static pthread_mutex_t fputs_mutex = PTHREAD_MUTEX_INITIALIZER; /* Chf data key */ static pthread_key_t data_key; /* This function is called to destroy a Chf context when the owning thread terminated. */ static void DestroyContext( void* context ) { free( ( ( ChfContext* )context )->message_buffer ); free( ( ( ChfContext* )context )->handler_stack ); free( ( ( ChfContext* )context )->condition_stack ); free( context ); } #endif /* ------------------------------------------------------------------------- Private functions ------------------------------------------------------------------------- */ /* .+ .title : DefaultHandler .kind : C function .creation : 17-May-1996 .description : This function is the default condition handler of CHF. It's automatically pushed into the condition handler stack by ChfInit() and performs the following functions: - if called during an unwind, it returns immediately to the caller, requesting the action CHF_RESIGNAL, else - if the severity of the condition being signalled is greater than CHF_SUCCESS, it prints the messages associated with the entire condition group on stderr using the standard function ChfBuildMessage() to build the messages. - if the severity of the condition being signalled is less than CHF_FATAL, it returns to the caller requesting the action CHF_CONTINUE, else - if the CHF_FATAL condition was NOT signalled during an unwind operation, it returns to the caller requesting the action CHF_UNWIND, otherwise it requests the action CHF_RESIGNAL. WIN32: - stderr stream is not available and modal MessageBox require the parent's handle to work reliably; the default handler does not print anything .call : action = DefaultHandler(desc, state, context); .input : const ChfDescriptor *desc, condition descriptor const ChfState state, current CHF state .output : ChfAction action, action requested by the handler .status_codes : none .notes : 1.1, 16-May-1996, creation 1.6, 15-Jan-1997, update: - the Chf condition handlers now have a third argument: the private handler context pointer. 2.1, 25-May-2000, update: - added multithreading support 2.2, 22-Jan-2001, update: - added Win32 support .- */ static ChfAction DefaultHandler( const ChfDescriptor* desc, const ChfState state, ChfPointer handler_context ) { ChfAction action; const ChfDescriptor* d; if ( state == CHF_UNWINDING ) /* If CHF is unwinding, do nothing */ action = CHF_RESIGNAL; else { /* Print the condition messages, if necessary. The sequence of fputs() is done atomically if multithreading support is enabled. In Win32, the default handler does not print anything. */ if ( ChfGetSeverity( desc ) > CHF_SUCCESS ) { #ifdef _REENTRANT if ( pthread_mutex_lock( &fputs_mutex ) ) ChfAbort( CHF_ABORT_PTHREAD ); #endif #ifndef _WIN32 for ( d = desc; d != CHF_NULL_DESCRIPTOR; d = ChfGetNextDescriptor( d ) ) fputs( ChfBuildMessage( d ), stderr ); #endif #ifdef _REENTRANT if ( pthread_mutex_unlock( &fputs_mutex ) ) ChfAbort( CHF_ABORT_PTHREAD ); #endif } /* Determine the handler action */ switch ( ChfGetSeverity( desc ) ) { case CHF_SUCCESS: case CHF_INFO: case CHF_WARNING: case CHF_ERROR: { /* Continue execution if the severity is less than CHF_FATAL */ action = CHF_CONTINUE; break; } default: { /* The severity of the condition is CHF_FATAL; appempt to unwind if the fatal condition wasn't signalled during another unwind. */ action = ( ( state == CHF_SIGNAL_UNWINDING ) ? CHF_RESIGNAL : CHF_UNWIND ); break; } } } /* Return the action code to the Chf handler dispatcher */ return action; } /* .+ .title : scopy .kind : C function .creation : 16-May-1996 .description : This function writes the NUL-terminated string pointed by 'q' starting from 'p', including the NUL terminator and without trepassing 'p_end'. The function returns a pointer to the NUL-terminator just written. .call : np = scopy(p, q, p_end); .input : char *p, starting position for the write const char *q, pointer to the string to be copied char *p_end, pointer to the end of the output area .output : char *np, pointer to the NUL-terminator just written .status_codes : none .notes : 1.1, 16-May-1996, creation .- */ static ChfChar* scopy( ChfChar* p, const ChfChar* q, ChfChar* p_end ) { size_t q_len = ChfStrlen( q ); size_t p_avail = p_end - p; if ( q_len < p_avail ) { ChfStrcpy( p, q ); p += q_len; } else if ( p_avail > 1 ) { ChfStrncpy( p, q, p_avail - 2 ); p[ p_avail - 1 ] = '\0'; p = p_end; } return p; } /* ------------------------------------------------------------------------- Public functions ------------------------------------------------------------------------- */ /* .+ .title : ChfGetMessage .kind : C function .creation : 17-May-1996 .description : This function retrieves the message associated with the pair ('module_id', 'condition_code') and returns a pointer to it. The function will return 'default_message' if it isn't able to retrieve the message. If module_id==CHF_ERRNO_SET, the function will use strerror(), if necessary, to retrieve the requested message. NOTE: This function will call ChfAbort() with abort code CHF_ABORT_INIT if CHF hasn't been correctly initialized. NOTE: The returned pointer points to per-thread static storage, which will be overwritten by subsequent calls to this function. WIN32: - strerror() is not supported; the last-chance translation of condition codes in CHF_ERRNO_SET is not performed .call : message = ChfGetMessage(module_id, condition_code, default_message); .input : const int module_id, module identifier const int condition_code, condition code const char *default_message, default message .output : const char *message, pointer to the retrieved message .status_codes : none .notes : 1.1, 17-May-1996, creation 2.2, 22-Jan-2001, update: - added Win32 support .- */ const ChfChar* ChfGetMessage( /* Retrieve a condition message */ const int module_id, const int condition_code, const ChfChar* default_message ) { const ChfChar* message; /* Check that CHF has been correctly initialized */ if ( chf_context.state == CHF_UNKNOWN ) ChfAbort( CHF_ABORT_INIT ); if ( ( message = chf_context.mrs_get( chf_context.mrs_data, module_id, condition_code, default_message ) ) == default_message && module_id == CHF_ERRNO_SET ) #ifdef _WIN32 message = default_message; #else message = strerror( condition_code ); #endif return ( message ); } /* .+ .title : ChfBuildMessage .kind : C function .creation : 16-May-1996 .description : This function builds the message associated with the given condition descriptor and returns a pointer to a string containing it. NOTE: This function will call ChfAbort() with abort code CHF_ABORT_INIT if CHF hasn't been correctly initialized. NOTE: The returned pointer points to per-thread static storage, which will be overwritten by subsequent calls to this function. WIN32: - to save space, the application's name and severity code are not included in the message .call : msg = ChfBuildMessage(descriptor); .input : const ChfDescriptor *descriptor, condition descriptor .output : char *msg, pointer to the message associated with 'descriptor' .status_codes : none .notes : 1.1, 16-May-1996, creation 2.2, 22-Jan-2001, update: - added Win32 support .- */ ChfChar* ChfBuildMessage( /* Build a condition message */ const ChfDescriptor* descriptor ) { ChfChar* tmp_p; ChfChar* tmp_end; ChfChar def_message[ CHF_DEF_MESSAGE_LENGTH ]; ChfSeverity severity; /* Check that CHF has been correctly initialized */ if ( chf_context.state == CHF_UNKNOWN ) ChfAbort( CHF_ABORT_INIT ); /* Set appropriate pointers to the start/end of the message buffer */ tmp_p = chf_context.message_buffer; tmp_end = tmp_p + CHF_MAX_MESSAGE_LENGTH; #ifndef _WIN32 /* The message starts with ": " if the condition is the first of its condition group, with "\t" if not. */ if ( descriptor == chf_context.condition_sp - 1 ) { tmp_p = scopy( tmp_p, chf_context.app_name, tmp_end ); tmp_p = scopy( tmp_p, separator, tmp_end ); } else tmp_p = scopy( tmp_p, ChfText( "\t" ), tmp_end ); #endif /* The message continues with the module name */ ChfSprintf( def_message, CHF_DEF_MID_MSG_FMT, ChfGetModuleId( descriptor ) ); tmp_p = scopy( tmp_p, ChfGetMessage( CHF_MODULE_NAMES_SET, ChfGetModuleId( descriptor ), def_message ), tmp_end ); /* Add also the extended information, if any */ if ( ChfGetLineNumber( descriptor ) != CHF_UNKNOWN_LINE_NUMBER ) { tmp_p = scopy( tmp_p, ChfText( " " ), tmp_end ); ChfSprintf( def_message, CHF_EXTENDED_INFO_FMT, ChfGetFileName( descriptor ), ChfGetLineNumber( descriptor ) ); tmp_p = scopy( tmp_p, def_message, tmp_end ); } tmp_p = scopy( tmp_p, separator, tmp_end ); #ifndef _WIN32 /* Add the severity code of the message */ tmp_p = scopy( tmp_p, ( ( severity = ChfGetSeverity( descriptor ) ) < CHF_SUCCESS || severity > CHF_FATAL ) ? CHF_UNKNOWN_SEVERITY : severity_name[ severity ], tmp_end ); tmp_p = scopy( tmp_p, separator, tmp_end ); #endif /* The message ends with the partial message from the descriptor */ tmp_p = scopy( tmp_p, ChfGetPartialMessage( descriptor ), tmp_end ); ( void )scopy( tmp_p, CHF_MESSAGE_TERMINATOR, tmp_end ); return chf_context.message_buffer; } /* .+ .title : ChfInit .kind : C function .creation : 13-May-1996 .description : This function initializes CHF and returns to the caller a condition code; that code will be either CHF_S_OK if the initialization was succesful, or one of the other values listed below. It's necessary to invoke succesfully ChfInit() before using any other CHF function. NOTE: This function will call ChfAbort() with abort code CHF_ABORT_DUP_INIT if CHF has already been initialized before. NOTE: This function will call ChfAbort() with abort code CHF_ABORT_PTHREAD if a pthread operation fails. .call : cc = ChfInit(app_name, options, mrs_data, mrs_get, mrs_exit, condition_stack_size, handler_stack_size, exit_code); .input : const char *app_name, Application's name const ChfOptions options, Options void *mrs_data, Message retrieval private data ChfMrsGet mrs_get, 'GetMessage' function ChfMrsExit mrs_exit, 'Exit' function const int condition_stack_size, Size of the condition stack const int handler_stack_size, Size of the handler stack const int exit_code, Abnormal exit code .output : int cc, condition code .status_codes : CHF_F_MALLOC, FATAL, dynamic memory allocation failed .notes : 1.1, 13-May-1996, creation 1.6, 15-Jan-1997, update: - updated the call to ChfPushHandler() to accomodate its new interface. 2.1, 19-May-2000, update: - added multithreading support 2.2, 22-Jan-2001, update: - added Win32 support; a malloc() call was not portable. .- */ int ChfInit( /* Generic initialization */ const ChfChar* app_name, /* Application's name */ const ChfOptions options, /* Options */ void* mrs_data, /* Message retrieval private data */ ChfMrsGet mrs_get, /* 'GetMessage' function */ ChfMrsExit mrs_exit, /* 'Exit' function */ const int condition_stack_size, /* Size of the condition stack */ const int handler_stack_size, /* Size of the handler stack */ const int exit_code /* Abnormal exit code */ ) { int cc; /* Check that CHF has not been initialized yet */ #ifndef _REENTRANT if ( _chf_context.state != CHF_UNKNOWN ) ChfAbort( CHF_ABORT_DUP_INIT ); #else /* Reentrant check; lock context_mutex first */ if ( pthread_mutex_lock( &context_mutex ) ) ChfAbort( CHF_ABORT_PTHREAD ); if ( _chf_context.state != CHF_UNKNOWN ) { if ( pthread_mutex_unlock( &context_mutex ) ) ChfAbort( CHF_ABORT_PTHREAD ); ChfAbort( CHF_ABORT_DUP_INIT ); } #endif #ifndef _REENTRANT if ( ( _chf_context.condition_stack = ( ChfDescriptor* )malloc( ( size_t )( condition_stack_size + 1 ) * sizeof( ChfDescriptor ) ) ) == CHF_NULL_DESCRIPTOR ) cc = CHF_F_MALLOC; else if ( ( _chf_context.handler_stack = ( ChfHandlerDescriptor* )malloc( ( size_t )handler_stack_size * sizeof( ChfHandlerDescriptor ) ) ) == ( ChfHandlerDescriptor* )NULL ) { free( _chf_context.condition_stack ); cc = CHF_F_MALLOC; } else if ( ( _chf_context.message_buffer = ( ChfChar* )malloc( ( size_t )( CHF_MAX_MESSAGE_LENGTH ) * sizeof( ChfChar ) ) ) == ( ChfChar* )NULL ) { free( _chf_context.condition_stack ); free( _chf_context.handler_stack ); cc = CHF_F_MALLOC; } else #else /* Reentrant init: condition_stack, handler_stack, message_buffer are not needed in the master Chf context. Init the Chf data key instead. */ _chf_context.condition_stack = CHF_NULL_DESCRIPTOR; _chf_context.handler_stack = ( ChfHandlerDescriptor* )NULL; _chf_context.message_buffer = ( char* )NULL; if ( pthread_key_create( &data_key, DestroyContext ) ) ChfAbort( CHF_ABORT_PTHREAD ); #endif { /* Initialize the CHF context */ _chf_context.app_name = app_name; _chf_context.options = options; _chf_context.mrs_data = mrs_data; _chf_context.mrs_get = mrs_get; _chf_context.mrs_exit = mrs_exit; _chf_context.condition_stack_size = condition_stack_size; _chf_context.handler_stack_size = handler_stack_size; _chf_context.exit_code = exit_code; _chf_context.condition_base = _chf_context.condition_sp = _chf_context.condition_stack; _chf_context.handler_sp = _chf_context.handler_stack; _chf_context.state = CHF_IDLE; #ifndef _REENTRANT /* Push the default handler; in the reentrant case, this will be done once per thread, when the thread-specific context is primed. */ ChfPushHandler( DefaultHandler, CHF_NULL_CONTEXT, CHF_NULL_POINTER ); #endif cc = CHF_S_OK; } #ifdef _REENTRANT if ( pthread_mutex_unlock( &context_mutex ) ) ChfAbort( CHF_ABORT_PTHREAD ); #endif return cc; } /* .+ .title : ChfExit .kind : C function .creation : 24-May-1996 .description : This function shuts down CHF and returns nothing to the caller; after calling ChfExit() the application can continue, but any subsequent call to any other CHF function, except the inizialization functions, will abort the application using ChfAbort() with abort code CHF_ABORT_INIT. NOTE: This function will call ChfAbort() with abort code CHF_ABORT_INIT if CHF hasn't been initialized. NOTE: This function will call ChfAbort() with abort code CHF_ABORT_PTHREAD if a pthread operation fails. .call : ChfExit(); .input : void .output : void .status_codes : none .notes : 1.1, 24-May-1996, creation 2.1, 19-May-2000, update: - added multithreading support .- */ void ChfExit( void ) { /* Check that CHF has been correctly initialized */ #ifndef _REENTRANT if ( _chf_context.state == CHF_UNKNOWN ) ChfAbort( CHF_ABORT_INIT ); #else /* Reentrant check; lock context_mutex first */ if ( pthread_mutex_lock( &context_mutex ) ) ChfAbort( CHF_ABORT_PTHREAD ); if ( _chf_context.state == CHF_UNKNOWN ) { if ( pthread_mutex_unlock( &context_mutex ) ) ChfAbort( CHF_ABORT_PTHREAD ); ChfAbort( CHF_ABORT_INIT ); } #endif /* Destroy the context associated with this thread now; this is necessary to ensure that the context is actually destroyed when a single-threaded application links with the multithreaded version of Chf: in this case, pthread_exit() is called *after* ChfExit(), the Chf data key no longer exists when pthread_exit() is called and the destructor registered with pthread_key_create() does not take place. The data pointer associated with the Chf data key is set to NULL to avoid any subsequent reactivation of the destructor. */ #ifdef _REENTRANT DestroyContext( &chf_context ); if ( pthread_setspecific( data_key, ( void* )NULL ) ) { ( void )pthread_mutex_unlock( &context_mutex ); ChfAbort( CHF_ABORT_PTHREAD ); } #endif /* Shut down the message retrieval subsystem first */ _chf_context.mrs_exit( _chf_context.mrs_data ); #ifndef _REENTRANT /* Free the dynamic memory previously allocated */ free( _chf_context.message_buffer ); free( _chf_context.handler_stack ); free( _chf_context.condition_stack ); #else /* Destroy the Chf data key */ if ( pthread_key_delete( data_key ) ) ChfAbort( CHF_ABORT_PTHREAD ); #endif /* Reset CHF state to prevent subsequent calls to ChfExit() itself */ _chf_context.state = CHF_UNKNOWN; #ifdef _REENTRANT if ( pthread_mutex_unlock( &context_mutex ) ) ChfAbort( CHF_ABORT_PTHREAD ); #endif } /* .+ .title : _ChfGetContext .kind : C function .creation : 19-May-2000 .description : This function dynamically primes a new Chf context for the calling thread (if necessary), and returns a pointer to that context to the caller. If something does wrong, it aborts the application with ChfAbort(CHF_ABORT_GET_CONTEXT). Results are unpredictable if this function is called before a successful call to ChfInit(). .call : context = _ChfGetContext(void); .input : .output : ChfContext *context, per-thread Chf context .status_codes : none .notes : 2.1, 19-May-2000, creation .- */ ChfContext* _ChfGetContext( void ) { ChfContext* context; #ifndef _REENTRANT /* This function is doomed to fail if _REENTRANT is not defined */ ChfAbort( CHF_ABORT_GET_CONTEXT ); return ( ( ChfContext* )NULL ); #else /* Get the thread-specific context pointer associated with the CHF data key */ if ( ( context = ( ChfContext* )pthread_getspecific( data_key ) ) == ( ChfContext* )NULL ) { /* No context pointer; prime a new one, cloning the master context */ if ( ( context = ( ChfContext* )malloc( sizeof( ChfContext ) ) ) == ( ChfContext* )NULL ) ChfAbort( CHF_ABORT_GET_CONTEXT ); memcpy( context, &_chf_context, sizeof( ChfContext ) ); /* Allocate per-thread stacks and message buffer */ if ( ( context->condition_stack = ( ChfDescriptor* )malloc( ( size_t )( context->condition_stack_size + 1 ) * sizeof( ChfDescriptor ) ) ) == CHF_NULL_DESCRIPTOR ) ChfAbort( CHF_ABORT_GET_CONTEXT ); if ( ( context->handler_stack = ( ChfHandlerDescriptor* )malloc( ( size_t )( context->handler_stack_size ) * sizeof( ChfHandlerDescriptor ) ) ) == ( ChfHandlerDescriptor* )NULL ) { free( context->condition_stack ); ChfAbort( CHF_ABORT_GET_CONTEXT ); } if ( ( context->message_buffer = ( char* )malloc( ( size_t )( CHF_MAX_MESSAGE_LENGTH ) ) ) == ( char* )NULL ) { free( context->condition_stack ); free( context->handler_stack ); ChfAbort( CHF_ABORT_GET_CONTEXT ); } /* Initialize stack pointers */ context->condition_base = context->condition_sp = context->condition_stack; context->handler_sp = context->handler_stack; /* Set the thread-specific context pointer; this must be done before invoking any other function using the context, including ChfPushHandler() below. */ if ( pthread_setspecific( data_key, context ) ) ChfAbort( CHF_ABORT_GET_CONTEXT ); /* Push the default handler */ ChfPushHandler( DefaultHandler, CHF_NULL_CONTEXT, CHF_NULL_POINTER ); } return context; #endif }