Skip to content

File error_manager.c

File List > base > error_manager.c

Go to the documentation of this file

/*
 * Copyright 2008 Search Solution Corporation
 * Copyright 2016 CUBRID Corporation
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 *
 */

/*
 * error_manager.c - Error module (both client & server)
 */

#ident "$Id$"

// fix porting issue _lseek64
#if defined (WINDOWS)
#include <io.h>
#endif /* WINDOWS */

#include "error_manager.h"

#include "chartype.h"
#include "error_context.hpp"
#include "environment_variable.h"
#if defined (SERVER_MODE)
#include "log_impl.h"
#endif /* !SERVER_MODE */
#include "memory_alloc.h"
#include "message_catalog.h"
#include "object_representation.h"
#if !defined (WINDOWS)
#include "release_string.h"
#endif // not WINDOWS
#include "system_parameter.h"
#include "stack_dump.h"
#if !defined (SERVER_MODE)
#include "transaction_cl.h"
#endif /* !SERVER_MODE */

#if !defined (WINDOWS) && defined (SERVER_MODE)
#include "boot_sr.h"
#endif /* !WINDWS && SERVER_MODE */

// c++ headers
#include <cassert>
#include <cstddef>
#include <cstring>

// c headers
#include <errno.h>
#if defined (SOLARIS)
#include <netdb.h>
#endif /* SOLARIS */
#if defined (WINDOWS)
#include <process.h>
#endif // WINDOWS
#if defined (SERVER_MODE) && !defined (WINDOWS)
#include <pthread.h>
#endif /* SERVER_MODE && !WINDOWS */
#include <setjmp.h>
#include <signal.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#if !defined(CS_MODE)
#include <sys/stat.h>
#endif /* !CS_MODE */
#if !defined (WINDOWS)
#include <sys/time.h>
#endif /* !WINDOWS */
#if !defined(CS_MODE)
#include <sys/types.h>
#endif /* !CS_MODE */
#if !defined (WINDOWS)
#include <syslog.h>
#endif /* !WINDOWS */
#include <time.h>
#if !defined (WINDOWS)
#include <unistd.h>
#endif /* !WINDOWS */

#include <mutex>
// XXX: SHOULD BE THE LAST INCLUDE HEADER
#include "memory_wrapper.hpp"

/*
 * Definition of error message structure. One structure is defined for each
 * thread of execution. Note message areas are stored in the structure for
 * multi-threading purposes.
 */
typedef struct er_copy_area ER_COPY_AREA;
struct er_copy_area
{
  int err_id;           /* error identifier of the current message */
  int severity;         /* warning, error, FATAL error, etc... */
  int length_msg;       /* length of the message */
  char area[1];         /* actually, more than one */
};

typedef struct er_spec ER_SPEC;
struct er_spec
{
  int width;            /* minimum width of field */
  char code;            /* what to retrieve from the va_list int, long, double, long double or char */
  char spec[10];        /* buffer to hold the actual sprintf code */
};

typedef struct er_fmt ER_FMT;
struct er_fmt
{
  int err_id;           /* The int associated with the msg */
  char *fmt;            /* A printf-style format for the msg */
  ER_SPEC *spec;        /* Pointer to real array; points to spec_buf if nspecs < DIM(spec_buf) */
  int fmt_length;       /* The strlen() of fmt */
  int must_free;        /* TRUE if fmt must be free_and_initd */
  int nspecs;           /* The number of format specs in fmt */
  int spec_top;         /* The capacity of spec */
  ER_SPEC spec_buf[16];     /* Array of format specs for args */
};

using namespace cuberr;     // until we add everything under cuberr

#if defined(WINDOWS)
#define LOG_ALERT 0
static int
syslog (long priority, const char *message, ...)
{
  return 0;
}
#endif

// *INDENT-OFF*
std::mutex er_Log_file_mutex;
std::mutex er_Message_cache_mutex;
// *INDENT-ON*

/*
 * These are done via complied constants rather than the message catalog, because they must be available if the message
 * catalog is not available.
 */
static const char *er_severity_string[] = { "FATAL ERROR", "ERROR", "SYNTAX ERROR", "WARNING", "NOTIFICATION" };

static const char *er_unknown_severity = "Unknown severity level";

#define ER_SEVERITY_STRING(severity) \
  (((severity) >= ER_FATAL_ERROR_SEVERITY && (severity) <= ER_MAX_SEVERITY) \
   ? er_severity_string[severity] : er_unknown_severity)

#define ER_ERROR_WARNING_STRING(severity) \
  (((severity) >= ER_FATAL_ERROR_SEVERITY && (severity) < ER_WARNING_SEVERITY) \
   ? er_severity_string[ER_ERROR_SEVERITY] : "")


/*
 * Message sets within the er.msg catalog.  Set #1 comprises the system
 * error messages proper, while set #2 comprises the specific messages
 * that the er_ module uses itself.
 */
#define ER_MSG_SET      1
#define ER_INTERNAL_MSG_SET 2

#define PRM_ER_MSGLEVEL         0

#define ER_MALLOC(size) er_malloc_helper ((size), __FILE__, __LINE__)

#define SPEC_CODE_LONGLONG ((char)0x88)
#define SPEC_CODE_SIZE_T ((char)0x89)

#define MAX_LINE 4096

/*
 * Default text for messages. These messages are used only when we can't
 * locate a message catalog from which to read their counterparts. There
 * is no need to localize these messages.
 *
 * Make sure that this enumeration remains consistent with the following
 * array of default messages and with the message entries in every
 * message catalog.  If you add a new code, you need to add a new entry
 * to default_internal_msg[] and to every catalog.
 */
enum er_msg_no
{
  /* skip msg no 0 */
  ER_ER_HEADER = 1,
  ER_ER_MISSING_MSG,
  ER_ER_OUT_OF_MEMORY,
  ER_ER_NO_CATALOG,
  ER_ER_LOG_MISSING_MSG,
  ER_ER_EXIT,
  ER_ER_ASK,
  ER_ER_UNKNOWN_FILE,
  ER_ER_SUBSTITUTE_MSG,
  ER_LOG_ASK_VALUE,
  ER_LOG_MSGLOG_WARNING,
  ER_LOG_SUSPECT_FMT,
  ER_LOG_UNKNOWN_CODE,
  ER_LOG_WRAPAROUND,
  ER_LOG_MSG_WRAPPER,
  ER_LOG_SYSLOG_WRAPPER,
  ER_LOG_MSG_WRAPPER_D,
  ER_LOG_SYSLOG_WRAPPER_D,
  ER_LOG_LAST_MSG,
  ER_LOG_DEBUG_NOTIFY,
  ER_STOP_MAIL_SUBJECT,
  ER_STOP_MAIL_BODY,
  ER_STOP_SYSLOG,
  ER_EVENT_HANDLER
};

static const char *er_Builtin_msg[] = {
  /* skip msg no 0 */
  NULL,
  /* ER_ER_HEADER */
  "Error in error subsystem (line %d): ",
  /*
   * ER_ER_MISSING_MSG
   *
   * It's important that this message have no conversion specs, because
   * it is sometimes used to replace messages in which we have no
   * confidence (e.g., because they're proven not to have the same
   * number of conversion specs as arguments they are given).  In those
   * cases we can't rely on the arguments at all, and we must use a
   * format that won't try to do anything with va_arg().
   */
  "No error message available.",
  /* ER_ER_OUT_OF_MEMORY */
  "Can't allocate %d bytes.",
  /* ER_ER_NO_CATALOG */
  "Can't find message catalog files.",
  /* ER_ER_LOG_MISSING_MSG */
  "Missing message for error code %d.",
  /* ER_ER_EXIT */
  "\n... ABORT/EXIT IMMEDIATELY ...\n",
  /* ER_ER_ASK */
  "Do you want to exit? 1/0 ",
  /* ER_ER_UNKNOWN_FILE */
  "Unknown file",
  /* ER_ER_SUBSTITUTE_MSG */
  "No message available; original message format in error.",
  /* ER_LOG_ASK_VALUE */
  "er_init: *** Incorrect exit_ask value = %d; will assume %s instead. ***\n",
  /* ER_LOG_MSGLOG_WARNING */
  "er_init: *** WARNING: Unable to open message log \"%s\"; will assume stderr instead. ***\n",
  /* ER_LOG_SUSPECT_FMT */
  "er_study_fmt: suspect message for error code %d.",
  /* ER_LOG_UNKNOWN_CODE */
  "er_estimate_size: unknown conversion code (%d).",
  /* ER_LOG_WRAPAROUND */
  "\n\n*** Message log wraparound. Messages will continue at top of the file. ***\n\n",
  /* ER_LOG_MSG_WRAPPER */
  "\nTime: %s - %s *** %s CODE = %d, Tran = %d%s\n%s\n",
  /* ER_LOG_SYSLOG_WRAPPER */
  "CUBRID (pid: %d) *** %s *** %s CODE = %d, Tran = %d, %s",
  /* ER_LOG_MSG_WRAPPER_D */
  "\nTime: %s - %s *** file %s, line %d %s CODE = %d, Tran = %d%s\n%s\n",
  /* ER_LOG_SYSLOG_WRAPPER_D */
  "CUBRID (pid: %d) *** %s *** file %s, line %d, %s CODE = %d, Tran = %d. %s",
  /* ER_LOG_LAST_MSG */
  "\n*** The previous error message is the last one. ***\n\n",
  /* ER_LOG_DEBUG_NOTIFY */
  "\nTime: %s - DEBUG *** file %s, line %d\n",
  /* ER_STOP_MAIL_SUBJECT */
  "Mail -s \"CUBRID has been stopped\" ",
  /* ER_STOP_MAIL_BODY */
  "--->>>\n%s has been stopped at your request when the following error was set:\nerrid = %d, %s\nUser: %s, pid: %d, host: %s, time: %s<<<---",
  /* ER_STOP_SYSLOG */
  "%s has been stopped on errid = %d. User: %s, pid: %d",
  /* ER_EVENT_HANDLER */
  "er_init: cannot install event handler \"%s\""
};

static char *er_Cached_msg[sizeof (er_Builtin_msg) / sizeof (const char *)];
static bool er_Is_cached_msg = false;

/* Error log message file related */
#define ER_MSG_LOG_FILE_SUFFIX ".err"
static char er_Msglog_filename_buff[PATH_MAX];
static const char *er_Msglog_filename = NULL;
static FILE *er_Msglog_fh = NULL;

/* Error log message file related */
#define ER_ACCESS_LOG_FILE_SUFFIX ".access"
static char er_Accesslog_filename_buff[PATH_MAX];
static const char *er_Accesslog_filename = NULL;
static FILE *er_Accesslog_fh = NULL;


static ER_FMT er_Fmt_list[(-ER_LAST_ERROR) + 1];
static int er_Fmt_msg_fail_count = -ER_LAST_ERROR;
static int er_Errid_not_initialized = 0;
#if !defined (SERVER_MODE)
static er_log_handler_t er_Handler = NULL;
#endif /* !SERVER_MODE */
static unsigned int er_Eid = 0;

/* Event handler related */
static FILE *er_Event_pipe = NULL;
static bool er_Event_started = false;
static jmp_buf er_Event_jmp_buf;
static SIGNAL_HANDLER_FUNCTION saved_Sig_handler;

/* Other supporting global variables */
static bool er_Logfile_opened = false;
static bool er_Hasalready_initiated = false;
static bool er_Has_sticky_init = false;
static bool er_Isa_null_device = false;
static int er_Exit_ask = ER_EXIT_DEFAULT;
static int er_Print_to_console = ER_DO_NOT_PRINT;
/* TODO : remove this when applylogdb and copylogdb are removed
 * multithreaded client processes which start+end database (and error module) in a loop, may need to log errors on
 * other threads (while error module is stopped); this flag prevents assertion failure of error module initialization
*  for such case */
#if defined (CS_MODE)
static bool er_Ignore_uninit = false;
#endif

#if !defined (SERVER_MODE)
// requires own context
static context *er_Singleton_context_p;
#endif // not SERVER_MODE

static void er_event_sigpipe_handler (int sig);
static void er_event (void);
static int er_event_init (void);
static void er_event_final (void);

#if defined (SERVER_MODE) || defined (SA_MODE)
static void er_set_access_log_filename (void);
#endif /* SERVER_MODE || SA_MODE */

