Commit 6bdb7aa4 authored by Tom Lane's avatar Tom Lane

libpq can now talk to either 3.0 or 2.0 protocol servers. It first tries

protocol 3, then falls back to 2 if postmaster rejects the startup packet
with an old-format error message.  A side benefit of the rewrite is that
SSL-encrypted connections can now be made without blocking.  (I think,
anyway, but do not have a good way to test.)
parent 152ce7a4
......@@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $Header: /cvsroot/pgsql/src/backend/libpq/ip.c,v 1.7 2003/04/22 03:52:56 tgl Exp $
* $Header: /cvsroot/pgsql/src/backend/libpq/ip.c,v 1.8 2003/06/08 17:42:59 tgl Exp $
*
* This file and the IPV6 implementation were initially provided by
* Nigel Kukard <nkukard@lbsd.net>, Linux Based Systems Design
......@@ -72,19 +72,20 @@ getaddrinfo2(const char *hostname, const char *servname,
/*
* freeaddrinfo2 - free IPv6 addrinfo structures
* freeaddrinfo2 - free addrinfo structures for IPv4, IPv6, or Unix
*/
void
freeaddrinfo2(int hint_ai_family, struct addrinfo *ai)
freeaddrinfo2(struct addrinfo *ai)
{
if (ai != NULL)
{
#ifdef HAVE_UNIX_SOCKETS
if (hint_ai_family == AF_UNIX)
if (ai->ai_family == AF_UNIX)
{
struct addrinfo *p;
while (ai != NULL)
{
p = ai;
struct addrinfo *p = ai;
ai = ai->ai_next;
free(p->ai_addr);
free(p);
......@@ -93,6 +94,7 @@ freeaddrinfo2(int hint_ai_family, struct addrinfo *ai)
else
#endif /* HAVE_UNIX_SOCKETS */
freeaddrinfo(ai);
}
}
......
......@@ -30,7 +30,7 @@
* Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* $Header: /cvsroot/pgsql/src/backend/libpq/pqcomm.c,v 1.154 2003/05/29 19:15:34 tgl Exp $
* $Header: /cvsroot/pgsql/src/backend/libpq/pqcomm.c,v 1.155 2003/06/08 17:43:00 tgl Exp $
*
*-------------------------------------------------------------------------
*/
......@@ -242,8 +242,7 @@ StreamServerPort(int family, char *hostName, unsigned short portNumber,
{
elog(LOG, "server socket failure: getaddrinfo2(): %s",
gai_strerror(ret));
if (addrs != NULL)
freeaddrinfo2(hint.ai_family, addrs);
freeaddrinfo2(addrs);
return STATUS_ERROR;
}
......@@ -251,7 +250,7 @@ StreamServerPort(int family, char *hostName, unsigned short portNumber,
{
elog(LOG, "server socket failure: socket(): %s",
strerror(errno));
freeaddrinfo2(hint.ai_family, addrs);
freeaddrinfo2(addrs);
return STATUS_ERROR;
}
......@@ -262,7 +261,7 @@ StreamServerPort(int family, char *hostName, unsigned short portNumber,
{
elog(LOG, "server socket failure: setsockopt(SO_REUSEADDR): %s",
strerror(errno));
freeaddrinfo2(hint.ai_family, addrs);
freeaddrinfo2(addrs);
return STATUS_ERROR;
}
}
......@@ -279,7 +278,7 @@ StreamServerPort(int family, char *hostName, unsigned short portNumber,
sock_path);
else
elog(LOG, "\tIf not, wait a few seconds and retry.");
freeaddrinfo2(hint.ai_family, addrs);
freeaddrinfo2(addrs);
return STATUS_ERROR;
}
......@@ -288,7 +287,7 @@ StreamServerPort(int family, char *hostName, unsigned short portNumber,
{
if (Setup_AF_UNIX() != STATUS_OK)
{
freeaddrinfo2(hint.ai_family, addrs);
freeaddrinfo2(addrs);
return STATUS_ERROR;
}
}
......@@ -308,12 +307,12 @@ StreamServerPort(int family, char *hostName, unsigned short portNumber,
{
elog(LOG, "server socket failure: listen(): %s",
strerror(errno));
freeaddrinfo2(hint.ai_family, addrs);
freeaddrinfo2(addrs);
return STATUS_ERROR;
}
*fdP = fd;
freeaddrinfo2(hint.ai_family, addrs);
freeaddrinfo2(addrs);
return STATUS_OK;
}
......
......@@ -5,7 +5,7 @@
*
* Copyright (c) 2003, PostgreSQL Global Development Group
*
* $Id: ip.h,v 1.3 2003/04/02 00:49:28 tgl Exp $
* $Id: ip.h,v 1.4 2003/06/08 17:43:00 tgl Exp $
*
*-------------------------------------------------------------------------
*/
......@@ -19,7 +19,7 @@
extern int getaddrinfo2(const char *hostname, const char *servname,
const struct addrinfo *hintp,
struct addrinfo **result);
extern void freeaddrinfo2(int hint_ai_family, struct addrinfo *ai);
extern void freeaddrinfo2(struct addrinfo *ai);
extern char *SockAddr_ntop(const SockAddr *sa, char *dst, size_t cnt,
int v4conv);
......
......@@ -4,7 +4,7 @@
#
# Copyright (c) 1994, Regents of the University of California
#
# $Header: /cvsroot/pgsql/src/interfaces/libpq/Makefile,v 1.79 2003/05/10 02:05:50 momjian Exp $
# $Header: /cvsroot/pgsql/src/interfaces/libpq/Makefile,v 1.80 2003/06/08 17:43:00 tgl Exp $
#
#-------------------------------------------------------------------------
......@@ -21,7 +21,7 @@ SO_MINOR_VERSION= 1
override CPPFLAGS := -I$(srcdir) $(CPPFLAGS) -DFRONTEND -DSYSCONFDIR='"$(sysconfdir)"'
OBJS= fe-auth.o fe-connect.o fe-exec.o fe-misc.o fe-print.o fe-lobj.o \
pqexpbuffer.o pqsignal.o fe-secure.o \
fe-protocol2.o fe-protocol3.o pqexpbuffer.o pqsignal.o fe-secure.o \
dllist.o md5.o ip.o wchar.o encnames.o \
$(filter crypt.o getaddrinfo.o inet_aton.o snprintf.o strerror.o path.o, $(LIBOBJS))
......
......@@ -10,7 +10,7 @@
* exceed INITIAL_EXPBUFFER_SIZE (currently 256 bytes).
*
* IDENTIFICATION
* $Header: /cvsroot/pgsql/src/interfaces/libpq/fe-auth.c,v 1.78 2003/05/16 04:58:03 tgl Exp $
* $Header: /cvsroot/pgsql/src/interfaces/libpq/fe-auth.c,v 1.79 2003/06/08 17:43:00 tgl Exp $
*
*-------------------------------------------------------------------------
*/
......@@ -559,7 +559,11 @@ pg_password_sendauth(PGconn *conn, const char *password, AuthRequest areq)
default:
return STATUS_ERROR;
}
/* Packet has a message type as of protocol 3.0 */
if (PG_PROTOCOL_MAJOR(conn->pversion) >= 3)
ret = pqPacketSend(conn, 'p', crypt_pwd, strlen(crypt_pwd) + 1);
else
ret = pqPacketSend(conn, 0, crypt_pwd, strlen(crypt_pwd) + 1);
if (areq == AUTH_REQ_MD5)
free(crypt_pwd);
return ret;
......
......@@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $Header: /cvsroot/pgsql/src/interfaces/libpq/fe-connect.c,v 1.241 2003/05/15 16:35:30 momjian Exp $
* $Header: /cvsroot/pgsql/src/interfaces/libpq/fe-connect.c,v 1.242 2003/06/08 17:43:00 tgl Exp $
*
*-------------------------------------------------------------------------
*/
......@@ -50,21 +50,16 @@
#include "mb/pg_wchar.h"
#ifdef WIN32
static int
inet_aton(const char *cp, struct in_addr * inp)
{
unsigned long a = inet_addr(cp);
if (a == -1)
return 0;
inp->s_addr = a;
return 1;
}
#endif
#define PGPASSFILE ".pgpass"
/* fall back options if they are not specified by arguments or defined
by environment variables */
#define DefaultHost "localhost"
#define DefaultTty ""
#define DefaultOption ""
#define DefaultAuthtype ""
#define DefaultPassword ""
#define PGPASSFILE ".pgpass"
/* ----------
* Definition of the conninfo parameters and their fallback resources.
......@@ -73,10 +68,9 @@ inet_aton(const char *cp, struct in_addr * inp)
* fallback is available. If after all no value can be determined
* for an option, an error is returned.
*
* The values for dbname and user are treated specially in conninfo_parse.
* The value for the username is treated specially in conninfo_parse.
* If the Compiled-in resource is specified as a NULL value, the
* user is determined by fe_getauthname() and for dbname the user
* name is copied.
* user is determined by fe_getauthname().
*
* The Label and Disp-Char entries are provided for applications that
* want to use PQconndefaults() to create a generic database connection
......@@ -145,12 +139,7 @@ static const PQconninfoOption PQconninfoOptions[] = {
NULL, NULL, 0}
};
static const struct EnvironmentOptions
{
const char *envName,
*pgName;
} EnvironmentOptions[] =
static const PQEnvironmentOption EnvironmentOptions[] =
{
/* common user-interface settings */
{
......@@ -183,7 +172,6 @@ static PQconninfoOption *conninfo_parse(const char *conninfo,
PQExpBuffer errorMessage);
static char *conninfo_getval(PQconninfoOption *connOptions,
const char *keyword);
static int build_startup_packet(const PGconn *conn, char *packet);
static void defaultNoticeProcessor(void *arg, const char *message);
static int parseServiceInfo(PQconninfoOption *options,
PQExpBuffer errorMessage);
......@@ -791,8 +779,8 @@ connectFailureMessage(PGconn *conn, int errorno)
/* ----------
* connectDBStart -
* Start to make a connection to the backend so it is ready to receive
* queries.
* Begin the process of making a connection to the backend.
*
* Returns 1 if successful, 0 if not.
* ----------
*/
......@@ -802,30 +790,26 @@ connectDBStart(PGconn *conn)
int portnum;
char portstr[64];
struct addrinfo *addrs = NULL;
struct addrinfo *addr_cur = NULL;
struct addrinfo hint;
const char *node = NULL;
const char *unix_node = "unix";
int ret;
if (!conn)
return 0;
/* Initialize hint structure */
MemSet(&hint, 0, sizeof(hint));
hint.ai_socktype = SOCK_STREAM;
/* Ensure our buffers are empty */
conn->inStart = conn->inCursor = conn->inEnd = 0;
conn->outCount = 0;
/*
* Set up the connection to postmaster/backend.
* Determine the parameters to pass to getaddrinfo2.
*/
MemSet((char *) &conn->raddr, 0, sizeof(conn->raddr));
/* Initialize hint structure */
MemSet(&hint, 0, sizeof(hint));
hint.ai_socktype = SOCK_STREAM;
/* Set port number */
/* Set up port number as a string */
if (conn->pgport != NULL && conn->pgport[0] != '\0')
portnum = atoi(conn->pgport);
else
......@@ -849,16 +833,11 @@ connectDBStart(PGconn *conn)
{
/* pghostaddr and pghost are NULL, so use Unix domain socket */
#ifdef HAVE_UNIX_SOCKETS
node = unix_node;
node = "unix";
hint.ai_family = AF_UNIX;
UNIXSOCK_PATH(conn->raddr.un, portnum, conn->pgunixsocket);
conn->raddr_len = UNIXSOCK_LEN(conn->raddr.un);
StrNCpy(portstr, conn->raddr.un.sun_path, sizeof(portstr));
#ifdef USE_SSL
/* Don't bother requesting SSL over a Unix socket */
conn->allow_ssl_try = false;
conn->require_ssl = false;
#endif
#endif /* HAVE_UNIX_SOCKETS */
}
......@@ -869,182 +848,26 @@ connectDBStart(PGconn *conn)
printfPQExpBuffer(&conn->errorMessage,
libpq_gettext("getaddrinfo() failed: %s\n"),
gai_strerror(ret));
freeaddrinfo2(addrs);
goto connect_errReturn;
}
/*
* We loop over the possible addresses returned by getaddrinfo2(),
* and fail only when they all fail (reporting the error returned
* for the *last* alternative, which may not be what users expect
* :-().
*
* Notice we never actually fall out of the loop; the only exits are
* via "break" or "goto connect_errReturn". Thus, there is no exit
* test in the for().
*/
for (addr_cur = addrs; ; addr_cur = addr_cur->ai_next)
{
/* Open a socket */
conn->sock = socket(addr_cur->ai_family, SOCK_STREAM,
addr_cur->ai_protocol);
if (conn->sock < 0)
{
/* ignore socket() failure if we have more addrs to try */
if (addr_cur->ai_next != NULL)
continue;
printfPQExpBuffer(&conn->errorMessage,
libpq_gettext("could not create socket: %s\n"),
SOCK_STRERROR(SOCK_ERRNO));
goto connect_errReturn;
}
/*
* 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 (isAF_INETx(addr_cur->ai_family))
{
if (!connectNoDelay(conn))
goto connect_errReturn;
}
#if !defined(USE_SSL)
if (connectMakeNonblocking(conn) == 0)
goto connect_errReturn;
#endif
/* ----------
* 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 using SSL.
* Thus, we have to make arrangements for all eventualities.
* ----------
* Set up to try to connect, with protocol 3.0 as the first attempt.
*/
retry1:
if (connect(conn->sock, addr_cur->ai_addr, addr_cur->ai_addrlen) < 0)
{
if (SOCK_ERRNO == EINTR)
/* Interrupted system call - we'll just try again */
goto retry1;
conn->addrlist = addrs;
conn->addr_cur = addrs;
conn->pversion = PG_PROTOCOL(3,0);
conn->status = CONNECTION_NEEDED;
if (SOCK_ERRNO == EINPROGRESS || SOCK_ERRNO == EWOULDBLOCK || SOCK_ERRNO == 0)
{
/*
* This is fine - we're in non-blocking mode, and the
* connection is in progress.
*/
conn->status = CONNECTION_STARTED;
break;
}
/* otherwise, trouble */
}
else
{
/* We're connected already */
conn->status = CONNECTION_MADE;
break;
}
/*
* This connection failed. We need to close the socket,
* and either loop to try the next address or report an error.
* The code for processing CONNECTION_NEEDED state is in PQconnectPoll(),
* so that it can easily be re-executed if needed again during the
* asynchronous startup process. However, we must run it once here,
* because callers expect a success return from this routine to mean
* that we are in PGRES_POLLING_WRITING connection state.
*/
/* ignore connect() failure if we have more addrs to try */
if (addr_cur->ai_next != NULL)
{
closesocket(conn->sock);
conn->sock = -1;
continue;
}
/* copy failed address for error report */
memcpy(&conn->raddr, addr_cur->ai_addr, addr_cur->ai_addrlen);
conn->raddr_len = addr_cur->ai_addrlen;
connectFailureMessage(conn, SOCK_ERRNO);
goto connect_errReturn;
} /* loop over addrs */
/* Remember the successfully opened address alternative */
memcpy(&conn->raddr, addr_cur->ai_addr, addr_cur->ai_addrlen);
conn->raddr_len = addr_cur->ai_addrlen;
/* and release the address list */
freeaddrinfo2(hint.ai_family, addrs);
addrs = NULL;
#ifdef USE_SSL
/* Attempt to negotiate SSL usage */
if (conn->allow_ssl_try)
{
ProtocolVersion pv;
char SSLok;
pv = htonl(NEGOTIATE_SSL_CODE);
if (pqPacketSend(conn, 0, &pv, sizeof(ProtocolVersion)) != STATUS_OK)
{
printfPQExpBuffer(&conn->errorMessage,
libpq_gettext("could not send SSL negotiation packet: %s\n"),
SOCK_STRERROR(SOCK_ERRNO));
goto connect_errReturn;
}
retry2:
/* Now receive the postmasters response */
if (recv(conn->sock, &SSLok, 1, 0) != 1)
{
if (SOCK_ERRNO == EINTR)
/* Interrupted system call - we'll just try again */
goto retry2;
printfPQExpBuffer(&conn->errorMessage,
libpq_gettext("could not receive server response to SSL negotiation packet: %s\n"),
SOCK_STRERROR(SOCK_ERRNO));
goto connect_errReturn;
}
if (SSLok == 'S')
{
if (pqsecure_initialize(conn) == -1 ||
pqsecure_open_client(conn) == -1)
goto connect_errReturn;
/* SSL connection finished. Continue to send startup packet */
}
else if (SSLok == 'E')
{
/* Received error - probably protocol mismatch */
if (conn->Pfdebug)
fprintf(conn->Pfdebug, "Postmaster reports error, attempting fallback to pre-7.0.\n");
pqsecure_close(conn);
closesocket(conn->sock);
conn->sock = -1;
conn->allow_ssl_try = FALSE;
return connectDBStart(conn);
}
else if (SSLok != 'N')
{
printfPQExpBuffer(&conn->errorMessage,
libpq_gettext("received invalid response to SSL negotiation: %c\n"),
SSLok);
goto connect_errReturn;
}
}
if (conn->require_ssl && !conn->ssl)
{
/* Require SSL, but server does not support/want it */
printfPQExpBuffer(&conn->errorMessage,
libpq_gettext("server does not support SSL, but SSL was required\n"));
goto connect_errReturn;
}
#endif
/*
* This makes the connection non-blocking, for all those cases which
* forced us not to do it above.
*/
#if defined(USE_SSL)
if (connectMakeNonblocking(conn) == 0)
goto connect_errReturn;
#endif
if (PQconnectPoll(conn) == PGRES_POLLING_WRITING)
return 1;
connect_errReturn:
......@@ -1055,8 +878,6 @@ connect_errReturn:
conn->sock = -1;
}
conn->status = CONNECTION_BAD;
if (addrs != NULL)
freeaddrinfo2(hint.ai_family, addrs);
return 0;
}
......@@ -1156,13 +977,12 @@ connectDBComplete(PGconn *conn)
* 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
* supply a host name instead) then PQconnectStart will block on
* gethostbyname. You will be fine if using Unix sockets (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.
*
* ----------------
*/
......@@ -1206,6 +1026,15 @@ PQconnectPoll(PGconn *conn)
case CONNECTION_MADE:
break;
/* We allow pqSetenvPoll to decide whether to proceed. */
case CONNECTION_SETENV:
break;
/* Special cases: proceed without waiting. */
case CONNECTION_SSL_STARTUP:
case CONNECTION_NEEDED:
break;
default:
printfPQExpBuffer(&conn->errorMessage,
libpq_gettext(
......@@ -1217,9 +1046,120 @@ PQconnectPoll(PGconn *conn)
keep_going: /* We will come back to here until there
* is nothing left to parse. */
* is nothing left to do. */
switch (conn->status)
{
case CONNECTION_NEEDED:
{
/*
* Try to initiate a connection to one of the addresses
* returned by getaddrinfo2(). conn->addr_cur is the
* next one to try. We fail when we run out of addresses
* (reporting the error returned for the *last* alternative,
* which may not be what users expect :-().
*/
while (conn->addr_cur != NULL)
{
struct addrinfo *addr_cur = conn->addr_cur;
/* Remember current address for possible error msg */
memcpy(&conn->raddr, addr_cur->ai_addr,
addr_cur->ai_addrlen);
conn->raddr_len = addr_cur->ai_addrlen;
/* Open a socket */
conn->sock = socket(addr_cur->ai_family,
SOCK_STREAM,
addr_cur->ai_protocol);
if (conn->sock < 0)
{
/*
* ignore socket() failure if we have more addresses
* to try
*/
if (addr_cur->ai_next != NULL)
{
conn->addr_cur = addr_cur->ai_next;
continue;
}
printfPQExpBuffer(&conn->errorMessage,
libpq_gettext("could not create socket: %s\n"),
SOCK_STRERROR(SOCK_ERRNO));
break;
}
/*
* Select socket options: no delay of outgoing data for
* TCP sockets, and nonblock mode. Fail if this fails.
*/
if (isAF_INETx(addr_cur->ai_family))
{
if (!connectNoDelay(conn))
break;
}
if (connectMakeNonblocking(conn) == 0)
break;
/*
* Start/make connection. This should not block, since
* we are in nonblock mode. If it does, well, too bad.
*/
retry_connect:
if (connect(conn->sock, addr_cur->ai_addr,
addr_cur->ai_addrlen) < 0)
{
if (SOCK_ERRNO == EINTR)
/* Interrupted system call - just try again */
goto retry_connect;
if (SOCK_ERRNO == EINPROGRESS ||
SOCK_ERRNO == EWOULDBLOCK ||
SOCK_ERRNO == 0)
{
/*
* This is fine - we're in non-blocking mode,
* and the connection is in progress. Tell
* caller to wait for write-ready on socket.
*/
conn->status = CONNECTION_STARTED;
return PGRES_POLLING_WRITING;
}
/* otherwise, trouble */
}
else
{
/*
* Hm, we're connected already --- seems the
* "nonblock connection" wasn't. Advance the state
* machine and go do the next stuff.
*/
conn->status = CONNECTION_STARTED;
goto keep_going;
}
/*
* This connection failed --- set up error report,
* then close socket (do it this way in case close()
* affects the value of errno...). We will ignore the
* connect() failure and keep going if there are
* more addresses.
*/
connectFailureMessage(conn, SOCK_ERRNO);
if (conn->sock >= 0)
{
closesocket(conn->sock);
conn->sock = -1;
}
/*
* Try the next address, if any.
*/
conn->addr_cur = addr_cur->ai_next;
} /* loop over addresses */
/*
* Ooops, no more addresses. An appropriate error message
* is already set up, so just set the right status.
*/
goto error_return;
}
case CONNECTION_STARTED:
{
ACCEPT_TYPE_ARG3 laddrlen;
......@@ -1228,7 +1168,7 @@ keep_going: /* We will come back to here until there
/*
* Write ready, since we've made it here, so the
* connection has been made.
* connection has been made ... or has failed.
*/
/*
......@@ -1252,6 +1192,21 @@ keep_going: /* We will come back to here until there
* friendly error message.
*/
connectFailureMessage(conn, optval);
/*
* If more addresses remain, keep trying, just as in
* the case where connect() returned failure immediately.
*/
if (conn->addr_cur->ai_next != NULL)
{
if (conn->sock >= 0)
{
closesocket(conn->sock);
conn->sock = -1;
}
conn->addr_cur = conn->addr_cur->ai_next;
conn->status = CONNECTION_NEEDED;
goto keep_going;
}
goto error_return;
}
......@@ -1265,6 +1220,9 @@ keep_going: /* We will come back to here until there
goto error_return;
}
/*
* Make sure we can write before advancing to next step.
*/
conn->status = CONNECTION_MADE;
return PGRES_POLLING_WRITING;
}
......@@ -1274,18 +1232,59 @@ keep_going: /* We will come back to here until there
char *startpacket;
int packetlen;
#ifdef USE_SSL
/*
* If SSL is enabled and we haven't already got it running,
* request it instead of sending the startup message.
*/
#ifdef HAVE_UNIX_SOCKETS
if (conn->raddr.sa.sa_family == AF_UNIX)
{
/* Don't bother requesting SSL over a Unix socket */
conn->allow_ssl_try = false;
conn->require_ssl = false;
}
#endif
if (conn->allow_ssl_try && conn->ssl == NULL)
{
ProtocolVersion pv;
/*
* Send the SSL request packet.
*
* Theoretically, this could block, but it really shouldn't
* since we only got here if the socket is write-ready.
*/
pv = htonl(NEGOTIATE_SSL_CODE);
if (pqPacketSend(conn, 0, &pv, sizeof(pv)) != STATUS_OK)
{
printfPQExpBuffer(&conn->errorMessage,
libpq_gettext("could not send SSL negotiation packet: %s\n"),
SOCK_STRERROR(SOCK_ERRNO));
goto error_return;
}
/* Ok, wait for response */
conn->status = CONNECTION_SSL_STARTUP;
return PGRES_POLLING_READING;
}
#endif /* USE_SSL */
/*
* Build the startup packet.
*/
packetlen = build_startup_packet(conn, NULL);
startpacket = (char *) malloc(packetlen);
if (PG_PROTOCOL_MAJOR(conn->pversion) >= 3)
startpacket = pqBuildStartupPacket3(conn, &packetlen,
EnvironmentOptions);
else
startpacket = pqBuildStartupPacket2(conn, &packetlen,
EnvironmentOptions);
if (!startpacket)
{
printfPQExpBuffer(&conn->errorMessage,
libpq_gettext("out of memory\n"));
goto error_return;
}
packetlen = build_startup_packet(conn, startpacket);
/*
* Send the startup packet.
......@@ -1309,7 +1308,109 @@ keep_going: /* We will come back to here until there
}
/*
* Handle the authentication exchange: wait for postmaster
* Handle SSL negotiation: wait for postmaster
* messages and respond as necessary.
*/
case CONNECTION_SSL_STARTUP:
{
#ifdef USE_SSL
PostgresPollingStatusType pollres;
/*
* On first time through, get the postmaster's response
* to our SSL negotiation packet. Be careful to read only
* one byte (if there's more, it could be SSL data).
*/
if (conn->ssl == NULL)
{
char SSLok;
int nread;
retry_ssl_read:
nread = recv(conn->sock, &SSLok, 1, 0);
if (nread < 0)
{
if (SOCK_ERRNO == EINTR)
/* Interrupted system call - just try again */
goto retry_ssl_read;
printfPQExpBuffer(&conn->errorMessage,
libpq_gettext("could not receive server response to SSL negotiation packet: %s\n"),
SOCK_STRERROR(SOCK_ERRNO));
goto error_return;
}
if (nread == 0)
/* caller failed to wait for data */
return PGRES_POLLING_READING;
if (SSLok == 'S')
{
/* Do one-time setup; this creates conn->ssl */
if (pqsecure_initialize(conn) == -1)
goto error_return;
}
else if (SSLok == 'N')
{
if (conn->require_ssl)
{
/* Require SSL, but server does not want it */
printfPQExpBuffer(&conn->errorMessage,
libpq_gettext("server does not support SSL, but SSL was required\n"));
goto error_return;
}
/* Otherwise, proceed with normal startup */
conn->allow_ssl_try = false;
conn->status = CONNECTION_MADE;
return PGRES_POLLING_WRITING;
}
else if (SSLok == 'E')
{
/* Received error - probably protocol mismatch */
if (conn->Pfdebug)
fprintf(conn->Pfdebug, "Postmaster reports error, attempting fallback to pre-7.0.\n");
if (conn->require_ssl)
{
/* Require SSL, but server is too old */
printfPQExpBuffer(&conn->errorMessage,
libpq_gettext("server does not support SSL, but SSL was required\n"));
goto error_return;
}
/* Otherwise, try again without SSL */
conn->allow_ssl_try = false;
/* Assume it ain't gonna handle protocol 3, either */
conn->pversion = PG_PROTOCOL(2,0);
/* Must drop the old connection */
closesocket(conn->sock);
conn->sock = -1;
conn->status = CONNECTION_NEEDED;
goto keep_going;
}
else
{
printfPQExpBuffer(&conn->errorMessage,
libpq_gettext("received invalid response to SSL negotiation: %c\n"),
SSLok);
goto error_return;
}
}
/*
* Begin or continue the SSL negotiation process.
*/
pollres = pqsecure_open_client(conn);
if (pollres == PGRES_POLLING_OK)
{
/* SSL handshake done, ready to send startup packet */
conn->status = CONNECTION_MADE;
return PGRES_POLLING_WRITING;
}
return pollres;
#else /* !USE_SSL */
/* can't get here */
goto error_return;
#endif /* USE_SSL */
}
/*
* Handle authentication exchange: wait for postmaster
* messages and respond as necessary.
*/
case CONNECTION_AWAITING_RESPONSE:
......@@ -1343,18 +1444,25 @@ keep_going: /* We will come back to here until there
printfPQExpBuffer(&conn->errorMessage,
libpq_gettext(
"expected authentication request from "
"server, but received %c\n"
),
"server, but received %c\n"),
beresp);
goto error_return;
}
if (PG_PROTOCOL_MAJOR(conn->pversion) >= 3)
{
/* Read message length word */
if (pqGetInt(&msgLength, 4, conn))
{
/* We'll come back when there is more data */
return PGRES_POLLING_READING;
}
}
else
{
/* Set phony message length to disable checks below */
msgLength = 8;
}
/*
* Try to validate message length before using it.
......@@ -1368,8 +1476,7 @@ keep_going: /* We will come back to here until there
printfPQExpBuffer(&conn->errorMessage,
libpq_gettext(
"expected authentication request from "
"server, but received %c\n"
),
"server, but received %c\n"),
beresp);
goto error_return;
}
......@@ -1392,11 +1499,30 @@ keep_going: /* We will come back to here until there
* conventions.
*/
appendPQExpBufferChar(&conn->errorMessage, '\n');
/*
* If we tried to open the connection in 3.0 protocol,
* fall back to 2.0 protocol.
*/
if (PG_PROTOCOL_MAJOR(conn->pversion) >= 3)
{
conn->pversion = PG_PROTOCOL(2,0);
/* Must drop the old connection */
pqsecure_close(conn);
closesocket(conn->sock);
conn->sock = -1;
conn->status = CONNECTION_NEEDED;
goto keep_going;
}
goto error_return;
}
/*
* Can't process if message body isn't all here yet.
*
* (In protocol 2.0 case, we are assuming messages carry
* at least 4 bytes of data.)
*/
msgLength -= 4;
avail = conn->inEnd - conn->inCursor;
......@@ -1405,7 +1531,7 @@ keep_going: /* We will come back to here until there
/*
* Before returning, try to enlarge the input buffer if
* needed to hold the whole message; see notes in
* parseInput.
* pqParseInput3.
*/
if (pqCheckInBufferSpace(conn->inCursor + msgLength, conn))
goto error_return;
......@@ -1416,11 +1542,22 @@ keep_going: /* We will come back to here until there
/* Handle errors. */
if (beresp == 'E')
{
if (pqGetErrorNotice(conn, true))
if (PG_PROTOCOL_MAJOR(conn->pversion) >= 3)
{
if (pqGetErrorNotice3(conn, true))
{
/* We'll come back when there is more data */
return PGRES_POLLING_READING;
}
}
else
{
if (pqGets(&conn->errorMessage, conn))
{
/* We'll come back when there is more data */
return PGRES_POLLING_READING;
}
}
/* OK, we read the message; mark data consumed */
conn->inStart = conn->inCursor;
goto error_return;
......@@ -1548,13 +1685,56 @@ keep_going: /* We will come back to here until there
goto error_return;
}
/* We can release the address list now. */
freeaddrinfo2(conn->addrlist);
conn->addrlist = NULL;
conn->addr_cur = NULL;
/* Fire up post-connection housekeeping if needed */
if (PG_PROTOCOL_MAJOR(conn->pversion) < 3)
{
conn->status = CONNECTION_SETENV;
conn->setenv_state = SETENV_STATE_OPTION_SEND;
conn->next_eo = EnvironmentOptions;
return PGRES_POLLING_WRITING;
}
/* Otherwise, we are open for business! */
conn->status = CONNECTION_OK;
return PGRES_POLLING_OK;
}
case CONNECTION_SETENV:
/*
* We are open for business!
* 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;
return PGRES_POLLING_OK;
switch (pqSetenvPoll(conn))
{
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:
printfPQExpBuffer(&conn->errorMessage,
libpq_gettext(
......@@ -1576,6 +1756,7 @@ error_return:
* socket closed when PQfinish is called (which is compulsory even
* after an error, since the connection structure must be freed).
*/
conn->status = CONNECTION_BAD;
return PGRES_POLLING_FAILED;
}
......@@ -1598,6 +1779,8 @@ makeEmptyPGconn(void)
conn->noticeHook = defaultNoticeProcessor;
conn->status = CONNECTION_BAD;
conn->asyncStatus = PGASYNC_IDLE;
conn->setenv_state = SETENV_STATE_IDLE;
conn->client_encoding = PG_SQL_ASCII;
conn->notifyList = DLNewList();
conn->sock = -1;
#ifdef USE_SSL
......@@ -1622,6 +1805,7 @@ makeEmptyPGconn(void)
conn->nonblocking = FALSE;
initPQExpBuffer(&conn->errorMessage);
initPQExpBuffer(&conn->workBuffer);
if (conn->inBuffer == NULL ||
conn->outBuffer == NULL ||
conn->errorMessage.data == NULL ||
......@@ -1631,6 +1815,7 @@ makeEmptyPGconn(void)
freePGconn(conn);
conn = NULL;
}
return conn;
}
......@@ -1660,6 +1845,8 @@ freePGconn(PGconn *conn)
free(conn->pgunixsocket);
if (conn->pgtty)
free(conn->pgtty);
if (conn->connect_timeout)
free(conn->connect_timeout);
if (conn->pgoptions)
free(conn->pgoptions);
if (conn->dbName)
......@@ -1668,11 +1855,10 @@ freePGconn(PGconn *conn)
free(conn->pguser);
if (conn->pgpass)
free(conn->pgpass);
if (conn->connect_timeout)
free(conn->connect_timeout);
/* Note that conn->Pfdebug is not ours to close or free */
if (conn->notifyList)
DLFreeList(conn->notifyList);
freeaddrinfo2(conn->addrlist);
if (conn->lobjfuncs)
free(conn->lobjfuncs);
if (conn->inBuffer)
......@@ -1701,7 +1887,7 @@ closePGconn(PGconn *conn)
* Try to send "close connection" message to backend. Ignore any
* error.
*/
pqPutMsgStart('X', conn);
pqPutMsgStart('X', false, conn);
pqPutMsgEnd(conn);
pqFlush(conn);
}
......@@ -1731,8 +1917,6 @@ closePGconn(PGconn *conn)
conn->lobjfuncs = NULL;
conn->inStart = conn->inCursor = conn->inEnd = 0;
conn->outCount = 0;
conn->nonblocking = FALSE;
}
/*
......@@ -1936,13 +2120,16 @@ cancel_errReturn:
*
* RETURNS: STATUS_ERROR if the write fails, STATUS_OK otherwise.
* SIDE_EFFECTS: may block.
*/
*
* Note: all messages sent with this routine have a length word, whether
* it's protocol 2.0 or 3.0.
*/
int
pqPacketSend(PGconn *conn, char pack_type,
const void *buf, size_t buf_len)
{
/* Start the message. */
if (pqPutMsgStart(pack_type, conn))
if (pqPutMsgStart(pack_type, true, conn))
return STATUS_ERROR;
/* Send the message body. */
......@@ -2341,11 +2528,6 @@ conninfo_parse(const char *conninfo, PQExpBuffer errorMessage)
/* note any error message is thrown away */
continue;
}
/*
* We used to special-case dbname too, but it's easier to let the
* backend handle the fallback for that.
*/
}
return options;
......@@ -2385,87 +2567,6 @@ PQconninfoFree(PQconninfoOption *connOptions)
}
/*
* Build a startup packet given a filled-in PGconn structure.
*
* We need to figure out how much space is needed, then fill it in.
* To avoid duplicate logic, this routine is called twice: the first time
* (with packet == NULL) just counts the space needed, the second time
* (with packet == allocated space) fills it in. Return value is the number
* of bytes used.
*/
static int
build_startup_packet(const PGconn *conn, char *packet)
{
int packet_len = 0;
const struct EnvironmentOptions *next_eo;
/* Protocol version comes first. */
if (packet)
{
ProtocolVersion pv = htonl(PG_PROTOCOL_LIBPQ);
memcpy(packet + packet_len, &pv, sizeof(ProtocolVersion));
}
packet_len += sizeof(ProtocolVersion);
/* Add user name, database name, options */
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;
}
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;
}
if (conn->pgoptions && conn->pgoptions[0])
{
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;
}
/* Add any environment-driven GUC settings needed */
for (next_eo = EnvironmentOptions; next_eo->envName; next_eo++)
{
const char *val;
if ((val = getenv(next_eo->envName)) != NULL)
{
if (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 trailing terminator */
if (packet)
packet[packet_len] = '\0';
packet_len++;
return packet_len;
}
/* =========== accessor functions for PGconn ========= */
char *
PQdb(const PGconn *conn)
......
......@@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $Header: /cvsroot/pgsql/src/interfaces/libpq/fe-exec.c,v 1.136 2003/05/26 20:05:20 tgl Exp $
* $Header: /cvsroot/pgsql/src/interfaces/libpq/fe-exec.c,v 1.137 2003/06/08 17:43:00 tgl Exp $
*
*-------------------------------------------------------------------------
*/
......@@ -42,22 +42,8 @@ char *const pgresStatus[] = {
};
/* Note: DONOTICE macro will work if applied to either PGconn or PGresult */
#define DONOTICE(conn,message) \
((*(conn)->noticeHook) ((conn)->noticeArg, (message)))
static void pqCatenateResultError(PGresult *res, const char *msg);
static void saveErrorResult(PGconn *conn);
static PGresult *prepareAsyncResult(PGconn *conn);
static int addTuple(PGresult *res, PGresAttValue * tup);
static void parseInput(PGconn *conn);
static void handleSendFailure(PGconn *conn);
static void handleSyncLoss(PGconn *conn, char id, int msgLength);
static int getRowDescriptions(PGconn *conn);
static int getAnotherTuple(PGconn *conn, int msgLength);
static int getParameterStatus(PGconn *conn);
static int getNotify(PGconn *conn);
/* ----------------
......@@ -142,7 +128,6 @@ PQmakeEmptyPGresult(PGconn *conn, ExecStatusType status)
result = (PGresult *) malloc(sizeof(PGresult));
result->xconn = conn; /* might be NULL */
result->ntups = 0;
result->numAttributes = 0;
result->attDescs = NULL;
......@@ -336,7 +321,7 @@ pqSetResultError(PGresult *res, const char *msg)
* pqCatenateResultError -
* concatenate a new error message to the one already in a PGresult
*/
static void
void
pqCatenateResultError(PGresult *res, const char *msg)
{
PQExpBufferData errorBuf;
......@@ -403,8 +388,8 @@ pqClearAsyncResult(PGconn *conn)
* and immediately closes the connection --- we want to report both the
* backend error and the connection closure error.)
*/
static void
saveErrorResult(PGconn *conn)
void
pqSaveErrorResult(PGconn *conn)
{
/*
* If no old async result, just let PQmakeEmptyPGresult make one.
......@@ -431,8 +416,8 @@ saveErrorResult(PGconn *conn)
* result storage and make sure PQerrorMessage will agree with the result's
* error string.
*/
static PGresult *
prepareAsyncResult(PGconn *conn)
PGresult *
pqPrepareAsyncResult(PGconn *conn)
{
PGresult *res;
......@@ -460,12 +445,12 @@ prepareAsyncResult(PGconn *conn)
}
/*
* addTuple
* pqAddTuple
* add a row pointer to the PGresult structure, growing it if necessary
* Returns TRUE if OK, FALSE if not enough memory to add the row
*/
static int
addTuple(PGresult *res, PGresAttValue * tup)
int
pqAddTuple(PGresult *res, PGresAttValue *tup)
{
if (res->ntups >= res->tupArrSize)
{
......@@ -500,6 +485,85 @@ addTuple(PGresult *res, PGresAttValue * tup)
}
/*
* pqSaveParameterStatus - remember parameter status sent by backend
*/
void
pqSaveParameterStatus(PGconn *conn, const char *name, const char *value)
{
pgParameterStatus *pstatus;
pgParameterStatus *prev;
if (conn->Pfdebug)
fprintf(conn->Pfdebug, "pqSaveParameterStatus: '%s' = '%s'\n",
name, value);
/*
* Forget any old information about the parameter
*/
for (pstatus = conn->pstatus, prev = NULL;
pstatus != NULL;
prev = pstatus, pstatus = pstatus->next)
{
if (strcmp(pstatus->name, name) == 0)
{
if (prev)
prev->next = pstatus->next;
else
conn->pstatus = pstatus->next;
free(pstatus); /* frees name and value strings too */
break;
}
}
/*
* Store new info as a single malloc block
*/
pstatus = (pgParameterStatus *) malloc(sizeof(pgParameterStatus) +
strlen(name) + strlen(value) + 2);
if (pstatus)
{
char *ptr;
ptr = ((char *) pstatus) + sizeof(pgParameterStatus);
pstatus->name = ptr;
strcpy(ptr, name);
ptr += strlen(name) + 1;
pstatus->value = ptr;
strcpy(ptr, value);
pstatus->next = conn->pstatus;
conn->pstatus = pstatus;
}
/*
* Special hacks: remember client_encoding as a numeric value, and
* remember at least the first few bytes of server version.
*/
if (strcmp(name, "client_encoding") == 0)
conn->client_encoding = pg_char_to_encoding(value);
if (strcmp(name, "server_version") == 0)
StrNCpy(conn->sversion, value, sizeof(conn->sversion));
}
/*
* pqGetParameterStatus - fetch parameter value, if available
*
* Returns NULL if info not available
*
* XXX this probably should be exported for client use
*/
const char *
pqGetParameterStatus(PGconn *conn, const char *name)
{
pgParameterStatus *pstatus;
for (pstatus = conn->pstatus; pstatus != NULL; pstatus = pstatus->next)
{
if (strcmp(pstatus->name, name) == 0)
return pstatus->value;
}
return NULL;
}
/*
* PQsendQuery
* Submit a query, but don't wait for it to finish
......@@ -543,11 +607,11 @@ PQsendQuery(PGconn *conn, const char *query)
conn->curTuple = NULL;
/* construct the outgoing Query message */
if (pqPutMsgStart('Q', conn) < 0 ||
if (pqPutMsgStart('Q', false, conn) < 0 ||
pqPuts(query, conn) < 0 ||
pqPutMsgEnd(conn) < 0)
{
handleSendFailure(conn);
pqHandleSendFailure(conn);
return 0;
}
......@@ -558,7 +622,7 @@ PQsendQuery(PGconn *conn, const char *query)
*/
if (pqFlush(conn) < 0)
{
handleSendFailure(conn);
pqHandleSendFailure(conn);
return 0;
}
......@@ -568,15 +632,15 @@ PQsendQuery(PGconn *conn, const char *query)
}
/*
* handleSendFailure: try to clean up after failure to send command.
* pqHandleSendFailure: try to clean up after failure to send command.
*
* Primarily, what we want to accomplish here is to process an async
* NOTICE message that the backend might have sent just before it died.
*
* NOTE: this routine should only be called in PGASYNC_IDLE state.
*/
static void
handleSendFailure(PGconn *conn)
void
pqHandleSendFailure(PGconn *conn)
{
/*
* Accept any available input data, ignoring errors. Note that if
......@@ -634,499 +698,15 @@ PQconsumeInput(PGconn *conn)
* until input is exhausted or a stopping state is reached.
* Note that this function will NOT attempt to read more data from the backend.
*/
static void
parseInput(PGconn *conn)
{
char id;
int msgLength;
int avail;
char noticeWorkspace[128];
/*
* Loop to parse successive complete messages available in the buffer.
*/
for (;;)
{
/*
* Try to read a message. First get the type code and length.
* Return if not enough data.
*/
conn->inCursor = conn->inStart;
if (pqGetc(&id, conn))
return;
if (pqGetInt(&msgLength, 4, conn))
return;
/*
* Try to validate message type/length here. A length less than 4
* is definitely broken. Large lengths should only be believed
* for a few message types.
*/
if (msgLength < 4)
{
handleSyncLoss(conn, id, msgLength);
return;
}
if (msgLength > 30000 &&
!(id == 'T' || id == 'D' || id == 'd'))
{
handleSyncLoss(conn, id, msgLength);
return;
}
/*
* Can't process if message body isn't all here yet.
*/
msgLength -= 4;
avail = conn->inEnd - conn->inCursor;
if (avail < msgLength)
{
/*
* Before returning, enlarge the input buffer if needed to hold
* the whole message. This is better than leaving it to
* pqReadData because we can avoid multiple cycles of realloc()
* when the message is large; also, we can implement a reasonable
* recovery strategy if we are unable to make the buffer big
* enough.
*/
if (pqCheckInBufferSpace(conn->inCursor + msgLength, conn))
{
/*
* XXX add some better recovery code... plan is to skip
* over the message using its length, then report an error.
* For the moment, just treat this like loss of sync (which
* indeed it might be!)
*/
handleSyncLoss(conn, id, msgLength);
}
return;
}
/*
* NOTIFY and NOTICE messages can happen in any state; always process
* them right away.
*
* Most other messages should only be processed while in BUSY state.
* (In particular, in READY state we hold off further parsing
* until the application collects the current PGresult.)
*
* However, if the state is IDLE then we got trouble; we need to deal
* with the unexpected message somehow.
*
* ParameterStatus ('S') messages are a special case: in IDLE state
* we must process 'em (this case could happen if a new value was
* adopted from config file due to SIGHUP), but otherwise we hold
* off until BUSY state.
*/
if (id == 'A')
{
if (getNotify(conn))
return;
}
else if (id == 'N')
{
if (pqGetErrorNotice(conn, false))
return;
}
else if (conn->asyncStatus != PGASYNC_BUSY)
{
/* If not IDLE state, just wait ... */
if (conn->asyncStatus != PGASYNC_IDLE)
return;
/*
* Unexpected message in IDLE state; need to recover somehow.
* ERROR messages are displayed using the notice processor;
* ParameterStatus is handled normally;
* anything else is just dropped on the floor after displaying
* a suitable warning notice. (An ERROR is very possibly the
* backend telling us why it is about to close the connection,
* so we don't want to just discard it...)
*/
if (id == 'E')
{
if (pqGetErrorNotice(conn, false /* treat as notice */))
return;
}
else if (id == 'S')
{
if (getParameterStatus(conn))
return;
}
else
{
snprintf(noticeWorkspace, sizeof(noticeWorkspace),
libpq_gettext("message type 0x%02x arrived from server while idle\n"),
id);
DONOTICE(conn, noticeWorkspace);
/* Discard the unexpected message */
conn->inCursor += msgLength;
}
}
if (PG_PROTOCOL_MAJOR(conn->pversion) >= 3)
pqParseInput3(conn);
else
{
/*
* In BUSY state, we can process everything.
*/
switch (id)
{
case 'C': /* command complete */
if (pqGets(&conn->workBuffer, conn))
return;
if (conn->result == NULL)
conn->result = PQmakeEmptyPGresult(conn,
PGRES_COMMAND_OK);
strncpy(conn->result->cmdStatus, conn->workBuffer.data,
CMDSTATUS_LEN);
conn->asyncStatus = PGASYNC_READY;
break;
case 'E': /* error return */
if (pqGetErrorNotice(conn, true))
return;
conn->asyncStatus = PGASYNC_READY;
break;
case 'Z': /* backend is ready for new query */
if (pqGetc(&conn->xact_status, conn))
return;
conn->asyncStatus = PGASYNC_IDLE;
break;
case 'I': /* empty query */
if (conn->result == NULL)
conn->result = PQmakeEmptyPGresult(conn,
PGRES_EMPTY_QUERY);
conn->asyncStatus = PGASYNC_READY;
break;
case 'S': /* parameter status */
if (getParameterStatus(conn))
return;
break;
case 'K': /* secret key data from the backend */
/*
* This is expected only during backend startup, but
* it's just as easy to handle it as part of the main
* loop. Save the data and continue processing.
*/
if (pqGetInt(&(conn->be_pid), 4, conn))
return;
if (pqGetInt(&(conn->be_key), 4, conn))
return;
break;
case 'T': /* row descriptions (start of query
* results) */
if (conn->result == NULL)
{
/* First 'T' in a query sequence */
if (getRowDescriptions(conn))
return;
}
else
{
/*
* A new 'T' message is treated as the start of
* another PGresult. (It is not clear that this
* is really possible with the current backend.)
* We stop parsing until the application accepts
* the current result.
*/
conn->asyncStatus = PGASYNC_READY;
return;
}
break;
case 'D': /* Data Row */
if (conn->result != NULL &&
conn->result->resultStatus == PGRES_TUPLES_OK)
{
/* Read another tuple of a normal query response */
if (getAnotherTuple(conn, msgLength))
return;
}
else if (conn->result != NULL &&
conn->result->resultStatus == PGRES_FATAL_ERROR)
{
/*
* We've already choked for some reason. Just discard
* tuples till we get to the end of the query.
*/
conn->inCursor += msgLength;
}
else
{
/* Set up to report error at end of query */
printfPQExpBuffer(&conn->errorMessage,
libpq_gettext("server sent data (\"D\" message) without prior row description (\"T\" message)\n"));
saveErrorResult(conn);
/* Discard the unexpected message */
conn->inCursor += msgLength;
}
break;
case 'G': /* Start Copy In */
if (pqGetc(&conn->copy_is_binary, conn))
return;
/* XXX we currently ignore the rest of the message */
conn->inCursor = conn->inStart + 5 + msgLength;
conn->asyncStatus = PGASYNC_COPY_IN;
break;
case 'H': /* Start Copy Out */
if (pqGetc(&conn->copy_is_binary, conn))
return;
/* XXX we currently ignore the rest of the message */
conn->inCursor = conn->inStart + 5 + msgLength;
conn->asyncStatus = PGASYNC_COPY_OUT;
conn->copy_already_done = 0;
break;
case 'd': /* Copy Data */
/*
* If we see Copy Data, just silently drop it. This
* would only occur if application exits COPY OUT mode
* too early.
*/
conn->inCursor += msgLength;
break;
case 'c': /* Copy Done */
/*
* If we see Copy Done, just silently drop it. This
* is the normal case during PQendcopy. We will keep
* swallowing data, expecting to see command-complete
* for the COPY command.
*/
break;
default:
printfPQExpBuffer(&conn->errorMessage,
libpq_gettext(
"unexpected response from server; first received character was \"%c\"\n"),
id);
/* build an error result holding the error message */
saveErrorResult(conn);
/* not sure if we will see more, so go to ready state */
conn->asyncStatus = PGASYNC_READY;
/* Discard the unexpected message */
conn->inCursor += msgLength;
break;
} /* switch on protocol character */
}
/* Successfully consumed this message */
if (conn->inCursor == conn->inStart + 5 + msgLength)
{
/* Normal case: parsing agrees with specified length */
conn->inStart = conn->inCursor;
}
else
{
/* Trouble --- report it */
printfPQExpBuffer(&conn->errorMessage,
libpq_gettext("Message contents do not agree with length in message type \"%c\"\n"),
id);
/* build an error result holding the error message */
saveErrorResult(conn);
conn->asyncStatus = PGASYNC_READY;
/* trust the specified message length as what to skip */
conn->inStart += 5 + msgLength;
}
}
}
/*
* handleSyncLoss: clean up after loss of message-boundary sync
*
* There isn't really a lot we can do here except abandon the connection.
*/
static void
handleSyncLoss(PGconn *conn, char id, int msgLength)
{
printfPQExpBuffer(&conn->errorMessage,
libpq_gettext(
"lost synchronization with server: got message type \"%c\", length %d\n"),
id, msgLength);
conn->status = CONNECTION_BAD; /* No more connection to backend */
pqsecure_close(conn);
closesocket(conn->sock);
conn->sock = -1;
conn->asyncStatus = PGASYNC_READY; /* drop out of GetResult wait loop */
}
/*
* parseInput subroutine to read a 'T' (row descriptions) message.
* We build a PGresult structure containing the attribute data.
* Returns: 0 if completed message, EOF if not enough data yet.
*
* Note that if we run out of data, we have to release the partially
* constructed PGresult, and rebuild it again next time. Fortunately,
* that shouldn't happen often, since 'T' messages usually fit in a packet.
*/
static int
getRowDescriptions(PGconn *conn)
{
PGresult *result;
int nfields;
int i;
result = PQmakeEmptyPGresult(conn, PGRES_TUPLES_OK);
/* parseInput already read the 'T' label and message length. */
/* the next two bytes are the number of fields */
if (pqGetInt(&(result->numAttributes), 2, conn))
{
PQclear(result);
return EOF;
}
nfields = result->numAttributes;
/* allocate space for the attribute descriptors */
if (nfields > 0)
{
result->attDescs = (PGresAttDesc *)
pqResultAlloc(result, nfields * sizeof(PGresAttDesc), TRUE);
MemSet((char *) result->attDescs, 0, nfields * sizeof(PGresAttDesc));
}
/* get type info */
for (i = 0; i < nfields; i++)
{
int tableid;
int columnid;
int typid;
int typlen;
int atttypmod;
int format;
if (pqGets(&conn->workBuffer, conn) ||
pqGetInt(&tableid, 4, conn) ||
pqGetInt(&columnid, 2, conn) ||
pqGetInt(&typid, 4, conn) ||
pqGetInt(&typlen, 2, conn) ||
pqGetInt(&atttypmod, 4, conn) ||
pqGetInt(&format, 2, conn))
{
PQclear(result);
return EOF;
}
/*
* Since pqGetInt treats 2-byte integers as unsigned, we need to
* coerce these results to signed form.
*/
columnid = (int) ((int16) columnid);
typlen = (int) ((int16) typlen);
format = (int) ((int16) format);
result->attDescs[i].name = pqResultStrdup(result,
conn->workBuffer.data);
result->attDescs[i].typid = typid;
result->attDescs[i].typlen = typlen;
result->attDescs[i].atttypmod = atttypmod;
/* XXX todo: save tableid/columnid, format too */
}
/* Success! */
conn->result = result;
return 0;
}
/*
* parseInput subroutine to read a 'D' (row data) message.
* We add another tuple to the existing PGresult structure.
* Returns: 0 if completed message, EOF if error or not enough data yet.
*
* Note that if we run out of data, we have to suspend and reprocess
* the message after more data is received. We keep a partially constructed
* tuple in conn->curTuple, and avoid reallocating already-allocated storage.
*/
static int
getAnotherTuple(PGconn *conn, int msgLength)
{
PGresult *result = conn->result;
int nfields = result->numAttributes;
PGresAttValue *tup;
int tupnfields; /* # fields from tuple */
int vlen; /* length of the current field value */
int i;
/* Allocate tuple space if first time for this data message */
if (conn->curTuple == NULL)
{
conn->curTuple = (PGresAttValue *)
pqResultAlloc(result, nfields * sizeof(PGresAttValue), TRUE);
if (conn->curTuple == NULL)
goto outOfMemory;
MemSet((char *) conn->curTuple, 0, nfields * sizeof(PGresAttValue));
}
tup = conn->curTuple;
/* Get the field count and make sure it's what we expect */
if (pqGetInt(&tupnfields, 2, conn))
return EOF;
if (tupnfields != nfields)
{
/* Replace partially constructed result with an error result */
printfPQExpBuffer(&conn->errorMessage,
libpq_gettext("unexpected field count in D message\n"));
saveErrorResult(conn);
/* Discard the failed message by pretending we read it */
conn->inCursor = conn->inStart + 5 + msgLength;
return 0;
}
/* Scan the fields */
for (i = 0; i < nfields; i++)
{
/* get the value length */
if (pqGetInt(&vlen, 4, conn))
return EOF;
if (vlen == -1)
{
/* null field */
tup[i].value = result->null_field;
tup[i].len = NULL_LEN;
continue;
}
if (vlen < 0)
vlen = 0;
if (tup[i].value == NULL)
{
tup[i].value = (char *) pqResultAlloc(result, vlen + 1, false);
if (tup[i].value == NULL)
goto outOfMemory;
}
tup[i].len = vlen;
/* read in the value */
if (vlen > 0)
if (pqGetnchar((char *) (tup[i].value), vlen, conn))
return EOF;
/* we have to terminate this ourselves */
tup[i].value[vlen] = '\0';
}
/* Success! Store the completed tuple in the result */
if (!addTuple(result, tup))
goto outOfMemory;
/* and reset for a new message */
conn->curTuple = NULL;
return 0;
outOfMemory:
/* Replace partially constructed result with an error result */
/*
* we do NOT use saveErrorResult() here, because of the likelihood
* that there's not enough memory to concatenate messages. Instead,
* discard the old result first to try to win back some memory.
*/
pqClearAsyncResult(conn);
printfPQExpBuffer(&conn->errorMessage,
libpq_gettext("out of memory for query result\n"));
conn->result = PQmakeEmptyPGresult(conn, PGRES_FATAL_ERROR);
/* Discard the failed message by pretending we read it */
conn->inCursor = conn->inStart + 5 + msgLength;
return 0;
pqParseInput2(conn);
}
/*
* PQisBusy
* Return TRUE if PQgetResult would block waiting for input.
......@@ -1174,9 +754,9 @@ PQgetResult(PGconn *conn)
* conn->errorMessage has been set by pqWait or pqReadData. We
* want to append it to any already-received error message.
*/
saveErrorResult(conn);
pqSaveErrorResult(conn);
conn->asyncStatus = PGASYNC_IDLE;
return prepareAsyncResult(conn);
return pqPrepareAsyncResult(conn);
}
/* Parse it. */
parseInput(conn);
......@@ -1189,7 +769,7 @@ PQgetResult(PGconn *conn)
res = NULL; /* query is complete */
break;
case PGASYNC_READY:
res = prepareAsyncResult(conn);
res = pqPrepareAsyncResult(conn);
/* Set the state back to BUSY, allowing parsing to proceed. */
conn->asyncStatus = PGASYNC_BUSY;
break;
......@@ -1304,205 +884,6 @@ errout:
return NULL;
}
/*
* Attempt to read an Error or Notice response message.
* This is possible in several places, so we break it out as a subroutine.
* Entry: 'E' or 'N' message type and length have already been consumed.
* Exit: returns 0 if successfully consumed message.
* returns EOF if not enough data.
*/
int
pqGetErrorNotice(PGconn *conn, bool isError)
{
PGresult *res;
PQExpBufferData workBuf;
char id;
/*
* Make a PGresult to hold the accumulated fields. We temporarily
* lie about the result status, so that PQmakeEmptyPGresult doesn't
* uselessly copy conn->errorMessage.
*/
res = PQmakeEmptyPGresult(conn, PGRES_EMPTY_QUERY);
res->resultStatus = PGRES_FATAL_ERROR;
/*
* Since the fields might be pretty long, we create a temporary
* PQExpBuffer rather than using conn->workBuffer. workBuffer is
* intended for stuff that is expected to be short. We shouldn't
* use conn->errorMessage either, since this might be only a notice.
*/
initPQExpBuffer(&workBuf);
/*
* Read the fields and save into res.
*/
for (;;)
{
if (pqGetc(&id, conn))
goto fail;
if (id == '\0')
break; /* terminator found */
if (pqGets(&workBuf, conn))
goto fail;
switch (id)
{
case 'S':
res->errSeverity = pqResultStrdup(res, workBuf.data);
break;
case 'C':
res->errCode = pqResultStrdup(res, workBuf.data);
break;
case 'M':
res->errPrimary = pqResultStrdup(res, workBuf.data);
break;
case 'D':
res->errDetail = pqResultStrdup(res, workBuf.data);
break;
case 'H':
res->errHint = pqResultStrdup(res, workBuf.data);
break;
case 'P':
res->errPosition = pqResultStrdup(res, workBuf.data);
break;
case 'W':
res->errContext = pqResultStrdup(res, workBuf.data);
break;
case 'F':
res->errFilename = pqResultStrdup(res, workBuf.data);
break;
case 'L':
res->errLineno = pqResultStrdup(res, workBuf.data);
break;
case 'R':
res->errFuncname = pqResultStrdup(res, workBuf.data);
break;
default:
/* silently ignore any other field type */
break;
}
}
/*
* Now build the "overall" error message for PQresultErrorMessage.
*
* XXX this should be configurable somehow.
*/
resetPQExpBuffer(&workBuf);
if (res->errSeverity)
appendPQExpBuffer(&workBuf, "%s: ", res->errSeverity);
if (res->errPrimary)
appendPQExpBufferStr(&workBuf, res->errPrimary);
/* translator: %s represents a digit string */
if (res->errPosition)
appendPQExpBuffer(&workBuf, libpq_gettext(" at character %s"),
res->errPosition);
appendPQExpBufferChar(&workBuf, '\n');
if (res->errDetail)
appendPQExpBuffer(&workBuf, libpq_gettext("DETAIL: %s\n"),
res->errDetail);
if (res->errHint)
appendPQExpBuffer(&workBuf, libpq_gettext("HINT: %s\n"),
res->errHint);
if (res->errContext)
appendPQExpBuffer(&workBuf, libpq_gettext("CONTEXT: %s\n"),
res->errContext);
/*
* Either save error as current async result, or just emit the notice.
*/
if (isError)
{
res->errMsg = pqResultStrdup(res, workBuf.data);
pqClearAsyncResult(conn);
conn->result = res;
resetPQExpBuffer(&conn->errorMessage);
appendPQExpBufferStr(&conn->errorMessage, workBuf.data);
}
else
{
DONOTICE(conn, workBuf.data);
PQclear(res);
}
termPQExpBuffer(&workBuf);
return 0;
fail:
PQclear(res);
termPQExpBuffer(&workBuf);
return EOF;
}
/*
* Attempt to read a ParameterStatus message.
* This is possible in several places, so we break it out as a subroutine.
* Entry: 'S' message type and length have already been consumed.
* Exit: returns 0 if successfully consumed message.
* returns EOF if not enough data.
*/
static int
getParameterStatus(PGconn *conn)
{
/* Get the parameter name */
if (pqGets(&conn->workBuffer, conn))
return EOF;
/* Is it one we care about? */
if (strcmp(conn->workBuffer.data, "client_encoding") == 0)
{
if (pqGets(&conn->workBuffer, conn))
return EOF;
conn->client_encoding = pg_char_to_encoding(conn->workBuffer.data);
}
else
{
/* Uninteresting parameter, ignore it */
if (pqGets(&conn->workBuffer, conn))
return EOF;
}
return 0;
}
/*
* Attempt to read a Notify response message.
* This is possible in several places, so we break it out as a subroutine.
* Entry: 'A' message type and length have already been consumed.
* Exit: returns 0 if successfully consumed Notify message.
* returns EOF if not enough data.
*/
static int
getNotify(PGconn *conn)
{
int be_pid;
PGnotify *newNotify;
if (pqGetInt(&be_pid, 4, conn))
return EOF;
if (pqGets(&conn->workBuffer, conn))
return EOF;
/*
* Store the relation name right after the PQnotify structure so it
* can all be freed at once. We don't use NAMEDATALEN because we
* don't want to tie this interface to a specific server name length.
*/
newNotify = (PGnotify *) malloc(sizeof(PGnotify) +
strlen(conn->workBuffer.data) +1);
if (newNotify)
{
newNotify->relname = (char *) newNotify + sizeof(PGnotify);
strcpy(newNotify->relname, conn->workBuffer.data);
newNotify->be_pid = be_pid;
DLAddTail(conn->notifyList, DLNewElem(newNotify));
}
/* Swallow extra string (not presently used) */
if (pqGets(&conn->workBuffer, conn))
return EOF;
return 0;
}
/*
* PQnotifies
* returns a PGnotify* structure of the latest async notification
......@@ -1561,51 +942,20 @@ PQnotifies(PGconn *conn)
int
PQgetline(PGconn *conn, char *s, int maxlen)
{
int status;
/* maxlen must be at least 3 to hold the \. terminator! */
if (!conn || !s || maxlen < 3)
if (!s || maxlen <= 0)
return EOF;
if (conn->sock < 0 ||
conn->asyncStatus != PGASYNC_COPY_OUT ||
conn->copy_is_binary)
{
printfPQExpBuffer(&conn->errorMessage,
libpq_gettext("PQgetline: not doing text COPY OUT\n"));
*s = '\0';
/* maxlen must be at least 3 to hold the \. terminator! */
if (maxlen < 3)
return EOF;
}
while ((status = PQgetlineAsync(conn, s, maxlen-1)) == 0)
{
/* need to load more data */
if (pqWait(TRUE, FALSE, conn) ||
pqReadData(conn) < 0)
{
*s = '\0';
if (!conn)
return EOF;
}
}
if (status < 0)
{
/* End of copy detected; gin up old-style terminator */
strcpy(s, "\\.");
return 0;
}
/* Add null terminator, and strip trailing \n if present */
if (s[status-1] == '\n')
{
s[status-1] = '\0';
return 0;
}
if (PG_PROTOCOL_MAJOR(conn->pversion) >= 3)
return pqGetline3(conn, s, maxlen);
else
{
s[status] = '\0';
return 1;
}
return pqGetline2(conn, s, maxlen);
}
/*
......@@ -1642,60 +992,13 @@ PQgetline(PGconn *conn, char *s, int maxlen)
int
PQgetlineAsync(PGconn *conn, char *buffer, int bufsize)
{
char id;
int msgLength;
int avail;
if (!conn || conn->asyncStatus != PGASYNC_COPY_OUT)
return -1; /* we are not doing a copy... */
/*
* Recognize the next input message. To make life simpler for async
* callers, we keep returning 0 until the next message is fully available
* even if it is not Copy Data. This should keep PQendcopy from blocking.
*/
conn->inCursor = conn->inStart;
if (pqGetc(&id, conn))
return 0;
if (pqGetInt(&msgLength, 4, conn))
return 0;
avail = conn->inEnd - conn->inCursor;
if (avail < msgLength - 4)
return 0;
/*
* Cannot proceed unless it's a Copy Data message. Anything else means
* end of copy mode.
*/
if (id != 'd')
if (!conn)
return -1;
/*
* Move data from libpq's buffer to the caller's. In the case where
* a prior call found the caller's buffer too small, we use
* conn->copy_already_done to remember how much of the row was already
* returned to the caller.
*/
conn->inCursor += conn->copy_already_done;
avail = msgLength - 4 - conn->copy_already_done;
if (avail <= bufsize)
{
/* Able to consume the whole message */
memcpy(buffer, &conn->inBuffer[conn->inCursor], avail);
/* Mark message consumed */
conn->inStart = conn->inCursor + avail;
/* Reset state for next time */
conn->copy_already_done = 0;
return avail;
}
if (PG_PROTOCOL_MAJOR(conn->pversion) >= 3)
return pqGetlineAsync3(conn, buffer, bufsize);
else
{
/* We must return a partial message */
memcpy(buffer, &conn->inBuffer[conn->inCursor], bufsize);
/* The message is NOT consumed from libpq's buffer */
conn->copy_already_done += bufsize;
return bufsize;
}
return pqGetlineAsync2(conn, buffer, bufsize);
}
/*
......@@ -1722,11 +1025,22 @@ PQputnbytes(PGconn *conn, const char *buffer, int nbytes)
return EOF;
if (nbytes > 0)
{
if (pqPutMsgStart('d', conn) < 0 ||
/* This is too simple to bother with separate subroutines */
if (PG_PROTOCOL_MAJOR(conn->pversion) >= 3)
{
if (pqPutMsgStart('d', false, conn) < 0 ||
pqPutnchar(buffer, nbytes, conn) < 0 ||
pqPutMsgEnd(conn) < 0)
return EOF;
}
else
{
if (pqPutMsgStart(0, false, conn) < 0 ||
pqPutnchar(buffer, nbytes, conn) < 0 ||
pqPutMsgEnd(conn) < 0)
return EOF;
}
}
return 0;
}
......@@ -1742,72 +1056,13 @@ PQputnbytes(PGconn *conn, const char *buffer, int nbytes)
int
PQendcopy(PGconn *conn)
{
PGresult *result;
if (!conn)
return 0;
if (conn->asyncStatus != PGASYNC_COPY_IN &&
conn->asyncStatus != PGASYNC_COPY_OUT)
{
printfPQExpBuffer(&conn->errorMessage,
libpq_gettext("no COPY in progress\n"));
return 1;
}
/* Send the CopyDone message if needed */
if (conn->asyncStatus == PGASYNC_COPY_IN)
{
if (pqPutMsgStart('c', conn) < 0 ||
pqPutMsgEnd(conn) < 0)
return 1;
}
/*
* make sure no data is waiting to be sent, abort if we are
* non-blocking and the flush fails
*/
if (pqFlush(conn) && pqIsnonblocking(conn))
return (1);
/* Return to active duty */
conn->asyncStatus = PGASYNC_BUSY;
resetPQExpBuffer(&conn->errorMessage);
/*
* Non blocking connections may have to abort at this point. If everyone
* played the game there should be no problem, but in error scenarios
* the expected messages may not have arrived yet. (We are assuming that
* the backend's packetizing will ensure that CommandComplete arrives
* along with the CopyDone; are there corner cases where that doesn't
* happen?)
*/
if (pqIsnonblocking(conn) && PQisBusy(conn))
return (1);
/* Wait for the completion response */
result = PQgetResult(conn);
/* Expecting a successful result */
if (result && result->resultStatus == PGRES_COMMAND_OK)
{
PQclear(result);
return 0;
}
/*
* Trouble. For backwards-compatibility reasons, we issue the error
* message as if it were a notice (would be nice to get rid of this
* silliness, but too many apps probably don't handle errors from
* PQendcopy reasonably). Note that the app can still obtain the
* error status from the PGconn object.
*/
if (conn->errorMessage.len > 0)
DONOTICE(conn, conn->errorMessage.data);
PQclear(result);
return 1;
if (PG_PROTOCOL_MAJOR(conn->pversion) >= 3)
return pqEndcopy3(conn);
else
return pqEndcopy2(conn);
}
......@@ -1843,13 +1098,6 @@ PQfn(PGconn *conn,
const PQArgBlock *args,
int nargs)
{
bool needInput = false;
ExecStatusType status = PGRES_FATAL_ERROR;
char id;
int msgLength;
int avail;
int i;
*actual_result_len = 0;
if (!conn)
......@@ -1866,200 +1114,16 @@ PQfn(PGconn *conn,
return NULL;
}
if (pqPutMsgStart('F', conn) < 0 || /* function call msg */
pqPutInt(fnid, 4, conn) < 0 || /* function id */
pqPutInt(1, 2, conn) < 0 || /* # of format codes */
pqPutInt(1, 2, conn) < 0 || /* format code: BINARY */
pqPutInt(nargs, 2, conn) < 0) /* # of args */
{
handleSendFailure(conn);
return NULL;
}
for (i = 0; i < nargs; ++i)
{ /* len.int4 + contents */
if (pqPutInt(args[i].len, 4, conn))
{
handleSendFailure(conn);
return NULL;
}
if (args[i].len == -1)
continue; /* it's NULL */
if (args[i].isint)
{
if (pqPutInt(args[i].u.integer, args[i].len, conn))
{
handleSendFailure(conn);
return NULL;
}
}
else
{
if (pqPutnchar((char *) args[i].u.ptr, args[i].len, conn))
{
handleSendFailure(conn);
return NULL;
}
}
}
if (pqPutInt(1, 2, conn) < 0) /* result format code: BINARY */
{
handleSendFailure(conn);
return NULL;
}
if (pqPutMsgEnd(conn) < 0 ||
pqFlush(conn))
{
handleSendFailure(conn);
return NULL;
}
for (;;)
{
if (needInput)
{
/* Wait for some data to arrive (or for the channel to close) */
if (pqWait(TRUE, FALSE, conn) ||
pqReadData(conn) < 0)
break;
}
/*
* Scan the message. If we run out of data, loop around to try
* again.
*/
needInput = true;
conn->inCursor = conn->inStart;
if (pqGetc(&id, conn))
continue;
if (pqGetInt(&msgLength, 4, conn))
continue;
/*
* Try to validate message type/length here. A length less than 4
* is definitely broken. Large lengths should only be believed
* for a few message types.
*/
if (msgLength < 4)
{
handleSyncLoss(conn, id, msgLength);
break;
}
if (msgLength > 30000 &&
!(id == 'T' || id == 'D' || id == 'd' || id == 'V'))
{
handleSyncLoss(conn, id, msgLength);
break;
}
/*
* Can't process if message body isn't all here yet.
*/
msgLength -= 4;
avail = conn->inEnd - conn->inCursor;
if (avail < msgLength)
{
/*
* Before looping, enlarge the input buffer if needed to hold
* the whole message. See notes in parseInput.
*/
if (pqCheckInBufferSpace(conn->inCursor + msgLength, conn))
{
/*
* XXX add some better recovery code... plan is to skip
* over the message using its length, then report an error.
* For the moment, just treat this like loss of sync (which
* indeed it might be!)
*/
handleSyncLoss(conn, id, msgLength);
break;
}
continue;
}
/*
* We should see V or E response to the command, but might get N
* and/or A notices first. We also need to swallow the final Z
* before returning.
*/
switch (id)
{
case 'V': /* function result */
if (pqGetInt(actual_result_len, 4, conn))
continue;
if (*actual_result_len != -1)
{
if (result_is_int)
{
if (pqGetInt(result_buf, *actual_result_len, conn))
continue;
}
if (PG_PROTOCOL_MAJOR(conn->pversion) >= 3)
return pqFunctionCall3(conn, fnid,
result_buf, actual_result_len,
result_is_int,
args, nargs);
else
{
if (pqGetnchar((char *) result_buf,
*actual_result_len,
conn))
continue;
}
}
/* correctly finished function result message */
status = PGRES_COMMAND_OK;
break;
case 'E': /* error return */
if (pqGetErrorNotice(conn, true))
continue;
status = PGRES_FATAL_ERROR;
break;
case 'A': /* notify message */
/* handle notify and go back to processing return values */
if (getNotify(conn))
continue;
break;
case 'N': /* notice */
/* handle notice and go back to processing return values */
if (pqGetErrorNotice(conn, false))
continue;
break;
case 'Z': /* backend is ready for new query */
if (pqGetc(&conn->xact_status, conn))
continue;
/* consume the message and exit */
conn->inStart += 5 + msgLength;
/* if we saved a result object (probably an error), use it */
if (conn->result)
return prepareAsyncResult(conn);
return PQmakeEmptyPGresult(conn, status);
case 'S': /* parameter status */
if (getParameterStatus(conn))
continue;
break;
default:
/* The backend violates the protocol. */
printfPQExpBuffer(&conn->errorMessage,
libpq_gettext("protocol error: id=0x%x\n"),
id);
saveErrorResult(conn);
/* trust the specified message length as what to skip */
conn->inStart += 5 + msgLength;
return prepareAsyncResult(conn);
}
/* Completed this message, keep going */
/* trust the specified message length as what to skip */
conn->inStart += 5 + msgLength;
needInput = false;
}
/*
* We fall out of the loop only upon failing to read data.
* conn->errorMessage has been set by pqWait or pqReadData. We want to
* append it to any already-received error message.
*/
saveErrorResult(conn);
return prepareAsyncResult(conn);
return pqFunctionCall2(conn, fnid,
result_buf, actual_result_len,
result_is_int,
args, nargs);
}
......@@ -2132,7 +1196,7 @@ check_field_number(const PGresult *res, int field_num)
snprintf(noticeBuf, sizeof(noticeBuf),
libpq_gettext("column number %d is out of range 0..%d\n"),
field_num, res->numAttributes - 1);
DONOTICE(res, noticeBuf);
PGDONOTICE(res, noticeBuf);
}
return FALSE;
}
......@@ -2154,7 +1218,7 @@ check_tuple_field_number(const PGresult *res,
snprintf(noticeBuf, sizeof(noticeBuf),
libpq_gettext("row number %d is out of range 0..%d\n"),
tup_num, res->ntups - 1);
DONOTICE(res, noticeBuf);
PGDONOTICE(res, noticeBuf);
}
return FALSE;
}
......@@ -2165,7 +1229,7 @@ check_tuple_field_number(const PGresult *res,
snprintf(noticeBuf, sizeof(noticeBuf),
libpq_gettext("column number %d is out of range 0..%d\n"),
field_num, res->numAttributes - 1);
DONOTICE(res, noticeBuf);
PGDONOTICE(res, noticeBuf);
}
return FALSE;
}
......@@ -2367,7 +1431,7 @@ PQcmdTuples(PGresult *res)
snprintf(noticeBuf, sizeof(noticeBuf),
libpq_gettext("could not interpret result from server: %s\n"),
res->cmdStatus);
DONOTICE(res, noticeBuf);
PGDONOTICE(res, noticeBuf);
}
return "";
}
......
......@@ -23,7 +23,7 @@
* Portions Copyright (c) 1994, Regents of the University of California
*
* IDENTIFICATION
* $Header: /cvsroot/pgsql/src/interfaces/libpq/fe-misc.c,v 1.91 2003/04/25 01:24:00 momjian Exp $
* $Header: /cvsroot/pgsql/src/interfaces/libpq/fe-misc.c,v 1.92 2003/06/08 17:43:00 tgl Exp $
*
*-------------------------------------------------------------------------
*/
......@@ -58,8 +58,6 @@
#include "pqsignal.h"
#include "mb/pg_wchar.h"
#define DONOTICE(conn,message) \
((*(conn)->noticeHook) ((conn)->noticeArg, (message)))
static int pqPutMsgBytes(const void *buf, size_t len, PGconn *conn);
static int pqSendSome(PGconn *conn, int len);
......@@ -227,7 +225,7 @@ pqGetInt(int *result, size_t bytes, PGconn *conn)
snprintf(noticeBuf, sizeof(noticeBuf),
libpq_gettext("integer of size %lu not supported by pqGetInt\n"),
(unsigned long) bytes);
DONOTICE(conn, noticeBuf);
PGDONOTICE(conn, noticeBuf);
return EOF;
}
......@@ -265,7 +263,7 @@ pqPutInt(int value, size_t bytes, PGconn *conn)
snprintf(noticeBuf, sizeof(noticeBuf),
libpq_gettext("integer of size %lu not supported by pqPutInt\n"),
(unsigned long) bytes);
DONOTICE(conn, noticeBuf);
PGDONOTICE(conn, noticeBuf);
return EOF;
}
......@@ -401,38 +399,57 @@ pqCheckInBufferSpace(int bytes_needed, PGconn *conn)
* msg_type is the message type byte, or 0 for a message without type byte
* (only startup messages have no type byte)
*
* force_len forces the message to have a length word; otherwise, we add
* a length word if protocol 3.
*
* Returns 0 on success, EOF on error
*
* The idea here is that we construct the message in conn->outBuffer,
* beginning just past any data already in outBuffer (ie, at
* outBuffer+outCount). We enlarge the buffer as needed to hold the message.
* When the message is complete, we fill in the length word and then advance
* outCount past the message, making it eligible to send. The state
* variable conn->outMsgStart points to the incomplete message's length word
* (it is either outCount or outCount+1 depending on whether there is a
* type byte). The state variable conn->outMsgEnd is the end of the data
* collected so far.
* When the message is complete, we fill in the length word (if needed) and
* then advance outCount past the message, making it eligible to send.
*
* The state variable conn->outMsgStart points to the incomplete message's
* length word: it is either outCount or outCount+1 depending on whether
* there is a type byte. If we are sending a message without length word
* (pre protocol 3.0 only), then outMsgStart is -1. The state variable
* conn->outMsgEnd is the end of the data collected so far.
*/
int
pqPutMsgStart(char msg_type, PGconn *conn)
pqPutMsgStart(char msg_type, bool force_len, PGconn *conn)
{
int lenPos;
int endPos;
/* where the message length word will go */
/* allow room for message type byte */
if (msg_type)
lenPos = conn->outCount + 1;
endPos = conn->outCount + 1;
else
lenPos = conn->outCount;
/* make sure there is room for it */
if (pqCheckOutBufferSpace(lenPos + 4, conn))
endPos = conn->outCount;
/* do we want a length word? */
if (force_len || PG_PROTOCOL_MAJOR(conn->pversion) >= 3)
{
lenPos = endPos;
/* allow room for message length */
endPos += 4;
}
else
{
lenPos = -1;
}
/* make sure there is room for message header */
if (pqCheckOutBufferSpace(endPos, conn))
return EOF;
/* okay, save the message type byte if any */
if (msg_type)
conn->outBuffer[conn->outCount] = msg_type;
/* set up the message pointers */
conn->outMsgStart = lenPos;
conn->outMsgEnd = lenPos + 4;
/* length word will be filled in by pqPutMsgEnd */
conn->outMsgEnd = endPos;
/* length word, if needed, will be filled in by pqPutMsgEnd */
if (conn->Pfdebug)
fprintf(conn->Pfdebug, "To backend> Msg %c\n",
......@@ -472,14 +489,20 @@ pqPutMsgBytes(const void *buf, size_t len, PGconn *conn)
int
pqPutMsgEnd(PGconn *conn)
{
uint32 msgLen = conn->outMsgEnd - conn->outMsgStart;
if (conn->Pfdebug)
fprintf(conn->Pfdebug, "To backend> Msg complete, length %u\n",
msgLen);
conn->outMsgEnd - conn->outCount);
/* Fill in length word if needed */
if (conn->outMsgStart >= 0)
{
uint32 msgLen = conn->outMsgEnd - conn->outMsgStart;
msgLen = htonl(msgLen);
memcpy(conn->outBuffer + conn->outMsgStart, &msgLen, 4);
}
/* Make message eligible to send */
conn->outCount = conn->outMsgEnd;
if (conn->outCount >= 8192)
......
/*-------------------------------------------------------------------------
*
* fe-protocol2.c
* functions that are specific to frontend/backend protocol version 2
*
* Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
*
* IDENTIFICATION
* $Header: /cvsroot/pgsql/src/interfaces/libpq/fe-protocol2.c,v 1.1 2003/06/08 17:43:00 tgl Exp $
*
*-------------------------------------------------------------------------
*/
#include "postgres_fe.h"
#include <errno.h>
#include <ctype.h>
#include <fcntl.h>
#include "libpq-fe.h"
#include "libpq-int.h"
#include "mb/pg_wchar.h"
#ifdef WIN32
#include "win32.h"
#else
#include <unistd.h>
#include <netinet/in.h>
#ifdef HAVE_NETINET_TCP_H
#include <netinet/tcp.h>
#endif
#include <arpa/inet.h>
#endif
static int getRowDescriptions(PGconn *conn);
static int getAnotherTuple(PGconn *conn, bool binary);
static int pqGetErrorNotice2(PGconn *conn, bool isError);
static int getNotify(PGconn *conn);
/*
* pqSetenvPoll
*
* Polls the process of passing the values of a standard set of environment
* variables to the backend.
*/
PostgresPollingStatusType
pqSetenvPoll(PGconn *conn)
{
PGresult *res;
if (conn == NULL || conn->status == CONNECTION_BAD)
return PGRES_POLLING_FAILED;
/* Check whether there are any data for us */
switch (conn->setenv_state)
{
/* These are reading states */
case SETENV_STATE_OPTION_WAIT:
case SETENV_STATE_QUERY1_WAIT:
case SETENV_STATE_QUERY2_WAIT:
{
/* Load waiting data */
int n = pqReadData(conn);
if (n < 0)
goto error_return;
if (n == 0)
return PGRES_POLLING_READING;
break;
}
/* These are writing states, so we just proceed. */
case SETENV_STATE_OPTION_SEND:
case SETENV_STATE_QUERY1_SEND:
case SETENV_STATE_QUERY2_SEND:
break;
/* Should we raise an error if called when not active? */
case SETENV_STATE_IDLE:
return PGRES_POLLING_OK;
default:
printfPQExpBuffer(&conn->errorMessage,
libpq_gettext(
"invalid setenv state %c, "
"probably indicative of memory corruption\n"
),
conn->setenv_state);
goto error_return;
}
/* We will loop here until there is nothing left to do in this call. */
for (;;)
{
switch (conn->setenv_state)
{
case SETENV_STATE_OPTION_SEND:
{
/*
* Send SET commands for stuff directed by Environment
* Options. Note: we assume that SET commands won't
* start transaction blocks, even in a 7.3 server with
* autocommit off.
*/
char setQuery[100]; /* note length limit in
* sprintf below */
if (conn->next_eo->envName)
{
const char *val;
if ((val = getenv(conn->next_eo->envName)))
{
if (strcasecmp(val, "default") == 0)
sprintf(setQuery, "SET %s = DEFAULT",
conn->next_eo->pgName);
else
sprintf(setQuery, "SET %s = '%.60s'",
conn->next_eo->pgName, val);
#ifdef CONNECTDEBUG
fprintf(stderr,
"Use environment variable %s to send %s\n",
conn->next_eo->envName, setQuery);
#endif
if (!PQsendQuery(conn, setQuery))
goto error_return;
conn->setenv_state = SETENV_STATE_OPTION_WAIT;
}
else
conn->next_eo++;
}
else
{
/* No more options to send, so move on to querying */
conn->setenv_state = SETENV_STATE_QUERY1_SEND;
}
break;
}
case SETENV_STATE_OPTION_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 send the next option */
conn->next_eo++;
conn->setenv_state = SETENV_STATE_OPTION_SEND;
}
break;
}
case SETENV_STATE_QUERY1_SEND:
{
/*
* Issue query to get information we need. Here we must
* use begin/commit in case autocommit is off by default
* in a 7.3 server.
*
* Note: version() and getdatabaseencoding() exist in
* all protocol-2.0-supporting backends.
*/
if (!PQsendQuery(conn, "begin; select version(), getdatabaseencoding(); end"))
goto error_return;
conn->setenv_state = SETENV_STATE_QUERY1_WAIT;
return PGRES_POLLING_READING;
}
case SETENV_STATE_QUERY1_WAIT:
{
if (PQisBusy(conn))
return PGRES_POLLING_READING;
res = PQgetResult(conn);
if (res)
{
char *val;
if (PQresultStatus(res) == PGRES_COMMAND_OK)
{
/* ignore begin/commit command results */
PQclear(res);
continue;
}
if (PQresultStatus(res) != PGRES_TUPLES_OK ||
PQntuples(res) != 1)
{
PQclear(res);
goto error_return;
}
/*
* Extract server version and database encoding,
* and save as if ParameterStatus
*/
val = PQgetvalue(res, 0, 0);
if (val && strncmp(val, "PostgreSQL ", 11) == 0)
{
char *ptr;
/* strip off PostgreSQL part */
val += 11;
/*
* strip off platform part (scribbles on result,
* naughty naughty)
*/
ptr = strchr(val, ' ');
if (ptr)
*ptr = '\0';
pqSaveParameterStatus(conn, "server_version",
val);
}
val = PQgetvalue(res, 0, 1);
if (val && *val) /* null should not happen, but */
pqSaveParameterStatus(conn, "server_encoding",
val);
PQclear(res);
/* Keep reading until PQgetResult returns NULL */
}
else
{
/* Query finished, move to next */
conn->setenv_state = SETENV_STATE_QUERY2_SEND;
}
break;
}
case SETENV_STATE_QUERY2_SEND:
{
const char *query;
/*
* pg_client_encoding does not exist in pre-7.2 servers.
* So we need to be prepared for an error here. Do *not*
* start a transaction block, except in 7.3 servers where
* we need to prevent autocommit-off from starting a
* transaction anyway.
*/
if (strncmp(conn->sversion, "7.3", 3) == 0)
query = "begin; select pg_client_encoding(); end";
else
query = "select pg_client_encoding()";
if (!PQsendQuery(conn, query))
goto error_return;
conn->setenv_state = SETENV_STATE_QUERY2_WAIT;
return PGRES_POLLING_READING;
}
case SETENV_STATE_QUERY2_WAIT:
{
if (PQisBusy(conn))
return PGRES_POLLING_READING;
res = PQgetResult(conn);
if (res)
{
const char *val;
if (PQresultStatus(res) == PGRES_COMMAND_OK)
{
/* ignore begin/commit command results */
PQclear(res);
continue;
}
if (PQresultStatus(res) == PGRES_TUPLES_OK &&
PQntuples(res) == 1)
{
/* Extract client encoding and save it */
val = PQgetvalue(res, 0, 0);
if (val && *val) /* null should not happen, but */
pqSaveParameterStatus(conn, "client_encoding",
val);
}
else
{
/*
* Error: presumably function not available, so
* use PGCLIENTENCODING or database encoding as
* the fallback.
*/
val = getenv("PGCLIENTENCODING");
if (val && *val)
pqSaveParameterStatus(conn, "client_encoding",
val);
else
{
val = pqGetParameterStatus(conn, "server_encoding");
if (val && *val)
pqSaveParameterStatus(conn, "client_encoding",
val);
}
}
PQclear(res);
/* Keep reading until PQgetResult returns NULL */
}
else
{
/* Query finished, so we're done */
conn->setenv_state = SETENV_STATE_IDLE;
return PGRES_POLLING_OK;
}
break;
}
default:
printfPQExpBuffer(&conn->errorMessage,
libpq_gettext("invalid state %c, "
"probably indicative of memory corruption\n"),
conn->setenv_state);
goto error_return;
}
}
/* Unreachable */
error_return:
conn->setenv_state = SETENV_STATE_IDLE;
return PGRES_POLLING_FAILED;
}
/*
* parseInput: if appropriate, parse input data from backend
* until input is exhausted or a stopping state is reached.
* Note that this function will NOT attempt to read more data from the backend.
*/
void
pqParseInput2(PGconn *conn)
{
char id;
char noticeWorkspace[128];
/*
* Loop to parse successive complete messages available in the buffer.
*/
for (;;)
{
/*
* Quit if in COPY_OUT state: we expect raw data from the server
* until PQendcopy is called. Don't try to parse it according to
* the normal protocol. (This is bogus. The data lines ought to
* be part of the protocol and have identifying leading
* characters.)
*/
if (conn->asyncStatus == PGASYNC_COPY_OUT)
return;
/*
* OK to try to read a message type code.
*/
conn->inCursor = conn->inStart;
if (pqGetc(&id, conn))
return;
/*
* NOTIFY and NOTICE messages can happen in any state besides
* COPY OUT; always process them right away.
*
* Most other messages should only be processed while in BUSY state.
* (In particular, in READY state we hold off further parsing
* until the application collects the current PGresult.)
*
* However, if the state is IDLE then we got trouble; we need to deal
* with the unexpected message somehow.
*/
if (id == 'A')
{
if (getNotify(conn))
return;
}
else if (id == 'N')
{
if (pqGetErrorNotice2(conn, false))
return;
}
else if (conn->asyncStatus != PGASYNC_BUSY)
{
/* If not IDLE state, just wait ... */
if (conn->asyncStatus != PGASYNC_IDLE)
return;
/*
* Unexpected message in IDLE state; need to recover somehow.
* ERROR messages are displayed using the notice processor;
* anything else is just dropped on the floor after displaying
* a suitable warning notice. (An ERROR is very possibly the
* backend telling us why it is about to close the connection,
* so we don't want to just discard it...)
*/
if (id == 'E')
{
if (pqGetErrorNotice2(conn, false /* treat as notice */))
return;
}
else
{
snprintf(noticeWorkspace, sizeof(noticeWorkspace),
libpq_gettext("message type 0x%02x arrived from server while idle\n"),
id);
PGDONOTICE(conn, noticeWorkspace);
/* Discard the unexpected message; good idea?? */
conn->inStart = conn->inEnd;
break;
}
}
else
{
/*
* In BUSY state, we can process everything.
*/
switch (id)
{
case 'C': /* command complete */
if (pqGets(&conn->workBuffer, conn))
return;
if (conn->result == NULL)
conn->result = PQmakeEmptyPGresult(conn,
PGRES_COMMAND_OK);
strncpy(conn->result->cmdStatus, conn->workBuffer.data,
CMDSTATUS_LEN);
conn->asyncStatus = PGASYNC_READY;
break;
case 'E': /* error return */
if (pqGetErrorNotice2(conn, true))
return;
conn->asyncStatus = PGASYNC_READY;
break;
case 'Z': /* backend is ready for new query */
conn->asyncStatus = PGASYNC_IDLE;
break;
case 'I': /* empty query */
/* read and throw away the closing '\0' */
if (pqGetc(&id, conn))
return;
if (id != '\0')
{
snprintf(noticeWorkspace, sizeof(noticeWorkspace),
libpq_gettext("unexpected character %c following empty query response (\"I\" message)\n"),
id);
PGDONOTICE(conn, noticeWorkspace);
}
if (conn->result == NULL)
conn->result = PQmakeEmptyPGresult(conn,
PGRES_EMPTY_QUERY);
conn->asyncStatus = PGASYNC_READY;
break;
case 'K': /* secret key data from the backend */
/*
* This is expected only during backend startup, but
* it's just as easy to handle it as part of the main
* loop. Save the data and continue processing.
*/
if (pqGetInt(&(conn->be_pid), 4, conn))
return;
if (pqGetInt(&(conn->be_key), 4, conn))
return;
break;
case 'P': /* synchronous (normal) portal */
if (pqGets(&conn->workBuffer, conn))
return;
/* We pretty much ignore this message type... */
break;
case 'T': /* row descriptions (start of query
* results) */
if (conn->result == NULL)
{
/* First 'T' in a query sequence */
if (getRowDescriptions(conn))
return;
}
else
{
/*
* A new 'T' message is treated as the start of
* another PGresult. (It is not clear that this
* is really possible with the current backend.)
* We stop parsing until the application accepts
* the current result.
*/
conn->asyncStatus = PGASYNC_READY;
return;
}
break;
case 'D': /* ASCII data tuple */
if (conn->result != NULL)
{
/* Read another tuple of a normal query response */
if (getAnotherTuple(conn, FALSE))
return;
}
else
{
snprintf(noticeWorkspace, sizeof(noticeWorkspace),
libpq_gettext("server sent data (\"D\" message) without prior row description (\"T\" message)\n"));
PGDONOTICE(conn, noticeWorkspace);
/* Discard the unexpected message; good idea?? */
conn->inStart = conn->inEnd;
return;
}
break;
case 'B': /* Binary data tuple */
if (conn->result != NULL)
{
/* Read another tuple of a normal query response */
if (getAnotherTuple(conn, TRUE))
return;
}
else
{
snprintf(noticeWorkspace, sizeof(noticeWorkspace),
libpq_gettext("server sent binary data (\"B\" message) without prior row description (\"T\" message)\n"));
PGDONOTICE(conn, noticeWorkspace);
/* Discard the unexpected message; good idea?? */
conn->inStart = conn->inEnd;
return;
}
break;
case 'G': /* Start Copy In */
conn->asyncStatus = PGASYNC_COPY_IN;
break;
case 'H': /* Start Copy Out */
conn->asyncStatus = PGASYNC_COPY_OUT;
break;
default:
printfPQExpBuffer(&conn->errorMessage,
libpq_gettext(
"unexpected response from server; first received character was \"%c\"\n"),
id);
/* build an error result holding the error message */
pqSaveErrorResult(conn);
/* Discard the unexpected message; good idea?? */
conn->inStart = conn->inEnd;
conn->asyncStatus = PGASYNC_READY;
return;
} /* switch on protocol character */
}
/* Successfully consumed this message */
conn->inStart = conn->inCursor;
}
}
/*
* parseInput subroutine to read a 'T' (row descriptions) message.
* We build a PGresult structure containing the attribute data.
* Returns: 0 if completed message, EOF if not enough data yet.
*
* Note that if we run out of data, we have to release the partially
* constructed PGresult, and rebuild it again next time. Fortunately,
* that shouldn't happen often, since 'T' messages usually fit in a packet.
*/
static int
getRowDescriptions(PGconn *conn)
{
PGresult *result;
int nfields;
int i;
result = PQmakeEmptyPGresult(conn, PGRES_TUPLES_OK);
/* parseInput already read the 'T' label. */
/* the next two bytes are the number of fields */
if (pqGetInt(&(result->numAttributes), 2, conn))
{
PQclear(result);
return EOF;
}
nfields = result->numAttributes;
/* allocate space for the attribute descriptors */
if (nfields > 0)
{
result->attDescs = (PGresAttDesc *)
pqResultAlloc(result, nfields * sizeof(PGresAttDesc), TRUE);
MemSet((char *) result->attDescs, 0, nfields * sizeof(PGresAttDesc));
}
/* get type info */
for (i = 0; i < nfields; i++)
{
int typid;
int typlen;
int atttypmod;
if (pqGets(&conn->workBuffer, conn) ||
pqGetInt(&typid, 4, conn) ||
pqGetInt(&typlen, 2, conn) ||
pqGetInt(&atttypmod, 4, conn))
{
PQclear(result);
return EOF;
}
/*
* Since pqGetInt treats 2-byte integers as unsigned, we need to
* coerce the result to signed form.
*/
typlen = (int) ((int16) typlen);
result->attDescs[i].name = pqResultStrdup(result,
conn->workBuffer.data);
result->attDescs[i].typid = typid;
result->attDescs[i].typlen = typlen;
result->attDescs[i].atttypmod = atttypmod;
}
/* Success! */
conn->result = result;
return 0;
}
/*
* parseInput subroutine to read a 'B' or 'D' (row data) message.
* We add another tuple to the existing PGresult structure.
* Returns: 0 if completed message, EOF if error or not enough data yet.
*
* Note that if we run out of data, we have to suspend and reprocess
* the message after more data is received. We keep a partially constructed
* tuple in conn->curTuple, and avoid reallocating already-allocated storage.
*/
static int
getAnotherTuple(PGconn *conn, bool binary)
{
PGresult *result = conn->result;
int nfields = result->numAttributes;
PGresAttValue *tup;
/* the backend sends us a bitmap of which attributes are null */
char std_bitmap[64]; /* used unless it doesn't fit */
char *bitmap = std_bitmap;
int i;
size_t nbytes; /* the number of bytes in bitmap */
char bmap; /* One byte of the bitmap */
int bitmap_index; /* Its index */
int bitcnt; /* number of bits examined in current byte */
int vlen; /* length of the current field value */
result->binary = binary;
/* Allocate tuple space if first time for this data message */
if (conn->curTuple == NULL)
{
conn->curTuple = (PGresAttValue *)
pqResultAlloc(result, nfields * sizeof(PGresAttValue), TRUE);
if (conn->curTuple == NULL)
goto outOfMemory;
MemSet((char *) conn->curTuple, 0, nfields * sizeof(PGresAttValue));
}
tup = conn->curTuple;
/* Get the null-value bitmap */
nbytes = (nfields + BITS_PER_BYTE - 1) / BITS_PER_BYTE;
/* malloc() only for unusually large field counts... */
if (nbytes > sizeof(std_bitmap))
bitmap = (char *) malloc(nbytes);
if (pqGetnchar(bitmap, nbytes, conn))
goto EOFexit;
/* Scan the fields */
bitmap_index = 0;
bmap = bitmap[bitmap_index];
bitcnt = 0;
for (i = 0; i < nfields; i++)
{
if (!(bmap & 0200))
{
/* if the field value is absent, make it a null string */
tup[i].value = result->null_field;
tup[i].len = NULL_LEN;
}
else
{
/* get the value length (the first four bytes are for length) */
if (pqGetInt(&vlen, 4, conn))
goto EOFexit;
if (!binary)
vlen = vlen - 4;
if (vlen < 0)
vlen = 0;
if (tup[i].value == NULL)
{
tup[i].value = (char *) pqResultAlloc(result, vlen + 1, binary);
if (tup[i].value == NULL)
goto outOfMemory;
}
tup[i].len = vlen;
/* read in the value */
if (vlen > 0)
if (pqGetnchar((char *) (tup[i].value), vlen, conn))
goto EOFexit;
/* we have to terminate this ourselves */
tup[i].value[vlen] = '\0';
}
/* advance the bitmap stuff */
bitcnt++;
if (bitcnt == BITS_PER_BYTE)
{
bitmap_index++;
bmap = bitmap[bitmap_index];
bitcnt = 0;
}
else
bmap <<= 1;
}
/* Success! Store the completed tuple in the result */
if (!pqAddTuple(result, tup))
goto outOfMemory;
/* and reset for a new message */
conn->curTuple = NULL;
if (bitmap != std_bitmap)
free(bitmap);
return 0;
outOfMemory:
/* Replace partially constructed result with an error result */
/*
* we do NOT use pqSaveErrorResult() here, because of the likelihood
* that there's not enough memory to concatenate messages...
*/
pqClearAsyncResult(conn);
printfPQExpBuffer(&conn->errorMessage,
libpq_gettext("out of memory for query result\n"));
conn->result = PQmakeEmptyPGresult(conn, PGRES_FATAL_ERROR);
conn->asyncStatus = PGASYNC_READY;
/* Discard the failed message --- good idea? */
conn->inStart = conn->inEnd;
EOFexit:
if (bitmap != std_bitmap)
free(bitmap);
return EOF;
}
/*
* Attempt to read an Error or Notice response message.
* This is possible in several places, so we break it out as a subroutine.
* Entry: 'E' or 'N' message type has already been consumed.
* Exit: returns 0 if successfully consumed message.
* returns EOF if not enough data.
*/
static int
pqGetErrorNotice2(PGconn *conn, bool isError)
{
PGresult *res;
PQExpBufferData workBuf;
/*
* Since the message might be pretty long, we create a temporary
* PQExpBuffer rather than using conn->workBuffer. workBuffer is
* intended for stuff that is expected to be short.
*/
initPQExpBuffer(&workBuf);
if (pqGets(&workBuf, conn))
{
termPQExpBuffer(&workBuf);
return EOF;
}
/*
* Make a PGresult to hold the message. We temporarily
* lie about the result status, so that PQmakeEmptyPGresult doesn't
* uselessly copy conn->errorMessage.
*/
res = PQmakeEmptyPGresult(conn, PGRES_EMPTY_QUERY);
res->resultStatus = isError ? PGRES_FATAL_ERROR : PGRES_NONFATAL_ERROR;
res->errMsg = pqResultStrdup(res, workBuf.data);
/*
* Either save error as current async result, or just emit the notice.
*/
if (isError)
{
pqClearAsyncResult(conn);
conn->result = res;
resetPQExpBuffer(&conn->errorMessage);
appendPQExpBufferStr(&conn->errorMessage, workBuf.data);
}
else
{
PGDONOTICE(conn, workBuf.data);
PQclear(res);
}
termPQExpBuffer(&workBuf);
return 0;
}
/*
* Attempt to read a Notify response message.
* This is possible in several places, so we break it out as a subroutine.
* Entry: 'A' message type and length have already been consumed.
* Exit: returns 0 if successfully consumed Notify message.
* returns EOF if not enough data.
*/
static int
getNotify(PGconn *conn)
{
int be_pid;
PGnotify *newNotify;
if (pqGetInt(&be_pid, 4, conn))
return EOF;
if (pqGets(&conn->workBuffer, conn))
return EOF;
/*
* Store the relation name right after the PQnotify structure so it
* can all be freed at once. We don't use NAMEDATALEN because we
* don't want to tie this interface to a specific server name length.
*/
newNotify = (PGnotify *) malloc(sizeof(PGnotify) +
strlen(conn->workBuffer.data) +1);
if (newNotify)
{
newNotify->relname = (char *) newNotify + sizeof(PGnotify);
strcpy(newNotify->relname, conn->workBuffer.data);
newNotify->be_pid = be_pid;
DLAddTail(conn->notifyList, DLNewElem(newNotify));
}
return 0;
}
/*
* PQgetline - gets a newline-terminated string from the backend.
*
* See fe-exec.c for documentation.
*/
int
pqGetline2(PGconn *conn, char *s, int maxlen)
{
int result = 1; /* return value if buffer overflows */
if (conn->sock < 0)
{
*s = '\0';
return EOF;
}
/*
* Since this is a purely synchronous routine, we don't bother to
* maintain conn->inCursor; there is no need to back up.
*/
while (maxlen > 1)
{
if (conn->inStart < conn->inEnd)
{
char c = conn->inBuffer[conn->inStart++];
if (c == '\n')
{
result = 0; /* success exit */
break;
}
*s++ = c;
maxlen--;
}
else
{
/* need to load more data */
if (pqWait(TRUE, FALSE, conn) ||
pqReadData(conn) < 0)
{
result = EOF;
break;
}
}
}
*s = '\0';
return result;
}
/*
* PQgetlineAsync - gets a COPY data row without blocking.
*
* See fe-exec.c for documentation.
*/
int
pqGetlineAsync2(PGconn *conn, char *buffer, int bufsize)
{
int avail;
if (conn->asyncStatus != PGASYNC_COPY_OUT)
return -1; /* we are not doing a copy... */
/*
* Move data from libpq's buffer to the caller's. We want to accept
* data only in units of whole lines, not partial lines. This ensures
* that we can recognize the terminator line "\\.\n". (Otherwise, if
* it happened to cross a packet/buffer boundary, we might hand the
* first one or two characters off to the caller, which we shouldn't.)
*/
conn->inCursor = conn->inStart;
avail = bufsize;
while (avail > 0 && conn->inCursor < conn->inEnd)
{
char c = conn->inBuffer[conn->inCursor++];
*buffer++ = c;
--avail;
if (c == '\n')
{
/* Got a complete line; mark the data removed from libpq */
conn->inStart = conn->inCursor;
/* Is it the endmarker line? */
if (bufsize - avail == 3 && buffer[-3] == '\\' && buffer[-2] == '.')
return -1;
/* No, return the data line to the caller */
return bufsize - avail;
}
}
/*
* We don't have a complete line. We'd prefer to leave it in libpq's
* buffer until the rest arrives, but there is a special case: what if
* the line is longer than the buffer the caller is offering us? In
* that case we'd better hand over a partial line, else we'd get into
* an infinite loop. Do this in a way that ensures we can't
* misrecognize a terminator line later: leave last 3 characters in
* libpq buffer.
*/
if (avail == 0 && bufsize > 3)
{
conn->inStart = conn->inCursor - 3;
return bufsize - 3;
}
return 0;
}
/*
* PQendcopy
*
* See fe-exec.c for documentation.
*/
int
pqEndcopy2(PGconn *conn)
{
PGresult *result;
if (conn->asyncStatus != PGASYNC_COPY_IN &&
conn->asyncStatus != PGASYNC_COPY_OUT)
{
printfPQExpBuffer(&conn->errorMessage,
libpq_gettext("no COPY in progress\n"));
return 1;
}
/*
* make sure no data is waiting to be sent, abort if we are
* non-blocking and the flush fails
*/
if (pqFlush(conn) && pqIsnonblocking(conn))
return (1);
/* non blocking connections may have to abort at this point. */
if (pqIsnonblocking(conn) && PQisBusy(conn))
return (1);
/* Return to active duty */
conn->asyncStatus = PGASYNC_BUSY;
resetPQExpBuffer(&conn->errorMessage);
/* Wait for the completion response */
result = PQgetResult(conn);
/* Expecting a successful result */
if (result && result->resultStatus == PGRES_COMMAND_OK)
{
PQclear(result);
return 0;
}
/*
* Trouble. The worst case is that we've lost sync with the backend
* entirely due to application screwup of the copy in/out protocol. To
* recover, reset the connection (talk about using a sledgehammer...)
*/
PQclear(result);
if (conn->errorMessage.len > 0)
PGDONOTICE(conn, conn->errorMessage.data);
PGDONOTICE(conn, libpq_gettext("lost synchronization with server, resetting connection\n"));
/*
* Users doing non-blocking connections need to handle the reset
* themselves, they'll need to check the connection status if we
* return an error.
*/
if (pqIsnonblocking(conn))
PQresetStart(conn);
else
PQreset(conn);
return 1;
}
/*
* PQfn - Send a function call to the POSTGRES backend.
*
* See fe-exec.c for documentation.
*/
PGresult *
pqFunctionCall2(PGconn *conn, Oid fnid,
int *result_buf, int *actual_result_len,
int result_is_int,
const PQArgBlock *args, int nargs)
{
bool needInput = false;
ExecStatusType status = PGRES_FATAL_ERROR;
char id;
int i;
/* PQfn already validated connection state */
if (pqPutMsgStart('F', false, conn) < 0 || /* function call msg */
pqPuts(" ", conn) < 0 || /* dummy string */
pqPutInt(fnid, 4, conn) != 0 || /* function id */
pqPutInt(nargs, 4, conn) != 0) /* # of args */
{
pqHandleSendFailure(conn);
return NULL;
}
for (i = 0; i < nargs; ++i)
{ /* len.int4 + contents */
if (pqPutInt(args[i].len, 4, conn))
{
pqHandleSendFailure(conn);
return NULL;
}
if (args[i].isint)
{
if (pqPutInt(args[i].u.integer, 4, conn))
{
pqHandleSendFailure(conn);
return NULL;
}
}
else
{
if (pqPutnchar((char *) args[i].u.ptr, args[i].len, conn))
{
pqHandleSendFailure(conn);
return NULL;
}
}
}
if (pqPutMsgEnd(conn) < 0 ||
pqFlush(conn))
{
pqHandleSendFailure(conn);
return NULL;
}
for (;;)
{
if (needInput)
{
/* Wait for some data to arrive (or for the channel to close) */
if (pqWait(TRUE, FALSE, conn) ||
pqReadData(conn) < 0)
break;
}
/*
* Scan the message. If we run out of data, loop around to try
* again.
*/
conn->inCursor = conn->inStart;
needInput = true;
if (pqGetc(&id, conn))
continue;
/*
* We should see V or E response to the command, but might get N
* and/or A notices first. We also need to swallow the final Z
* before returning.
*/
switch (id)
{
case 'V': /* function result */
if (pqGetc(&id, conn))
continue;
if (id == 'G')
{
/* function returned nonempty value */
if (pqGetInt(actual_result_len, 4, conn))
continue;
if (result_is_int)
{
if (pqGetInt(result_buf, 4, conn))
continue;
}
else
{
if (pqGetnchar((char *) result_buf,
*actual_result_len,
conn))
continue;
}
if (pqGetc(&id, conn)) /* get the last '0' */
continue;
}
if (id == '0')
{
/* correctly finished function result message */
status = PGRES_COMMAND_OK;
}
else
{
/* The backend violates the protocol. */
printfPQExpBuffer(&conn->errorMessage,
libpq_gettext("protocol error: id=0x%x\n"),
id);
pqSaveErrorResult(conn);
conn->inStart = conn->inCursor;
return pqPrepareAsyncResult(conn);
}
break;
case 'E': /* error return */
if (pqGetErrorNotice2(conn, true))
continue;
status = PGRES_FATAL_ERROR;
break;
case 'A': /* notify message */
/* handle notify and go back to processing return values */
if (getNotify(conn))
continue;
break;
case 'N': /* notice */
/* handle notice and go back to processing return values */
if (pqGetErrorNotice2(conn, false))
continue;
break;
case 'Z': /* backend is ready for new query */
/* consume the message and exit */
conn->inStart = conn->inCursor;
/* if we saved a result object (probably an error), use it */
if (conn->result)
return pqPrepareAsyncResult(conn);
return PQmakeEmptyPGresult(conn, status);
default:
/* The backend violates the protocol. */
printfPQExpBuffer(&conn->errorMessage,
libpq_gettext("protocol error: id=0x%x\n"),
id);
pqSaveErrorResult(conn);
conn->inStart = conn->inCursor;
return pqPrepareAsyncResult(conn);
}
/* Completed this message, keep going */
conn->inStart = conn->inCursor;
needInput = false;
}
/*
* We fall out of the loop only upon failing to read data.
* conn->errorMessage has been set by pqWait or pqReadData. We want to
* append it to any already-received error message.
*/
pqSaveErrorResult(conn);
return pqPrepareAsyncResult(conn);
}
/*
* Construct startup packet
*
* Returns a malloc'd packet buffer, or NULL if out of memory
*/
char *
pqBuildStartupPacket2(PGconn *conn, int *packetlen,
const PQEnvironmentOption *options)
{
StartupPacket *startpacket;
*packetlen = sizeof(StartupPacket);
startpacket = (StartupPacket *) malloc(sizeof(StartupPacket));
if (!startpacket)
return NULL;
MemSet((char *) startpacket, 0, sizeof(StartupPacket));
startpacket->protoVersion = htonl(conn->pversion);
strncpy(startpacket->user, conn->pguser, SM_USER);
strncpy(startpacket->database, conn->dbName, SM_DATABASE);
strncpy(startpacket->tty, conn->pgtty, SM_TTY);
if (conn->pgoptions)
strncpy(startpacket->options, conn->pgoptions, SM_OPTIONS);
return (char *) startpacket;
}
/*-------------------------------------------------------------------------
*
* fe-protocol3.c
* functions that are specific to frontend/backend protocol version 3
*
* Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
*
* IDENTIFICATION
* $Header: /cvsroot/pgsql/src/interfaces/libpq/fe-protocol3.c,v 1.1 2003/06/08 17:43:00 tgl Exp $
*
*-------------------------------------------------------------------------
*/
#include "postgres_fe.h"
#include <errno.h>
#include <ctype.h>
#include <fcntl.h>
#include "libpq-fe.h"
#include "libpq-int.h"
#include "mb/pg_wchar.h"
#ifdef WIN32
#include "win32.h"
#else
#include <unistd.h>
#include <netinet/in.h>
#ifdef HAVE_NETINET_TCP_H
#include <netinet/tcp.h>
#endif
#include <arpa/inet.h>
#endif
static void handleSyncLoss(PGconn *conn, char id, int msgLength);
static int getRowDescriptions(PGconn *conn);
static int getAnotherTuple(PGconn *conn, int msgLength);
static int getParameterStatus(PGconn *conn);
static int getNotify(PGconn *conn);
static int build_startup_packet(const PGconn *conn, char *packet,
const PQEnvironmentOption *options);
/*
* parseInput: if appropriate, parse input data from backend
* until input is exhausted or a stopping state is reached.
* Note that this function will NOT attempt to read more data from the backend.
*/
void
pqParseInput3(PGconn *conn)
{
char id;
int msgLength;
int avail;
char noticeWorkspace[128];
/*
* Loop to parse successive complete messages available in the buffer.
*/
for (;;)
{
/*
* Try to read a message. First get the type code and length.
* Return if not enough data.
*/
conn->inCursor = conn->inStart;
if (pqGetc(&id, conn))
return;
if (pqGetInt(&msgLength, 4, conn))
return;
/*
* Try to validate message type/length here. A length less than 4
* is definitely broken. Large lengths should only be believed
* for a few message types.
*/
if (msgLength < 4)
{
handleSyncLoss(conn, id, msgLength);
return;
}
if (msgLength > 30000 &&
!(id == 'T' || id == 'D' || id == 'd'))
{
handleSyncLoss(conn, id, msgLength);
return;
}
/*
* Can't process if message body isn't all here yet.
*/
msgLength -= 4;
avail = conn->inEnd - conn->inCursor;
if (avail < msgLength)
{
/*
* Before returning, enlarge the input buffer if needed to hold
* the whole message. This is better than leaving it to
* pqReadData because we can avoid multiple cycles of realloc()
* when the message is large; also, we can implement a reasonable
* recovery strategy if we are unable to make the buffer big
* enough.
*/
if (pqCheckInBufferSpace(conn->inCursor + msgLength, conn))
{
/*
* XXX add some better recovery code... plan is to skip
* over the message using its length, then report an error.
* For the moment, just treat this like loss of sync (which
* indeed it might be!)
*/
handleSyncLoss(conn, id, msgLength);
}
return;
}
/*
* NOTIFY and NOTICE messages can happen in any state; always process
* them right away.
*
* Most other messages should only be processed while in BUSY state.
* (In particular, in READY state we hold off further parsing
* until the application collects the current PGresult.)
*
* However, if the state is IDLE then we got trouble; we need to deal
* with the unexpected message somehow.
*
* ParameterStatus ('S') messages are a special case: in IDLE state
* we must process 'em (this case could happen if a new value was
* adopted from config file due to SIGHUP), but otherwise we hold
* off until BUSY state.
*/
if (id == 'A')
{
if (getNotify(conn))
return;
}
else if (id == 'N')
{
if (pqGetErrorNotice3(conn, false))
return;
}
else if (conn->asyncStatus != PGASYNC_BUSY)
{
/* If not IDLE state, just wait ... */
if (conn->asyncStatus != PGASYNC_IDLE)
return;
/*
* Unexpected message in IDLE state; need to recover somehow.
* ERROR messages are displayed using the notice processor;
* ParameterStatus is handled normally;
* anything else is just dropped on the floor after displaying
* a suitable warning notice. (An ERROR is very possibly the
* backend telling us why it is about to close the connection,
* so we don't want to just discard it...)
*/
if (id == 'E')
{
if (pqGetErrorNotice3(conn, false /* treat as notice */))
return;
}
else if (id == 'S')
{
if (getParameterStatus(conn))
return;
}
else
{
snprintf(noticeWorkspace, sizeof(noticeWorkspace),
libpq_gettext("message type 0x%02x arrived from server while idle\n"),
id);
PGDONOTICE(conn, noticeWorkspace);
/* Discard the unexpected message */
conn->inCursor += msgLength;
}
}
else
{
/*
* In BUSY state, we can process everything.
*/
switch (id)
{
case 'C': /* command complete */
if (pqGets(&conn->workBuffer, conn))
return;
if (conn->result == NULL)
conn->result = PQmakeEmptyPGresult(conn,
PGRES_COMMAND_OK);
strncpy(conn->result->cmdStatus, conn->workBuffer.data,
CMDSTATUS_LEN);
conn->asyncStatus = PGASYNC_READY;
break;
case 'E': /* error return */
if (pqGetErrorNotice3(conn, true))
return;
conn->asyncStatus = PGASYNC_READY;
break;
case 'Z': /* backend is ready for new query */
if (pqGetc(&conn->xact_status, conn))
return;
conn->asyncStatus = PGASYNC_IDLE;
break;
case 'I': /* empty query */
if (conn->result == NULL)
conn->result = PQmakeEmptyPGresult(conn,
PGRES_EMPTY_QUERY);
conn->asyncStatus = PGASYNC_READY;
break;
case 'S': /* parameter status */
if (getParameterStatus(conn))
return;
break;
case 'K': /* secret key data from the backend */
/*
* This is expected only during backend startup, but
* it's just as easy to handle it as part of the main
* loop. Save the data and continue processing.
*/
if (pqGetInt(&(conn->be_pid), 4, conn))
return;
if (pqGetInt(&(conn->be_key), 4, conn))
return;
break;
case 'T': /* row descriptions (start of query
* results) */
if (conn->result == NULL)
{
/* First 'T' in a query sequence */
if (getRowDescriptions(conn))
return;
}
else
{
/*
* A new 'T' message is treated as the start of
* another PGresult. (It is not clear that this
* is really possible with the current backend.)
* We stop parsing until the application accepts
* the current result.
*/
conn->asyncStatus = PGASYNC_READY;
return;
}
break;
case 'D': /* Data Row */
if (conn->result != NULL &&
conn->result->resultStatus == PGRES_TUPLES_OK)
{
/* Read another tuple of a normal query response */
if (getAnotherTuple(conn, msgLength))
return;
}
else if (conn->result != NULL &&
conn->result->resultStatus == PGRES_FATAL_ERROR)
{
/*
* We've already choked for some reason. Just discard
* tuples till we get to the end of the query.
*/
conn->inCursor += msgLength;
}
else
{
/* Set up to report error at end of query */
printfPQExpBuffer(&conn->errorMessage,
libpq_gettext("server sent data (\"D\" message) without prior row description (\"T\" message)\n"));
pqSaveErrorResult(conn);
/* Discard the unexpected message */
conn->inCursor += msgLength;
}
break;
case 'G': /* Start Copy In */
if (pqGetc(&conn->copy_is_binary, conn))
return;
/* XXX we currently ignore the rest of the message */
conn->inCursor = conn->inStart + 5 + msgLength;
conn->asyncStatus = PGASYNC_COPY_IN;
break;
case 'H': /* Start Copy Out */
if (pqGetc(&conn->copy_is_binary, conn))
return;
/* XXX we currently ignore the rest of the message */
conn->inCursor = conn->inStart + 5 + msgLength;
conn->asyncStatus = PGASYNC_COPY_OUT;
conn->copy_already_done = 0;
break;
case 'd': /* Copy Data */
/*
* If we see Copy Data, just silently drop it. This
* would only occur if application exits COPY OUT mode
* too early.
*/
conn->inCursor += msgLength;
break;
case 'c': /* Copy Done */
/*
* If we see Copy Done, just silently drop it. This
* is the normal case during PQendcopy. We will keep
* swallowing data, expecting to see command-complete
* for the COPY command.
*/
break;
default:
printfPQExpBuffer(&conn->errorMessage,
libpq_gettext(
"unexpected response from server; first received character was \"%c\"\n"),
id);
/* build an error result holding the error message */
pqSaveErrorResult(conn);
/* not sure if we will see more, so go to ready state */
conn->asyncStatus = PGASYNC_READY;
/* Discard the unexpected message */
conn->inCursor += msgLength;
break;
} /* switch on protocol character */
}
/* Successfully consumed this message */
if (conn->inCursor == conn->inStart + 5 + msgLength)
{
/* Normal case: parsing agrees with specified length */
conn->inStart = conn->inCursor;
}
else
{
/* Trouble --- report it */
printfPQExpBuffer(&conn->errorMessage,
libpq_gettext("Message contents do not agree with length in message type \"%c\"\n"),
id);
/* build an error result holding the error message */
pqSaveErrorResult(conn);
conn->asyncStatus = PGASYNC_READY;
/* trust the specified message length as what to skip */
conn->inStart += 5 + msgLength;
}
}
}
/*
* handleSyncLoss: clean up after loss of message-boundary sync
*
* There isn't really a lot we can do here except abandon the connection.
*/
static void
handleSyncLoss(PGconn *conn, char id, int msgLength)
{
printfPQExpBuffer(&conn->errorMessage,
libpq_gettext(
"lost synchronization with server: got message type \"%c\", length %d\n"),
id, msgLength);
/* build an error result holding the error message */
pqSaveErrorResult(conn);
conn->asyncStatus = PGASYNC_READY; /* drop out of GetResult wait loop */
pqsecure_close(conn);
closesocket(conn->sock);
conn->sock = -1;
conn->status = CONNECTION_BAD; /* No more connection to backend */
}
/*
* parseInput subroutine to read a 'T' (row descriptions) message.
* We build a PGresult structure containing the attribute data.
* Returns: 0 if completed message, EOF if not enough data yet.
*
* Note that if we run out of data, we have to release the partially
* constructed PGresult, and rebuild it again next time. Fortunately,
* that shouldn't happen often, since 'T' messages usually fit in a packet.
*/
static int
getRowDescriptions(PGconn *conn)
{
PGresult *result;
int nfields;
int i;
result = PQmakeEmptyPGresult(conn, PGRES_TUPLES_OK);
/* parseInput already read the 'T' label and message length. */
/* the next two bytes are the number of fields */
if (pqGetInt(&(result->numAttributes), 2, conn))
{
PQclear(result);
return EOF;
}
nfields = result->numAttributes;
/* allocate space for the attribute descriptors */
if (nfields > 0)
{
result->attDescs = (PGresAttDesc *)
pqResultAlloc(result, nfields * sizeof(PGresAttDesc), TRUE);
MemSet((char *) result->attDescs, 0, nfields * sizeof(PGresAttDesc));
}
/* get type info */
for (i = 0; i < nfields; i++)
{
int tableid;
int columnid;
int typid;
int typlen;
int atttypmod;
int format;
if (pqGets(&conn->workBuffer, conn) ||
pqGetInt(&tableid, 4, conn) ||
pqGetInt(&columnid, 2, conn) ||
pqGetInt(&typid, 4, conn) ||
pqGetInt(&typlen, 2, conn) ||
pqGetInt(&atttypmod, 4, conn) ||
pqGetInt(&format, 2, conn))
{
PQclear(result);
return EOF;
}
/*
* Since pqGetInt treats 2-byte integers as unsigned, we need to
* coerce these results to signed form.
*/
columnid = (int) ((int16) columnid);
typlen = (int) ((int16) typlen);
format = (int) ((int16) format);
result->attDescs[i].name = pqResultStrdup(result,
conn->workBuffer.data);
result->attDescs[i].typid = typid;
result->attDescs[i].typlen = typlen;
result->attDescs[i].atttypmod = atttypmod;
/* XXX todo: save tableid/columnid, format too */
}
/* Success! */
conn->result = result;
return 0;
}
/*
* parseInput subroutine to read a 'D' (row data) message.
* We add another tuple to the existing PGresult structure.
* Returns: 0 if completed message, EOF if error or not enough data yet.
*
* Note that if we run out of data, we have to suspend and reprocess
* the message after more data is received. We keep a partially constructed
* tuple in conn->curTuple, and avoid reallocating already-allocated storage.
*/
static int
getAnotherTuple(PGconn *conn, int msgLength)
{
PGresult *result = conn->result;
int nfields = result->numAttributes;
PGresAttValue *tup;
int tupnfields; /* # fields from tuple */
int vlen; /* length of the current field value */
int i;
/* Allocate tuple space if first time for this data message */
if (conn->curTuple == NULL)
{
conn->curTuple = (PGresAttValue *)
pqResultAlloc(result, nfields * sizeof(PGresAttValue), TRUE);
if (conn->curTuple == NULL)
goto outOfMemory;
MemSet((char *) conn->curTuple, 0, nfields * sizeof(PGresAttValue));
}
tup = conn->curTuple;
/* Get the field count and make sure it's what we expect */
if (pqGetInt(&tupnfields, 2, conn))
return EOF;
if (tupnfields != nfields)
{
/* Replace partially constructed result with an error result */
printfPQExpBuffer(&conn->errorMessage,
libpq_gettext("unexpected field count in D message\n"));
pqSaveErrorResult(conn);
/* Discard the failed message by pretending we read it */
conn->inCursor = conn->inStart + 5 + msgLength;
return 0;
}
/* Scan the fields */
for (i = 0; i < nfields; i++)
{
/* get the value length */
if (pqGetInt(&vlen, 4, conn))
return EOF;
if (vlen == -1)
{
/* null field */
tup[i].value = result->null_field;
tup[i].len = NULL_LEN;
continue;
}
if (vlen < 0)
vlen = 0;
if (tup[i].value == NULL)
{
tup[i].value = (char *) pqResultAlloc(result, vlen + 1, false);
if (tup[i].value == NULL)
goto outOfMemory;
}
tup[i].len = vlen;
/* read in the value */
if (vlen > 0)
if (pqGetnchar((char *) (tup[i].value), vlen, conn))
return EOF;
/* we have to terminate this ourselves */
tup[i].value[vlen] = '\0';
}
/* Success! Store the completed tuple in the result */
if (!pqAddTuple(result, tup))
goto outOfMemory;
/* and reset for a new message */
conn->curTuple = NULL;
return 0;
outOfMemory:
/*
* Replace partially constructed result with an error result.
* First discard the old result to try to win back some memory.
*/
pqClearAsyncResult(conn);
printfPQExpBuffer(&conn->errorMessage,
libpq_gettext("out of memory for query result\n"));
pqSaveErrorResult(conn);
/* Discard the failed message by pretending we read it */
conn->inCursor = conn->inStart + 5 + msgLength;
return 0;
}
/*
* Attempt to read an Error or Notice response message.
* This is possible in several places, so we break it out as a subroutine.
* Entry: 'E' or 'N' message type and length have already been consumed.
* Exit: returns 0 if successfully consumed message.
* returns EOF if not enough data.
*/
int
pqGetErrorNotice3(PGconn *conn, bool isError)
{
PGresult *res;
PQExpBufferData workBuf;
char id;
/*
* Make a PGresult to hold the accumulated fields. We temporarily
* lie about the result status, so that PQmakeEmptyPGresult doesn't
* uselessly copy conn->errorMessage.
*/
res = PQmakeEmptyPGresult(conn, PGRES_EMPTY_QUERY);
res->resultStatus = isError ? PGRES_FATAL_ERROR : PGRES_NONFATAL_ERROR;
/*
* Since the fields might be pretty long, we create a temporary
* PQExpBuffer rather than using conn->workBuffer. workBuffer is
* intended for stuff that is expected to be short. We shouldn't
* use conn->errorMessage either, since this might be only a notice.
*/
initPQExpBuffer(&workBuf);
/*
* Read the fields and save into res.
*/
for (;;)
{
if (pqGetc(&id, conn))
goto fail;
if (id == '\0')
break; /* terminator found */
if (pqGets(&workBuf, conn))
goto fail;
switch (id)
{
case 'S':
res->errSeverity = pqResultStrdup(res, workBuf.data);
break;
case 'C':
res->errCode = pqResultStrdup(res, workBuf.data);
break;
case 'M':
res->errPrimary = pqResultStrdup(res, workBuf.data);
break;
case 'D':
res->errDetail = pqResultStrdup(res, workBuf.data);
break;
case 'H':
res->errHint = pqResultStrdup(res, workBuf.data);
break;
case 'P':
res->errPosition = pqResultStrdup(res, workBuf.data);
break;
case 'W':
res->errContext = pqResultStrdup(res, workBuf.data);
break;
case 'F':
res->errFilename = pqResultStrdup(res, workBuf.data);
break;
case 'L':
res->errLineno = pqResultStrdup(res, workBuf.data);
break;
case 'R':
res->errFuncname = pqResultStrdup(res, workBuf.data);
break;
default:
/* silently ignore any other field type */
break;
}
}
/*
* Now build the "overall" error message for PQresultErrorMessage.
*
* XXX this should be configurable somehow.
*/
resetPQExpBuffer(&workBuf);
if (res->errSeverity)
appendPQExpBuffer(&workBuf, "%s: ", res->errSeverity);
if (res->errPrimary)
appendPQExpBufferStr(&workBuf, res->errPrimary);
/* translator: %s represents a digit string */
if (res->errPosition)
appendPQExpBuffer(&workBuf, libpq_gettext(" at character %s"),
res->errPosition);
appendPQExpBufferChar(&workBuf, '\n');
if (res->errDetail)
appendPQExpBuffer(&workBuf, libpq_gettext("DETAIL: %s\n"),
res->errDetail);
if (res->errHint)
appendPQExpBuffer(&workBuf, libpq_gettext("HINT: %s\n"),
res->errHint);
if (res->errContext)
appendPQExpBuffer(&workBuf, libpq_gettext("CONTEXT: %s\n"),
res->errContext);
/*
* Either save error as current async result, or just emit the notice.
*/
if (isError)
{
res->errMsg = pqResultStrdup(res, workBuf.data);
pqClearAsyncResult(conn);
conn->result = res;
resetPQExpBuffer(&conn->errorMessage);
appendPQExpBufferStr(&conn->errorMessage, workBuf.data);
}
else
{
PGDONOTICE(conn, workBuf.data);
PQclear(res);
}
termPQExpBuffer(&workBuf);
return 0;
fail:
PQclear(res);
termPQExpBuffer(&workBuf);
return EOF;
}
/*
* Attempt to read a ParameterStatus message.
* This is possible in several places, so we break it out as a subroutine.
* Entry: 'S' message type and length have already been consumed.
* Exit: returns 0 if successfully consumed message.
* returns EOF if not enough data.
*/
static int
getParameterStatus(PGconn *conn)
{
PQExpBufferData valueBuf;
/* Get the parameter name */
if (pqGets(&conn->workBuffer, conn))
return EOF;
/* Get the parameter value (could be large) */
initPQExpBuffer(&valueBuf);
if (pqGets(&valueBuf, conn))
{
termPQExpBuffer(&valueBuf);
return EOF;
}
/* And save it */
pqSaveParameterStatus(conn, conn->workBuffer.data, valueBuf.data);
termPQExpBuffer(&valueBuf);
return 0;
}
/*
* Attempt to read a Notify response message.
* This is possible in several places, so we break it out as a subroutine.
* Entry: 'A' message type and length have already been consumed.
* Exit: returns 0 if successfully consumed Notify message.
* returns EOF if not enough data.
*/
static int
getNotify(PGconn *conn)
{
int be_pid;
PGnotify *newNotify;
if (pqGetInt(&be_pid, 4, conn))
return EOF;
if (pqGets(&conn->workBuffer, conn))
return EOF;
/*
* Store the relation name right after the PQnotify structure so it
* can all be freed at once. We don't use NAMEDATALEN because we
* don't want to tie this interface to a specific server name length.
*/
newNotify = (PGnotify *) malloc(sizeof(PGnotify) +
strlen(conn->workBuffer.data) +1);
if (newNotify)
{
newNotify->relname = (char *) newNotify + sizeof(PGnotify);
strcpy(newNotify->relname, conn->workBuffer.data);
newNotify->be_pid = be_pid;
DLAddTail(conn->notifyList, DLNewElem(newNotify));
}
/* Swallow extra string (not presently used) */
if (pqGets(&conn->workBuffer, conn))
return EOF;
return 0;
}
/*
* PQgetline - gets a newline-terminated string from the backend.
*
* See fe-exec.c for documentation.
*/
int
pqGetline3(PGconn *conn, char *s, int maxlen)
{
int status;
if (conn->sock < 0 ||
conn->asyncStatus != PGASYNC_COPY_OUT ||
conn->copy_is_binary)
{
printfPQExpBuffer(&conn->errorMessage,
libpq_gettext("PQgetline: not doing text COPY OUT\n"));
*s = '\0';
return EOF;
}
while ((status = PQgetlineAsync(conn, s, maxlen-1)) == 0)
{
/* need to load more data */
if (pqWait(TRUE, FALSE, conn) ||
pqReadData(conn) < 0)
{
*s = '\0';
return EOF;
}
}
if (status < 0)
{
/* End of copy detected; gin up old-style terminator */
strcpy(s, "\\.");
return 0;
}
/* Add null terminator, and strip trailing \n if present */
if (s[status-1] == '\n')
{
s[status-1] = '\0';
return 0;
}
else
{
s[status] = '\0';
return 1;
}
}
/*
* PQgetlineAsync - gets a COPY data row without blocking.
*
* See fe-exec.c for documentation.
*/
int
pqGetlineAsync3(PGconn *conn, char *buffer, int bufsize)
{
char id;
int msgLength;
int avail;
if (conn->asyncStatus != PGASYNC_COPY_OUT)
return -1; /* we are not doing a copy... */
/*
* Recognize the next input message. To make life simpler for async
* callers, we keep returning 0 until the next message is fully available
* even if it is not Copy Data. This should keep PQendcopy from blocking.
*/
conn->inCursor = conn->inStart;
if (pqGetc(&id, conn))
return 0;
if (pqGetInt(&msgLength, 4, conn))
return 0;
avail = conn->inEnd - conn->inCursor;
if (avail < msgLength - 4)
return 0;
/*
* Cannot proceed unless it's a Copy Data message. Anything else means
* end of copy mode.
*/
if (id != 'd')
return -1;
/*
* Move data from libpq's buffer to the caller's. In the case where
* a prior call found the caller's buffer too small, we use
* conn->copy_already_done to remember how much of the row was already
* returned to the caller.
*/
conn->inCursor += conn->copy_already_done;
avail = msgLength - 4 - conn->copy_already_done;
if (avail <= bufsize)
{
/* Able to consume the whole message */
memcpy(buffer, &conn->inBuffer[conn->inCursor], avail);
/* Mark message consumed */
conn->inStart = conn->inCursor + avail;
/* Reset state for next time */
conn->copy_already_done = 0;
return avail;
}
else
{
/* We must return a partial message */
memcpy(buffer, &conn->inBuffer[conn->inCursor], bufsize);
/* The message is NOT consumed from libpq's buffer */
conn->copy_already_done += bufsize;
return bufsize;
}
}
/*
* PQendcopy
*
* See fe-exec.c for documentation.
*/
int
pqEndcopy3(PGconn *conn)
{
PGresult *result;
if (conn->asyncStatus != PGASYNC_COPY_IN &&
conn->asyncStatus != PGASYNC_COPY_OUT)
{
printfPQExpBuffer(&conn->errorMessage,
libpq_gettext("no COPY in progress\n"));
return 1;
}
/* Send the CopyDone message if needed */
if (conn->asyncStatus == PGASYNC_COPY_IN)
{
if (pqPutMsgStart('c', false, conn) < 0 ||
pqPutMsgEnd(conn) < 0)
return 1;
}
/*
* make sure no data is waiting to be sent, abort if we are
* non-blocking and the flush fails
*/
if (pqFlush(conn) && pqIsnonblocking(conn))
return (1);
/* Return to active duty */
conn->asyncStatus = PGASYNC_BUSY;
resetPQExpBuffer(&conn->errorMessage);
/*
* Non blocking connections may have to abort at this point. If everyone
* played the game there should be no problem, but in error scenarios
* the expected messages may not have arrived yet. (We are assuming that
* the backend's packetizing will ensure that CommandComplete arrives
* along with the CopyDone; are there corner cases where that doesn't
* happen?)
*/
if (pqIsnonblocking(conn) && PQisBusy(conn))
return (1);
/* Wait for the completion response */
result = PQgetResult(conn);
/* Expecting a successful result */
if (result && result->resultStatus == PGRES_COMMAND_OK)
{
PQclear(result);
return 0;
}
/*
* Trouble. For backwards-compatibility reasons, we issue the error
* message as if it were a notice (would be nice to get rid of this
* silliness, but too many apps probably don't handle errors from
* PQendcopy reasonably). Note that the app can still obtain the
* error status from the PGconn object.
*/
if (conn->errorMessage.len > 0)
PGDONOTICE(conn, conn->errorMessage.data);
PQclear(result);
return 1;
}
/*
* PQfn - Send a function call to the POSTGRES backend.
*
* See fe-exec.c for documentation.
*/
PGresult *
pqFunctionCall3(PGconn *conn, Oid fnid,
int *result_buf, int *actual_result_len,
int result_is_int,
const PQArgBlock *args, int nargs)
{
bool needInput = false;
ExecStatusType status = PGRES_FATAL_ERROR;
char id;
int msgLength;
int avail;
int i;
/* PQfn already validated connection state */
if (pqPutMsgStart('F', false, conn) < 0 || /* function call msg */
pqPutInt(fnid, 4, conn) < 0 || /* function id */
pqPutInt(1, 2, conn) < 0 || /* # of format codes */
pqPutInt(1, 2, conn) < 0 || /* format code: BINARY */
pqPutInt(nargs, 2, conn) < 0) /* # of args */
{
pqHandleSendFailure(conn);
return NULL;
}
for (i = 0; i < nargs; ++i)
{ /* len.int4 + contents */
if (pqPutInt(args[i].len, 4, conn))
{
pqHandleSendFailure(conn);
return NULL;
}
if (args[i].len == -1)
continue; /* it's NULL */
if (args[i].isint)
{
if (pqPutInt(args[i].u.integer, args[i].len, conn))
{
pqHandleSendFailure(conn);
return NULL;
}
}
else
{
if (pqPutnchar((char *) args[i].u.ptr, args[i].len, conn))
{
pqHandleSendFailure(conn);
return NULL;
}
}
}
if (pqPutInt(1, 2, conn) < 0) /* result format code: BINARY */
{
pqHandleSendFailure(conn);
return NULL;
}
if (pqPutMsgEnd(conn) < 0 ||
pqFlush(conn))
{
pqHandleSendFailure(conn);
return NULL;
}
for (;;)
{
if (needInput)
{
/* Wait for some data to arrive (or for the channel to close) */
if (pqWait(TRUE, FALSE, conn) ||
pqReadData(conn) < 0)
break;
}
/*
* Scan the message. If we run out of data, loop around to try
* again.
*/
needInput = true;
conn->inCursor = conn->inStart;
if (pqGetc(&id, conn))
continue;
if (pqGetInt(&msgLength, 4, conn))
continue;
/*
* Try to validate message type/length here. A length less than 4
* is definitely broken. Large lengths should only be believed
* for a few message types.
*/
if (msgLength < 4)
{
handleSyncLoss(conn, id, msgLength);
break;
}
if (msgLength > 30000 &&
!(id == 'T' || id == 'D' || id == 'd' || id == 'V'))
{
handleSyncLoss(conn, id, msgLength);
break;
}
/*
* Can't process if message body isn't all here yet.
*/
msgLength -= 4;
avail = conn->inEnd - conn->inCursor;
if (avail < msgLength)
{
/*
* Before looping, enlarge the input buffer if needed to hold
* the whole message. See notes in parseInput.
*/
if (pqCheckInBufferSpace(conn->inCursor + msgLength, conn))
{
/*
* XXX add some better recovery code... plan is to skip
* over the message using its length, then report an error.
* For the moment, just treat this like loss of sync (which
* indeed it might be!)
*/
handleSyncLoss(conn, id, msgLength);
break;
}
continue;
}
/*
* We should see V or E response to the command, but might get N
* and/or A notices first. We also need to swallow the final Z
* before returning.
*/
switch (id)
{
case 'V': /* function result */
if (pqGetInt(actual_result_len, 4, conn))
continue;
if (*actual_result_len != -1)
{
if (result_is_int)
{
if (pqGetInt(result_buf, *actual_result_len, conn))
continue;
}
else
{
if (pqGetnchar((char *) result_buf,
*actual_result_len,
conn))
continue;
}
}
/* correctly finished function result message */
status = PGRES_COMMAND_OK;
break;
case 'E': /* error return */
if (pqGetErrorNotice3(conn, true))
continue;
status = PGRES_FATAL_ERROR;
break;
case 'A': /* notify message */
/* handle notify and go back to processing return values */
if (getNotify(conn))
continue;
break;
case 'N': /* notice */
/* handle notice and go back to processing return values */
if (pqGetErrorNotice3(conn, false))
continue;
break;
case 'Z': /* backend is ready for new query */
if (pqGetc(&conn->xact_status, conn))
continue;
/* consume the message and exit */
conn->inStart += 5 + msgLength;
/* if we saved a result object (probably an error), use it */
if (conn->result)
return pqPrepareAsyncResult(conn);
return PQmakeEmptyPGresult(conn, status);
case 'S': /* parameter status */
if (getParameterStatus(conn))
continue;
break;
default:
/* The backend violates the protocol. */
printfPQExpBuffer(&conn->errorMessage,
libpq_gettext("protocol error: id=0x%x\n"),
id);
pqSaveErrorResult(conn);
/* trust the specified message length as what to skip */
conn->inStart += 5 + msgLength;
return pqPrepareAsyncResult(conn);
}
/* Completed this message, keep going */
/* trust the specified message length as what to skip */
conn->inStart += 5 + msgLength;
needInput = false;
}
/*
* We fall out of the loop only upon failing to read data.
* conn->errorMessage has been set by pqWait or pqReadData. We want to
* append it to any already-received error message.
*/
pqSaveErrorResult(conn);
return pqPrepareAsyncResult(conn);
}
/*
* Construct startup packet
*
* Returns a malloc'd packet buffer, or NULL if out of memory
*/
char *
pqBuildStartupPacket3(PGconn *conn, int *packetlen,
const PQEnvironmentOption *options)
{
char *startpacket;
*packetlen = build_startup_packet(conn, NULL, options);
startpacket = (char *) malloc(*packetlen);
if (!startpacket)
return NULL;
*packetlen = build_startup_packet(conn, startpacket, options);
return startpacket;
}
/*
* Build a startup packet given a filled-in PGconn structure.
*
* We need to figure out how much space is needed, then fill it in.
* To avoid duplicate logic, this routine is called twice: the first time
* (with packet == NULL) just counts the space needed, the second time
* (with packet == allocated space) fills it in. Return value is the number
* of bytes used.
*/
static int
build_startup_packet(const PGconn *conn, char *packet,
const PQEnvironmentOption *options)
{
int packet_len = 0;
const PQEnvironmentOption *next_eo;
/* Protocol version comes first. */
if (packet)
{
ProtocolVersion pv = htonl(conn->pversion);
memcpy(packet + packet_len, &pv, sizeof(ProtocolVersion));
}
packet_len += sizeof(ProtocolVersion);
/* Add user name, database name, options */
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;
}
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;
}
if (conn->pgoptions && conn->pgoptions[0])
{
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;
}
/* 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 (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 trailing terminator */
if (packet)
packet[packet_len] = '\0';
packet_len++;
return packet_len;
}
......@@ -11,7 +11,7 @@
*
*
* IDENTIFICATION
* $Header: /cvsroot/pgsql/src/interfaces/libpq/fe-secure.c,v 1.22 2003/04/10 23:03:08 tgl Exp $
* $Header: /cvsroot/pgsql/src/interfaces/libpq/fe-secure.c,v 1.23 2003/06/08 17:43:00 tgl Exp $
*
* NOTES
* The client *requires* a valid server certificate. Since
......@@ -132,7 +132,7 @@ static DH *tmp_dh_cb(SSL *s, int is_export, int keylength);
static int client_cert_cb(SSL *, X509 **, EVP_PKEY **);
static int initialize_SSL(PGconn *);
static void destroy_SSL(void);
static int open_client_SSL(PGconn *);
static PostgresPollingStatusType open_client_SSL(PGconn *);
static void close_SSL(PGconn *);
static const char *SSLerrmessage(void);
#endif
......@@ -231,16 +231,30 @@ pqsecure_destroy(void)
/*
* Attempt to negotiate secure session.
*/
int
PostgresPollingStatusType
pqsecure_open_client(PGconn *conn)
{
int r = 0;
#ifdef USE_SSL
r = open_client_SSL(conn);
/* First time through? */
if (conn->ssl == NULL)
{
if (!(conn->ssl = SSL_new(SSL_context)) ||
!SSL_set_app_data(conn->ssl, conn) ||
!SSL_set_fd(conn->ssl, conn->sock))
{
printfPQExpBuffer(&conn->errorMessage,
libpq_gettext("could not establish SSL connection: %s\n"),
SSLerrmessage());
close_SSL(conn);
return PGRES_POLLING_FAILED;
}
}
/* Begin or continue the actual handshake */
return open_client_SSL(conn);
#else
/* shouldn't get here */
return PGRES_POLLING_FAILED;
#endif
return r;
}
/*
......@@ -273,8 +287,15 @@ pqsecure_read(PGconn *conn, void *ptr, size_t len)
case SSL_ERROR_NONE:
break;
case SSL_ERROR_WANT_READ:
n = 0;
break;
case SSL_ERROR_WANT_WRITE:
/* XXX to support nonblock I/O, we should return 0 here */
/*
* Returning 0 here would cause caller to wait for read-ready,
* which is not correct since what SSL wants is wait for
* write-ready. The former could get us stuck in an infinite
* wait, so don't risk it; busy-loop instead.
*/
goto rloop;
case SSL_ERROR_SYSCALL:
if (n == -1)
......@@ -322,16 +343,22 @@ pqsecure_write(PGconn *conn, const void *ptr, size_t len)
#ifdef USE_SSL
if (conn->ssl)
{
wloop:
n = SSL_write(conn->ssl, ptr, len);
switch (SSL_get_error(conn->ssl, n))
{
case SSL_ERROR_NONE:
break;
case SSL_ERROR_WANT_READ:
/*
* Returning 0 here causes caller to wait for write-ready,
* which is not really the right thing, but it's the best
* we can do.
*/
n = 0;
break;
case SSL_ERROR_WANT_WRITE:
/* XXX to support nonblock I/O, we should return 0 here */
goto wloop;
n = 0;
break;
case SSL_ERROR_SYSCALL:
if (n == -1)
printfPQExpBuffer(&conn->errorMessage,
......@@ -802,23 +829,45 @@ destroy_SSL(void)
/*
* Attempt to negotiate SSL connection.
*/
static int
static PostgresPollingStatusType
open_client_SSL(PGconn *conn)
{
#ifdef NOT_USED
int r;
#endif
if (!(conn->ssl = SSL_new(SSL_context)) ||
!SSL_set_app_data(conn->ssl, conn) ||
!SSL_set_fd(conn->ssl, conn->sock) ||
SSL_connect(conn->ssl) <= 0)
r = SSL_connect(conn->ssl);
if (r <= 0)
{
switch (SSL_get_error(conn->ssl, r))
{
case SSL_ERROR_WANT_READ:
return PGRES_POLLING_READING;
case SSL_ERROR_WANT_WRITE:
return PGRES_POLLING_WRITING;
case SSL_ERROR_SYSCALL:
if (r == -1)
printfPQExpBuffer(&conn->errorMessage,
libpq_gettext("could not establish SSL connection: %s\n"),
SSLerrmessage());
libpq_gettext("SSL SYSCALL error: %s\n"),
SOCK_STRERROR(SOCK_ERRNO));
else
printfPQExpBuffer(&conn->errorMessage,
libpq_gettext("SSL SYSCALL error: EOF detected\n"));
close_SSL(conn);
return -1;
return PGRES_POLLING_FAILED;
case SSL_ERROR_SSL:
printfPQExpBuffer(&conn->errorMessage,
libpq_gettext("SSL error: %s\n"), SSLerrmessage());
close_SSL(conn);
return PGRES_POLLING_FAILED;
default:
printfPQExpBuffer(&conn->errorMessage,
libpq_gettext("Unknown SSL error code\n"));
close_SSL(conn);
return PGRES_POLLING_FAILED;
}
}
/* check the certificate chain of the server */
......@@ -836,7 +885,7 @@ open_client_SSL(PGconn *conn)
libpq_gettext("certificate could not be validated: %s\n"),
X509_verify_cert_error_string(r));
close_SSL(conn);
return -1;
return PGRES_POLLING_FAILED;
}
#endif
......@@ -848,7 +897,7 @@ open_client_SSL(PGconn *conn)
libpq_gettext("certificate could not be obtained: %s\n"),
SSLerrmessage());
close_SSL(conn);
return -1;
return PGRES_POLLING_FAILED;
}
X509_NAME_oneline(X509_get_subject_name(conn->peer),
......@@ -871,11 +920,12 @@ open_client_SSL(PGconn *conn)
if (verify_peer(conn) == -1)
{
close_SSL(conn);
return -1;
return PGRES_POLLING_FAILED;
}
#endif
return 0;
/* SSL handshake is complete */
return PGRES_POLLING_OK;
}
/*
......
......@@ -7,7 +7,7 @@
* Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* $Id: libpq-fe.h,v 1.92 2003/04/19 00:02:30 tgl Exp $
* $Id: libpq-fe.h,v 1.93 2003/06/08 17:43:00 tgl Exp $
*
*-------------------------------------------------------------------------
*/
......@@ -38,9 +38,9 @@ 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.
* Although it is okay to add to this list, values which become unused
* should never be removed, nor should constants be redefined - that would
* break compatibility with existing code.
*/
CONNECTION_OK,
CONNECTION_BAD,
......@@ -56,7 +56,9 @@ typedef enum
* postmaster. */
CONNECTION_AUTH_OK, /* Received authentication; waiting for
* backend startup. */
CONNECTION_SETENV /* Negotiating environment. */
CONNECTION_SETENV, /* Negotiating environment. */
CONNECTION_SSL_STARTUP, /* Negotiating SSL. */
CONNECTION_NEEDED /* Internal state: connect() needed */
} ConnStatusType;
typedef enum
......@@ -71,7 +73,7 @@ typedef enum
typedef enum
{
PGRES_EMPTY_QUERY = 0,
PGRES_EMPTY_QUERY = 0, /* empty query string was executed */
PGRES_COMMAND_OK, /* a query command that doesn't return
* anything was executed properly by the
* backend */
......@@ -82,8 +84,8 @@ typedef enum
PGRES_COPY_IN, /* Copy In data transfer in progress */
PGRES_BAD_RESPONSE, /* an unexpected response was recv'd from
* the backend */
PGRES_NONFATAL_ERROR,
PGRES_FATAL_ERROR
PGRES_NONFATAL_ERROR, /* notice or warning message */
PGRES_FATAL_ERROR /* query failed */
} ExecStatusType;
/* PGconn encapsulates a connection to the backend.
......
......@@ -12,7 +12,7 @@
* Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* $Id: libpq-int.h,v 1.70 2003/05/08 18:33:39 tgl Exp $
* $Id: libpq-int.h,v 1.71 2003/06/08 17:43:00 tgl Exp $
*
*-------------------------------------------------------------------------
*/
......@@ -35,6 +35,7 @@ typedef int ssize_t; /* ssize_t doesn't exist in VC (atleast
#include "postgres_fe.h"
/* include stuff common to fe and be */
#include "getaddrinfo.h"
#include "libpq/pqcomm.h"
#include "lib/dllist.h"
/* include stuff found in fe only */
......@@ -45,23 +46,9 @@ typedef int ssize_t; /* ssize_t doesn't exist in VC (atleast
#include <openssl/err.h>
#endif
/* libpq supports this version of the frontend/backend protocol.
*
* NB: we used to use PG_PROTOCOL_LATEST from the backend pqcomm.h file,
* but that's not really the right thing: just recompiling libpq
* against a more recent backend isn't going to magically update it
* for most sorts of protocol changes. So, when you change libpq
* to support a different protocol revision, you have to change this
* constant too. PG_PROTOCOL_EARLIEST and PG_PROTOCOL_LATEST in
* pqcomm.h describe what the backend knows, not what libpq knows.
*/
#define PG_PROTOCOL_LIBPQ PG_PROTOCOL(3,0)
/*
* POSTGRES backend dependent Constants.
*/
#define PQERRORMSG_LENGTH 1024
#define CMDSTATUS_LEN 40
......@@ -96,20 +83,22 @@ typedef struct pgresAttDesc
int atttypmod; /* type-specific modifier info */
} PGresAttDesc;
/* Data for a single attribute of a single tuple */
/* We use char* for Attribute values.
The value pointer always points to a null-terminated area; we add a
null (zero) byte after whatever the backend sends us. This is only
particularly useful for text tuples ... with a binary value, the
value might have embedded nulls, so the application can't use C string
operators on it. But we add a null anyway for consistency.
Note that the value itself does not contain a length word.
A NULL attribute is a special case in two ways: its len field is NULL_LEN
and its value field points to null_field in the owning PGresult. All the
NULL attributes in a query result point to the same place (there's no need
to store a null string separately for each one).
/*
* Data for a single attribute of a single tuple
*
* We use char* for Attribute values.
*
* The value pointer always points to a null-terminated area; we add a
* null (zero) byte after whatever the backend sends us. This is only
* particularly useful for text tuples ... with a binary value, the
* value might have embedded nulls, so the application can't use C string
* operators on it. But we add a null anyway for consistency.
* Note that the value itself does not contain a length word.
*
* A NULL attribute is a special case in two ways: its len field is NULL_LEN
* and its value field points to null_field in the owning PGresult. All the
* NULL attributes in a query result point to the same place (there's no need
* to store a null string separately for each one).
*/
#define NULL_LEN (-1) /* pg_result len for NULL value */
......@@ -135,15 +124,6 @@ struct pg_result
int binary; /* binary tuple values if binary == 1,
* otherwise text */
/*
* The conn link in PGresult is no longer used by any libpq code. It
* should be removed entirely, because it could be a dangling link
* (the application could keep the PGresult around longer than it
* keeps the PGconn!) But there may be apps out there that depend on
* it, so we will leave it here at least for a release or so.
*/
PGconn *xconn; /* connection we did the query on, if any */
/*
* These fields are copied from the originating PGconn, so that
* operations on the PGresult don't have to reference the PGconn.
......@@ -194,6 +174,35 @@ typedef enum
PGASYNC_COPY_OUT /* Copy Out data transfer in progress */
} PGAsyncStatusType;
/* PGSetenvStatusType defines the state of the PQSetenv state machine */
/* (this is used only for 2.0-protocol connections) */
typedef enum
{
SETENV_STATE_OPTION_SEND, /* About to send an Environment Option */
SETENV_STATE_OPTION_WAIT, /* Waiting for above send to complete */
SETENV_STATE_QUERY1_SEND, /* About to send a status query */
SETENV_STATE_QUERY1_WAIT, /* Waiting for query to complete */
SETENV_STATE_QUERY2_SEND, /* About to send a status query */
SETENV_STATE_QUERY2_WAIT, /* Waiting for query to complete */
SETENV_STATE_IDLE
} PGSetenvStatusType;
/* Typedef for the EnvironmentOptions[] array */
typedef struct PQEnvironmentOption
{
const char *envName, /* name of an environment variable */
*pgName; /* name of corresponding SET variable */
} PQEnvironmentOption;
/* Typedef for parameter-status list entries */
typedef struct pgParameterStatus
{
struct pgParameterStatus *next; /* list link */
char *name; /* parameter name */
char *value; /* parameter value */
/* Note: name and value are stored in same malloc block as struct is */
} pgParameterStatus;
/* large-object-access data ... allocated only if large-object code is used. */
typedef struct pgLobjfuncs
{
......@@ -207,7 +216,8 @@ typedef struct pgLobjfuncs
Oid fn_lo_write; /* OID of backend function LOwrite */
} PGlobjfuncs;
/* PGconn stores all the state data associated with a single connection
/*
* PGconn stores all the state data associated with a single connection
* to a backend.
*/
struct pg_conn
......@@ -254,12 +264,21 @@ struct pg_conn
SockAddr laddr; /* Local address */
SockAddr raddr; /* Remote address */
int raddr_len; /* Length of remote address */
ProtocolVersion pversion; /* FE/BE protocol version in use */
char sversion[8]; /* The first few bytes of server version */
/* Transient state needed while establishing connection */
struct addrinfo *addrlist; /* list of possible backend addresses */
struct addrinfo *addr_cur; /* the one currently being tried */
PGSetenvStatusType setenv_state; /* for 2.0 protocol only */
const PQEnvironmentOption *next_eo;
/* Miscellaneous stuff */
int be_pid; /* PID of backend --- needed for cancels */
int be_key; /* key of backend --- needed for cancels */
char md5Salt[4]; /* password salt received from backend */
char cryptSalt[2]; /* password salt received from backend */
pgParameterStatus *pstatus; /* ParameterStatus data */
int client_encoding; /* encoding id */
PGlobjfuncs *lobjfuncs; /* private state for large-object access
* fns */
......@@ -279,7 +298,8 @@ struct pg_conn
int outCount; /* number of chars waiting in buffer */
/* State for constructing messages in outBuffer */
int outMsgStart; /* offset to msg start (length word) */
int outMsgStart; /* offset to msg start (length word);
* if -1, msg has no length word */
int outMsgEnd; /* offset to msg end (so far) */
/* Status for asynchronous result construction */
......@@ -324,10 +344,46 @@ extern int pqPacketSend(PGconn *conn, char pack_type,
/* === in fe-exec.c === */
extern void pqSetResultError(PGresult *res, const char *msg);
extern void pqCatenateResultError(PGresult *res, const char *msg);
extern void *pqResultAlloc(PGresult *res, size_t nBytes, bool isBinary);
extern char *pqResultStrdup(PGresult *res, const char *str);
extern void pqClearAsyncResult(PGconn *conn);
extern int pqGetErrorNotice(PGconn *conn, bool isError);
extern void pqSaveErrorResult(PGconn *conn);
extern PGresult *pqPrepareAsyncResult(PGconn *conn);
extern int pqAddTuple(PGresult *res, PGresAttValue *tup);
extern void pqSaveParameterStatus(PGconn *conn, const char *name,
const char *value);
extern const char *pqGetParameterStatus(PGconn *conn, const char *name);
extern void pqHandleSendFailure(PGconn *conn);
/* === in fe-protocol2.c === */
extern PostgresPollingStatusType pqSetenvPoll(PGconn *conn);
extern char *pqBuildStartupPacket2(PGconn *conn, int *packetlen,
const PQEnvironmentOption *options);
extern void pqParseInput2(PGconn *conn);
extern int pqGetline2(PGconn *conn, char *s, int maxlen);
extern int pqGetlineAsync2(PGconn *conn, char *buffer, int bufsize);
extern int pqEndcopy2(PGconn *conn);
extern PGresult *pqFunctionCall2(PGconn *conn, Oid fnid,
int *result_buf, int *actual_result_len,
int result_is_int,
const PQArgBlock *args, int nargs);
/* === in fe-protocol3.c === */
extern char *pqBuildStartupPacket3(PGconn *conn, int *packetlen,
const PQEnvironmentOption *options);
extern void pqParseInput3(PGconn *conn);
extern int pqGetErrorNotice3(PGconn *conn, bool isError);
extern int pqGetline3(PGconn *conn, char *s, int maxlen);
extern int pqGetlineAsync3(PGconn *conn, char *buffer, int bufsize);
extern int pqEndcopy3(PGconn *conn);
extern PGresult *pqFunctionCall3(PGconn *conn, Oid fnid,
int *result_buf, int *actual_result_len,
int result_is_int,
const PQArgBlock *args, int nargs);
/* === in fe-misc.c === */
......@@ -345,7 +401,7 @@ extern int pqGetnchar(char *s, size_t len, PGconn *conn);
extern int pqPutnchar(const char *s, size_t len, PGconn *conn);
extern int pqGetInt(int *result, size_t bytes, PGconn *conn);
extern int pqPutInt(int value, size_t bytes, PGconn *conn);
extern int pqPutMsgStart(char msg_type, PGconn *conn);
extern int pqPutMsgStart(char msg_type, bool force_len, PGconn *conn);
extern int pqPutMsgEnd(PGconn *conn);
extern int pqReadData(PGconn *conn);
extern int pqFlush(PGconn *conn);
......@@ -359,21 +415,14 @@ extern int pqWriteReady(PGconn *conn);
extern int pqsecure_initialize(PGconn *);
extern void pqsecure_destroy(void);
extern int pqsecure_open_client(PGconn *);
extern PostgresPollingStatusType pqsecure_open_client(PGconn *);
extern void pqsecure_close(PGconn *);
extern ssize_t pqsecure_read(PGconn *, void *ptr, size_t len);
extern ssize_t pqsecure_write(PGconn *, const void *ptr, size_t len);
/* bits in a byte */
#define BYTELEN 8
/* fall back options if they are not specified by arguments or defined
by environment variables */
#define DefaultHost "localhost"
#define DefaultTty ""
#define DefaultOption ""
#define DefaultAuthtype ""
#define DefaultPassword ""
/* Note: PGDONOTICE macro will work if applied to either PGconn or PGresult */
#define PGDONOTICE(conn,message) \
((*(conn)->noticeHook) ((conn)->noticeArg, (message)))
/*
* this is so that we can check is a connection is non-blocking internally
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment