diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c index aa1c0ced8b..79644b3413 100644 --- a/src/interfaces/libpq/fe-connect.c +++ b/src/interfaces/libpq/fe-connect.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/interfaces/libpq/fe-connect.c,v 1.380 2009/11/29 20:14:53 petere Exp $ + * $PostgreSQL: pgsql/src/interfaces/libpq/fe-connect.c,v 1.381 2009/12/02 04:38:35 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -83,8 +83,18 @@ static int ldapServiceLookup(const char *purl, PQconninfoOption *options, #define PGPASSFILE "pgpass.conf" #endif -/* fall back options if they are not specified by arguments or defined - by environment variables */ +/* + * Pre-8.5 servers will return this SQLSTATE if asked to set + * application_name in a startup packet. We hard-wire the value rather + * than looking into errcodes.h since it reflects historical behavior + * rather than that of the current code. + */ +#define ERRCODE_APPNAME_UNKNOWN "42704" + +/* + * fall back options if they are not specified by arguments or defined + * by environment variables + */ #define DefaultHost "localhost" #define DefaultTty "" #define DefaultOption "" @@ -262,7 +272,6 @@ static int parseServiceInfo(PQconninfoOption *options, static char *pwdfMatchesString(char *buf, char *token); static char *PasswordFromFile(char *hostname, char *port, char *dbname, char *username); -static PostgresPollingStatusType pqAppnamePoll(PGconn *conn); static void default_threadlock(int acquire); @@ -901,6 +910,7 @@ connectDBStart(PGconn *conn) conn->addr_cur = addrs; conn->addrlist_family = hint.ai_family; conn->pversion = PG_PROTOCOL(3, 0); + conn->send_appname = true; conn->status = CONNECTION_NEEDED; /* @@ -1075,7 +1085,7 @@ PQconnectPoll(PGconn *conn) case CONNECTION_MADE: break; - /* pqSetenvPoll/pqAppnamePoll will decide whether to proceed. */ + /* We allow pqSetenvPoll to decide whether to proceed. */ case CONNECTION_SETENV: break; @@ -1880,6 +1890,35 @@ keep_going: /* We will come back to here until there is if (res->resultStatus != PGRES_FATAL_ERROR) appendPQExpBuffer(&conn->errorMessage, libpq_gettext("unexpected message from server during startup\n")); + else if (conn->send_appname && + (conn->appname || conn->fbappname)) + { + /* + * If we tried to send application_name, check to see + * if the error is about that --- pre-8.5 servers will + * reject it at this stage of the process. If so, + * close the connection and retry without sending + * application_name. We could possibly get a false + * SQLSTATE match here and retry uselessly, but there + * seems no great harm in that; we'll just get the + * same error again if it's unrelated. + */ + const char *sqlstate; + + sqlstate = PQresultErrorField(res, PG_DIAG_SQLSTATE); + if (sqlstate && + strcmp(sqlstate, ERRCODE_APPNAME_UNKNOWN) == 0) + { + PQclear(res); + conn->send_appname = false; + /* Must drop the old connection */ + pqsecure_close(conn); + closesocket(conn->sock); + conn->sock = -1; + conn->status = CONNECTION_NEEDED; + goto keep_going; + } + } /* * if the resultStatus is FATAL, then conn->errorMessage @@ -1899,12 +1938,6 @@ keep_going: /* We will come back to here until there is conn->addrlist = NULL; conn->addr_cur = NULL; - /* - * Note: To avoid changing the set of application-visible - * connection states, v2 environment setup and v3 application - * name setup both happen in the CONNECTION_SETENV state. - */ - /* Fire up post-connection housekeeping if needed */ if (PG_PROTOCOL_MAJOR(conn->pversion) < 3) { @@ -1913,13 +1946,6 @@ keep_going: /* We will come back to here until there is conn->next_eo = EnvironmentOptions; return PGRES_POLLING_WRITING; } - else if (conn->sversion >= 80500 && - (conn->appname || conn->fbappname)) - { - conn->status = CONNECTION_SETENV; - conn->appname_state = APPNAME_STATE_CMD_SEND; - return PGRES_POLLING_WRITING; - } /* Otherwise, we are open for business! */ conn->status = CONNECTION_OK; @@ -1927,45 +1953,36 @@ keep_going: /* We will come back to here until there is } case CONNECTION_SETENV: + + /* + * Do post-connection housekeeping (only needed in protocol 2.0). + * + * We pretend that the connection is OK for the duration of these + * queries. + */ + conn->status = CONNECTION_OK; + + switch (pqSetenvPoll(conn)) { - PostgresPollingStatusType ret; + case PGRES_POLLING_OK: /* Success */ + break; - /* - * Do post-connection housekeeping (only needed in protocol - * 2.0), or send the application name in PG8.5+. - * - * We pretend that the connection is OK for the duration of - * these queries. - */ - conn->status = CONNECTION_OK; + case PGRES_POLLING_READING: /* Still going */ + conn->status = CONNECTION_SETENV; + return PGRES_POLLING_READING; - if (PG_PROTOCOL_MAJOR(conn->pversion) < 3) - ret = pqSetenvPoll(conn); - else /* must be here to send app name */ - ret = pqAppnamePoll(conn); + case PGRES_POLLING_WRITING: /* Still going */ + conn->status = CONNECTION_SETENV; + return PGRES_POLLING_WRITING; - switch (ret) - { - case PGRES_POLLING_OK: /* Success */ - break; - - case PGRES_POLLING_READING: /* Still going */ - conn->status = CONNECTION_SETENV; - return PGRES_POLLING_READING; - - case PGRES_POLLING_WRITING: /* Still going */ - conn->status = CONNECTION_SETENV; - return PGRES_POLLING_WRITING; - - default: - goto error_return; - } - - /* We are open for business! */ - conn->status = CONNECTION_OK; - return PGRES_POLLING_OK; + default: + goto error_return; } + /* We are open for business! */ + conn->status = CONNECTION_OK; + return PGRES_POLLING_OK; + default: appendPQExpBuffer(&conn->errorMessage, libpq_gettext("invalid connection state %d, " @@ -2031,7 +2048,6 @@ makeEmptyPGconn(void) conn->options_valid = false; conn->nonblocking = false; conn->setenv_state = SETENV_STATE_IDLE; - conn->appname_state = APPNAME_STATE_IDLE; conn->client_encoding = PG_SQL_ASCII; conn->std_strings = false; /* unless server says differently */ conn->verbosity = PQERRORS_DEFAULT; @@ -4048,129 +4064,6 @@ pqGetHomeDirectory(char *buf, int bufsize) #endif } -/* - * pqAppnamePoll - * - * Polls the process of passing the application name to the backend. - * - * Ideally, we'd include the appname in the startup packet, but that would - * cause old backends to reject the unknown parameter. So we send it in a - * separate query after we have determined the backend version. Once there - * is no interest in pre-8.5 backends, this should be folded into the startup - * packet logic. - */ -static PostgresPollingStatusType -pqAppnamePoll(PGconn *conn) -{ - PGresult *res; - - if (conn == NULL || conn->status == CONNECTION_BAD) - return PGRES_POLLING_FAILED; - - /* Check whether there is any data for us */ - switch (conn->appname_state) - { - /* This is a reading state. */ - case APPNAME_STATE_CMD_WAIT: - { - /* Load waiting data */ - int n = pqReadData(conn); - - if (n < 0) - goto error_return; - if (n == 0) - return PGRES_POLLING_READING; - - break; - } - - /* This is a writing state, so we just proceed. */ - case APPNAME_STATE_CMD_SEND: - break; - - /* Should we raise an error if called when not active? */ - case APPNAME_STATE_IDLE: - return PGRES_POLLING_OK; - - default: - printfPQExpBuffer(&conn->errorMessage, - libpq_gettext("invalid appname state %d, " - "probably indicative of memory corruption\n"), - conn->appname_state); - goto error_return; - } - - /* We will loop here until there is nothing left to do in this call. */ - for (;;) - { - switch (conn->appname_state) - { - case APPNAME_STATE_CMD_SEND: - { - const char *val; - char escVal[NAMEDATALEN*2 + 1]; - char setQuery[NAMEDATALEN*2 + 26 + 1]; - - /* Use appname if present, otherwise use fallback */ - val = conn->appname ? conn->appname : conn->fbappname; - - /* - * Escape the data as needed. We can truncate to NAMEDATALEN, - * so there's no need to cope with malloc. - */ - PQescapeStringConn(conn, escVal, val, NAMEDATALEN, NULL); - - sprintf(setQuery, "SET application_name = '%s'", escVal); - - if (!PQsendQuery(conn, setQuery)) - goto error_return; - - conn->appname_state = APPNAME_STATE_CMD_WAIT; - break; - } - - case APPNAME_STATE_CMD_WAIT: - { - if (PQisBusy(conn)) - return PGRES_POLLING_READING; - - res = PQgetResult(conn); - - if (res) - { - if (PQresultStatus(res) != PGRES_COMMAND_OK) - { - PQclear(res); - goto error_return; - } - PQclear(res); - /* Keep reading until PQgetResult returns NULL */ - } - else - { - /* Query finished, so we're done */ - conn->appname_state = APPNAME_STATE_IDLE; - return PGRES_POLLING_OK; - } - break; - } - - default: - printfPQExpBuffer(&conn->errorMessage, - libpq_gettext("invalid appname state %d, " - "probably indicative of memory corruption\n"), - conn->appname_state); - goto error_return; - } - } - - /* Unreachable */ - -error_return: - conn->appname_state = APPNAME_STATE_IDLE; - return PGRES_POLLING_FAILED; -} - /* * To keep the API consistent, the locking stubs are always provided, even * if they are not required. diff --git a/src/interfaces/libpq/fe-protocol3.c b/src/interfaces/libpq/fe-protocol3.c index 10e5edd756..bec8c019a8 100644 --- a/src/interfaces/libpq/fe-protocol3.c +++ b/src/interfaces/libpq/fe-protocol3.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/interfaces/libpq/fe-protocol3.c,v 1.39 2009/06/11 14:49:14 momjian Exp $ + * $PostgreSQL: pgsql/src/interfaces/libpq/fe-protocol3.c,v 1.40 2009/12/02 04:38:35 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -1882,6 +1882,7 @@ build_startup_packet(const PGconn *conn, char *packet, { int packet_len = 0; const PQEnvironmentOption *next_eo; + const char *val; /* Protocol version comes first. */ if (packet) @@ -1893,50 +1894,38 @@ build_startup_packet(const PGconn *conn, char *packet, packet_len += sizeof(ProtocolVersion); /* Add user name, database name, options */ + +#define ADD_STARTUP_OPTION(optname, optval) \ + do { \ + if (packet) \ + strcpy(packet + packet_len, optname); \ + packet_len += strlen(optname) + 1; \ + if (packet) \ + strcpy(packet + packet_len, optval); \ + packet_len += strlen(optval) + 1; \ + } while(0) + if (conn->pguser && conn->pguser[0]) - { - if (packet) - strcpy(packet + packet_len, "user"); - packet_len += strlen("user") + 1; - if (packet) - strcpy(packet + packet_len, conn->pguser); - packet_len += strlen(conn->pguser) + 1; - } + ADD_STARTUP_OPTION("user", conn->pguser); if (conn->dbName && conn->dbName[0]) - { - if (packet) - strcpy(packet + packet_len, "database"); - packet_len += strlen("database") + 1; - if (packet) - strcpy(packet + packet_len, conn->dbName); - packet_len += strlen(conn->dbName) + 1; - } + ADD_STARTUP_OPTION("database", conn->dbName); if (conn->pgoptions && conn->pgoptions[0]) + ADD_STARTUP_OPTION("options", conn->pgoptions); + if (conn->send_appname) { - if (packet) - strcpy(packet + packet_len, "options"); - packet_len += strlen("options") + 1; - if (packet) - strcpy(packet + packet_len, conn->pgoptions); - packet_len += strlen(conn->pgoptions) + 1; + /* Use appname if present, otherwise use fallback */ + val = conn->appname ? conn->appname : conn->fbappname; + if (val && val[0]) + ADD_STARTUP_OPTION("application_name", val); } /* Add any environment-driven GUC settings needed */ for (next_eo = options; next_eo->envName; next_eo++) { - const char *val; - if ((val = getenv(next_eo->envName)) != NULL) { if (pg_strcasecmp(val, "default") != 0) - { - if (packet) - strcpy(packet + packet_len, next_eo->pgName); - packet_len += strlen(next_eo->pgName) + 1; - if (packet) - strcpy(packet + packet_len, val); - packet_len += strlen(val) + 1; - } + ADD_STARTUP_OPTION(next_eo->pgName, val); } } diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h index a496aebbba..36d443970b 100644 --- a/src/interfaces/libpq/libpq-int.h +++ b/src/interfaces/libpq/libpq-int.h @@ -12,7 +12,7 @@ * Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/interfaces/libpq/libpq-int.h,v 1.145 2009/11/28 23:38:08 tgl Exp $ + * $PostgreSQL: pgsql/src/interfaces/libpq/libpq-int.h,v 1.146 2009/12/02 04:38:35 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -244,14 +244,6 @@ typedef enum SETENV_STATE_IDLE } PGSetenvStatusType; -/* PGAppnameStatusType defines the state of the PQAppname state machine */ -typedef enum -{ - APPNAME_STATE_CMD_SEND, /* About to send the appname */ - APPNAME_STATE_CMD_WAIT, /* Waiting for above send to complete */ - APPNAME_STATE_IDLE -} PGAppnameStatusType; - /* Typedef for the EnvironmentOptions[] array */ typedef struct PQEnvironmentOption { @@ -359,8 +351,8 @@ struct pg_conn 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 */ - PGAppnameStatusType appname_state; const PQEnvironmentOption *next_eo; + bool send_appname; /* okay to send application_name? */ /* Miscellaneous stuff */ int be_pid; /* PID of backend --- needed for cancels */