Re: libpq should append auth failures, not overwrite - Mailing list pgsql-hackers

From Tom Lane
Subject Re: libpq should append auth failures, not overwrite
Date
Msg-id 25918.1533918960@sss.pgh.pa.us
Whole thread Raw
In response to Re: libpq should append auth failures, not overwrite  (Michael Paquier <michael@paquier.xyz>)
Responses Re: libpq should append auth failures, not overwrite
List pgsql-hackers
Michael Paquier <michael@paquier.xyz> writes:
> On Thu, Aug 09, 2018 at 11:44:27AM -0400, Tom Lane wrote:
>> I noticed that, although most error reports during libpq's connection
>> setup code append to conn->errorMessage, the ones in fe-auth.c and
>> fe-auth-scram.c don't: they're all printfPQExpBuffer() not
>> appendPQExpBuffer().  This seems wrong to me.  It makes no difference
>> in simple cases with a single target server, but as soon as you have
>> multiple servers listed in "host", this coding makes it impossible
>> to tell which server rejected your login.

> +1.

So I thought this would be a trivial patch barely even worthy of posting,
but an awful lot of critters crawled out from under that rock.  It's not
just fe-auth*.c at fault; low-level failures such as timeout errors were
also clearing conn->errorMessage, and if you got an actual server error
(like "role does not exist"), that did too.  What I ended up doing was a
wholesale conversion of libpq's management of conn->errorMessage.  Now,
there are a few identified points at connection start or query start that
explicitly clear errorMessage, and everyplace else in libpq is supposed to
append to it, never just printf onto it.  (Interestingly, all of those
"identified points" already did clear errorMessage, meaning that a large
fraction of the existing printf's would always have started with an empty
errorMessage anyway.)

The first patch attached does that, and then the second one is a proposed
modification in the way we report failures for multi-host connection
attempts: let's explicitly label each section of conn->errorMessage with
the host we're trying to connect to.  This can replace the ad-hoc labeling
that somebody thought would be a good idea for two errors associated with
the target_session_attrs=read-write feature.  (That's not a bad idea in
itself, but doing it only for those two errors is.)

Here's some examples of the kind of connection failure reports the code
can produce with these patches:

$ psql "host=refusenik,dead,localhost user=readonly dbname=postgres connect_timeout=3 target_session_attrs=read-write"
psql: server "refusenik" port 5432:
could not connect to server: Connection refused
        Is the server running on host "refusenik" (192.168.1.43) and accepting
        TCP/IP connections on port 5432?
server "dead" port 5432:
timeout expired
server "localhost" port 5432:
could not open a read-write session

$ psql "host=refusenik,dead,localhost user=nobody dbname=postgres"
psql: server "refusenik" port 5432:
could not connect to server: Connection refused
        Is the server running on host "refusenik" (192.168.1.43) and accepting
        TCP/IP connections on port 5432?
server "dead" port 5432:
timeout expired
server "localhost" port 5432:
FATAL:  role "nobody" does not exist

Before, you'd get a rather random subset of these messages depending on
which parts of the code decided to clear conn->errorMessage, and in
many cases it'd be really hard to tell which server was involved with
which message(s).

Some points for discussion and review:

1.  The bulk of patch 0001 is indeed just
s/printfPQExpBuffer/appendPQExpBuffer/g, though I also made an attempt
to use appendPQExpBufferStr wherever possible.  There are two categories
of printfPQExpBuffer calls I didn't change:

1a. Calls reporting an out-of-memory situation.  There was already a
policy in some places that we'd intentionally printf not append such
messages, and I made that explicit.  The idea is that we might not
have room to add more text to errorMessage, so resetting the buffer
provides more certainty of success.  However, this makes for a pretty
weird inconsistency in the code; there are a lot of functions now in
which half of the messages are printf'd and half are append'd, so I'm
afraid that future patches will get it wrong as to which to use.  Also,
in reality there often *would* be room to append "out of memory" without
enlarging errorMessage's buffer, so that this could just be pointless
destruction of useful error history.  I didn't do anything about it here,
but I'm tempted to replace all the printf's for OOM messages with a
subroutine that will append if it can do so without enlarging the existing
buffer, else replace.

1b. There are a few places where it didn't seem worth changing anything
because it'd just result in needing to add a resetPQExpBuffer in the same
function or very nearby.  Mostly in fe-lobj.c.

2. I had to drop some code (notably in pqPrepareAsyncResult) that
attempted to force conn->errorMessage to always have the same contents
as the current PGresult's PQresultErrorMessage; as it stood, server
errors such as "role does not exist" wiped out preceding contents of
errorMessage, breaking cases such as the second example above.  This is
slightly annoying, but in the new dispensation it's possible that
conn->errorMessage contains a mix of libpq-generated errors and text
received from the server, so I'm not sure that preserving the old
equivalence is a useful goal anyway.  We shouldn't cram pre-existing
errorMessage text back into a server-generated error; that would
invalidate the server's auxiliary error info such as the SQLSTATE.
I don't know if it's possible to do better here.  One idea is to tweak
pqPrepareAsyncResult so that it does the message synchronization only
when in CONNECTION_OK state, allowing conn->errorMessage to be a
historical record only for connection processing.  But that seems like
a hack.

3. In some places I failed to resist the temptation to copy-edit error
messages that didn't meet style guidelines, or to add libpq_gettext()
annotation where it seemed to have been missed.

4. connectDBComplete() cleared errorMessage after a successful
completion, but that's the wrong place to do it; we have to do that
at the success exits from PQconnectPoll, else applications that
use PQconnectPoll directly will see leftover garbage there.

5. There were places in PQconnectPoll that temporarily set
conn->status = CONNECTION_OK to prevent PQsendQueryStart from
complaining.  A better idea is to leave the status alone and change
that test in PQsendQueryStart to be status != CONNECTION_BAD.  This
not only simplifies PQconnectPoll, but allows PQsendQueryStart
to tell whether we're still in the connection sequence.  I fixed
it to not clear errorMessage when we are, thus solving the problem
of PQsendQuery destroying prior connection error history.  That allows
getting rid of saveErrorMessage/restoreErrorMessage, which were
(a) an incredibly ugly and inefficient hack, and (b) inadequate,
since there were other paths through PQconnectPoll that could reach
PQsendQueryStart but were not protected with calls to those.

6. In the 0002 patch, I have it printing the annotation only when
there's more than one connhost[], and only once per connhost not
once per IP address.  The motivation for doing it like that was to
not change the behavior for simple cases with only one host name.
There is certainly room to argue that we should make an annotation
once per IP address, but that'd change the behavior for pretty common
cases like "localhost" resolving to both 127.0.0.1 and ::1, so I don't
know if it'd be a good idea or not.  There's also an interaction to
consider with my patch at
https://www.postgresql.org/message-id/4913.1533827102%40sss.pgh.pa.us
namely that we have to be sure the labeling behaves sanely for
connhosts that fail to resolve any IP addresses.

I'll put this into the September commitfest.

            regards, tom lane

