diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml
index 7a7bb48ff390d26a8ffa25b6ad5e4c76807b55e1..5ba7e6468c313c81085002e0555cde8d2bb41886 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.119 2003/04/19 00:02:29 tgl Exp $
+$Header: /cvsroot/pgsql/doc/src/sgml/libpq.sgml,v 1.120 2003/04/22 00:08:06 tgl Exp $
 -->
 
  <chapter id="libpq">
@@ -175,7 +175,8 @@ PGconn *PQconnectdb(const char *conninfo);
      <term><literal>connect_timeout</literal></term>
      <listitem>
      <para>
-      Time space in seconds given to connection function. Zero or not set means infinite.
+      Maximum wait for connection, in seconds (write as a decimal integer
+      string). Zero or not specified means infinite.
      </para>
      </listitem>
     </varlistentry>
@@ -184,7 +185,7 @@ PGconn *PQconnectdb(const char *conninfo);
      <term><literal>options</literal></term>
      <listitem>
       <para>
-       Configuration options to be sent to the server.
+       Command-line options to be sent to the server.
       </para>
      </listitem>
     </varlistentry>
@@ -252,8 +253,9 @@ PGconn *PQsetdbLogin(const char *pghost,
 </para>
 
 <para>
-   This is the predecessor of <function>PQconnectdb</function> with a fixed number
-   of parameters but the same functionality.   
+   This is the predecessor of <function>PQconnectdb</function> with a fixed
+   number of parameters.  It has the same functionality except that the
+   missing parameters cannot be specified in the call.
    </para>
   </listitem>
  </varlistentry>
@@ -274,8 +276,8 @@ PGconn *PQsetdb(char *pghost,
 
 <para>
    This is a macro that calls <function>PQsetdbLogin</function> with null pointers
-   for the <parameter>login</> and <parameter>pwd</> parameters.  It is provided primarily
-   for backward compatibility with old programs.
+   for the <parameter>login</> and <parameter>pwd</> parameters.  It is provided
+   for backward compatibility with very old programs.
    </para>
   </listitem>
  </varlistentry>
@@ -454,7 +456,7 @@ switch(PQstatus(conn))
   </para>
 
   <para>
-   Finally, these functions leave the socket in a nonblocking state as if 
+   Finally, these functions leave the connection in a nonblocking state as if 
    <function>PQsetnonblocking</function> had been called.
   </para>
   </listitem>
@@ -486,8 +488,6 @@ typedef struct
 </para>
 
 <para>
-   converts an escaped string representation of binary data into binary
-   data --- the reverse of <function>PQescapeBytea</function>.
    Returns a connection options array.  This may
    be used to determine all possible <function>PQconnectdb</function> options and their
    current default values.  The return value points to an array of
@@ -683,7 +683,7 @@ char *PQtty(const PGconn *conn);
 <term><function>PQoptions</function></term>
 <listitem>
 <para>
-       Returns the configuration options passed in the connection request.
+       Returns the command-line options passed in the connection request.
 <synopsis>
 char *PQoptions(const PGconn *conn);
 </synopsis>
@@ -2047,13 +2047,13 @@ contains example functions that correctly handle the <command>COPY</command> pro
 <term><function>PQgetlineAsync</function></term>
 <listitem>
 <para>
-          Reads  a  newline-terminated  line  of  characters
+          Reads a row of COPY data
           (transmitted  by the server) into a buffer
           without blocking.
 <synopsis>
 int PQgetlineAsync(PGconn *conn,
                    char *buffer,
-                   int length);
+                   int bufsize);
 </synopsis>
 </para>
 
@@ -2070,24 +2070,27 @@ end-of-data signal is detected.
 <para>
 Unlike <function>PQgetline</function>, this function takes
 responsibility for detecting end-of-data.
-On each call, <function>PQgetlineAsync</function> will return data if a complete newline-
-terminated data line is available in <application>libpq</>'s input buffer, or if the
-incoming data line is too long to fit in the buffer offered by the caller.
-Otherwise, no data is returned until the rest of the line arrives.
+</para>
+<para>
+On each call, <function>PQgetlineAsync</function> will return data if a
+complete data row is available in <application>libpq</>'s input buffer.
+Otherwise, no data is returned until the rest of the row arrives.
 The function returns -1 if the end-of-copy-data marker has been recognized,
 or 0 if no data is available, or a positive number giving the number of
 bytes of data returned.  If -1 is returned, the caller must next call
 <function>PQendcopy</function>, and then return to normal processing.
 </para>
 <para>
-The data returned will not extend beyond a newline character.  If possible
-a whole line will be returned at one time.  But if the buffer offered by
-the caller is too small to hold a line sent by the server, then a partial
-data line will be returned.  This can be detected by testing whether the
-last returned byte is <literal>\n</literal> or not.
+The data returned will not extend beyond a data-row boundary.  If possible
+a whole row will be returned at one time.  But if the buffer offered by
+the caller is too small to hold a row sent by the server, then a partial
+data row will be returned.  With textual data this can be detected by testing
+whether the last returned byte is <literal>\n</literal> or not.  (In a binary
+COPY, actual parsing of the COPY data format will be needed to make the
+equivalent determination.)
 The returned string is not null-terminated.  (If you want to add a
-terminating null, be sure to pass a <parameter>length</parameter> one smaller than the room
-actually available.)
+terminating null, be sure to pass a <parameter>bufsize</parameter> one smaller
+than the room actually available.)
 </para>
 </listitem>
 </varlistentry>