static FILE *er_file_open (const char *path);
static bool er_file_isa_null_device (const char *path);
static FILE *er_file_backup (FILE * fp, const char *path);

static void er_call_stack_dump_on_error (int severity, int err_id);
static void er_notify_event_on_error (int err_id);
static int er_set_internal (int severity, const char *file_name, const int line_no, int err_id, int num_args,
                bool include_os_error, FILE * fp, va_list * ap_ptr);
static void er_log (int err_id);
static void er_stop_on_error (void);

static int er_study_spec (const char *conversion_spec, char *simple_spec, int *position, int *width, int *va_class);
static void er_study_fmt (ER_FMT * fmt);
static size_t er_estimate_size (ER_FMT * fmt, va_list * ap);
static ER_FMT *er_find_fmt (int err_id, int num_args);
static void er_init_fmt (ER_FMT * fmt);
static ER_FMT *er_create_fmt_msg (ER_FMT * fmt, int err_id, const char *msg);
static void er_clear_fmt (ER_FMT * fmt);
static void er_internal_msg (ER_FMT * fmt, int code, int msg_num);
static void *er_malloc_helper (std::size_t size, const char *file, int line);
static void er_emergency (const char *file, int line, const char *fmt, ...);
static int er_vsprintf (er_message * er_entry_p, ER_FMT * fmt, va_list * ap);

static int er_call_stack_init (void);
static int er_fname_free (const void *key, void *data, void *args);
static void er_call_stack_final (void);

static void _er_log_debug_internal (const char *file_name, const int line_no, const char *fmt, va_list * ap);

static bool er_is_error_severity (er_severity severity);

/* vector of functions to call when an error is set */
static const PTR_FNERLOG er_Fnlog[ER_MAX_SEVERITY + 1] = {
  er_log,           /* ER_FATAL_ERROR_SEVERITY */
  er_log,           /* ER_ERROR_SEVERITY */
  er_log,           /* ER_SYNTAX_ERROR_SEVERITY */
  er_log,           /* ER_WARNING_SEVERITY */
  er_log            /* ER_NOTIFICATION_SEVERITY */
};

/*
 * er_get_msglog_filename - Find the error message log file name
 *   return: log file name
 */
const char *
er_get_msglog_filename (void)
{
  return er_Msglog_filename;
}

/*
 * er_event_sigpipe_handler
 */
static void
er_event_sigpipe_handler (int sig)
{
  _longjmp (er_Event_jmp_buf, 1);
}

/*
 * er_event - Notify a error event to the handler
 *   return: none
 */
static void
er_event (void)
{
  int err_id, severity, nlevels, line_no;
  const char *file_name, *msg;

  if (er_Event_pipe == NULL || er_Event_started == false)
    {
      return;
    }

  /* Get the most detailed error message available */
  er_all (&err_id, &severity, &nlevels, &line_no, &file_name, &msg);

#if !defined(WINDOWS)
  saved_Sig_handler = os_set_signal_handler (SIGPIPE, er_event_sigpipe_handler);
#endif /* not WINDOWS */
  if (_setjmp (er_Event_jmp_buf) == 0)
    {
      fprintf (er_Event_pipe, "%d %s %s\n", err_id, ER_SEVERITY_STRING (severity), msg);
      fflush (er_Event_pipe);
    }
  else
    {
      er_Event_started = false;
      if (er_Hasalready_initiated)
    {
      er_set (ER_ERROR_SEVERITY, ARG_FILE_LINE, ER_EV_BROKEN_PIPE, 1, prm_get_string_value (PRM_ID_EVENT_HANDLER));
    }
      if (er_Event_pipe != NULL)
    {
      fclose (er_Event_pipe);
      er_Event_pipe = NULL;
    }
    }
#if !defined(WINDOWS)
  os_set_signal_handler (SIGPIPE, saved_Sig_handler);
#endif /* not WINDOWS */
}

/*
 * er_evnet_init
 */
static int
er_event_init (void)
{
  volatile int error = NO_ERROR;
  const char *msg;

#if !defined(WINDOWS)
  saved_Sig_handler = os_set_signal_handler (SIGPIPE, er_event_sigpipe_handler);
#endif /* not WINDOWS */
  if (_setjmp (er_Event_jmp_buf) == 0)
    {
      er_Event_started = false;
      if (er_Event_pipe != NULL)
    {
      if (er_Hasalready_initiated)
        {
          er_set (ER_NOTIFICATION_SEVERITY, ARG_FILE_LINE, ER_EV_STOPPED, 0);
        }
      msg = msgcat_message (MSGCAT_CATALOG_CUBRID, MSGCAT_SET_ERROR, -ER_EV_STOPPED);
      fprintf (er_Event_pipe, "%d %s %s", ER_EV_STOPPED, ER_SEVERITY_STRING (ER_NOTIFICATION_SEVERITY),
           (msg ? msg : "?"));

      fflush (er_Event_pipe);
      pclose (er_Event_pipe);
      er_Event_pipe = NULL;
    }

      er_Event_pipe = popen (prm_get_string_value (PRM_ID_EVENT_HANDLER), "w");
      if (er_Event_pipe != NULL)
    {
      if (er_Hasalready_initiated)
        {
          er_set (ER_NOTIFICATION_SEVERITY, ARG_FILE_LINE, ER_EV_STARTED, 0);
        }
      msg = msgcat_message (MSGCAT_CATALOG_CUBRID, MSGCAT_SET_ERROR, -ER_EV_STARTED);
      fprintf (er_Event_pipe, "%d %s %s", ER_EV_STARTED, ER_SEVERITY_STRING (ER_NOTIFICATION_SEVERITY),
           (msg ? msg : "?"));

      fflush (er_Event_pipe);
      er_Event_started = true;
    }
      else
    {
      if (er_Hasalready_initiated)
        {
          er_set (ER_ERROR_SEVERITY, ARG_FILE_LINE, ER_EV_CONNECT_HANDLER, 1,
              prm_get_string_value (PRM_ID_EVENT_HANDLER));
        }
      error = ER_EV_CONNECT_HANDLER;
    }
    }
  else
    {
      if (er_Hasalready_initiated)
    {
      er_set (ER_ERROR_SEVERITY, ARG_FILE_LINE, ER_EV_BROKEN_PIPE, 1, prm_get_string_value (PRM_ID_EVENT_HANDLER));
    }
      if (er_Event_pipe != NULL)
    {
      fclose (er_Event_pipe);
      er_Event_pipe = NULL;
    }
      error = ER_EV_BROKEN_PIPE;
    }
#if !defined(WINDOWS)
  os_set_signal_handler (SIGPIPE, saved_Sig_handler);
#endif /* not WINDOWS */

  return error;
}

/*
 * er_event_final
 */
static void
er_event_final (void)
{
  const char *msg;

#if !defined(WINDOWS)
  saved_Sig_handler = os_set_signal_handler (SIGPIPE, er_event_sigpipe_handler);
#endif /* not WINDOWS */
  if (_setjmp (er_Event_jmp_buf) == 0)
    {
      er_Event_started = false;
      if (er_Event_pipe != NULL)
    {
      if (er_Hasalready_initiated)
        {
          er_set (ER_NOTIFICATION_SEVERITY, ARG_FILE_LINE, ER_EV_STOPPED, 0);
        }
      msg = msgcat_message (MSGCAT_CATALOG_CUBRID, MSGCAT_SET_ERROR, -ER_EV_STOPPED);
      fprintf (er_Event_pipe, "%d %s %s", ER_EV_STOPPED, ER_SEVERITY_STRING (ER_NOTIFICATION_SEVERITY),
           (msg ? msg : "?"));

      fflush (er_Event_pipe);
      pclose (er_Event_pipe);
      er_Event_pipe = NULL;
    }
    }
  else
    {
      if (er_Hasalready_initiated)
    {
      er_set (ER_ERROR_SEVERITY, ARG_FILE_LINE, ER_EV_BROKEN_PIPE, 1, prm_get_string_value (PRM_ID_EVENT_HANDLER));
    }
      if (er_Event_pipe != NULL)
    {
      fclose (er_Event_pipe);
      er_Event_pipe = NULL;
    }
    }
#if !defined(WINDOWS)
  os_set_signal_handler (SIGPIPE, saved_Sig_handler);
#endif /* not WINDOWS */
}

/*
 * er_call_stack_init -
 *   return: error code
 */
static int
er_call_stack_init (void)
{
#if defined(LINUX)
  fname_table = mht_create (0, 100, mht_5strhash, mht_compare_strings_are_equal);
  if (fname_table == NULL)
    {
      return ER_FAILED;
    }
#endif

  return NO_ERROR;
}

/*
 * er_call_stack_final -
 *   return: none
 */
static void
er_call_stack_final (void)
{
#if defined(LINUX)
  if (fname_table == NULL)
    {
      return;
    }

  mht_map (fname_table, er_fname_free, NULL);
  mht_destroy (fname_table);
  fname_table = NULL;
#endif
}

/*
 * er_fname_free -
 *   return: error code
 */
static int
er_fname_free (const void *key, void *data, void *args)
{
  free ((void *) key);
  free (data);

  return NO_ERROR;
}

#if defined (SERVER_MODE) || defined (SA_MODE)
/*
 * er_set_access_log_filename - set er_Accesslog_filename_buff, er_Accesslog_filename
 *   return:
 */
static void
er_set_access_log_filename (void)
{
  char tmp[PATH_MAX];
  std::size_t len, suffix_len;

  if (er_Msglog_filename == NULL)
    {
      er_Accesslog_filename = NULL;
      return;
    }

  len = std::strlen (er_Msglog_filename);
  suffix_len = std::strlen (ER_MSG_LOG_FILE_SUFFIX);

  if (len < suffix_len || strncmp (&er_Msglog_filename[len - suffix_len], ER_MSG_LOG_FILE_SUFFIX, suffix_len) != 0)
    {
      if (snprintf (er_Accesslog_filename_buff, PATH_MAX, "%s%s", er_Msglog_filename, ER_ACCESS_LOG_FILE_SUFFIX) < 0)
    {
      er_Accesslog_filename = NULL;
      return;
    }
      /* ex) server.log => server.log.access */
    }
  else
    {
      strncpy (tmp, er_Msglog_filename, PATH_MAX);
      tmp[len - suffix_len] = '\0';
      if (snprintf (er_Accesslog_filename_buff, PATH_MAX - 1, "%s%s", tmp, ER_ACCESS_LOG_FILE_SUFFIX) < 0)
    {
      er_Accesslog_filename = NULL;
      return;
    }
      /* ex) server_log.err => server_log.access */
    }

  er_Accesslog_filename = er_Accesslog_filename_buff;

  /* in case of strlen(er_Msglog_filename) > PATH_MAX - 7 */
  if (strnlen (er_Accesslog_filename_buff, PATH_MAX) >= PATH_MAX)
    {
      er_Accesslog_filename = NULL;
    }
}
#endif /* SERVER_MODE || SA_MODE */

/*
 * er_init - Initialize parameters for message module
 *   return: none
 *   msglog_filename(in): name of message log file
 *   exit_ask(in): define behavior when a sever error is found
 *
 * Note: This function initializes parameters that define the behavior
 *       of the message module (i.e., logging file , and fatal error
 *       exit condition). If the value of msglog_filename is NULL,
 *       error messages are logged/sent to PRM_ER_LOG_FILE. If that
 *       is null, then messages go to stderr. If msglog_filename
 *       is equal to /dev/null, error messages are not logged.
 */
