From 09a893117a9fe0ae5e7e5c6a6a5496e9e6826f4c Mon Sep 17 00:00:00 2001
From: Tom Lane <tgl@sss.pgh.pa.us>
Date: Sun, 26 Sep 2004 00:26:28 +0000
Subject: [PATCH] Repair bug that would allow libpq to think a command had
 succeeded when it really hadn't, due to double output of previous command's
 response. Fix prevents recursive entry to libpq routines.  Found by Jan
 Wieck.

---
 src/backend/libpq/pqcomm.c  | 106 +++++++++++++++++++++++++++++-------
 src/backend/tcop/postgres.c |   5 +-
 src/include/libpq/libpq.h   |   3 +-
 3 files changed, 93 insertions(+), 21 deletions(-)

diff --git a/src/backend/libpq/pqcomm.c b/src/backend/libpq/pqcomm.c
index a8ce982bdb9..805577bd774 100644
--- a/src/backend/libpq/pqcomm.c
+++ b/src/backend/libpq/pqcomm.c
@@ -30,7 +30,7 @@
  * Portions Copyright (c) 1996-2004, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- *	$PostgreSQL: pgsql/src/backend/libpq/pqcomm.c,v 1.171 2004/08/29 05:06:43 momjian Exp $
+ *	$PostgreSQL: pgsql/src/backend/libpq/pqcomm.c,v 1.172 2004/09/26 00:26:19 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -44,6 +44,7 @@
  *		StreamClose			- Close a client/backend connection
  *		TouchSocketFile		- Protect socket file against /tmp cleaners
  *		pq_init			- initialize libpq at backend startup
+ *		pq_comm_reset	- reset libpq during error recovery
  *		pq_close		- shutdown libpq at backend exit
  *
  * low-level I/O:
@@ -88,14 +89,6 @@
 #include "storage/ipc.h"
 
 
-static void pq_close(int code, Datum arg);
-
-#ifdef HAVE_UNIX_SOCKETS
-static int	Lock_AF_UNIX(unsigned short portNumber, char *unixSocketName);
-static int	Setup_AF_UNIX(void);
-#endif   /* HAVE_UNIX_SOCKETS */
-
-
 /*
  * Configuration options
  */
@@ -103,6 +96,10 @@ int			Unix_socket_permissions;
 char	   *Unix_socket_group;
 
 
+/* Where the Unix socket file is */
+static char sock_path[MAXPGPATH];
+
+
 /*
  * Buffers for low-level I/O
  */
@@ -121,9 +118,20 @@ static int	PqRecvLength;		/* End of data available in PqRecvBuffer */
 /*
  * Message status
  */
+static bool PqCommBusy;
 static bool DoingCopyOut;
 
 
+/* Internal functions */
+static void pq_close(int code, Datum arg);
+static int	internal_putbytes(const char *s, size_t len);
+static int	internal_flush(void);
+#ifdef HAVE_UNIX_SOCKETS
+static int	Lock_AF_UNIX(unsigned short portNumber, char *unixSocketName);
+static int	Setup_AF_UNIX(void);
+#endif   /* HAVE_UNIX_SOCKETS */
+
+
 /* --------------------------------
  *		pq_init - initialize libpq at backend startup
  * --------------------------------
@@ -132,10 +140,27 @@ void
 pq_init(void)
 {
 	PqSendPointer = PqRecvPointer = PqRecvLength = 0;
+	PqCommBusy = false;
 	DoingCopyOut = false;
 	on_proc_exit(pq_close, 0);
 }
 
+/* --------------------------------
+ *		pq_comm_reset - reset libpq during error recovery
+ *
+ * This is called from error recovery at the outer idle loop.  It's
+ * just to get us out of trouble if we somehow manage to elog() from
+ * inside a pqcomm.c routine (which ideally will never happen, but...)
+ * --------------------------------
+ */
+void
+pq_comm_reset(void)
+{
+	/* Do not throw away pending data, but do reset the busy flag */
+	PqCommBusy = false;
+	/* We can abort any old-style COPY OUT, too */
+	pq_endcopyout(true);
+}
 
 /* --------------------------------
  *		pq_close - shutdown libpq at backend exit
@@ -174,8 +199,6 @@ pq_close(int code, Datum arg)
  *		Stream functions are used for vanilla TCP connection protocol.
  */
 
-static char sock_path[MAXPGPATH];
-
 
 /* StreamDoUnlink()
  * Shutdown routine for backend connection
@@ -885,13 +908,30 @@ pq_getmessage(StringInfo s, int maxlen)
  */
 int
 pq_putbytes(const char *s, size_t len)