@@ -2105,10 +2108,24 @@ int PQputline(PGconn *conn,
 </para>
 
 <para>
-Note the application must explicitly  send  the  two
-characters  <literal>\.</literal> on a final line  to indicate to
-the server that it has finished sending its data.
+The COPY datastream sent by a series of calls to
+<function>PQputline</function> has the same format as that returned by
+<function>PQgetlineAsync</function>, except that applications are not
+obliged to send exactly one data row per <function>PQputline</function>
+call; it is okay to send a partial line or multiple lines per call.
 </para>
+
+<note>
+<para>
+Before <productname>PostgreSQL</productname> 7.4, it was necessary for the
+application to explicitly send the two characters <literal>\.</literal> as a
+final line to indicate to the server that it had finished sending COPY data.
+While this still works, it is deprecated and the special meaning of
+<literal>\.</literal> can be expected to be removed in a future release.
+It is sufficient to call <function>PQendcopy</function> after having sent the
+actual data.
+</para>
+</note>
 </listitem>
 </varlistentry>
 
@@ -2126,9 +2143,9 @@ int PQputnbytes(PGconn *conn,
 </para>
 
 <para>
-This is exactly like <function>PQputline</function>, except that the data buffer need
-not be null-terminated since the number of bytes to send is
-specified directly.
+This is exactly like <function>PQputline</function>, except that the data
+buffer need not be null-terminated since the number of bytes to send is
+specified directly.  Use this procedure when sending binary data.
 </para>
 </listitem>
 </varlistentry>
@@ -2147,11 +2164,12 @@ int PQendcopy(PGconn *conn);
  sent  to  the  server using <function>PQputline</function> or when the
  last string has been  received  from  the  server
  using <function>PGgetline</function>.  It must be issued or the server
- may get <quote>out of sync</quote> with  the client.   Upon
+ will get <quote>out of sync</quote> with  the client.   Upon
  return from this function, the server is ready to
  receive the next SQL command.
  The return value is 0  on  successful  completion,
- nonzero otherwise.
+ nonzero otherwise.  (Use <function>PQerrorMessage</function> to retrieve
+ details if the return value is nonzero.)
 </para>
 
 <para>
@@ -2187,7 +2205,6 @@ PQexec(conn, "COPY foo FROM STDIN;");
 PQputline(conn, "3\thello world\t4.5\n");
 PQputline(conn, "4\tgoodbye world\t7.11\n");
 ...
-PQputline(conn, "\\.\n");
 PQendcopy(conn);
 </programlisting>
 </para>
diff --git a/doc/src/sgml/protocol.sgml b/doc/src/sgml/protocol.sgml
index 529baa1f31be37784e1b17dad8d805e27785b57c..52d2a60c3b2bc0169463cd587ca7e58ebaff41a0 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.28 2003/04/19 00:02:29 tgl Exp $ -->
+<!-- $Header: /cvsroot/pgsql/doc/src/sgml/protocol.sgml,v 1.29 2003/04/22 00:08:06 tgl Exp $ -->
 
 <chapter id="protocol">
  <title>Frontend/Backend Protocol</title>
@@ -3691,7 +3691,8 @@ Terminate (F)
 <para>
 This section describes the fields that may appear in ErrorResponse and
 NoticeResponse messages.  Each field type has a single-byte identification
-token.
+token.  Note that any given field type should appear at most once per
+message.
 </para>
 
 <VariableList>
@@ -3863,7 +3864,29 @@ PasswordMessage now has a type byte.
 
 <para>
 COPY data is now encapsulated into CopyData and CopyDone messages.  There
-is a well-defined way to recover from errors during COPY.
+is a well-defined way to recover from errors during COPY.  The special
+<quote><literal>\.</></quote> last line is not needed anymore, and is not sent
+during COPY OUT.
+(It is still recognized as a terminator during COPY IN, but its use is
+deprecated and will eventually be removed.)  Binary COPY is supported.
+The CopyInResponse and CopyOutResponse messages carry a field indicating
+whether the COPY operation is text or binary.
+</para>
+
+<para>
+The CursorResponse ('<literal>P</>') message is no longer generated by
+the backend.
+</para>
+
+<para>
+The NotificationResponse ('<literal>A</>') message has an additional string
+field, which is presently empty but may someday carry additional data passed
+from the NOTIFY event sender.
+</para>
+
+<para>
+The EmptyQueryResponse ('<literal>I</>') message used to include an empty
+string parameter; this has been removed.
 </para>
 
 <note>
diff --git a/src/backend/access/common/printtup.c b/src/backend/access/common/printtup.c
index f1f96f18868e4f5a3e62f5e7da60290c9fea7db2..c88dedd93fd62697ae96e68b8bee444986155136 100644
--- a/src/backend/access/common/printtup.c
+++ b/src/backend/access/common/printtup.c
@@ -9,7 +9,7 @@
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/access/common/printtup.c,v 1.65 2002/09/04 20:31:08 momjian Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/access/common/printtup.c,v 1.66 2003/04/22 00:08:06 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -77,15 +77,18 @@ static void
 printtup_setup(DestReceiver *self, int operation,
 			   const char *portalName, TupleDesc typeinfo)
 {
-	/*
-	 * Send portal name to frontend.
-	 *
-	 * If portal name not specified, use "blank" portal.
-	 */
-	if (portalName == NULL)
-		portalName = "blank";
-
-	pq_puttextmessage('P', portalName);
+	if (PG_PROTOCOL_MAJOR(FrontendProtocol) < 3)
+	{
+		/*
+		 * Send portal name to frontend (obsolete cruft, gone in proto 3.0)
+		 *
+		 * If portal name not specified, use "blank" portal.
+		 */
+		if (portalName == NULL)
+			portalName = "blank";
+
+		pq_puttextmessage('P', portalName);
+	}
 
 	/*
 	 * if this is a retrieve, then we send back the tuple descriptor of
@@ -98,8 +101,7 @@ printtup_setup(DestReceiver *self, int operation,
 		int			i;
 		StringInfoData buf;
 
-		pq_beginmessage(&buf);
-		pq_sendbyte(&buf, 'T'); /* tuple descriptor message type */
+		pq_beginmessage(&buf, 'T'); /* tuple descriptor message type */
 		pq_sendint(&buf, natts, 2);		/* # of attrs in tuples */
 
 		for (i = 0; i < natts; ++i)
@@ -174,8 +176,7 @@ printtup(HeapTuple tuple, TupleDesc typeinfo, DestReceiver *self)
 	/*
 	 * tell the frontend to expect new tuple data (in ASCII style)
 	 */
-	pq_beginmessage(&buf);
-	pq_sendbyte(&buf, 'D');
+	pq_beginmessage(&buf, 'D');
 
 	/*
 	 * send a bitmap of which attributes are not null
@@ -388,8 +389,7 @@ printtup_internal(HeapTuple tuple, TupleDesc typeinfo, DestReceiver *self)
 	/*
 	 * tell the frontend to expect new tuple data (in binary style)
 	 */
-	pq_beginmessage(&buf);
-	pq_sendbyte(&buf, 'B');
+	pq_beginmessage(&buf, 'B');
 
 	/*
 	 * send a bitmap of which attributes are not null
diff --git a/src/backend/commands/async.c b/src/backend/commands/async.c
index 169c4ce278c86f6489a3c9f610276708c10a3704..1d9fbf6580916998e52d4909fc64d9386085fdb5 100644
--- a/src/backend/commands/async.c
+++ b/src/backend/commands/async.c
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/commands/async.c,v 1.92 2003/02/18 02:53:29 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/commands/async.c,v 1.93 2003/04/22 00:08:06 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -847,10 +847,14 @@ NotifyMyFrontEnd(char *relname, int32 listenerPID)
 	{
 		StringInfoData buf;
 
-		pq_beginmessage(&buf);
-		pq_sendbyte(&buf, 'A');
+		pq_beginmessage(&buf, 'A');
 		pq_sendint(&buf, listenerPID, sizeof(int32));
 		pq_sendstring(&buf, relname);
+		if (PG_PROTOCOL_MAJOR(FrontendProtocol) >= 3)
+		{
+			/* XXX Add parameter string here later */
+			pq_sendstring(&buf, "");
+		}
 		pq_endmessage(&buf);
 
 		/*
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 32e2362e99ba2b8e8c4ca363c0e61fb4b7a83c7a..40948e3a3b51c17df85a403ff5bbcf61a0d3a208 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.194 2003/04/19 20:36:03 momjian Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/commands/copy.c,v 1.195 2003/04/22 00:08:06 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -50,13 +50,6 @@
 #define ISOCTAL(c) (((c) >= '0') && ((c) <= '7'))
 #define OCTVALUE(c) ((c) - '0')
 
-/* Default line termination */
-#ifndef WIN32
-#define PGEOL	"\n"
-#else
-#define PGEOL	"\r\n"
-#endif
-
 /*
  * Represents the different source/dest cases we need to worry about at
  * the bottom level
@@ -92,7 +85,7 @@ typedef enum EolType
 
 /* non-export function prototypes */
 static void CopyTo(Relation rel, List *attnumlist, bool binary, bool oids,
-				   bool pipe, char *delim, char *null_print);
+				   char *delim, char *null_print);
 static void CopyFrom(Relation rel, List *attnumlist, bool binary, bool oids,
 					 char *delim, char *null_print);
 static Oid	GetInputFunction(Oid type);
@@ -101,8 +94,7 @@ static char *CopyReadAttribute(const char *delim, CopyReadResult *result);
 static void CopyAttributeOut(char *string, char *delim);
 static List *CopyGetAttnums(Relation rel, List *attnamelist);
 
-/* The trailing null is part of the signature */
-static const char BinarySignature[] = "PGBCOPY\n\377\r\n"; 
+static const char BinarySignature[12] = "PGBCOPY\n\377\r\n\0";
 
 /*
  * Static communication variables ... pretty grotty, but COPY has
@@ -135,10 +127,11 @@ static int	server_encoding;
  */
 static void SendCopyBegin(bool binary);
 static void ReceiveCopyBegin(bool binary);
-static void SendCopyEnd(bool binary, bool pipe);
+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 CopySendEndOfRow(bool binary);
 static void CopyGetData(void *databuf, int datasize);
 static int	CopyGetChar(void);
 #define CopyGetEof()  (fe_eof)
@@ -154,22 +147,32 @@ SendCopyBegin(bool binary)
 {
 	if (PG_PROTOCOL_MAJOR(FrontendProtocol) >= 3)
 	{
-		pq_putbytes("H", 1);	/* new way */
-		/* XXX grottiness needed for old protocol */
-		pq_startcopyout();
+		/* new way */
+		StringInfoData buf;
+
+		pq_beginmessage(&buf, 'H');
+		pq_sendbyte(&buf, binary ? 1 : 0);
+		pq_endmessage(&buf);
 		copy_dest = COPY_NEW_FE;
+		copy_msgbuf = makeStringInfo();
 	}
 	else if (PG_PROTOCOL_MAJOR(FrontendProtocol) >= 2)
 	{
-		pq_putbytes("H", 1);	/* old way */
-		/* grottiness needed for old protocol */
+		/* old way */
+		if (binary)
+			elog(ERROR, "COPY BINARY is not supported to stdout or from stdin");
+		pq_putemptymessage('H');
+		/* grottiness needed for old COPY OUT protocol */
 		pq_startcopyout();
 		copy_dest = COPY_OLD_FE;
 	}
 	else
 	{
-		pq_putbytes("B", 1);	/* very old way */
-		/* grottiness needed for old protocol */
+		/* very old way */
+		if (binary)
+			elog(ERROR, "COPY BINARY is not supported to stdout or from stdin");
+		pq_putemptymessage('B');
+		/* grottiness needed for old COPY OUT protocol */
 		pq_startcopyout();
 		copy_dest = COPY_OLD_FE;
 	}
@@ -180,18 +183,29 @@ ReceiveCopyBegin(bool binary)
 {
 	if (PG_PROTOCOL_MAJOR(FrontendProtocol) >= 3)
 	{
-		pq_putbytes("G", 1);	/* new way */
+		/* new way */
+		StringInfoData buf;
+
+		pq_beginmessage(&buf, 'G');
+		pq_sendbyte(&buf, binary ? 1 : 0);
+		pq_endmessage(&buf);
 		copy_dest = COPY_NEW_FE;
 		copy_msgbuf = makeStringInfo();
 	}
 	else if (PG_PROTOCOL_MAJOR(FrontendProtocol) >= 2)
 	{
-		pq_putbytes("G", 1);	/* old way */
+		/* old way */
+		if (binary)
+			elog(ERROR, "COPY BINARY is not supported to stdout or from stdin");
+		pq_putemptymessage('G');
 		copy_dest = COPY_OLD_FE;
 	}
 	else
 	{
-		pq_putbytes("D", 1);	/* very old way */
+		/* very old way */
+		if (binary)
+			elog(ERROR, "COPY BINARY is not supported to stdout or from stdin");
+		pq_putemptymessage('D');
 		copy_dest = COPY_OLD_FE;
 	}
 	/* We *must* flush here to ensure FE knows it can send. */
@@ -199,22 +213,39 @@ ReceiveCopyBegin(bool binary)
 }
 
 static void
-SendCopyEnd(bool binary, bool pipe)
+SendCopyEnd(bool binary)
 {
-	if (!binary)
+	if (copy_dest == COPY_NEW_FE)
 	{
-		CopySendString("\\.");
-		CopySendString(!pipe ? PGEOL : "\n");
+		if (binary)
+		{
+			/* Need to flush out file trailer word */
+			CopySendEndOfRow(true);
+		}
+		else
+		{
+			/* Shouldn't have any unsent data */
+			Assert(copy_msgbuf->len == 0);
+		}
+		/* Send Copy Done message */
+		pq_putemptymessage('c');
+	}
+	else
+	{
+		/* The FE/BE protocol uses \n as newline for all platforms */
+		CopySendData("\\.\n", 3);
+		pq_endcopyout(false);
 	}
-	pq_endcopyout(false);
 }
 
-/*
+/*----------
  * 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
+ * CopySendEndOfRow does the appropriate thing at end of each data row
  *
  * NB: no data conversion is applied by these functions
+ *----------
  */
 static void
 CopySendData(void *databuf, int datasize)
@@ -228,12 +259,13 @@ CopySendData(void *databuf, int datasize)
 			break;
 		case COPY_OLD_FE:
 			if (pq_putbytes((char *) databuf, datasize))
-				fe_eof = true;
+			{
+				/* no hope of recovering connection sync, so FATAL */
+				elog(FATAL, "CopySendData: connection lost");
+			}
 			break;
 		case COPY_NEW_FE:
-			/* XXX fix later */
-			if (pq_putbytes((char *) databuf, datasize))
-				fe_eof = true;
+			appendBinaryStringInfo(copy_msgbuf, (char *) databuf, datasize);
 			break;
 	}
 }
@@ -250,6 +282,40 @@ CopySendChar(char c)
 	CopySendData(&c, 1);
 }
 
