aboutsummaryrefslogtreecommitdiff
path: root/src/interfaces/libpq/fe-misc.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/interfaces/libpq/fe-misc.c')
-rw-r--r--src/interfaces/libpq/fe-misc.c461
1 files changed, 390 insertions, 71 deletions
diff --git a/src/interfaces/libpq/fe-misc.c b/src/interfaces/libpq/fe-misc.c
index 8a16950cdcd..d7fc71dd134 100644
--- a/src/interfaces/libpq/fe-misc.c
+++ b/src/interfaces/libpq/fe-misc.c
@@ -5,177 +5,496 @@
*
* DESCRIPTION
* miscellaneous useful functions
- * these routines are analogous to the ones in libpq/pqcomm.c
+ *
+ * The communication routines here are analogous to the ones in
+ * backend/libpq/pqcomm.c and backend/libpq/pqcomprim.c, but operate
+ * in the considerably different environment of the frontend libpq.
+ * In particular, we work with a bare nonblock-mode socket, rather than
+ * a stdio stream, so that we can avoid unwanted blocking of the application.
+ *
+ * XXX: MOVE DEBUG PRINTOUT TO HIGHER LEVEL. As is, block and restart
+ * will cause repeat printouts.
+ *
+ * We must speak the same transmitted data representations as the backend
+ * routines. Note that this module supports *only* network byte order
+ * for transmitted ints, whereas the backend modules (as of this writing)
+ * still handle either network or little-endian byte order.
*
* Copyright (c) 1994, Regents of the University of California
*
*
* IDENTIFICATION
- * $Header: /cvsroot/pgsql/src/interfaces/libpq/fe-misc.c,v 1.10 1998/02/26 04:45:09 momjian Exp $
+ * $Header: /cvsroot/pgsql/src/interfaces/libpq/fe-misc.c,v 1.11 1998/05/06 23:51:14 momjian Exp $
*
*-------------------------------------------------------------------------
*/
#include <stdlib.h>
#include <stdio.h>
+#include <string.h>
+#include <time.h>
+#if !defined(NO_UNISTD_H)
+#include <unistd.h>
+#endif
+#include <sys/types.h> /* for fd_set stuff */
+#ifdef HAVE_SYS_SELECT_H
+#include <sys/select.h>
+#endif
#include "postgres.h"
-
#include "libpq-fe.h"
/* --------------------------------------------------------------------- */
/* pqGetc:
- get a character from stream f
+ get a character from the connection
- if debug is set, also echo the character fetched
+ All these routines return 0 on success, EOF on error.
+ Note that for the Get routines, EOF only means there is not enough
+ data in the buffer, not that there is necessarily a hard error.
*/
int
-pqGetc(FILE *fin, FILE *debug)
+pqGetc(char *result, PGconn *conn)
{
- int c;
+ if (conn->inCursor >= conn->inEnd)
+ return EOF;
- c = getc(fin);
+ *result = conn->inBuffer[conn->inCursor++];
- if (debug && c != EOF)
- fprintf(debug, "From backend> %c\n", c);
+ if (conn->Pfdebug)
+ fprintf(conn->Pfdebug, "From backend> %c\n", *result);
- return c;
+ return 0;
}
+
/* --------------------------------------------------------------------- */
-/* pqPutnchar:
- send a string of exactly len length into stream f
+/* pqPutBytes: local routine to write N bytes to the connection,
+ with buffering
+ */
+static int
+pqPutBytes(const char *s, int nbytes, PGconn *conn)
+{
+ int avail = conn->outBufSize - conn->outCount;
+
+ while (nbytes > avail)
+ {
+ memcpy(conn->outBuffer + conn->outCount, s, avail);
+ conn->outCount += avail;
+ s += avail;
+ nbytes -= avail;
+ if (pqFlush(conn))
+ return EOF;
+ avail = conn->outBufSize;
+ }
- returns 1 if there was an error, 0 otherwise.
+ memcpy(conn->outBuffer + conn->outCount, s, nbytes);
+ conn->outCount += nbytes;
+
+ return 0;
+}
+
+/* --------------------------------------------------------------------- */
+/* pqGets:
+ get a null-terminated string from the connection,
+ and store it in a buffer of size maxlen bytes.
+ If the incoming string is >= maxlen bytes, all of it is read,
+ but the excess characters are silently discarded.
*/
int
-pqPutnchar(const char *s, int len, FILE *f, FILE *debug)
+pqGets(char *s, int maxlen, PGconn *conn)
+{
+ /* Copy conn data to locals for faster search loop */
+ char *inBuffer = conn->inBuffer;
+ int inCursor = conn->inCursor;
+ int inEnd = conn->inEnd;
+ int slen;
+
+ while (inCursor < inEnd && inBuffer[inCursor])
+ inCursor++;
+
+ if (inCursor >= inEnd)
+ return EOF;
+
+ slen = inCursor - conn->inCursor;
+ if (slen < maxlen)
+ strcpy(s, inBuffer + conn->inCursor);
+ else
+ {
+ strncpy(s, inBuffer + conn->inCursor, maxlen-1);
+ s[maxlen-1] = '\0';
+ }
+
+ conn->inCursor = ++inCursor;
+
+ if (conn->Pfdebug)
+ fprintf(conn->Pfdebug, "From backend> \"%s\"\n", s);
+
+ return 0;
+}
+
+/* --------------------------------------------------------------------- */
+int
+pqPuts(const char *s, PGconn *conn)
{
- if (debug)
- fprintf(debug, "To backend> %s\n", s);
+ if (pqPutBytes(s, strlen(s)+1, conn))
+ return EOF;
+
+ if (conn->Pfdebug)
+ fprintf(conn->Pfdebug, "To backend> %s\n", s);
- return (pqPutNBytes(s, len, f) == EOF ? 1 : 0);
+ return 0;
}
/* --------------------------------------------------------------------- */
/* pqGetnchar:
get a string of exactly len bytes in buffer s (which must be 1 byte
- longer) from stream f and terminate it with a '\0'.
+ longer) and terminate it with a '\0'.
*/
int
-pqGetnchar(char *s, int len, FILE *f, FILE *debug)
+pqGetnchar(char *s, int len, PGconn *conn)
{
- int status;
+ if (len < 0 || len > conn->inEnd - conn->inCursor)
+ return EOF;
+
+ memcpy(s, conn->inBuffer + conn->inCursor, len);
+ s[len] = '\0';
- status = pqGetNBytes(s, len, f);
+ conn->inCursor += len;
- if (debug)
- fprintf(debug, "From backend (%d)> %s\n", len, s);
+ if (conn->Pfdebug)
+ fprintf(conn->Pfdebug, "From backend (%d)> %s\n", len, s);
- return (status == EOF ? 1 : 0);
+ return 0;
}
/* --------------------------------------------------------------------- */
-/* pqGets:
- get a string of up to length len from stream f
+/* pqPutnchar:
+ send a string of exactly len bytes
+ The buffer should have a terminating null, but it's not sent.
*/
int
-pqGets(char *s, int len, FILE *f, FILE *debug)
+pqPutnchar(const char *s, int len, PGconn *conn)
{
- int status;
-
- status = pqGetString(s, len, f);
+ if (pqPutBytes(s, len, conn))
+ return EOF;
- if (debug)
- fprintf(debug, "From backend> \"%s\"\n", s);
+ if (conn->Pfdebug)
+ fprintf(conn->Pfdebug, "To backend> %s\n", s);
- return (status == EOF ? 1 : 0);
+ return 0;
}
/* --------------------------------------------------------------------- */
-/* pgPutInt
- send an integer of 2 or 4 bytes to the file stream, compensate
- for host endianness.
- returns 0 if successful, 1 otherwise
+/* pgGetInt
+ read a 2 or 4 byte integer and convert from network byte order
+ to local byte order
*/
int
-pqPutInt(const int integer, int bytes, FILE *f, FILE *debug)
+pqGetInt(int *result, int bytes, PGconn *conn)
{
- int retval = 0;
+ uint16 tmp2;
+ uint32 tmp4;
switch (bytes)
{
case 2:
- retval = pqPutShort(integer, f);
+ if (conn->inCursor + 2 > conn->inEnd)
+ return EOF;
+ memcpy(&tmp2, conn->inBuffer + conn->inCursor, 2);
+ conn->inCursor += 2;
+ *result = (int) ntohs(tmp2);
break;
case 4:
- retval = pqPutLong(integer, f);
+ if (conn->inCursor + 4 > conn->inEnd)
+ return EOF;
+ memcpy(&tmp4, conn->inBuffer + conn->inCursor, 4);
+ conn->inCursor += 4;
+ *result = (int) ntohl(tmp4);
break;
default:
fprintf(stderr, "** int size %d not supported\n", bytes);
- retval = 1;
+ return EOF;
}
- if (debug)
- fprintf(debug, "To backend (%d#)> %d\n", bytes, integer);
+ if (conn->Pfdebug)
+ fprintf(conn->Pfdebug, "From backend (#%d)> %d\n", bytes, *result);
- return retval;
+ return 0;
}
/* --------------------------------------------------------------------- */
-/* pgGetInt
- read a 2 or 4 byte integer from the stream and swab it around
- to compensate for different endianness
- returns 0 if successful
+/* pgPutInt
+ send an integer of 2 or 4 bytes, converting from host byte order
+ to network byte order.
*/
int
-pqGetInt(int *result, int bytes, FILE *f, FILE *debug)
+pqPutInt(int value, int bytes, PGconn *conn)
{
- int retval = 0;
+ uint16 tmp2;
+ uint32 tmp4;
switch (bytes)
{
case 2:
- retval = pqGetShort(result, f);
+ tmp2 = htons((uint16) value);
+ if (pqPutBytes((const char*) &tmp2, 2, conn))
+ return EOF;
break;
case 4:
- retval = pqGetLong(result, f);
+ tmp4 = htonl((uint32) value);
+ if (pqPutBytes((const char*) &tmp4, 4, conn))
+ return EOF;
break;
default:
fprintf(stderr, "** int size %d not supported\n", bytes);
- retval = 1;
+ return EOF;
}
- if (debug)
- fprintf(debug, "From backend (#%d)> %d\n", bytes, *result);
+ if (conn->Pfdebug)
+ fprintf(conn->Pfdebug, "To backend (%d#)> %d\n", bytes, value);
- return retval;
+ return 0;
}
/* --------------------------------------------------------------------- */
+/* pqReadReady: is select() saying the file is ready to read?
+ */
+static int
+pqReadReady(PGconn *conn)
+{
+ fd_set input_mask;
+ struct timeval timeout;
+
+ if (conn->sock < 0)
+ return 0;
+
+ FD_ZERO(&input_mask);
+ FD_SET(conn->sock, &input_mask);
+ timeout.tv_sec = 0;
+ timeout.tv_usec = 0;
+ if (select(conn->sock+1, &input_mask, (fd_set *) NULL, (fd_set *) NULL,
+ &timeout) < 0)
+ {
+ sprintf(conn->errorMessage,
+ "pqReadReady() -- select() failed: errno=%d\n%s\n",
+ errno, strerror(errno));
+ return 0;
+ }
+ return FD_ISSET(conn->sock, &input_mask);
+}
+
+/* --------------------------------------------------------------------- */
+/* pqReadData: read more data, if any is available
+ * Possible return values:
+ * 1: successfully loaded at least one more byte
+ * 0: no data is presently available, but no error detected
+ * -1: error detected (including EOF = connection closure);
+ * conn->errorMessage set
+ * NOTE: callers must not assume that pointers or indexes into conn->inBuffer
+ * remain valid across this call!
+ */
int
-pqPuts(const char *s, FILE *f, FILE *debug)
+pqReadData(PGconn *conn)
{
- if (pqPutString(s, f) == EOF)
- return 1;
+ int nread;
+
+ if (conn->sock < 0)
+ {
+ strcpy(conn->errorMessage, "pqReadData() -- connection not open\n");
+ return -1;
+ }
- fflush(f);
+ /* Left-justify any data in the buffer to make room */
+ if (conn->inStart < conn->inEnd)
+ {
+ memmove(conn->inBuffer, conn->inBuffer + conn->inStart,
+ conn->inEnd - conn->inStart);
+ conn->inEnd -= conn->inStart;
+ conn->inCursor -= conn->inStart;
+ conn->inStart = 0;
+ }
+ else
+ {
+ conn->inStart = conn->inCursor = conn->inEnd = 0;
+ }
+ /* If the buffer is fairly full, enlarge it.
+ * We need to be able to enlarge the buffer in case a single message
+ * exceeds the initial buffer size. We enlarge before filling the
+ * buffer entirely so as to avoid asking the kernel for a partial packet.
+ * The magic constant here should be at least one TCP packet.
+ */
+ if (conn->inBufSize - conn->inEnd < 2000)
+ {
+ int newSize = conn->inBufSize * 2;
+ char * newBuf = (char *) realloc(conn->inBuffer, newSize);
+ if (newBuf)
+ {
+ conn->inBuffer = newBuf;
+ conn->inBufSize = newSize;
+ }
+ }
- if (debug)
- fprintf(debug, "To backend> %s\n", s);
+ /* OK, try to read some data */
+tryAgain:
+ nread = recv(conn->sock, conn->inBuffer + conn->inEnd,
+ conn->inBufSize - conn->inEnd, 0);
+ if (nread < 0)
+ {
+ if (errno == EINTR)
+ goto tryAgain;
+ sprintf(conn->errorMessage,
+ "pqReadData() -- read() failed: errno=%d\n%s\n",
+ errno, strerror(errno));
+ return -1;
+ }
+ if (nread > 0)
+ {
+ conn->inEnd += nread;
+ return 1;
+ }
- return 0;
+ /* A return value of 0 could mean just that no data is now available,
+ * or it could mean EOF --- that is, the server has closed the connection.
+ * Since we have the socket in nonblock mode, the only way to tell the
+ * difference is to see if select() is saying that the file is ready.
+ * Grumble. Fortunately, we don't expect this path to be taken much,
+ * since in normal practice we should not be trying to read data unless
+ * the file selected for reading already.
+ */
+ if (! pqReadReady(conn))
+ return 0; /* definitely no data available */
+
+ /* Still not sure that it's EOF,
+ * because some data could have just arrived.
+ */
+tryAgain2:
+ nread = recv(conn->sock, conn->inBuffer + conn->inEnd,
+ conn->inBufSize - conn->inEnd, 0);
+ if (nread < 0)
+ {
+ if (errno == EINTR)
+ goto tryAgain2;
+ sprintf(conn->errorMessage,
+ "pqReadData() -- read() failed: errno=%d\n%s\n",
+ errno, strerror(errno));
+ return -1;
+ }
+ if (nread > 0)
+ {
+ conn->inEnd += nread;
+ return 1;
+ }
+
+ /* OK, we are getting a zero read even though select() says ready.
+ * This means the connection has been closed. Cope.
+ */
+ sprintf(conn->errorMessage,
+ "pqReadData() -- backend closed the channel unexpectedly.\n"
+ "\tThis probably means the backend terminated abnormally"
+ " before or while processing the request.\n");
+ conn->status = CONNECTION_BAD; /* No more connection to
+ * backend */
+ close(conn->sock);
+ conn->sock = -1;
+
+ return -1;
}
/* --------------------------------------------------------------------- */
-void
-pqFlush(FILE *f, FILE *debug)
+/* pqFlush: send any data waiting in the output buffer
+ */
+int
+pqFlush(PGconn *conn)
{
- if (f)
- fflush(f);
+ char * ptr = conn->outBuffer;
+ int len = conn->outCount;
+
+ if (conn->sock < 0)
+ {
+ strcpy(conn->errorMessage, "pqFlush() -- connection not open\n");
+ return EOF;
+ }
- if (debug)
- fflush(debug);
+ while (len > 0)
+ {
+ int sent = send(conn->sock, ptr, len, 0);
+ if (sent < 0)
+ {
+ /* Anything except EAGAIN or EWOULDBLOCK is trouble */
+ switch (errno)
+ {
+#ifdef EAGAIN
+ case EAGAIN:
+ break;
+#endif
+#if defined(EWOULDBLOCK) && (!defined(EAGAIN) || (EWOULDBLOCK != EAGAIN))
+ case EWOULDBLOCK:
+ break;
+#endif
+ default:
+ sprintf(conn->errorMessage,
+ "pqFlush() -- couldn't send data: errno=%d\n%s\n",
+ errno, strerror(errno));
+ return EOF;
+ }
+ }
+ else
+ {
+ ptr += sent;
+ len -= sent;
+ }
+ if (len > 0)
+ {
+ /* We didn't send it all, wait till we can send more */
+ if (pqWait(FALSE, TRUE, conn))
+ return EOF;
+ }
+ }
+
+ conn->outCount = 0;
+
+ if (conn->Pfdebug)
+ fflush(conn->Pfdebug);
+
+ return 0;
}
/* --------------------------------------------------------------------- */
+/* pqWait: wait until we can read or write the connection socket
+ */
+int
+pqWait(int forRead, int forWrite, PGconn *conn)
+{
+ fd_set input_mask;
+ fd_set output_mask;
+
+ if (conn->sock < 0)
+ {
+ strcpy(conn->errorMessage, "pqWait() -- connection not open\n");
+ return EOF;
+ }
+
+ /* loop in case select returns EINTR */
+ for (;;) {
+ FD_ZERO(&input_mask);
+ FD_ZERO(&output_mask);
+ if (forRead)
+ FD_SET(conn->sock, &input_mask);
+ if (forWrite)
+ FD_SET(conn->sock, &output_mask);
+ if (select(conn->sock+1, &input_mask, &output_mask, (fd_set *) NULL,
+ (struct timeval *) NULL) < 0)
+ {
+ if (errno == EINTR)
+ continue;
+ sprintf(conn->errorMessage,
+ "pqWait() -- select() failed: errno=%d\n%s\n",
+ errno, strerror(errno));
+ return EOF;
+ }
+ /* On nonerror return, assume we're done */
+ break;
+ }
+
+ return 0;
+}