diff --git a/src/bin/psql/copy.c b/src/bin/psql/copy.c
index 22fcc5975e5c42ecf3b20f0de586549397f8b4dd..7459a8c7c879311b6d02eb11f76ff5e1705529b5 100644
--- a/src/bin/psql/copy.c
+++ b/src/bin/psql/copy.c
@@ -363,15 +363,15 @@ handleCopyOut(PGconn *conn, FILE *copystream)
 		ret = PQgetCopyData(conn, &buf, 0);
 
 		if (ret < 0)
-			break;				/* done or error */
+			break;				/* done or server/connection error */
 
 		if (buf)
 		{
-			if (fwrite(buf, 1, ret, copystream) != ret)
+			if (OK && fwrite(buf, 1, ret, copystream) != ret)
 			{
-				if (OK)			/* complain only once, keep reading data */
-					psql_error("could not write COPY data: %s\n",
-							   strerror(errno));
+				psql_error("could not write COPY data: %s\n",
+						   strerror(errno));
+				/* complain only once, keep reading data from server */
 				OK = false;
 			}
 			PQfreemem(buf);
@@ -392,29 +392,18 @@ handleCopyOut(PGconn *conn, FILE *copystream)
 	}
 
 	/*
-	 * Check command status and return to normal libpq state.  After a
-	 * client-side error, the server will remain ready to deliver data.  The
-	 * cleanest thing is to fully drain and discard that data.	If the
-	 * client-side error happened early in a large file, this takes a long
-	 * time.  Instead, take advantage of the fact that PQexec() will silently
-	 * end any ongoing PGRES_COPY_OUT state.  This does cause us to lose the
-	 * results of any commands following the COPY in a single command string.
-	 * It also only works for protocol version 3.  XXX should we clean up
-	 * using the slow way when the connection is using protocol version 2?
+	 * Check command status and return to normal libpq state.
 	 *
-	 * We must not ever return with the status still PGRES_COPY_OUT.  Our
-	 * caller is unable to distinguish that situation from reaching the next
-	 * COPY in a command string that happened to contain two consecutive COPY
-	 * TO STDOUT commands.	We trust that no condition can make PQexec() fail
-	 * indefinitely while retaining status PGRES_COPY_OUT.
+	 * If for some reason libpq is still reporting PGRES_COPY_OUT state, we
+	 * would like to forcibly exit that state, since our caller would be
+	 * unable to distinguish that situation from reaching the next COPY in a
+	 * command string that happened to contain two consecutive COPY TO STDOUT
+	 * commands.  However, libpq provides no API for doing that, and in
+	 * principle it's a libpq bug anyway if PQgetCopyData() returns -1 or -2
+	 * but hasn't exited COPY_OUT state internally.  So we ignore the
+	 * possibility here.
 	 */
-	while (res = PQgetResult(conn), PQresultStatus(res) == PGRES_COPY_OUT)
-	{
-		OK = false;
-		PQclear(res);
-
-		PQexec(conn, "-- clear PGRES_COPY_OUT state");
-	}
+	res = PQgetResult(conn);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
 	{
 		psql_error("%s", PQerrorMessage(conn));
@@ -457,7 +446,9 @@ handleCopyIn(PGconn *conn, FILE *copystream, bool isbinary)
 		/* got here with longjmp */
 
 		/* Terminate data transfer */
-		PQputCopyEnd(conn, _("canceled by user"));
+		PQputCopyEnd(conn,
+					 (PQprotocolVersion(conn) < 3) ? NULL :
+					 _("canceled by user"));
 
 		OK = false;
 		goto copyin_cleanup;
@@ -578,29 +569,37 @@ handleCopyIn(PGconn *conn, FILE *copystream, bool isbinary)
 	if (ferror(copystream))
 		OK = false;
 
-	/* Terminate data transfer */
+	/*
+	 * Terminate data transfer.  We can't send an error message if we're using
+	 * protocol version 2.
+	 */
 	if (PQputCopyEnd(conn,
-					 OK ? NULL : _("aborted because of read failure")) <= 0)
+					 (OK || PQprotocolVersion(conn) < 3) ? NULL :
+					 _("aborted because of read failure")) <= 0)
 		OK = false;
 
 copyin_cleanup:
 
 	/*
-	 * Check command status and return to normal libpq state
+	 * Check command status and return to normal libpq state.
 	 *
-	 * We must not ever return with the status still PGRES_COPY_IN.  Our
-	 * caller is unable to distinguish that situation from reaching the next
-	 * COPY in a command string that happened to contain two consecutive COPY
-	 * FROM STDIN commands.  XXX if something makes PQputCopyEnd() fail
-	 * indefinitely while retaining status PGRES_COPY_IN, we get an infinite
-	 * loop.  This is more realistic than handleCopyOut()'s counterpart risk.
+	 * We do not want to return with the status still PGRES_COPY_IN: our
+	 * caller would be unable to distinguish that situation from reaching the
+	 * next COPY in a command string that happened to contain two consecutive
+	 * COPY FROM STDIN commands.  We keep trying PQputCopyEnd() in the hope
+	 * it'll work eventually.  (What's actually likely to happen is that in
+	 * attempting to flush the data, libpq will eventually realize that the
+	 * connection is lost.	But that's fine; it will get us out of COPY_IN
+	 * state, which is what we need.)
 	 */
 	while (res = PQgetResult(conn), PQresultStatus(res) == PGRES_COPY_IN)
 	{
 		OK = false;
 		PQclear(res);
-
-		PQputCopyEnd(pset.db, _("trying to exit copy mode"));
+		/* We can't send an error message if we're using protocol version 2 */
+		PQputCopyEnd(conn,
+					 (PQprotocolVersion(conn) < 3) ? NULL :
+					 _("trying to exit copy mode"));
 	}
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
 	{