libpq: Move cancellation related functions to fe-cancel.c
authorAlvaro Herrera <alvherre@alvh.no-ip.org>
Mon, 29 Jan 2024 09:53:34 +0000 (10:53 +0100)
committerAlvaro Herrera <alvherre@alvh.no-ip.org>
Mon, 29 Jan 2024 11:39:59 +0000 (12:39 +0100)
In follow up commits we'll add more functions related to query
cancellations.  This groups those all together instead of mixing them
with the other functions in fe-connect.c.

The formerly static parse_int_param() function had to be exported to
other libpq users, so it's been renamed pqParseIntParam() and moved to a
more reasonable place within fe-connect.c (rather than randomly between
various keepalive-related routines).

Author: Jelte Fennema-Nio <jelte.fennema@microsoft.com>
Discussion: https://postgr.es/m/AM5PR83MB0178D3B31CA1B6EC4A8ECC42F7529@AM5PR83MB0178.EURPRD83.prod.outlook.com

src/interfaces/libpq/Makefile
src/interfaces/libpq/fe-cancel.c [new file with mode: 0644]
src/interfaces/libpq/fe-connect.c
src/interfaces/libpq/libpq-int.h
src/interfaces/libpq/meson.build

index fce17bc72a06145cea7a6c9e3b47dec9dd888b98..bfcc7cdde99315e70d5c22647c2818ea5ec6033d 100644 (file)
@@ -30,6 +30,7 @@ endif
 OBJS = \
        $(WIN32RES) \
        fe-auth-scram.o \
+       fe-cancel.o \
        fe-connect.o \
        fe-exec.o \
        fe-lobj.o \