+{
+	int			res;
+
+	/* Should only be called by old-style COPY OUT */
+	Assert(DoingCopyOut);
+	/* No-op if reentrant call */
+	if (PqCommBusy)
+		return 0;
+	PqCommBusy = true;
+	res = internal_putbytes(s, len);
+	PqCommBusy = false;
+	return res;
+}
+
+static int
+internal_putbytes(const char *s, size_t len)
 {
 	size_t		amount;
 
 	while (len > 0)
 	{
+		/* If buffer is full, then flush it out */
 		if (PqSendPointer >= PQ_BUFFER_SIZE)
-			if (pq_flush())		/* If buffer is full, then flush it out */
+			if (internal_flush())
 				return EOF;
 		amount = PQ_BUFFER_SIZE - PqSendPointer;
 		if (amount > len)
@@ -912,6 +952,20 @@ pq_putbytes(const char *s, size_t len)
  */
 int
 pq_flush(void)
+{
+	int			res;
+
+	/* No-op if reentrant call */
+	if (PqCommBusy)
+		return 0;
+	PqCommBusy = true;
+	res = internal_flush();
+	PqCommBusy = false;
+	return res;
+}
+
+static int
+internal_flush(void)
 {
 	static int	last_reported_send_errno = 0;
 
@@ -988,26 +1042,40 @@ pq_flush(void)
  *		then; dropping them is annoying, but at least they will still appear
  *		in the postmaster log.)
  *
+ *		We also suppress messages generated while pqcomm.c is busy.  This
+ *		avoids any possibility of messages being inserted within other
+ *		messages.  The only known trouble case arises if SIGQUIT occurs
+ *		during a pqcomm.c routine --- quickdie() will try to send a warning
+ *		message, and the most reasonable approach seems to be to drop it.
+ *
  *		returns 0 if OK, EOF if trouble
  * --------------------------------
  */
 int
 pq_putmessage(char msgtype, const char *s, size_t len)
 {
-	if (DoingCopyOut)
+	if (DoingCopyOut || PqCommBusy)
 		return 0;
+	PqCommBusy = true;
 	if (msgtype)
-		if (pq_putbytes(&msgtype, 1))
-			return EOF;
+		if (internal_putbytes(&msgtype, 1))
+			goto fail;
 	if (PG_PROTOCOL_MAJOR(FrontendProtocol) >= 3)
 	{
 		uint32		n32;
 
 		n32 = htonl((uint32) (len + 4));
-		if (pq_putbytes((char *) &n32, 4))
-			return EOF;
+		if (internal_putbytes((char *) &n32, 4))
+			goto fail;
 	}
-	return pq_putbytes(s, len);
+	if (internal_putbytes(s, len))
+		goto fail;
+	PqCommBusy = false;
+	return 0;
+
+fail:
+	PqCommBusy = false;
+	return EOF;
 }
 
 /* --------------------------------
@@ -1036,8 +1104,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/tcop/postgres.c b/src/backend/tcop/postgres.c
index 85fcc49c839..5658e10d453 100644
--- a/src/backend/tcop/postgres.c
+++ b/src/backend/tcop/postgres.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/tcop/postgres.c,v 1.432 2004/09/13 20:07:05 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/tcop/postgres.c,v 1.433 2004/09/26 00:26:25 tgl Exp $
  *
  * NOTES
  *	  this is the "main" module of the postgres backend and
@@ -2811,6 +2811,9 @@ PostgresMain(int argc, char *argv[], const char *username)
 		DisableNotifyInterrupt();
 		DisableCatchupInterrupt();
 
+		/* Make sure libpq is in a good state */
+		pq_comm_reset();
+
 		/* Report the error to the client and/or server log */
 		EmitErrorReport();
 
diff --git a/src/include/libpq/libpq.h b/src/include/libpq/libpq.h
index bf58f75ac28..839d665d3c7 100644
--- a/src/include/libpq/libpq.h
+++ b/src/include/libpq/libpq.h
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2004, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/libpq/libpq.h,v 1.62 2004/08/29 04:13:07 momjian Exp $
+ * $PostgreSQL: pgsql/src/include/libpq/libpq.h,v 1.63 2004/09/26 00:26:28 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -52,6 +52,7 @@ extern int	StreamConnection(int server_fd, Port *port);
 extern void StreamClose(int sock);
 extern void TouchSocketFile(void);
 extern void pq_init(void);
+extern void pq_comm_reset(void);
 extern int	pq_getbytes(char *s, size_t len);
 extern int	pq_getstring(StringInfo s);
 extern int	pq_getmessage(StringInfo s, int maxlen);
-- 
GitLab