Avoid extra system calls to block SIGPIPE if the platform provides either
authorTom Lane <[email protected]>
Fri, 24 Jul 2009 17:58:31 +0000 (17:58 +0000)
committerTom Lane <[email protected]>
Fri, 24 Jul 2009 17:58:31 +0000 (17:58 +0000)
sockopt(SO_NOSIGPIPE) or the MSG_NOSIGNAL flag to send().

We assume these features are available if (1) the symbol is defined at
compile time and (2) the kernel doesn't reject the call at runtime.
It might turn out that there are some platforms where (1) and (2) are
true and yet the signal isn't really blocked, in which case applications
would die on server crash.  If that sort of thing gets reported, then
we'll have to add additional defenses of some kind.

Jeremy Kerr

src/interfaces/libpq/fe-connect.c
src/interfaces/libpq/fe-secure.c
src/interfaces/libpq/libpq-int.h

index d20d0b601063efb032997ef7aaa3b4e3445e4fbd..41c753a4f8cdb172fb0a17265ab559d098639adb 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *   $PostgreSQL: pgsql/src/interfaces/libpq/fe-connect.c,v 1.375 2009/06/11 14:49:13 momjian Exp $
+ *   $PostgreSQL: pgsql/src/interfaces/libpq/fe-connect.c,v 1.376 2009/07/24 17:58:31 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -1018,6 +1018,7 @@ PQconnectPoll(PGconn *conn)
 {
    PGresult   *res;
    char        sebuf[256];
+   int         optval;
 
    if (conn == NULL)
        return PGRES_POLLING_FAILED;
@@ -1153,6 +1154,46 @@ keep_going:                      /* We will come back to here until there is
                    }
 #endif   /* F_SETFD */
 
+                   /*----------
+                    * We have three methods of blocking SIGPIPE during
+                    * send() calls to this socket:
+                    *
+                    *  - setsockopt(sock, SO_NOSIGPIPE)
+                    *  - send(sock, ..., MSG_NOSIGNAL)
+                    *  - setting the signal mask to SIG_IGN during send()
+                    *
+                    * The third method requires three syscalls per send,
+                    * so we prefer either of the first two, but they are
+                    * less portable.  The state is tracked in the following
+                    * members of PGconn:
+                    *
+                    * conn->sigpipe_so     - we have set up SO_NOSIGPIPE
+                    * conn->sigpipe_flag   - we're specifying MSG_NOSIGNAL
+                    *
+                    * If we can use SO_NOSIGPIPE, then set sigpipe_so here
+                    * and we're done.  Otherwise, set sigpipe_flag so that
+                    * we will try MSG_NOSIGNAL on sends.  If we get an error
+                    * with MSG_NOSIGNAL, we'll clear that flag and revert to
+                    * signal masking.
+                    *----------
+                    */
+                   conn->sigpipe_so = false;
+#ifdef MSG_NOSIGNAL
+                   conn->sigpipe_flag = true;
+#else
+                   conn->sigpipe_flag = false;
+#endif /* MSG_NOSIGNAL */
+
+#ifdef SO_NOSIGPIPE
+                   optval = 1;
+                   if (setsockopt(conn->sock, SOL_SOCKET, SO_NOSIGPIPE,
+                                  (char *) &optval, sizeof(optval)) == 0)
+                   {
+                       conn->sigpipe_so = true;
+                       conn->sigpipe_flag = false;
+                   }
+#endif /* SO_NOSIGPIPE */
+
                    /*
                     * Start/make connection.  This should not block, since we
                     * are in nonblock mode.  If it does, well, too bad.
@@ -1214,7 +1255,6 @@ keep_going:                       /* We will come back to here until there is
 
        case CONNECTION_STARTED:
            {
-               int         optval;
                ACCEPT_TYPE_ARG3 optlen = sizeof(optval);
 
                /*
index c6e64147872ad45232c46c7d30513845d75d705c..dbd37d50390d7aa6a92591dd2289151442c4b5d0 100644 (file)
@@ -11,7 +11,7 @@
  *
  *
  * IDENTIFICATION
- *   $PostgreSQL: pgsql/src/interfaces/libpq/fe-secure.c,v 1.127 2009/06/23 18:13:23 mha Exp $
+ *   $PostgreSQL: pgsql/src/interfaces/libpq/fe-secure.c,v 1.128 2009/07/24 17:58:31 tgl Exp $
  *
  * NOTES
  *
@@ -118,44 +118,76 @@ static long win32_ssl_create_mutex = 0;
 
 /*
  * Macros to handle disabling and then restoring the state of SIGPIPE handling.
- * Note that DISABLE_SIGPIPE() must appear at the start of a block.
+ * On Windows, these are all no-ops since there's no SIGPIPEs.
  */
 
 #ifndef WIN32
+
+#define SIGPIPE_MASKED(conn)   ((conn)->sigpipe_so || (conn)->sigpipe_flag)
+
 #ifdef ENABLE_THREAD_SAFETY
 
-#define DISABLE_SIGPIPE(failaction) \
-   sigset_t    osigmask; \
-   bool        sigpipe_pending; \
-   bool        got_epipe = false; \
-\
-   if (pq_block_sigpipe(&osigmask, &sigpipe_pending) < 0) \
-       failaction
+struct sigpipe_info
+{
+   sigset_t    oldsigmask;
+   bool        sigpipe_pending;
+   bool        got_epipe;
+};
 
-#define REMEMBER_EPIPE(cond) \
+#define DECLARE_SIGPIPE_INFO(spinfo) struct sigpipe_info spinfo
+
+#define DISABLE_SIGPIPE(conn, spinfo, failaction) \
+   do { \
+       (spinfo).got_epipe = false; \
+       if (!SIGPIPE_MASKED(conn)) \
+       { \
+           if (pq_block_sigpipe(&(spinfo).oldsigmask, \
+                                &(spinfo).sigpipe_pending) < 0) \
+               failaction; \
+       } \
+   } while (0)
+
+#define REMEMBER_EPIPE(spinfo, cond) \
    do { \
        if (cond) \
-           got_epipe = true; \
+           (spinfo).got_epipe = true; \
    } while (0)
 
-#define RESTORE_SIGPIPE() \
-   pq_reset_sigpipe(&osigmask, sigpipe_pending, got_epipe)
-#else                          /* !ENABLE_THREAD_SAFETY */
+#define RESTORE_SIGPIPE(conn, spinfo) \
+   do { \
+       if (!SIGPIPE_MASKED(conn)) \
+           pq_reset_sigpipe(&(spinfo).oldsigmask, (spinfo).sigpipe_pending, \
+                            (spinfo).got_epipe); \
+   } while (0)
 
-#define DISABLE_SIGPIPE(failaction) \
-   pqsigfunc   oldsighandler = pqsignal(SIGPIPE, SIG_IGN)
+#else /* !ENABLE_THREAD_SAFETY */
 
-#define REMEMBER_EPIPE(cond)
+#define DECLARE_SIGPIPE_INFO(spinfo) pqsigfunc spinfo = NULL
 
-#define RESTORE_SIGPIPE() \
-   pqsignal(SIGPIPE, oldsighandler)
-#endif   /* ENABLE_THREAD_SAFETY */
-#else                          /* WIN32 */
+#define DISABLE_SIGPIPE(conn, spinfo, failaction) \
+   do { \
+       if (!SIGPIPE_MASKED(conn)) \
+           spinfo = pqsignal(SIGPIPE, SIG_IGN); \
+   } while (0)
+
+#define REMEMBER_EPIPE(spinfo, cond)
+
+#define RESTORE_SIGPIPE(conn, spinfo) \
+   do { \
+       if (!SIGPIPE_MASKED(conn)) \
+           pqsignal(SIGPIPE, spinfo); \
+   } while (0)
+
+#endif /* ENABLE_THREAD_SAFETY */
 
-#define DISABLE_SIGPIPE(failaction)
-#define REMEMBER_EPIPE(cond)
-#define RESTORE_SIGPIPE()
-#endif   /* WIN32 */
+#else  /* WIN32 */
+
+#define DECLARE_SIGPIPE_INFO(spinfo)
+#define DISABLE_SIGPIPE(conn, spinfo, failaction)
+#define REMEMBER_EPIPE(spinfo, cond)
+#define RESTORE_SIGPIPE(conn, spinfo)
+
+#endif /* WIN32 */
 
 /* ------------------------------------------------------------ */
 /*          Procedures common to all secure sessions           */
@@ -231,6 +263,9 @@ pqsecure_open_client(PGconn *conn)
    /* First time through? */
    if (conn->ssl == NULL)
    {
+       /* We cannot use MSG_NOSIGNAL to block SIGPIPE when using SSL */
+       conn->sigpipe_flag = false;
+
        if (!(conn->ssl = SSL_new(SSL_context)) ||
            !SSL_set_app_data(conn->ssl, conn) ||
            !SSL_set_fd(conn->ssl, conn->sock))
@@ -283,9 +318,10 @@ pqsecure_read(PGconn *conn, void *ptr, size_t len)
    if (conn->ssl)
    {
        int         err;
+       DECLARE_SIGPIPE_INFO(spinfo);
 
        /* SSL_read can write to the socket, so we need to disable SIGPIPE */
-       DISABLE_SIGPIPE(return -1);
+       DISABLE_SIGPIPE(conn, spinfo, return -1);
 
 rloop:
        n = SSL_read(conn->ssl, ptr, len);
@@ -312,7 +348,7 @@ rloop:
 
                    if (n == -1)
                    {
-                       REMEMBER_EPIPE(SOCK_ERRNO == EPIPE);
+                       REMEMBER_EPIPE(spinfo, SOCK_ERRNO == EPIPE);
                        printfPQExpBuffer(&conn->errorMessage,
                                    libpq_gettext("SSL SYSCALL error: %s\n"),
                            SOCK_STRERROR(SOCK_ERRNO, sebuf, sizeof(sebuf)));
@@ -348,7 +384,7 @@ rloop:
                break;
        }
 
-       RESTORE_SIGPIPE();
+       RESTORE_SIGPIPE(conn, spinfo);
    }
    else
 #endif
@@ -364,14 +400,15 @@ ssize_t
 pqsecure_write(PGconn *conn, const void *ptr, size_t len)
 {
    ssize_t     n;
-
-   DISABLE_SIGPIPE(return -1);
+   DECLARE_SIGPIPE_INFO(spinfo);
 
 #ifdef USE_SSL
    if (conn->ssl)
    {
        int         err;
 
+       DISABLE_SIGPIPE(conn, spinfo, return -1);
+
        n = SSL_write(conn->ssl, ptr, len);
        err = SSL_get_error(conn->ssl, n);
        switch (err)
@@ -396,7 +433,7 @@ pqsecure_write(PGconn *conn, const void *ptr, size_t len)
 
                    if (n == -1)
                    {
-                       REMEMBER_EPIPE(SOCK_ERRNO == EPIPE);
+                       REMEMBER_EPIPE(spinfo, SOCK_ERRNO == EPIPE);
                        printfPQExpBuffer(&conn->errorMessage,
                                    libpq_gettext("SSL SYSCALL error: %s\n"),
                            SOCK_STRERROR(SOCK_ERRNO, sebuf, sizeof(sebuf)));
@@ -434,11 +471,41 @@ pqsecure_write(PGconn *conn, const void *ptr, size_t len)
    else
 #endif
    {
-       n = send(conn->sock, ptr, len, 0);
-       REMEMBER_EPIPE(n < 0 && SOCK_ERRNO == EPIPE);
+       int     flags = 0;
+
+#ifdef MSG_NOSIGNAL
+       if (conn->sigpipe_flag)
+           flags |= MSG_NOSIGNAL;
+
+retry_masked:
+
+#endif /* MSG_NOSIGNAL */
+
+       DISABLE_SIGPIPE(conn, spinfo, return -1);
+
+       n = send(conn->sock, ptr, len, flags);
+
+       if (n < 0)
+       {
+           /*
+            * If we see an EINVAL, it may be because MSG_NOSIGNAL isn't
+            * available on this machine.  So, clear sigpipe_flag so we don't
+            * try the flag again, and retry the send().
+            */
+#ifdef MSG_NOSIGNAL
+           if (flags != 0 && SOCK_ERRNO == EINVAL)
+           {
+               conn->sigpipe_flag = false;
+               flags = 0;
+               goto retry_masked;
+           }
+#endif /* MSG_NOSIGNAL */
+
+           REMEMBER_EPIPE(spinfo, SOCK_ERRNO == EPIPE);
+       }
    }
 
-   RESTORE_SIGPIPE();
+   RESTORE_SIGPIPE(conn, spinfo);
 
    return n;
 }
@@ -1220,14 +1287,16 @@ close_SSL(PGconn *conn)
 {
    if (conn->ssl)
    {
-       DISABLE_SIGPIPE((void) 0);
+       DECLARE_SIGPIPE_INFO(spinfo);
+
+       DISABLE_SIGPIPE(conn, spinfo, (void) 0);
        SSL_shutdown(conn->ssl);
        SSL_free(conn->ssl);
        conn->ssl = NULL;
        pqsecure_destroy();
        /* We have to assume we got EPIPE */
-       REMEMBER_EPIPE(true);
-       RESTORE_SIGPIPE();
+       REMEMBER_EPIPE(spinfo, true);
+       RESTORE_SIGPIPE(conn, spinfo);
    }
 
    if (conn->peer)
index 51b7128b862c292bddf09c69a559ef28b13efeb8..d93ba4d25e230657863770f23f0f7351827e0293 100644 (file)
@@ -12,7 +12,7 @@
  * Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/interfaces/libpq/libpq-int.h,v 1.143 2009/06/23 18:13:23 mha Exp $
+ * $PostgreSQL: pgsql/src/interfaces/libpq/libpq-int.h,v 1.144 2009/07/24 17:58:31 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -341,6 +341,8 @@ struct pg_conn
    ProtocolVersion pversion;   /* FE/BE protocol version in use */
    int         sversion;       /* server version, e.g. 70401 for 7.4.1 */
    bool        password_needed;    /* true if server demanded a password */
+   bool        sigpipe_so;     /* have we masked SIGPIPE via SO_NOSIGPIPE? */
+   bool        sigpipe_flag;   /* can we mask SIGPIPE via MSG_NOSIGNAL? */
 
    /* Transient state needed while establishing connection */
    struct addrinfo *addrlist;  /* list of possible backend addresses */