diff --git a/src/interfaces/libpq/fe-cancel.c b/src/interfaces/libpq/fe-cancel.c
new file mode 100644 (file)
index 0000000..51f8d8a
--- /dev/null
@@ -0,0 +1,387 @@
+/*-------------------------------------------------------------------------
+ *
+ * fe-cancel.c
+ *       functions related to setting up a connection to the backend
+ *
+ * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ *       src/interfaces/libpq/fe-cancel.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres_fe.h"
+
+#include <unistd.h>
+
+#include "libpq-fe.h"
+#include "libpq-int.h"
+#include "port/pg_bswap.h"
+
+/*
+ * PQgetCancel: get a PGcancel structure corresponding to a connection.
+ *
+ * A copy is needed to be able to cancel a running query from a different
+ * thread. If the same structure is used all structure members would have
+ * to be individually locked (if the entire structure was locked, it would
+ * be impossible to cancel a synchronous query because the structure would
+ * have to stay locked for the duration of the query).
+ */
+PGcancel *
+PQgetCancel(PGconn *conn)
+{
+       PGcancel   *cancel;
+
+       if (!conn)
+               return NULL;
+
+       if (conn->sock == PGINVALID_SOCKET)
+               return NULL;
+
+       cancel = malloc(sizeof(PGcancel));
+       if (cancel == NULL)
+               return NULL;
+
+       memcpy(&cancel->raddr, &conn->raddr, sizeof(SockAddr));
+       cancel->be_pid = conn->be_pid;
+       cancel->be_key = conn->be_key;
+       /* We use -1 to indicate an unset connection option */
+       cancel->pgtcp_user_timeout = -1;
+       cancel->keepalives = -1;
+       cancel->keepalives_idle = -1;
+       cancel->keepalives_interval = -1;
+       cancel->keepalives_count = -1;
+       if (conn->pgtcp_user_timeout != NULL)
+       {
+               if (!pqParseIntParam(conn->pgtcp_user_timeout,
+                                                        &cancel->pgtcp_user_timeout,
+                                                        conn, "tcp_user_timeout"))
+                       goto fail;
+       }
+       if (conn->keepalives != NULL)
+       {
+               if (!pqParseIntParam(conn->keepalives,
+                                                        &cancel->keepalives,
+                                                        conn, "keepalives"))
+                       goto fail;
+       }
+       if (conn->keepalives_idle != NULL)
+       {
+               if (!pqParseIntParam(conn->keepalives_idle,
+                                                        &cancel->keepalives_idle,
+                                                        conn, "keepalives_idle"))
+                       goto fail;
+       }
+       if (conn->keepalives_interval != NULL)
+       {
+               if (!pqParseIntParam(conn->keepalives_interval,
+                                                        &cancel->keepalives_interval,
+                                                        conn, "keepalives_interval"))
+                       goto fail;
+       }
+       if (conn->keepalives_count != NULL)
+       {
+               if (!pqParseIntParam(conn->keepalives_count,
+                                                        &cancel->keepalives_count,
+                                                        conn, "keepalives_count"))
+                       goto fail;
+       }
+
+       return cancel;
+
+fail:
+       free(cancel);
+       return NULL;
+}
+
+/* PQfreeCancel: free a cancel structure */
+void
+PQfreeCancel(PGcancel *cancel)
+{
+       free(cancel);
+}
+
+
+/*
+ * Sets an integer socket option on a TCP socket, if the provided value is
+ * not negative.  Returns false if setsockopt fails for some reason.
+ *
+ * CAUTION: This needs to be signal safe, since it's used by PQcancel.
+ */
+#if defined(TCP_USER_TIMEOUT) || !defined(WIN32)
+static bool
+optional_setsockopt(int fd, int protoid, int optid, int value)
+{
+       if (value < 0)
+               return true;
+       if (setsockopt(fd, protoid, optid, (char *) &value, sizeof(value)) < 0)
+               return false;
+       return true;
+}
+#endif
+
+
+/*
+ * PQcancel: request query cancel
+ *
+ * The return value is true if the cancel request was successfully
+ * dispatched, false if not (in which case an error message is available).
+ * Note: successful dispatch is no guarantee that there will be any effect at
+ * the backend.  The application must read the operation result as usual.
+ *
+ * On failure, an error message is stored in *errbuf, which must be of size
+ * errbufsize (recommended size is 256 bytes).  *errbuf is not changed on
+ * success return.
+ *
+ * CAUTION: we want this routine to be safely callable from a signal handler
+ * (for example, an application might want to call it in a SIGINT handler).
+ * This means we cannot use any C library routine that might be non-reentrant.
+ * malloc/free are often non-reentrant, and anything that might call them is
+ * just as dangerous.  We avoid sprintf here for that reason.  Building up
+ * error messages with strcpy/strcat is tedious but should be quite safe.
+ * We also save/restore errno in case the signal handler support doesn't.
+ */
+int
+PQcancel(PGcancel *cancel, char *errbuf, int errbufsize)
+{
+       int                     save_errno = SOCK_ERRNO;
+       pgsocket        tmpsock = PGINVALID_SOCKET;
+       int                     maxlen;
+       struct
+       {
+               uint32          packetlen;
+               CancelRequestPacket cp;
+       }                       crp;
+
+       if (!cancel)
+       {
+               strlcpy(errbuf, "PQcancel() -- no cancel object supplied", errbufsize);
+               /* strlcpy probably doesn't change errno, but be paranoid */
+               SOCK_ERRNO_SET(save_errno);
+               return false;
+       }
+
+       /*
+        * We need to open a temporary connection to the postmaster. Do this with
+        * only kernel calls.
+        */
+       if ((tmpsock = socket(cancel->raddr.addr.ss_family, SOCK_STREAM, 0)) == PGINVALID_SOCKET)
+       {
+               strlcpy(errbuf, "PQcancel() -- socket() failed: ", errbufsize);
+               goto cancel_errReturn;
+       }
+
+       /*
+        * Since this connection will only be used to send a single packet of
+        * data, we don't need NODELAY.  We also don't set the socket to
+        * nonblocking mode, because the API definition of PQcancel requires the
+        * cancel to be sent in a blocking way.
+        *
+        * We do set socket options related to keepalives and other TCP timeouts.
+        * This ensures that this function does not block indefinitely when
+        * reasonable keepalive and timeout settings have been provided.
+        */
+       if (cancel->raddr.addr.ss_family != AF_UNIX &&
+               cancel->keepalives != 0)
+       {
+#ifndef WIN32
+               if (!optional_setsockopt(tmpsock, SOL_SOCKET, SO_KEEPALIVE, 1))
+               {
+                       strlcpy(errbuf, "PQcancel() -- setsockopt(SO_KEEPALIVE) failed: ", errbufsize);
+                       goto cancel_errReturn;
+               }
+
+#ifdef PG_TCP_KEEPALIVE_IDLE
+               if (!optional_setsockopt(tmpsock, IPPROTO_TCP, PG_TCP_KEEPALIVE_IDLE,
+                                                                cancel->keepalives_idle))
+               {
+                       strlcpy(errbuf, "PQcancel() -- setsockopt(" PG_TCP_KEEPALIVE_IDLE_STR ") failed: ", errbufsize);
+                       goto cancel_errReturn;
+               }
+#endif
+
+#ifdef TCP_KEEPINTVL
+               if (!optional_setsockopt(tmpsock, IPPROTO_TCP, TCP_KEEPINTVL,
+                                                                cancel->keepalives_interval))
+               {
+                       strlcpy(errbuf, "PQcancel() -- setsockopt(TCP_KEEPINTVL) failed: ", errbufsize);
+                       goto cancel_errReturn;
+               }
+#endif
+
+#ifdef TCP_KEEPCNT
+               if (!optional_setsockopt(tmpsock, IPPROTO_TCP, TCP_KEEPCNT,
+                                                                cancel->keepalives_count))
+               {
+                       strlcpy(errbuf, "PQcancel() -- setsockopt(TCP_KEEPCNT) failed: ", errbufsize);
+                       goto cancel_errReturn;
+               }
+#endif
+
+#else                                                  /* WIN32 */
+
+#ifdef SIO_KEEPALIVE_VALS
+               if (!pqSetKeepalivesWin32(tmpsock,
+                                                                 cancel->keepalives_idle,
+                                                                 cancel->keepalives_interval))
+               {
+                       strlcpy(errbuf, "PQcancel() -- WSAIoctl(SIO_KEEPALIVE_VALS) failed: ", errbufsize);
+                       goto cancel_errReturn;
+               }
+#endif                                                 /* SIO_KEEPALIVE_VALS */
+#endif                                                 /* WIN32 */
+
+               /* TCP_USER_TIMEOUT works the same way on Unix and Windows */
+#ifdef TCP_USER_TIMEOUT
+               if (!optional_setsockopt(tmpsock, IPPROTO_TCP, TCP_USER_TIMEOUT,
+                                                                cancel->pgtcp_user_timeout))
+               {
+                       strlcpy(errbuf, "PQcancel() -- setsockopt(TCP_USER_TIMEOUT) failed: ", errbufsize);
+                       goto cancel_errReturn;
+               }
+#endif
+       }
+
+retry3:
+       if (connect(tmpsock, (struct sockaddr *) &cancel->raddr.addr,
+                               cancel->raddr.salen) < 0)
+       {
+               if (SOCK_ERRNO == EINTR)
+                       /* Interrupted system call - we'll just try again */
+                       goto retry3;
+               strlcpy(errbuf, "PQcancel() -- connect() failed: ", errbufsize);
+               goto cancel_errReturn;
+       }
+
+       /* Create and send the cancel request packet. */
+
+       crp.packetlen = pg_hton32((uint32) sizeof(crp));
+       crp.cp.cancelRequestCode = (MsgType) pg_hton32(CANCEL_REQUEST_CODE);
+       crp.cp.backendPID = pg_hton32(cancel->be_pid);
+       crp.cp.cancelAuthCode = pg_hton32(cancel->be_key);
+
+retry4:
+       if (send(tmpsock, (char *) &crp, sizeof(crp), 0) != (int) sizeof(crp))
+       {
+               if (SOCK_ERRNO == EINTR)
+                       /* Interrupted system call - we'll just try again */
+                       goto retry4;
+               strlcpy(errbuf, "PQcancel() -- send() failed: ", errbufsize);
+               goto cancel_errReturn;
+       }
+
+       /*
+        * Wait for the postmaster to close the connection, which indicates that
+        * it's processed the request.  Without this delay, we might issue another
+        * command only to find that our cancel zaps that command instead of the
+        * one we thought we were canceling.  Note we don't actually expect this
+        * read to obtain any data, we are just waiting for EOF to be signaled.
+        */
+retry5:
+       if (recv(tmpsock, (char *) &crp, 1, 0) < 0)
+       {
+               if (SOCK_ERRNO == EINTR)
+                       /* Interrupted system call - we'll just try again */
+                       goto retry5;
+               /* we ignore other error conditions */
+       }
+
+       /* All done */
+       closesocket(tmpsock);
+       SOCK_ERRNO_SET(save_errno);
+       return true;
+
+cancel_errReturn:
+
+       /*
+        * Make sure we don't overflow the error buffer. Leave space for the \n at
+        * the end, and for the terminating zero.
+        */
+       maxlen = errbufsize - strlen(errbuf) - 2;
+       if (maxlen >= 0)
+       {
+               /*
+                * We can't invoke strerror here, since it's not signal-safe.  Settle
+                * for printing the decimal value of errno.  Even that has to be done
+                * the hard way.
+                */
+               int                     val = SOCK_ERRNO;
+               char            buf[32];
+               char       *bufp;
+
+               bufp = buf + sizeof(buf) - 1;
+               *bufp = '\0';
+               do
+               {
+                       *(--bufp) = (val % 10) + '0';
+                       val /= 10;
+               } while (val > 0);
+               bufp -= 6;
+               memcpy(bufp, "error ", 6);
+               strncat(errbuf, bufp, maxlen);
+               strcat(errbuf, "\n");
+       }
+       if (tmpsock != PGINVALID_SOCKET)
+               closesocket(tmpsock);
+       SOCK_ERRNO_SET(save_errno);
+       return false;
+}
+
+/*
+ * PQrequestCancel: old, not thread-safe function for requesting query cancel
+ *
+ * Returns true if able to send the cancel request, false if not.
+ *
+ * On failure, the error message is saved in conn->errorMessage; this means
+ * that this can't be used when there might be other active operations on
+ * the connection object.
+ *
+ * NOTE: error messages will be cut off at the current size of the
+ * error message buffer, since we dare not try to expand conn->errorMessage!
+ */
+int
+PQrequestCancel(PGconn *conn)
+{
+       int                     r;
+       PGcancel   *cancel;
+
+       /* Check we have an open connection */
+       if (!conn)
+               return false;
+
+       if (conn->sock == PGINVALID_SOCKET)
+       {
+               strlcpy(conn->errorMessage.data,
+                               "PQrequestCancel() -- connection is not open\n",
+                               conn->errorMessage.maxlen);
+               conn->errorMessage.len = strlen(conn->errorMessage.data);
+               conn->errorReported = 0;
+
+               return false;
+       }
+
+       cancel = PQgetCancel(conn);
+       if (cancel)
+       {
+               r = PQcancel(cancel, conn->errorMessage.data,
+                                        conn->errorMessage.maxlen);
+               PQfreeCancel(cancel);
+       }
+       else
+       {
+               strlcpy(conn->errorMessage.data, "out of memory",
+                               conn->errorMessage.maxlen);
+               r = false;
+       }
+
+       if (!r)
+       {
+               conn->errorMessage.len = strlen(conn->errorMessage.data);
+               conn->errorReported = 0;
+       }
+
+       return r;
+}
index 79e0b73d6183b346e8c9a7b9c2cf2d11c4f52182..c0dea144a00e192733c5168e0b00d5ceefd8792a 100644 (file)
@@ -443,8 +443,6 @@ static void pgpassfileWarning(PGconn *conn);
 static void default_threadlock(int acquire);
 static bool sslVerifyProtocolVersion(const char *version);
 static bool sslVerifyProtocolRange(const char *min, const char *max);
