mirror of
				https://github.com/postgres/postgres.git
				synced 2025-10-31 00:03:57 -04:00 
			
		
		
		
	PQcancelCreate failed to copy struct pg_conn_host's "type" field, instead leaving it zero (a/k/a CHT_HOST_NAME). This seemingly has no great ill effects if it should have been CHT_UNIX_SOCKET instead, but if it should have been CHT_HOST_ADDRESS then a null-pointer dereference will occur when the cancelConn is used. Bug: #18974 Reported-by: Maxim Boguk <maxim.boguk@gmail.com> Author: Sergei Kornilov <sk@zsrv.org> Reviewed-by: Tom Lane <tgl@sss.pgh.pa.us> Discussion: https://postgr.es/m/18974-575f02b2168b36b3@postgresql.org Backpatch-through: 17
		
			
				
	
	
		
			768 lines
		
	
	
		
			20 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			768 lines
		
	
	
		
			20 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| /*-------------------------------------------------------------------------
 | |
|  *
 | |
|  * fe-cancel.c
 | |
|  *	  functions related to query cancellation
 | |
|  *
 | |
|  * Portions Copyright (c) 1996-2025, 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"
 | |
| 
 | |
| 
 | |
| /*
 | |
|  * pg_cancel_conn (backing struct for PGcancelConn) is a wrapper around a
 | |
|  * PGconn to send cancellations using PQcancelBlocking and PQcancelStart.
 | |
|  * This isn't just a typedef because we want the compiler to complain when a
 | |
|  * PGconn is passed to a function that expects a PGcancelConn, and vice versa.
 | |
|  */
 | |
| struct pg_cancel_conn
 | |
| {
 | |
| 	PGconn		conn;
 | |
| };
 | |
| 
 | |
| /*
 | |
|  * pg_cancel (backing struct for PGcancel) stores all data necessary to send a
 | |
|  * cancel request.
 | |
|  */
 | |
| struct pg_cancel
 | |
| {
 | |
| 	SockAddr	raddr;			/* Remote address */
 | |
| 	int			be_pid;			/* PID of to-be-canceled backend */
 | |
| 	int			pgtcp_user_timeout; /* tcp user timeout */
 | |
| 	int			keepalives;		/* use TCP keepalives? */
 | |
| 	int			keepalives_idle;	/* time between TCP keepalives */
 | |
| 	int			keepalives_interval;	/* time between TCP keepalive
 | |
| 										 * retransmits */
 | |
| 	int			keepalives_count;	/* maximum number of TCP keepalive
 | |
| 									 * retransmits */
 | |
| 
 | |
| 	/* Pre-constructed cancel request packet starts here */
 | |
| 	int32		cancel_pkt_len; /* in network byte order */
 | |
| 	char		cancel_req[FLEXIBLE_ARRAY_MEMBER];	/* CancelRequestPacket */
 | |
| };
 | |
| 
 | |
| 
 | |
| /*
 | |
|  *		PQcancelCreate
 | |
|  *
 | |
|  * Create and return a PGcancelConn, which can be used to securely cancel a
 | |
|  * query on the given connection.
 | |
|  *
 | |
|  * This requires either following the non-blocking flow through
 | |
|  * PQcancelStart() and PQcancelPoll(), or the blocking PQcancelBlocking().
 | |
|  */
 | |
| PGcancelConn *
 | |
| PQcancelCreate(PGconn *conn)
 | |
| {
 | |
| 	PGconn	   *cancelConn = pqMakeEmptyPGconn();
 | |
| 	pg_conn_host originalHost;
 | |
| 
 | |
| 	if (cancelConn == NULL)
 | |
| 		return NULL;
 | |
| 
 | |
| 	/* Check we have an open connection */
 | |
| 	if (!conn)
 | |
| 	{
 | |
| 		libpq_append_conn_error(cancelConn, "connection pointer is NULL");
 | |
| 		return (PGcancelConn *) cancelConn;
 | |
| 	}
 | |
| 
 | |
| 	if (conn->sock == PGINVALID_SOCKET)
 | |
| 	{
 | |
| 		libpq_append_conn_error(cancelConn, "connection not open");
 | |
| 		return (PGcancelConn *) cancelConn;
 | |
| 	}
 | |
| 
 | |
| 	/* Check that we have received a cancellation key */
 | |
| 	if (conn->be_cancel_key_len == 0)
 | |
| 	{
 | |
| 		libpq_append_conn_error(cancelConn, "no cancellation key received");
 | |
| 		return (PGcancelConn *) cancelConn;
 | |
| 	}
 | |
| 
 | |
| 	/*
 | |
| 	 * Indicate that this connection is used to send a cancellation
 | |
| 	 */
 | |
| 	cancelConn->cancelRequest = true;
 | |
| 
 | |
| 	if (!pqCopyPGconn(conn, cancelConn))
 | |
| 		return (PGcancelConn *) cancelConn;
 | |
| 
 | |
| 	/*
 | |
| 	 * Compute derived options
 | |
| 	 */
 | |
| 	if (!pqConnectOptions2(cancelConn))
 | |
| 		return (PGcancelConn *) cancelConn;
 | |
| 
 | |
| 	/*
 | |
| 	 * Copy cancellation token data from the original connection
 | |
| 	 */
 | |
| 	cancelConn->be_pid = conn->be_pid;
 | |
| 	if (conn->be_cancel_key != NULL)
 | |
| 	{
 | |
| 		cancelConn->be_cancel_key = malloc(conn->be_cancel_key_len);
 | |
| 		if (cancelConn->be_cancel_key == NULL)
 | |
| 			goto oom_error;
 | |
| 		memcpy(cancelConn->be_cancel_key, conn->be_cancel_key, conn->be_cancel_key_len);
 | |
| 	}
 | |
| 	cancelConn->be_cancel_key_len = conn->be_cancel_key_len;
 | |
| 	cancelConn->pversion = conn->pversion;
 | |
| 
 | |
| 	/*
 | |
| 	 * Cancel requests should not iterate over all possible hosts. The request
 | |
| 	 * needs to be sent to the exact host and address that the original
 | |
| 	 * connection used. So we manually create the host and address arrays with
 | |
| 	 * a single element after freeing the host array that we generated from
 | |
| 	 * the connection options.
 | |
| 	 */
 | |
| 	pqReleaseConnHosts(cancelConn);
 | |
| 	cancelConn->nconnhost = 1;
 | |
| 	cancelConn->naddr = 1;
 | |
| 
 | |
| 	cancelConn->connhost = calloc(cancelConn->nconnhost, sizeof(pg_conn_host));
 | |
| 	if (!cancelConn->connhost)
 | |
| 		goto oom_error;
 | |
| 
 | |
| 	originalHost = conn->connhost[conn->whichhost];
 | |
| 	cancelConn->connhost[0].type = originalHost.type;
 | |
| 	if (originalHost.host)
 | |
| 	{
 | |
| 		cancelConn->connhost[0].host = strdup(originalHost.host);
 | |
| 		if (!cancelConn->connhost[0].host)
 | |
| 			goto oom_error;
 | |
| 	}
 | |
| 	if (originalHost.hostaddr)
 | |
| 	{
 | |
| 		cancelConn->connhost[0].hostaddr = strdup(originalHost.hostaddr);
 | |
| 		if (!cancelConn->connhost[0].hostaddr)
 | |
| 			goto oom_error;
 | |
| 	}
 | |
| 	if (originalHost.port)
 | |
| 	{
 | |
| 		cancelConn->connhost[0].port = strdup(originalHost.port);
 | |
| 		if (!cancelConn->connhost[0].port)
 | |
| 			goto oom_error;
 | |
| 	}
 | |
| 	if (originalHost.password)
 | |
| 	{
 | |
| 		cancelConn->connhost[0].password = strdup(originalHost.password);
 | |
| 		if (!cancelConn->connhost[0].password)
 | |
| 			goto oom_error;
 | |
| 	}
 | |
| 
 | |
| 	cancelConn->addr = calloc(cancelConn->naddr, sizeof(AddrInfo));
 | |
| 	if (!cancelConn->addr)
 | |
| 		goto oom_error;
 | |
| 
 | |
| 	cancelConn->addr[0].addr = conn->raddr;
 | |
| 	cancelConn->addr[0].family = conn->raddr.addr.ss_family;
 | |
| 
 | |
| 	cancelConn->status = CONNECTION_ALLOCATED;
 | |
| 	return (PGcancelConn *) cancelConn;
 | |
| 
 | |
| oom_error:
 | |
| 	cancelConn->status = CONNECTION_BAD;
 | |
| 	libpq_append_conn_error(cancelConn, "out of memory");
 | |
| 	return (PGcancelConn *) cancelConn;
 | |
| }
 | |
