mirror of
https://github.com/postgres/postgres.git
synced 2025-10-27 00:12:01 +03:00
PQtrace() was generating its output for non-printable characters without
casting the characters printed with unsigned char, leading to some extra
"\xffffff" generated in the output due to the fact that char may be
signed.
Oversights introduced by commit 198b3716db, so backpatch down to v14.
Author: Ran Benita <ran@unusedvar.com>
Discussion: https://postgr.es/m/a3383211-4539-459b-9d51-95c736ef08e0@app.fastmail.com
Backpatch-through: 14
730 lines
17 KiB
C
730 lines
17 KiB
C
/*-------------------------------------------------------------------------
|
|
*
|
|
* fe-trace.c
|
|
* functions for libpq protocol tracing
|
|
*
|
|
* Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
|
|
* Portions Copyright (c) 1994, Regents of the University of California
|
|
*
|
|
* IDENTIFICATION
|
|
* src/interfaces/libpq/fe-trace.c
|
|
*
|
|
*-------------------------------------------------------------------------
|
|
*/
|
|
|
|
#include "postgres_fe.h"
|
|
|
|
#include <ctype.h>
|
|
#include <limits.h>
|
|
#include <sys/time.h>
|
|
#include <time.h>
|
|
|
|
#ifdef WIN32
|
|
#include "win32.h"
|
|
#else
|
|
#include <unistd.h>
|
|
#endif
|
|
|
|
#include "libpq-fe.h"
|
|
#include "libpq-int.h"
|
|
#include "port/pg_bswap.h"
|
|
|
|
|
|
/* Enable tracing */
|
|
void
|
|
PQtrace(PGconn *conn, FILE *debug_port)
|
|
{
|
|
if (conn == NULL)
|
|
return;
|
|
PQuntrace(conn);
|
|
if (debug_port == NULL)
|
|
return;
|
|
|
|
conn->Pfdebug = debug_port;
|
|
conn->traceFlags = 0;
|
|
}
|
|
|
|
/* Disable tracing */
|
|
void
|
|
PQuntrace(PGconn *conn)
|
|
{
|
|
if (conn == NULL)
|
|
return;
|
|
if (conn->Pfdebug)
|
|
{
|
|
fflush(conn->Pfdebug);
|
|
conn->Pfdebug = NULL;
|
|
}
|
|
|
|
conn->traceFlags = 0;
|
|
}
|
|
|
|
/* Set flags for current tracing session */
|
|
void
|
|
PQsetTraceFlags(PGconn *conn, int flags)
|
|
{
|
|
if (conn == NULL)
|
|
return;
|
|
/* If PQtrace() failed, do nothing. */
|
|
if (conn->Pfdebug == NULL)
|
|
return;
|
|
conn->traceFlags = flags;
|
|
}
|
|
|
|
/*
|
|
* Print the current time, with microseconds, into a caller-supplied
|
|
* buffer.
|
|
* Cribbed from setup_formatted_log_time, but much simpler.
|
|
*/
|
|
static void
|
|
pqTraceFormatTimestamp(char *timestr, size_t ts_len)
|
|
{
|
|
struct timeval tval;
|
|
time_t now;
|
|
|
|
gettimeofday(&tval, NULL);
|
|
|
|
/*
|
|
* MSVC's implementation of timeval uses a long for tv_sec, however,
|
|
* localtime() expects a time_t pointer. Here we'll assign tv_sec to a
|
|
* local time_t variable so that we pass localtime() the correct pointer
|
|
* type.
|
|
*/
|
|
now = tval.tv_sec;
|
|
strftime(timestr, ts_len,
|
|
"%Y-%m-%d %H:%M:%S",
|
|
localtime(&now));
|
|
/* append microseconds */
|
|
snprintf(timestr + strlen(timestr), ts_len - strlen(timestr),
|
|
".%06u", (unsigned int) (tval.tv_usec));
|
|
}
|
|
|
|
/*
|
|
* pqTraceOutputByte1: output a 1-char message to the log
|
|
*/
|
|
static void
|
|
pqTraceOutputByte1(FILE *pfdebug, const char *data, int *cursor)
|
|
{
|
|
const char *v = data + *cursor;
|
|
|
|
/*
|
|
* Show non-printable data in hex format, including the terminating \0
|
|
* that completes ErrorResponse and NoticeResponse messages.
|
|
*/
|
|
if (!isprint((unsigned char) *v))
|
|
fprintf(pfdebug, " \\x%02x", (unsigned char) *v);
|
|
else
|
|
fprintf(pfdebug, " %c", *v);
|
|
*cursor += 1;
|
|
}
|
|
|
|
/*
|
|
* pqTraceOutputInt16: output a 2-byte integer message to the log
|
|
*/
|
|
static int
|
|
pqTraceOutputInt16(FILE *pfdebug, const char *data, int *cursor)
|
|
{
|
|
uint16 tmp;
|
|
int result;
|
|
|
|
memcpy(&tmp, data + *cursor, 2);
|
|
*cursor += 2;
|
|
result = (int) pg_ntoh16(tmp);
|
|
fprintf(pfdebug, " %d", result);
|
|
|
|
return result;
|
|
}
|
|
|
|
/*
|
|
* pqTraceOutputInt32: output a 4-byte integer message to the log
|
|
*
|
|
* If 'suppress' is true, print a literal NNNN instead of the actual number.
|
|
*/
|
|
static int
|
|
pqTraceOutputInt32(FILE *pfdebug, const char *data, int *cursor, bool suppress)
|
|
{
|
|
int result;
|
|
|
|
memcpy(&result, data + *cursor, 4);
|
|
*cursor += 4;
|
|
result = (int) pg_ntoh32(result);
|
|
if (suppress)
|
|
fprintf(pfdebug, " NNNN");
|
|
else
|
|
fprintf(pfdebug, " %d", result);
|
|
|
|
return result;
|
|
}
|
|
|
|
/*
|
|
* pqTraceOutputString: output a string message to the log
|
|
*/
|
|
static void
|
|
pqTraceOutputString(FILE *pfdebug, const char *data, int *cursor, bool suppress)
|
|
{
|
|
int len;
|
|
|
|
if (suppress)
|
|
{
|
|
fprintf(pfdebug, " \"SSSS\"");
|
|
*cursor += strlen(data + *cursor) + 1;
|
|
}
|
|
else
|
|
{
|
|
len = fprintf(pfdebug, " \"%s\"", data + *cursor);
|
|
|
|
/*
|
|
* This is a null-terminated string. So add 1 after subtracting 3
|
|
* which is the double quotes and space length from len.
|
|
*/
|
|
*cursor += (len - 3 + 1);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* pqTraceOutputNchar: output a string of exactly len bytes message to the log
|
|
*/
|
|
static void
|
|
pqTraceOutputNchar(FILE *pfdebug, int len, const char *data, int *cursor)
|
|
{
|
|
int i,
|
|
next; /* first char not yet printed */
|
|
const char *v = data + *cursor;
|
|
|
|
fprintf(pfdebug, " \'");
|
|
|
|
for (next = i = 0; i < len; ++i)
|
|
{
|
|
if (isprint((unsigned char) v[i]))
|
|
continue;
|
|
else
|
|
{
|
|
fwrite(v + next, 1, i - next, pfdebug);
|
|
fprintf(pfdebug, "\\x%02x", (unsigned char) v[i]);
|
|
next = i + 1;
|
|
}
|
|
}
|
|
if (next < len)
|
|
fwrite(v + next, 1, len - next, pfdebug);
|
|
|
|
fprintf(pfdebug, "\'");
|
|
*cursor += len;
|
|
}
|
|
|
|
/*
|
|
* Output functions by protocol message type
|
|
*/
|
|
|
|
/* NotificationResponse */
|
|
static void
|
|
pqTraceOutputA(FILE *f, const char *message, int *cursor, bool regress)
|
|
{
|
|
fprintf(f, "NotificationResponse\t");
|
|
pqTraceOutputInt32(f, message, cursor, regress);
|
|
pqTraceOutputString(f, message, cursor, false);
|
|
pqTraceOutputString(f, message, cursor, false);
|
|
}
|
|
|
|
/* Bind */
|
|
static void
|
|
pqTraceOutputB(FILE *f, const char *message, int *cursor)
|
|
{
|
|
int nparams;
|
|
|
|
fprintf(f, "Bind\t");
|
|
pqTraceOutputString(f, message, cursor, false);
|
|
pqTraceOutputString(f, message, cursor, false);
|
|
nparams = pqTraceOutputInt16(f, message, cursor);
|
|
|
|
for (int i = 0; i < nparams; i++)
|
|
pqTraceOutputInt16(f, message, cursor);
|
|
|
|
nparams = pqTraceOutputInt16(f, message, cursor);
|
|
|
|
for (int i = 0; i < nparams; i++)
|
|
{
|
|
int nbytes;
|
|
|
|
nbytes = pqTraceOutputInt32(f, message, cursor, false);
|
|
if (nbytes == -1)
|
|
continue;
|
|
pqTraceOutputNchar(f, nbytes, message, cursor);
|
|
}
|
|
|
|
nparams = pqTraceOutputInt16(f, message, cursor);
|
|
for (int i = 0; i < nparams; i++)
|
|
pqTraceOutputInt16(f, message, cursor);
|
|
}
|
|
|
|
/* Close(F) or CommandComplete(B) */
|
|
static void
|
|
pqTraceOutputC(FILE *f, bool toServer, const char *message, int *cursor)
|
|
{
|
|
if (toServer)
|
|
{
|
|
fprintf(f, "Close\t");
|
|
pqTraceOutputByte1(f, message, cursor);
|
|
pqTraceOutputString(f, message, cursor, false);
|
|
}
|
|
else
|
|
{
|
|
fprintf(f, "CommandComplete\t");
|
|
pqTraceOutputString(f, message, cursor, false);
|
|
}
|
|
}
|
|
|
|
/* Describe(F) or DataRow(B) */
|
|
static void
|
|
pqTraceOutputD(FILE *f, bool toServer, const char *message, int *cursor)
|
|
{
|
|
if (toServer)
|
|
{
|
|
fprintf(f, "Describe\t");
|
|
pqTraceOutputByte1(f, message, cursor);
|
|
pqTraceOutputString(f, message, cursor, false);
|
|
}
|
|
else
|
|
{
|
|
int nfields;
|
|
int len;
|
|
int i;
|
|
|
|
fprintf(f, "DataRow\t");
|
|
nfields = pqTraceOutputInt16(f, message, cursor);
|
|
for (i = 0; i < nfields; i++)
|
|
{
|
|
len = pqTraceOutputInt32(f, message, cursor, false);
|
|
if (len == -1)
|
|
continue;
|
|
pqTraceOutputNchar(f, len, message, cursor);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* NoticeResponse / ErrorResponse */
|
|
static void
|
|
pqTraceOutputNR(FILE *f, const char *type, const char *message, int *cursor,
|
|
bool regress)
|
|
{
|
|
fprintf(f, "%s\t", type);
|
|
for (;;)
|
|
{
|
|
char field;
|
|
bool suppress;
|
|
|
|
pqTraceOutputByte1(f, message, cursor);
|
|
field = message[*cursor - 1];
|
|
if (field == '\0')
|
|
break;
|
|
|
|
suppress = regress && (field == 'L' || field == 'F' || field == 'R');
|
|
pqTraceOutputString(f, message, cursor, suppress);
|
|
}
|
|
}
|
|
|
|
/* Execute(F) or ErrorResponse(B) */
|
|
static void
|
|
pqTraceOutputE(FILE *f, bool toServer, const char *message, int *cursor, bool regress)
|
|
{
|
|
if (toServer)
|
|
{
|
|
fprintf(f, "Execute\t");
|
|
pqTraceOutputString(f, message, cursor, false);
|
|
pqTraceOutputInt32(f, message, cursor, false);
|
|
}
|
|
else
|
|
pqTraceOutputNR(f, "ErrorResponse", message, cursor, regress);
|
|
}
|
|
|
|
/* CopyFail */
|
|
static void
|
|
pqTraceOutputf(FILE *f, const char *message, int *cursor)
|
|
{
|
|
fprintf(f, "CopyFail\t");
|
|
pqTraceOutputString(f, message, cursor, false);
|
|
}
|
|
|
|
/* FunctionCall */
|
|
static void
|
|
pqTraceOutputF(FILE *f, const char *message, int *cursor, bool regress)
|
|
{
|
|
int nfields;
|
|
int nbytes;
|
|
|
|
fprintf(f, "FunctionCall\t");
|
|
pqTraceOutputInt32(f, message, cursor, regress);
|
|
nfields = pqTraceOutputInt16(f, message, cursor);
|
|
|
|
for (int i = 0; i < nfields; i++)
|
|
pqTraceOutputInt16(f, message, cursor);
|
|
|
|
nfields = pqTraceOutputInt16(f, message, cursor);
|
|
|
|
for (int i = 0; i < nfields; i++)
|
|
{
|
|
nbytes = pqTraceOutputInt32(f, message, cursor, false);
|
|
if (nbytes == -1)
|
|
continue;
|
|
pqTraceOutputNchar(f, nbytes, message, cursor);
|
|
}
|
|
|
|
pqTraceOutputInt16(f, message, cursor);
|
|
}
|
|
|
|
/* CopyInResponse */
|
|
static void
|
|
pqTraceOutputG(FILE *f, const char *message, int *cursor)
|
|
{
|
|
int nfields;
|
|
|
|
fprintf(f, "CopyInResponse\t");
|
|
pqTraceOutputByte1(f, message, cursor);
|
|
nfields = pqTraceOutputInt16(f, message, cursor);
|
|
|
|
for (int i = 0; i < nfields; i++)
|
|
pqTraceOutputInt16(f, message, cursor);
|
|
}
|
|
|
|
/* CopyOutResponse */
|
|
static void
|
|
pqTraceOutputH(FILE *f, const char *message, int *cursor)
|
|
{
|
|
int nfields;
|
|
|
|
fprintf(f, "CopyOutResponse\t");
|
|
pqTraceOutputByte1(f, message, cursor);
|
|
nfields = pqTraceOutputInt16(f, message, cursor);
|
|
|
|
for (int i = 0; i < nfields; i++)
|
|
pqTraceOutputInt16(f, message, cursor);
|
|
}
|
|
|
|
/* BackendKeyData */
|
|
static void
|
|
pqTraceOutputK(FILE *f, const char *message, int *cursor, bool regress)
|
|
{
|
|
fprintf(f, "BackendKeyData\t");
|
|
pqTraceOutputInt32(f, message, cursor, regress);
|
|
pqTraceOutputInt32(f, message, cursor, regress);
|
|
}
|
|
|
|
/* Parse */
|
|
static void
|
|
pqTraceOutputP(FILE *f, const char *message, int *cursor, bool regress)
|
|
{
|
|
int nparams;
|
|
|
|
fprintf(f, "Parse\t");
|
|
pqTraceOutputString(f, message, cursor, false);
|
|
pqTraceOutputString(f, message, cursor, false);
|
|
nparams = pqTraceOutputInt16(f, message, cursor);
|
|
|
|
for (int i = 0; i < nparams; i++)
|
|
pqTraceOutputInt32(f, message, cursor, regress);
|
|
}
|
|
|
|
/* Query */
|
|
static void
|
|
pqTraceOutputQ(FILE *f, const char *message, int *cursor)
|
|
{
|
|
fprintf(f, "Query\t");
|
|
pqTraceOutputString(f, message, cursor, false);
|
|
}
|
|
|
|
/* Authentication */
|
|
static void
|
|
pqTraceOutputR(FILE *f, const char *message, int *cursor)
|
|
{
|
|
fprintf(f, "Authentication\t");
|
|
pqTraceOutputInt32(f, message, cursor, false);
|
|
}
|
|
|
|
/* ParameterStatus */
|
|
static void
|
|
pqTraceOutputS(FILE *f, const char *message, int *cursor)
|
|
{
|
|
fprintf(f, "ParameterStatus\t");
|
|
pqTraceOutputString(f, message, cursor, false);
|
|
pqTraceOutputString(f, message, cursor, false);
|
|
}
|
|
|
|
/* ParameterDescription */
|
|
static void
|
|
pqTraceOutputt(FILE *f, const char *message, int *cursor, bool regress)
|
|
{
|
|
int nfields;
|
|
|
|
fprintf(f, "ParameterDescription\t");
|
|
nfields = pqTraceOutputInt16(f, message, cursor);
|
|
|
|
for (int i = 0; i < nfields; i++)
|
|
pqTraceOutputInt32(f, message, cursor, regress);
|
|
}
|
|
|
|
/* RowDescription */
|
|
static void
|
|
pqTraceOutputT(FILE *f, const char *message, int *cursor, bool regress)
|
|
{
|
|
int nfields;
|
|
|
|
fprintf(f, "RowDescription\t");
|
|
nfields = pqTraceOutputInt16(f, message, cursor);
|
|
|
|
for (int i = 0; i < nfields; i++)
|
|
{
|
|
pqTraceOutputString(f, message, cursor, false);
|
|
pqTraceOutputInt32(f, message, cursor, regress);
|
|
pqTraceOutputInt16(f, message, cursor);
|
|
pqTraceOutputInt32(f, message, cursor, regress);
|
|
pqTraceOutputInt16(f, message, cursor);
|
|
pqTraceOutputInt32(f, message, cursor, false);
|
|
pqTraceOutputInt16(f, message, cursor);
|
|
}
|
|
}
|
|
|
|
/* NegotiateProtocolVersion */
|
|
static void
|
|
pqTraceOutputv(FILE *f, const char *message, int *cursor)
|
|
{
|
|
fprintf(f, "NegotiateProtocolVersion\t");
|
|
pqTraceOutputInt32(f, message, cursor, false);
|
|
pqTraceOutputInt32(f, message, cursor, false);
|
|
}
|
|
|
|
/* FunctionCallResponse */
|
|
static void
|
|
pqTraceOutputV(FILE *f, const char *message, int *cursor)
|
|
{
|
|
int len;
|
|
|
|
fprintf(f, "FunctionCallResponse\t");
|
|
len = pqTraceOutputInt32(f, message, cursor, false);
|
|
if (len != -1)
|
|
pqTraceOutputNchar(f, len, message, cursor);
|
|
}
|
|
|
|
/* CopyBothResponse */
|
|
static void
|
|
pqTraceOutputW(FILE *f, const char *message, int *cursor, int length)
|
|
{
|
|
fprintf(f, "CopyBothResponse\t");
|
|
pqTraceOutputByte1(f, message, cursor);
|
|
|
|
while (length > *cursor)
|
|
pqTraceOutputInt16(f, message, cursor);
|
|
}
|
|
|
|
/* ReadyForQuery */
|
|
static void
|
|
pqTraceOutputZ(FILE *f, const char *message, int *cursor)
|
|
{
|
|
fprintf(f, "ReadyForQuery\t");
|
|
pqTraceOutputByte1(f, message, cursor);
|
|
}
|
|
|
|
/*
|
|
* Print the given message to the trace output stream.
|
|
*/
|
|
void
|
|
pqTraceOutputMessage(PGconn *conn, const char *message, bool toServer)
|
|
{
|
|
char id;
|
|
int length;
|
|
char *prefix = toServer ? "F" : "B";
|
|
int logCursor = 0;
|
|
bool regress;
|
|
|
|
if ((conn->traceFlags & PQTRACE_SUPPRESS_TIMESTAMPS) == 0)
|
|
{
|
|
char timestr[128];
|
|
|
|
pqTraceFormatTimestamp(timestr, sizeof(timestr));
|
|
fprintf(conn->Pfdebug, "%s\t", timestr);
|
|
}
|
|
regress = (conn->traceFlags & PQTRACE_REGRESS_MODE) != 0;
|
|
|
|
id = message[logCursor++];
|
|
|
|
memcpy(&length, message + logCursor, 4);
|
|
length = (int) pg_ntoh32(length);
|
|
logCursor += 4;
|
|
|
|
/*
|
|
* In regress mode, suppress the length of ErrorResponse and
|
|
* NoticeResponse. The F (file name), L (line number) and R (routine
|
|
* name) fields can change as server code is modified, and if their
|
|
* lengths differ from the originals, that would break tests.
|
|
*/
|
|
if (regress && !toServer && (id == 'E' || id == 'N'))
|
|
fprintf(conn->Pfdebug, "%s\tNN\t", prefix);
|
|
else
|
|
fprintf(conn->Pfdebug, "%s\t%d\t", prefix, length);
|
|
|
|
switch (id)
|
|
{
|
|
case '1':
|
|
fprintf(conn->Pfdebug, "ParseComplete");
|
|
/* No message content */
|
|
break;
|
|
case '2':
|
|
fprintf(conn->Pfdebug, "BindComplete");
|
|
/* No message content */
|
|
break;
|
|
case '3':
|
|
fprintf(conn->Pfdebug, "CloseComplete");
|
|
/* No message content */
|
|
break;
|
|
case 'A': /* Notification Response */
|
|
pqTraceOutputA(conn->Pfdebug, message, &logCursor, regress);
|
|
break;
|
|
case 'B': /* Bind */
|
|
pqTraceOutputB(conn->Pfdebug, message, &logCursor);
|
|
break;
|
|
case 'c':
|
|
fprintf(conn->Pfdebug, "CopyDone");
|
|
/* No message content */
|
|
break;
|
|
case 'C': /* Close(F) or Command Complete(B) */
|
|
pqTraceOutputC(conn->Pfdebug, toServer, message, &logCursor);
|
|
break;
|
|
case 'd': /* Copy Data */
|
|
/* Drop COPY data to reduce the overhead of logging. */
|
|
break;
|
|
case 'D': /* Describe(F) or Data Row(B) */
|
|
pqTraceOutputD(conn->Pfdebug, toServer, message, &logCursor);
|
|
break;
|
|
case 'E': /* Execute(F) or Error Response(B) */
|
|
pqTraceOutputE(conn->Pfdebug, toServer, message, &logCursor,
|
|
regress);
|
|
break;
|
|
case 'f': /* Copy Fail */
|
|
pqTraceOutputf(conn->Pfdebug, message, &logCursor);
|
|
break;
|
|
case 'F': /* Function Call */
|
|
pqTraceOutputF(conn->Pfdebug, message, &logCursor, regress);
|
|
break;
|
|
case 'G': /* Start Copy In */
|
|
pqTraceOutputG(conn->Pfdebug, message, &logCursor);
|
|
break;
|
|
case 'H': /* Flush(F) or Start Copy Out(B) */
|
|
if (!toServer)
|
|
pqTraceOutputH(conn->Pfdebug, message, &logCursor);
|
|
else
|
|
fprintf(conn->Pfdebug, "Flush"); /* no message content */
|
|
break;
|
|
case 'I':
|
|
fprintf(conn->Pfdebug, "EmptyQueryResponse");
|
|
/* No message content */
|
|
break;
|
|
case 'K': /* secret key data from the backend */
|
|
pqTraceOutputK(conn->Pfdebug, message, &logCursor, regress);
|
|
break;
|
|
case 'n':
|
|
fprintf(conn->Pfdebug, "NoData");
|
|
/* No message content */
|
|
break;
|
|
case 'N':
|
|
pqTraceOutputNR(conn->Pfdebug, "NoticeResponse", message,
|
|
&logCursor, regress);
|
|
break;
|
|
case 'P': /* Parse */
|
|
pqTraceOutputP(conn->Pfdebug, message, &logCursor, regress);
|
|
break;
|
|
case 'Q': /* Query */
|
|
pqTraceOutputQ(conn->Pfdebug, message, &logCursor);
|
|
break;
|
|
case 'R': /* Authentication */
|
|
pqTraceOutputR(conn->Pfdebug, message, &logCursor);
|
|
break;
|
|
case 's':
|
|
fprintf(conn->Pfdebug, "PortalSuspended");
|
|
/* No message content */
|
|
break;
|
|
case 'S': /* Parameter Status(B) or Sync(F) */
|
|
if (!toServer)
|
|
pqTraceOutputS(conn->Pfdebug, message, &logCursor);
|
|
else
|
|
fprintf(conn->Pfdebug, "Sync"); /* no message content */
|
|
break;
|
|
case 't': /* Parameter Description */
|
|
pqTraceOutputt(conn->Pfdebug, message, &logCursor, regress);
|
|
break;
|
|
case 'T': /* Row Description */
|
|
pqTraceOutputT(conn->Pfdebug, message, &logCursor, regress);
|
|
break;
|
|
case 'v': /* Negotiate Protocol Version */
|
|
pqTraceOutputv(conn->Pfdebug, message, &logCursor);
|
|
break;
|
|
case 'V': /* Function Call response */
|
|
pqTraceOutputV(conn->Pfdebug, message, &logCursor);
|
|
break;
|
|
case 'W': /* Start Copy Both */
|
|
pqTraceOutputW(conn->Pfdebug, message, &logCursor, length);
|
|
break;
|
|
case 'X':
|
|
fprintf(conn->Pfdebug, "Terminate");
|
|
/* No message content */
|
|
break;
|
|
case 'Z': /* Ready For Query */
|
|
pqTraceOutputZ(conn->Pfdebug, message, &logCursor);
|
|
break;
|
|
default:
|
|
fprintf(conn->Pfdebug, "Unknown message: %02x", id);
|
|
break;
|
|
}
|
|
|
|
fputc('\n', conn->Pfdebug);
|
|
|
|
/*
|
|
* Verify the printing routine did it right. Note that the one-byte
|
|
* message identifier is not included in the length, but our cursor does
|
|
* include it.
|
|
*/
|
|
if (logCursor - 1 != length)
|
|
fprintf(conn->Pfdebug,
|
|
"mismatched message length: consumed %d, expected %d\n",
|
|
logCursor - 1, length);
|
|
}
|
|
|
|
/*
|
|
* Print special messages (those containing no type byte) to the trace output
|
|
* stream.
|
|
*/
|
|
void
|
|
pqTraceOutputNoTypeByteMessage(PGconn *conn, const char *message)
|
|
{
|
|
int length;
|
|
int logCursor = 0;
|
|
|
|
if ((conn->traceFlags & PQTRACE_SUPPRESS_TIMESTAMPS) == 0)
|
|
{
|
|
char timestr[128];
|
|
|
|
pqTraceFormatTimestamp(timestr, sizeof(timestr));
|
|
fprintf(conn->Pfdebug, "%s\t", timestr);
|
|
}
|
|
|
|
memcpy(&length, message + logCursor, 4);
|
|
length = (int) pg_ntoh32(length);
|
|
logCursor += 4;
|
|
|
|
fprintf(conn->Pfdebug, "F\t%d\t", length);
|
|
|
|
switch (length)
|
|
{
|
|
case 16: /* CancelRequest */
|
|
fprintf(conn->Pfdebug, "CancelRequest\t");
|
|
pqTraceOutputInt32(conn->Pfdebug, message, &logCursor, false);
|
|
pqTraceOutputInt32(conn->Pfdebug, message, &logCursor, false);
|
|
pqTraceOutputInt32(conn->Pfdebug, message, &logCursor, false);
|
|
break;
|
|
case 8: /* GSSENCRequest or SSLRequest */
|
|
/* These messages do not reach here. */
|
|
default:
|
|
fprintf(conn->Pfdebug, "Unknown message: length is %d", length);
|
|
break;
|
|
}
|
|
|
|
fputc('\n', conn->Pfdebug);
|
|
}
|