-static bool parse_int_param(const char *value, int *result, PGconn *conn,
-                                                       const char *context);
 
 
 /* global variable because fe-auth.c needs to access it */
@@ -2076,52 +2074,6 @@ useKeepalives(PGconn *conn)
        return val != 0 ? 1 : 0;
 }
 
-/*
- * Parse and try to interpret "value" as an integer value, and if successful,
- * store it in *result, complaining if there is any trailing garbage or an
- * overflow.  This allows any number of leading and trailing whitespaces.
- */
-static bool
-parse_int_param(const char *value, int *result, PGconn *conn,
-                               const char *context)
-{
-       char       *end;
-       long            numval;
-
-       Assert(value != NULL);
-
-       *result = 0;
-
-       /* strtol(3) skips leading whitespaces */
-       errno = 0;
-       numval = strtol(value, &end, 10);
-
-       /*
-        * If no progress was done during the parsing or an error happened, fail.
-        * This tests properly for overflows of the result.
-        */
-       if (value == end || errno != 0 || numval != (int) numval)
-               goto error;
-
-       /*
-        * Skip any trailing whitespace; if anything but whitespace remains before
-        * the terminating character, fail
-        */
-       while (*end != '\0' && isspace((unsigned char) *end))
-               end++;
-
-       if (*end != '\0')
-               goto error;
-
-       *result = numval;
-       return true;
-
-error:
-       libpq_append_conn_error(conn, "invalid integer value \"%s\" for connection option \"%s\"",
-                                                       value, context);
-       return false;
-}
-
 #ifndef WIN32
 /*
  * Set the keepalive idle timer.
@@ -2134,7 +2086,7 @@ setKeepalivesIdle(PGconn *conn)
        if (conn->keepalives_idle == NULL)
                return 1;
 
-       if (!parse_int_param(conn->keepalives_idle, &idle, conn,
+       if (!pqParseIntParam(conn->keepalives_idle, &idle, conn,
                                                 "keepalives_idle"))
                return 0;
        if (idle < 0)
@@ -2168,7 +2120,7 @@ setKeepalivesInterval(PGconn *conn)
        if (conn->keepalives_interval == NULL)
                return 1;
 
-       if (!parse_int_param(conn->keepalives_interval, &interval, conn,
+       if (!pqParseIntParam(conn->keepalives_interval, &interval, conn,
                                                 "keepalives_interval"))
                return 0;
        if (interval < 0)
@@ -2203,7 +2155,7 @@ setKeepalivesCount(PGconn *conn)
        if (conn->keepalives_count == NULL)
                return 1;
 
-       if (!parse_int_param(conn->keepalives_count, &count, conn,
+       if (!pqParseIntParam(conn->keepalives_count, &count, conn,
                                                 "keepalives_count"))
                return 0;
        if (count < 0)
@@ -2233,8 +2185,8 @@ setKeepalivesCount(PGconn *conn)
  *
  * CAUTION: This needs to be signal safe, since it's used by PQcancel.
  */