int
er_init (const char *msglog_filename, int exit_ask)
{
  int i;
  const char *msg;
  MSG_CATD msg_catd;

  if (er_Has_sticky_init)
    {
      assert (er_Hasalready_initiated);
      // do not reinitialize
      return NO_ERROR;
    }

  if (er_Hasalready_initiated)
    {
      er_final (ER_ALL_FINAL);
    }

  for (i = 0; i < (int) DIM (er_Builtin_msg); i++)
    {
      if (er_Cached_msg[i] && er_Cached_msg[i] != er_Builtin_msg[i])
    {
      free_and_init (er_Cached_msg[i]);
    }
      er_Cached_msg[i] = (char *) er_Builtin_msg[i];
    }

  assert (DIM (er_Fmt_list) > abs (ER_LAST_ERROR));

  for (i = 0; i < abs (ER_LAST_ERROR); i++)
    {
      er_init_fmt (&er_Fmt_list[i]);
    }

  msg_catd = msgcat_get_descriptor (MSGCAT_CATALOG_CUBRID);
  if (msg_catd != NULL)
    {
      er_Fmt_msg_fail_count = 0;
      for (i = 2; i < abs (ER_LAST_ERROR); i++)
    {
      msg = msgcat_gets (msg_catd, MSGCAT_SET_ERROR, i, NULL);
      if (msg == NULL || msg[0] == '\0')
        {
          er_Fmt_msg_fail_count++;
          continue;
        }
      else
        {
          if (er_create_fmt_msg (&er_Fmt_list[i], -i, msg) == NULL)
        {
          er_Fmt_msg_fail_count++;
        }
        }
    }
    }
  else
    {
      er_Fmt_msg_fail_count = abs (ER_LAST_ERROR) - 2;
    }

  er_Hasalready_initiated = true;
  // quick-fix for reloading error manager issues. the broker generated cas client need to reload error manager with
  // update file name parameter. however, csql and other utilities should keep their initial file name.
  // we need to treat this case properly. for now, I just consider "sticky" initialization when a specific file name
  // is provided as argument. error manager is not reloaded after.
  // for cas case, error manager is first initialized without a specific filename, so it won't be "sticky". It is
  // reloaded during boot_register_client.
  er_Has_sticky_init = (msglog_filename != NULL);

#if !defined (SERVER_MODE)
  // we need to register a context
  er_Singleton_context_p = new context ();
  er_Singleton_context_p->register_thread_local ();
  // SERVER_MODE should have already one
#endif // not SERVER_MODE

  switch (exit_ask)
    {
    case ER_EXIT_ASK:
    case ER_EXIT_DONT_ASK:
    case ER_NEVER_EXIT:
    case ER_ABORT:
      er_Exit_ask = exit_ask;
      break;

    default:
      er_Exit_ask = ER_EXIT_DEFAULT;
      er_log_debug (ARG_FILE_LINE, er_Cached_msg[ER_LOG_ASK_VALUE], exit_ask,
            ((er_Exit_ask == ER_EXIT_ASK) ? "ER_EXIT_ASK" : "ER_NEVER_EXIT"));
      break;
    }

  /*
   * Install event handler
   */
  if (prm_get_string_value (PRM_ID_EVENT_HANDLER) != NULL && *prm_get_string_value (PRM_ID_EVENT_HANDLER) != '\0')
    {
      if (er_event_init () != NO_ERROR)
    {
      er_log_debug (ARG_FILE_LINE, er_Cached_msg[ER_EVENT_HANDLER], prm_get_string_value (PRM_ID_EVENT_HANDLER));
    }
    }

  /*
   * Remember the name of the message log file
   */
  if (msglog_filename == NULL)
    {
      if (prm_get_string_value (PRM_ID_ER_LOG_FILE))
    {
      msglog_filename = prm_get_string_value (PRM_ID_ER_LOG_FILE);
    }
    }

  // *INDENT-OFF*
  // protect log file mutex
  std::unique_lock<std::mutex> log_file_lock (er_Log_file_mutex); // mutex is released on destructor
  // *INDENT-ON*

  if (msglog_filename != NULL)
    {
      if (IS_ABS_PATH (msglog_filename) || msglog_filename[0] == PATH_CURRENT)
    {
      strncpy (er_Msglog_filename_buff, msglog_filename, PATH_MAX - 1);
    }
      else
    {
      envvar_logdir_file (er_Msglog_filename_buff, PATH_MAX, msglog_filename);
    }

      er_Msglog_filename_buff[PATH_MAX - 1] = '\0';
      er_Msglog_filename = er_Msglog_filename_buff;
    }
  else
    {
      er_Msglog_filename = NULL;
    }

#if defined (SERVER_MODE) || defined (SA_MODE)
  er_set_access_log_filename ();
#endif /* SERVER_MODE || SA_MODE */

  /* Define message log file */
  if (er_Logfile_opened == false)
    {
      if (er_Msglog_filename)
    {
      er_Isa_null_device = er_file_isa_null_device (er_Msglog_filename);

      if (er_Isa_null_device)
        {
          er_Msglog_fh = er_file_open (er_Msglog_filename);
        }
      else
        {
          char path[PATH_MAX];
          const char *er_file_path;

          if (prm_get_bool_value (PRM_ID_ER_PRODUCTION_MODE))
        {
          er_file_path = er_Msglog_filename;
        }
          else
        {
          /* want to err on the side of doing production style error logs because this may be getting set at some
           * naive customer site. */
          sprintf (path, "%s.%d", er_Msglog_filename, getpid ());
          er_file_path = path;
        }

          er_Msglog_fh = er_file_open (er_file_path);

#if !defined (WINDOWS) && defined (SERVER_MODE)
          if (er_Msglog_fh != NULL)
        {
          er_file_create_link_to_current_log_file (er_file_path, ER_MSG_LOG_FILE_SUFFIX);
        }
#endif /* !WINDOWS && SERVER_MODE */
        }

      if (er_Msglog_fh == NULL)
        {
          er_Msglog_fh = stderr;
          er_log_debug (ARG_FILE_LINE, er_Cached_msg[ER_LOG_MSGLOG_WARNING], er_Msglog_filename);
        }
    }
      else
    {
      er_Msglog_fh = stderr;
    }

      if (er_Accesslog_filename)
    {
      er_Isa_null_device = er_file_isa_null_device (er_Accesslog_filename);

      if (er_Isa_null_device)
        {
          er_Accesslog_fh = er_file_open (er_Accesslog_filename);
        }
      else
        {
          char path[PATH_MAX];
          const char *ac_file_path;

          if (prm_get_bool_value (PRM_ID_ER_PRODUCTION_MODE))
        {
          ac_file_path = er_Accesslog_filename;
        }
          else
        {
          /* want to err on the side of doing production style error logs because this may be getting set at some
           * naive customer site. */
          sprintf (path, "%s.%d", er_Accesslog_filename, getpid ());
          ac_file_path = path;
        }

          er_Accesslog_fh = er_file_open (ac_file_path);

#if !defined (WINDOWS) && defined (SERVER_MODE)
          if (er_Accesslog_fh != NULL)
        {
          er_file_create_link_to_current_log_file (ac_file_path, ER_ACCESS_LOG_FILE_SUFFIX);
        }
#endif /* !WINDOWS && SERVER_MODE */
        }

      if (er_Accesslog_fh == NULL)
        {
          er_Accesslog_fh = stderr;
          er_log_debug (ARG_FILE_LINE, er_Cached_msg[ER_LOG_MSGLOG_WARNING], er_Accesslog_filename);
        }
    }
      else
    {
      er_Accesslog_fh = stderr;
    }

      er_Logfile_opened = true;
    }

  log_file_lock.unlock ();

  /*
   * Message catalog may be initialized by msgcat_init() during bootstrap.
   * But, try once more to call msgcat_init() because there could be
   * an exception case that get here before bootstrap.
   */
  int status = NO_ERROR;
  if (msgcat_init () != NO_ERROR)
    {
      // todo
      er_emergency (__FILE__, __LINE__, er_Cached_msg[ER_ER_NO_CATALOG]);
      status = ER_FAILED;
    }
  else if (er_Is_cached_msg == false)
    {
      /* cache the messages */

      /*
       * Remember, we skip code 0.  If we can't find enough memory to
       * copy the silly message, things are going to be pretty tight
       * anyway, so just use the default version.
       */
      for (i = 1; i < (int) DIM (er_Cached_msg); i++)
    {
      const char *msg;
      char *tmp;

      msg = msgcat_message (MSGCAT_CATALOG_CUBRID, MSGCAT_SET_INTERNAL, i);
      if (msg && *msg)
        {
          tmp = (char *) malloc (std::strlen (msg) + 1);
          if (tmp)
        {
          strcpy (tmp, msg);
          er_Cached_msg[i] = tmp;
        }
        }
    }

      er_Is_cached_msg = true;
    }

  if (er_call_stack_init () != NO_ERROR)
    {
      status = ER_FAILED;
    }

  return status;
}

/*
 * er_is_initialized () - return if error manager was initialized.
 *
 * return :
 * void (in) :
 */
bool
er_is_initialized (void)
{
  return er_Hasalready_initiated;
}

/*
 * er_set_print_property -
 *   return: void
 *   print_console(in):
 */
void
er_set_print_property (int print_console)
{
  er_Print_to_console = print_console;
}

/*
 * er_file_open - small utility function to open error log file
 *   return: FILE *
 *   path(in): file path
 */
static FILE *
er_file_open (const char *path)
{
  FILE *fp;
  char dir[PATH_MAX], *tpath;

  assert (path != NULL);

  tpath = strdup (path);
  while (1)
    {
      if (cub_dirname_r (tpath, dir, PATH_MAX) > 0 && access (dir, F_OK) < 0)
    {
      if (mkdir (dir, 0777) < 0 && errno == ENOENT)
        {
          free_and_init (tpath);
          tpath = strdup (dir);
          continue;
        }
    }
      break;
    }
  free_and_init (tpath);

  /* note: in "a+" mode, output is always appended */
  fp = fopen (path, "r+");
  if (fp != NULL)
    {
      fseek (fp, 0, SEEK_END);
      if (ftell (fp) > prm_get_integer_value (PRM_ID_ER_LOG_SIZE))
    {
      /* not a null device file */
      fp = er_file_backup (fp, path);
    }
    }
  else
    {
      fp = fopen (path, "w");
    }
  return fp;
}

/*
 * er_file_isa_null_device - check if it is a null device
 *    return: true if the path is a null device. false otherwise
 *    path(in): path to the file
 */
static bool
er_file_isa_null_device (const char *path)
{
#if defined(WINDOWS)
  const char *null_dev = "NUL";
#else /* UNIX family */
  const char *null_dev = "/dev/null";
#endif

  if (path != NULL && strcmp (path, null_dev) == 0)
    {
      return true;
    }
  else
    {
      return false;
    }
}

static FILE *
er_file_backup (FILE * fp, const char *path)
{
  char backup_file[PATH_MAX];

  assert (fp != NULL);
  assert (path != NULL);

  fclose (fp);

  sprintf (backup_file, "%s.bak", path);
  (void) unlink (backup_file);
  (void) rename (path, backup_file);

  return fopen (path, "w");
}

#if !defined (WINDOWS) && defined (SERVER_MODE)
/*
 * er_file_create_link_to_current_log_file - creates a symbolic link to the current log file
 *   return: none
 *   log_file_path (in): path to the log file
 *   suffix (in): file suffix
 *
 * The symbolic link is something like $CUBRID/log/server/{db_name}_latest{suffix}.
 */
void
er_file_create_link_to_current_log_file (const char *log_file_path, const char *suffix)
{
  FILE *fp;
  char link_path[PATH_MAX];
  char link_dir_path[PATH_MAX];
  const char *db_name;

  assert (log_file_path != NULL);

  db_name = boot_db_name ();
  if (*db_name == '\0')
    {
      // boot in progress
      return;
    }

  cub_dirname_r (log_file_path, link_dir_path, PATH_MAX);

  if (snprintf (link_path, PATH_MAX, "%s%c%s_latest%s", link_dir_path, PATH_SEPARATOR, db_name, suffix) >= PATH_MAX)
    {
      // overflow
      return;
    }

  (void) unlink (link_path);    // remove existing link file
  symlink (log_file_path, link_path);
}
#endif /* !WINDOWS && SERVER_MODE */

/*
 * er_final - Terminate the error message module
 *   return: none
 *   do_global_final(in):
 *   need_csect(in): server_mode only; if true, get ENTER_LOG_FILE critical section
 */
