blob: cf197a4638439eef9f89052d9ed256d3ed17d8bc [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.
*/
/*-------------------------------------------------------------------------
*
* elog.c
* error logging and reporting
*
* Some notes about recursion and errors during error processing:
*
* We need to be robust about recursive-error scenarios --- for example,
* if we run out of memory, it's important to be able to report that fact.
* There are a number of considerations that go into this.
*
* First, distinguish between re-entrant use and actual recursion. It
* is possible for an error or warning message to be emitted while the
* parameters for an error message are being computed. In this case
* errstart has been called for the outer message, and some field values
* may have already been saved, but we are not actually recursing. We handle
* this by providing a (small) stack of ErrorData records. The inner message
* can be computed and sent without disturbing the state of the outer message.
* (If the inner message is actually an error, this isn't very interesting
* because control won't come back to the outer message generator ... but
* if the inner message is only debug or log data, this is critical.)
*
* Second, actual recursion will occur if an error is reported by one of
* the elog.c routines or something they call. By far the most probable
* scenario of this sort is "out of memory"; and it's also the nastiest
* to handle because we'd likely also run out of memory while trying to
* report this error! Our escape hatch for this case is to reset the
* ErrorContext to empty before trying to process the inner error. Since
* ErrorContext is guaranteed to have at least 8K of space in it (see mcxt.c),
* we should be able to process an "out of memory" message successfully.
* Since we lose the prior error state due to the reset, we won't be able
* to return to processing the original error, but we wouldn't have anyway.
* (NOTE: the escape hatch is not used for recursive situations where the
* inner message is of less than ERROR severity; in that case we just
* try to process it and return normally. Usually this will work, but if
* it ends up in infinite recursion, we will PANIC due to error stack
* overflow.)
*
*
* Portions Copyright (c) 2005-2009, Greenplum inc
* Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/utils/error/elog.c,v 1.178.2.4 2007/07/21 22:12:11 tgl Exp $
*
*-------------------------------------------------------------------------
*/
#include "postgres.h"
#include <fcntl.h>
#include <time.h>
#include <unistd.h>
#include <signal.h>
#include <ctype.h>
#ifdef HAVE_SYSLOG
#include <syslog.h>
#endif
#include "access/transam.h"
#include "access/xact.h"
#include "libpq/libpq.h"
#include "libpq/pqformat.h"
#include "libpq/pqsignal.h"
#include "mb/pg_wchar.h"
#include "miscadmin.h"
#include "postmaster/postmaster.h"
#include "postmaster/syslogger.h"
#include "storage/ipc.h"
#include "storage/proc.h"
#include "tcop/tcopprot.h"
#include "utils/memutils.h"
#include "utils/ps_status.h"
#include "cdb/cdbvars.h" /*Gp_segment */
#include "utils/ps_status.h" /* get_ps_display_username() */
#include "cdb/cdbselect.h"
#include "pgtime.h"
#include "utils/elog.h" /* backtrace() prototype for solaris */
#include "utils/debugbreak.h"
#include "utils/builtins.h" /* gp_elog() */
#include "miscadmin.h"
/*
* dlfcn.h on OSX only has dladdr visible if _DARWIN_C_SOURCE is defined.
*/
#define _DARWIN_C_SOURCE 1
#define _STDBOOL_H_
#include <dlfcn.h>
/*
* Argh... dlfcn.h on OSX pulls in stdbool.h, changing the type of bool from our
* definition (char) to gcc's definition (unsigned char if C99, int if not).
* For some reason the #define _STDBOOL_H_ isn't preventing this. So, we need to undef bool
* to get back to our bool type.
*/
#undef bool
#undef _
#define _(x) err_gettext(x)
static const char *err_gettext(const char *str)
/* This extension allows gcc to check the format string for consistency with
the supplied arguments. */
__attribute__((format_arg(1)));
/* External declaration to allow demangling of C++ symbols in backtrace */
extern char *
__cxa_demangle(const char *__mangled_name, char *__output_buffer, size_t *__length, int *__status);
/* Global variables */
ErrorContextCallback *error_context_stack = NULL;
sigjmp_buf *PG_exception_stack = NULL;
extern bool redirection_done;
/* GUC parameters */
int Log_error_verbosity = PGERROR_VERBOSE;
char *Log_line_prefix = NULL; /* format for extra log line info */
int Log_destination = LOG_DESTINATION_STDERR;
#ifdef HAVE_SYSLOG
/*
* Max string length to send to syslog(). Note that this doesn't count the
* sequence-number prefix we add, and of course it doesn't count the prefix
* added by syslog itself. On many implementations it seems that the hard
* limit is approximately 2K bytes including both those prefixes.
*/
#ifndef PG_SYSLOG_LIMIT
#define PG_SYSLOG_LIMIT 1024
#endif
static bool openlog_done = false;
static char *syslog_ident = NULL;
static int syslog_facility = LOG_LOCAL0;
static void write_syslog(int level, const char *line);
#endif
#ifdef WIN32
static void write_eventlog(int level, const char *line);
#endif
/* We provide a small stack of ErrorData records for re-entrant cases */
#define ERRORDATA_STACK_SIZE 10
#define CMD_BUFFER_SIZE 1024
#define SYMBOL_SIZE 512
#define ADDRESS_SIZE 20
#define STACK_DEPTH_MAX 100
/*
* Assembly code, gets the values of the frame pointer.
* It only works for x86 processors.
*/
#if defined(__i386)
#define ASMFP asm volatile ("movl %%ebp, %0" : "=g" (ulp));
#define GET_PTR_FROM_VALUE(value) ((uint32)value)
#define GET_FRAME_POINTER(x) do { uint64 ulp; ASMFP; x = ulp; } while (0)
#elif defined(__x86_64__)
#define ASMFP asm volatile ("movq %%rbp, %0" : "=g" (ulp));
#define GET_PTR_FROM_VALUE(value) (value)
#define GET_FRAME_POINTER(x) do { uint64 ulp; ASMFP; x = ulp; } while (0)
#else
#define ASMFP
#define GET_PTR_FROM_VALUE(value) (value)
#define GET_FRAME_POINTER(x)
#endif
static ErrorData errordata[ERRORDATA_STACK_SIZE];
static int errordata_stack_depth = -1; /* index of topmost active frame */
static int recursion_depth = 0; /* to detect actual recursion */
/* buffers for formatted timestamps that might be used by both
* log_line_prefix and csv logs.
*/
#define FORMATTED_TS_LEN 128
static char formatted_start_time[FORMATTED_TS_LEN];
//static char formatted_log_time[FORMATTED_TS_LEN];
/* Macro for checking errordata_stack_depth is reasonable */
#define CHECK_STACK_DEPTH() \
do { \
if (errordata_stack_depth < 0) \
{ \
errordata_stack_depth = -1; \
ereport(ERROR, (errmsg_internal("errstart was not called"))); \
} \
} while (0)
bool SuppressPanic = false; /* GP */
static void cdb_tidy_message(ErrorData *edata);
static void log_line_prefix(StringInfo buf);
static void send_message_to_server_log(ErrorData *edata);
static void send_message_to_frontend(ErrorData *edata);
static char *expand_fmt_string(const char *fmt, ErrorData *edata);
static const char *useful_strerror(int errnum);
static const char *error_severity(int elevel);
static void append_with_tabs(StringInfo buf, const char *str);
static bool is_log_level_output(int elevel, int log_min_level);
static void write_pipe_chunks(char *data, int len);
static void elog_debug_linger(ErrorData *edata);
static void setup_formatted_start_time(void);
/* verify string is correctly encoded, and escape it if invalid */
static void verify_and_replace_mbstr(char **str, int len)
{
Assert(pg_verifymbstr(*str, len, true));
if (!pg_verifymbstr(*str, len, true))
{
pfree(*str);
*str = pstrdup("Message skipped due to incorrect encoding.");
}
}
/*
* OpenSolaris has this header, but Solaris 10 doesn't.
* Linux and OSX 10.5 have it.
*/
#if !defined(pg_on_solaris) && !defined(_WIN32) && !defined(_WIN64)
#include <execinfo.h>
#endif
/*
* in_error_recursion_trouble --- are we at risk of infinite error recursion?
*
* This function exists to provide common control of various fallback steps
* that we take if we think we are facing infinite error recursion. See the
* callers for details.
*/
bool
in_error_recursion_trouble(void)
{
/* Pull the plug if recurse more than once */
return (recursion_depth > 2);
}
/*
* One of those fallback steps is to stop trying to localize the error
* message, since there's a significant probability that that's exactly
* what's causing the recursion.
*/
static inline const char *
err_gettext(const char *str)
{
#ifdef ENABLE_NLS
if (in_error_recursion_trouble())
return str;
else
return gettext(str);
#else
return str;
#endif
}
/*
* elog_internalerror -- report an internal error
*
* GPDB only
*
* Does not return; exits via longjmp to PG_CATCH error handler.
*/
void
elog_internalerror(const char *filename, int lineno, const char *funcname)
{
/*
* TODO Chuck asks: Why isn't this a FATAL error?
*
* Also, why aren't we allowing for a specific err message to be passed in?
*/
if (errstart(ERROR, filename, lineno, funcname,TEXTDOMAIN))
{
errfinish(errcode(ERRCODE_INTERNAL_ERROR),
errmsg("Unexpected internal error"));
}
/* not reached */
abort();
} /* elog_internalerror */
/*
* errstart --- begin an error-reporting cycle
*
* Create a stack entry and store the given parameters in it. Subsequently,
* errmsg() and perhaps other routines will be called to further populate
* the stack entry. Finally, errfinish() will be called to actually process
* the error report.
*
* Returns TRUE in normal case. Returns FALSE to short-circuit the error
* report (if it's a warning or lower and not to be reported anywhere).
*/
bool
errstart(int elevel, const char *filename, int lineno,
const char *funcname, const char *domain)
{
ErrorData *edata;
bool output_to_server = false;
bool output_to_client = false;
int i;
/*
* Check some cases in which we want to promote an error into a more
* severe error. None of this logic applies for non-error messages.
*/
if (elevel >= ERROR)
{
/*
* If we are inside a critical section, all errors become PANIC
* errors. See miscadmin.h.
*/
/*
* TODO Chuck asks: Isn't it dangerous to supress the PANIC?
* Can't we do this in a better way?
*/
if (CritSectionCount > 0 && !SuppressPanic)
elevel = PANIC;
/*
* Check reasons for treating ERROR as FATAL:
*
* 1. we have no handler to pass the error to (implies we are in the
* postmaster or in backend startup).
*
* 2. ExitOnAnyError mode switch is set (initdb uses this).
*
* 3. the error occurred after proc_exit has begun to run. (It's
* proc_exit's responsibility to see that this doesn't turn into
* infinite recursion!)
*/
if (elevel == ERROR)
{
if (PG_exception_stack == NULL ||
ExitOnAnyError ||
proc_exit_inprogress)
elevel = FATAL;
}
/*
* If the error level is ERROR or more, errfinish is not going to
* return to caller; therefore, if there is any stacked error already
* in progress it will be lost. This is more or less okay, except we
* do not want to have a FATAL or PANIC error downgraded because the
* reporting process was interrupted by a lower-grade error. So check
* the stack and make sure we panic if panic is warranted.
*/
for (i = 0; i <= errordata_stack_depth; i++)
elevel = Max(elevel, errordata[i].elevel);
}
/*
* Now decide whether we need to process this report at all; if it's
* warning or less and not enabled for logging, just return FALSE without
* starting up any error logging machinery.
*/
/* Determine whether message is enabled for server log output */
if (IsPostmasterEnvironment)
output_to_server = is_log_level_output(elevel, log_min_messages);
else
/* In bootstrap/standalone case, do not sort LOG out-of-order */
output_to_server = (elevel >= log_min_messages);
/* Determine whether message is enabled for client output */
if (whereToSendOutput == DestRemote && elevel != COMMERROR)
{
/*
* client_min_messages is honored only after we complete the
* authentication handshake. This is required both for security
* reasons and because many clients can't handle NOTICE messages
* during authentication.
*/
if (ClientAuthInProgress)
output_to_client = (elevel >= ERROR);
else
output_to_client = (elevel >= client_min_messages ||
elevel == INFO);
}
/* Skip processing effort if non-error message will not be output */
if (elevel < ERROR && !output_to_server && !output_to_client)
return false;
/*
* Okay, crank up a stack entry to store the info in.
*/
if (recursion_depth++ > 0 && elevel >= ERROR)
{
/*
* Ooops, error during error processing. Clear ErrorContext as
* discussed at top of file. We will not return to the original
* error's reporter or handler, so we don't need it.
*/
MemoryContextReset(ErrorContext);
/*
* Infinite error recursion might be due to something broken in a
* context traceback routine. Abandon them too. We also abandon
* attempting to print the error statement (which, if long, could
* itself be the source of the recursive failure).
*/
if (in_error_recursion_trouble())
{
error_context_stack = NULL;
debug_query_string = NULL;
/*
* If we recurse too many times, this could mean that we have
* serious out of memory problems. We bail out immediately here.
* See MPP-2440.
*/
if (recursion_depth > 2 * ERRORDATA_STACK_SIZE)
{
ImmediateInterruptOK = false;
fflush(stdout);
fflush(stderr);
return false;
}
}
}
if (++errordata_stack_depth >= ERRORDATA_STACK_SIZE)
{
/*
* Wups, stack not big enough. We treat this as a PANIC condition
* because it suggests an infinite loop of errors during error
* recovery.
*/
errordata_stack_depth = -1; /* make room on stack */
ereport(PANIC, (errmsg_internal("ERRORDATA_STACK_SIZE exceeded")));
}
/* Initialize data for this error frame */
edata = &errordata[errordata_stack_depth];
MemSet(edata, 0, sizeof(ErrorData));
edata->elevel = elevel;
edata->output_to_server = output_to_server;
edata->output_to_client = output_to_client;
edata->filename = filename;
edata->lineno = lineno;
edata->funcname = funcname;
/* the default text domain is the backend's */
edata->domain = domain ? domain : PG_TEXTDOMAIN("postgres");
edata->omit_location = true;
/* Select default errcode based on elevel */
if (elevel >= ERROR)
{
edata->sqlerrcode = ERRCODE_INTERNAL_ERROR;
edata->omit_location = false;
}
else if (elevel == WARNING)
edata->sqlerrcode = ERRCODE_WARNING;
else
edata->sqlerrcode = ERRCODE_SUCCESSFUL_COMPLETION;
if (elevel > ERROR)
edata->send_alert = true; /* Send alerts for PANIC and FATAL errors */
/* errno is saved here so that error parameter eval can't change it */
edata->saved_errno = errno;
#ifndef WIN32
edata->stacktracesize = backtrace(edata->stacktracearray, 30);
#else
edata->stacktracesize = 0;
#endif
recursion_depth--;
return true;
}
/*
* errfinish --- end an error-reporting cycle
*
* Produce the appropriate error report(s) and pop the error stack.
*
* If elevel is ERROR or worse, control does not return to the caller.
* See elog.h for the error level definitions.
*/
void
errfinish(int dummy __attribute__((unused)),...)
{
ErrorData *edata = &errordata[errordata_stack_depth];
int elevel = edata->elevel;
MemoryContext oldcontext;
ErrorContextCallback *econtext;
int saved_errno; /*CDB*/
recursion_depth++;
CHECK_STACK_DEPTH();
saved_errno = edata->saved_errno; /*CDB*/
/*
* Do processing in ErrorContext, which we hope has enough reserved space
* to report an error.
*/
oldcontext = MemoryContextSwitchTo(ErrorContext);
/*
* Call any context callback functions. Errors occurring in callback
* functions will be treated as recursive errors --- this ensures we will
* avoid infinite recursion (see errstart).
*/
for (econtext = error_context_stack;
econtext != NULL;
econtext = econtext->previous)
(*econtext->callback) (econtext->arg);
/*
* If ERROR (not more nor less) we pass it off to the current handler.
* Printing it and popping the stack is the responsibility of the handler.
*/
if (elevel == ERROR)
{
/*
* GP: While doing local error try/catch, do not reset all these
* important variables!
*/
if (!SuppressPanic)
{ // GP: Don't indent so we can watch this section...
/*
* We do some minimal cleanup before longjmp'ing so that handlers can
* execute in a reasonably sane state.
*/
/* This is just in case the error came while waiting for input */
ImmediateInterruptOK = false;
/*
* Reset InterruptHoldoffCount in case we ereport'd from inside an
* interrupt holdoff section. (We assume here that no handler will
* itself be inside a holdoff section. If necessary, such a handler
* could save and restore InterruptHoldoffCount for itself, but this
* should make life easier for most.)
*/
InterruptHoldoffCount = 0;
CritSectionCount = 0; /* should be unnecessary, but... */
}// GP: End watching section.
/*
* Note that we leave CurrentMemoryContext set to ErrorContext. The
* handler should reset it to something else soon.
*/
recursion_depth--;
PG_RE_THROW();
}
/*
* If we are doing FATAL or PANIC, abort any old-style COPY OUT in
* progress, so that we can report the message before dying. (Without
* this, pq_putmessage will refuse to send the message at all, which is
* what we want for NOTICE messages, but not for fatal exits.) This hack
* is necessary because of poor design of old-style copy protocol. Note
* we must do this even if client is fool enough to have set
* client_min_messages above FATAL, so don't look at output_to_client.
*/
if (elevel >= FATAL && whereToSendOutput == DestRemote)
pq_endcopyout(true);
/* CDB: If fatal internal error, linger so user can attach a debugger. */
if (elevel == FATAL &&
edata->sqlerrcode == ERRCODE_INTERNAL_ERROR &&
gp_debug_linger > 0)
elog_debug_linger(edata);
/* Emit the message to the right places. */
else
EmitErrorReport();
/*
* CDB: Let caller take care of terminating the process, if requested.
* Used by CdbProgramErrorHandler() to re-raise a signal such as SIGSEGV
* in order to produce a core file. We don't want to get involved in
* platform dependent signal handling here, so let caller do it.
*/
if (elevel == FATAL &&
edata->fatal_return)
{
fflush(stdout);
fflush(stderr);
errno = saved_errno;
return;
}
/* Now free up subsidiary data attached to stack entry, and release it */
if (edata->message)
pfree(edata->message);
if (edata->detail)
pfree(edata->detail);
if (edata->detail_log)
pfree(edata->detail_log);
if (edata->hint)
pfree(edata->hint);
if (edata->context)
pfree(edata->context);
if (edata->internalquery)
pfree(edata->internalquery);
errordata_stack_depth--;
/* Exit error-handling context */
MemoryContextSwitchTo(oldcontext);
recursion_depth--;
/*
* Perform error recovery action as specified by elevel.
*/
if (elevel == FATAL)
{
/*
* For a FATAL error, we let proc_exit clean up and exit.
*/
ImmediateInterruptOK = false;
/*
* If we just reported a startup failure, the client will disconnect
* on receiving it, so don't send any more to the client.
*/
if (PG_exception_stack == NULL && whereToSendOutput == DestRemote)
whereToSendOutput = DestNone;
/*
* fflush here is just to improve the odds that we get to see the
* error message, in case things are so hosed that proc_exit crashes.
* Any other code you might be tempted to add here should probably be
* in an on_proc_exit or on_shmem_exit callback instead.
*/
fflush(stdout);
fflush(stderr);
/*
* Do normal process-exit cleanup, then return exit code 1 to indicate
* FATAL termination. The postmaster may or may not consider this
* worthy of panic, depending on which subprocess returns it.
*/
proc_exit(1);
}
if (elevel >= PANIC)
{
/*
* Serious crash time. Postmaster will observe SIGABRT process exit
* status and kill the other backends too.
*
* XXX: what if we are *in* the postmaster? abort() won't kill our
* children...
*/
ImmediateInterruptOK = false;
fflush(stdout);
fflush(stderr);
abort();
}
/*
* We reach here if elevel <= WARNING. OK to return to caller.
*
* But check for cancel/die interrupt first --- this is so that the user
* can stop a query emitting tons of notice or warning messages, even if
* it's in a loop that otherwise fails to check for interrupts.
*/
CHECK_FOR_INTERRUPTS();
errno = saved_errno; /*CDB*/
}
/*
* errcode --- add SQLSTATE error code to the current error
*
* The code is expected to be represented as per MAKE_SQLSTATE().
*/
int
errcode(int sqlerrcode)
{
ErrorData *edata = &errordata[errordata_stack_depth];
/* we don't bother incrementing recursion_depth */
CHECK_STACK_DEPTH();
edata->sqlerrcode = sqlerrcode;
/* Indicate that we want stack traces etc for internal errors */
if (sqlerrcode == ERRCODE_INTERNAL_ERROR)
edata->omit_location = false;
else
edata->omit_location = true;
return 0; /* return value does not matter */
}
/*
* errcode_for_file_access --- add SQLSTATE error code to the current error
*
* The SQLSTATE code is chosen based on the saved errno value. We assume
* that the failing operation was some type of disk file access.
*
* NOTE: the primary error message string should generally include %m
* when this is used.
*/
int
errcode_for_file_access(void)
{
ErrorData *edata = &errordata[errordata_stack_depth];
/* we don't bother incrementing recursion_depth */
CHECK_STACK_DEPTH();
switch (edata->saved_errno)
{
/* Permission-denied failures */
case EPERM: /* Not super-user */
case EACCES: /* Permission denied */
#ifdef EROFS
case EROFS: /* Read only file system */
#endif
edata->sqlerrcode = ERRCODE_INSUFFICIENT_PRIVILEGE;
break;
/* File not found */
case ENOENT: /* No such file or directory */
edata->sqlerrcode = ERRCODE_UNDEFINED_FILE;
break;
/* Duplicate file */
case EEXIST: /* File exists */
edata->sqlerrcode = ERRCODE_DUPLICATE_FILE;
break;
/* Wrong object type or state */
case ENOTDIR: /* Not a directory */
case EISDIR: /* Is a directory */
#if defined(ENOTEMPTY) && (ENOTEMPTY != EEXIST) /* same code on AIX */
case ENOTEMPTY: /* Directory not empty */
#endif
edata->sqlerrcode = ERRCODE_WRONG_OBJECT_TYPE;
break;
/* Insufficient resources */
case ENOSPC: /* No space left on device */
edata->sqlerrcode = ERRCODE_DISK_FULL;
break;
case ENFILE: /* File table overflow */
case EMFILE: /* Too many open files */
edata->sqlerrcode = ERRCODE_INSUFFICIENT_RESOURCES;
break;
/* Hardware failure */
case EIO: /* I/O error */
edata->sqlerrcode = ERRCODE_IO_ERROR;
break;
/* All else is classified as internal errors */
default:
edata->sqlerrcode = ERRCODE_INTERNAL_ERROR;
edata->omit_location = false;
break;
}
return 0; /* return value does not matter */
}
/*
* errcode_for_socket_access --- add SQLSTATE error code to the current error
*
* The SQLSTATE code is chosen based on the saved errno value. We assume
* that the failing operation was some type of socket access.
*
* NOTE: the primary error message string should generally include %m
* when this is used.
*/
int
errcode_for_socket_access(void)
{
ErrorData *edata = &errordata[errordata_stack_depth];
/* we don't bother incrementing recursion_depth */
CHECK_STACK_DEPTH();
switch (edata->saved_errno)
{
/* Loss of connection */
case EPIPE:
#ifdef ECONNRESET
case ECONNRESET:
#endif
edata->sqlerrcode = ERRCODE_CONNECTION_FAILURE;
break;
/* All else is classified as internal errors */
default:
edata->sqlerrcode = ERRCODE_INTERNAL_ERROR;
edata->omit_location = false;
break;
}
return 0; /* return value does not matter */
}
/*
* This macro handles expansion of a format string and associated parameters;
* it's common code for errmsg(), errdetail(), etc. Must be called inside
* a routine that is declared like "const char *fmt, ..." and has an edata
* pointer set up. The message is assigned to edata->targetfield, or
* appended to it if appendval is true. The message is subject to translation
* if translateit is true.
*
* Note: we pstrdup the buffer rather than just transferring its storage
* to the edata field because the buffer might be considerably larger than
* really necessary.
*/
#define EVALUATE_MESSAGE(targetfield, appendval, translateit) \
{ \
char *fmtbuf; \
StringInfoData buf; \
/* Internationalize the error format string */ \
if (translateit && !in_error_recursion_trouble()) \
fmt = dgettext(edata->domain, fmt); \
/* Expand %m in format string */ \
fmtbuf = expand_fmt_string(fmt, edata); \
initStringInfo(&buf); \
if ((appendval) && edata->targetfield) \
appendStringInfo(&buf, "%s\n", edata->targetfield); \
/* Generate actual output --- have to use appendStringInfoVA */ \
for (;;) \
{ \
va_list args; \
bool success; \
va_start(args, fmt); \
success = appendStringInfoVA(&buf, fmtbuf, args); \
va_end(args); \
if (success) \
break; \
enlargeStringInfo(&buf, buf.maxlen); \
} \
/* Done with expanded fmt */ \
pfree(fmtbuf); \
/* Save the completed message into the stack item */ \
if (edata->targetfield) \
pfree(edata->targetfield); \
edata->targetfield = pstrdup(buf.data); \
pfree(buf.data); \
}
/*
* Same as above, except for pluralized error messages. The calling routine
* must be declared like "const char *fmt_singular, const char *fmt_plural,
* unsigned long n, ...". Translation is assumed always wanted.
*/
#define EVALUATE_MESSAGE_PLURAL(targetfield, appendval) \
{ \
const char *fmt; \
char *fmtbuf; \
StringInfoData buf; \
/* Internationalize the error format string */ \
if (!in_error_recursion_trouble()) \
fmt = dngettext(edata->domain, fmt_singular, fmt_plural, n); \
else \
fmt = (n == 1 ? fmt_singular : fmt_plural); \
/* Expand %m in format string */ \
fmtbuf = expand_fmt_string(fmt, edata); \
initStringInfo(&buf); \
if ((appendval) && edata->targetfield) \
appendStringInfo(&buf, "%s\n", edata->targetfield); \
/* Generate actual output --- have to use appendStringInfoVA */ \
for (;;) \
{ \
va_list args; \
bool success; \
va_start(args, n); \
success = appendStringInfoVA(&buf, fmtbuf, args); \
va_end(args); \
if (success) \
break; \
enlargeStringInfo(&buf, buf.maxlen); \
} \
/* Done with expanded fmt */ \
pfree(fmtbuf); \
/* Save the completed message into the stack item */ \
if (edata->targetfield) \
pfree(edata->targetfield); \
edata->targetfield = pstrdup(buf.data); \
pfree(buf.data); \
}
/*
* errmsg --- add a primary error message text to the current error
*
* In addition to the usual %-escapes recognized by printf, "%m" in
* fmt is replaced by the error message for the caller's value of errno.
*
* Note: no newline is needed at the end of the fmt string, since
* ereport will provide one for the output methods that need it.
*/
int
errmsg(const char *fmt,...)
{
ErrorData *edata = &errordata[errordata_stack_depth];
MemoryContext oldcontext;
recursion_depth++;
CHECK_STACK_DEPTH();
oldcontext = MemoryContextSwitchTo(ErrorContext);
EVALUATE_MESSAGE(message, false, true);
/* enforce correct encoding */
verify_and_replace_mbstr(&(edata->message), strlen(edata->message));
MemoryContextSwitchTo(oldcontext);
recursion_depth--;
errno = edata->saved_errno; /*CDB*/
return 0; /* return value does not matter */
}
/*
* errmsg_internal --- add a primary error message text to the current error
*
* This is exactly like errmsg() except that strings passed to errmsg_internal
* are not translated, and are customarily left out of the
* internationalization message dictionary. This should be used for "can't
* happen" cases that are probably not worth spending translation effort on.
* We also use this for certain cases where we *must* not try to translate
* the message because the translation would fail and result in infinite
* error recursion.
*/
int
errmsg_internal(const char *fmt,...)
{
ErrorData *edata = &errordata[errordata_stack_depth];
MemoryContext oldcontext;
recursion_depth++;
CHECK_STACK_DEPTH();
oldcontext = MemoryContextSwitchTo(ErrorContext);
EVALUATE_MESSAGE(message, false, false);
/* enforce correct encoding */
verify_and_replace_mbstr(&(edata->message), strlen(edata->message));
MemoryContextSwitchTo(oldcontext);
recursion_depth--;
errno = edata->saved_errno; /*CDB*/
return 0; /* return value does not matter */
}
/*
* errmsg_plural --- add a primary error message text to the current error,
* with support for pluralization of the message text
*/
int
errmsg_plural(const char *fmt_singular, const char *fmt_plural,
unsigned long n, ...)
{
ErrorData *edata = &errordata[errordata_stack_depth];
MemoryContext oldcontext;
recursion_depth++;
CHECK_STACK_DEPTH();
oldcontext = MemoryContextSwitchTo(ErrorContext);
EVALUATE_MESSAGE_PLURAL(message, false);
/* enforce correct encoding */
verify_and_replace_mbstr(&(edata->message), strlen(edata->message));
MemoryContextSwitchTo(oldcontext);
recursion_depth--;
errno = edata->saved_errno; /*CDB*/
return 0; /* return value does not matter */
}
/*
* errdetail --- add a detail error message text to the current error
*/
int
errdetail(const char *fmt,...)
{
ErrorData *edata = &errordata[errordata_stack_depth];
MemoryContext oldcontext;
recursion_depth++;
CHECK_STACK_DEPTH();
oldcontext = MemoryContextSwitchTo(ErrorContext);
EVALUATE_MESSAGE(detail, false, true);
/* enforce correct encoding */
verify_and_replace_mbstr(&(edata->detail), strlen(edata->detail));
MemoryContextSwitchTo(oldcontext);
recursion_depth--;
errno = edata->saved_errno; /*CDB*/
return 0; /* return value does not matter */
}
/*
* errdetail_log --- add a detail_log error message text to the current error
*/
int
errdetail_log(const char *fmt,...)
{
ErrorData *edata = &errordata[errordata_stack_depth];
MemoryContext oldcontext;
recursion_depth++;
CHECK_STACK_DEPTH();
oldcontext = MemoryContextSwitchTo(ErrorContext);
EVALUATE_MESSAGE(detail_log, false, true);
/* enforce correct encoding */
verify_and_replace_mbstr(&(edata->detail_log), strlen(edata->detail_log));
MemoryContextSwitchTo(oldcontext);
recursion_depth--;
errno = edata->saved_errno; /*CDB*/
return 0; /* return value does not matter */
}
/*
* errdetail_plural --- add a detail error message text to the current error,
* with support for pluralization of the message text
*/
int
errdetail_plural(const char *fmt_singular, const char *fmt_plural,
unsigned long n, ...)
{
ErrorData *edata = &errordata[errordata_stack_depth];
MemoryContext oldcontext;
recursion_depth++;
CHECK_STACK_DEPTH();
oldcontext = MemoryContextSwitchTo(ErrorContext);
EVALUATE_MESSAGE_PLURAL(detail, false);
/* enforce correct encoding */
verify_and_replace_mbstr(&(edata->detail), strlen(edata->detail));
MemoryContextSwitchTo(oldcontext);
recursion_depth--;
errno = edata->saved_errno; /*CDB*/
return 0; /* return value does not matter */
}
/*
* errhint --- add a hint error message text to the current error
*/
int
errhint(const char *fmt,...)
{
ErrorData *edata = &errordata[errordata_stack_depth];
MemoryContext oldcontext;
recursion_depth++;
CHECK_STACK_DEPTH();
oldcontext = MemoryContextSwitchTo(ErrorContext);
EVALUATE_MESSAGE(hint, false, true);
/* enforce correct encoding */
verify_and_replace_mbstr(&(edata->hint), strlen(edata->hint));
MemoryContextSwitchTo(oldcontext);
recursion_depth--;
errno = edata->saved_errno; /*CDB*/
return 0; /* return value does not matter */
}
/*
* errcontext --- add a context error message text to the current error
*
* Unlike other cases, multiple calls are allowed to build up a stack of
* context information. We assume earlier calls represent more-closely-nested
* states.
*/
int
errcontext(const char *fmt,...)
{
ErrorData *edata = &errordata[errordata_stack_depth];
MemoryContext oldcontext;
recursion_depth++;
CHECK_STACK_DEPTH();
oldcontext = MemoryContextSwitchTo(ErrorContext);
EVALUATE_MESSAGE(context, true, true);
/* enforce correct encoding */
verify_and_replace_mbstr(&(edata->context), strlen(edata->context));
MemoryContextSwitchTo(oldcontext);
recursion_depth--;
errno = edata->saved_errno; /*CDB*/
return 0; /* return value does not matter */
}
/*
* errhidestmt --- optionally suppress STATEMENT: field of log entry
*
* This should be called if the message text already includes the statement.
*/
int
errhidestmt(bool hide_stmt)
{
ErrorData *edata = &errordata[errordata_stack_depth];
/* we don't bother incrementing recursion_depth */
CHECK_STACK_DEPTH();
edata->hide_stmt = hide_stmt;
return 0; /* return value does not matter */
}
/*
* errfunction --- add reporting function name to the current error
*
* This is used when backwards compatibility demands that the function
* name appear in messages sent to old-protocol clients. Note that the
* passed string is expected to be a non-freeable constant string.
*/
int
errfunction(const char *funcname)
{
ErrorData *edata = &errordata[errordata_stack_depth];
/* we don't bother incrementing recursion_depth */
CHECK_STACK_DEPTH();
edata->funcname = funcname;
edata->show_funcname = true;
return 0; /* return value does not matter */
}
/*
* errposition --- add cursor position to the current error
*/
int
errposition(int cursorpos)
{
ErrorData *edata = &errordata[errordata_stack_depth];
/* we don't bother incrementing recursion_depth */
CHECK_STACK_DEPTH();
edata->cursorpos = cursorpos;
return 0; /* return value does not matter */
}
/*
* errprintstack -- force print out stack trace
*/
int
errprintstack(bool printstack)
{
ErrorData *edata = &errordata[errordata_stack_depth];
edata->printstack = printstack;
return 0; /* return value does not matter */
}
/*
* internalerrposition --- add internal cursor position to the current error
*/
int
internalerrposition(int cursorpos)
{
ErrorData *edata = &errordata[errordata_stack_depth];
/* we don't bother incrementing recursion_depth */
CHECK_STACK_DEPTH();
edata->internalpos = cursorpos;
return 0; /* return value does not matter */
}
/*
* internalerrquery --- add internal query text to the current error
*
* Can also pass NULL to drop the internal query text entry. This case
* is intended for use in error callback subroutines that are editorializing
* on the layout of the error report.
*/
int
internalerrquery(const char *query)
{
ErrorData *edata = &errordata[errordata_stack_depth];
/* we don't bother incrementing recursion_depth */
CHECK_STACK_DEPTH();
if (edata->internalquery)
{
pfree(edata->internalquery);
edata->internalquery = NULL;
}
if (query)
edata->internalquery = MemoryContextStrdup(ErrorContext, query);
errno = edata->saved_errno; /*CDB*/
return 0; /* return value does not matter */
}
/*
* geterrcode --- return the currently set SQLSTATE error code
*
* This is only intended for use in error callback subroutines, since there
* is no other place outside elog.c where the concept is meaningful.
*/
int
geterrcode(void)
{
ErrorData *edata = &errordata[errordata_stack_depth];
/* we don't bother incrementing recursion_depth */
CHECK_STACK_DEPTH();
return edata->sqlerrcode;
}
/*
* geterrposition --- return the currently set error position (0 if none)
*
* This is only intended for use in error callback subroutines, since there
* is no other place outside elog.c where the concept is meaningful.
*/
int
geterrposition(void)
{
ErrorData *edata = &errordata[errordata_stack_depth];
/* we don't bother incrementing recursion_depth */
CHECK_STACK_DEPTH();
return edata->cursorpos;
}
/*
* getinternalerrposition --- same for internal error position
*
* This is only intended for use in error callback subroutines, since there
* is no other place outside elog.c where the concept is meaningful.
*/
int
getinternalerrposition(void)
{
ErrorData *edata = &errordata[errordata_stack_depth];
/* we don't bother incrementing recursion_depth */
CHECK_STACK_DEPTH();
return edata->internalpos;
}
/*
* GPDB: errOmitLocation -- set flag indicating the error was reported by a qExec
*/
int
errOmitLocation(bool omitLocation)
{
ErrorData *edata = &errordata[errordata_stack_depth];
/* we don't bother incrementing recursion_depth */
CHECK_STACK_DEPTH();
edata->omit_location = omitLocation;
return 0; /* return value does not matter */
}
/*
* GPDB: errSendAlert -- set flag indicating the error should trigger an alert via e-mail or SNMP
*/
int
errSendAlert(bool sendAlert)
{
ErrorData *edata = &errordata[errordata_stack_depth];
/* we don't bother incrementing recursion_depth */
CHECK_STACK_DEPTH();
edata->send_alert = sendAlert;
return 0; /* return value does not matter */
}
/*
* GP: errSuppressOutputToLog -- set flag indicating message is not to go to
* the system log.
*/
int errSuppressOutputToLog(void)
{
ErrorData *edata = &errordata[errordata_stack_depth];
/* we don't bother incrementing recursion_depth */
CHECK_STACK_DEPTH();
edata->output_to_server = false;
return 0; /* return value does not matter */
}
/*
* CDB: errFatalReturn -- set flag indicating errfinish() should return
* to the caller instead of calling proc_exit() after reporting a FATAL
* error. Allows termination by re-raising a signal in order to obtain
* a core dump.
*/
int
errFatalReturn(bool fatalReturn)
{
ErrorData *edata = &errordata[errordata_stack_depth];
/* we don't bother incrementing recursion_depth */
CHECK_STACK_DEPTH();
edata->fatal_return = fatalReturn;
return 0; /* return value does not matter */
}
/*
* elog_start --- startup for old-style API
*
* All that we do here is stash the hidden filename/lineno/funcname
* arguments into a stack entry.
*
* We need this to be separate from elog_finish because there's no other
* portable way to deal with inserting extra arguments into the elog call.
* (If macros with variable numbers of arguments were portable, it'd be
* easy, but they aren't.)
*/
void
elog_start(const char *filename, int lineno, const char *funcname)
{
ErrorData *edata;
if (++errordata_stack_depth >= ERRORDATA_STACK_SIZE)
{
/*
* Wups, stack not big enough. We treat this as a PANIC condition
* because it suggests an infinite loop of errors during error
* recovery. Note that the message is intentionally not localized,
* else failure to convert it to client encoding could cause further
* recursion.
*/
errordata_stack_depth = -1; /* make room on stack */
ereport(PANIC, (errmsg_internal("ERRORDATA_STACK_SIZE exceeded")));
}
edata = &errordata[errordata_stack_depth];
edata->filename = filename;
edata->lineno = lineno;
edata->funcname = funcname;
/* errno is saved now so that error parameter eval can't change it */
edata->saved_errno = errno;
#ifdef USE_ASSERT_CHECKING
if (IsUnderPostmaster && mainthread() != 0 && !pthread_equal(main_tid, pthread_self()))
{
#if defined(__darwin__)
write_log("elog called from thread (OS-X pthread_sigmask is broken: MPP-4923)\n");
#else
write_log("elog called from thread (%s:%d)\n", filename, lineno);
#endif
}
#endif
}
/*
* elog_finish --- finish up for old-style API
*/
void
elog_finish(int elevel, const char *fmt,...)
{
ErrorData *edata = &errordata[errordata_stack_depth];
MemoryContext oldcontext;
CHECK_STACK_DEPTH();
/*
* Do errstart() to see if we actually want to report the message.
*/
errordata_stack_depth--;
errno = edata->saved_errno;
if (!errstart(elevel, edata->filename, edata->lineno, edata->funcname, NULL))
return; /* nothing to do */
/*
* Format error message just like errmsg_internal().
*/
recursion_depth++;
oldcontext = MemoryContextSwitchTo(ErrorContext);
EVALUATE_MESSAGE(message, false, false);
/* enforce correct encoding */
verify_and_replace_mbstr(&(edata->message), strlen(edata->message));
MemoryContextSwitchTo(oldcontext);
recursion_depth--;
/*
* And let errfinish() finish up.
*/
errfinish(0);
}
/*
* Actual output of the top-of-stack error message
*
* In the ereport(ERROR) case this is called from PostgresMain (or not at all,
* if the error is caught by somebody). For all other severity levels this
* is called by errfinish.
*/
void
EmitErrorReport(void)
{
ErrorData *edata = &errordata[errordata_stack_depth];
MemoryContext oldcontext;
recursion_depth++;
CHECK_STACK_DEPTH();
oldcontext = MemoryContextSwitchTo(ErrorContext);
/* CDB: Tidy up the message */
/*
* TODO Chuck asks: Why do we want to do this? it seems pointless
* and makes the error messages harder to read.
*/
if (edata->output_to_server ||
edata->output_to_client)
cdb_tidy_message(edata);
/* Send to server log, if enabled */
if (edata->output_to_server)
send_message_to_server_log(edata);
/* Send to client, if enabled */
if (edata->output_to_client)
send_message_to_frontend(edata);
MemoryContextSwitchTo(oldcontext);
recursion_depth--;
}
/*
* CopyErrorData --- obtain a copy of the topmost error stack entry
*
* This is only for use in error handler code. The data is copied into the
* current memory context, so callers should always switch away from
* ErrorContext first; otherwise it will be lost when FlushErrorState is done.
*/
ErrorData *
CopyErrorData(void)
{
ErrorData *edata = &errordata[errordata_stack_depth];
ErrorData *newedata;
/*
* we don't increment recursion_depth because out-of-memory here does not
* indicate a problem within the error subsystem.
*/
CHECK_STACK_DEPTH();
Assert(CurrentMemoryContext != ErrorContext);
/* Copy the struct itself */
newedata = (ErrorData *) palloc(sizeof(ErrorData));
memcpy(newedata, edata, sizeof(ErrorData));
/* Make copies of separately-allocated fields */
if (newedata->message)
newedata->message = pstrdup(newedata->message);
if (newedata->detail)
newedata->detail = pstrdup(newedata->detail);
if (newedata->detail_log)
newedata->detail_log = pstrdup(newedata->detail_log);
if (newedata->hint)
newedata->hint = pstrdup(newedata->hint);
if (newedata->context)
newedata->context = pstrdup(newedata->context);
if (newedata->internalquery)
newedata->internalquery = pstrdup(newedata->internalquery);
return newedata;
}
/*
* FreeErrorData --- free the structure returned by CopyErrorData.
*
* Error handlers should use this in preference to assuming they know all
* the separately-allocated fields.
*/
void
FreeErrorData(ErrorData *edata)
{
if (edata->message)
pfree(edata->message);
if (edata->detail)
pfree(edata->detail);
if (edata->detail_log)
pfree(edata->detail_log);
if (edata->hint)
pfree(edata->hint);
if (edata->context)
pfree(edata->context);
if (edata->internalquery)
pfree(edata->internalquery);
pfree(edata);
}
/*
* FlushErrorState --- flush the error state after error recovery
*
* This should be called by an error handler after it's done processing
* the error; or as soon as it's done CopyErrorData, if it intends to
* do stuff that is likely to provoke another error. You are not "out" of
* the error subsystem until you have done this.
*/
void
FlushErrorState(void)
{
/*
* Reset stack to empty. The only case where it would be more than one
* deep is if we serviced an error that interrupted construction of
* another message. We assume control escaped out of that message
* construction and won't ever go back.
*/
errordata_stack_depth = -1;
recursion_depth = 0;
/* Delete all data in ErrorContext */
MemoryContextResetAndDeleteChildren(ErrorContext);
}
/*
* ReThrowError --- re-throw a previously copied error
*
* A handler can do CopyErrorData/FlushErrorState to get out of the error
* subsystem, then do some processing, and finally ReThrowError to re-throw
* the original error. This is slower than just PG_RE_THROW() but should
* be used if the "some processing" is likely to incur another error.
*/
void
ReThrowError(ErrorData *edata)
{
ErrorData *newedata;
Assert(edata->elevel <= ERROR); /* CDB: Ok to rethrow elog_demote'd error */
/* Push the data back into the error context */
recursion_depth++;
MemoryContextSwitchTo(ErrorContext);
if (++errordata_stack_depth >= ERRORDATA_STACK_SIZE)
{
/*
* Wups, stack not big enough. We treat this as a PANIC condition
* because it suggests an infinite loop of errors during error
* recovery.
*/
errordata_stack_depth = -1; /* make room on stack */
ereport(PANIC, (errmsg_internal("ERRORDATA_STACK_SIZE exceeded")));
}
newedata = &errordata[errordata_stack_depth];
memcpy(newedata, edata, sizeof(ErrorData));
/* Make copies of separately-allocated fields */
if (newedata->message)
newedata->message = pstrdup(newedata->message);
if (newedata->detail)
newedata->detail = pstrdup(newedata->detail);
if (newedata->detail_log)
newedata->detail_log = pstrdup(newedata->detail_log);
if (newedata->hint)
newedata->hint = pstrdup(newedata->hint);
if (newedata->context)
newedata->context = pstrdup(newedata->context);
if (newedata->internalquery)
newedata->internalquery = pstrdup(newedata->internalquery);
recursion_depth--;
PG_RE_THROW();
}
/*
* pg_re_throw --- out-of-line implementation of PG_RE_THROW() macro
*/
void
pg_re_throw(void)
{
/* If possible, throw the error to the next outer setjmp handler */
if (PG_exception_stack != NULL)
siglongjmp(*PG_exception_stack, 1);
else
{
/*
* If we get here, elog(ERROR) was thrown inside a PG_TRY block, which
* we have now exited only to discover that there is no outer setjmp
* handler to pass the error to. Had the error been thrown outside
* the block to begin with, we'd have promoted the error to FATAL, so
* the correct behavior is to make it FATAL now; that is, emit it and
* then call proc_exit.
*/
ErrorData *edata = &errordata[errordata_stack_depth];
Assert(errordata_stack_depth >= 0);
Assert(edata->elevel == ERROR);
edata->elevel = FATAL;
/*
* At least in principle, the increase in severity could have changed
* where-to-output decisions, so recalculate. This should stay in
* sync with errstart(), which see for comments.
*/
if (IsPostmasterEnvironment)
edata->output_to_server = is_log_level_output(FATAL,
log_min_messages);
else
edata->output_to_server = (FATAL >= log_min_messages);
if (whereToSendOutput == DestRemote)
{
if (ClientAuthInProgress)
edata->output_to_client = true;
else
edata->output_to_client = (FATAL >= client_min_messages);
}
/*
* We can use errfinish() for the rest, but we don't want it to call
* any error context routines a second time. Since we know we are
* about to exit, it should be OK to just clear the context stack.
*/
error_context_stack = NULL;
errfinish(0);
}
/* We mustn't return... */
ExceptionalCondition("pg_re_throw tried to return", "FailedAssertion",
__FILE__, __LINE__);
/*
* Since ExceptionalCondition isn't declared noreturn because of
* TrapMacro(), we need this to keep gcc from complaining.
*/
abort();
}
/*
* CDB: elog_demote
*
* A PG_CATCH() handler can call this to downgrade the error that it is
* currently handling to a level lower than ERROR. The caller should
* then do PG_RE_THROW() to proceed to the next error handler.
*
* Clients using libpq cannot receive normal output together with an error.
* The libpq frontend discards any results already buffered when a command
* completes with an error notification of level ERROR or higher.
*
* elog_demote() can be used to reduce the error level reported to the client
* so that libpq won't suppress normal output, while the backend still frees
* resources, aborts the transaction, etc, as usual.
*
* Returns true if successful, false if the request is disallowed.
*/
bool
elog_demote(int downgrade_to_elevel)
{
ErrorData *edata = &errordata[errordata_stack_depth];
if (downgrade_to_elevel >= ERROR ||
recursion_depth != 0 ||
errordata_stack_depth < 0 ||
errordata_stack_depth >= ERRORDATA_STACK_SIZE - 1 ||
edata->elevel > ERROR ||
edata->elevel < downgrade_to_elevel)
return false;
edata->elevel = downgrade_to_elevel;
return true;
} /* elog_demote */
/*
* CDB: elog_dismiss
*
* A PG_CATCH() handler can call this to downgrade the error that it is
* currently handling to a level lower than ERROR, report it to the log
* and/or client as appropriate, and purge it from the error system.
*
* This shouldn't be attempted unless the caller is certain that the
* error does not need the services of upper level error handlers to
* release resources, abort the transaction, etc.
*
* Returns true if successful, in which case the error has been expunged
* and the caller should not do PG_RE_THROW(), but should instead fall or
* jump out of the PG_CATCH() handler and resume normal execution.
*
* Returns false if unsuccessful; then the caller should carry on as
* PG_CATCH() handlers ordinarily do, and exit via PG_RE_THROW().
*/
bool
elog_dismiss(int downgrade_to_elevel)
{
ErrorContextCallback *saveCallbackStack = error_context_stack;
ErrorData *edata = &errordata[errordata_stack_depth];
bool shouldEmit = false;
if (downgrade_to_elevel >= ERROR ||
recursion_depth != 0 ||
errordata_stack_depth < 0 ||
errordata_stack_depth >= ERRORDATA_STACK_SIZE - 1 ||
edata->elevel > ERROR)
return false;
/*
* Context callbacks, if any, were already invoked when this error
* first passed through errfinish. Hide them so they won't be
* called redundantly.
*/
error_context_stack = NULL;
/* Use errstart to decide where to send the error report. */
shouldEmit = errstart(downgrade_to_elevel, NULL, 0, NULL, TEXTDOMAIN);
/* Send error report to log and/or client. */
if (shouldEmit)
{
ErrorData *newedata = &errordata[errordata_stack_depth];
/* errstart has stacked a new ErrorData entry. */
Insist(newedata == edata + 1);
/* It tells us where to send the error report for the new elevel. */
edata->elevel = newedata->elevel;
edata->output_to_client = newedata->output_to_client;
edata->output_to_server = newedata->output_to_server;
/* Pop temp ErrorData entry. Nothing was palloc'ed; no need to pfree. */
errordata_stack_depth--;
}
/* Nobody wants the error report. */
else
{
edata->elevel = downgrade_to_elevel;
edata->output_to_client = false;
edata->output_to_server = false;
}
/*
* Sneak the caller's error through errfinish again (it has been through
* once already) to emit the error report (if requested) and clean up.
*/
errfinish(0);
/* Restore the context callback stack. */
error_context_stack = saveCallbackStack;
/* Error not pending anymore, so caller should not do PG_RE_THROW(). */
return true; /* success */
} /* elog_dismiss */
/*
* CDB: elog_geterrcode
* Return the SQLSTATE code for the error currently being handled, or 0.
*
* This is only intended for use in error handlers.
*/
int
elog_geterrcode(void)
{
return (errordata_stack_depth < 0)
? 0
: errordata[errordata_stack_depth].sqlerrcode;
} /* elog_geterrcode */
int
elog_getelevel(void)
{
return (errordata_stack_depth < 0)
? NOTICE
: errordata[errordata_stack_depth].elevel;
} /* elog_getelevel */
/*
* Note: A pointer is returned. Make a copy of the message
* before re-throwing or flusing the error state.
*/
char*
elog_message(void)
{
return (errordata_stack_depth < 0)
? NULL
: errordata[errordata_stack_depth].message;
}
/*
* Initialization of error output file
*/
void
DebugFileOpen(void)
{
int fd,
istty;
if (OutputFileName[0])
{
/*
* A debug-output file name was given.
*
* Make sure we can write the file, and find out if it's a tty.
*/
if ((fd = open(OutputFileName, O_CREAT | O_APPEND | O_WRONLY,
0666)) < 0)
ereport(FATAL,
(errcode_for_file_access(),
errmsg("could not open file \"%s\": %m", OutputFileName)));
istty = isatty(fd);
close(fd);
/*
* Redirect our stderr to the debug output file.
*/
if (!freopen(OutputFileName, "a", stderr))
ereport(FATAL,
(errcode_for_file_access(),
errmsg("could not reopen file \"%s\" as stderr: %m",
OutputFileName)));
/*
* If the file is a tty and we're running under the postmaster, try to
* send stdout there as well (if it isn't a tty then stderr will block
* out stdout, so we may as well let stdout go wherever it was going
* before).
*/
if (istty && IsUnderPostmaster)
if (!freopen(OutputFileName, "a", stdout))
ereport(FATAL,
(errcode_for_file_access(),
errmsg("could not reopen file \"%s\" as stdout: %m",
OutputFileName)));
}
}
#ifdef HAVE_SYSLOG
/*
* Set or update the parameters for syslog logging
*/
void
set_syslog_parameters(const char *ident, int facility)
{
/*
* guc.c is likely to call us repeatedly with same parameters, so don't
* thrash the syslog connection unnecessarily. Also, we do not re-open
* the connection until needed, since this routine will get called whether
* or not Log_destination actually mentions syslog.
*
* Note that we make our own copy of the ident string rather than relying
* on guc.c's. This may be overly paranoid, but it ensures that we cannot
* accidentally free a string that syslog is still using.
*/
if (syslog_ident == NULL || strcmp(syslog_ident, ident) != 0 ||
syslog_facility != facility)
{
if (openlog_done)
{
closelog();
openlog_done = false;
}
if (syslog_ident)
free(syslog_ident);
syslog_ident = strdup(ident);
/* if the strdup fails, we will cope in write_syslog() */
syslog_facility = facility;
}
}
/*
* Write a message line to syslog
*/
static void
write_syslog(int level, const char *line)
{
static unsigned long seq = 0;
int len;
const char *nlpos;
/* Open syslog connection if not done yet */
if (!openlog_done)
{
openlog(syslog_ident ? syslog_ident : "postgres",
LOG_PID | LOG_NDELAY | LOG_NOWAIT,
syslog_facility);
openlog_done = true;
}
/*
* We add a sequence number to each log message to suppress "same"
* messages.
*/
seq++;
/*
* Our problem here is that many syslog implementations don't handle long
* messages in an acceptable manner. While this function doesn't help that
* fact, it does work around by splitting up messages into smaller pieces.
*
* We divide into multiple syslog() calls if message is too long or if the
* message contains embedded newline(s).
*/
len = strlen(line);
nlpos = strchr(line, '\n');
if (len > PG_SYSLOG_LIMIT || nlpos != NULL)
{
int chunk_nr = 0;
while (len > 0)
{
char buf[PG_SYSLOG_LIMIT + 1];
int buflen;
int i;
/* if we start at a newline, move ahead one char */
if (line[0] == '\n')
{
line++;
len--;
/* we need to recompute the next newline's position, too */
nlpos = strchr(line, '\n');
continue;
}
/* copy one line, or as much as will fit, to buf */
if (nlpos != NULL)
buflen = nlpos - line;
else
buflen = len;
buflen = Min(buflen, PG_SYSLOG_LIMIT);
memcpy(buf, line, buflen);
buf[buflen] = '\0';
/* trim to multibyte letter boundary */
buflen = pg_mbcliplen(buf, buflen, buflen);
if (buflen <= 0)
return;
buf[buflen] = '\0';
/* already word boundary? */
if (line[buflen] != '\0' &&
!isspace((unsigned char) line[buflen]))
{
/* try to divide at word boundary */
i = buflen - 1;
while (i > 0 && !isspace((unsigned char) buf[i]))
i--;
if (i > 0) /* else couldn't divide word boundary */
{
buflen = i;
buf[i] = '\0';
}
}
chunk_nr++;
syslog(level, "[%lu-%d] %s", seq, chunk_nr, buf);
line += buflen;
len -= buflen;
}
}
else
{
/* message short enough */
syslog(level, "[%lu] %s", seq, line);
}
}
#endif /* HAVE_SYSLOG */
#ifdef WIN32
/*
* Write a message line to the windows event log
*/
static void
write_eventlog(int level, const char *line)
{
int eventlevel = EVENTLOG_ERROR_TYPE;
static HANDLE evtHandle = INVALID_HANDLE_VALUE;
if (evtHandle == INVALID_HANDLE_VALUE)
{
evtHandle = RegisterEventSource(NULL, "PostgreSQL");
if (evtHandle == NULL)
{
evtHandle = INVALID_HANDLE_VALUE;
return;
}
}
switch (level)
{
case DEBUG5:
case DEBUG4:
case DEBUG3:
case DEBUG2:
case DEBUG1:
case LOG:
case COMMERROR:
case INFO:
case NOTICE:
eventlevel = EVENTLOG_INFORMATION_TYPE;
break;
case WARNING:
eventlevel = EVENTLOG_WARNING_TYPE;
break;
case ERROR:
case FATAL:
case PANIC:
default:
eventlevel = EVENTLOG_ERROR_TYPE;
break;
}
ReportEvent(evtHandle,
eventlevel,
0,
0, /* All events are Id 0 */
NULL,
1,
0,
&line,
NULL);
}
#endif /* WIN32 */
/*
* setup formatted_start_time
*/
static void
setup_formatted_start_time(void)
{
pg_time_t stamp_time = (pg_time_t) MyStartTime;
pg_tz *tz;
/*
* Normally we print log timestamps in log_timezone, but during startup we
* could get here before that's set. If so, fall back to gmt_timezone
* (which guc.c ensures is set up before Log_line_prefix can become
* nonempty).
*/
tz = log_timezone ? log_timezone : gmt_timezone;
pg_strftime(formatted_start_time, FORMATTED_TS_LEN,
"%Y-%m-%d %H:%M:%S %Z",
pg_localtime(&stamp_time, tz));
}
/*
* CDB: Tidy up the error message
*/
static void
cdb_strip_trailing_whitespace(char **buf)
{
if (*buf)
{
char *bp = *buf;
char *ep = bp + strlen(bp);
while (bp < ep &&
ep[-1] <= ' ' &&
ep[-1] > '\0')
*--ep = '\0';
if (bp == ep)
{
pfree(*buf);
*buf = NULL;
}
}
} /* cdb_strip_trailing_whitespace */
void
cdb_tidy_message(ErrorData *edata)
{
char *bp;
char *cp;
char *ep;
char *tp;
int m, n;
cdb_strip_trailing_whitespace(&edata->hint);
cdb_strip_trailing_whitespace(&edata->detail);
cdb_strip_trailing_whitespace(&edata->detail_log);
cdb_strip_trailing_whitespace(&edata->message);
/* Look at main error message. */
if (edata->message)
{
bp = edata->message;
while (*bp <= ' ' &&
*bp > '\0')
bp++;
ep = bp + strlen(bp);
}
else
ep = bp = "";
/*
* If more than one line, move lines after the first to errdetail.
* Make an exception for LOG messages because statement logging would
* be uglified. Skip DEBUG messages too, 'cause users don't see 'em.
*/
if (edata->elevel > LOG &&
0 != (cp = strchr(bp, '\n')))
{
char *dp = cp;
/* If just one extra line, strip its leading '\n' and whitespace. */
if (!strchr(dp+1, '\n'))
{
while (*dp <= ' ' &&
*dp > '\0')
dp++;
}
/* Insert in front of detail message. */
if (!edata->detail)
edata->detail = pstrdup(dp);
else
{
m = ep - dp;
n = strlen(edata->detail) + 1;
tp = palloc(m + 1 + n);
memcpy(tp, dp, m);
tp[m] = '\n';
memcpy(tp + m + 1, edata->detail, n);
pfree(edata->detail);
edata->detail = tp;
}
/* Drop from main message. */
ep = cp;
while (bp < ep &&
ep[-1] <= ' ' &&
ep[-1] > '\0')
ep--;
*ep = '\0';
}
/*
* If internal error, append the filename and line number.
* (Skip if error came from QE, because QE already added the info.)
*/
if (!edata->omit_location &&
edata->sqlerrcode == ERRCODE_INTERNAL_ERROR &&
edata->filename)
{
char buf[60];
const char *bfn;
/* With some compilers __FILE__ is absolute path. Strip directory. */
bfn = edata->filename + strlen(edata->filename);
while (edata->filename < bfn &&
bfn[-1] != '/' &&
bfn[-1] != '\\')
bfn--;
/* Format the error location. */
n = snprintf(buf, sizeof(buf)-1, " (%s:%d)", bfn, edata->lineno);
/* Append to main error message. */
m = ep - bp;
tp = palloc(m + n + 1);
memcpy(tp, bp, m);
memcpy(tp+m, buf, n);
tp[m+n] = '\0';
if (edata->message)
pfree(edata->message);
edata->message = tp;
}
} /* cdb_tidy_message */
static struct timeval LastLogTimeVal = {0, 0};
void
GetLastLogTimeVal(struct timeval *lastLogTimeVal)
{
Assert(lastLogTimeVal != NULL);
*lastLogTimeVal = LastLogTimeVal;
}
/*
* Format tag info for log lines; append to the provided buffer.
*/
static void
log_line_prefix(StringInfo buf)
{
/* static counter for line numbers */
static long log_line_number = 0;
/* has counter been reset in current process? */
static int log_my_pid = 0;
int format_len;
int i;
int j;
/*
* This is one of the few places where we'd rather not inherit a static
* variable's value from the postmaster. But since we will, reset it when
* MyProcPid changes.
*/
if (log_my_pid != MyProcPid)
{
log_line_number = 0;
log_my_pid = MyProcPid;
formatted_start_time[0] = '\0';
}
log_line_number++;
if (Log_line_prefix == NULL)
return; /* in case guc hasn't run yet */
format_len = strlen(Log_line_prefix);
for (i = 0; i < format_len; i++)
{
if (Log_line_prefix[i] != '%')
{
/* literal char, just copy */
appendStringInfoChar(buf, Log_line_prefix[i]);
continue;
}
/* go to char after '%' */
i++;
if (i >= format_len)
break; /* format error - ignore it */
/* process the option */
switch (Log_line_prefix[i])
{
case 'u':
if (MyProcPort)
{
const char *username = MyProcPort->user_name;
if (username == NULL || *username == '\0')
username = _("[unknown]");
appendStringInfo(buf, "%s", username);
}
break;
case 'd':
if (MyProcPort)
{
const char *dbname = MyProcPort->database_name;
if (dbname == NULL || *dbname == '\0')
dbname = _("[unknown]");
appendStringInfo(buf, "%s", dbname);
}
break;
case 'c':
appendStringInfo(buf, "%lx.%x", (long) (MyStartTime), MyProcPid);
break;
case 'p':
appendStringInfo(buf, "%d", MyProcPid);
break;
case 'l':
appendStringInfo(buf, "%ld", log_line_number);
break;
case 'm':
{
/*
* Note: for %m, %t, and %s we deliberately use the C
* library's strftime/localtime, and not the equivalent
* functions from src/timezone. This ensures that all
* backends will report log entries in the same timezone,
* namely whatever C-library setting they inherit from the
* postmaster. If we used src/timezone then local
* settings of the TimeZone GUC variable would confuse the
* log.
*
* CDB: It is not safe to call strftime since it is not async-safe, and it
* is expensive to call strftime to get timezone everytime, we use
* pg_strftime, but stick on a fixed timezone (log_timezone)
* instead a settable timezone as PostgreSQL does, since we want all
* log messages to have the same time format. See MPP-2591.
*
*/
pg_time_t stamp_time;
char strfbuf[128],
msbuf[8];
struct timeval tv;
gettimeofday(&tv, NULL);
stamp_time = (pg_time_t)tv.tv_sec;
/*
* GP: Save the time of the last log line.
*/
LastLogTimeVal = tv;
pg_strftime(strfbuf, sizeof(strfbuf),
/* leave room for microseconds... */
/* Win32 timezone names are too long so don't print them */
#ifndef WIN32
"%Y-%m-%d %H:%M:%S %Z",
#else
"%Y-%m-%d %H:%M:%S ",
#endif
pg_localtime(&stamp_time, log_timezone ? log_timezone : gmt_timezone));
/* 'paste' milliseconds into place... */
sprintf(msbuf, ".%06d", (int) (tv.tv_usec));
strncpy(strfbuf + 19, msbuf, 7);
appendStringInfoString(buf, strfbuf);
}
break;
case 't':
{
pg_time_t stamp_time = (pg_time_t)time(NULL);
pg_tz *tz;
char strfbuf[128];
tz = log_timezone ? log_timezone : gmt_timezone;
pg_strftime(strfbuf, sizeof(strfbuf),
/* Win32 timezone names are too long so don't print them */
#ifndef WIN32
"%Y-%m-%d %H:%M:%S %Z",
#else
"%Y-%m-%d %H:%M:%S",
#endif
pg_localtime(&stamp_time, tz));
appendStringInfoString(buf, strfbuf);
}
break;
case 's':
if (formatted_start_time[0] == '\0')
setup_formatted_start_time();
appendStringInfoString(buf, formatted_start_time);
break;
case 'i':
if (MyProcPort)
{
const char *psdisp;
int displen;
psdisp = get_ps_display(&displen);
appendStringInfo(buf, "%.*s", displen, psdisp);
}
break;
case 'r':
if (MyProcPort && MyProcPort->remote_host)
{
appendStringInfo(buf, "%s", MyProcPort->remote_host);
if (MyProcPort->remote_port &&
MyProcPort->remote_port[0] != '\0')
appendStringInfo(buf, "(%s)",
MyProcPort->remote_port);
}
break;
case 'h':
if (MyProcPort && MyProcPort->remote_host)
appendStringInfo(buf, "%s", MyProcPort->remote_host);
break;
case 'q':
/* in postmaster and friends, stop if %q is seen */
/* in a backend, just ignore */
if (MyProcPort == NULL)
i = format_len;
break;
case 'x':
if (MyProcPort)
{
if (IsTransactionState())
appendStringInfo(buf, "%u", GetTopTransactionId());
else
appendStringInfo(buf, "%u", InvalidTransactionId);
}
break;
case 'I':
/* prints a succinct description of an MPP process. */
if (!MyProcPort)
{
const char *sp;
const char *uname = get_ps_display_username();
if (!uname || !uname[0])
appendStringInfoString(buf, "postmaster");
else if ((sp = strstr(uname, " process")) != NULL)
appendBinaryStringInfo(buf, uname, sp - uname);
else
appendStringInfoString(buf, uname);
break;
}
j = buf->len;
if (gp_session_id > 0)
appendStringInfo(buf, "con%d ", gp_session_id);
if (gp_command_count > 0)
appendStringInfo(buf, "cmd%d ", gp_command_count);
if (Gp_role == GP_ROLE_EXECUTE)
appendStringInfo(buf, "seg%d ", GetQEIndex());
if (currentSliceId > 0)
appendStringInfo(buf, "slice%d ", currentSliceId);
if (j < buf->len &&
buf->data[buf->len - 1] == ' ')
buf->len--;
break;
case 'P':
if( Gp_role == GP_ROLE_EXECUTE )
{
appendStringInfoChar(buf, 'P');
}
break;
case 'R':
if (!MyProcPort)
{
const char *uname = get_ps_display_username();
appendStringInfoString(buf, uname && uname[0] ? uname : "postmaster");
}
else
appendStringInfoString(buf, role_to_string(Gp_role));
break;
case 'S':
if (currentSliceId >= 0)
{
appendStringInfo(buf, "%d", currentSliceId );
}
break;
case 'T':
if (MyProcPort)
{
if (Gp_role == GP_ROLE_EXECUTE)
appendStringInfo(buf, "qe");
else if (Gp_role == GP_ROLE_DISPATCH)
appendStringInfo(buf, "qd");
}
break;
case 'X':
{
DistributedTransactionId distribXid;
TransactionId localXid;
TransactionId subXid;
GetAllTransactionXids(
&distribXid,
&localXid,
&subXid);
if (localXid != InvalidTransactionId)
{
if (distribXid >= FirstDistributedTransactionId)
appendStringInfo(buf, "dx%u, ", distribXid);
appendStringInfo(buf, "x%u", localXid);
if (subXid >= FirstNormalTransactionId)
appendStringInfo(buf, ", sx%u, ", subXid);
}
}
break;
case '%':
appendStringInfoChar(buf, '%');
break;
default:
/* format error - ignore it */
break;
}
}
}
/*
* Unpack MAKE_SQLSTATE code. Note that this returns a pointer to a
* static buffer.
*/
char *
unpack_sql_state(int sql_state)
{
static char buf[12];
int i;
for (i = 0; i < 5; i++)
{
buf[i] = PGUNSIXBIT(sql_state);
sql_state >>= 6;
}
buf[i] = '\0';
return buf;
}
#define NUM_RETRIES 1000
/*
* Send the data through the pipe.
*/
static inline void
gp_write_pipe_chunk(const char *buffer, int len, bool check_interrupts)
{
int retval = -1;
fd_set wfds;
struct timeval tv;
int retry_no = 0;
while (retry_no < NUM_RETRIES)
{
if (check_interrupts && pthread_equal(main_tid, pthread_self()))
{
CHECK_FOR_INTERRUPTS();
}
retry_no++;
/*
* Wait up to 1 second to see if stderr is available for write.
* If not, we ignore the error message. This could happen when
* the logger process crashes.
*/
tv.tv_sec = 1;
tv.tv_usec = 0;
FD_ZERO(&wfds);
FD_SET(fileno(stderr), &wfds);
retval = select(fileno(stderr) + 1, NULL, &wfds, NULL, &tv);
if (retval == 0 || (retval < 0 && errno == EINTR))
{
/* select() timeout or interruptted. Retry */
continue;
}
else
{
/*
* When the stderr is ready (retval == 1), or errors (other cases),
* break out the loop.
*/
break;
}
}
Assert((retval == 1) || (retval < 0 && errno != EINTR) || (retry_no == NUM_RETRIES));
if (retval == 1)
{
int bytes;
do
{
#ifdef USE_ASSERT_CHECKING
{
PipeProtoChunk *chunk = (PipeProtoChunk *) buffer;
Assert(chunk->hdr.zero == 0);
Assert(chunk->hdr.pid != 0);
Assert(chunk->hdr.thid != 0);
Assert(len <= PIPE_CHUNK_SIZE);
}
#endif
bytes = write(fileno(stderr), buffer, len);
}
while (bytes < 0 && errno == EINTR);
}
}
/*
* Append a string (termniated by '\0') to the GpPipeProtoChunk.
*
* If GpPipeProtoChunk does not have space for the given string,
* this function appends enough data to fill the buffer, and
* sends out the buffer. After that, the payload session of
* GpPipeProtoChunk is reset and the rest of the given string
* is appended. If the given string is pretty large, this function
* may send out multiple chunks.
*/
static inline void
append_string_to_pipe_chunk(PipeProtoChunk *buffer, const char* input)
{
if(am_syslogger)
return;
int len = 0;
if (input != NULL)
{
len = strlen(input);
}
/*
* If this is a really long message, it does not really
* make lots of sense to print them all. Truncate it.
*/
if (len >= PIPE_MAX_PAYLOAD * 20)
{
len = PIPE_MAX_PAYLOAD * 20 - 1;
}
char *data = buffer->data + buffer->hdr.len;
int offset = 0;
while (buffer->hdr.len + len >= PIPE_MAX_PAYLOAD)
{
int bytes = PIPE_MAX_PAYLOAD - buffer->hdr.len;
memcpy(data, input + offset, bytes);
Assert(bytes + buffer->hdr.len == PIPE_MAX_PAYLOAD);
buffer->hdr.len = PIPE_MAX_PAYLOAD;
gp_write_pipe_chunk((char *)buffer, PIPE_CHUNK_SIZE, true);
buffer->hdr.len = 0;
buffer->hdr.chunk_no++;
data = buffer->data;
len -= bytes;
offset += bytes;
}
/* Copy the remaining data, and add '\0' at the end */
memcpy(data, input + offset, len);
data[len] = 0;
buffer->hdr.len += len+1;
Assert(buffer->hdr.len > 0 && buffer->hdr.len <= PIPE_MAX_PAYLOAD);
}
/*
* Append the backtrace to the given PipeProtoChunk or the syslogger file or stderr.
*
* We can not use the default backtrace_symbols since it calls malloc, which
* is not async-safe, to allocate space for symbols. Even though we don't
* really support async-safe error logging yet, the malloc has caused several
* deadlock issues in the past, we should avoid using them in our error handler.
*
* If buffer is NULL, the stack is written to the syslogger file if amsyslogger is true.
* Otherwise, write to stderr.
*/
static void
append_stacktrace(PipeProtoChunk *buffer, StringInfo append, void *const *stackarray,
int stacksize, bool amsyslogger)
{
#if !defined(WIN32) && !defined(_AIX)
int stack_no;
char symbol[SYMBOL_SIZE]; /* a reasonable size for a symbol */
Dl_info dli;
int symbol_len;
FILE * fd = NULL;
char cmd[CMD_BUFFER_SIZE];
char cmdresult[STACK_DEPTH_MAX][SYMBOL_SIZE];
char addrtxt[ADDRESS_SIZE];
#if defined(__darwin__)
const char * prog = "atos -o";
#elif pg_on_solaris
const char * prog = "gaddr2line -s -e";
#else
const char * prog = "addr2line -s -e";
#endif
static bool in_translate_stacktrace = false;
bool addr2line_ok = gp_log_stack_trace_lines;
if (stacksize == 0)
{
return;
}
if (!in_translate_stacktrace && addr2line_ok)
{
/*
* Keep a record that we are doing this work, so if we crash during it, we don't
* try to do it again when we recurse back here,
*/
in_translate_stacktrace = true;
snprintf(cmd,sizeof(cmd),"%s %s ",prog,my_exec_path);
for (stack_no = 0; stack_no < stacksize && stack_no < 100; stack_no++)
{
cmdresult[stack_no][0] = '\0'; /* clear this array for later */
snprintf(addrtxt, sizeof(addrtxt),"%p ",stackarray[stack_no]);
Assert(sizeof(cmd) > strlen(cmd));
strncat(cmd, addrtxt, sizeof(cmd) - strlen(cmd) - 1);
}
cmdresult[0][0] = '\0';
fd = popen(cmd,"r");
if (fd != NULL)
{
for (stack_no = 0; stack_no < stacksize && stack_no < STACK_DEPTH_MAX && errno == 0; stack_no++)
{
/* initialize the string */
cmdresult[stack_no][0] = '\0';
// Get one line of the result from addr2line (or atos)
if (fgets(cmdresult[stack_no],SYMBOL_SIZE,fd) == NULL)
{
break;
}
// Force it to be a valid string (in case it was too long)
cmdresult[stack_no][SYMBOL_SIZE-1] = '\0';
// Get rid of the newline at the end.
if (strlen(cmdresult[stack_no]) > 0 &&
cmdresult[stack_no][strlen(cmdresult[stack_no])-1] == '\n')
{
cmdresult[stack_no][strlen(cmdresult[stack_no])-1] = '\0';
}
}
if (strlen(cmdresult[0]) > 1)
{
addr2line_ok = true;
}
Assert(NULL != fd);
pclose(fd);
}
in_translate_stacktrace = false;
}
for (stack_no = 0; stack_no < stacksize; stack_no++)
{
/* check if file/line info is available */
char *lineInfo = "";
if (addr2line_ok && stack_no < STACK_DEPTH_MAX)
{
lineInfo = cmdresult[stack_no];
}
if (dladdr(stackarray[stack_no], &dli) != 0)
{
const char *file = dli.dli_fname;
if (file != NULL && file[0] != '\0')
{
const char *dir_path = strrchr(file, '/');
if (strncmp(file, "postgres:", strlen("postgres:")) == 0)
{
file = "postgres";
}
else if (dir_path != NULL)
{
/* don't print path to file */
file = dir_path + 1;
}
}
else
{
file = "";
}
const char *function_name = dli.dli_sname;
#ifdef USE_ASSERT_CHECKING
bool function_needfree = false;
#endif
if (function_name == NULL || function_name[0] == '\0')
{
function_name = "<symbol not found>";
}
#ifdef USE_ASSERT_CHECKING
/* In debug mode only, try to demangle the function name string.
* In general, this is not completely safe to do in the error handler,
* since __cxa_demangle is not async-safe.
*/
else
{
int status = 0;
char *cmddemangle = __cxa_demangle(function_name, NULL, NULL, &status);
if (0 == status)
{
function_name = cmddemangle;
function_needfree = true;
}
}
#endif
/* check if lineInfo was retrieved */
if (strchr(lineInfo, ':') == NULL)
{
/* no line info, print offset in function */
symbol_len = snprintf(symbol,
ARRAY_SIZE(symbol),
"%-4d %p %s %s + 0x%x\n",
stack_no + 1,
stackarray[stack_no],
file,
function_name,
(int)((char *)(stackarray[stack_no]) - (char *)(dli.dli_saddr)));
}
else
{
/* keep file:line info; required for atos */
char *parenth = strrchr(lineInfo, '(');
if (parenth != NULL)
{
lineInfo = parenth + 1;
parenth = strrchr(lineInfo, ')');
*parenth = '\0';
}
/* line info added, print file and line info */
symbol_len = snprintf(symbol,
ARRAY_SIZE(symbol),
"%-4d %p %s %s (%s)\n",
stack_no + 1,
stackarray[stack_no],
file,
function_name,
lineInfo);
}
#ifdef USE_ASSERT_CHECKING
/* If we demangled the function name, the string is malloc-ed; free it here */
if (function_needfree)
{
free((void *)function_name);
}
#endif
}
else
{
if (lineInfo[0] == '\0')
{
lineInfo = "<symbol not found>";
}
symbol_len = snprintf(symbol,
ARRAY_SIZE(symbol),
"%-4d %p %s\n",
stack_no + 1,
stackarray[stack_no],
lineInfo);
}
if (buffer != NULL)
{
append_string_to_pipe_chunk(buffer, symbol);
if (stack_no != stacksize - 1)
{
/* Eliminate the last '\0' */
buffer->hdr.len --;
}
}
else
{
if (append)
{
appendStringInfo(append, "%s", symbol);
}
else
{
if (amsyslogger)
write_syslogger_file_binary(symbol, symbol_len);
else
write(fileno(stderr), symbol, symbol_len);
}
}
}
#endif
}
/*
* Directly write a string to the syslogger file or stderr.
*/
static inline void
write_syslogger_file_string(const char *str, bool amsyslogger, bool append_comma)
{
if (str != NULL && str[0] != '\0')
{
if (amsyslogger)
{
write_syslogger_file_binary("\"", 1);
syslogger_write_str(str, strlen(str), true, true);
write_syslogger_file_binary("\"", 1);
}
else
{
write(fileno(stderr), "\"", 1);
syslogger_write_str(str, strlen(str), false, true);
write(fileno(stderr), "\"", 1);
}
}
if (append_comma)
{
if (amsyslogger)
write_syslogger_file_binary(",", 1);
else
write(fileno(stderr), ",", 1);
}
}
/*
* Directly write the message in CSV format to the syslogger file or stderr.
*/
static void
write_syslogger_in_csv(ErrorData *edata, bool amsyslogger)
{
/* timestamp_with_millisecond */
syslogger_append_current_timestamp(amsyslogger);
/* username */
if (MyProcPort != NULL && MyProcPort->user_name != NULL)
write_syslogger_file_string(MyProcPort->user_name, amsyslogger, true);
else
write_syslogger_file_string(NULL, amsyslogger, true);
/* databasename */
if (MyProcPort != NULL && MyProcPort->database_name != NULL)
write_syslogger_file_string(MyProcPort->database_name, amsyslogger, true);
else
write_syslogger_file_string(NULL, amsyslogger, true);
/* Process id, thread id */
syslogger_write_int32(false, "p", MyProcPid, amsyslogger, true);
syslogger_write_int32(false, "th", mythread(), amsyslogger, true);
/* Remote host, remote port */
if (MyProcPort != NULL && MyProcPort->remote_host != NULL)
write_syslogger_file_string(MyProcPort->remote_host, amsyslogger, true);
else
write_syslogger_file_string(NULL, amsyslogger, true);
if (MyProcPort != NULL && MyProcPort->remote_port != NULL)
write_syslogger_file_string(MyProcPort->remote_port, amsyslogger, true);
else
write_syslogger_file_string(NULL, amsyslogger, true);
/* session start timestamp */
syslogger_append_timestamp((MyProcPort != NULL ?
(pg_time_t) timestamptz_to_time_t(MyProcPort->SessionStartTime): 0),
amsyslogger, true);
/* transaction id */
if (MyProcPort != NULL && IsTransactionState())
syslogger_write_int32(false, "", GetTopTransactionId(), amsyslogger, true);
else
syslogger_write_int32(false, "", InvalidTransactionId, amsyslogger, true);
/* GPDB specific options */
syslogger_write_int32(true, "con", gp_session_id, amsyslogger, true);
syslogger_write_int32(true, "cmd", gp_command_count, amsyslogger, true);
syslogger_write_int32(false, "seg", GetQEIndex(), amsyslogger, true);
syslogger_write_int32(true, "slice", currentSliceId, amsyslogger, true);
{
DistributedTransactionId dist_trans_id;
TransactionId local_trans_id;
TransactionId subtrans_id;
GetAllTransactionXids(&dist_trans_id,
&local_trans_id,
&subtrans_id);
syslogger_write_int32(true, "dx", dist_trans_id, amsyslogger, true);
syslogger_write_int32(true, "x", local_trans_id, amsyslogger, true);
syslogger_write_int32(true, "sx", subtrans_id, amsyslogger, true);
}
/* error severity */
write_syslogger_file_string(error_severity(edata->elevel), amsyslogger, true);
/* sql state code */
write_syslogger_file_string(unpack_sql_state(edata->sqlerrcode), amsyslogger, true);
/* error message */
write_syslogger_file_string(edata->message, amsyslogger, true);
/* errdetail */
if (edata->detail_log)
write_syslogger_file_string(edata->detail_log, amsyslogger, true);
else
write_syslogger_file_string(edata->detail, amsyslogger, true);
/* errhint */
write_syslogger_file_string(edata->hint, amsyslogger, true);
/* internal query */
write_syslogger_file_string(edata->internalquery, amsyslogger, true);
/* internal query pos */
syslogger_write_int32(true, "", edata->internalpos, amsyslogger, true);
/* error context */
write_syslogger_file_string(edata->context, amsyslogger, true);
/* user query */
write_syslogger_file_string(debug_query_string, amsyslogger, true);
/* cursor pos */
syslogger_write_int32(true, "", edata->cursorpos, amsyslogger, true);
/* func name */
write_syslogger_file_string(edata->funcname, amsyslogger, true);
/* file name */
write_syslogger_file_string(edata->filename, amsyslogger, true);
/* line number */
syslogger_write_int32(true, "", edata->lineno, amsyslogger, true);
/* stack trace */
if ((edata->printstack ||
(edata->elevel >= ERROR &&
(edata->elevel == PANIC || !edata->omit_location))) &&
edata->stacktracesize > 0 &&
edata->stacktracearray != NULL)
{
append_stacktrace(NULL /*PipeProtoChunk*/, NULL /*StringInfo*/, edata->stacktracearray,
edata->stacktracesize, amsyslogger);
}
/* EOL */
if (amsyslogger)
write_syslogger_file_binary(LOG_EOL, strlen(LOG_EOL));
else
write(fileno(stderr), LOG_EOL, strlen(LOG_EOL));
}
/*
* Write error report to server's log.
*
* This is an equivalent function as send_message_to_server_log, but will write
* the error report in the format of GpPipeProtoHeader, followed by a serialized
* format of GpErrorData. The error report is sent over to the syslogger process
* through the pipe.
*
* This function is thread-safe. Here, we assume that sprintf is thread-safe.
*/
void
write_message_to_server_log(int elevel,
int sqlerrcode,
const char *message,
const char *detail,
const char *hint,
const char *query_text,
int cursorpos,
int internalpos,
const char *internalquery,
const char *context,
const char *funcname,
bool show_funcname,
const char *filename,
int lineno,
int stacktracesize,
bool omit_location,
bool send_alert,
void* const *stacktracearray,
bool printstack
)
{
PipeProtoChunk buffer;
char *data = buffer.data;
GpErrorDataFixFields fix_fields;
static uint64 log_line_number = 0;
Assert(!am_syslogger);
buffer.hdr.zero = 0;
buffer.hdr.len = 0;
buffer.hdr.pid = MyProcPid;
buffer.hdr.thid = mythread();
buffer.hdr.main_thid = mainthread();
buffer.hdr.chunk_no = 0;
buffer.hdr.is_last = 'f';
buffer.hdr.log_format = 'c';
buffer.hdr.log_line_number = log_line_number++;
buffer.hdr.is_segv_msg = 'f';
buffer.hdr.next = -1;
/* Serialize edata in the order defined in GpErrorData. */
fix_fields.session_start_time =
(MyProcPort == NULL) ? 0 : (pg_time_t)timestamptz_to_time_t(MyProcPort->SessionStartTime);
fix_fields.send_alert = send_alert ? 't' : 'f';
fix_fields.omit_location = omit_location ? 't' : 'f';
fix_fields.gp_is_primary = 't';
fix_fields.gp_session_id = gp_session_id;
fix_fields.gp_command_count = gp_command_count;
fix_fields.gp_segment_id = GetQEIndex();
fix_fields.slice_id = currentSliceId;
fix_fields.error_cursor_pos = cursorpos;
fix_fields.internal_query_pos = internalpos;
fix_fields.error_fileline = lineno;
fix_fields.top_trans_id = InvalidTransactionId;
if (MyProcPort != NULL && IsTransactionState())
fix_fields.top_trans_id = GetTopTransactionId();
GetAllTransactionXids(&(fix_fields.dist_trans_id),
&(fix_fields.local_trans_id),
&(fix_fields.subtrans_id));
Assert(buffer.hdr.len + sizeof(GpErrorDataFixFields) <= PIPE_MAX_PAYLOAD);
memcpy(data, &fix_fields, sizeof(GpErrorDataFixFields));
buffer.hdr.len += sizeof(GpErrorDataFixFields);
/* Variable-length fields */
/* username */
if (MyProcPort == NULL || MyProcPort->user_name == NULL)
append_string_to_pipe_chunk(&buffer, NULL);
else
append_string_to_pipe_chunk(&buffer, MyProcPort->user_name);
/* databasename */
if (MyProcPort == NULL || MyProcPort->database_name == NULL)
append_string_to_pipe_chunk(&buffer, NULL);
else
append_string_to_pipe_chunk(&buffer, MyProcPort->database_name);
/* remote_host */
if (MyProcPort == NULL || MyProcPort->remote_host == NULL)
append_string_to_pipe_chunk(&buffer, NULL);
else
append_string_to_pipe_chunk(&buffer, MyProcPort->remote_host);
/* remote_port */
if (MyProcPort == NULL || MyProcPort->remote_port == NULL)
append_string_to_pipe_chunk(&buffer, NULL);
else
append_string_to_pipe_chunk(&buffer, MyProcPort->remote_port);
/* error severity */
append_string_to_pipe_chunk(&buffer, error_severity(elevel));
/* sql state */
append_string_to_pipe_chunk(&buffer, unpack_sql_state(sqlerrcode));
/* error_message */
append_string_to_pipe_chunk(&buffer, message);
/* error_detail */
append_string_to_pipe_chunk(&buffer, detail);
/* error_hint */
append_string_to_pipe_chunk(&buffer, hint);
/* internal_query */
append_string_to_pipe_chunk(&buffer, internalquery);
/* error_context */
append_string_to_pipe_chunk(&buffer, context);
/* debug_query_string */
append_string_to_pipe_chunk(&buffer, query_text);
/* error_func_name */
if (show_funcname)
append_string_to_pipe_chunk(&buffer, funcname);
else
append_string_to_pipe_chunk(&buffer, NULL);
/* error_filename */
append_string_to_pipe_chunk(&buffer, filename);
/* stacktrace */
if ((printstack ||
(elevel >= ERROR &&
(elevel == PANIC || !omit_location))) &&
stacktracesize > 0 &&
stacktracearray != NULL)
{
// move stack trace to new line
append_string_to_pipe_chunk(&buffer, "Stack trace:\n");
buffer.hdr.len --;
append_stacktrace(&buffer, NULL /*StringInfo*/, stacktracearray, stacktracesize,
false /*amsyslogger*/);
}
/* Send the last chunk */
buffer.hdr.is_last = 't';
gp_write_pipe_chunk((char *)&buffer, buffer.hdr.len + PIPE_HEADER_SIZE, true);
}
/*
* Write error report to server's log
*/
static void
send_message_to_server_log(ErrorData *edata)
{
StringInfoData buf;
StringInfoData prefix;
int nc;
AssertImply(mainthread() != 0, mythread() == mainthread());
if (Log_destination & LOG_DESTINATION_STDERR)
{
if (Redirect_stderr && gp_log_format == 1)
{
if (redirection_done)
{
if (!am_syslogger)
write_message_to_server_log(edata->elevel,
edata->sqlerrcode,
edata->message,
edata->detail_log != NULL ? edata->detail_log : edata->detail,
edata->hint,
debug_query_string,
edata->cursorpos,
edata->internalpos,
edata->internalquery,
edata->context,
edata->funcname,
edata->show_funcname,
edata->filename,
edata->lineno,
edata->stacktracesize,
edata->omit_location,
edata->send_alert,
edata->stacktracearray,
edata->printstack);
else
write_syslogger_in_csv(edata, true);
}
else
{
write_syslogger_in_csv(edata, false);
}
return;
}
}
/* Format message prefix. */
initStringInfo(&buf);
log_line_prefix(&buf);
nc = buf.len;
appendStringInfo(&buf, "%s: ", error_severity(edata->elevel));
/* Save copy of prefix for subsequent lines of multi-line message. */
initStringInfo(&prefix);
appendBinaryStringInfo(&prefix, buf.data, nc);
nc = 2 + buf.len - prefix.len;
enlargeStringInfo(&prefix, nc);
memset(prefix.data+prefix.len, ' ', nc);
prefix.len += nc;
prefix.data[prefix.len] = '\0';
if (Log_error_verbosity >= PGERROR_VERBOSE &&
edata->sqlerrcode)
{
/* unpack MAKE_SQLSTATE code */
char tbuf[12];
int ssval;
int i;
ssval = edata->sqlerrcode;
for (i = 0; i < 5; i++)
{
tbuf[i] = PGUNSIXBIT(ssval);
ssval >>= 6;
}
tbuf[i] = '\0';
appendStringInfo(&buf, "(%s) ", tbuf);
}
if (edata->message)
{
char *cp = edata->message;
while (*cp <= ' ' &&
*cp > '\0')
cp++;
append_with_tabs(&buf, cp);
}
else
append_with_tabs(&buf, _("missing error text"));
if (edata->cursorpos > 0)
appendStringInfo(&buf, _(" at character %d"),
edata->cursorpos);
else if (edata->internalpos > 0)
appendStringInfo(&buf, _(" at character %d"),
edata->internalpos);
appendStringInfoChar(&buf, '\n');
if (Log_error_verbosity >= PGERROR_DEFAULT)
{
if (edata->detail)
{
appendBinaryStringInfo(&buf, prefix.data, prefix.len);
appendStringInfoString(&buf, _("DETAIL: "));
append_with_tabs(&buf, edata->detail);
appendStringInfoChar(&buf, '\n');
}
if (edata->hint)
{
log_line_prefix(&buf);
appendStringInfoString(&buf, _("HINT: "));
append_with_tabs(&buf, edata->hint);
appendStringInfoChar(&buf, '\n');
}
if (edata->internalquery)
{
log_line_prefix(&buf);
appendStringInfoString(&buf, _("QUERY: "));
append_with_tabs(&buf, edata->internalquery);
appendStringInfoChar(&buf, '\n');
}
if (edata->context)
{
log_line_prefix(&buf);
appendStringInfoString(&buf, _("CONTEXT: "));
append_with_tabs(&buf, edata->context);
appendStringInfoChar(&buf, '\n');
}
if (Log_error_verbosity >= PGERROR_VERBOSE)
{
if (edata->elevel == INFO || edata->omit_location)
{}
/* assume no newlines in funcname or filename... */
else if (edata->funcname && edata->filename)
{
appendBinaryStringInfo(&buf, prefix.data, prefix.len);
appendStringInfo(&buf, _("LOCATION: %s, %s:%d\n"),
edata->funcname, edata->filename,
edata->lineno);
}
else if (edata->filename)
{
log_line_prefix(&buf);
appendStringInfo(&buf, _("LOCATION: %s:%d\n"),
edata->filename, edata->lineno);
}
}
}
/*
* If the user wants the query that generated this error logged, do it.
*/
if (is_log_level_output(edata->elevel, log_min_error_statement) &&
debug_query_string != NULL &&
!edata->hide_stmt)
{
log_line_prefix(&buf);
appendStringInfoString(&buf, _("STATEMENT: "));
append_with_tabs(&buf, debug_query_string);
appendStringInfoChar(&buf, '\n');
}
if (edata->elevel >= ERROR &&
(edata->elevel == PANIC || !edata->omit_location) &&
edata->stacktracesize > 0 &&
edata->stacktracearray != NULL)
{
#ifndef WIN32
char **strings;
size_t i;
strings = backtrace_symbols(edata->stacktracearray, edata->stacktracesize);
if (strings != NULL)
{
for (i = 0; i < edata->stacktracesize; i++)
{
appendBinaryStringInfo(&buf, prefix.data, prefix.len);
appendStringInfo(&buf, "Traceback %d: %.200s", (int)i, strings[i]);
appendStringInfoChar(&buf, '\n');
}
free(strings);
}
#endif
}
#ifdef HAVE_SYSLOG
/* Write to syslog, if enabled */
if (Log_destination & LOG_DESTINATION_SYSLOG)
{
int syslog_level;
switch (edata->elevel)
{
case DEBUG5:
case DEBUG4:
case DEBUG3:
case DEBUG2:
case DEBUG1:
syslog_level = LOG_DEBUG;
break;
case LOG:
case COMMERROR:
case INFO:
syslog_level = LOG_INFO;
break;
case NOTICE:
case WARNING:
syslog_level = LOG_NOTICE;
break;
case ERROR:
syslog_level = LOG_WARNING;
break;
case FATAL:
syslog_level = LOG_ERR;
break;
case PANIC:
default:
syslog_level = LOG_CRIT;
break;
}
write_syslog(syslog_level, buf.data);
}
#endif /* HAVE_SYSLOG */
#ifdef WIN32
/* Write to eventlog, if enabled */
if (Log_destination & LOG_DESTINATION_EVENTLOG)
{
write_eventlog(edata->elevel, buf.data);
}
#endif /* WIN32 */
/* Write to stderr, if enabled */
if ((Log_destination & LOG_DESTINATION_STDERR) || whereToSendOutput == DestDebug)
{
#ifdef WIN32
/*
* In a win32 service environment, there is no usable stderr. Capture
* anything going there and write it to the eventlog instead.
*
* If stderr redirection is active, it was OK to write to stderr above
* because that's really a pipe to the syslogger process.
*/
if (pgwin32_is_service() && (!redirection_done || am_syslogger) )
write_eventlog(edata->elevel, buf.data);
else
#endif
/* only use the chunking protocol if we know the syslogger should
* be catching stderr output, and we are not ourselves the
* syslogger. Otherwise, go directly to stderr.
*/
if (redirection_done && !am_syslogger)
write_pipe_chunks(buf.data, buf.len);
else
write(fileno(stderr), buf.data, buf.len);
}
/* If in the syslogger process, try to write messages direct to file */
if (am_syslogger)
write_syslogger_file_binary(buf.data, buf.len);
pfree(prefix.data);
pfree(buf.data);
}
/*
* Send data to the syslogger using the chunked protocol
*/
static void
write_pipe_chunks(char *data, int len)
{
PipeProtoChunk p;
int fd = fileno(stderr);
Assert(len > 0);
p.hdr.zero = 0;
p.hdr.pid = MyProcPid;
p.hdr.thid = mythread();
p.hdr.main_thid = mainthread();
p.hdr.chunk_no = 0;
p.hdr.log_format = 't';
p.hdr.is_segv_msg = 'f';
p.hdr.next = -1;
/* write all but the last chunk */
while (len > PIPE_MAX_PAYLOAD)
{
p.hdr.is_last = 'f';
p.hdr.len = PIPE_MAX_PAYLOAD;
memcpy(p.data, data, PIPE_MAX_PAYLOAD);
#ifdef USE_ASSERT_CHECKING
Assert(p.hdr.zero == 0);
Assert(p.hdr.pid != 0);
Assert(p.hdr.thid != 0);
#endif
write(fd, &p, PIPE_CHUNK_SIZE);
data += PIPE_MAX_PAYLOAD;
len -= PIPE_MAX_PAYLOAD;
++p.hdr.chunk_no;
}
/* write the last chunk */
p.hdr.is_last = 't';
p.hdr.len = len;
#ifdef USE_ASSERT_CHECKING
Assert(p.hdr.zero == 0);
Assert(p.hdr.pid != 0);
Assert(p.hdr.thid != 0);
Assert(PIPE_HEADER_SIZE + len <= PIPE_CHUNK_SIZE);
#endif
memcpy(p.data, data, len);
write(fd, &p, PIPE_HEADER_SIZE + len);
}
/*
* Append a text string to the error report being built for the client.
*
* This is ordinarily identical to pq_sendstring(), but if we are in
* error recursion trouble we skip encoding conversion, because of the
* possibility that the problem is a failure in the encoding conversion
* subsystem itself. Code elsewhere should ensure that the passed-in
* strings will be plain 7-bit ASCII, and thus not in need of conversion,
* in such cases. (In particular, we disable localization of error messages
* to help ensure that's true.)
*/
static void
err_sendstring(StringInfo buf, const char *str)
{
if (in_error_recursion_trouble())
pq_send_ascii_string(buf, str);
else
pq_sendstring(buf, str);
}
/*
* Write error report to client
*/
static void
send_message_to_frontend(ErrorData *edata)
{
StringInfoData msgbuf;
/* 'N' (Notice) is for nonfatal conditions, 'E' is for errors */
pq_beginmessage(&msgbuf, (edata->elevel < ERROR) ? 'N' : 'E');
if (PG_PROTOCOL_MAJOR(FrontendProtocol) >= 3)
{
/* New style with separate fields */
char tbuf[12];
int ssval;
int i;
pq_sendbyte(&msgbuf, PG_DIAG_SEVERITY);
err_sendstring(&msgbuf, error_severity(edata->elevel));
/* unpack MAKE_SQLSTATE code */
ssval = edata->sqlerrcode;
for (i = 0; i < 5; i++)
{
tbuf[i] = PGUNSIXBIT(ssval);
ssval >>= 6;
}
tbuf[i] = '\0';
pq_sendbyte(&msgbuf, PG_DIAG_SQLSTATE);
err_sendstring(&msgbuf, tbuf);
/* M field is required per protocol, so always send something */
pq_sendbyte(&msgbuf, PG_DIAG_MESSAGE_PRIMARY);
if (edata->message)
err_sendstring(&msgbuf, edata->message);
else
err_sendstring(&msgbuf, _("missing error text"));
if (edata->detail)
{
pq_sendbyte(&msgbuf, PG_DIAG_MESSAGE_DETAIL);
err_sendstring(&msgbuf, edata->detail);
}
/* detail_log is intentionally not used here */
if (edata->hint)
{
pq_sendbyte(&msgbuf, PG_DIAG_MESSAGE_HINT);
err_sendstring(&msgbuf, edata->hint);
}
if (edata->context)
{
pq_sendbyte(&msgbuf, PG_DIAG_CONTEXT);
err_sendstring(&msgbuf, edata->context);
}
if (edata->cursorpos > 0)
{
snprintf(tbuf, sizeof(tbuf), "%d", edata->cursorpos);
pq_sendbyte(&msgbuf, PG_DIAG_STATEMENT_POSITION);
err_sendstring(&msgbuf, tbuf);
}
if (edata->internalpos > 0)
{
snprintf(tbuf, sizeof(tbuf), "%d", edata->internalpos);
pq_sendbyte(&msgbuf, PG_DIAG_INTERNAL_POSITION);
err_sendstring(&msgbuf, tbuf);
}
if (edata->internalquery)
{
pq_sendbyte(&msgbuf, PG_DIAG_INTERNAL_QUERY);
err_sendstring(&msgbuf, edata->internalquery);
}
if (edata->filename)
{
pq_sendbyte(&msgbuf, PG_DIAG_SOURCE_FILE);
err_sendstring(&msgbuf, edata->filename);
}
if (edata->lineno > 0)
{
snprintf(tbuf, sizeof(tbuf), "%d", edata->lineno);
pq_sendbyte(&msgbuf, PG_DIAG_SOURCE_LINE);
err_sendstring(&msgbuf, tbuf);
}
if (edata->funcname)
{
pq_sendbyte(&msgbuf, PG_DIAG_SOURCE_FUNCTION);
err_sendstring(&msgbuf, edata->funcname);
}
pq_sendbyte(&msgbuf, '\0'); /* terminator */
}
else
{
/* Old style --- gin up a backwards-compatible message */
StringInfoData buf;
initStringInfo(&buf);
appendStringInfo(&buf, "%s: ", error_severity(edata->elevel));
if (edata->show_funcname && edata->funcname)
appendStringInfo(&buf, "%s: ", edata->funcname);
if (edata->message)
appendStringInfoString(&buf, edata->message);
else
appendStringInfoString(&buf, _("missing error text"));
if (edata->cursorpos > 0)
appendStringInfo(&buf, _(" at character %d"),
edata->cursorpos);
else if (edata->internalpos > 0)
appendStringInfo(&buf, _(" at character %d"),
edata->internalpos);
appendStringInfoChar(&buf, '\n');
err_sendstring(&msgbuf, buf.data);
pfree(buf.data);
}
pq_endmessage(&msgbuf);
/*
* This flush is normally not necessary, since postgres.c will flush out
* waiting data when control returns to the main loop. But it seems best
* to leave it here, so that the client has some clue what happened if the
* backend dies before getting back to the main loop ... error/notice
* messages should not be a performance-critical path anyway, so an extra
* flush won't hurt much ...
*/
pq_flush();
}
/*
* Support routines for formatting error messages.
*/
/*
* expand_fmt_string --- process special format codes in a format string
*
* We must replace %m with the appropriate strerror string, since vsnprintf
* won't know what to do with it.
*
* CDB: "%m%s" expands to the strerror string along with the decimal errno,
* embedding the %s in the message. Note there is no space between the
* %m and %s. For example,
* errmsg("System error: %m%s; retry in %d sec", "bind", 15)
* produces something like this (assuming errno == EADDRINUSE):
* "System error: Address already in use (bind errno 98); retry in 15 sec"
*
* The result is a palloc'd string.
*/
static char *
expand_fmt_string(const char *fmt, ErrorData *edata)
{
StringInfoData buf;
const char *cp;
initStringInfo(&buf);
for (cp = fmt; *cp; cp++)
{
if (cp[0] == '%' && cp[1] != '\0')
{
cp++;
if (*cp == 'm')
{
/*
* Replace %m by system error string. If there are any %'s in
* the string, we'd better double them so that vsnprintf won't
* misinterpret.
*/
const char *cp2;
cp2 = useful_strerror(edata->saved_errno);
for (; *cp2; cp2++)
{
if (*cp2 == '%')
appendStringInfoCharMacro(&buf, '%');
appendStringInfoCharMacro(&buf, *cp2);
}
if (cp[1] == '%' && cp[2] == 's') /*CDB: "%m%s" */
{
appendStringInfo(&buf, " (%%s errno %d)", edata->saved_errno);
cp += 2;
}
}
else
{
/* copy % and next char --- this avoids trouble with %%m */
appendStringInfoCharMacro(&buf, '%');
appendStringInfoCharMacro(&buf, *cp);
}
}
else
appendStringInfoCharMacro(&buf, *cp);
}
return buf.data;
}
/*
* A slightly cleaned-up version of strerror()
*/
static const char *
useful_strerror(int errnum)
{
/* this buffer is only used if errno has a bogus value */
static char errorstr_buf[48];
const char *str;
#ifdef WIN32
/* Winsock error code range, per WinError.h */
if (errnum >= 10000 && errnum <= 11999)
return pgwin32_socket_strerror(errnum);
#endif
str = strerror(errnum);
/*
* Some strerror()s return an empty string for out-of-range errno. This is
* ANSI C spec compliant, but not exactly useful.
*/
if (str == NULL || *str == '\0')
{
snprintf(errorstr_buf, sizeof(errorstr_buf),
/*------
translator: This string will be truncated at 47
characters expanded. */
_("operating system error %d"), errnum);
str = errorstr_buf;
}
return str;
}
/*
* error_severity --- get localized string representing elevel
*/
static const char *
error_severity(int elevel)
{
const char *prefix;
switch (elevel)
{
case DEBUG1:
prefix = _("DEBUG1");
break;
case DEBUG2:
prefix = _("DEBUG2");
break;
case DEBUG3:
prefix = _("DEBUG3");
break;
case DEBUG4:
prefix = _("DEBUG4");
break;
case DEBUG5:
prefix = _("DEBUG5");
break;
case LOG:
case COMMERROR:
prefix = _("LOG");
break;
case INFO:
prefix = _("INFO");
break;
case NOTICE:
prefix = _("NOTICE");
break;
case WARNING:
prefix = _("WARNING");
break;
case ERROR:
prefix = _("ERROR");
break;
case FATAL:
prefix = _("FATAL");
break;
case PANIC:
prefix = _("PANIC");
break;
default:
prefix = "???";
break;
}
return prefix;
}
/*
* append_with_tabs
*
* Append the string to the StringInfo buffer, inserting a tab after any
* newline.
*/
static void
append_with_tabs(StringInfo buf, const char *str)
{
char ch;
while ((ch = *str++) != '\0')
{
appendStringInfoCharMacro(buf, ch);
if (ch == '\n')
appendStringInfoCharMacro(buf, '\t');
}
}
/*
* Write errors to stderr (or by equal means when stderr is
* not available). Used before ereport/elog can be used
* safely (memory context, GUC load etc)
*/
void
write_stderr(const char *fmt,...)
{
va_list ap;
fmt = _(fmt);
va_start(ap, fmt);
if (Redirect_stderr && gp_log_format == 1)
{
char errbuf[2048]; /* Arbitrary size? */
vsnprintf(errbuf, sizeof(errbuf), fmt, ap);
if (!am_syslogger)
{
/* Write the message in the CSV format */
write_message_to_server_log(LOG,
0,
errbuf,
NULL,
NULL,
NULL,
0,
0,
NULL,
NULL,
NULL,
false,
NULL,
0,
0,
true,
false,
NULL,
false);
}
else
{
ErrorData edata;
memset(&edata, 0, sizeof(ErrorData));
edata.elevel = LOG;
edata.message = errbuf;
edata.omit_location = true;
if (redirection_done)
write_syslogger_in_csv(&edata, true);
else
write_syslogger_in_csv(&edata, false);
}
va_end(ap);
return;
}
#ifndef WIN32
/* On Unix, we just fprintf to stderr */
vfprintf(stderr, fmt, ap);
fflush(stderr);
#else
/*
* On Win32, we print to stderr if running on a console, or write to
* eventlog if running as a service
*/
if (pgwin32_is_service()) /* Running as a service */
{
char errbuf[2048]; /* Arbitrary size? */
vsnprintf(errbuf, sizeof(errbuf), fmt, ap);
write_eventlog(EVENTLOG_ERROR_TYPE, errbuf);
}
else
{
/* Not running as service, write to stderr */
vfprintf(stderr, fmt, ap);
fflush(stderr);
}
#endif
va_end(ap);
}
/*
* is_log_level_output -- is elevel logically >= log_min_level?
*
* We use this for tests that should consider LOG to sort out-of-order,
* between ERROR and FATAL. Generally this is the right thing for testing
* whether a message should go to the postmaster log, whereas a simple >=
* test is correct for testing whether the message should go to the client.
*/
static bool
is_log_level_output(int elevel, int log_min_level)
{
if (elevel == LOG || elevel == COMMERROR)
{
if (log_min_level == LOG || log_min_level <= ERROR)
return true;
}
else if (log_min_level == LOG)
{
/* elevel != LOG */
if (elevel >= FATAL)
return true;
}
/* Neither is LOG */
else if (elevel >= log_min_level)
return true;
return false;
}
void
pending_for_debug(int timeout)
{
int seconds_to_linger = timeout;
int seconds_lingered = 0;
while (seconds_lingered < seconds_to_linger)
{
int seconds_left = seconds_to_linger - seconds_lingered;
int minutes_left = seconds_left / 60;
int setproctitle_seconds = (minutes_left <= 1) ? 5 :
(minutes_left <= 5) ? 30 : 60;
int sleep_seconds;
char buf[50];
/* Update 'ps' display. */
snprintf(buf, sizeof(buf)-1,
"wait for debug, continue in %dm %ds", minutes_left, seconds_left - minutes_left * 60);
set_ps_display(buf, true);
/* Sleep. */
sleep_seconds = Min(seconds_left, setproctitle_seconds);
pg_usleep(sleep_seconds * 1000000L);
seconds_lingered += sleep_seconds;
}
}
/*
* elog_debug_linger
*/
void
elog_debug_linger(ErrorData *edata)
{
int seconds_to_linger = gp_debug_linger;
int seconds_lingered = 0;
/* Don't linger again in the event of another error. */
gp_debug_linger = 0;
/* A word of explanation to the user... */
errhint("%s%sProcess %d will wait for gp_debug_linger=%d seconds before termination.\n"
"Note that its locks and other resources will not be released until then.",
edata->hint ? edata->hint : "",
edata->hint ? "\n" : "",
MyProcPid,
seconds_to_linger);
/* Log the error and notify the client. */
EmitErrorReport();
fflush(stdout);
fflush(stderr);
/* Terminate the client connection. */
pq_comm_close_fatal();
while (seconds_lingered < seconds_to_linger)
{
int seconds_left = seconds_to_linger - seconds_lingered;
int minutes_left = seconds_left / 60;
int setproctitle_seconds = (minutes_left <= 1) ? 5
: (minutes_left <= 5) ? 30
: 60;
int sleep_seconds;
char buf[50];
/* Update 'ps' display. */
snprintf(buf, sizeof(buf)-1,
"error exit in %dm %ds",
minutes_left,
seconds_left - minutes_left * 60);
set_ps_display(buf, true);
/* Sleep. */
sleep_seconds = Min(seconds_left, setproctitle_seconds);
pg_usleep(sleep_seconds * 1000000L);
seconds_lingered += sleep_seconds;
}
} /* elog_debug_linger */
/*
* gp_elog
*
* This externally callable function allows a user or application to insert records into the log.
* Only superusers are allowed to call this function due to the potential for abuse.
*
* The function takes two arguments, the first is the message to insert into the log (type text).
* The second is optional, type bool, that specifies if we should send an alert after inserting
* the message into the log.
*
*
*/
Datum
gp_elog(PG_FUNCTION_ARGS)
{
ErrorData edata;
char *errormsg;
const char *save_debug_query;
bool sendalert = false;
/* Validate input arguments */
if (PG_NARGS() != 1 && PG_NARGS() != 2)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("gp_elog(): called with %hd arguments",
PG_NARGS())));
if (PG_ARGISNULL(0))
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("gp_elog(): called with null arguments")));
if (PG_NARGS() == 2 && PG_ARGISNULL(1))
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("gp_elog(): called with null arguments")));
/* Must be super user */
if (!superuser())
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("permission denied"),
errhint("gp_elog(): requires superuser privileges.")));
errormsg = text_to_cstring(PG_GETARG_TEXT_PP(0));
/* text_to_cstring is guaranteed to not return a NULL, so we don't need to check */
if (PG_NARGS() > 1)
sendalert = PG_GETARG_BOOL(1);
memset(&edata, 0, sizeof(edata));
edata.elevel = LOG;
edata.output_to_server = true;
edata.output_to_client = false;
edata.show_funcname = false;
edata.omit_location = true;
edata.hide_stmt = true;
edata.internalquery = "";
edata.send_alert = sendalert;
edata.message = (char *)errormsg; /* edata.message really should be const char * */
edata.sqlerrcode = MAKE_SQLSTATE('X','X', '1','0','0'); /* use a special SQLSTATE to make it easy to search the log */
/* We don't need to log the SQL statement as part of this, it's just noise */
save_debug_query = debug_query_string;
debug_query_string = "";
send_message_to_server_log(&edata);
debug_query_string = save_debug_query;
pfree(errormsg);
PG_RETURN_VOID();
}
#if defined(pg_on_solaris)
/* Use the Solaris stack-walker to build a (sort of) glibc compatible
* backtrace */
#include <ucontext.h>
struct frame_info
{
int frame;
int max;
void **addr;
};
static int
get_stack_frames(uintptr_t fp, int sig __attribute__((unused)), void *context)
{
struct frame_info *fi = (struct frame_info *)context;
int cur = fi->frame;
/* stop filling in after we've hit the limit */
if (cur == fi->max)
return 0;
fi->addr[cur] = (void *)fp;
fi->frame++;
return 0;
}
size_t
backtrace(void **buffer, int size)
{
ucontext_t uc;
struct frame_info fi;
if (getcontext(&uc) != 0)
return 0;
fi.addr = buffer;
fi.max = size;
fi.frame = 0;
if (walkcontext(&uc, get_stack_frames, &fi) != 0)
return 0;
return fi.frame;
}
char **
backtrace_symbols(void *const *buffer, int size)
{
char **result;
size_t bufsize;
int i;
bufsize = size * sizeof(char *);
/* Calculate exact size */
for (i=0; i < size; i++)
{
Dl_info dli;
if (dladdr(buffer[i], &dli) == 0)
{
bufsize += snprintf(NULL, 0, "%p: <not found>", buffer[i]) + 1;
}
else
{
bufsize += snprintf(NULL, 0, "%p: %s %s+0x%x", buffer[i],
dli.dli_fname, dli.dli_sname,
(int)((char *)(buffer[i]) - (char *)(dli.dli_saddr))) + 1;
}
}
result = malloc(bufsize);
if(!result)
ereport(ERROR, (errcode(ERRCODE_OUT_OF_MEMORY),
errmsg("Failed to get backtrace symbol: out of memory")));
{
int n;
char *cur = (char *)(result + size);
memset(result, 0, bufsize);
for (i=0; i < size; i++)
{
Dl_info dli;
if (dladdr(buffer[i], &dli) == 0)
{
n = sprintf(cur, "%p: <not found>", buffer[i]);
}
else
{
n = sprintf(cur, "%p: %s %s+0x%x", buffer[i],
dli.dli_fname, dli.dli_sname,
(int)((char *)(buffer[i]) - (char *)(dli.dli_saddr)));
}
result[i] = cur;
cur += n + 1;
}
}
return result;
}
#endif
void
debug_backtrace(void)
{
#ifndef WIN32
int stacktracesize;
void *stacktracearray[30];
stacktracesize = backtrace(stacktracearray, 30);
append_stacktrace(NULL /*PipeProtoChunk*/, NULL /*StringInfo*/, stacktracearray, stacktracesize,
false/*amsyslogger*/);
#endif
}
/*
* Unwind stack up to a given depth and store frame addresses to passed array;
* return stack depth;
*/
uint32 gp_backtrace(void **stackAddresses, uint32 maxStackDepth)
{
#if defined(__i386) || defined(__x86_64__)
Assert(stack_base_ptr != NULL);
/* get base pointer of current frame */
uint64 framePtrValue = 0;
GET_FRAME_POINTER(framePtrValue);
uint32 depth = 0;
void **pFramePtr = (void**) GET_PTR_FROM_VALUE(framePtrValue);
/* check if the frame pointer is valid */
if (pFramePtr != NULL && (void *) &depth < (void *) pFramePtr)
{
/* consider the first maxStackDepth frames only, below the stack base pointer */
for (depth = 0; depth < maxStackDepth; depth++)
{
/* check if next frame is within stack */
if (pFramePtr == NULL ||
(void *) pFramePtr > *pFramePtr ||
(void *) stack_base_ptr < *pFramePtr)
{
break;
}
/* get return address (one above the frame pointer) */
const uintptr_t *returnAddr = (uintptr_t *)(pFramePtr + 1);
/* store return address */
stackAddresses[depth] = (void *) *returnAddr;
/* move to next frame */
pFramePtr = (void**)*pFramePtr;
}
}
else
{
depth = backtrace(stackAddresses, maxStackDepth);
}
Assert(depth > 0);
return depth;
#else
return backtrace(stackAddresses, maxStackDepth);
#endif
}
/*
* Build stack trace
*/
char *gp_stacktrace(void **stackAddresses, uint32 stackDepth)
{
StringInfoData append;
initStringInfo(&append);
#ifdef WIN32
appendStringInfoString(&append, "stack trace is not available for this platform");
#else
append_stacktrace(NULL /*PipeProtoChunk*/, &append, stackAddresses, stackDepth,
false/*amsyslogger*/);
#endif
/* we may fail to retrieve stack on opt build */
if (0 == append.len)
{
appendStringInfoString(&append, "failed to retrieve stack");
}
return append.data;
}
/*
* SignalName
* Convert a SEGV/BUS/ILL to name.
*/
const char *
SegvBusIllName(int signal)
{
Assert(signal == SIGILL ||
signal == SIGSEGV ||
signal == SIGBUS);
switch (signal)
{
#ifdef SIGILL
case SIGILL:
return "SIGILL";
#endif
#ifdef SIGSEGV
case SIGSEGV:
return "SIGSEGV";
#endif
#ifdef SIGBUS
case SIGBUS:
return "SIGBUS";
#endif
}
return NULL;
}
/*
* SafeHandlerForSegvBusIll
* Async-safe signal handler for SEGV/BUS/ILL.
*
* This function simple collects the stack addresses and some process information
* and write them to the pipe.
*/
static void
SafeHandlerForSegvBusIll(char *processName, SIGNAL_ARGS)
{
PG_SETMASK(&BlockSig);
/* Unblock SEGV/BUS/ILL signals, and set them to their default settings. */
#ifdef SIGILL
pqsignal(SIGILL, SIG_DFL);
#endif
#ifdef SIGSEGV
pqsignal(SIGSEGV, SIG_DFL);
#endif
#ifdef SIGBUS
pqsignal(SIGBUS, SIG_DFL);
#endif
PipeProtoChunk buffer;
buffer.hdr.zero = 0;
buffer.hdr.len = 0;
buffer.hdr.pid = MyProcPid;
/*
* mythread() are not really async-safe, but syslogger requires this value
* to be set as part of an identifier of a chunk. We create a fake value here to
* satisfy the condition of a valid chunk. But in the syslogger, we reset its
* value to 0.
*/
buffer.hdr.thid = FIXED_THREAD_ID;
buffer.hdr.main_thid = mainthread();
buffer.hdr.chunk_no = 0;
buffer.hdr.is_last = 't';
buffer.hdr.log_format = 'c';
buffer.hdr.is_segv_msg = 't';
buffer.hdr.log_line_number = 0;
buffer.hdr.next = -1;
char *data = buffer.data;
GpSegvErrorData *errorData = (GpSegvErrorData *)data;
errorData->session_start_time = 0;
if (MyProcPort)
{
errorData->session_start_time = (pg_time_t)timestamptz_to_time_t(MyProcPort->SessionStartTime);
}
errorData->gp_session_id = gp_session_id;
errorData->gp_command_count = gp_command_count;
errorData->gp_segment_id = GetQEIndex();
errorData->slice_id = currentSliceId;
errorData->signal_num = (int32)postgres_signal_arg;
errorData->frame_depth = 0;
/*
* Compute how many frame addresses we are able to send in a single chunk. The total space
* that is available for frame addresses is (PIPE_MAX_PAYLOAD - MAXALIGN(sizeof(GpSegvErrorData))).
*/
int frameDepth = (PIPE_MAX_PAYLOAD - MAXALIGN(sizeof(GpSegvErrorData))) / sizeof(void *);
Assert(frameDepth > 0);
void *stackAddressArray = data + MAXALIGN(sizeof(GpSegvErrorData));
void **stackAddresses = stackAddressArray;
errorData->frame_depth = gp_backtrace(stackAddresses, frameDepth);
buffer.hdr.len =
MAXALIGN(sizeof(GpSegvErrorData)) +
errorData->frame_depth * sizeof(void *);
gp_write_pipe_chunk((char *)&buffer, buffer.hdr.len + PIPE_HEADER_SIZE, false);
/* re-raise the signal to OS */
raise(postgres_signal_arg);
}
/**
* Unsafe handler that can be called from a specific processes handler for
* SIGILL, SIGSEGV, and SIGBUS
*
* This handler logs the error and, if gp_reraise_signal is set, reraises
* the signal so the operating system can create a core file.
*
* Control will never return from this signal handler
*
* MUST be called from the main thread, if this process uses threads.
*/
static void
UnsafeHandlerForSegvBusIll(char *processName, SIGNAL_ARGS)
{
int save_errno = errno;
PG_SETMASK(&BlockSig);
#ifdef SIGILL
pqsignal(SIGILL, SIG_DFL);
#endif
#ifdef SIGSEGV
pqsignal(SIGSEGV, SIG_DFL);
#endif
#ifdef SIGBUS
pqsignal(SIGBUS, SIG_DFL);
#endif
write_stderr("\nUnexpected internal error: %s %d received signal %s\n"
"(interrupt holdoff count %d, critical section count %d)\n",
processName, MyProcPid, SegvBusIllName(postgres_signal_arg),
InterruptHoldoffCount, CritSectionCount);
errno = save_errno;
ereport(FATAL, (errcode(ERRCODE_INTERNAL_ERROR),
errFatalReturn(gp_reraise_signal),
errOmitLocation(true),
errmsg("Unexpected internal error: %s received signal %s",
processName, SegvBusIllName(postgres_signal_arg)),
errdetail("(interrupt holdoff count %d, critical section count %d)",
InterruptHoldoffCount, CritSectionCount)));
/**
* We reach this point only if gp_reraise_signal is on.
*/
Assert(gp_reraise_signal);
if (CritSectionCount == 0)
{
/* Some filerep processes may not set MyProc. */
if (MyProc != NULL)
{
MyProc->postmasterResetRequired = false;
}
/* Clean up shared memory by calling callback functions. Do not exit, though. We need to raise signal to get a core. */
proc_exit_prepare(0);
AssertImply(MyProc != NULL, !MyProc->postmasterResetRequired && "postmaster reset required flag changed unexpectedly");
}
/* CDB: Control returns here only if gp_reraise_signal is true. */
raise(postgres_signal_arg);
abort(); /* not reached */
} /* CdbProgramErrorHandler */
/*
* StandardHandlerForSigillSigsegvSigbus_OnMainThread
* Signal handlers for SEGV/BUS/ILL.
*/
void
StandardHandlerForSigillSigsegvSigbus_OnMainThread(char * processName, SIGNAL_ARGS)
{
if (gp_crash_handler_async)
{
SafeHandlerForSegvBusIll(processName, postgres_signal_arg);
}
else
{
UnsafeHandlerForSegvBusIll(processName, postgres_signal_arg);
}
}
/* EOF */