+static void
+CopySendEndOfRow(bool binary)
+{
+	switch (copy_dest)
+	{
+		case COPY_FILE:
+			if (!binary)
+			{
+				/* Default line termination depends on platform */
+#ifndef WIN32
+				CopySendChar('\n');
+#else
+				CopySendString("\r\n");
+#endif
+			}
+			break;
+		case COPY_OLD_FE:
+			/* The FE/BE protocol uses \n as newline for all platforms */
+			if (!binary)
+				CopySendChar('\n');
+			break;
+		case COPY_NEW_FE:
+			/* The FE/BE protocol uses \n as newline for all platforms */
+			if (!binary)
+				CopySendChar('\n');
+			/* Dump the accumulated row as one CopyData message */
+			(void) pq_putmessage('d', copy_msgbuf->data, copy_msgbuf->len);
+			/* Reset copy_msgbuf to empty */
+			copy_msgbuf->len = 0;
+			copy_msgbuf->data[0] = '\0';
+			break;
+	}
+}
+
 /*
  * CopyGetData reads data from the source (file or frontend)
  * CopyGetChar does the same for single characters
@@ -568,13 +634,6 @@ DoCopy(const CopyStmt *stmt)
 			 "directly to or from a file.  Anyone can COPY to stdout or "
 			 "from stdin.  Psql's \\copy command also works for anyone.");
 
-	/*
-	 * This restriction is unfortunate, but necessary until the frontend
-	 * COPY protocol is redesigned to be binary-safe...
-	 */
-	if (pipe && binary)
-		elog(ERROR, "COPY BINARY is not supported to stdout or from stdin");
-
 	/*
 	 * Presently, only single-character delimiter strings are supported.
 	 */
@@ -698,13 +757,13 @@ DoCopy(const CopyStmt *stmt)
 				elog(ERROR, "COPY: %s is a directory", filename);
 			}
 		}
-		CopyTo(rel, attnumlist, binary, oids, pipe, delim, null_print);
+		CopyTo(rel, attnumlist, binary, oids, delim, null_print);
 	}
 
 	if (!pipe)
 		FreeFile(copy_file);
 	else if (IsUnderPostmaster && !is_from)
-		SendCopyEnd(binary, pipe);
+		SendCopyEnd(binary);
 	pfree(attribute_buf.data);
 
 	/*
@@ -721,7 +780,7 @@ DoCopy(const CopyStmt *stmt)
  * Copy from relation TO file.
  */
 static void
-CopyTo(Relation rel, List *attnumlist, bool binary, bool oids, bool pipe,
+CopyTo(Relation rel, List *attnumlist, bool binary, bool oids,
 	   char *delim, char *null_print)
 {
 	HeapTuple	tuple;
@@ -786,7 +845,7 @@ CopyTo(Relation rel, List *attnumlist, bool binary, bool oids, bool pipe,
 		int32		tmp;
 
 		/* Signature */
-		CopySendData((char *) BinarySignature, sizeof(BinarySignature));
+		CopySendData((char *) BinarySignature, 12);
 		/* Integer layout field */
 		tmp = 0x01020304;
 		CopySendData(&tmp, sizeof(int32));
@@ -918,8 +977,7 @@ CopyTo(Relation rel, List *attnumlist, bool binary, bool oids, bool pipe,
 			}
 		}
 
-		if (!binary)
-			CopySendString(!pipe ? PGEOL : "\n");
+		CopySendEndOfRow(binary);
 
 		MemoryContextSwitchTo(oldcontext);
 	}
@@ -1100,8 +1158,7 @@ CopyFrom(Relation rel, List *attnumlist, bool binary, bool oids,
 
 		/* Signature */
 		CopyGetData(readSig, 12);
-		if (CopyGetEof() || memcmp(readSig, BinarySignature,
-								   sizeof(BinarySignature)) != 0)
+		if (CopyGetEof() || memcmp(readSig, BinarySignature, 12) != 0)
 			elog(ERROR, "COPY BINARY: file signature not recognized");
 		/* Integer layout field */
 		CopyGetData(&tmp, sizeof(int32));
diff --git a/src/backend/libpq/auth.c b/src/backend/libpq/auth.c
index a5dc8eff2da02191773fc3439a6f60fa7c6cd83e..2edc919c6d25e19a3c338d1a5d8e10a9d5df9a03 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.99 2003/04/19 00:02:29 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/libpq/auth.c,v 1.100 2003/04/22 00:08:06 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -512,8 +512,7 @@ sendAuthRequest(Port *port, AuthRequest areq)
 {
 	StringInfoData buf;
 
-	pq_beginmessage(&buf);
-	pq_sendbyte(&buf, 'R');
+	pq_beginmessage(&buf, 'R');
 	pq_sendint(&buf, (int32) areq, sizeof(int32));
 
 	/* Add the salt for encrypted passwords. */
diff --git a/src/backend/libpq/pqcomm.c b/src/backend/libpq/pqcomm.c
index 9a4f51b7786939355d45e123ecbe1df133df4a3c..2cf2a36b7b3ca6dd86f78f9403af9a339785c041 100644
--- a/src/backend/libpq/pqcomm.c
+++ b/src/backend/libpq/pqcomm.c
@@ -12,15 +12,16 @@
  * 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
  * the frontend gets back into sync.  Therefore, these routines have to be
- * aware of COPY OUT state.
+ * aware of COPY OUT state.  (New COPY-OUT is message-based and does *not*
+ * set the DoingCopyOut flag.)
  *
  * NOTE: generally, it's a bad idea to emit outgoing messages directly with
  * pq_putbytes(), especially if the message would require multiple calls
  * to send.  Instead, use the routines in pqformat.c to construct the message
- * in a buffer and then emit it in one call to pq_putmessage.  This helps
- * ensure that the channel will not be clogged by an incomplete message
- * if execution is aborted by elog(ERROR) partway through the message.
- * The only non-libpq code that should call pq_putbytes directly is COPY OUT.
+ * in a buffer and then emit it in one call to pq_putmessage.  This ensures
+ * that the channel will not be clogged by an incomplete message if execution
+ * is aborted by elog(ERROR) partway through the message.  The only non-libpq
+ * code that should call pq_putbytes directly is old-style COPY OUT.
  *
  * At one time, libpq was shared between frontend and backend, but now
  * the backend's "backend/libpq" is quite separate from "interfaces/libpq".
@@ -29,7 +30,7 @@
  * Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- *	$Header: /cvsroot/pgsql/src/backend/libpq/pqcomm.c,v 1.150 2003/04/19 00:02:29 tgl Exp $
+ *	$Header: /cvsroot/pgsql/src/backend/libpq/pqcomm.c,v 1.151 2003/04/22 00:08:06 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -846,13 +847,17 @@ pq_flush(void)
  *		pq_putmessage	- send a normal message (suppressed in COPY OUT mode)
  *
  *		If msgtype is not '\0', it is a message type code to place before
- *		the message body (len counts only the body size!).
- *		If msgtype is '\0', then the buffer already includes the type code.
+ *		the message body.  If msgtype is '\0', then the message has no type
+ *		code (this is only valid in pre-3.0 protocols).
  *
- *		All normal messages are suppressed while COPY OUT is in progress.
- *		(In practice only a few messages might get emitted then; dropping
- *		them is annoying, but at least they will still appear in the
- *		postmaster log.)
+ *		len is the length of the message body data at *s.  In protocol 3.0
+ *		and later, a message length word (equal to len+4 because it counts
+ *		itself too) is inserted by this routine.
+ *
+ *		All normal messages are suppressed while old-style COPY OUT is in
+ *		progress.  (In practice only a few notice messages might get emitted
+ *		then; dropping them is annoying, but at least they will still appear
+ *		in the postmaster log.)
  *
  *		returns 0 if OK, EOF if trouble
  * --------------------------------
@@ -865,6 +870,14 @@ pq_putmessage(char msgtype, const char *s, size_t len)
 	if (msgtype)
 		if (pq_putbytes(&msgtype, 1))
 			return EOF;
+	if (PG_PROTOCOL_MAJOR(FrontendProtocol) >= 3)
+	{
+		uint32		n32;
+
+		n32 = htonl((uint32) (len + 4));
+		if (pq_putbytes((char *) &n32, 4))
+			return EOF;
+	}
 	return pq_putbytes(s, len);
 }
 
@@ -880,12 +893,13 @@ pq_startcopyout(void)
 }
 
 /* --------------------------------
- *		pq_endcopyout	- end a COPY OUT transfer
+ *		pq_endcopyout	- end an old-style COPY OUT transfer
  *
  *		If errorAbort is indicated, we are aborting a COPY OUT due to an error,
  *		and must send a terminator line.  Since a partial data line might have
  *		been emitted, send a couple of newlines first (the first one could
- *		get absorbed by a backslash...)
+ *		get absorbed by a backslash...)  Note that old-style COPY OUT does
+ *		not allow binary transfers, so a textual terminator is always correct.
  * --------------------------------
  */
 void
@@ -893,8 +907,8 @@ pq_endcopyout(bool errorAbort)
 {
 	if (!DoingCopyOut)
 		return;
+	DoingCopyOut = false;
 	if (errorAbort)
 		pq_putbytes("\n\n\\.\n", 5);
 	/* in non-error case, copy.c will have emitted the terminator line */
-	DoingCopyOut = false;
 }
diff --git a/src/backend/libpq/pqformat.c b/src/backend/libpq/pqformat.c
index 80ca3190999c5f5eb30f1b4f810ae4dbb2dce9ec..dacfa93ecc7c0f2bef6a8d18e929df7ef122c6d1 100644
--- a/src/backend/libpq/pqformat.c
+++ b/src/backend/libpq/pqformat.c
@@ -18,7 +18,7 @@
  * Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- *	$Header: /cvsroot/pgsql/src/backend/libpq/pqformat.c,v 1.27 2003/04/19 00:02:29 tgl Exp $
+ *	$Header: /cvsroot/pgsql/src/backend/libpq/pqformat.c,v 1.28 2003/04/22 00:08:06 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -38,6 +38,7 @@
  *
  * Special-case message output:
  *		pq_puttextmessage - generate a character set-converted message in one step
+ *		pq_putemptymessage - convenience routine for message with empty body
  *
  * Message parsing after input:
  *		pq_getmsgbyte	- get a raw byte from a message buffer
@@ -63,6 +64,22 @@
 #include "mb/pg_wchar.h"
 
 
+/* --------------------------------
+ *		pq_beginmessage		- initialize for sending a message
+ * --------------------------------
+ */
+void
+pq_beginmessage(StringInfo buf, char msgtype)
+{
+	initStringInfo(buf);
+	/*
+	 * We stash the message type into the buffer's cursor field, expecting
+	 * that the pq_sendXXX routines won't touch it.  We could alternatively
+	 * make it the first byte of the buffer contents, but this seems easier.
+	 */
+	buf->cursor = msgtype;
+}
+
 /* --------------------------------
  *		pq_sendbyte		- append a raw byte to a StringInfo buffer
  * --------------------------------
@@ -176,7 +193,8 @@ pq_sendint(StringInfo buf, int i, int b)
 void
 pq_endmessage(StringInfo buf)
 {
-	(void) pq_putmessage('\0', buf->data, buf->len);
+	/* msgtype was saved in cursor field */
+	(void) pq_putmessage(buf->cursor, buf->data, buf->len);
 	/* no need to complain about any failure, since pqcomm.c already did */
 	pfree(buf->data);
 	buf->data = NULL;
@@ -188,11 +206,9 @@ pq_endmessage(StringInfo buf)
  *		This is the same as the pqcomm.c routine pq_putmessage, except that
  *		the message body is a null-terminated string to which encoding
  *		conversion applies.
- *
- *		returns 0 if OK, EOF if trouble
  * --------------------------------
  */
-int
+void
 pq_puttextmessage(char msgtype, const char *str)
 {
 	int			slen = strlen(str);
@@ -201,12 +217,22 @@ pq_puttextmessage(char msgtype, const char *str)
 	p = (char *) pg_server_to_client((unsigned char *) str, slen);
 	if (p != str)				/* actual conversion has been done? */
 	{
-		int			result = pq_putmessage(msgtype, p, strlen(p) + 1);
-
+		(void) pq_putmessage(msgtype, p, strlen(p) + 1);
 		pfree(p);
-		return result;
+		return;
 	}
-	return pq_putmessage(msgtype, str, slen + 1);
+	(void) pq_putmessage(msgtype, str, slen + 1);
+}
+
+
+/* --------------------------------
+ *		pq_putemptymessage - convenience routine for message with empty body
+ * --------------------------------
+ */
+void
+pq_putemptymessage(char msgtype)
+{
+	(void) pq_putmessage(msgtype, NULL, 0);
 }
 
 
diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c
index d6beb0fc1a6262a133518fd009bc2ef407656bf2..834b03ab6284bfdbb24c5915139220d6680d6b3f 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.313 2003/04/19 00:02:29 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/postmaster/postmaster.c,v 1.314 2003/04/22 00:08:06 tgl Exp $
  *
  * NOTES
  *
@@ -1118,7 +1118,13 @@ ProcessStartupPacket(Port *port, bool SSLdone)
 
 	if (pq_getbytes((char *) &len, 4) == EOF)
 	{
-		elog(COMMERROR, "incomplete startup packet");
+		/*
+		 * EOF after SSLdone probably means the client didn't like our
+		 * response to NEGOTIATE_SSL_CODE.  That's not an error condition,
+		 * so don't clutter the log with a complaint.
+		 */
+		if (!SSLdone)
+			elog(COMMERROR, "incomplete startup packet");
 		return STATUS_ERROR;
 	}
 
@@ -1127,7 +1133,10 @@ ProcessStartupPacket(Port *port, bool SSLdone)
 
 	if (len < (int32) sizeof(ProtocolVersion) ||
 		len > MAX_STARTUP_PACKET_LENGTH)
-		elog(FATAL, "invalid length of startup packet");
+	{
+		elog(COMMERROR, "invalid length of startup packet");
+		return STATUS_ERROR;
+	}
 
 	/*
 	 * Allocate at least the size of an old-style startup packet, plus one
@@ -1173,7 +1182,7 @@ ProcessStartupPacket(Port *port, bool SSLdone)
 #endif
 		if (send(port->sock, &SSLok, 1, 0) != 1)
 		{
-			elog(LOG, "failed to send SSL negotiation response: %m");
+			elog(COMMERROR, "failed to send SSL negotiation response: %m");
 			return STATUS_ERROR;	/* close the connection */
 		}
 
