| /*------------------------------------------------------------------------- |
| * |
| * fe-trace.c |
| * functions for libpq protocol tracing |
| * |
| * Portions Copyright (c) 1996-2023, 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 get_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", *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", 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); |
| } |