| /* |
| * 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 */ |