From bd8d4417757b1f3edd9ef36897cf47fe96b6e37a Mon Sep 17 00:00:00 2001 From: Tom Lane <tgl@sss.pgh.pa.us> Date: Sat, 19 Apr 2003 00:02:30 +0000 Subject: [PATCH] Second round of FE/BE protocol changes. Frontend->backend messages now have length counts, and COPY IN data is packetized into messages. --- doc/src/sgml/libpq.sgml | 9 +- doc/src/sgml/protocol.sgml | 174 ++++----- src/backend/commands/copy.c | 560 ++++++++++++++++++---------- src/backend/lib/stringinfo.c | 86 +++-- src/backend/libpq/auth.c | 113 ++++-- src/backend/libpq/be-secure.c | 9 +- src/backend/libpq/pqcomm.c | 140 ++++--- src/backend/libpq/pqformat.c | 150 +++++--- src/backend/postmaster/postmaster.c | 26 +- src/backend/tcop/dest.c | 33 +- src/backend/tcop/fastpath.c | 206 +++++----- src/backend/tcop/postgres.c | 166 +++++---- src/include/lib/stringinfo.h | 12 +- src/include/libpq/libpq.h | 16 +- src/include/libpq/pqcomm.h | 4 +- src/include/libpq/pqformat.h | 12 +- src/include/tcop/dest.h | 4 +- src/include/tcop/fastpath.h | 6 +- src/interfaces/libpq/fe-auth.c | 4 +- src/interfaces/libpq/fe-connect.c | 40 +- src/interfaces/libpq/fe-exec.c | 129 +++---- src/interfaces/libpq/fe-misc.c | 374 +++++++++++-------- src/interfaces/libpq/libpq-fe.h | 3 +- src/interfaces/libpq/libpq-int.h | 11 +- 24 files changed, 1321 insertions(+), 966 deletions(-) diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml index a1f8d6b5f6b..7a7bb48ff39 100644 --- a/doc/src/sgml/libpq.sgml +++ b/doc/src/sgml/libpq.sgml @@ -1,5 +1,5 @@ <!-- -$Header: /cvsroot/pgsql/doc/src/sgml/libpq.sgml,v 1.118 2003/04/17 22:26:00 tgl Exp $ +$Header: /cvsroot/pgsql/doc/src/sgml/libpq.sgml,v 1.119 2003/04/19 00:02:29 tgl Exp $ --> <chapter id="libpq"> @@ -1749,9 +1749,10 @@ state will never end. <term><function>PQflush</function></term> <listitem> <para> -Attempts to flush any data queued to the server, -returns 0 if successful (or if the send queue is empty) or <symbol>EOF</symbol> if it failed for -some reason. +Attempts to flush any data queued to the server. +Returns 0 if successful (or if the send queue is empty), -1 if it failed for +some reason, or 1 if it was unable to send all the data in the send queue yet +(this case can only occur if the connection is nonblocking). <synopsis> int PQflush(PGconn *conn); </synopsis> diff --git a/doc/src/sgml/protocol.sgml b/doc/src/sgml/protocol.sgml index bb25eb74b14..529baa1f31b 100644 --- a/doc/src/sgml/protocol.sgml +++ b/doc/src/sgml/protocol.sgml @@ -1,4 +1,4 @@ -<!-- $Header: /cvsroot/pgsql/doc/src/sgml/protocol.sgml,v 1.27 2003/04/16 20:53:38 tgl Exp $ --> +<!-- $Header: /cvsroot/pgsql/doc/src/sgml/protocol.sgml,v 1.28 2003/04/19 00:02:29 tgl Exp $ --> <chapter id="protocol"> <title>Frontend/Backend Protocol</title> @@ -819,8 +819,9 @@ Copy-in mode (data transfer to the server) is initiated when the backend executes a <command>COPY FROM STDIN</> SQL statement. The backend sends a CopyInResponse message to the frontend. The frontend should - then send zero or more CopyDataRow messages, one per row to be loaded. - (For <command>COPY BINARY</>, send CopyBinaryRow messages instead.) + then send zero or more CopyData messages, forming a stream of input + data. (The message boundaries are not required to have anything to do + with row boundaries, although that is often a reasonable choice.) The frontend can terminate the copy-in mode by sending either a CopyDone message (allowing successful termination) or a CopyFail message (which will cause the <command>COPY</> SQL statement to fail with an @@ -833,37 +834,33 @@ <para> In the event of a backend-detected error during copy-in mode (including receipt of a CopyFail message, or indeed any frontend message other than - CopyDataRow, CopyBinaryRow, or CopyDone), the backend will issue an - ErrorResponse + CopyData or CopyDone), the backend will issue an ErrorResponse message. If the <command>COPY</> command was issued via an extended-query message, the backend will now discard frontend messages until a Sync message is received, then it will issue ReadyForQuery and return to normal processing. If the <command>COPY</> command was issued in a simple Query message, the rest of that message is discarded and ReadyForQuery - is issued. In either case, any subsequent CopyDataRow, CopyBinaryRow, - CopyDone, or CopyFail messages issued by the frontend will simply be - dropped. + is issued. In either case, any subsequent CopyData, CopyDone, or CopyFail + messages issued by the frontend will simply be dropped. </para> <para> Copy-out mode (data transfer from the server) is initiated when the backend executes a <command>COPY TO STDOUT</> SQL statement. The backend sends a CopyOutResponse message to the frontend, followed by - zero or more CopyDataRow messages, one per row, followed by CopyDone. - (For <command>COPY BINARY</>, CopyBinaryRow messages are sent instead.) + zero or more CopyData messages (always one per row), followed by CopyDone. The backend then reverts to the command-processing mode it was in before the <command>COPY</> started, and sends CommandComplete. - The frontend cannot abort - the transfer (short of closing the connection), but it can discard - unwanted CopyDataRow, CopyBinaryRow, and CopyDone messages. + The frontend cannot abort the transfer (short of closing the connection), + but it can discard unwanted CopyData and CopyDone messages. </para> <para> In the event of a backend-detected error during copy-out mode, the backend will issue an ErrorResponse message and revert to normal processing. The frontend should treat receipt of ErrorResponse (or - indeed any message type other than CopyDataRow, CopyBinaryRow, or - CopyDone) as terminating the copy-out mode. + indeed any message type other than CopyData or CopyDone) as terminating + the copy-out mode. </para> </sect2> @@ -1157,7 +1154,9 @@ indicate that it may be sent by a frontend (F), a backend (B), or both (F & B). Notice that although each message includes a byte count at the beginning, the message format is defined so that the message end can be found without -reference to the byte count. This aids validity checking. +reference to the byte count. This aids validity checking. (The CopyData +message is an exception, because it forms part of a data stream; the contents +may not be interpretable on their own.) </para> <VariableList> @@ -2002,83 +2001,7 @@ CommandComplete (B) <VarListEntry> <Term> -CopyBinaryRow (F & B) -</Term> -<ListItem> -<Para> -<VariableList> -<VarListEntry> -<Term> - Byte1('b') -</Term> -<ListItem> -<Para> - Identifies the message as binary COPY data. - Note that the message body format is identical to the - <command>COPY BINARY</> file-format representation for - a single row of data. -</Para> -</ListItem> -</VarListEntry> -<VarListEntry> -<Term> - Int32 -</Term> -<ListItem> -<Para> - Length of message contents in bytes, including self. -</Para> -</ListItem> -</VarListEntry> -<VarListEntry> -<Term> - Int16 -</Term> -<ListItem> -<Para> - Specifies the number of fields in the row (can be zero). -</Para> -</ListItem> -</VarListEntry> -</VariableList> - Then, for each field, there is the following: -<VariableList> -<VarListEntry> -<Term> - Int16 -</Term> -<ListItem> -<Para> - Zero if the field is null, otherwise the <varname>typlen</> - for the field datatype. -</Para> -</ListItem> -</VarListEntry> -<VarListEntry> -<Term> - Byte<Replaceable>n</Replaceable> -</Term> -<ListItem> -<Para> - The value of the field itself in binary format. - Omitted if the field is null. - <Replaceable>n</Replaceable> is the <varname>typlen</> - value if <varname>typlen</> is positive. If - <varname>typlen</> is -1 then the field value begins with - its own length as an Int32 (the length includes itself). -</Para> -</ListItem> -</VarListEntry> -</VariableList> - -</Para> -</ListItem> -</VarListEntry> - - -<VarListEntry> -<Term> -CopyDataRow (F & B) +CopyData (F & B) </Term> <ListItem> <Para> @@ -2089,7 +2012,7 @@ CopyDataRow (F & B) </Term> <ListItem> <Para> - Identifies the message as textual COPY data. + Identifies the message as COPY data. </Para> </ListItem> </VarListEntry> @@ -2105,12 +2028,14 @@ CopyDataRow (F & B) </VarListEntry> <VarListEntry> <Term> - String + Byte<Replaceable>n</Replaceable> </Term> <ListItem> <Para> - The textual representation of a single row of table data. - It should end with a newline. + Data that forms part of a COPY datastream. Messages sent + from the backend will always correspond to single data rows, + but messages sent by frontends may divide the datastream + arbitrarily. </Para> </ListItem> </VarListEntry> @@ -2236,8 +2161,7 @@ CopyInResponse (B) </Term> <ListItem> <Para> - 0 for textual copy (CopyDataRow is expected), 1 for - binary copy (CopyBinaryRow is expected). + 0 for textual copy, 1 for binary copy. </Para> </ListItem> </VarListEntry> @@ -2283,8 +2207,7 @@ CopyOutResponse (B) </Term> <ListItem> <Para> - 0 for textual copy (CopyDataRow will follow), 1 for - binary copy (CopyBinaryRow will follow). + 0 for textual copy, 1 for binary copy. </Para> </ListItem> </VarListEntry> @@ -3606,8 +3529,9 @@ StartupMessage (F) <ListItem> <Para> The protocol version number. The most significant 16 bits are - the major version number (3 for the format described here). - The least 16 significant bits are the minor version number. + the major version number (3 or more for the format described + here). + The least significant 16 bits are the minor version number. </Para> </ListItem> </VarListEntry> @@ -3654,17 +3578,18 @@ StartupMessage (F) <ListItem> <Para> Command-line arguments for the backend. (This is - deprecated in favor of setting individual GUC + deprecated in favor of setting individual run-time parameters.) </Para> </ListItem> </VarListEntry> </VariableList> - In addition to the above, any GUC parameter that can be + In addition to the above, any run-time parameter that can be set at backend start time may be listed. Such settings will be applied during backend start (after parsing the - command-line options if any). + command-line options if any). The values will act as + session defaults. </Para> </ListItem> </VarListEntry> @@ -3913,4 +3838,41 @@ not line breaks. </sect1> +<Sect1 id="protocol-changes"> +<Title>Summary of Changes since Protocol 2.0</Title> + +<para> +This section provides a quick checklist of changes, for the benefit of +developers trying to update existing client libraries to protocol 3.0. +</para> + +<para> +The initial startup packet uses a flexible list-of-strings format +instead of a fixed format. Notice that session default values for run-time +parameters can now be specified directly in the startup packet. (Actually, +you could do that before using the <literal>options</> field, but given the +limited width of <literal>options</> and the lack of any way to quote +whitespace in the values, it wasn't a very safe technique.) +</para> + +<para> +All messages now have a length count immediately following the message type +byte (except for startup packets, which have no type byte). Also note that +PasswordMessage now has a type byte. +</para> + +<para> +COPY data is now encapsulated into CopyData and CopyDone messages. There +is a well-defined way to recover from errors during COPY. +</para> + +<note> +<para> +Additional changes will be documented as they are implemented. +</para> +</note> + +</sect1> + + </Chapter> diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c index 0f20bfb2aea..fd85f48b592 100644 --- a/src/backend/commands/copy.c +++ b/src/backend/commands/copy.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/commands/copy.c,v 1.191 2003/04/04 20:42:11 momjian Exp $ + * $Header: /cvsroot/pgsql/src/backend/commands/copy.c,v 1.192 2003/04/19 00:02:29 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -31,6 +31,7 @@ #include "commands/trigger.h" #include "executor/executor.h" #include "libpq/libpq.h" +#include "libpq/pqformat.h" #include "mb/pg_wchar.h" #include "miscadmin.h" #include "nodes/makefuncs.h" @@ -49,6 +50,17 @@ #define ISOCTAL(c) (((c) >= '0') && ((c) <= '7')) #define OCTVALUE(c) ((c) - '0') +/* + * Represents the different source/dest cases we need to worry about at + * the bottom level + */ +typedef enum CopyDest +{ + COPY_FILE, /* to/from file */ + COPY_OLD_FE, /* to/from frontend (old protocol) */ + COPY_NEW_FE /* to/from frontend (new protocol) */ +} CopyDest; + /* * Represents the type of data returned by CopyReadAttribute() */ @@ -61,13 +73,13 @@ typedef enum CopyReadResult /* non-export function prototypes */ static void CopyTo(Relation rel, List *attnumlist, bool binary, bool oids, - FILE *fp, char *delim, char *null_print); + char *delim, char *null_print); static void CopyFrom(Relation rel, List *attnumlist, bool binary, bool oids, - FILE *fp, char *delim, char *null_print); + char *delim, char *null_print); static Oid GetInputFunction(Oid type); static Oid GetTypeElement(Oid type); -static char *CopyReadAttribute(FILE *fp, const char *delim, CopyReadResult *result); -static void CopyAttributeOut(FILE *fp, char *string, char *delim); +static char *CopyReadAttribute(const char *delim, CopyReadResult *result); +static void CopyAttributeOut(char *string, char *delim); static List *CopyGetAttnums(Relation rel, List *attnamelist); static const char BinarySignature[12] = "PGBCOPY\n\377\r\n\0"; @@ -77,7 +89,11 @@ static const char BinarySignature[12] = "PGBCOPY\n\377\r\n\0"; * never been reentrant... */ int copy_lineno = 0; /* exported for use by elog() -- dz */ -static bool fe_eof; + +static CopyDest copy_dest; +static FILE *copy_file; /* if copy_dest == COPY_FILE */ +static StringInfo copy_msgbuf; /* if copy_dest == COPY_NEW_FE */ +static bool fe_eof; /* true if detected end of copy data */ /* * These static variables are used to avoid incurring overhead for each @@ -96,98 +112,229 @@ static int server_encoding; /* * Internal communications functions */ -static void CopySendData(void *databuf, int datasize, FILE *fp); -static void CopySendString(const char *str, FILE *fp); -static void CopySendChar(char c, FILE *fp); -static void CopyGetData(void *databuf, int datasize, FILE *fp); -static int CopyGetChar(FILE *fp); -static int CopyGetEof(FILE *fp); -static int CopyPeekChar(FILE *fp); -static void CopyDonePeek(FILE *fp, int c, bool pickup); +static void SendCopyBegin(bool binary); +static void ReceiveCopyBegin(bool binary); +static void SendCopyEnd(bool binary); +static void CopySendData(void *databuf, int datasize); +static void CopySendString(const char *str); +static void CopySendChar(char c); +static void CopyGetData(void *databuf, int datasize); +static int CopyGetChar(void); +#define CopyGetEof() (fe_eof) +static int CopyPeekChar(void); +static void CopyDonePeek(int c, bool pickup); /* - * CopySendData sends output data either to the file - * specified by fp or, if fp is NULL, using the standard - * backend->frontend functions - * - * CopySendString does the same for null-terminated strings - * CopySendChar does the same for single characters - * - * NB: no data conversion is applied by these functions + * Send copy start/stop messages for frontend copies. These have changed + * in past protocol redesigns. */ static void -CopySendData(void *databuf, int datasize, FILE *fp) +SendCopyBegin(bool binary) { - if (!fp) + if (PG_PROTOCOL_MAJOR(FrontendProtocol) >= 3) + { + pq_putbytes("H", 1); /* new way */ + /* XXX grottiness needed for old protocol */ + pq_startcopyout(); + copy_dest = COPY_NEW_FE; + } + else if (PG_PROTOCOL_MAJOR(FrontendProtocol) >= 2) { - if (pq_putbytes((char *) databuf, datasize)) - fe_eof = true; + pq_putbytes("H", 1); /* old way */ + /* grottiness needed for old protocol */ + pq_startcopyout(); + copy_dest = COPY_OLD_FE; } else { - fwrite(databuf, datasize, 1, fp); - if (ferror(fp)) - elog(ERROR, "CopySendData: %m"); + pq_putbytes("B", 1); /* very old way */ + /* grottiness needed for old protocol */ + pq_startcopyout(); + copy_dest = COPY_OLD_FE; } } static void -CopySendString(const char *str, FILE *fp) +ReceiveCopyBegin(bool binary) { - CopySendData((void *) str, strlen(str), fp); + if (PG_PROTOCOL_MAJOR(FrontendProtocol) >= 3) + { + pq_putbytes("G", 1); /* new way */ + copy_dest = COPY_NEW_FE; + copy_msgbuf = makeStringInfo(); + } + else if (PG_PROTOCOL_MAJOR(FrontendProtocol) >= 2) + { + pq_putbytes("G", 1); /* old way */ + copy_dest = COPY_OLD_FE; + } + else + { + pq_putbytes("D", 1); /* very old way */ + copy_dest = COPY_OLD_FE; + } + /* We *must* flush here to ensure FE knows it can send. */ + pq_flush(); } static void -CopySendChar(char c, FILE *fp) +SendCopyEnd(bool binary) { - CopySendData(&c, 1, fp); + if (!binary) + CopySendData("\\.\n", 3); + pq_endcopyout(false); } /* - * CopyGetData reads output data either from the file - * specified by fp or, if fp is NULL, using the standard - * backend->frontend functions - * - * CopyGetChar does the same for single characters - * CopyGetEof checks if it's EOF on the input (or, check for EOF result - * from CopyGetChar) + * CopySendData sends output data to the destination (file or frontend) + * CopySendString does the same for null-terminated strings + * CopySendChar does the same for single characters * * NB: no data conversion is applied by these functions */ static void -CopyGetData(void *databuf, int datasize, FILE *fp) +CopySendData(void *databuf, int datasize) { - if (!fp) + switch (copy_dest) { - if (pq_getbytes((char *) databuf, datasize)) - fe_eof = true; + case COPY_FILE: + fwrite(databuf, datasize, 1, copy_file); + if (ferror(copy_file)) + elog(ERROR, "CopySendData: %m"); + break; + case COPY_OLD_FE: + if (pq_putbytes((char *) databuf, datasize)) + fe_eof = true; + break; + case COPY_NEW_FE: + /* XXX fix later */ + if (pq_putbytes((char *) databuf, datasize)) + fe_eof = true; + break; } - else - fread(databuf, datasize, 1, fp); } -static int -CopyGetChar(FILE *fp) +static void +CopySendString(const char *str) +{ + CopySendData((void *) str, strlen(str)); +} + +static void +CopySendChar(char c) +{ + CopySendData(&c, 1); +} + +/* + * CopyGetData reads data from the source (file or frontend) + * CopyGetChar does the same for single characters + * + * CopyGetEof checks if EOF was detected by previous Get operation. + * + * Note: when copying from the frontend, we expect a proper EOF mark per + * protocol; if the frontend simply drops the connection, we raise error. + * It seems unwise to allow the COPY IN to complete normally in that case. + * + * NB: no data conversion is applied by these functions + */ +static void +CopyGetData(void *databuf, int datasize) { - if (!fp) + switch (copy_dest) { - int ch = pq_getbyte(); + case COPY_FILE: + fread(databuf, datasize, 1, copy_file); + if (feof(copy_file)) + fe_eof = true; + break; + case COPY_OLD_FE: + if (pq_getbytes((char *) databuf, datasize)) + { + /* Only a \. terminator is legal EOF in old protocol */ + elog(ERROR, "unexpected EOF on client connection"); + } + break; + case COPY_NEW_FE: + while (datasize > 0 && !fe_eof) + { + int avail; - if (ch == EOF) - fe_eof = true; - return ch; + while (copy_msgbuf->cursor >= copy_msgbuf->len) + { + /* Try to receive another message */ + int mtype; + + mtype = pq_getbyte(); + if (mtype == EOF) + elog(ERROR, "unexpected EOF on client connection"); + if (pq_getmessage(copy_msgbuf, 0)) + elog(ERROR, "unexpected EOF on client connection"); + switch (mtype) + { + case 'd': /* CopyData */ + break; + case 'c': /* CopyDone */ + /* COPY IN correctly terminated by frontend */ + fe_eof = true; + return; + case 'f': /* CopyFail */ + elog(ERROR, "COPY IN failed: %s", + pq_getmsgstring(copy_msgbuf)); + break; + default: + elog(ERROR, "unexpected message type %c during COPY IN", + mtype); + break; + } + } + avail = copy_msgbuf->len - copy_msgbuf->cursor; + if (avail > datasize) + avail = datasize; + pq_copymsgbytes(copy_msgbuf, databuf, avail); + databuf = (void *) ((char *) databuf + avail); + datasize =- avail; + } + break; } - else - return getc(fp); } static int -CopyGetEof(FILE *fp) +CopyGetChar(void) { - if (!fp) - return fe_eof; - else - return feof(fp); + int ch; + + switch (copy_dest) + { + case COPY_FILE: + ch = getc(copy_file); + break; + case COPY_OLD_FE: + ch = pq_getbyte(); + if (ch == EOF) + { + /* Only a \. terminator is legal EOF in old protocol */ + elog(ERROR, "unexpected EOF on client connection"); + } + break; + case COPY_NEW_FE: + { + unsigned char cc; + + CopyGetData(&cc, 1); + if (fe_eof) + ch = EOF; + else + ch = cc; + break; + } + default: + ch = EOF; + break; + } + if (ch == EOF) + fe_eof = true; + return ch; } /* @@ -200,40 +347,74 @@ CopyGetEof(FILE *fp) * (if pickup is true) or leave it on the stream (if pickup is false). */ static int -CopyPeekChar(FILE *fp) +CopyPeekChar(void) { - if (!fp) + int ch; + + switch (copy_dest) { - int ch = pq_peekbyte(); + case COPY_FILE: + ch = getc(copy_file); + break; + case COPY_OLD_FE: + ch = pq_peekbyte(); + if (ch == EOF) + { + /* Only a \. terminator is legal EOF in old protocol */ + elog(ERROR, "unexpected EOF on client connection"); + } + break; + case COPY_NEW_FE: + { + unsigned char cc; - if (ch == EOF) - fe_eof = true; - return ch; + CopyGetData(&cc, 1); + if (fe_eof) + ch = EOF; + else + ch = cc; + break; + } + default: + ch = EOF; + break; } - else - return getc(fp); + if (ch == EOF) + fe_eof = true; + return ch; } static void -CopyDonePeek(FILE *fp, int c, bool pickup) +CopyDonePeek(int c, bool pickup) { - if (!fp) - { - if (pickup) - { - /* We want to pick it up */ - (void) pq_getbyte(); - } - /* If we didn't want to pick it up, just leave it where it sits */ - } - else + if (fe_eof) + return; /* can't unget an EOF */ + switch (copy_dest) { - if (!pickup) - { - /* We don't want to pick it up - so put it back in there */ - ungetc(c, fp); - } - /* If we wanted to pick it up, it's already done */ + case COPY_FILE: + if (!pickup) + { + /* We don't want to pick it up - so put it back in there */ + ungetc(c, copy_file); + } + /* If we wanted to pick it up, it's already done */ + break; + case COPY_OLD_FE: + if (pickup) + { + /* We want to pick it up */ + (void) pq_getbyte(); + } + /* If we didn't want to pick it up, just leave it where it sits */ + break; + case COPY_NEW_FE: + if (!pickup) + { + /* We don't want to pick it up - so put it back in there */ + copy_msgbuf->cursor--; + } + /* If we wanted to pick it up, it's already done */ + break; } } @@ -287,7 +468,6 @@ DoCopy(const CopyStmt *stmt) bool oids = false; char *delim = NULL; char *null_print = NULL; - FILE *fp; Relation rel; AclMode required_access = (is_from ? ACL_INSERT : ACL_SELECT); AclResult aclresult; @@ -397,6 +577,11 @@ DoCopy(const CopyStmt *stmt) client_encoding = pg_get_client_encoding(); server_encoding = GetDatabaseEncoding(); + copy_dest = COPY_FILE; /* default */ + copy_file = NULL; + copy_msgbuf = NULL; + fe_eof = false; + if (is_from) { /* copy from file to database */ if (rel->rd_rel->relkind != RELKIND_RELATION) @@ -414,33 +599,30 @@ DoCopy(const CopyStmt *stmt) if (pipe) { if (IsUnderPostmaster) - { - ReceiveCopyBegin(); - fp = NULL; - } + ReceiveCopyBegin(binary); else - fp = stdin; + copy_file = stdin; } else { struct stat st; - fp = AllocateFile(filename, PG_BINARY_R); + copy_file = AllocateFile(filename, PG_BINARY_R); - if (fp == NULL) + if (copy_file == NULL) elog(ERROR, "COPY command, running in backend with " "effective uid %d, could not open file '%s' for " "reading. Errno = %s (%d).", (int) geteuid(), filename, strerror(errno), errno); - fstat(fileno(fp), &st); + fstat(fileno(copy_file), &st); if (S_ISDIR(st.st_mode)) { - FreeFile(fp); + FreeFile(copy_file); elog(ERROR, "COPY: %s is a directory", filename); } } - CopyFrom(rel, attnumlist, binary, oids, fp, delim, null_print); + CopyFrom(rel, attnumlist, binary, oids, delim, null_print); } else { /* copy from database to file */ @@ -459,13 +641,9 @@ DoCopy(const CopyStmt *stmt) if (pipe) { if (IsUnderPostmaster) - { - SendCopyBegin(); - pq_startcopyout(); - fp = NULL; - } + SendCopyBegin(binary); else - fp = stdout; + copy_file = stdout; } else { @@ -481,33 +659,28 @@ DoCopy(const CopyStmt *stmt) " COPY command"); oumask = umask((mode_t) 022); - fp = AllocateFile(filename, PG_BINARY_W); + copy_file = AllocateFile(filename, PG_BINARY_W); umask(oumask); - if (fp == NULL) + if (copy_file == NULL) elog(ERROR, "COPY command, running in backend with " "effective uid %d, could not open file '%s' for " "writing. Errno = %s (%d).", (int) geteuid(), filename, strerror(errno), errno); - fstat(fileno(fp), &st); + fstat(fileno(copy_file), &st); if (S_ISDIR(st.st_mode)) { - FreeFile(fp); + FreeFile(copy_file); elog(ERROR, "COPY: %s is a directory", filename); } } - CopyTo(rel, attnumlist, binary, oids, fp, delim, null_print); + CopyTo(rel, attnumlist, binary, oids, delim, null_print); } if (!pipe) - FreeFile(fp); - else if (!is_from) - { - if (!binary) - CopySendData("\\.\n", 3, fp); - if (IsUnderPostmaster) - pq_endcopyout(false); - } + FreeFile(copy_file); + else if (IsUnderPostmaster && !is_from) + SendCopyEnd(binary); pfree(attribute_buf.data); /* @@ -525,7 +698,7 @@ DoCopy(const CopyStmt *stmt) */ static void CopyTo(Relation rel, List *attnumlist, bool binary, bool oids, - FILE *fp, char *delim, char *null_print) + char *delim, char *null_print) { HeapTuple tuple; TupleDesc tupDesc; @@ -589,18 +762,18 @@ CopyTo(Relation rel, List *attnumlist, bool binary, bool oids, int32 tmp; /* Signature */ - CopySendData((char *) BinarySignature, 12, fp); + CopySendData((char *) BinarySignature, 12); /* Integer layout field */ tmp = 0x01020304; - CopySendData(&tmp, sizeof(int32), fp); + CopySendData(&tmp, sizeof(int32)); /* Flags field */ tmp = 0; if (oids) tmp |= (1 << 16); - CopySendData(&tmp, sizeof(int32), fp); + CopySendData(&tmp, sizeof(int32)); /* No header extension */ tmp = 0; - CopySendData(&tmp, sizeof(int32), fp); + CopySendData(&tmp, sizeof(int32)); } mySnapshot = CopyQuerySnapshot(); @@ -621,15 +794,15 @@ CopyTo(Relation rel, List *attnumlist, bool binary, bool oids, /* Binary per-tuple header */ int16 fld_count = attr_count; - CopySendData(&fld_count, sizeof(int16), fp); + CopySendData(&fld_count, sizeof(int16)); /* Send OID if wanted --- note fld_count doesn't include it */ if (oids) { Oid oid = HeapTupleGetOid(tuple); fld_size = sizeof(Oid); - CopySendData(&fld_size, sizeof(int16), fp); - CopySendData(&oid, sizeof(Oid), fp); + CopySendData(&fld_size, sizeof(int16)); + CopySendData(&oid, sizeof(Oid)); } } else @@ -639,7 +812,7 @@ CopyTo(Relation rel, List *attnumlist, bool binary, bool oids, { string = DatumGetCString(DirectFunctionCall1(oidout, ObjectIdGetDatum(HeapTupleGetOid(tuple)))); - CopySendString(string, fp); + CopySendString(string); need_delim = true; } } @@ -655,7 +828,7 @@ CopyTo(Relation rel, List *attnumlist, bool binary, bool oids, if (!binary) { if (need_delim) - CopySendChar(delim[0], fp); + CopySendChar(delim[0]); need_delim = true; } @@ -663,12 +836,12 @@ CopyTo(Relation rel, List *attnumlist, bool binary, bool oids, { if (!binary) { - CopySendString(null_print, fp); /* null indicator */ + CopySendString(null_print); /* null indicator */ } else { fld_size = 0; /* null marker */ - CopySendData(&fld_size, sizeof(int16), fp); + CopySendData(&fld_size, sizeof(int16)); } } else @@ -679,12 +852,12 @@ CopyTo(Relation rel, List *attnumlist, bool binary, bool oids, value, ObjectIdGetDatum(elements[attnum - 1]), Int32GetDatum(attr[attnum - 1]->atttypmod))); - CopyAttributeOut(fp, string, delim); + CopyAttributeOut(string, delim); } else { fld_size = attr[attnum - 1]->attlen; - CopySendData(&fld_size, sizeof(int16), fp); + CopySendData(&fld_size, sizeof(int16)); if (isvarlena[attnum - 1]) { /* varlena */ @@ -694,16 +867,14 @@ CopyTo(Relation rel, List *attnumlist, bool binary, bool oids, value = PointerGetDatum(PG_DETOAST_DATUM(value)); CopySendData(DatumGetPointer(value), - VARSIZE(value), - fp); + VARSIZE(value)); } else if (!attr[attnum - 1]->attbyval) { /* fixed-length pass-by-reference */ Assert(fld_size > 0); CopySendData(DatumGetPointer(value), - fld_size, - fp); + fld_size); } else { @@ -717,15 +888,14 @@ CopyTo(Relation rel, List *attnumlist, bool binary, bool oids, */ store_att_byval(&datumBuf, value, fld_size); CopySendData(&datumBuf, - fld_size, - fp); + fld_size); } } } } if (!binary) - CopySendChar('\n', fp); + CopySendChar('\n'); MemoryContextSwitchTo(oldcontext); } @@ -737,7 +907,7 @@ CopyTo(Relation rel, List *attnumlist, bool binary, bool oids, /* Generate trailer for a binary copy */ int16 fld_count = -1; - CopySendData(&fld_count, sizeof(int16), fp); + CopySendData(&fld_count, sizeof(int16)); } MemoryContextDelete(mycontext); @@ -753,7 +923,7 @@ CopyTo(Relation rel, List *attnumlist, bool binary, bool oids, */ static void CopyFrom(Relation rel, List *attnumlist, bool binary, bool oids, - FILE *fp, char *delim, char *null_print) + char *delim, char *null_print) { HeapTuple tuple; TupleDesc tupDesc; @@ -905,30 +1075,30 @@ CopyFrom(Relation rel, List *attnumlist, bool binary, bool oids, int32 tmp; /* Signature */ - CopyGetData(readSig, 12, fp); - if (CopyGetEof(fp) || memcmp(readSig, BinarySignature, 12) != 0) + CopyGetData(readSig, 12); + if (CopyGetEof() || memcmp(readSig, BinarySignature, 12) != 0) elog(ERROR, "COPY BINARY: file signature not recognized"); /* Integer layout field */ - CopyGetData(&tmp, sizeof(int32), fp); - if (CopyGetEof(fp) || tmp != 0x01020304) + CopyGetData(&tmp, sizeof(int32)); + if (CopyGetEof() || tmp != 0x01020304) elog(ERROR, "COPY BINARY: incompatible integer layout"); /* Flags field */ - CopyGetData(&tmp, sizeof(int32), fp); - if (CopyGetEof(fp)) + CopyGetData(&tmp, sizeof(int32)); + if (CopyGetEof()) elog(ERROR, "COPY BINARY: bogus file header (missing flags)"); file_has_oids = (tmp & (1 << 16)) != 0; tmp &= ~(1 << 16); if ((tmp >> 16) != 0) elog(ERROR, "COPY BINARY: unrecognized critical flags in header"); /* Header extension length */ - CopyGetData(&tmp, sizeof(int32), fp); - if (CopyGetEof(fp) || tmp < 0) + CopyGetData(&tmp, sizeof(int32)); + if (CopyGetEof() || tmp < 0) elog(ERROR, "COPY BINARY: bogus file header (missing length)"); /* Skip extension header, if present */ while (tmp-- > 0) { - CopyGetData(readSig, 1, fp); - if (CopyGetEof(fp)) + CopyGetData(readSig, 1); + if (CopyGetEof()) elog(ERROR, "COPY BINARY: bogus file header (wrong length)"); } } @@ -936,6 +1106,7 @@ CopyFrom(Relation rel, List *attnumlist, bool binary, bool oids, values = (Datum *) palloc(num_phys_attrs * sizeof(Datum)); nulls = (char *) palloc(num_phys_attrs * sizeof(char)); + /* Initialize static variables */ copy_lineno = 0; fe_eof = false; @@ -970,7 +1141,7 @@ CopyFrom(Relation rel, List *attnumlist, bool binary, bool oids, if (file_has_oids) { - string = CopyReadAttribute(fp, delim, &result); + string = CopyReadAttribute(delim, &result); if (result == END_OF_FILE && *string == '\0') { @@ -1006,7 +1177,7 @@ CopyFrom(Relation rel, List *attnumlist, bool binary, bool oids, elog(ERROR, "Missing data for column \"%s\"", NameStr(attr[m]->attname)); - string = CopyReadAttribute(fp, delim, &result); + string = CopyReadAttribute(delim, &result); if (result == END_OF_FILE && *string == '\0' && cur == attnumlist && !file_has_oids) @@ -1051,8 +1222,8 @@ CopyFrom(Relation rel, List *attnumlist, bool binary, bool oids, int16 fld_count, fld_size; - CopyGetData(&fld_count, sizeof(int16), fp); - if (CopyGetEof(fp) || fld_count == -1) + CopyGetData(&fld_count, sizeof(int16)); + if (CopyGetEof() || fld_count == -1) { done = true; break; @@ -1064,14 +1235,14 @@ CopyFrom(Relation rel, List *attnumlist, bool binary, bool oids, if (file_has_oids) { - CopyGetData(&fld_size, sizeof(int16), fp); - if (CopyGetEof(fp)) + CopyGetData(&fld_size, sizeof(int16)); + if (CopyGetEof()) elog(ERROR, "COPY BINARY: unexpected EOF"); if (fld_size != (int16) sizeof(Oid)) elog(ERROR, "COPY BINARY: sizeof(Oid) is %d, expected %d", (int) fld_size, (int) sizeof(Oid)); - CopyGetData(&loaded_oid, sizeof(Oid), fp); - if (CopyGetEof(fp)) + CopyGetData(&loaded_oid, sizeof(Oid)); + if (CopyGetEof()) elog(ERROR, "COPY BINARY: unexpected EOF"); if (loaded_oid == InvalidOid) elog(ERROR, "COPY BINARY: Invalid Oid"); @@ -1085,8 +1256,8 @@ CopyFrom(Relation rel, List *attnumlist, bool binary, bool oids, i++; - CopyGetData(&fld_size, sizeof(int16), fp); - if (CopyGetEof(fp)) + CopyGetData(&fld_size, sizeof(int16)); + if (CopyGetEof()) elog(ERROR, "COPY BINARY: unexpected EOF"); if (fld_size == 0) continue; /* it's NULL; nulls[attnum-1] already set */ @@ -1099,17 +1270,16 @@ CopyFrom(Relation rel, List *attnumlist, bool binary, bool oids, int32 varlena_size; Pointer varlena_ptr; - CopyGetData(&varlena_size, sizeof(int32), fp); - if (CopyGetEof(fp)) + CopyGetData(&varlena_size, sizeof(int32)); + if (CopyGetEof()) elog(ERROR, "COPY BINARY: unexpected EOF"); if (varlena_size < (int32) sizeof(int32)) elog(ERROR, "COPY BINARY: bogus varlena length"); varlena_ptr = (Pointer) palloc(varlena_size); VARATT_SIZEP(varlena_ptr) = varlena_size; CopyGetData(VARDATA(varlena_ptr), - varlena_size - sizeof(int32), - fp); - if (CopyGetEof(fp)) + varlena_size - sizeof(int32)); + if (CopyGetEof()) elog(ERROR, "COPY BINARY: unexpected EOF"); values[m] = PointerGetDatum(varlena_ptr); } @@ -1120,8 +1290,8 @@ CopyFrom(Relation rel, List *attnumlist, bool binary, bool oids, Assert(fld_size > 0); refval_ptr = (Pointer) palloc(fld_size); - CopyGetData(refval_ptr, fld_size, fp); - if (CopyGetEof(fp)) + CopyGetData(refval_ptr, fld_size); + if (CopyGetEof()) elog(ERROR, "COPY BINARY: unexpected EOF"); values[m] = PointerGetDatum(refval_ptr); } @@ -1135,8 +1305,8 @@ CopyFrom(Relation rel, List *attnumlist, bool binary, bool oids, * how shorter data values are aligned within a Datum. */ Assert(fld_size > 0 && fld_size <= sizeof(Datum)); - CopyGetData(&datumBuf, fld_size, fp); - if (CopyGetEof(fp)) + CopyGetData(&datumBuf, fld_size); + if (CopyGetEof()) elog(ERROR, "COPY BINARY: unexpected EOF"); values[m] = fetch_att(&datumBuf, true, fld_size); } @@ -1324,7 +1494,7 @@ GetTypeElement(Oid type) */ static char * -CopyReadAttribute(FILE *fp, const char *delim, CopyReadResult *result) +CopyReadAttribute(const char *delim, CopyReadResult *result) { int c; int delimc = (unsigned char) delim[0]; @@ -1344,7 +1514,7 @@ CopyReadAttribute(FILE *fp, const char *delim, CopyReadResult *result) for (;;) { - c = CopyGetChar(fp); + c = CopyGetChar(); if (c == EOF) { *result = END_OF_FILE; @@ -1359,7 +1529,7 @@ CopyReadAttribute(FILE *fp, const char *delim, CopyReadResult *result) break; if (c == '\\') { - c = CopyGetChar(fp); + c = CopyGetChar(); if (c == EOF) { *result = END_OF_FILE; @@ -1379,16 +1549,16 @@ CopyReadAttribute(FILE *fp, const char *delim, CopyReadResult *result) int val; val = OCTVALUE(c); - c = CopyPeekChar(fp); + c = CopyPeekChar(); if (ISOCTAL(c)) { val = (val << 3) + OCTVALUE(c); - CopyDonePeek(fp, c, true /* pick up */ ); - c = CopyPeekChar(fp); + CopyDonePeek(c, true /* pick up */ ); + c = CopyPeekChar(); if (ISOCTAL(c)) { val = (val << 3) + OCTVALUE(c); - CopyDonePeek(fp, c, true /* pick up */ ); + CopyDonePeek(c, true /* pick up */ ); } else { @@ -1397,7 +1567,7 @@ CopyReadAttribute(FILE *fp, const char *delim, CopyReadResult *result) *result = END_OF_FILE; goto copy_eof; } - CopyDonePeek(fp, c, false /* put back */ ); + CopyDonePeek(c, false /* put back */ ); } } else @@ -1407,7 +1577,7 @@ CopyReadAttribute(FILE *fp, const char *delim, CopyReadResult *result) *result = END_OF_FILE; goto copy_eof; } - CopyDonePeek(fp, c, false /* put back */ ); + CopyDonePeek(c, false /* put back */ ); } c = val & 0377; } @@ -1441,9 +1611,21 @@ CopyReadAttribute(FILE *fp, const char *delim, CopyReadResult *result) c = '\v'; break; case '.': - c = CopyGetChar(fp); + c = CopyGetChar(); if (c != '\n') elog(ERROR, "CopyReadAttribute: end of record marker corrupted"); + /* + * In protocol version 3, we should ignore anything after + * \. up to the protocol end of copy data. (XXX maybe + * better not to treat \. as special?) + */ + if (copy_dest == COPY_NEW_FE) + { + while (c != EOF) + { + c = CopyGetChar(); + } + } *result = END_OF_FILE; goto copy_eof; } @@ -1458,7 +1640,7 @@ CopyReadAttribute(FILE *fp, const char *delim, CopyReadResult *result) mblen = pg_encoding_mblen(client_encoding, s); for (j = 1; j < mblen; j++) { - c = CopyGetChar(fp); + c = CopyGetChar(); if (c == EOF) { *result = END_OF_FILE; @@ -1488,7 +1670,7 @@ copy_eof: } static void -CopyAttributeOut(FILE *fp, char *server_string, char *delim) +CopyAttributeOut(char *server_string, char *delim) { char *string; char c; @@ -1511,30 +1693,30 @@ CopyAttributeOut(FILE *fp, char *server_string, char *delim) switch (c) { case '\b': - CopySendString("\\b", fp); + CopySendString("\\b"); break; case '\f': - CopySendString("\\f", fp); + CopySendString("\\f"); break; case '\n': - CopySendString("\\n", fp); + CopySendString("\\n"); break; case '\r': - CopySendString("\\r", fp); + CopySendString("\\r"); break; case '\t': - CopySendString("\\t", fp); + CopySendString("\\t"); break; case '\v': - CopySendString("\\v", fp); + CopySendString("\\v"); break; case '\\': - CopySendString("\\\\", fp); + CopySendString("\\\\"); break; default: if (c == delimc) - CopySendChar('\\', fp); - CopySendChar(c, fp); + CopySendChar('\\'); + CopySendChar(c); /* * We can skip pg_encoding_mblen() overhead when encoding @@ -1546,7 +1728,7 @@ CopyAttributeOut(FILE *fp, char *server_string, char *delim) /* send additional bytes of the char, if any */ mblen = pg_encoding_mblen(client_encoding, string); for (i = 1; i < mblen; i++) - CopySendChar(string[i], fp); + CopySendChar(string[i]); } break; } diff --git a/src/backend/lib/stringinfo.c b/src/backend/lib/stringinfo.c index 9b9fc3d1800..0f758b1bd2d 100644 --- a/src/backend/lib/stringinfo.c +++ b/src/backend/lib/stringinfo.c @@ -9,15 +9,15 @@ * Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $Id: stringinfo.c,v 1.32 2002/09/04 20:31:18 momjian Exp $ + * $Id: stringinfo.c,v 1.33 2003/04/19 00:02:29 tgl Exp $ * *------------------------------------------------------------------------- */ - - #include "postgres.h" + #include "lib/stringinfo.h" + /* * makeStringInfo * @@ -50,41 +50,7 @@ initStringInfo(StringInfo str) str->maxlen = size; str->len = 0; str->data[0] = '\0'; -} - -/* - * enlargeStringInfo - * - * Internal routine: make sure there is enough space for 'needed' more bytes - * ('needed' does not include the terminating null). - * - * NB: because we use repalloc() to enlarge the buffer, the string buffer - * will remain allocated in the same memory context that was current when - * initStringInfo was called, even if another context is now current. - * This is the desired and indeed critical behavior! - */ -static void -enlargeStringInfo(StringInfo str, int needed) -{ - int newlen; - - needed += str->len + 1; /* total space required now */ - if (needed <= str->maxlen) - return; /* got enough space already */ - - /* - * We don't want to allocate just a little more space with each - * append; for efficiency, double the buffer size each time it - * overflows. Actually, we might need to more than double it if - * 'needed' is big... - */ - newlen = 2 * str->maxlen; - while (needed > newlen) - newlen = 2 * newlen; - - str->data = (char *) repalloc(str->data, newlen); - - str->maxlen = newlen; + str->cursor = 0; } /* @@ -147,8 +113,9 @@ appendStringInfo(StringInfo str, const char *fmt,...) } } -/*------------------------ +/* * appendStringInfoChar + * * Append a single byte to str. * Like appendStringInfo(str, "%c", ch) but much faster. */ @@ -189,3 +156,44 @@ appendBinaryStringInfo(StringInfo str, const char *data, int datalen) */ str->data[str->len] = '\0'; } + +/* + * enlargeStringInfo + * + * Make sure there is enough space for 'needed' more bytes + * ('needed' does not include the terminating null). + * + * External callers need not concern themselves with this, since all + * stringinfo.c routines do it automatically. However, if a caller + * knows that a StringInfo will eventually become X bytes large, it + * can save some palloc overhead by enlarging the buffer before starting + * to store data in it. + * + * NB: because we use repalloc() to enlarge the buffer, the string buffer + * will remain allocated in the same memory context that was current when + * initStringInfo was called, even if another context is now current. + * This is the desired and indeed critical behavior! + */ +void +enlargeStringInfo(StringInfo str, int needed) +{ + int newlen; + + needed += str->len + 1; /* total space required now */ + if (needed <= str->maxlen) + return; /* got enough space already */ + + /* + * We don't want to allocate just a little more space with each + * append; for efficiency, double the buffer size each time it + * overflows. Actually, we might need to more than double it if + * 'needed' is big... + */ + newlen = 2 * str->maxlen; + while (needed > newlen) + newlen = 2 * newlen; + + str->data = (char *) repalloc(str->data, newlen); + + str->maxlen = newlen; +} diff --git a/src/backend/libpq/auth.c b/src/backend/libpq/auth.c index 5396cc47c18..a5dc8eff2da 100644 --- a/src/backend/libpq/auth.c +++ b/src/backend/libpq/auth.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/libpq/auth.c,v 1.98 2003/04/17 22:26:01 tgl Exp $ + * $Header: /cvsroot/pgsql/src/backend/libpq/auth.c,v 1.99 2003/04/19 00:02:29 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -37,6 +37,7 @@ static void sendAuthRequest(Port *port, AuthRequest areq); static void auth_failed(Port *port, int status); +static char *recv_password_packet(Port *port); static int recv_and_check_password_packet(Port *port); char *pg_krb_server_keyfile; @@ -539,11 +540,9 @@ sendAuthRequest(Port *port, AuthRequest areq) */ static int -pam_passwd_conv_proc(int num_msg, const struct pam_message ** msg, struct pam_response ** resp, void *appdata_ptr) +pam_passwd_conv_proc(int num_msg, const struct pam_message ** msg, + struct pam_response ** resp, void *appdata_ptr) { - StringInfoData buf; - int32 len; - if (num_msg != 1 || msg[0]->msg_style != PAM_PROMPT_ECHO_OFF) { switch (msg[0]->msg_style) @@ -574,23 +573,20 @@ pam_passwd_conv_proc(int num_msg, const struct pam_message ** msg, struct pam_re */ if (strlen(appdata_ptr) == 0) { - sendAuthRequest(pam_port_cludge, AUTH_REQ_PASSWORD); - if (pq_eof() == EOF || pq_getint(&len, 4) == EOF) - return PAM_CONV_ERR; /* client didn't want to send password */ + char *passwd; - initStringInfo(&buf); - if (pq_getstr_bounded(&buf, 1000) == EOF) - return PAM_CONV_ERR; /* EOF while reading password */ + sendAuthRequest(pam_port_cludge, AUTH_REQ_PASSWORD); + passwd = recv_password_packet(pam_port_cludge); - /* Do not echo failed password to logs, for security. */ - elog(DEBUG5, "received PAM packet"); + if (passwd == NULL) + return PAM_CONV_ERR; /* client didn't want to send password */ - if (strlen(buf.data) == 0) + if (strlen(passwd) == 0) { elog(LOG, "pam_passwd_conv_proc: no password"); return PAM_CONV_ERR; } - appdata_ptr = buf.data; + appdata_ptr = passwd; } /* @@ -601,8 +597,6 @@ pam_passwd_conv_proc(int num_msg, const struct pam_message ** msg, struct pam_re if (!*resp) { elog(LOG, "pam_passwd_conv_proc: Out of memory!"); - if (buf.data) - pfree(buf.data); return PAM_CONV_ERR; } @@ -708,42 +702,87 @@ CheckPAMAuth(Port *port, char *user, char *password) /* - * Called when we have received the password packet. + * Collect password response packet from frontend. + * + * Returns NULL if couldn't get password, else palloc'd string. */ -static int -recv_and_check_password_packet(Port *port) +static char * +recv_password_packet(Port *port) { StringInfoData buf; - int32 len; - int result; - if (pq_eof() == EOF || pq_getint(&len, 4) == EOF) - return STATUS_EOF; /* client didn't want to send password */ + if (PG_PROTOCOL_MAJOR(port->proto) >= 3) + { + /* Expect 'p' message type */ + int mtype; + + mtype = pq_getbyte(); + if (mtype != 'p') + { + /* + * If the client just disconnects without offering a password, + * don't make a log entry. This is legal per protocol spec and + * in fact commonly done by psql, so complaining just clutters + * the log. + */ + if (mtype != EOF) + elog(COMMERROR, "Expected password response, got %c", mtype); + return NULL; /* EOF or bad message type */ + } + } + else + { + /* For pre-3.0 clients, avoid log entry if they just disconnect */ + if (pq_peekbyte() == EOF) + return NULL; /* EOF */ + } initStringInfo(&buf); - if (pq_getstr_bounded(&buf, 1000) == EOF) /* receive password */ + if (pq_getmessage(&buf, 1000)) /* receive password */ { + /* EOF - pq_getmessage already logged a suitable message */ pfree(buf.data); - return STATUS_EOF; + return NULL; } /* - * We don't actually use the password packet length the frontend sent - * us; however, it's a reasonable sanity check to ensure that we - * actually read as much data as we expected to. - * - * The password packet size is the length of the buffer, plus the size - * field itself (4 bytes), plus a 1-byte terminator. + * Apply sanity check: password packet length should agree with length + * of contained string. Note it is safe to use strlen here because + * StringInfo is guaranteed to have an appended '\0'. */ - if (len != (buf.len + 4 + 1)) - elog(LOG, "unexpected password packet size: read %d, expected %d", - buf.len + 4 + 1, len); + if (strlen(buf.data) + 1 != buf.len) + elog(COMMERROR, "bogus password packet size"); /* Do not echo password to logs, for security. */ elog(DEBUG5, "received password packet"); - result = md5_crypt_verify(port, port->user_name, buf.data); + /* + * Return the received string. Note we do not attempt to do any + * character-set conversion on it; since we don't yet know the + * client's encoding, there wouldn't be much point. + */ + return buf.data; +} + + +/* + * Called when we have sent an authorization request for a password. + * Get the response and check it. + */ +static int +recv_and_check_password_packet(Port *port) +{ + char *passwd; + int result; + + passwd = recv_password_packet(port); + + if (passwd == NULL) + return STATUS_EOF; /* client wouldn't send password */ + + result = md5_crypt_verify(port, port->user_name, passwd); + + pfree(passwd); - pfree(buf.data); return result; } diff --git a/src/backend/libpq/be-secure.c b/src/backend/libpq/be-secure.c index 923f38a98c9..e7674c7807c 100644 --- a/src/backend/libpq/be-secure.c +++ b/src/backend/libpq/be-secure.c @@ -11,7 +11,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/libpq/be-secure.c,v 1.29 2003/04/10 23:03:08 tgl Exp $ + * $Header: /cvsroot/pgsql/src/backend/libpq/be-secure.c,v 1.30 2003/04/19 00:02:29 tgl Exp $ * * Since the server static private key ($DataDir/server.key) * will normally be stored unencrypted so that the database @@ -110,13 +110,6 @@ extern void ExitPostmaster(int); extern void postmaster_error(const char *fmt,...); -int secure_initialize(void); -void secure_destroy(void); -int secure_open_server(Port *); -void secure_close(Port *); -ssize_t secure_read(Port *, void *ptr, size_t len); -ssize_t secure_write(Port *, void *ptr, size_t len); - #ifdef USE_SSL static DH *load_dh_file(int keylength); static DH *load_dh_buffer(const char *, size_t); diff --git a/src/backend/libpq/pqcomm.c b/src/backend/libpq/pqcomm.c index cc06347e45d..9a4f51b7786 100644 --- a/src/backend/libpq/pqcomm.c +++ b/src/backend/libpq/pqcomm.c @@ -6,8 +6,8 @@ * These routines handle the low-level details of communication between * frontend and backend. They just shove data across the communication * channel, and are ignorant of the semantics of the data --- or would be, - * except for major brain damage in the design of the COPY OUT protocol. - * Unfortunately, COPY OUT is designed to commandeer the communication + * except for major brain damage in the design of the old COPY OUT protocol. + * Unfortunately, COPY OUT was designed to commandeer the communication * channel (it just transfers data without wrapping it into messages). * No other messages can be sent while COPY OUT is in progress; and if the * copy is aborted by an elog(ERROR), we need to close out the copy so that @@ -29,7 +29,7 @@ * Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $Id: pqcomm.c,v 1.149 2003/04/02 00:49:28 tgl Exp $ + * $Header: /cvsroot/pgsql/src/backend/libpq/pqcomm.c,v 1.150 2003/04/19 00:02:29 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -48,12 +48,13 @@ * low-level I/O: * pq_getbytes - get a known number of bytes from connection * pq_getstring - get a null terminated string from connection + * pq_getmessage - get a message with length word from connection * pq_getbyte - get next byte from connection * pq_peekbyte - peek at next byte from connection * pq_putbytes - send bytes to connection (not flushed until pq_flush) * pq_flush - flush pending output * - * message-level I/O (and COPY OUT cruft): + * message-level I/O (and old-style-COPY-OUT cruft): * pq_putmessage - send a normal message (suppressed in COPY OUT mode) * pq_startcopyout - inform libpq that a COPY OUT transfer is beginning * pq_endcopyout - end a COPY OUT transfer @@ -85,9 +86,6 @@ #include "miscadmin.h" #include "storage/ipc.h" -extern void secure_close(Port *); -extern ssize_t secure_read(Port *, void *, size_t); -extern ssize_t secure_write(Port *, const void *, size_t); static void pq_close(void); @@ -562,8 +560,10 @@ pq_recvbuf(void) } if (r == 0) { - /* as above, only write to postmaster log */ - elog(COMMERROR, "pq_recvbuf: unexpected EOF on client connection"); + /* + * EOF detected. We used to write a log message here, but it's + * better to expect the ultimate caller to do that. + */ return EOF; } /* r contains number of bytes read, so just incr length */ @@ -636,35 +636,29 @@ pq_getbytes(char *s, size_t len) /* -------------------------------- * pq_getstring - get a null terminated string from connection * - * The return value is placed in an expansible StringInfo. - * Note that space allocation comes from the current memory context! + * The return value is placed in an expansible StringInfo, which has + * already been initialized by the caller. * - * If maxlen is not zero, it is an upper limit on the length of the - * string we are willing to accept. We abort the connection (by - * returning EOF) if client tries to send more than that. Note that - * since we test maxlen in the outer per-bufferload loop, the limit - * is fuzzy: we might accept up to PQ_BUFFER_SIZE more bytes than - * specified. This is fine for the intended purpose, which is just - * to prevent DoS attacks from not-yet-authenticated clients. - * - * NOTE: this routine does not do any character set conversion, - * even though it is presumably useful only for text, because - * no code in this module should depend on the encoding. - * See pq_getstr_bounded in pqformat.c for that. + * This is used only for dealing with old-protocol clients. The idea + * is to produce a StringInfo that looks the same as we would get from + * pq_getmessage() with a newer client; we will then process it with + * pq_getmsgstring. Therefore, no character set conversion is done here, + * even though this is presumably useful only for text. * * returns 0 if OK, EOF if trouble * -------------------------------- */ int -pq_getstring(StringInfo s, int maxlen) +pq_getstring(StringInfo s) { int i; /* Reset string to empty */ s->len = 0; s->data[0] = '\0'; + s->cursor = 0; - /* Read until we get the terminating '\0' or overrun maxlen */ + /* Read until we get the terminating '\0' */ for (;;) { while (PqRecvPointer >= PqRecvLength) @@ -677,9 +671,9 @@ pq_getstring(StringInfo s, int maxlen) { if (PqRecvBuffer[i] == '\0') { - /* does not copy the \0 */ + /* include the '\0' in the copy */ appendBinaryStringInfo(s, PqRecvBuffer + PqRecvPointer, - i - PqRecvPointer); + i - PqRecvPointer + 1); PqRecvPointer = i + 1; /* advance past \0 */ return 0; } @@ -689,11 +683,70 @@ pq_getstring(StringInfo s, int maxlen) appendBinaryStringInfo(s, PqRecvBuffer + PqRecvPointer, PqRecvLength - PqRecvPointer); PqRecvPointer = PqRecvLength; + } +} + + +/* -------------------------------- + * pq_getmessage - get a message with length word from connection + * + * The return value is placed in an expansible StringInfo, which has + * already been initialized by the caller. + * Only the message body is placed in the StringInfo; the length word + * is removed. Also, s->cursor is initialized to zero for convenience + * in scanning the message contents. + * + * If maxlen is not zero, it is an upper limit on the length of the + * message we are willing to accept. We abort the connection (by + * returning EOF) if client tries to send more than that. + * + * returns 0 if OK, EOF if trouble + * -------------------------------- + */ +int +pq_getmessage(StringInfo s, int maxlen) +{ + int32 len; + + /* Reset message buffer to empty */ + s->len = 0; + s->data[0] = '\0'; + s->cursor = 0; - /* If maxlen is specified, check for overlength input. */ - if (maxlen > 0 && s->len > maxlen) + /* Read message length word */ + if (pq_getbytes((char *) &len, 4) == EOF) + { + elog(COMMERROR, "unexpected EOF within message length word"); + return EOF; + } + + len = ntohl(len); + len -= 4; /* discount length itself */ + + if (len < 0 || + (maxlen > 0 && len > maxlen)) + { + elog(COMMERROR, "invalid message length"); + return EOF; + } + + if (len > 0) + { + /* Allocate space for message */ + enlargeStringInfo(s, len); + + /* And grab the message */ + if (pq_getbytes(s->data, len) == EOF) + { + elog(COMMERROR, "incomplete client message"); return EOF; + } + s->len = len; + /* Place a trailing null per StringInfo convention */ + s->data[len] = '\0'; } + + return 0; } @@ -781,34 +834,10 @@ pq_flush(void) } -/* - * Return EOF if the connection has been broken, else 0. - */ -int -pq_eof(void) -{ - char x; - int res; - - res = recv(MyProcPort->sock, &x, 1, MSG_PEEK); - - if (res < 0) - { - /* can log to postmaster log only */ - elog(COMMERROR, "pq_eof: recv() failed: %m"); - return EOF; - } - if (res == 0) - return EOF; - else - return 0; -} - - /* -------------------------------- * Message-level I/O routines begin here. * - * These routines understand about COPY OUT protocol. + * These routines understand about the old-style COPY OUT protocol. * -------------------------------- */ @@ -840,7 +869,8 @@ pq_putmessage(char msgtype, const char *s, size_t len) } /* -------------------------------- - * pq_startcopyout - inform libpq that a COPY OUT transfer is beginning + * pq_startcopyout - inform libpq that an old-style COPY OUT transfer + * is beginning * -------------------------------- */ void diff --git a/src/backend/libpq/pqformat.c b/src/backend/libpq/pqformat.c index 50f17977d3e..80ca3190999 100644 --- a/src/backend/libpq/pqformat.c +++ b/src/backend/libpq/pqformat.c @@ -8,15 +8,17 @@ * formatting/conversion routines that are needed to produce valid messages. * Note in particular the distinction between "raw data" and "text"; raw data * is message protocol characters and binary values that are not subject to - * character set conversion, while text is converted by character encoding rules. + * character set conversion, while text is converted by character encoding + * rules. * - * Incoming messages are read directly off the wire, as it were, but there - * are still data-conversion tasks to be performed. + * Incoming messages are similarly read into a StringInfo buffer, via + * pq_getmessage, and then parsed and converted from that using the routines + * in this module. * * Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $Id: pqformat.c,v 1.26 2003/04/02 00:49:28 tgl Exp $ + * $Header: /cvsroot/pgsql/src/backend/libpq/pqformat.c,v 1.27 2003/04/19 00:02:29 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -37,12 +39,13 @@ * Special-case message output: * pq_puttextmessage - generate a character set-converted message in one step * - * Message input: - * pq_getint - get an integer from connection - * pq_getstr_bounded - get a null terminated string from connection - * pq_getstr_bounded performs character set conversion on the collected - * string. Use the raw pqcomm.c routines pq_getstring or pq_getbytes - * to fetch data without conversion. + * Message parsing after input: + * pq_getmsgbyte - get a raw byte from a message buffer + * pq_getmsgint - get a binary integer from a message buffer + * pq_getmsgbytes - get raw data from a message buffer + * pq_copymsgbytes - copy raw data from a message buffer + * pq_getmsgstring - get a null-terminated text string (with conversion) + * pq_getmsgend - verify message fully consumed */ #include "postgres.h" @@ -206,16 +209,29 @@ pq_puttextmessage(char msgtype, const char *str) return pq_putmessage(msgtype, str, slen + 1); } + /* -------------------------------- - * pq_getint - get an integer from connection - * - * returns 0 if OK, EOF if trouble + * pq_getmsgbyte - get a raw byte from a message buffer * -------------------------------- */ int -pq_getint(int *result, int b) +pq_getmsgbyte(StringInfo msg) +{ + if (msg->cursor >= msg->len) + elog(ERROR, "pq_getmsgbyte: no data left in message"); + return (unsigned char) msg->data[msg->cursor++]; +} + +/* -------------------------------- + * pq_getmsgint - get a binary integer from a message buffer + * + * Values are treated as unsigned. + * -------------------------------- + */ +unsigned int +pq_getmsgint(StringInfo msg, int b) { - int status; + unsigned int result; unsigned char n8; uint16 n16; uint32 n32; @@ -223,59 +239,93 @@ pq_getint(int *result, int b) switch (b) { case 1: - status = pq_getbytes((char *) &n8, 1); - *result = (int) n8; + pq_copymsgbytes(msg, (char *) &n8, 1); + result = n8; break; case 2: - status = pq_getbytes((char *) &n16, 2); - *result = (int) (ntohs(n16)); + pq_copymsgbytes(msg, (char *) &n16, 2); + result = ntohs(n16); break; case 4: - status = pq_getbytes((char *) &n32, 4); - *result = (int) (ntohl(n32)); + pq_copymsgbytes(msg, (char *) &n32, 4); + result = ntohl(n32); break; default: - - /* - * if we elog(ERROR) here, we will lose sync with the - * frontend, so just complain to postmaster log instead... - */ - elog(COMMERROR, "pq_getint: unsupported size %d", b); - status = EOF; - *result = 0; + elog(ERROR, "pq_getmsgint: unsupported size %d", b); + result = 0; /* keep compiler quiet */ break; } - return status; + return result; } /* -------------------------------- - * pq_getstr_bounded - get a null terminated string from connection + * pq_getmsgbytes - get raw data from a message buffer * - * The return value is placed in an expansible StringInfo. - * Note that space allocation comes from the current memory context! + * Returns a pointer directly into the message buffer; note this + * may not have any particular alignment. + * -------------------------------- + */ +const char * +pq_getmsgbytes(StringInfo msg, int datalen) +{ + const char *result; + + if (datalen > (msg->len - msg->cursor)) + elog(ERROR, "pq_getmsgbytes: insufficient data left in message"); + result = &msg->data[msg->cursor]; + msg->cursor += datalen; + return result; +} + +/* -------------------------------- + * pq_copymsgbytes - copy raw data from a message buffer * - * The maxlen parameter is interpreted as per pq_getstring. + * Same as above, except data is copied to caller's buffer. + * -------------------------------- + */ +void +pq_copymsgbytes(StringInfo msg, char *buf, int datalen) +{ + if (datalen > (msg->len - msg->cursor)) + elog(ERROR, "pq_copymsgbytes: insufficient data left in message"); + memcpy(buf, &msg->data[msg->cursor], datalen); + msg->cursor += datalen; +} + +/* -------------------------------- + * pq_getmsgstring - get a null-terminated text string (with conversion) * - * returns 0 if OK, EOF if trouble + * May return a pointer directly into the message buffer, or a pointer + * to a palloc'd conversion result. * -------------------------------- */ -int -pq_getstr_bounded(StringInfo s, int maxlen) +const char * +pq_getmsgstring(StringInfo msg) { - int result; - char *p; + char *str; + int slen; - result = pq_getstring(s, maxlen); + str = &msg->data[msg->cursor]; + /* + * It's safe to use strlen() here because a StringInfo is guaranteed + * to have a trailing null byte. But check we found a null inside + * the message. + */ + slen = strlen(str); + if (msg->cursor + slen >= msg->len) + elog(ERROR, "pq_getmsgstring: invalid string in message"); + msg->cursor += slen + 1; - p = (char *) pg_client_to_server((unsigned char *) s->data, s->len); - if (p != s->data) /* actual conversion has been done? */ - { - /* reset s to empty, and append the new string p */ - s->len = 0; - s->data[0] = '\0'; - appendBinaryStringInfo(s, p, strlen(p)); - pfree(p); - } + return (const char *) pg_client_to_server((unsigned char *) str, slen); +} - return result; +/* -------------------------------- + * pq_getmsgend - verify message fully consumed + * -------------------------------- + */ +void +pq_getmsgend(StringInfo msg) +{ + if (msg->cursor != msg->len) + elog(ERROR, "pq_getmsgend: invalid message format"); } diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c index 499c4f25ca2..d6beb0fc1a6 100644 --- a/src/backend/postmaster/postmaster.c +++ b/src/backend/postmaster/postmaster.c @@ -37,7 +37,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/postmaster/postmaster.c,v 1.312 2003/04/18 01:03:42 momjian Exp $ + * $Header: /cvsroot/pgsql/src/backend/postmaster/postmaster.c,v 1.313 2003/04/19 00:02:29 tgl Exp $ * * NOTES * @@ -1118,7 +1118,7 @@ ProcessStartupPacket(Port *port, bool SSLdone) if (pq_getbytes((char *) &len, 4) == EOF) { - elog(LOG, "incomplete startup packet"); + elog(COMMERROR, "incomplete startup packet"); return STATUS_ERROR; } @@ -1142,7 +1142,7 @@ ProcessStartupPacket(Port *port, bool SSLdone) if (pq_getbytes(buf, len) == EOF) { - elog(LOG, "incomplete startup packet"); + elog(COMMERROR, "incomplete startup packet"); return STATUS_ERROR; } @@ -1189,6 +1189,16 @@ ProcessStartupPacket(Port *port, bool SSLdone) /* Could add additional special packet types here */ + /* + * XXX temporary for 3.0 protocol development: we are using the minor + * number as a test-version number. Insist it match exactly so people + * don't get burnt by using yesterday's libpq with today's server. + * XXX this must go away before release!!! + */ + if (PG_PROTOCOL_MAJOR(proto) == 3 && + PG_PROTOCOL_MINOR(proto) != PG_PROTOCOL_MINOR(PG_PROTOCOL_LATEST)) + elog(FATAL, "Your development libpq is out of sync with the server"); + /* Check we can handle the protocol the frontend is using. */ if (PG_PROTOCOL_MAJOR(proto) < PG_PROTOCOL_MAJOR(PG_PROTOCOL_EARLIEST) || @@ -1201,16 +1211,6 @@ ProcessStartupPacket(Port *port, bool SSLdone) PG_PROTOCOL_MAJOR(PG_PROTOCOL_LATEST), PG_PROTOCOL_MINOR(PG_PROTOCOL_LATEST)); - /* - * XXX temporary for 3.0 protocol development: we are using the minor - * number as a test-version number. Insist it match exactly so people - * don't get burnt by using yesterday's libpq with today's server. - * XXX this must go away before release!!! - */ - if (PG_PROTOCOL_MAJOR(proto) == 3 && - PG_PROTOCOL_MINOR(proto) != PG_PROTOCOL_MINOR(PG_PROTOCOL_LATEST)) - elog(FATAL, "Your development libpq is out of sync with the server"); - /* * Now fetch parameters out of startup packet and save them into the * Port structure. All data structures attached to the Port struct diff --git a/src/backend/tcop/dest.c b/src/backend/tcop/dest.c index ad9d2327717..07e4614e799 100644 --- a/src/backend/tcop/dest.c +++ b/src/backend/tcop/dest.c @@ -8,7 +8,7 @@ * Portions Copyright (c) 1994, Regents of the University of California * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/tcop/dest.c,v 1.51 2003/03/27 16:51:29 momjian Exp $ + * $Header: /cvsroot/pgsql/src/backend/tcop/dest.c,v 1.52 2003/04/19 00:02:29 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -134,37 +134,6 @@ EndCommand(const char *commandTag, CommandDest dest) } } -/* - * These are necessary to sync communications between fe/be processes doing - * COPY rel TO stdout - * - * or - * - * COPY rel FROM stdin - * - * NOTE: the message code letters are changed at protocol version 2.0 - * to eliminate possible confusion with data tuple messages. - */ -void -SendCopyBegin(void) -{ - if (PG_PROTOCOL_MAJOR(FrontendProtocol) >= 2) - pq_putbytes("H", 1); /* new way */ - else - pq_putbytes("B", 1); /* old way */ -} - -void -ReceiveCopyBegin(void) -{ - if (PG_PROTOCOL_MAJOR(FrontendProtocol) >= 2) - pq_putbytes("G", 1); /* new way */ - else - pq_putbytes("D", 1); /* old way */ - /* We *must* flush here to ensure FE knows it can send. */ - pq_flush(); -} - /* ---------------- * NullCommand - tell dest that an empty query string was recognized * diff --git a/src/backend/tcop/fastpath.c b/src/backend/tcop/fastpath.c index 53b3a09ba27..eeddea6f6eb 100644 --- a/src/backend/tcop/fastpath.c +++ b/src/backend/tcop/fastpath.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/tcop/fastpath.c,v 1.57 2003/01/09 18:00:23 tgl Exp $ + * $Header: /cvsroot/pgsql/src/backend/tcop/fastpath.c,v 1.58 2003/04/19 00:02:29 tgl Exp $ * * NOTES * This cruft is the server side of PQfn. @@ -28,38 +28,13 @@ * back to the frontend. If the return returns by reference, * send down only the data portion and set the return size appropriately. * - * OLD COMMENTS FOLLOW - * - * The VAR_LENGTH_{ARGS,RESULT} stuff is limited to MAX_STRING_LENGTH - * (see src/backend/tmp/fastpath.h) for no obvious reason. Since its - * primary use (for us) is for Inversion path names, it should probably - * be increased to 256 (MAXPATHLEN for Inversion, hidden in pg_type - * as well as utils/adt/filename.c). - * - * Quoth PMA on 08/15/93: - * - * This code has been almost completely rewritten with an eye to - * keeping it as compatible as possible with the previous (broken) - * implementation. - * - * The previous implementation would assume (1) that any value of - * length <= 4 bytes was passed-by-value, and that any other value - * was a struct varlena (by-reference). There was NO way to pass a - * fixed-length by-reference argument (like name) or a struct - * varlena of size <= 4 bytes. - * - * The new implementation checks the catalogs to determine whether - * a value is by-value (type "0" is null-delimited character string, - * as it is for, e.g., the parser). The only other item obtained - * from the catalogs is whether or not the value should be placed in - * a struct varlena or not. Otherwise, the size given by the - * frontend is assumed to be correct (probably a bad decision, but - * we do strange things in the name of compatibility). - * *------------------------------------------------------------------------- */ #include "postgres.h" +#include <netinet/in.h> +#include <arpa/inet.h> + #include "access/xact.h" #include "catalog/pg_proc.h" #include "libpq/libpq.h" @@ -72,6 +47,67 @@ #include "utils/tqual.h" +/* ---------------- + * GetOldFunctionMessage + * + * In pre-3.0 protocol, there is no length word on the message, so we have + * to have code that understands the message layout to absorb the message + * into a buffer. We want to do this before we start execution, so that + * we do not lose sync with the frontend if there's an error. + * + * The caller should already have initialized buf to empty. + * ---------------- + */ +static int +GetOldFunctionMessage(StringInfo buf) +{ + int32 ibuf; + int nargs; + + /* Dummy string argument */ + if (pq_getstring(buf)) + return EOF; + /* Function OID */ + if (pq_getbytes((char *) &ibuf, 4)) + return EOF; + appendBinaryStringInfo(buf, (char *) &ibuf, 4); + /* Number of arguments */ + if (pq_getbytes((char *) &ibuf, 4)) + return EOF; + appendBinaryStringInfo(buf, (char *) &ibuf, 4); + nargs = ntohl(ibuf); + /* For each argument ... */ + while (nargs-- > 0) + { + int argsize; + + /* argsize */ + if (pq_getbytes((char *) &ibuf, 4)) + return EOF; + appendBinaryStringInfo(buf, (char *) &ibuf, 4); + argsize = ntohl(ibuf); + if (argsize < 0) + { + /* FATAL here since no hope of regaining message sync */ + elog(FATAL, "HandleFunctionRequest: bogus argsize %d", + argsize); + } + /* and arg contents */ + if (argsize > 0) + { + /* Allocate space for arg */ + enlargeStringInfo(buf, argsize); + /* And grab it */ + if (pq_getbytes(buf->data + buf->len, argsize)) + return EOF; + buf->len += argsize; + /* Place a trailing null per StringInfo convention */ + buf->data[buf->len] = '\0'; + } + } + return 0; +} + /* ---------------- * SendFunctionResult * @@ -205,6 +241,12 @@ fetch_fp_info(Oid func_id, struct fp_info * fip) * Server side of PQfn (fastpath function calls from the frontend). * This corresponds to the libpq protocol symbol "F". * + * INPUT: + * In protocol version 3, postgres.c has already read the message body + * and will pass it in msgBuf. + * In old protocol, the passed msgBuf is empty and we must read the + * message here. + * * RETURNS: * 0 if successful completion, EOF if frontend connection lost. * @@ -218,54 +260,44 @@ fetch_fp_info(Oid func_id, struct fp_info * fip) * control returns to PostgresMain. */ int -HandleFunctionRequest(void) +HandleFunctionRequest(StringInfo msgBuf) { Oid fid; - int argsize; int nargs; - int tmp; AclResult aclresult; FunctionCallInfoData fcinfo; Datum retval; int i; - char *p; struct fp_info my_fp; struct fp_info *fip; /* - * XXX FIXME: This protocol is misdesigned. - * - * We really do not want to elog() before having swallowed all of the - * frontend's fastpath message; otherwise we will lose sync with the - * input datastream. What should happen is we absorb all of the input - * message per protocol syntax, and *then* do error checking - * (including lookup of the given function ID) and elog if - * appropriate. Unfortunately, because we cannot even read the - * message properly without knowing whether the data types are - * pass-by-ref or pass-by-value, it's not all that easy to do :-(. The - * protocol should require the client to supply what it thinks is the - * typbyval and typlen value for each arg, so that we can read the - * data without having to do any lookups. Then after we've read the - * message, we should do the lookups, verify agreement of the actual - * function arg types with what we received, and finally call the - * function. - * - * As things stand, not only will we lose sync for an invalid message - * (such as requested function OID doesn't exist), but we may lose - * sync for a perfectly valid message if we are in transaction-aborted - * state! This can happen because our database lookup attempts may - * fail entirely in abort state. - * - * Unfortunately I see no way to fix this without breaking a lot of - * existing clients. Maybe do it as part of next protocol version - * change. + * Read message contents if not already done. */ + if (PG_PROTOCOL_MAJOR(FrontendProtocol) < 3) + { + if (GetOldFunctionMessage(msgBuf)) + { + elog(COMMERROR, "unexpected EOF on client connection"); + return EOF; + } + } - if (pq_getint(&tmp, 4)) /* function oid */ - return EOF; - fid = (Oid) tmp; - if (pq_getint(&nargs, 4)) /* # of arguments */ - return EOF; + /* + * Now that we've eaten the input message, check to see if we actually + * want to do the function call or not. It's now safe to elog(); we won't + * lose sync with the frontend. + */ + if (IsAbortedTransactionBlockState()) + elog(ERROR, "current transaction is aborted, " + "queries ignored until end of transaction block"); + + /* + * Parse the buffer contents. + */ + (void) pq_getmsgstring(msgBuf); /* dummy string */ + fid = (Oid) pq_getmsgint(msgBuf, 4); /* function oid */ + nargs = pq_getmsgint(msgBuf, 4); /* # of arguments */ /* * There used to be a lame attempt at caching lookup info here. Now we @@ -274,11 +306,14 @@ HandleFunctionRequest(void) fip = &my_fp; fetch_fp_info(fid, fip); + /* Check permission to call function */ + aclresult = pg_proc_aclcheck(fid, GetUserId(), ACL_EXECUTE); + if (aclresult != ACLCHECK_OK) + aclcheck_error(aclresult, get_func_name(fid)); + if (fip->flinfo.fn_nargs != nargs || nargs > FUNC_MAX_ARGS) - { elog(ERROR, "HandleFunctionRequest: actual arguments (%d) != registered arguments (%d)", nargs, fip->flinfo.fn_nargs); - } MemSet(&fcinfo, 0, sizeof(fcinfo)); fcinfo.flinfo = &fip->flinfo; @@ -286,21 +321,21 @@ HandleFunctionRequest(void) /* * Copy supplied arguments into arg vector. Note there is no way for - * frontend to specify a NULL argument --- more misdesign. + * frontend to specify a NULL argument --- this protocol is misdesigned. */ for (i = 0; i < nargs; ++i) { - if (pq_getint(&argsize, 4)) - return EOF; + int argsize; + char *p; + + argsize = pq_getmsgint(msgBuf, 4); if (fip->argbyval[i]) { /* by-value */ if (argsize < 1 || argsize > 4) elog(ERROR, "HandleFunctionRequest: bogus argsize %d", argsize); /* XXX should we demand argsize == fip->arglen[i] ? */ - if (pq_getint(&tmp, argsize)) - return EOF; - fcinfo.arg[i] = (Datum) tmp; + fcinfo.arg[i] = (Datum) pq_getmsgint(msgBuf, argsize); } else { /* by-reference ... */ @@ -309,13 +344,9 @@ HandleFunctionRequest(void) if (argsize < 0) elog(ERROR, "HandleFunctionRequest: bogus argsize %d", argsize); - /* I suspect this +1 isn't really needed - tgl 5/2000 */ - p = palloc(argsize + VARHDRSZ + 1); /* Added +1 to solve - * memory leak - Peter - * 98 Jan 6 */ + p = palloc(argsize + VARHDRSZ); VARATT_SIZEP(p) = argsize + VARHDRSZ; - if (pq_getbytes(VARDATA(p), argsize)) - return EOF; + pq_copymsgbytes(msgBuf, VARDATA(p), argsize); } else { /* ... fixed */ @@ -323,29 +354,12 @@ HandleFunctionRequest(void) elog(ERROR, "HandleFunctionRequest: bogus argsize %d, should be %d", argsize, fip->arglen[i]); p = palloc(argsize + 1); /* +1 in case argsize is 0 */ - if (pq_getbytes(p, argsize)) - return EOF; + pq_copymsgbytes(msgBuf, p, argsize); } fcinfo.arg[i] = PointerGetDatum(p); } } - /* - * Now that we've eaten the input message, check to see if we actually - * want to do the function call or not. - * - * Currently, we report an error if in ABORT state, or return a dummy - * NULL response if fastpath support has been compiled out. - */ - if (IsAbortedTransactionBlockState()) - elog(ERROR, "current transaction is aborted, " - "queries ignored until end of transaction block"); - - /* Check permission to call function */ - aclresult = pg_proc_aclcheck(fid, GetUserId(), ACL_EXECUTE); - if (aclresult != ACLCHECK_OK) - aclcheck_error(aclresult, get_func_name(fid)); - /* * Set up a query snapshot in case function needs one. (It is not safe * to do this if we are in transaction-abort state, so we have to postpone diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c index 1048d2fa1c6..fcc6591f7c0 100644 --- a/src/backend/tcop/postgres.c +++ b/src/backend/tcop/postgres.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/tcop/postgres.c,v 1.321 2003/04/17 22:26:01 tgl Exp $ + * $Header: /cvsroot/pgsql/src/backend/tcop/postgres.c,v 1.322 2003/04/19 00:02:29 tgl Exp $ * * NOTES * this is the "main" module of the postgres backend and @@ -133,7 +133,9 @@ static const char *CreateCommandTag(Node *parsetree); /* ---------------- * InteractiveBackend() is called for user interactive connections - * the string entered by the user is placed in its parameter inBuf. + * + * the string entered by the user is placed in its parameter inBuf, + * and we act like a Q message was received. * * EOF is returned if end-of-file input is seen; time to shut down. * ---------------- @@ -155,6 +157,7 @@ InteractiveBackend(StringInfo inBuf) /* Reset inBuf to empty */ inBuf->len = 0; inBuf->data[0] = '\0'; + inBuf->cursor = 0; for (;;) { @@ -214,6 +217,9 @@ InteractiveBackend(StringInfo inBuf) break; } + /* Add '\0' to make it look the same as message case. */ + appendStringInfoChar(inBuf, (char) '\0'); + /* * if the query echo flag was given, print the query.. */ @@ -227,66 +233,79 @@ InteractiveBackend(StringInfo inBuf) /* ---------------- * SocketBackend() Is called for frontend-backend connections * - * If the input is a query (case 'Q') then the string entered by - * the user is placed in its parameter inBuf. - * - * If the input is a fastpath function call (case 'F') then - * the function call is processed in HandleFunctionRequest() - * (now called from PostgresMain()). + * Returns the message type code, and loads message body data into inBuf. * * EOF is returned if the connection is lost. * ---------------- */ - static int SocketBackend(StringInfo inBuf) { int qtype; /* - * get input from the frontend + * Get message type code from the frontend. */ qtype = pq_getbyte(); + if (qtype == EOF) /* frontend disconnected */ + { + elog(COMMERROR, "unexpected EOF on client connection"); + return qtype; + } + + /* + * Validate message type code before trying to read body; if we have + * lost sync, better to say "command unknown" than to run out of memory + * because we used garbage as a length word. + */ switch (qtype) { - case EOF: - /* frontend disconnected */ + case 'Q': /* simple query */ + if (PG_PROTOCOL_MAJOR(FrontendProtocol) < 3) + { + /* old style without length word; convert */ + if (pq_getstring(inBuf)) + { + elog(COMMERROR, "unexpected EOF on client connection"); + return EOF; + } + } break; - /* - * 'Q': user entered a query - */ - case 'Q': - if (pq_getstr(inBuf)) - return EOF; + case 'F': /* fastpath function call */ break; - /* - * 'F': calling user/system functions - */ - case 'F': - if (pq_getstr(inBuf)) - return EOF; /* ignore "string" at start of F message */ + case 'X': /* terminate */ break; - /* - * 'X': frontend is exiting - */ - case 'X': + case 'd': /* copy data */ + case 'c': /* copy done */ + case 'f': /* copy fail */ + /* Accept but ignore these messages, per protocol spec */ break; + default: /* - * otherwise we got garbage from the frontend. - * - * XXX are we certain that we want to do an elog(FATAL) here? - * -cim 1/24/90 + * Otherwise we got garbage from the frontend. We treat this + * as fatal because we have probably lost message boundary sync, + * and there's no good way to recover. */ - default: elog(FATAL, "Socket command type %c unknown", qtype); break; } + /* + * In protocol version 3, all frontend messages have a length word + * next after the type code; we can read the message contents + * independently of the type. + */ + if (PG_PROTOCOL_MAJOR(FrontendProtocol) >= 3) + { + if (pq_getmessage(inBuf, 0)) + return EOF; /* suitable message already logged */ + } + return qtype; } @@ -1220,19 +1239,17 @@ int PostgresMain(int argc, char *argv[], const char *username) { int flag; - const char *DBName = NULL; + char *potential_DataDir = NULL; bool secure; int errs = 0; int debug_flag = 0; GucContext ctx; GucSource gucsource; char *tmp; - int firstchar; StringInfo parser_input; - - char *potential_DataDir = NULL; + bool send_rfq; /* * Catch standard options before doing much else. This even works on @@ -1815,7 +1832,7 @@ PostgresMain(int argc, char *argv[], const char *username) if (!IsUnderPostmaster) { puts("\nPOSTGRES backend interactive interface "); - puts("$Revision: 1.321 $ $Date: 2003/04/17 22:26:01 $\n"); + puts("$Revision: 1.322 $ $Date: 2003/04/19 00:02:29 $\n"); } /* @@ -1902,6 +1919,8 @@ PostgresMain(int argc, char *argv[], const char *username) PG_SETMASK(&UnBlockSig); + send_rfq = true; /* initially, or after error */ + /* * Non-error queries loop here. */ @@ -1922,7 +1941,11 @@ PostgresMain(int argc, char *argv[], const char *username) * * Note: this includes fflush()'ing the last of the prior output. */ - ReadyForQuery(whereToSendOutput); + if (send_rfq) + { + ReadyForQuery(whereToSendOutput); + send_rfq = false; + } /* ---------- * Tell the statistics collector what we've collected @@ -1986,20 +2009,36 @@ PostgresMain(int argc, char *argv[], const char *username) */ switch (firstchar) { + case 'Q': /* simple query */ /* - * 'F' indicates a fastpath call. - */ - case 'F': - /* ---------- - * Tell the collector what we're doing - * ---------- + * Process the query string. + * + * Note: transaction command start/end is now done within + * pg_exec_query_string(), not here. */ + if (log_statement_stats) + ResetUsage(); + + pgstat_report_activity(parser_input->data); + + pg_exec_query_string(parser_input, + whereToSendOutput, + QueryContext); + + if (log_statement_stats) + ShowUsage("QUERY STATISTICS"); + + send_rfq = true; + break; + + case 'F': /* fastpath function call */ + /* Tell the collector what we're doing */ pgstat_report_activity("<FASTPATH> function call"); /* start an xact for this function invocation */ start_xact_command(); - if (HandleFunctionRequest() == EOF) + if (HandleFunctionRequest(parser_input) == EOF) { /* lost frontend connection during F message input */ @@ -2015,29 +2054,8 @@ PostgresMain(int argc, char *argv[], const char *username) /* commit the function-invocation transaction */ finish_xact_command(false); - break; - - /* - * 'Q' indicates a user query - */ - case 'Q': - /* - * otherwise, process the input string. - * - * Note: transaction command start/end is now done within - * pg_exec_query_string(), not here. - */ - if (log_statement_stats) - ResetUsage(); - pgstat_report_activity(parser_input->data); - - pg_exec_query_string(parser_input, - whereToSendOutput, - QueryContext); - - if (log_statement_stats) - ShowUsage("QUERY STATISTICS"); + send_rfq = true; break; /* @@ -2064,8 +2082,18 @@ PostgresMain(int argc, char *argv[], const char *username) */ proc_exit(0); + case 'd': /* copy data */ + case 'c': /* copy done */ + case 'f': /* copy fail */ + /* + * Accept but ignore these messages, per protocol spec; + * we probably got here because a COPY failed, and the + * frontend is still sending data. + */ + break; + default: - elog(ERROR, "unknown frontend message was received"); + elog(FATAL, "Socket command type %c unknown", firstchar); } #ifdef MEMORY_CONTEXT_CHECKING diff --git a/src/include/lib/stringinfo.h b/src/include/lib/stringinfo.h index 051ce239a7a..c49a73c0eb1 100644 --- a/src/include/lib/stringinfo.h +++ b/src/include/lib/stringinfo.h @@ -10,7 +10,7 @@ * Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $Id: stringinfo.h,v 1.24 2002/06/20 20:29:49 momjian Exp $ + * $Id: stringinfo.h,v 1.25 2003/04/19 00:02:29 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -27,6 +27,9 @@ * string size (including the terminating '\0' char) that we can * currently store in 'data' without having to reallocate * more space. We must always have maxlen > len. + * cursor is initialized to zero by makeStringInfo or initStringInfo, + * but is not otherwise touched by the stringinfo.c routines. + * Some routines use it to scan through a StringInfo. *------------------------- */ typedef struct StringInfoData @@ -34,6 +37,7 @@ typedef struct StringInfoData char *data; int len; int maxlen; + int cursor; } StringInfoData; typedef StringInfoData *StringInfo; @@ -111,4 +115,10 @@ extern void appendStringInfoChar(StringInfo str, char ch); extern void appendBinaryStringInfo(StringInfo str, const char *data, int datalen); +/*------------------------ + * enlargeStringInfo + * Make sure a StringInfo's buffer can hold at least 'needed' more bytes. + */ +extern void enlargeStringInfo(StringInfo str, int needed); + #endif /* STRINGINFO_H */ diff --git a/src/include/libpq/libpq.h b/src/include/libpq/libpq.h index 04248b5c95c..cbe0b646e74 100644 --- a/src/include/libpq/libpq.h +++ b/src/include/libpq/libpq.h @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $Id: libpq.h,v 1.56 2003/01/25 05:19:47 tgl Exp $ + * $Id: libpq.h,v 1.57 2003/04/19 00:02:29 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -52,14 +52,24 @@ extern void StreamClose(int sock); extern void TouchSocketFile(void); extern void pq_init(void); extern int pq_getbytes(char *s, size_t len); -extern int pq_getstring(StringInfo s, int maxlen); +extern int pq_getstring(StringInfo s); +extern int pq_getmessage(StringInfo s, int maxlen); extern int pq_getbyte(void); extern int pq_peekbyte(void); extern int pq_putbytes(const char *s, size_t len); extern int pq_flush(void); -extern int pq_eof(void); extern int pq_putmessage(char msgtype, const char *s, size_t len); extern void pq_startcopyout(void); extern void pq_endcopyout(bool errorAbort); +/* + * prototypes for functions in be-secure.c + */ +extern int secure_initialize(void); +extern void secure_destroy(void); +extern int secure_open_server(Port *port); +extern void secure_close(Port *port); +extern ssize_t secure_read(Port *port, void *ptr, size_t len); +extern ssize_t secure_write(Port *port, void *ptr, size_t len); + #endif /* LIBPQ_H */ diff --git a/src/include/libpq/pqcomm.h b/src/include/libpq/pqcomm.h index fabfb0cb253..61aa695e272 100644 --- a/src/include/libpq/pqcomm.h +++ b/src/include/libpq/pqcomm.h @@ -9,7 +9,7 @@ * Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $Id: pqcomm.h,v 1.76 2003/04/17 22:26:01 tgl Exp $ + * $Id: pqcomm.h,v 1.77 2003/04/19 00:02:29 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -106,7 +106,7 @@ typedef union SockAddr /* The earliest and latest frontend/backend protocol version supported. */ #define PG_PROTOCOL_EARLIEST PG_PROTOCOL(1,0) -#define PG_PROTOCOL_LATEST PG_PROTOCOL(3,100) /* XXX temporary value */ +#define PG_PROTOCOL_LATEST PG_PROTOCOL(3,101) /* XXX temporary value */ typedef uint32 ProtocolVersion; /* FE/BE protocol version number */ diff --git a/src/include/libpq/pqformat.h b/src/include/libpq/pqformat.h index 829727c38f0..cb80ec2c201 100644 --- a/src/include/libpq/pqformat.h +++ b/src/include/libpq/pqformat.h @@ -6,7 +6,7 @@ * Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $Id: pqformat.h,v 1.13 2002/09/04 23:31:35 tgl Exp $ + * $Id: pqformat.h,v 1.14 2003/04/19 00:02:29 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -26,9 +26,11 @@ extern void pq_endmessage(StringInfo buf); extern int pq_puttextmessage(char msgtype, const char *str); -extern int pq_getint(int *result, int b); -extern int pq_getstr_bounded(StringInfo s, int maxlen); - -#define pq_getstr(s) pq_getstr_bounded(s, 0) +extern int pq_getmsgbyte(StringInfo msg); +extern unsigned int pq_getmsgint(StringInfo msg, int b); +extern const char *pq_getmsgbytes(StringInfo msg, int datalen); +extern void pq_copymsgbytes(StringInfo msg, char *buf, int datalen); +extern const char *pq_getmsgstring(StringInfo msg); +extern void pq_getmsgend(StringInfo msg); #endif /* PQFORMAT_H */ diff --git a/src/include/tcop/dest.h b/src/include/tcop/dest.h index bbf86ef4ca6..39063af6e16 100644 --- a/src/include/tcop/dest.h +++ b/src/include/tcop/dest.h @@ -44,7 +44,7 @@ * Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $Id: dest.h,v 1.33 2003/03/27 16:51:29 momjian Exp $ + * $Id: dest.h,v 1.34 2003/04/19 00:02:30 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -102,8 +102,6 @@ extern void EndCommand(const char *commandTag, CommandDest dest); /* Additional functions that go with destination management, more or less. */ -extern void SendCopyBegin(void); -extern void ReceiveCopyBegin(void); extern void NullCommand(CommandDest dest); extern void ReadyForQuery(CommandDest dest); diff --git a/src/include/tcop/fastpath.h b/src/include/tcop/fastpath.h index e9b961d8f05..9e0f5e6bbb9 100644 --- a/src/include/tcop/fastpath.h +++ b/src/include/tcop/fastpath.h @@ -6,13 +6,15 @@ * Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $Id: fastpath.h,v 1.13 2002/06/20 20:29:52 momjian Exp $ + * $Id: fastpath.h,v 1.14 2003/04/19 00:02:30 tgl Exp $ * *------------------------------------------------------------------------- */ #ifndef FASTPATH_H #define FASTPATH_H -extern int HandleFunctionRequest(void); +#include "lib/stringinfo.h" + +extern int HandleFunctionRequest(StringInfo msgBuf); #endif /* FASTPATH_H */ diff --git a/src/interfaces/libpq/fe-auth.c b/src/interfaces/libpq/fe-auth.c index fca2d2e3035..10e2ee15f13 100644 --- a/src/interfaces/libpq/fe-auth.c +++ b/src/interfaces/libpq/fe-auth.c @@ -10,7 +10,7 @@ * exceed INITIAL_EXPBUFFER_SIZE (currently 256 bytes). * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/interfaces/libpq/fe-auth.c,v 1.75 2003/04/17 22:26:01 tgl Exp $ + * $Header: /cvsroot/pgsql/src/interfaces/libpq/fe-auth.c,v 1.76 2003/04/19 00:02:30 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -559,7 +559,7 @@ pg_password_sendauth(PGconn *conn, const char *password, AuthRequest areq) default: return STATUS_ERROR; } - ret = pqPacketSend(conn, 0, crypt_pwd, strlen(crypt_pwd) + 1); + ret = pqPacketSend(conn, 'p', crypt_pwd, strlen(crypt_pwd) + 1); if (areq == AUTH_REQ_MD5) free(crypt_pwd); return ret; diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c index 9f5c8714a68..a322d8a73d1 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.232 2003/04/17 22:26:02 tgl Exp $ + * $Header: /cvsroot/pgsql/src/interfaces/libpq/fe-connect.c,v 1.233 2003/04/19 00:02:30 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -1820,11 +1820,11 @@ makeEmptyPGconn(void) #endif /* - * The output buffer size is set to 8K, which is the usual size of - * pipe buffers on Unix systems. That way, when we are sending a + * We try to send at least 8K at a time, which is the usual size + * of pipe buffers on Unix systems. That way, when we are sending a * large amount of data, we avoid incurring extra kernel context swaps - * for partial bufferloads. Note that we currently don't ever enlarge - * the output buffer. + * for partial bufferloads. The output buffer is initially made 16K + * in size, and we try to dump it after accumulating 8K. * * With the same goal of minimizing context swaps, the input buffer will * be enlarged anytime it has less than 8K free, so we initially @@ -1832,7 +1832,7 @@ makeEmptyPGconn(void) */ conn->inBufSize = 16 * 1024; conn->inBuffer = (char *) malloc(conn->inBufSize); - conn->outBufSize = 8 * 1024; + conn->outBufSize = 16 * 1024; conn->outBuffer = (char *) malloc(conn->outBufSize); conn->nonblocking = FALSE; initPQExpBuffer(&conn->errorMessage); @@ -1918,11 +1918,10 @@ closePGconn(PGconn *conn) { /* * Try to send "close connection" message to backend. Ignore any - * error. Note: this routine used to go to substantial lengths to - * avoid getting SIGPIPE'd if the connection were already closed. - * Now we rely on pqFlush to avoid the signal. + * error. */ - pqPutc('X', conn); + pqPutMsgStart('X', conn); + pqPutMsgEnd(conn); pqFlush(conn); } @@ -2152,7 +2151,7 @@ cancel_errReturn: /* - * pqPacketSend() -- send a single-packet message. + * pqPacketSend() -- convenience routine to send a message to server. * * pack_type: the single-byte message type code. (Pass zero for startup * packets, which have no message type code.) @@ -2167,19 +2166,18 @@ int pqPacketSend(PGconn *conn, char pack_type, const void *buf, size_t buf_len) { - /* Send the message type. */ - if (pack_type != 0) - if (pqPutc(pack_type, conn)) - return STATUS_ERROR; - - /* Send the (self-inclusive) message length word. */ - if (pqPutInt(buf_len + 4, 4, conn)) + /* Start the message. */ + if (pqPutMsgStart(pack_type, conn)) return STATUS_ERROR; /* Send the message body. */ if (pqPutnchar(buf, buf_len, conn)) return STATUS_ERROR; + /* Finish the message. */ + if (pqPutMsgEnd(conn)) + return STATUS_ERROR; + /* Flush to ensure backend gets it. */ if (pqFlush(conn)) return STATUS_ERROR; @@ -2624,7 +2622,7 @@ build_startup_packet(const PGconn *conn, char *packet) packet_len += sizeof(ProtocolVersion); /* Add user name, database name, options */ - if (conn->pguser) + if (conn->pguser && conn->pguser[0]) { if (packet) strcpy(packet + packet_len, "user"); @@ -2633,7 +2631,7 @@ build_startup_packet(const PGconn *conn, char *packet) strcpy(packet + packet_len, conn->pguser); packet_len += strlen(conn->pguser) + 1; } - if (conn->dbName) + if (conn->dbName && conn->dbName[0]) { if (packet) strcpy(packet + packet_len, "database"); @@ -2642,7 +2640,7 @@ build_startup_packet(const PGconn *conn, char *packet) strcpy(packet + packet_len, conn->dbName); packet_len += strlen(conn->dbName) + 1; } - if (conn->pgoptions) + if (conn->pgoptions && conn->pgoptions[0]) { if (packet) strcpy(packet + packet_len, "options"); diff --git a/src/interfaces/libpq/fe-exec.c b/src/interfaces/libpq/fe-exec.c index 9e86b3aa672..487acff83df 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.128 2003/03/25 02:44:36 momjian Exp $ + * $Header: /cvsroot/pgsql/src/interfaces/libpq/fe-exec.c,v 1.129 2003/04/19 00:02:30 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -734,7 +734,6 @@ addTuple(PGresult *res, PGresAttValue * tup) * Returns: 1 if successfully submitted * 0 if error (conn->errorMessage is set) */ - int PQsendQuery(PGconn *conn, const char *query) { @@ -770,51 +769,24 @@ PQsendQuery(PGconn *conn, const char *query) conn->result = NULL; conn->curTuple = NULL; - /* send the query to the backend; */ + /* construct the outgoing Query message */ + if (pqPutMsgStart('Q', conn) < 0 || + pqPuts(query, conn) < 0 || + pqPutMsgEnd(conn) < 0) + { + handleSendFailure(conn); + return 0; + } /* - * in order to guarantee that we don't send a partial query where we - * would become out of sync with the backend and/or block during a - * non-blocking connection we must first flush the send buffer before - * sending more data - * - * an alternative is to implement 'queue reservations' where we are able - * to roll up a transaction (the 'Q' along with our query) and make - * sure we have enough space for it all in the send buffer. + * Give the data a push. In nonblock mode, don't complain if we're + * unable to send it all; PQconsumeInput() will do any additional flushing + * needed. */ - if (pqIsnonblocking(conn)) + if (pqFlush(conn) < 0) { - /* - * the buffer must have emptied completely before we allow a new - * query to be buffered - */ - if (pqFlush(conn)) - return 0; - /* 'Q' == queries */ - /* XXX: if we fail here we really ought to not block */ - if (pqPutc('Q', conn) != 0 || pqPuts(query, conn) != 0) - { - handleSendFailure(conn); - return 0; - } - - /* - * give the data a push, ignore the return value as ConsumeInput() - * will do any additional flushing if needed - */ - pqFlush(conn); - } - else - { - /* - * the frontend-backend protocol uses 'Q' to designate queries - */ - if (pqPutc('Q', conn) != 0 || pqPuts(query, conn) != 0 || - pqFlush(conn) != 0) - { - handleSendFailure(conn); - return 0; - } + handleSendFailure(conn); + return 0; } /* OK, it's launched! */ @@ -830,7 +802,6 @@ PQsendQuery(PGconn *conn, const char *query) * * NOTE: this routine should only be called in PGASYNC_IDLE state. */ - static void handleSendFailure(PGconn *conn) { @@ -854,13 +825,23 @@ handleSendFailure(PGconn *conn) * 0 return: some kind of trouble * 1 return: no problem */ - int PQconsumeInput(PGconn *conn) { if (!conn) return 0; + /* + * for non-blocking connections try to flush the send-queue, + * otherwise we may never get a response for something that may + * not have already been sent because it's in our write buffer! + */ + if (pqIsnonblocking(conn)) + { + if (pqFlush(conn) < 0) + return 0; + } + /* * Load more data, if available. We do this no matter what state we * are in, since we are probably getting called because the @@ -868,16 +849,8 @@ PQconsumeInput(PGconn *conn) * we will NOT block waiting for more input. */ if (pqReadData(conn) < 0) - { - /* - * for non-blocking connections try to flush the send-queue - * otherwise we may never get a responce for something that may - * not have already been sent because it's in our write buffer! - */ - if (pqIsnonblocking(conn)) - (void) pqFlush(conn); return 0; - } + /* Parsing of the data waits till later. */ return 1; } @@ -1733,14 +1706,13 @@ PQgetlineAsync(PGconn *conn, char *buffer, int bufsize) * PQputline -- sends a string to the backend. * Returns 0 if OK, EOF if not. * - * Chiefly here so that applications can use "COPY <rel> from stdin". + * This exists to support "COPY <rel> from stdin". The backend will ignore + * the string if not doing COPY. */ int PQputline(PGconn *conn, const char *s) { - if (!conn || conn->sock < 0) - return EOF; - return pqPutnchar(s, strlen(s), conn); + return PQputnbytes(conn, s, strlen(s)); } /* @@ -1752,7 +1724,14 @@ PQputnbytes(PGconn *conn, const char *buffer, int nbytes) { if (!conn || conn->sock < 0) return EOF; - return pqPutnchar(buffer, nbytes, conn); + if (nbytes > 0) + { + if (pqPutMsgStart('d', conn) < 0 || + pqPutnchar(buffer, nbytes, conn) < 0 || + pqPutMsgEnd(conn) < 0) + return EOF; + } + return 0; } /* @@ -1780,6 +1759,14 @@ PQendcopy(PGconn *conn) return 1; } + /* Send the CopyDone message if needed */ + if (conn->asyncStatus == PGASYNC_COPY_IN) + { + if (pqPutMsgStart('c', conn) < 0 || + pqPutMsgEnd(conn) < 0) + return 1; + } + /* * make sure no data is waiting to be sent, abort if we are * non-blocking and the flush fails @@ -1884,9 +1871,10 @@ PQfn(PGconn *conn, return NULL; } - if (pqPuts("F ", conn) != 0 || /* function */ - pqPutInt(fnid, 4, conn) != 0 || /* function id */ - pqPutInt(nargs, 4, conn) != 0) /* # of args */ + if (pqPutMsgStart('F', conn) < 0 || /* function call msg */ + pqPuts("", conn) < 0 || /* useless string */ + pqPutInt(fnid, 4, conn) < 0 || /* function id */ + pqPutInt(nargs, 4, conn) < 0) /* # of args */ { handleSendFailure(conn); return NULL; @@ -1917,7 +1905,9 @@ PQfn(PGconn *conn, } } } - if (pqFlush(conn)) + + if (pqPutMsgEnd(conn) < 0 || + pqFlush(conn)) { handleSendFailure(conn); return NULL; @@ -2409,7 +2399,6 @@ PQgetisnull(const PGresult *res, int tup_num, int field_num) int PQsetnonblocking(PGconn *conn, int arg) { - arg = (arg == TRUE) ? 1 : 0; /* early out if the socket is already in the state requested */ if (arg == conn->nonblocking) @@ -2437,7 +2426,6 @@ PQsetnonblocking(PGconn *conn, int arg) int PQisnonblocking(const PGconn *conn) { - return (pqIsnonblocking(conn)); } @@ -2445,18 +2433,9 @@ PQisnonblocking(const PGconn *conn) int PQflush(PGconn *conn) { - return (pqFlush(conn)); } -/* try to force data out, really only useful for non-blocking users. - * This implementation actually works for non-blocking connections */ -int -PQsendSome(PGconn *conn) -{ - return pqSendSome(conn); -} - /* * PQfreeNotify - free's the memory associated with a PGnotify * @@ -2473,5 +2452,3 @@ PQfreeNotify(PGnotify *notify) { PQfreemem(notify); } - - diff --git a/src/interfaces/libpq/fe-misc.c b/src/interfaces/libpq/fe-misc.c index 0f971343ccc..dfc46fdf598 100644 --- a/src/interfaces/libpq/fe-misc.c +++ b/src/interfaces/libpq/fe-misc.c @@ -16,16 +16,14 @@ * will cause repeat printouts. * * We must speak the same transmitted data representations as the backend - * routines. Note that this module supports *only* network byte order - * for transmitted ints, whereas the backend modules (as of this writing) - * still handle either network or little-endian byte order. + * routines. + * * * Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/interfaces/libpq/fe-misc.c,v 1.88 2003/04/02 00:49:28 tgl Exp $ + * $Header: /cvsroot/pgsql/src/interfaces/libpq/fe-misc.c,v 1.89 2003/04/19 00:02:30 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -63,15 +61,15 @@ #define DONOTICE(conn,message) \ ((*(conn)->noticeHook) ((conn)->noticeArg, (message))) +static int pqPutMsgBytes(const void *buf, size_t len, PGconn *conn); +static int pqSendSome(PGconn *conn, int len); static int pqSocketCheck(PGconn *conn, int forRead, int forWrite, time_t end_time); static int pqSocketPoll(int sock, int forRead, int forWrite, time_t end_time); -static int pqPutBytes(const char *s, size_t nbytes, PGconn *conn); /* - * pqGetc: - * get a character from the connection + * pqGetc: get 1 character from the connection * * All these routines return 0 on success, EOF on error. * Note that for the Get routines, EOF only means there is not enough @@ -93,12 +91,12 @@ pqGetc(char *result, PGconn *conn) /* - * write 1 char to the connection + * pqPutc: write 1 char to the current message */ int pqPutc(char c, PGconn *conn) { - if (pqPutBytes(&c, 1, conn) == EOF) + if (pqPutMsgBytes(&c, 1, conn)) return EOF; if (conn->Pfdebug) @@ -108,93 +106,6 @@ pqPutc(char c, PGconn *conn) } -/* - * pqPutBytes: local routine to write N bytes to the connection, - * with buffering - */ -static int -pqPutBytes(const char *s, size_t nbytes, PGconn *conn) -{ - /* - * Strategy to handle blocking and non-blocking connections: Fill the - * output buffer and flush it repeatedly until either all data has - * been sent or is at least queued in the buffer. - * - * For non-blocking connections, grow the buffer if not all data fits - * into it and the buffer can't be sent because the socket would - * block. - */ - - while (nbytes) - { - size_t avail, - remaining; - - /* fill the output buffer */ - avail = Max(conn->outBufSize - conn->outCount, 0); - remaining = Min(avail, nbytes); - memcpy(conn->outBuffer + conn->outCount, s, remaining); - conn->outCount += remaining; - s += remaining; - nbytes -= remaining; - - /* - * if the data didn't fit completely into the buffer, try to flush - * the buffer - */ - if (nbytes) - { - int send_result = pqSendSome(conn); - - /* if there were errors, report them */ - if (send_result < 0) - return EOF; - - /* - * if not all data could be sent, increase the output buffer, - * put the rest of s into it and return successfully. This - * case will only happen in a non-blocking connection - */ - if (send_result > 0) - { - /* - * try to grow the buffer. FIXME: The new size could be - * chosen more intelligently. - */ - size_t buflen = (size_t) conn->outCount + nbytes; - - if (buflen > (size_t) conn->outBufSize) - { - char *newbuf = realloc(conn->outBuffer, buflen); - - if (!newbuf) - { - /* realloc failed. Probably out of memory */ - printfPQExpBuffer(&conn->errorMessage, - "cannot allocate memory for output buffer\n"); - return EOF; - } - conn->outBuffer = newbuf; - conn->outBufSize = buflen; - } - /* put the data into it */ - memcpy(conn->outBuffer + conn->outCount, s, nbytes); - conn->outCount += nbytes; - - /* report success. */ - return 0; - } - } - - /* - * pqSendSome was able to send all data. Continue with the next - * chunk of s. - */ - } /* while */ - - return 0; -} - /* * pqGets: * get a null-terminated string from the connection, @@ -232,14 +143,17 @@ pqGets(PQExpBuffer buf, PGconn *conn) } +/* + * pqPuts: write a null-terminated string to the current message + */ int pqPuts(const char *s, PGconn *conn) { - if (pqPutBytes(s, strlen(s) + 1, conn)) + if (pqPutMsgBytes(s, strlen(s) + 1, conn)) return EOF; if (conn->Pfdebug) - fprintf(conn->Pfdebug, "To backend> %s\n", s); + fprintf(conn->Pfdebug, "To backend> '%s'\n", s); return 0; } @@ -267,12 +181,12 @@ pqGetnchar(char *s, size_t len, PGconn *conn) /* * pqPutnchar: - * send a string of exactly len bytes, no null termination needed + * write exactly len bytes to the current message */ int pqPutnchar(const char *s, size_t len, PGconn *conn) { - if (pqPutBytes(s, len, conn)) + if (pqPutMsgBytes(s, len, conn)) return EOF; if (conn->Pfdebug) @@ -325,7 +239,7 @@ pqGetInt(int *result, size_t bytes, PGconn *conn) /* * pgPutInt - * send an integer of 2 or 4 bytes, converting from host byte order + * write an integer of 2 or 4 bytes, converting from host byte order * to network byte order. */ int @@ -339,12 +253,12 @@ pqPutInt(int value, size_t bytes, PGconn *conn) { case 2: tmp2 = htons((uint16) value); - if (pqPutBytes((const char *) &tmp2, 2, conn)) + if (pqPutMsgBytes((const char *) &tmp2, 2, conn)) return EOF; break; case 4: tmp4 = htonl((uint32) value); - if (pqPutBytes((const char *) &tmp4, 4, conn)) + if (pqPutMsgBytes((const char *) &tmp4, 4, conn)) return EOF; break; default: @@ -362,24 +276,162 @@ pqPutInt(int value, size_t bytes, PGconn *conn) } /* - * pqReadReady: is select() saying the file is ready to read? - * JAB: -or- if SSL is enabled and used, is it buffering bytes? - * Returns -1 on failure, 0 if not ready, 1 if ready. + * Make sure conn's output buffer can hold bytes_needed bytes (caller must + * include existing outCount into the value!) + * + * Returns 0 on success, EOF on error + */ +static int +checkOutBufferSpace(int bytes_needed, PGconn *conn) +{ + int newsize = conn->outBufSize; + 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->outBuffer, newsize); + if (newbuf) + { + /* realloc succeeded */ + conn->outBuffer = newbuf; + conn->outBufSize = newsize; + return 0; + } + } + + newsize = conn->outBufSize; + do { + newsize += 8192; + } while (bytes_needed > newsize && newsize > 0); + + if (bytes_needed <= newsize) + { + newbuf = realloc(conn->outBuffer, newsize); + if (newbuf) + { + /* realloc succeeded */ + conn->outBuffer = newbuf; + conn->outBufSize = newsize; + return 0; + } + } + + /* realloc failed. Probably out of memory */ + printfPQExpBuffer(&conn->errorMessage, + "cannot allocate memory for output 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 + * (only startup messages have no type byte) + * + * Returns 0 on success, EOF on error + * + * The idea here is that we construct the message in conn->outBuffer, + * beginning just past any data already in outBuffer (ie, at + * outBuffer+outCount). We enlarge the buffer as needed to hold the message. + * When the message is complete, we fill in the length word and then advance + * outCount past the message, making it eligible to send. The state + * variable conn->outMsgStart points to the incomplete message's length word + * (it is either outCount or outCount+1 depending on whether there is a + * type byte). The state variable conn->outMsgEnd is the end of the data + * collected so far. */ int -pqReadReady(PGconn *conn) +pqPutMsgStart(char msg_type, PGconn *conn) { - return pqSocketCheck(conn, 1, 0, (time_t) 0); + int lenPos; + + /* where the message length word will go */ + if (msg_type) + lenPos = conn->outCount + 1; + else + lenPos = conn->outCount; + /* make sure there is room for it */ + if (checkOutBufferSpace(lenPos + 4, conn)) + return EOF; + /* okay, save the message type byte if any */ + if (msg_type) + conn->outBuffer[conn->outCount] = msg_type; + /* set up the message pointers */ + conn->outMsgStart = lenPos; + conn->outMsgEnd = lenPos + 4; + /* length word will be filled in by pqPutMsgEnd */ + + if (conn->Pfdebug) + fprintf(conn->Pfdebug, "To backend> Msg %c\n", + msg_type ? msg_type : ' '); + + return 0; } /* - * pqWriteReady: is select() saying the file is ready to write? - * Returns -1 on failure, 0 if not ready, 1 if ready. + * pqPutMsgBytes: add bytes to a partially-constructed message + * + * Returns 0 on success, EOF on error + */ +static int +pqPutMsgBytes(const void *buf, size_t len, PGconn *conn) +{ + /* make sure there is room for it */ + if (checkOutBufferSpace(conn->outMsgEnd + len, conn)) + return EOF; + /* okay, save the data */ + memcpy(conn->outBuffer + conn->outMsgEnd, buf, len); + conn->outMsgEnd += len; + /* no Pfdebug call here, caller should do it */ + return 0; +} + +/* + * pqPutMsgEnd: finish constructing a message and possibly send it + * + * Returns 0 on success, EOF on error + * + * We don't actually send anything here unless we've accumulated at least + * 8K worth of data (the typical size of a pipe buffer on Unix systems). + * This avoids sending small partial packets. The caller must use pqFlush + * when it's important to flush all the data out to the server. */ int -pqWriteReady(PGconn *conn) +pqPutMsgEnd(PGconn *conn) { - return pqSocketCheck(conn, 0, 1, (time_t) 0); + uint32 msgLen = conn->outMsgEnd - conn->outMsgStart; + + if (conn->Pfdebug) + fprintf(conn->Pfdebug, "To backend> Msg complete, length %u\n", + msgLen); + + msgLen = htonl(msgLen); + memcpy(conn->outBuffer + conn->outMsgStart, &msgLen, 4); + conn->outCount = conn->outMsgEnd; + + if (conn->outCount >= 8192) + { + int toSend = conn->outCount - (conn->outCount % 8192); + + if (pqSendSome(conn, toSend) < 0) + return EOF; + /* in nonblock mode, don't complain if unable to send it all */ + } + + return 0; } /* ---------- @@ -580,16 +632,20 @@ definitelyFailed: } /* - * pqSendSome: send any data waiting in the output buffer. + * pqSendSome: send data waiting in the output buffer. + * + * len is how much to try to send (typically equal to outCount, but may + * be less). * - * Return 0 on sucess, -1 on failure and 1 when data remains because the - * socket would block and the connection is non-blocking. + * Return 0 on success, -1 on failure and 1 when not all data could be sent + * because the socket would block and the connection is non-blocking. */ -int -pqSendSome(PGconn *conn) +static int +pqSendSome(PGconn *conn, int len) { char *ptr = conn->outBuffer; - int len = conn->outCount; + int remaining = conn->outCount; + int result = 0; if (conn->sock < 0) { @@ -598,13 +654,6 @@ pqSendSome(PGconn *conn) return -1; } - /* - * don't try to send zero data, allows us to use this function without - * too much worry about overhead - */ - if (len == 0) - return (0); - /* while there's still data to send */ while (len > 0) { @@ -648,8 +697,9 @@ pqSendSome(PGconn *conn) * (typically, a NOTICE message from the backend * telling us it's committing hara-kiri...). Leave * the socket open until pqReadData finds no more data - * can be read. + * can be read. But abandon attempt to send data. */ + conn->outCount = 0; return -1; default: @@ -657,6 +707,7 @@ pqSendSome(PGconn *conn) libpq_gettext("could not send data to server: %s\n"), SOCK_STRERROR(SOCK_ERRNO)); /* We don't assume it's a fatal error... */ + conn->outCount = 0; return -1; } } @@ -664,6 +715,7 @@ pqSendSome(PGconn *conn) { ptr += sent; len -= sent; + remaining -= sent; } if (len > 0) @@ -681,46 +733,49 @@ pqSendSome(PGconn *conn) #endif if (pqIsnonblocking(conn)) { - /* shift the contents of the buffer */ - memmove(conn->outBuffer, ptr, len); - conn->outCount = len; - return 1; + result = 1; + break; } #ifdef USE_SSL } #endif if (pqWait(FALSE, TRUE, conn)) - return -1; + { + result = -1; + break; + } } } - conn->outCount = 0; + /* shift the remaining contents of the buffer */ + if (remaining > 0) + memmove(conn->outBuffer, ptr, remaining); + conn->outCount = remaining; - if (conn->Pfdebug) - fflush(conn->Pfdebug); - - return 0; + return result; } - /* * pqFlush: send any data waiting in the output buffer * - * Implemented in terms of pqSendSome to recreate the old behavior which - * returned 0 if all data was sent or EOF. EOF was sent regardless of - * whether an error occurred or not all data was sent on a non-blocking - * socket. + * Return 0 on success, -1 on failure and 1 when not all data could be sent + * because the socket would block and the connection is non-blocking. */ int pqFlush(PGconn *conn) { - if (pqSendSome(conn)) - return EOF; + if (conn->Pfdebug) + fflush(conn->Pfdebug); + + if (conn->outCount > 0) + return pqSendSome(conn, conn->outCount); + return 0; } + /* * pqWait: wait until we can read or write the connection socket * @@ -766,10 +821,31 @@ pqWaitTimed(int forRead, int forWrite, PGconn *conn, time_t finish_time) return 0; } +/* + * pqReadReady: is select() saying the file is ready to read? + * Returns -1 on failure, 0 if not ready, 1 if ready. + */ +int +pqReadReady(PGconn *conn) +{ + return pqSocketCheck(conn, 1, 0, (time_t) 0); +} + +/* + * pqWriteReady: is select() saying the file is ready to write? + * Returns -1 on failure, 0 if not ready, 1 if ready. + */ +int +pqWriteReady(PGconn *conn) +{ + return pqSocketCheck(conn, 0, 1, (time_t) 0); +} + /* * Checks a socket, using poll or select, for data to be read, written, * or both. Returns >0 if one or more conditions are met, 0 if it timed * out, -1 if an error occurred. + * * If SSL is in use, the SSL buffer is checked prior to checking the socket * for read data directly. */ @@ -787,8 +863,8 @@ pqSocketCheck(PGconn *conn, int forRead, int forWrite, time_t end_time) return -1; } -/* JAB: Check for SSL library buffering read bytes */ #ifdef USE_SSL + /* Check for SSL library buffering read bytes */ if (forRead && conn->ssl && SSL_pending(conn->ssl) > 0) { /* short-circuit the select */ @@ -819,6 +895,7 @@ pqSocketCheck(PGconn *conn, int forRead, int forWrite, time_t end_time) * If neither forRead nor forWrite are set, immediately return a timeout * condition (without waiting). Return >0 if condition is met, 0 * if a timeout occurred, -1 if an error or interrupt occurred. + * * Timeout is infinite if end_time is -1. Timeout is immediate (no blocking) * if end_time is 0 (or indeed, any time before now). */ @@ -830,16 +907,17 @@ pqSocketPoll(int sock, int forRead, int forWrite, time_t end_time) struct pollfd input_fd; int timeout_ms; + if (!forRead && !forWrite) + return 0; + input_fd.fd = sock; - input_fd.events = 0; + input_fd.events = POLLERR; input_fd.revents = 0; if (forRead) input_fd.events |= POLLIN; if (forWrite) input_fd.events |= POLLOUT; - if (!input_fd.events) - return 0; /* Compute appropriate timeout interval */ if (end_time == ((time_t) -1)) diff --git a/src/interfaces/libpq/libpq-fe.h b/src/interfaces/libpq/libpq-fe.h index d32b6fdeea3..a86b63eadae 100644 --- a/src/interfaces/libpq/libpq-fe.h +++ b/src/interfaces/libpq/libpq-fe.h @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $Id: libpq-fe.h,v 1.91 2003/03/25 02:44:36 momjian Exp $ + * $Id: libpq-fe.h,v 1.92 2003/04/19 00:02:30 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -284,7 +284,6 @@ extern int PQisnonblocking(const PGconn *conn); /* Force the write buffer to be written (or at least try) */ extern int PQflush(PGconn *conn); -extern int PQsendSome(PGconn *conn); /* * "Fast path" interface --- not really recommended for application diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h index 43c3bd11c56..8671922547d 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.61 2003/04/17 22:26:02 tgl Exp $ + * $Id: libpq-int.h,v 1.62 2003/04/19 00:02:30 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,100) /* XXX temporary value */ +#define PG_PROTOCOL_LIBPQ PG_PROTOCOL(3,101) /* XXX temporary value */ /* * POSTGRES backend dependent Constants. @@ -266,6 +266,10 @@ struct pg_conn int outBufSize; /* allocated size of buffer */ int outCount; /* number of chars waiting in buffer */ + /* State for constructing messages in outBuffer */ + int outMsgStart; /* offset to msg start (length word) */ + int outMsgEnd; /* offset to msg end (so far) */ + /* Status for asynchronous result construction */ PGresult *result; /* result being constructed */ PGresAttValue *curTuple; /* tuple currently being read */ @@ -334,9 +338,10 @@ extern int pqGetnchar(char *s, size_t len, PGconn *conn); extern int pqPutnchar(const char *s, size_t len, PGconn *conn); extern int pqGetInt(int *result, size_t bytes, PGconn *conn); extern int pqPutInt(int value, size_t bytes, PGconn *conn); +extern int pqPutMsgStart(char msg_type, PGconn *conn); +extern int pqPutMsgEnd(PGconn *conn); extern int pqReadData(PGconn *conn); extern int pqFlush(PGconn *conn); -extern int pqSendSome(PGconn *conn); extern int pqWait(int forRead, int forWrite, PGconn *conn); extern int pqWaitTimed(int forRead, int forWrite, PGconn *conn, time_t finish_time); -- GitLab