/* ------------------------------------------------------------------------- * * fe-connect.c * functions related to setting up a connection to the backend * * Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * Portions Copyright (c) 2010-2012 Postgres-XC Development Group * * * IDENTIFICATION * $PostgreSQL: pgsql/src/interfaces/libpq/fe-connect.c,v 1.371 2008/12/15 10:28:21 mha Exp $ * * ------------------------------------------------------------------------- */ #include "gtm/gtm_c.h" #include #include #include #include #include #include #include "gtm/libpq-fe.h" #include "gtm/libpq-int.h" #include #include #include #include #include #include "gtm/gtm_ip.h" #include "gtm/gtm_msg.h" /* fall back options if they are not specified by arguments or defined by environment variables */ #define DefaultHost "localhost" /* ---------- * Definition of the conninfo parameters and their fallback resources. * * GTMPQconninfoOptions[] is a constant static array that we use to initialize * a dynamically allocated working copy. All the "val" fields in * GTMPQconninfoOptions[] *must* be NULL. In a working copy, non-null "val" * fields point to malloc'd strings that should be freed when the working * array is freed (see GTMPQconninfoFree). * ---------- */ static const GTMPQconninfoOption GTMPQconninfoOptions[] = {{"connect_timeout", NULL}, {"host", NULL}, {"hostaddr", NULL}, {"port", NULL}, {"node_name", NULL}, {"remote_type", NULL}, {"postmaster", NULL}, /* Terminating entry --- MUST BE LAST */ {NULL, NULL}}; static bool connectOptions1(GTM_Conn* conn, const char* conninfo); static int connectGTMStart(GTM_Conn* conn); static int connectGTMComplete(GTM_Conn* conn); static GTM_Conn* makeEmptyGTM_Conn(void); static void freeGTM_Conn(GTM_Conn* conn); static void closeGTM_Conn(GTM_Conn* conn); static GTMPQconninfoOption* conninfo_parse(const char* conninfo, PQExpBuffer errorMessage, bool use_defaults); static char* conninfo_getval(GTMPQconninfoOption* connOptions, const char* keyword); static int pqPacketSend(GTM_Conn* conn, char packet_type, const void* buf, size_t buf_len); GTM_Conn* PQconnectGTM(const char* conninfo) { GTM_Conn* conn = PQconnectGTMStart(conninfo); if (conn && conn->status != CONNECTION_BAD) { (void)connectGTMComplete(conn); } else if (conn != NULL) { freeGTM_Conn(conn); conn = NULL; } return conn; } /* * PQconnectGTMStart * * Returns a GTM_Conn*. If NULL is returned, a malloc error has occurred, and * you should not attempt to proceed with this connection. If the status * field of the connection returned is CONNECTION_BAD, an error has * occurred. In this case you should call GTMPQfinish on the result, (perhaps * inspecting the error message first). Other fields of the structure may not * be valid if that occurs. If the status field is not CONNECTION_BAD, then * this stage has succeeded - call GTMPQconnectPoll, using select(2) to see when * this is necessary. * * See GTMPQconnectPoll for more info. */ GTM_Conn* PQconnectGTMStart(const char* conninfo) { GTM_Conn* conn; /* * Allocate memory for the conn structure */ conn = makeEmptyGTM_Conn(); if (conn == NULL) { return NULL; } /* * Parse the conninfo string */ if (!connectOptions1(conn, conninfo)) { return conn; } /* * Connect to the database */ if (!connectGTMStart(conn)) { /* Just in case we failed to set it in connectGTMStart */ conn->status = CONNECTION_BAD; } return conn; } /* * connectOptions1 * * Internal subroutine to set up connection parameters given an already- * created GTM_Conn and a conninfo string. * * Returns true if OK, false if trouble (in which case errorMessage is set * and so is conn->status). */ static bool connectOptions1(GTM_Conn* conn, const char* conninfo) { GTMPQconninfoOption* connOptions; char* tmp; /* * Parse the conninfo string */ connOptions = conninfo_parse(conninfo, &conn->errorMessage, true); if (connOptions == NULL) { conn->status = CONNECTION_BAD; /* errorMessage is already set */ return false; } /* * Move option values into conn structure * * XXX: probably worth checking strdup() return value here... */ tmp = conninfo_getval(connOptions, "hostaddr"); conn->pghostaddr = tmp ? strdup(tmp) : NULL; tmp = conninfo_getval(connOptions, "host"); conn->pghost = tmp ? strdup(tmp) : NULL; tmp = conninfo_getval(connOptions, "port"); conn->pgport = tmp ? strdup(tmp) : NULL; tmp = conninfo_getval(connOptions, "connect_timeout"); conn->connect_timeout = tmp ? strdup(tmp) : NULL; tmp = conninfo_getval(connOptions, "node_name"); conn->gc_node_name = tmp ? strdup(tmp) : NULL; tmp = conninfo_getval(connOptions, "postmaster"); conn->is_postmaster = tmp ? atoi(tmp) : 0; tmp = conninfo_getval(connOptions, "remote_type"); conn->remote_type = tmp ? atoi(tmp) : GTM_NODE_DEFAULT; /* * Free the option info - all is in conn now */ GTMPQconninfoFree(connOptions); return true; } /* ---------- * connectNoDelay - * Sets the TCP_NODELAY socket option. * Returns 1 if successful, 0 if not. * ---------- */ static int connectNoDelay(GTM_Conn* conn) { #ifdef TCP_NODELAY int on = 1; if (setsockopt(conn->sock, IPPROTO_TCP, TCP_NODELAY, (char*)&on, sizeof(on)) < 0) { appendGTMPQExpBuffer(&conn->errorMessage, "could not set socket to TCP no delay mode: \n"); return 0; } #endif return 1; } /* ---------- * connectFailureMessage - * create a friendly error message on connection failure. * ---------- */ static void connectFailureMessage(GTM_Conn* conn, int errorno) { { appendGTMPQExpBuffer(&conn->errorMessage, "could not connect to server: \n" "\tIs the server running on host \"%s\" and accepting\n" "\tTCP/IP connections on port %s?\n", conn->pghostaddr ? conn->pghostaddr : (conn->pghost ? conn->pghost : "???"), conn->pgport); } } /* ---------- * connectGTMStart - * Begin the process of making a connection to the backend. * * Returns 1 if successful, 0 if not. * ---------- */ static int connectGTMStart(GTM_Conn* conn) { int portnum = 0; char portstr[128]; struct addrinfo* addrs = NULL; struct addrinfo hint; const char* node; int ret; int rc = 0; if (!conn) { return 0; } /* Ensure our buffers are empty */ conn->inStart = conn->inCursor = conn->inEnd = 0; conn->outCount = 0; /* * Determine the parameters to pass to gtm_getaddrinfo_all. */ /* Initialize hint structure */ rc = memset_s(&hint, sizeof(hint), 0, sizeof(hint)); securec_check(rc, "", ""); hint.ai_socktype = SOCK_STREAM; hint.ai_family = AF_UNSPEC; /* Set up port number as a string */ if (conn->pgport != NULL && conn->pgport[0] != '\0') { portnum = atoi(conn->pgport); } rc = snprintf_s(portstr, sizeof(portstr), sizeof(portstr) - 1, "%d", portnum); securec_check_ss(rc, "", ""); if (conn->pghostaddr != NULL && conn->pghostaddr[0] != '\0') { /* Using pghostaddr avoids a hostname lookup */ node = conn->pghostaddr; hint.ai_family = AF_UNSPEC; hint.ai_flags = AI_NUMERICHOST; } else if (conn->pghost != NULL && conn->pghost[0] != '\0') { /* Using pghost, so we have to look-up the hostname */ node = conn->pghost; hint.ai_family = AF_UNSPEC; } else { /* Without Unix sockets, default to localhost instead */ node = "localhost"; hint.ai_family = AF_UNSPEC; } /* Use gtm_getaddrinfo_all() to resolve the address */ ret = gtm_getaddrinfo_all(node, portstr, &hint, &addrs); if (ret || !addrs) { if (node) { appendGTMPQExpBuffer( &conn->errorMessage, "could not translate host name \"%s\" to address: %s\n", node, gai_strerror(ret)); } else { appendGTMPQExpBuffer(&conn->errorMessage, "could not translate Unix-domain socket path \"%s\" to address: %s\n", portstr, gai_strerror(ret)); } if (addrs) { gtm_freeaddrinfo_all(hint.ai_family, addrs); } goto connect_errReturn; } /* * Set up to try to connect, with protocol 3.0 as the first attempt. */ conn->addrlist = addrs; conn->addr_cur = addrs; conn->addrlist_family = hint.ai_family; conn->status = CONNECTION_NEEDED; /* * The code for processing CONNECTION_NEEDED state is in GTMPQconnectPoll(), * so that it can easily be re-executed if needed again during the * asynchronous startup process. However, we must run it once here, * because callers expect a success return from this routine to mean that * we are in PGRES_POLLING_WRITING connection state. */ if (GTMPQconnectPoll(conn) == PGRES_POLLING_WRITING) { return 1; } connect_errReturn: if (conn->sock >= 0) { close(conn->sock); conn->sock = -1; } conn->status = CONNECTION_BAD; return 0; } /* * connectGTMComplete * * Block and complete a connection. * * Returns 1 on success, 0 on failure. */ static int connectGTMComplete(GTM_Conn* conn) { GTMClientPollingStatusType flag = PGRES_POLLING_WRITING; time_t finish_time = ((time_t)-1); if (conn == NULL || conn->status == CONNECTION_BAD) { return 0; } /* * Set up a time limit, if connect_timeout isn't zero. */ if (conn->connect_timeout != NULL) { int timeout = atoi(conn->connect_timeout); if (timeout > 0) { /* * Rounding could cause connection to fail; need at least 2 secs */ if (timeout < 2) { timeout = 2; } /* calculate the finish time based on start + timeout */ finish_time = time(NULL) + timeout; } } for (;;) { /* * Wait, if necessary. Note that the initial state (just after * PQconnectGTMStart) is to wait for the socket to select for writing. */ switch (flag) { case PGRES_POLLING_OK: /* Reset stored error messages since we now have a working connection */ resetGTMPQExpBuffer(&conn->errorMessage); return 1; /* success! */ case PGRES_POLLING_READING: if (gtmpqWaitTimed(1, 0, conn, finish_time)) { conn->status = CONNECTION_BAD; return 0; } break; case PGRES_POLLING_WRITING: if (gtmpqWaitTimed(0, 1, conn, finish_time)) { conn->status = CONNECTION_BAD; return 0; } break; default: /* Just in case we failed to set it in GTMPQconnectPoll */ conn->status = CONNECTION_BAD; return 0; } /* * Now try to advance the state machine. */ flag = GTMPQconnectPoll(conn); } } /* ---------------- * 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 */ int rc = memcpy_s(&conn->raddr.addr, addr_cur->ai_addrlen, addr_cur->ai_addr, addr_cur->ai_addrlen); securec_check(rc, "", ""); 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 = (GTM_StartupPacket*)malloc(sizeof(GTM_StartupPacket)); int packetlen = sizeof(GTM_StartupPacket); int rc = memset_s(sp, sizeof(GTM_StartupPacket), 0, sizeof(GTM_StartupPacket)); securec_check(rc, "", ""); /* * 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. */ int rc = strncpy_s(sp->sp_node_name, SP_NODE_NAME, conn->gc_node_name, SP_NODE_NAME); securec_check(rc, "", ""); 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', (char*)sp, packetlen) != STATUS_OK) { appendGTMPQExpBuffer(&conn->errorMessage, "could not send startup packet: \n"); goto error_return; } conn->status = CONNECTION_AWAITING_RESPONSE; /* Clean up startup packet */ free(sp); 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; } /* * makeEmptyGTM_Conn * - create a GTM_Conn data structure with (as yet) no interesting data */ static GTM_Conn* makeEmptyGTM_Conn(void) { GTM_Conn* conn; conn = (GTM_Conn*)malloc(sizeof(GTM_Conn)); if (conn == NULL) { return conn; } /* Zero all pointers and booleans */ int rc = memset_s(conn, sizeof(GTM_Conn), 0, sizeof(GTM_Conn)); securec_check(rc, "", ""); conn->status = CONNECTION_BAD; /* * We try to send at least 8K at a time, which is the usual size of pipe * buffers on Unix systems. That way, when we are sending a large amount * of data, we avoid incurring extra kernel context swaps for partial * bufferloads. The output buffer is initially made 16K in size, and we * try to dump it after accumulating 8K. * * With the same goal of minimizing context swaps, the input buffer will * be enlarged anytime it has less than 8K free, so we initially allocate * twice that. */ conn->inBufSize = 16 * 1024; conn->inBuffer = (char*)malloc(conn->inBufSize); conn->outBufSize = 16 * 1024; conn->outBuffer = (char*)malloc(conn->outBufSize); initGTMPQExpBuffer(&conn->errorMessage); initGTMPQExpBuffer(&conn->workBuffer); /* out of memory already :-( */ if (conn->inBuffer == NULL || conn->outBuffer == NULL || PQExpBufferBroken(&conn->errorMessage) || PQExpBufferBroken(&conn->workBuffer)) { freeGTM_Conn(conn); conn = NULL; } return conn; } /* * freeGTM_Conn * - free an idle (closed) GTM_Conn data structure * * NOTE: this should not overlap any functionality with closeGTM_Conn(). * Clearing/resetting of transient state belongs there; what we do here is * release data that is to be held for the life of the GTM_Conn structure. * If a value ought to be cleared/freed during PQreset(), do it there not here. */ static void freeGTM_Conn(GTM_Conn* conn) { if (conn->pghost) { free(conn->pghost); } if (conn->pghostaddr) { free(conn->pghostaddr); } if (conn->pgport) { free(conn->pgport); } if (conn->connect_timeout) { free(conn->connect_timeout); } if (conn->gc_node_name) { free(conn->gc_node_name); } if (conn->inBuffer) { free(conn->inBuffer); } if (conn->outBuffer) { free(conn->outBuffer); } termGTMPQExpBuffer(&conn->errorMessage); termGTMPQExpBuffer(&conn->workBuffer); free(conn); } /* * closeGTM_Conn * - properly close a connection to the backend * * This should reset or release all transient state, but NOT the connection * parameters. On exit, the GTM_Conn should be in condition to start a fresh * connection with the same parameters (see PQreset()). */ static void closeGTM_Conn(GTM_Conn* conn) { /* * Note that the protocol doesn't allow us to send Terminate messages * during the startup phase. */ if (conn->sock >= 0 && conn->status == CONNECTION_OK) { /* * Try to send "close connection" message to backend. Ignore any * error. * * Force length word for backends may try to read that in a generic * code */ gtmpqPutMsgStart('X', true, conn); gtmpqPutMsgEnd(conn); gtmpqFlush(conn); } /* * Close the connection, reset all transient state, flush I/O buffers. */ if (conn->sock >= 0) { close(conn->sock); } conn->sock = -1; /* Well, not really _bad_ - just absent */ conn->status = CONNECTION_BAD; gtm_freeaddrinfo_all(conn->addrlist_family, conn->addrlist); conn->addrlist = NULL; conn->addr_cur = NULL; conn->inStart = conn->inCursor = conn->inEnd = 0; conn->outCount = 0; } /* * GTMPQfinish: properly close a connection to the backend. Also frees * the GTM_Conn data structure so it shouldn't be re-used after this. */ void GTMPQfinish(GTM_Conn* conn) { if (conn) { closeGTM_Conn(conn); freeGTM_Conn(conn); } } /* * pqPacketSend() -- convenience routine to send a message to server. * * pack_type: the single-byte message type code. (Pass zero for startup * packets, which have no message type code.) * * buf, buf_len: contents of message. The given length includes only what * is in buf; the message type and message length fields are added here. * * RETURNS: STATUS_ERROR if the write fails, STATUS_OK otherwise. * SIDE_EFFECTS: may block. * * Note: all messages sent with this routine have a length word, whether * it's protocol 2.0 or 3.0. */ static int pqPacketSend(GTM_Conn* conn, char pack_type, const void* buf, size_t buf_len) { /* Start the message. */ if (gtmpqPutMsgStart(pack_type, true, conn)) { return STATUS_ERROR; } /* Send the message body. */ if (gtmpqPutnchar(buf, buf_len, conn)) { return STATUS_ERROR; } /* Finish the message. */ if (gtmpqPutMsgEnd(conn)) { return STATUS_ERROR; } /* Flush to ensure backend gets it. */ if (gtmpqFlush(conn)) { return STATUS_ERROR; } return STATUS_OK; } /* * GTMPQconninfoParse * * Parse a string like PQconnectGTM() would do and return the * resulting connection options array. NULL is returned on failure. * The result contains only options specified directly in the string, * not any possible default values. * * If errmsg isn't NULL, *errmsg is set to NULL on success, or a malloc'd * string on failure (use PQfreemem to free it). In out-of-memory conditions * both *errmsg and the result could be NULL. * * NOTE: the returned array is dynamically allocated and should * be freed when no longer needed via GTMPQconninfoFree(). */ GTMPQconninfoOption* GTMPQconninfoParse(const char* conninfo, char** errmsg) { PQExpBufferData errorBuf; GTMPQconninfoOption* connOptions; /* default */ if (errmsg) { *errmsg = NULL; } initGTMPQExpBuffer(&errorBuf); /* out of memory already :-( */ if (PQExpBufferDataBroken(errorBuf)) { return NULL; } connOptions = conninfo_parse(conninfo, &errorBuf, false); if (connOptions == NULL && errmsg) { *errmsg = errorBuf.data; } else { termGTMPQExpBuffer(&errorBuf); } return connOptions; } /* * Conninfo parser routine * * If successful, a malloc'd GTMPQconninfoOption array is returned. * If not successful, NULL is returned and an error message is * left in errorMessage. * Defaults are supplied (from a service file, environment variables, etc) * for unspecified options, but only if use_defaults is TRUE. */ static GTMPQconninfoOption* conninfo_parse(const char* conninfo, PQExpBuffer errorMessage, bool use_defaults) { char* pname; char* pval; char* buf; char* cp; char* cp2; GTMPQconninfoOption* options; GTMPQconninfoOption* option; /* Make a working copy of GTMPQconninfoOptions */ options = malloc(sizeof(GTMPQconninfoOptions)); if (options == NULL) { printfGTMPQExpBuffer(errorMessage, libpq_gettext("out of memory\n")); return NULL; } int rc = memcpy_s(options, sizeof(GTMPQconninfoOptions), GTMPQconninfoOptions, sizeof(GTMPQconninfoOptions)); securec_check(rc, "", ""); /* Need a modifiable copy of the input string */ if ((buf = strdup(conninfo)) == NULL) { printfGTMPQExpBuffer(errorMessage, libpq_gettext("out of memory\n")); GTMPQconninfoFree(options); return NULL; } cp = buf; while (*cp) { /* Skip blanks before the parameter name */ if (isspace((unsigned char)*cp)) { cp++; continue; } /* Get the parameter name */ pname = cp; while (*cp) { if (*cp == '=') { break; } if (isspace((unsigned char)*cp)) { *cp++ = '\0'; while (*cp) { if (!isspace((unsigned char)*cp)) { break; } cp++; } break; } cp++; } /* Check that there is a following '=' */ if (*cp != '=') { printfGTMPQExpBuffer( errorMessage, libpq_gettext("missing \"=\" after \"%s\" in connection info string\n"), pname); GTMPQconninfoFree(options); free(buf); return NULL; } *cp++ = '\0'; /* Skip blanks after the '=' */ while (*cp) { if (!isspace((unsigned char)*cp)) { break; } cp++; } /* Get the parameter value */ pval = cp; if (*cp != '\'') { cp2 = pval; while (*cp) { if (isspace((unsigned char)*cp)) { *cp++ = '\0'; break; } if (*cp == '\\') { cp++; if (*cp != '\0') { *cp2++ = *cp++; } } else { *cp2++ = *cp++; } } *cp2 = '\0'; } else { cp2 = pval; cp++; for (;;) { if (*cp == '\0') { printfGTMPQExpBuffer( errorMessage, libpq_gettext("unterminated quoted string in connection info string\n")); GTMPQconninfoFree(options); free(buf); return NULL; } if (*cp == '\\') { cp++; if (*cp != '\0') *cp2++ = *cp++; continue; } if (*cp == '\'') { *cp2 = '\0'; cp++; break; } *cp2++ = *cp++; } } /* * Now we have the name and the value. Search for the param record. */ for (option = options; option->keyword != NULL; option++) { if (strcmp(option->keyword, pname) == 0) { break; } } if (option->keyword == NULL) { printfGTMPQExpBuffer(errorMessage, libpq_gettext("invalid connection option \"%s\"\n"), pname); GTMPQconninfoFree(options); free(buf); return NULL; } /* * Store the value */ if (option->val) { free(option->val); } option->val = strdup(pval); if (!option->val) { printfGTMPQExpBuffer(errorMessage, libpq_gettext("out of memory\n")); GTMPQconninfoFree(options); free(buf); return NULL; } } /* Done with the modifiable input string */ free(buf); return options; } static char* conninfo_getval(GTMPQconninfoOption* connOptions, const char* keyword) { GTMPQconninfoOption* option; for (option = connOptions; option->keyword != NULL; option++) { if (strcmp(option->keyword, keyword) == 0) { return option->val; } } return NULL; } void GTMPQconninfoFree(GTMPQconninfoOption* connOptions) { GTMPQconninfoOption* option; if (connOptions == NULL) { return; } for (option = connOptions; option->keyword != NULL; option++) { if (option->val != NULL) { free(option->val); } } free(connOptions); } char* GTMPQhost(const GTM_Conn* conn) { if (!conn) { return NULL; } return conn->pghost; } char* GTMPQport(const GTM_Conn* conn) { if (!conn) { return NULL; } return conn->pgport; } ConnStatusType GTMPQstatus(const GTM_Conn* conn) { if (!conn) { return CONNECTION_BAD; } return conn->status; } int GTMPQispostmaster(const GTM_Conn* conn) { if (!conn) { return 0; } return conn->is_postmaster; } char* GTMPQerrorMessage(const GTM_Conn* conn) { if (!conn) { return libpq_gettext("connection pointer is NULL\n"); } return conn->errorMessage.data; } int GTMPQsocket(const GTM_Conn* conn) { if (!conn) { return -1; } return conn->sock; } void GTMPQtrace(GTM_Conn* conn, FILE* debug_port) { if (conn == NULL) { return; } GTMPQuntrace(conn); conn->Pfdebug = debug_port; } void GTMPQuntrace(GTM_Conn* conn) { if (conn == NULL) { return; } if (conn->Pfdebug) { fflush(conn->Pfdebug); conn->Pfdebug = NULL; } }