Avoid extra system calls to block SIGPIPE if the platform provides either
authorTom Lane <tgl@sss.pgh.pa.us>
Fri, 24 Jul 2009 17:58:31 +0000 (17:58 +0000)
committerTom Lane <tgl@sss.pgh.pa.us>
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 70dfd90d183c8181bb618c6e39b75ab1c9779443..38b382358682f53f0205558e5af69ccc29488d76 100644 (file)
@@ -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 985072aaf472b5c8c3687d43b84950f58998ee7e..883d62ab449caed09147fc630cb6c37389c539b3 100644 (file)
@@ -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 ff9e6b87ed382ace555eb96a614eb813736fa3f7..724e92282c496499e77a2fc22acc231d5866d2c6 100644 (file)
@@ -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 */