void
er_final (ER_FINAL_CODE do_global_final)
{
  FILE *fh = NULL;
  int i = 0;

  if (!er_Hasalready_initiated)
    {
      // not initialized
      return;
    }

  if (do_global_final == ER_ALL_FINAL)
    {
#if !defined (SERVER_MODE)
      // destroy singleton context
      er_Singleton_context_p->deregister_thread_local ();
      delete er_Singleton_context_p;
      er_Singleton_context_p = NULL;
#endif // not SERVER_MODE

      er_event_final ();

      // *INDENT-OFF*
      // protect log file mutex
      std::unique_lock<std::mutex> log_file_lock (er_Log_file_mutex); // mutex is released on destructor
      // *INDENT-ON*

      if (er_Logfile_opened == true)
    {
      if (er_Msglog_fh != NULL && er_Msglog_fh != stderr)
        {
          fh = er_Msglog_fh;
          er_Msglog_fh = NULL;
          (void) fclose (fh);
        }
      er_Logfile_opened = false;

      if (er_Accesslog_fh != NULL && er_Accesslog_fh != stderr)
        {
          fh = er_Accesslog_fh;
          er_Accesslog_fh = NULL;
          (void) fclose (fh);
        }
    }

      log_file_lock.unlock ();

      for (i = 0; i < (int) DIM (er_Fmt_list); i++)
    {
      er_clear_fmt (&er_Fmt_list[i]);
    }

      for (i = 0; i < (int) DIM (er_Cached_msg); i++)
    {
      if (er_Cached_msg[i] != er_Builtin_msg[i])
        {
          free_and_init (er_Cached_msg[i]);
          er_Cached_msg[i] = (char *) er_Builtin_msg[i];
        }
    }

      er_call_stack_final ();

      er_Hasalready_initiated = false;
      er_Has_sticky_init = false;
    }
}

/*
 * er_clear - Clear any error message
 *   return: none
 *
 * Note: This function is used to ignore an occurred error.
 */
void
er_clear (void)
{
  if (!er_is_initialized ())
    {
      // ignore
      return;
    }
  context::get_thread_local_context ().clear_current_error_level ();
}

/*
 * er_set - Set an error
 *   return: none
 *   severity(in): may exit if severity == ER_FATAL_ERROR_SEVERITY
 *   file_name(in): file name setting the error
 *   line_no(in): line in file where the error is set
 *   err_id(in): the error identifier
 *   num_args(in): number of arguments...
 *   ...(in): variable list of arguments (just like fprintf)
 *
 * Note: The error message associated with the given error identifier
 *       is parsed and the arguments are substituted for later
 *       retrieval. The caller must supply the exact number of
 *       arguments to set all level messages of the error. The error
 *       logging function (if any) associated with the error is called.
 */
void
er_set (int severity, const char *file_name, const int line_no, int err_id, int num_args, ...)
{
  va_list ap;

  va_start (ap, num_args);
  (void) er_set_internal (severity, file_name, line_no, err_id, num_args, false, NULL, &ap);
  va_end (ap);
}

/*
 * er_set_with_file - Set an error and print file contents into
 *                    the error log file
 *   return: none
 *   severity(in): may exit if severity == ER_FATAL_ERROR_SEVERITY
 *   file_name(in): file name setting the error
 *   line_no(in): line in file where the error is set
 *   err_id(in): the error identifier
 *   fp(in): file pointer
 *   num_args(in): number of arguments...
 *   ...(in): variable list of arguments (just like fprintf)
 *
 * Note: The error message associated with the given error identifier
 *       is parsed and the arguments are substituted for later
 *       retrieval. The caller must supply the exact number of
 *       arguments to set all level messages of the error. The error
 *       logging function (if any) associated with the error is called.
 */
void
er_set_with_file (int severity, const char *file_name, const int line_no, int err_id, FILE * fp, int num_args, ...)
{
  va_list ap;

  va_start (ap, num_args);
  (void) er_set_internal (severity, file_name, line_no, err_id, num_args, false, fp, &ap);
  va_end (ap);
}

/*
 * er_set_with_oserror - Set an error and include the OS last error
 *   return: none
 *   severity(in): may exit if severity == ER_FATAL_ERROR_SEVERITY
 *   file_name(in): file name setting the error
 *   line_no(in): line in file where the error is set
 *   err_id(in): the error identifier
 *   num_args(in): number of arguments...
 *   ...(in): variable list of arguments (just like fprintf)
 *
 * Note: This function is the same as er_set + append Unix error message.
 *       The error message associated with the given error identifier
 *       is parsed and the arguments are substituted for later
 *       retrieval. In addition the latest OS error message is appended
 *       in all level messages for the error. The caller must supply
 *       the exact number of arguments to set all level messages of the
 *       error. The log error message function associated with the
 *       error is called.
 */
void
er_set_with_oserror (int severity, const char *file_name, const int line_no, int err_id, int num_args, ...)
{
  va_list ap;

  va_start (ap, num_args);
  (void) er_set_internal (severity, file_name, line_no, err_id, num_args, true, NULL, &ap);
  va_end (ap);
}

/*
 * er_notify_event_on_error - notify event
 *   return: none
 *   err_id(in):
 */
static void
er_notify_event_on_error (int err_id)
{
  assert (err_id != NO_ERROR);

  if (sysprm_find_err_in_integer_list (PRM_ID_EVENT_ACTIVATION, err_id))
    {
      er_event ();
    }
}

/*
 * er_print_callstack () - Print message with callstack (should help for
 *             debug).
 *
 * return :
 * const char * file_name (in) :
 * const int line_no (in) :
 * const char * fmt (in) :
 * ... (in) :
 */
void
er_print_callstack (const char *file_name, const int line_no, const char *fmt, ...)
{
  va_list ap;

  // *INDENT-OFF*
  // protect log file mutex
  std::unique_lock<std::mutex> log_file_lock (er_Log_file_mutex); // mutex is released on destructor
  // *INDENT-ON*

  va_start (ap, fmt);
  _er_log_debug_internal (file_name, line_no, fmt, &ap);
  va_end (ap);

  FILE *out = (er_Msglog_fh != NULL) ? er_Msglog_fh : stderr;
  er_dump_call_stack (out);
  fprintf (out, "\n");
}

/*
 * er_call_stack_dump_on_error - call stack dump
 *   return: none
 *   severity(in):
 *   err_id(in):
 */
static void
er_call_stack_dump_on_error (int severity, int err_id)
{
  assert (err_id != NO_ERROR);

  if (severity == ER_FATAL_ERROR_SEVERITY)
    {
      er_dump_call_stack (er_Msglog_fh);
    }
  else if (prm_get_bool_value (PRM_ID_CALL_STACK_DUMP_ON_ERROR))
    {
      if (!sysprm_find_err_in_integer_list (PRM_ID_CALL_STACK_DUMP_DEACTIVATION, err_id))
    {
      er_dump_call_stack (er_Msglog_fh);
    }
    }
  else
    {
      if (sysprm_find_err_in_integer_list (PRM_ID_CALL_STACK_DUMP_ACTIVATION, err_id))
    {
      er_dump_call_stack (er_Msglog_fh);
    }
    }
}

/*
 * er_set_internal - Set an error and an optionally the Unix error
 *   return:
 *   severity(in): may exit if severity == ER_FATAL_ERROR_SEVERITY
 *   file_name(in): file name setting the error
 *   line_no(in): line in file where the error is set
 *   err_id(in): the error identifier
 *   num_args(in): number of arguments...
 *   fp(in): file pointer
 *   ...(in): variable list of arguments (just like fprintf)
 *
 * Note:
 */
static int
er_set_internal (int severity, const char *file_name, const int line_no, int err_id, int num_args,
         bool include_os_error, FILE * fp, va_list * ap_ptr)
{
  va_list ap;
  const char *os_error;
  std::size_t new_size;
  ER_FMT *er_fmt = NULL;
  int ret_val = NO_ERROR;
  bool need_stack_pop = false;

  /* check if not used error code */
  assert (err_id != ER_TP_INCOMPATIBLE_DOMAINS);
  assert (err_id != ER_NUM_OVERFLOW);
  assert (err_id != ER_QPROC_OVERFLOW_COERCION);
  assert (err_id != ER_FK_CANT_ASSIGN_CACHE_ATTR);
  assert (err_id != ER_FK_CANT_DROP_CACHE_ATTR);
  assert (err_id != ER_SM_CATALOG_SPACE);
  assert (err_id != ER_CT_UNKNOWN_CLASSID);
  assert (err_id != ER_CT_REPRCNT_OVERFLOW);

  if (er_Hasalready_initiated == false)
    {
#if defined (CS_MODE) && !defined (NDEBUG)
      /* temporary workaround for HA process which may encounter missing er_module */
      if (er_Ignore_uninit)
    {
      return ER_FAILED;
    }
#endif
      assert (false);
      er_Errid_not_initialized = err_id;
      return ER_FAILED;
    }

  // *INDENT-OFF*
  context &tl_context = context::get_thread_local_context ();
  // *INDENT-ON*

  /*
   * Get the UNIX error message if needed. We need to get this as soon
   * as possible to avoid resetting the error.
   */
  os_error = (include_os_error && errno != 0) ? strerror (errno) : NULL;

  memcpy (&ap, ap_ptr, sizeof (ap));

  // *INDENT-OFF*
  // should force error stacking? yes if:
  // 1. this is a notification and an error was already set
  // 2. current error is interrupted error.
  er_message &prev_err = tl_context.get_current_error_level ();
  // *INDENT-ON*

  if ((severity == ER_NOTIFICATION_SEVERITY && prev_err.err_id != NO_ERROR) || (prev_err.err_id == ER_INTERRUPTED))
    {
      tl_context.push_error_stack ();
      need_stack_pop = true;
    }

  // *INDENT-OFF*
  // get current error reference
  er_message &crt_error = tl_context.get_current_error_level ();
  // *INDENT-ON*

  /* Initialize the area... */
  crt_error.set_error (err_id, severity, file_name, line_no);

  /*
   * Get hold of the compiled format string for this message and get an
   * estimate of the size of the buffer required to print it.
   */
  er_fmt = er_find_fmt (err_id, num_args);
  if (er_fmt == NULL)
    {
      /*
       * Assumes that er_find_fmt() has already called er_emergency().
       */
      ret_val = ER_FAILED;
      goto end;
    }

  if (err_id >= ER_FAILED || err_id <= ER_LAST_ERROR)
    {
      assert (0);       /* invalid error id */
      err_id = ER_FAILED;   /* invalid error id handling */
    }

  new_size = er_estimate_size (er_fmt, ap_ptr);

  /* Do we need to copy the OS error message? */
  if (os_error)
    {
      new_size += 4 + strlen (os_error);
    }

  /* Do any necessary allocation for the buffer. */
  crt_error.reserve_message_area (new_size + 1);

  /* And now format the silly thing. */
  if (er_vsprintf (&crt_error, er_fmt, ap_ptr) == ER_FAILED)
    {
      ret_val = ER_FAILED;
      goto end;
    }
  if (os_error != NULL)
    {
      strcat (crt_error.msg_area, "... ");
      strcat (crt_error.msg_area, os_error);
    }

  /* Call the logging function if any */
  if (severity <= prm_get_integer_value (PRM_ID_ER_LOG_LEVEL)
      && !(prm_get_bool_value (PRM_ID_ER_LOG_WARNING) == false && severity == ER_WARNING_SEVERITY)
      && er_Fnlog[severity] != NULL)
    {
      // *INDENT-OFF*
      // protect log file mutex
      std::unique_lock<std::mutex> log_file_lock (er_Log_file_mutex); // mutex is released on destructor
      // *INDENT-ON*
      (*er_Fnlog[severity]) (err_id);

      /* call stack dump */
      er_call_stack_dump_on_error (severity, err_id);

      /* event handler */
      er_notify_event_on_error (err_id);

      if (fp != NULL)
    {
      /* print file contents */
      if (fseek (fp, 0L, SEEK_SET) == 0)
        {
          char buf[MAX_LINE];
          while (fgets (buf, MAX_LINE, fp) != NULL)
        {
          fprintf (er_Msglog_fh, "%s", buf);
        }
          (void) fflush (er_Msglog_fh);
        }
    }

      log_file_lock.unlock ();

      if (er_Print_to_console && severity <= ER_ERROR_SEVERITY && crt_error.msg_area)
    {
      fprintf (stderr, "%s\n", crt_error.msg_area);
    }
    }

  /*
   * Do we want to stop the system on this error ... for debugging purposes?
   */
  if (prm_get_integer_value (PRM_ID_ER_STOP_ON_ERROR) == err_id)
    {
      er_stop_on_error ();
    }

  if (severity == ER_NOTIFICATION_SEVERITY)
    {
      crt_error.clear_error ();
    }

end:

  if (need_stack_pop)
    {
      tl_context.pop_error_stack_and_destroy ();
    }

  return ret_val;
}

/*
 * er_stop_on_error - Stop the system when an error occurs
 *   return: none
 *
 * Note: This feature can be used when debugging a particular error outside the debugger. The user is asked whether or
 *       not to continue.
 */
