/*  File: messubs.c
 *  Author: Richard Durbin (rd@mrc-lmb.cam.ac.uk)
 *  Copyright (C) J Thierry-Mieg and R Durbin, 1992
 * -------------------------------------------------------------------
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 * -------------------------------------------------------------------
 * This file is part of the ACEDB genome database package, written by
 * 	Richard Durbin (MRC LMB, UK) rd@mrc-lmb.cam.ac.uk, and
 *	Jean Thierry-Mieg (CRBM du CNRS, France) mieg@kaa.cnrs-mop.fr
 *
 * Description: low level messaging/exit routines, encapsulate vararg messages,
 *              *printf, crash handler, calling of application registered exit
 *              routines for cleaning up etc.
 *
 * Exported functions: see regular.h
 *
 * HISTORY:
 * Last edited: Jul  8 15:11 2003 (edgrif)
 * * May 23 10:18 2000 (edgrif): Fix SANgc07771
 * * Apr 13 16:10 2000 (edgrif): Attempt to improve messages output when
 *              messcrash recurses, totally rubbish before.
 * * Mar 22 14:41 1999 (edgrif): Replaced messErrorInit with messSetMsgInfo.
 * * Jan 27 09:19 1999 (edgrif): Put small orphan functions in utils.c
 * * Jan 13 11:20 1999 (edgrif): Small correction to Jeans changes to messGetErrorProgram
 *              and messGetErrorFile.
 * * Nov 19 13:26 1998 (edgrif): Removed the test for errorCount and messQuery
 *              in messerror, really the wrong place.
 * * Oct 22 15:26 1998 (edgrif): Replaced strdup's with strnew.
 * * Oct 21 15:07 1998 (edgrif): Removed messErrorCount stuff from graphcon.c
 *              and added to messerror (still not perfect), this was a new.
 *              bug in the message system.
 * * Sep 24 16:47 1998 (edgrif): Remove references to ACEDB in messages,
 *              change messExit prefix to "EXIT: "
 * * Sep 22 14:35 1998 (edgrif): Correct errors in buffer usage by message
 *              outputting routines and message formatting routines.
 * * Sep 11 09:22 1998 (edgrif): Add messExit routine.
 * * Sep  9 16:52 1998 (edgrif): Add a messErrorInit function to allow an
 *              application to register its name for use in crash messages.
 * * Sep  3 11:32 1998 (edgrif): Rationalise strings used as prefixes for
 *              messages. Add support for new messcrash macro to replace
 *              messcrash routine, this includes file/line info. for
 *              debugging (see regular.h for macro def.) and a new
 *              uMessCrash routine.
 * * Aug 25 14:51 1998 (edgrif): Made BUFSIZE enum (shows up in debugger).
 *              Rationalise the use of va_xx calls into a single macro/
 *              function and improve error checking on vsprintf.
 *              messdump was writing into messbuf half way up, I've stopped
 *              this and made two buffers of half the original size, one for
 *              messages and one for messdump.
 * * Aug 21 13:43 1998 (rd): major changes to make clean from NON_GRAPHICS
 *              and ACEDB.  Callbacks can be registered for essentially
 *              all functions.  mess*() versions continue to centralise
 *              handling of ... via stdarg.
 * * Aug 20 17:10 1998 (rd): moved memory handling to memsubs.c
 * * Jul  9 11:54 1998 (edgrif): 
 *              Fixed problem with SunOS not having strerror function, system
 *              is too old to have standard C libraries, have reverted to
 *              referencing sys_errlist for SunOS only.
 *              Also fixed problem with getpwuid in getLogin function, code
 *              did not check return value from getpwuid function.
 * * Jul  7 10:36 1998 (edgrif):
 *      -       Replaced reference to sys_errlist with strerror function.
 * * DON'T KNOW WHO MADE THESE CHANGES...NO RECORD IN HEADER....(edgrif)
 *      -       newformat added for the log file on mess dump.
 *      -       Time, host and pid are now always the first things written.
 *      -       This is for easier checking og the log.wrm with scripts etc.
 *      -       Messquery added for > 50 minor errors to ask if user wants to crash.
 *      -       Made user,pid and host static in messdump.
 * * Dec  3 15:52 1997 (rd)
 * 	-	messout(): defined(_WINDOW) =>!defined(NON_GRAPHIC)
 * * Dec 16 17:26 1996 (srk)
 * * Aug 15 13:29 1996 (srk)
 *	-	WIN32 and MACINTOSH: seteuid() etc. are stub functions
 * * Jun 6 10:50 1996 (rbrusk): compile error fixes
 * * Jun  4 23:31 1996 (rd)
 * Created: Mon Jun 29 14:15:56 1992 (rd)
 * CVS info:   $Id: messubs.c,v 1.122 2004/04/09 22:18:21 mieg Exp $
 *-------------------------------------------------------------------
 */



