libpq: Move cancellation related functions to fe-cancel.c
authorAlvaro Herrera <[email protected]>
Mon, 29 Jan 2024 09:53:34 +0000 (10:53 +0100)
committerAlvaro Herrera <[email protected]>
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 <[email protected]>
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',