static void
er_stop_on_error (void)
{
  int exit_requested;

#if !defined (WINDOWS)
  syslog (LOG_ALERT, er_Cached_msg[ER_STOP_SYSLOG], rel_name (), er_errid (), cuserid (NULL), getpid ());
#endif /* !WINDOWS */

  (void) fprintf (stderr, "%s", er_Cached_msg[ER_ER_ASK]);
  if (scanf ("%d", &exit_requested) != 1)
    {
      exit_requested = TRUE;
    }

  if (exit_requested)
    {
      exit (EXIT_FAILURE);
    }
}

/*
 * er_log - Log the error message
 *   return: none
 *
 * Note: The maximum available level of the error is logged into file.
 *       This function is used at the default logging
 *       function to call when error are set. The calling logging
 *       function can be modified by calling the function er_fnerlog.
 */
void
er_log (int err_id)
{
  int severity;
  int nlevels;
  int line_no;
  const char *file_name;
  const char *msg;
  time_t er_time;
  struct tm er_tm;
  struct tm *er_tm_p = &er_tm;
  struct timeval tv;
  char time_array[256];
  int tran_index;
  char *more_info_p;
  int ret;
  char more_info[CUB_MAXHOSTNAMELEN + PATH_MAX + 64];
  const char *log_file_name;
  const char *log_file_suffix;
  FILE **log_fh;

  if (er_Accesslog_filename != NULL && err_id == ER_BO_CLIENT_CONNECTED)
    {
      log_file_name = er_Accesslog_filename;
      log_file_suffix = ER_ACCESS_LOG_FILE_SUFFIX;
      log_fh = &er_Accesslog_fh;
    }
  else
    {
      log_file_name = er_Msglog_filename;
      log_file_suffix = ER_MSG_LOG_FILE_SUFFIX;
      log_fh = &er_Msglog_fh;
    }

  /* Check for an invalid output file */
  /* (stderr is not valid under Windows and is set to NULL) */
  if (log_fh == NULL || *log_fh == NULL)
    {
      return;
    }

  /* Make sure that we have a valid error identifier */

  /* Get the most detailed error message available */
  er_all (&err_id, &severity, &nlevels, &line_no, &file_name, &msg);

  /*
   * Don't let the file of log messages get very long. Backup or go back to the top if need be.
   */
  if (*log_fh != stderr && *log_fh != stdout && ftell (*log_fh) > (int) prm_get_integer_value (PRM_ID_ER_LOG_SIZE))
    {
      (void) fflush (*log_fh);
      (void) fprintf (*log_fh, "%s", er_Cached_msg[ER_LOG_WRAPAROUND]);

      if (!er_Isa_null_device)
    {
      *log_fh = er_file_backup (*log_fh, log_file_name);

      if (*log_fh == NULL)
        {
          *log_fh = stderr;
          er_log_debug (ARG_FILE_LINE, er_Cached_msg[ER_LOG_MSGLOG_WARNING], log_file_name);
        }
#if !defined (WINDOWS) && defined (SERVER_MODE)
      else
        {
          er_file_create_link_to_current_log_file (log_file_name, log_file_suffix);
        }
#endif /* !WINDOWS && SERVER_MODE */
    }
      else
    {
      /* Should be rewinded to avoid repeated limit check hitting */
      (void) fseek (*log_fh, 0L, SEEK_SET);
    }
    }

  if (*log_fh == stderr || *log_fh == stdout)
    {
      /*
       * Try to avoid out of sequence stderr & stdout.
       *
       */
      (void) fflush (stderr);
      (void) fflush (stdout);
    }

  /* LOG THE MESSAGE */

  er_time = time (NULL);
  er_tm_p = localtime_r (&er_time, &er_tm);
  if (er_tm_p == NULL)
    {
      strcpy (time_array, "00/00/00 00:00:00.000");
    }
  else
    {
      gettimeofday (&tv, NULL);

      size_t bufsize = sizeof (time_array);
      size_t len = strftime (time_array, bufsize, "%m/%d/%y %H:%M:%S", er_tm_p);

      if (len > 0 && len < bufsize)
    {
      // space left includes the null terminator, snprintf will overwrite it safely
      int remaining = (int) (bufsize - len);
      snprintf (time_array + len, remaining, ".%03ld", tv.tv_usec / 1000);
    }
    }

  more_info_p = (char *) "";

  if (++er_Eid == 0)
    {
      er_Eid = 1;
    }

#if defined (SERVER_MODE)
  {
    const char *prog_name = NULL;
    const char *user_name = NULL;
    const char *host_name = NULL;
    int pid = 0;

    if (logtb_find_client_tran_name_host_pid (tran_index, &prog_name, &user_name, &host_name, &pid) == NO_ERROR)
      {
    ret = snprintf (more_info, sizeof (more_info), ", CLIENT = %s:%s(%d), EID = %u",
            host_name ? host_name : "unknown", prog_name ? prog_name : "unknown", pid, er_Eid);
    if (ret > 0)
      {
        more_info_p = &more_info[0];
      }
      }
  }
#else /* SERVER_MODE */
  tran_index = TM_TRAN_INDEX ();
  ret = snprintf (more_info, sizeof (more_info), ", EID = %u", er_Eid);
  if (ret > 0)
    {
      more_info_p = &more_info[0];
    }

  if (er_Handler != NULL)
    {
      (*er_Handler) (er_Eid);
    }
#endif /* !SERVER_MODE */

  /* If file is not exist, it will recreate *log_fh file. */
  if ((access (log_file_name, F_OK) == -1) && *log_fh != stderr && *log_fh != stdout)
    {
      (void) fclose (*log_fh);
      *log_fh = er_file_open (log_file_name);

      if (*log_fh == NULL)
    {
      *log_fh = stderr;
      er_log_debug (ARG_FILE_LINE, er_Cached_msg[ER_LOG_MSGLOG_WARNING], log_file_name);
    }
    }

  fprintf (*log_fh, er_Cached_msg[ER_LOG_MSG_WRAPPER_D], time_array, ER_SEVERITY_STRING (severity), file_name, line_no,
       ER_ERROR_WARNING_STRING (severity), err_id, tran_index, more_info_p, msg);

  /* Flush the message so it is printed immediately */
  if (*log_fh != stderr && *log_fh != stdout)
    {
      int wsz = fprintf (*log_fh, "%s", er_Cached_msg[ER_LOG_LAST_MSG]);
      (void) fflush (*log_fh);
      (void) fseek (*log_fh, (-wsz), SEEK_CUR);
    }
  else
    {
      (void) fflush (*log_fh);
    }

  /* Do we want to exit ? */

  if (severity == ER_FATAL_ERROR_SEVERITY)
    {
      switch (er_Exit_ask)
    {
    case ER_ABORT:
      abort ();
      break;

    case ER_EXIT_ASK:
#if defined (NDEBUG)
      er_stop_on_error ();
      break;
#endif /* NDEBUG */

    case ER_EXIT_DONT_ASK:
      (void) fprintf (er_Msglog_fh, "%s", er_Cached_msg[ER_ER_EXIT]);
      (void) fflush (er_Msglog_fh);
      er_final (ER_ALL_FINAL);
      exit (EXIT_FAILURE);
      break;

    case ER_NEVER_EXIT:
    default:
      break;
    }
    }
}

er_log_handler_t
er_register_log_handler (er_log_handler_t handler)
{
#if !defined (SERVER_MODE)
  er_log_handler_t prev;

  prev = er_Handler;
  er_Handler = handler;
  return prev;
#else
  assert (0);

  return NULL;
#endif
}

/*
 * er_errid - Retrieve last error identifier set before
 *   return: error identifier
 *
 * Note: In most cases, it is simply enough to know whether or not
 *       there was an error. However, in some cases it is convenient to
 *       design the system and application to anticipate and handle
 *       some errors.
 */
int
er_errid (void)
{
  if (!er_Hasalready_initiated)
    {
#if defined (CS_MODE) && !defined (NDEBUG)
      /* temporary workaround for HA process which may encounter missing er_module */
      if (er_Ignore_uninit)
    {
      return er_Errid_not_initialized;
    }
#endif
      assert (false);
      return er_Errid_not_initialized;
    }

  return context::get_thread_local_error ().err_id;
}

/*
 * er_errid_if_has_error - Retrieve last error identifier set before
 *   return: error identifier
 *
 * Note: The function ignores an error with ER_WARNING_SEVERITY or
 *       ER_NOTIFICATION_SEVERITY.
 */
int
er_errid_if_has_error (void)
{
  // *INDENT-OFF*
  er_message &crt_error = context::get_thread_local_error ();
  // *INDENT-ON*

  return er_is_error_severity ((er_severity) crt_error.severity) ? crt_error.err_id : NO_ERROR;
}

/*
 * er_clearid - Clear only error identifier
 *   return: none
 */
void
er_clearid (void)
{
  // todo: is this necessary?
#if defined (CS_MODE) && !defined (NDEBUG)
  /* temporary workaround for HA process which may encounter missing er_module */
  if (!er_Hasalready_initiated && er_Ignore_uninit)
    {
      return;
    }
#endif
  assert (er_Hasalready_initiated);

  context::get_thread_local_error ().err_id = NO_ERROR;
}

/*
 * er_setid - Set only an error identifier
 *   return: none
 *   err_id(in):
 */
void
er_setid (int err_id)
{
  // todo: is this necessary?
#if defined (CS_MODE) && !defined (NDEBUG)
  /* temporary workaround for HA process which may encounter missing er_module */
  if (!er_Hasalready_initiated && er_Ignore_uninit)
    {
      return;
    }
#endif
  assert (er_Hasalready_initiated);

  context::get_thread_local_error ().err_id = err_id;
}

/*
 * er_get_severity - Get severity of the last error set before
 *   return: severity
 */
int
er_get_severity (void)
{
  return context::get_thread_local_error ().severity;
}

/*
 * er_has_error -
 *   return: true if it has an actual error, otherwise false.
 *   note: NOTIFICATION and WARNING are not regarded as an actual error.
 */
bool
er_has_error (void)
{
  return er_is_error_severity ((er_severity) context::get_thread_local_error ().severity);
}

/*
 * er_msg - Retrieve current error message
 *   return: a string, at the given level, describing the last error
 *
 * Note: The string returned is overwritten when the next error is set,
 *       so it may be necessary to copy it to a static area if you
 *       need to keep it for some length of time.
 */
const char *
er_msg (void)
{
#if defined (CS_MODE) && !defined (NDEBUG)
  /* temporary workaround for HA process which may encounter missing er_module */
  if (!er_Hasalready_initiated && er_Ignore_uninit)
    {
      return "Not available";
    }
#endif

  if (!er_Hasalready_initiated)
    {
      assert (false);
      return "Not available";   // todo: is this safe?
    }

  // *INDENT-OFF*
  er_message &crt_error = context::get_thread_local_error ();
  // *INDENT-ON*

  if (crt_error.msg_area[0] == '\0')
    {
      std::strncpy (crt_error.msg_area, er_Cached_msg[ER_ER_MISSING_MSG], crt_error.msg_area_size);
      crt_error.msg_area[crt_error.msg_area_size - 1] = '\0';
    }

  return crt_error.msg_area;
}

/*
 * er_all - Return everything about the last error
 *   return: none
 *   err_id(out): error identifier
 *   severity(out): severity of the error
 *   n_levels(out): number of levels of the error
 *   line_no(out): line number in the file where the error was set
 *   file_name(out): file name where the error was set
 *   error_msg(out): the formatted message of the error
 *
 * Note:
 */
void
er_all (int *err_id, int *severity, int *n_levels, int *line_no, const char **file_name, const char **error_msg)
{
  // *INDENT-OFF*
  er_message &crt_error = context::get_thread_local_error ();
  // *INDENT-ON*

  *err_id = crt_error.err_id;
  *severity = crt_error.severity;
  *n_levels = 1;        // is this stack level?
  *line_no = crt_error.line_no;
  *file_name = crt_error.file_name;
  *error_msg = er_msg ();
}

/*
 * _er_log_debug () - Print message to error log file.
 *
 * return     : Void.
 * file_name (in) : __FILE__
 * line_no (in)   : __LINE__
 * fmt (in)   : Formatted message.
 * ... (in)   : Arguments for formatted message.
 */