diff --git a/src/interfaces/libpq/fe-auth-scram.c b/src/interfaces/libpq/fe-auth-scram.c
index 1d9c937..0d2b590 100644
--- a/src/interfaces/libpq/fe-auth-scram.c
+++ b/src/interfaces/libpq/fe-auth-scram.c
@@ -185,14 +185,14 @@ pg_fe_scram_exchange(void *opaq, char *input, int inputlen,
     {
         if (inputlen == 0)
         {
-            printfPQExpBuffer(&conn->errorMessage,
-                              libpq_gettext("malformed SCRAM message (empty message)\n"));
+            appendPQExpBufferStr(&conn->errorMessage,
+                                 libpq_gettext("malformed SCRAM message (empty message)\n"));
             goto error;
         }
         if (inputlen != strlen(input))
         {
-            printfPQExpBuffer(&conn->errorMessage,
-                              libpq_gettext("malformed SCRAM message (length mismatch)\n"));
+            appendPQExpBufferStr(&conn->errorMessage,
+                                 libpq_gettext("malformed SCRAM message (length mismatch)\n"));
             goto error;
         }
     }
@@ -240,17 +240,17 @@ pg_fe_scram_exchange(void *opaq, char *input, int inputlen,
             else
             {
                 *success = false;
-                printfPQExpBuffer(&conn->errorMessage,
-                                  libpq_gettext("incorrect server signature\n"));
+                appendPQExpBufferStr(&conn->errorMessage,
+                                     libpq_gettext("incorrect server signature\n"));
             }
             *done = true;
             state->state = FE_SCRAM_FINISHED;
             break;

         default:
-            /* shouldn't happen */
-            printfPQExpBuffer(&conn->errorMessage,
-                              libpq_gettext("invalid SCRAM exchange state\n"));
+            /* shouldn't happen, so we don't translate the msg */
+            appendPQExpBufferStr(&conn->errorMessage,
+                                 "invalid SCRAM exchange state\n");
             goto error;
     }
     return;
@@ -272,7 +272,7 @@ read_attr_value(char **input, char attr, PQExpBuffer errorMessage)

     if (*begin != attr)
     {
-        printfPQExpBuffer(errorMessage,
+        appendPQExpBuffer(errorMessage,
                           libpq_gettext("malformed SCRAM message (attribute \"%c\" expected)\n"),
                           attr);
         return NULL;
@@ -281,7 +281,7 @@ read_attr_value(char **input, char attr, PQExpBuffer errorMessage)

     if (*begin != '=')
     {
-        printfPQExpBuffer(errorMessage,
+        appendPQExpBuffer(errorMessage,
                           libpq_gettext("malformed SCRAM message (expected character \"=\" for attribute \"%c\")\n"),
                           attr);
         return NULL;
@@ -322,8 +322,8 @@ build_client_first_message(fe_scram_state *state)
      */
     if (!pg_frontend_random(raw_nonce, SCRAM_RAW_NONCE_LEN))
     {
-        printfPQExpBuffer(&conn->errorMessage,
-                          libpq_gettext("could not generate nonce\n"));
+        appendPQExpBufferStr(&conn->errorMessage,
+                             libpq_gettext("could not generate nonce\n"));
         return NULL;
     }

@@ -471,13 +471,13 @@ build_client_final_message(fe_scram_state *state)
 #else
         /*
          * Chose channel binding, but the SSL library doesn't support it.
-         * Shouldn't happen.
+         * Shouldn't happen, so we don't translate the msg.
          */
         termPQExpBuffer(&buf);
-        printfPQExpBuffer(&conn->errorMessage,
-                          "channel binding not supported by this build\n");
+        appendPQExpBufferStr(&conn->errorMessage,
+                             "channel binding not supported by this build\n");
         return NULL;
-#endif    /* HAVE_PGTLS_GET_PEER_CERTIFICATE_HASH */
+#endif                            /* HAVE_PGTLS_GET_PEER_CERTIFICATE_HASH */
     }
 #ifdef HAVE_PGTLS_GET_PEER_CERTIFICATE_HASH
     else if (conn->ssl_in_use)
@@ -557,8 +557,8 @@ read_server_first_message(fe_scram_state *state, char *input)
     if (strlen(nonce) < strlen(state->client_nonce) ||
         memcmp(nonce, state->client_nonce, strlen(state->client_nonce)) != 0)
     {
-        printfPQExpBuffer(&conn->errorMessage,
-                          libpq_gettext("invalid SCRAM response (nonce mismatch)\n"));
+        appendPQExpBufferStr(&conn->errorMessage,
+                             libpq_gettext("invalid SCRAM response (nonce mismatch)\n"));
         return false;
     }

@@ -596,14 +596,14 @@ read_server_first_message(fe_scram_state *state, char *input)
     state->iterations = strtol(iterations_str, &endptr, 10);
     if (*endptr != '\0' || state->iterations < 1)
     {
-        printfPQExpBuffer(&conn->errorMessage,
-                          libpq_gettext("malformed SCRAM message (invalid iteration count)\n"));
+        appendPQExpBufferStr(&conn->errorMessage,
+                             libpq_gettext("malformed SCRAM message (invalid iteration count)\n"));
         return false;
     }

     if (*input != '\0')
-        printfPQExpBuffer(&conn->errorMessage,
-                          libpq_gettext("malformed SCRAM message (garbage at end of server-first-message)\n"));
+        appendPQExpBufferStr(&conn->errorMessage,
+                             libpq_gettext("malformed SCRAM message (garbage at end of server-first-message)\n"));

     return true;
 }
@@ -632,7 +632,7 @@ read_server_final_message(fe_scram_state *state, char *input)
         char       *errmsg = read_attr_value(&input, 'e',
                                              &conn->errorMessage);

-        printfPQExpBuffer(&conn->errorMessage,
+        appendPQExpBuffer(&conn->errorMessage,
                           libpq_gettext("error received from server in SCRAM exchange: %s\n"),
                           errmsg);
         return false;
@@ -648,16 +648,16 @@ read_server_final_message(fe_scram_state *state, char *input)
     }

     if (*input != '\0')
-        printfPQExpBuffer(&conn->errorMessage,
-                          libpq_gettext("malformed SCRAM message (garbage at end of server-final-message)\n"));
+        appendPQExpBufferStr(&conn->errorMessage,
+                             libpq_gettext("malformed SCRAM message (garbage at end of server-final-message)\n"));

     server_signature_len = pg_b64_decode(encoded_server_signature,
                                          strlen(encoded_server_signature),
                                          state->ServerSignature);
     if (server_signature_len != SCRAM_KEY_LEN)
     {
-        printfPQExpBuffer(&conn->errorMessage,
-                          libpq_gettext("malformed SCRAM message (invalid server signature)\n"));
+        appendPQExpBufferStr(&conn->errorMessage,
+                             libpq_gettext("malformed SCRAM message (invalid server signature)\n"));
         return false;
     }

diff --git a/src/interfaces/libpq/fe-auth.c b/src/interfaces/libpq/fe-auth.c
index 540aba9..aa44cef 100644
--- a/src/interfaces/libpq/fe-auth.c
+++ b/src/interfaces/libpq/fe-auth.c
@@ -81,14 +81,12 @@ pg_GSS_error_int(PQExpBuffer str, const char *mprefix,
 }

 /*
- * GSSAPI errors contain two parts; put both into conn->errorMessage.
+ * GSSAPI errors contain two parts; append both to conn->errorMessage.
  */
 static void
 pg_GSS_error(const char *mprefix, PGconn *conn,
              OM_uint32 maj_stat, OM_uint32 min_stat)
 {
-    resetPQExpBuffer(&conn->errorMessage);
-
     /* Fetch major error codes */
     pg_GSS_error_int(&conn->errorMessage, mprefix, maj_stat, GSS_C_GSS_CODE);

@@ -203,15 +201,15 @@ pg_GSS_startup(PGconn *conn, int payloadlen)

     if (!(host && host[0] != '\0'))
     {
-        printfPQExpBuffer(&conn->errorMessage,
-                          libpq_gettext("host name must be specified\n"));
+        appendPQExpBufferStr(&conn->errorMessage,
+                             libpq_gettext("host name must be specified\n"));
         return STATUS_ERROR;
     }

     if (conn->gctx)
     {
-        printfPQExpBuffer(&conn->errorMessage,
-                          libpq_gettext("duplicate GSS authentication request\n"));
+        appendPQExpBufferStr(&conn->errorMessage,
+                             libpq_gettext("duplicate GSS authentication request\n"));
         return STATUS_ERROR;
     }

@@ -268,10 +266,10 @@ pg_SSPI_error(PGconn *conn, const char *mprefix, SECURITY_STATUS r)
                       FORMAT_MESSAGE_FROM_SYSTEM,
                       NULL, r, 0,
                       sysmsg, sizeof(sysmsg), NULL) == 0)
-        printfPQExpBuffer(&conn->errorMessage, "%s: SSPI error %x\n",
+        appendPQExpBuffer(&conn->errorMessage, "%s: SSPI error %x\n",
                           mprefix, (unsigned int) r);
     else
-        printfPQExpBuffer(&conn->errorMessage, "%s: %s (%x)\n",
+        appendPQExpBuffer(&conn->errorMessage, "%s: %s (%x)\n",
                           mprefix, sysmsg, (unsigned int) r);
 }

@@ -376,9 +374,11 @@ pg_SSPI_continue(PGconn *conn, int payloadlen)
             /*
              * This should never happen, at least not for Kerberos
              * authentication. Keep check in case it shows up with other
-             * authentication methods later.
+             * authentication methods later.  We intentionally don't translate
+             * the message, since we aren't expecting it to happen.
              */
-            printfPQExpBuffer(&conn->errorMessage, "SSPI returned invalid number of output buffers\n");
+            appendPQExpBufferStr(&conn->errorMessage,
+                                 "SSPI returned invalid number of output buffers\n");
             return STATUS_ERROR;
         }

@@ -418,8 +418,8 @@ pg_SSPI_startup(PGconn *conn, int use_negotiate, int payloadlen)

     if (conn->sspictx)
     {
-        printfPQExpBuffer(&conn->errorMessage,
-                          libpq_gettext("duplicate SSPI authentication request\n"));
+        appendPQExpBufferStr(&conn->errorMessage,
+                             libpq_gettext("duplicate SSPI authentication request\n"));
         return STATUS_ERROR;
     }

@@ -457,8 +457,8 @@ pg_SSPI_startup(PGconn *conn, int use_negotiate, int payloadlen)
      */
     if (!(host && host[0] != '\0'))
     {
-        printfPQExpBuffer(&conn->errorMessage,
-                          libpq_gettext("host name must be specified\n"));
+        appendPQExpBufferStr(&conn->errorMessage,
+                             libpq_gettext("host name must be specified\n"));
         return STATUS_ERROR;
     }
     conn->sspitarget = malloc(strlen(conn->krbsrvname) + strlen(host) + 2);
@@ -497,8 +497,8 @@ pg_SASL_init(PGconn *conn, int payloadlen)

     if (conn->sasl_state)
     {
-        printfPQExpBuffer(&conn->errorMessage,
-                          libpq_gettext("duplicate SASL authentication request\n"));
+        appendPQExpBufferStr(&conn->errorMessage,
+                             libpq_gettext("duplicate SASL authentication request\n"));
         goto error;
     }

@@ -513,8 +513,8 @@ pg_SASL_init(PGconn *conn, int payloadlen)
     {
         if (pqGets(&mechanism_buf, conn))
         {
-            printfPQExpBuffer(&conn->errorMessage,
-                              "fe_sendauth: invalid authentication request from server: invalid list of authentication
mechanisms\n");
+            appendPQExpBufferStr(&conn->errorMessage,
+                                 libpq_gettext("invalid authentication request from server: invalid list of
authenticationmechanisms\n")); 
             goto error;
         }
         if (PQExpBufferDataBroken(mechanism_buf))
@@ -545,8 +545,8 @@ pg_SASL_init(PGconn *conn, int payloadlen)
                  * the client and server supported it. The SCRAM exchange
                  * checks for that, to prevent downgrade attacks.
                  */
-                printfPQExpBuffer(&conn->errorMessage,
-                                  libpq_gettext("server offered SCRAM-SHA-256-PLUS authentication over a non-SSL
connection\n"));
+                appendPQExpBufferStr(&conn->errorMessage,
+                                     libpq_gettext("server offered SCRAM-SHA-256-PLUS authentication over a non-SSL
connection\n"));
                 goto error;
             }
         }
@@ -557,8 +557,8 @@ pg_SASL_init(PGconn *conn, int payloadlen)

     if (!selected_mechanism)
     {
-        printfPQExpBuffer(&conn->errorMessage,
-                          libpq_gettext("none of the server's SASL authentication mechanisms are supported\n"));
+        appendPQExpBufferStr(&conn->errorMessage,
+                             libpq_gettext("none of the server's SASL authentication mechanisms are supported\n"));
         goto error;
     }

@@ -578,8 +578,9 @@ pg_SASL_init(PGconn *conn, int payloadlen)
         password = conn->pgpass;
     if (password == NULL || password[0] == '\0')
     {
-        printfPQExpBuffer(&conn->errorMessage,
-                          PQnoPasswordSupplied);
+        /* Intentionally not translated. */
+        appendPQExpBufferStr(&conn->errorMessage,
+                             PQnoPasswordSupplied);
         goto error;
     }

@@ -688,8 +689,8 @@ pg_SASL_continue(PGconn *conn, int payloadlen, bool final)
         if (outputlen != 0)
             free(output);

-        printfPQExpBuffer(&conn->errorMessage,
-                          libpq_gettext("AuthenticationSASLFinal received from server, but SASL authentication was not
completed\n"));
+        appendPQExpBufferStr(&conn->errorMessage,
+                             libpq_gettext("AuthenticationSASLFinal received from server, but SASL authentication was
notcompleted\n")); 
         return STATUS_ERROR;
     }
     if (outputlen != 0)
@@ -758,15 +759,15 @@ pg_local_sendauth(PGconn *conn)
     {
         char        sebuf[256];

-        printfPQExpBuffer(&conn->errorMessage,
-                          "pg_local_sendauth: sendmsg: %s\n",
+        appendPQExpBuffer(&conn->errorMessage,
+                          libpq_gettext("could not send authentication message: %s\n"),
                           pqStrerror(errno, sebuf, sizeof(sebuf)));
         return STATUS_ERROR;
     }
     return STATUS_OK;
 #else
-    printfPQExpBuffer(&conn->errorMessage,
-                      libpq_gettext("SCM_CRED authentication method not supported\n"));
+    appendPQExpBufferStr(&conn->errorMessage,
+                         libpq_gettext("SCM_CRED authentication method not supported\n"));
     return STATUS_ERROR;
 #endif
 }
@@ -856,13 +857,13 @@ pg_fe_sendauth(AuthRequest areq, int payloadlen, PGconn *conn)
             break;

         case AUTH_REQ_KRB4:
-            printfPQExpBuffer(&conn->errorMessage,
-                              libpq_gettext("Kerberos 4 authentication not supported\n"));
+            appendPQExpBufferStr(&conn->errorMessage,
+                                 libpq_gettext("Kerberos 4 authentication not supported\n"));
             return STATUS_ERROR;

         case AUTH_REQ_KRB5:
-            printfPQExpBuffer(&conn->errorMessage,
-                              libpq_gettext("Kerberos 5 authentication not supported\n"));
+            appendPQExpBufferStr(&conn->errorMessage,
+                                 libpq_gettext("Kerberos 5 authentication not supported\n"));
             return STATUS_ERROR;

 #if defined(ENABLE_GSS) || defined(ENABLE_SSPI)
@@ -932,8 +933,8 @@ pg_fe_sendauth(AuthRequest areq, int payloadlen, PGconn *conn)
             /* No GSSAPI *or* SSPI support */
         case AUTH_REQ_GSS:
         case AUTH_REQ_GSS_CONT:
-            printfPQExpBuffer(&conn->errorMessage,
-                              libpq_gettext("GSSAPI authentication not supported\n"));
+            appendPQExpBufferStr(&conn->errorMessage,
+                                 libpq_gettext("GSSAPI authentication not supported\n"));
             return STATUS_ERROR;
 #endif                            /* defined(ENABLE_GSS) || defined(ENABLE_SSPI) */

@@ -964,16 +965,16 @@ pg_fe_sendauth(AuthRequest areq, int payloadlen, PGconn *conn)
              */
 #if !defined(ENABLE_GSS)
         case AUTH_REQ_SSPI:
-            printfPQExpBuffer(&conn->errorMessage,
-                              libpq_gettext("SSPI authentication not supported\n"));
+            appendPQExpBufferStr(&conn->errorMessage,
+                                 libpq_gettext("SSPI authentication not supported\n"));
             return STATUS_ERROR;
 #endif                            /* !define(ENABLE_GSSAPI) */
 #endif                            /* ENABLE_SSPI */


         case AUTH_REQ_CRYPT:
-            printfPQExpBuffer(&conn->errorMessage,
-                              libpq_gettext("Crypt authentication not supported\n"));
+            appendPQExpBufferStr(&conn->errorMessage,
+                                 libpq_gettext("Crypt authentication not supported\n"));
             return STATUS_ERROR;

         case AUTH_REQ_MD5:
@@ -987,14 +988,15 @@ pg_fe_sendauth(AuthRequest areq, int payloadlen, PGconn *conn)
                     password = conn->pgpass;
                 if (password == NULL || password[0] == '\0')
                 {
-                    printfPQExpBuffer(&conn->errorMessage,
-                                      PQnoPasswordSupplied);
+                    /* Intentionally not translated. */
+                    appendPQExpBufferStr(&conn->errorMessage,
+                                         PQnoPasswordSupplied);
                     return STATUS_ERROR;
                 }
                 if (pg_password_sendauth(conn, password, areq) != STATUS_OK)
                 {
-                    printfPQExpBuffer(&conn->errorMessage,
-                                      "fe_sendauth: error sending password authentication\n");
+                    appendPQExpBufferStr(&conn->errorMessage,
+                                         libpq_gettext("error sending password authentication\n"));
                     return STATUS_ERROR;
                 }
                 break;
@@ -1017,17 +1019,15 @@ pg_fe_sendauth(AuthRequest areq, int payloadlen, PGconn *conn)
         case AUTH_REQ_SASL_FIN:
             if (conn->sasl_state == NULL)
             {
-                printfPQExpBuffer(&conn->errorMessage,
-                                  "fe_sendauth: invalid authentication request from server: AUTH_REQ_SASL_CONT without
AUTH_REQ_SASL\n");
+                appendPQExpBufferStr(&conn->errorMessage,
+                                     libpq_gettext("invalid authentication request from server: AUTH_REQ_SASL_CONT
withoutAUTH_REQ_SASL\n")); 
                 return STATUS_ERROR;
             }
             if (pg_SASL_continue(conn, payloadlen,
                                  (areq == AUTH_REQ_SASL_FIN)) != STATUS_OK)
             {
-                /* Use error message, if set already */
-                if (conn->errorMessage.len == 0)
-                    printfPQExpBuffer(&conn->errorMessage,
-                                      "fe_sendauth: error in SASL authentication\n");
+                appendPQExpBufferStr(&conn->errorMessage,
+                                     libpq_gettext("error in SASL authentication\n"));
                 return STATUS_ERROR;
             }
             break;
@@ -1038,7 +1038,7 @@ pg_fe_sendauth(AuthRequest areq, int payloadlen, PGconn *conn)
             break;

         default:
-            printfPQExpBuffer(&conn->errorMessage,
+            appendPQExpBuffer(&conn->errorMessage,
                               libpq_gettext("authentication method %u not supported\n"), areq);
             return STATUS_ERROR;
     }
@@ -1052,7 +1052,7 @@ pg_fe_sendauth(AuthRequest areq, int payloadlen, PGconn *conn)
  *
  * Returns a pointer to malloc'd space containing whatever name the user
  * has authenticated to the system.  If there is an error, return NULL,
- * and put a suitable error message in *errorMessage if that's not NULL.
+ * and append a suitable error message to *errorMessage if that's not NULL.
  */
 char *
 pg_fe_getauthname(PQExpBuffer errorMessage)
@@ -1085,7 +1085,7 @@ pg_fe_getauthname(PQExpBuffer errorMessage)
     if (GetUserName(username, &namesize))
         name = username;
     else if (errorMessage)
-        printfPQExpBuffer(errorMessage,
+        appendPQExpBuffer(errorMessage,
                           libpq_gettext("user name lookup failure: error code %lu\n"),
                           GetLastError());
 #else
@@ -1095,12 +1095,12 @@ pg_fe_getauthname(PQExpBuffer errorMessage)
     else if (errorMessage)
     {
         if (pwerr != 0)
-            printfPQExpBuffer(errorMessage,
+            appendPQExpBuffer(errorMessage,
                               libpq_gettext("could not look up local user ID %d: %s\n"),
                               (int) user_id,
                               pqStrerror(pwerr, pwdbuf, sizeof(pwdbuf)));
         else
-            printfPQExpBuffer(errorMessage,
+            appendPQExpBuffer(errorMessage,
                               libpq_gettext("local user with ID %d does not exist\n"),
                               (int) user_id);
     }
diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c
index 986f74f..9b6a065 100644
--- a/src/interfaces/libpq/fe-connect.c
+++ b/src/interfaces/libpq/fe-connect.c
@@ -1757,10 +1757,15 @@ connectDBStart(PGconn *conn)
     conn->outCount = 0;

     /*
-     * Ensure errorMessage is empty, too.  PQconnectPoll will append messages
-     * to it in the process of scanning for a working server.  Thus, if we
-     * fail to connect to multiple hosts, the final error message will include
-     * details about each failure.
+     * Ensure errorMessage is empty, too.  PQconnectPoll and its subroutines
+     * will append messages to it in the process of scanning for a working
+     * server.  Thus, if we fail to connect to multiple hosts, the final error
+     * message will include details about each failure.
+     *
+     * (The exception to the rule of "always append to conn->errorMessage" is
+     * "out of memory" complaints; for those it seems better to reset
+     * errorMessage, so as to minimize the risk of a further OOM failure while
+     * trying to report the condition.)
      */
     resetPQExpBuffer(&conn->errorMessage);

@@ -1938,12 +1943,6 @@ connectDBComplete(PGconn *conn)
         switch (flag)
         {
             case PGRES_POLLING_OK:
-
-                /*
-                 * Reset stored error messages since we now have a working
-                 * connection
-                 */
-                resetPQExpBuffer(&conn->errorMessage);
                 return 1;        /* success! */

             case PGRES_POLLING_READING:
@@ -1996,46 +1995,6 @@ connectDBComplete(PGconn *conn)
     }
 }

-/*
- * This subroutine saves conn->errorMessage, which will be restored back by
- * restoreErrorMessage subroutine.  Returns false on OOM failure.
- */
-static bool
-saveErrorMessage(PGconn *conn, PQExpBuffer savedMessage)
-{
-    initPQExpBuffer(savedMessage);
-    appendPQExpBufferStr(savedMessage,
-                         conn->errorMessage.data);
-    if (PQExpBufferBroken(savedMessage))
-    {
-        printfPQExpBuffer(&conn->errorMessage,
-                          libpq_gettext("out of memory\n"));
-        return false;
-    }
-    /* Clear whatever is in errorMessage now */
-    resetPQExpBuffer(&conn->errorMessage);
-    return true;
-}
-
-/*
- * Restores saved error messages back to conn->errorMessage, prepending them
- * to whatever is in conn->errorMessage already.  (This does the right thing
- * if anything's been added to conn->errorMessage since saveErrorMessage.)
- */
-static void
-restoreErrorMessage(PGconn *conn, PQExpBuffer savedMessage)
-{
-    appendPQExpBufferStr(savedMessage, conn->errorMessage.data);
-    resetPQExpBuffer(&conn->errorMessage);
-    appendPQExpBufferStr(&conn->errorMessage, savedMessage->data);
-    /* If any step above hit OOM, just report that */
-    if (PQExpBufferBroken(savedMessage) ||
-        PQExpBufferBroken(&conn->errorMessage))
-        printfPQExpBuffer(&conn->errorMessage,
-                          libpq_gettext("out of memory\n"));
-    termPQExpBuffer(savedMessage);
-}
-
 /* ----------------
  *        PQconnectPoll
  *
@@ -2071,7 +2030,6 @@ PQconnectPoll(PGconn *conn)
     PGresult   *res;
     char        sebuf[256];
     int            optval;
-    PQExpBufferData savedMessage;

     if (conn == NULL)
         return PGRES_POLLING_FAILED;
@@ -2592,10 +2550,6 @@ keep_going:                        /* We will come back to here until there is
                                                         EnvironmentOptions);
                 if (!startpacket)
                 {
-                    /*
-                     * will not appendbuffer here, since it's likely to also
-                     * run out of memory
-                     */
                     printfPQExpBuffer(&conn->errorMessage,
                                       libpq_gettext("out of memory\n"));
                     goto error_return;
@@ -2983,7 +2937,6 @@ keep_going:                        /* We will come back to here until there is
                  * avoid the Kerberos code doing a hostname look-up.
                  */
                 res = pg_fe_sendauth(areq, msgLength, conn);
-                conn->errorMessage.len = strlen(conn->errorMessage.data);

                 /* OK, we have processed the message; mark data consumed */
                 conn->inStart = conn->inCursor;
@@ -3103,31 +3056,19 @@ keep_going:                        /* We will come back to here until there is
                     conn->target_session_attrs != NULL &&
                     strcmp(conn->target_session_attrs, "read-write") == 0)
                 {
-                    /*
-                     * Save existing error messages across the PQsendQuery
-                     * attempt.  This is necessary because PQsendQuery is
-                     * going to reset conn->errorMessage, so we would lose
-                     * error messages related to previous hosts we have tried
-                     * and failed to connect to.
-                     */
-                    if (!saveErrorMessage(conn, &savedMessage))
-                        goto error_return;
-
-                    conn->status = CONNECTION_OK;
                     if (!PQsendQuery(conn,
                                      "SHOW transaction_read_only"))
-                    {
-                        restoreErrorMessage(conn, &savedMessage);
                         goto error_return;
-                    }
                     conn->status = CONNECTION_CHECK_WRITABLE;
-                    restoreErrorMessage(conn, &savedMessage);
                     return PGRES_POLLING_READING;
                 }

                 /* We can release the address lists now. */
                 release_all_addrinfo(conn);

+                /* Likewise drop any stored error messages. */
+                resetPQExpBuffer(&conn->errorMessage);
+
                 /* We are open for business! */
                 conn->status = CONNECTION_OK;
                 return PGRES_POLLING_OK;
@@ -3137,23 +3078,16 @@ keep_going:                        /* We will come back to here until there is

             /*
              * Do post-connection housekeeping (only needed in protocol 2.0).
-             *
-             * We pretend that the connection is OK for the duration of these
-             * queries.
              */
-            conn->status = CONNECTION_OK;
-
             switch (pqSetenvPoll(conn))
             {
                 case PGRES_POLLING_OK:    /* Success */
                     break;

                 case PGRES_POLLING_READING: /* Still going */
-                    conn->status = CONNECTION_SETENV;
                     return PGRES_POLLING_READING;

                 case PGRES_POLLING_WRITING: /* Still going */
-                    conn->status = CONNECTION_SETENV;
                     return PGRES_POLLING_WRITING;

                 default:
@@ -3173,39 +3107,30 @@ keep_going:                        /* We will come back to here until there is
                 conn->target_session_attrs != NULL &&
                 strcmp(conn->target_session_attrs, "read-write") == 0)
             {
-                if (!saveErrorMessage(conn, &savedMessage))
-                    goto error_return;
-
-                conn->status = CONNECTION_OK;
                 if (!PQsendQuery(conn,
                                  "SHOW transaction_read_only"))
-                {
-                    restoreErrorMessage(conn, &savedMessage);
                     goto error_return;
-                }
                 conn->status = CONNECTION_CHECK_WRITABLE;
-                restoreErrorMessage(conn, &savedMessage);
                 return PGRES_POLLING_READING;
             }

             /* We can release the address lists now. */
             release_all_addrinfo(conn);

+            /* Likewise drop any stored error messages. */
+            resetPQExpBuffer(&conn->errorMessage);
+
             /* We are open for business! */
             conn->status = CONNECTION_OK;
             return PGRES_POLLING_OK;

         case CONNECTION_CONSUME:
             {
-                conn->status = CONNECTION_OK;
                 if (!PQconsumeInput(conn))
                     goto error_return;

                 if (PQisBusy(conn))
-                {
-                    conn->status = CONNECTION_CONSUME;
                     return PGRES_POLLING_READING;
-                }

                 /*
                  * Call PQgetResult() again to consume NULL result.
@@ -3214,10 +3139,15 @@ keep_going:                        /* We will come back to here until there is
                 if (res != NULL)
                 {
                     PQclear(res);
-                    conn->status = CONNECTION_CONSUME;
                     goto keep_going;
                 }

+                /* We can release the address lists now. */
+                release_all_addrinfo(conn);
+
+                /* Likewise drop any stored error messages. */
+                resetPQExpBuffer(&conn->errorMessage);
+
                 /* We are open for business! */
                 conn->status = CONNECTION_OK;
                 return PGRES_POLLING_OK;
@@ -3227,22 +3157,11 @@ keep_going:                        /* We will come back to here until there is
                 const char *displayed_host;
                 const char *displayed_port;

-                if (!saveErrorMessage(conn, &savedMessage))
-                    goto error_return;
-
-                conn->status = CONNECTION_OK;
                 if (!PQconsumeInput(conn))
-                {
-                    restoreErrorMessage(conn, &savedMessage);
                     goto error_return;
-                }

                 if (PQisBusy(conn))
-                {
-                    conn->status = CONNECTION_CHECK_WRITABLE;
-                    restoreErrorMessage(conn, &savedMessage);
                     return PGRES_POLLING_READING;
-                }

                 res = PQgetResult(conn);
                 if (res && (PQresultStatus(res) == PGRES_TUPLES_OK) &&
@@ -3258,7 +3177,6 @@ keep_going:                        /* We will come back to here until there is
                         const char *displayed_port;

                         PQclear(res);
-                        restoreErrorMessage(conn, &savedMessage);

                         /* Append error report to conn->errorMessage. */
                         if (conn->connhost[conn->whichhost].type == CHT_HOST_ADDRESS)
@@ -3289,10 +3207,6 @@ keep_going:                        /* We will come back to here until there is

                     /* Session is read-write, so we're good. */
                     PQclear(res);
-                    termPQExpBuffer(&savedMessage);
-
-                    /* We can release the address lists now. */
-                    release_all_addrinfo(conn);

                     /*
                      * Finish reading any remaining messages before being
@@ -3308,7 +3222,6 @@ keep_going:                        /* We will come back to here until there is
                  */
                 if (res)
                     PQclear(res);
-                restoreErrorMessage(conn, &savedMessage);

                 /* Append error report to conn->errorMessage. */
                 if (conn->connhost[conn->whichhost].type == CHT_HOST_ADDRESS)
@@ -3762,7 +3675,7 @@ PQreset(PGconn *conn)
                                           conn->events[i].passThrough))
                 {
                     conn->status = CONNECTION_BAD;
-                    printfPQExpBuffer(&conn->errorMessage,
+                    appendPQExpBuffer(&conn->errorMessage,
                                       libpq_gettext("PGEventProc \"%s\" failed during PGEVT_CONNRESET event\n"),
                                       conn->events[i].name);
                     break;
@@ -3822,7 +3735,7 @@ PQresetPoll(PGconn *conn)
                                           conn->events[i].passThrough))
                 {
                     conn->status = CONNECTION_BAD;
-                    printfPQExpBuffer(&conn->errorMessage,
+                    appendPQExpBuffer(&conn->errorMessage,
                                       libpq_gettext("PGEventProc \"%s\" failed during PGEVT_CONNRESET event\n"),
                                       conn->events[i].name);
                     return PGRES_POLLING_FAILED;
@@ -4169,8 +4082,9 @@ ldapServiceLookup(const char *purl, PQconninfoOption *options,

     if (pg_strncasecmp(url, LDAP_URL, strlen(LDAP_URL)) != 0)
     {
-        printfPQExpBuffer(errorMessage,
-                          libpq_gettext("invalid LDAP URL \"%s\": scheme must be ldap://\n"), purl);
+        appendPQExpBuffer(errorMessage,
+                          libpq_gettext("invalid LDAP URL \"%s\": scheme must be ldap://\n"),
+                          purl);
         free(url);
         return 3;
     }
@@ -4184,8 +4098,9 @@ ldapServiceLookup(const char *purl, PQconninfoOption *options,
     p = strchr(url + strlen(LDAP_URL), '/');
     if (p == NULL || *(p + 1) == '\0' || *(p + 1) == '?')
     {
-        printfPQExpBuffer(errorMessage, libpq_gettext(
-                                                      "invalid LDAP URL \"%s\": missing distinguished name\n"), purl);
+        appendPQExpBuffer(errorMessage,
+                          libpq_gettext("invalid LDAP URL \"%s\": missing distinguished name\n"),
+                          purl);
         free(url);
         return 3;
     }
@@ -4195,8 +4110,9 @@ ldapServiceLookup(const char *purl, PQconninfoOption *options,
     /* attribute */
     if ((p = strchr(dn, '?')) == NULL || *(p + 1) == '\0' || *(p + 1) == '?')
     {
-        printfPQExpBuffer(errorMessage, libpq_gettext(
-                                                      "invalid LDAP URL \"%s\": must have exactly one attribute\n"),
purl);
+        appendPQExpBuffer(errorMessage,
+                          libpq_gettext("invalid LDAP URL \"%s\": must have exactly one attribute\n"),
+                          purl);
         free(url);
         return 3;
     }
@@ -4206,7 +4122,9 @@ ldapServiceLookup(const char *purl, PQconninfoOption *options,
     /* scope */
     if ((p = strchr(attrs[0], '?')) == NULL || *(p + 1) == '\0' || *(p + 1) == '?')
     {
-        printfPQExpBuffer(errorMessage, libpq_gettext("invalid LDAP URL \"%s\": must have search scope
(base/one/sub)\n"),purl); 
+        appendPQExpBuffer(errorMessage,
+                          libpq_gettext("invalid LDAP URL \"%s\": must have search scope (base/one/sub)\n"),
+                          purl);
         free(url);
         return 3;
     }
@@ -4216,7 +4134,7 @@ ldapServiceLookup(const char *purl, PQconninfoOption *options,
     /* filter */
     if ((p = strchr(scopestr, '?')) == NULL || *(p + 1) == '\0' || *(p + 1) == '?')
     {
-        printfPQExpBuffer(errorMessage,
+        appendPQExpBuffer(errorMessage,
                           libpq_gettext("invalid LDAP URL \"%s\": no filter\n"), purl);
         free(url);
         return 3;
@@ -4237,8 +4155,9 @@ ldapServiceLookup(const char *purl, PQconninfoOption *options,
         lport = strtol(portstr, &endptr, 10);
         if (*portstr == '\0' || *endptr != '\0' || errno || lport < 0 || lport > 65535)
         {
-            printfPQExpBuffer(errorMessage, libpq_gettext(
-                                                          "invalid LDAP URL \"%s\": invalid port number\n"), purl);
+            appendPQExpBuffer(errorMessage,
+                              libpq_gettext("invalid LDAP URL \"%s\": invalid port number\n"),
+                              purl);
             free(url);
             return 3;
         }
@@ -4248,8 +4167,9 @@ ldapServiceLookup(const char *purl, PQconninfoOption *options,
     /* Allow only one attribute */
     if (strchr(attrs[0], ',') != NULL)
     {
-        printfPQExpBuffer(errorMessage, libpq_gettext(
-                                                      "invalid LDAP URL \"%s\": must have exactly one attribute\n"),
purl);
+        appendPQExpBuffer(errorMessage,
+                          libpq_gettext("invalid LDAP URL \"%s\": must have exactly one attribute\n"),
+                          purl);
         free(url);
         return 3;
     }
@@ -4263,7 +4183,9 @@ ldapServiceLookup(const char *purl, PQconninfoOption *options,
         scope = LDAP_SCOPE_SUBTREE;
     else
     {
-        printfPQExpBuffer(errorMessage, libpq_gettext("invalid LDAP URL \"%s\": must have search scope
(base/one/sub)\n"),purl); 
+        appendPQExpBuffer(errorMessage,
+                          libpq_gettext("invalid LDAP URL \"%s\": must have search scope (base/one/sub)\n"),
+                          purl);
         free(url);
         return 3;
     }
@@ -4271,8 +4193,8 @@ ldapServiceLookup(const char *purl, PQconninfoOption *options,
     /* initialize LDAP structure */
     if ((ld = ldap_init(hostname, port)) == NULL)
     {
-        printfPQExpBuffer(errorMessage,
-                          libpq_gettext("could not create LDAP structure\n"));
+        appendPQExpBufferStr(errorMessage,
+                             libpq_gettext("could not create LDAP structure\n"));
         free(url);
         return 3;
     }
@@ -4347,7 +4269,7 @@ ldapServiceLookup(const char *purl, PQconninfoOption *options,
     {
         if (res != NULL)
             ldap_msgfree(res);
-        printfPQExpBuffer(errorMessage,
+        appendPQExpBuffer(errorMessage,
                           libpq_gettext("lookup on LDAP server failed: %s\n"),
                           ldap_err2string(rc));
         ldap_unbind(ld);
@@ -4358,9 +4280,9 @@ ldapServiceLookup(const char *purl, PQconninfoOption *options,
     /* complain if there was not exactly one result */
     if ((rc = ldap_count_entries(ld, res)) != 1)
     {
-        printfPQExpBuffer(errorMessage,
-                          rc ? libpq_gettext("more than one entry found on LDAP lookup\n")
-                          : libpq_gettext("no entry found on LDAP lookup\n"));
+        appendPQExpBufferStr(errorMessage,
+                             rc ? libpq_gettext("more than one entry found on LDAP lookup\n")
+                             : libpq_gettext("no entry found on LDAP lookup\n"));
         ldap_msgfree(res);
         ldap_unbind(ld);
         free(url);
@@ -4371,8 +4293,8 @@ ldapServiceLookup(const char *purl, PQconninfoOption *options,
     if ((entry = ldap_first_entry(ld, res)) == NULL)
     {
         /* should never happen */
-        printfPQExpBuffer(errorMessage,
-                          libpq_gettext("no entry found on LDAP lookup\n"));
+        appendPQExpBufferStr(errorMessage,
+                             libpq_gettext("no entry found on LDAP lookup\n"));
         ldap_msgfree(res);
         ldap_unbind(ld);
         free(url);
@@ -4382,8 +4304,8 @@ ldapServiceLookup(const char *purl, PQconninfoOption *options,
     /* get values */
     if ((values = ldap_get_values_len(ld, entry, attrs[0])) == NULL)
     {
-        printfPQExpBuffer(errorMessage,
-                          libpq_gettext("attribute has no values on LDAP lookup\n"));
+        appendPQExpBufferStr(errorMessage,
+                             libpq_gettext("attribute has no values on LDAP lookup\n"));
         ldap_msgfree(res);
         ldap_unbind(ld);
         free(url);
@@ -4395,8 +4317,8 @@ ldapServiceLookup(const char *purl, PQconninfoOption *options,

     if (values[0] == NULL)
     {
-        printfPQExpBuffer(errorMessage,
-                          libpq_gettext("attribute has no values on LDAP lookup\n"));
+        appendPQExpBufferStr(errorMessage,
+                             libpq_gettext("attribute has no values on LDAP lookup\n"));
         ldap_value_free_len(values);
         ldap_unbind(ld);
         return 1;
@@ -4447,8 +4369,8 @@ ldapServiceLookup(const char *purl, PQconninfoOption *options,
                 }
                 else if (ld_is_nl_cr(*p))
                 {
-                    printfPQExpBuffer(errorMessage, libpq_gettext(
-                                                                  "missing \"=\" after \"%s\" in connection info
string\n"),
+                    appendPQExpBuffer(errorMessage,
+                                      libpq_gettext("missing \"=\" after \"%s\" in connection info string\n"),
                                       optname);
                     free(result);
                     return 3;
@@ -4466,8 +4388,8 @@ ldapServiceLookup(const char *purl, PQconninfoOption *options,
                 }
                 else if (!ld_is_sp_tab(*p))
                 {
-                    printfPQExpBuffer(errorMessage, libpq_gettext(
-                                                                  "missing \"=\" after \"%s\" in connection info
string\n"),
+                    appendPQExpBuffer(errorMessage,
+                                      libpq_gettext("missing \"=\" after \"%s\" in connection info string\n"),
                                       optname);
                     free(result);
                     return 3;
@@ -4539,7 +4461,7 @@ ldapServiceLookup(const char *purl, PQconninfoOption *options,
             }
             if (!found_keyword)
             {
-                printfPQExpBuffer(errorMessage,
+                appendPQExpBuffer(errorMessage,
                                   libpq_gettext("invalid connection option \"%s\"\n"),
                                   optname);
                 free(result);
@@ -4555,8 +4477,8 @@ ldapServiceLookup(const char *purl, PQconninfoOption *options,

     if (state == 5 || state == 6)
     {
-        printfPQExpBuffer(errorMessage, libpq_gettext(
-                                                      "unterminated quoted string in connection info string\n"));
+        appendPQExpBufferStr(errorMessage,
+                             libpq_gettext("unterminated quoted string in connection info string\n"));
         return 3;
     }

@@ -4574,8 +4496,8 @@ ldapServiceLookup(const char *purl, PQconninfoOption *options,
  * Returns 0 on success, nonzero on failure.  On failure, if errorMessage
  * isn't null, also store an error message there.  (Note: the only reason
  * this function and related ones don't dump core on errorMessage == NULL
- * is the undocumented fact that printfPQExpBuffer does nothing when passed
- * a null PQExpBuffer pointer.)
+ * is the undocumented fact that printfPQExpBuffer and friends do nothing
+ * when passed a null PQExpBuffer pointer.)
  */
 static int
 parseServiceInfo(PQconninfoOption *options, PQExpBuffer errorMessage)
@@ -4638,7 +4560,7 @@ next_file:
 last_file:
     if (!group_found)
     {
-        printfPQExpBuffer(errorMessage,
+        appendPQExpBuffer(errorMessage,
                           libpq_gettext("definition of service \"%s\" not found\n"), service);
         return 3;
     }
@@ -4662,7 +4584,7 @@ parseServiceFile(const char *serviceFile,
     f = fopen(serviceFile, "r");
     if (f == NULL)
     {
-        printfPQExpBuffer(errorMessage, libpq_gettext("service file \"%s\" not found\n"),
+        appendPQExpBuffer(errorMessage, libpq_gettext("service file \"%s\" not found\n"),
                           serviceFile);
         return 1;
     }
@@ -4674,7 +4596,7 @@ parseServiceFile(const char *serviceFile,
         if (strlen(line) >= sizeof(buf) - 1)
         {
             fclose(f);
-            printfPQExpBuffer(errorMessage,
+            appendPQExpBuffer(errorMessage,
                               libpq_gettext("line %d too long in service file \"%s\"\n"),
                               linenr,
                               serviceFile);
@@ -4745,7 +4667,7 @@ parseServiceFile(const char *serviceFile,
                 val = strchr(line, '=');
                 if (val == NULL)
                 {
-                    printfPQExpBuffer(errorMessage,
+                    appendPQExpBuffer(errorMessage,
                                       libpq_gettext("syntax error in service file \"%s\", line %d\n"),
                                       serviceFile,
                                       linenr);
@@ -4756,7 +4678,7 @@ parseServiceFile(const char *serviceFile,

                 if (strcmp(key, "service") == 0)
                 {
-                    printfPQExpBuffer(errorMessage,
+                    appendPQExpBuffer(errorMessage,
                                       libpq_gettext("nested service specifications not supported in service file
\"%s\",line %d\n"), 
                                       serviceFile,
                                       linenr);
@@ -4789,7 +4711,7 @@ parseServiceFile(const char *serviceFile,

                 if (!found_keyword)
                 {
-                    printfPQExpBuffer(errorMessage,
+                    appendPQExpBuffer(errorMessage,
                                       libpq_gettext("syntax error in service file \"%s\", line %d\n"),
                                       serviceFile,
                                       linenr);
@@ -4995,7 +4917,7 @@ conninfo_parse(const char *conninfo, PQExpBuffer errorMessage,
         /* Check that there is a following '=' */
         if (*cp != '=')
         {
-            printfPQExpBuffer(errorMessage,
+            appendPQExpBuffer(errorMessage,
                               libpq_gettext("missing \"=\" after \"%s\" in connection info string\n"),
                               pname);
             PQconninfoFree(options);
@@ -5044,8 +4966,8 @@ conninfo_parse(const char *conninfo, PQExpBuffer errorMessage,
             {
                 if (*cp == '\0')
                 {
-                    printfPQExpBuffer(errorMessage,
-                                      libpq_gettext("unterminated quoted string in connection info string\n"));
+                    appendPQExpBufferStr(errorMessage,
+                                         libpq_gettext("unterminated quoted string in connection info string\n"));
                     PQconninfoFree(options);
                     free(buf);
                     return NULL;
@@ -5180,7 +5102,7 @@ conninfo_array_parse(const char *const *keywords, const char *const *values,
             /* Check for invalid connection option */
             if (option->keyword == NULL)
             {
-                printfPQExpBuffer(errorMessage,
+                appendPQExpBuffer(errorMessage,
                                   libpq_gettext("invalid connection option \"%s\"\n"),
                                   pname);
                 PQconninfoFree(options);
@@ -5476,7 +5398,7 @@ conninfo_uri_parse_options(PQconninfoOption *options, const char *uri,
     if (prefix_len == 0)
     {
         /* Should never happen */
-        printfPQExpBuffer(errorMessage,
+        appendPQExpBuffer(errorMessage,
                           libpq_gettext("invalid URI propagated to internal parser routine: \"%s\"\n"),
                           uri);
         goto cleanup;
@@ -5553,14 +5475,14 @@ conninfo_uri_parse_options(PQconninfoOption *options, const char *uri,
                 ++p;
             if (!*p)
             {
-                printfPQExpBuffer(errorMessage,
+                appendPQExpBuffer(errorMessage,
                                   libpq_gettext("end of string reached when looking for matching \"]\" in IPv6 host
addressin URI: \"%s\"\n"), 
                                   uri);
                 goto cleanup;
             }
             if (p == host)
             {
-                printfPQExpBuffer(errorMessage,
+                appendPQExpBuffer(errorMessage,
                                   libpq_gettext("IPv6 host address may not be empty in URI: \"%s\"\n"),
                                   uri);
                 goto cleanup;
@@ -5575,7 +5497,7 @@ conninfo_uri_parse_options(PQconninfoOption *options, const char *uri,
              */
             if (*p && *p != ':' && *p != '/' && *p != '?' && *p != ',')
             {
-                printfPQExpBuffer(errorMessage,
+                appendPQExpBuffer(errorMessage,
                                   libpq_gettext("unexpected character \"%c\" at position %d in URI (expected \":\" or
\"/\"):\"%s\"\n"), 
                                   *p, (int) (p - buf + 1), uri);
                 goto cleanup;
@@ -5704,7 +5626,7 @@ conninfo_uri_parse_params(char *params,
                 /* Was there '=' already? */
                 if (value != NULL)
                 {
-                    printfPQExpBuffer(errorMessage,
+                    appendPQExpBuffer(errorMessage,
                                       libpq_gettext("extra key/value separator \"=\" in URI query parameter:
\"%s\"\n"),
                                       keyword);
                     return false;
@@ -5724,7 +5646,7 @@ conninfo_uri_parse_params(char *params,
                 /* Was there '=' at all? */
                 if (value == NULL)
                 {
-                    printfPQExpBuffer(errorMessage,
+                    appendPQExpBuffer(errorMessage,
                                       libpq_gettext("missing key/value separator \"=\" in URI query parameter:
\"%s\"\n"),
                                       keyword);
                     return false;
@@ -5775,7 +5697,7 @@ conninfo_uri_parse_params(char *params,
         {
             /* Insert generic message if conninfo_storeval didn't give one. */
             if (errorMessage->len == 0)
-                printfPQExpBuffer(errorMessage,
+                appendPQExpBuffer(errorMessage,
                                   libpq_gettext("invalid URI query parameter: \"%s\"\n"),
                                   keyword);
             /* And fail. */
@@ -5849,7 +5771,7 @@ conninfo_uri_decode(const char *str, PQExpBuffer errorMessage)
              */
             if (!(get_hexdigit(*q++, &hi) && get_hexdigit(*q++, &lo)))
             {
-                printfPQExpBuffer(errorMessage,
+                appendPQExpBuffer(errorMessage,
                                   libpq_gettext("invalid percent-encoded token: \"%s\"\n"),
                                   str);
                 free(buf);
@@ -5859,7 +5781,7 @@ conninfo_uri_decode(const char *str, PQExpBuffer errorMessage)
             c = (hi << 4) | lo;
             if (c == 0)
             {
-                printfPQExpBuffer(errorMessage,
+                appendPQExpBuffer(errorMessage,
                                   libpq_gettext("forbidden value %%00 in percent-encoded value: \"%s\"\n"),
                                   str);
                 free(buf);
@@ -5954,7 +5876,7 @@ conninfo_storeval(PQconninfoOption *connOptions,
     if (option == NULL)
     {
         if (!ignoreMissing)
-            printfPQExpBuffer(errorMessage,
+            appendPQExpBuffer(errorMessage,
                               libpq_gettext("invalid connection option \"%s\"\n"),
                               keyword);
         return NULL;
diff --git a/src/interfaces/libpq/fe-exec.c b/src/interfaces/libpq/fe-exec.c
index 4c0114c..f51be2e 100644
--- a/src/interfaces/libpq/fe-exec.c
+++ b/src/interfaces/libpq/fe-exec.c
@@ -770,8 +770,7 @@ pqSaveErrorResult(PGconn *conn)
  * This subroutine prepares an async result object for return to the caller.
  * If there is not already an async result object, build an error object
  * using whatever is in conn->errorMessage.  In any case, clear the async
- * result storage and make sure PQerrorMessage will agree with the result's
- * error string.
+ * result storage.
  */
 PGresult *
 pqPrepareAsyncResult(PGconn *conn)
@@ -781,21 +780,11 @@ pqPrepareAsyncResult(PGconn *conn)
     /*
      * conn->result is the PGresult to return.  If it is NULL (which probably
      * shouldn't happen) we assume there is an appropriate error message in
-     * conn->errorMessage.
+     * conn->errorMessage, and copy that into a PGresult.
      */
     res = conn->result;
     if (!res)
         res = PQmakeEmptyPGresult(conn, PGRES_FATAL_ERROR);
-    else
-    {
-        /*
-         * Make sure PQerrorMessage agrees with result; it could be different
-         * if we have concatenated messages.
-         */
-        resetPQExpBuffer(&conn->errorMessage);
-        appendPQExpBufferStr(&conn->errorMessage,
-                             PQresultErrorMessage(res));
-    }

     /*
      * Replace conn->result with next_result, if any.  In the normal case
@@ -1188,8 +1177,8 @@ PQsendQuery(PGconn *conn, const char *query)
     /* check the argument */
     if (!query)
     {
-        printfPQExpBuffer(&conn->errorMessage,
-                          libpq_gettext("command string is a null pointer\n"));
+        appendPQExpBufferStr(&conn->errorMessage,
+                             libpq_gettext("command string is a null pointer\n"));
         return 0;
     }

@@ -1246,14 +1235,14 @@ PQsendQueryParams(PGconn *conn,
     /* check the arguments */
     if (!command)
     {
-        printfPQExpBuffer(&conn->errorMessage,
-                          libpq_gettext("command string is a null pointer\n"));
+        appendPQExpBufferStr(&conn->errorMessage,
+                             libpq_gettext("command string is a null pointer\n"));
         return 0;
     }
     if (nParams < 0 || nParams > 65535)
     {
-        printfPQExpBuffer(&conn->errorMessage,
-                          libpq_gettext("number of parameters must be between 0 and 65535\n"));
+        appendPQExpBufferStr(&conn->errorMessage,
+                             libpq_gettext("number of parameters must be between 0 and 65535\n"));
         return 0;
     }

@@ -1286,28 +1275,28 @@ PQsendPrepare(PGconn *conn,
     /* check the arguments */
     if (!stmtName)
     {
-        printfPQExpBuffer(&conn->errorMessage,
-                          libpq_gettext("statement name is a null pointer\n"));
+        appendPQExpBufferStr(&conn->errorMessage,
+                             libpq_gettext("statement name is a null pointer\n"));
         return 0;
     }
     if (!query)
     {
-        printfPQExpBuffer(&conn->errorMessage,
-                          libpq_gettext("command string is a null pointer\n"));
+        appendPQExpBufferStr(&conn->errorMessage,
+                             libpq_gettext("command string is a null pointer\n"));
         return 0;
     }
     if (nParams < 0 || nParams > 65535)
     {
-        printfPQExpBuffer(&conn->errorMessage,
-                          libpq_gettext("number of parameters must be between 0 and 65535\n"));
+        appendPQExpBufferStr(&conn->errorMessage,
+                             libpq_gettext("number of parameters must be between 0 and 65535\n"));
         return 0;
     }

     /* This isn't gonna work on a 2.0 server */
     if (PG_PROTOCOL_MAJOR(conn->pversion) < 3)
     {
-        printfPQExpBuffer(&conn->errorMessage,
-                          libpq_gettext("function requires at least protocol version 3.0\n"));
+        appendPQExpBufferStr(&conn->errorMessage,
+                             libpq_gettext("function requires at least protocol version 3.0\n"));
         return 0;
     }

@@ -1387,14 +1376,14 @@ PQsendQueryPrepared(PGconn *conn,
     /* check the arguments */
     if (!stmtName)
     {
-        printfPQExpBuffer(&conn->errorMessage,
-                          libpq_gettext("statement name is a null pointer\n"));
+        appendPQExpBufferStr(&conn->errorMessage,
+                             libpq_gettext("statement name is a null pointer\n"));
         return 0;
     }
     if (nParams < 0 || nParams > 65535)
     {
-        printfPQExpBuffer(&conn->errorMessage,
-                          libpq_gettext("number of parameters must be between 0 and 65535\n"));
+        appendPQExpBufferStr(&conn->errorMessage,
+                             libpq_gettext("number of parameters must be between 0 and 65535\n"));
         return 0;
     }

@@ -1418,21 +1407,36 @@ PQsendQueryStart(PGconn *conn)
     if (!conn)
         return false;

-    /* clear the error string */
-    resetPQExpBuffer(&conn->errorMessage);
+    /*
+     * Clear the error string at start of a new query.  All libpq code
+     * executed while we're running a query should append messages to
+     * errorMessage.
+     *
+     * (The exception to this rule is "out of memory" complaints; for those it
+     * seems better to reset errorMessage, so as to minimize the risk of a
+     * further OOM failure while trying to report the condition.)
+     *
+     * However, if we're in a connecting state (anything but CONNECTION_OK or
+     * CONNECTION_BAD), then we don't want to clear the errorMessage.  We must
+     * be doing some libpq-generated query to check server state, and we want
+     * to continue building up the history of connection attempts across
+     * multiple servers --- see comments in connectDBStart.
+     */
+    if (conn->status <= CONNECTION_BAD)
+        resetPQExpBuffer(&conn->errorMessage);

     /* Don't try to send if we know there's no live connection. */
-    if (conn->status != CONNECTION_OK)
+    if (conn->status == CONNECTION_BAD)
     {
-        printfPQExpBuffer(&conn->errorMessage,
-                          libpq_gettext("no connection to the server\n"));
+        appendPQExpBufferStr(&conn->errorMessage,
+                             libpq_gettext("no connection to the server\n"));
         return false;
     }
     /* Can't send while already busy, either. */
     if (conn->asyncStatus != PGASYNC_IDLE)
     {
-        printfPQExpBuffer(&conn->errorMessage,
-                          libpq_gettext("another command is already in progress\n"));
+        appendPQExpBufferStr(&conn->errorMessage,
+                             libpq_gettext("another command is already in progress\n"));
         return false;
     }

@@ -1469,8 +1473,8 @@ PQsendQueryGuts(PGconn *conn,
     /* This isn't gonna work on a 2.0 server */
     if (PG_PROTOCOL_MAJOR(conn->pversion) < 3)
     {
-        printfPQExpBuffer(&conn->errorMessage,
-                          libpq_gettext("function requires at least protocol version 3.0\n"));
+        appendPQExpBufferStr(&conn->errorMessage,
+                             libpq_gettext("function requires at least protocol version 3.0\n"));
         return 0;
     }

@@ -1545,8 +1549,8 @@ PQsendQueryGuts(PGconn *conn,
                     nbytes = paramLengths[i];
                 else
                 {
-                    printfPQExpBuffer(&conn->errorMessage,
-                                      libpq_gettext("length must be given for binary parameter\n"));
+                    appendPQExpBufferStr(&conn->errorMessage,
+                                         libpq_gettext("length must be given for binary parameter\n"));
                     goto sendFailed;
                 }
             }
@@ -1817,7 +1821,7 @@ PQgetResult(PGconn *conn)
             res = getCopyResult(conn, PGRES_COPY_BOTH);
             break;
         default:
-            printfPQExpBuffer(&conn->errorMessage,
+            appendPQExpBuffer(&conn->errorMessage,
                               libpq_gettext("unexpected asyncStatus: %d\n"),
                               (int) conn->asyncStatus);
             res = PQmakeEmptyPGresult(conn, PGRES_FATAL_ERROR);
@@ -1837,7 +1841,7 @@ PQgetResult(PGconn *conn)
             if (!res->events[i].proc(PGEVT_RESULTCREATE, &evt,
                                      res->events[i].passThrough))
             {
-                printfPQExpBuffer(&conn->errorMessage,
+                appendPQExpBuffer(&conn->errorMessage,
                                   libpq_gettext("PGEventProc \"%s\" failed during PGEVT_RESULTCREATE event\n"),
                                   res->events[i].name);
                 pqSetResultError(res, conn->errorMessage.data);
@@ -2005,8 +2009,8 @@ PQexecStart(PGconn *conn)
             else
             {
                 /* In older protocols we have to punt */
-                printfPQExpBuffer(&conn->errorMessage,
-                                  libpq_gettext("COPY IN state must be terminated first\n"));
+                appendPQExpBufferStr(&conn->errorMessage,
+                                     libpq_gettext("COPY IN state must be terminated first\n"));
                 return false;
             }
         }
@@ -2025,16 +2029,16 @@ PQexecStart(PGconn *conn)
             else
             {
                 /* In older protocols we have to punt */
-                printfPQExpBuffer(&conn->errorMessage,
-                                  libpq_gettext("COPY OUT state must be terminated first\n"));
+                appendPQExpBufferStr(&conn->errorMessage,
+                                     libpq_gettext("COPY OUT state must be terminated first\n"));
                 return false;
             }
         }
         else if (resultStatus == PGRES_COPY_BOTH)
         {
             /* We don't allow PQexec during COPY BOTH */
-            printfPQExpBuffer(&conn->errorMessage,
-                              libpq_gettext("PQexec not allowed during COPY BOTH\n"));
+            appendPQExpBufferStr(&conn->errorMessage,
+                                 libpq_gettext("PQexec not allowed during COPY BOTH\n"));
             return false;
         }
         /* check for loss of connection, too */
@@ -2076,12 +2080,6 @@ PQexecFinish(PGconn *conn)
                 pqCatenateResultError(lastResult, result->errMsg);
                 PQclear(result);
                 result = lastResult;
-
-                /*
-                 * Make sure PQerrorMessage agrees with concatenated result
-                 */
-                resetPQExpBuffer(&conn->errorMessage);
-                appendPQExpBufferStr(&conn->errorMessage, result->errMsg);
             }
             else
                 PQclear(lastResult);
@@ -2187,8 +2185,8 @@ PQsendDescribe(PGconn *conn, char desc_type, const char *desc_target)
     /* This isn't gonna work on a 2.0 server */
     if (PG_PROTOCOL_MAJOR(conn->pversion) < 3)
     {
-        printfPQExpBuffer(&conn->errorMessage,
-                          libpq_gettext("function requires at least protocol version 3.0\n"));
+        appendPQExpBufferStr(&conn->errorMessage,
+                             libpq_gettext("function requires at least protocol version 3.0\n"));
         return 0;
     }

@@ -2276,8 +2274,8 @@ PQputCopyData(PGconn *conn, const char *buffer, int nbytes)
     if (conn->asyncStatus != PGASYNC_COPY_IN &&
         conn->asyncStatus != PGASYNC_COPY_BOTH)
     {
-        printfPQExpBuffer(&conn->errorMessage,
-                          libpq_gettext("no COPY in progress\n"));
+        appendPQExpBufferStr(&conn->errorMessage,
+                             libpq_gettext("no COPY in progress\n"));
         return -1;
     }

@@ -2343,8 +2341,8 @@ PQputCopyEnd(PGconn *conn, const char *errormsg)
     if (conn->asyncStatus != PGASYNC_COPY_IN &&
         conn->asyncStatus != PGASYNC_COPY_BOTH)
     {
-        printfPQExpBuffer(&conn->errorMessage,
-                          libpq_gettext("no COPY in progress\n"));
+        appendPQExpBufferStr(&conn->errorMessage,
+                             libpq_gettext("no COPY in progress\n"));
         return -1;
     }

@@ -2386,8 +2384,8 @@ PQputCopyEnd(PGconn *conn, const char *errormsg)
         if (errormsg)
         {
             /* Oops, no way to do this in 2.0 */
-            printfPQExpBuffer(&conn->errorMessage,
-                              libpq_gettext("function requires at least protocol version 3.0\n"));
+            appendPQExpBufferStr(&conn->errorMessage,
+                                 libpq_gettext("function requires at least protocol version 3.0\n"));
             return -1;
         }
         else
@@ -2405,7 +2403,6 @@ PQputCopyEnd(PGconn *conn, const char *errormsg)
         conn->asyncStatus = PGASYNC_COPY_OUT;
     else
         conn->asyncStatus = PGASYNC_BUSY;
-    resetPQExpBuffer(&conn->errorMessage);

     /* Try to flush data */
     if (pqFlush(conn) < 0)
@@ -2433,8 +2430,8 @@ PQgetCopyData(PGconn *conn, char **buffer, int async)
     if (conn->asyncStatus != PGASYNC_COPY_OUT &&
         conn->asyncStatus != PGASYNC_COPY_BOTH)
     {
-        printfPQExpBuffer(&conn->errorMessage,
-                          libpq_gettext("no COPY in progress\n"));
+        appendPQExpBufferStr(&conn->errorMessage,
+                             libpq_gettext("no COPY in progress\n"));
         return -2;
     }
     if (PG_PROTOCOL_MAJOR(conn->pversion) >= 3)
@@ -2617,14 +2614,14 @@ PQfn(PGconn *conn,
     if (!conn)
         return NULL;

-    /* clear the error string */
+    /* Clear the error string, like PQsendQueryStart() */
     resetPQExpBuffer(&conn->errorMessage);

     if (conn->sock == PGINVALID_SOCKET || conn->asyncStatus != PGASYNC_IDLE ||
         conn->result != NULL)
     {
-        printfPQExpBuffer(&conn->errorMessage,
-                          libpq_gettext("connection in wrong state\n"));
+        appendPQExpBufferStr(&conn->errorMessage,
+                             libpq_gettext("connection in wrong state\n"));
         return NULL;
     }

@@ -3343,8 +3340,8 @@ PQescapeStringInternal(PGconn *conn,
             if (error)
                 *error = 1;
             if (conn)
-                printfPQExpBuffer(&conn->errorMessage,
-                                  libpq_gettext("incomplete multibyte character\n"));
+                appendPQExpBufferStr(&conn->errorMessage,
+                                     libpq_gettext("incomplete multibyte character\n"));
             for (; i < len; i++)
             {
                 if (((size_t) (target - to)) / 2 >= length)
@@ -3427,8 +3424,8 @@ PQescapeInternal(PGconn *conn, const char *str, size_t len, bool as_ident)
             /* Multibyte character overruns allowable length. */
             if ((s - str) + charlen > len || memchr(s, 0, charlen) != NULL)
             {
-                printfPQExpBuffer(&conn->errorMessage,
-                                  libpq_gettext("incomplete multibyte character\n"));
+                appendPQExpBufferStr(&conn->errorMessage,
+                                     libpq_gettext("incomplete multibyte character\n"));
                 return NULL;
             }

diff --git a/src/interfaces/libpq/fe-misc.c b/src/interfaces/libpq/fe-misc.c
index 2a6637f..627d4d8 100644
--- a/src/interfaces/libpq/fe-misc.c
+++ b/src/interfaces/libpq/fe-misc.c
@@ -394,7 +394,7 @@ pqCheckOutBufferSpace(size_t bytes_needed, PGconn *conn)

     /* realloc failed. Probably out of memory */
     printfPQExpBuffer(&conn->errorMessage,
-                      "cannot allocate memory for output buffer\n");
+                      libpq_gettext("out of memory\n"));
     return EOF;
 }

@@ -488,7 +488,7 @@ pqCheckInBufferSpace(size_t bytes_needed, PGconn *conn)

     /* realloc failed. Probably out of memory */
     printfPQExpBuffer(&conn->errorMessage,
-                      "cannot allocate memory for input buffer\n");
+                      libpq_gettext("out of memory\n"));
     return EOF;
 }

@@ -633,8 +633,8 @@ pqReadData(PGconn *conn)

     if (conn->sock == PGINVALID_SOCKET)
     {
-        printfPQExpBuffer(&conn->errorMessage,
-                          libpq_gettext("connection not open\n"));
+        appendPQExpBufferStr(&conn->errorMessage,
+                             libpq_gettext("connection not open\n"));
         return -1;
     }

@@ -802,11 +802,10 @@ retry4:
      * means the connection has been closed.  Cope.
      */
 definitelyEOF:
-    printfPQExpBuffer(&conn->errorMessage,
-                      libpq_gettext(
-                                    "server closed the connection unexpectedly\n"
-                                    "\tThis probably means the server terminated abnormally\n"
-                                    "\tbefore or while processing the request.\n"));
+    appendPQExpBufferStr(&conn->errorMessage,
+                         libpq_gettext("server closed the connection unexpectedly\n"
+                                       "\tThis probably means the server terminated abnormally\n"
+                                       "\tbefore or while processing the request.\n"));

     /* Come here if lower-level code already set a suitable errorMessage */
 definitelyFailed:
@@ -834,8 +833,8 @@ pqSendSome(PGconn *conn, int len)

     if (conn->sock == PGINVALID_SOCKET)
     {
-        printfPQExpBuffer(&conn->errorMessage,
-                          libpq_gettext("connection not open\n"));
+        appendPQExpBufferStr(&conn->errorMessage,
+                             libpq_gettext("connection not open\n"));
         /* Discard queued data; no chance it'll ever be sent */
         conn->outCount = 0;
         return -1;
@@ -1005,8 +1004,8 @@ pqWaitTimed(int forRead, int forWrite, PGconn *conn, time_t finish_time)

     if (result == 0)
     {
-        printfPQExpBuffer(&conn->errorMessage,
-                          libpq_gettext("timeout expired\n"));
+        appendPQExpBufferStr(&conn->errorMessage,
+                             libpq_gettext("timeout expired\n"));
         return 1;
     }

@@ -1050,8 +1049,8 @@ pqSocketCheck(PGconn *conn, int forRead, int forWrite, time_t end_time)
         return -1;
     if (conn->sock == PGINVALID_SOCKET)
     {
-        printfPQExpBuffer(&conn->errorMessage,
-                          libpq_gettext("invalid socket\n"));
+        appendPQExpBufferStr(&conn->errorMessage,
+                             libpq_gettext("invalid socket\n"));
         return -1;
     }

@@ -1073,7 +1072,7 @@ pqSocketCheck(PGconn *conn, int forRead, int forWrite, time_t end_time)
     {
         char        sebuf[256];

-        printfPQExpBuffer(&conn->errorMessage,
+        appendPQExpBuffer(&conn->errorMessage,
                           libpq_gettext("select() failed: %s\n"),
                           SOCK_STRERROR(SOCK_ERRNO, sebuf, sizeof(sebuf)));
     }
diff --git a/src/interfaces/libpq/fe-protocol2.c b/src/interfaces/libpq/fe-protocol2.c
index 53e5083..521b03b 100644
--- a/src/interfaces/libpq/fe-protocol2.c
+++ b/src/interfaces/libpq/fe-protocol2.c
@@ -85,11 +85,9 @@ pqSetenvPoll(PGconn *conn)
             return PGRES_POLLING_OK;

         default:
-            printfPQExpBuffer(&conn->errorMessage,
-                              libpq_gettext(
-                                            "invalid setenv state %c, "
-                                            "probably indicative of memory corruption\n"
-                                            ),
+            appendPQExpBuffer(&conn->errorMessage,
+                              libpq_gettext("invalid setenv state %c, "
+                                            "probably indicative of memory corruption\n"),
                               conn->setenv_state);
             goto error_return;
     }
@@ -385,7 +383,7 @@ pqSetenvPoll(PGconn *conn)
                 }

             default:
-                printfPQExpBuffer(&conn->errorMessage,
+                appendPQExpBuffer(&conn->errorMessage,
                                   libpq_gettext("invalid state %c, "
                                                 "probably indicative of memory corruption\n"),
                                   conn->setenv_state);
@@ -499,7 +497,7 @@ pqParseInput2(PGconn *conn)
                         if (!conn->result)
                         {
                             printfPQExpBuffer(&conn->errorMessage,
-                                              libpq_gettext("out of memory"));
+                                              libpq_gettext("out of memory\n"));
                             pqSaveErrorResult(conn);
                         }
                     }
@@ -534,7 +532,7 @@ pqParseInput2(PGconn *conn)
                         if (!conn->result)
                         {
                             printfPQExpBuffer(&conn->errorMessage,
-                                              libpq_gettext("out of memory"));
+                                              libpq_gettext("out of memory\n"));
                             pqSaveErrorResult(conn);
                         }
                     }
@@ -627,9 +625,8 @@ pqParseInput2(PGconn *conn)
                      * never arrives from the server during protocol 2.0.
                      */
                 default:
-                    printfPQExpBuffer(&conn->errorMessage,
-                                      libpq_gettext(
-                                                    "unexpected response from server; first received character was
\"%c\"\n"),
+                    appendPQExpBuffer(&conn->errorMessage,
+                                      libpq_gettext("unexpected response from server; first received character was
\"%c\"\n"),
                                       id);
                     /* build an error result holding the error message */
                     pqSaveErrorResult(conn);
@@ -760,7 +757,7 @@ advance_and_error:
     if (!errmsg)
         errmsg = libpq_gettext("out of memory for query result");

-    printfPQExpBuffer(&conn->errorMessage, "%s\n", errmsg);
+    appendPQExpBuffer(&conn->errorMessage, "%s\n", errmsg);

     /*
      * XXX: if PQmakeEmptyPGresult() fails, there's probably not much we can
@@ -935,7 +932,7 @@ set_error_result:
     if (!errmsg)
         errmsg = libpq_gettext("out of memory for query result");

-    printfPQExpBuffer(&conn->errorMessage, "%s\n", errmsg);
+    appendPQExpBuffer(&conn->errorMessage, "%s\n", errmsg);

     /*
      * XXX: if PQmakeEmptyPGresult() fails, there's probably not much we can
@@ -1048,12 +1045,11 @@ pqGetErrorNotice2(PGconn *conn, bool isError)
     {
         pqClearAsyncResult(conn);    /* redundant, but be safe */
         conn->result = res;
-        resetPQExpBuffer(&conn->errorMessage);
         if (res && !PQExpBufferDataBroken(workBuf) && res->errMsg)
             appendPQExpBufferStr(&conn->errorMessage, res->errMsg);
         else
             printfPQExpBuffer(&conn->errorMessage,
-                              libpq_gettext("out of memory"));
+                              libpq_gettext("out of memory\n"));
         if (conn->xactStatus == PQTRANS_INTRANS)
             conn->xactStatus = PQTRANS_INERROR;
     }
@@ -1355,8 +1351,8 @@ pqEndcopy2(PGconn *conn)
     if (conn->asyncStatus != PGASYNC_COPY_IN &&
         conn->asyncStatus != PGASYNC_COPY_OUT)
     {
-        printfPQExpBuffer(&conn->errorMessage,
-                          libpq_gettext("no COPY in progress\n"));
+        appendPQExpBufferStr(&conn->errorMessage,
+                             libpq_gettext("no COPY in progress\n"));
         return 1;
     }

@@ -1373,7 +1369,6 @@ pqEndcopy2(PGconn *conn)

     /* Return to active duty */
     conn->asyncStatus = PGASYNC_BUSY;
-    resetPQExpBuffer(&conn->errorMessage);

     /* Wait for the completion response */
     result = PQgetResult(conn);
@@ -1544,7 +1539,7 @@ pqFunctionCall2(PGconn *conn, Oid fnid,
                 else
                 {
                     /* The backend violates the protocol. */
-                    printfPQExpBuffer(&conn->errorMessage,
+                    appendPQExpBuffer(&conn->errorMessage,
                                       libpq_gettext("protocol error: id=0x%x\n"),
                                       id);
                     pqSaveErrorResult(conn);
@@ -1576,7 +1571,7 @@ pqFunctionCall2(PGconn *conn, Oid fnid,
                 return PQmakeEmptyPGresult(conn, status);
             default:
                 /* The backend violates the protocol. */
-                printfPQExpBuffer(&conn->errorMessage,
+                appendPQExpBuffer(&conn->errorMessage,
                                   libpq_gettext("protocol error: id=0x%x\n"),
                                   id);
                 pqSaveErrorResult(conn);
diff --git a/src/interfaces/libpq/fe-protocol3.c b/src/interfaces/libpq/fe-protocol3.c
index 8345faf..4a25d21 100644
--- a/src/interfaces/libpq/fe-protocol3.c
+++ b/src/interfaces/libpq/fe-protocol3.c
@@ -205,7 +205,7 @@ pqParseInput3(PGconn *conn)
                         if (!conn->result)
                         {
                             printfPQExpBuffer(&conn->errorMessage,
-                                              libpq_gettext("out of memory"));
+                                              libpq_gettext("out of memory\n"));
                             pqSaveErrorResult(conn);
                         }
                     }
@@ -232,7 +232,7 @@ pqParseInput3(PGconn *conn)
                         if (!conn->result)
                         {
                             printfPQExpBuffer(&conn->errorMessage,
-                                              libpq_gettext("out of memory"));
+                                              libpq_gettext("out of memory\n"));
                             pqSaveErrorResult(conn);
                         }
                     }
@@ -249,7 +249,7 @@ pqParseInput3(PGconn *conn)
                             if (!conn->result)
                             {
                                 printfPQExpBuffer(&conn->errorMessage,
-                                                  libpq_gettext("out of memory"));
+                                                  libpq_gettext("out of memory\n"));
                                 pqSaveErrorResult(conn);
                             }
                         }
@@ -329,7 +329,7 @@ pqParseInput3(PGconn *conn)
                             if (!conn->result)
                             {
                                 printfPQExpBuffer(&conn->errorMessage,
-                                                  libpq_gettext("out of memory"));
+                                                  libpq_gettext("out of memory\n"));
                                 pqSaveErrorResult(conn);
                             }
                         }
@@ -363,8 +363,8 @@ pqParseInput3(PGconn *conn)
                     else
                     {
                         /* Set up to report error at end of query */
-                        printfPQExpBuffer(&conn->errorMessage,
-                                          libpq_gettext("server sent data (\"D\" message) without prior row
description(\"T\" message)\n")); 
+                        appendPQExpBufferStr(&conn->errorMessage,
+                                             libpq_gettext("server sent data (\"D\" message) without prior row
description(\"T\" message)\n")); 
                         pqSaveErrorResult(conn);
                         /* Discard the unexpected message */
                         conn->inCursor += msgLength;
@@ -406,9 +406,8 @@ pqParseInput3(PGconn *conn)
                      */
                     break;
                 default:
-                    printfPQExpBuffer(&conn->errorMessage,
-                                      libpq_gettext(
-                                                    "unexpected response from server; first received character was
\"%c\"\n"),
+                    appendPQExpBuffer(&conn->errorMessage,
+                                      libpq_gettext("unexpected response from server; first received character was
\"%c\"\n"),
                                       id);
                     /* build an error result holding the error message */
                     pqSaveErrorResult(conn);
@@ -428,7 +427,7 @@ pqParseInput3(PGconn *conn)
         else
         {
             /* Trouble --- report it */
-            printfPQExpBuffer(&conn->errorMessage,
+            appendPQExpBuffer(&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 */
@@ -448,9 +447,8 @@ pqParseInput3(PGconn *conn)
 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"),
+    appendPQExpBuffer(&conn->errorMessage,
+                      libpq_gettext("lost synchronization with server: got message type \"%c\", length %d\n"),
                       id, msgLength);
     /* build an error result holding the error message */
     pqSaveErrorResult(conn);
@@ -625,7 +623,7 @@ advance_and_error:
     if (!errmsg)
         errmsg = libpq_gettext("out of memory for query result");

-    printfPQExpBuffer(&conn->errorMessage, "%s\n", errmsg);
+    appendPQExpBuffer(&conn->errorMessage, "%s\n", errmsg);
     pqSaveErrorResult(conn);

     /*
@@ -725,7 +723,7 @@ advance_and_error:
      */
     if (!errmsg)
         errmsg = libpq_gettext("out of memory");
-    printfPQExpBuffer(&conn->errorMessage, "%s\n", errmsg);
+    appendPQExpBuffer(&conn->errorMessage, "%s\n", errmsg);
     pqSaveErrorResult(conn);

     /*
@@ -852,7 +850,7 @@ set_error_result:
     if (!errmsg)
         errmsg = libpq_gettext("out of memory for query result");

-    printfPQExpBuffer(&conn->errorMessage, "%s\n", errmsg);
+    appendPQExpBuffer(&conn->errorMessage, "%s\n", errmsg);
     pqSaveErrorResult(conn);

     /*
@@ -953,9 +951,11 @@ pqGetErrorNotice3(PGconn *conn, bool isError)
             res->errMsg = pqResultStrdup(res, workBuf.data);
         pqClearAsyncResult(conn);    /* redundant, but be safe */
         conn->result = res;
+
+        /* We also keep a running history of errors in conn->errorMessage. */
         if (PQExpBufferDataBroken(workBuf))
             printfPQExpBuffer(&conn->errorMessage,
-                              libpq_gettext("out of memory"));
+                              libpq_gettext("out of memory\n"));
         else
             appendPQExpBufferStr(&conn->errorMessage, workBuf.data);
     }
@@ -1714,8 +1714,8 @@ pqGetline3(PGconn *conn, char *s, int maxlen)
          conn->asyncStatus != PGASYNC_COPY_BOTH) ||
         conn->copy_is_binary)
     {
-        printfPQExpBuffer(&conn->errorMessage,
-                          libpq_gettext("PQgetline: not doing text COPY OUT\n"));
+        appendPQExpBufferStr(&conn->errorMessage,
+                             libpq_gettext("PQgetline: not doing text COPY OUT\n"));
         *s = '\0';
         return EOF;
     }
@@ -1820,8 +1820,8 @@ pqEndcopy3(PGconn *conn)
         conn->asyncStatus != PGASYNC_COPY_OUT &&
         conn->asyncStatus != PGASYNC_COPY_BOTH)
     {
-        printfPQExpBuffer(&conn->errorMessage,
-                          libpq_gettext("no COPY in progress\n"));
+        appendPQExpBufferStr(&conn->errorMessage,
+                             libpq_gettext("no COPY in progress\n"));
         return 1;
     }

@@ -1854,7 +1854,6 @@ pqEndcopy3(PGconn *conn)

     /* Return to active duty */
     conn->asyncStatus = PGASYNC_BUSY;
-    resetPQExpBuffer(&conn->errorMessage);

     /*
      * Non blocking connections may have to abort at this point.  If everyone
@@ -2092,7 +2091,7 @@ pqFunctionCall3(PGconn *conn, Oid fnid,
                 break;
             default:
                 /* The backend violates the protocol. */
-                printfPQExpBuffer(&conn->errorMessage,
+                appendPQExpBuffer(&conn->errorMessage,
                                   libpq_gettext("protocol error: id=0x%x\n"),
                                   id);
                 pqSaveErrorResult(conn);
diff --git a/src/interfaces/libpq/fe-secure-common.c b/src/interfaces/libpq/fe-secure-common.c
index b3f580f..772aca4 100644
--- a/src/interfaces/libpq/fe-secure-common.c
+++ b/src/interfaces/libpq/fe-secure-common.c
@@ -94,8 +94,8 @@ pq_verify_peer_name_matches_certificate_name(PGconn *conn,

     if (!(host && host[0] != '\0'))
     {
-        printfPQExpBuffer(&conn->errorMessage,
-                          libpq_gettext("host name must be specified\n"));
+        appendPQExpBufferStr(&conn->errorMessage,
+                             libpq_gettext("host name must be specified\n"));
         return -1;
     }

@@ -120,8 +120,8 @@ pq_verify_peer_name_matches_certificate_name(PGconn *conn,
     if (namelen != strlen(name))
     {
         free(name);
-        printfPQExpBuffer(&conn->errorMessage,
-                          libpq_gettext("SSL certificate's name contains embedded null\n"));
+        appendPQExpBufferStr(&conn->errorMessage,
+                             libpq_gettext("SSL certificate's name contains embedded null\n"));
         return -1;
     }

@@ -167,8 +167,8 @@ pq_verify_peer_name_matches_certificate(PGconn *conn)
     /* Check that we have a hostname to compare with. */
     if (!(host && host[0] != '\0'))
     {
-        printfPQExpBuffer(&conn->errorMessage,
-                          libpq_gettext("host name must be specified for a verified SSL connection\n"));
+        appendPQExpBufferStr(&conn->errorMessage,
+                             libpq_gettext("host name must be specified for a verified SSL connection\n"));
         return false;
     }

@@ -184,7 +184,7 @@ pq_verify_peer_name_matches_certificate(PGconn *conn)
          */
         if (names_examined > 1)
         {
-            printfPQExpBuffer(&conn->errorMessage,
+            appendPQExpBuffer(&conn->errorMessage,
                               libpq_ngettext("server certificate for \"%s\" (and %d other name) does not match host
name\"%s\"\n", 
                                              "server certificate for \"%s\" (and %d other names) does not match host
name\"%s\"\n", 
                                              names_examined - 1),
@@ -192,14 +192,14 @@ pq_verify_peer_name_matches_certificate(PGconn *conn)
         }
         else if (names_examined == 1)
         {
-            printfPQExpBuffer(&conn->errorMessage,
+            appendPQExpBuffer(&conn->errorMessage,
                               libpq_gettext("server certificate for \"%s\" does not match host name \"%s\"\n"),
                               first_name, host);
         }
         else
         {
-            printfPQExpBuffer(&conn->errorMessage,
-                              libpq_gettext("could not get server's host name from server certificate\n"));
+            appendPQExpBufferStr(&conn->errorMessage,
+                                 libpq_gettext("could not get server's host name from server certificate\n"));
         }
     }

diff --git a/src/interfaces/libpq/fe-secure-openssl.c b/src/interfaces/libpq/fe-secure-openssl.c
index bbae8ef..f995ed9 100644
--- a/src/interfaces/libpq/fe-secure-openssl.c
+++ b/src/interfaces/libpq/fe-secure-openssl.c
@@ -177,8 +177,8 @@ rloop:
             if (n < 0)
             {
                 /* Not supposed to happen, so we don't translate the msg */
-                printfPQExpBuffer(&conn->errorMessage,
-                                  "SSL_read failed but did not provide error information\n");
+                appendPQExpBufferStr(&conn->errorMessage,
+                                     "SSL_read failed but did not provide error information\n");
                 /* assume the connection is broken */
                 result_errno = ECONNRESET;
             }
@@ -201,21 +201,20 @@ rloop:
                 result_errno = SOCK_ERRNO;
                 if (result_errno == EPIPE ||
                     result_errno == ECONNRESET)
-                    printfPQExpBuffer(&conn->errorMessage,
-                                      libpq_gettext(
-                                                    "server closed the connection unexpectedly\n"
-                                                    "\tThis probably means the server terminated abnormally\n"
-                                                    "\tbefore or while processing the request.\n"));
+                    appendPQExpBufferStr(&conn->errorMessage,
+                                         libpq_gettext("server closed the connection unexpectedly\n"
+                                                       "\tThis probably means the server terminated abnormally\n"
+                                                       "\tbefore or while processing the request.\n"));
                 else
-                    printfPQExpBuffer(&conn->errorMessage,
+                    appendPQExpBuffer(&conn->errorMessage,
                                       libpq_gettext("SSL SYSCALL error: %s\n"),
                                       SOCK_STRERROR(result_errno,
                                                     sebuf, sizeof(sebuf)));
             }
             else
             {
-                printfPQExpBuffer(&conn->errorMessage,
-                                  libpq_gettext("SSL SYSCALL error: EOF detected\n"));
+                appendPQExpBufferStr(&conn->errorMessage,
+                                     libpq_gettext("SSL SYSCALL error: EOF detected\n"));
                 /* assume the connection is broken */
                 result_errno = ECONNRESET;
                 n = -1;
@@ -225,7 +224,7 @@ rloop:
             {
                 char       *errm = SSLerrmessage(ecode);

-                printfPQExpBuffer(&conn->errorMessage,
+                appendPQExpBuffer(&conn->errorMessage,
                                   libpq_gettext("SSL error: %s\n"), errm);
                 SSLerrfree(errm);
                 /* assume the connection is broken */
@@ -240,13 +239,13 @@ rloop:
              * a clean connection closure, so we should not report it as a
              * server crash.
              */
-            printfPQExpBuffer(&conn->errorMessage,
-                              libpq_gettext("SSL connection has been closed unexpectedly\n"));
+            appendPQExpBufferStr(&conn->errorMessage,
+                                 libpq_gettext("SSL connection has been closed unexpectedly\n"));
             result_errno = ECONNRESET;
             n = -1;
             break;
         default:
-            printfPQExpBuffer(&conn->errorMessage,
+            appendPQExpBuffer(&conn->errorMessage,
                               libpq_gettext("unrecognized SSL error code: %d\n"),
                               err);
             /* assume the connection is broken */
@@ -287,8 +286,8 @@ pgtls_write(PGconn *conn, const void *ptr, size_t len)
             if (n < 0)
             {
                 /* Not supposed to happen, so we don't translate the msg */
-                printfPQExpBuffer(&conn->errorMessage,
-                                  "SSL_write failed but did not provide error information\n");
+                appendPQExpBufferStr(&conn->errorMessage,
+                                     "SSL_write failed but did not provide error information\n");
                 /* assume the connection is broken */
                 result_errno = ECONNRESET;
             }
@@ -309,21 +308,20 @@ pgtls_write(PGconn *conn, const void *ptr, size_t len)
             {
                 result_errno = SOCK_ERRNO;
                 if (result_errno == EPIPE || result_errno == ECONNRESET)
-                    printfPQExpBuffer(&conn->errorMessage,
-                                      libpq_gettext(
-                                                    "server closed the connection unexpectedly\n"
-                                                    "\tThis probably means the server terminated abnormally\n"
-                                                    "\tbefore or while processing the request.\n"));
+                    appendPQExpBufferStr(&conn->errorMessage,
+                                         libpq_gettext("server closed the connection unexpectedly\n"
+                                                       "\tThis probably means the server terminated abnormally\n"
+                                                       "\tbefore or while processing the request.\n"));
                 else
-                    printfPQExpBuffer(&conn->errorMessage,
+                    appendPQExpBuffer(&conn->errorMessage,
                                       libpq_gettext("SSL SYSCALL error: %s\n"),
                                       SOCK_STRERROR(result_errno,
                                                     sebuf, sizeof(sebuf)));
             }
             else
             {
-                printfPQExpBuffer(&conn->errorMessage,
-                                  libpq_gettext("SSL SYSCALL error: EOF detected\n"));
+                appendPQExpBufferStr(&conn->errorMessage,
+                                     libpq_gettext("SSL SYSCALL error: EOF detected\n"));
                 /* assume the connection is broken */
                 result_errno = ECONNRESET;
                 n = -1;
@@ -333,7 +331,7 @@ pgtls_write(PGconn *conn, const void *ptr, size_t len)
             {
                 char       *errm = SSLerrmessage(ecode);

-                printfPQExpBuffer(&conn->errorMessage,
+                appendPQExpBuffer(&conn->errorMessage,
                                   libpq_gettext("SSL error: %s\n"), errm);
                 SSLerrfree(errm);
                 /* assume the connection is broken */
@@ -348,13 +346,13 @@ pgtls_write(PGconn *conn, const void *ptr, size_t len)
              * a clean connection closure, so we should not report it as a
              * server crash.
              */
-            printfPQExpBuffer(&conn->errorMessage,
-                              libpq_gettext("SSL connection has been closed unexpectedly\n"));
+            appendPQExpBufferStr(&conn->errorMessage,
+                                 libpq_gettext("SSL connection has been closed unexpectedly\n"));
             result_errno = ECONNRESET;
             n = -1;
             break;
         default:
-            printfPQExpBuffer(&conn->errorMessage,
+            appendPQExpBuffer(&conn->errorMessage,
                               libpq_gettext("unrecognized SSL error code: %d\n"),
                               err);
             /* assume the connection is broken */
@@ -394,8 +392,8 @@ pgtls_get_peer_certificate_hash(PGconn *conn, size_t *len)
     if (!OBJ_find_sigid_algs(X509_get_signature_nid(peer_cert),
                              &algo_nid, NULL))
     {
-        printfPQExpBuffer(&conn->errorMessage,
-                          libpq_gettext("could not determine server certificate signature algorithm\n"));
+        appendPQExpBufferStr(&conn->errorMessage,
+                             libpq_gettext("could not determine server certificate signature algorithm\n"));
         return NULL;
     }

@@ -415,7 +413,7 @@ pgtls_get_peer_certificate_hash(PGconn *conn, size_t *len)
             algo_type = EVP_get_digestbynid(algo_nid);
             if (algo_type == NULL)
             {
-                printfPQExpBuffer(&conn->errorMessage,
+                appendPQExpBuffer(&conn->errorMessage,
                                   libpq_gettext("could not find digest for NID %s\n"),
                                   OBJ_nid2sn(algo_nid));
                 return NULL;
@@ -425,8 +423,8 @@ pgtls_get_peer_certificate_hash(PGconn *conn, size_t *len)

     if (!X509_digest(peer_cert, algo_type, hash, &hash_size))
     {
-        printfPQExpBuffer(&conn->errorMessage,
-                          libpq_gettext("could not generate peer certificate hash\n"));
+        appendPQExpBufferStr(&conn->errorMessage,
+                             libpq_gettext("could not generate peer certificate hash\n"));
         return NULL;
     }

@@ -443,7 +441,7 @@ pgtls_get_peer_certificate_hash(PGconn *conn, size_t *len)

     return cert_hash;
 }
-#endif /* HAVE_X509_GET_SIGNATURE_NID */
+#endif                            /* HAVE_X509_GET_SIGNATURE_NID */

 /* ------------------------------------------------------------ */
 /*                        OpenSSL specific code                    */
@@ -482,8 +480,8 @@ openssl_verify_peer_name_matches_certificate_name(PGconn *conn, ASN1_STRING *nam
     /* Should not happen... */
     if (name_entry == NULL)
     {
-        printfPQExpBuffer(&conn->errorMessage,
-                          libpq_gettext("SSL certificate's name entry is missing\n"));
+        appendPQExpBufferStr(&conn->errorMessage,
+                             libpq_gettext("SSL certificate's name entry is missing\n"));
         return -1;
     }

@@ -811,7 +809,7 @@ initialize_SSL(PGconn *conn)
     {
         char       *err = SSLerrmessage(ERR_get_error());

-        printfPQExpBuffer(&conn->errorMessage,
+        appendPQExpBuffer(&conn->errorMessage,
                           libpq_gettext("could not create SSL context: %s\n"),
                           err);
         SSLerrfree(err);
@@ -848,7 +846,7 @@ initialize_SSL(PGconn *conn)
         {
             char       *err = SSLerrmessage(ERR_get_error());

-            printfPQExpBuffer(&conn->errorMessage,
+            appendPQExpBuffer(&conn->errorMessage,
                               libpq_gettext("could not read root certificate file \"%s\": %s\n"),
                               fnbuf, err);
             SSLerrfree(err);
@@ -876,7 +874,7 @@ initialize_SSL(PGconn *conn)
 #else
                 char       *err = SSLerrmessage(ERR_get_error());

-                printfPQExpBuffer(&conn->errorMessage,
+                appendPQExpBuffer(&conn->errorMessage,
                                   libpq_gettext("SSL library does not support CRL certificates (file \"%s\")\n"),
                                   fnbuf);
                 SSLerrfree(err);
@@ -904,11 +902,11 @@ initialize_SSL(PGconn *conn)
              * that it seems worth having a specialized error message for it.
              */
             if (fnbuf[0] == '\0')
-                printfPQExpBuffer(&conn->errorMessage,
-                                  libpq_gettext("could not get home directory to locate root certificate file\n"
-                                                "Either provide the file or change sslmode to disable server
certificateverification.\n")); 
+                appendPQExpBufferStr(&conn->errorMessage,
+                                     libpq_gettext("could not get home directory to locate root certificate file\n"
+                                                   "Either provide the file or change sslmode to disable server
certificateverification.\n")); 
             else
-                printfPQExpBuffer(&conn->errorMessage,
+                appendPQExpBuffer(&conn->errorMessage,
                                   libpq_gettext("root certificate file \"%s\" does not exist\n"
                                                 "Either provide the file or change sslmode to disable server
certificateverification.\n"), fnbuf); 
             SSL_CTX_free(SSL_context);
@@ -939,7 +937,7 @@ initialize_SSL(PGconn *conn)
          */
         if (errno != ENOENT && errno != ENOTDIR)
         {
-            printfPQExpBuffer(&conn->errorMessage,
+            appendPQExpBuffer(&conn->errorMessage,
                               libpq_gettext("could not open certificate file \"%s\": %s\n"),
                               fnbuf, pqStrerror(errno, sebuf, sizeof(sebuf)));
             SSL_CTX_free(SSL_context);
@@ -958,7 +956,7 @@ initialize_SSL(PGconn *conn)
         {
             char       *err = SSLerrmessage(ERR_get_error());

-            printfPQExpBuffer(&conn->errorMessage,
+            appendPQExpBuffer(&conn->errorMessage,
                               libpq_gettext("could not read certificate file \"%s\": %s\n"),
                               fnbuf, err);
             SSLerrfree(err);
@@ -983,7 +981,7 @@ initialize_SSL(PGconn *conn)
     {
         char       *err = SSLerrmessage(ERR_get_error());

-        printfPQExpBuffer(&conn->errorMessage,
+        appendPQExpBuffer(&conn->errorMessage,
                           libpq_gettext("could not establish SSL connection: %s\n"),
                           err);
         SSLerrfree(err);
@@ -1037,7 +1035,7 @@ initialize_SSL(PGconn *conn)
             {
                 char       *err = SSLerrmessage(ERR_get_error());

-                printfPQExpBuffer(&conn->errorMessage,
+                appendPQExpBuffer(&conn->errorMessage,
                                   libpq_gettext("could not load SSL engine \"%s\": %s\n"),
                                   engine_str, err);
                 SSLerrfree(err);
@@ -1049,7 +1047,7 @@ initialize_SSL(PGconn *conn)
             {
                 char       *err = SSLerrmessage(ERR_get_error());

-                printfPQExpBuffer(&conn->errorMessage,
+                appendPQExpBuffer(&conn->errorMessage,
                                   libpq_gettext("could not initialize SSL engine \"%s\": %s\n"),
                                   engine_str, err);
                 SSLerrfree(err);
@@ -1065,7 +1063,7 @@ initialize_SSL(PGconn *conn)
             {
                 char       *err = SSLerrmessage(ERR_get_error());

-                printfPQExpBuffer(&conn->errorMessage,
+                appendPQExpBuffer(&conn->errorMessage,
                                   libpq_gettext("could not read private SSL key \"%s\" from engine \"%s\": %s\n"),
                                   engine_colon, engine_str, err);
                 SSLerrfree(err);
@@ -1079,7 +1077,7 @@ initialize_SSL(PGconn *conn)
             {
                 char       *err = SSLerrmessage(ERR_get_error());

-                printfPQExpBuffer(&conn->errorMessage,
+                appendPQExpBuffer(&conn->errorMessage,
                                   libpq_gettext("could not load private SSL key \"%s\" from engine \"%s\": %s\n"),
                                   engine_colon, engine_str, err);
                 SSLerrfree(err);
@@ -1116,7 +1114,7 @@ initialize_SSL(PGconn *conn)

         if (stat(fnbuf, &buf) != 0)
         {
-            printfPQExpBuffer(&conn->errorMessage,
+            appendPQExpBuffer(&conn->errorMessage,
                               libpq_gettext("certificate present, but not private key file \"%s\"\n"),
                               fnbuf);
             return -1;
@@ -1124,7 +1122,7 @@ initialize_SSL(PGconn *conn)
 #ifndef WIN32
         if (!S_ISREG(buf.st_mode) || buf.st_mode & (S_IRWXG | S_IRWXO))
         {
-            printfPQExpBuffer(&conn->errorMessage,
+            appendPQExpBuffer(&conn->errorMessage,
                               libpq_gettext("private key file \"%s\" has group or world access; permissions should be
u=rw(0600) or less\n"), 
                               fnbuf);
             return -1;
@@ -1135,7 +1133,7 @@ initialize_SSL(PGconn *conn)
         {
             char       *err = SSLerrmessage(ERR_get_error());

-            printfPQExpBuffer(&conn->errorMessage,
+            appendPQExpBuffer(&conn->errorMessage,
                               libpq_gettext("could not load private key file \"%s\": %s\n"),
                               fnbuf, err);
             SSLerrfree(err);
@@ -1149,7 +1147,7 @@ initialize_SSL(PGconn *conn)
     {
         char       *err = SSLerrmessage(ERR_get_error());

-        printfPQExpBuffer(&conn->errorMessage,
+        appendPQExpBuffer(&conn->errorMessage,
                           libpq_gettext("certificate does not match private key file \"%s\": %s\n"),
                           fnbuf, err);
         SSLerrfree(err);
@@ -1215,12 +1213,12 @@ open_client_SSL(PGconn *conn)
                     char        sebuf[256];

                     if (r == -1)
-                        printfPQExpBuffer(&conn->errorMessage,
+                        appendPQExpBuffer(&conn->errorMessage,
                                           libpq_gettext("SSL SYSCALL error: %s\n"),
                                           SOCK_STRERROR(SOCK_ERRNO, sebuf, sizeof(sebuf)));
                     else
-                        printfPQExpBuffer(&conn->errorMessage,
-                                          libpq_gettext("SSL SYSCALL error: EOF detected\n"));
+                        appendPQExpBufferStr(&conn->errorMessage,
+                                             libpq_gettext("SSL SYSCALL error: EOF detected\n"));
                     pgtls_close(conn);
                     return PGRES_POLLING_FAILED;
                 }
@@ -1228,7 +1226,7 @@ open_client_SSL(PGconn *conn)
                 {
                     char       *err = SSLerrmessage(ecode);

-                    printfPQExpBuffer(&conn->errorMessage,
+                    appendPQExpBuffer(&conn->errorMessage,
                                       libpq_gettext("SSL error: %s\n"),
                                       err);
                     SSLerrfree(err);
@@ -1237,7 +1235,7 @@ open_client_SSL(PGconn *conn)
                 }

             default:
-                printfPQExpBuffer(&conn->errorMessage,
+                appendPQExpBuffer(&conn->errorMessage,
                                   libpq_gettext("unrecognized SSL error code: %d\n"),
                                   err);
                 pgtls_close(conn);
@@ -1258,7 +1256,7 @@ open_client_SSL(PGconn *conn)

         err = SSLerrmessage(ERR_get_error());

-        printfPQExpBuffer(&conn->errorMessage,
+        appendPQExpBuffer(&conn->errorMessage,
                           libpq_gettext("certificate could not be obtained: %s\n"),
                           err);
         SSLerrfree(err);
diff --git a/src/interfaces/libpq/fe-secure.c b/src/interfaces/libpq/fe-secure.c
index f7dc249..157a738 100644
--- a/src/interfaces/libpq/fe-secure.c
+++ b/src/interfaces/libpq/fe-secure.c
@@ -205,8 +205,8 @@ pqsecure_close(PGconn *conn)
 /*
  *    Read data from a secure connection.
  *
- * On failure, this function is responsible for putting a suitable message
- * into conn->errorMessage.  The caller must still inspect errno, but only
+ * On failure, this function is responsible for appending a suitable message
+ * to conn->errorMessage.  The caller must still inspect errno, but only
  * to determine whether to continue/retry after error.
  */
 ssize_t
@@ -256,16 +256,15 @@ pqsecure_raw_read(PGconn *conn, void *ptr, size_t len)

 #ifdef ECONNRESET
             case ECONNRESET:
-                printfPQExpBuffer(&conn->errorMessage,
-                                  libpq_gettext(
-                                                "server closed the connection unexpectedly\n"
-                                                "\tThis probably means the server terminated abnormally\n"
-                                                "\tbefore or while processing the request.\n"));
+                appendPQExpBufferStr(&conn->errorMessage,
+                                     libpq_gettext("server closed the connection unexpectedly\n"
+                                                   "\tThis probably means the server terminated abnormally\n"
+                                                   "\tbefore or while processing the request.\n"));
                 break;
 #endif

             default:
-                printfPQExpBuffer(&conn->errorMessage,
+                appendPQExpBuffer(&conn->errorMessage,
                                   libpq_gettext("could not receive data from server: %s\n"),
                                   SOCK_STRERROR(result_errno,
                                                 sebuf, sizeof(sebuf)));
@@ -282,8 +281,8 @@ pqsecure_raw_read(PGconn *conn, void *ptr, size_t len)
 /*
  *    Write data to a secure connection.
  *
- * On failure, this function is responsible for putting a suitable message
- * into conn->errorMessage.  The caller must still inspect errno, but only
+ * On failure, this function is responsible for appending a suitable message
+ * to conn->errorMessage.  The caller must still inspect errno, but only
  * to determine whether to continue/retry after error.
  */
 ssize_t
@@ -366,15 +365,14 @@ retry_masked:

             case ECONNRESET:
 #endif
-                printfPQExpBuffer(&conn->errorMessage,
-                                  libpq_gettext(
-                                                "server closed the connection unexpectedly\n"
-                                                "\tThis probably means the server terminated abnormally\n"
-                                                "\tbefore or while processing the request.\n"));
+                appendPQExpBufferStr(&conn->errorMessage,
+                                     libpq_gettext("server closed the connection unexpectedly\n"
+                                                   "\tThis probably means the server terminated abnormally\n"
+                                                   "\tbefore or while processing the request.\n"));
                 break;

             default:
-                printfPQExpBuffer(&conn->errorMessage,
+                appendPQExpBuffer(&conn->errorMessage,
                                   libpq_gettext("could not send data to server: %s\n"),
                                   SOCK_STRERROR(result_errno,
                                                 sebuf, sizeof(sebuf)));
diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h
index bc60373..6df3af0 100644
--- a/src/interfaces/libpq/libpq-int.h
+++ b/src/interfaces/libpq/libpq-int.h
@@ -679,7 +679,7 @@ extern void pgtls_init_library(bool do_ssl, int do_crypto);
  * The conn parameter is only used to be able to pass back an error
  * message - no connection-local setup is made here.
  *
- * Returns 0 if OK, -1 on failure (with a message in conn->errorMessage).
+ * Returns 0 if OK, -1 on failure (with a message added to conn->errorMessage).
  */
 extern int    pgtls_init(PGconn *conn);

@@ -696,8 +696,8 @@ extern void pgtls_close(PGconn *conn);
 /*
  *    Read data from a secure connection.
  *
- * On failure, this function is responsible for putting a suitable message
- * into conn->errorMessage.  The caller must still inspect errno, but only
+ * On failure, this function is responsible for appending a suitable message
+ * to conn->errorMessage.  The caller must still inspect errno, but only
  * to determine whether to continue/retry after error.
  */
 extern ssize_t pgtls_read(PGconn *conn, void *ptr, size_t len);
@@ -710,8 +710,8 @@ extern bool pgtls_read_pending(PGconn *conn);
 /*
  *    Write data to a secure connection.
  *
- * On failure, this function is responsible for putting a suitable message
- * into conn->errorMessage.  The caller must still inspect errno, but only
+ * On failure, this function is responsible for appending a suitable message
+ * to conn->errorMessage.  The caller must still inspect errno, but only
  * to determine whether to continue/retry after error.
  */
 extern ssize_t pgtls_write(PGconn *conn, const void *ptr, size_t len);
diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c
index 9b6a065..a16ad4b 100644
--- a/src/interfaces/libpq/fe-connect.c
+++ b/src/interfaces/libpq/fe-connect.c
@@ -2115,6 +2115,31 @@ keep_going:                        /* We will come back to here until there is
             goto error_return;
         }
         conn->whichhost++;
+
+        /*
+         * If we're given more than one target host, append identification
+         * info to conn->errorMessage as we start to consider each one.  This
+         * makes it easier to figure out which host error messages apply to,
+         * in case we fail to connect to all of them.
+         */
+        if (conn->nconnhost > 1)
+        {
+            pg_conn_host *ch = &conn->connhost[conn->whichhost];
+            const char *displayed_host;
+            const char *displayed_port;
+
+            if (ch->type == CHT_HOST_ADDRESS)
+                displayed_host = ch->hostaddr;
+            else
+                displayed_host = ch->host;
+            displayed_port = ch->port;
+            if (displayed_port == NULL || displayed_port[0] == '\0')
+                displayed_port = DEF_PGPORT_STR;
+            appendPQExpBuffer(&conn->errorMessage,
+                              libpq_gettext("server \"%s\" port %s:\n"),
+                              displayed_host, displayed_port);
+        }
+
         conn->addr_cur = conn->connhost[conn->whichhost].addrlist;
         /* If no addresses for this host, just try the next one */
         if (conn->addr_cur == NULL)
@@ -3154,9 +3179,6 @@ keep_going:                        /* We will come back to here until there is
             }
         case CONNECTION_CHECK_WRITABLE:
             {
-                const char *displayed_host;
-                const char *displayed_port;
-
                 if (!PQconsumeInput(conn))
                     goto error_return;

@@ -3173,25 +3195,10 @@ keep_going:                        /* We will come back to here until there is
                     if (strncmp(val, "on", 2) == 0)
                     {
                         /* Not writable; fail this connection. */
-                        const char *displayed_host;
-                        const char *displayed_port;
-
                         PQclear(res);

-                        /* Append error report to conn->errorMessage. */
-                        if (conn->connhost[conn->whichhost].type == CHT_HOST_ADDRESS)
-                            displayed_host = conn->connhost[conn->whichhost].hostaddr;
-                        else
-                            displayed_host = conn->connhost[conn->whichhost].host;
-                        displayed_port = conn->connhost[conn->whichhost].port;
-                        if (displayed_port == NULL || displayed_port[0] == '\0')
-                            displayed_port = DEF_PGPORT_STR;
-
-                        appendPQExpBuffer(&conn->errorMessage,
-                                          libpq_gettext("could not make a writable "
-                                                        "connection to server "
-                                                        "\"%s:%s\"\n"),
-                                          displayed_host, displayed_port);
+                        appendPQExpBufferStr(&conn->errorMessage,
+                                             libpq_gettext("could not open a read-write session\n"));

                         /* Close connection politely. */
                         conn->status = CONNECTION_OK;
@@ -3217,25 +3224,17 @@ keep_going:                        /* We will come back to here until there is
                 }

                 /*
-                 * Something went wrong with "SHOW transaction_read_only". We
-                 * should try next addresses.
+                 * Something went wrong with "SHOW transaction_read_only".  If
+                 * there was a server error, it'll already have been appended
+                 * to errorMessage, else say something generic.
                  */
+                if (res == NULL || PQresultStatus(res) != PGRES_FATAL_ERROR)
+                    appendPQExpBufferStr(&conn->errorMessage,
+                                         libpq_gettext("\"SHOW transaction_read_only\" failed\n"));
+
                 if (res)
                     PQclear(res);

-                /* Append error report to conn->errorMessage. */
-                if (conn->connhost[conn->whichhost].type == CHT_HOST_ADDRESS)
-                    displayed_host = conn->connhost[conn->whichhost].hostaddr;
-                else
-                    displayed_host = conn->connhost[conn->whichhost].host;
-                displayed_port = conn->connhost[conn->whichhost].port;
-                if (displayed_port == NULL || displayed_port[0] == '\0')
-                    displayed_port = DEF_PGPORT_STR;
-                appendPQExpBuffer(&conn->errorMessage,
-                                  libpq_gettext("test \"SHOW transaction_read_only\" failed "
-                                                "on server \"%s:%s\"\n"),
-                                  displayed_host, displayed_port);
-
                 /* Close connection politely. */
                 conn->status = CONNECTION_OK;
                 sendTerminateConn(conn);

pgsql-hackers by date:

Previous
From: Michael Paquier
Date:
Subject: Re: Improve behavior of concurrent TRUNCATE
Next
From: Michael Paquier
Date:
Subject: Re: xact_start meaning when dealing with procedures?