diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml index 8f926922241e84c98fabc07053f6a51f91b48848..560feffadc988300919c1e05af992660301380ea 100644 --- a/doc/src/sgml/libpq.sgml +++ b/doc/src/sgml/libpq.sgml @@ -57,14 +57,13 @@ header file <FileName>libpq-fe.h</FileName> and must link with the <synopsis> PGconn *PQconnectdb(const char *conninfo) </synopsis> - This routine opens a new database connection using the parameters - taken from the string <literal>conninfo</literal>. Unlike PQsetdbLogin() - below, the parameter set - can be extended without changing the function signature, so use - of this routine is prefered for application programming. The passed - string can be empty to use all default - parameters, or it can contain one or more parameter settings - separated by whitespace. + This routine opens a new database connection using the parameters taken + from the string <literal>conninfo</literal>. Unlike PQsetdbLogin() below, + the parameter set can be extended without changing the function signature, + so use either of this routine or the non-blocking analogues PQconnectStart + / PQconnectPoll is prefered for application programming. The passed string + can be empty to use all default parameters, or it can contain one or more + parameter settings separated by whitespace. </Para> <Para> Each parameter setting is in the form <literal>keyword = value</literal>. @@ -80,8 +79,38 @@ PGconn *PQconnectdb(const char *conninfo) <term><literal>host</literal></term> <ListItem> <Para> - Host to connect to. If a non-zero-length string is specified, TCP/IP communication is used. - Without a host name, libpq will connect using a local Unix domain socket. + Name of host to connect to. If a non-zero-length string is specified, TCP/IP + communication is used. Using this parameter causes a hostname look-up. + See hostaddr. + </Para> + </ListItem> + </VarListEntry> + + <VarListEntry> + <term><literal>hostaddr</literal></term> + <ListItem> + <Para> + IP address of host to connect to. This should be in standard + numbers-and-dots form, as used by the BSD functions inet_aton et al. If + a non-zero-length string is specified, TCP/IP communication is used. + </Para> + <Para> + Using hostaddr instead of host allows the application to avoid a host + name look-up, which may be important in applications with time + constraints. However, Kerberos authentication requires the host + name. The following therefore applies. If host is specified without + hostaddr, a hostname look-up is forced. If hostaddr is specified without + host, the value for hostaddr gives the remote address; if Kerberos is + used, this causes a reverse name query. If both host and hostaddr are + specified, the value for hostaddr gives the remote address; the value + for host is ignored, unless Kerberos is used, in which case that value + is used for Kerberos authentication. Note that authentication is likely + to fail if libpq is passed a host name which is not the name of the + machine at hostaddr. + </Para> + <Para> + Without both a host name and host address, libpq will connect using a + local Unix domain socket. </Para> </ListItem> </VarListEntry> @@ -149,6 +178,9 @@ PGconn *PQconnectdb(const char *conninfo) The return value is a pointer to an abstract struct representing the connection to the backend. </Para> + <Para> + This function is not thread-safe. + </Para> </ListItem> <ListItem> @@ -167,6 +199,9 @@ PGconn *PQsetdbLogin(const char *pghost, This is the predecessor of <function>PQconnectdb</function> with a fixed number of parameters but the same functionality. </Para> + <Para> + This function is not thread-safe. + </Para> </ListItem> <ListItem> @@ -185,6 +220,173 @@ PGconn *PQsetdb(char *pghost, </Para> </ListItem> + <ListItem> + <Para> + <Function>PQconnectStart</Function> + <Function>PQconnectPoll</Function> + Make a connection to the database server in a non-blocking manner. +<synopsis> +PGconn *PQconnectStart(const char *conninfo) +</synopsis> +<synopsis> +PostgresPollingStatusType *PQconnectPoll(PQconn *conn) +</synopsis> + These two routines are used to open a connection to a database server such + that your application's thread of execution is not blocked on remote I/O + whilst doing so. + </Para> + <Para> + The database connection is made using the parameters taken from the string + <literal>conninfo</literal>, passed to PQconnectStart. This string is in + the same format as described above for PQconnectdb. + </Para> + <Para> + Neither PQconnectStart nor PQconnectPoll will block, as long as a number of + restrictions are met: + <ItemizedList> + <ListItem> + <Para> + The hostaddr and host parameters are used appropriately to ensure that + name and reverse name queries are not made. See the documentation of + these parameters under PQconnectdb above for details. + </Para> + </ListItem> + + <ListItem> + <Para> + If you call PQtrace, ensure that the stream object into which you trace + will not block. + </Para> + </ListItem> + + <ListItem> + <Para> + You ensure for yourself that the socket is in the appropriate state + before calling PQconnectPoll, as described below. + </Para> + </ListItem> + </ItemizedList> + </Para> + + <Para> + To begin, call conn=PQconnectStart("<connection_info_string>"). If + conn is NULL, then libpq has been unable to allocate a new PGconn + structure. Otherwise, a valid PGconn pointer is returned (though not yet + representing a valid connection to the database). On return from + PQconnectStart, call status=PQstatus(conn). If status equals + CONNECTION_BAD, PQconnectStart has failed. + </Para> + <Para> + If PQconnectStart succeeds, the next stage is to poll libpq so that it may + proceed with the connection sequence. Loop thus: Consider a connection + 'inactive' by default. If PQconnectPoll last returned PGRES_POLLING_ACTIVE, + consider it 'active' instead. If PQconnectPoll(conn) last returned + PGRES_POLLING_READING, perform a select for reading on PQsocket(conn). If + it last returned PGRES_POLLING_WRITING, perform a select for writing on + PQsocket(conn). If you have yet to call PQconnectPoll, i.e. after the call + to PQconnectStart, behave as if it last returned PGRES_POLLING_WRITING. If + the select shows that the socket is ready, consider it 'active'. If it has + been decided that this connection is 'active', call PQconnectPoll(conn) + again. If this call returns PGRES_POLLING_FAILED, the connection procedure + has failed. If this call returns PGRES_POLLING_OK, the connection has been + successfully made. + </Para> + <Para> + Note that the use of select() to ensure that the socket is ready is merely + a (likely) example; those with other facilities available, such as a + poll() call, may of course use that instead. + </Para> + <Para> + At any time during connection, the status of the connection may be + checked, by calling PQstatus. If this is CONNECTION_BAD, then the + connection procedure has failed; if this is CONNECTION_OK, then the + connection is ready. Either of these states should be equally detectable + from the return value of PQconnectPoll, as above. Other states may be + shown during (and only during) an asynchronous connection procedure. These + indicate the current stage of the connection procedure, and may be useful + to provide feedback to the user for example. These statuses may include: + <ItemizedList> + <ListItem> + <Para> + CONNECTION_STARTED: Waiting for connection to be made. + </Para> + </ListItem> + <ListItem> + <Para> + CONNECTION_MADE: Connection OK; waiting to send. + </Para> + </ListItem> + <ListItem> + <Para> + CONNECTION_AWAITING_RESPONSE: Waiting for a response from the backend. + </Para> + </ListItem> + <ListItem> + <Para> + CONNECTION_AUTH_RESPONSE: Got an authentication response; about to deal + with it. + </Para> + </ListItem> + <ListItem> + <Para> + CONNECTION_ERROR_RESPONSE: Got an error response; about to deal with it. + </Para> + </ListItem> + <ListItem> + <Para> + CONNECTION_AUTH_OK: Received authentication; waiting for ReadyForQuery etc. + </Para> + </ListItem> + <ListItem> + <Para> + CONNECTION_SETENV: Negotiating environment. + </Para> + </ListItem> + </ItemizedList> + + Note that, although these constants will remain (in order to maintain + compatibility) an application should never rely upon these appearing in a + particular order, or at all, or on the status always being one of these + documented values. An application may do something like this: +<ProgramListing> + switch(PQstatus(conn)) + { + case CONNECTION_STARTED: + feedback = "Connecting..."; + break; + + case CONNECTION_MADE: + feedback = "Connected to server..."; + break; +. +. +. + default: + feedback = "Connecting..."; + } +</ProgramListing> + </Para> + <Para> + Note that if PQconnectStart returns a non-NULL pointer, you must call + PQfinish upon that, when you are finished with it, in order to dispose of + the structure and any associated memory blocks. This must be done even if a + call to PQconnectStart or PQconnectPoll failed. + </Para> + <Para> + PQconnectPoll will currently block if libpq is compiled with USE_SSL + defined. This restriction may be removed in the future. + </Para> + <Para> + PQconnectPoll will currently block under Windows, unless libpq is compiled + with WIN32_NON_BLOCKING_CONNECTIONS defined. This code has not yet been + tested under Windows, and so it is currently off by default. This may be + changed in the future. + </Para> + <Para> + These functions are not thread-safe. + </Para> + </ListItem> + <ListItem> <Para> <Function>PQconndefaults</Function> Returns the default connection options. @@ -215,6 +417,9 @@ struct PQconninfoOption will depend on environment variables and other context. Callers must treat the connection options data as read-only. </Para> + <Para> + This function is not thread-safe. + </Para> </ListItem> <ListItem> @@ -247,6 +452,31 @@ void PQreset(PGconn *conn) </Para> </ListItem> + <ListItem> + <Para> + <Function>PQresetStart</Function> + <Function>PQresetPoll</Function> + Reset the communication port with the backend, in a non-blocking manner. +<synopsis> +int PQresetStart(PGconn *conn); +</synopsis> +<synopsis> +PostgresPollingStatusType PQresetPoll(PGconn *conn); +</synopsis> + These functions will close the connection to the backend and attempt to + reestablish a new connection to the same postmaster, using all the same + parameters previously used. This may be useful for error recovery if a + working connection is lost. They differ from PQreset (above) in that they + act in a non-blocking manner. These functions suffer from the same + restrictions as PQconnectStart and PQconnectPoll. + </Para> + <Para> + Call PQresetStart. If it returns 0, the reset has failed. If it returns 1, + poll the reset using PQresetPoll in exactly the same way as you would + create the connection using PQconnectPoll. + </Para> + </ListItem> + </ItemizedList> </Para> @@ -338,19 +568,25 @@ const char *PQoptions(const PGconn *conn) <Para> <Function>PQstatus</Function> Returns the status of the connection. - The status can be <literal>CONNECTION_OK</literal> or <literal>CONNECTION_BAD</literal>. <synopsis> ConnStatusType PQstatus(const PGconn *conn) </synopsis> </Para> <Para> -A failed connection attempt is signaled by status <literal>CONNECTION_BAD</literal>. -Ordinarily, an OK status will remain so until <function>PQfinish</function>, but a +The status can be one of a number of values. However, only two of these are +seen outside of an asynchronous connection procedure - +<literal>CONNECTION_OK</literal> or <literal>CONNECTION_BAD</literal>. A good +connection to the database has the status CONNECTION_OK. A failed connection +attempt is signaled by status <literal>CONNECTION_BAD</literal>. Ordinarily, +an OK status will remain so until <function>PQfinish</function>, but a communications failure might result in the status changing to -<literal>CONNECTION_BAD</literal> prematurely. In that case the application could -try to recover by calling <function>PQreset</function>. +<literal>CONNECTION_BAD</literal> prematurely. In that case the application +could try to recover by calling <function>PQreset</function>. </Para> +<Para> +See the entry for PQconnectStart and PQconnectPoll with regards to other status codes +that might be seen. </ListItem> <ListItem> @@ -385,6 +621,60 @@ server host, not the local host! </Para> </ListItem> + <ListItem> + <Para> + <Function>PQsetenvStart</Function> + <Function>PQsetenvPoll</Function> + <Function>PQsetenvAbort</Function> + Perform an environment negotiation. +<synopsis> +PGsetenvHandle *PQsetenvStart(PGconn *conn) +</synopsis> +<synopsis> +PostgresPollingStatusType *PQsetenvPoll(PGsetenvHandle handle) +</synopsis> +<synopsis> +void PQsetenvAbort(PGsetenvHandle handle) +</synopsis> + These two routines can be used to re-perform the environment negotiation + that occurs during the opening of a connection to a database server. I have + no idea why this might be useful (XXX anyone?) but it might prove useful + for users to be able to reconfigure their character encodings on-the-fly, + for example. + </Para> + <Para> + These functions will not block, subject to the restrictions applied to + PQconnectStart and PQconnectPoll. + </Para> + <Para> + To begin, call handle=PQsetenvStart(conn), where conn is an open connection + to the database server. If handle is NULL, then libpq has been unable to + allocate a new PGsetenvHandle structure. Otherwise, a valid handle is + returned. This handle is intended to be opaque - you may only use it to + call other functions in libpq (PQsetenvPoll, for example). + </Para> + <Para> + Poll the procedure using PQsetenvPoll, in exactly the same way as you would + create a connection using PQconnectPoll. + </Para> + <Para> + The procedure may be aborted at any time by calling PQsetenvAbort(handle). + </Para> + <Para> + These functions are not thread-safe. + </Para> + </ListItem> + + <ListItem> + <Para> + <Function>PQsetenv</Function> + Perform an environment negotiation. +<synopsis> +int PQsetenv(PGconn *conn) +</synopsis> + This function performs the same duties as PQsetenvStart and PQsetenvPoll, but + blocks to do so. It returns 1 on success and 0 on failure. + </Para> </ItemizedList> </Para> </Sect1> diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c index 7e2fb4437975807ccfbc8a5d8321c75a613d1b77..85764b1e9d405d0cbfcea057ddadbc39199163bb 100644 --- a/src/interfaces/libpq/fe-connect.c +++ b/src/interfaces/libpq/fe-connect.c @@ -7,11 +7,13 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/interfaces/libpq/fe-connect.c,v 1.106 1999/11/11 00:10:13 momjian Exp $ + * $Header: /cvsroot/pgsql/src/interfaces/libpq/fe-connect.c,v 1.107 1999/11/30 03:08:18 momjian Exp $ * *------------------------------------------------------------------------- */ +#include <sys/types.h> +#include <sys/socket.h> #include <fcntl.h> #include <errno.h> #include <ctype.h> @@ -27,6 +29,7 @@ #include <unistd.h> #include <netdb.h> #include <netinet/tcp.h> +#include <arpa/inet.h> #endif #ifndef HAVE_STRDUP @@ -40,11 +43,37 @@ #include "mb/pg_wchar.h" #endif +/* ---------- + * pg_setenv_state + * A struct used when polling a setenv request. This is referred to externally + * using a PGsetenvHandle. + * ---------- + */ +struct pg_setenv_state +{ + enum + { + SETENV_STATE_OPTION_SEND, /* About to send an Environment Option */ + SETENV_STATE_OPTION_WAIT, /* Waiting for above send to complete */ +#ifdef MULTIBYTE + SETENV_STATE_ENCODINGS_SEND, /* About to send an "encodings" query */ + SETENV_STATE_ENCODINGS_WAIT, /* Waiting for query to complete */ +#endif + SETENV_STATE_OK, + SETENV_STATE_FAILED + } state; + PGconn *conn; + PGresult *res; + struct EnvironmentOptions *eo; +} ; + +static int connectDBStart(PGconn *conn); +static int connectDBComplete(PGconn *conn); + #ifdef USE_SSL static SSL_CTX *SSL_context = NULL; #endif -static ConnStatusType connectDB(PGconn *conn); static PGconn *makeEmptyPGconn(void); static void freePGconn(PGconn *conn); static void closePGconn(PGconn *conn); @@ -53,9 +82,6 @@ static char *conninfo_getval(char *keyword); static void conninfo_free(void); static void defaultNoticeProcessor(void *arg, const char *message); -/* XXX Why is this not static? */ -void PQsetenv(PGconn *conn); - #define NOTIFYLIST_INITIAL_SIZE 10 #define NOTIFYLIST_GROWBY 10 @@ -98,6 +124,9 @@ static PQconninfoOption PQconninfoOptions[] = { {"host", "PGHOST", NULL, NULL, "Database-Host", "", 40}, + {"hostaddr", "PGHOSTADDR", NULL, NULL, + "Database-Host-IPv4-Address", "", 15}, /* Room for abc.def.ghi.jkl */ + {"port", "PGPORT", DEF_PGPORT, NULL, "Database-Port", "", 6}, @@ -145,27 +174,86 @@ static struct EnvironmentOptions } }; + +/* ---------------- + * Connecting to a Database + * + * There are now four different ways a user of this API can connect to the + * database. Two are not recommended for use in new code, because of their + * lack of extensibility with respect to the passing of options to the + * backend. These are PQsetdb and PQsetdbLogin (the former now being a macro + * to the latter). + * + * If it is desired to connect in a synchronous (blocking) manner, use the + * function PQconnectdb. + * + * To connect in an asychronous (non-blocking) manner, use the functions + * PQconnectStart, and PQconnectPoll. + * + * Internally, the static functions connectDBStart, connectDBComplete + * are part of the connection procedure. + * + * ---------------- + */ + /* ---------------- * PQconnectdb * * establishes a connection to a postgres backend through the postmaster * using connection information in a string. * - * The conninfo string is a list of + * The conninfo string is a white-separated list of * * option = value * - * definitions. Value might be a single value containing no whitespaces - * or a single quoted string. If a single quote should appear everywhere - * in the value, it must be escaped with a backslash like \' + * definitions. Value might be a single value containing no whitespaces or + * a single quoted string. If a single quote should appear anywhere in + * the value, it must be escaped with a backslash like \' * - * Returns a PGconn* which is needed for all subsequent libpq calls - * if the status field of the connection returned is CONNECTION_BAD, - * then some fields may be null'ed out instead of having valid values - * ---------------- - */ + * Returns a PGconn* which is needed for all subsequent libpq calls, or NULL + * if a memory allocation failed. + * If the status field of the connection returned is CONNECTION_BAD, + * then some fields may be null'ed out instead of having valid values. + * + * You should call PQfinish (if conn is not NULL) regardless of whether this + * call succeeded. + * + * ---------------- */ + PGconn * PQconnectdb(const char *conninfo) +{ + PGconn *conn = PQconnectStart(conninfo); + + (void)(!conn || (conn->status == CONNECTION_BAD) || + !connectDBComplete(conn)); + + return conn; +} + +/* ---------------- + * PQconnectStart + * + * Begins the establishment of a connection to a postgres backend through the + * postmaster using connection information in a string. + * + * See comment for PQconnectdb for the definition of the string format. + * + * Returns a PGconn*. 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 PQfinish 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 PQconnectPoll, using select(2) to see when + * this is necessary. + * + * See PQconnectPoll for more info. + * + * ---------------- */ + +PGconn * +PQconnectStart(const char *conninfo) { PGconn *conn; char *tmp; @@ -174,6 +262,7 @@ PQconnectdb(const char *conninfo) * Allocate memory for the conn structure * ---------- */ + conn = makeEmptyPGconn(); if (conn == NULL) return (PGconn *) NULL; @@ -188,6 +277,8 @@ PQconnectdb(const char *conninfo) conninfo_free(); return conn; } + tmp = conninfo_getval("hostaddr"); + conn->pghostaddr = tmp ? strdup(tmp) : NULL; tmp = conninfo_getval("host"); conn->pghost = tmp ? strdup(tmp) : NULL; tmp = conninfo_getval("port"); @@ -213,7 +304,11 @@ PQconnectdb(const char *conninfo) * Connect to the database * ---------- */ - conn->status = connectDB(conn); + if (!connectDBStart(conn)) + { + /* Just in case we failed to set it in connectDBStart */ + conn->status = CONNECTION_BAD; + } return conn; } @@ -384,10 +479,14 @@ PQsetdbLogin(const char *pghost, const char *pgport, const char *pgoptions, cons } if (error) + { conn->status = CONNECTION_BAD; + } else - conn->status = connectDB(conn); - + { + (void)(!connectDBStart(conn) || !connectDBComplete(conn)); + } + return conn; } @@ -480,7 +579,9 @@ update_db_info(PGconn *conn) if (strcmp(old + offset, "localhost") != 0) { printfPQExpBuffer(&conn->errorMessage, - "connectDB() -- non-tcp access only possible on localhost\n"); + "connectDBStart() -- " + "non-tcp access only possible on " + "localhost\n"); return 1; } } @@ -494,24 +595,84 @@ update_db_info(PGconn *conn) return 0; } -/* - * connectDB - - * make a connection to the backend so it is ready to receive queries. - * return CONNECTION_OK if successful, CONNECTION_BAD if not. - * + +/* ---------- + * connectMakeNonblocking - + * Make a connection non-blocking. + * Returns 1 if successful, 0 if not. + * ---------- */ -static ConnStatusType -connectDB(PGconn *conn) +static int +connectMakeNonblocking(PGconn *conn) +{ +#ifndef WIN32 + if (fcntl(conn->sock, F_SETFL, O_NONBLOCK) < 0) +#else + if (ioctlsocket(conn->sock, FIONBIO, &on) != 0) +#endif + { + printfPQExpBuffer(&conn->errorMessage, + "connectMakeNonblocking -- fcntl() failed: errno=%d\n%s\n", + errno, strerror(errno)); + return 0; + } + + return 1; +} + +/* ---------- + * connectNoDelay - + * Sets the TCP_NODELAY socket option. + * Returns 1 if successful, 0 if not. + * ---------- + */ +static int +connectNoDelay(PGconn *conn) +{ + struct protoent *pe; + int on = 1; + + pe = getprotobyname("TCP"); + if (pe == NULL) + { + printfPQExpBuffer(&conn->errorMessage, + "connectNoDelay() -- " + "getprotobyname failed: errno=%d\n%s\n", + errno, strerror(errno)); + return 0; + } + if (setsockopt(conn->sock, pe->p_proto, TCP_NODELAY, +#ifdef WIN32 + (char *) +#endif + &on, + sizeof(on)) < 0) + { + printfPQExpBuffer(&conn->errorMessage, + "connectNoDelay() -- setsockopt failed: errno=%d\n%s\n", + errno, strerror(errno)); +#ifdef WIN32 + printf("Winsock error: %i\n", WSAGetLastError()); +#endif + return 0; + } + + return 1; +} + + +/* ---------- + * connectDBStart - + * Start to make a connection to the backend so it is ready to receive + * queries. + * Returns 1 if successful, 0 if not. + * ---------- + */ +static int +connectDBStart(PGconn *conn) { - PGresult *res; - struct hostent *hp; - StartupPacket sp; - AuthRequest areq; - SOCKET_SIZE_TYPE laddrlen; int portno, family; - char beresp; - int on = 1; #ifdef USE_SSL StartupPacket np; /* Used to negotiate SSL connection */ char SSLok; @@ -519,92 +680,126 @@ connectDB(PGconn *conn) int tried_ssl = 0; /* Set if SSL negotiation was tried */ #endif + if (!conn) + return 0; /* * parse dbName to get all additional info in it, if any */ if (update_db_info(conn) != 0) goto connect_errReturn; + /* Ensure our buffers are empty */ + conn->inStart = conn->inCursor = conn->inEnd = 0; + conn->outCount = 0; + /* - * Initialize the startup packet. + * Set up the connection to postmaster/backend. + * Note that this supports IPv4 and UDP only. */ - MemSet((char *) &sp, 0, sizeof(StartupPacket)); - - sp.protoVersion = (ProtocolVersion) htonl(PG_PROTOCOL_LIBPQ); + MemSet((char *) &conn->raddr, 0, sizeof(conn->raddr)); - strncpy(sp.user, conn->pguser, SM_USER); - strncpy(sp.database, conn->dbName, SM_DATABASE); - strncpy(sp.tty, conn->pgtty, SM_TTY); + if (conn->pghostaddr != NULL) + { + /* Using pghostaddr avoids a hostname lookup */ + /* Note that this supports IPv4 only */ + struct in_addr addr; - if (conn->pgoptions) - strncpy(sp.options, conn->pgoptions, SM_OPTIONS); + if(!inet_aton(conn->pghostaddr, &addr)) + { + printfPQExpBuffer(&conn->errorMessage, + "connectDBStart() -- " + "invalid host address: %s\n", conn->pghostaddr); + goto connect_errReturn; + } - /* - * Open a connection to postmaster/backend. - */ + family = AF_INET; - if (conn->pghost != NULL) + memmove((char *) &(conn->raddr.in.sin_addr), + (char *) &addr, sizeof(addr)); + } + else if (conn->pghost != NULL) { + /* Using pghost, so we have to look-up the hostname */ + struct hostent *hp; + hp = gethostbyname(conn->pghost); if ((hp == NULL) || (hp->h_addrtype != AF_INET)) { printfPQExpBuffer(&conn->errorMessage, - "connectDB() -- unknown hostname: %s\n", + "connectDBStart() -- unknown hostname: %s\n", conn->pghost); goto connect_errReturn; } family = AF_INET; + + memmove((char *) &(conn->raddr.in.sin_addr), + (char *) hp->h_addr, + hp->h_length); } else { - hp = NULL; + /* pghostaddr and pghost are NULL, so use UDP */ family = AF_UNIX; } - MemSet((char *) &conn->raddr, 0, sizeof(conn->raddr)); + /* Set family */ conn->raddr.sa.sa_family = family; + /* Set port number */ portno = atoi(conn->pgport); if (family == AF_INET) { - memmove((char *) &(conn->raddr.in.sin_addr), - (char *) hp->h_addr, - hp->h_length); conn->raddr.in.sin_port = htons((unsigned short) (portno)); conn->raddr_len = sizeof(struct sockaddr_in); } #if !defined(WIN32) && !defined(__CYGWIN32__) - else - conn->raddr_len = UNIXSOCK_PATH(conn->raddr.un, portno); + else + conn->raddr_len = UNIXSOCK_PATH(conn->raddr.un, portno); #endif - /* Connect to the server */ + /* Open a socket */ if ((conn->sock = socket(family, SOCK_STREAM, 0)) < 0) { printfPQExpBuffer(&conn->errorMessage, - "connectDB() -- socket() failed: errno=%d\n%s\n", + "connectDBStart() -- " + "socket() failed: errno=%d\n%s\n", errno, strerror(errno)); goto connect_errReturn; } - if (connect(conn->sock, &conn->raddr.sa, conn->raddr_len) < 0) + + /* ---------- + * Set the right options. Normally, we need nonblocking I/O, and we don't + * want delay of outgoing data for AF_INET sockets. If we are using SSL, + * then we need the blocking I/O (XXX Can this be fixed?). + * ---------- */ + + if (family == AF_INET) { - printfPQExpBuffer(&conn->errorMessage, - "connectDB() -- connect() failed: %s\n" - "Is the postmaster running%s at '%s' and accepting connections on %s '%s'?\n", - strerror(errno), - (family == AF_INET) ? " (with -i)" : "", - conn->pghost ? conn->pghost : "localhost", - (family == AF_INET) ? "TCP/IP port" : "Unix socket", - conn->pgport); - goto connect_errReturn; + if (!connectNoDelay(conn)) + goto connect_errReturn; } + /* ---------- + * Since I have no idea whether this is a valid thing to do under Windows + * before a connection is made, and since I have no way of testing it, I + * leave the code looking as below. When someone decides that they want + * non-blocking connections under Windows, they can define + * WIN32_NON_BLOCKING_CONNECTIONS before compilation. If it works, then + * this code can be cleaned up. + * + * Ewan Mellor <eem21@cam.ac.uk>. + * ---------- */ +#if (!defined(WIN32) || defined(WIN32_NON_BLOCKING_CONNECTIONS)) && !defined(USE_SSL) + if (!connectMakeNonblocking(conn)) + goto connect_errReturn; +#endif + +#ifdef USE_SSL /* This needs to be done before we set into nonblocking, since SSL negotiation * does not like that mode */ -#ifdef USE_SSL /* Attempt to negotiate SSL usage */ if (allow_ssl_try) { tried_ssl = 1; @@ -653,7 +848,7 @@ connectDB(PGconn *conn) fprintf(conn->Pfdebug, "Backend reports error, attempting fallback to pre-6.6.\n"); close(conn->sock); allow_ssl_try = 0; - return connectDB(conn); + return connectDBStart(conn); } else if (SSLok != 'N') { strcpy(conn->errorMessage, @@ -665,242 +860,812 @@ connectDB(PGconn *conn) allow_ssl_try = 1; /* We'll allow an attempt to use SSL next time */ #endif - /* - * Set the right options. We need nonblocking I/O, and we don't want - * delay of outgoing data. + /* ---------- + * Start / make connection. We are hopefully in non-blocking mode + * now, but it is possible that: + * 1. Older systems will still block on connect, despite the + * non-blocking flag. (Anyone know if this is true?) + * 2. We are running under Windows, and aren't even trying + * to be non-blocking (see above). + * 3. We are using SSL. + * Thus, we have make arrangements for all eventualities. + * ---------- */ - -#ifndef WIN32 - if (fcntl(conn->sock, F_SETFL, O_NONBLOCK) < 0) -#else - if (ioctlsocket(conn->sock, FIONBIO, &on) != 0) -#endif - { - printfPQExpBuffer(&conn->errorMessage, - "connectDB() -- fcntl() failed: errno=%d\n%s\n", - errno, strerror(errno)); - goto connect_errReturn; - } - - if (family == AF_INET) + if (connect(conn->sock, &conn->raddr.sa, conn->raddr_len) < 0) { - struct protoent *pe; - - pe = getprotobyname("TCP"); - if (pe == NULL) + if (errno == EINPROGRESS) { - printfPQExpBuffer(&conn->errorMessage, - "connectDB(): getprotobyname failed\n"); - goto connect_errReturn; + /* This is fine - we're in non-blocking mode, and the + * connection is in progress. */ + conn->status = CONNECTION_STARTED; } - if (setsockopt(conn->sock, pe->p_proto, TCP_NODELAY, -#ifdef WIN32 - (char *) -#endif - &on, - sizeof(on)) < 0) + else { + /* Something's gone wrong */ printfPQExpBuffer(&conn->errorMessage, - "connectDB() -- setsockopt failed: errno=%d\n%s\n", - errno, strerror(errno)); -#ifdef WIN32 - printf("Winsock error: %i\n", WSAGetLastError()); -#endif + "connectDBStart() -- connect() failed: %s\n" + "Is the postmaster running%s at '%s' " + "and accepting connections on %s '%s'?\n", + strerror(errno), + (family == AF_INET) ? " (with -i)" : "", + conn->pghost ? conn->pghost : "localhost", + (family == AF_INET) ? + "TCP/IP port" : "Unix socket", + conn->pgport); goto connect_errReturn; } } - - /* Fill in the client address */ - laddrlen = sizeof(conn->laddr); - if (getsockname(conn->sock, &conn->laddr.sa, &laddrlen) < 0) + else { - printfPQExpBuffer(&conn->errorMessage, - "connectDB() -- getsockname() failed: errno=%d\n%s\n", - errno, strerror(errno)); - goto connect_errReturn; + /* We're connected already */ + conn->status = CONNECTION_MADE; } - /* Ensure our buffers are empty */ - conn->inStart = conn->inCursor = conn->inEnd = 0; - conn->outCount = 0; + /* This makes the connection non-blocking, for all those cases which forced us + not to do it above. */ +#if (defined(WIN32) && !defined(WIN32_NON_BLOCKING_CONNECTIONS)) || defined(USE_SSL) + if (!connectMakeNonblocking(conn)) + goto connect_errReturn; +#endif - /* Send the startup packet. */ + return 1; - if (pqPacketSend(conn, (char *) &sp, sizeof(StartupPacket)) != STATUS_OK) +connect_errReturn: + if (conn->sock >= 0) { - printfPQExpBuffer(&conn->errorMessage, - "connectDB() -- couldn't send startup packet: errno=%d\n%s\n", - errno, strerror(errno)); - goto connect_errReturn; +#ifdef WIN32 + closesocket(conn->sock); +#else + close(conn->sock); +#endif + conn->sock = -1; } + conn->status = CONNECTION_BAD; - /* - * Perform the authentication exchange: wait for backend messages and - * respond as necessary. We fall out of this loop when done talking to - * the postmaster. - */ + return 0; +} - for (;;) - { - /* Wait for some data to arrive (or for the channel to close) */ - if (pqWait(TRUE, FALSE, conn)) - goto connect_errReturn; - /* Load data, or detect EOF */ - if (pqReadData(conn) < 0) - goto connect_errReturn; - /* - * Scan the message. If we run out of data, loop around to try - * again. - */ - conn->inCursor = conn->inStart; +/* ---------------- + * connectDBComplete + * + * Block and complete a connection. + * + * Returns 1 on success, 0 on failure. + * ---------------- + */ +static int +connectDBComplete(PGconn *conn) +{ + PostgresPollingStatusType flag; + int r = 0, w = 1; - if (pqGetc(&beresp, conn)) - continue; /* no data yet */ + do + { + if(pqWait(r, w, conn)) + { + conn->status = CONNECTION_BAD; + return 0; + } - /* Handle errors. */ - if (beresp == 'E') + again: + switch(flag = PQconnectPoll(conn)) { - if (pqGets(&conn->errorMessage, conn)) - continue; - goto connect_errReturn; + case PGRES_POLLING_ACTIVE: + goto again; + + case PGRES_POLLING_OK: + break; + + case PGRES_POLLING_READING: + r = 1; + w = 0; + break; + + case PGRES_POLLING_WRITING: + r = 0; + w = 1; + break; + + default: + /* Just in case we failed to set it in PQconnectPoll */ + conn->status = CONNECTION_BAD; + return 0; } + } while (flag != PGRES_POLLING_OK); + + return 1; +} + + +/* ---------------- + * PQconnectPoll + * + * Poll an asynchronous connection. + * + * Returns a PostgresPollingStatusType. + * Before calling this function, use select(2) to determine when data arrive. + * + * You must call PQfinish whether or not this fails. + * + * This function and PQconnectStart are intended to allow connections to be + * made without blocking the execution of your program on remote I/O. However, + * there are a number of caveats: + * + * o If you call PQtrace, ensure that the stream object into which you trace + will not block. + * o If you do not supply an IP address for the remote host (i.e. you + * supply a host name instead) then this function will block on + * gethostbyname. You will be fine if using UDP (i.e. by supplying + * neither a host name nor a host address). + * o If your backend wants to use Kerberos authentication then you must + * supply both a host name and a host address, otherwise this function + * may block on gethostname. + * o This function will block if compiled with USE_SSL. + * + * ---------------- */ +PostgresPollingStatusType +PQconnectPoll(PGconn *conn) +{ + PGresult *res; - /* Otherwise it should be an authentication request. */ - if (beresp != 'R') + 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_RESPONSE: + case CONNECTION_ERROR_RESPONSE: + case CONNECTION_AUTH_OK: { - printfPQExpBuffer(&conn->errorMessage, - "connectDB() -- expected authentication request\n"); - goto connect_errReturn; + /* Load waiting data */ + int n = pqReadData(conn); + + if (n < 0) + goto error_return; + if (n == 0) + return PGRES_POLLING_READING; + + break; } - /* Get the type of request. */ - if (pqGetInt((int *) &areq, 4, conn)) - continue; + /* These are writing states, so we just proceed. */ + case CONNECTION_STARTED: + case CONNECTION_MADE: + break; - /* Get the password salt if there is one. */ - if (areq == AUTH_REQ_CRYPT) + case CONNECTION_SETENV: + /* We allow PQsetenvPoll to decide whether to proceed */ + break; + + default: + printfPQExpBuffer(&conn->errorMessage, + "PQconnectPoll() -- unknown 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 + parse. */ + switch(conn->status) + { + case CONNECTION_STARTED: { - if (pqGetnchar(conn->salt, sizeof(conn->salt), conn)) - continue; + SOCKET_SIZE_TYPE laddrlen; + int optval; + socklen_t optlen = sizeof(int); + + /* Write ready, since we've made it here, so the connection + * has been made. */ + + /* 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, + &optval, &optlen) == -1) + { + printfPQExpBuffer(&conn->errorMessage, + "PQconnectPoll() -- getsockopt() failed: " + "errno=%d\n%s\n", + errno, strerror(errno)); + goto error_return; + } + else if (optval != 0) + { + printfPQExpBuffer(&conn->errorMessage, + "PQconnectPoll() -- " + "socket has error condition %d: %s.\n", + optval, strerror(optval)); + goto error_return; + } + + /* Fill in the client address */ + laddrlen = sizeof(conn->laddr); + if (getsockname(conn->sock, &conn->laddr.sa, &laddrlen) < 0) + { + printfPQExpBuffer(&conn->errorMessage, + "PQconnectPoll() -- getsockname() failed: " + "errno=%d\n%s\n", + errno, strerror(errno)); + goto error_return; + } + + conn->status = CONNECTION_MADE; + return PGRES_POLLING_WRITING; } - /* OK, we successfully read the message; mark data consumed */ - conn->inStart = conn->inCursor; + case CONNECTION_MADE: + { + StartupPacket sp; + + /* + * Initialize the startup packet. + */ - /* Respond to the request if necessary. */ - /* fe-auth.c has not been fixed to support PQExpBuffers, so: */ - if (fe_sendauth(areq, conn, conn->pghost, conn->pgpass, - conn->errorMessage.data) != STATUS_OK) + MemSet((char *) &sp, 0, sizeof(StartupPacket)); + + sp.protoVersion = (ProtocolVersion) htonl(PG_PROTOCOL_LIBPQ); + + strncpy(sp.user, conn->pguser, SM_USER); + strncpy(sp.database, conn->dbName, SM_DATABASE); + strncpy(sp.tty, conn->pgtty, SM_TTY); + + if (conn->pgoptions) + strncpy(sp.options, conn->pgoptions, SM_OPTIONS); + + /* Send the startup packet. */ + + if (pqPacketSend(conn, (char *) &sp, + sizeof(StartupPacket)) != STATUS_OK) + { + printfPQExpBuffer(&conn->errorMessage, + "PQconnectPoll() -- " + "couldn't send startup packet: " + "errno=%d\n%s\n", + errno, strerror(errno)); + goto error_return; + } + + conn->status = CONNECTION_AWAITING_RESPONSE; + return PGRES_POLLING_READING; + } + + + /* + * Handle the authentication exchange: wait for backend messages + * and respond as necessary. + */ + case CONNECTION_AWAITING_RESPONSE: + { + char beresp; + + /* Scan the message */ + conn->inCursor = conn->inStart; + + if (pqGetc(&beresp, conn)) + { + /* We'll come back when there are more data */ + return PGRES_POLLING_READING; + } + + /* Handle errors. */ + if (beresp == 'E') + { + conn->status = CONNECTION_ERROR_RESPONSE; + goto keep_going; + } + + /* Otherwise it should be an authentication request. */ + if (beresp != 'R') + { + printfPQExpBuffer(&conn->errorMessage, + "PQconnectPoll() -- expected authentication " + "request\n"); + goto error_return; + } + + /* Got an authentication request, so that's OK */ + conn->status = CONNECTION_AUTH_RESPONSE; + goto keep_going; + } + + case CONNECTION_AUTH_RESPONSE: { + AuthRequest areq; + + /* Get the type of request. */ + if (pqGetInt((int *) &areq, 4, conn)) + { + /* We'll come back when there are more data */ + return PGRES_POLLING_READING; + } + + /* Get the password salt if there is one. */ + if (areq == AUTH_REQ_CRYPT) + { + if (pqGetnchar(conn->salt, sizeof(conn->salt), conn)) + { + /* We'll come back when there are more data */ + return PGRES_POLLING_READING; + } + } + + /* OK, we successfully read the message; mark data consumed */ + conn->inStart = conn->inCursor; + + /* Respond to the request if necessary. */ + /* Note that conn->pghost must be non-NULL if we are going + * avoid the Kerberos code doing a hostname look-up. */ + /* XXX fe-auth.c has not been fixed to support PQExpBuffers, so: */ + if (fe_sendauth(areq, conn, conn->pghost, conn->pgpass, + conn->errorMessage.data) != STATUS_OK) + { + conn->errorMessage.len = strlen(conn->errorMessage.data); + goto error_return; + } conn->errorMessage.len = strlen(conn->errorMessage.data); - goto connect_errReturn; + + /* This function has a section near the end that looks like it + * should block. I think that it will be OK though, since the + * socket is non-blocking, and thus the data should get out + * as quickly as possible. */ + if (pqFlush(conn)) + goto error_return; + + if (areq == AUTH_REQ_OK) + { + /* We are done with authentication exchange */ + conn->status = CONNECTION_AUTH_OK; + /* Set asyncStatus so that PQsetResult will think that what + * comes back next is the result of a query. See below. */ + conn->asyncStatus = PGASYNC_BUSY; + goto keep_going; + } + + conn->status = CONNECTION_AWAITING_RESPONSE; + return PGRES_POLLING_READING; } - if (pqFlush(conn)) - goto connect_errReturn; + case CONNECTION_ERROR_RESPONSE: + if (pqGets(&conn->errorMessage, conn)) + { + /* We'll come back when there are more data */ + return PGRES_POLLING_READING; + } + goto error_return; - /* Are we done? */ - if (areq == AUTH_REQ_OK) - break; - } + case CONNECTION_AUTH_OK: + { + /* ---------- + * Now we expect to hear from the backend. A ReadyForQuery + * message indicates that startup is successful, but we might + * also get an Error message indicating failure. (Notice + * messages indicating nonfatal warnings are also allowed by + * the protocol, as is a BackendKeyData message.) Easiest way + * to handle this is to let PQgetResult() read the messages. We + * just have to fake it out about the state of the connection. + *---------- + */ - /* - * Now we expect to hear from the backend. A ReadyForQuery message - * indicates that startup is successful, but we might also get an - * Error message indicating failure. (Notice messages indicating - * nonfatal warnings are also allowed by the protocol, as is a - * BackendKeyData message.) Easiest way to handle this is to let - * PQgetResult() read the messages. We just have to fake it out about - * the state of the connection. - */ + if (!PQconsumeInput(conn)) + goto error_return; - conn->status = CONNECTION_OK; - conn->asyncStatus = PGASYNC_BUSY; - res = PQgetResult(conn); - /* NULL return indicating we have gone to IDLE state is expected */ - if (res) - { - if (res->resultStatus != PGRES_FATAL_ERROR) + if(PQisBusy(conn)) + return PGRES_POLLING_READING; + + res = PQgetResult(conn); + /* NULL return indicating we have gone to + IDLE state is expected */ + if (res) + { + if (res->resultStatus != PGRES_FATAL_ERROR) + printfPQExpBuffer(&conn->errorMessage, + "PQconnectPoll() -- unexpected message " + "during startup\n"); + PQclear(res); + goto error_return; + } + + /* + * Post-connection housekeeping. Send environment variables + * to server. + */ + + if ((conn->setenv_handle = PQsetenvStart(conn)) == NULL) + goto error_return; + + conn->status = CONNECTION_SETENV; + + goto keep_going; + } + + case CONNECTION_SETENV: + /* We pretend that the connection is OK for the duration of + theses queries. */ + conn->status = CONNECTION_OK; + + switch(PQsetenvPoll(conn->setenv_handle)) + { + case PGRES_POLLING_OK: /* Success */ + conn->status = CONNECTION_OK; + return PGRES_POLLING_OK; + + 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: + conn->status = CONNECTION_SETENV; + goto error_return; + } + /* Unreachable */ + + default: printfPQExpBuffer(&conn->errorMessage, - "connectDB() -- unexpected message during startup\n"); - PQclear(res); - goto connect_errReturn; + "PQconnectPoll() -- unknown connection state - " + "probably indicative of memory corruption!\n", + sizeof(conn->errorMessage)); + goto error_return; } - /* - * Post-connection housekeeping. Send environment variables to server + /* 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 PQfinish is called (which is compulsory even after an + * error, since the connection structure must be freed). + * ---------- */ + return PGRES_POLLING_FAILED; +} - PQsetenv(conn); - return CONNECTION_OK; +/* ---------------- + * PQsetenvStart + * + * Starts the process of passing the values of a standard set of environment + * variables to the backend. + * + * ---------------- */ +PGsetenvHandle +PQsetenvStart(PGconn *conn) +{ + struct pg_setenv_state *handle; -connect_errReturn: - if (conn->sock >= 0) + if ((handle = malloc(sizeof(struct pg_setenv_state))) == NULL) { -#ifdef WIN32 - closesocket(conn->sock); -#else - close(conn->sock); -#endif - conn->sock = -1; + printfPQExpBuffer(&conn->errorMessage, + "PQsetenvStart() -- malloc error - %s\n", + strerror(errno)); + return NULL; } - return CONNECTION_BAD; + handle->conn = conn; + handle->res = NULL; + handle->eo = EnvironmentOptions; + +#ifdef MULTIBYTE + handle->state = SETENV_STATE_ENCODINGS_SEND; +#else + handle->state = SETENV_STATE_OPTION_SEND; +#endif + + return handle; /* Note that a struct pg_setenv_state * is the same as a + PGsetenvHandle */ } -void -PQsetenv(PGconn *conn) +/* ---------------- + * PQsetenvPoll + * + * Polls the process of passing the values of a standard set of environment + * variables to the backend. + * + * ---------------- */ +PostgresPollingStatusType +PQsetenvPoll(PGsetenvHandle handle) { - struct EnvironmentOptions *eo; - char setQuery[100]; /* note length limits in sprintf's below */ - const char *val; - PGresult *res; #ifdef MULTIBYTE - char *envname = "PGCLIENTENCODING"; + const char envname[] = "PGCLIENTENCODING"; +#endif - /* Set env. variable PGCLIENTENCODING if it's not set already */ - val = getenv(envname); - if (!val || *val == '\0') + if (!handle || handle->state == SETENV_STATE_FAILED) + return PGRES_POLLING_FAILED; + + /* Check whether there are any data for us */ + switch (handle->state) { - const char *encoding = NULL; - - /* query server encoding */ - res = PQexec(conn, "select getdatabaseencoding()"); - if (res && PQresultStatus(res) == PGRES_TUPLES_OK) - encoding = PQgetvalue(res, 0, 0); - if (!encoding) /* this should not happen */ - encoding = pg_encoding_to_char(MULTIBYTE); - if (encoding) + /* These are reading states */ +#ifdef MULTIBYTE + case SETENV_STATE_ENCODINGS_WAIT: +#endif + case SETENV_STATE_OPTION_WAIT: { - /* set client encoding via environment variable */ - char *envbuf; + /* Load waiting data */ + int n = pqReadData(handle->conn); + + if (n < 0) + goto error_return; + if (n == 0) + return PGRES_POLLING_READING; - envbuf = (char *) malloc(strlen(envname) + strlen(encoding) + 2); - sprintf(envbuf, "%s=%s", envname, encoding); - putenv(envbuf); + break; } - PQclear(res); - } + + /* These are writing states, so we just proceed. */ +#ifdef MULTIBYTE + case SETENV_STATE_ENCODINGS_SEND: #endif + case SETENV_STATE_OPTION_SEND: + break; + + default: + printfPQExpBuffer(&handle->conn->errorMessage, + "PQsetenvPoll() -- unknown state - " + "probably indicative of memory corruption!\n"); + goto error_return; + } - for (eo = EnvironmentOptions; eo->envName; eo++) + + keep_going: /* We will come back to here until there is nothing left to + parse. */ + switch(handle->state) { - if ((val = getenv(eo->envName))) + +#ifdef MULTIBYTE + case SETENV_STATE_ENCODINGS_SEND: { - if (strcasecmp(val, "default") == 0) - sprintf(setQuery, "SET %s = %.60s", eo->pgName, val); - else - sprintf(setQuery, "SET %s = '%.60s'", eo->pgName, val); + const char *env; + + /* query server encoding */ + env = getenv(envname); + if (!env || *env == '\0') + { + if (!PQsendQuery(handle->conn, + "select getdatabaseencoding()")) + goto error_return; + + handle->state = SETENV_STATE_ENCODINGS_WAIT; + return PGRES_POLLING_READING; + } + } + + case SETENV_STATE_ENCODINGS_WAIT: + { + const char *encoding = 0; + + if (!PQconsumeInput(handle->conn)) + goto error_return; + + if (PQisBusy(handle->conn)) + return PGRES_POLLING_READING; + + handle->res = PQgetResult(handle->conn); + + if (handle->res) + { + if (PQresultStatus(handle->res) != PGRES_TUPLES_OK) + { + PQclear(handle->res); + goto error_return; + } + + encoding = PQgetvalue(handle->res, 0, 0); + if (!encoding) /* this should not happen */ + encoding = pg_encoding_to_char(MULTIBYTE); + + if (encoding) + { + /* set client encoding via environment variable */ + char *envbuf; + + envbuf = (char *) malloc(strlen(envname) + strlen(encoding) + 2); + sprintf(envbuf, "%s=%s", envname, encoding); + putenv(envbuf); + } + PQclear(handle->res); + /* We have to keep going in order to clear up the query */ + goto keep_going; + } + + /* NULL result indicates that the query is finished */ + + /* Move on to setting the environment options */ + handle->state = SETENV_STATE_OPTION_SEND; + goto keep_going; + } +#endif + + case SETENV_STATE_OPTION_SEND: + { + /* Send an Environment Option */ + char setQuery[100]; /* note length limits in sprintf's below */ + + if (handle->eo->envName) + { + const char *val; + + if ((val = getenv(handle->eo->envName))) + { + if (strcasecmp(val, "default") == 0) + sprintf(setQuery, "SET %s = %.60s", + handle->eo->pgName, val); + else + sprintf(setQuery, "SET %s = '%.60s'", + handle->eo->pgName, val); #ifdef CONNECTDEBUG - printf("Use environment variable %s to send %s\n", eo->envName, setQuery); + printf("Use environment variable %s to send %s\n", + handle->eo->envName, setQuery); #endif - res = PQexec(conn, setQuery); - PQclear(res); /* Don't care? */ + if (!PQsendQuery(handle->conn, setQuery)) + goto error_return; + + handle->state = SETENV_STATE_OPTION_WAIT; + } + else + { + handle->eo++; + } + } + else + { + /* No option to send, so we are done. */ + handle->state = SETENV_STATE_OK; + } + + goto keep_going; } + + case SETENV_STATE_OPTION_WAIT: + { + if (!PQconsumeInput(handle->conn)) + goto error_return; + + if (PQisBusy(handle->conn)) + return PGRES_POLLING_READING; + + handle->res = PQgetResult(handle->conn); + + if (handle->res) + { + if (PQresultStatus(handle->res) != PGRES_COMMAND_OK) + { + PQclear(handle->res); + goto error_return; + } + /* Don't need the result */ + PQclear(handle->res); + /* We have to keep going in order to clear up the query */ + goto keep_going; + } + + /* NULL result indicates that the query is finished */ + + /* Send the next option */ + handle->eo++; + handle->state = SETENV_STATE_OPTION_SEND; + goto keep_going; + } + + case SETENV_STATE_OK: + /* Tidy up */ + free(handle); + return PGRES_POLLING_OK; + + default: + printfPQExpBuffer(&handle->conn->errorMessage, + "PQsetenvPoll() -- unknown state - " + "probably indicative of memory corruption!\n"); + goto error_return; + } + + /* Unreachable */ + + error_return: + handle->state = SETENV_STATE_FAILED; /* This may protect us even if we + * are called after the handle + * has been freed. */ + free(handle); + return PGRES_POLLING_FAILED; +} + + +/* ---------------- + * PQsetenvAbort + * + * Aborts the process of passing the values of a standard set of environment + * variables to the backend. + * + * ---------------- */ +void +PQsetenvAbort(PGsetenvHandle handle) +{ + /* We should not have been called in the FAILED state, but we can cope by + * not freeing the handle (it has probably been freed by now anyway). */ + if (handle->state != SETENV_STATE_FAILED) + { + handle->state = SETENV_STATE_FAILED; + free(handle); } -} /* PQsetenv() */ +} + + +/* ---------------- + * PQsetenv + * + * Passes the values of a standard set of environment variables to the + * backend. + * + * Returns 1 on success, 0 on failure. + * + * This function used to return void. I don't think that there should be + * compatibility problems caused by giving it a return value, especially as + * this function has not been documented previously. + * + * ---------------- */ +int +PQsetenv(PGconn *conn) +{ + PostgresPollingStatusType flag; + PGsetenvHandle handle; + int r = 0, w = 1; + + if((handle = PQsetenvStart(conn)) == NULL) + return 0; + + do + { + if(pqWait(r, w, conn)) + { + /* XXX This is not a good sign - perhaps we should mark the + connection as bad here... */ + return 0; + } + + again: + switch(flag = PQsetenvPoll(handle)) + { + case PGRES_POLLING_ACTIVE: + goto again; + + case PGRES_POLLING_OK: + break; + + case PGRES_POLLING_READING: + r = 1; + w = 0; + break; + + case PGRES_POLLING_WRITING: + r = 0; + w = 1; + break; + + default: /* Failed */ + return 0; + } + } while (flag != PGRES_POLLING_OK); + + return 1; +} + /* * makeEmptyPGconn @@ -973,6 +1738,8 @@ freePGconn(PGconn *conn) #endif if (conn->pghost) free(conn->pghost); + if (conn->pghostaddr) + free(conn->pghostaddr); if (conn->pgport) free(conn->pgport); if (conn->pgtty) @@ -1006,6 +1773,12 @@ freePGconn(PGconn *conn) static void closePGconn(PGconn *conn) { + if (conn->status == CONNECTION_SETENV) + { + /* We have to abort the setenv process as well */ + PQsetenvAbort(conn->setenv_handle); + } + if (conn->sock >= 0) { @@ -1067,8 +1840,47 @@ PQreset(PGconn *conn) if (conn) { closePGconn(conn); - conn->status = connectDB(conn); + + (void)(!connectDBStart(conn) || !connectDBComplete(conn)); } + + return; +} + + +/* PQresetStart : + resets the connection to the backend + closes the existing connection and makes a new one + Returns 1 on success, 0 on failure. +*/ +int +PQresetStart(PGconn *conn) +{ + if (conn) + { + closePGconn(conn); + + return connectDBStart(conn); + } + + return 1; +} + + +/* PQresetPoll : + resets the connection to the backend + closes the existing connection and makes a new one +*/ + +PostgresPollingStatusType +PQresetPoll(PGconn *conn) +{ + if (conn) + { + return PQconnectPoll(conn); + } + + return PGRES_POLLING_FAILED; } diff --git a/src/interfaces/libpq/fe-misc.c b/src/interfaces/libpq/fe-misc.c index d29277bf2004276bfaab4b9515848d347b81ed00..89425e0034c40c1d3de798a72b8260fb89c4a6aa 100644 --- a/src/interfaces/libpq/fe-misc.c +++ b/src/interfaces/libpq/fe-misc.c @@ -24,7 +24,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/interfaces/libpq/fe-misc.c,v 1.32 1999/11/11 00:10:14 momjian Exp $ + * $Header: /cvsroot/pgsql/src/interfaces/libpq/fe-misc.c,v 1.33 1999/11/30 03:08:19 momjian Exp $ * *------------------------------------------------------------------------- */ @@ -269,29 +269,69 @@ pqPutInt(int value, size_t bytes, PGconn *conn) /* --------------------------------------------------------------------- */ /* pqReadReady: is select() saying the file is ready to read? + * Returns -1 on failure, 0 if not ready, 1 if ready. */ -static int +int pqReadReady(PGconn *conn) { fd_set input_mask; struct timeval timeout; - if (conn->sock < 0) - return 0; + if (!conn || conn->sock < 0) + return -1; FD_ZERO(&input_mask); FD_SET(conn->sock, &input_mask); timeout.tv_sec = 0; timeout.tv_usec = 0; + retry: if (select(conn->sock + 1, &input_mask, (fd_set *) NULL, (fd_set *) NULL, &timeout) < 0) { + if (errno == EINTR) + /* Interrupted system call - we'll just try again */ + goto retry; + printfPQExpBuffer(&conn->errorMessage, "pqReadReady() -- select() failed: errno=%d\n%s\n", errno, strerror(errno)); - return 0; + return -1; + } + + return FD_ISSET(conn->sock, &input_mask) ? 1 : 0; +} + +/* --------------------------------------------------------------------- */ +/* pqWriteReady: is select() saying the file is ready to write? + * Returns -1 on failure, 0 if not ready, 1 if ready. + */ +int +pqWriteReady(PGconn *conn) +{ + fd_set input_mask; + struct timeval timeout; + + if (!conn || conn->sock < 0) + return -1; + + FD_ZERO(&input_mask); + FD_SET(conn->sock, &input_mask); + timeout.tv_sec = 0; + timeout.tv_usec = 0; + retry: + if (select(conn->sock + 1, (fd_set *) NULL, &input_mask, (fd_set *) NULL, + &timeout) < 0) + { + if (errno == EINTR) + /* Interrupted system call - we'll just try again */ + goto retry; + + printfPQExpBuffer(&conn->errorMessage, + "pqWriteReady() -- select() failed: errno=%d\n%s\n", + errno, strerror(errno)); + return -1; } - return FD_ISSET(conn->sock, &input_mask); + return FD_ISSET(conn->sock, &input_mask) ? 1 : 0; } /* --------------------------------------------------------------------- */ @@ -418,8 +458,17 @@ tryAgain: * be taken much, since in normal practice we should not be trying to * read data unless the file selected for reading already. */ - if (!pqReadReady(conn)) - return 0; /* definitely no data available */ + switch (pqReadReady(conn)) + { + case 0: + /* definitely no data available */ + return 0; + case 1: + /* ready for read */ + break; + default: + goto definitelyFailed; + } /* * Still not sure that it's EOF, because some data could have just @@ -570,6 +619,10 @@ pqFlush(PGconn *conn) if (len > 0) { /* We didn't send it all, wait till we can send more */ + + /* At first glance this looks as though it should block. I think + * that it will be OK though, as long as the socket is + * non-blocking. */ if (pqWait(FALSE, TRUE, conn)) return EOF; } @@ -599,9 +652,9 @@ pqWait(int forRead, int forWrite, PGconn *conn) return EOF; } - /* loop in case select returns EINTR */ - for (;;) + if (forRead || forWrite) { + retry: FD_ZERO(&input_mask); FD_ZERO(&output_mask); if (forRead) @@ -612,14 +665,12 @@ pqWait(int forRead, int forWrite, PGconn *conn) (struct timeval *) NULL) < 0) { if (errno == EINTR) - continue; + goto retry; printfPQExpBuffer(&conn->errorMessage, "pqWait() -- select() failed: errno=%d\n%s\n", errno, strerror(errno)); return EOF; } - /* On nonerror return, assume we're done */ - break; } return 0; diff --git a/src/interfaces/libpq/libpq-fe.h b/src/interfaces/libpq/libpq-fe.h index b1c97e046a60ec93439798182fd063bd3a4279d0..5a25c40121f199cc642f52da0d6361a700286bf5 100644 --- a/src/interfaces/libpq/libpq-fe.h +++ b/src/interfaces/libpq/libpq-fe.h @@ -6,7 +6,7 @@ * * Copyright (c) 1994, Regents of the University of California * - * $Id: libpq-fe.h,v 1.52 1999/11/11 00:10:14 momjian Exp $ + * $Id: libpq-fe.h,v 1.53 1999/11/30 03:08:19 momjian Exp $ * *------------------------------------------------------------------------- */ @@ -29,10 +29,40 @@ extern "C" typedef enum { + /* Although you may decide to change this list in some way, + values which become unused should never be removed, nor + should constants be redefined - that would break + compatibility with existing code. */ CONNECTION_OK, - CONNECTION_BAD + CONNECTION_BAD, + /* Non-blocking mode only below here */ + /* The existence of these should never be relied upon - they + should only be used for user feedback or similar purposes. */ + CONNECTION_STARTED, /* Waiting for connection to be made. */ + CONNECTION_MADE, /* Connection OK; waiting to send. */ + CONNECTION_AWAITING_RESPONSE, /* Waiting for a response + from the backend. */ + CONNECTION_AUTH_RESPONSE, /* Got an authentication + response; about to deal + with it. */ + CONNECTION_ERROR_RESPONSE, /* Got an error + response; about to deal + with it. */ + CONNECTION_AUTH_OK, /* Received authentication; + waiting for ReadyForQuery + etc. */ + CONNECTION_SETENV /* Negotiating environment. */ } ConnStatusType; + typedef enum + { + PGRES_POLLING_FAILED = 0, + PGRES_POLLING_READING, /* These two indicate that one may */ + PGRES_POLLING_WRITING, /* use select before polling again. */ + PGRES_POLLING_OK, + PGRES_POLLING_ACTIVE /* Can call poll function immediately.*/ + } PostgresPollingStatusType; + typedef enum { PGRES_EMPTY_QUERY = 0, @@ -67,6 +97,12 @@ extern "C" */ typedef struct pg_result PGresult; +/* PGsetenvHandle is an opaque handle which is returned by PQsetenvStart and + * which should be passed to PQsetenvPoll or PQsetenvAbort in order to refer + * to the particular process being performed. + */ + typedef struct pg_setenv_state *PGsetenvHandle; + /* PGnotify represents the occurrence of a NOTIFY message. * Ideally this would be an opaque typedef, but it's so simple that it's * unlikely to change. @@ -152,11 +188,15 @@ extern "C" /* === in fe-connect.c === */ /* make a new client connection to the backend */ + /* Asynchronous (non-blocking) */ + extern PGconn *PQconnectStart(const char *conninfo); + extern PostgresPollingStatusType PQconnectPoll(PGconn *conn); + /* Synchronous (blocking) */ extern PGconn *PQconnectdb(const char *conninfo); extern PGconn *PQsetdbLogin(const char *pghost, const char *pgport, const char *pgoptions, const char *pgtty, - const char *dbName, - const char *login, const char *pwd); + const char *dbName, + const char *login, const char *pwd); #define PQsetdb(M_PGHOST,M_PGPORT,M_PGOPT,M_PGTTY,M_DBNAME) \ PQsetdbLogin(M_PGHOST, M_PGPORT, M_PGOPT, M_PGTTY, M_DBNAME, NULL, NULL) @@ -170,6 +210,10 @@ extern "C" * close the current connection and restablish a new one with the same * parameters */ + /* Asynchronous (non-blocking) */ + extern int PQresetStart(PGconn *conn); + extern PostgresPollingStatusType PQresetPoll(PGconn *conn); + /* Synchronous (blocking) */ extern void PQreset(PGconn *conn); /* issue a cancel request */ @@ -195,6 +239,15 @@ extern "C" /* Override default notice processor */ extern PQnoticeProcessor PQsetNoticeProcessor(PGconn *conn, PQnoticeProcessor proc, void *arg); + /* Passing of environment variables */ + /* Asynchronous (non-blocking) */ + extern PGsetenvHandle PQsetenvStart(PGconn *conn); + extern PostgresPollingStatusType PQsetenvPoll(PGsetenvHandle handle); + extern void PQsetenvAbort(PGsetenvHandle handle); + + /* Synchronous (blocking) */ + extern int PQsetenv(PGconn *conn); + /* === in fe-exec.c === */ /* Simple synchronous query */ diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h index 7323e9fe065db71d8f429b29c0706bbfdc444a78..64566d6c743f5273e24f8757c81e79d5a109e532 100644 --- a/src/interfaces/libpq/libpq-int.h +++ b/src/interfaces/libpq/libpq-int.h @@ -11,7 +11,7 @@ * * Copyright (c) 1994, Regents of the University of California * - * $Id: libpq-int.h,v 1.13 1999/11/11 00:10:14 momjian Exp $ + * $Id: libpq-int.h,v 1.14 1999/11/30 03:08:19 momjian Exp $ * *------------------------------------------------------------------------- */ @@ -168,6 +168,10 @@ struct pg_conn /* Saved values of connection options */ char *pghost; /* the machine on which the server is * running */ + char *pghostaddr; /* the IPv4 address of the machine on + * which the server is running, in + * IPv4 numbers-and-dots notation. Takes + * precedence over above. */ char *pgport; /* the server's communication port */ char *pgtty; /* tty on which the backend messages is * displayed (NOT ACTUALLY USED???) */ @@ -220,6 +224,9 @@ struct pg_conn PGresult *result; /* result being constructed */ PGresAttValue *curTuple; /* tuple currently being read */ + /* Handle for setenv request. Used during connection only. */ + PGsetenvHandle setenv_handle; + #ifdef USE_SSL SSL *ssl; #endif @@ -268,6 +275,8 @@ extern int pqPutInt(int value, size_t bytes, PGconn *conn); extern int pqReadData(PGconn *conn); extern int pqFlush(PGconn *conn); extern int pqWait(int forRead, int forWrite, PGconn *conn); +extern int pqReadReady(PGconn *conn); +extern int pqWriteReady(PGconn *conn); /* bits in a byte */ #define BYTELEN 8