void
_er_log_debug (const char *file_name, const int line_no, const char *fmt, ...)
{
  va_list ap;

#if defined (CS_MODE) && !defined (NDEBUG)
  /* temporary workaround for HA process which may encounter missing er_module */
  if (!er_Hasalready_initiated && er_Ignore_uninit)
    {
      return;
    }
#endif

  assert (er_Hasalready_initiated);

  // *INDENT-OFF*
  // protect log file mutex
  std::unique_lock<std::mutex> log_file_lock (er_Log_file_mutex); // mutex is released on destructor
  // *INDENT-ON*

  va_start (ap, fmt);
  _er_log_debug_internal (file_name, line_no, fmt, &ap);
  va_end (ap);
}

/*
 * er_log_debug - Print debugging message to the log file
 *   return: none
 *   file_name(in):
 *   line_no(in):
 *   fmt(in):
 *   ap(in):
 *
 * Note:
 */
static void
_er_log_debug_internal (const char *file_name, const int line_no, const char *fmt, va_list * ap)
{
  FILE *out;
  time_t er_time;
  struct tm er_tm;
  struct tm *er_tm_p = &er_tm;
  struct timeval tv;
  char time_array[256];

  if (er_Hasalready_initiated == false)
    {
      /* do not print debug info */
      return;
    }

  out = (er_Msglog_fh != NULL) ? er_Msglog_fh : stderr;

  er_time = time (NULL);

  er_tm_p = localtime_r (&er_time, &er_tm);
  if (er_tm_p == NULL)
    {
      strcpy (time_array, "00/00/00 00:00:00.000");
    }
  else
    {
      gettimeofday (&tv, NULL);

      size_t bufsize = sizeof (time_array);
      size_t len = strftime (time_array, bufsize, "%m/%d/%y %H:%M:%S", er_tm_p);

      if (len > 0 && len < bufsize)
    {
      // space left includes the null terminator, snprintf will overwrite it safely
      int remaining = (int) (bufsize - len);
      snprintf (time_array + len, remaining, ".%03ld", tv.tv_usec / 1000);
    }
    }

  fprintf (out, er_Cached_msg[ER_LOG_DEBUG_NOTIFY], time_array, file_name, line_no);

  /* Print out remainder of message */
  vfprintf (out, fmt, *ap);
  fflush (out);
}

/*
 * er_get_ermsg_from_area_error -
 *   return: ermsg string from the flatten error area
 *   buffer(in): flatten error area
 */
char *
er_get_ermsg_from_area_error (char *buffer)
{
  assert (buffer != NULL);

  return buffer + (OR_INT_SIZE * 3);
}

/*
 * er_get_area_error - Flatten error information into an area
 *   return: packed error information that can be transmitted to the client
 *   length(out): length of the flatten area (set as a side effect)
 *
 * Note: This function is used for Client/Server transfering of errors.
 */
char *
er_get_area_error (char *buffer, int *length)
{
  std::size_t len, max_msglen;
  char *ptr;
  const char *msg;

  assert (*length > OR_INT_SIZE * 3);

  // *INDENT-OFF*
  er_message &crt_error = context::get_thread_local_error ();
  // *INDENT-ON*

  /* Now copy the information */
  msg = strlen (crt_error.msg_area) != 0 ? crt_error.msg_area : "(null)";

  len = (OR_INT_SIZE * 3) + strlen (msg) + 1;
  len = MIN (len, *length);
  *length = (int) len;
  max_msglen = len - (OR_INT_SIZE * 3) - 1;

  ptr = buffer;
  ASSERT_ALIGN (ptr, INT_ALIGNMENT);

  OR_PUT_INT (ptr, (int) crt_error.err_id);
  ptr += OR_INT_SIZE;

  OR_PUT_INT (ptr, (int) crt_error.severity);
  ptr += OR_INT_SIZE;

  OR_PUT_INT (ptr, len);
  ptr += OR_INT_SIZE;

  strncpy (ptr, msg, max_msglen);
  *(ptr + max_msglen) = '\0';   /* bullet proofing */

  return buffer;
}

/*
 * er_set_area_error - Reset the error information
 *   return: error id which was contained in the given error information
 *   server_area(in): the flatten area with error information
 *
 * Note: Error information is reset with the one provided by the packed area,
 *       which is the last error found in the server.
 */
int
er_set_area_error (char *server_area)
{
  char *ptr;
  int err_id, severity;
  std::size_t length = 0;

  if (server_area == NULL)
    {
      er_clear ();
      return NO_ERROR;
    }

  // *INDENT-OFF*
  er_message &crt_error = context::get_thread_local_error ();
  // *INDENT-ON*

  ptr = server_area;
  ASSERT_ALIGN (ptr, INT_ALIGNMENT);

  err_id = OR_GET_INT (ptr);
  ptr += OR_INT_SIZE;
  assert (err_id <= NO_ERROR && ER_LAST_ERROR < err_id);

  severity = OR_GET_INT (ptr);
  ptr += OR_INT_SIZE;
  assert (ER_FATAL_ERROR_SEVERITY <= severity && severity <= ER_MAX_SEVERITY);

  length = OR_GET_INT (ptr);
  ptr += OR_INT_SIZE;

  crt_error.err_id = ((err_id >= 0 || err_id <= ER_LAST_ERROR) ? -1 : err_id);
  crt_error.severity = severity;
  crt_error.file_name = "Unknown from server";
  crt_error.line_no = -1;

  /* Note, current length is the length of the packet not the string, considering that this is NULL terminated, we
   * don't really need to be sending this. Use the actual string length in the memcpy here! */
  length = strlen (ptr) + 1;

  crt_error.reserve_message_area (length);
  memcpy (crt_error.msg_area, ptr, length);

  /* Call the logging function if any */
  if (severity <= prm_get_integer_value (PRM_ID_ER_LOG_LEVEL)
      && !(prm_get_bool_value (PRM_ID_ER_LOG_WARNING) == false && severity == ER_WARNING_SEVERITY)
      && er_Fnlog[severity] != NULL)
    {
      // *INDENT-OFF*
      // protect log file mutex
      std::unique_lock<std::mutex> log_file_lock (er_Log_file_mutex); // mutex is released on destructor
      // *INDENT-ON*

      (*er_Fnlog[severity]) (err_id);
      log_file_lock.unlock ();

      if (er_Print_to_console && severity <= ER_ERROR_SEVERITY && crt_error.msg_area)
    {
      fprintf (stderr, "%s\n", crt_error.msg_area);
    }
    }

  return crt_error.err_id;
}

/*
 * er_stack_push - Save the current error onto the stack
 *   return: NO_ERROR or ER_FAILED
 *
 * Note: The current set error information is saved onto a stack.
 *       This function can be used in conjunction with er_stack_pop when
 *       the caller function wants to return the current error message
 *       no matter what other additional errors are set. For example,
 *       a function may detect an error, then call another function to
 *       do some cleanup. If the cleanup function set an error, the
 *       desired error can be lost.
 *       A function that push something should pop or clear the entry,
 *       otherwise, a function doing a pop may not get the right entry.
 */
void
er_stack_push (void)
{
  (void) context::get_thread_local_context ().push_error_stack ();
}

/*
 * er_stack_push_if_exists - Save the last error if exists onto the stack
 *
 * Note: Please notice the difference from er_stack_push.
 *       This function only pushes when an error was set, while er_stack_push always makes a room
 *       and pushes the current entry. It will be used in conjunction with er_restore_last_error.
 */
void
er_stack_push_if_exists (void)
{
  // *INDENT-OFF*
  context &tl_context = context::get_thread_local_context ();
  // *INDENT-ON*

  if (tl_context.get_current_error_level ().err_id == NO_ERROR && !tl_context.has_error_stack ())
    {
      /* If neither an error was set nor pushed, keep using the current error entry.
       *
       * If this is not a base error entry,
       * we have to push one since it means that caller pushed one and latest entry will be soon popped.
       */
      return;
    }

  // push
  (void) tl_context.push_error_stack ();
}

/*
 * er_stack_pop - Restore the previous error from the stack.
 *                The latest saved error is restored in the error area.
 *   return: NO_ERROR or ER_FAILED
 */
void
er_stack_pop (void)
{
  context::get_thread_local_context ().pop_error_stack_and_destroy ();
}

/*
 * er_stack_pop_and_keep_error - Clear the latest saved error message in the stack
 *                  That is, pop without restore.
 *   return: none
 */
void
er_stack_pop_and_keep_error (void)
{
  // *INDENT-OFF*
  context &tl_context = context::get_thread_local_context ();
  // *INDENT-ON*
  er_message top (tl_context.get_logging ());

  if (!tl_context.has_error_stack ())
    {
      // bad pop
      assert (false);
      return;
    }

  tl_context.pop_error_stack (top);
  if (top.err_id != NO_ERROR)
    {
      /* keep the error. push it to current top */
      top.swap (tl_context.get_thread_local_error ());
    }
  else
    {
      // leave as is
    }

  // popped error stack is destroyed
}

/*
 * er_restore_last_error - Restore the last error between the current entry and the pushed one.
 *                         If the current entry has an error, clear the pushed entry which is no longer needed.
 *                         Otherwise, pop the current entry and restore the saved one.
 *
 *   return: none
 *
 * Note: Please see also er_stack_push_if_exists
 */
void
er_restore_last_error (void)
{
  // *INDENT-OFF*
  context &tl_context = context::get_thread_local_context ();
  // *INDENT-ON*

  if (!tl_context.has_error_stack ())
    {
      /* When no pushed entry exists, keep using the current entry. */
      return;
    }

  er_stack_pop_and_keep_error ();
}

/*
 * er_stack_clearall - Clear all saved error messages
 *   return: none
 */
void
er_stack_clearall (void)
{
  // *INDENT-OFF*
  context &tl_context = context::get_thread_local_context ();
  // *INDENT-ON*

  // remove all stacks, but keep last error
  while (tl_context.has_error_stack ())
    {
      er_stack_pop_and_keep_error ();
    }
}


/*
 * er_study_spec -
 *   return: the length of the spec
 *   conversion_spec(in): a single printf() conversion spec, without the '%'
 *   simple_spec(out): a pointer to a buffer to receive a simple version of
 *                     the spec (one without a positional specifier)
 *   position(out): the position of the spec
 *   width(out): the nominal width of the field
 *   va_class(out): a classification of the base (va_list)
 *                  type of the arguments described by the spec
 *
 * Note: Breaks apart the individual components of the conversion spec
 *   (as described in the Sun man page) and sets the appropriate
 *   buffers to record that info.
 */
static int
er_study_spec (const char *conversion_spec, char *simple_spec, int *position, int *width, int *va_class)
{
  char *p;
  const char *q;
  int n, code, class_;

  code = 0;
  class_ = 0;

  simple_spec[0] = '%';
  p = &simple_spec[1];
  q = conversion_spec;

  /*
   * Skip leading flags...
   */

  while (*q == '-' || *q == '+' || *q == ' ' || *q == '#')
    {
      *p++ = *q++;
    }

  /*
   * Now look for a numeric field.  This could be either a position
   * specifier or a width specifier; we won't know until we find out
   * what follows it.
   */

  n = 0;
  while (char_isdigit (*q))
    {
      n *= 10;
      n += (*q) - '0';
      *p++ = *q++;
    }

  if (*q == '$')
    {
      /*
       * The number was a position specifier, so record that, skip the
       * '$', and start over depositing conversion spec characters at
       * the beginning of simple_spec.
       */
      q++;

      if (n)
    {
      *position = n;
    }
      p = &simple_spec[1];

      /*
       * Look for flags again...
       */
      while (*q == '-' || *q == '+' || *q == ' ' || *q == '#')
    {
      *p++ = *q++;
    }

      /*
       * And then look for a width specifier...
       */
      n = 0;
      while (char_isdigit (*q))
    {
      n *= 10;
      n += (*q) - '0';
      *p++ = *q++;
    }
      *width = n;
    }

  /*
   * Look for an optional precision...
   */
  if (*q == '.')
    {
      *p++ = *q++;
      while (char_isdigit (*q))
    {
      *p++ = *q++;
    }
    }

  /*
   * And then for modifier flags...
   */
  if (*q == 'l' && *(q + 1) == 'l')
    {
      /* long long type */
      class_ = SPEC_CODE_LONGLONG;
      *p++ = *q++;
      *p++ = *q++;
    }
  else if (*q == 'z')
    {
      /* size_t type */
      class_ = SPEC_CODE_SIZE_T;
#if defined (WINDOWS)
      *p++ = 'I';
      q++;
#elif defined (__GNUC__)
      *p++ = *q++;
#else
      if (sizeof (size_t) == sizeof (long long int))
    {
      *p++ = 'l';
      *p++ = 'l';
    }
      else
    {
      /* no size modifier */
    }
      q++;
#endif /* WINDOWS */
    }
  else if (*q == 'h')
    {
      /*
       * Ignore this spec and use the class determined (later) by
       * examining the coversion code.  According to Plauger, the
       * standard dictates that stdarg.h be implemented so that short
       * values are all coerced to int.
       */
      *p++ = *q++;
    }

  /*
   * Now copy the actual conversion code.
   */
  code = *p++ = *q++;
  *p++ = '\0';

  if (class_ == 0)
    {
      switch (code)
    {
    case 'c':
    case 'd':
    case 'i':
    case 'o':
    case 'u':
    case 'x':
    case 'X':
      class_ = 'i';
      break;
    case 'p':
      class_ = 'p';
      break;
    case 'e':
    case 'f':
    case 'g':
    case 'E':
    case 'F':
    case 'G':
      class_ = 'f';
      break;
    case 's':
      class_ = 's';
      break;
    default:
      assert (0);
      er_log_debug (ARG_FILE_LINE, er_Cached_msg[ER_LOG_UNKNOWN_CODE], code);
      break;
    }
    }
  *va_class = class_;

  return CAST_STRLEN (q - conversion_spec);
}

