/* .+ .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 }