#ifdef ED_G_NEVER_INCLUDE_THIS_CODE
/* To get this compile on Solaris 5.8 will require a hack to get round glibs */
/* stupid  #define G_VA_COPY va_copy  this is not ANSI-C...                  */
/* Hopefully systems support will fix glib for us.                           */

#include </usr/include/varargs.h>

#endif /* ED_G_NEVER_INCLUDE_THIS_CODE */


#if defined(SGI) || defined(SOLARIS)
#include </usr/include/varargs.h>
#endif

#include <glib.h>
#include <wh/regular.h>
#include <wh/aceio.h>
#include <wh/sigsubs.h>


/***************************************************************/

/* This is horrible...a hack for sunos which is not standard C compliant.    */
/* to allow accessing system library error messages, will disappear....      */
#ifdef SUN
extern const char *sys_errlist[] ;
#endif


/* The messOutCond system */
typedef struct _MessErrorCondContextStruct
  {
    MessErrorCondMsg which_msg ;
    BOOL stop_on_first_failure ;
    void *msg_func_data ;
  } MessErrorCondContextStruct ;




/* This buffer is used only by the routines that OUTPUT a message. Routines  */
/* that format messages into buffers (e.g. messprintf, messSysErrorText)     */
/* have their own buffers. Note that there is a problem here in that this    */
/* buffer can be overflowed, unfortunately because we use vsprintf to do     */
/* our formatting, this can only be detected after the event.                */
/*                                                                           */
/* Constraints on message buffer size - applicable to ALL routines that      */
/* format externally supplied strings.                                       */
/*                                                                           */
/* BUFSIZE:  size of message buffers (messbuf, a global buffer for general   */
/*           message stuff and a private ones in messdump & messprintf).     */
/* PREFIX:   length of message prefix (used to report details such as the    */
/*           file/line info. for where the error occurred.                   */
/* MAINTEXT: space left in buffer is the rest after the prefix and string    */
/*           terminator (NULL) are subtracted.                               */
/* Is there an argument for putting this buffer size in regular.h ??         */
/*                                                                           */
enum {BUFSIZE = 32768, PREFIXSIZE = 1024, MAINTEXTSIZE = BUFSIZE - PREFIXSIZE - 1} ;

static char messbuf[BUFSIZE] ;



/* Some standard defines for titles/text for messages:                       */
/*                                                                           */
#define       ERROR_PREFIX             "ERROR -  "
#define        EXIT_PREFIX              "EXIT -  "
#define       CRASH_PREFIX       "FATAL ERROR (%s, file: %s, line: %d) -  "

#define CRASH_INFO_FORMAT "ruid: %s, euid: %s, nodeid: %s, program: %s, version: %s"
#define CRASH_INFO_DEFAULT "ruid: null, euid: null, nodeid: null, program: null, version: null"

#if defined(MACINTOSH)
#define      SYSERR_FORMAT             "system error %d"
#else
#define      SYSERR_FORMAT             "system error %d - %s"
#endif


/* Macro to format strings using va_xx calls, it calls uMessFormat whose     */
/* prototype is given below.                                                 */
/*                                                                           */
/* Arguments to the macro must have the following types:                     */
/*                                                                           */
/*   FORMAT_ARGS:   va_list used to get the variable argument list.          */
/*        FORMAT:   char *  to a string containing the printf format string. */
/*    TARGET_PTR:   char *  the formatted string will be returned in this    */
/*                          string pointer, N.B. do not put &TARGET_PTR      */
/*        PREFIX:   char *  to a string to be used as a prefix to the rest   */
/*                          of the string, or NULL.                          */
/*        BUFFER:   char *  the buffer where the formatting will take place, */
/*                          if NULL then the global messbuf buffer will be   */
/*                          used.                                            */
/*        BUFLEN:   unsigned                                                 */
/*                     int  the length of the buffer given by BUFFER (ignored*/
/*                          if BUFFER is NULL.                               */
/*                                                                           */
#define MESSFORMATSTRING(FORMAT_ARGS, FORMAT, TARGET_PTR, PREFIX, BUFFER, BUFLEN)  \
va_start(FORMAT_ARGS, FORMAT) ;                                                   \
TARGET_PTR = uMessFormat(FORMAT_ARGS, FORMAT, PREFIX, BUFFER, BUFLEN) ;           \
va_end(FORMAT_ARGS) ;

static char *uMessFormat(va_list args, char *format, char *prefix,
			 char *buffer, unsigned int buflen) ;


/* messcrash now reports the file/line no. where the messcrash was issued    */
/* as an aid to debugging. We do this using a static structure which holds   */
/* the information which is filled in by a macro version of messcrash        */
/* (see regular.h), the structure elements are retrieved using access        */
/* functions.                                                                */
/*                                                                           */
/* Originally messGetErrorProgram was an internal function, now it has       */
/* become external and I have made it return an 'unknown' string, this is    */
/* because the result may be used in a printf which will crash if passed a   */
/* null string on some systems.                                              */
/*                                                                           */
typedef struct _MessErrorInfo
  {
  char *progname ;					    /* Name of executable reporting
							       error. */
  char *progversion ;					    /* version of executable. */
  char *real_userid ;					    /* real userid */
  char *effective_userid ;				    /* effective userid */
  char *machineid ;					    /* machineid */
  const char *filename ;				    /* Filename where error reported */
  int line_num ;					    /* Line number of file where error
							       reported. */
  } MessErrorInfo ;