@@ -1188,6 +1197,11 @@ ProcessStartupPacket(Port *port, bool SSLdone)
 
 	/* Could add additional special packet types here */
 
+	/*
+	 * Set FrontendProtocol now so that elog() knows what format to send
+	 * if we fail during startup.
+	 */
+	FrontendProtocol = proto;
 
 	/*
 	 * XXX temporary for 3.0 protocol development: we are using the minor
diff --git a/src/backend/tcop/dest.c b/src/backend/tcop/dest.c
index 07e4614e799be2c030a32320d49c07d2e19cc496..5ccaa60995c5ae39a4693c4a8ae877eab5ff13c1 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.52 2003/04/19 00:02:29 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/tcop/dest.c,v 1.53 2003/04/22 00:08:07 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -141,7 +141,9 @@ EndCommand(const char *commandTag, CommandDest dest)
  *		libpq's crufty way of determining whether a multiple-command
  *		query string is done.  In protocol 2.0 it's probably not really
  *		necessary to distinguish empty queries anymore, but we still do it
- *		for backwards compatibility with 1.0.
+ *		for backwards compatibility with 1.0.  In protocol 3.0 it has some
+ *		use again, since it ensures that there will be a recognizable end
+ *		to the response to an Execute message.
  * ----------------
  */
 void
@@ -153,9 +155,13 @@ NullCommand(CommandDest dest)
 		case Remote:
 
 			/*
-			 * tell the fe that we saw an empty query string
+			 * tell the fe that we saw an empty query string.  In protocols
+			 * before 3.0 this has a useless empty-string message body.
 			 */
-			pq_putbytes("I", 2);	/* note we send I and \0 */
+			if (PG_PROTOCOL_MAJOR(FrontendProtocol) >= 3)
+				pq_putemptymessage('I');
+			else
+				pq_puttextmessage('I', "");
 			break;
 
 		case Debug:
@@ -184,7 +190,7 @@ ReadyForQuery(CommandDest dest)
 		case RemoteInternal:
 		case Remote:
 			if (PG_PROTOCOL_MAJOR(FrontendProtocol) >= 2)
-				pq_putbytes("Z", 1);
+				pq_putemptymessage('Z');
 			/* Flush output at end of cycle in any case. */
 			pq_flush();
 			break;
diff --git a/src/backend/tcop/fastpath.c b/src/backend/tcop/fastpath.c
index eeddea6f6eb8cb82c6dc63f6e3754bbe20f4b983..b87509573497abb51715cac3a9e0cf3cd3bcd4fe 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.58 2003/04/19 00:02:29 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/tcop/fastpath.c,v 1.59 2003/04/22 00:08:07 tgl Exp $
  *
  * NOTES
  *	  This cruft is the server side of PQfn.
