Exemple #1
0
/*
 * Attempt to read an Error or Notice response message.
 * This is possible in several places, so we break it out as a subroutine.
 * Entry: 'E' message type and length have already been consumed.
 * Exit: returns 0 if successfully consumed message.
 *		 returns EOF if not enough data.
 */
int
gtmpqGetError(GTM_Conn *conn, GTM_Result *result)
{
	char		id;

	/*
	 * If we are a GTM proxy, expect an additional proxy header in the incoming
	 * message.
	 */
	if (conn->remote_type == GTM_NODE_GTM_PROXY)
	{
		if (gtmpqGetnchar((char *)&result->gr_proxyhdr,
					sizeof (GTM_ProxyMsgHeader), conn))
			return 1;
		result->gr_msglen -= sizeof (GTM_ProxyMsgHeader);

		/*
		 * If the allocated buffer is not large enough to hold the proxied
		 * data, realloc the buffer.
		 *
		 * Since the client side code is shared between the proxy and the
		 * backend, we don't want any memory context management etc here. So
		 * just use plain realloc. Anyways, we don't indent to free the memory.
		 */
		if (result->gr_proxy_datalen < result->gr_msglen)
		{
			result->gr_proxy_data = (char *)realloc(
					result->gr_proxy_data, result->gr_msglen);
			result->gr_proxy_datalen = result->gr_msglen;
		}

		if (gtmpqGetnchar((char *)result->gr_proxy_data,
					result->gr_msglen, conn))
		{
			result->gr_status = GTM_RESULT_UNKNOWN;
			return 1;
		}

		return 0;
	}
	else
		result->gr_proxyhdr.ph_conid = InvalidGTMProxyConnID;

	/*
	 * Read the fields and save into res.
	 */
	for (;;)
	{
		if (gtmpqGetc(&id, conn))
			goto fail;
		if (id == '\0')
			break;
		if (gtmpqGets(&conn->errorMessage, conn))
			goto fail;
	}
	return 0;

fail:
	return EOF;
}
Exemple #2
0
/* ----------------
 *		GTMPQconnectPoll
 *
 * Poll an asynchronous connection.
 *
 * Returns a GTMClientPollingStatusType.
 * Before calling this function, use select(2) to determine when data
 * has arrived..
 *
 * You must call GTMPQfinish whether or not this fails.
 */