/*
 * er_study_fmt -
 *   return:
 *   fmt(in/out): a pointer to an ER_FMT structure to be initialized
 *
 * Note: Scans the printf format string in fmt->fmt and compiles
 *   interesting information about the conversion specs in the
 *   string into the spec[] array.
 */
static void
er_study_fmt (ER_FMT * fmt)
{
  const char *p;
  int width, va_class;
  char buf[10];

  int i, n;

  fmt->nspecs = 0;
  for (p = strchr (fmt->fmt, '%'); p; p = strchr (p, '%'))
    {
      if (p[1] == '%')
    {           /* " ...%%..." ??? */
      p += 1;
    }
      else
    {
      /*
       * Set up the position parameter off by one so that we can
       * decrement it without checking later.
       */
      n = ++fmt->nspecs;
      width = 0;
      va_class = 0;

      p += er_study_spec (&p[1], buf, &n, &width, &va_class);

      /*
       * 'n' may have been modified by er_study_spec() if we ran
       * into a conversion spec with a positional component (e.g.,
       * %3$d).
       */
      n -= 1;

      if (n >= fmt->spec_top)
        {
          ER_SPEC *new_spec;
          int size;

          /*
           * Grow the conversion spec array.
           */
          size = (n + 1) * sizeof (ER_SPEC);
          new_spec = (ER_SPEC *) ER_MALLOC (size);
          if (new_spec == NULL)
        return;
          memcpy (new_spec, fmt->spec, fmt->spec_top * sizeof (ER_SPEC));
          if (fmt->spec != fmt->spec_buf)
        free_and_init (fmt->spec);
          fmt->spec = new_spec;
          fmt->spec_top = (n + 1);
        }

      strcpy (fmt->spec[n].spec, buf);
      fmt->spec[n].code = va_class;
      fmt->spec[n].width = width;
    }
    }

  /*
   * Make sure that there were no "holes" left in the parameter space
   * (e.g., "%1$d" and "%3$d", but no "%2$d" spec), and that there were
   * no unknown conversion codes.  If there was a problem, we can't
   * count on being able to safely decode the va_list we'll get, and
   * we're better off just printing a generic message that requires no
   * formatting.
   */
  for (i = 0; i < fmt->nspecs; i++)
    {
      if (fmt->spec[i].code == 0)
    {
      int code;
      code = fmt->err_id;
      er_log_debug (ARG_FILE_LINE, er_Cached_msg[ER_LOG_SUSPECT_FMT], code);
      er_internal_msg (fmt, code, ER_ER_SUBSTITUTE_MSG);
      break;
    }
    }
}

#define MAX_INT_WIDTH       20
#define MAX_DOUBLE_WIDTH    32
/*
 * er_estimate_size -
 *   return: a byte count
 *   fmt(in/out): a pointer to an already-studied ER_FMT structure
 *   ap(in): a va_list of arguments
 *
 * Note:
 * Uses the arg_spec[] info in *fmt, along with the actual args
 * in ap, to make a conservative guess of how many bytes will be
 * required by vsprintf().
 *
 * If fmt hasn't already been studied by er_study_fmt(), this
 * will yield total garbage, if it doesn't blow up.
 *
 * DOESN'T AFFECT THE CALLER'S VIEW OF THE VA_LIST.
 */
static size_t
er_estimate_size (ER_FMT * fmt, va_list * ap)
{
  int i, width;
  size_t n;
  size_t len;
  va_list args;
  const char *str;

  /*
   * fmt->fmt can be NULL if something went wrong while studying it.
   */
  if (fmt->fmt == NULL)
    {
      return strlen (er_Cached_msg[ER_ER_SUBSTITUTE_MSG]);
    }

  memcpy (&args, ap, sizeof (args));

  len = fmt->fmt_length;

  for (i = 0; i < fmt->nspecs; i++)
    {
      switch (fmt->spec[i].code)
    {
    case 'i':
      (void) va_arg (args, int);
      n = MAX_INT_WIDTH;
      break;

    case SPEC_CODE_LONGLONG:
      (void) va_arg (args, long long int);
      n = MAX_INT_WIDTH;
      break;

    case SPEC_CODE_SIZE_T:
      if (sizeof (size_t) == sizeof (long long int))
        {
          (void) va_arg (args, long long int);
        }
      else
        {
          (void) va_arg (args, int);
        }
      n = MAX_INT_WIDTH;
      break;

    case 'p':
      (void) va_arg (args, void *);
      n = MAX_INT_WIDTH;
      break;

    case 'f':
      (void) va_arg (args, double);
      n = MAX_DOUBLE_WIDTH;
      break;

    case 'L':
      (void) va_arg (args, long double);
      n = MAX_DOUBLE_WIDTH;
      break;

    case 's':
      str = va_arg (args, char *);
      if (str == NULL)
        str = "(null)";
      n = strlen (str);
      break;

    default:
      er_log_debug (ARG_FILE_LINE, er_Cached_msg[ER_LOG_UNKNOWN_CODE], fmt->spec[i].code);
      /*
       * Pray for protection...  We really shouldn't be able to get
       * here, since er_study_fmt() should protect us from it.
       */
      n = MAX_DOUBLE_WIDTH;
      break;
    }
      width = fmt->spec[i].width;
      len += MAX (width, n);
    }

  return len;
}

/*
 * er_find_fmt -
 *   return: error formats
 *   err_id(in): error identifier
 *
 * Note: "er_cache.lock" should have been acquired before calling this function.
 *       And this thread should not release the mutex before return.
 */
static ER_FMT *
er_find_fmt (int err_id, int num_args)
{
  const char *msg;
  ER_FMT *fmt;

  // *INDENT-OFF*
  // protect log file mutex
  std::unique_lock<std::mutex> log_msg_cache (er_Message_cache_mutex, std::defer_lock); // mutex is released on
                                                                                        // destructor
  // *INDENT-ON*

  if (err_id < ER_FAILED && err_id > ER_LAST_ERROR)
    {
      fmt = &er_Fmt_list[-err_id];
    }
  else
    {
      assert (0);
      fmt = &er_Fmt_list[-ER_FAILED];
    }

  if (er_Fmt_msg_fail_count > 0)
    {
      log_msg_cache.lock ();
    }

  if (fmt->fmt == NULL)
    {
      assert (er_Fmt_msg_fail_count > 0);

      msg = msgcat_message (MSGCAT_CATALOG_CUBRID, MSGCAT_SET_ERROR, -err_id);
      if (msg == NULL || msg[0] == '\0')
    {
      er_log_debug (ARG_FILE_LINE, er_Cached_msg[ER_ER_LOG_MISSING_MSG], err_id);
      msg = er_Cached_msg[ER_ER_MISSING_MSG];
    }

      fmt = er_create_fmt_msg (fmt, err_id, msg);

      if (fmt != NULL)
    {
      /*
       * Be sure that we have the same number of arguments before calling
       * er_estimate_size().  Because it uses straight va_arg() and friends
       * to grab its arguments, it is vulnerable to argument mismatches
       * (e.g., too few arguments, ints in string positions, etc).  This
       * won't save us when someone passes an integer argument where the
       * format says to expect a string, but it will save us if someone
       * just plain forgets how many args there are.
       */
      if (fmt->nspecs != num_args)
        {
          er_log_debug (ARG_FILE_LINE, er_Cached_msg[ER_LOG_SUSPECT_FMT], err_id);
          er_internal_msg (fmt, err_id, ER_ER_SUBSTITUTE_MSG);
        }
    }
      er_Fmt_msg_fail_count--;
    }

  return fmt;
}

static ER_FMT *
er_create_fmt_msg (ER_FMT * fmt, int err_id, const char *msg)
{
  std::size_t msg_length;

  msg_length = strlen (msg);

  fmt->fmt = (char *) ER_MALLOC (msg_length + 1);
  if (fmt->fmt == NULL)
    {
      er_internal_msg (fmt, err_id, ER_ER_MISSING_MSG);
      return NULL;
    }

  fmt->fmt_length = (int) msg_length;
  fmt->must_free = 1;

  strcpy (fmt->fmt, msg);

  /*
   * Now study the format specs and squirrel away info about them.
   */
  fmt->err_id = err_id;
  er_study_fmt (fmt);

  return fmt;
}

/*
 * er_init_fmt -
 *   return: none
 *   fmt(in/out):
 */
static void
er_init_fmt (ER_FMT * fmt)
{
  fmt->err_id = 0;
  fmt->fmt = NULL;
  fmt->fmt_length = 0;
  fmt->must_free = 0;
  fmt->nspecs = 0;
  fmt->spec_top = DIM (fmt->spec_buf);
  fmt->spec = fmt->spec_buf;
}

/*
 * er_clear_fmt -
 *   return: none
 *   fmt(in/out):
 */
static void
er_clear_fmt (ER_FMT * fmt)
{
  if (fmt->fmt && fmt->must_free)
    {
      free_and_init (fmt->fmt);
    }
  fmt->fmt = NULL;
  fmt->fmt_length = 0;
  fmt->must_free = 0;

  if (fmt->spec && fmt->spec != fmt->spec_buf)
    {
      free_and_init (fmt->spec);
    }
  fmt->spec = fmt->spec_buf;
  fmt->spec_top = DIM (fmt->spec_buf);
  fmt->nspecs = 0;
}

/*
 * er_internal_msg -
 *   return:
 *   fmt(in/out):
 *   code(in):
 *   msg_num(in):
 */
static void
er_internal_msg (ER_FMT * fmt, int code, int msg_num)
{
  er_clear_fmt (fmt);

  fmt->err_id = code;
  fmt->fmt = (char *) er_Cached_msg[msg_num];
  fmt->fmt_length = (int) strlen (fmt->fmt);
  fmt->must_free = 0;
}

/*
 * er_malloc_helper -
 *   return:
 *   size(in):
 *   file(in):
 *   line(in):
 */
static void *
er_malloc_helper (std::size_t size, const char *file, int line)
{
  void *mem;

  mem = malloc (size);
  if (mem == NULL)
    {
      er_emergency (file, line, er_Cached_msg[ER_ER_OUT_OF_MEMORY], size);
    }

  return mem;
}

/*
 * er_emergency - Does a poor man's sprintf()
 *                which understands only '%s' and '%d'
 *   return: none
 *   file(in):
 *   line(in):
 *   fmt(in):
 *   ...(in):
 *
 * Note: Do not malloc() any memory since this can be called
 *       from low-memory situations
 */