@@ -119,8 +119,7 @@ SendFunctionResult(Datum retval, bool retbyval, int retlen)
 {
 	StringInfoData buf;
 
-	pq_beginmessage(&buf);
-	pq_sendbyte(&buf, 'V');
+	pq_beginmessage(&buf, 'V');
 
 	if (retlen != 0)
 	{
diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c
index fcc6591f7c017fa12aa4f9b394280618b6c26a5b..5c51a1056a266b582042a7114c9c51403a2d039e 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.322 2003/04/19 00:02:29 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/tcop/postgres.c,v 1.323 2003/04/22 00:08:07 tgl Exp $
  *
  * NOTES
  *	  this is the "main" module of the postgres backend and
@@ -1821,8 +1821,7 @@ PostgresMain(int argc, char *argv[], const char *username)
 	{
 		StringInfoData buf;
 
-		pq_beginmessage(&buf);
-		pq_sendbyte(&buf, 'K');
+		pq_beginmessage(&buf, 'K');
 		pq_sendint(&buf, (int32) MyProcPid, sizeof(int32));
 		pq_sendint(&buf, (int32) MyCancelKey, sizeof(int32));
 		pq_endmessage(&buf);
@@ -1832,7 +1831,7 @@ PostgresMain(int argc, char *argv[], const char *username)
 	if (!IsUnderPostmaster)
 	{
 		puts("\nPOSTGRES backend interactive interface ");
-		puts("$Revision: 1.322 $ $Date: 2003/04/19 00:02:29 $\n");
+		puts("$Revision: 1.323 $ $Date: 2003/04/22 00:08:07 $\n");
 	}
 
 	/*
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index 763024b5773905f5d43124e0acbaff24f529ebee..01250f9a2f06495998b119783ab2e3b46f9244ba 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/utils/error/elog.c,v 1.107 2003/03/20 03:34:56 momjian Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/utils/error/elog.c,v 1.108 2003/04/22 00:08:07 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -406,20 +406,19 @@ elog(int lev, const char *fmt,...)
 		 */
 		oldcxt = MemoryContextSwitchTo(ErrorContext);
 
-		if (lev <= WARNING)
-			/* exclude the timestamp from msg sent to frontend */
-			send_message_to_frontend(lev, msg_buf + timestamp_size);
-		else
+		if (lev >= ERROR)
 		{
 			/*
 			 * Abort any COPY OUT in progress when an error is detected.
-			 * This hack is necessary because of poor design of copy
-			 * protocol.
+			 * This hack is necessary because of poor design of old-style
+			 * copy protocol.
 			 */
 			pq_endcopyout(true);
-			send_message_to_frontend(ERROR, msg_buf + timestamp_size);
 		}
 
+		/* Exclude the timestamp from msg sent to frontend */
+		send_message_to_frontend(lev, msg_buf + timestamp_size);
+
 		MemoryContextSwitchTo(oldcxt);
 	}
 
@@ -745,11 +744,9 @@ send_message_to_frontend(int type, const char *msg)
 {
 	StringInfoData buf;
 
-	AssertArg(type <= ERROR);
-
-	pq_beginmessage(&buf);
 	/* 'N' (Notice) is for nonfatal conditions, 'E' is for errors */
-	pq_sendbyte(&buf, type < ERROR ? 'N' : 'E');
+	pq_beginmessage(&buf, (type < ERROR) ? 'N' : 'E');
+	/* XXX more to do here */
 	pq_sendstring(&buf, msg);
 	pq_endmessage(&buf);
 
diff --git a/src/include/libpq/pqcomm.h b/src/include/libpq/pqcomm.h
index 61aa695e27217356e9fdc99a1647c1b3f216cda5..420f1e438e328b9d024eed5ebe92c0486217dc30 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.77 2003/04/19 00:02:29 tgl Exp $
+ * $Id: pqcomm.h,v 1.78 2003/04/22 00:08:07 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,101) /* XXX temporary value */
+#define PG_PROTOCOL_LATEST		PG_PROTOCOL(3,102) /* 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 cb80ec2c2014dbee579aa3c4e223e0831eea8077..229de38c9b36d3e6213235bea6ee178d37f8cd15 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.14 2003/04/19 00:02:29 tgl Exp $
+ * $Id: pqformat.h,v 1.15 2003/04/22 00:08:07 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -15,8 +15,7 @@
 
 #include "lib/stringinfo.h"
 
-#define pq_beginmessage(buf)  initStringInfo(buf)
-
+extern void pq_beginmessage(StringInfo buf, char msgtype);
 extern void pq_sendbyte(StringInfo buf, int byt);
 extern void pq_sendbytes(StringInfo buf, const char *data, int datalen);
 extern void pq_sendcountedtext(StringInfo buf, const char *str, int slen);
@@ -24,7 +23,8 @@ extern void pq_sendstring(StringInfo buf, const char *str);
 extern void pq_sendint(StringInfo buf, int i, int b);
 extern void pq_endmessage(StringInfo buf);
 
-extern int	pq_puttextmessage(char msgtype, const char *str);
+extern void pq_puttextmessage(char msgtype, const char *str);
+extern void pq_putemptymessage(char msgtype);
 
 extern int	pq_getmsgbyte(StringInfo msg);
 extern unsigned int pq_getmsgint(StringInfo msg, int b);
diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c
index a322d8a73d132da84a37f51b043861ade6f7a9d5..086462094ff2c65a70b3cc9ead27b40ddb6a7d2e 100644
--- a/src/interfaces/libpq/fe-connect.c
+++ b/src/interfaces/libpq/fe-connect.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/interfaces/libpq/fe-connect.c,v 1.233 2003/04/19 00:02:30 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/interfaces/libpq/fe-connect.c,v 1.234 2003/04/22 00:08:07 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -1328,6 +1328,8 @@ keep_going:						/* We will come back to here until there
 		case CONNECTION_AWAITING_RESPONSE:
 			{
 				char		beresp;
+				int			msgLength;
+				int			avail;
 				AuthRequest areq;
 
 				/*
@@ -1337,15 +1339,58 @@ keep_going:						/* We will come back to here until there
 				 */
 				conn->inCursor = conn->inStart;
 
+				/* Read type byte */
 				if (pqGetc(&beresp, conn))
 				{
 					/* We'll come back when there is more data */
 					return PGRES_POLLING_READING;
 				}
 
-				/* Handle errors. */
-				if (beresp == 'E')
+				/*
+				 * Validate message type: we expect only an authentication
+				 * request or an error here.  Anything else probably means
+				 * it's not Postgres on the other end at all.
+				 */
+				if (!(beresp == 'R' || beresp == 'E'))
+				{
+					printfPQExpBuffer(&conn->errorMessage,
+									  libpq_gettext(
+								  "expected authentication request from "
+											  "server, but received %c\n"
+													),
+									  beresp);
+					goto error_return;
+				}
+
+				/* Read message length word */
+				if (pqGetInt(&msgLength, 4, conn))
+				{
+					/* We'll come back when there is more data */
+					return PGRES_POLLING_READING;
+				}
+
+				/*
+				 * Try to validate message length before using it.
+				 * Authentication requests can't be very large.  Errors
+				 * can be a little larger, but not huge.  If we see a large
+				 * apparent length in an error, it means we're really talking
+				 * to a pre-3.0-protocol server; cope.
+				 */
+				if (beresp == 'R' && (msgLength < 8 || msgLength > 100))
+				{
+					printfPQExpBuffer(&conn->errorMessage,
+									  libpq_gettext(
+								  "expected authentication request from "
+											  "server, but received %c\n"
+													),
+									  beresp);
+					goto error_return;
+				}
+
+				if (beresp == 'E' && (msgLength < 8 || msgLength > 30000))
 				{
+					/* Handle error from a pre-3.0 server */
+					conn->inCursor = conn->inStart + 1;	/* reread data */
 					if (pqGets(&conn->errorMessage, conn))
 					{
 						/* We'll come back when there is more data */
@@ -1363,18 +1408,45 @@ keep_going:						/* We will come back to here until there
 					goto error_return;
 				}
 
-				/* Otherwise it should be an authentication request. */
-				if (beresp != 'R')
+				/*
+				 * Can't process if message body isn't all here yet.
+				 */
+				msgLength -= 4;
+				avail = conn->inEnd - conn->inCursor;
+				if (avail < msgLength)
 				{
-					printfPQExpBuffer(&conn->errorMessage,
-									  libpq_gettext(
-								  "expected authentication request from "
-											  "server, but received %c\n"
-													),
-									  beresp);
+					/*
+					 * Before returning, try to enlarge the input buffer if
+					 * needed to hold the whole message; see notes in
+					 * parseInput.
+					 */
+					if (pqCheckInBufferSpace(conn->inCursor + msgLength, conn))
+						goto error_return;
+					/* We'll come back when there is more data */
+					return PGRES_POLLING_READING;
+				}
+
+				/* Handle errors. */
+				if (beresp == 'E')
+				{
+					if (pqGets(&conn->errorMessage, conn))
+					{
+						/* We'll come back when there is more data */
+						return PGRES_POLLING_READING;
+					}
+					/* OK, we read the message; mark data consumed */
+					conn->inStart = conn->inCursor;
+
+					/*
+					 * The postmaster typically won't end its message with
+					 * a newline, so add one to conform to libpq
+					 * conventions.
+					 */
+					appendPQExpBufferChar(&conn->errorMessage, '\n');
 					goto error_return;
 				}
 
+				/* It is an authentication request. */
 				/* Get the type of request. */
 				if (pqGetInt((int *) &areq, 4, conn))
 				{
diff --git a/src/interfaces/libpq/fe-exec.c b/src/interfaces/libpq/fe-exec.c
index 487acff83dfcdca4be8a9a0c4eef8f92ff41f038..16e63f7f68f0944cbcd21f6429a0558f7cbd901e 100644
--- a/src/interfaces/libpq/fe-exec.c
+++ b/src/interfaces/libpq/fe-exec.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/interfaces/libpq/fe-exec.c,v 1.129 2003/04/19 00:02:30 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/interfaces/libpq/fe-exec.c,v 1.130 2003/04/22 00:08:07 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -51,6 +51,7 @@ static PGresult *prepareAsyncResult(PGconn *conn);
 static int	addTuple(PGresult *res, PGresAttValue * tup);
 static void parseInput(PGconn *conn);
 static void handleSendFailure(PGconn *conn);
+static void handleSyncLoss(PGconn *conn, char id, int msgLength);
 static int	getRowDescriptions(PGconn *conn);
 static int	getAnotherTuple(PGconn *conn, int binary);
 static int	getNotify(PGconn *conn);
@@ -866,6 +867,8 @@ static void
 parseInput(PGconn *conn)
 {
 	char		id;
+	int			msgLength;
+	int			avail;
 	char		noticeWorkspace[128];
 
 	/*
@@ -874,25 +877,63 @@ parseInput(PGconn *conn)
 	for (;;)
 	{
 		/*
-		 * Quit if in COPY_OUT state: we expect raw data from the server
-		 * until PQendcopy is called.  Don't try to parse it according to
-		 * the normal protocol.  (This is bogus.  The data lines ought to
-		 * be part of the protocol and have identifying leading
-		 * characters.)
+		 * Try to read a message.  First get the type code and length.
+		 * Return if not enough data.
 		 */
-		if (conn->asyncStatus == PGASYNC_COPY_OUT)
+		conn->inCursor = conn->inStart;
+		if (pqGetc(&id, conn))
+			return;
+		if (pqGetInt(&msgLength, 4, conn))
 			return;
 
 		/*
-		 * OK to try to read a message type code.
+		 * Try to validate message type/length here.  A length less than 4
+		 * is definitely broken.  Large lengths should only be believed
+		 * for a few message types.
 		 */
-		conn->inCursor = conn->inStart;
-		if (pqGetc(&id, conn))
+		if (msgLength < 4)
+		{
+			handleSyncLoss(conn, id, msgLength);
+			return;
+		}
+		if (msgLength > 30000 &&
+			!(id == 'T' || id == 'D' || id == 'B' || id == 'd'))
+		{
+			handleSyncLoss(conn, id, msgLength);
+			return;
+		}
+
+		/*
+		 * Can't process if message body isn't all here yet.
+		 */
+		msgLength -= 4;
+		avail = conn->inEnd - conn->inCursor;
+		if (avail < msgLength)
+		{
+			/*
+			 * Before returning, enlarge the input buffer if needed to hold
+			 * the whole message.  This is better than leaving it to
+			 * pqReadData because we can avoid multiple cycles of realloc()
+			 * when the message is large; also, we can implement a reasonable
+			 * recovery strategy if we are unable to make the buffer big
+			 * enough.
+			 */
+			if (pqCheckInBufferSpace(conn->inCursor + msgLength, conn))
+			{
+				/*
+				 * XXX add some better recovery code... plan is to skip
+				 * over the message using its length, then report an error.
+				 * For the moment, just treat this like loss of sync (which
+				 * indeed it might be!)
+				 */
+				handleSyncLoss(conn, id, msgLength);
+			}
 			return;
+		}
 
 		/*
-		 * NOTIFY and NOTICE messages can happen in any state besides
-		 * COPY OUT; always process them right away.
+		 * NOTIFY and NOTICE messages can happen in any state; always process
+		 * them right away.
 		 *
 		 * Most other messages should only be processed while in BUSY state.
 		 * (In particular, in READY state we hold off further parsing
@@ -936,9 +977,8 @@ parseInput(PGconn *conn)
 						 libpq_gettext("message type 0x%02x arrived from server while idle\n"),
 						 id);
 				DONOTICE(conn, noticeWorkspace);
-				/* Discard the unexpected message; good idea?? */
-				conn->inStart = conn->inEnd;
-				break;
+				/* Discard the unexpected message */
+				conn->inCursor += msgLength;
 			}
 		}
 		else
@@ -969,16 +1009,6 @@ parseInput(PGconn *conn)
 					conn->asyncStatus = PGASYNC_IDLE;
 					break;
 				case 'I':		/* empty query */
-					/* read and throw away the closing '\0' */
-					if (pqGetc(&id, conn))
-						return;
-					if (id != '\0')
-					{
-						snprintf(noticeWorkspace, sizeof(noticeWorkspace),
-								 libpq_gettext("unexpected character %c following empty query response (\"I\" message)\n"),
-								 id);
-						DONOTICE(conn, noticeWorkspace);
-					}
 					if (conn->result == NULL)
 						conn->result = PQmakeEmptyPGresult(conn,
 													  PGRES_EMPTY_QUERY);
@@ -996,11 +1026,6 @@ parseInput(PGconn *conn)
 					if (pqGetInt(&(conn->be_key), 4, conn))
 						return;
 					break;
-				case 'P':		/* synchronous (normal) portal */
-					if (pqGets(&conn->workBuffer, conn))
-						return;
-					/* We pretty much ignore this message type... */
-					break;
 				case 'T':		/* row descriptions (start of query
 								 * results) */
 					if (conn->result == NULL)
@@ -1034,9 +1059,8 @@ parseInput(PGconn *conn)
 						snprintf(noticeWorkspace, sizeof(noticeWorkspace),
 								 libpq_gettext("server sent data (\"D\" message) without prior row description (\"T\" message)\n"));
 						DONOTICE(conn, noticeWorkspace);
-						/* Discard the unexpected message; good idea?? */
-						conn->inStart = conn->inEnd;
-						return;
+						/* Discard the unexpected message */
+						conn->inCursor += msgLength;
 					}
 					break;
 				case 'B':		/* Binary data tuple */
@@ -1051,16 +1075,36 @@ parseInput(PGconn *conn)
 						snprintf(noticeWorkspace, sizeof(noticeWorkspace),
 								 libpq_gettext("server sent binary data (\"B\" message) without prior row description (\"T\" message)\n"));
 						DONOTICE(conn, noticeWorkspace);
-						/* Discard the unexpected message; good idea?? */
-						conn->inStart = conn->inEnd;
-						return;
+						/* Discard the unexpected message */
+						conn->inCursor += msgLength;
 					}
 					break;
 				case 'G':		/* Start Copy In */
+					if (pqGetc(&conn->copy_is_binary, conn))
+						return;
 					conn->asyncStatus = PGASYNC_COPY_IN;
 					break;
 				case 'H':		/* Start Copy Out */
+					if (pqGetc(&conn->copy_is_binary, conn))
+						return;
 					conn->asyncStatus = PGASYNC_COPY_OUT;
+					conn->copy_already_done = 0;
+					break;
+				case 'd':		/* Copy Data */
+					/*
+					 * If we see Copy Data, just silently drop it.  This
+					 * would only occur if application exits COPY OUT mode
+					 * too early.
+					 */
+					conn->inCursor += msgLength;
+					break;
+				case 'c':		/* Copy Done */
+					/*
+					 * If we see Copy Done, just silently drop it.  This
+					 * is the normal case during PQendcopy.  We will keep
+					 * swallowing data, expecting to see command-complete
+					 * for the COPY command.
+					 */
 					break;
 				default:
 					printfPQExpBuffer(&conn->errorMessage,
@@ -1069,17 +1113,54 @@ parseInput(PGconn *conn)
 									  id);
 					/* build an error result holding the error message */
 					saveErrorResult(conn);
-					/* Discard the unexpected message; good idea?? */
-					conn->inStart = conn->inEnd;
 					conn->asyncStatus = PGASYNC_READY;
-					return;
+					/* Discard the unexpected message */
+					conn->inCursor += msgLength;
+					break;
 			}					/* switch on protocol character */
 		}
 		/* Successfully consumed this message */