static MessErrorInfo message_L = 
{
  NULL,
  NULL,
  NULL,
  NULL,
  NULL,
  0
} ;


/* If the above have not been set we may need to return some form of string  */
/* for external callers enquiries.                                           */
static char *PROG_NOT_SET = "program_not_set" ;

/* This is where we keep the prefix for exit/crash errors.                   */
static char *errorPrefix_L = NULL ;

/* Keeps a running total of errors so far (incremented whenever messerror is */
/* called).                                                                  */
static int errorCount_L = 0 ;



/***************************************************************/

/* Exception handling routines:                                              */
/*                                                                           */
/* Use these when you want to subvert the action of either messerror or      */
/* messcrash completely (see also "callback" routines below). You are        */
/* completely responsible for any recursion that may occur.                  */
/*                                                                           */
/*                          These allow an application to recover from an    */
/* error or a crash by jumping back to the routine that originally started   */
/* actions that led to the error or crash. A good example of when you would  */
/* want to do this is for dumping a corrupted database, you might be able to */
/* dump individual classes but not the whole database, so you could keep     */
/* jumping back to the dump routine from the messcrash routine and carry on  */
/* dumping until you crash again, then you can jump back and try to carry on */
/* dumping.                                                                  */
/*                                                                           */
/* These are a very poor approximation to C++ exception handling like        */
/* behaviour.                                                                */
/*                                                                           */
static jmp_buf *errorJmpBuf = 0 ;
static jmp_buf *crashJmpBuf = 0 ;


/*****************************************************************************/
/* Message callback functions:                                               */
/*                                                                           */
/* Use these when you want to augment the action of any of the messaging     */
/* routines (messerror etc.) with actions of your own (e.g. graph uses this  */
/* as a mechanism to output messages in windows). This mechanism will trap   */
/* recursion so that your routine and the message routine do not call each   */
/* other endlessly.                                                          */
/*                                                                           */
/* These statics define a call-back for each different type of message 
 * interaction. The user can register its own functions to deal with every 
 * type of message. The messContextStruct defines a function and a generic 
 * pointer. This means that messout can be called context-less from anywahere 
 * in the code, but when the user-defined routine is dispatched it is called
 * with a parameter to tell the user-defined handler function about the
 * context of the message.
 *
 * It would be better if these statics were kept bundled up in a stack
 * so one could push and pop these handler stack with a complete set of
 * handler functions at a time. pooping the stack would save the need
 * to remember previous values to restore old behaviour.
 */
static struct messContextStruct outContext = { NULL, NULL };
static struct messContextStruct errorContext = { NULL, NULL };
static struct messContextStruct exitContext = { NULL, NULL };
static struct messContextStruct crashContext = { NULL, NULL };
static struct messContextStruct dumpContext = { NULL, NULL };
static struct messContextStruct statusContext = { NULL, NULL };
static QueryRoutine	  queryRoutine = 0 ;
static PromptRoutine	  promptRoutine = 0 ;
static VoidRoutine	  beepRoutine = 0 ;
static IsInterruptRoutine isInterruptRoutine = 0 ;
static ResetInterruptRoutine resetInterruptRoutine = 0 ;


/* internal utility routines.                                                */
/*                                                                           */
static int getErrorLine() ;
static const char *getErrorFile() ;
static char* getErrorPrefix(void) ;
static void makeErrorPrefix(MessErrorInfo *messinfo) ;


/* Used by messcrash to decide whether to abort() or exit() on termination.  */
static BOOL mess_abort_G = FALSE ;



/***************************************************************/


/* Callback registering functions.                                           */

struct messContextStruct messOutRegister (struct messContextStruct context)
{
  struct messContextStruct old = outContext;
  outContext = context;

  return old;
} /* messOutRegister */

struct messContextStruct messErrorRegister (struct messContextStruct context)
{  
  struct messContextStruct old = errorContext;
  errorContext = context;

  return old;
} /* messErrorRegister */

struct messContextStruct messExitRegister (struct messContextStruct context)
{  
  struct messContextStruct old = exitContext;
  exitContext = context;

  return old;
} /* messExitRegister */

struct messContextStruct messCrashRegister (struct messContextStruct context)
{  
  struct messContextStruct old = crashContext;
  crashContext = context;

  return old;
} /* messCrashRegister */

struct messContextStruct messDumpRegister (struct messContextStruct context)
{  
  struct messContextStruct old = dumpContext;
  dumpContext = context;

  return old;
} /* messDumpRegister */

struct messContextStruct messStatusRegister (struct messContextStruct context)
{  
  struct messContextStruct old = statusContext;
  statusContext = context;