GTMClientPollingStatusType
GTMPQconnectPoll(GTM_Conn *conn)
{
	if (conn == NULL)
		return PGRES_POLLING_FAILED;

	/* Get the new data */
	switch (conn->status)
	{
			/*
			 * We really shouldn't have been polled in these two cases, but we
			 * can handle it.
			 */
		case CONNECTION_BAD:
			return PGRES_POLLING_FAILED;
		case CONNECTION_OK:
			return PGRES_POLLING_OK;

			/* These are reading states */
		case CONNECTION_AWAITING_RESPONSE:
		case CONNECTION_AUTH_OK:
			{
				/* Load waiting data */
				int			n = gtmpqReadData(conn);

				if (n < 0)
					goto error_return;
				if (n == 0)
					return PGRES_POLLING_READING;

				break;
			}

			/* These are writing states, so we just proceed. */
		case CONNECTION_STARTED:
		case CONNECTION_MADE:
			break;

		case CONNECTION_NEEDED:
			break;

		default:
			appendGTMPQExpBuffer(&conn->errorMessage,
											"invalid connection state, "
								 "probably indicative of memory corruption\n"
											);
			goto error_return;
	}


keep_going:						/* We will come back to here until there is
								 * nothing left to do. */
	switch (conn->status)
	{
		case CONNECTION_NEEDED:
			{
				/*
				 * Try to initiate a connection to one of the addresses
				 * returned by gtm_getaddrinfo_all().  conn->addr_cur is the
				 * next one to try. We fail when we run out of addresses
				 * (reporting the error returned for the *last* alternative,
				 * which may not be what users expect :-().
				 */
				while (conn->addr_cur != NULL)
				{
					struct addrinfo *addr_cur = conn->addr_cur;

					/* Remember current address for possible error msg */
					memcpy(&conn->raddr.addr, addr_cur->ai_addr,
						   addr_cur->ai_addrlen);
					conn->raddr.salen = addr_cur->ai_addrlen;

					/* Open a socket */
					conn->sock = socket(addr_cur->ai_family, SOCK_STREAM, 0);
					if (conn->sock < 0)
					{
						/*
						 * ignore socket() failure if we have more addresses
						 * to try
						 */
						if (addr_cur->ai_next != NULL)
						{
							conn->addr_cur = addr_cur->ai_next;
							continue;
						}
						appendGTMPQExpBuffer(&conn->errorMessage,
							  "could not create socket: \n");
						break;
					}

					/*
					 * Select socket options: no delay of outgoing data for
					 * TCP sockets, nonblock mode, close-on-exec. Fail if any
					 * of this fails.
					 */
					if (!IS_AF_UNIX(addr_cur->ai_family))
					{
						if (!connectNoDelay(conn))
						{
							close(conn->sock);
							conn->sock = -1;
							conn->addr_cur = addr_cur->ai_next;
							continue;
						}
					}

					/*
					 * Start/make connection.  This should not block, since we
					 * are in nonblock mode.  If it does, well, too bad.
					 */
					if (connect(conn->sock, addr_cur->ai_addr,
								addr_cur->ai_addrlen) < 0)
					{
						if (SOCK_ERRNO == EINPROGRESS ||
							SOCK_ERRNO == EWOULDBLOCK ||
							SOCK_ERRNO == EINTR ||
							SOCK_ERRNO == 0)
						{
							/*
							 * This is fine - we're in non-blocking mode, and
							 * the connection is in progress.  Tell caller to
							 * wait for write-ready on socket.
							 */
							conn->status = CONNECTION_STARTED;
							return PGRES_POLLING_WRITING;
						}
						/* otherwise, trouble */
					}
					else
					{
						/*
						 * Hm, we're connected already --- seems the "nonblock
						 * connection" wasn't.  Advance the state machine and
						 * go do the next stuff.
						 */
						conn->status = CONNECTION_STARTED;
						goto keep_going;
					}

					/*
					 * This connection failed --- set up error report, then
					 * close socket (do it this way in case close() affects
					 * the value of errno...).	We will ignore the connect()
					 * failure and keep going if there are more addresses.
					 */
					connectFailureMessage(conn, SOCK_ERRNO);
					if (conn->sock >= 0)
					{
						close(conn->sock);
						conn->sock = -1;
					}

					/*
					 * Try the next address, if any.
					 */
					conn->addr_cur = addr_cur->ai_next;
				}				/* loop over addresses */

				/*
				 * Ooops, no more addresses.  An appropriate error message is
				 * already set up, so just set the right status.
				 */
				goto error_return;
			}

		case CONNECTION_STARTED:
			{
				int			optval;
				size_t optlen = sizeof(optval);

				/*
				 * Write ready, since we've made it here, so the connection
				 * has been made ... or has failed.
				 */

				/*
				 * Now check (using getsockopt) that there is not an error
				 * state waiting for us on the socket.
				 */

				if (getsockopt(conn->sock, SOL_SOCKET, SO_ERROR,
						       (char *) &optval, (socklen_t *)&optlen) == -1)
				{
					appendGTMPQExpBuffer(&conn->errorMessage,
					libpq_gettext("could not get socket error status: \n"));
					goto error_return;
				}
				else if (optval != 0)
				{
					/*
					 * When using a nonblocking connect, we will typically see
					 * connect failures at this point, so provide a friendly
					 * error message.
					 */
					connectFailureMessage(conn, optval);

					/*
					 * If more addresses remain, keep trying, just as in the
					 * case where connect() returned failure immediately.
					 */
					if (conn->addr_cur->ai_next != NULL)
					{
						if (conn->sock >= 0)
						{
							close(conn->sock);
							conn->sock = -1;
						}
						conn->addr_cur = conn->addr_cur->ai_next;
						conn->status = CONNECTION_NEEDED;
						goto keep_going;
					}
					goto error_return;
				}

				/* Fill in the client address */
				conn->laddr.salen = sizeof(conn->laddr.addr);
				if (getsockname(conn->sock,
								(struct sockaddr *) & conn->laddr.addr,
								(socklen_t *)&conn->laddr.salen) < 0)
				{
					appendGTMPQExpBuffer(&conn->errorMessage,
									  "could not get client address from socket:\n");
					goto error_return;
				}

				/*
				 * Make sure we can write before advancing to next step.
				 */
				conn->status = CONNECTION_MADE;
				return PGRES_POLLING_WRITING;
			}

		case CONNECTION_MADE:
			{
				GTM_StartupPacket sp;

				/*
				 * Build a startup packet. We tell the GTM server/proxy our
				 * PGXC Node name and whether we are a proxy or not.
				 *
				 * When the connection is made from the proxy, we let the GTM
				 * server know about it so that some special headers are
				 * handled correctly by the server.
				 */
				strcpy(sp.sp_node_name, conn->gc_node_name);
				sp.sp_remotetype = conn->remote_type;
				sp.sp_ispostmaster = conn->is_postmaster;

				/*
				 * Send the startup packet.
				 *
				 * Theoretically, this could block, but it really shouldn't
				 * since we only got here if the socket is write-ready.
				 */
				if (pqPacketSend(conn, 'A', &sp, sizeof (GTM_StartupPacket)) != STATUS_OK)
				{
					appendGTMPQExpBuffer(&conn->errorMessage,
						"could not send startup packet: \n");
					goto error_return;
				}

				conn->status = CONNECTION_AWAITING_RESPONSE;
				return PGRES_POLLING_READING;
			}

			/*
			 * Handle authentication exchange: wait for postmaster messages
			 * and respond as necessary.
			 */
		case CONNECTION_AWAITING_RESPONSE:
			{
				char		beresp;

				/*
				 * Scan the message from current point (note that if we find
				 * the message is incomplete, we will return without advancing
				 * inStart, and resume here next time).
				 */
				conn->inCursor = conn->inStart;

				/* Read type byte */
				if (gtmpqGetc(&beresp, conn))
				{
					/* We'll come back when there is more data */
					return PGRES_POLLING_READING;
				}

				/*
				 * Validate message type: we expect only an authentication
				 * request or an error here.  Anything else probably means
				 * it's not GTM on the other end at all.
				 */
				if (!(beresp == 'R' || beresp == 'E'))
				{
					appendGTMPQExpBuffer(&conn->errorMessage,
									  "expected authentication request from "
												"server, but received %c\n",
									  beresp);
					goto error_return;
				}


				/* Handle errors. */
				if (beresp == 'E')
				{
					if (gtmpqGets_append(&conn->errorMessage, conn))
					{
						/* We'll come back when there is more data */
						return PGRES_POLLING_READING;
					}
					/* OK, we read the message; mark data consumed */
					conn->inStart = conn->inCursor;
					goto error_return;
				}

				{
					/*
					 * Server sends a dummy message body of size 4 bytes
					 */
					int tmp_int;
					gtmpqGetInt(&tmp_int, 4, conn);
				}

				/*
				 * OK, we successfully read the message; mark data consumed
				 */
				conn->inStart = conn->inCursor;

				/* We are done with authentication exchange */
				conn->status = CONNECTION_AUTH_OK;

				/* Look to see if we have more data yet. */
				goto keep_going;
			}

		case CONNECTION_AUTH_OK:
			{
				/* We can release the address list now. */
				gtm_freeaddrinfo_all(conn->addrlist_family, conn->addrlist);
				conn->addrlist = NULL;
				conn->addr_cur = NULL;

				/* Otherwise, we are open for business! */
				conn->status = CONNECTION_OK;
				return PGRES_POLLING_OK;
			}


		default:
			appendGTMPQExpBuffer(&conn->errorMessage,
											"invalid connection state %c, "
								 "probably indicative of memory corruption\n"
											,
							  conn->status);
			goto error_return;
	}

	/* Unreachable */

error_return:

	/*
	 * We used to close the socket at this point, but that makes it awkward
	 * for those above us if they wish to remove this socket from their own
	 * records (an fd_set for example).  We'll just have this socket closed
	 * when GTMPQfinish is called (which is compulsory even after an error, since
	 * the connection structure must be freed).
	 */
	conn->status = CONNECTION_BAD;
	return PGRES_POLLING_FAILED;
}
Exemple #3
0
/*
 * parseInput: if appropriate, parse input data from backend
 * until input is exhausted or a stopping state is reached.
 * Note that this function will NOT attempt to read more data from the backend.
 */