-static int
-setKeepalivesWin32(pgsocket sock, int idle, int interval)
+int
+pqSetKeepalivesWin32(pgsocket sock, int idle, int interval)
 {
        struct tcp_keepalive ka;
        DWORD           retsize;
@@ -2269,15 +2221,15 @@ prepKeepalivesWin32(PGconn *conn)
        int                     interval = -1;
 
        if (conn->keepalives_idle &&
-               !parse_int_param(conn->keepalives_idle, &idle, conn,
+               !pqParseIntParam(conn->keepalives_idle, &idle, conn,
                                                 "keepalives_idle"))
                return 0;
        if (conn->keepalives_interval &&
-               !parse_int_param(conn->keepalives_interval, &interval, conn,
+               !pqParseIntParam(conn->keepalives_interval, &interval, conn,
                                                 "keepalives_interval"))
                return 0;
 
-       if (!setKeepalivesWin32(conn->sock, idle, interval))
+       if (!pqSetKeepalivesWin32(conn->sock, idle, interval))
        {
                libpq_append_conn_error(conn, "%s(%s) failed: error code %d",
                                                                "WSAIoctl", "SIO_KEEPALIVE_VALS",
@@ -2300,7 +2252,7 @@ setTCPUserTimeout(PGconn *conn)
        if (conn->pgtcp_user_timeout == NULL)
                return 1;
 
-       if (!parse_int_param(conn->pgtcp_user_timeout, &timeout, conn,
+       if (!pqParseIntParam(conn->pgtcp_user_timeout, &timeout, conn,
                                                 "tcp_user_timeout"))
                return 0;
 
@@ -2418,7 +2370,7 @@ connectDBComplete(PGconn *conn)
         */
        if (conn->connect_timeout != NULL)
        {
-               if (!parse_int_param(conn->connect_timeout, &timeout, conn,
+               if (!pqParseIntParam(conn->connect_timeout, &timeout, conn,
                                                         "connect_timeout"))
                {
                        /* mark the connection as bad to report the parsing failure */
@@ -2666,7 +2618,7 @@ keep_going:                                               /* We will come back to here until there is
                        thisport = DEF_PGPORT;
                else
                {
-                       if (!parse_int_param(ch->port, &thisport, conn, "port"))
+                       if (!pqParseIntParam(ch->port, &thisport, conn, "port"))
                                goto error_return;
 
                        if (thisport < 1 || thisport > 65535)
@@ -4694,373 +4646,6 @@ PQresetPoll(PGconn *conn)
        return PGRES_POLLING_FAILED;
 }
 
-/*
- * PQgetCancel: get a PGcancel structure corresponding to a connection.
- *
- * A copy is needed to be able to cancel a running query from a different
- * thread. If the same structure is used all structure members would have
- * to be individually locked (if the entire structure was locked, it would
- * be impossible to cancel a synchronous query because the structure would
- * have to stay locked for the duration of the query).
- */
-PGcancel *
-PQgetCancel(PGconn *conn)
-{
-       PGcancel   *cancel;
-
-       if (!conn)
-               return NULL;
-
-       if (conn->sock == PGINVALID_SOCKET)
-               return NULL;
-
-       cancel = malloc(sizeof(PGcancel));
-       if (cancel == NULL)
-               return NULL;
-
-       memcpy(&cancel->raddr, &conn->raddr, sizeof(SockAddr));
-       cancel->be_pid = conn->be_pid;
-       cancel->be_key = conn->be_key;
-       /* We use -1 to indicate an unset connection option */
-       cancel->pgtcp_user_timeout = -1;
-       cancel->keepalives = -1;
-       cancel->keepalives_idle = -1;
-       cancel->keepalives_interval = -1;
-       cancel->keepalives_count = -1;
-       if (conn->pgtcp_user_timeout != NULL)
-       {
-               if (!parse_int_param(conn->pgtcp_user_timeout,
-                                                        &cancel->pgtcp_user_timeout,
-                                                        conn, "tcp_user_timeout"))
-                       goto fail;
-       }
-       if (conn->keepalives != NULL)
-       {
-               if (!parse_int_param(conn->keepalives,
-                                                        &cancel->keepalives,
-                                                        conn, "keepalives"))
-                       goto fail;
-       }
-       if (conn->keepalives_idle != NULL)
-       {
-               if (!parse_int_param(conn->keepalives_idle,
-                                                        &cancel->keepalives_idle,
-                                                        conn, "keepalives_idle"))
-                       goto fail;
-       }
-       if (conn->keepalives_interval != NULL)
-       {
-               if (!parse_int_param(conn->keepalives_interval,
-                                                        &cancel->keepalives_interval,
-                                                        conn, "keepalives_interval"))
-                       goto fail;
-       }
-       if (conn->keepalives_count != NULL)
-       {
-               if (!parse_int_param(conn->keepalives_count,
-                                                        &cancel->keepalives_count,
-                                                        conn, "keepalives_count"))
-                       goto fail;
-       }
-
-       return cancel;
-
-fail:
-       free(cancel);
-       return NULL;
-}
-
-/* PQfreeCancel: free a cancel structure */
-void
-PQfreeCancel(PGcancel *cancel)
-{
-       free(cancel);
-}
-
-
-/*
- * Sets an integer socket option on a TCP socket, if the provided value is
- * not negative.  Returns false if setsockopt fails for some reason.
- *
- * CAUTION: This needs to be signal safe, since it's used by PQcancel.
- */
-#if defined(TCP_USER_TIMEOUT) || !defined(WIN32)
-static bool
-optional_setsockopt(int fd, int protoid, int optid, int value)
-{
-       if (value < 0)
-               return true;
-       if (setsockopt(fd, protoid, optid, (char *) &value, sizeof(value)) < 0)
-               return false;
-       return true;
-}
-#endif
-
-
-/*
- * PQcancel: request query cancel
- *
- * The return value is true if the cancel request was successfully
- * dispatched, false if not (in which case an error message is available).
- * Note: successful dispatch is no guarantee that there will be any effect at
- * the backend.  The application must read the operation result as usual.
- *
- * On failure, an error message is stored in *errbuf, which must be of size
- * errbufsize (recommended size is 256 bytes).  *errbuf is not changed on
- * success return.
- *
- * CAUTION: we want this routine to be safely callable from a signal handler
- * (for example, an application might want to call it in a SIGINT handler).
- * This means we cannot use any C library routine that might be non-reentrant.
- * malloc/free are often non-reentrant, and anything that might call them is
- * just as dangerous.  We avoid sprintf here for that reason.  Building up
- * error messages with strcpy/strcat is tedious but should be quite safe.
- * We also save/restore errno in case the signal handler support doesn't.
- */
-int
-PQcancel(PGcancel *cancel, char *errbuf, int errbufsize)
-{
-       int                     save_errno = SOCK_ERRNO;
-       pgsocket        tmpsock = PGINVALID_SOCKET;
-       int                     maxlen;
-       struct
-       {
-               uint32          packetlen;
-               CancelRequestPacket cp;
-       }                       crp;
-
-       if (!cancel)
-       {
-               strlcpy(errbuf, "PQcancel() -- no cancel object supplied", errbufsize);
-               /* strlcpy probably doesn't change errno, but be paranoid */
-               SOCK_ERRNO_SET(save_errno);
-               return false;
-       }
-
-       /*
-        * We need to open a temporary connection to the postmaster. Do this with
-        * only kernel calls.
-        */
-       if ((tmpsock = socket(cancel->raddr.addr.ss_family, SOCK_STREAM, 0)) == PGINVALID_SOCKET)
-       {
-               strlcpy(errbuf, "PQcancel() -- socket() failed: ", errbufsize);
-               goto cancel_errReturn;
-       }
-
-       /*
-        * Since this connection will only be used to send a single packet of
-        * data, we don't need NODELAY.  We also don't set the socket to
-        * nonblocking mode, because the API definition of PQcancel requires the
-        * cancel to be sent in a blocking way.
-        *
-        * We do set socket options related to keepalives and other TCP timeouts.
-        * This ensures that this function does not block indefinitely when
-        * reasonable keepalive and timeout settings have been provided.
-        */
-       if (cancel->raddr.addr.ss_family != AF_UNIX &&
-               cancel->keepalives != 0)
-       {
-#ifndef WIN32
-               if (!optional_setsockopt(tmpsock, SOL_SOCKET, SO_KEEPALIVE, 1))
-               {
-                       strlcpy(errbuf, "PQcancel() -- setsockopt(SO_KEEPALIVE) failed: ", errbufsize);
-                       goto cancel_errReturn;
-               }
-
-#ifdef PG_TCP_KEEPALIVE_IDLE
-               if (!optional_setsockopt(tmpsock, IPPROTO_TCP, PG_TCP_KEEPALIVE_IDLE,
-                                                                cancel->keepalives_idle))
-               {
-                       strlcpy(errbuf, "PQcancel() -- setsockopt(" PG_TCP_KEEPALIVE_IDLE_STR ") failed: ", errbufsize);
-                       goto cancel_errReturn;
-               }
-#endif
-
-#ifdef TCP_KEEPINTVL
-               if (!optional_setsockopt(tmpsock, IPPROTO_TCP, TCP_KEEPINTVL,
-                                                                cancel->keepalives_interval))
-               {
-                       strlcpy(errbuf, "PQcancel() -- setsockopt(TCP_KEEPINTVL) failed: ", errbufsize);
-                       goto cancel_errReturn;
-               }
-#endif
-
-#ifdef TCP_KEEPCNT
-               if (!optional_setsockopt(tmpsock, IPPROTO_TCP, TCP_KEEPCNT,
-                                                                cancel->keepalives_count))
-               {
-                       strlcpy(errbuf, "PQcancel() -- setsockopt(TCP_KEEPCNT) failed: ", errbufsize);
-                       goto cancel_errReturn;
-               }
-#endif
-
-#else                                                  /* WIN32 */
-
-#ifdef SIO_KEEPALIVE_VALS
-               if (!setKeepalivesWin32(tmpsock,
-                                                               cancel->keepalives_idle,
-                                                               cancel->keepalives_interval))
-               {
-                       strlcpy(errbuf, "PQcancel() -- WSAIoctl(SIO_KEEPALIVE_VALS) failed: ", errbufsize);
-                       goto cancel_errReturn;
-               }
-#endif                                                 /* SIO_KEEPALIVE_VALS */
-#endif                                                 /* WIN32 */
-
-               /* TCP_USER_TIMEOUT works the same way on Unix and Windows */
-#ifdef TCP_USER_TIMEOUT
-               if (!optional_setsockopt(tmpsock, IPPROTO_TCP, TCP_USER_TIMEOUT,
-                                                                cancel->pgtcp_user_timeout))
-               {
-                       strlcpy(errbuf, "PQcancel() -- setsockopt(TCP_USER_TIMEOUT) failed: ", errbufsize);
-                       goto cancel_errReturn;
-               }
-#endif
-       }
-
-retry3:
-       if (connect(tmpsock, (struct sockaddr *) &cancel->raddr.addr,
-                               cancel->raddr.salen) < 0)
-       {
-               if (SOCK_ERRNO == EINTR)
-                       /* Interrupted system call - we'll just try again */
-                       goto retry3;
-               strlcpy(errbuf, "PQcancel() -- connect() failed: ", errbufsize);
-               goto cancel_errReturn;
-       }
-
-       /* Create and send the cancel request packet. */
-
-       crp.packetlen = pg_hton32((uint32) sizeof(crp));
-       crp.cp.cancelRequestCode = (MsgType) pg_hton32(CANCEL_REQUEST_CODE);
-       crp.cp.backendPID = pg_hton32(cancel->be_pid);
-       crp.cp.cancelAuthCode = pg_hton32(cancel->be_key);
-
-retry4:
-       if (send(tmpsock, (char *) &crp, sizeof(crp), 0) != (int) sizeof(crp))
-       {
-               if (SOCK_ERRNO == EINTR)
-                       /* Interrupted system call - we'll just try again */
-                       goto retry4;
-               strlcpy(errbuf, "PQcancel() -- send() failed: ", errbufsize);
-               goto cancel_errReturn;
-       }
-
-       /*
-        * Wait for the postmaster to close the connection, which indicates that
-        * it's processed the request.  Without this delay, we might issue another
-        * command only to find that our cancel zaps that command instead of the
-        * one we thought we were canceling.  Note we don't actually expect this
-        * read to obtain any data, we are just waiting for EOF to be signaled.
-        */
-retry5:
-       if (recv(tmpsock, (char *) &crp, 1, 0) < 0)
-       {
-               if (SOCK_ERRNO == EINTR)
-                       /* Interrupted system call - we'll just try again */
-                       goto retry5;
-               /* we ignore other error conditions */
-       }
-
-       /* All done */
-       closesocket(tmpsock);
-       SOCK_ERRNO_SET(save_errno);
-       return true;
-
-cancel_errReturn:
-
-       /*
-        * Make sure we don't overflow the error buffer. Leave space for the \n at
-        * the end, and for the terminating zero.
-        */
-       maxlen = errbufsize - strlen(errbuf) - 2;
-       if (maxlen >= 0)
-       {
-               /*
-                * We can't invoke strerror here, since it's not signal-safe.  Settle
-                * for printing the decimal value of errno.  Even that has to be done
-                * the hard way.
-                */
-               int                     val = SOCK_ERRNO;
-               char            buf[32];
-               char       *bufp;
-
-               bufp = buf + sizeof(buf) - 1;
-               *bufp = '\0';
-               do
-               {
-                       *(--bufp) = (val % 10) + '0';
-                       val /= 10;
-               } while (val > 0);
-               bufp -= 6;
-               memcpy(bufp, "error ", 6);
-               strncat(errbuf, bufp, maxlen);
-               strcat(errbuf, "\n");
-       }
-       if (tmpsock != PGINVALID_SOCKET)
-               closesocket(tmpsock);
-       SOCK_ERRNO_SET(save_errno);
-       return false;
-}
-
-
-/*
- * PQrequestCancel: old, not thread-safe function for requesting query cancel
- *
- * Returns true if able to send the cancel request, false if not.
- *
- * On failure, the error message is saved in conn->errorMessage; this means
- * that this can't be used when there might be other active operations on
- * the connection object.
- *
- * NOTE: error messages will be cut off at the current size of the
- * error message buffer, since we dare not try to expand conn->errorMessage!
- */
-int
-PQrequestCancel(PGconn *conn)
-{
-       int                     r;
-       PGcancel   *cancel;
-
-       /* Check we have an open connection */
-       if (!conn)
-               return false;
-
-       if (conn->sock == PGINVALID_SOCKET)
-       {
-               strlcpy(conn->errorMessage.data,
-                               "PQrequestCancel() -- connection is not open\n",
-                               conn->errorMessage.maxlen);
-               conn->errorMessage.len = strlen(conn->errorMessage.data);
-               conn->errorReported = 0;
-
-               return false;
-       }
-
-       cancel = PQgetCancel(conn);
-       if (cancel)
-       {
-               r = PQcancel(cancel, conn->errorMessage.data,
-                                        conn->errorMessage.maxlen);
-               PQfreeCancel(cancel);
-       }
-       else
-       {
-               strlcpy(conn->errorMessage.data, "out of memory",
-                               conn->errorMessage.maxlen);
-               r = false;
-       }
-
-       if (!r)
-       {
-               conn->errorMessage.len = strlen(conn->errorMessage.data);
-               conn->errorReported = 0;
-       }
-
-       return r;
-}
-
-
 /*
  * pqPacketSend() -- convenience routine to send a message to server.
  *
@@ -7774,6 +7359,52 @@ pqGetHomeDirectory(char *buf, int bufsize)
 #endif
 }
 
+/*
+ * Parse and try to interpret "value" as an integer value, and if successful,
+ * store it in *result, complaining if there is any trailing garbage or an
+ * overflow.  This allows any number of leading and trailing whitespaces.
+ */
+bool
+pqParseIntParam(const char *value, int *result, PGconn *conn,
+                               const char *context)
+{
+       char       *end;
+       long            numval;
+
+       Assert(value != NULL);
+
+       *result = 0;
+
+       /* strtol(3) skips leading whitespaces */
+       errno = 0;
+       numval = strtol(value, &end, 10);
+
+       /*
+        * If no progress was done during the parsing or an error happened, fail.
+        * This tests properly for overflows of the result.
+        */
+       if (value == end || errno != 0 || numval != (int) numval)
+               goto error;
+
+       /*
+        * Skip any trailing whitespace; if anything but whitespace remains before
+        * the terminating character, fail
+        */
+       while (*end != '\0' && isspace((unsigned char) *end))
+               end++;
+
+       if (*end != '\0')
+               goto error;
+
+       *result = numval;
+       return true;
+
+error:
+       libpq_append_conn_error(conn, "invalid integer value \"%s\" for connection option \"%s\"",
+                                                       value, context);
+       return false;
+}
+
 /*
  * To keep the API consistent, the locking stubs are always provided, even
  * if they are not required.
index f0143726bbc058ad1008328bfa9dbe0ec91486b3..ff8e0dce776dba681b5b02d8b8e19c6bdf72d4cc 100644 (file)
@@ -675,9 +675,14 @@ extern char *const pgresStatus[];
 /* === in fe-connect.c === */
 
 extern void pqDropConnection(PGconn *conn, bool flushInput);
+#if defined(WIN32) && defined(SIO_KEEPALIVE_VALS)
+extern int     pqSetKeepalivesWin32(pgsocket sock, int idle, int interval);
+#endif
 extern int     pqPacketSend(PGconn *conn, char pack_type,
                                                 const void *buf, size_t buf_len);
 extern bool pqGetHomeDirectory(char *buf, int bufsize);
+extern bool pqParseIntParam(const char *value, int *result, PGconn *conn,
+                                                       const char *context);
 
 extern pgthreadlock_t pg_g_threadlock;
 
index c76a1e40c83e6b52e6c7955cabddaa89a2b44b5e..a47b6f425ddaeb788aa228b3858f361c8b27b086 100644 (file)
@@ -6,6 +6,7 @@
 libpq_sources = files(
   'fe-auth-scram.c',
   'fe-auth.c',
+  'fe-cancel.c',
   'fe-connect.c',
   'fe-exec.c',
   'fe-lobj.c',