| 
 | |
| 
 | |
| /*
 | |
|  *		PQcancelBlocking
 | |
|  *
 | |
|  * Send a cancellation request in a blocking fashion.
 | |
|  * Returns 1 if successful 0 if not.
 | |
|  */
 | |
| int
 | |
| PQcancelBlocking(PGcancelConn *cancelConn)
 | |
| {
 | |
| 	if (!PQcancelStart(cancelConn))
 | |
| 		return 0;
 | |
| 	return pqConnectDBComplete(&cancelConn->conn);
 | |
| }
 | |
| 
 | |
| /*
 | |
|  *		PQcancelStart
 | |
|  *
 | |
|  * Starts sending a cancellation request in a non-blocking fashion. Returns
 | |
|  * 1 if successful 0 if not.
 | |
|  */
 | |
| int
 | |
| PQcancelStart(PGcancelConn *cancelConn)
 | |
| {
 | |
| 	if (!cancelConn || cancelConn->conn.status == CONNECTION_BAD)
 | |
| 		return 0;
 | |
| 
 | |
| 	if (cancelConn->conn.status != CONNECTION_ALLOCATED)
 | |
| 	{
 | |
| 		libpq_append_conn_error(&cancelConn->conn,
 | |
| 								"cancel request is already being sent on this connection");
 | |
| 		cancelConn->conn.status = CONNECTION_BAD;
 | |
| 		return 0;
 | |
| 	}
 | |
| 
 | |
| 	return pqConnectDBStart(&cancelConn->conn);
 | |
| }
 | |
| 
 | |
| /*
 | |
|  *		PQcancelPoll
 | |
|  *
 | |
|  * Poll a cancel connection. For usage details see PQconnectPoll.
 | |
|  */
 | |
| PostgresPollingStatusType
 | |
| PQcancelPoll(PGcancelConn *cancelConn)
 | |
| {
 | |
| 	PGconn	   *conn = &cancelConn->conn;
 | |
| 	int			n;
 | |
| 
 | |
| 	/*
 | |
| 	 * We leave most of the connection establishment to PQconnectPoll, since
 | |
| 	 * it's very similar to normal connection establishment. But once we get
 | |
| 	 * to the CONNECTION_AWAITING_RESPONSE we need to start doing our own
 | |
| 	 * thing.
 | |
| 	 */
 | |
| 	if (conn->status != CONNECTION_AWAITING_RESPONSE)
 | |
| 	{
 | |
| 		return PQconnectPoll(conn);
 | |
| 	}
 | |
| 
 | |
| 	/*
 | |
| 	 * At this point we are waiting on the server to close the connection,
 | |
| 	 * which is its way of communicating that the cancel has been handled.
 | |
| 	 */
 | |
| 
 | |
| 	n = pqReadData(conn);
 | |
| 
 | |
| 	if (n == 0)
 | |
| 		return PGRES_POLLING_READING;
 | |
| 
 | |
| #ifndef WIN32
 | |
| 
 | |
| 	/*
 | |
| 	 * If we receive an error report it, but only if errno is non-zero.
 | |
| 	 * Otherwise we assume it's an EOF, which is what we expect from the
 | |
| 	 * server.
 | |
| 	 *
 | |
| 	 * We skip this for Windows, because Windows is a bit special in its EOF
 | |
| 	 * behaviour for TCP. Sometimes it will error with an ECONNRESET when
 | |
| 	 * there is a clean connection closure. See these threads for details:
 | |
| 	 * https://www.postgresql.org/message-id/flat/90b34057-4176-7bb0-0dbb-9822a5f6425b%40greiz-reinsdorf.de
 | |
| 	 *
 | |
| 	 * https://www.postgresql.org/message-id/flat/CA%2BhUKG%2BOeoETZQ%3DQw5Ub5h3tmwQhBmDA%3DnuNO3KG%3DzWfUypFAw%40mail.gmail.com
 | |
| 	 *
 | |
| 	 * PQcancel ignores such errors and reports success for the cancellation
 | |
| 	 * anyway, so even if this is not always correct we do the same here.
 | |
| 	 */
 | |
| 	if (n < 0 && errno != 0)
 | |
| 	{
 | |
| 		conn->status = CONNECTION_BAD;
 | |
| 		return PGRES_POLLING_FAILED;
 | |
| 	}
 | |
| #endif
 | |
| 
 | |
| 	/*
 | |
| 	 * We don't expect any data, only connection closure. So if we strangely
 | |
| 	 * do receive some data we consider that an error.
 | |
| 	 */
 | |
| 	if (n > 0)
 | |
| 	{
 | |
| 		libpq_append_conn_error(conn, "unexpected response from server");
 | |
| 		conn->status = CONNECTION_BAD;
 | |
| 		return PGRES_POLLING_FAILED;
 | |
| 	}
 | |
| 
 | |
| 	/*
 | |
| 	 * Getting here means that we received an EOF, which is what we were
 | |
| 	 * expecting -- the cancel request has completed.
 | |
| 	 */
 | |
| 	cancelConn->conn.status = CONNECTION_OK;
 | |
| 	resetPQExpBuffer(&conn->errorMessage);
 | |
| 	return PGRES_POLLING_OK;
 | |
| }
 | |