static GTM_Result *
pqParseInput(GTM_Conn *conn)
{
	char		id;
	int			msgLength;
	int			avail;
	GTM_Result	*result = NULL;

	if (conn->result == NULL)
	{
		conn->result = (GTM_Result *) malloc(sizeof (GTM_Result));
		memset(conn->result, 0, sizeof (GTM_Result));
	}
	else
		gtmpqFreeResultData(conn->result, conn->remote_type);

	result = conn->result;

	/*
	 * Try to read a message.  First get the type code and length. Return
	 * if not enough data.
	 */
	conn->inCursor = conn->inStart;
	if (gtmpqGetc(&id, conn))
		return NULL;
	if (gtmpqGetInt(&msgLength, 4, conn))
		return NULL;

	/*
	 * Try to validate message type/length here.  A length less than 4 is
	 * definitely broken.  Large lengths should only be believed for a few
	 * message types.
	 */
	if (msgLength < 4)
	{
		handleSyncLoss(conn, id, msgLength);
		return NULL;
	}
	if (msgLength > 30000 && !VALID_LONG_MESSAGE_TYPE(id))
	{
		handleSyncLoss(conn, id, msgLength);
		return NULL;
	}

	/*
	 * Can't process if message body isn't all here yet.
	 */
	conn->result->gr_msglen = msgLength -= 4;
	avail = conn->inEnd - conn->inCursor;
	if (avail < msgLength)
	{
		/*
		 * Before returning, enlarge the input buffer if needed to hold
		 * the whole message.  This is better than leaving it to
		 * gtmpqReadData because we can avoid multiple cycles of realloc()
		 * when the message is large; also, we can implement a reasonable
		 * recovery strategy if we are unable to make the buffer big
		 * enough.
		 */
		if (gtmpqCheckInBufferSpace(conn->inCursor + (size_t) msgLength,
								 conn))
		{
			/*
			 * XXX add some better recovery code... plan is to skip over
			 * the message using its length, then report an error. For the
			 * moment, just treat this like loss of sync (which indeed it
			 * might be!)
			 */
			handleSyncLoss(conn, id, msgLength);
		}
		return NULL;
	}

	switch (id)
	{
		case 'S':		/* command complete */
			if (gtmpqParseSuccess(conn, result))
				return NULL;
			break;

		case 'E':		/* error return */
			if (gtmpqGetError(conn, result))
				return NULL;
			result->gr_status = GTM_RESULT_ERROR;
			break;
		default:
			printfGTMPQExpBuffer(&conn->errorMessage,
							  "unexpected response from server; first received character was \"%c\"\n",
							  id);
			conn->inCursor += msgLength;
			break;
	}					/* switch on protocol character */
	/* Successfully consumed this message */
	if (conn->inCursor == conn->inStart + 5 + msgLength)
	{
		/* Normal case: parsing agrees with specified length */
		conn->inStart = conn->inCursor;
	}
	else
	{
		/* Trouble --- report it */
		printfGTMPQExpBuffer(&conn->errorMessage,
						  "message contents do not agree with length in message type \"%c\"\n",
						  id);
		/* trust the specified message length as what to skip */
		conn->inStart += 5 + msgLength;
	}

	return result;
}