diff options
author | Tom Lane <tgl@sss.pgh.pa.us> | 2003-04-22 00:08:07 +0000 |
---|---|---|
committer | Tom Lane <tgl@sss.pgh.pa.us> | 2003-04-22 00:08:07 +0000 |
commit | 5ed27e35f35f6c354b1a7120ec3a3ce57f93e73e (patch) | |
tree | 9ed912fbf02c36160a88881764735f8eab6103b9 /src/interfaces | |
parent | ca944bd2d41814712cb4a4810ab4aa490f23a853 (diff) | |
download | postgresql-5ed27e35f35f6c354b1a7120ec3a3ce57f93e73e.tar.gz postgresql-5ed27e35f35f6c354b1a7120ec3a3ce57f93e73e.zip |
Another round of protocol changes. Backend-to-frontend messages now all
have length words. COPY OUT reimplemented per new protocol: it doesn't
need \. anymore, thank goodness. COPY BINARY to/from frontend works,
at least as far as the backend is concerned --- libpq's PQgetline API
is not up to snuff, and will have to be replaced with something that is
null-safe. libpq uses message length words for performance improvement
(no cycles wasted rescanning long messages), but not yet for error
recovery.
Diffstat (limited to 'src/interfaces')
-rw-r--r-- | src/interfaces/libpq/fe-connect.c | 94 | ||||
-rw-r--r-- | src/interfaces/libpq/fe-exec.c | 444 | ||||
-rw-r--r-- | src/interfaces/libpq/fe-misc.c | 84 | ||||
-rw-r--r-- | src/interfaces/libpq/libpq-int.h | 20 |
4 files changed, 460 insertions, 182 deletions
diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c index a322d8a73d1..086462094ff 100644 --- a/src/interfaces/libpq/fe-connect.c +++ b/src/interfaces/libpq/fe-connect.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/interfaces/libpq/fe-connect.c,v 1.233 2003/04/19 00:02:30 tgl Exp $ + * $Header: /cvsroot/pgsql/src/interfaces/libpq/fe-connect.c,v 1.234 2003/04/22 00:08:07 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -1328,6 +1328,8 @@ keep_going: /* We will come back to here until there case CONNECTION_AWAITING_RESPONSE: { char beresp; + int msgLength; + int avail; AuthRequest areq; /* @@ -1337,15 +1339,58 @@ keep_going: /* We will come back to here until there */ conn->inCursor = conn->inStart; + /* Read type byte */ if (pqGetc(&beresp, conn)) { /* We'll come back when there is more data */ return PGRES_POLLING_READING; } - /* Handle errors. */ - if (beresp == 'E') + /* + * Validate message type: we expect only an authentication + * request or an error here. Anything else probably means + * it's not Postgres on the other end at all. + */ + if (!(beresp == 'R' || beresp == 'E')) + { + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext( + "expected authentication request from " + "server, but received %c\n" + ), + beresp); + goto error_return; + } + + /* Read message length word */ + if (pqGetInt(&msgLength, 4, conn)) + { + /* We'll come back when there is more data */ + return PGRES_POLLING_READING; + } + + /* + * Try to validate message length before using it. + * Authentication requests can't be very large. Errors + * can be a little larger, but not huge. If we see a large + * apparent length in an error, it means we're really talking + * to a pre-3.0-protocol server; cope. + */ + if (beresp == 'R' && (msgLength < 8 || msgLength > 100)) + { + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext( + "expected authentication request from " + "server, but received %c\n" + ), + beresp); + goto error_return; + } + + if (beresp == 'E' && (msgLength < 8 || msgLength > 30000)) { + /* Handle error from a pre-3.0 server */ + conn->inCursor = conn->inStart + 1; /* reread data */ if (pqGets(&conn->errorMessage, conn)) { /* We'll come back when there is more data */ @@ -1363,18 +1408,45 @@ keep_going: /* We will come back to here until there goto error_return; } - /* Otherwise it should be an authentication request. */ - if (beresp != 'R') + /* + * Can't process if message body isn't all here yet. + */ + msgLength -= 4; + avail = conn->inEnd - conn->inCursor; + if (avail < msgLength) { - printfPQExpBuffer(&conn->errorMessage, - libpq_gettext( - "expected authentication request from " - "server, but received %c\n" - ), - beresp); + /* + * Before returning, try to enlarge the input buffer if + * needed to hold the whole message; see notes in + * parseInput. + */ + if (pqCheckInBufferSpace(conn->inCursor + msgLength, conn)) + goto error_return; + /* We'll come back when there is more data */ + return PGRES_POLLING_READING; + } + + /* Handle errors. */ + if (beresp == 'E') + { + if (pqGets(&conn->errorMessage, conn)) + { + /* We'll come back when there is more data */ + return PGRES_POLLING_READING; + } + /* OK, we read the message; mark data consumed */ + conn->inStart = conn->inCursor; + + /* + * The postmaster typically won't end its message with + * a newline, so add one to conform to libpq + * conventions. + */ + appendPQExpBufferChar(&conn->errorMessage, '\n'); goto error_return; } + /* It is an authentication request. */ /* Get the type of request. */ if (pqGetInt((int *) &areq, 4, conn)) { diff --git a/src/interfaces/libpq/fe-exec.c b/src/interfaces/libpq/fe-exec.c index 487acff83df..16e63f7f68f 100644 --- a/src/interfaces/libpq/fe-exec.c +++ b/src/interfaces/libpq/fe-exec.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/interfaces/libpq/fe-exec.c,v 1.129 2003/04/19 00:02:30 tgl Exp $ + * $Header: /cvsroot/pgsql/src/interfaces/libpq/fe-exec.c,v 1.130 2003/04/22 00:08:07 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -51,6 +51,7 @@ static PGresult *prepareAsyncResult(PGconn *conn); static int addTuple(PGresult *res, PGresAttValue * tup); static void parseInput(PGconn *conn); static void handleSendFailure(PGconn *conn); +static void handleSyncLoss(PGconn *conn, char id, int msgLength); static int getRowDescriptions(PGconn *conn); static int getAnotherTuple(PGconn *conn, int binary); static int getNotify(PGconn *conn); @@ -866,6 +867,8 @@ static void parseInput(PGconn *conn) { char id; + int msgLength; + int avail; char noticeWorkspace[128]; /* @@ -874,25 +877,63 @@ parseInput(PGconn *conn) for (;;) { /* - * Quit if in COPY_OUT state: we expect raw data from the server - * until PQendcopy is called. Don't try to parse it according to - * the normal protocol. (This is bogus. The data lines ought to - * be part of the protocol and have identifying leading - * characters.) + * Try to read a message. First get the type code and length. + * Return if not enough data. */ - if (conn->asyncStatus == PGASYNC_COPY_OUT) + conn->inCursor = conn->inStart; + if (pqGetc(&id, conn)) + return; + if (pqGetInt(&msgLength, 4, conn)) return; /* - * OK to try to read a message type code. + * Try to validate message type/length here. A length less than 4 + * is definitely broken. Large lengths should only be believed + * for a few message types. */ - conn->inCursor = conn->inStart; - if (pqGetc(&id, conn)) + if (msgLength < 4) + { + handleSyncLoss(conn, id, msgLength); + return; + } + if (msgLength > 30000 && + !(id == 'T' || id == 'D' || id == 'B' || id == 'd')) + { + handleSyncLoss(conn, id, msgLength); + return; + } + + /* + * Can't process if message body isn't all here yet. + */ + msgLength -= 4; + avail = conn->inEnd - conn->inCursor; + if (avail < msgLength) + { + /* + * Before returning, enlarge the input buffer if needed to hold + * the whole message. This is better than leaving it to + * pqReadData because we can avoid multiple cycles of realloc() + * when the message is large; also, we can implement a reasonable + * recovery strategy if we are unable to make the buffer big + * enough. + */ + if (pqCheckInBufferSpace(conn->inCursor + msgLength, conn)) + { + /* + * XXX add some better recovery code... plan is to skip + * over the message using its length, then report an error. + * For the moment, just treat this like loss of sync (which + * indeed it might be!) + */ + handleSyncLoss(conn, id, msgLength); + } return; + } /* - * NOTIFY and NOTICE messages can happen in any state besides - * COPY OUT; always process them right away. + * NOTIFY and NOTICE messages can happen in any state; always process + * them right away. * * Most other messages should only be processed while in BUSY state. * (In particular, in READY state we hold off further parsing @@ -936,9 +977,8 @@ parseInput(PGconn *conn) libpq_gettext("message type 0x%02x arrived from server while idle\n"), id); DONOTICE(conn, noticeWorkspace); - /* Discard the unexpected message; good idea?? */ - conn->inStart = conn->inEnd; - break; + /* Discard the unexpected message */ + conn->inCursor += msgLength; } } else @@ -969,16 +1009,6 @@ parseInput(PGconn *conn) conn->asyncStatus = PGASYNC_IDLE; break; case 'I': /* empty query */ - /* read and throw away the closing '\0' */ - if (pqGetc(&id, conn)) - return; - if (id != '\0') - { - snprintf(noticeWorkspace, sizeof(noticeWorkspace), - libpq_gettext("unexpected character %c following empty query response (\"I\" message)\n"), - id); - DONOTICE(conn, noticeWorkspace); - } if (conn->result == NULL) conn->result = PQmakeEmptyPGresult(conn, PGRES_EMPTY_QUERY); @@ -996,11 +1026,6 @@ parseInput(PGconn *conn) if (pqGetInt(&(conn->be_key), 4, conn)) return; break; - case 'P': /* synchronous (normal) portal */ - if (pqGets(&conn->workBuffer, conn)) - return; - /* We pretty much ignore this message type... */ - break; case 'T': /* row descriptions (start of query * results) */ if (conn->result == NULL) @@ -1034,9 +1059,8 @@ parseInput(PGconn *conn) snprintf(noticeWorkspace, sizeof(noticeWorkspace), libpq_gettext("server sent data (\"D\" message) without prior row description (\"T\" message)\n")); DONOTICE(conn, noticeWorkspace); - /* Discard the unexpected message; good idea?? */ - conn->inStart = conn->inEnd; - return; + /* Discard the unexpected message */ + conn->inCursor += msgLength; } break; case 'B': /* Binary data tuple */ @@ -1051,16 +1075,36 @@ parseInput(PGconn *conn) snprintf(noticeWorkspace, sizeof(noticeWorkspace), libpq_gettext("server sent binary data (\"B\" message) without prior row description (\"T\" message)\n")); DONOTICE(conn, noticeWorkspace); - /* Discard the unexpected message; good idea?? */ - conn->inStart = conn->inEnd; - return; + /* Discard the unexpected message */ + conn->inCursor += msgLength; } break; case 'G': /* Start Copy In */ + if (pqGetc(&conn->copy_is_binary, conn)) + return; conn->asyncStatus = PGASYNC_COPY_IN; break; case 'H': /* Start Copy Out */ + if (pqGetc(&conn->copy_is_binary, conn)) + return; conn->asyncStatus = PGASYNC_COPY_OUT; + conn->copy_already_done = 0; + break; + case 'd': /* Copy Data */ + /* + * If we see Copy Data, just silently drop it. This + * would only occur if application exits COPY OUT mode + * too early. + */ + conn->inCursor += msgLength; + break; + case 'c': /* Copy Done */ + /* + * If we see Copy Done, just silently drop it. This + * is the normal case during PQendcopy. We will keep + * swallowing data, expecting to see command-complete + * for the COPY command. + */ break; default: printfPQExpBuffer(&conn->errorMessage, @@ -1069,17 +1113,54 @@ parseInput(PGconn *conn) id); /* build an error result holding the error message */ saveErrorResult(conn); - /* Discard the unexpected message; good idea?? */ - conn->inStart = conn->inEnd; conn->asyncStatus = PGASYNC_READY; - return; + /* Discard the unexpected message */ + conn->inCursor += msgLength; + break; } /* switch on protocol character */ } /* Successfully consumed this message */ - conn->inStart = conn->inCursor; + if (conn->inCursor == conn->inStart + 5 + msgLength) + { + /* Normal case: parsing agrees with specified length */ + conn->inStart = conn->inCursor; + } + else + { + /* Trouble --- report it */ + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("Message contents do not agree with length in message type \"%c\"\n"), + id); + /* build an error result holding the error message */ + saveErrorResult(conn); + conn->asyncStatus = PGASYNC_READY; + /* trust the specified message length as what to skip */ + conn->inStart += 5 + msgLength; + } } } +/* + * handleSyncLoss: clean up after loss of message-boundary sync + * + * There isn't really a lot we can do here except abandon the connection. + */ +static void +handleSyncLoss(PGconn *conn, char id, int msgLength) +{ + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext( + "lost synchronization with server: got message type \"%c\", length %d\n"), + id, msgLength); + conn->status = CONNECTION_BAD; /* No more connection to backend */ + pqsecure_close(conn); +#ifdef WIN32 + closesocket(conn->sock); +#else + close(conn->sock); +#endif + conn->sock = -1; +} /* * parseInput subroutine to read a 'T' (row descriptions) message. @@ -1100,7 +1181,7 @@ getRowDescriptions(PGconn *conn) result = PQmakeEmptyPGresult(conn, PGRES_TUPLES_OK); - /* parseInput already read the 'T' label. */ + /* parseInput already read the 'T' label and message length. */ /* the next two bytes are the number of fields */ if (pqGetInt(&(result->numAttributes), 2, conn)) { @@ -1461,7 +1542,7 @@ errout: /* * Attempt to read a Notice response message. * This is possible in several places, so we break it out as a subroutine. - * Entry: 'N' flag character has already been consumed. + * Entry: 'N' message type and length have already been consumed. * Exit: returns 0 if successfully consumed Notice message. * returns EOF if not enough data. */ @@ -1489,7 +1570,7 @@ getNotice(PGconn *conn) /* * Attempt to read a Notify response message. * This is possible in several places, so we break it out as a subroutine. - * Entry: 'A' flag character has already been consumed. + * Entry: 'A' message type and length have already been consumed. * Exit: returns 0 if successfully consumed Notify message. * returns EOF if not enough data. */ @@ -1511,10 +1592,18 @@ getNotify(PGconn *conn) */ newNotify = (PGnotify *) malloc(sizeof(PGnotify) + strlen(conn->workBuffer.data) +1); - newNotify->relname = (char *) newNotify + sizeof(PGnotify); - strcpy(newNotify->relname, conn->workBuffer.data); - newNotify->be_pid = be_pid; - DLAddTail(conn->notifyList, DLNewElem(newNotify)); + if (newNotify) + { + newNotify->relname = (char *) newNotify + sizeof(PGnotify); + strcpy(newNotify->relname, conn->workBuffer.data); + newNotify->be_pid = be_pid; + DLAddTail(conn->notifyList, DLNewElem(newNotify)); + } + + /* Swallow extra string (not presently used) */ + if (pqGets(&conn->workBuffer, conn)) + return EOF; + return 0; } @@ -1556,6 +1645,9 @@ PQnotifies(PGconn *conn) * Chiefly here so that applications can use "COPY <rel> to stdout" * and read the output string. Returns a null-terminated string in s. * + * XXX this routine is now deprecated, because it can't handle binary data. + * If called during a COPY BINARY we return EOF. + * * PQgetline reads up to maxlen-1 characters (like fgets(3)) but strips * the terminating \n (like gets(3)). * @@ -1563,7 +1655,7 @@ PQnotifies(PGconn *conn) * (a line containing just "\.") when using this routine. * * RETURNS: - * EOF if it is detected or invalid arguments are given + * EOF if error (eg, invalid arguments are given) * 0 if EOL is reached (i.e., \n has been read) * (this is required for backward-compatibility -- this * routine used to always return EOF or 0, assuming that @@ -1573,53 +1665,55 @@ PQnotifies(PGconn *conn) int PQgetline(PGconn *conn, char *s, int maxlen) { - int result = 1; /* return value if buffer overflows */ + int status; - if (!s || maxlen <= 0) + /* maxlen must be at least 3 to hold the \. terminator! */ + if (!conn || !s || maxlen < 3) return EOF; - if (!conn || conn->sock < 0) + if (conn->sock < 0 || + conn->asyncStatus != PGASYNC_COPY_OUT || + conn->copy_is_binary) { + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("PQgetline: not doing text COPY OUT\n")); *s = '\0'; return EOF; } - /* - * Since this is a purely synchronous routine, we don't bother to - * maintain conn->inCursor; there is no need to back up. - */ - while (maxlen > 1) + while ((status = PQgetlineAsync(conn, s, maxlen-1)) == 0) { - if (conn->inStart < conn->inEnd) - { - char c = conn->inBuffer[conn->inStart++]; - - if (c == '\n') - { - result = 0; /* success exit */ - break; - } - *s++ = c; - maxlen--; - } - else + /* need to load more data */ + if (pqWait(TRUE, FALSE, conn) || + pqReadData(conn) < 0) { - /* need to load more data */ - if (pqWait(TRUE, FALSE, conn) || - pqReadData(conn) < 0) - { - result = EOF; - break; - } + *s = '\0'; + return EOF; } } - *s = '\0'; - return result; + if (status < 0) + { + /* End of copy detected; gin up old-style terminator */ + strcpy(s, "\\."); + return 0; + } + + /* Add null terminator, and strip trailing \n if present */ + if (s[status-1] == '\n') + { + s[status-1] = '\0'; + return 0; + } + else + { + s[status] = '\0'; + return 1; + } } /* - * PQgetlineAsync - gets a newline-terminated string without blocking. + * PQgetlineAsync - gets a COPY data row without blocking. * * This routine is for applications that want to do "COPY <rel> to stdout" * asynchronously, that is without blocking. Having issued the COPY command @@ -1627,10 +1721,9 @@ PQgetline(PGconn *conn, char *s, int maxlen) * and this routine until the end-of-data signal is detected. Unlike * PQgetline, this routine takes responsibility for detecting end-of-data. * - * On each call, PQgetlineAsync will return data if a complete newline- - * terminated data line is available in libpq's input buffer, or if the - * incoming data line is too long to fit in the buffer offered by the caller. - * Otherwise, no data is returned until the rest of the line arrives. + * On each call, PQgetlineAsync will return data if a complete data row + * is available in libpq's input buffer. Otherwise, no data is returned + * until the rest of the row arrives. * * If -1 is returned, the end-of-data signal has been recognized (and removed * from libpq's input buffer). The caller *must* next call PQendcopy and @@ -1640,66 +1733,73 @@ PQgetline(PGconn *conn, char *s, int maxlen) * -1 if the end-of-copy-data marker has been recognized * 0 if no data is available * >0 the number of bytes returned. - * The data returned will not extend beyond a newline character. If possible - * a whole line will be returned at one time. But if the buffer offered by - * the caller is too small to hold a line sent by the backend, then a partial - * data line will be returned. This can be detected by testing whether the - * last returned byte is '\n' or not. - * The returned string is *not* null-terminated. + * + * The data returned will not extend beyond a data-row boundary. If possible + * a whole row will be returned at one time. But if the buffer offered by + * the caller is too small to hold a row sent by the backend, then a partial + * data row will be returned. In text mode this can be detected by testing + * whether the last returned byte is '\n' or not. + * + * The returned data is *not* null-terminated. */ int PQgetlineAsync(PGconn *conn, char *buffer, int bufsize) { + char id; + int msgLength; int avail; if (!conn || conn->asyncStatus != PGASYNC_COPY_OUT) return -1; /* we are not doing a copy... */ /* - * Move data from libpq's buffer to the caller's. We want to accept - * data only in units of whole lines, not partial lines. This ensures - * that we can recognize the terminator line "\\.\n". (Otherwise, if - * it happened to cross a packet/buffer boundary, we might hand the - * first one or two characters off to the caller, which we shouldn't.) + * Recognize the next input message. To make life simpler for async + * callers, we keep returning 0 until the next message is fully available + * even if it is not Copy Data. This should keep PQendcopy from blocking. */ - conn->inCursor = conn->inStart; + if (pqGetc(&id, conn)) + return 0; + if (pqGetInt(&msgLength, 4, conn)) + return 0; + avail = conn->inEnd - conn->inCursor; + if (avail < msgLength - 4) + return 0; - avail = bufsize; - while (avail > 0 && conn->inCursor < conn->inEnd) - { - char c = conn->inBuffer[conn->inCursor++]; - - *buffer++ = c; - --avail; - if (c == '\n') - { - /* Got a complete line; mark the data removed from libpq */ - conn->inStart = conn->inCursor; - /* Is it the endmarker line? */ - if (bufsize - avail == 3 && buffer[-3] == '\\' && buffer[-2] == '.') - return -1; - /* No, return the data line to the caller */ - return bufsize - avail; - } - } + /* + * Cannot proceed unless it's a Copy Data message. Anything else means + * end of copy mode. + */ + if (id != 'd') + return -1; /* - * We don't have a complete line. We'd prefer to leave it in libpq's - * buffer until the rest arrives, but there is a special case: what if - * the line is longer than the buffer the caller is offering us? In - * that case we'd better hand over a partial line, else we'd get into - * an infinite loop. Do this in a way that ensures we can't - * misrecognize a terminator line later: leave last 3 characters in - * libpq buffer. + * Move data from libpq's buffer to the caller's. In the case where + * a prior call found the caller's buffer too small, we use + * conn->copy_already_done to remember how much of the row was already + * returned to the caller. */ - if (avail == 0 && bufsize > 3) + conn->inCursor += conn->copy_already_done; + avail = msgLength - 4 - conn->copy_already_done; + if (avail <= bufsize) { - conn->inStart = conn->inCursor - 3; - return bufsize - 3; + /* Able to consume the whole message */ + memcpy(buffer, &conn->inBuffer[conn->inCursor], avail); + /* Mark message consumed */ + conn->inStart = conn->inCursor + avail; + /* Reset state for next time */ + conn->copy_already_done = 0; + return avail; + } + else + { + /* We must return a partial message */ + memcpy(buffer, &conn->inBuffer[conn->inCursor], bufsize); + /* The message is NOT consumed from libpq's buffer */ + conn->copy_already_done += bufsize; + return bufsize; } - return 0; } /* @@ -1774,14 +1874,21 @@ PQendcopy(PGconn *conn) if (pqFlush(conn) && pqIsnonblocking(conn)) return (1); - /* non blocking connections may have to abort at this point. */ - if (pqIsnonblocking(conn) && PQisBusy(conn)) - return (1); - /* Return to active duty */ conn->asyncStatus = PGASYNC_BUSY; resetPQExpBuffer(&conn->errorMessage); + /* + * Non blocking connections may have to abort at this point. If everyone + * played the game there should be no problem, but in error scenarios + * the expected messages may not have arrived yet. (We are assuming that + * the backend's packetizing will ensure that CommandComplete arrives + * along with the CopyDone; are there corner cases where that doesn't + * happen?) + */ + if (pqIsnonblocking(conn) && PQisBusy(conn)) + return (1); + /* Wait for the completion response */ result = PQgetResult(conn); @@ -1793,26 +1900,16 @@ PQendcopy(PGconn *conn) } /* - * Trouble. The worst case is that we've lost sync with the backend - * entirely due to application screwup of the copy in/out protocol. To - * recover, reset the connection (talk about using a sledgehammer...) + * Trouble. For backwards-compatibility reasons, we issue the error + * message as if it were a notice (would be nice to get rid of this + * silliness, but too many apps probably don't handle errors from + * PQendcopy reasonably). Note that the app can still obtain the + * error status from the PGconn object. */ - PQclear(result); - if (conn->errorMessage.len > 0) DONOTICE(conn, conn->errorMessage.data); - DONOTICE(conn, libpq_gettext("lost synchronization with server, resetting connection\n")); - - /* - * Users doing non-blocking connections need to handle the reset - * themselves, they'll need to check the connection status if we - * return an error. - */ - if (pqIsnonblocking(conn)) - PQresetStart(conn); - else - PQreset(conn); + PQclear(result); return 1; } @@ -1853,6 +1950,8 @@ PQfn(PGconn *conn, bool needInput = false; ExecStatusType status = PGRES_FATAL_ERROR; char id; + int msgLength; + int avail; int i; *actual_result_len = 0; @@ -1927,11 +2026,55 @@ PQfn(PGconn *conn, * Scan the message. If we run out of data, loop around to try * again. */ - conn->inCursor = conn->inStart; needInput = true; + conn->inCursor = conn->inStart; if (pqGetc(&id, conn)) continue; + if (pqGetInt(&msgLength, 4, conn)) + continue; + + /* + * Try to validate message type/length here. A length less than 4 + * is definitely broken. Large lengths should only be believed + * for a few message types. + */ + if (msgLength < 4) + { + handleSyncLoss(conn, id, msgLength); + break; + } + if (msgLength > 30000 && + !(id == 'T' || id == 'D' || id == 'B' || id == 'd' || id == 'V')) + { + handleSyncLoss(conn, id, msgLength); + break; + } + + /* + * Can't process if message body isn't all here yet. + */ + msgLength -= 4; + avail = conn->inEnd - conn->inCursor; + if (avail < msgLength) + { + /* + * Before looping, enlarge the input buffer if needed to hold + * the whole message. See notes in parseInput. + */ + if (pqCheckInBufferSpace(conn->inCursor + msgLength, conn)) + { + /* + * XXX add some better recovery code... plan is to skip + * over the message using its length, then report an error. + * For the moment, just treat this like loss of sync (which + * indeed it might be!) + */ + handleSyncLoss(conn, id, msgLength); + break; + } + continue; + } /* * We should see V or E response to the command, but might get N @@ -1975,7 +2118,7 @@ PQfn(PGconn *conn, libpq_gettext("protocol error: id=0x%x\n"), id); saveErrorResult(conn); - conn->inStart = conn->inCursor; + conn->inStart += 5 + msgLength; return prepareAsyncResult(conn); } break; @@ -1998,7 +2141,8 @@ PQfn(PGconn *conn, break; case 'Z': /* backend is ready for new query */ /* consume the message and exit */ - conn->inStart = conn->inCursor; + conn->inStart += 5 + msgLength; + /* XXX expect additional fields here */ /* if we saved a result object (probably an error), use it */ if (conn->result) return prepareAsyncResult(conn); @@ -2009,11 +2153,13 @@ PQfn(PGconn *conn, libpq_gettext("protocol error: id=0x%x\n"), id); saveErrorResult(conn); - conn->inStart = conn->inCursor; + /* trust the specified message length as what to skip */ + conn->inStart += 5 + msgLength; return prepareAsyncResult(conn); } /* Completed this message, keep going */ - conn->inStart = conn->inCursor; + /* trust the specified message length as what to skip */ + conn->inStart += 5 + msgLength; needInput = false; } diff --git a/src/interfaces/libpq/fe-misc.c b/src/interfaces/libpq/fe-misc.c index dfc46fdf598..76de4a87086 100644 --- a/src/interfaces/libpq/fe-misc.c +++ b/src/interfaces/libpq/fe-misc.c @@ -23,7 +23,7 @@ * Portions Copyright (c) 1994, Regents of the University of California * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/interfaces/libpq/fe-misc.c,v 1.89 2003/04/19 00:02:30 tgl Exp $ + * $Header: /cvsroot/pgsql/src/interfaces/libpq/fe-misc.c,v 1.90 2003/04/22 00:08:07 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -277,12 +277,12 @@ pqPutInt(int value, size_t bytes, PGconn *conn) /* * Make sure conn's output buffer can hold bytes_needed bytes (caller must - * include existing outCount into the value!) + * include already-stored data into the value!) * - * Returns 0 on success, EOF on error + * Returns 0 on success, EOF if failed to enlarge buffer */ static int -checkOutBufferSpace(int bytes_needed, PGconn *conn) +pqCheckOutBufferSpace(int bytes_needed, PGconn *conn) { int newsize = conn->outBufSize; char *newbuf; @@ -336,6 +336,66 @@ checkOutBufferSpace(int bytes_needed, PGconn *conn) } /* + * Make sure conn's input buffer can hold bytes_needed bytes (caller must + * include already-stored data into the value!) + * + * Returns 0 on success, EOF if failed to enlarge buffer + */ +int +pqCheckInBufferSpace(int bytes_needed, PGconn *conn) +{ + int newsize = conn->inBufSize; + char *newbuf; + + if (bytes_needed <= newsize) + return 0; + /* + * If we need to enlarge the buffer, we first try to double it in size; + * if that doesn't work, enlarge in multiples of 8K. This avoids + * thrashing the malloc pool by repeated small enlargements. + * + * Note: tests for newsize > 0 are to catch integer overflow. + */ + do { + newsize *= 2; + } while (bytes_needed > newsize && newsize > 0); + + if (bytes_needed <= newsize) + { + newbuf = realloc(conn->inBuffer, newsize); + if (newbuf) + { + /* realloc succeeded */ + conn->inBuffer = newbuf; + conn->inBufSize = newsize; + return 0; + } + } + + newsize = conn->inBufSize; + do { + newsize += 8192; + } while (bytes_needed > newsize && newsize > 0); + + if (bytes_needed <= newsize) + { + newbuf = realloc(conn->inBuffer, newsize); + if (newbuf) + { + /* realloc succeeded */ + conn->inBuffer = newbuf; + conn->inBufSize = newsize; + return 0; + } + } + + /* realloc failed. Probably out of memory */ + printfPQExpBuffer(&conn->errorMessage, + "cannot allocate memory for input buffer\n"); + return EOF; +} + +/* * pqPutMsgStart: begin construction of a message to the server * * msg_type is the message type byte, or 0 for a message without type byte @@ -364,7 +424,7 @@ pqPutMsgStart(char msg_type, PGconn *conn) else lenPos = conn->outCount; /* make sure there is room for it */ - if (checkOutBufferSpace(lenPos + 4, conn)) + if (pqCheckOutBufferSpace(lenPos + 4, conn)) return EOF; /* okay, save the message type byte if any */ if (msg_type) @@ -390,7 +450,7 @@ static int pqPutMsgBytes(const void *buf, size_t len, PGconn *conn) { /* make sure there is room for it */ - if (checkOutBufferSpace(conn->outMsgEnd + len, conn)) + if (pqCheckOutBufferSpace(conn->outMsgEnd + len, conn)) return EOF; /* okay, save the data */ memcpy(conn->outBuffer + conn->outMsgEnd, buf, len); @@ -486,13 +546,13 @@ pqReadData(PGconn *conn) */ if (conn->inBufSize - conn->inEnd < 8192) { - int newSize = conn->inBufSize * 2; - char *newBuf = (char *) realloc(conn->inBuffer, newSize); - - if (newBuf) + if (pqCheckInBufferSpace(conn->inEnd + 8192, conn)) { - conn->inBuffer = newBuf; - conn->inBufSize = newSize; + /* + * We don't insist that the enlarge worked, but we need some room + */ + if (conn->inBufSize - conn->inEnd < 100) + return -1; /* errorMessage already set */ } } diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h index 8671922547d..35e3208eb0e 100644 --- a/src/interfaces/libpq/libpq-int.h +++ b/src/interfaces/libpq/libpq-int.h @@ -12,7 +12,7 @@ * Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $Id: libpq-int.h,v 1.62 2003/04/19 00:02:30 tgl Exp $ + * $Id: libpq-int.h,v 1.63 2003/04/22 00:08:07 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -56,7 +56,7 @@ typedef int ssize_t; /* ssize_t doesn't exist in VC (atleast * pqcomm.h describe what the backend knows, not what libpq knows. */ -#define PG_PROTOCOL_LIBPQ PG_PROTOCOL(3,101) /* XXX temporary value */ +#define PG_PROTOCOL_LIBPQ PG_PROTOCOL(3,102) /* XXX temporary value */ /* * POSTGRES backend dependent Constants. @@ -216,7 +216,8 @@ struct pg_conn * is listening on; if NULL, uses a * default constructed from pgport */ char *pgtty; /* tty on which the backend messages is - * displayed (NOT ACTUALLY USED???) */ + * displayed (OBSOLETE, NOT USED) */ + char *connect_timeout; /* connection timeout (numeric string) */ char *pgoptions; /* options to start the backend with */ char *dbName; /* database name */ char *pguser; /* Postgres username and password, if any */ @@ -232,6 +233,10 @@ struct pg_conn /* Status indicators */ ConnStatusType status; PGAsyncStatusType asyncStatus; + char copy_is_binary; /* 1 = copy binary, 0 = copy text */ + int copy_already_done; /* # bytes already returned in COPY OUT */ + int nonblocking; /* whether this connection is using a + * blocking socket to the backend or not */ Dllist *notifyList; /* Notify msgs not yet handed to * application */ @@ -246,6 +251,7 @@ struct pg_conn int be_key; /* key of backend --- needed for cancels */ char md5Salt[4]; /* password salt received from backend */ char cryptSalt[2]; /* password salt received from backend */ + int client_encoding; /* encoding id */ PGlobjfuncs *lobjfuncs; /* private state for large-object access * fns */ @@ -258,9 +264,6 @@ struct pg_conn int inEnd; /* offset to first position after avail * data */ - int nonblocking; /* whether this connection is using a - * blocking socket to the backend or not */ - /* Buffer for data not yet sent to backend */ char *outBuffer; /* currently allocated buffer */ int outBufSize; /* allocated size of buffer */ @@ -291,10 +294,6 @@ struct pg_conn /* Buffer for receiving various parts of messages */ PQExpBufferData workBuffer; /* expansible string */ - - int client_encoding; /* encoding id */ - - char *connect_timeout; }; /* String descriptions of the ExecStatusTypes. @@ -330,6 +329,7 @@ extern void pqClearAsyncResult(PGconn *conn); * for Get, EOF merely means the buffer is exhausted, not that there is * necessarily any error. */ +extern int pqCheckInBufferSpace(int bytes_needed, PGconn *conn); extern int pqGetc(char *result, PGconn *conn); extern int pqPutc(char c, PGconn *conn); extern int pqGets(PQExpBuffer buf, PGconn *conn); |