static void
er_emergency (const char *file, int line, const char *fmt, ...)
{
  va_list args;
  const char *str, *p, *q;
  int limit, span;
  char buf[32];

  // *INDENT-OFF*
  er_message &crt_error = context::get_thread_local_error ();
  // *INDENT-ON*

  crt_error.err_id = ER_GENERIC_ERROR;
  crt_error.severity = ER_ERROR_SEVERITY;
  crt_error.file_name = file;
  crt_error.line_no = line;

  /* it is assumed that default message buffer is big enough to hold this */
  sprintf (crt_error.msg_area, er_Cached_msg[ER_ER_HEADER], line);
  limit = (int) (crt_error.msg_area_size - strlen (crt_error.msg_area) - 1);

  va_start (args, fmt);

  p = fmt;
  crt_error.msg_area[0] = '\0';
  while ((q = strchr (p, '%')) && limit > 0)
    {
      /*
       * Copy the text between the last conversion spec and the next.
       */
      span = CAST_STRLEN (q - p);
      span = MIN (limit, span);
      strncat (crt_error.msg_area, p, span);
      p = q + 2;
      limit -= span;

      /*
       * Now convert and print the arg.
       */
      switch (q[1])
    {
    case 'd':
      sprintf (buf, "%d", va_arg (args, int));
      str = buf;
      break;
    case 'l':
      if (q[2] == 'd')
        {
          sprintf (buf, "%d", va_arg (args, int));
          str = buf;
        }
      else
        {
          str = "???";
        }
      break;
    case 's':
      str = va_arg (args, const char *);
      if (str == NULL)
        {
          str = "(null)";
        }
      break;
    case '%':
      str = "%";
      break;
    default:
      str = "???";
      break;
    }
      strncat (crt_error.msg_area, str, limit);
      limit -= (int) strlen (str);
      limit = MAX (limit, 0);
    }

  va_end (args);

  /*
   * Now copy the message text following the last conversion spec,
   * making sure that we null-terminate the buffer (since strncat won't
   * do it if it reaches the end of the buffer).
   */
  strncat (crt_error.msg_area, p, limit);
  crt_error.msg_area[crt_error.msg_area_size - 1] = '\0';

  /* Now get it into the log. */
  er_log (crt_error.err_id);
}

/*
 * er_vsprintf -
 *   return:
 *   fmt(in/out):
 *   ap(in):
 */
static int
er_vsprintf (er_message * er_entry_p, ER_FMT * fmt, va_list * ap)
{
  const char *p;        /* The start of the current non-spec part of fmt */
  const char *q;        /* The start of the next conversion spec */
  char *s;          /* The end of the valid part of er_entry_p->msg_area */
  int n;            /* The va_list position of the current arg */
  int i;
  va_list args;

  assert (er_entry_p != NULL);

  /*
   *                  *** WARNING ***
   *
   * This routine assumes that er_entry_p->msg_area is large enough to
   * receive the message being formatted.  If you haven't done your
   * homework with er_estimate_size() before calling this, you may be
   * in for a bruising.
   */

  /*
   * If there was trouble with the format for some reason, print out
   * something that seems a little reassuring.
   */
  if (fmt == NULL || fmt->fmt == NULL)
    {
      strncpy (er_entry_p->msg_area, er_Cached_msg[ER_ER_SUBSTITUTE_MSG], er_entry_p->msg_area_size);
      return ER_FAILED;
    }

  memcpy (&args, ap, sizeof (args));

  /*
   * Make room for the args that we are about to print.  These have to
   * be snatched from the va_list in the proper order and stored in an
   * array so that we can have random access to them in order to
   * support the %<num>$<code> notation in the message, that is, when
   * we're printing the format, we may not have the luxury of printing
   * the arguments in the same order that they appear in the va_list.
   */
  if (er_entry_p->nargs < fmt->nspecs)
    {
      int size;

      er_entry_p->clear_args ();

      size = fmt->nspecs * sizeof (er_va_arg);
      er_entry_p->args = (er_va_arg *) ER_MALLOC (size);
      if (er_entry_p->args == NULL)
    {
      return ER_FAILED;
    }
      er_entry_p->nargs = fmt->nspecs;
    }

  /*
   * Now grab the args and put them in er_msg->args.  The work that we
   * did earlier in er_study_fmt() tells us what the base type of each
   * va_list item is, and we use that info here.
   */
  for (i = 0; i < fmt->nspecs; i++)
    {
      switch (fmt->spec[i].code)
    {
    case 'i':
      er_entry_p->args[i].int_value = va_arg (args, int);
      break;
    case SPEC_CODE_LONGLONG:
      er_entry_p->args[i].longlong_value = va_arg (args, long long);
      break;
    case SPEC_CODE_SIZE_T:
      if (sizeof (size_t) == sizeof (long long int))
        {
          er_entry_p->args[i].longlong_value = va_arg (args, long long);
        }
      else
        {
          er_entry_p->args[i].int_value = va_arg (args, int);
        }
      break;
    case 'p':
      er_entry_p->args[i].pointer_value = va_arg (args, void *);
      break;
    case 'f':
      er_entry_p->args[i].double_value = va_arg (args, double);
      break;
    case 'L':
      er_entry_p->args[i].longdouble_value = va_arg (args, long double);
      break;
    case 's':
      er_entry_p->args[i].string_value = va_arg (args, char *);
      if (er_entry_p->args[i].string_value == NULL)
        {
          er_entry_p->args[i].string_value = "(null)";
        }
      break;
    default:
      /*
       * There should be no way to get in here with any other code;
       * er_study_fmt() should have protected us from that.  If
       * we're here, it's likely that memory has been corrupted.
       */
      er_emergency (__FILE__, __LINE__, er_Cached_msg[ER_LOG_UNKNOWN_CODE], fmt->spec[i].code);
      return ER_FAILED;
    }
    }

  /*
   * Now do the printing.  Use sprintf to do the actual formatting,
   * this time using the simplified conversion specs we saved during
   * er_study_fmt().  This frees us from relying on sprintf (or
   * vsprintf) actually implementing the %<num>$<code> feature, which
   * is evidently unimplemented on some platforms (Sun ANSI C, at
   * least).
   */

  p = fmt->fmt;
  q = p;
  s = er_entry_p->msg_area;
  i = 0;
  while ((q = strchr (p, '%')))
    {
      /*
       * Copy the text between the last conversion spec and the next
       * and then advance the pointers.
       */
      strncpy (s, p, q - p);
      s += q - p;
      p = q;
      q += 1;

      if (q[0] == '%')
    {
      *s++ = '%';
      p = q + 2;
      i += 1;
      continue;
    }

      /*
       * See if we've got a position specifier; it will look like a
       * sequence of digits followed by a '$'.  Anything else is
       * assumed to be part of a conversion spec.  If there is no
       * explicit position specifier, we use the current loop index as
       * the position specifier.
       */
      n = 0;
      while (char_isdigit (*q))
    {
      n *= 10;
      n += (*q) - '0';
      q += 1;
    }
      n = (*q == '$' && n) ? (n - 1) : i;

      /*
       * Format the specified argument using the simplified
       * (non-positional) conversion spec that we produced earlier.
       */
      switch (fmt->spec[n].code)
    {
    case 'i':
      sprintf (s, fmt->spec[n].spec, er_entry_p->args[n].int_value);
      break;
    case SPEC_CODE_LONGLONG:
      sprintf (s, fmt->spec[n].spec, er_entry_p->args[n].longlong_value);
      break;
    case SPEC_CODE_SIZE_T:
      if (sizeof (size_t) == sizeof (long long int))
        {
          sprintf (s, fmt->spec[n].spec, er_entry_p->args[n].longlong_value);
        }
      else
        {
          sprintf (s, fmt->spec[n].spec, er_entry_p->args[n].int_value);
        }
      break;
    case 'p':
      sprintf (s, fmt->spec[n].spec, er_entry_p->args[n].pointer_value);
      break;
    case 'f':
      sprintf (s, fmt->spec[n].spec, er_entry_p->args[n].double_value);
      break;
    case 'L':
      sprintf (s, fmt->spec[n].spec, er_entry_p->args[n].longdouble_value);
      break;
    case 's':
      sprintf (s, fmt->spec[n].spec, er_entry_p->args[n].string_value);
      break;
    default:
      /*
       * Can't get here.
       */
      break;
    }

      /*
       * Advance the pointers.  The conversion spec has to end with one
       * of the characters in the strcspn() argument, and none of
       * those characters can appear before the end of the spec.
       */
      s += strlen (s);
      p += strcspn (p, "cdiopsuxXefgEFG") + 1;
      i += 1;
    }

  /*
   * And get the last part of the fmt string after the last conversion
   * spec...
   */
  strcpy (s, p);
  s[strlen (p)] = '\0';

  return NO_ERROR;
}

static bool
er_is_error_severity (er_severity severity)
{
  switch (severity)
    {
    case ER_FATAL_ERROR_SEVERITY:
    case ER_ERROR_SEVERITY:
    case ER_SYNTAX_ERROR_SEVERITY:
      return true;
    case ER_WARNING_SEVERITY:
    case ER_NOTIFICATION_SEVERITY:
      return false;
    default:
      assert (false);
      return false;
    }
}

#if defined (CS_MODE)
void
er_set_ignore_uninit (bool ignore)
{
  er_Ignore_uninit = ignore;
}
#endif

/* *INDENT-OFF* */
namespace cuberr
{
  manager::manager (const char * msg_file, er_exit_ask exit_arg)
  {
    if (er_init (msg_file, exit_arg) != NO_ERROR)
      {
        assert_release (false);
      }
  }

  manager::~manager (void)
  {
    er_final (ER_ALL_FINAL);
  }
} // namespace cuberr
/* *INDENT-ON* */

#if !defined(WINDOWS)
void
er_print_crash_callstack (int sig)
{
  switch (sig)
    {
    case SIGABRT:
    case SIGILL:
    case SIGFPE:
    case SIGBUS:
    case SIGSEGV:
    case SIGSYS:
      break;
    default:
      return;
    }

  /* get CUBRID env */
  char *env = getenv ("CUBRID");

  if (!env)
    {
      return;
    }

  /* get cmdline information (process name, args) */
  char buffer[513];
  FILE *fp = fopen ("/proc/self/cmdline", "r");
  if (!fp)
    {
      return;
    }

  size_t byteRead = fread (buffer, 1, sizeof (buffer) - 1, fp);
  if (byteRead <= 0)
    {
      fclose (fp);
      return;
    }
  buffer[byteRead] = '\0';
  fclose (fp);

  /* get current direcory, chdir to $CUBRID/log and check and make directory $CUBRID/log/coredump */
  char cdir[PATH_MAX], logdir[PATH_MAX], *p = logdir;

  getcwd (cdir, sizeof (cdir));
  if (chdir (env) != 0)
    {
      return;
    }
  sprintf (logdir, "log/coredump");

  while (p != NULL)
    {
      p = strchr (p, '/');
      if (p != NULL)
    {
      *p = '\0';
    }
      if (access (logdir, F_OK) < 0)
    {
      if (mkdir (logdir, 0777) < 0)
        {
          chdir (cdir);
          return;
        }
    }
      if (p != NULL)
    {
      *p = '/';
      p++;
    }
    }

  /* make coredump filename : processname_YYYYMMDDHHSSMM.min.coredump */
  struct timeval tv;
  struct tm *tm_info;
  char filename[PATH_MAX];
  char *args = buffer;

  gettimeofday (&tv, NULL);
  tm_info = localtime (&tv.tv_sec);

  if (snprintf (filename, PATH_MAX, "%s/%s_%04d%02d%02d%02d%02d%02d.%03ld.coredump", logdir, args,  // process name
        tm_info->tm_year + 1900,
        tm_info->tm_mon + 1,
        tm_info->tm_mday, tm_info->tm_hour, tm_info->tm_min, tm_info->tm_sec, tv.tv_usec / 1000) >= PATH_MAX)
    {
      assert_release (0);
      filename[PATH_MAX - 1] = '\0';
    }

  /* print process information and callstack into coredump file */
  fp = fopen (filename, "w+");
  if (!fp)
    {
      chdir (cdir);
      return;
    }

  fprintf (fp, "process info : ");
  while (args < buffer + byteRead)
    {
      fprintf (fp, "%s ", args);
      args += strlen (args) + 1;
    }
  fprintf (fp, "\n\n");

  if (!fname_table)
    {
      if (er_call_stack_init () == ER_FAILED)
    {
      fclose (fp);
      chdir (cdir);
      return;
    }
      er_dump_call_stack (fp);
      er_call_stack_final ();
    }
  else
    {
      er_dump_call_stack (fp);
    }
  fclose (fp);

  /* chdir orignal path */
  chdir (cdir);

  return;
}
#else
void
er_print_crash_callstack (int sig)
{
}
#endif