-		conn->inStart = conn->inCursor;
+		if (conn->inCursor == conn->inStart + 5 + msgLength)
+		{
+			/* Normal case: parsing agrees with specified length */
+			conn->inStart = conn->inCursor;
+		}
+		else
+		{
+			/* Trouble --- report it */
+			printfPQExpBuffer(&conn->errorMessage,
+							  libpq_gettext("Message contents do not agree with length in message type \"%c\"\n"),
+							  id);
+			/* build an error result holding the error message */
+			saveErrorResult(conn);
+			conn->asyncStatus = PGASYNC_READY;
+			/* trust the specified message length as what to skip */
+			conn->inStart += 5 + msgLength;
+		}
 	}
 }
 
+/*
+ * handleSyncLoss: clean up after loss of message-boundary sync
+ *
+ * There isn't really a lot we can do here except abandon the connection.
+ */
+static void
+handleSyncLoss(PGconn *conn, char id, int msgLength)
+{
+	printfPQExpBuffer(&conn->errorMessage,
+					  libpq_gettext(
+						  "lost synchronization with server: got message type \"%c\", length %d\n"),
+					  id, msgLength);
+	conn->status = CONNECTION_BAD;		/* No more connection to backend */
+	pqsecure_close(conn);
+#ifdef WIN32
+	closesocket(conn->sock);
+#else
+	close(conn->sock);
+#endif
+	conn->sock = -1;
+}
 
 /*
  * parseInput subroutine to read a 'T' (row descriptions) message.
@@ -1100,7 +1181,7 @@ getRowDescriptions(PGconn *conn)
 
 	result = PQmakeEmptyPGresult(conn, PGRES_TUPLES_OK);
 
-	/* parseInput already read the 'T' label. */
+	/* parseInput already read the 'T' label and message length. */
 	/* the next two bytes are the number of fields	*/
 	if (pqGetInt(&(result->numAttributes), 2, conn))
 	{
@@ -1461,7 +1542,7 @@ errout:
 /*
  * Attempt to read a Notice response message.
  * This is possible in several places, so we break it out as a subroutine.
- * Entry: 'N' flag character has already been consumed.
+ * Entry: 'N' message type and length have already been consumed.
  * Exit: returns 0 if successfully consumed Notice message.
  *		 returns EOF if not enough data.
  */
@@ -1489,7 +1570,7 @@ getNotice(PGconn *conn)
 /*
  * Attempt to read a Notify response message.
  * This is possible in several places, so we break it out as a subroutine.
- * Entry: 'A' flag character has already been consumed.
+ * Entry: 'A' message type and length have already been consumed.
  * Exit: returns 0 if successfully consumed Notify message.
  *		 returns EOF if not enough data.
  */
@@ -1511,10 +1592,18 @@ getNotify(PGconn *conn)
 	 */
 	newNotify = (PGnotify *) malloc(sizeof(PGnotify) +
 									strlen(conn->workBuffer.data) +1);
-	newNotify->relname = (char *) newNotify + sizeof(PGnotify);
-	strcpy(newNotify->relname, conn->workBuffer.data);
-	newNotify->be_pid = be_pid;
-	DLAddTail(conn->notifyList, DLNewElem(newNotify));
+	if (newNotify)
+	{
+		newNotify->relname = (char *) newNotify + sizeof(PGnotify);
+		strcpy(newNotify->relname, conn->workBuffer.data);
+		newNotify->be_pid = be_pid;
+		DLAddTail(conn->notifyList, DLNewElem(newNotify));
+	}
+
+	/* Swallow extra string (not presently used) */
+	if (pqGets(&conn->workBuffer, conn))
+		return EOF;
+
 	return 0;
 }
 
@@ -1556,6 +1645,9 @@ PQnotifies(PGconn *conn)
  * Chiefly here so that applications can use "COPY <rel> to stdout"
  * and read the output string.	Returns a null-terminated string in s.
  *
+ * XXX this routine is now deprecated, because it can't handle binary data.
+ * If called during a COPY BINARY we return EOF.
+ *
  * PQgetline reads up to maxlen-1 characters (like fgets(3)) but strips
  * the terminating \n (like gets(3)).
  *
@@ -1563,7 +1655,7 @@ PQnotifies(PGconn *conn)
  * (a line containing just "\.") when using this routine.
  *
  * RETURNS:
- *		EOF if it is detected or invalid arguments are given
+ *		EOF if error (eg, invalid arguments are given)
  *		0 if EOL is reached (i.e., \n has been read)
  *				(this is required for backward-compatibility -- this
  *				 routine used to always return EOF or 0, assuming that
@@ -1573,53 +1665,55 @@ PQnotifies(PGconn *conn)
 int
 PQgetline(PGconn *conn, char *s, int maxlen)
 {
-	int			result = 1;		/* return value if buffer overflows */
+	int			status;
 
-	if (!s || maxlen <= 0)
+	/* maxlen must be at least 3 to hold the \. terminator! */
+	if (!conn || !s || maxlen < 3)
 		return EOF;
 
-	if (!conn || conn->sock < 0)
+	if (conn->sock < 0 ||
+		conn->asyncStatus != PGASYNC_COPY_OUT ||
+		conn->copy_is_binary)
 	{
+		printfPQExpBuffer(&conn->errorMessage,
+					libpq_gettext("PQgetline: not doing text COPY OUT\n"));
 		*s = '\0';
 		return EOF;
 	}
 
-	/*
-	 * Since this is a purely synchronous routine, we don't bother to
-	 * maintain conn->inCursor; there is no need to back up.
-	 */
-	while (maxlen > 1)
+	while ((status = PQgetlineAsync(conn, s, maxlen-1)) == 0)
 	{
-		if (conn->inStart < conn->inEnd)
-		{
-			char		c = conn->inBuffer[conn->inStart++];
-
-			if (c == '\n')
-			{
-				result = 0;		/* success exit */
-				break;
-			}
-			*s++ = c;
-			maxlen--;
-		}
-		else
+		/* need to load more data */
+		if (pqWait(TRUE, FALSE, conn) ||
+			pqReadData(conn) < 0)
 		{
-			/* need to load more data */
-			if (pqWait(TRUE, FALSE, conn) ||
-				pqReadData(conn) < 0)
-			{
-				result = EOF;
-				break;
-			}
+			*s = '\0';
+			return EOF;
 		}
 	}
-	*s = '\0';
 
-	return result;
+	if (status < 0)
+	{
+		/* End of copy detected; gin up old-style terminator */
+		strcpy(s, "\\.");
+		return 0;
+	}
+
+	/* Add null terminator, and strip trailing \n if present */
+	if (s[status-1] == '\n')
+	{
+		s[status-1] = '\0';
+		return 0;
+	}
+	else
+	{
+		s[status] = '\0';
+		return 1;
+	}
 }
 
 /*
- * PQgetlineAsync - gets a newline-terminated string without blocking.
+ * PQgetlineAsync - gets a COPY data row without blocking.
  *
  * This routine is for applications that want to do "COPY <rel> to stdout"
  * asynchronously, that is without blocking.  Having issued the COPY command
@@ -1627,10 +1721,9 @@ PQgetline(PGconn *conn, char *s, int maxlen)
  * and this routine until the end-of-data signal is detected.  Unlike
  * PQgetline, this routine takes responsibility for detecting end-of-data.
  *
- * On each call, PQgetlineAsync will return data if a complete newline-
- * terminated data line is available in libpq's input buffer, or if the
- * incoming data line is too long to fit in the buffer offered by the caller.
- * Otherwise, no data is returned until the rest of the line arrives.
+ * On each call, PQgetlineAsync will return data if a complete data row
+ * is available in libpq's input buffer.  Otherwise, no data is returned
+ * until the rest of the row arrives.
  *
  * If -1 is returned, the end-of-data signal has been recognized (and removed
  * from libpq's input buffer).  The caller *must* next call PQendcopy and
@@ -1640,66 +1733,73 @@ PQgetline(PGconn *conn, char *s, int maxlen)
  *	 -1    if the end-of-copy-data marker has been recognized
  *	 0	   if no data is available
  *	 >0    the number of bytes returned.
- * The data returned will not extend beyond a newline character.  If possible
- * a whole line will be returned at one time.  But if the buffer offered by
- * the caller is too small to hold a line sent by the backend, then a partial
- * data line will be returned.	This can be detected by testing whether the
- * last returned byte is '\n' or not.
- * The returned string is *not* null-terminated.
+ *
+ * The data returned will not extend beyond a data-row boundary.  If possible
+ * a whole row will be returned at one time.  But if the buffer offered by
+ * the caller is too small to hold a row sent by the backend, then a partial
+ * data row will be returned.  In text mode this can be detected by testing
+ * whether the last returned byte is '\n' or not.
+ *
+ * The returned data is *not* null-terminated.
  */
 
 int
 PQgetlineAsync(PGconn *conn, char *buffer, int bufsize)
 {
+	char		id;
+	int			msgLength;
 	int			avail;
 
 	if (!conn || conn->asyncStatus != PGASYNC_COPY_OUT)
 		return -1;				/* we are not doing a copy... */
 
 	/*
-	 * Move data from libpq's buffer to the caller's. We want to accept
-	 * data only in units of whole lines, not partial lines.  This ensures
-	 * that we can recognize the terminator line "\\.\n".  (Otherwise, if
-	 * it happened to cross a packet/buffer boundary, we might hand the
-	 * first one or two characters off to the caller, which we shouldn't.)
+	 * Recognize the next input message.  To make life simpler for async
+	 * callers, we keep returning 0 until the next message is fully available
+	 * even if it is not Copy Data.  This should keep PQendcopy from blocking.
 	 */
-
 	conn->inCursor = conn->inStart;
+	if (pqGetc(&id, conn))
+		return 0;
+	if (pqGetInt(&msgLength, 4, conn))
+		return 0;
+	avail = conn->inEnd - conn->inCursor;
+	if (avail < msgLength - 4)
+		return 0;
 
-	avail = bufsize;
-	while (avail > 0 && conn->inCursor < conn->inEnd)
-	{
-		char		c = conn->inBuffer[conn->inCursor++];
-
-		*buffer++ = c;
-		--avail;
-		if (c == '\n')
-		{
-			/* Got a complete line; mark the data removed from libpq */
-			conn->inStart = conn->inCursor;
-			/* Is it the endmarker line? */
-			if (bufsize - avail == 3 && buffer[-3] == '\\' && buffer[-2] == '.')
-				return -1;
-			/* No, return the data line to the caller */
-			return bufsize - avail;
-		}
-	}
+	/*
+	 * Cannot proceed unless it's a Copy Data message.  Anything else means
+	 * end of copy mode.
+	 */
+	if (id != 'd')
+		return -1;
 
 	/*
-	 * We don't have a complete line. We'd prefer to leave it in libpq's
-	 * buffer until the rest arrives, but there is a special case: what if
-	 * the line is longer than the buffer the caller is offering us?  In
-	 * that case we'd better hand over a partial line, else we'd get into
-	 * an infinite loop. Do this in a way that ensures we can't
-	 * misrecognize a terminator line later: leave last 3 characters in
-	 * libpq buffer.
+	 * Move data from libpq's buffer to the caller's.  In the case where
+	 * a prior call found the caller's buffer too small, we use
+	 * conn->copy_already_done to remember how much of the row was already
+	 * returned to the caller.
 	 */
-	if (avail == 0 && bufsize > 3)
+	conn->inCursor += conn->copy_already_done;
+	avail = msgLength - 4 - conn->copy_already_done;
+	if (avail <= bufsize)
 	{
-		conn->inStart = conn->inCursor - 3;
-		return bufsize - 3;
+		/* Able to consume the whole message */
+		memcpy(buffer, &conn->inBuffer[conn->inCursor], avail);
+		/* Mark message consumed */
+		conn->inStart = conn->inCursor + avail;
+		/* Reset state for next time */
+		conn->copy_already_done = 0;
+		return avail;
+	}
+	else
+	{
+		/* We must return a partial message */
+		memcpy(buffer, &conn->inBuffer[conn->inCursor], bufsize);
+		/* The message is NOT consumed from libpq's buffer */
+		conn->copy_already_done += bufsize;
+		return bufsize;
 	}
-	return 0;
 }
 
 /*
@@ -1774,14 +1874,21 @@ PQendcopy(PGconn *conn)
 	if (pqFlush(conn) && pqIsnonblocking(conn))
 		return (1);
 
-	/* non blocking connections may have to abort at this point. */
-	if (pqIsnonblocking(conn) && PQisBusy(conn))
-		return (1);
-
 	/* Return to active duty */
 	conn->asyncStatus = PGASYNC_BUSY;
 	resetPQExpBuffer(&conn->errorMessage);
 
+	/*
+	 * Non blocking connections may have to abort at this point.  If everyone
+	 * played the game there should be no problem, but in error scenarios
+	 * the expected messages may not have arrived yet.  (We are assuming that
+	 * the backend's packetizing will ensure that CommandComplete arrives
+	 * along with the CopyDone; are there corner cases where that doesn't
+	 * happen?)
+	 */
+	if (pqIsnonblocking(conn) && PQisBusy(conn))
+		return (1);
+
 	/* Wait for the completion response */
 	result = PQgetResult(conn);
 
@@ -1793,26 +1900,16 @@ PQendcopy(PGconn *conn)
 	}
 
 	/*
-	 * Trouble. The worst case is that we've lost sync with the backend
-	 * entirely due to application screwup of the copy in/out protocol. To
-	 * recover, reset the connection (talk about using a sledgehammer...)
+	 * Trouble. For backwards-compatibility reasons, we issue the error
+	 * message as if it were a notice (would be nice to get rid of this
+	 * silliness, but too many apps probably don't handle errors from
+	 * PQendcopy reasonably).  Note that the app can still obtain the
+	 * error status from the PGconn object.
 	 */
-	PQclear(result);
-
 	if (conn->errorMessage.len > 0)
 		DONOTICE(conn, conn->errorMessage.data);
 
-	DONOTICE(conn, libpq_gettext("lost synchronization with server, resetting connection\n"));
-
-	/*
-	 * Users doing non-blocking connections need to handle the reset
-	 * themselves, they'll need to check the connection status if we
-	 * return an error.
-	 */
-	if (pqIsnonblocking(conn))
-		PQresetStart(conn);
-	else
-		PQreset(conn);
+	PQclear(result);
 
 	return 1;
 }
@@ -1853,6 +1950,8 @@ PQfn(PGconn *conn,
 	bool		needInput = false;
 	ExecStatusType status = PGRES_FATAL_ERROR;
 	char		id;
+	int			msgLength;
+	int			avail;
 	int			i;
 
 	*actual_result_len = 0;
@@ -1927,11 +2026,55 @@ PQfn(PGconn *conn,
 		 * Scan the message. If we run out of data, loop around to try
 		 * again.
 		 */
-		conn->inCursor = conn->inStart;
 		needInput = true;
 
+		conn->inCursor = conn->inStart;
 		if (pqGetc(&id, conn))
 			continue;
+		if (pqGetInt(&msgLength, 4, conn))
+			continue;
+
+		/*
+		 * Try to validate message type/length here.  A length less than 4
+		 * is definitely broken.  Large lengths should only be believed
+		 * for a few message types.
+		 */
+		if (msgLength < 4)
+		{
+			handleSyncLoss(conn, id, msgLength);
+			break;
+		}
+		if (msgLength > 30000 &&
+			!(id == 'T' || id == 'D' || id == 'B' || id == 'd' || id == 'V'))
+		{
+			handleSyncLoss(conn, id, msgLength);
+			break;
+		}
+
+		/*
+		 * Can't process if message body isn't all here yet.
+		 */
+		msgLength -= 4;
+		avail = conn->inEnd - conn->inCursor;
+		if (avail < msgLength)
+		{
+			/*
+			 * Before looping, enlarge the input buffer if needed to hold
+			 * the whole message.  See notes in parseInput.
+			 */
+			if (pqCheckInBufferSpace(conn->inCursor + msgLength, conn))
+			{
+				/*
+				 * XXX add some better recovery code... plan is to skip
+				 * over the message using its length, then report an error.
+				 * For the moment, just treat this like loss of sync (which
+				 * indeed it might be!)
+				 */
+				handleSyncLoss(conn, id, msgLength);
+				break;
+			}
+			continue;
+		}
 
 		/*
 		 * We should see V or E response to the command, but might get N
@@ -1975,7 +2118,7 @@ PQfn(PGconn *conn,
 							  libpq_gettext("protocol error: id=0x%x\n"),
 									  id);
 					saveErrorResult(conn);
-					conn->inStart = conn->inCursor;
+					conn->inStart += 5 + msgLength;
 					return prepareAsyncResult(conn);
 				}
 				break;
@@ -1998,7 +2141,8 @@ PQfn(PGconn *conn,
 				break;
 			case 'Z':			/* backend is ready for new query */
 				/* consume the message and exit */
-				conn->inStart = conn->inCursor;
+				conn->inStart += 5 + msgLength;
+				/* XXX expect additional fields here */
 				/* if we saved a result object (probably an error), use it */
 				if (conn->result)
 					return prepareAsyncResult(conn);
@@ -2009,11 +2153,13 @@ PQfn(PGconn *conn,
 							  libpq_gettext("protocol error: id=0x%x\n"),
 								  id);
 				saveErrorResult(conn);
-				conn->inStart = conn->inCursor;
+				/* trust the specified message length as what to skip */
+				conn->inStart += 5 + msgLength;
 				return prepareAsyncResult(conn);
 		}
 		/* Completed this message, keep going */
-		conn->inStart = conn->inCursor;
+		/* trust the specified message length as what to skip */
+		conn->inStart += 5 + msgLength;
 		needInput = false;
 	}
 
diff --git a/src/interfaces/libpq/fe-misc.c b/src/interfaces/libpq/fe-misc.c
index dfc46fdf5987ce9f031cae23bffc7cf62b143b69..76de4a8708628468a7696477744bb58a20e77fee 100644
--- a/src/interfaces/libpq/fe-misc.c
+++ b/src/interfaces/libpq/fe-misc.c
@@ -23,7 +23,7 @@
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/interfaces/libpq/fe-misc.c,v 1.89 2003/04/19 00:02:30 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/interfaces/libpq/fe-misc.c,v 1.90 2003/04/22 00:08:07 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -277,12 +277,12 @@ pqPutInt(int value, size_t bytes, PGconn *conn)
 
 /*
  * Make sure conn's output buffer can hold bytes_needed bytes (caller must
- * include existing outCount into the value!)
+ * include already-stored data into the value!)
  *
- * Returns 0 on success, EOF on error
+ * Returns 0 on success, EOF if failed to enlarge buffer
  */
 static int