  return old;
} /* messStatusRegister */

QueryRoutine messQueryRegister (QueryRoutine func)
{ QueryRoutine old = queryRoutine ; queryRoutine = func ; return old ; }

PromptRoutine messPromptRegister (PromptRoutine func)
{ PromptRoutine old = promptRoutine ; promptRoutine = func ; return old ; }

VoidRoutine messBeepRegister (VoidRoutine func)
{ VoidRoutine old = beepRoutine ; beepRoutine = func ; return old ; }

IsInterruptRoutine messIsInterruptRegister (IsInterruptRoutine func)
{ IsInterruptRoutine old = isInterruptRoutine ; isInterruptRoutine = func ; return old ; }

ResetInterruptRoutine messResetInterruptRegister (ResetInterruptRoutine func)
{ ResetInterruptRoutine old = resetInterruptRoutine ; resetInterruptRoutine = func ; return old ; }


/************************************************************/

/*                                                                           */
/* The message output routines.                                              */
/*                                                                           */
void messout (char *format,...)
{
  va_list args ;
  char *mesg_buf ;

  /* Format the message string.                                              */
  MESSFORMATSTRING(args, format, mesg_buf, NULL, NULL, 0)

  if (outContext.user_func)
    (*outContext.user_func)(mesg_buf, outContext.user_pointer);
  else
    fprintf (stdout, "// %s\n", mesg_buf) ;

  return;
} /* messout */

/************************************************************/

/* Access function for returning running error total.                        */
int messErrorCount (void) { return errorCount_L ; }


/* Output a non-fatal error message, for all messages a call to messdump is  */
/* made which may result in the message being logged. The single error count */
/* is also incremented so that functions can use this to check how many      */
/* errors have been recorded so far.                                         */
void messerror (char *format, ...)
{
  char *prefix = ERROR_PREFIX ;
  char *mesg_buf = NULL ;
  va_list args ;

  /* always increment the error count.                                       */
  ++errorCount_L ;

  /* Contruct the error message, may be needed either by the exception       */
  /* handlers (jmpbuf stuff) or by the application callback routines.        */
  MESSFORMATSTRING(args, format, mesg_buf, prefix, NULL, 0) ;

  /* If application registered an error exception handler routine, jump off  */
  /* to it.                                                                  */
  if (errorJmpBuf)
    longjmp (*errorJmpBuf, 1) ;

  /* Log the message.                                                        */
  messdump(mesg_buf) ;

  /* Output the message or call the application registered error routine.    */
  if (errorContext.user_func)
    (*errorContext.user_func)(mesg_buf, errorContext.user_pointer) ;
  else
    fprintf (stderr, "// %s\n", mesg_buf) ;

  invokeDebugger () ;

  return;
} /* messerror */

/************************************************************/

/* Use this function for errors that while being unrecoverable are not a     */
/* problem with the acedb code, e.g. if the user starts xace without         */
/* specifying a database.                                                    */
/* Note that there errors are logged but that this routine will exit without */
/* any chance to interrupt it (e.g. the crash routine in uMessCrash), this   */
/* could be changed to allow the application to register an exit handler.    */
/*                                                                           */
void messExit (char *format, ...)
{
  char *prefix = EXIT_PREFIX ;
  char *mesg_buf = NULL ;
  va_list args ;

  /* Format the message string.  */
  MESSFORMATSTRING(args, format, mesg_buf, prefix, NULL, 0) ;

  /* call user-defined exit-routine */
  if (exitContext.user_func)
    (*exitContext.user_func)(mesg_buf, exitContext.user_pointer) ;
  else
    fprintf (stderr, "// %s\n", mesg_buf);

  exit(EXIT_FAILURE) ;

  return ;			/* Should never get here. */
} /* messExit */


/************************************************************/

