mirror of
https://github.com/postgres/postgres.git
synced 2025-07-26 01:22:12 +03:00
libpq: Move cancellation related functions to fe-cancel.c
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
This commit is contained in:
@ -30,6 +30,7 @@ endif
|
|||||||
OBJS = \
|
OBJS = \
|
||||||
$(WIN32RES) \
|
$(WIN32RES) \
|
||||||
fe-auth-scram.o \
|
fe-auth-scram.o \
|
||||||
|
fe-cancel.o \
|
||||||
fe-connect.o \
|
fe-connect.o \
|
||||||
fe-exec.o \
|
fe-exec.o \
|
||||||
fe-lobj.o \
|
fe-lobj.o \
|
||||||
|
387
src/interfaces/libpq/fe-cancel.c
Normal file
387
src/interfaces/libpq/fe-cancel.c
Normal file
@ -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;
|
||||||
|
}
|
@ -443,8 +443,6 @@ static void pgpassfileWarning(PGconn *conn);
|
|||||||
static void default_threadlock(int acquire);
|
static void default_threadlock(int acquire);
|
||||||
static bool sslVerifyProtocolVersion(const char *version);
|
static bool sslVerifyProtocolVersion(const char *version);
|
||||||
static bool sslVerifyProtocolRange(const char *min, const char *max);
|
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 */
|
/* global variable because fe-auth.c needs to access it */
|
||||||
@ -2076,52 +2074,6 @@ useKeepalives(PGconn *conn)
|
|||||||
return val != 0 ? 1 : 0;
|
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
|
#ifndef WIN32
|
||||||
/*
|
/*
|
||||||
* Set the keepalive idle timer.
|
* Set the keepalive idle timer.
|
||||||
@ -2134,7 +2086,7 @@ setKeepalivesIdle(PGconn *conn)
|
|||||||
if (conn->keepalives_idle == NULL)
|
if (conn->keepalives_idle == NULL)
|
||||||
return 1;
|
return 1;
|
||||||
|
|
||||||
if (!parse_int_param(conn->keepalives_idle, &idle, conn,
|
if (!pqParseIntParam(conn->keepalives_idle, &idle, conn,
|
||||||
"keepalives_idle"))
|
"keepalives_idle"))
|
||||||
return 0;
|
return 0;
|
||||||
if (idle < 0)
|
if (idle < 0)
|
||||||
@ -2168,7 +2120,7 @@ setKeepalivesInterval(PGconn *conn)
|
|||||||
if (conn->keepalives_interval == NULL)
|
if (conn->keepalives_interval == NULL)
|
||||||
return 1;
|
return 1;
|
||||||
|
|
||||||
if (!parse_int_param(conn->keepalives_interval, &interval, conn,
|
if (!pqParseIntParam(conn->keepalives_interval, &interval, conn,
|
||||||
"keepalives_interval"))
|
"keepalives_interval"))
|
||||||
return 0;
|
return 0;
|
||||||
if (interval < 0)
|
if (interval < 0)
|
||||||
@ -2203,7 +2155,7 @@ setKeepalivesCount(PGconn *conn)
|
|||||||
if (conn->keepalives_count == NULL)
|
if (conn->keepalives_count == NULL)
|
||||||
return 1;
|
return 1;
|
||||||
|
|
||||||
if (!parse_int_param(conn->keepalives_count, &count, conn,
|
if (!pqParseIntParam(conn->keepalives_count, &count, conn,
|
||||||
"keepalives_count"))
|
"keepalives_count"))
|
||||||
return 0;
|
return 0;
|
||||||
if (count < 0)
|
if (count < 0)
|
||||||
@ -2233,8 +2185,8 @@ setKeepalivesCount(PGconn *conn)
|
|||||||
*
|
*
|
||||||
* CAUTION: This needs to be signal safe, since it's used by PQcancel.
|
* CAUTION: This needs to be signal safe, since it's used by PQcancel.
|
||||||
*/
|
*/
|
||||||
static int
|
int
|
||||||
setKeepalivesWin32(pgsocket sock, int idle, int interval)
|
pqSetKeepalivesWin32(pgsocket sock, int idle, int interval)
|
||||||
{
|
{
|
||||||
struct tcp_keepalive ka;
|
struct tcp_keepalive ka;
|
||||||
DWORD retsize;
|
DWORD retsize;
|
||||||
@ -2269,15 +2221,15 @@ prepKeepalivesWin32(PGconn *conn)
|
|||||||
int interval = -1;
|
int interval = -1;
|
||||||
|
|
||||||
if (conn->keepalives_idle &&
|
if (conn->keepalives_idle &&
|
||||||
!parse_int_param(conn->keepalives_idle, &idle, conn,
|
!pqParseIntParam(conn->keepalives_idle, &idle, conn,
|
||||||
"keepalives_idle"))
|
"keepalives_idle"))
|
||||||
return 0;
|
return 0;
|
||||||
if (conn->keepalives_interval &&
|
if (conn->keepalives_interval &&
|
||||||
!parse_int_param(conn->keepalives_interval, &interval, conn,
|
!pqParseIntParam(conn->keepalives_interval, &interval, conn,
|
||||||
"keepalives_interval"))
|
"keepalives_interval"))
|
||||||
return 0;
|
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",
|
libpq_append_conn_error(conn, "%s(%s) failed: error code %d",
|
||||||
"WSAIoctl", "SIO_KEEPALIVE_VALS",
|
"WSAIoctl", "SIO_KEEPALIVE_VALS",
|
||||||
@ -2300,7 +2252,7 @@ setTCPUserTimeout(PGconn *conn)
|
|||||||
if (conn->pgtcp_user_timeout == NULL)
|
if (conn->pgtcp_user_timeout == NULL)
|
||||||
return 1;
|
return 1;
|
||||||
|
|
||||||
if (!parse_int_param(conn->pgtcp_user_timeout, &timeout, conn,
|
if (!pqParseIntParam(conn->pgtcp_user_timeout, &timeout, conn,
|
||||||
"tcp_user_timeout"))
|
"tcp_user_timeout"))
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
@ -2418,7 +2370,7 @@ connectDBComplete(PGconn *conn)
|
|||||||
*/
|
*/
|
||||||
if (conn->connect_timeout != NULL)
|
if (conn->connect_timeout != NULL)
|
||||||
{
|
{
|
||||||
if (!parse_int_param(conn->connect_timeout, &timeout, conn,
|
if (!pqParseIntParam(conn->connect_timeout, &timeout, conn,
|
||||||
"connect_timeout"))
|
"connect_timeout"))
|
||||||
{
|
{
|
||||||
/* mark the connection as bad to report the parsing failure */
|
/* 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;
|
thisport = DEF_PGPORT;
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (!parse_int_param(ch->port, &thisport, conn, "port"))
|
if (!pqParseIntParam(ch->port, &thisport, conn, "port"))
|
||||||
goto error_return;
|
goto error_return;
|
||||||
|
|
||||||
if (thisport < 1 || thisport > 65535)
|
if (thisport < 1 || thisport > 65535)
|
||||||
@ -4694,373 +4646,6 @@ PQresetPoll(PGconn *conn)
|
|||||||
return PGRES_POLLING_FAILED;
|
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.
|
* pqPacketSend() -- convenience routine to send a message to server.
|
||||||
*
|
*
|
||||||
@ -7774,6 +7359,52 @@ pqGetHomeDirectory(char *buf, int bufsize)
|
|||||||
#endif
|
#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
|
* To keep the API consistent, the locking stubs are always provided, even
|
||||||
* if they are not required.
|
* if they are not required.
|
||||||
|
@ -675,9 +675,14 @@ extern char *const pgresStatus[];
|
|||||||
/* === in fe-connect.c === */
|
/* === in fe-connect.c === */
|
||||||
|
|
||||||
extern void pqDropConnection(PGconn *conn, bool flushInput);
|
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,
|
extern int pqPacketSend(PGconn *conn, char pack_type,
|
||||||
const void *buf, size_t buf_len);
|
const void *buf, size_t buf_len);
|
||||||
extern bool pqGetHomeDirectory(char *buf, int bufsize);
|
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;
|
extern pgthreadlock_t pg_g_threadlock;
|
||||||
|
|
||||||
|
@ -6,6 +6,7 @@
|
|||||||
libpq_sources = files(
|
libpq_sources = files(
|
||||||
'fe-auth-scram.c',
|
'fe-auth-scram.c',
|
||||||
'fe-auth.c',
|
'fe-auth.c',
|
||||||
|
'fe-cancel.c',
|
||||||
'fe-connect.c',
|
'fe-connect.c',
|
||||||
'fe-exec.c',
|
'fe-exec.c',
|
||||||
'fe-lobj.c',
|
'fe-lobj.c',
|
||||||
|
Reference in New Issue
Block a user