| 
 | |
| /*
 | |
|  *		PQcancelStatus
 | |
|  *
 | |
|  * Get the status of a cancel connection.
 | |
|  */
 | |
| ConnStatusType
 | |
| PQcancelStatus(const PGcancelConn *cancelConn)
 | |
| {
 | |
| 	return PQstatus(&cancelConn->conn);
 | |
| }
 | |
| 
 | |
| /*
 | |
|  *		PQcancelSocket
 | |
|  *
 | |
|  * Get the socket of the cancel connection.
 | |
|  */
 | |
| int
 | |
| PQcancelSocket(const PGcancelConn *cancelConn)
 | |
| {
 | |
| 	return PQsocket(&cancelConn->conn);
 | |
| }
 | |
| 
 | |
| /*
 | |
|  *		PQcancelErrorMessage
 | |
|  *
 | |
|  * Returns the error message most recently generated by an operation on the
 | |
|  * cancel connection.
 | |
|  */
 | |
| char *
 | |
| PQcancelErrorMessage(const PGcancelConn *cancelConn)
 | |
| {
 | |
| 	return PQerrorMessage(&cancelConn->conn);
 | |
| }
 | |
| 
 | |
| /*
 | |
|  *		PQcancelReset
 | |
|  *
 | |
|  * Resets the cancel connection, so it can be reused to send a new cancel
 | |
|  * request.
 | |
|  */
 | |
| void
 | |
| PQcancelReset(PGcancelConn *cancelConn)
 | |
| {
 | |
| 	pqClosePGconn(&cancelConn->conn);
 | |
| 	cancelConn->conn.status = CONNECTION_ALLOCATED;
 | |
| 	cancelConn->conn.whichhost = 0;
 | |
| 	cancelConn->conn.whichaddr = 0;
 | |
| 	cancelConn->conn.try_next_host = false;
 | |
| 	cancelConn->conn.try_next_addr = false;
 | |
| }
 | |
| 
 | |
| /*
 | |
|  *		PQcancelFinish
 | |
|  *
 | |
|  * Closes and frees the cancel connection.
 | |
|  */
 | |
| void
 | |
| PQcancelFinish(PGcancelConn *cancelConn)
 | |