/* This is the routine called by the messcrash macro (see regular.h) which   */
/* actually does the message/handling and exit.                              */
/* This routine may encounter errors itself, in which case it will attempt   */
/* to call itself to report the error. To avoid infinite recursion we limit  */
/* this to just one reporting of an internal error and then we abort.        */
/*                                                                           */
/* NOTE that the order in which things happen is very important, don't change*/
/* without understanding what you are doing, you may screw up dumping of     */
/* corrupted databases or something else vital.                              */
/*                                                                           */
void uMessCrash (char *format, ...)
{
  static BOOL called_already = FALSE ;
  static char *prev_called_message = NULL ;
  char prefix[2056] ;
  int rc ;
  char *mesg_buf = NULL ;
  va_list args ;


  /* Contruct the error message, may be needed either by the exception       */
  /* handlers (jmpbuf stuff) or by the application callback routines.        */
  rc = sprintf(prefix, CRASH_PREFIX, getErrorPrefix(), getErrorFile(), getErrorLine()) ;
  if (rc < 0)
    fprintf(stderr, "sprintf failed in messcrash routine, cannot continue.") ;

  MESSFORMATSTRING(args, format, mesg_buf, prefix, NULL, 0) ;


  /* Application may want to keep trying to operate, e.g. to try to dump as  */
  /* much as possible of a corrupted database, so we jump directly to the    */
  /* application registered routine.                                         */
  /* Note, here we are at the mercy of the application, we may recurse for   */
  /* ever, its really up to the application to control this.                 */
  if (crashJmpBuf)
    longjmp(*crashJmpBuf, 1) ;


  /* From now on, if we recurse it is because the applications crash
   * routine has failed and we really have to exit at this point because
   * the whole application is probably out of control. */


  /* Check for recursive calls and abort if necessary, we cannot cope with
   * recursion here, we just abort to hopefully produce a core dump. */
  if (called_already) 
    {
      fprintf(stderr, "\n// Fatal internal error:\n"
	      "// %s has called messCrash and when messCrash called the %s\n"
	      "// clean up routine, this routine called messCrash again.\n"
	      "// messCrash will now abort the program as it cannot be terminated cleanly\n",
	      message_L.progname, message_L.progname) ;

      if (prev_called_message != NULL)
	fprintf(stderr, "// The original error was:\n"
		"// %s\n",
		prev_called_message) ;

      /* Hopefully will write core file.                                     */
      abort() ;
    }
  else
    called_already = TRUE ;


  /* Remember this message in case the user-defined routine calls messcrash  */
  /* again, in this case we have to abort, but we will be able to print out  */
  /* this previously remembered message.                                     */
  prev_called_message = mesg_buf ;

  /* call user-defined crash-routine or just output the message. */
  if (crashContext.user_func)
    (*crashContext.user_func)(mesg_buf, crashContext.user_pointer) ;
  else
    fprintf(stderr, "%s\n", mesg_buf) ;

  invokeDebugger() ;

  if (mess_abort_G)
    abort() ;
  else
    exit(EXIT_FAILURE) ;

  /* Should never get here. */
  return ;
}


/*****************************************************************/

/* If call_abort is TRUE then if messcrash is called it will terminate by
 * calling the system abort() function to take a core dump, otherwise
 * messcrash simply calls exit(). */
void messAbortOnCrash(BOOL call_abort)
{
  mess_abort_G = call_abort ;
  return ;
}


/*****************************************************************/

void messdump (char *format,...)
{
  static char dumpbuf[BUFSIZE] ;		  /* BEWARE limited buffer size. */
  char *mesg_buf ;
  va_list args ;

  /* Format the message string.                                              */
  MESSFORMATSTRING(args, format, mesg_buf, NULL, &dumpbuf[0], BUFSIZE)

  /* call user-defined dump-routine */
  if (dumpContext.user_func)
    (*dumpContext.user_func)(mesg_buf, dumpContext.user_pointer) ;

  return;
} /* messdump */


/************************************************************/

/* Creates a message context (this is opaque to the caller) which must be passed
 * to subsequent calls to messErrorCond(). */
MessErrorCondContext messErrorCondCreate(MessErrorCondMsg message_policy,
					 BOOL signal_fail_on_first_error,
					 void *msg_func_data)
{
  MessErrorCondContext context ;

  context = messalloc(sizeof(MessErrorCondContextStruct)) ;

  context->which_msg = message_policy ;
  context->stop_on_first_failure = signal_fail_on_first_error ;
  context->msg_func_data = msg_func_data ;

  return context ;
}

/* Outputs error messages in the same way as messerror() but according to state
 * data in msg_context. This allows the application to select whether none, only
 * the first or all messages should be shown.
 * The function always returns TRUE unless signal_fail_on_first_error was set to TRUE
 * when the context was created, in which case it returns TRUE the first time its
 * called and FALSE thereafter. This enables an application to stop processing on the
 * first error without having to implmenent the state/logic required all over the place. */
BOOL messErrorCond(MessErrorCondContext msg_context, char *format, ...)
{
  BOOL status ;

  if (msg_context->stop_on_first_failure)
    status = FALSE ;
  else
    status = TRUE ;

  if (msg_context->which_msg == MESSERRORCONDMSGS_FIRST
      || msg_context->which_msg == MESSERRORCONDMSGS_ALL)
    {

      /* Lots of common code with messerror here, could extract into a new subfunction
       * that both messerror and this func call.... */

      char *prefix = ERROR_PREFIX ;
      char *mesg_buf = NULL ;
      va_list args ;

      /* Contruct the error message, may be needed either by the exception       */
      /* handlers (jmpbuf stuff) or by the application callback routines.        */
      MESSFORMATSTRING(args, format, mesg_buf, prefix, NULL, 0) ;

      /* Log the message.                                                        */
      messdump(mesg_buf) ;

      /* Output the message or call the application registered error routine.    */
      if (errorContext.user_func)
	(*errorContext.user_func)(mesg_buf, errorContext.user_pointer) ;
      else
	fprintf (stderr, "// %s\n", mesg_buf) ;

      if (msg_context->which_msg == MESSERRORCONDMSGS_FIRST)
	msg_context->which_msg = MESSERRORCONDMSGS_NONE ;
    }

  return status ;
}



