Files
openGauss-server/src/common/backend/utils/error/elog.cpp
2023-09-12 10:22:33 +08:00

6358 lines
230 KiB
C++

/* -------------------------------------------------------------------------
*
* elog.c
* error logging and reporting
*
* Because of the extremely high rate at which log messages can be generated,
* we need to be mindful of the performance cost of obtaining any information
* that may be logged. Also, it's important to keep in mind that this code may
* get called from within an aborted transaction, in which case operations
* such as syscache lookups are unsafe.
*
* 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) 1996-2012, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
* Portions Copyright (c) 2021, openGauss Contributors
*
*
* IDENTIFICATION
* src/backend/utils/error/elog.c
*
* -------------------------------------------------------------------------
*/
#include "postgres.h"
#include "knl/knl_variable.h"
#include <fcntl.h>
#ifdef HAVE_SYSLOG
#include <syslog.h>
#endif
#include "nodes/parsenodes.h"
#include "access/transam.h"
#include "access/xact.h"
#include "libpq/libpq.h"
#include "libpq/pqformat.h"
#include "mb/pg_wchar.h"
#include "miscadmin.h"
#include "instruments/instr_slow_query_log.h"
#include "parser/gramparse.h"
#include "parser/parser.h"
#include "postmaster/postmaster.h"
#include "postmaster/syslogger.h"
#include "storage/ipc.h"
#include "storage/proc.h"
#include "tcop/tcopprot.h"
#include "utils/be_module.h"
#include "utils/guc.h"
#include "utils/memutils.h"
#include "utils/ps_status.h"
#include "utils/selfuncs.h"
#include "auditfuncs.h"
#include "utils/elog.h"
#ifdef PGXC
#include "pgxc/pgxc.h"
#include "pgxc/execRemote.h"
#endif
#include "executor/exec/execStream.h"
#include "executor/executor.h"
#include "workload/workload.h"
#include "../bin/gsqlerr/errmsg.h"
#include "optimizer/randomplan.h"
#include <execinfo.h>
#include "tcop/stmt_retry.h"
#include "replication/walsender.h"
#include "nodes/pg_list.h"
#include "utils/builtins.h"
#include "funcapi.h"
#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)));
#ifndef ENABLE_LLT
extern void clean_ec_conn();
extern void delete_ec_ctrl();
#endif
#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. Solaris and sysklogd truncate the final message
* at 1024 bytes, so this value leaves 124 bytes for those prefixes. (Most
* other syslog implementations seem to have limits of 2KB or so.)
*/
#ifndef PG_SYSLOG_LIMIT
#define PG_SYSLOG_LIMIT 900
#endif
static void write_syslog(int level, const char* line);
#endif
static void write_console(const char* line, int len);
#ifdef WIN32
extern THR_LOCAL char* g_instance.attr.attr_common.event_source;
static void write_eventlog(int level, const char* line, int len);
#endif
static const int CREATE_ALTER_SUBSCRIPTION = 16;
static const int CREATE_ALTER_USERMAPPING = 18;
static const int GRANT_USAGE = 19;
/* Macro for checking t_thrd.log_cxt.errordata_stack_depth is reasonable */
#define CHECK_STACK_DEPTH() \
do { \
if (t_thrd.log_cxt.errordata_stack_depth < 0) { \
t_thrd.log_cxt.errordata_stack_depth = -1; \
ereport(ERROR, (errmsg_internal("errstart was not called"))); \
} \
} while (0)
static void log_line_prefix(StringInfo buf, ErrorData* edata);
static void send_message_to_server_log(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, int dest);
static void write_csvlog(ErrorData* edata);
static void setup_formatted_log_time(void);
static void setup_formatted_start_time(void);
extern void send_only_message_to_frontend(const char* message, bool is_putline);
static char* mask_Password_internal(const char* query_string);
static void truncate_identified_by(char* query_string, int query_len);
static char* mask_execute_direct_cmd(const char* query_string);
static bool is_execute_cmd(const char* query_string);
static void tolower_func(char *convert_query);
static void mask_espaced_character(char* source_str);
static void eraseSingleQuotes(char* query_string);
static int output_backtrace_to_log(StringInfoData* pOutBuf);
static void write_asp_chunks(char *data, int len, bool end);
static void write_asplog(char *data, int len, bool end);
const char* password_mask = "********";
#define MASK_OBS_PATH() \
do { \
char* childStmt = mask_funcs3_parameters(yylval.str); \
if (childStmt != NULL) { \
if (mask_string == NULL) { \
mask_string = MemoryContextStrdup( \
SESS_GET_MEM_CXT_GROUP(MEMORY_CONTEXT_SECURITY), query_string); \
vol_mask_string = mask_string; \
} \
if (unlikely(yyextra.literallen != (int)strlen(childStmt))) { \
ereport(ERROR, \
(errcode(ERRCODE_SYNTAX_ERROR), \
errmsg("parse error on statement %s.", childStmt))); \
} \
rc = memcpy_s(mask_string + yylloc + 1, yyextra.literallen, childStmt, yyextra.literallen); \
securec_check(rc, "\0", "\0"); \
rc = memset_s(childStmt, yyextra.literallen, 0, yyextra.literallen); \
securec_check(rc, "", ""); \
pfree(childStmt); \
} \
} while (0)
/*
* 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 (t_thrd.log_cxt.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
}
/*
* 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 = NULL;
bool output_to_server = false;
bool output_to_client = false;
int i = 0;
bool verbose = false;
#ifdef ENABLE_UT
if (t_thrd.log_cxt.disable_log_output)
return false;
#endif
/*
* 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 && u_sess != NULL) {
/*
* If we are inside a critical section, all errors become PANIC
* errors. See miscadmin.h.
*/
if (t_thrd.int_cxt.CritSectionCount > 0)
elevel = PANIC;
/* error accurs, cannot just release slot to pool */
u_sess->libpq_cxt.HasErrorAccurs = true;
/*
* 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. u_sess->attr.attr_common.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 (t_thrd.log_cxt.PG_exception_stack == NULL ||
t_thrd.proc_cxt.proc_exit_inprogress ||
t_thrd.xact_cxt.executeSubxactUndo)
{
elevel = FATAL;
}
if (u_sess->attr.attr_common.ExitOnAnyError && !AmPostmasterProcess()) {
/*
* The following processes rely on u_sess->attr.attr_common.ExitOnAnyError to terminate successfully,
* during which panic is not expected.
*/
if (AmCheckpointerProcess() || AmBackgroundWriterProcess() || AmWalReceiverWriterProcess() ||
AmDataReceiverWriterProcess() || AmWLMArbiterProcess())
elevel = FATAL;
else
elevel = PANIC;
}
if (u_sess->utils_cxt.test_err_type >= 3) {
int save_type = u_sess->utils_cxt.test_err_type;
u_sess->utils_cxt.test_err_type = 0;
force_backtrace_messages = false;
ereport(ERROR, (errmsg_internal("ERR CONTAINS ERR, %d", save_type)));
}
}
/*
* 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 <= t_thrd.log_cxt.errordata_stack_depth; i++)
elevel = Max(elevel, t_thrd.log_cxt.errordata[i].elevel);
if (elevel == FATAL && (t_thrd.role == JOB_WORKER || t_thrd.role == DMS_WORKER)) {
elevel = ERROR;
}
}
if (u_sess == NULL && (g_instance.role == VUNKNOWN || t_thrd.role == MASTER_THREAD)) {
// there out of memory in startup, we exit directly.
/* Ooops, hard crash time; very little we can do safely here */
write_stderr("error occurred at %s:%d before error message processing is available\n"
" u_sess is NULL! gaussdb is exit now.\n",
filename ? filename : "(unknown file)",
lineno);
_exit(-1);
}
/*
* 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 (t_thrd.postgres_cxt.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 (u_sess && u_sess->ClientAuthInProgress)
output_to_client = (elevel >= ERROR);
else
output_to_client = (elevel >= client_min_messages || elevel == INFO);
}
/* send to client for NOTICE messages in Stream thread */
if (StreamThreadAmI() && elevel == NOTICE)
output_to_client = true;
#ifdef ENABLE_QUNIT
if (u_sess->utils_cxt.qunit_case_number != 0 && elevel >= WARNING)
output_to_client = true;
#endif
if (VERBOSEMESSAGE == elevel) {
output_to_client = true;
verbose = true;
/* for CN elevel is restored to INFO for the coming opetions. */
if (IS_PGXC_COORDINATOR)
elevel = INFO;
}
if ((AmWLMWorkerProcess() || AmWLMMonitorProcess() || AmWLMArbiterProcess() || AmCPMonitorProcess()) &&
elevel >= ERROR) {
output_to_client = false;
}
/* Skip processing effort if non-error message will not be output */
if (elevel < ERROR && !output_to_server && !output_to_client)
return false;
/*
* We need to do some actual work. Make sure that memory context
* initialization has finished, else we can't do anything useful.
*/
if (ErrorContext == NULL) {
/* Ooops, hard crash time; very little we can do safely here */
write_stderr("error occurred at %s:%d before error message processing is available\n"
" ERRORContext is NULL now! Thread is exiting.\n",
filename ? filename : "(unknown file)",
lineno);
/*
* Libcomm permanent thread must be not exit,
* don't allow to call ereport in libcomm thread, abort for generating core file.
* In other cases, restart process now.
*/
if (t_thrd.comm_cxt.LibcommThreadType != LIBCOMM_NONE)
abort();
if (!IsPostmasterEnvironment || t_thrd.proc_cxt.MyProcPid == PostmasterPid) {
write_stderr("Gaussdb exit code is 2.\n");
pg_usleep(1000);
_exit(2);
} else {
/* release the Top memory context */
force_backtrace_messages = false;
MemoryContextDestroyAtThreadExit(t_thrd.top_mem_cxt);
ThreadExitCXX(2);
}
}
/*
* Okay, crank up a stack entry to store the info in.
*/
if (t_thrd.log_cxt.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()) {
t_thrd.log_cxt.error_context_stack = NULL;
t_thrd.postgres_cxt.debug_query_string = NULL;
}
}
if (++t_thrd.log_cxt.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.
*/
force_backtrace_messages = false;
t_thrd.log_cxt.errordata_stack_depth = -1; /* make room on stack */
/*Stack full, abort() directly instead of using erreport which goes to a deadloop*/
t_thrd.int_cxt.ImmediateInterruptOK = false;
abort();
}
/* Initialize data for this error frame */
edata = &t_thrd.log_cxt.errordata[t_thrd.log_cxt.errordata_stack_depth];
errno_t rc = memset_s(edata, sizeof(ErrorData), 0, sizeof(ErrorData));
securec_check(rc, "", "");
edata->elevel = elevel;
if (verbose)
edata->verbose = true;
edata->output_to_server = output_to_server;
edata->output_to_client = output_to_client;
if (filename != NULL) {
const char* slash = NULL;
/* keep only base name, useful especially for vpath builds */
slash = strrchr(filename, '/');
if (slash != NULL) {
filename = slash + 1;
}
}
edata->lineno = lineno;
edata->filename = (char*)filename;
edata->funcname = (char*)funcname;
/* the default text domain is the backend's */
edata->domain = domain ? domain : PG_TEXTDOMAIN("postgres");
/* Select default errcode based on elevel */
if (elevel >= ERROR)
edata->sqlerrcode = ERRCODE_WRONG_OBJECT_TYPE;
else if (elevel == WARNING)
edata->sqlerrcode = ERRCODE_WARNING;
else
edata->sqlerrcode = ERRCODE_SUCCESSFUL_COMPLETION;
/* errno is saved here so that error parameter eval can't change it */
edata->saved_errno = errno;
/* default module name will be used */
edata->mod_id = MOD_MAX;
edata->backtrace_log = NULL;
edata->sqlstate = NULL;
edata->class_origin = NULL;
edata->subclass_origin = NULL;
edata->cons_catalog = NULL;
edata->cons_schema = NULL;
edata->cons_name = NULL;
edata->catalog_name = NULL;
edata->schema_name = NULL;
edata->table_name = NULL;
edata->column_name = NULL;
edata->cursor_name = NULL;
t_thrd.log_cxt.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, ...)
{
ErrorData* edata = &t_thrd.log_cxt.errordata[t_thrd.log_cxt.errordata_stack_depth];
ErrorData* producer_save_edata = NULL;
int elevel = edata->elevel;
bool is_warnings_throw = edata->is_warnings_throw;
MemoryContext oldcontext;
ErrorContextCallback* econtext = NULL;
t_thrd.log_cxt.recursion_depth++;
force_backtrace_messages = false;
CHECK_STACK_DEPTH();
/*
* If procuer thread save a edata when report ERROR,
* now top consumer need use the saved edata.
*/
if (StreamTopConsumerAmI() && u_sess->stream_cxt.global_obj != NULL && elevel >= ERROR) {
producer_save_edata = u_sess->stream_cxt.global_obj->getProducerEdata();
/*
* In executing stream operator, when top consumer's elevel is greater than
* producer's elevel, we can't update top consumer's elevel, because that operator
* may decrease top consumer's elevel in some scene.
*/
if (producer_save_edata != NULL && producer_save_edata->elevel >= elevel) {
UpdateErrorData(edata, producer_save_edata);
elevel = edata->elevel;
}
}
/*
* 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 = t_thrd.log_cxt.error_context_stack; econtext != NULL; econtext = econtext->previous)
(*econtext->callback)(econtext->arg);
/* Database Security: Support database audit */
/* Audit beyond privileges */
if (edata->sqlerrcode == ERRCODE_INSUFFICIENT_PRIVILEGE) {
pgaudit_user_no_privileges(NULL, edata->message);
}
/*
* Because ErrorContext will be reset during FlushErrorState,
* so we can reset NULL here
*/
edata->backtrace_log = NULL;
/* get backtrace info */
if (edata->elevel >= u_sess->attr.attr_common.backtrace_min_messages && !t_thrd.log_cxt.output_backtrace_log) {
StringInfoData buf;
initStringInfo(&buf);
HOLD_INTERRUPTS();
int ret = output_backtrace_to_log(&buf);
RESUME_INTERRUPTS();
if (0 == ret) {
edata->backtrace_log = pstrdup(buf.data);
}
pfree(buf.data);
}
#ifdef MEMORY_CONTEXT_CHECKING
/* Check all memory contexts when there is an error or a fatal */
if (elevel >= ERROR || is_warnings_throw) {
MemoryContextCheck(t_thrd.top_mem_cxt, false);
}
#endif
/*
* Make sure reset create schema flag if error happen,
* even in pg_try_catch case and procedure exception case.
* Top transaction memcxt will release the memory, just set NULL.
*/
if (elevel >= ERROR || is_warnings_throw) {
u_sess->catalog_cxt.setCurCreateSchema = false;
u_sess->catalog_cxt.curCreateSchema = NULL;
u_sess->exec_cxt.isLockRows = false;
u_sess->exec_cxt.isFlashBack = false;
}
/*
* 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 || is_warnings_throw) {
/*
* 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 */
t_thrd.int_cxt.ImmediateInterruptOK = false;
/*
* Reset t_thrd.int_cxt.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 t_thrd.int_cxt.InterruptHoldoffCount for itself, but this
* should make life easier for most.)
*/
t_thrd.int_cxt.InterruptHoldoffCount = 0;
t_thrd.int_cxt.QueryCancelHoldoffCount = 0;
t_thrd.int_cxt.InterruptCountResetFlag = true;
t_thrd.int_cxt.CritSectionCount = 0; /* should be unnecessary, but... */
/*
* Note that we leave CurrentMemoryContext set to ErrorContext. The
* handler should reset it to something else soon.
*/
t_thrd.log_cxt.recursion_depth--;
if (EnableLocalSysCache()) {
MemoryContextSwitchTo(oldcontext);
}
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.
*/
if (elevel >= FATAL && t_thrd.postgres_cxt.whereToSendOutput == DestRemote)
pq_endcopyout(true);
bool isVerbose = false;
if (edata->elevel == VERBOSEMESSAGE) {
edata->elevel = INFO;
handle_in_client(true);
isVerbose = true;
}
if (StreamThreadAmI() && u_sess->stream_cxt.producer_obj != NULL && elevel == FATAL) {
/*
* Just like reportError() in longjump point of StreamMain(),
* report FATAL error to consumer here.
*/
((StreamProducer*)u_sess->stream_cxt.producer_obj)->reportError();
} else if (StreamThreadAmI() && u_sess->stream_cxt.producer_obj != NULL && elevel < ERROR) {
/* Send to server log, if enabled */
if (edata->output_to_server && is_errmodule_enable(edata->elevel, edata->mod_id))
send_message_to_server_log(edata);
/* Send to client, if enabled */
if (edata->output_to_client) {
/* report NOTICE to consumer here. */
u_sess->stream_cxt.producer_obj->reportNotice();
}
} else {
/* Emit the message to the right places */
EmitErrorReport();
}
#ifdef ENABLE_MULTIPLE_NODES
if (elevel >= ERROR) {
clean_ec_conn();
delete_ec_ctrl();
}
#endif
if (isVerbose)
handle_in_client(false);
/* 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);
if (edata->backtrace_log)
pfree(edata->backtrace_log);
if (edata->cause)
pfree(edata->cause);
if (edata->action)
pfree(edata->action);
if (edata->sqlstate)
pfree(edata->sqlstate);
if (edata->class_origin)
pfree(edata->class_origin);
if (edata->subclass_origin)
pfree(edata->subclass_origin);
if (edata->cons_catalog)
pfree(edata->cons_catalog);
if (edata->cons_schema)
pfree(edata->cons_schema);
if (edata->cons_name)
pfree(edata->cons_name);
if (edata->catalog_name)
pfree(edata->catalog_name);
if (edata->schema_name)
pfree(edata->schema_name);
if (edata->table_name)
pfree(edata->table_name);
if (edata->column_name)
pfree(edata->column_name);
if (edata->cursor_name)
pfree(edata->cursor_name);
t_thrd.log_cxt.errordata_stack_depth--;
/* Exit error-handling context */
MemoryContextSwitchTo(oldcontext);
t_thrd.log_cxt.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.
*/
t_thrd.int_cxt.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 (t_thrd.log_cxt.PG_exception_stack == NULL && t_thrd.postgres_cxt.whereToSendOutput == DestRemote)
t_thrd.postgres_cxt.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);
/* release operator-level hash table in memory */
releaseExplainTable();
if (StreamTopConsumerAmI() && u_sess->debug_query_id != 0) {
gs_close_all_stream_by_debug_id(u_sess->debug_query_id);
}
/*
* 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...
*/
t_thrd.int_cxt.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.
* Just check for interrupts when ignore_interrupt is not set to true.
*
* Well, I think CHECK_FOR_INTERRUPTS() here is somewhat _terrible_. Programmers have put elog nearly everywhere in
* the code, including critical section which should be executed atomically. CHECK_FOR_INTERRUPTS() here have the
* probability to break such critical section. This will result in unexpected behaviors!
*/
}
/*
* @Description: set module id for logging
* @IN id: module id
* @Return:
* @See also:
*/
int errmodule(ModuleId id)
{
ErrorData* edata = &t_thrd.log_cxt.errordata[t_thrd.log_cxt.errordata_stack_depth];
/* we don't bother incrementing t_thrd.log_cxt.recursion_depth */
CHECK_STACK_DEPTH();
/* set module id */
Assert(VALID_SINGLE_MODULE(id));
edata->mod_id = id;
/* return value does not matter */
return 0;
}
/*
* 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 = &t_thrd.log_cxt.errordata[t_thrd.log_cxt.errordata_stack_depth];
/* we don't bother incrementing t_thrd.log_cxt.recursion_depth */
CHECK_STACK_DEPTH();
edata->sqlerrcode = sqlerrcode;
return 0; /* return value does not matter */
}
/*
* save error message for history session info
*/
void save_error_message(void)
{
if (IS_PGXC_DATANODE || IsConnFromCoord() || !u_sess->wlm_cxt->wlm_params.memtrack ||
t_thrd.wlm_cxt.collect_info->sdetail.msg)
return;
START_CRIT_SECTION();
for (int i = t_thrd.log_cxt.errordata_stack_depth; i >= 0; --i) {
ErrorData* edata = t_thrd.log_cxt.errordata + i;
if (edata->elevel >= ERROR) {
USE_MEMORY_CONTEXT(g_instance.wlm_cxt->query_resource_track_mcxt);
if (edata->message)
t_thrd.wlm_cxt.collect_info->sdetail.msg = pstrdup(edata->message);
else
t_thrd.wlm_cxt.collect_info->sdetail.msg = pstrdup("missing error text");
break;
}
}
END_CRIT_SECTION();
}
/*
* 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 = &t_thrd.log_cxt.errordata[t_thrd.log_cxt.errordata_stack_depth];
/* we don't bother incrementing t_thrd.log_cxt.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 */
case ERR_DSS_FILE_NOT_EXIST: /* No such file in dss */
case ERR_DSS_DIR_NOT_EXIST: /* No such directory in dss */
edata->sqlerrcode = ERRCODE_UNDEFINED_FILE;
break;
/* Duplicate file */
case EEXIST: /* File exists */
case ERR_DSS_DIR_CREATE_DUPLICATED: /* File or directory already existed in DSS */
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 */
case ERR_DSS_NO_SPACE: /* No space left on dss */
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_WRONG_OBJECT_TYPE;
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 = &t_thrd.log_cxt.errordata[t_thrd.log_cxt.errordata_stack_depth];
/* we don't bother incrementing t_thrd.log_cxt.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_WRONG_OBJECT_TYPE;
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 = NULL; \
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) { \
appendStringInfoString(&buf, edata->targetfield); \
appendStringInfoChar(&buf, '\n'); \
} \
/* Generate actual output --- have to use appendStringInfoVA */ \
for (; ; ) { \
va_list args; \
bool success = false; \
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 = NULL; \
char* fmtbuf = NULL; \
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) { \
appendStringInfoString(&buf, edata->targetfield); \
appendStringInfoChar(&buf, '\n'); \
} \
/* Generate actual output --- have to use appendStringInfoVA */ \
for (; ; ) { \
va_list args; \
bool success = false; \
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 = &t_thrd.log_cxt.errordata[t_thrd.log_cxt.errordata_stack_depth];
MemoryContext oldcontext;
t_thrd.log_cxt.recursion_depth++;
CHECK_STACK_DEPTH();
oldcontext = MemoryContextSwitchTo(ErrorContext);
EVALUATE_MESSAGE(message, false, true);
MemoryContextSwitchTo(oldcontext);
t_thrd.log_cxt.recursion_depth--;
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 = &t_thrd.log_cxt.errordata[t_thrd.log_cxt.errordata_stack_depth];
MemoryContext oldcontext;
t_thrd.log_cxt.recursion_depth++;
CHECK_STACK_DEPTH();
oldcontext = MemoryContextSwitchTo(ErrorContext);
EVALUATE_MESSAGE(message, false, false);
MemoryContextSwitchTo(oldcontext);
t_thrd.log_cxt.recursion_depth--;
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 = &t_thrd.log_cxt.errordata[t_thrd.log_cxt.errordata_stack_depth];
MemoryContext oldcontext;
t_thrd.log_cxt.recursion_depth++;
CHECK_STACK_DEPTH();
oldcontext = MemoryContextSwitchTo(ErrorContext);
EVALUATE_MESSAGE_PLURAL(message, false);
MemoryContextSwitchTo(oldcontext);
t_thrd.log_cxt.recursion_depth--;
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 = &t_thrd.log_cxt.errordata[t_thrd.log_cxt.errordata_stack_depth];
MemoryContext oldcontext;
t_thrd.log_cxt.recursion_depth++;
CHECK_STACK_DEPTH();
oldcontext = MemoryContextSwitchTo(ErrorContext);
EVALUATE_MESSAGE(detail, false, true);
MemoryContextSwitchTo(oldcontext);
t_thrd.log_cxt.recursion_depth--;
return 0; /* return value does not matter */
}
/*
* errdetail_internal --- add a detail error message text to the current error
*
* This is exactly like errdetail() except that strings passed to
* errdetail_internal are not translated, and are customarily left out of the
* internationalization message dictionary. This should be used for detail
* messages that seem not worth translating for one reason or another
* (typically, that they don't seem to be useful to average users).
*/
int errdetail_internal(const char* fmt, ...)
{
ErrorData* edata = &t_thrd.log_cxt.errordata[t_thrd.log_cxt.errordata_stack_depth];
MemoryContext oldcontext;
t_thrd.log_cxt.recursion_depth++;
CHECK_STACK_DEPTH();
oldcontext = MemoryContextSwitchTo(ErrorContext);
EVALUATE_MESSAGE(detail, false, false);
MemoryContextSwitchTo(oldcontext);
t_thrd.log_cxt.recursion_depth--;
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 = &t_thrd.log_cxt.errordata[t_thrd.log_cxt.errordata_stack_depth];
MemoryContext oldcontext;
t_thrd.log_cxt.recursion_depth++;
CHECK_STACK_DEPTH();
oldcontext = MemoryContextSwitchTo(ErrorContext);
EVALUATE_MESSAGE(detail_log, false, true);
MemoryContextSwitchTo(oldcontext);
t_thrd.log_cxt.recursion_depth--;
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 = &t_thrd.log_cxt.errordata[t_thrd.log_cxt.errordata_stack_depth];
MemoryContext oldcontext;
t_thrd.log_cxt.recursion_depth++;
CHECK_STACK_DEPTH();
oldcontext = MemoryContextSwitchTo(ErrorContext);
EVALUATE_MESSAGE_PLURAL(detail, false);
MemoryContextSwitchTo(oldcontext);
t_thrd.log_cxt.recursion_depth--;
return 0; /* return value does not matter */
}
int errcause(const char* fmt, ...)
{
ErrorData* edata = &t_thrd.log_cxt.errordata[t_thrd.log_cxt.errordata_stack_depth];
MemoryContext oldcontext;
t_thrd.log_cxt.recursion_depth++;
CHECK_STACK_DEPTH();
oldcontext = MemoryContextSwitchTo(ErrorContext);
EVALUATE_MESSAGE(cause, false, true);
MemoryContextSwitchTo(oldcontext);
t_thrd.log_cxt.recursion_depth--;
return 0; /* return value does not matter */
}
int erraction(const char* fmt, ...)
{
ErrorData* edata = &t_thrd.log_cxt.errordata[t_thrd.log_cxt.errordata_stack_depth];
MemoryContext oldcontext;
t_thrd.log_cxt.recursion_depth++;
CHECK_STACK_DEPTH();
oldcontext = MemoryContextSwitchTo(ErrorContext);
EVALUATE_MESSAGE(action, false, true);
MemoryContextSwitchTo(oldcontext);
t_thrd.log_cxt.recursion_depth--;
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 = &t_thrd.log_cxt.errordata[t_thrd.log_cxt.errordata_stack_depth];
MemoryContext oldcontext;
t_thrd.log_cxt.recursion_depth++;
CHECK_STACK_DEPTH();
oldcontext = MemoryContextSwitchTo(ErrorContext);
EVALUATE_MESSAGE(hint, false, true);
MemoryContextSwitchTo(oldcontext);
t_thrd.log_cxt.recursion_depth--;
return 0; /* return value does not matter */
}
/*
* errquery --- add a query error message text to the current error
*/
int errquery(const char* fmt, ...)
{
ErrorData* edata = &t_thrd.log_cxt.errordata[t_thrd.log_cxt.errordata_stack_depth];
MemoryContext oldcontext;
t_thrd.log_cxt.recursion_depth++;
CHECK_STACK_DEPTH();
oldcontext = MemoryContextSwitchTo(ErrorContext);
EVALUATE_MESSAGE(internalquery, false, true);
MemoryContextSwitchTo(oldcontext);
t_thrd.log_cxt.recursion_depth--;
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 = &t_thrd.log_cxt.errordata[t_thrd.log_cxt.errordata_stack_depth];
MemoryContext oldcontext;
t_thrd.log_cxt.recursion_depth++;
CHECK_STACK_DEPTH();
oldcontext = MemoryContextSwitchTo(ErrorContext);
EVALUATE_MESSAGE(context, true, true);
MemoryContextSwitchTo(oldcontext);
t_thrd.log_cxt.recursion_depth--;
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 = &t_thrd.log_cxt.errordata[t_thrd.log_cxt.errordata_stack_depth];
/* we don't bother incrementing t_thrd.log_cxt.recursion_depth */
CHECK_STACK_DEPTH();
edata->hide_stmt = hide_stmt;
return 0; /* return value does not matter */
}
/*
* errhideprefix --- optionally suppress line prefix: field of log entry
*
* This should be called if the message text already includes the line prefix.
*/
int errhideprefix(bool hide_prefix)
{
ErrorData* edata = &t_thrd.log_cxt.errordata[t_thrd.log_cxt.errordata_stack_depth];
/* we don't bother incrementing t_thrd.log_cxt.recursion_depth */
CHECK_STACK_DEPTH();
edata->hide_prefix = hide_prefix;
return 0; /* return value does not matter */
}
/*
* errposition --- add cursor position to the current error
*/
int errposition(int cursorpos)
{
ErrorData* edata = &t_thrd.log_cxt.errordata[t_thrd.log_cxt.errordata_stack_depth];
/* we don't bother incrementing t_thrd.log_cxt.recursion_depth */
CHECK_STACK_DEPTH();
edata->cursorpos = cursorpos;
return 0; /* return value does not matter */
}
/*
* internalerrposition --- add internal cursor position to the current error
*/
int internalerrposition(int cursorpos)
{
ErrorData* edata = &t_thrd.log_cxt.errordata[t_thrd.log_cxt.errordata_stack_depth];
/* we don't bother incrementing t_thrd.log_cxt.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 = &t_thrd.log_cxt.errordata[t_thrd.log_cxt.errordata_stack_depth];
/* we don't bother incrementing t_thrd.log_cxt.recursion_depth */
CHECK_STACK_DEPTH();
if (edata->internalquery) {
pfree(edata->internalquery);
edata->internalquery = NULL;
}
if (query != NULL) {
edata->internalquery = MemoryContextStrdup(ErrorContext, query);
}
return 0; /* return value does not matter */
}
int signal_is_warnings_throw(bool is_warning_throw)
{
ErrorData* edata = &t_thrd.log_cxt.errordata[t_thrd.log_cxt.errordata_stack_depth];
/* we don't bother incrementing t_thrd.log_cxt.recursion_depth */
CHECK_STACK_DEPTH();
edata->is_warnings_throw = is_warning_throw;
return 0; /* return value does not matter */
}
int signal_is_signal(int is_signal)
{
ErrorData* edata = &t_thrd.log_cxt.errordata[t_thrd.log_cxt.errordata_stack_depth];
/* we don't bother incrementing t_thrd.log_cxt.recursion_depth */
CHECK_STACK_DEPTH();
edata->is_signal = is_signal;
return 0; /* return value does not matter */
}
/*
* signal_returnd_sqlstate --- add returnd_sqlstate to the current error
*/
int signal_returnd_sqlstate(const char *returnd_sqlstate)
{
ErrorData* edata = &t_thrd.log_cxt.errordata[t_thrd.log_cxt.errordata_stack_depth];
/* we don't bother incrementing t_thrd.log_cxt.recursion_depth */
CHECK_STACK_DEPTH();
if (edata->sqlstate != NULL) {
pfree(edata->sqlstate);
edata->sqlstate = NULL;
}
if (returnd_sqlstate != NULL) {
edata->sqlstate = MemoryContextStrdup(ErrorContext, returnd_sqlstate);
}
return 0; /* return value does not matter */
}
/*
* siganl_class_origin --- add class_origin to the current error
*/
int signal_class_origin(const char *class_origin)
{
ErrorData* edata = &t_thrd.log_cxt.errordata[t_thrd.log_cxt.errordata_stack_depth];
/* we don't bother incrementing t_thrd.log_cxt.recursion_depth */
CHECK_STACK_DEPTH();
if (edata->class_origin != NULL) {
pfree(edata->class_origin);
edata->class_origin = NULL;
}
if (class_origin != NULL) {
edata->class_origin = MemoryContextStrdup(ErrorContext, class_origin);
}
return 0; /* return value does not matter */
}
/*
* siganl_subclass_origin --- add subclass_origin to the current error
*/
int signal_subclass_origin(const char *subclass_origin)
{
ErrorData* edata = &t_thrd.log_cxt.errordata[t_thrd.log_cxt.errordata_stack_depth];
/* we don't bother incrementing t_thrd.log_cxt.recursion_depth */
CHECK_STACK_DEPTH();
if (edata->subclass_origin != NULL) {
pfree(edata->subclass_origin);
edata->subclass_origin = NULL;
}
if (subclass_origin != NULL) {
edata->subclass_origin = MemoryContextStrdup(ErrorContext, subclass_origin);
}
return 0; /* return value does not matter */
}
/*
* signal_constraint_catalog --- add constraint_catalog to the current error
*/
int signal_constraint_catalog(const char *constraint_catalog)
{
ErrorData* edata = &t_thrd.log_cxt.errordata[t_thrd.log_cxt.errordata_stack_depth];
/* we don't bother incrementing t_thrd.log_cxt.recursion_depth */
CHECK_STACK_DEPTH();
if (edata->cons_catalog != NULL) {
pfree(edata->cons_catalog);
edata->cons_catalog = NULL;
}
if (constraint_catalog != NULL) {
edata->cons_catalog = MemoryContextStrdup(ErrorContext, constraint_catalog);
}
return 0; /* return value does not matter */
}
/*
* signal_constraint_schema --- add constraint_schema to the current error
*/
int signal_constraint_schema(const char *constraint_schema)
{
ErrorData* edata = &t_thrd.log_cxt.errordata[t_thrd.log_cxt.errordata_stack_depth];
/* we don't bother incrementing t_thrd.log_cxt.recursion_depth */
CHECK_STACK_DEPTH();
if (edata->cons_schema != NULL) {
pfree(edata->cons_schema);
edata->cons_schema = NULL;
}
if (constraint_schema != NULL) {
edata->cons_schema = MemoryContextStrdup(ErrorContext, constraint_schema);
}
return 0; /* return value does not matter */
}
/*
* signal_constraint_name --- add constraint_name to the current error
*/
int signal_constraint_name(const char *constraint_name)
{
ErrorData* edata = &t_thrd.log_cxt.errordata[t_thrd.log_cxt.errordata_stack_depth];
/* we don't bother incrementing t_thrd.log_cxt.recursion_depth */
CHECK_STACK_DEPTH();
if (edata->cons_name != NULL) {
pfree(edata->cons_name);
edata->cons_name = NULL;
}
if (constraint_name != NULL) {
edata->cons_name = MemoryContextStrdup(ErrorContext, constraint_name);
}
return 0; /* return value does not matter */
}
/*
* signal_catalog_name --- add catalog_name to the current error
*/
int signal_catalog_name(const char *catalog_name)
{
ErrorData* edata = &t_thrd.log_cxt.errordata[t_thrd.log_cxt.errordata_stack_depth];
/* we don't bother incrementing t_thrd.log_cxt.recursion_depth */
CHECK_STACK_DEPTH();
if (edata->catalog_name != NULL) {
pfree(edata->catalog_name);
edata->catalog_name = NULL;
}
if (catalog_name != NULL) {
edata->catalog_name = MemoryContextStrdup(ErrorContext, catalog_name);
}
return 0; /* return value does not matter */
}
/*
* signal_schema_name --- add schema_name to the current error
*/
int signal_schema_name(const char *schema_name)
{
ErrorData* edata = &t_thrd.log_cxt.errordata[t_thrd.log_cxt.errordata_stack_depth];
/* we don't bother incrementing t_thrd.log_cxt.recursion_depth */
CHECK_STACK_DEPTH();
if (edata->schema_name != NULL) {
pfree(edata->schema_name);
edata->schema_name = NULL;
}
if (schema_name != NULL) {
edata->schema_name = MemoryContextStrdup(ErrorContext, schema_name);
}
return 0; /* return value does not matter */
}
/*
* signal_table_name --- add table_name to the current error
*/
int signal_table_name(const char *table_name)
{
ErrorData* edata = &t_thrd.log_cxt.errordata[t_thrd.log_cxt.errordata_stack_depth];
/* we don't bother incrementing t_thrd.log_cxt.recursion_depth */
CHECK_STACK_DEPTH();
if (edata->table_name != NULL) {
pfree(edata->table_name);
edata->table_name = NULL;
}
if (table_name != NULL) {
edata->table_name = MemoryContextStrdup(ErrorContext, table_name);
}
return 0; /* return value does not matter */
}
/*
* signal_column_name --- add column_name to the current error
*/
int signal_column_name(const char *column_name)
{
ErrorData* edata = &t_thrd.log_cxt.errordata[t_thrd.log_cxt.errordata_stack_depth];
/* we don't bother incrementing t_thrd.log_cxt.recursion_depth */
CHECK_STACK_DEPTH();
if (edata->column_name != NULL) {
pfree(edata->column_name);
edata->column_name = NULL;
}
if (column_name != NULL) {
edata->column_name = MemoryContextStrdup(ErrorContext, column_name);
}
return 0; /* return value does not matter */
}
/*
* signal_cursor_name --- add cursor_name to the current error
*/
int signal_cursor_name(const char *cursor_name)
{
ErrorData* edata = &t_thrd.log_cxt.errordata[t_thrd.log_cxt.errordata_stack_depth];
/* we don't bother incrementing t_thrd.log_cxt.recursion_depth */
CHECK_STACK_DEPTH();
if (edata->cursor_name != NULL) {
pfree(edata->cursor_name);
edata->cursor_name = NULL;
}
if (cursor_name != NULL) {
edata->cursor_name = MemoryContextStrdup(ErrorContext, cursor_name);
}
return 0; /* return value does not matter */
}
/*
* ErrOutToClient --- sets whether to send error output to client or not.
*/
int
ErrOutToClient(bool outToClient)
{
ErrorData *edata = &t_thrd.log_cxt.errordata[t_thrd.log_cxt.errordata_stack_depth];
/* we don't bother incrementing recursion_depth */
CHECK_STACK_DEPTH();
edata->output_to_client = outToClient;
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 = &t_thrd.log_cxt.errordata[t_thrd.log_cxt.errordata_stack_depth];
/* we don't bother incrementing t_thrd.log_cxt.recursion_depth */
CHECK_STACK_DEPTH();
return edata->sqlerrcode;
}
/*
* Geterrmsg --- return the currently set error message
*
* This is only intended for use in error callback subroutines, since there
* is no other place outside elog.c where the concept is meaningful.
*/
char *Geterrmsg(void)
{
ErrorData* edata = &t_thrd.log_cxt.errordata[t_thrd.log_cxt.errordata_stack_depth];
/* we don't bother incrementing t_thrd.log_cxt.recursion_depth */
CHECK_STACK_DEPTH();
return edata->message;
}
/*
* 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 = &t_thrd.log_cxt.errordata[t_thrd.log_cxt.errordata_stack_depth];
/* we don't bother incrementing t_thrd.log_cxt.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 = &t_thrd.log_cxt.errordata[t_thrd.log_cxt.errordata_stack_depth];
/* we don't bother incrementing t_thrd.log_cxt.recursion_depth */
CHECK_STACK_DEPTH();
return edata->internalpos;
}
/*
* handle_in_client --- mark if the message should be sent and handled in client
*/
int handle_in_client(bool handle)
{
ErrorData* edata = &t_thrd.log_cxt.errordata[t_thrd.log_cxt.errordata_stack_depth];
/* we don't bother incrementing t_thrd.log_cxt.recursion_depth */
CHECK_STACK_DEPTH();
edata->handle_in_client = handle;
return 0; /* return value does not matter */
}
/*
* ignore_interrupt --- mark if should ignore interrupt when writing server log
*/
int ignore_interrupt(bool ignore)
{
ErrorData* edata = &t_thrd.log_cxt.errordata[t_thrd.log_cxt.errordata_stack_depth];
/* we don't bother incrementing t_thrd.log_cxt.recursion_depth */
CHECK_STACK_DEPTH();
edata->ignore_interrupt = ignore;
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 = NULL;
#ifdef ENABLE_UT
if (t_thrd.log_cxt.disable_log_output)
return;
#endif
/* Make sure that memory context initialization has finished */
if (ErrorContext == NULL) {
/* Ooops, hard crash time; very little we can do safely here */
write_stderr("error occurred at %s:%d before error message processing is available\n",
filename ? filename : "(unknown file)",
lineno);
pg_usleep(1000);
_exit(2);
}
if (++t_thrd.log_cxt.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.
*/
t_thrd.log_cxt.errordata_stack_depth = -1; /* make room on stack */
ereport(PANIC, (errmsg_internal("ERRORDATA_STACK_SIZE exceeded")));
}
edata = &t_thrd.log_cxt.errordata[t_thrd.log_cxt.errordata_stack_depth];
if (filename != NULL) {
const char* slash = NULL;
/* keep only base name, useful especially for vpath builds */
slash = strrchr(filename, '/');
if (slash != NULL) {
filename = slash + 1;
}
}
edata->filename = (char*)filename;
edata->lineno = lineno;
edata->funcname = (char*)funcname;
/* errno is saved now so that error parameter eval can't change it */
edata->saved_errno = errno;
edata->backtrace_log = NULL;
}
/*
* elog_finish --- finish up for old-style API
*/
void elog_finish(int elevel, const char* fmt, ...)
{
ErrorData* edata = &t_thrd.log_cxt.errordata[t_thrd.log_cxt.errordata_stack_depth];
MemoryContext oldcontext;
#ifdef ENABLE_UT
if (t_thrd.log_cxt.disable_log_output)
return;
#endif
CHECK_STACK_DEPTH();
/*
* Do errstart() to see if we actually want to report the message.
*/
t_thrd.log_cxt.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().
*/
t_thrd.log_cxt.recursion_depth++;
oldcontext = MemoryContextSwitchTo(ErrorContext);
EVALUATE_MESSAGE(message, false, false);
MemoryContextSwitchTo(oldcontext);
t_thrd.log_cxt.recursion_depth--;
/*
* And let errfinish() finish up.
*/
errfinish(0);
}
/*
* Functions to allow construction of error message strings separately from
* the ereport() call itself.
*
* The expected calling convention is
*
* pre_format_elog_string(errno, domain), var = format_elog_string(format,...)
*
* which can be hidden behind a macro such as GUC_check_errdetail(). We
* assume that any functions called in the arguments of format_elog_string()
* cannot result in re-entrant use of these functions --- otherwise the wrong
* text domain might be used, or the wrong errno substituted for %m. This is
* okay for the current usage with GUC check hooks, but might need further
* effort someday.
*
* The result of format_elog_string() is stored in ErrorContext, and will
* therefore survive until FlushErrorState() is called.
*/
void pre_format_elog_string(int errnumber, const char* domain)
{
/* Save errno before evaluation of argument functions can change it */
t_thrd.log_cxt.save_format_errnumber = errnumber;
/* Save caller's text domain */
t_thrd.log_cxt.save_format_domain = domain;
}
char* format_elog_string(const char* fmt, ...)
{
ErrorData errdata;
ErrorData* edata = NULL;
MemoryContext oldcontext;
/* Initialize a mostly-dummy error frame */
edata = &errdata;
errno_t rc = memset_s(edata, sizeof(ErrorData), 0, sizeof(ErrorData));
securec_check(rc, "", "");
/* the default text domain is the backend's */
edata->domain = t_thrd.log_cxt.save_format_domain ? t_thrd.log_cxt.save_format_domain : PG_TEXTDOMAIN("postgres");
/* set the errno to be used to interpret %m */
edata->saved_errno = t_thrd.log_cxt.save_format_errnumber;
oldcontext = MemoryContextSwitchTo(ErrorContext);
EVALUATE_MESSAGE(message, false, true);
MemoryContextSwitchTo(oldcontext);
return edata->message;
}
/*
* 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 = &t_thrd.log_cxt.errordata[t_thrd.log_cxt.errordata_stack_depth];
MemoryContext oldcontext;
t_thrd.log_cxt.recursion_depth++;
CHECK_STACK_DEPTH();
oldcontext = MemoryContextSwitchTo(ErrorContext);
/* Send to server log, if enabled */
if (edata->output_to_server && is_errmodule_enable(edata->elevel, edata->mod_id))
send_message_to_server_log(edata);
/* Send to client, if enabled */
if (edata->output_to_client) {
bool need_skip_by_retry = IsStmtRetryAvaliable(edata->elevel, edata->sqlerrcode);
bool can_skip = (edata->elevel < FATAL);
if (can_skip && need_skip_by_retry) {
/* skip sending messsage to front, do noting for now */
} else {
if (u_sess->proc_cxt.MyProcPort && u_sess->proc_cxt.MyProcPort->protocol_config) {
u_sess->proc_cxt.MyProcPort->protocol_config->fn_send_message(edata);
}
}
}
MemoryContextSwitchTo(oldcontext);
t_thrd.log_cxt.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 = &t_thrd.log_cxt.errordata[t_thrd.log_cxt.errordata_stack_depth];
ErrorData* newedata = NULL;
/*
* we don't increment t_thrd.log_cxt.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));
errno_t rc = memcpy_s(newedata, sizeof(ErrorData), edata, sizeof(ErrorData));
securec_check(rc, "\0", "\0");
/* 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);
if (newedata->filename)
newedata->filename = pstrdup(newedata->filename);
if (newedata->funcname)
newedata->funcname = pstrdup(newedata->funcname);
if (newedata->backtrace_log)
newedata->backtrace_log = pstrdup(newedata->backtrace_log);
if (newedata->cause)
newedata->cause = pstrdup(newedata->cause);
if (newedata->action)
newedata->action = pstrdup(newedata->action);
if (edata->sqlstate)
newedata->sqlstate = pstrdup(newedata->sqlstate);
if (newedata->class_origin)
newedata->class_origin = pstrdup(newedata->class_origin);
if (newedata->subclass_origin)
newedata->subclass_origin = pstrdup(newedata->subclass_origin);
if (newedata->cons_catalog)
newedata->cons_catalog = pstrdup(newedata->cons_catalog);
if (newedata->cons_schema)
newedata->cons_schema = pstrdup(newedata->cons_schema);
if (newedata->cons_name)
newedata->cons_name = pstrdup(newedata->cons_name);
if (newedata->catalog_name)
newedata->catalog_name = pstrdup(newedata->catalog_name);
if (newedata->schema_name)
newedata->schema_name = pstrdup(newedata->schema_name);
if (newedata->table_name)
newedata->table_name = pstrdup(newedata->table_name);
if (newedata->column_name)
newedata->column_name = pstrdup(newedata->column_name);
if (newedata->cursor_name)
newedata->cursor_name = pstrdup(newedata->cursor_name);
return newedata;
}
/*
* UpdateErrorData --- update current edata from newData.
*/
void UpdateErrorData(ErrorData* edata, ErrorData* newData)
{
FREE_POINTER(edata->message);
FREE_POINTER(edata->detail);
FREE_POINTER(edata->detail_log);
FREE_POINTER(edata->hint);
FREE_POINTER(edata->context);
FREE_POINTER(edata->internalquery);
FREE_POINTER(edata->backtrace_log);
FREE_POINTER(edata->cause);
FREE_POINTER(edata->action);
FREE_POINTER(edata->sqlstate);
FREE_POINTER(edata->class_origin);
FREE_POINTER(edata->subclass_origin);
FREE_POINTER(edata->cons_catalog);
FREE_POINTER(edata->cons_schema);
FREE_POINTER(edata->cons_name);
FREE_POINTER(edata->catalog_name);
FREE_POINTER(edata->schema_name);
FREE_POINTER(edata->table_name);
FREE_POINTER(edata->column_name);
FREE_POINTER(edata->cursor_name);
MemoryContext oldcontext = MemoryContextSwitchTo(ErrorContext);
edata->elevel = newData->elevel;
edata->filename = pstrdup(newData->filename);
edata->lineno = newData->lineno;
edata->funcname = pstrdup(newData->funcname);
edata->sqlerrcode = newData->sqlerrcode;
edata->message = pstrdup(newData->message);
edata->detail = pstrdup(newData->detail);
edata->detail_log = pstrdup(newData->detail_log);
edata->hint = pstrdup(newData->hint);
edata->context = pstrdup(newData->context);
edata->cursorpos = newData->cursorpos;
edata->internalpos = newData->internalpos;
edata->internalquery = pstrdup(newData->internalquery);
edata->saved_errno = newData->saved_errno;
edata->backtrace_log = pstrdup(newData->backtrace_log);
edata->internalerrcode = newData->internalerrcode;
edata->cause = newData->cause;
edata->action = newData->action;
edata->sqlstate = pstrdup(newData->sqlstate);
edata->class_origin = pstrdup(newData->class_origin);
edata->subclass_origin = pstrdup(newData->subclass_origin);
edata->cons_catalog = pstrdup(newData->cons_catalog);
edata->cons_schema = pstrdup(newData->cons_schema);
edata->cons_name = pstrdup(newData->cons_name);
edata->catalog_name = pstrdup(newData->catalog_name);
edata->schema_name = pstrdup(newData->schema_name);
edata->table_name = pstrdup(newData->table_name);
edata->column_name = pstrdup(newData->column_name);
edata->cursor_name = pstrdup(newData->cursor_name);
MemoryContextSwitchTo(oldcontext);
}
/*
* 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);
if (edata->filename)
pfree(edata->filename);
if (edata->funcname)
pfree(edata->funcname);
if (edata->backtrace_log) {
pfree(edata->backtrace_log);
edata->backtrace_log = NULL;
}
if (edata->cause) {
pfree(edata->cause);
edata->cause = NULL;
}
if (edata->action) {
pfree(edata->action);
edata->action = NULL;
}
if (edata->sqlstate) {
pfree(edata->sqlstate);
edata->sqlstate = NULL;
}
if (edata->class_origin) {
pfree(edata->class_origin);
edata->class_origin = NULL;
}
if (edata->subclass_origin) {
pfree(edata->subclass_origin);
edata->subclass_origin = NULL;
}
if (edata->cons_catalog) {
pfree(edata->cons_catalog);
edata->cons_catalog = NULL;
}
if (edata->cons_schema) {
pfree(edata->cons_schema);
edata->cons_schema = NULL;
}
if (edata->cons_name) {
pfree(edata->cons_name);
edata->cons_name = NULL;
}
if (edata->catalog_name) {
pfree(edata->catalog_name);
edata->catalog_name = NULL;
}
if (edata->schema_name) {
pfree(edata->schema_name);
edata->schema_name = NULL;
}
if (edata->table_name) {
pfree(edata->table_name);
edata->table_name = NULL;
}
if (edata->column_name) {
pfree(edata->column_name);
edata->column_name = NULL;
}
if (edata->cursor_name) {
pfree(edata->cursor_name);
edata->cursor_name = NULL;
}
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.
*/
t_thrd.log_cxt.errordata_stack_depth = -1;
t_thrd.log_cxt.recursion_depth = 0;
/* Delete all data in ErrorContext */
MemoryContextResetAndDeleteChildren(ErrorContext);
}
void FlushErrorStateWithoutDeleteChildrenContext(void)
{
t_thrd.log_cxt.errordata_stack_depth = -1;
t_thrd.log_cxt.recursion_depth = 0;
MemoryContextReset(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 = NULL;
if (DB_IS_CMPT(B_FORMAT)) {
Assert(edata->elevel >= WARNING);
} else {
Assert(edata->elevel == ERROR);
}
/* Push the data back into the error context */
t_thrd.log_cxt.recursion_depth++;
MemoryContextSwitchTo(ErrorContext);
if (++t_thrd.log_cxt.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.
*/
t_thrd.log_cxt.errordata_stack_depth = -1; /* make room on stack */
ereport(PANIC, (errmsg_internal("ERRORDATA_STACK_SIZE exceeded")));
}
newedata = &t_thrd.log_cxt.errordata[t_thrd.log_cxt.errordata_stack_depth];
errno_t rc = memcpy_s(newedata, sizeof(ErrorData), edata, sizeof(ErrorData));
securec_check(rc, "\0", "\0");
/* 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);
if (newedata->filename)
newedata->filename = pstrdup(newedata->filename);
if (newedata->funcname)
newedata->funcname = pstrdup(newedata->funcname);
if (newedata->backtrace_log)
newedata->backtrace_log = pstrdup(newedata->backtrace_log);
if (newedata->cause)
newedata->cause = pstrdup(newedata->cause);
if (newedata->action)
newedata->action = pstrdup(newedata->action);
if (newedata->sqlstate)
newedata->sqlstate = pstrdup(newedata->sqlstate);
if (newedata->class_origin)
newedata->class_origin = pstrdup(newedata->class_origin);
if (newedata->subclass_origin)
newedata->subclass_origin = pstrdup(newedata->subclass_origin);
if (newedata->cons_catalog)
newedata->cons_catalog = pstrdup(newedata->cons_catalog);
if (newedata->cons_schema)
newedata->cons_schema = pstrdup(newedata->cons_schema);
if (newedata->cons_name)
newedata->cons_name = pstrdup(newedata->cons_name);
if (newedata->catalog_name)
newedata->catalog_name = pstrdup(newedata->catalog_name);
if (newedata->schema_name)
newedata->schema_name = pstrdup(newedata->schema_name);
if (newedata->table_name)
newedata->table_name = pstrdup(newedata->table_name);
if (newedata->column_name)
newedata->column_name = pstrdup(newedata->column_name);
if (newedata->cursor_name)
newedata->cursor_name = pstrdup(newedata->cursor_name);
t_thrd.log_cxt.recursion_depth--;
if (DB_IS_CMPT(B_FORMAT)) {
if (edata->elevel == WARNING) {
EmitErrorReport();
} else {
PG_RE_THROW();
}
} else {
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 (t_thrd.log_cxt.PG_exception_stack != NULL)
siglongjmp(*t_thrd.log_cxt.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 = &t_thrd.log_cxt.errordata[t_thrd.log_cxt.errordata_stack_depth];
Assert(t_thrd.log_cxt.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 (t_thrd.postgres_cxt.whereToSendOutput == DestRemote)
edata->output_to_client = true;
/*
* 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.
*/
t_thrd.log_cxt.error_context_stack = NULL;
errfinish(0);
}
/* Doesn't return ... */
ExceptionalCondition("pg_re_throw tried to return", "FailedAssertion", __FILE__, __LINE__);
abort(); // keep compiler quiet
}
/*
* PgRethrowAsFatal - Promote the error level to fatal.
*/
void
PgRethrowAsFatal(void)
{
ErrorData *edata = &t_thrd.log_cxt.errordata[t_thrd.log_cxt.errordata_stack_depth];
Assert(t_thrd.log_cxt.errordata_stack_depth >= 0);
edata->elevel = FATAL;
PG_RE_THROW();
}
/*
* Initialization of error output file
*/
void DebugFileOpen(void)
{
int fd = 0;
int istty = 0;
if (t_thrd.proc_cxt.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(t_thrd.proc_cxt.OutputFileName, O_CREAT | O_APPEND | O_WRONLY, 0600)) < 0) {
ereport(FATAL,
(errcode_for_file_access(), errmsg("could not open file \"%s\": %m", t_thrd.proc_cxt.OutputFileName)));
}
istty = isatty(fd);
close(fd);
/*
* Redirect our stderr to the debug output file.
*/
if (!freopen(t_thrd.proc_cxt.OutputFileName, "a", stderr)) {
ereport(FATAL,
(errcode_for_file_access(),
errmsg("could not reopen file \"%s\" as stderr: %m", t_thrd.proc_cxt.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(t_thrd.proc_cxt.OutputFileName, "a", stdout)) {
ereport(FATAL,
(errcode_for_file_access(),
errmsg("could not reopen file \"%s\" as stdout: %m", t_thrd.proc_cxt.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 t_thrd.log_cxt.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 (u_sess->log_cxt.syslog_ident == NULL || strcmp(u_sess->log_cxt.syslog_ident, ident) != 0 ||
u_sess->attr.attr_common.syslog_facility != facility) {
if (t_thrd.log_cxt.openlog_done) {
closelog();
t_thrd.log_cxt.openlog_done = false;
}
if (u_sess->log_cxt.syslog_ident)
pfree(u_sess->log_cxt.syslog_ident);
u_sess->log_cxt.syslog_ident = MemoryContextStrdup(
SESS_GET_MEM_CXT_GROUP(MEMORY_CONTEXT_STORAGE), ident);
/* if the strdup fails, we will cope in write_syslog() */
u_sess->attr.attr_common.syslog_facility = facility;
}
}
/*
* Write a message line to syslog
*/
static void write_syslog(int level, const char* line)
{
const char* nlpos = NULL;
errno_t rc = EOK;
/* Open syslog connection if not done yet */
if (!t_thrd.log_cxt.openlog_done) {
openlog(u_sess->log_cxt.syslog_ident ? u_sess->log_cxt.syslog_ident : "postgres",
LOG_PID | LOG_NDELAY | LOG_NOWAIT,
u_sess->attr.attr_common.syslog_facility);
t_thrd.log_cxt.openlog_done = true;
}
/*
* We add a sequence number to each log message to suppress "same"
* messages.
*/
t_thrd.log_cxt.syslog_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).
*/
int 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);
rc = memcpy_s(buf, PG_SYSLOG_LIMIT + 1, line, buflen);
securec_check(rc, "\0", "\0");
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--;
/* else couldn't divide word boundary */
if (i > 0) {
buflen = i;
buf[i] = '\0';
}
}
chunk_nr++;
syslog(level, "[%lu-%d] %s", t_thrd.log_cxt.syslog_seq, chunk_nr, buf);
line += buflen;
len -= buflen;
}
} else {
/* message short enough */
syslog(level, "[%lu] %s", t_thrd.log_cxt.syslog_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 len)
{
WCHAR* utf16 = NULL;
int eventlevel = EVENTLOG_ERROR_TYPE;
static HANDLE evtHandle = INVALID_HANDLE_VALUE;
if (evtHandle == INVALID_HANDLE_VALUE) {
evtHandle = RegisterEventSource(
NULL, g_instance.attr.attr_common.event_source ? g_instance.attr.attr_common.event_source : "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;
}
/*
* Convert message to UTF16 text and write it with ReportEventW, but
* fall-back into ReportEventA if conversion failed.
*
* Also verify that we are not on our way into error recursion trouble due
* to error messages thrown deep inside pgwin32_toUTF16().
*/
if (GetDatabaseEncoding() != GetPlatformEncoding() && !in_error_recursion_trouble()) {
utf16 = pgwin32_toUTF16(line, len, NULL);
if (NULL != utf16) {
ReportEventW(evtHandle,
eventlevel,
0,
0, /* All events are Id 0 */
NULL,
1,
0,
(LPCWSTR*)&utf16,
NULL);
pfree(utf16);
return;
}
}
ReportEventA(evtHandle,
eventlevel,
0,
0, /* All events are Id 0 */
NULL,
1,
0,
&line,
NULL);
}
#endif /* WIN32 */
static void write_console(const char* line, int len)
{
int rc;
#ifdef WIN32
/*
* WriteConsoleW() will fail if stdout is redirected, so just fall through
* to writing unconverted to the logfile in this case.
*
* Since we palloc the structure required for conversion, also fall
* through to writing unconverted if we have not yet set up
* CurrentMemoryContext.
*/
if (GetDatabaseEncoding() != GetPlatformEncoding() && !in_error_recursion_trouble() &&
!t_thrd.postmaster_cxt.redirection_done && CurrentMemoryContext != NULL) {
WCHAR* utf16 = NULL;
int utf16len;
utf16 = pgwin32_toUTF16(line, len, &utf16len);
if (utf16 != NULL) {
HANDLE stdHandle;
DWORD written;
stdHandle = GetStdHandle(STD_ERROR_HANDLE);
if (WriteConsoleW(stdHandle, utf16, utf16len, &written, NULL)) {
pfree(utf16);
return;
}
/*
* In case WriteConsoleW() failed, fall back to writing the
* message unconverted.
*/
pfree(utf16);
}
}
#else
/*
* Conversion on non-win32 platforms is not implemented yet. It requires
* non-throw version of pg_do_encoding_conversion(), that converts
* unconvertable characters to '?' without errors.
*/
#endif
/*
* We ignore any error from write() here. We have no useful way to report
* it ... certainly whining on stderr isn't likely to be productive.
*/
rc = write(fileno(stderr), line, len);
(void)rc;
}
/*
* setup t_thrd.log_cxt.formatted_log_time, for consistent times between CSV and regular logs
*/
static void setup_formatted_log_time(void)
{
struct timeval tv;
pg_time_t stamp_time;
char msbuf[8];
errno_t rc = EOK;
pg_tm* localTime = NULL;
gettimeofday(&tv, NULL);
stamp_time = (pg_time_t)tv.tv_sec;
/*
* Note: we expect that guc.c will ensure that log_timezone is set up (at
* least with a minimal GMT value) before u_sess->attr.attr_common.Log_line_prefix can become
* nonempty or CSV mode can be selected.
*/
localTime = pg_localtime(&stamp_time, log_timezone);
if (localTime == NULL) {
ereport(ERROR, (errmsg("pg_localtime must not be null!")));
}
pg_strftime(t_thrd.log_cxt.formatted_log_time,
FORMATTED_TS_LEN,
/* leave room for milliseconds... */
"%Y-%m-%d %H:%M:%S %Z",
localTime);
/* 'paste' milliseconds into place... */
rc = sprintf_s(msbuf, sizeof(msbuf), ".%03d", (int)(tv.tv_usec / 1000));
securec_check_ss(rc, "\0", "\0");
rc = strncpy_s(t_thrd.log_cxt.formatted_log_time + 19, FORMATTED_TS_LEN - 19, msbuf, 4);
securec_check(rc, "\0", "\0");
}
/*
* setup t_thrd.log_cxt.formatted_start_time
*/
static void setup_formatted_start_time(void)
{
pg_tm* localTime = NULL;
pg_time_t stamp_time = (pg_time_t)t_thrd.proc_cxt.MyStartTime;
/*
* Note: we expect that guc.c will ensure that log_timezone is set up (at
* least with a minimal GMT value) before u_sess->attr.attr_common.Log_line_prefix can become
* nonempty or CSV mode can be selected.
*/
localTime = pg_localtime(&stamp_time, log_timezone);
if (localTime == NULL) {
ereport(ERROR, (errmsg("pg_localtime must not be null!")));
}
pg_strftime(t_thrd.log_cxt.formatted_start_time,
FORMATTED_TS_LEN,
"%Y-%m-%d %H:%M:%S %Z",
localTime);
}
/*
* Format tag info for log lines; append to the provided buffer.
*/
static void log_line_prefix(StringInfo buf, ErrorData* edata)
{
int format_len;
int i;
pg_tm* localTime = NULL;
t_thrd.log_cxt.error_with_nodename = false;
/*
* 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
* t_thrd.proc_cxt.MyProcPid changes. t_thrd.proc_cxt.MyStartTime also changes when t_thrd.proc_cxt.MyProcPid does,
* so reset the formatted start timestamp too.
*/
if (t_thrd.log_cxt.log_my_pid != t_thrd.proc_cxt.MyProcPid) {
t_thrd.log_cxt.log_line_number = 0;
t_thrd.log_cxt.log_my_pid = t_thrd.proc_cxt.MyProcPid;
t_thrd.log_cxt.formatted_start_time[0] = '\0';
}
t_thrd.log_cxt.log_line_number++;
if (edata->hide_prefix)
return;
if (u_sess->attr.attr_common.Log_line_prefix == NULL) {
/* for --single, do not append query id */
if (IsPostmasterEnvironment)
appendStringInfo(buf, "%lu ", u_sess->debug_query_id);
return; /* in case guc hasn't run yet */
}
format_len = strlen(u_sess->attr.attr_common.Log_line_prefix);
for (i = 0; i < format_len; i++) {
if (u_sess->attr.attr_common.Log_line_prefix[i] != '%') {
/* literal char, just copy */
appendStringInfoChar(buf, u_sess->attr.attr_common.Log_line_prefix[i]);
continue;
}
/* go to char after '%' */
i++;
if (i >= format_len)
break; /* format error - ignore it */
/* process the option */
switch (u_sess->attr.attr_common.Log_line_prefix[i]) {
case 'a':
if (u_sess->proc_cxt.MyProcPort && u_sess->attr.attr_common.application_name &&
u_sess->attr.attr_common.application_name[0] != '\0') {
appendStringInfoString(buf, u_sess->attr.attr_common.application_name);
} else {
appendStringInfoString(buf, "[unknown]");
}
break;
case 'u':
if (u_sess->proc_cxt.MyProcPort && u_sess->proc_cxt.MyProcPort->user_name &&
u_sess->proc_cxt.MyProcPort->user_name[0] != '\0') {
appendStringInfoString(buf, u_sess->proc_cxt.MyProcPort->user_name);
} else {
appendStringInfoString(buf, "[unknown]");
}
break;
case 'd':
if (u_sess->proc_cxt.MyProcPort && u_sess->proc_cxt.MyProcPort->database_name &&
u_sess->proc_cxt.MyProcPort->database_name[0] != '\0') {
appendStringInfoString(buf, u_sess->proc_cxt.MyProcPort->database_name);
} else {
appendStringInfoString(buf, "[unknown]");
}
break;
case 'c':
appendStringInfo(buf, "%lx.%d", (long)(t_thrd.proc_cxt.MyStartTime), t_thrd.myLogicTid);
break;
case 'p':
appendStringInfo(buf, "%lu", t_thrd.proc_cxt.MyProcPid);
break;
case 'l':
appendStringInfo(buf, "%ld", t_thrd.log_cxt.log_line_number);
break;
case 'm':
setup_formatted_log_time();
appendStringInfoString(buf, t_thrd.log_cxt.formatted_log_time);
break;
case 't': {
pg_time_t stamp_time = (pg_time_t)time(NULL);
char strfbuf[128];
localTime = pg_localtime(&stamp_time, log_timezone);
if (localTime == NULL) {
ereport(ERROR, (errmsg("pg_localtime must not be null!")));
}
pg_strftime(strfbuf, sizeof(strfbuf), "%Y-%m-%d %H:%M:%S %Z", localTime);
appendStringInfoString(buf, strfbuf);
} break;
case 's':
if (t_thrd.log_cxt.formatted_start_time[0] == '\0')
setup_formatted_start_time();
appendStringInfoString(buf, t_thrd.log_cxt.formatted_start_time);
break;
case 'i':
if (u_sess->proc_cxt.MyProcPort) {
const char* psdisp = NULL;
int displen;
psdisp = get_ps_display(&displen);
appendBinaryStringInfo(buf, psdisp, displen);
} else {
appendStringInfoString(buf, "[unknown]");
}
break;
case 'r':
if (u_sess->proc_cxt.MyProcPort && u_sess->proc_cxt.MyProcPort->remote_host) {
appendStringInfoString(buf, u_sess->proc_cxt.MyProcPort->remote_host);
if (u_sess->proc_cxt.MyProcPort->remote_port && u_sess->proc_cxt.MyProcPort->remote_port[0] != '\0')
appendStringInfo(buf, "(%s)", u_sess->proc_cxt.MyProcPort->remote_port);
} else {
appendStringInfoString(buf, "localhost");
}
break;
case 'h':
if (u_sess->proc_cxt.MyProcPort && u_sess->proc_cxt.MyProcPort->remote_host)
appendStringInfoString(buf, u_sess->proc_cxt.MyProcPort->remote_host);
else
appendStringInfoString(buf, "localhost");
break;
case 'q':
/* in postmaster and friends, stop if %q is seen */
/* in a backend, just ignore */
if (u_sess->proc_cxt.MyProcPort == NULL)
i = format_len;
break;
case 'v':
/* keep VXID format in sync with lockfuncs.c */
if (t_thrd.proc != NULL && t_thrd.proc->backendId != InvalidBackendId)
appendStringInfo(buf, "%d/" XID_FMT, t_thrd.proc->backendId, t_thrd.proc->lxid);
else {
appendStringInfo(buf, "0/0");
}
break;
case 'x':
appendStringInfo(buf, XID_FMT, GetTopTransactionIdIfAny());
break;
case 'e':
appendStringInfoString(buf, plpgsql_get_sqlstate(edata->sqlerrcode));
break;
case 'n':
appendStringInfoString(buf, g_instance.attr.attr_common.PGXCNodeName);
t_thrd.log_cxt.error_with_nodename = true;
break;
case 'S':
appendStringInfo(buf, "%lu[%d:%lu#%lu]", u_sess->session_id,
(int)u_sess->globalSessionId.nodeId,
u_sess->globalSessionId.sessionId, u_sess->globalSessionId.seq);
break;
case 'T':
if (u_sess->trace_cxt.trace_id[0]) {
appendStringInfo(buf, "%s", u_sess->trace_cxt.trace_id);
}
break;
case '%':
appendStringInfoChar(buf, '%');
break;
default:
/* format error - ignore it */
break;
}
}
/* for --single, do not append query id */
if (IsPostmasterEnvironment)
appendStringInfo(buf, " %lu", u_sess->debug_query_id);
/* module name information */
appendStringInfo(buf, " [%s] ", get_valid_module_name(edata->mod_id));
}
/*
* append a CSV'd version of a string to a StringInfo
* We use the openGauss defaults for CSV, i.e. quote = escape = '"'
* If it's NULL, append nothing.
*/
static inline void appendCSVLiteral(StringInfo buf, const char* data)
{
const char* p = data;
char c;
/* avoid confusing an empty string with NULL */
if (p == NULL)
return;
appendStringInfoCharMacro(buf, '"');
while ((c = *p++) != '\0') {
if (c == '"')
appendStringInfoCharMacro(buf, '"');
appendStringInfoCharMacro(buf, c);
}
appendStringInfoCharMacro(buf, '"');
}
/*
* Constructs the error message, depending on the Errordata it gets, in a CSV
* format which is described in doc/src/sgml/config.sgml.
*/
static void write_csvlog(ErrorData* edata)
{
StringInfoData buf;
bool print_stmt = false;
/*
* 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
* t_thrd.proc_cxt.MyProcPid changes.
*/
if (t_thrd.log_cxt.csv_log_my_pid != t_thrd.proc_cxt.MyProcPid) {
t_thrd.log_cxt.csv_log_line_number = 0;
t_thrd.log_cxt.csv_log_my_pid = t_thrd.proc_cxt.MyProcPid;
t_thrd.log_cxt.formatted_start_time[0] = '\0';
}
t_thrd.log_cxt.csv_log_line_number++;
initStringInfo(&buf);
/*
* timestamp with milliseconds
*
* Check if the timestamp is already calculated for the syslog message,
* and use it if so. Otherwise, get the current timestamp. This is done
* to put same timestamp in both syslog and csvlog messages.
*/
if (t_thrd.log_cxt.formatted_log_time[0] == '\0') {
setup_formatted_log_time();
}
/* @CSV_SCHMA@ log_time timestamp with time zone, @ */
appendStringInfoString(&buf, t_thrd.log_cxt.formatted_log_time);
appendStringInfoChar(&buf, ',');
/* @CSV_SCHMA@ node_name text, @ */
appendCSVLiteral(&buf, g_instance.attr.attr_common.PGXCNodeName);
appendStringInfoChar(&buf, ',');
/* username */
/* @CSV_SCHMA@ user_name text, @ */
if (u_sess->proc_cxt.MyProcPort) {
appendCSVLiteral(&buf, u_sess->proc_cxt.MyProcPort->user_name);
}
appendStringInfoChar(&buf, ',');
/* database name */
/* @CSV_SCHMA@ dbname text, @ */
if (u_sess->proc_cxt.MyProcPort) {
appendCSVLiteral(&buf, u_sess->proc_cxt.MyProcPort->database_name);
}
appendStringInfoChar(&buf, ',');
/* Process id */
/* @CSV_SCHMA@ thread_id bigint, @ */
if (t_thrd.proc_cxt.MyProcPid != 0)
appendStringInfo(&buf, "%lu", t_thrd.proc_cxt.MyProcPid);
appendStringInfoChar(&buf, ',');
/* Remote host and port */
/* @CSV_SCHMA@ remote_host text, @ */
if (u_sess->proc_cxt.MyProcPort && u_sess->proc_cxt.MyProcPort->remote_host) {
appendStringInfoChar(&buf, '"');
appendStringInfoString(&buf, u_sess->proc_cxt.MyProcPort->remote_host);
if (u_sess->proc_cxt.MyProcPort->remote_port != NULL && u_sess->proc_cxt.MyProcPort->remote_port[0] != '\0') {
appendStringInfoChar(&buf, ':');
appendStringInfoString(&buf, u_sess->proc_cxt.MyProcPort->remote_port);
}
appendStringInfoChar(&buf, '"');
}
appendStringInfoChar(&buf, ',');
/* session id */
/* OLAP: keep the same value with %c in log_line_prefix, so
* replace t_thrd.proc_cxt.MyProcPid with myLogicTid.
*/
/* @CSV_SCHMA@ session_id text, @ */
appendStringInfo(&buf, "%lx.%d", (long)t_thrd.proc_cxt.MyStartTime, t_thrd.myLogicTid);
appendStringInfoChar(&buf, ',');
/* Line number */
/* @CSV_SCHMA@ lineno bigint, @ */
appendStringInfo(&buf, "%ld", t_thrd.log_cxt.csv_log_line_number);
appendStringInfoChar(&buf, ',');
/* PS display */
/* @CSV_SCHMA@ psdisp text, @ */
if (u_sess->proc_cxt.MyProcPort) {
StringInfoData msgbuf;
const char* psdisp = NULL;
int displen = 0;
initStringInfo(&msgbuf);
psdisp = get_ps_display(&displen);
appendBinaryStringInfo(&msgbuf, psdisp, displen);
appendCSVLiteral(&buf, msgbuf.data);
pfree(msgbuf.data);
}
appendStringInfoChar(&buf, ',');
/* session start timestamp */
/* @CSV_SCHMA@ session_start_tm timestamp with time zone , @ */
if (t_thrd.log_cxt.formatted_start_time[0] == '\0') {
setup_formatted_start_time();
}
appendStringInfoString(&buf, t_thrd.log_cxt.formatted_start_time);
appendStringInfoChar(&buf, ',');
/* Virtual transaction id */
/* keep VXID format in sync with lockfuncs.c */
/* @CSV_SCHMA@ vxid text , @ */
if (t_thrd.proc != NULL && t_thrd.proc->backendId != InvalidBackendId) {
appendStringInfo(&buf, "%d/" XID_FMT, t_thrd.proc->backendId, t_thrd.proc->lxid);
}
appendStringInfoChar(&buf, ',');
/* Transaction id */
/* @CSV_SCHMA@ xid bigint , @ */
appendStringInfo(&buf, XID_FMT, GetTopTransactionIdIfAny());
appendStringInfoChar(&buf, ',');
/* OLAP: debug query id */
/* @CSV_SCHMA@ query_id bigint , @ */
if (IsPostmasterEnvironment) {
appendStringInfo(&buf, "%lu", u_sess->debug_query_id);
}
appendStringInfoChar(&buf, ',');
/* OLAP: Module/Feature ID */
/* @CSV_SCHMA@ module text , @ */
appendStringInfoChar(&buf, '"');
appendStringInfo(&buf, "%s", get_valid_module_name(edata->mod_id));
appendStringInfoChar(&buf, '"');
appendStringInfoChar(&buf, ',');
/* Error severity */
/* @CSV_SCHMA@ log_level text, @ */
appendStringInfoString(&buf, error_severity(edata->elevel));
appendStringInfoChar(&buf, ',');
/* SQL state code */
/* @CSV_SCHMA@ sql_state text, @ */
appendStringInfoString(&buf, plpgsql_get_sqlstate(edata->sqlerrcode));
appendStringInfoChar(&buf, ',');
/* errmessage */
/* @CSV_SCHMA@ msg text, @ */
appendCSVLiteral(&buf, edata->message);
appendStringInfoChar(&buf, ',');
/* errdetail or errdetail_log */
/* @CSV_SCHMA@ detail text, @ */
if (edata->detail_log)
appendCSVLiteral(&buf, edata->detail_log);
else
appendCSVLiteral(&buf, edata->detail);
appendStringInfoChar(&buf, ',');
/* errhint */
/* @CSV_SCHMA@ hint text, @ */
appendCSVLiteral(&buf, edata->hint);
appendStringInfoChar(&buf, ',');
/* internal query */
/* @CSV_SCHMA@ internal_query text, @ */
if (edata->internalquery) {
char* mask_string = NULL;
/* mask the query whenever including sensitive information. */
mask_string = maskPassword(edata->internalquery);
/* with nothing to mask, just use the source query. */
if (mask_string == NULL)
mask_string = edata->internalquery;
appendCSVLiteral(&buf, mask_string);
appendStringInfoChar(&buf, ',');
/* free the memory malloced for mask_string. */
if (mask_string != edata->internalquery)
pfree(mask_string);
} else {
appendCSVLiteral(&buf, edata->internalquery);
appendStringInfoChar(&buf, ',');
}
/* if printed internal query, print internal pos too */
/* @CSV_SCHMA@ internal_pos int, @ */
if (edata->internalpos > 0 && edata->internalquery != NULL)
appendStringInfo(&buf, "%d", edata->internalpos);
appendStringInfoChar(&buf, ',');
/* errcontext */
/* @CSV_SCHMA@ errcontext text, @ */
appendCSVLiteral(&buf, edata->context);
appendStringInfoChar(&buf, ',');
/* user query --- only reported if not disabled by the caller */
if (is_log_level_output(edata->elevel, u_sess->attr.attr_common.log_min_error_statement) &&
t_thrd.postgres_cxt.debug_query_string != NULL && !edata->hide_stmt)
print_stmt = true;
/* @CSV_SCHMA@ user_query text, @ */
if (print_stmt) {
char* mask_string = maskPassword(t_thrd.postgres_cxt.debug_query_string);
if (mask_string == NULL) {
mask_string = (char*)t_thrd.postgres_cxt.debug_query_string;
}
appendCSVLiteral(&buf, mask_string);
if (mask_string != t_thrd.postgres_cxt.debug_query_string) {
pfree(mask_string);
}
}
appendStringInfoChar(&buf, ',');
/* @CSV_SCHMA@ user_query_pos int, @ */
if (print_stmt && edata->cursorpos > 0) {
appendStringInfo(&buf, "%d", edata->cursorpos);
}
appendStringInfoChar(&buf, ',');
/* file error location */
/* @CSV_SCHMA@ fun_name text, @ */
/* @CSV_SCHMA@ file_location text, @ */
if (u_sess->attr.attr_common.Log_error_verbosity >= PGERROR_VERBOSE) {
StringInfoData msgbuf;
initStringInfo(&msgbuf);
if (edata->funcname && edata->filename) {
appendStringInfo(&msgbuf, "%s,%s:%d", edata->funcname, edata->filename, edata->lineno);
} else if (edata->filename) {
/* make filename field null */
appendStringInfo(&msgbuf, ",%s:%d", edata->filename, edata->lineno);
}
appendCSVLiteral(&buf, msgbuf.data);
pfree(msgbuf.data);
}
appendStringInfoChar(&buf, ',');
/* application name */
/* @CSV_SCHMA@ appname text @ */
if (u_sess->attr.attr_common.application_name) {
appendCSVLiteral(&buf, u_sess->attr.attr_common.application_name);
}
/* append line end char */
appendStringInfoChar(&buf, '\n');
/* If in the syslogger process, try to write messages direct to file */
if (t_thrd.role == SYSLOGGER) {
write_syslogger_file(buf.data, buf.len, LOG_DESTINATION_CSVLOG);
} else {
write_pipe_chunks(buf.data, buf.len, LOG_DESTINATION_CSVLOG);
}
pfree(buf.data);
}
/*
* Unpack MAKE_SQLSTATE code. Note that this returns a pointer to a
* static THR_LOCAL buffer.
*/
const char* unpack_sql_state(int sql_state)
{
char* buf = t_thrd.buf_cxt.unpack_sql_state_buf;
int i;
for (i = 0; i < 5; i++) {
buf[i] = PGUNSIXBIT(sql_state);
sql_state >>= 6;
}
buf[i] = '\0';
return buf;
}
/* if sqlcode is init by the database, it must be positive; if is init by the user, then it must be negative */
const char *plpgsql_get_sqlstate(int sqlcode)
{
if (sqlcode >= 0) {
return unpack_sql_state(sqlcode);
} else {
return plpgsql_code_int2cstring(sqlcode);
}
}
const char* mask_encrypted_key(const char *query_string, int str_len)
{
int mask_len = 8;
const char *mask_string = NULL;
errno_t rc = EOK;
const char *column_setting_str = "encrypted_value";
size_t iden_len = strlen(column_setting_str);
char *lower_string = (char*)palloc0(str_len + 1);
rc = memcpy_s(lower_string, str_len + 1, query_string, str_len);
securec_check(rc, "\0", "\0");
tolower_func(lower_string);
char *word_ptr = strstr(lower_string, column_setting_str);
if (word_ptr == NULL) {
pfree_ext(lower_string);
return query_string;
}
char *tmp_string = (char*)palloc0(str_len + 1);
rc = memcpy_s(tmp_string, str_len + 1, query_string, str_len);
securec_check(rc, "\0", "\0");
word_ptr = tmp_string + (word_ptr - lower_string);
char *token_ptr = word_ptr + iden_len;
size_t head_len = word_ptr - tmp_string + iden_len;
char *first_quote = NULL;
char *second_quote = NULL;
for (size_t i = 0; i < str_len - head_len; i++) {
if (token_ptr[i] == '\'') {
if (first_quote == NULL) {
first_quote = token_ptr + i;
} else if (second_quote == NULL) {
second_quote = token_ptr + i;
}
}
}
if (first_quote == NULL || second_quote == NULL) {
if (token_ptr != NULL && strlen(token_ptr) > 0) {
token_ptr[0] = '\0';
}
mask_string = MemoryContextStrdup(SESS_GET_MEM_CXT_GROUP(MEMORY_CONTEXT_SECURITY), tmp_string);
pfree_ext(lower_string);
pfree_ext(tmp_string);
return mask_string;
}
int offset = second_quote - first_quote - 1;
if (offset <= 0) {
pfree_ext(lower_string);
pfree_ext(tmp_string);
return query_string;
}
if (offset < mask_len) {
mask_len = offset;
}
int first_quote_len = strlen(first_quote);
rc = memset_s(first_quote + 1, offset, '*', mask_len);
securec_check(rc, "\0", "\0");
rc = memmove_s(first_quote + mask_len + 1, first_quote_len - mask_len - 1, second_quote, strlen(second_quote));
securec_check(rc, "\0", "\0");
first_quote[first_quote_len - offset + mask_len] = '\0';
pfree_ext(lower_string);
mask_string = MemoryContextStrdup(SESS_GET_MEM_CXT_GROUP(MEMORY_CONTEXT_SECURITY), tmp_string);
pfree_ext(tmp_string);
return mask_string;
}
int output_backtrace_to_log(StringInfoData* pOutBuf)
{
const int max_buffer_size = 128;
void* buffer[max_buffer_size];
char title[max_buffer_size];
AutoMutexLock btLock(&bt_lock);
btLock.lock();
t_thrd.log_cxt.output_backtrace_log = true;
if (t_thrd.log_cxt.thd_bt_symbol) {
free(t_thrd.log_cxt.thd_bt_symbol);
t_thrd.log_cxt.thd_bt_symbol = NULL;
}
int len_symbols = backtrace(buffer, max_buffer_size);
t_thrd.log_cxt.thd_bt_symbol = backtrace_symbols(buffer, len_symbols);
int ret = snprintf_s(title, sizeof(title), sizeof(title) - 1, "tid[%d]'s backtrace:\n", gettid());
securec_check_ss_c(ret, "\0", "\0");
appendStringInfoString(pOutBuf, title);
if (NULL == t_thrd.log_cxt.thd_bt_symbol) {
appendStringInfoString(pOutBuf, "Failed to get backtrace symbols.\n");
t_thrd.log_cxt.output_backtrace_log = false;
btLock.unLock();
return -1;
}
for (int i = 0; i < len_symbols; i++) {
appendStringInfoString(pOutBuf, t_thrd.log_cxt.thd_bt_symbol[i]);
appendStringInfoString(pOutBuf, "\n");
}
appendStringInfoString(pOutBuf, "Use addr2line to get pretty function name and line\n");
/*
* If above code longjmp, we should free this pointer when call this function again.
* for normal case, free it when exit from function.
*/
free(t_thrd.log_cxt.thd_bt_symbol);
t_thrd.log_cxt.thd_bt_symbol = NULL;
t_thrd.log_cxt.output_backtrace_log = false;
btLock.unLock();
return 0;
}
/*
* Write error report to server's log
*/
static void send_message_to_server_log(ErrorData* edata)
{
StringInfoData buf;
initStringInfo(&buf);
t_thrd.log_cxt.formatted_log_time[0] = '\0';
if (!edata->hide_prefix) {
log_line_prefix(&buf, edata);
appendStringInfo(&buf, "%s: ", error_severity(edata->elevel));
}
if (u_sess->attr.attr_common.Log_error_verbosity >= PGERROR_VERBOSE && !edata->hide_prefix)
appendStringInfo(&buf, "%s: ", plpgsql_get_sqlstate(edata->sqlerrcode));
if (edata->message) {
append_with_tabs(&buf, edata->message);
} 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 (u_sess->attr.attr_common.Log_error_verbosity >= PGERROR_DEFAULT && !edata->hide_prefix) {
if (edata->detail_log) {
log_line_prefix(&buf, edata);
appendStringInfoString(&buf, _("DETAIL: "));
append_with_tabs(&buf, edata->detail_log);
appendStringInfoChar(&buf, '\n');
} else if (edata->detail) {
log_line_prefix(&buf, edata);
appendStringInfoString(&buf, _("DETAIL: "));
append_with_tabs(&buf, edata->detail);
appendStringInfoChar(&buf, '\n');
}
if (edata->hint) {
log_line_prefix(&buf, edata);
appendStringInfoString(&buf, _("HINT: "));
append_with_tabs(&buf, edata->hint);
appendStringInfoChar(&buf, '\n');
}
if (edata->cause) {
log_line_prefix(&buf, edata);
appendStringInfoString(&buf, _("CAUSE: "));
append_with_tabs(&buf, edata->cause);
appendStringInfoChar(&buf, '\n');
}
if (edata->action) {
log_line_prefix(&buf, edata);
appendStringInfoString(&buf, _("ACTION: "));
append_with_tabs(&buf, edata->action);
appendStringInfoChar(&buf, '\n');
}
if (edata->internalquery) {
char* mask_string = NULL;
log_line_prefix(&buf, edata);
appendStringInfoString(&buf, _("QUERY: "));
mask_string = maskPassword(edata->internalquery);
if (mask_string == NULL) {
mask_string = (char*)edata->internalquery;
}
append_with_tabs(&buf, mask_string);
if (mask_string != edata->internalquery) {
pfree(mask_string);
}
appendStringInfoChar(&buf, '\n');
}
if (edata->context) {
log_line_prefix(&buf, edata);
appendStringInfoString(&buf, _("CONTEXT: "));
append_with_tabs(&buf, edata->context);
appendStringInfoChar(&buf, '\n');
}
if (u_sess->attr.attr_common.Log_error_verbosity >= PGERROR_VERBOSE && !edata->hide_prefix) {
/* assume no newlines in funcname or filename... */
if (edata->funcname && edata->filename) {
log_line_prefix(&buf, edata);
appendStringInfo(&buf, _("LOCATION: %s, %s:%d\n"), edata->funcname, edata->filename, edata->lineno);
} else if (edata->filename) {
log_line_prefix(&buf, edata);
appendStringInfo(&buf, _("LOCATION: %s:%d\n"), edata->filename, edata->lineno);
}
}
}
/* Omit the query part for non-error messages in Datanode. */
if (IS_PGXC_DATANODE && edata->elevel < ERROR)
edata->hide_stmt = true;
/*
* If the user wants the query that generated this error logged, do it.
*/
if (is_log_level_output(edata->elevel, u_sess->attr.attr_common.log_min_error_statement) &&
t_thrd.postgres_cxt.debug_query_string != NULL && !edata->hide_stmt) {
char* randomPlanInfo = NULL;
char* mask_string = NULL;
mask_string = maskPassword(t_thrd.postgres_cxt.debug_query_string);
/*
* For B-compatible database with dolphin, there're some scenarios that
* maskPassword couldn't cover.
*/
if (edata->sqlerrcode == ERRCODE_SYNTAX_ERROR || u_sess->attr.attr_sql.dolphin) {
if (mask_string != NULL) {
truncate_identified_by(mask_string, strlen(mask_string));
} else {
mask_string = mask_error_password(t_thrd.postgres_cxt.debug_query_string,
strlen(t_thrd.postgres_cxt.debug_query_string));
}
}
if (mask_string == NULL)
mask_string = (char*)t_thrd.postgres_cxt.debug_query_string;
char* mask_key = (char *)mask_encrypted_key(mask_string, strlen(mask_string));
if (mask_key != mask_string) {
if (mask_string != t_thrd.postgres_cxt.debug_query_string) {
pfree(mask_string);
}
mask_string = mask_key;
}
log_line_prefix(&buf, edata);
appendStringInfoString(&buf, _("STATEMENT: "));
/*
* In log injection attack scene, syntax error and espaced characters are dangerous,
* we need mask the espaced characters here.
*/
if (ERRCODE_SYNTAX_ERROR == edata->sqlerrcode)
mask_espaced_character(mask_string);
append_with_tabs(&buf, mask_string);
appendStringInfoChar(&buf, '\n');
/* show random plan seed if u_sess->attr.attr_sql.plan_mode_seed is not OPTIMIZE_PLAN */
randomPlanInfo = get_random_plan_string();
if (NULL != randomPlanInfo) {
appendStringInfoString(&buf, randomPlanInfo);
appendStringInfoChar(&buf, '\n');
pfree(randomPlanInfo);
}
if (mask_string != t_thrd.postgres_cxt.debug_query_string)
pfree(mask_string);
}
if (edata->backtrace_log) {
log_line_prefix(&buf, edata);
appendStringInfoString(&buf, _("BACKTRACELOG: "));
append_with_tabs(&buf, edata->backtrace_log);
appendStringInfoChar(&buf, '\n');
}
#ifdef HAVE_SYSLOG
/* Write to syslog, if enabled */
if (t_thrd.log_cxt.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 (t_thrd.log_cxt.Log_destination & LOG_DESTINATION_EVENTLOG) {
write_eventlog(edata->elevel, buf.data, buf.len);
}
#endif /* WIN32 */
/* Write to stderr, if enabled */
if ((t_thrd.log_cxt.Log_destination & LOG_DESTINATION_STDERR) ||
t_thrd.postgres_cxt.whereToSendOutput == DestDebug) {
/*
* Use the chunking protocol if we know the syslogger should be
* catching stderr output, and we are not ourselves the syslogger.
* Otherwise, just do a vanilla write to stderr.
*/
if (t_thrd.postmaster_cxt.redirection_done && t_thrd.role != SYSLOGGER) {
if (edata->sqlerrcode == ERRCODE_SLOW_QUERY)
write_local_slow_log(buf.data, buf.len, true);
else if (edata->sqlerrcode == ERRCODE_ACTIVE_SESSION_PROFILE)
write_asplog(buf.data, buf.len, true);
else
write_pipe_chunks(buf.data, buf.len, LOG_DESTINATION_STDERR);
#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.
*/
} else if (pgwin32_is_service())
write_eventlog(edata->elevel, buf.data, buf.len);
#endif
} else if (t_thrd.role != SYSLOGGER)
write_console(buf.data, buf.len);
}
/* If in the syslogger process, try to write messages direct to file */
if (t_thrd.role == SYSLOGGER &&
t_thrd.log_cxt.Log_destination & LOG_DESTINATION_STDERR &&
t_thrd.logger.syslogFile != NULL) {
write_syslogger_file(buf.data, buf.len, LOG_DESTINATION_STDERR);
}
/* Write to CSV log if enabled */
if (t_thrd.log_cxt.Log_destination & LOG_DESTINATION_CSVLOG) {
if (t_thrd.postmaster_cxt.redirection_done || t_thrd.role == SYSLOGGER) {
/*
* send CSV data if it's safe to do so (syslogger doesn't need the
* pipe). First get back the space in the message buffer.
*/
pfree(buf.data);
write_csvlog(edata);
} else {
/*
* syslogger not up (yet), so just dump the message to stderr,
* unless we already did so above.
*/
if (!(t_thrd.log_cxt.Log_destination & LOG_DESTINATION_STDERR) &&
t_thrd.postgres_cxt.whereToSendOutput != DestDebug)
write_console(buf.data, buf.len);
pfree(buf.data);
}
} else {
pfree(buf.data);
}
}
/*
* @Description: Write error report to server's log in a simple way without errstack
* @param: elevel, fmt
* @return: void
*/
void SimpleLogToServer(int elevel, bool silent, const char* fmt, ...)
{
if (silent == true || !is_log_level_output(elevel, log_min_messages))
return;
ErrorData errdata;
ErrorData* edata = NULL;
MemoryContext oldcontext;
/* Initialize a mostly-dummy error frame */
edata = &errdata;
errno_t rc = EOK;
rc = memset_s(edata, sizeof(ErrorData), 0, sizeof(ErrorData));
securec_check(rc, "", "");
/* the default text domain is the backend's */
edata->domain = t_thrd.log_cxt.save_format_domain ? t_thrd.log_cxt.save_format_domain : PG_TEXTDOMAIN("postgres");
/* set the errno to be used to interpret %m */
edata->saved_errno = t_thrd.log_cxt.save_format_errnumber;
edata->elevel = elevel;
edata->mod_id = MOD_CN_RETRY;
oldcontext = MemoryContextSwitchTo(ErrorContext);
EVALUATE_MESSAGE(message, false, true);
MemoryContextSwitchTo(oldcontext);
send_message_to_server_log(edata);
}
/*
* @Description: Write error report to server's log for stream thread
*
* @param: void
* @return: void
*/
void stream_send_message_to_server_log(void)
{
ErrorData* edata = &t_thrd.log_cxt.errordata[t_thrd.log_cxt.errordata_stack_depth];
MemoryContext oldcontext;
/*
* Since cancel is always driven by Coordinator, internal-cancel message
* of stream thread can be ignored to avoid message misorder.
*/
if (ERRCODE_QUERY_INTERNAL_CANCEL == edata->sqlerrcode)
return;
t_thrd.log_cxt.recursion_depth++;
CHECK_STACK_DEPTH();
oldcontext = MemoryContextSwitchTo(ErrorContext);
/* Send to server log, if enabled */
if (edata->output_to_server && is_errmodule_enable(edata->elevel, edata->mod_id))
send_message_to_server_log(edata);
MemoryContextSwitchTo(oldcontext);
t_thrd.log_cxt.recursion_depth--;
}
/*
* @Description: Write error report to client for stream thread
*
* @param: void
* @return: void
*/
void stream_send_message_to_consumer(void)
{
ErrorData* edata = &t_thrd.log_cxt.errordata[t_thrd.log_cxt.errordata_stack_depth];
MemoryContext oldcontext;
/*
* Since cancel is always driven by Coordinator, internal-cancel message
* of stream thread can be ignored to avoid message misorder.
*/
t_thrd.log_cxt.recursion_depth++;
CHECK_STACK_DEPTH();
oldcontext = MemoryContextSwitchTo(ErrorContext);
send_message_to_frontend(edata);
MemoryContextSwitchTo(oldcontext);
t_thrd.log_cxt.recursion_depth--;
}
/*
* Send data to the syslogger using the chunked protocol
*
* Note: when there are multiple backends writing into the syslogger pipe,
* it's critical that each write go into the pipe indivisibly, and not
* get interleaved with data from other processes. Fortunately, the POSIX
* spec requires that writes to pipes be atomic so long as they are not
* more than PIPE_BUF bytes long. So we divide long messages into chunks
* that are no more than that length, and send one chunk per write() call.
* The collector process knows how to reassemble the chunks.
*
* Because of the atomic write requirement, there are only two possible
* results from write() here: -1 for failure, or the requested number of
* bytes. There is not really anything we can do about a failure; retry would
* probably be an infinite loop, and we can't even report the error usefully.
* (There is noplace else we could send it!) So we might as well just ignore
* the result from write(). However, on some platforms you get a compiler
* warning from ignoring write()'s result, so do a little dance with casting
* rc to void to shut up the compiler.
*/
static void write_pipe_chunks(char* data, int len, int dest)
{
LogPipeProtoChunk p;
int fd = fileno(stderr);
int rc;
Assert(len > 0);
p.proto.nuls[0] = p.proto.nuls[1] = '\0';
p.proto.pid = t_thrd.proc_cxt.MyProcPid;
p.proto.logtype = LOG_TYPE_ELOG;
p.proto.magic = PROTO_HEADER_MAGICNUM;
/* write all but the last chunk */
while (len > LOGPIPE_MAX_PAYLOAD) {
p.proto.is_last = (dest == LOG_DESTINATION_CSVLOG ? 'F' : 'f');
p.proto.len = LOGPIPE_MAX_PAYLOAD;
rc = memcpy_s(p.proto.data, LOGPIPE_MAX_PAYLOAD, data, LOGPIPE_MAX_PAYLOAD);
securec_check(rc, "\0", "\0");
rc = write(fd, &p, LOGPIPE_HEADER_SIZE + LOGPIPE_MAX_PAYLOAD);
(void)rc;
data += LOGPIPE_MAX_PAYLOAD;
len -= LOGPIPE_MAX_PAYLOAD;
}
/* write the last chunk */
p.proto.is_last = (dest == LOG_DESTINATION_CSVLOG ? 'T' : 't');
p.proto.len = len;
rc = memcpy_s(p.proto.data, len, data, len);
securec_check(rc, "\0", "\0");
rc = write(fd, &p, LOGPIPE_HEADER_SIZE + len);
(void)rc;
}
/*
* 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);
}
/*
* Brief : Write only message to client, is_putline is to declare the
* caller is dbe_output.print_line or dbe_output.print
*/
void send_only_message_to_frontend(const char* message, bool is_putline)
{
MemoryContext oldcontext;
bool alloced = false;
if (t_thrd.postgres_cxt.whereToSendOutput != DestRemote)
return;
/*
* 'N' (Notice) is for nonfatal conditions, 'E' is for errors
* see if the t_thrd.log_cxt.msgbuf is allocated. if so, append data to it
*/
if (!t_thrd.log_cxt.msgbuf->data) {
oldcontext = MemoryContextSwitchTo(THREAD_GET_MEM_CXT_GROUP(MEMORY_CONTEXT_DFX));
pq_beginmessage(t_thrd.log_cxt.msgbuf, 'N');
MemoryContextSwitchTo(oldcontext);
} else
alloced = true;
if (alloced) {
if (PG_PROTOCOL_MAJOR(FrontendProtocol) >= 3) {
err_sendstring(t_thrd.log_cxt.msgbuf, message);
t_thrd.log_cxt.msgbuf->len -= 1;
} else {
if (NULL != message) {
appendStringInfoString(t_thrd.log_cxt.msgbuf, message);
} else {
appendStringInfoString(t_thrd.log_cxt.msgbuf, _("missing error text"));
appendStringInfoChar(t_thrd.log_cxt.msgbuf, '\n');
}
}
} else if (PG_PROTOCOL_MAJOR(FrontendProtocol) >= 3) {
/* M field is required per protocol, so always send something */
pq_sendbyte(t_thrd.log_cxt.msgbuf, PG_DIAG_MESSAGE_ONLY);
err_sendstring(t_thrd.log_cxt.msgbuf, message);
t_thrd.log_cxt.msgbuf->len -= 1;
} else {
/* Old style --- gin up a backwards-compatible message */
StringInfoData buf;
initStringInfo(&buf);
if (NULL != message)
appendStringInfoString(&buf, message);
else
appendStringInfoString(&buf, _("missing error text"));
if (is_putline) {
appendStringInfoChar(&buf, '\n');
}
err_sendstring(t_thrd.log_cxt.msgbuf, buf.data);
pfree_ext(buf.data);
}
if (is_putline) {
if (PG_PROTOCOL_MAJOR(FrontendProtocol) >= 3) {
/* add two '\0' to adapt the Protocol, which has two '\0' at the end of data */
pq_sendbyte(t_thrd.log_cxt.msgbuf, '\0');
pq_sendbyte(t_thrd.log_cxt.msgbuf, '\0');
}
pq_endmessage(t_thrd.log_cxt.msgbuf);
}
}
/* Get internal error code by the location(filename and lineno) of error message arised */
static int pg_geterrcode_byerrmsg(ErrorData* edata)
{
unsigned int i = 0;
unsigned int j = 0;
const char* szExtName = NULL;
if (NULL == edata) {
return 0;
}
for (i = 0; i < lengthof(g_mppdb_errors); i++) {
for (j = 0; j < lengthof(g_mppdb_errors[i].astErrLocate); j++) {
if ((0 == strcmp(g_mppdb_errors[i].astErrLocate[j].szFileName, edata->filename)) &&
(g_mppdb_errors[i].astErrLocate[j].ulLineno == (unsigned int)edata->lineno)) {
return g_mppdb_errors[i].ulSqlErrcode;
} else if (0 == strcmp(g_mppdb_errors[i].astErrLocate[j].szFileName, edata->filename)) {
/* file name is valid or not */
szExtName = strrchr(edata->filename, '.');
if (NULL == szExtName) {
return 0;
}
/* *.l file */
if ((*(szExtName + 1) == 'l') &&
((g_mppdb_errors[i].astErrLocate[j].ulLineno + 1) == (unsigned int)edata->lineno)) {
return g_mppdb_errors[i].ulSqlErrcode;
}
}
}
}
return 0;
}
/**
* @Description: cn add all error info from dn
* @in/out pErrData - remote error data from dn
* @return - 0 is ok
*/
int combiner_errdata(RemoteErrorData* pErrData)
{
ErrorData* edata = &t_thrd.log_cxt.errordata[t_thrd.log_cxt.errordata_stack_depth];
/* we don't bother incrementing t_thrd.log_cxt.recursion_depth */
CHECK_STACK_DEPTH();
edata->internalerrcode = pErrData->internalerrcode;
edata->filename = pErrData->filename;
edata->funcname = pErrData->errorfuncname;
edata->lineno = pErrData->lineno;
edata->mod_id = pErrData->mod_id;
return 0; /* return value does not matter */
}
/*
* Write error report to client
*/
void send_message_to_frontend(ErrorData* edata)
{
StringInfoData msgbuf;
#ifndef USE_ASSERT_CHECKING
/* Send too much detail to client is not allowed, stored them in system log is enough. */
if (IS_PGXC_COORDINATOR && IsConnFromApp() && edata->elevel <= LOG)
return;
#endif
/*
* Since cancel is always driven by Coordinator, internal-cancel message
* of datanode openGauss thread can be ignored to avoid libcomm waitting quota in here.
* If a single node, always send message to front.
*/
/*
* Since the ('N') message is ignored in old PGXC handle_response, we
* can simply ignore the message here if not marked by handle_in_client
* when invoking ereport.
* If u_sess->utils_cxt.qunit_case_number != 0, it(CN/DN) serves as a QUNIT backend thread, and it(CN/DN)
* needs to send all ERROR messages to the client(gsql).
*/
if ((IsConnFromCoord() || StreamThreadAmI() ||
(t_thrd.proc_cxt.MyProgName != NULL && strcmp(t_thrd.proc_cxt.MyProgName, "BackgroundWorker") == 0)) &&
edata->elevel < ERROR && !edata->handle_in_client
#ifdef ENABLE_QUNIT
&& u_sess->utils_cxt.qunit_case_number == 0
#endif
)
return;
if (DB_IS_CMPT(B_FORMAT) && edata->sqlerrcode != ERRCODE_STACKED_DIAGNOSTICS_ACCESSED_WITHOUT_ACTIVE_HANDLER) {
pushErrorData(edata);
}
/* '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] = {0};
int ssval;
int i;
char vbuf[256] = {0};
errno_t rc = 0;
pq_sendbyte(&msgbuf, PG_DIAG_SEVERITY);
err_sendstring(&msgbuf, error_severity(edata->elevel));
/* get mpp internal errcode */
if (edata->elevel >= ERROR) {
if (0 == edata->internalerrcode && edata->filename && edata->lineno > 0) {
#ifdef ENABLE_MOT
/*
* In case of error from MOT module we skip getting the internal error code,
* since fdw classes are not scanned by scanEreport.cpp.
*/
if (IsMOTEngineUsed()) {
edata->internalerrcode = ERRCODE_SUCCESSFUL_COMPLETION;
} else {
#endif
edata->internalerrcode = pg_geterrcode_byerrmsg(edata);
#ifdef ENABLE_MOT
}
#endif
}
} else {
edata->internalerrcode = ERRCODE_SUCCESSFUL_COMPLETION;
}
rc = snprintf_s(tbuf, sizeof(tbuf), sizeof(tbuf) - 1, "%d", edata->internalerrcode);
securec_check_ss(rc, "\0", "\0");
pq_sendbyte(&msgbuf, PG_DIAG_INTERNEL_ERRCODE);
err_sendstring(&msgbuf, tbuf);
/* M field is required per protocol, so always send something */
pq_sendbyte(&msgbuf, PG_DIAG_MESSAGE_PRIMARY);
/* Add node_name before error message */
if (COORDINATOR_NOT_SINGLE && t_thrd.log_cxt.error_with_nodename) {
appendStringInfoString(&msgbuf, g_instance.attr.attr_common.PGXCNodeName);
appendStringInfoString(&msgbuf, ": ");
}
if (edata->message) {
if (edata->verbose) {
rc = snprintf_s(vbuf,
sizeof(vbuf),
sizeof(vbuf) - 1,
"(%s pid=%d)",
g_instance.attr.attr_common.PGXCNodeName,
getpid());
securec_check_ss(rc, "\0", "\0");
}
/*
* We treat FATAL as ERROR when reporting error message to consumer/Coordinator.
* So add keyword '[FATAL]' before error message.
*/
if (IS_PGXC_DATANODE && !IsConnFromApp() && FATAL == edata->elevel)
appendStringInfoString(&msgbuf, _("[FATAL] "));
appendStringInfoString(&msgbuf, edata->message);
err_sendstring(&msgbuf, vbuf);
} else
err_sendstring(&msgbuf, _("missing error text"));
/* 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);
if (edata->mod_id) {
pq_sendbyte(&msgbuf, PG_DIAG_MODULE_ID);
err_sendstring(&msgbuf, get_valid_module_name(edata->mod_id));
}
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) {
rc = snprintf_s(tbuf, sizeof(tbuf), sizeof(tbuf) - 1, "%d", edata->cursorpos);
securec_check_ss_c(rc, "\0", "\0");
pq_sendbyte(&msgbuf, PG_DIAG_STATEMENT_POSITION);
err_sendstring(&msgbuf, tbuf);
}
if (edata->internalpos > 0) {
rc = snprintf_s(tbuf, sizeof(tbuf), sizeof(tbuf) - 1, "%d", edata->internalpos);
securec_check_ss_c(rc, "\0", "\0");
pq_sendbyte(&msgbuf, PG_DIAG_INTERNAL_POSITION);
err_sendstring(&msgbuf, tbuf);
}
if (edata->internalquery) {
char* mask_string = NULL;
/* mask the query whenever including sensitive information. */
mask_string = maskPassword(edata->internalquery);
/* with nothing to mask, just use the source querystring. */
if (mask_string == NULL)
mask_string = edata->internalquery;
pq_sendbyte(&msgbuf, PG_DIAG_INTERNAL_QUERY);
err_sendstring(&msgbuf, mask_string);
/* free the memory malloced for mask_string. */
if (mask_string != edata->internalquery)
pfree(mask_string);
}
#if defined(USE_ASSERT_CHECKING) || defined(FASTCHECK)
/* Send filename lineno and funcname to client is not allowed. */
if (edata->filename) {
pq_sendbyte(&msgbuf, PG_DIAG_SOURCE_FILE);
err_sendstring(&msgbuf, edata->filename);
}
if (edata->lineno > 0) {
rc = snprintf_s(tbuf, sizeof(tbuf), sizeof(tbuf) - 1, "%d", edata->lineno);
securec_check_ss_c(rc, "\0", "\0");
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);
}
#endif
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);
}
if (u_sess->stream_cxt.producer_obj &&
STREAM_IS_LOCAL_NODE(u_sess->stream_cxt.producer_obj->getParallelDesc().distriType)) {
gs_message_by_memory(
&msgbuf, u_sess->stream_cxt.producer_obj->getSharedContext(), u_sess->stream_cxt.producer_obj->getNth());
} else {
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 ...
*/
/* if CN retry is enabled, we need to avoid flushing data to client before ReadyForQuery is called */
if (STMT_RETRY_ENABLED && edata->elevel < ERROR && IS_PGXC_COORDINATOR &&
!t_thrd.log_cxt.flush_message_immediately)
return;
/*
* If it is WalSender's timeout message, choose pq_flush_if_writable()
* to avoid blokcing in send() if the send buffer of the socket is full.
*/
if (AM_WAL_NORMAL_SENDER && t_thrd.walsender_cxt.isWalSndSendTimeoutMessage) {
pq_flush_if_writable();
} else {
pq_flush();
}
if (edata->elevel == FATAL)
t_thrd.log_cxt.flush_message_immediately = true;
}
if (AM_WAL_DB_SENDER) {
ReadyForQuery((CommandDest)t_thrd.postgres_cxt.whereToSendOutput);
}
}
/*
* 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.
*
* The result is a palloc'd string.
*/
static char* expand_fmt_string(const char* fmt, ErrorData* edata)
{
StringInfoData buf;
const char* cp = NULL;
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 = NULL;
cp2 = useful_strerror(edata->saved_errno);
for (; *cp2; cp2++) {
if (*cp2 == '%')
appendStringInfoCharMacro(&buf, '%');
appendStringInfoCharMacro(&buf, *cp2);
}
} 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 */
char* errorstr_buf = t_thrd.buf_cxt.errorstr_buf;
int size = sizeof(t_thrd.buf_cxt.errorstr_buf);
const char* str = NULL;
int infolen = 0;
errno_t rc = EOK;
#ifdef WIN32
/* Winsock error code range, per WinError.h */
if (errnum >= 10000 && errnum <= 11999)
return pgwin32_socket_strerror(errnum);
#endif
str = gs_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') {
infolen = strlen("operating system error ") + sizeof(int) + 1;
rc = snprintf_s(errorstr_buf,
size,
infolen,
/* ------
translator: This string will be truncated at 47
characters expanded. */
_("operating system error %d"),
errnum);
securec_check_ss_c(rc, "\0", "\0");
str = errorstr_buf;
}
return str;
}
/*
* error_severity --- get localized string representing elevel
*/
static const char* error_severity(int elevel)
{
const char* prefix = NULL;
switch (elevel) {
case DEBUG1:
case DEBUG2:
case DEBUG3:
case DEBUG4:
case DEBUG5:
prefix = _("DEBUG");
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 = 0;
while ((ch = *str++) != '\0') {
appendStringInfoCharMacro(buf, ch);
if (ch == '\n')
appendStringInfoCharMacro(buf, '\t');
}
}
/*
* Reaper -- get current time.
*/
void get_time_now(char* nowTime, int timeLen)
{
time_t formatTime;
struct timeval current = {0};
const int tmpBufSize = 32;
char tmpBuf[tmpBufSize] = {0};
if (nowTime == NULL || timeLen == 0) {
return;
}
(void)gettimeofday(&current, NULL);
formatTime = current.tv_sec;
struct tm* pTime = localtime(&formatTime);
strftime(tmpBuf, sizeof(tmpBuf), "%Y-%m-%d %H:%M:%S", pTime);
errno_t rc = sprintf_s(nowTime, timeLen - 1, "%s.%ld ", tmpBuf, current.tv_usec / 1000);
securec_check_ss(rc, "\0", "\0");
}
void write_stderr_with_prefix(const char* fmt, ...)
{
va_list ap;
const int timeBufSize = 256;
const int bufSize = 2048;
char timeBuf[timeBufSize] = {0};
char buf[bufSize] = {0};
/* syslogger thread can not write log into pipe */
if (t_thrd.role == SYSLOGGER) {
return;
}
get_time_now(timeBuf, timeBufSize);
fmt = _(fmt);
va_start(ap, fmt);
errno_t rc = sprintf_s(buf, bufSize - 1, "%s[%lu] %s\n", timeBuf, t_thrd.proc_cxt.MyProcPid, fmt);
securec_check_ss(rc, "\0", "\0");
vfprintf(stderr, buf, ap);
fflush(stderr);
va_end(ap);
}
/*
* 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;
/* syslogger thread can not write log into pipe */
if (t_thrd.role == SYSLOGGER) {
return;
}
#ifdef WIN32
char errbuf[2048]; /* Arbitrary size? */
#endif
fmt = _(fmt);
va_start(ap, fmt);
#ifndef WIN32
/* On Unix, we just fprintf to stderr */
vfprintf(stderr, fmt, ap);
fflush(stderr);
#else
errno_t rc = vsnprintf_s(errbuf, sizeof(errbuf), sizeof(errbuf) - 1, fmt, ap);
securec_check_ss_c(rc, "\0", "\0");
/*
* 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 */
{
write_eventlog(ERROR, errbuf, strlen(errbuf));
} else {
/* Not running as service, write to stderr */
write_console(errbuf, strlen(errbuf));
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;
}
/* Neither is LOG */
else if (elevel >= log_min_level)
return true;
return false;
}
/*
* Adjust the level of a recovery-related message per u_sess->attr.attr_common.trace_recovery_messages.
*
* The argument is the default log level of the message, eg, DEBUG2. (This
* should only be applied to DEBUGn log messages, otherwise it's a no-op.)
* If the level is >= u_sess->attr.attr_common.trace_recovery_messages, we return LOG, causing the
* message to be logged unconditionally (for most settings of
* log_min_messages). Otherwise, we return the argument unchanged.
* The message will then be shown based on the setting of log_min_messages.
*
* Intention is to keep this for at least the whole of the 9.0 production
* release, so we can more easily diagnose production problems in the field.
* It should go away eventually, though, because it's an ugly and
* hard-to-explain kluge.
*/
int trace_recovery(int trace_level)
{
if (trace_level < LOG && trace_level >= u_sess->attr.attr_common.trace_recovery_messages)
return LOG;
return trace_level;
}
void getElevelAndSqlstate(int* eLevel, int* sqlState)
{
if (NULL == eLevel || NULL == sqlState)
return;
*eLevel = t_thrd.log_cxt.errordata[t_thrd.log_cxt.errordata_stack_depth].elevel;
*sqlState = t_thrd.log_cxt.errordata[t_thrd.log_cxt.errordata_stack_depth].sqlerrcode;
}
/*
* When the SQL statement is truncated, this function cannot perform normal password masking.
* maskPassword will return null if the statement does not need to be masked or any error occurs.
*/
char* maskPassword(const char* query_string)
{
char* mask_string = NULL;
MemoryContext oldCxt = NULL;
if (t_thrd.log_cxt.on_mask_password)
return NULL;
t_thrd.log_cxt.on_mask_password = true;
oldCxt = MemoryContextSwitchTo(t_thrd.mem_cxt.mask_password_mem_cxt);
if (is_execute_cmd(query_string)) {
mask_string = mask_execute_direct_cmd(query_string);
} else {
mask_string = mask_Password_internal(query_string);
}
(void)MemoryContextSwitchTo(oldCxt);
MemoryContextReset(t_thrd.mem_cxt.mask_password_mem_cxt);
t_thrd.log_cxt.on_mask_password = false;
return mask_string;
}
static char* mask_word(char* query_string, int query_len, const char* word, int word_len, int mask_len)
{
char *head_ptr, *token_ptr, *tail_ptr;
int head_len, token_len, tail_len;
errno_t rc = EOK;
char* mask_string = (char*)palloc0(query_len + mask_len + 1);
char* lower_string = (char*)palloc0(query_len + 1);
rc = memcpy_s(lower_string, query_len, query_string, query_len);
securec_check(rc, "\0", "\0");
bool truncate = false;
int set_password_len = 0;
tolower_func(lower_string);
char* word_ptr = strstr(lower_string, word);
if (word_ptr == NULL) {
pfree_ext(mask_string);
pfree_ext(lower_string);
return query_string;
}
word_ptr = query_string + (word_ptr - lower_string);
head_ptr = query_string;
head_len = word_ptr - query_string + word_len;
token_ptr = word_ptr + word_len;
token_len = 0;
for (int i = 0; i < query_len - head_len; i++) {
if (token_ptr[i] == ';' || isspace(token_ptr[i])) {
break;
}
token_len++;
}
/* set password for user=password */
if (strncmp(token_ptr, "for", token_len) == 0) {
token_ptr += token_len;
set_password_len = token_len + 1;
for (int i = 0; i < query_len - head_len - token_len; i++) {
if (token_ptr[i] == '=') {
truncate = true;
break;
}
set_password_len++;
}
}
tail_ptr = token_ptr + token_len;
tail_len = query_len - head_len - token_len;
rc = memcpy_s(mask_string, query_len + mask_len + 1, query_string, head_len + set_password_len);
securec_check(rc, "\0", "\0");
rc = memset_s(mask_string + head_len + set_password_len, query_len + mask_len + 1 - head_len - set_password_len,
'*', mask_len);
securec_check(rc, "\0", "\0");
truncate = truncate || ((strstr(word, " identified by ") == NULL) ? false : true);
if (truncate) {
mask_string[head_len + mask_len + set_password_len] = '\0';
} else {
rc = memcpy_s(mask_string + head_len + mask_len, query_len + 1 - head_len, tail_ptr, tail_len);
securec_check(rc, "\0", "\0");
mask_string[head_len + mask_len + tail_len] = '\0';
}
pfree_ext(query_string);
pfree_ext(lower_string);
return mask_string;
}
static void truncate_identified_by(char* query_string, int query_len)
{
const char* iden_str = " identified by ";
int iden_len = strlen(iden_str);
char* token_ptr;
int token_len;
int head_len;
errno_t rc = EOK;
char* lower_string = (char*)palloc0(query_len + 1);
rc = memcpy_s(lower_string, query_len, query_string, query_len);
securec_check(rc, "\0", "\0");
tolower_func(lower_string);
char* word_ptr = strstr(lower_string, iden_str);
if (word_ptr == NULL) {
pfree_ext(lower_string);
return;
}
word_ptr = query_string + (word_ptr - lower_string);
token_ptr = word_ptr + iden_len;
head_len = word_ptr - query_string + iden_len;
token_len = 0;
for (int i = 0; i < query_len - head_len; i++) {
if (isspace(token_ptr[i])) {
break;
}
token_len++;
}
query_string[head_len + token_len] = '\0';
pfree_ext(lower_string);
}
char* mask_error_password(const char* query_string, int str_len)
{
char* mask_string = NULL;
errno_t rc = EOK;
const char* pass_str = " password ";
const char* iden_str = " identified by ";
int mask_len = u_sess->attr.attr_security.Password_min_length;
char* tmp_string = (char*)palloc0(str_len + 1);
rc = memcpy_s(tmp_string, str_len, query_string, str_len);
securec_check(rc, "\0", "\0");
tmp_string = mask_word(tmp_string, str_len, pass_str, strlen(pass_str), mask_len);
tmp_string = mask_word(tmp_string, strlen(tmp_string), iden_str, strlen(iden_str), mask_len);
mask_string = MemoryContextStrdup(SESS_GET_MEM_CXT_GROUP(MEMORY_CONTEXT_SECURITY), tmp_string);
pfree_ext(tmp_string);
return mask_string;
}
static char* mask_execute_direct_cmd(const char* query_string)
{
char* mask_string = NULL;
char* tmp_string = NULL;
char* mask_query = NULL;
char* parse_query = NULL;
char symbol = '\'';
const char* exe_direct = "EXECUTE DIRECT ON";
int query_len = 0;
int exe_len = 0;
int mask_len = 0;
int position = 0;
const int delete_position = 2;
errno_t rc = EOK;
query_len = strlen(query_string);
exe_len = strlen(exe_direct);
for (int i = exe_len; i < query_len; i++) {
position = i + 1;
if (query_string[i] == symbol) {
break;
}
}
if (position >= query_len) {
return NULL;
}
/* Parsing execute direct on detail content */
parse_query = (char*)palloc0(query_len - position);
rc = memcpy_s(parse_query, (query_len - position),
(query_string + position), (query_len - position - delete_position));
securec_check(rc, "\0", "\0");
rc = strcat_s(parse_query, (query_len - position), ";\0");
securec_check(rc, "\0", "\0");
mask_query = mask_Password_internal(parse_query);
if (mask_query == NULL) {
pfree_ext(parse_query);
return NULL;
}
mask_len = strlen(mask_query);
/* Concatenate character string */
tmp_string = (char*)palloc0(mask_len + 1 + position + 1);
rc = memcpy_s(tmp_string, (mask_len + 1 + position + 1), exe_direct, exe_len);
securec_check(rc, "\0", "\0");
rc = memcpy_s((tmp_string + exe_len), (mask_len + 1 + position + 1),
(query_string + exe_len), (position - exe_len));
securec_check(rc, "\0", "\0");
rc = memcpy_s((tmp_string + position), (mask_len + 1 + position + 1), mask_query, (mask_len - 1));
securec_check(rc, "\0", "\0");
rc = strcat_s(tmp_string, (mask_len + 1 + position + 1), "';\0");
securec_check(rc, "\0", "\0");
mask_string = MemoryContextStrdup(SESS_GET_MEM_CXT_GROUP(MEMORY_CONTEXT_SECURITY), tmp_string);
pfree_ext(parse_query);
pfree_ext(tmp_string);
return mask_string;
}
static bool is_execute_cmd(const char* query_string)
{
char* format_str = NULL;
char* result = NULL;
char* encrypt = NULL;
char* decrypt = NULL;
int query_len = 0;
errno_t rc = EOK;
query_len = strlen(query_string);
format_str = (char*)palloc0(query_len + 1);
rc = memcpy_s(format_str, (query_len + 1), query_string, (query_len + 1));
securec_check(rc, "\0", "\0");
tolower_func(format_str);
result = strstr(format_str, "execute direct on");
if (result != NULL) {
encrypt = strstr(format_str, "gs_encrypt_aes128");
decrypt = strstr(format_str, "gs_decrypt_aes128");
if ((encrypt != NULL) || (decrypt != NULL)) {
pfree_ext(format_str);
return true;
}
encrypt = strstr(format_str, "gs_encrypt");
decrypt = strstr(format_str, "gs_decrypt");
if ((encrypt != NULL) || (decrypt != NULL)) {
pfree_ext(format_str);
return true;
}
encrypt = strstr(format_str, "aes_encrypt");
decrypt = strstr(format_str, "aes_decrypt");
if ((encrypt != NULL) || (decrypt != NULL)) {
pfree_ext(format_str);
return true;
}
}
pfree_ext(format_str);
return false;
}
static void tolower_func(char *convert_query)
{
int to_lower = 32;
char *ret = convert_query;
for (int i = 0; ret[i] != '\0'; i++) {
if((ret[i] >= 'A')&&(ret[i] <= 'Z')) {
ret[i] = ret[i] + to_lower;
}
}
}
static void apply_funcs3_mask(char *string, char** end, char replace)
{
errno_t rc = EOK;
if (*end == NULL || **end == '\0') {
return;
}
char *start = *end;
/* find '=' in param: field ^= 123abc */
while (**end != '=' && **end != '\0') {
(*end)++;
}
if (**end != '=') {
return;
}
(*end)++;
/* find start of value in param: field = ^123abc */
while (**end == ' ') {
(*end)++;
}
if (**end == '\0') {
return;
}
start = *end;
(*end)++;
/* find end of value in param: field = 123abc^ */
while (**end != ' ' && **end != '\0') {
(*end)++;
}
size_t param_len = *end - start;
Assert(param_len < strlen(string));
/*
* replace value with '*': field = ****** ...
* ^ ^
* start end
*/
rc = memset_s(start, param_len, replace, param_len);
securec_check_c(rc, "\0", "\0");
}
char* mask_funcs3_parameters(const char* query_string)
{
const char* funcs3Mask[] = {"accesskey", "secretkey"};
int funcs3MaskNum = sizeof(funcs3Mask) / sizeof(funcs3Mask[0]);
char* mask_string = MemoryContextStrdup(SESS_GET_MEM_CXT_GROUP(MEMORY_CONTEXT_STORAGE), query_string);
for (int i = 0; i < funcs3MaskNum; i++) {
char *start = mask_string;
while ((start = strstr(start, funcs3Mask[i])) != NULL) {
/* take the original string and replace the key value with '*' */
apply_funcs3_mask(mask_string, &start, '*');
}
}
return mask_string;
}
static inline void ClearYylval(const core_YYSTYPE *yylval)
{
int rc = memset_s(yylval->str, strlen(yylval->str), 0, strlen(yylval->str));
securec_check(rc, "\0", "\0");
rc = memset_s((char*)yylval->keyword, strlen(yylval->keyword), 0, strlen(yylval->keyword));
securec_check(rc, "\0", "\0");
}
static int get_reallen_of_credential(char *param)
{
int len = 0;
for (int i = 0; param[i] != '\0'; i++) {
if (param[i] == '\'') {
len += 2;
} else {
len++;
}
}
return len;
}
/*
* Mask the password in statment CREATE ROLE, CREATE USER, ALTER ROLE, ALTER USER, CREATE GROUP
* SET ROLE, CREATE DATABASE LINK, and some function
*/
static char* mask_Password_internal(const char* query_string)
{
int i = 0;
core_yyscan_t yyscanner;
core_yy_extra_type yyextra;
core_YYSTYPE yylval;
YYLTYPE yylloc;
int currToken = 59; /* initialize prevToken as ';' */
bool isPassword = false;
char* mask_string = NULL;
/*
* In the release version, the compiler optimizes 'mask_string' parameter.
* After siglongjump in PG_CATCH, the value of 'mask_string' may not be up-to-date.
* Therefore, the return value of mask_Password_internal maybe uncontrollable.
* We use a valatile copy of 'mask_string' named 'vol_mask_string' as the return value
* to void uncontrollable behavior caused by compiler optimizations.
*/
volatile char* vol_mask_string = mask_string;
/* the function list need mask */
const char* funcs[] = {"create_credential"};
bool is_create_credential = false;
bool is_create_credential_passwd = false;
int funcNum = sizeof(funcs) / sizeof(funcs[0]);
int position[16] = {0};
int length[16] = {0};
int idx = 0;
bool isCreateFunc = false;
bool isChildStmt = false;
errno_t rc = EOK;
int truncateLen = 0; /* accumulate total length for each truncate */
YYLTYPE conninfoStartPos = 0; /* connection start postion for CreateSubscriptionStmt */
/* the functions need to mask all contents */
const char* funCrypt[] = {"gs_encrypt_aes128", "gs_decrypt_aes128", "gs_encrypt", "gs_decrypt",
"aes_encrypt", "aes_decrypt", "pg_create_physical_replication_slot_extern", "dblink_connect"};
int funCryptNum = sizeof(funCrypt) / sizeof(funCrypt[0]);
bool isCryptFunc = false;
int length_crypt = 0;
int count_crypt = 0;
int position_crypt = 0;
/* functions whose second paramter will be masked as a child stmt. */
const char* funcs2[] = {"exec_on_extension", "exec_hadoop_sql"};
int funcNum2 = sizeof(funcs2) / sizeof(funcs2[0]);
const char* funcs3[] = {"gs_extend_library"};
int funcNum3 = sizeof(funcs3) / sizeof(funcs3[0]);
int count_funcs = 0;
/*stmt type:
* 0 - unknown type
* 1 - create role
* 2 - create user
* 3 - alter role
* 4 - alter user
* 5 - create group
* 6 - set role/session
* 7 - create database link
* 8 - exec function
* 9 - create function or procedure
* 10 - create/alter server; create/alter foreign table;
* 11 - create/alter data source
* 12 - for funcs2
* 13 - for funcs3
* 14 - create/alter text search dictionary
* 15 - for funCrypt
* 16 - create/alter subscription(CREATE_ALTER_SUBSCRIPTION)
* 17 - set password (b compatibility)
* 18 - create/alter user mapping
* 19 - grant usage on *.*(b compatibility, same as create/alter user)
*/
int curStmtType = 0;
int prevToken[5] = {0};
sigjmp_buf compile_sigjmp_buf;
sigjmp_buf* saveExceptionStack = t_thrd.log_cxt.PG_exception_stack;
ErrorContextCallback* saveContextStack = t_thrd.log_cxt.error_context_stack;
int saveStackDepth = t_thrd.log_cxt.errordata_stack_depth;
int saveRecursionDepth = t_thrd.log_cxt.recursion_depth;
int saveInterruptHoldoffCount = t_thrd.int_cxt.InterruptHoldoffCount;
bool saveEscapeStringWarning = u_sess->attr.attr_sql.escape_string_warning;
bool need_clear_yylval = false;
/* initialize the flex scanner */
yyscanner = scanner_init(query_string, &yyextra, &ScanKeywords, ScanKeywordTokens);
yyextra.warnOnTruncateIdent = false;
u_sess->attr.attr_sql.escape_string_warning = false;
/* forbid password truncate */
u_sess->parser_cxt.isForbidTruncate = true;
/*set t_thrd.log_cxt.recursion_depth to 0 for avoiding MemoryContextReset called*/
t_thrd.log_cxt.recursion_depth = 0;
/*set t_thrd.log_cxt.error_context_stack to NULL for avoiding context callback called*/
t_thrd.log_cxt.error_context_stack = NULL;
/* replace globe JUMP point, ensurance return here if syntex error */
t_thrd.log_cxt.PG_exception_stack = &compile_sigjmp_buf;
PG_TRY();
{
while (1) {
prevToken[0] = currToken;
currToken = core_yylex(&yylval, &yylloc, yyscanner);
/*
* currToken = 0 means there are no token any more mainly for non-semicolon condition.
* Just break here is enough as the query need masked have been masked, here need
* comprehensive test validation in properly time.
*/
if (currToken == 0) {
break;
}
/* For function procedure and anonymous blocks condition. */
if (isChildStmt) {
isChildStmt = false;
if (currToken == SCONST && (yylval.str != NULL) && (yylval.str[0] != '\0')) {
/*
* Actually erase single quotes which was originally expected
* to do on IMMEDIATE branch.
*/
if (prevToken[0] == IMMEDIATE)
eraseSingleQuotes(yylval.str);
char* childStmt = NULL;
if (curStmtType == 13) {
childStmt = mask_funcs3_parameters(yylval.str);
} else
childStmt = mask_Password_internal(yylval.str);
if (childStmt != NULL) {
if (mask_string == NULL) {
mask_string = MemoryContextStrdup(
SESS_GET_MEM_CXT_GROUP(MEMORY_CONTEXT_SECURITY), query_string);
vol_mask_string = mask_string;
}
/*
* After mask child statement, child statement length maybe be large than origin query
* statement length. So we should enlarge buffer size.
*/
int childStmtLen = strlen(childStmt);
int subQueryLen = strlen(yylval.str);
int maskStringLen = strlen(mask_string);
if (subQueryLen < childStmtLen) {
/* Need more space, enlarge length is (childStmtLen - subQueryLen) */
maskStringLen += (childStmtLen - subQueryLen) + 1;
char* maskStrNew = (char*)MemoryContextAllocZero(
SESS_GET_MEM_CXT_GROUP(MEMORY_CONTEXT_SECURITY), maskStringLen);
rc = memcpy_s(maskStrNew, maskStringLen, mask_string, strlen(mask_string));
securec_check(rc, "\0", "\0");
pfree_ext(mask_string);
mask_string = maskStrNew;
vol_mask_string = mask_string;
}
/*
* After enlarge buffer size, value of new buffer position is '0', and using strlen() will
* get wrong result of buffer length, which is smaller than real length.
* So use 'maskStringLen' here to indicate real buffer length.
*/
rc = memcpy_s(mask_string + yylloc + 1, maskStringLen - yylloc,
childStmt, strlen(childStmt) + 1);
securec_check(rc, "\0", "\0");
rc = memset_s(childStmt, strlen(childStmt), 0, strlen(childStmt));
securec_check(rc, "", "");
pfree(childStmt);
}
continue;
}
}
/*
* Password and function parameters is always SCONST or IDENT.
* The token have been assigned consistent numbers according to
* the order in gram.y(e.g. IDENT = 258 and SCONST = 260).
*/
if (curStmtType > 0 && curStmtType != 12 && curStmtType != 13 && curStmtType != 14 && curStmtType != 15 &&
(currToken == SCONST || currToken == IDENT)) {
if (unlikely(yylloc >= (int)strlen(query_string))) {
ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR),
errmsg("current index (%d) out of length of query string (%lu).",
yylloc, strlen(query_string))));
}
char ch = query_string[yylloc];
position[idx] = yylloc;
if (ch == '\'' || ch == '\"')
++position[idx];
if (!is_create_credential) {
length[idx] = strlen(yylval.str);
} else if (isPassword) {
is_create_credential_passwd = true;
length[idx] = strlen(yylval.str);
} else {
if (idx == 2 && !is_create_credential_passwd) {
length[idx] = get_reallen_of_credential(yylval.str);
} else {
length[idx] = 0;
}
}
++idx;
/* record the conninfo start pos, we will use it to calculate the actual length of conninfo */
if (curStmtType == CREATE_ALTER_SUBSCRIPTION) {
conninfoStartPos = yylloc;
/* the yylval store the conninfo, so we clear it here */
ClearYylval(&yylval);
}
/*
* use a fixed length of masked password.
* For a matched token, position[idx] is query_string's position, but mask_string is truncated,
* real position of mask_string be located at (position[idx] - truncateLen).
*/
if (idx == 16 || isPassword) {
if (mask_string == NULL) {
mask_string = MemoryContextStrdup(
SESS_GET_MEM_CXT_GROUP(MEMORY_CONTEXT_SECURITY), query_string);
vol_mask_string = mask_string;
}
int maskLen = u_sess->attr.attr_security.Password_min_length;
for (i = 0; i < idx; ++i) {
/*
* while masking password, if password is't quoted by \' or \",
* the len of password may be shorter than actual,
* we need to find the start position of password word by looking forward.
*/
char wordHead = position[i] > 0 ? query_string[position[i] - 1] : '\0';
if (isPassword && wordHead != '\0' && wordHead != '\'' && wordHead != '\"') {
while (position[i] > 0 && !isspace(wordHead) && wordHead != '\'' && wordHead != '\"') {
position[i]--;
wordHead = query_string[position[i] - 1];
}
length[i] = strlen(query_string + position[i]);
/* if the last char is ';', we should keep it */
if (query_string[position[i] + length[i] - 1] == ';') {
length[i]--;
}
curStmtType = 0;
isPassword = false;
}
/*
* After core_yylex, double quotation marks will be parsed to single quotation mark.
* Calcute length of '\'' and double this length.
*/
int lengthOfQuote = 0;
int yyvalLen = (yylval.str != NULL) ? (int)strlen(yylval.str) : 0;
for (int len = 0; len < length[i]; len++) {
if ((yylval.str != NULL) && len < yyvalLen && (yylval.str[len] == '\'')) {
lengthOfQuote++;
}
}
length[i] += lengthOfQuote;
if (length[i] < maskLen) {
/* need more space. */
int plen = strlen(mask_string) + maskLen - length[i] + 1;
char* maskStrNew = (char*)MemoryContextAllocZero(
SESS_GET_MEM_CXT_GROUP(MEMORY_CONTEXT_SECURITY), plen);
rc = memcpy_s(maskStrNew, plen, mask_string, strlen(mask_string));
securec_check(rc, "\0", "\0");
pfree_ext(mask_string);
mask_string = maskStrNew;
vol_mask_string = mask_string;
}
char* maskBegin = mask_string + (position[i] - truncateLen);
int copySize = strlen(mask_string) - (position[i] - truncateLen) - length[i] + 1;
if ((position[i] - truncateLen) >= 0 && copySize > 0) {
rc = memmove_s(maskBegin + maskLen, copySize, maskBegin + length[i], copySize);
securec_check(rc, "", "");
rc = memset_s(maskBegin, maskLen, '*', maskLen);
securec_check(rc, "", "");
}
/*
* After masking password, the origin password had been transformed to '*', which length equals
* to u_sess->attr.attr_security.Password_min_length.
* So we should record the difference between origin password length and mask password length.
*/
truncateLen = strlen(query_string) - strlen(mask_string);
need_clear_yylval = true;
}
if (need_clear_yylval) {
ClearYylval(&yylval);
need_clear_yylval = false;
}
idx = 0;
isPassword = false;
if (curStmtType == 10 || curStmtType == 11 || curStmtType == CREATE_ALTER_USERMAPPING) {
curStmtType = 0;
}
}
}
switch (currToken) {
case CREATE:
case ALTER:
case SET:
break;
case ROLE:
case SESSION:
if (curStmtType > 0)
break;
if (prevToken[0] == CREATE) {
curStmtType = 1;
} else if (prevToken[0] == ALTER) {
curStmtType = 3;
} else if (prevToken[0] == SET) {
curStmtType = 6;
} else if (prevToken[1] == SET && (prevToken[0] == LOCAL || prevToken[0] == SESSION)) {
curStmtType = 6;
prevToken[1] = 0;
}
break;
case USER:
if (curStmtType > 0)
break;
if (prevToken[0] == CREATE)
curStmtType = 2;
else if (prevToken[0] == ALTER)
curStmtType = 4;
break;
case LOCAL: // set local role
if (prevToken[0] == SET)
prevToken[1] = SET;
break;
case GROUP_P:
if (curStmtType > 0)
break;
if (prevToken[0] == CREATE)
curStmtType = 5;
break;
case DATABASE:
if (prevToken[0] == CREATE)
prevToken[1] = CREATE;
break;
case PASSWORD:
if (prevToken[1] == SERVER && prevToken[2] == OPTIONS) {
curStmtType = 10;
currToken = IDENT;
} else if (prevToken[1] == DATA_P && prevToken[2] == SOURCE_P && prevToken[3] == OPTIONS) {
/* For create/alter data source: sensitive opt is 'password' */
curStmtType = 11;
currToken = IDENT;
} else if (DB_IS_CMPT(B_FORMAT) && prevToken[0] == SET) {
curStmtType = 17;
} else if (prevToken[1] == MAPPING && prevToken[2] == SERVER && prevToken[3] == OPTIONS) {
curStmtType = CREATE_ALTER_USERMAPPING;
currToken = IDENT;
}
if (curStmtType != 17) {
isPassword = true;
idx = 0;
}
break;
case BY:
isPassword = (curStmtType > 0 && prevToken[0] == IDENTIFIED);
if (isPassword)
idx = 0;
break;
case REPLACE:
isPassword = (curStmtType == 3 || curStmtType == 4);
if (isPassword)
idx = 0;
break;
case FUNCTION:
case PROCEDURE:
if (curStmtType > 0)
break;
if (prevToken[0] == CREATE || prevToken[0] == REPLACE)
isCreateFunc = true;
break;
case DO:
isCreateFunc = true;
if (isCreateFunc) {
isCreateFunc = false;
isChildStmt = true;
}
break;
case AS:
case IS:
if (isCreateFunc) {
isCreateFunc = false;
isChildStmt = true;
}
break;
case IMMEDIATE:
if (curStmtType > 0)
break;
if (prevToken[0] == EXECUTE) {
isChildStmt = true;
eraseSingleQuotes(yyextra.scanbuf + yylloc);
}
break;
case 40: /* character '(' */
if (prevToken[0] == IDENT) {
/* first, check funcs[] */
for (i = 0; i < funcNum; ++i) {
if (pg_strcasecmp(yylval.str, funcs[i]) == 0) {
is_create_credential = false;
if (pg_strcasecmp(yylval.str, "create_credential") == 0) {
is_create_credential = true;
}
curStmtType = 8;
break;
}
}
/* if found, just break; */
if (i < funcNum)
break;
/* otherwise, check if it is in funcs2[] */
for (i = 0; i < funcNum2; i++) {
if (pg_strcasecmp(yylval.str, funcs2[i]) == 0) {
/* for funcs2, we will mask its second parameter as child stmt. */
isChildStmt = false;
prevToken[1] = 40;
curStmtType = 12;
break;
}
}
/* if found, just break; */
if (i < funcNum2)
break;
/* otherwise, check if it is in funcs3[] */
for (i = 0; i < funcNum3; i++) {
if (pg_strcasecmp(yylval.str, funcs3[i]) == 0) {
/* for funcs3, we will mask its second parameter */
isChildStmt = false;
prevToken[1] = 40;
curStmtType = 13;
count_funcs++;
break;
}
}
if (i < funcNum3) {
break;
}
/* otherwise, check if it is in funCrypt[] */
for (i = 0; i < funCryptNum; i++) {
if (pg_strcasecmp(yylval.str, funCrypt[i]) == 0) {
/* for funCrypt, we will mask all contents in (). */
isCryptFunc = true;
curStmtType = 15;
if (count_crypt == 0) {
position_crypt = yylloc + 1;
}
count_crypt++;
break;
}
}
}
break;
case 41: /* character ')' */
if (isCryptFunc) {
count_crypt--;
if (count_crypt == 0) {
if (mask_string == NULL) {
mask_string = MemoryContextStrdup(
SESS_GET_MEM_CXT_GROUP(MEMORY_CONTEXT_SECURITY), query_string);
vol_mask_string = mask_string;
}
if (yylloc > position_crypt) {
int len_mask_string = strlen(mask_string);
rc = memset_s(mask_string + position_crypt,
len_mask_string - position_crypt,
'*',
yylloc - position_crypt);
securec_check(rc, "\0", "\0");
if ((mask_string[yylloc + 1] == ';') ||
(mask_string[yylloc + 1] == '\0')) {
const int maskLen = 8; /* for funCrypt, we will mask all contents in () with 8 '*' */
if (yylloc - position_crypt > maskLen) {
rc = memmove_s(mask_string + position_crypt + maskLen,
len_mask_string - position_crypt - maskLen,
&mask_string[yylloc],
len_mask_string - yylloc + 1);
securec_check(rc, "\0", "\0");
}
} else {
count_crypt++;
break;
}
}
isCryptFunc = false;
length_crypt = 0;
position_crypt = 0;
}
break;
}
if (curStmtType == 8) {
if (NULL == mask_string) {
mask_string = MemoryContextStrdup(
SESS_GET_MEM_CXT_GROUP(MEMORY_CONTEXT_SECURITY), query_string);
vol_mask_string = mask_string;
}
for (i = 0; i < idx; ++i) {
rc = memset_s(mask_string + (position[i] - truncateLen), length[i], '*', length[i]);
securec_check(rc, "", "");
}
idx = 0;
curStmtType = 0;
}
/* for funcs2: exec_on_extension, exec_hadoop_sql
* for funcs3: gs_extend_library
*/
if (curStmtType == 12 || curStmtType == 13) {
count_funcs--;
if (count_funcs == 0) {
curStmtType = 0;
prevToken[1] = 0;
}
}
break;
case 44: /* character ',' */
/* for mask funcs2 and funcs3 */
if (curStmtType == 12 || curStmtType == 13) {
if (prevToken[1] == 40) {
/* only mask its second parameter as a child stmt. */
isChildStmt = true;
break;
}
}
break;
case 59: /* character ';' */
/*
* Since the sensitive data always follow 'password', 'identified by',
* and 'replace' syntax, and we do mask before. We can just finish
* the masking task and reset all the parameters when meet the end.
*/
curStmtType = 0;
isPassword = false;
idx = 0;
break;
case 61: /* character '=' */
/* for mask 'set password' in b compatibility */
isPassword = (curStmtType == 17);
if (isPassword)
idx = 0;
break;
case FOREIGN:
if (prevToken[0] == CREATE || prevToken[0] == ALTER) {
prevToken[1] = FOREIGN;
}
break;
case TABLE:
if (prevToken[1] == FOREIGN) {
prevToken[2] = TABLE;
}
break;
case SERVER:
if (prevToken[0] == CREATE || prevToken[0] == ALTER) {
prevToken[1] = SERVER;
} else if (prevToken[1] == MAPPING) {
prevToken[2] = SERVER;
}
break;
case OPTIONS:
if (prevToken[1] == SERVER) {
prevToken[2] = OPTIONS;
} else if (prevToken[1] == FOREIGN && prevToken[2] == TABLE) {
prevToken[3] = OPTIONS;
} else if (prevToken[1] == DATA_P && prevToken[2] == SOURCE_P) {
prevToken[3] = OPTIONS;
} else if (prevToken[1] == MAPPING && prevToken[2] == SERVER) {
prevToken[3] = OPTIONS;
}
break;
/* For create/alter text search dictionary */
case TEXT_P:
if (prevToken[0] == CREATE || prevToken[0] == ALTER) {
prevToken[1] = TEXT_P;
}
break;
case SEARCH:
if (prevToken[1] == TEXT_P) {
prevToken[2] = SEARCH;
}
break;
case DICTIONARY:
if (prevToken[1] == TEXT_P && prevToken[2] == SEARCH) {
prevToken[3] = DICTIONARY;
}
break;
/* For create/alter data source */
case DATA_P:
if (prevToken[0] == CREATE || prevToken[0] == ALTER) {
prevToken[1] = DATA_P;
}
break;
case SOURCE_P:
if (prevToken[1] == DATA_P) {
prevToken[2] = SOURCE_P;
}
break;
case IDENT:
if (curStmtType == 14) {
if (pg_strncasecmp(yylval.str, "obs", strlen("obs")) == 0) {
MASK_OBS_PATH();
}
curStmtType = 0;
}
if ((prevToken[1] == SERVER && prevToken[2] == OPTIONS) ||
(prevToken[1] == FOREIGN && prevToken[2] == TABLE && prevToken[3] == OPTIONS)) {
if (pg_strcasecmp(yylval.str, "secret_access_key") == 0 ||
pg_strcasecmp(yylval.str, "password") == 0) {
/* create/alter server */
curStmtType = 10;
} else {
curStmtType = 0;
}
idx = 0;
} else if (prevToken[1] == DATA_P && prevToken[2] == SOURCE_P && prevToken[3] == OPTIONS) {
/*
* For create/alter data source: sensitive opts are 'username' and 'password'.
* 'username' is marked here, while 'password' is usually marked as a standard Token.
* However, password will be taken as SCONST if wrapped around double-quote, which needs
* to be handled here.
*/
if (pg_strcasecmp(yylval.str, "username") == 0 || pg_strcasecmp(yylval.str, "password") == 0) {
curStmtType = 11;
} else {
curStmtType = 0;
}
idx = 0;
} else if (prevToken[1] == TEXT_P && prevToken[2] == SEARCH && prevToken[3] == DICTIONARY) {
if (pg_strcasecmp(yylval.str, "filepath") == 0) {
curStmtType = 14;
} else {
curStmtType = 0;
}
} else if (prevToken[1] == ALTER && prevToken[2] == SUBSCRIPTION) {
/*
* For SUBSCRIPTION, there are 3 cases need to mask conninfo(which has username and password):
* 1. CREATE SUBSCRIPTION name CONNECTION Sconst. Which could be coverd by case CONNECTION.
* 2. ALTER SUBSCRIPTION name CONNECTION Sconst. Which could be coverd by case CONNECTION.
* 3. ALTER SUBSCRIPTION name SET (conninfo='xx'). Here we deal with this case.
*/
curStmtType = pg_strcasecmp(yylval.str, "conninfo") == 0 ? CREATE_ALTER_SUBSCRIPTION : 0;
idx = 0;
} else if (prevToken[1] == MAPPING && prevToken[2] == SERVER && prevToken[3] == OPTIONS) {
/*
* For create/alter user mapping: sensitive opt is 'password'.
* 'password' is usually marked as a standard Token.
* However, password will be taken as SCONST if wrapped around double-quote, which needs
* to be handled here.
*/
if (pg_strcasecmp(yylval.str, "password") == 0) {
curStmtType = CREATE_ALTER_USERMAPPING;
isPassword = true;
} else {
curStmtType = 0;
}
idx = 0;
} else if (DB_IS_CMPT(B_FORMAT) && prevToken[1] == GRANT &&
pg_strcasecmp(yylval.str, "usage") == 0) {
curStmtType = GRANT_USAGE;
}
break;
case SCONST:
/* create/alter server || create/alter data source: masked here */
if (curStmtType == 10 || curStmtType == 11) {
if (prevToken[0] == IDENT) {
if (mask_string == NULL) {
mask_string = MemoryContextStrdup(
SESS_GET_MEM_CXT_GROUP(MEMORY_CONTEXT_SECURITY), query_string);
vol_mask_string = mask_string;
}
if (strstr(mask_string, "secret_access_key") != NULL) {
int plen = strlen(mask_string) + strlen(password_mask) + 1;
char* maskStrTmp = (char*)palloc(plen);
rc = memset_s(maskStrTmp, plen, '\0', plen);
securec_check(rc, "", "");
rc = memcpy_s(maskStrTmp, plen, mask_string, strlen(mask_string));
securec_check(rc, "", "");
char* after_ak = pg_strdup(maskStrTmp + position[0] + length[0]);
rc = memcpy_s(maskStrTmp + position[0], plen, password_mask, strlen(password_mask));
securec_check(rc, "", "");
rc = memcpy_s(maskStrTmp + position[0] + strlen(password_mask),
plen, after_ak, strlen(after_ak) + 1);
securec_check(rc, "", "");
char* maskStrNew = MemoryContextStrdup(
SESS_GET_MEM_CXT_GROUP(MEMORY_CONTEXT_SECURITY), maskStrTmp);
rc = memset_s(mask_string, strlen(mask_string), '\0', strlen(mask_string));
securec_check(rc, "", "");
pfree_ext(mask_string);
pfree_ext(maskStrTmp);
mask_string = maskStrNew;
vol_mask_string = mask_string;
free(after_ak);
after_ak = NULL;
} else {
rc = memset_s(mask_string + position[0], length[0], '*', length[0]);
securec_check(rc, "", "");
}
idx = 0;
curStmtType = 0;
}
} else if (curStmtType == 14) {
if (pg_strncasecmp(yylval.str, "obs", strlen("obs")) == 0) {
MASK_OBS_PATH();
}
curStmtType = 0;
} else if (curStmtType == CREATE_ALTER_SUBSCRIPTION &&
(prevToken[0] == '=' || prevToken[1] == ALTER)) {
/*
* ALTER SUBSCRIPTION name SET (conninfo='xx')
* ALTER SUBSCRIPTION name CONNECTION Sconst
* mask connection info
*/
if (mask_string == NULL) {
mask_string = MemoryContextStrdup(
SESS_GET_MEM_CXT_GROUP(MEMORY_CONTEXT_SECURITY), query_string);
vol_mask_string = mask_string;
}
/*
* mask to the end of string, cause the length[0] may be shorter than actual.
* For example:
* ALTER SUBSCRIPTION name SET (conninfo='host=''1.1.1.1'' password=''password_123''');
* ALTER SUBSCRIPTION name CONNECTION 'host=''1.1.1.1'' password=''password_123''';
*/
int maskLen = strlen(query_string + position[0]);
/* if the last char is ';', we should keep it */
if (query_string[position[0] + maskLen - 1] == ';') {
maskLen--;
}
rc = memset_s(mask_string + (position[0] - truncateLen), maskLen, '*', maskLen);
securec_check(rc, "", "");
/* the yylval store the conninfo, so we clear it here */
ClearYylval(&yylval);
idx = 0;
curStmtType = 0;
}
break;
case SUBSCRIPTION:
if (prevToken[0] == CREATE || prevToken[0] == ALTER) {
prevToken[1] = prevToken[0];
prevToken[2] = SUBSCRIPTION;
}
break;
case CONNECTION:
if (prevToken[2] == SUBSCRIPTION) {
curStmtType = CREATE_ALTER_SUBSCRIPTION;
prevToken[3] = CONNECTION;
}
break;
case PUBLICATION:
if (curStmtType == CREATE_ALTER_SUBSCRIPTION && prevToken[2] == SUBSCRIPTION &&
prevToken[3] == CONNECTION) {
/*
* CREATE SUBSCRIPTION name CONNECTION Sconst PUBLICATION xxx, try to mask Sconst.
* it should not happen that conninfoStartPos < 0, if it does, we will mask the
* string from the beginning to current pos to ensure all sensetive info is masked.
*/
if (mask_string == NULL) {
mask_string = MemoryContextStrdup(
SESS_GET_MEM_CXT_GROUP(MEMORY_CONTEXT_SECURITY), query_string);
vol_mask_string = mask_string;
}
int maskLen = yylloc - conninfoStartPos;
rc = memset_s(mask_string + conninfoStartPos, maskLen, '*', maskLen);
securec_check(rc, "", "");
idx = 0;
curStmtType = 0;
}
break;
case MAPPING:
if (prevToken[0] == USER) {
prevToken[1] = MAPPING;
}
break;
case GRANT:
if (DB_IS_CMPT(B_FORMAT) && prevToken[0] == ';') {
prevToken[1] = GRANT;
}
break;
default:
break;
}
}
}
PG_CATCH();
{
for (i = saveStackDepth + 1; i <= t_thrd.log_cxt.errordata_stack_depth; ++i) {
ErrorData* edata = &t_thrd.log_cxt.errordata[t_thrd.log_cxt.errordata_stack_depth];
/* Now free up subsidiary data attached to stack entry, and release it */
if (NULL != edata->message) {
pfree_ext(edata->message);
}
if (NULL != edata->detail) {
pfree_ext(edata->detail);
}
if (NULL != edata->detail_log) {
pfree_ext(edata->detail_log);
}
if (NULL != edata->hint) {
pfree_ext(edata->hint);
}
if (NULL != edata->context) {
pfree_ext(edata->context);
}
if (NULL != edata->internalquery) {
pfree_ext(edata->internalquery);
}
if (NULL != edata->sqlstate) {
pfree_ext(edata->sqlstate);
}
if (NULL != edata->class_origin) {
pfree_ext(edata->class_origin);
}
if (NULL != edata->subclass_origin) {
pfree_ext(edata->subclass_origin);
}
if (NULL != edata->cons_catalog) {
pfree_ext(edata->cons_catalog);
}
if (NULL != edata->cons_schema) {
pfree_ext(edata->cons_schema);
}
if (NULL != edata->cons_name) {
pfree_ext(edata->cons_name);
}
if (NULL != edata->catalog_name) {
pfree_ext(edata->catalog_name);
}
if (NULL != edata->schema_name) {
pfree_ext(edata->schema_name);
}
if (NULL != edata->table_name) {
pfree_ext(edata->table_name);
}
if (NULL != edata->column_name) {
pfree_ext(edata->column_name);
}
if (NULL != edata->cursor_name) {
pfree_ext(edata->cursor_name);
}
t_thrd.log_cxt.errordata_stack_depth--;
}
}
PG_END_TRY();
/* restore the globe jump, globe jump if encounter errors in compile */
t_thrd.log_cxt.PG_exception_stack = saveExceptionStack;
t_thrd.log_cxt.error_context_stack = saveContextStack;
t_thrd.log_cxt.recursion_depth = saveRecursionDepth;
t_thrd.int_cxt.InterruptHoldoffCount = saveInterruptHoldoffCount;
u_sess->attr.attr_sql.escape_string_warning = saveEscapeStringWarning;
/* reset the forbid truncate flag */
u_sess->parser_cxt.isForbidTruncate = false;
if (yyextra.scanbuflen > 0) {
rc = memset_s(yyextra.scanbuf, yyextra.scanbuflen, 0, yyextra.scanbuflen);
securec_check(rc, "\0", "\0");
pfree_ext(yyextra.scanbuf);
}
if (yyextra.literalalloc > 0) {
rc = memset_s(yyextra.literalbuf, yyextra.literallen, 0, yyextra.literallen);
securec_check(rc, "\0", "\0");
pfree_ext(yyextra.literalbuf);
}
/* return the valatile copy of mask_string to ensure that the latest value is returned. */
return (char *)vol_mask_string;
}
static void eraseSingleQuotes(char* query_string)
{
char* curr = NULL;
int count = 0;
bool inDoubleQuotes = false;
curr = query_string;
while (curr != NULL && curr[0] != '\0') {
// 0x27 is symbol '\''
if (*curr == 0x27 && !inDoubleQuotes) {
*curr = ' ';
++count;
} else if (*curr == ';') {
if (count % 2 == 0)
break;
} else if (*curr == 0x22) {
inDoubleQuotes = !inDoubleQuotes;
}
if (*curr == '|' && *(curr + 1) == '|') {
*curr = ' ';
*(curr + 1) = ' ';
}
curr++;
}
}
/*
* Report error according to the return value.
* At the same time, we should free the space alloced by developers.
*/
void freeSecurityFuncSpace(const char* charList, ...)
{
va_list argptr;
/* if the first parameter is not empty */
if (strcmp(charList, "\0") != 0) {
/* free the first charList */
pfree_ext(charList);
/* if have move charList */
va_start(argptr, charList);
while (true) {
char* szBuf = va_arg(argptr, char*);
if (strcmp(szBuf, "\0") == 0) /* empty string */
break;
pfree_ext(szBuf);
}
va_end(argptr);
}
return;
}
/*
* @Description: mask part of espaced characters which may cause log injection attack.
* @in source_str : the messages which need mask before write to syslog.
* @return : non-return
*/
static void mask_espaced_character(char* source_str)
{
/*
* Our syslog is splited by new line, so we just mask the "\n" here temporarily.
* Replace all the "\n" with "*" in the string.
*/
char* match_pos = strstr(source_str, "\n");
while (NULL != match_pos) {
*match_pos = '*';
match_pos = strstr(match_pos, "\n");
}
return;
}
static void write_asp_chunks(char *data, int len, bool end)
{
LogPipeProtoChunk p;
int fd = fileno(stderr);
int rc;
Assert(len > 0);
p.proto.nuls[0] = p.proto.nuls[1] = '\0';
p.proto.pid = t_thrd.proc_cxt.MyProcPid;
p.proto.logtype = LOG_TYPE_ASP_LOG;
p.proto.magic = PROTO_HEADER_MAGICNUM;
/* write all but the last chunk */
while (len > LOGPIPE_MAX_PAYLOAD) {
p.proto.is_last = 'F';
p.proto.len = LOGPIPE_MAX_PAYLOAD;
rc = memcpy_s(p.proto.data, LOGPIPE_MAX_PAYLOAD, data, LOGPIPE_MAX_PAYLOAD);
securec_check(rc, "\0", "\0");
rc = write(fd, &p, LOGPIPE_HEADER_SIZE + LOGPIPE_MAX_PAYLOAD);
(void) rc;
data += LOGPIPE_MAX_PAYLOAD;
len -= LOGPIPE_MAX_PAYLOAD;
}
/* write the last chunk */
p.proto.is_last = end ? 'T' : 'F';
p.proto.len = len;
rc = memcpy_s(p.proto.data, LOGPIPE_MAX_PAYLOAD, data, len);
securec_check(rc, "\0", "\0");
rc = write(fd, &p, LOGPIPE_HEADER_SIZE + len);
(void) rc;
}
static void write_asplog(char *data, int len, bool end)
{
if (t_thrd.role == SYSLOGGER || !t_thrd.postmaster_cxt.redirection_done) {
write_syslogger_file(data, len, LOG_DESTINATION_ASPLOG);
} else {
write_asp_chunks(data, len, end);
}
}
ErrorDataArea *initErrorDataArea()
{
ErrorDataArea *errorDataArea = (ErrorDataArea *)palloc0(sizeof(ErrorDataArea));
errorDataArea->current_edata_count_by_level = (uint64 *)palloc0(4 * sizeof(uint64));
errorDataArea->sqlErrorDataList = NIL;
errorDataArea->current_edata_count = 0;
for (int i = 0; i <= enum_dolphin_error_level::B_END; i++) {
errorDataArea->current_edata_count_by_level[i] = 0;
}
return errorDataArea;
}
void cleanErrorDataArea(ErrorDataArea *errorDataArea)
{
Assert(errorDataArea != NULL);
MemoryContext oldcontext;
oldcontext = MemoryContextSwitchTo(u_sess->dolphin_errdata_ctx.dolphinErrorDataMemCxt);
ListCell *lc = NULL;
foreach (lc, errorDataArea->sqlErrorDataList) {
DolphinErrorData *eData = (DolphinErrorData *)lfirst(lc);
pfree_ext(eData->errorcode);
pfree_ext(eData->sqlstatestr);
pfree_ext(eData->class_origin);
pfree_ext(eData->subclass_origin);
pfree_ext(eData->constraint_catalog);
pfree_ext(eData->constraint_schema);
pfree_ext(eData->constraint_name);
pfree_ext(eData->catalog_name);
pfree_ext(eData->schema_name);
pfree_ext(eData->table_name);
pfree_ext(eData->column_name);
pfree_ext(eData->cursor_name);
pfree_ext(eData->message_text);
}
list_free_deep(errorDataArea->sqlErrorDataList);
errorDataArea->sqlErrorDataList = NIL;
errorDataArea->current_edata_count = 0;
for (int i = 0; i <= enum_dolphin_error_level::B_END; i++) {
errorDataArea->current_edata_count_by_level[i] = 0;
}
MemoryContextSwitchTo(oldcontext);
}
void copyErrorDataArea(ErrorDataArea *from, ErrorDataArea *to)
{
cleanErrorDataArea(to);
MemoryContext oldcontext;
oldcontext = MemoryContextSwitchTo(u_sess->dolphin_errdata_ctx.dolphinErrorDataMemCxt);
ListCell *lc = NULL;
lc = list_head(from->sqlErrorDataList);
foreach (lc, from->sqlErrorDataList) {
DolphinErrorData *eData = (DolphinErrorData *)lfirst(lc);
DolphinErrorData *newErrData = (DolphinErrorData *)palloc(sizeof(DolphinErrorData));
newErrData->elevel = eData->elevel;
newErrData->errorcode = pstrdup(eData->errorcode);
newErrData->sqlstatestr = pstrdup(eData->sqlstatestr);
newErrData->message_text = pstrdup(eData->message_text);
newErrData->class_origin = pstrdup(eData->class_origin);
newErrData->subclass_origin = pstrdup(eData->subclass_origin);
newErrData->constraint_catalog = pstrdup(eData->constraint_catalog);
newErrData->constraint_schema = pstrdup(eData->constraint_schema);
newErrData->constraint_name = pstrdup(eData->constraint_name);
newErrData->catalog_name = pstrdup(eData->catalog_name);
newErrData->schema_name = pstrdup(eData->schema_name);
newErrData->table_name = pstrdup(eData->table_name);
newErrData->column_name = pstrdup(eData->column_name);
newErrData->cursor_name = pstrdup(eData->cursor_name);
to->sqlErrorDataList = lappend(to->sqlErrorDataList, newErrData);
}
to->current_edata_count = from->current_edata_count;
for (int i = 0; i <= enum_dolphin_error_level::B_END; i++) {
to->current_edata_count_by_level[i] = from->current_edata_count_by_level[i];
}
MemoryContextSwitchTo(oldcontext);
}
bool strcompare(char* str1, char* str2)
{
if(str1 != NULL) {
if (str2 != NULL) {
if (strcmp(str1, str2) != 0) {
return false;
}
} else {
return true;
}
} else {
if (str2 != NULL) {
return false;
}
}
return true;
}
bool compareErrorData(DolphinErrorData* err1, ErrorData* err2)
{
if (err1->elevel != errorLevelToDolphin(err2->elevel))
return false;
int errcode = MAKE_SQLSTATE(err1->errorcode[0],
err1->errorcode[1],
err1->errorcode[2],
err1->errorcode[3],
err1->errorcode[4]);
if((errcode == err2->sqlerrcode) && strcompare(err1->class_origin,err2->class_origin)
&& strcompare(err1->subclass_origin,err2->subclass_origin) && strcompare(err1->constraint_catalog,err2->cons_catalog)
&& strcompare(err1->constraint_schema,err2->cons_schema) && strcompare(err1->constraint_name,err2->cons_name)
&& strcompare(err1->catalog_name,err2->catalog_name) && strcompare(err1->schema_name,err2->schema_name)
&& strcompare(err1->table_name,err2->table_name) && strcompare(err1->column_name,err2->column_name)
&& strcompare(err1->cursor_name,err2->cursor_name) && strcompare(err1->message_text,err2->message))
return true;
else
return false;
}
void copyDiffErrorDataArea(ErrorDataArea *from, ErrorDataArea *to, ErrorData *edata)
{
cleanErrorDataArea(to);
bool is_same = false;
MemoryContext oldcontext;
int count = 0;
oldcontext = MemoryContextSwitchTo(u_sess->dolphin_errdata_ctx.dolphinErrorDataMemCxt);
ListCell *lc = NULL;
lc = list_head(from->sqlErrorDataList);
foreach (lc, from->sqlErrorDataList) {
DolphinErrorData *eData = (DolphinErrorData *)lfirst(lc);
is_same = compareErrorData(eData, edata);
if (is_same)
continue;
DolphinErrorData *newErrData = (DolphinErrorData *)palloc(sizeof(DolphinErrorData));
newErrData->elevel = eData->elevel;
newErrData->errorcode = pstrdup(eData->errorcode);
newErrData->sqlstatestr = pstrdup(eData->sqlstatestr);
newErrData->message_text = pstrdup(eData->message_text);
newErrData->class_origin = pstrdup(eData->class_origin);
newErrData->subclass_origin = pstrdup(eData->subclass_origin);
newErrData->constraint_catalog = pstrdup(eData->constraint_catalog);
newErrData->constraint_schema = pstrdup(eData->constraint_schema);
newErrData->constraint_name = pstrdup(eData->constraint_name);
newErrData->catalog_name = pstrdup(eData->catalog_name);
newErrData->schema_name = pstrdup(eData->schema_name);
newErrData->table_name = pstrdup(eData->table_name);
newErrData->column_name = pstrdup(eData->column_name);
newErrData->cursor_name = pstrdup(eData->cursor_name);
count++;
to->sqlErrorDataList = lappend(to->sqlErrorDataList, newErrData);
}
if (count != 0) {
to->current_edata_count = count;
for (int i = 0; i <= enum_dolphin_error_level::B_END; i++) {
to->current_edata_count_by_level[i] = from->current_edata_count_by_level[i];
}
}
MemoryContextSwitchTo(oldcontext);
}
void resetErrorDataArea(bool stacked, bool handler_active)
{
/* reset all count to zero and list to null */
MemoryContext oldcontext;
oldcontext = MemoryContextSwitchTo(u_sess->dolphin_errdata_ctx.dolphinErrorDataMemCxt);
ErrorDataArea *errorDataArea = u_sess->dolphin_errdata_ctx.errorDataArea;
ErrorDataArea *lastErrorDataArea = u_sess->dolphin_errdata_ctx.lastErrorDataArea;
if (stacked) {
if (!handler_active) {
copyErrorDataArea(errorDataArea, lastErrorDataArea);
}
} else {
if (!handler_active) {
cleanErrorDataArea(lastErrorDataArea);
}
}
cleanErrorDataArea(errorDataArea);
MemoryContextSwitchTo(oldcontext);
}
enum_dolphin_error_level errorLevelToDolphin(int elevel)
{
if (elevel < WARNING) {
return enum_dolphin_error_level::B_NOTE;
} else if (elevel == WARNING) {
return enum_dolphin_error_level::B_WARNING;
} else {
return enum_dolphin_error_level::B_ERROR;
}
}
void pushErrorData(ErrorData *edata)
{
MemoryContext oldcontext;
if (edata->is_signal == PLpgSQL_signal_resignal::PLPGSQL_RESIGNAL_WITHOUT_SQLSTATE) {
/* resignal without sqlstate */
resetErrorDataArea(false, u_sess->dolphin_errdata_ctx.handler_active);
} else if (edata->is_signal == PLpgSQL_signal_resignal::PLPGSQL_RESIGNAL_WITH_SQLSTATE) {
/* resignal sqlstate */
copyErrorDataArea(u_sess->dolphin_errdata_ctx.lastErrorDataArea, u_sess->dolphin_errdata_ctx.errorDataArea);
}
ErrorDataArea *errorDataArea = u_sess->dolphin_errdata_ctx.errorDataArea;
oldcontext = MemoryContextSwitchTo(u_sess->dolphin_errdata_ctx.dolphinErrorDataMemCxt);
if (u_sess->dolphin_errdata_ctx.max_error_count >= SqlErrorDataCount()) {
if (u_sess->dolphin_errdata_ctx.sql_note == true ||
errorLevelToDolphin(edata->elevel) != enum_dolphin_error_level::B_NOTE) {
DolphinErrorData *dolphinErrorData = (DolphinErrorData *)palloc(sizeof(DolphinErrorData));
dolphinErrorData->elevel = errorLevelToDolphin(edata->elevel);
const char* cls = plpgsql_get_sqlstate(edata->sqlerrcode);
char* class_origin = NULL;
char* subclass_origin = NULL;
if (((cls[0] >= '0' && cls[0] <= '4') || (cls[0] >= 'A' && cls[0] <= 'H')) &&
((cls[1] >= '0' && cls[1] <= '9') || (cls[1] >= 'A' && cls[1] <= 'Z'))) {
class_origin = pstrdup("ISO 9075");
subclass_origin = pstrdup("ISO 9075");
} else {
class_origin = pstrdup("MySQL");
if (!memcmp(cls + 2, "000", strlen("000"))) {
subclass_origin = pstrdup("ISO 9075");
} else {
subclass_origin = pstrdup("MySQL");
}
}
if (edata->is_signal > 0) {
dolphinErrorData->class_origin = pstrdup(edata->class_origin);
dolphinErrorData->subclass_origin = pstrdup(edata->subclass_origin);
dolphinErrorData->sqlstatestr = pstrdup(edata->sqlstate);
char sqlerrcode [128];
int ret = sprintf_s(sqlerrcode, 128, "%d", edata->sqlerrcode);
securec_check_ss_c(ret, "\0", "\0");
dolphinErrorData->errorcode = pstrdup(sqlerrcode);
} else {
dolphinErrorData->class_origin = pstrdup(class_origin);
dolphinErrorData->subclass_origin = pstrdup(subclass_origin);
dolphinErrorData->errorcode = pstrdup(plpgsql_get_sqlstate(edata->sqlerrcode));
dolphinErrorData->sqlstatestr = pstrdup(plpgsql_get_sqlstate(edata->sqlerrcode));
}
dolphinErrorData->message_text = pstrdup(edata->message);
dolphinErrorData->constraint_catalog = pstrdup(edata->cons_catalog);
dolphinErrorData->constraint_schema = pstrdup(edata->cons_schema);
dolphinErrorData->constraint_name = pstrdup(edata->cons_name);
dolphinErrorData->catalog_name = pstrdup(edata->catalog_name);
dolphinErrorData->schema_name = pstrdup(edata->schema_name);
dolphinErrorData->table_name = pstrdup(edata->table_name);
dolphinErrorData->column_name = pstrdup(edata->column_name);
dolphinErrorData->cursor_name = pstrdup(edata->cursor_name);
errorDataArea->sqlErrorDataList = lappend(errorDataArea->sqlErrorDataList, dolphinErrorData);
errorDataArea->current_edata_count++;
errorDataArea->current_edata_count_by_level[errorLevelToDolphin(edata->elevel)]++;
}
}
MemoryContextSwitchTo(oldcontext);
if(edata->is_signal == PLpgSQL_signal_resignal::PLPGSQL_RESIGNAL_WITHOUT_SQLSTATE || edata->is_signal == PLpgSQL_signal_resignal::PLPGSQL_RESIGNAL_WITH_SQLSTATE) {
copyErrorDataArea(u_sess->dolphin_errdata_ctx.errorDataArea, u_sess->dolphin_errdata_ctx.lastErrorDataArea);
}
}
int32 SqlErrorDataErrorCount(ErrorDataArea *errorDataArea)
{
return errorDataArea->current_edata_count_by_level[enum_dolphin_error_level::B_ERROR];
}
int32 SqlErrorDataWarnCount(ErrorDataArea *errorDataArea)
{
return errorDataArea->current_edata_count_by_level[enum_dolphin_error_level::B_NOTE] +
errorDataArea->current_edata_count_by_level[enum_dolphin_error_level::B_WARNING] +
errorDataArea->current_edata_count_by_level[enum_dolphin_error_level::B_ERROR];
}
int SqlErrorDataCount()
{
return list_length(u_sess->dolphin_errdata_ctx.errorDataArea->sqlErrorDataList);
}
#define NUM_SHOW_WARNINGS_COLUMNS 3
typedef struct {
ErrorDataArea *errorDataArea;
int currIdx;
int limit;
ListCell *lc = NULL;
} ErrorDataAreaStatus;
void gramShowWarningsErrors(int offset, int count, DestReceiver *dest, bool isShowErrors)
{
TupOutputState *tstate = NULL;
TupleDesc tupdesc;
/* need a tuple descriptor representing three TEXT columns */
tupdesc = CreateTemplateTupleDesc(NUM_SHOW_WARNINGS_COLUMNS, false);
TupleDescInitEntry(tupdesc, (AttrNumber)1, "level", TEXTOID, -1, 0);
TupleDescInitEntry(tupdesc, (AttrNumber)2, "code", TEXTOID, -1, 0);
TupleDescInitEntry(tupdesc, (AttrNumber)3, "message", TEXTOID, -1, 0);
/* prepare for projection of tuples */
tstate = begin_tup_output_tupdesc(dest, tupdesc);
ErrorDataArea *errorDataArea = u_sess->dolphin_errdata_ctx.lastErrorDataArea;
int currIdx = 0;
int limit = count;
ListCell *lc = NULL;
while (currIdx < list_length(errorDataArea->sqlErrorDataList)) {
if (limit <= 0) {
break;
}
if (currIdx < offset) {
currIdx++;
continue;
}
Datum values[NUM_SHOW_WARNINGS_COLUMNS] = {0};
bool nulls[NUM_SHOW_WARNINGS_COLUMNS] = {false};
if (lc == NULL) {
List *sqlErrorDataList = errorDataArea->sqlErrorDataList;
lc = list_head(sqlErrorDataList);
} else {
lc = lnext(lc);
}
Assert(lc != NULL);
DolphinErrorData *eData = (DolphinErrorData *)lfirst(lc);
values[1] = CStringGetTextDatum(eData->errorcode);
if (isShowErrors) {
if (eData->elevel == enum_dolphin_error_level::B_ERROR) {
values[0] = CStringGetTextDatum("Error");
} else {
currIdx++;
continue;
}
} else {
switch (eData->elevel) {
case enum_dolphin_error_level::B_NOTE:
values[0] = CStringGetTextDatum("Note");
break;
case enum_dolphin_error_level::B_WARNING:
values[0] = CStringGetTextDatum("Warning");
break;
case enum_dolphin_error_level::B_ERROR:
default:
values[0] = CStringGetTextDatum("Error");
break;
}
}
if (eData->message_text) {
values[2] = CStringGetTextDatum(eData->message_text);
} else {
values[2] = CStringGetTextDatum("");
}
limit--;
currIdx++;
do_tup_output(tstate, values, NUM_SHOW_WARNINGS_COLUMNS, nulls, NUM_SHOW_WARNINGS_COLUMNS);
/* clean up */
pfree(DatumGetPointer(values[0]));
pfree(DatumGetPointer(values[1]));
pfree(DatumGetPointer(values[2]));
}
end_tup_output(tstate);
copyErrorDataArea(u_sess->dolphin_errdata_ctx.lastErrorDataArea, u_sess->dolphin_errdata_ctx.errorDataArea);
}
void gramShowWarningsErrorsCount(DestReceiver *dest, bool isShowErrors)
{
TupOutputState *tstate = NULL;
TupleDesc tupdesc;
Datum values[1] = {0};
bool isnull[1] = {false};
/* need a tuple descriptor representing three TEXT columns */
tupdesc = CreateTemplateTupleDesc(1, false);
TupleDescInitEntry(tupdesc, (AttrNumber)1, "count", INT4OID, -1, 0);
/* prepare for projection of tuples */
tstate = begin_tup_output_tupdesc(dest, tupdesc);
ErrorDataArea *errorDataArea = u_sess->dolphin_errdata_ctx.lastErrorDataArea;
if (isShowErrors) {
values[0] = Int32GetDatum(SqlErrorDataErrorCount(errorDataArea));
} else {
values[0] = Int32GetDatum(SqlErrorDataWarnCount(errorDataArea));
}
do_tup_output(tstate, values, 1, isnull, 1);
end_tup_output(tstate);
copyErrorDataArea(u_sess->dolphin_errdata_ctx.lastErrorDataArea, u_sess->dolphin_errdata_ctx.errorDataArea);
}
int getUserVarVal(char* str)
{
bool found = false;
GucUserParamsEntry* entry = NULL;
int condition_number = 0;
if (u_sess->utils_cxt.set_user_params_htab != NULL) {
entry = (GucUserParamsEntry*)hash_search(u_sess->utils_cxt.set_user_params_htab, str, HASH_FIND, &found);
if (found) {
entry = (GucUserParamsEntry*)hash_search(u_sess->utils_cxt.set_user_params_htab, str, HASH_ENTER, &found);
if (entry != NULL) {
Const* con = entry->value;
Datum constval = con->constvalue;
Oid consttype = con->consttype;
switch(consttype) {
case INT1OID:
case INT2OID:
case INT4OID:
case INT8OID:
{
condition_number = DatumGetInt64(constval);
} break;
case TEXTOID:
{
condition_number = (atof(TextDatumGetCString(constval)) + 0.5);
} break;
case FLOAT4OID:
case FLOAT8OID:
{
float8 value = DatumGetFloat8(constval);
char *value_str = DatumGetCString(DirectFunctionCall1(float8out, Float8GetDatum(value)));
condition_number = (atof(value_str) + 0.5);
} break;
case BOOLOID:
{
bool value = DatumGetBool(constval);
if (value) {
condition_number = 1;
} else {
condition_number = 0;
}
} break;
default:
break;
}
}
}
}
return condition_number;
}
int getConditionNum(List* condNum)
{
int condition_number = 0;
ListCell* valueCell = NULL;
Node* n = NULL;
foreach(valueCell, condNum) {
n = (Node*)lfirst(valueCell);
switch(nodeTag(n)) {
case T_A_Const: {
A_Const *a_const = (A_Const*)n;
Value* v = &a_const->val;
if (v->type == T_Integer) {
condition_number = intVal(v);
}
} break;
case T_UserVar: {
UserVar* uv = (UserVar*)n;
condition_number = getUserVarVal(uv->name);
} break;
default:
ereport(ERROR,
(errcode(ERRCODE_INVALID_CONDITION_NUMBER),
errmsg("Invalid condition number.")));
break;
}
}
return condition_number;
}
void getDiagnosticsInfo(List* condInfo, bool hasCondNum, List* condNum)
{
ListCell *lc = NULL;
StringInfoData buf;
ErrorDataArea *errorDataArea = u_sess->dolphin_errdata_ctx.lastErrorDataArea;
int condCount = list_length(errorDataArea->sqlErrorDataList);
int ret = 0;
initStringInfo(&buf);
/* condition number */
if (hasCondNum) {
int conditionNum = getConditionNum(condNum);
if (conditionNum < 1 || conditionNum > condCount) {
ErrorData* edata = (ErrorData *)palloc0(sizeof(ErrorData));
edata->elevel = ERROR;
edata->sqlerrcode = ERRCODE_INVALID_CONDITION_NUMBER;
edata->message = "Invalid condition number";
edata->class_origin = edata->sqlstate = edata->subclass_origin = edata->cons_catalog = edata->cons_schema = NULL;
edata->cons_name = edata->catalog_name = edata->schema_name = edata->table_name = edata->column_name = edata->cursor_name = NULL;
copyErrorDataArea(u_sess->dolphin_errdata_ctx.lastErrorDataArea, u_sess->dolphin_errdata_ctx.errorDataArea);
pushErrorData(edata);
pfree_ext(edata);
FreeStringInfo(&buf);
return;
}
DolphinErrorData* eData = (DolphinErrorData *)list_nth(errorDataArea->sqlErrorDataList, conditionNum - 1);
foreach(lc, condInfo) {
CondInfo *cond = (CondInfo*)lfirst(lc);
UserVar *uv = (UserVar *)lfirst(list_head(cond->target));
switch(cond->kind) {
case COND_INFO_CLASS_ORIGIN:
appendStringInfo(&buf, "set @%s = '%s';", uv->name, eData->class_origin);
break;
case COND_INFO_SUBCLASS_ORIGIN:
appendStringInfo(&buf, "set @%s = '%s';", uv->name, eData->subclass_origin);
break;
case COND_INFO_CONSTRAINT_CATALOG:
appendStringInfo(&buf, "set @%s = '%s';", uv->name, eData->constraint_catalog);
break;
case COND_INFO_CONSTRAINT_SCHEMA:
appendStringInfo(&buf, "set @%s = '%s';", uv->name, eData->constraint_schema);
break;
case COND_INFO_CONSTRAINT_NAME:
appendStringInfo(&buf, "set @%s = '%s';", uv->name, eData->constraint_name);
break;
case COND_INFO_CATALOG_NAME:
appendStringInfo(&buf, "set @%s = '%s';", uv->name, eData->catalog_name);
break;
case COND_INFO_SCHEMA_NAME:
appendStringInfo(&buf, "set @%s = '%s';", uv->name, eData->schema_name);
break;
case COND_INFO_TABLE_NAME:
appendStringInfo(&buf, "set @%s = '%s';", uv->name, eData->table_name);
break;
case COND_INFO_COLUMN_NAME:
appendStringInfo(&buf, "set @%s = '%s';", uv->name, eData->column_name);
break;
case COND_INFO_CURSOR_NAME:
appendStringInfo(&buf, "set @%s = '%s';", uv->name, eData->cursor_name);
break;
case COND_INFO_MESSAGE_TEXT:
appendStringInfo(&buf, "set @%s = '%s';", uv->name, eData->message_text);
break;
case COND_INFO_MYSQL_ERRNO:
appendStringInfo(&buf, "set @%s = '%s';", uv->name, eData->errorcode);
break;
case COND_INFO_RETURNED_SQLSTATE:
appendStringInfo(&buf, "set @%s = '%s';", uv->name, eData->sqlstatestr);
break;
default:
break;
}
}
} else {
lc = list_head(condInfo);
foreach(lc, condInfo) {
CondInfo *cond = (CondInfo*)lfirst(lc);
UserVar *uv = (UserVar *)lfirst(list_head(cond->target));
switch(cond->kind) {
case COND_INFO_NUMBER:
appendStringInfo(&buf, "set @%s = %d;", uv->name, condCount);
break;
case COND_INFO_ROW_COUNT:
appendStringInfo(&buf, "set @%s = %ld;", uv->name, u_sess->statement_cxt.current_row_count);
break;
default:
break;
}
}
}
if ((ret = SPI_connect()) != SPI_OK_CONNECT) {
ereport(ERROR, (errcode(ERRCODE_INTERNAL_ERROR), errmsg("SPI_connect failed: %s", SPI_result_code_string(ret))));
}
ret = SPI_execute(buf.data, false, 0);
if (ret != SPI_OK_UTILITY) {
ereport(ERROR,
(errcode(ERRCODE_SPI_EXECUTE_FAILURE),
errmsg("SPI_execute execute job interval fail, job_id: %d.", ret)));
}
SPI_finish();
FreeStringInfo(&buf);
copyErrorDataArea(u_sess->dolphin_errdata_ctx.lastErrorDataArea, u_sess->dolphin_errdata_ctx.errorDataArea);
}