-checkOutBufferSpace(int bytes_needed, PGconn *conn)
+pqCheckOutBufferSpace(int bytes_needed, PGconn *conn)
 {
 	int			newsize = conn->outBufSize;
 	char	   *newbuf;
@@ -335,6 +335,66 @@ checkOutBufferSpace(int bytes_needed, PGconn *conn)
 	return EOF;
 }
 
+/*
+ * Make sure conn's input buffer can hold bytes_needed bytes (caller must
+ * include already-stored data into the value!)
+ *
+ * Returns 0 on success, EOF if failed to enlarge buffer
+ */
+int
+pqCheckInBufferSpace(int bytes_needed, PGconn *conn)
+{
+	int			newsize = conn->inBufSize;
+	char	   *newbuf;
+
+	if (bytes_needed <= newsize)
+		return 0;
+	/*
+	 * If we need to enlarge the buffer, we first try to double it in size;
+	 * if that doesn't work, enlarge in multiples of 8K.  This avoids
+	 * thrashing the malloc pool by repeated small enlargements.
+	 *
+	 * Note: tests for newsize > 0 are to catch integer overflow.
+	 */
+	do {
+		newsize *= 2;
+	} while (bytes_needed > newsize && newsize > 0);
+
+	if (bytes_needed <= newsize)
+	{
+		newbuf = realloc(conn->inBuffer, newsize);
+		if (newbuf)
+		{
+			/* realloc succeeded */
+			conn->inBuffer = newbuf;
+			conn->inBufSize = newsize;
+			return 0;
+		}
+	}
+
+	newsize = conn->inBufSize;
+	do {
+		newsize += 8192;
+	} while (bytes_needed > newsize && newsize > 0);
+
+	if (bytes_needed <= newsize)
+	{
+		newbuf = realloc(conn->inBuffer, newsize);
+		if (newbuf)
+		{
+			/* realloc succeeded */
+			conn->inBuffer = newbuf;
+			conn->inBufSize = newsize;
+			return 0;
+		}
+	}
+
+	/* realloc failed. Probably out of memory */
+	printfPQExpBuffer(&conn->errorMessage,
+					  "cannot allocate memory for input buffer\n");
+	return EOF;
+}
+
 /*
  * pqPutMsgStart: begin construction of a message to the server
  *
@@ -364,7 +424,7 @@ pqPutMsgStart(char msg_type, PGconn *conn)
 	else
 		lenPos = conn->outCount;
 	/* make sure there is room for it */
-	if (checkOutBufferSpace(lenPos + 4, conn))
+	if (pqCheckOutBufferSpace(lenPos + 4, conn))
 		return EOF;
 	/* okay, save the message type byte if any */
 	if (msg_type)
@@ -390,7 +450,7 @@ static int
 pqPutMsgBytes(const void *buf, size_t len, PGconn *conn)
 {
 	/* make sure there is room for it */
-	if (checkOutBufferSpace(conn->outMsgEnd + len, conn))
+	if (pqCheckOutBufferSpace(conn->outMsgEnd + len, conn))
 		return EOF;
 	/* okay, save the data */
 	memcpy(conn->outBuffer + conn->outMsgEnd, buf, len);
@@ -486,13 +546,13 @@ pqReadData(PGconn *conn)
 	 */
 	if (conn->inBufSize - conn->inEnd < 8192)
 	{
-		int			newSize = conn->inBufSize * 2;
-		char	   *newBuf = (char *) realloc(conn->inBuffer, newSize);
-
-		if (newBuf)
+		if (pqCheckInBufferSpace(conn->inEnd + 8192, conn))
 		{
-			conn->inBuffer = newBuf;
-			conn->inBufSize = newSize;
+			/*
+			 * We don't insist that the enlarge worked, but we need some room
+			 */
+			if (conn->inBufSize - conn->inEnd < 100)
+				return -1;		/* errorMessage already set */
 		}
 	}
 
diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h
index 8671922547d1cfceb518ccb61ae777831977bdf8..35e3208eb0efb1e05302d2409828684acddeb9ca 100644
--- a/src/interfaces/libpq/libpq-int.h
+++ b/src/interfaces/libpq/libpq-int.h
@@ -12,7 +12,7 @@
  * Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $Id: libpq-int.h,v 1.62 2003/04/19 00:02:30 tgl Exp $
+ * $Id: libpq-int.h,v 1.63 2003/04/22 00:08:07 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -56,7 +56,7 @@ typedef int ssize_t;			/* ssize_t doesn't exist in VC (atleast
  * pqcomm.h describe what the backend knows, not what libpq knows.
  */
 
-#define PG_PROTOCOL_LIBPQ	PG_PROTOCOL(3,101) /* XXX temporary value */
+#define PG_PROTOCOL_LIBPQ	PG_PROTOCOL(3,102) /* XXX temporary value */
 
 /*
  * POSTGRES backend dependent Constants.
@@ -216,7 +216,8 @@ struct pg_conn
 								 * is listening on; if NULL, uses a
 								 * default constructed from pgport */
 	char	   *pgtty;			/* tty on which the backend messages is
-								 * displayed (NOT ACTUALLY USED???) */
+								 * displayed (OBSOLETE, NOT USED) */
+	char	   *connect_timeout; /* connection timeout (numeric string) */
 	char	   *pgoptions;		/* options to start the backend with */
 	char	   *dbName;			/* database name */
 	char	   *pguser;			/* Postgres username and password, if any */
@@ -232,6 +233,10 @@ struct pg_conn
 	/* Status indicators */
 	ConnStatusType status;
 	PGAsyncStatusType asyncStatus;
+	char		copy_is_binary;	/* 1 = copy binary, 0 = copy text */
+	int			copy_already_done; /* # bytes already returned in COPY OUT */
+	int			nonblocking;	/* whether this connection is using a
+								 * blocking socket to the backend or not */
 	Dllist	   *notifyList;		/* Notify msgs not yet handed to
 								 * application */
 
@@ -246,6 +251,7 @@ struct pg_conn
 	int			be_key;			/* key of backend --- needed for cancels */
 	char		md5Salt[4];		/* password salt received from backend */
 	char		cryptSalt[2];	/* password salt received from backend */
+	int			client_encoding; /* encoding id */
 	PGlobjfuncs *lobjfuncs;		/* private state for large-object access
 								 * fns */
 
@@ -258,9 +264,6 @@ struct pg_conn
 	int			inEnd;			/* offset to first position after avail
 								 * data */
 
-	int			nonblocking;	/* whether this connection is using a
-								 * blocking socket to the backend or not */
-
 	/* Buffer for data not yet sent to backend */
 	char	   *outBuffer;		/* currently allocated buffer */
 	int			outBufSize;		/* allocated size of buffer */
@@ -291,10 +294,6 @@ struct pg_conn
 
 	/* Buffer for receiving various parts of messages */
 	PQExpBufferData workBuffer; /* expansible string */
-
-	int			client_encoding;	/* encoding id */
-
-	char	   *connect_timeout;
 };
 
 /* String descriptions of the ExecStatusTypes.
@@ -330,6 +329,7 @@ extern void pqClearAsyncResult(PGconn *conn);
   * for Get, EOF merely means the buffer is exhausted, not that there is
   * necessarily any error.
   */
+extern int	pqCheckInBufferSpace(int bytes_needed, PGconn *conn);
 extern int	pqGetc(char *result, PGconn *conn);
 extern int	pqPutc(char c, PGconn *conn);
 extern int	pqGets(PQExpBuffer buf, PGconn *conn);
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index 341f4ded1e00ee64c458d7a9627bc70b96924e25..4d1458f891624917093aed3a72c11338370bbeda 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -995,7 +995,6 @@ copy test("........pg.dropped.1........") to stdout;
 ERROR:  Relation "test" has no column "........pg.dropped.1........"
 copy test from stdin;
 ERROR:  copy: line 1, Extra data after last expected column
-lost synchronization with server, resetting connection
 SET autocommit TO 'on';
 select * from test;
  b | c 
diff --git a/src/test/regress/expected/copy2.out b/src/test/regress/expected/copy2.out
index cf28af8c198de5b26392bd50ce7e463df5eb3d75..983e6bb4a41af71dd2eb726a6110a12953d6728f 100644
--- a/src/test/regress/expected/copy2.out
+++ b/src/test/regress/expected/copy2.out
@@ -35,17 +35,13 @@ ERROR:  Attribute "d" specified more than once
 -- missing data: should fail
 COPY x from stdin;
 ERROR:  copy: line 1, pg_atoi: zero-length string
-lost synchronization with server, resetting connection
 COPY x from stdin;
 ERROR:  copy: line 1, Missing data for column "e"
-lost synchronization with server, resetting connection
 COPY x from stdin;
 ERROR:  copy: line 1, Missing data for column "e"
-lost synchronization with server, resetting connection
 -- extra data: should fail
 COPY x from stdin;
 ERROR:  copy: line 1, Extra data after last expected column
-lost synchronization with server, resetting connection
 SET autocommit TO 'on';
 -- various COPY options: delimiters, oids, NULL string
 COPY x (b, c, d, e) from stdin with oids delimiter ',' null 'x';
diff --git a/src/test/regress/expected/domain.out b/src/test/regress/expected/domain.out
index 1aaa4a85ef43cbda576ba8a3fc2e362f021ab4f3..13eb14cfa2f0d17df66f8629fa1b81986c2161ad 100644
--- a/src/test/regress/expected/domain.out
+++ b/src/test/regress/expected/domain.out
@@ -40,7 +40,6 @@ INSERT INTO basictest values ('88', 'haha', 'short', '123.1212');    -- Truncate
 -- Test copy
 COPY basictest (testvarchar) FROM stdin; -- fail
 ERROR:  copy: line 1, value too long for type character varying(5)
-lost synchronization with server, resetting connection
 SET autocommit TO 'on';
 COPY basictest (testvarchar) FROM stdin;
 select * from basictest;
@@ -128,12 +127,10 @@ INSERT INTO nulltest values ('a', 'b', 'c', NULL, 'd'); -- Good
 -- Test copy
 COPY nulltest FROM stdin; --fail
 ERROR:  copy: line 1, Domain dcheck does not allow NULL values
-lost synchronization with server, resetting connection
 SET autocommit TO 'on';
 -- Last row is bad
 COPY nulltest FROM stdin;
 ERROR:  copy: line 3, CopyFrom: rejected due to CHECK constraint "nulltest_col5" on "nulltest"
-lost synchronization with server, resetting connection
 select * from nulltest;
  col1 | col2 | col3 | col4 | col5 
 ------+------+------+------+------