/************************************************************/

void messStatus (char* text)
{
  /* call user-defined status-routine */
  if (statusContext.user_func)
    (*statusContext.user_func)(text, statusContext.user_pointer);
  
  return ;
} /* messStatus */

/************************************************************/

ACEIN messPrompt (char *prompt, char *dfault, char *fmt, 
		  STORE_HANDLE handle)
{ 
  ACEIN fi ;
  
  if (promptRoutine)
    fi = (*promptRoutine)(prompt, dfault, fmt, handle) ;
  else
    /* by default get result from stdin */
    {
      fi = aceInCreateFromStdin(TRUE, "", handle);
      aceInPrompt (fi, messprintf("%s > ", prompt));
      if (!aceInCheck (fi, fmt))
	aceInDestroy (fi);	/* will set fi NULL */
    }

  return fi ;
} /* messPrompt */

/*****************************************************************/

BOOL messQuery (char *format,...)
{ 
  BOOL answer ;
  char *mesg_buf = NULL ;
  va_list args ;

  /* Format the message string.                                              */
  MESSFORMATSTRING(args, format, mesg_buf, NULL, NULL, 0)

  if (queryRoutine)
    answer = (*queryRoutine)(mesg_buf) ;
  else
    answer = freequery (mesg_buf) ;

  return answer ;
} /* messQuery */

/************************************************************/

void messbeep (void)
{
  if (beepRoutine)
    (*beepRoutine)() ;
  else
    { 
      /* write bell-character to terminal */
      if (isatty ( fileno(stdout) ) !=  0)
	{
	  fprintf (stdout, "%c",0x07);
	  fflush (stdout);
	}
    }

  return;
} /* messbeep */


/************************************************************/

/* Routines to call registered interrupt handlers, used to implement stuff   */
/* like user pressing F4 in xace.                                            */
/*                                                                           */
BOOL messIsInterruptCalled (void)
{
  if (isInterruptRoutine)
    return (*isInterruptRoutine)() ;

  /* unless a routine is registered, we assume no interrupt
     (e.g. F4 keypress in graph-window) has been called */
  return FALSE;
} /* messIsInterruptCalled */

void messResetInterrupt(void)
{
  if (resetInterruptRoutine)
    (*resetInterruptRoutine)() ;

  return ;
}

/************************************************************/



/******* interface to crash/error exception trapping *******/

jmp_buf* messCatchError (jmp_buf* new)
{
  jmp_buf* old = errorJmpBuf ;
  errorJmpBuf = new ;
  return old ;
}

jmp_buf* messCatchCrash (jmp_buf* new)
{
  jmp_buf* old = crashJmpBuf ;
  crashJmpBuf = new ;
  return old ;
}

char* messCaughtMessage (void)
{
  return messbuf ;
}



/* Message formatting routines.                                              */
/*                                                                           */
/*                                                                           */

/* This function writes into its own buffer, note that this has finite size  */
/* see top of file: BUFSIZE, also note that subsequent calls will overwrite  */
/* this buffer.                                                              */
/*                                                                           */
char *messprintf (char *format, ...)
{
  static char buffer[BUFSIZE] ;
  char *mesg_buf ;
  va_list args ;

  /* Format the message string.                                              */
  MESSFORMATSTRING(args, format, mesg_buf, NULL, &buffer[0], BUFSIZE)

  return mesg_buf ;
}


/* Used internally for formatting into a specified buffer.                   */
/* (currently only used as a cover function to enable us to use ACEFORMAT-   */
/* STRING from messSysErrorText)                                             */
static char *printToBuf(char *buffer, unsigned int buflen, char *format, ...)
{
  char *mesg_buf ;
  va_list args ;

  /* Format the message string.                                              */
  MESSFORMATSTRING(args, format, mesg_buf, NULL, buffer, buflen)

  return mesg_buf ;
}



/* Return the string for a given errno from the standard C library.          */
/*                                                                           */
char* messSysErrorText (void)
{
  return messErrnoText(errno);
}
 
char* messErrnoText (int error)
  {
  enum {ERRBUFSIZE = 2000} ;				    /* Should be enough. */
  static char errmess[ERRBUFSIZE] ;
  char *mess ;

#ifdef SUN
  /* horrible hack for Sunos/Macs(?) which are not standard C compliant */
  mess = printToBuf(&errmess[0], ERRBUFSIZE, SYSERR_FORMAT, error, sys_errlist[errno]) ;
#elif defined(MACINTOSH)
  mess = printToBuf(&errmess[0], ERRBUFSIZE, SYSERR_FORMAT, error) ;
#else
  mess = printToBuf(&errmess[0], ERRBUFSIZE, SYSERR_FORMAT, error, strerror(errno)) ;
#endif

  return mess ;
  }


