/* * 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; }
/* ---------------- * 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; }
/* * 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; }