diff options
-rw-r--r-- | doc/src/sgml/libpq.sgml | 78 | ||||
-rw-r--r-- | src/interfaces/libpq/fe-connect.c | 249 | ||||
-rw-r--r-- | src/interfaces/libpq/libpq-int.h | 5 |
3 files changed, 164 insertions, 168 deletions
diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml index 49de975e7f5..5e7931ba901 100644 --- a/doc/src/sgml/libpq.sgml +++ b/doc/src/sgml/libpq.sgml @@ -303,9 +303,9 @@ PostgresPollingStatusType PQconnectPoll(PGconn *conn); <itemizedlist> <listitem> <para> - The <literal>hostaddr</literal> and <literal>host</literal> parameters are used appropriately to ensure that - name and reverse name queries are not made. See the documentation of - these parameters in <xref linkend="libpq-paramkeywords"/> for details. + The <literal>hostaddr</literal> parameter must be used appropriately + to prevent DNS queries from being made. See the documentation of + this parameter in <xref linkend="libpq-paramkeywords"/> for details. </para> </listitem> @@ -318,7 +318,7 @@ PostgresPollingStatusType PQconnectPoll(PGconn *conn); <listitem> <para> - You ensure that the socket is in the appropriate state + You must ensure that the socket is in the appropriate state before calling <function>PQconnectPoll</function>, as described below. </para> </listitem> @@ -326,24 +326,27 @@ PostgresPollingStatusType PQconnectPoll(PGconn *conn); </para> <para> - Note: use of <function>PQconnectStartParams</function> is analogous to - <function>PQconnectStart</function> shown below. + To begin a nonblocking connection request, + call <function>PQconnectStart</function> + or <function>PQconnectStartParams</function>. If the result is null, + then <application>libpq</application> has been unable to allocate a + new <structname>PGconn</structname> structure. Otherwise, a + valid <structname>PGconn</structname> pointer is returned (though not + yet representing a valid connection to the database). Next + call <literal>PQstatus(conn)</literal>. If the result + is <symbol>CONNECTION_BAD</symbol>, the connection attempt has already + failed, typically because of invalid connection parameters. </para> <para> - To begin a nonblocking connection request, call <literal>conn = PQconnectStart("<replaceable>connection_info_string</replaceable>")</literal>. - If <varname>conn</varname> is null, then <application>libpq</application> has been unable to allocate a new <structname>PGconn</structname> - structure. Otherwise, a valid <structname>PGconn</structname> pointer is returned (though not yet - representing a valid connection to the database). On return from - <function>PQconnectStart</function>, call <literal>status = PQstatus(conn)</literal>. If <varname>status</varname> equals - <symbol>CONNECTION_BAD</symbol>, <function>PQconnectStart</function> has failed. - </para> - - <para> - If <function>PQconnectStart</function> succeeds, the next stage is to poll - <application>libpq</application> so that it can proceed with the connection sequence. + If <function>PQconnectStart</function> + or <function>PQconnectStartParams</function> succeeds, the next stage + is to poll <application>libpq</application> so that it can proceed with + the connection sequence. Use <function>PQsocket(conn)</function> to obtain the descriptor of the socket underlying the database connection. + (Caution: do not assume that the socket remains the same + across <function>PQconnectPoll</function> calls.) Loop thus: If <function>PQconnectPoll(conn)</function> last returned <symbol>PGRES_POLLING_READING</symbol>, wait until the socket is ready to read (as indicated by <function>select()</function>, <function>poll()</function>, or @@ -352,9 +355,8 @@ PostgresPollingStatusType PQconnectPoll(PGconn *conn); Conversely, if <function>PQconnectPoll(conn)</function> last returned <symbol>PGRES_POLLING_WRITING</symbol>, wait until the socket is ready to write, then call <function>PQconnectPoll(conn)</function> again. - If you have yet to call - <function>PQconnectPoll</function>, i.e., just after the call to - <function>PQconnectStart</function>, behave as if it last returned + On the first iteration, i.e. if you have yet to call + <function>PQconnectPoll</function>, behave as if it last returned <symbol>PGRES_POLLING_WRITING</symbol>. Continue this loop until <function>PQconnectPoll(conn)</function> returns <symbol>PGRES_POLLING_FAILED</symbol>, indicating the connection procedure @@ -479,10 +481,12 @@ switch(PQstatus(conn)) </para> <para> - Note that if <function>PQconnectStart</function> returns a non-null pointer, you must call - <function>PQfinish</function> when you are finished with it, in order to dispose of - the structure and any associated memory blocks. This must be done even if - the connection attempt fails or is abandoned. + Note that when <function>PQconnectStart</function> + or <function>PQconnectStartParams</function> returns a non-null + pointer, you must call <function>PQfinish</function> when you are + finished with it, in order to dispose of the structure and any + associated memory blocks. This must be done even if the connection + attempt fails or is abandoned. </para> </listitem> </varlistentry> @@ -913,7 +917,8 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname It is possible to specify multiple hosts to connect to, so that they are tried in the given order. In the Keyword/Value format, the <literal>host</literal>, <literal>hostaddr</literal>, and <literal>port</literal> options accept a comma-separated - list of values. The same number of elements must be given in each option, such + list of values. The same number of elements must be given in each + option that is specified, such that e.g. the first <literal>hostaddr</literal> corresponds to the first host name, the second <literal>hostaddr</literal> corresponds to the second host name, and so forth. As an exception, if only one <literal>port</literal> is specified, it @@ -922,9 +927,13 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname <para> In the connection URI format, you can list multiple <literal>host:port</literal> pairs - separated by commas, in the <literal>host</literal> component of the URI. In either - format, a single host name can also translate to multiple network addresses. A - common example of this is a host that has both an IPv4 and an IPv6 address. + separated by commas, in the <literal>host</literal> component of the URI. + </para> + + <para> + In either format, a single host name can translate to multiple network + addresses. A common example of this is a host that has both an IPv4 and + an IPv6 address. </para> <para> @@ -958,9 +967,8 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname Name of host to connect to.<indexterm><primary>host name</primary></indexterm> If a host name begins with a slash, it specifies Unix-domain communication rather than TCP/IP communication; the value is the - name of the directory in which the socket file is stored. If - multiple host names are specified, each will be tried in turn in - the order given. The default behavior when <literal>host</literal> is + name of the directory in which the socket file is stored. + The default behavior when <literal>host</literal> is not specified, or is empty, is to connect to a Unix-domain socket<indexterm><primary>Unix domain socket</primary></indexterm> in <filename>/tmp</filename> (or whatever socket directory was specified @@ -997,8 +1005,12 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname <itemizedlist> <listitem> <para> - If <literal>host</literal> is specified without <literal>hostaddr</literal>, - a host name lookup occurs. + If <literal>host</literal> is specified + without <literal>hostaddr</literal>, a host name lookup occurs. + (When using <function>PQconnectPoll</function>, the lookup occurs + when <function>PQconnectPoll</function> first considers this host + name, and it may cause <function>PQconnectPoll</function> to block + for a significant amount of time.) </para> </listitem> <listitem> diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c index 9315a27561f..a8048ffad20 100644 --- a/src/interfaces/libpq/fe-connect.c +++ b/src/interfaces/libpq/fe-connect.c @@ -360,7 +360,7 @@ static PGconn *makeEmptyPGconn(void); static bool fillPGconn(PGconn *conn, PQconninfoOption *connOptions); static void freePGconn(PGconn *conn); static void closePGconn(PGconn *conn); -static void release_all_addrinfo(PGconn *conn); +static void release_conn_addrinfo(PGconn *conn); static void sendTerminateConn(PGconn *conn); static PQconninfoOption *conninfo_init(PQExpBuffer errorMessage); static PQconninfoOption *parse_connection_string(const char *conninfo, @@ -1742,10 +1742,6 @@ setKeepalivesWin32(PGconn *conn) static int connectDBStart(PGconn *conn) { - char portstr[MAXPGPATH]; - int ret; - int i; - if (!conn) return 0; @@ -1765,101 +1761,6 @@ connectDBStart(PGconn *conn) resetPQExpBuffer(&conn->errorMessage); /* - * Look up socket addresses for each possible host using - * pg_getaddrinfo_all. - */ - for (i = 0; i < conn->nconnhost; ++i) - { - pg_conn_host *ch = &conn->connhost[i]; - struct addrinfo hint; - int thisport; - - /* Initialize hint structure */ - MemSet(&hint, 0, sizeof(hint)); - hint.ai_socktype = SOCK_STREAM; - hint.ai_family = AF_UNSPEC; - - /* Figure out the port number we're going to use. */ - if (ch->port == NULL || ch->port[0] == '\0') - thisport = DEF_PGPORT; - else - { - thisport = atoi(ch->port); - if (thisport < 1 || thisport > 65535) - { - appendPQExpBuffer(&conn->errorMessage, - libpq_gettext("invalid port number: \"%s\"\n"), - ch->port); - conn->options_valid = false; - goto connect_errReturn; - } - } - snprintf(portstr, sizeof(portstr), "%d", thisport); - - /* Use pg_getaddrinfo_all() to resolve the address */ - ret = 1; - switch (ch->type) - { - case CHT_HOST_NAME: - ret = pg_getaddrinfo_all(ch->host, portstr, &hint, &ch->addrlist); - if (ret || !ch->addrlist) - appendPQExpBuffer(&conn->errorMessage, - libpq_gettext("could not translate host name \"%s\" to address: %s\n"), - ch->host, gai_strerror(ret)); - break; - - case CHT_HOST_ADDRESS: - hint.ai_flags = AI_NUMERICHOST; - ret = pg_getaddrinfo_all(ch->hostaddr, portstr, &hint, &ch->addrlist); - if (ret || !ch->addrlist) - appendPQExpBuffer(&conn->errorMessage, - libpq_gettext("could not parse network address \"%s\": %s\n"), - ch->hostaddr, gai_strerror(ret)); - break; - - case CHT_UNIX_SOCKET: -#ifdef HAVE_UNIX_SOCKETS - hint.ai_family = AF_UNIX; - UNIXSOCK_PATH(portstr, thisport, ch->host); - if (strlen(portstr) >= UNIXSOCK_PATH_BUFLEN) - { - appendPQExpBuffer(&conn->errorMessage, - libpq_gettext("Unix-domain socket path \"%s\" is too long (maximum %d bytes)\n"), - portstr, - (int) (UNIXSOCK_PATH_BUFLEN - 1)); - conn->options_valid = false; - goto connect_errReturn; - } - - /* - * NULL hostname tells pg_getaddrinfo_all to parse the service - * name as a Unix-domain socket path. - */ - ret = pg_getaddrinfo_all(NULL, portstr, &hint, &ch->addrlist); - if (ret || !ch->addrlist) - appendPQExpBuffer(&conn->errorMessage, - libpq_gettext("could not translate Unix-domain socket path \"%s\" to address: %s\n"), - portstr, gai_strerror(ret)); - break; -#else - Assert(false); - conn->options_valid = false; - goto connect_errReturn; -#endif - } - if (ret || !ch->addrlist) - { - if (ch->addrlist) - { - pg_freeaddrinfo_all(hint.ai_family, ch->addrlist); - ch->addrlist = NULL; - } - conn->options_valid = false; - goto connect_errReturn; - } - } - - /* * Set up to try to connect to the first host. (Setting whichhost = -1 is * a bit of a cheat, but PQconnectPoll will advance it to 0 before * anything else looks at it.) @@ -2157,6 +2058,12 @@ keep_going: /* We will come back to here until there is /* Time to advance to next connhost[] entry? */ if (conn->try_next_host) { + pg_conn_host *ch; + struct addrinfo hint; + int thisport; + int ret; + char portstr[MAXPGPATH]; + if (conn->whichhost + 1 >= conn->nconnhost) { /* @@ -2166,10 +2073,100 @@ keep_going: /* We will come back to here until there is goto error_return; } conn->whichhost++; - conn->addr_cur = conn->connhost[conn->whichhost].addrlist; - /* If no addresses for this host, just try the next one */ - if (conn->addr_cur == NULL) - goto keep_going; + + /* Drop any address info for previous host */ + release_conn_addrinfo(conn); + + /* + * Look up info for the new host. On failure, log the problem in + * conn->errorMessage, then loop around to try the next host. (Note + * we don't clear try_next_host until we've succeeded.) + */ + ch = &conn->connhost[conn->whichhost]; + + /* Initialize hint structure */ + MemSet(&hint, 0, sizeof(hint)); + hint.ai_socktype = SOCK_STREAM; + conn->addrlist_family = hint.ai_family = AF_UNSPEC; + + /* Figure out the port number we're going to use. */ + if (ch->port == NULL || ch->port[0] == '\0') + thisport = DEF_PGPORT; + else + { + thisport = atoi(ch->port); + if (thisport < 1 || thisport > 65535) + { + appendPQExpBuffer(&conn->errorMessage, + libpq_gettext("invalid port number: \"%s\"\n"), + ch->port); + goto keep_going; + } + } + snprintf(portstr, sizeof(portstr), "%d", thisport); + + /* Use pg_getaddrinfo_all() to resolve the address */ + switch (ch->type) + { + case CHT_HOST_NAME: + ret = pg_getaddrinfo_all(ch->host, portstr, &hint, + &conn->addrlist); + if (ret || !conn->addrlist) + { + appendPQExpBuffer(&conn->errorMessage, + libpq_gettext("could not translate host name \"%s\" to address: %s\n"), + ch->host, gai_strerror(ret)); + goto keep_going; + } + break; + + case CHT_HOST_ADDRESS: + hint.ai_flags = AI_NUMERICHOST; + ret = pg_getaddrinfo_all(ch->hostaddr, portstr, &hint, + &conn->addrlist); + if (ret || !conn->addrlist) + { + appendPQExpBuffer(&conn->errorMessage, + libpq_gettext("could not parse network address \"%s\": %s\n"), + ch->hostaddr, gai_strerror(ret)); + goto keep_going; + } + break; + + case CHT_UNIX_SOCKET: +#ifdef HAVE_UNIX_SOCKETS + conn->addrlist_family = hint.ai_family = AF_UNIX; + UNIXSOCK_PATH(portstr, thisport, ch->host); + if (strlen(portstr) >= UNIXSOCK_PATH_BUFLEN) + { + appendPQExpBuffer(&conn->errorMessage, + libpq_gettext("Unix-domain socket path \"%s\" is too long (maximum %d bytes)\n"), + portstr, + (int) (UNIXSOCK_PATH_BUFLEN - 1)); + goto keep_going; + } + + /* + * NULL hostname tells pg_getaddrinfo_all to parse the service + * name as a Unix-domain socket path. + */ + ret = pg_getaddrinfo_all(NULL, portstr, &hint, + &conn->addrlist); + if (ret || !conn->addrlist) + { + appendPQExpBuffer(&conn->errorMessage, + libpq_gettext("could not translate Unix-domain socket path \"%s\" to address: %s\n"), + portstr, gai_strerror(ret)); + goto keep_going; + } +#else + Assert(false); +#endif + break; + } + + /* OK, scan this addrlist for a working server address */ + conn->addr_cur = conn->addrlist; reset_connection_state_machine = true; conn->try_next_host = false; } @@ -3134,8 +3131,8 @@ keep_going: /* We will come back to here until there is return PGRES_POLLING_READING; } - /* We can release the address lists now. */ - release_all_addrinfo(conn); + /* We can release the address list now. */ + release_conn_addrinfo(conn); /* We are open for business! */ conn->status = CONNECTION_OK; @@ -3197,8 +3194,8 @@ keep_going: /* We will come back to here until there is return PGRES_POLLING_READING; } - /* We can release the address lists now. */ - release_all_addrinfo(conn); + /* We can release the address list now. */ + release_conn_addrinfo(conn); /* We are open for business! */ conn->status = CONNECTION_OK; @@ -3227,6 +3224,9 @@ keep_going: /* We will come back to here until there is goto keep_going; } + /* We can release the address list now. */ + release_conn_addrinfo(conn); + /* We are open for business! */ conn->status = CONNECTION_OK; return PGRES_POLLING_OK; @@ -3300,9 +3300,6 @@ keep_going: /* We will come back to here until there is PQclear(res); termPQExpBuffer(&savedMessage); - /* We can release the address lists now. */ - release_all_addrinfo(conn); - /* * Finish reading any remaining messages before being * considered as ready. @@ -3639,32 +3636,18 @@ freePGconn(PGconn *conn) } /* - * release_all_addrinfo - * - free addrinfo of all hostconn elements. + * release_conn_addrinfo + * - Free any addrinfo list in the PGconn. */ - static void -release_all_addrinfo(PGconn *conn) +release_conn_addrinfo(PGconn *conn) { - if (conn->connhost != NULL) + if (conn->addrlist) { - int i; - - for (i = 0; i < conn->nconnhost; ++i) - { - int family = AF_UNSPEC; - -#ifdef HAVE_UNIX_SOCKETS - if (conn->connhost[i].type == CHT_UNIX_SOCKET) - family = AF_UNIX; -#endif - - pg_freeaddrinfo_all(family, - conn->connhost[i].addrlist); - conn->connhost[i].addrlist = NULL; - } + pg_freeaddrinfo_all(conn->addrlist_family, conn->addrlist); + conn->addrlist = NULL; + conn->addr_cur = NULL; /* for safety */ } - conn->addr_cur = NULL; } /* @@ -3723,7 +3706,7 @@ closePGconn(PGconn *conn) conn->xactStatus = PQTRANS_IDLE; pqClearAsyncResult(conn); /* deallocate result */ resetPQExpBuffer(&conn->errorMessage); - release_all_addrinfo(conn); + release_conn_addrinfo(conn); /* Reset all state obtained from server, too */ pqDropServerData(conn); diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h index bc60373cb0f..fb04c8c61dc 100644 --- a/src/interfaces/libpq/libpq-int.h +++ b/src/interfaces/libpq/libpq-int.h @@ -312,7 +312,6 @@ typedef struct pg_conn_host char *password; /* password for this host, read from the * password file; NULL if not sought or not * found in password file. */ - struct addrinfo *addrlist; /* list of possible backend addresses */ } pg_conn_host; /* @@ -412,7 +411,9 @@ struct pg_conn /* Transient state needed while establishing connection */ bool try_next_addr; /* time to advance to next address/host? */ bool try_next_host; /* time to advance to next connhost[]? */ - struct addrinfo *addr_cur; /* backend address currently being tried */ + struct addrinfo *addrlist; /* list of addresses for current connhost */ + struct addrinfo *addr_cur; /* the one currently being tried */ + int addrlist_family; /* needed to know how to free addrlist */ PGSetenvStatusType setenv_state; /* for 2.0 protocol only */ const PQEnvironmentOption *next_eo; bool send_appname; /* okay to send application_name? */ |