/************************* message formatting ********************************/
/* This routine does the formatting of the message string using vsprintf,    */
/* it copes with the format string accidentally being our internal buffer.   */
/*                                                                           */
/* This routine does its best to check that the vsprintf is successful, if   */
/* not the routine bombs out with an error message. Note that num_bytes is   */
/* the return value from vsprintf.                                           */
/* Failures trapped:                                                         */
/*              num_bytes < 0  =>  vsprintf failed, reason is reported.      */
/*    num_bytes + 1 > BUFSIZE  =>  our internal buffer size was exceeded.    */
/*                                 (vsprintf returns number of bytes written */
/*                                  _minus_ terminating NULL)                */
/*                                                                           */
static char *uMessFormat(va_list args, char *format, char *prefix,
			 char *buffer, unsigned int buflen)
{
  char *buf_ptr ;
  unsigned int buf_len ;
  int prefix_len ;


  /* Check arguments.                                                        */
  if (format == NULL)
    { 
      fprintf(stderr, "uMessFormat() : "
	      "invalid call, no format string.\n") ;
      invokeDebugger();
      exit (EXIT_FAILURE);
    }

  if (prefix == NULL) 
    prefix_len = 0 ;
  else
    {
      prefix_len = strlen(prefix) ;
      if ((prefix_len + 1) > PREFIXSIZE) 
	{
	  fprintf (stderr, "uMessFormat() : "
		   "prefix string is too long.\n") ;
	  invokeDebugger();
	  exit (EXIT_FAILURE);
	}
    }

  /* If they supply their own buffer to receive the formatted 
     message then use this, otherwise use the global messbuf buffer. */
  if (buffer != NULL)
    {
      buf_ptr = buffer ;
      buf_len = buflen ;
      if (buf_len == 0) 
	{
	  fprintf (stderr, "uMessFormat() : "
		   "zero length buffer supplied for message format.\n") ;
	  invokeDebugger();
	  exit (EXIT_FAILURE);
	}
    }
  else
    {
      buf_ptr = &messbuf[0] ;
      buf_len = BUFSIZE ;
    }

  /* Add the prefix if there is one. */
  if (prefix != NULL)
    {
      if (strcpy (buf_ptr, prefix) == NULL) 
	{
	  fprintf (stderr, "uMessFormat() : strcpy failed\n") ;
	  invokeDebugger();
	  exit (EXIT_FAILURE);
	}
    }
  

  /* CHECK PERFORMANCE ISSUES....how is database dumped/logged.              */
  /* Fred has suggested that we could do a vprintf to /dev/null and see how  */
  /* many bytes that is then we could get away from a fixed internal buffer  */
  /* at all....but watch out, if messdump say is in a tight loop then this   */
  /* will kill performance...                                                */
  /* We could add a #define to allow a check to be included for debug code.  */
  /*                                                                         */


  /* Do the format. */
#ifdef SUN
  {
    char *return_str;

    /* NOTE, that SUNs vsprintf returns a char* */
    return_str = vsprintf((buf_ptr + prefix_len), format, args) + prefix_len + 1 ;
    
    /* Check the result. */
    if (!return_str)
      {
	fprintf(stderr, "uMessFormat() : "
		"vsprintf failed: %s\n", messSysErrorText()) ;
	invokeDebugger();
	exit (EXIT_FAILURE);
      }
    else if (strlen(return_str) > buf_len)
      {
	fprintf (stderr, "uMessFormat() : "
		 "messubs internal buffer size (%d) exceeded, "
		 "a total of %ld bytes were written\n",
		 buf_len, strlen(return_str)) ;  
	invokeDebugger();
	exit (EXIT_FAILURE);
      }
  }
#else  /* !SUN */
  {
    /* all other System's vsprintf returns an integer, of how many bytes have been written */
    int num_bytes = vsprintf((buf_ptr + prefix_len), format, args) + prefix_len + 1 ;
    
    /* Check the result.                                                       */
    if (num_bytes < 0)
      {
	fprintf(stderr, "uMessFormat() : "
		"vsprintf failed: %s\n", messSysErrorText()) ;
	invokeDebugger();
	exit (EXIT_FAILURE);
      }
    else if (num_bytes > buf_len)
      {
	fprintf (stderr, "uMessFormat() : "
		 "messubs internal buffer size (%d) exceeded, "
		 "a total of %d bytes were written\n",
		 buf_len, num_bytes) ;  
	invokeDebugger();
	exit (EXIT_FAILURE);
      }
  }
#endif /* !SUN */

  return(buf_ptr) ;
}



/********************** serious error info. routines. ************************/
/* When the acedb needs to exit because there has been an unrecoverable      */
/* error we want to output the file and line number of the code that         */
/* detected the error and various other bits of information, here are the    */
/* routines that provide the interface for that.                             */
/*                                                                           */