| {
 | |
| 	PQfinish(&cancelConn->conn);
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * 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;
 | |
| 	int			cancel_req_len;
 | |
| 	CancelRequestPacket *req;
 | |
| 
 | |
| 	if (!conn)
 | |
| 		return NULL;
 | |
| 
 | |
| 	if (conn->sock == PGINVALID_SOCKET)
 | |
| 		return NULL;
 | |
| 
 | |
| 	/* Check that we have received a cancellation key */
 | |
| 	if (conn->be_cancel_key_len == 0)
 | |
| 		return NULL;
 | |
| 
 | |
| 	cancel_req_len = offsetof(CancelRequestPacket, cancelAuthCode) + conn->be_cancel_key_len;
 | |
| 	cancel = malloc(offsetof(PGcancel, cancel_req) + cancel_req_len);
 | |
| 	if (cancel == NULL)
 | |
| 		return NULL;
 | |
| 
 | |
| 	memcpy(&cancel->raddr, &conn->raddr, sizeof(SockAddr));
 | |
| 
 | |
| 	/* 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;
 | |
| 	}
 | |
| 
 | |
| 	req = (CancelRequestPacket *) &cancel->cancel_req;
 | |
| 	req->cancelRequestCode = (MsgType) pg_hton32(CANCEL_REQUEST_CODE);
 | |
| 	req->backendPID = pg_hton32(conn->be_pid);
 | |
| 	memcpy(req->cancelAuthCode, conn->be_cancel_key, conn->be_cancel_key_len);
 | |
| 	/* include the length field itself in the length */
 | |
| 	cancel->cancel_pkt_len = pg_hton32(cancel_req_len + 4);
 | |
| 
 | |
| 	return cancel;
 | |
| 
 | |
| fail:
 | |
| 	free(cancel);
 | |
| 	return NULL;
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * PQsendCancelRequest
 | |
|  *	 Submit a CancelRequest message, but don't wait for it to finish
 | |
|  *
 | |
|  * Returns: 1 if successfully submitted
 | |
|  *			0 if error (conn->errorMessage is set)
 | |
|  */
 | |
| int
 | |
| PQsendCancelRequest(PGconn *cancelConn)
 | |
| {
 | |
| 	CancelRequestPacket req;
 | |
| 
 | |
| 	/* Start the message. */
 | |
| 	if (pqPutMsgStart(0, cancelConn))
 | |
| 		return STATUS_ERROR;
 | |
| 
 | |
| 	/* Send the message body. */
 | |
| 	memset(&req, 0, offsetof(CancelRequestPacket, cancelAuthCode));
 | |
| 	req.cancelRequestCode = (MsgType) pg_hton32(CANCEL_REQUEST_CODE);
 | |
| 	req.backendPID = pg_hton32(cancelConn->be_pid);
 | |
| 	if (pqPutnchar(&req, offsetof(CancelRequestPacket, cancelAuthCode), cancelConn))
 | |
| 		return STATUS_ERROR;
 | |
| 	if (pqPutnchar(cancelConn->be_cancel_key, cancelConn->be_cancel_key_len, cancelConn))
 | |
| 		return STATUS_ERROR;
 | |
| 
 | |
| 	/* Finish the message. */
 | |
| 	if (pqPutMsgEnd(cancelConn))
 | |
| 		return STATUS_ERROR;
 | |
| 
 | |
| 	/* Flush to ensure backend gets it. */
 | |
| 	if (pqFlush(cancelConn))
 | |
| 		return STATUS_ERROR;
 | |
| 
 | |
| 	return STATUS_OK;
 | |
| }
 | |
| 
 | |
| /* 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: old, non-encrypted, but signal-safe way of requesting 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;
 | |
| 	char		recvbuf;
 | |
| 	int			cancel_pkt_len;
 | |
| 
 | |
| 	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;
 | |
| 	}
 | |
| 
 | |
| 	cancel_pkt_len = pg_ntoh32(cancel->cancel_pkt_len);
 | |
| 
 | |
| retry4:
 | |
| 
 | |
| 	/*
 | |
| 	 * Send the cancel request packet. It starts with the message length at
 | |
| 	 * cancel_pkt_len, followed by the actual packet.
 | |
| 	 */
 | |
| 	if (send(tmpsock, (char *) &cancel->cancel_pkt_len, cancel_pkt_len, 0) != cancel_pkt_len)
 | |
| 	{
 | |
| 		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, &recvbuf, 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;
 | |
| }
 |