/* Allows the application to set information to be formatted into a serious  */
/* error message.                                                            */
/*                                                                           */
/* The application can set these at any time, this would allow the server    */
/* for example to set the user/machine id for the request it is currently    */
/* processing.                                                               */
/* Note that some things have to be set by the application, the program      */
/* name (given by argv[0]) cannot easily be obtained any other way (you      */
/* can't get it in a portable way from the process information).             */
/*                                                                           */
void messSetMsgInfo(char *progname, char *progversion)
{
  if (progname != NULL)
    message_L.progname = strnew(filGetFilename(progname), 0) ;
  if (progversion != NULL)
    message_L.progversion = strnew(progversion, 0) ;
  message_L.real_userid = strnew(getLogin(TRUE), 0) ;
  message_L.effective_userid = strnew(getLogin(FALSE), 0) ;
  message_L.machineid = strnew(getSystemName(), 0) ;
  
  makeErrorPrefix(&message_L) ;

  return ;
}


/* I don't want to expand this interface, we don't want this message utility */
/* to become the repository of correct userids/machineids etc without        */
/* some careful thought....                                                  */
/*                                                                           */
/* This function always returns a string, saves caller checking for NULL     */
/* string if they want to use the result in a printf.                        */
char *messGetErrorProgram (void)
{
  char *cp = message_L.progname ;

  if (!cp)
    cp = g_get_prgname() ; /* mieg, apr 2004: this call may return NULL */
  if (!cp)
    cp = "unkwon program" ;
  return cp ;
}  


/* This function is called by the messcrash macro which inserts the file and */
/* line information using the __FILE__ & __LINE__ macros.                    */
/*                                                                           */
/* Like all uNNNN routines it SHOULD NOT be called directly.                 */
/*                                                                           */
void uMessSetErrorOrigin(const char *filename, int line_num)
{
  assert(filename != NULL && line_num != 0) ;
  
  /* We take the filename pointer as it is, it came from a 
   * static const char* embedded in the code, which is safe,
   * any strnew etc. would be unsafe, 
   * as we may messcrash for memory trouble */
  message_L.filename = filename;
  
  message_L.line_num = line_num ;

  return ;
}



static const char *getErrorFile (void)
{
  return message_L.filename ;
}  

static int getErrorLine()
{
  return message_L.line_num ;
}  

/* I'm thinking that we should not be calling messCrash anywhere in these    */
/* routines, its tricky because the application could call this routine      */
/* at a time when it needs this message in a window....                      */
/*                                                                           */
/* Constructs the error message prefix, note that this is redone each time   */
/* messSetMsgInfo() is called so that we don't try to do this when an error  */
/* comes along.                                                              */
/*                                                                           */
/* Here is what we try to construct....                                      */
/*
    "FATAL ERROR (ruid: <user>, euid: <user>, nodeid: <machine>, program: <name>, version: <x_xn>, file <name>, line: <num>)
                 ===========================================================================================================
*/
static void makeErrorPrefix(MessErrorInfo *messinfo)
{
  static char *prefix = NULL ;

  if (prefix != NULL)
    {
      messfree(prefix) ;
      prefix = errorPrefix_L = NULL ;
    }

  prefix = hprintf(0, CRASH_INFO_FORMAT,
		   (messinfo->real_userid ? messinfo->real_userid : "null"),
		   (messinfo->effective_userid ? messinfo->effective_userid : "null"),
		   (messinfo->machineid ? messinfo->machineid : "null"),
		   (messinfo->progname ? messinfo->progname : "null"),
		   (messinfo->progversion ? messinfo->progversion : "null")) ;

  errorPrefix_L = prefix ;

  return ;
}


/* Return the error prefix for crashes/exits, if this is still NULL we DO
 * NOT try call makeErrorPrefix() to fill in the prefix because this can
 * cause recursion in messcrash, its up to the application to make sure that
 * it calls messSetMsgInfo() to fill in the information at the right time.
 * We use a predefined string to use minimum memory in a crash situation.    */
static char* getErrorPrefix(void)
{
  if (errorPrefix_L == NULL)
    errorPrefix_L = CRASH_INFO_DEFAULT ;
  
  return errorPrefix_L ;
}


/*****************************************************************************/

/* Utility function, do printf into dynamically allocated memory,
 * allocate the memory on a handle, if provided.
 * Consider this to replace messprintf everywhere....
 */
char *hprintf(STORE_HANDLE handle, char *format, ...)
{
  va_list args1;
  va_list args2;
  int buflen, chars_written ;
  char *buf;
  
  va_start(args1, format);
  G_VA_COPY (args2, args1);

  buflen = g_printf_string_upper_bound (format, args1) ;    /* Includes terminating null. */
  buf = halloc(buflen, handle) ;

  /* n.b. g_vsnprintf() returns length without terminating null. */
  if ((chars_written = g_vsnprintf(buf, buflen, format, args2)) >= buflen)
    messcrash("Error in hprintf(), expected to write no more than %d chars, but wrote %d.",
	      (buflen - 1), chars_written) ;

  va_end(args2);
  va_end(args1);

  return buf;
}
