have length counts, and COPY IN data is packetized into messages.
<!--
-$Header: /cvsroot/pgsql/doc/src/sgml/libpq.sgml,v 1.118 2003/04/17 22:26:00 tgl Exp $
+$Header: /cvsroot/pgsql/doc/src/sgml/libpq.sgml,v 1.119 2003/04/19 00:02:29 tgl Exp $
-->
<chapter id="libpq">
<term><function>PQflush</function></term>
<listitem>
<para>
-Attempts to flush any data queued to the server,
-returns 0 if successful (or if the send queue is empty) or <symbol>EOF</symbol> if it failed for
-some reason.
+Attempts to flush any data queued to the server.
+Returns 0 if successful (or if the send queue is empty), -1 if it failed for
+some reason, or 1 if it was unable to send all the data in the send queue yet
+(this case can only occur if the connection is nonblocking).
<synopsis>
int PQflush(PGconn *conn);
</synopsis>
-<!-- $Header: /cvsroot/pgsql/doc/src/sgml/protocol.sgml,v 1.27 2003/04/16 20:53:38 tgl Exp $ -->
+<!-- $Header: /cvsroot/pgsql/doc/src/sgml/protocol.sgml,v 1.28 2003/04/19 00:02:29 tgl Exp $ -->
<chapter id="protocol">
<title>Frontend/Backend Protocol</title>
Copy-in mode (data transfer to the server) is initiated when the
backend executes a <command>COPY FROM STDIN</> SQL statement. The backend
sends a CopyInResponse message to the frontend. The frontend should
- then send zero or more CopyDataRow messages, one per row to be loaded.
- (For <command>COPY BINARY</>, send CopyBinaryRow messages instead.)
+ then send zero or more CopyData messages, forming a stream of input
+ data. (The message boundaries are not required to have anything to do
+ with row boundaries, although that is often a reasonable choice.)
The frontend can terminate the copy-in mode by sending either a CopyDone
message (allowing successful termination) or a CopyFail message (which
will cause the <command>COPY</> SQL statement to fail with an
<para>
In the event of a backend-detected error during copy-in mode (including
receipt of a CopyFail message, or indeed any frontend message other than
- CopyDataRow, CopyBinaryRow, or CopyDone), the backend will issue an
- ErrorResponse
+ CopyData or CopyDone), the backend will issue an ErrorResponse
message. If the <command>COPY</> command was issued via an extended-query
message, the backend will now discard frontend messages until a Sync
message is received, then it will issue ReadyForQuery and return to normal
processing. If the <command>COPY</> command was issued in a simple
Query message, the rest of that message is discarded and ReadyForQuery
- is issued. In either case, any subsequent CopyDataRow, CopyBinaryRow,
- CopyDone, or CopyFail messages issued by the frontend will simply be
- dropped.
+ is issued. In either case, any subsequent CopyData, CopyDone, or CopyFail
+ messages issued by the frontend will simply be dropped.
</para>
<para>
Copy-out mode (data transfer from the server) is initiated when the
backend executes a <command>COPY TO STDOUT</> SQL statement. The backend
sends a CopyOutResponse message to the frontend, followed by
- zero or more CopyDataRow messages, one per row, followed by CopyDone.
- (For <command>COPY BINARY</>, CopyBinaryRow messages are sent instead.)
+ zero or more CopyData messages (always one per row), followed by CopyDone.
The backend then reverts to the command-processing mode it was
in before the <command>COPY</> started, and sends CommandComplete.
- The frontend cannot abort
- the transfer (short of closing the connection), but it can discard
- unwanted CopyDataRow, CopyBinaryRow, and CopyDone messages.
+ The frontend cannot abort the transfer (short of closing the connection),
+ but it can discard unwanted CopyData and CopyDone messages.
</para>
<para>
In the event of a backend-detected error during copy-out mode,
the backend will issue an ErrorResponse message and revert to normal
processing. The frontend should treat receipt of ErrorResponse (or
- indeed any message type other than CopyDataRow, CopyBinaryRow, or
- CopyDone) as terminating the copy-out mode.
+ indeed any message type other than CopyData or CopyDone) as terminating
+ the copy-out mode.
</para>
</sect2>
(F & B).
Notice that although each message includes a byte count at the beginning,
the message format is defined so that the message end can be found without
-reference to the byte count. This aids validity checking.
+reference to the byte count. This aids validity checking. (The CopyData
+message is an exception, because it forms part of a data stream; the contents
+may not be interpretable on their own.)
</para>
<VariableList>
<VarListEntry>
<Term>
-CopyBinaryRow (F & B)
-</Term>
-<ListItem>
-<Para>
-<VariableList>
-<VarListEntry>
-<Term>
- Byte1('b')
-</Term>
-<ListItem>
-<Para>
- Identifies the message as binary COPY data.
- Note that the message body format is identical to the
- <command>COPY BINARY</> file-format representation for
- a single row of data.
-</Para>
-</ListItem>
-</VarListEntry>
-<VarListEntry>
-<Term>
- Int32
-</Term>
-<ListItem>
-<Para>
- Length of message contents in bytes, including self.
-</Para>
-</ListItem>
-</VarListEntry>
-<VarListEntry>
-<Term>
- Int16
-</Term>
-<ListItem>
-<Para>
- Specifies the number of fields in the row (can be zero).
-</Para>
-</ListItem>
-</VarListEntry>
-</VariableList>
- Then, for each field, there is the following:
-<VariableList>
-<VarListEntry>
-<Term>
- Int16
-</Term>
-<ListItem>
-<Para>
- Zero if the field is null, otherwise the <varname>typlen</>
- for the field datatype.
-</Para>
-</ListItem>
-</VarListEntry>
-<VarListEntry>
-<Term>
- Byte<Replaceable>n</Replaceable>
-</Term>
-<ListItem>
-<Para>
- The value of the field itself in binary format.
- Omitted if the field is null.
- <Replaceable>n</Replaceable> is the <varname>typlen</>
- value if <varname>typlen</> is positive. If
- <varname>typlen</> is -1 then the field value begins with
- its own length as an Int32 (the length includes itself).
-</Para>
-</ListItem>
-</VarListEntry>
-</VariableList>
-
-</Para>
-</ListItem>
-</VarListEntry>
-
-
-<VarListEntry>
-<Term>
-CopyDataRow (F & B)
+CopyData (F & B)
</Term>
<ListItem>
<Para>
</Term>
<ListItem>
<Para>
- Identifies the message as textual COPY data.
+ Identifies the message as COPY data.
</Para>
</ListItem>
</VarListEntry>
</VarListEntry>
<VarListEntry>
<Term>
- String
+ Byte<Replaceable>n</Replaceable>
</Term>
<ListItem>
<Para>
- The textual representation of a single row of table data.
- It should end with a newline.
+ Data that forms part of a COPY datastream. Messages sent
+ from the backend will always correspond to single data rows,
+ but messages sent by frontends may divide the datastream
+ arbitrarily.
</Para>
</ListItem>
</VarListEntry>
</Term>
<ListItem>
<Para>
- 0 for textual copy (CopyDataRow is expected), 1 for
- binary copy (CopyBinaryRow is expected).
+ 0 for textual copy, 1 for binary copy.
</Para>
</ListItem>
</VarListEntry>
</Term>
<ListItem>
<Para>
- 0 for textual copy (CopyDataRow will follow), 1 for
- binary copy (CopyBinaryRow will follow).
+ 0 for textual copy, 1 for binary copy.
</Para>
</ListItem>
</VarListEntry>
<ListItem>
<Para>
The protocol version number. The most significant 16 bits are
- the major version number (3 for the format described here).
- The least 16 significant bits are the minor version number.
+ the major version number (3 or more for the format described
+ here).
+ The least significant 16 bits are the minor version number.
</Para>
</ListItem>
</VarListEntry>
<ListItem>
<Para>
Command-line arguments for the backend. (This is
- deprecated in favor of setting individual GUC
+ deprecated in favor of setting individual run-time
parameters.)
</Para>
</ListItem>
</VarListEntry>
</VariableList>
- In addition to the above, any GUC parameter that can be
+ In addition to the above, any run-time parameter that can be
set at backend start time may be listed. Such settings
will be applied during backend start (after parsing the
- command-line options if any).
+ command-line options if any). The values will act as
+ session defaults.
</Para>
</ListItem>
</VarListEntry>
</sect1>
+<Sect1 id="protocol-changes">
+<Title>Summary of Changes since Protocol 2.0</Title>
+
+<para>
+This section provides a quick checklist of changes, for the benefit of
+developers trying to update existing client libraries to protocol 3.0.
+</para>
+
+<para>
+The initial startup packet uses a flexible list-of-strings format
+instead of a fixed format. Notice that session default values for run-time
+parameters can now be specified directly in the startup packet. (Actually,
+you could do that before using the <literal>options</> field, but given the
+limited width of <literal>options</> and the lack of any way to quote
+whitespace in the values, it wasn't a very safe technique.)
+</para>
+
+<para>
+All messages now have a length count immediately following the message type
+byte (except for startup packets, which have no type byte). Also note that
+PasswordMessage now has a type byte.
+</para>
+
+<para>
+COPY data is now encapsulated into CopyData and CopyDone messages. There
+is a well-defined way to recover from errors during COPY.
+</para>
+
+<note>
+<para>
+Additional changes will be documented as they are implemented.
+</para>
+</note>
+
+</sect1>
+
+
</Chapter>
*
*
* IDENTIFICATION
- * $Header: /cvsroot/pgsql/src/backend/commands/copy.c,v 1.191 2003/04/04 20:42:11 momjian Exp $
+ * $Header: /cvsroot/pgsql/src/backend/commands/copy.c,v 1.192 2003/04/19 00:02:29 tgl Exp $
*
*-------------------------------------------------------------------------
*/
#include "commands/trigger.h"
#include "executor/executor.h"
#include "libpq/libpq.h"
+#include "libpq/pqformat.h"
#include "mb/pg_wchar.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
#define ISOCTAL(c) (((c) >= '0') && ((c) <= '7'))
#define OCTVALUE(c) ((c) - '0')
+/*
+ * Represents the different source/dest cases we need to worry about at
+ * the bottom level
+ */
+typedef enum CopyDest
+{
+ COPY_FILE, /* to/from file */
+ COPY_OLD_FE, /* to/from frontend (old protocol) */
+ COPY_NEW_FE /* to/from frontend (new protocol) */
+} CopyDest;
+
/*
* Represents the type of data returned by CopyReadAttribute()
*/
/* non-export function prototypes */
static void CopyTo(Relation rel, List *attnumlist, bool binary, bool oids,
- FILE *fp, char *delim, char *null_print);
+ char *delim, char *null_print);
static void CopyFrom(Relation rel, List *attnumlist, bool binary, bool oids,
- FILE *fp, char *delim, char *null_print);
+ char *delim, char *null_print);
static Oid GetInputFunction(Oid type);
static Oid GetTypeElement(Oid type);
-static char *CopyReadAttribute(FILE *fp, const char *delim, CopyReadResult *result);
-static void CopyAttributeOut(FILE *fp, char *string, char *delim);
+static char *CopyReadAttribute(const char *delim, CopyReadResult *result);
+static void CopyAttributeOut(char *string, char *delim);
static List *CopyGetAttnums(Relation rel, List *attnamelist);
static const char BinarySignature[12] = "PGBCOPY\n\377\r\n\0";
* never been reentrant...
*/
int copy_lineno = 0; /* exported for use by elog() -- dz */
-static bool fe_eof;
+
+static CopyDest copy_dest;
+static FILE *copy_file; /* if copy_dest == COPY_FILE */
+static StringInfo copy_msgbuf; /* if copy_dest == COPY_NEW_FE */
+static bool fe_eof; /* true if detected end of copy data */
/*
* These static variables are used to avoid incurring overhead for each
/*
* Internal communications functions
*/
-static void CopySendData(void *databuf, int datasize, FILE *fp);
-static void CopySendString(const char *str, FILE *fp);
-static void CopySendChar(char c, FILE *fp);
-static void CopyGetData(void *databuf, int datasize, FILE *fp);
-static int CopyGetChar(FILE *fp);
-static int CopyGetEof(FILE *fp);
-static int CopyPeekChar(FILE *fp);
-static void CopyDonePeek(FILE *fp, int c, bool pickup);
+static void SendCopyBegin(bool binary);
+static void ReceiveCopyBegin(bool binary);
+static void SendCopyEnd(bool binary);
+static void CopySendData(void *databuf, int datasize);
+static void CopySendString(const char *str);
+static void CopySendChar(char c);
+static void CopyGetData(void *databuf, int datasize);
+static int CopyGetChar(void);
+#define CopyGetEof() (fe_eof)
+static int CopyPeekChar(void);
+static void CopyDonePeek(int c, bool pickup);
/*
- * CopySendData sends output data either to the file
- * specified by fp or, if fp is NULL, using the standard
- * backend->frontend functions
- *
- * CopySendString does the same for null-terminated strings
- * CopySendChar does the same for single characters
- *
- * NB: no data conversion is applied by these functions
+ * Send copy start/stop messages for frontend copies. These have changed
+ * in past protocol redesigns.
*/
static void
-CopySendData(void *databuf, int datasize, FILE *fp)
+SendCopyBegin(bool binary)
{
- if (!fp)
+ if (PG_PROTOCOL_MAJOR(FrontendProtocol) >= 3)
+ {
+ pq_putbytes("H", 1); /* new way */
+ /* XXX grottiness needed for old protocol */
+ pq_startcopyout();
+ copy_dest = COPY_NEW_FE;
+ }
+ else if (PG_PROTOCOL_MAJOR(FrontendProtocol) >= 2)
{
- if (pq_putbytes((char *) databuf, datasize))
- fe_eof = true;
+ pq_putbytes("H", 1); /* old way */
+ /* grottiness needed for old protocol */
+ pq_startcopyout();
+ copy_dest = COPY_OLD_FE;
}
else
{
- fwrite(databuf, datasize, 1, fp);
- if (ferror(fp))
- elog(ERROR, "CopySendData: %m");
+ pq_putbytes("B", 1); /* very old way */
+ /* grottiness needed for old protocol */
+ pq_startcopyout();
+ copy_dest = COPY_OLD_FE;
}
}
static void
-CopySendString(const char *str, FILE *fp)
+ReceiveCopyBegin(bool binary)
{
- CopySendData((void *) str, strlen(str), fp);
+ if (PG_PROTOCOL_MAJOR(FrontendProtocol) >= 3)
+ {
+ pq_putbytes("G", 1); /* new way */
+ copy_dest = COPY_NEW_FE;
+ copy_msgbuf = makeStringInfo();
+ }
+ else if (PG_PROTOCOL_MAJOR(FrontendProtocol) >= 2)
+ {
+ pq_putbytes("G", 1); /* old way */
+ copy_dest = COPY_OLD_FE;
+ }
+ else
+ {
+ pq_putbytes("D", 1); /* very old way */
+ copy_dest = COPY_OLD_FE;
+ }
+ /* We *must* flush here to ensure FE knows it can send. */
+ pq_flush();
}
static void
-CopySendChar(char c, FILE *fp)
+SendCopyEnd(bool binary)
{
- CopySendData(&c, 1, fp);
+ if (!binary)
+ CopySendData("\\.\n", 3);
+ pq_endcopyout(false);
}
/*
- * CopyGetData reads output data either from the file
- * specified by fp or, if fp is NULL, using the standard
- * backend->frontend functions
- *
- * CopyGetChar does the same for single characters
- * CopyGetEof checks if it's EOF on the input (or, check for EOF result
- * from CopyGetChar)
+ * CopySendData sends output data to the destination (file or frontend)
+ * CopySendString does the same for null-terminated strings
+ * CopySendChar does the same for single characters
*
* NB: no data conversion is applied by these functions
*/
static void
-CopyGetData(void *databuf, int datasize, FILE *fp)
+CopySendData(void *databuf, int datasize)
{
- if (!fp)
+ switch (copy_dest)
{
- if (pq_getbytes((char *) databuf, datasize))
- fe_eof = true;
+ case COPY_FILE:
+ fwrite(databuf, datasize, 1, copy_file);
+ if (ferror(copy_file))
+ elog(ERROR, "CopySendData: %m");
+ break;
+ case COPY_OLD_FE:
+ if (pq_putbytes((char *) databuf, datasize))
+ fe_eof = true;
+ break;
+ case COPY_NEW_FE:
+ /* XXX fix later */
+ if (pq_putbytes((char *) databuf, datasize))
+ fe_eof = true;
+ break;
}
- else
- fread(databuf, datasize, 1, fp);
}
-static int
-CopyGetChar(FILE *fp)
+static void
+CopySendString(const char *str)
+{
+ CopySendData((void *) str, strlen(str));
+}
+
+static void
+CopySendChar(char c)
+{
+ CopySendData(&c, 1);
+}
+
+/*
+ * CopyGetData reads data from the source (file or frontend)
+ * CopyGetChar does the same for single characters
+ *
+ * CopyGetEof checks if EOF was detected by previous Get operation.
+ *
+ * Note: when copying from the frontend, we expect a proper EOF mark per
+ * protocol; if the frontend simply drops the connection, we raise error.
+ * It seems unwise to allow the COPY IN to complete normally in that case.
+ *
+ * NB: no data conversion is applied by these functions
+ */
+static void
+CopyGetData(void *databuf, int datasize)
{
- if (!fp)
+ switch (copy_dest)
{
- int ch = pq_getbyte();
+ case COPY_FILE:
+ fread(databuf, datasize, 1, copy_file);
+ if (feof(copy_file))
+ fe_eof = true;
+ break;
+ case COPY_OLD_FE:
+ if (pq_getbytes((char *) databuf, datasize))
+ {
+ /* Only a \. terminator is legal EOF in old protocol */
+ elog(ERROR, "unexpected EOF on client connection");
+ }
+ break;
+ case COPY_NEW_FE:
+ while (datasize > 0 && !fe_eof)
+ {
+ int avail;
- if (ch == EOF)
- fe_eof = true;
- return ch;
+ while (copy_msgbuf->cursor >= copy_msgbuf->len)
+ {
+ /* Try to receive another message */
+ int mtype;
+
+ mtype = pq_getbyte();
+ if (mtype == EOF)
+ elog(ERROR, "unexpected EOF on client connection");
+ if (pq_getmessage(copy_msgbuf, 0))
+ elog(ERROR, "unexpected EOF on client connection");
+ switch (mtype)
+ {
+ case 'd': /* CopyData */
+ break;
+ case 'c': /* CopyDone */
+ /* COPY IN correctly terminated by frontend */
+ fe_eof = true;
+ return;
+ case 'f': /* CopyFail */
+ elog(ERROR, "COPY IN failed: %s",
+ pq_getmsgstring(copy_msgbuf));
+ break;
+ default:
+ elog(ERROR, "unexpected message type %c during COPY IN",
+ mtype);
+ break;
+ }
+ }
+ avail = copy_msgbuf->len - copy_msgbuf->cursor;
+ if (avail > datasize)
+ avail = datasize;
+ pq_copymsgbytes(copy_msgbuf, databuf, avail);
+ databuf = (void *) ((char *) databuf + avail);
+ datasize =- avail;
+ }
+ break;
}
- else
- return getc(fp);
}
static int
-CopyGetEof(FILE *fp)
+CopyGetChar(void)
{
- if (!fp)
- return fe_eof;
- else
- return feof(fp);
+ int ch;
+
+ switch (copy_dest)
+ {
+ case COPY_FILE:
+ ch = getc(copy_file);
+ break;
+ case COPY_OLD_FE:
+ ch = pq_getbyte();
+ if (ch == EOF)
+ {
+ /* Only a \. terminator is legal EOF in old protocol */
+ elog(ERROR, "unexpected EOF on client connection");
+ }
+ break;
+ case COPY_NEW_FE:
+ {
+ unsigned char cc;
+
+ CopyGetData(&cc, 1);
+ if (fe_eof)
+ ch = EOF;
+ else
+ ch = cc;
+ break;
+ }
+ default:
+ ch = EOF;
+ break;
+ }
+ if (ch == EOF)
+ fe_eof = true;
+ return ch;
}
/*
* (if pickup is true) or leave it on the stream (if pickup is false).
*/
static int
-CopyPeekChar(FILE *fp)
+CopyPeekChar(void)
{
- if (!fp)
+ int ch;
+
+ switch (copy_dest)
{
- int ch = pq_peekbyte();
+ case COPY_FILE:
+ ch = getc(copy_file);
+ break;
+ case COPY_OLD_FE:
+ ch = pq_peekbyte();
+ if (ch == EOF)
+ {
+ /* Only a \. terminator is legal EOF in old protocol */
+ elog(ERROR, "unexpected EOF on client connection");
+ }
+ break;
+ case COPY_NEW_FE:
+ {
+ unsigned char cc;
- if (ch == EOF)
- fe_eof = true;
- return ch;
+ CopyGetData(&cc, 1);
+ if (fe_eof)
+ ch = EOF;
+ else
+ ch = cc;
+ break;
+ }
+ default:
+ ch = EOF;
+ break;
}
- else
- return getc(fp);
+ if (ch == EOF)
+ fe_eof = true;
+ return ch;
}
static void
-CopyDonePeek(FILE *fp, int c, bool pickup)
+CopyDonePeek(int c, bool pickup)
{
- if (!fp)
- {
- if (pickup)
- {
- /* We want to pick it up */
- (void) pq_getbyte();
- }
- /* If we didn't want to pick it up, just leave it where it sits */
- }
- else
+ if (fe_eof)
+ return; /* can't unget an EOF */
+ switch (copy_dest)
{
- if (!pickup)
- {
- /* We don't want to pick it up - so put it back in there */
- ungetc(c, fp);
- }
- /* If we wanted to pick it up, it's already done */
+ case COPY_FILE:
+ if (!pickup)
+ {
+ /* We don't want to pick it up - so put it back in there */
+ ungetc(c, copy_file);
+ }
+ /* If we wanted to pick it up, it's already done */
+ break;
+ case COPY_OLD_FE:
+ if (pickup)
+ {
+ /* We want to pick it up */
+ (void) pq_getbyte();
+ }
+ /* If we didn't want to pick it up, just leave it where it sits */
+ break;
+ case COPY_NEW_FE:
+ if (!pickup)
+ {
+ /* We don't want to pick it up - so put it back in there */
+ copy_msgbuf->cursor--;
+ }
+ /* If we wanted to pick it up, it's already done */
+ break;
}
}
bool oids = false;
char *delim = NULL;
char *null_print = NULL;
- FILE *fp;
Relation rel;
AclMode required_access = (is_from ? ACL_INSERT : ACL_SELECT);
AclResult aclresult;
client_encoding = pg_get_client_encoding();
server_encoding = GetDatabaseEncoding();
+ copy_dest = COPY_FILE; /* default */
+ copy_file = NULL;
+ copy_msgbuf = NULL;
+ fe_eof = false;
+
if (is_from)
{ /* copy from file to database */
if (rel->rd_rel->relkind != RELKIND_RELATION)
if (pipe)
{
if (IsUnderPostmaster)
- {
- ReceiveCopyBegin();
- fp = NULL;
- }
+ ReceiveCopyBegin(binary);
else
- fp = stdin;
+ copy_file = stdin;
}
else
{
struct stat st;
- fp = AllocateFile(filename, PG_BINARY_R);
+ copy_file = AllocateFile(filename, PG_BINARY_R);
- if (fp == NULL)
+ if (copy_file == NULL)
elog(ERROR, "COPY command, running in backend with "
"effective uid %d, could not open file '%s' for "
"reading. Errno = %s (%d).",
(int) geteuid(), filename, strerror(errno), errno);
- fstat(fileno(fp), &st);
+ fstat(fileno(copy_file), &st);
if (S_ISDIR(st.st_mode))
{
- FreeFile(fp);
+ FreeFile(copy_file);
elog(ERROR, "COPY: %s is a directory", filename);
}
}
- CopyFrom(rel, attnumlist, binary, oids, fp, delim, null_print);
+ CopyFrom(rel, attnumlist, binary, oids, delim, null_print);
}
else
{ /* copy from database to file */
if (pipe)
{
if (IsUnderPostmaster)
- {
- SendCopyBegin();
- pq_startcopyout();
- fp = NULL;
- }
+ SendCopyBegin(binary);
else
- fp = stdout;
+ copy_file = stdout;
}
else
{
" COPY command");
oumask = umask((mode_t) 022);
- fp = AllocateFile(filename, PG_BINARY_W);
+ copy_file = AllocateFile(filename, PG_BINARY_W);
umask(oumask);
- if (fp == NULL)
+ if (copy_file == NULL)
elog(ERROR, "COPY command, running in backend with "
"effective uid %d, could not open file '%s' for "
"writing. Errno = %s (%d).",
(int) geteuid(), filename, strerror(errno), errno);
- fstat(fileno(fp), &st);
+ fstat(fileno(copy_file), &st);
if (S_ISDIR(st.st_mode))
{
- FreeFile(fp);
+ FreeFile(copy_file);
elog(ERROR, "COPY: %s is a directory", filename);
}
}
- CopyTo(rel, attnumlist, binary, oids, fp, delim, null_print);
+ CopyTo(rel, attnumlist, binary, oids, delim, null_print);
}
if (!pipe)
- FreeFile(fp);
- else if (!is_from)
- {
- if (!binary)
- CopySendData("\\.\n", 3, fp);
- if (IsUnderPostmaster)
- pq_endcopyout(false);
- }
+ FreeFile(copy_file);
+ else if (IsUnderPostmaster && !is_from)
+ SendCopyEnd(binary);
pfree(attribute_buf.data);
/*
*/
static void
CopyTo(Relation rel, List *attnumlist, bool binary, bool oids,
- FILE *fp, char *delim, char *null_print)
+ char *delim, char *null_print)
{
HeapTuple tuple;
TupleDesc tupDesc;
int32 tmp;
/* Signature */
- CopySendData((char *) BinarySignature, 12, fp);
+ CopySendData((char *) BinarySignature, 12);
/* Integer layout field */
tmp = 0x01020304;
- CopySendData(&tmp, sizeof(int32), fp);
+ CopySendData(&tmp, sizeof(int32));
/* Flags field */
tmp = 0;
if (oids)
tmp |= (1 << 16);
- CopySendData(&tmp, sizeof(int32), fp);
+ CopySendData(&tmp, sizeof(int32));
/* No header extension */
tmp = 0;
- CopySendData(&tmp, sizeof(int32), fp);
+ CopySendData(&tmp, sizeof(int32));
}
mySnapshot = CopyQuerySnapshot();
/* Binary per-tuple header */
int16 fld_count = attr_count;
- CopySendData(&fld_count, sizeof(int16), fp);
+ CopySendData(&fld_count, sizeof(int16));
/* Send OID if wanted --- note fld_count doesn't include it */
if (oids)
{
Oid oid = HeapTupleGetOid(tuple);
fld_size = sizeof(Oid);
- CopySendData(&fld_size, sizeof(int16), fp);
- CopySendData(&oid, sizeof(Oid), fp);
+ CopySendData(&fld_size, sizeof(int16));
+ CopySendData(&oid, sizeof(Oid));
}
}
else
{
string = DatumGetCString(DirectFunctionCall1(oidout,
ObjectIdGetDatum(HeapTupleGetOid(tuple))));
- CopySendString(string, fp);
+ CopySendString(string);
need_delim = true;
}
}
if (!binary)
{
if (need_delim)
- CopySendChar(delim[0], fp);
+ CopySendChar(delim[0]);
need_delim = true;
}
{
if (!binary)
{
- CopySendString(null_print, fp); /* null indicator */
+ CopySendString(null_print); /* null indicator */
}
else
{
fld_size = 0; /* null marker */
- CopySendData(&fld_size, sizeof(int16), fp);
+ CopySendData(&fld_size, sizeof(int16));
}
}
else
value,
ObjectIdGetDatum(elements[attnum - 1]),
Int32GetDatum(attr[attnum - 1]->atttypmod)));
- CopyAttributeOut(fp, string, delim);
+ CopyAttributeOut(string, delim);
}
else
{
fld_size = attr[attnum - 1]->attlen;
- CopySendData(&fld_size, sizeof(int16), fp);
+ CopySendData(&fld_size, sizeof(int16));
if (isvarlena[attnum - 1])
{
/* varlena */
value = PointerGetDatum(PG_DETOAST_DATUM(value));
CopySendData(DatumGetPointer(value),
- VARSIZE(value),
- fp);
+ VARSIZE(value));
}
else if (!attr[attnum - 1]->attbyval)
{
/* fixed-length pass-by-reference */
Assert(fld_size > 0);
CopySendData(DatumGetPointer(value),
- fld_size,
- fp);
+ fld_size);
}
else
{
*/
store_att_byval(&datumBuf, value, fld_size);
CopySendData(&datumBuf,
- fld_size,
- fp);
+ fld_size);
}
}
}
}
if (!binary)
- CopySendChar('\n', fp);
+ CopySendChar('\n');
MemoryContextSwitchTo(oldcontext);
}
/* Generate trailer for a binary copy */
int16 fld_count = -1;
- CopySendData(&fld_count, sizeof(int16), fp);
+ CopySendData(&fld_count, sizeof(int16));
}
MemoryContextDelete(mycontext);
*/
static void
CopyFrom(Relation rel, List *attnumlist, bool binary, bool oids,
- FILE *fp, char *delim, char *null_print)
+ char *delim, char *null_print)
{
HeapTuple tuple;
TupleDesc tupDesc;
int32 tmp;
/* Signature */
- CopyGetData(readSig, 12, fp);
- if (CopyGetEof(fp) || memcmp(readSig, BinarySignature, 12) != 0)
+ CopyGetData(readSig, 12);
+ if (CopyGetEof() || memcmp(readSig, BinarySignature, 12) != 0)
elog(ERROR, "COPY BINARY: file signature not recognized");
/* Integer layout field */
- CopyGetData(&tmp, sizeof(int32), fp);
- if (CopyGetEof(fp) || tmp != 0x01020304)
+ CopyGetData(&tmp, sizeof(int32));
+ if (CopyGetEof() || tmp != 0x01020304)
elog(ERROR, "COPY BINARY: incompatible integer layout");
/* Flags field */
- CopyGetData(&tmp, sizeof(int32), fp);
- if (CopyGetEof(fp))
+ CopyGetData(&tmp, sizeof(int32));
+ if (CopyGetEof())
elog(ERROR, "COPY BINARY: bogus file header (missing flags)");
file_has_oids = (tmp & (1 << 16)) != 0;
tmp &= ~(1 << 16);
if ((tmp >> 16) != 0)
elog(ERROR, "COPY BINARY: unrecognized critical flags in header");
/* Header extension length */
- CopyGetData(&tmp, sizeof(int32), fp);
- if (CopyGetEof(fp) || tmp < 0)
+ CopyGetData(&tmp, sizeof(int32));
+ if (CopyGetEof() || tmp < 0)
elog(ERROR, "COPY BINARY: bogus file header (missing length)");
/* Skip extension header, if present */
while (tmp-- > 0)
{
- CopyGetData(readSig, 1, fp);
- if (CopyGetEof(fp))
+ CopyGetData(readSig, 1);
+ if (CopyGetEof())
elog(ERROR, "COPY BINARY: bogus file header (wrong length)");
}
}
values = (Datum *) palloc(num_phys_attrs * sizeof(Datum));
nulls = (char *) palloc(num_phys_attrs * sizeof(char));
+ /* Initialize static variables */
copy_lineno = 0;
fe_eof = false;
if (file_has_oids)
{
- string = CopyReadAttribute(fp, delim, &result);
+ string = CopyReadAttribute(delim, &result);
if (result == END_OF_FILE && *string == '\0')
{
elog(ERROR, "Missing data for column \"%s\"",
NameStr(attr[m]->attname));
- string = CopyReadAttribute(fp, delim, &result);
+ string = CopyReadAttribute(delim, &result);
if (result == END_OF_FILE && *string == '\0' &&
cur == attnumlist && !file_has_oids)
int16 fld_count,
fld_size;
- CopyGetData(&fld_count, sizeof(int16), fp);
- if (CopyGetEof(fp) || fld_count == -1)
+ CopyGetData(&fld_count, sizeof(int16));
+ if (CopyGetEof() || fld_count == -1)
{
done = true;
break;
if (file_has_oids)
{
- CopyGetData(&fld_size, sizeof(int16), fp);
- if (CopyGetEof(fp))
+ CopyGetData(&fld_size, sizeof(int16));
+ if (CopyGetEof())
elog(ERROR, "COPY BINARY: unexpected EOF");
if (fld_size != (int16) sizeof(Oid))
elog(ERROR, "COPY BINARY: sizeof(Oid) is %d, expected %d",
(int) fld_size, (int) sizeof(Oid));
- CopyGetData(&loaded_oid, sizeof(Oid), fp);
- if (CopyGetEof(fp))
+ CopyGetData(&loaded_oid, sizeof(Oid));
+ if (CopyGetEof())
elog(ERROR, "COPY BINARY: unexpected EOF");
if (loaded_oid == InvalidOid)
elog(ERROR, "COPY BINARY: Invalid Oid");
i++;
- CopyGetData(&fld_size, sizeof(int16), fp);
- if (CopyGetEof(fp))
+ CopyGetData(&fld_size, sizeof(int16));
+ if (CopyGetEof())
elog(ERROR, "COPY BINARY: unexpected EOF");
if (fld_size == 0)
continue; /* it's NULL; nulls[attnum-1] already set */
int32 varlena_size;
Pointer varlena_ptr;
- CopyGetData(&varlena_size, sizeof(int32), fp);
- if (CopyGetEof(fp))
+ CopyGetData(&varlena_size, sizeof(int32));
+ if (CopyGetEof())
elog(ERROR, "COPY BINARY: unexpected EOF");
if (varlena_size < (int32) sizeof(int32))
elog(ERROR, "COPY BINARY: bogus varlena length");
varlena_ptr = (Pointer) palloc(varlena_size);
VARATT_SIZEP(varlena_ptr) = varlena_size;
CopyGetData(VARDATA(varlena_ptr),
- varlena_size - sizeof(int32),
- fp);
- if (CopyGetEof(fp))
+ varlena_size - sizeof(int32));
+ if (CopyGetEof())
elog(ERROR, "COPY BINARY: unexpected EOF");
values[m] = PointerGetDatum(varlena_ptr);
}
Assert(fld_size > 0);
refval_ptr = (Pointer) palloc(fld_size);
- CopyGetData(refval_ptr, fld_size, fp);
- if (CopyGetEof(fp))
+ CopyGetData(refval_ptr, fld_size);
+ if (CopyGetEof())
elog(ERROR, "COPY BINARY: unexpected EOF");
values[m] = PointerGetDatum(refval_ptr);
}
* how shorter data values are aligned within a Datum.
*/
Assert(fld_size > 0 && fld_size <= sizeof(Datum));
- CopyGetData(&datumBuf, fld_size, fp);
- if (CopyGetEof(fp))
+ CopyGetData(&datumBuf, fld_size);
+ if (CopyGetEof())
elog(ERROR, "COPY BINARY: unexpected EOF");
values[m] = fetch_att(&datumBuf, true, fld_size);
}
*/
static char *
-CopyReadAttribute(FILE *fp, const char *delim, CopyReadResult *result)
+CopyReadAttribute(const char *delim, CopyReadResult *result)
{
int c;
int delimc = (unsigned char) delim[0];
for (;;)
{
- c = CopyGetChar(fp);
+ c = CopyGetChar();
if (c == EOF)
{
*result = END_OF_FILE;
break;
if (c == '\\')
{
- c = CopyGetChar(fp);
+ c = CopyGetChar();
if (c == EOF)
{
*result = END_OF_FILE;
int val;
val = OCTVALUE(c);
- c = CopyPeekChar(fp);
+ c = CopyPeekChar();
if (ISOCTAL(c))
{
val = (val << 3) + OCTVALUE(c);
- CopyDonePeek(fp, c, true /* pick up */ );
- c = CopyPeekChar(fp);
+ CopyDonePeek(c, true /* pick up */ );
+ c = CopyPeekChar();
if (ISOCTAL(c))
{
val = (val << 3) + OCTVALUE(c);
- CopyDonePeek(fp, c, true /* pick up */ );
+ CopyDonePeek(c, true /* pick up */ );
}
else
{
*result = END_OF_FILE;
goto copy_eof;
}
- CopyDonePeek(fp, c, false /* put back */ );
+ CopyDonePeek(c, false /* put back */ );
}
}
else
*result = END_OF_FILE;
goto copy_eof;
}
- CopyDonePeek(fp, c, false /* put back */ );
+ CopyDonePeek(c, false /* put back */ );
}
c = val & 0377;
}
c = '\v';
break;
case '.':
- c = CopyGetChar(fp);
+ c = CopyGetChar();
if (c != '\n')
elog(ERROR, "CopyReadAttribute: end of record marker corrupted");
+ /*
+ * In protocol version 3, we should ignore anything after
+ * \. up to the protocol end of copy data. (XXX maybe
+ * better not to treat \. as special?)
+ */
+ if (copy_dest == COPY_NEW_FE)
+ {
+ while (c != EOF)
+ {
+ c = CopyGetChar();
+ }
+ }
*result = END_OF_FILE;
goto copy_eof;
}
mblen = pg_encoding_mblen(client_encoding, s);
for (j = 1; j < mblen; j++)
{
- c = CopyGetChar(fp);
+ c = CopyGetChar();
if (c == EOF)
{
*result = END_OF_FILE;
}
static void
-CopyAttributeOut(FILE *fp, char *server_string, char *delim)
+CopyAttributeOut(char *server_string, char *delim)
{
char *string;
char c;
switch (c)
{
case '\b':
- CopySendString("\\b", fp);
+ CopySendString("\\b");
break;
case '\f':
- CopySendString("\\f", fp);
+ CopySendString("\\f");
break;
case '\n':
- CopySendString("\\n", fp);
+ CopySendString("\\n");
break;
case '\r':
- CopySendString("\\r", fp);
+ CopySendString("\\r");
break;
case '\t':
- CopySendString("\\t", fp);
+ CopySendString("\\t");
break;
case '\v':
- CopySendString("\\v", fp);
+ CopySendString("\\v");
break;
case '\\':
- CopySendString("\\\\", fp);
+ CopySendString("\\\\");
break;
default:
if (c == delimc)
- CopySendChar('\\', fp);
- CopySendChar(c, fp);
+ CopySendChar('\\');
+ CopySendChar(c);
/*
* We can skip pg_encoding_mblen() overhead when encoding
/* send additional bytes of the char, if any */
mblen = pg_encoding_mblen(client_encoding, string);
for (i = 1; i < mblen; i++)
- CopySendChar(string[i], fp);
+ CopySendChar(string[i]);
}
break;
}
* Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
- * $Id: stringinfo.c,v 1.32 2002/09/04 20:31:18 momjian Exp $
+ * $Id: stringinfo.c,v 1.33 2003/04/19 00:02:29 tgl Exp $
*
*-------------------------------------------------------------------------
*/
-
-
#include "postgres.h"
+
#include "lib/stringinfo.h"
+
/*
* makeStringInfo
*
str->maxlen = size;
str->len = 0;
str->data[0] = '\0';
-}
-
-/*
- * enlargeStringInfo
- *
- * Internal routine: make sure there is enough space for 'needed' more bytes
- * ('needed' does not include the terminating null).
- *
- * NB: because we use repalloc() to enlarge the buffer, the string buffer
- * will remain allocated in the same memory context that was current when
- * initStringInfo was called, even if another context is now current.
- * This is the desired and indeed critical behavior!
- */
-static void
-enlargeStringInfo(StringInfo str, int needed)
-{
- int newlen;
-
- needed += str->len + 1; /* total space required now */
- if (needed <= str->maxlen)
- return; /* got enough space already */
-
- /*
- * We don't want to allocate just a little more space with each
- * append; for efficiency, double the buffer size each time it
- * overflows. Actually, we might need to more than double it if
- * 'needed' is big...
- */
- newlen = 2 * str->maxlen;
- while (needed > newlen)
- newlen = 2 * newlen;
-
- str->data = (char *) repalloc(str->data, newlen);
-
- str->maxlen = newlen;
+ str->cursor = 0;
}
/*
}
}
-/*------------------------
+/*
* appendStringInfoChar
+ *
* Append a single byte to str.
* Like appendStringInfo(str, "%c", ch) but much faster.
*/
*/
str->data[str->len] = '\0';
}
+
+/*
+ * enlargeStringInfo
+ *
+ * Make sure there is enough space for 'needed' more bytes
+ * ('needed' does not include the terminating null).
+ *
+ * External callers need not concern themselves with this, since all
+ * stringinfo.c routines do it automatically. However, if a caller
+ * knows that a StringInfo will eventually become X bytes large, it
+ * can save some palloc overhead by enlarging the buffer before starting
+ * to store data in it.
+ *
+ * NB: because we use repalloc() to enlarge the buffer, the string buffer
+ * will remain allocated in the same memory context that was current when
+ * initStringInfo was called, even if another context is now current.
+ * This is the desired and indeed critical behavior!
+ */
+void
+enlargeStringInfo(StringInfo str, int needed)
+{
+ int newlen;
+
+ needed += str->len + 1; /* total space required now */
+ if (needed <= str->maxlen)
+ return; /* got enough space already */
+
+ /*
+ * We don't want to allocate just a little more space with each
+ * append; for efficiency, double the buffer size each time it
+ * overflows. Actually, we might need to more than double it if
+ * 'needed' is big...
+ */
+ newlen = 2 * str->maxlen;
+ while (needed > newlen)
+ newlen = 2 * newlen;
+
+ str->data = (char *) repalloc(str->data, newlen);
+
+ str->maxlen = newlen;
+}
*
*
* IDENTIFICATION
- * $Header: /cvsroot/pgsql/src/backend/libpq/auth.c,v 1.98 2003/04/17 22:26:01 tgl Exp $
+ * $Header: /cvsroot/pgsql/src/backend/libpq/auth.c,v 1.99 2003/04/19 00:02:29 tgl Exp $
*
*-------------------------------------------------------------------------
*/
static void sendAuthRequest(Port *port, AuthRequest areq);
static void auth_failed(Port *port, int status);
+static char *recv_password_packet(Port *port);
static int recv_and_check_password_packet(Port *port);
char *pg_krb_server_keyfile;
*/
static int
-pam_passwd_conv_proc(int num_msg, const struct pam_message ** msg, struct pam_response ** resp, void *appdata_ptr)
+pam_passwd_conv_proc(int num_msg, const struct pam_message ** msg,
+ struct pam_response ** resp, void *appdata_ptr)
{
- StringInfoData buf;
- int32 len;
-
if (num_msg != 1 || msg[0]->msg_style != PAM_PROMPT_ECHO_OFF)
{
switch (msg[0]->msg_style)
*/
if (strlen(appdata_ptr) == 0)
{
- sendAuthRequest(pam_port_cludge, AUTH_REQ_PASSWORD);
- if (pq_eof() == EOF || pq_getint(&len, 4) == EOF)
- return PAM_CONV_ERR; /* client didn't want to send password */
+ char *passwd;
- initStringInfo(&buf);
- if (pq_getstr_bounded(&buf, 1000) == EOF)
- return PAM_CONV_ERR; /* EOF while reading password */
+ sendAuthRequest(pam_port_cludge, AUTH_REQ_PASSWORD);
+ passwd = recv_password_packet(pam_port_cludge);
- /* Do not echo failed password to logs, for security. */
- elog(DEBUG5, "received PAM packet");
+ if (passwd == NULL)
+ return PAM_CONV_ERR; /* client didn't want to send password */
- if (strlen(buf.data) == 0)
+ if (strlen(passwd) == 0)
{
elog(LOG, "pam_passwd_conv_proc: no password");
return PAM_CONV_ERR;
}
- appdata_ptr = buf.data;
+ appdata_ptr = passwd;
}
/*
if (!*resp)
{
elog(LOG, "pam_passwd_conv_proc: Out of memory!");
- if (buf.data)
- pfree(buf.data);
return PAM_CONV_ERR;
}
/*
- * Called when we have received the password packet.
+ * Collect password response packet from frontend.
+ *
+ * Returns NULL if couldn't get password, else palloc'd string.
*/
-static int
-recv_and_check_password_packet(Port *port)
+static char *
+recv_password_packet(Port *port)
{
StringInfoData buf;
- int32 len;
- int result;
- if (pq_eof() == EOF || pq_getint(&len, 4) == EOF)
- return STATUS_EOF; /* client didn't want to send password */
+ if (PG_PROTOCOL_MAJOR(port->proto) >= 3)
+ {
+ /* Expect 'p' message type */
+ int mtype;
+
+ mtype = pq_getbyte();
+ if (mtype != 'p')
+ {
+ /*
+ * If the client just disconnects without offering a password,
+ * don't make a log entry. This is legal per protocol spec and
+ * in fact commonly done by psql, so complaining just clutters
+ * the log.
+ */
+ if (mtype != EOF)
+ elog(COMMERROR, "Expected password response, got %c", mtype);
+ return NULL; /* EOF or bad message type */
+ }
+ }
+ else
+ {
+ /* For pre-3.0 clients, avoid log entry if they just disconnect */
+ if (pq_peekbyte() == EOF)
+ return NULL; /* EOF */
+ }
initStringInfo(&buf);
- if (pq_getstr_bounded(&buf, 1000) == EOF) /* receive password */
+ if (pq_getmessage(&buf, 1000)) /* receive password */
{
+ /* EOF - pq_getmessage already logged a suitable message */
pfree(buf.data);
- return STATUS_EOF;
+ return NULL;
}
/*
- * We don't actually use the password packet length the frontend sent
- * us; however, it's a reasonable sanity check to ensure that we
- * actually read as much data as we expected to.
- *
- * The password packet size is the length of the buffer, plus the size
- * field itself (4 bytes), plus a 1-byte terminator.
+ * Apply sanity check: password packet length should agree with length
+ * of contained string. Note it is safe to use strlen here because
+ * StringInfo is guaranteed to have an appended '\0'.
*/
- if (len != (buf.len + 4 + 1))
- elog(LOG, "unexpected password packet size: read %d, expected %d",
- buf.len + 4 + 1, len);
+ if (strlen(buf.data) + 1 != buf.len)
+ elog(COMMERROR, "bogus password packet size");
/* Do not echo password to logs, for security. */
elog(DEBUG5, "received password packet");
- result = md5_crypt_verify(port, port->user_name, buf.data);
+ /*
+ * Return the received string. Note we do not attempt to do any
+ * character-set conversion on it; since we don't yet know the
+ * client's encoding, there wouldn't be much point.
+ */
+ return buf.data;
+}
+
+
+/*
+ * Called when we have sent an authorization request for a password.
+ * Get the response and check it.
+ */
+static int
+recv_and_check_password_packet(Port *port)
+{
+ char *passwd;
+ int result;
+
+ passwd = recv_password_packet(port);
+
+ if (passwd == NULL)
+ return STATUS_EOF; /* client wouldn't send password */
+
+ result = md5_crypt_verify(port, port->user_name, passwd);
+
+ pfree(passwd);
- pfree(buf.data);
return result;
}
*
*
* IDENTIFICATION
- * $Header: /cvsroot/pgsql/src/backend/libpq/be-secure.c,v 1.29 2003/04/10 23:03:08 tgl Exp $
+ * $Header: /cvsroot/pgsql/src/backend/libpq/be-secure.c,v 1.30 2003/04/19 00:02:29 tgl Exp $
*
* Since the server static private key ($DataDir/server.key)
* will normally be stored unencrypted so that the database
extern void ExitPostmaster(int);
extern void postmaster_error(const char *fmt,...);
-int secure_initialize(void);
-void secure_destroy(void);
-int secure_open_server(Port *);
-void secure_close(Port *);
-ssize_t secure_read(Port *, void *ptr, size_t len);
-ssize_t secure_write(Port *, void *ptr, size_t len);
-
#ifdef USE_SSL
static DH *load_dh_file(int keylength);
static DH *load_dh_buffer(const char *, size_t);
* These routines handle the low-level details of communication between
* frontend and backend. They just shove data across the communication
* channel, and are ignorant of the semantics of the data --- or would be,
- * except for major brain damage in the design of the COPY OUT protocol.
- * Unfortunately, COPY OUT is designed to commandeer the communication
+ * except for major brain damage in the design of the old COPY OUT protocol.
+ * Unfortunately, COPY OUT was designed to commandeer the communication
* channel (it just transfers data without wrapping it into messages).
* No other messages can be sent while COPY OUT is in progress; and if the
* copy is aborted by an elog(ERROR), we need to close out the copy so that
* Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
- * $Id: pqcomm.c,v 1.149 2003/04/02 00:49:28 tgl Exp $
+ * $Header: /cvsroot/pgsql/src/backend/libpq/pqcomm.c,v 1.150 2003/04/19 00:02:29 tgl Exp $
*
*-------------------------------------------------------------------------
*/
* low-level I/O:
* pq_getbytes - get a known number of bytes from connection
* pq_getstring - get a null terminated string from connection
+ * pq_getmessage - get a message with length word from connection
* pq_getbyte - get next byte from connection
* pq_peekbyte - peek at next byte from connection
* pq_putbytes - send bytes to connection (not flushed until pq_flush)
* pq_flush - flush pending output
*
- * message-level I/O (and COPY OUT cruft):
+ * message-level I/O (and old-style-COPY-OUT cruft):
* pq_putmessage - send a normal message (suppressed in COPY OUT mode)
* pq_startcopyout - inform libpq that a COPY OUT transfer is beginning
* pq_endcopyout - end a COPY OUT transfer
#include "miscadmin.h"
#include "storage/ipc.h"
-extern void secure_close(Port *);
-extern ssize_t secure_read(Port *, void *, size_t);
-extern ssize_t secure_write(Port *, const void *, size_t);
static void pq_close(void);
}
if (r == 0)
{
- /* as above, only write to postmaster log */
- elog(COMMERROR, "pq_recvbuf: unexpected EOF on client connection");
+ /*
+ * EOF detected. We used to write a log message here, but it's
+ * better to expect the ultimate caller to do that.
+ */
return EOF;
}
/* r contains number of bytes read, so just incr length */
/* --------------------------------
* pq_getstring - get a null terminated string from connection
*
- * The return value is placed in an expansible StringInfo.
- * Note that space allocation comes from the current memory context!
+ * The return value is placed in an expansible StringInfo, which has
+ * already been initialized by the caller.
*
- * If maxlen is not zero, it is an upper limit on the length of the
- * string we are willing to accept. We abort the connection (by
- * returning EOF) if client tries to send more than that. Note that
- * since we test maxlen in the outer per-bufferload loop, the limit
- * is fuzzy: we might accept up to PQ_BUFFER_SIZE more bytes than
- * specified. This is fine for the intended purpose, which is just
- * to prevent DoS attacks from not-yet-authenticated clients.
- *
- * NOTE: this routine does not do any character set conversion,
- * even though it is presumably useful only for text, because
- * no code in this module should depend on the encoding.
- * See pq_getstr_bounded in pqformat.c for that.
+ * This is used only for dealing with old-protocol clients. The idea
+ * is to produce a StringInfo that looks the same as we would get from
+ * pq_getmessage() with a newer client; we will then process it with
+ * pq_getmsgstring. Therefore, no character set conversion is done here,
+ * even though this is presumably useful only for text.
*
* returns 0 if OK, EOF if trouble
* --------------------------------
*/
int
-pq_getstring(StringInfo s, int maxlen)
+pq_getstring(StringInfo s)
{
int i;
/* Reset string to empty */
s->len = 0;
s->data[0] = '\0';
+ s->cursor = 0;
- /* Read until we get the terminating '\0' or overrun maxlen */
+ /* Read until we get the terminating '\0' */
for (;;)
{
while (PqRecvPointer >= PqRecvLength)
{
if (PqRecvBuffer[i] == '\0')
{
- /* does not copy the \0 */
+ /* include the '\0' in the copy */
appendBinaryStringInfo(s, PqRecvBuffer + PqRecvPointer,
- i - PqRecvPointer);
+ i - PqRecvPointer + 1);
PqRecvPointer = i + 1; /* advance past \0 */
return 0;
}
appendBinaryStringInfo(s, PqRecvBuffer + PqRecvPointer,
PqRecvLength - PqRecvPointer);
PqRecvPointer = PqRecvLength;
+ }
+}
+
+
+/* --------------------------------
+ * pq_getmessage - get a message with length word from connection
+ *
+ * The return value is placed in an expansible StringInfo, which has
+ * already been initialized by the caller.
+ * Only the message body is placed in the StringInfo; the length word
+ * is removed. Also, s->cursor is initialized to zero for convenience
+ * in scanning the message contents.
+ *
+ * If maxlen is not zero, it is an upper limit on the length of the
+ * message we are willing to accept. We abort the connection (by
+ * returning EOF) if client tries to send more than that.
+ *
+ * returns 0 if OK, EOF if trouble
+ * --------------------------------
+ */
+int
+pq_getmessage(StringInfo s, int maxlen)
+{
+ int32 len;
+
+ /* Reset message buffer to empty */
+ s->len = 0;
+ s->data[0] = '\0';
+ s->cursor = 0;
- /* If maxlen is specified, check for overlength input. */
- if (maxlen > 0 && s->len > maxlen)
+ /* Read message length word */
+ if (pq_getbytes((char *) &len, 4) == EOF)
+ {
+ elog(COMMERROR, "unexpected EOF within message length word");
+ return EOF;
+ }
+
+ len = ntohl(len);
+ len -= 4; /* discount length itself */
+
+ if (len < 0 ||
+ (maxlen > 0 && len > maxlen))
+ {
+ elog(COMMERROR, "invalid message length");
+ return EOF;
+ }
+
+ if (len > 0)
+ {
+ /* Allocate space for message */
+ enlargeStringInfo(s, len);
+
+ /* And grab the message */
+ if (pq_getbytes(s->data, len) == EOF)
+ {
+ elog(COMMERROR, "incomplete client message");
return EOF;
+ }
+ s->len = len;
+ /* Place a trailing null per StringInfo convention */
+ s->data[len] = '\0';
}
+
+ return 0;
}
}
-/*
- * Return EOF if the connection has been broken, else 0.
- */
-int
-pq_eof(void)
-{
- char x;
- int res;
-
- res = recv(MyProcPort->sock, &x, 1, MSG_PEEK);
-
- if (res < 0)
- {
- /* can log to postmaster log only */
- elog(COMMERROR, "pq_eof: recv() failed: %m");
- return EOF;
- }
- if (res == 0)
- return EOF;
- else
- return 0;
-}
-
-
/* --------------------------------
* Message-level I/O routines begin here.
*
- * These routines understand about COPY OUT protocol.
+ * These routines understand about the old-style COPY OUT protocol.
* --------------------------------
*/
}
/* --------------------------------
- * pq_startcopyout - inform libpq that a COPY OUT transfer is beginning
+ * pq_startcopyout - inform libpq that an old-style COPY OUT transfer
+ * is beginning
* --------------------------------
*/
void
* formatting/conversion routines that are needed to produce valid messages.
* Note in particular the distinction between "raw data" and "text"; raw data
* is message protocol characters and binary values that are not subject to
- * character set conversion, while text is converted by character encoding rules.
+ * character set conversion, while text is converted by character encoding
+ * rules.
*
- * Incoming messages are read directly off the wire, as it were, but there
- * are still data-conversion tasks to be performed.
+ * Incoming messages are similarly read into a StringInfo buffer, via
+ * pq_getmessage, and then parsed and converted from that using the routines
+ * in this module.
*
* Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
- * $Id: pqformat.c,v 1.26 2003/04/02 00:49:28 tgl Exp $
+ * $Header: /cvsroot/pgsql/src/backend/libpq/pqformat.c,v 1.27 2003/04/19 00:02:29 tgl Exp $
*
*-------------------------------------------------------------------------
*/
* Special-case message output:
* pq_puttextmessage - generate a character set-converted message in one step
*
- * Message input:
- * pq_getint - get an integer from connection
- * pq_getstr_bounded - get a null terminated string from connection
- * pq_getstr_bounded performs character set conversion on the collected
- * string. Use the raw pqcomm.c routines pq_getstring or pq_getbytes
- * to fetch data without conversion.
+ * Message parsing after input:
+ * pq_getmsgbyte - get a raw byte from a message buffer
+ * pq_getmsgint - get a binary integer from a message buffer
+ * pq_getmsgbytes - get raw data from a message buffer
+ * pq_copymsgbytes - copy raw data from a message buffer
+ * pq_getmsgstring - get a null-terminated text string (with conversion)
+ * pq_getmsgend - verify message fully consumed
*/
#include "postgres.h"
return pq_putmessage(msgtype, str, slen + 1);
}
+
/* --------------------------------
- * pq_getint - get an integer from connection
- *
- * returns 0 if OK, EOF if trouble
+ * pq_getmsgbyte - get a raw byte from a message buffer
* --------------------------------
*/
int
-pq_getint(int *result, int b)
+pq_getmsgbyte(StringInfo msg)
+{
+ if (msg->cursor >= msg->len)
+ elog(ERROR, "pq_getmsgbyte: no data left in message");
+ return (unsigned char) msg->data[msg->cursor++];
+}
+
+/* --------------------------------
+ * pq_getmsgint - get a binary integer from a message buffer
+ *
+ * Values are treated as unsigned.
+ * --------------------------------
+ */
+unsigned int
+pq_getmsgint(StringInfo msg, int b)
{
- int status;
+ unsigned int result;
unsigned char n8;
uint16 n16;
uint32 n32;
switch (b)
{
case 1:
- status = pq_getbytes((char *) &n8, 1);
- *result = (int) n8;
+ pq_copymsgbytes(msg, (char *) &n8, 1);
+ result = n8;
break;
case 2:
- status = pq_getbytes((char *) &n16, 2);
- *result = (int) (ntohs(n16));
+ pq_copymsgbytes(msg, (char *) &n16, 2);
+ result = ntohs(n16);
break;
case 4:
- status = pq_getbytes((char *) &n32, 4);
- *result = (int) (ntohl(n32));
+ pq_copymsgbytes(msg, (char *) &n32, 4);
+ result = ntohl(n32);
break;
default:
-
- /*
- * if we elog(ERROR) here, we will lose sync with the
- * frontend, so just complain to postmaster log instead...
- */
- elog(COMMERROR, "pq_getint: unsupported size %d", b);
- status = EOF;
- *result = 0;
+ elog(ERROR, "pq_getmsgint: unsupported size %d", b);
+ result = 0; /* keep compiler quiet */
break;
}
- return status;
+ return result;
}
/* --------------------------------
- * pq_getstr_bounded - get a null terminated string from connection
+ * pq_getmsgbytes - get raw data from a message buffer
*
- * The return value is placed in an expansible StringInfo.
- * Note that space allocation comes from the current memory context!
+ * Returns a pointer directly into the message buffer; note this
+ * may not have any particular alignment.
+ * --------------------------------
+ */
+const char *
+pq_getmsgbytes(StringInfo msg, int datalen)
+{
+ const char *result;
+
+ if (datalen > (msg->len - msg->cursor))
+ elog(ERROR, "pq_getmsgbytes: insufficient data left in message");
+ result = &msg->data[msg->cursor];
+ msg->cursor += datalen;
+ return result;
+}
+
+/* --------------------------------
+ * pq_copymsgbytes - copy raw data from a message buffer
*
- * The maxlen parameter is interpreted as per pq_getstring.
+ * Same as above, except data is copied to caller's buffer.
+ * --------------------------------
+ */
+void
+pq_copymsgbytes(StringInfo msg, char *buf, int datalen)
+{
+ if (datalen > (msg->len - msg->cursor))
+ elog(ERROR, "pq_copymsgbytes: insufficient data left in message");
+ memcpy(buf, &msg->data[msg->cursor], datalen);
+ msg->cursor += datalen;
+}
+
+/* --------------------------------
+ * pq_getmsgstring - get a null-terminated text string (with conversion)
*
- * returns 0 if OK, EOF if trouble
+ * May return a pointer directly into the message buffer, or a pointer
+ * to a palloc'd conversion result.
* --------------------------------
*/
-int
-pq_getstr_bounded(StringInfo s, int maxlen)
+const char *
+pq_getmsgstring(StringInfo msg)
{
- int result;
- char *p;
+ char *str;
+ int slen;
- result = pq_getstring(s, maxlen);
+ str = &msg->data[msg->cursor];
+ /*
+ * It's safe to use strlen() here because a StringInfo is guaranteed
+ * to have a trailing null byte. But check we found a null inside
+ * the message.
+ */
+ slen = strlen(str);
+ if (msg->cursor + slen >= msg->len)
+ elog(ERROR, "pq_getmsgstring: invalid string in message");
+ msg->cursor += slen + 1;
- p = (char *) pg_client_to_server((unsigned char *) s->data, s->len);
- if (p != s->data) /* actual conversion has been done? */
- {
- /* reset s to empty, and append the new string p */
- s->len = 0;
- s->data[0] = '\0';
- appendBinaryStringInfo(s, p, strlen(p));
- pfree(p);
- }
+ return (const char *) pg_client_to_server((unsigned char *) str, slen);
+}
- return result;
+/* --------------------------------
+ * pq_getmsgend - verify message fully consumed
+ * --------------------------------
+ */
+void
+pq_getmsgend(StringInfo msg)
+{
+ if (msg->cursor != msg->len)
+ elog(ERROR, "pq_getmsgend: invalid message format");
}
*
*
* IDENTIFICATION
- * $Header: /cvsroot/pgsql/src/backend/postmaster/postmaster.c,v 1.312 2003/04/18 01:03:42 momjian Exp $
+ * $Header: /cvsroot/pgsql/src/backend/postmaster/postmaster.c,v 1.313 2003/04/19 00:02:29 tgl Exp $
*
* NOTES
*
if (pq_getbytes((char *) &len, 4) == EOF)
{
- elog(LOG, "incomplete startup packet");
+ elog(COMMERROR, "incomplete startup packet");
return STATUS_ERROR;
}
if (pq_getbytes(buf, len) == EOF)
{
- elog(LOG, "incomplete startup packet");
+ elog(COMMERROR, "incomplete startup packet");
return STATUS_ERROR;
}
/* Could add additional special packet types here */
+ /*
+ * XXX temporary for 3.0 protocol development: we are using the minor
+ * number as a test-version number. Insist it match exactly so people
+ * don't get burnt by using yesterday's libpq with today's server.
+ * XXX this must go away before release!!!
+ */
+ if (PG_PROTOCOL_MAJOR(proto) == 3 &&
+ PG_PROTOCOL_MINOR(proto) != PG_PROTOCOL_MINOR(PG_PROTOCOL_LATEST))
+ elog(FATAL, "Your development libpq is out of sync with the server");
+
/* Check we can handle the protocol the frontend is using. */
if (PG_PROTOCOL_MAJOR(proto) < PG_PROTOCOL_MAJOR(PG_PROTOCOL_EARLIEST) ||
PG_PROTOCOL_MAJOR(PG_PROTOCOL_LATEST),
PG_PROTOCOL_MINOR(PG_PROTOCOL_LATEST));
- /*
- * XXX temporary for 3.0 protocol development: we are using the minor
- * number as a test-version number. Insist it match exactly so people
- * don't get burnt by using yesterday's libpq with today's server.
- * XXX this must go away before release!!!
- */
- if (PG_PROTOCOL_MAJOR(proto) == 3 &&
- PG_PROTOCOL_MINOR(proto) != PG_PROTOCOL_MINOR(PG_PROTOCOL_LATEST))
- elog(FATAL, "Your development libpq is out of sync with the server");
-
/*
* Now fetch parameters out of startup packet and save them into the
* Port structure. All data structures attached to the Port struct
* Portions Copyright (c) 1994, Regents of the University of California
*
* IDENTIFICATION
- * $Header: /cvsroot/pgsql/src/backend/tcop/dest.c,v 1.51 2003/03/27 16:51:29 momjian Exp $
+ * $Header: /cvsroot/pgsql/src/backend/tcop/dest.c,v 1.52 2003/04/19 00:02:29 tgl Exp $
*
*-------------------------------------------------------------------------
*/
}
}
-/*
- * These are necessary to sync communications between fe/be processes doing
- * COPY rel TO stdout
- *
- * or
- *
- * COPY rel FROM stdin
- *
- * NOTE: the message code letters are changed at protocol version 2.0
- * to eliminate possible confusion with data tuple messages.
- */
-void
-SendCopyBegin(void)
-{
- if (PG_PROTOCOL_MAJOR(FrontendProtocol) >= 2)
- pq_putbytes("H", 1); /* new way */
- else
- pq_putbytes("B", 1); /* old way */
-}
-
-void
-ReceiveCopyBegin(void)
-{
- if (PG_PROTOCOL_MAJOR(FrontendProtocol) >= 2)
- pq_putbytes("G", 1); /* new way */
- else
- pq_putbytes("D", 1); /* old way */
- /* We *must* flush here to ensure FE knows it can send. */
- pq_flush();
-}
-
/* ----------------
* NullCommand - tell dest that an empty query string was recognized
*
*
*
* IDENTIFICATION
- * $Header: /cvsroot/pgsql/src/backend/tcop/fastpath.c,v 1.57 2003/01/09 18:00:23 tgl Exp $
+ * $Header: /cvsroot/pgsql/src/backend/tcop/fastpath.c,v 1.58 2003/04/19 00:02:29 tgl Exp $
*
* NOTES
* This cruft is the server side of PQfn.
* back to the frontend. If the return returns by reference,
* send down only the data portion and set the return size appropriately.
*
- * OLD COMMENTS FOLLOW
- *
- * The VAR_LENGTH_{ARGS,RESULT} stuff is limited to MAX_STRING_LENGTH
- * (see src/backend/tmp/fastpath.h) for no obvious reason. Since its
- * primary use (for us) is for Inversion path names, it should probably
- * be increased to 256 (MAXPATHLEN for Inversion, hidden in pg_type
- * as well as utils/adt/filename.c).
- *
- * Quoth PMA on 08/15/93:
- *
- * This code has been almost completely rewritten with an eye to
- * keeping it as compatible as possible with the previous (broken)
- * implementation.
- *
- * The previous implementation would assume (1) that any value of
- * length <= 4 bytes was passed-by-value, and that any other value
- * was a struct varlena (by-reference). There was NO way to pass a
- * fixed-length by-reference argument (like name) or a struct
- * varlena of size <= 4 bytes.
- *
- * The new implementation checks the catalogs to determine whether
- * a value is by-value (type "0" is null-delimited character string,
- * as it is for, e.g., the parser). The only other item obtained
- * from the catalogs is whether or not the value should be placed in
- * a struct varlena or not. Otherwise, the size given by the
- * frontend is assumed to be correct (probably a bad decision, but
- * we do strange things in the name of compatibility).
- *
*-------------------------------------------------------------------------
*/
#include "postgres.h"
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
#include "access/xact.h"
#include "catalog/pg_proc.h"
#include "libpq/libpq.h"
#include "utils/tqual.h"
+/* ----------------
+ * GetOldFunctionMessage
+ *
+ * In pre-3.0 protocol, there is no length word on the message, so we have
+ * to have code that understands the message layout to absorb the message
+ * into a buffer. We want to do this before we start execution, so that
+ * we do not lose sync with the frontend if there's an error.
+ *
+ * The caller should already have initialized buf to empty.
+ * ----------------
+ */
+static int
+GetOldFunctionMessage(StringInfo buf)
+{
+ int32 ibuf;
+ int nargs;
+
+ /* Dummy string argument */
+ if (pq_getstring(buf))
+ return EOF;
+ /* Function OID */
+ if (pq_getbytes((char *) &ibuf, 4))
+ return EOF;
+ appendBinaryStringInfo(buf, (char *) &ibuf, 4);
+ /* Number of arguments */
+ if (pq_getbytes((char *) &ibuf, 4))
+ return EOF;
+ appendBinaryStringInfo(buf, (char *) &ibuf, 4);
+ nargs = ntohl(ibuf);
+ /* For each argument ... */
+ while (nargs-- > 0)
+ {
+ int argsize;
+
+ /* argsize */
+ if (pq_getbytes((char *) &ibuf, 4))
+ return EOF;
+ appendBinaryStringInfo(buf, (char *) &ibuf, 4);
+ argsize = ntohl(ibuf);
+ if (argsize < 0)
+ {
+ /* FATAL here since no hope of regaining message sync */
+ elog(FATAL, "HandleFunctionRequest: bogus argsize %d",
+ argsize);
+ }
+ /* and arg contents */
+ if (argsize > 0)
+ {
+ /* Allocate space for arg */
+ enlargeStringInfo(buf, argsize);
+ /* And grab it */
+ if (pq_getbytes(buf->data + buf->len, argsize))
+ return EOF;
+ buf->len += argsize;
+ /* Place a trailing null per StringInfo convention */
+ buf->data[buf->len] = '\0';
+ }
+ }
+ return 0;
+}
+
/* ----------------
* SendFunctionResult
*
* Server side of PQfn (fastpath function calls from the frontend).
* This corresponds to the libpq protocol symbol "F".
*
+ * INPUT:
+ * In protocol version 3, postgres.c has already read the message body
+ * and will pass it in msgBuf.
+ * In old protocol, the passed msgBuf is empty and we must read the
+ * message here.
+ *
* RETURNS:
* 0 if successful completion, EOF if frontend connection lost.
*
* control returns to PostgresMain.
*/
int
-HandleFunctionRequest(void)
+HandleFunctionRequest(StringInfo msgBuf)
{
Oid fid;
- int argsize;
int nargs;
- int tmp;
AclResult aclresult;
FunctionCallInfoData fcinfo;
Datum retval;
int i;
- char *p;
struct fp_info my_fp;
struct fp_info *fip;
/*
- * XXX FIXME: This protocol is misdesigned.
- *
- * We really do not want to elog() before having swallowed all of the
- * frontend's fastpath message; otherwise we will lose sync with the
- * input datastream. What should happen is we absorb all of the input
- * message per protocol syntax, and *then* do error checking
- * (including lookup of the given function ID) and elog if
- * appropriate. Unfortunately, because we cannot even read the
- * message properly without knowing whether the data types are
- * pass-by-ref or pass-by-value, it's not all that easy to do :-(. The
- * protocol should require the client to supply what it thinks is the
- * typbyval and typlen value for each arg, so that we can read the
- * data without having to do any lookups. Then after we've read the
- * message, we should do the lookups, verify agreement of the actual
- * function arg types with what we received, and finally call the
- * function.
- *
- * As things stand, not only will we lose sync for an invalid message
- * (such as requested function OID doesn't exist), but we may lose
- * sync for a perfectly valid message if we are in transaction-aborted
- * state! This can happen because our database lookup attempts may
- * fail entirely in abort state.
- *
- * Unfortunately I see no way to fix this without breaking a lot of
- * existing clients. Maybe do it as part of next protocol version
- * change.
+ * Read message contents if not already done.
*/
+ if (PG_PROTOCOL_MAJOR(FrontendProtocol) < 3)
+ {
+ if (GetOldFunctionMessage(msgBuf))
+ {
+ elog(COMMERROR, "unexpected EOF on client connection");
+ return EOF;
+ }
+ }
- if (pq_getint(&tmp, 4)) /* function oid */
- return EOF;
- fid = (Oid) tmp;
- if (pq_getint(&nargs, 4)) /* # of arguments */
- return EOF;
+ /*
+ * Now that we've eaten the input message, check to see if we actually
+ * want to do the function call or not. It's now safe to elog(); we won't
+ * lose sync with the frontend.
+ */
+ if (IsAbortedTransactionBlockState())
+ elog(ERROR, "current transaction is aborted, "
+ "queries ignored until end of transaction block");
+
+ /*
+ * Parse the buffer contents.
+ */
+ (void) pq_getmsgstring(msgBuf); /* dummy string */
+ fid = (Oid) pq_getmsgint(msgBuf, 4); /* function oid */
+ nargs = pq_getmsgint(msgBuf, 4); /* # of arguments */
/*
* There used to be a lame attempt at caching lookup info here. Now we
fip = &my_fp;
fetch_fp_info(fid, fip);
+ /* Check permission to call function */
+ aclresult = pg_proc_aclcheck(fid, GetUserId(), ACL_EXECUTE);
+ if (aclresult != ACLCHECK_OK)
+ aclcheck_error(aclresult, get_func_name(fid));
+
if (fip->flinfo.fn_nargs != nargs || nargs > FUNC_MAX_ARGS)
- {
elog(ERROR, "HandleFunctionRequest: actual arguments (%d) != registered arguments (%d)",
nargs, fip->flinfo.fn_nargs);
- }
MemSet(&fcinfo, 0, sizeof(fcinfo));
fcinfo.flinfo = &fip->flinfo;
/*
* Copy supplied arguments into arg vector. Note there is no way for
- * frontend to specify a NULL argument --- more misdesign.
+ * frontend to specify a NULL argument --- this protocol is misdesigned.
*/
for (i = 0; i < nargs; ++i)
{
- if (pq_getint(&argsize, 4))
- return EOF;
+ int argsize;
+ char *p;
+
+ argsize = pq_getmsgint(msgBuf, 4);
if (fip->argbyval[i])
{ /* by-value */
if (argsize < 1 || argsize > 4)
elog(ERROR, "HandleFunctionRequest: bogus argsize %d",
argsize);
/* XXX should we demand argsize == fip->arglen[i] ? */
- if (pq_getint(&tmp, argsize))
- return EOF;
- fcinfo.arg[i] = (Datum) tmp;
+ fcinfo.arg[i] = (Datum) pq_getmsgint(msgBuf, argsize);
}
else
{ /* by-reference ... */
if (argsize < 0)
elog(ERROR, "HandleFunctionRequest: bogus argsize %d",
argsize);
- /* I suspect this +1 isn't really needed - tgl 5/2000 */
- p = palloc(argsize + VARHDRSZ + 1); /* Added +1 to solve
- * memory leak - Peter
- * 98 Jan 6 */
+ p = palloc(argsize + VARHDRSZ);
VARATT_SIZEP(p) = argsize + VARHDRSZ;
- if (pq_getbytes(VARDATA(p), argsize))
- return EOF;
+ pq_copymsgbytes(msgBuf, VARDATA(p), argsize);
}
else
{ /* ... fixed */
elog(ERROR, "HandleFunctionRequest: bogus argsize %d, should be %d",
argsize, fip->arglen[i]);
p = palloc(argsize + 1); /* +1 in case argsize is 0 */
- if (pq_getbytes(p, argsize))
- return EOF;
+ pq_copymsgbytes(msgBuf, p, argsize);
}
fcinfo.arg[i] = PointerGetDatum(p);
}
}
- /*
- * Now that we've eaten the input message, check to see if we actually
- * want to do the function call or not.
- *
- * Currently, we report an error if in ABORT state, or return a dummy
- * NULL response if fastpath support has been compiled out.
- */
- if (IsAbortedTransactionBlockState())
- elog(ERROR, "current transaction is aborted, "
- "queries ignored until end of transaction block");
-
- /* Check permission to call function */
- aclresult = pg_proc_aclcheck(fid, GetUserId(), ACL_EXECUTE);
- if (aclresult != ACLCHECK_OK)
- aclcheck_error(aclresult, get_func_name(fid));
-
/*
* Set up a query snapshot in case function needs one. (It is not safe
* to do this if we are in transaction-abort state, so we have to postpone
*
*
* IDENTIFICATION
- * $Header: /cvsroot/pgsql/src/backend/tcop/postgres.c,v 1.321 2003/04/17 22:26:01 tgl Exp $
+ * $Header: /cvsroot/pgsql/src/backend/tcop/postgres.c,v 1.322 2003/04/19 00:02:29 tgl Exp $
*
* NOTES
* this is the "main" module of the postgres backend and
/* ----------------
* InteractiveBackend() is called for user interactive connections
- * the string entered by the user is placed in its parameter inBuf.
+ *
+ * the string entered by the user is placed in its parameter inBuf,
+ * and we act like a Q message was received.
*
* EOF is returned if end-of-file input is seen; time to shut down.
* ----------------
/* Reset inBuf to empty */
inBuf->len = 0;
inBuf->data[0] = '\0';
+ inBuf->cursor = 0;
for (;;)
{
break;
}
+ /* Add '\0' to make it look the same as message case. */
+ appendStringInfoChar(inBuf, (char) '\0');
+
/*
* if the query echo flag was given, print the query..
*/
/* ----------------
* SocketBackend() Is called for frontend-backend connections
*
- * If the input is a query (case 'Q') then the string entered by
- * the user is placed in its parameter inBuf.
- *
- * If the input is a fastpath function call (case 'F') then
- * the function call is processed in HandleFunctionRequest()
- * (now called from PostgresMain()).
+ * Returns the message type code, and loads message body data into inBuf.
*
* EOF is returned if the connection is lost.
* ----------------
*/
-
static int
SocketBackend(StringInfo inBuf)
{
int qtype;
/*
- * get input from the frontend
+ * Get message type code from the frontend.
*/
qtype = pq_getbyte();
+ if (qtype == EOF) /* frontend disconnected */
+ {
+ elog(COMMERROR, "unexpected EOF on client connection");
+ return qtype;
+ }
+
+ /*
+ * Validate message type code before trying to read body; if we have
+ * lost sync, better to say "command unknown" than to run out of memory
+ * because we used garbage as a length word.
+ */
switch (qtype)
{
- case EOF:
- /* frontend disconnected */
+ case 'Q': /* simple query */
+ if (PG_PROTOCOL_MAJOR(FrontendProtocol) < 3)
+ {
+ /* old style without length word; convert */
+ if (pq_getstring(inBuf))
+ {
+ elog(COMMERROR, "unexpected EOF on client connection");
+ return EOF;
+ }
+ }
break;
- /*
- * 'Q': user entered a query
- */
- case 'Q':
- if (pq_getstr(inBuf))
- return EOF;
+ case 'F': /* fastpath function call */
break;
- /*
- * 'F': calling user/system functions
- */
- case 'F':
- if (pq_getstr(inBuf))
- return EOF; /* ignore "string" at start of F message */
+ case 'X': /* terminate */
break;
- /*
- * 'X': frontend is exiting
- */
- case 'X':
+ case 'd': /* copy data */
+ case 'c': /* copy done */
+ case 'f': /* copy fail */
+ /* Accept but ignore these messages, per protocol spec */
break;
+ default:
/*
- * otherwise we got garbage from the frontend.
- *
- * XXX are we certain that we want to do an elog(FATAL) here?
- * -cim 1/24/90
+ * Otherwise we got garbage from the frontend. We treat this
+ * as fatal because we have probably lost message boundary sync,
+ * and there's no good way to recover.
*/
- default:
elog(FATAL, "Socket command type %c unknown", qtype);
break;
}
+ /*
+ * In protocol version 3, all frontend messages have a length word
+ * next after the type code; we can read the message contents
+ * independently of the type.
+ */
+ if (PG_PROTOCOL_MAJOR(FrontendProtocol) >= 3)
+ {
+ if (pq_getmessage(inBuf, 0))
+ return EOF; /* suitable message already logged */
+ }
+
return qtype;
}
PostgresMain(int argc, char *argv[], const char *username)
{
int flag;
-
const char *DBName = NULL;
+ char *potential_DataDir = NULL;
bool secure;
int errs = 0;
int debug_flag = 0;
GucContext ctx;
GucSource gucsource;
char *tmp;
-
int firstchar;
StringInfo parser_input;
-
- char *potential_DataDir = NULL;
+ bool send_rfq;
/*
* Catch standard options before doing much else. This even works on
if (!IsUnderPostmaster)
{
puts("\nPOSTGRES backend interactive interface ");
- puts("$Revision: 1.321 $ $Date: 2003/04/17 22:26:01 $\n");
+ puts("$Revision: 1.322 $ $Date: 2003/04/19 00:02:29 $\n");
}
/*
PG_SETMASK(&UnBlockSig);
+ send_rfq = true; /* initially, or after error */
+
/*
* Non-error queries loop here.
*/
*
* Note: this includes fflush()'ing the last of the prior output.
*/
- ReadyForQuery(whereToSendOutput);
+ if (send_rfq)
+ {
+ ReadyForQuery(whereToSendOutput);
+ send_rfq = false;
+ }
/* ----------
* Tell the statistics collector what we've collected
*/
switch (firstchar)
{
+ case 'Q': /* simple query */
/*
- * 'F' indicates a fastpath call.
- */
- case 'F':
- /* ----------
- * Tell the collector what we're doing
- * ----------
+ * Process the query string.
+ *
+ * Note: transaction command start/end is now done within
+ * pg_exec_query_string(), not here.
*/
+ if (log_statement_stats)
+ ResetUsage();
+
+ pgstat_report_activity(parser_input->data);
+
+ pg_exec_query_string(parser_input,
+ whereToSendOutput,
+ QueryContext);
+
+ if (log_statement_stats)
+ ShowUsage("QUERY STATISTICS");
+
+ send_rfq = true;
+ break;
+
+ case 'F': /* fastpath function call */
+ /* Tell the collector what we're doing */
pgstat_report_activity("<FASTPATH> function call");
/* start an xact for this function invocation */
start_xact_command();
- if (HandleFunctionRequest() == EOF)
+ if (HandleFunctionRequest(parser_input) == EOF)
{
/* lost frontend connection during F message input */
/* commit the function-invocation transaction */
finish_xact_command(false);
- break;
-
- /*
- * 'Q' indicates a user query
- */
- case 'Q':
- /*
- * otherwise, process the input string.
- *
- * Note: transaction command start/end is now done within
- * pg_exec_query_string(), not here.
- */
- if (log_statement_stats)
- ResetUsage();
- pgstat_report_activity(parser_input->data);
-
- pg_exec_query_string(parser_input,
- whereToSendOutput,
- QueryContext);
-
- if (log_statement_stats)
- ShowUsage("QUERY STATISTICS");
+ send_rfq = true;
break;
/*
*/
proc_exit(0);
+ case 'd': /* copy data */
+ case 'c': /* copy done */
+ case 'f': /* copy fail */
+ /*
+ * Accept but ignore these messages, per protocol spec;
+ * we probably got here because a COPY failed, and the
+ * frontend is still sending data.
+ */
+ break;
+
default:
- elog(ERROR, "unknown frontend message was received");
+ elog(FATAL, "Socket command type %c unknown", firstchar);
}
#ifdef MEMORY_CONTEXT_CHECKING
* Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
- * $Id: stringinfo.h,v 1.24 2002/06/20 20:29:49 momjian Exp $
+ * $Id: stringinfo.h,v 1.25 2003/04/19 00:02:29 tgl Exp $
*
*-------------------------------------------------------------------------
*/
* string size (including the terminating '\0' char) that we can
* currently store in 'data' without having to reallocate
* more space. We must always have maxlen > len.
+ * cursor is initialized to zero by makeStringInfo or initStringInfo,
+ * but is not otherwise touched by the stringinfo.c routines.
+ * Some routines use it to scan through a StringInfo.
*-------------------------
*/
typedef struct StringInfoData
char *data;
int len;
int maxlen;
+ int cursor;
} StringInfoData;
typedef StringInfoData *StringInfo;
extern void appendBinaryStringInfo(StringInfo str,
const char *data, int datalen);
+/*------------------------
+ * enlargeStringInfo
+ * Make sure a StringInfo's buffer can hold at least 'needed' more bytes.
+ */
+extern void enlargeStringInfo(StringInfo str, int needed);
+
#endif /* STRINGINFO_H */
* Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
- * $Id: libpq.h,v 1.56 2003/01/25 05:19:47 tgl Exp $
+ * $Id: libpq.h,v 1.57 2003/04/19 00:02:29 tgl Exp $
*
*-------------------------------------------------------------------------
*/
extern void TouchSocketFile(void);
extern void pq_init(void);
extern int pq_getbytes(char *s, size_t len);
-extern int pq_getstring(StringInfo s, int maxlen);
+extern int pq_getstring(StringInfo s);
+extern int pq_getmessage(StringInfo s, int maxlen);
extern int pq_getbyte(void);
extern int pq_peekbyte(void);
extern int pq_putbytes(const char *s, size_t len);
extern int pq_flush(void);
-extern int pq_eof(void);
extern int pq_putmessage(char msgtype, const char *s, size_t len);
extern void pq_startcopyout(void);
extern void pq_endcopyout(bool errorAbort);
+/*
+ * prototypes for functions in be-secure.c
+ */
+extern int secure_initialize(void);
+extern void secure_destroy(void);
+extern int secure_open_server(Port *port);
+extern void secure_close(Port *port);
+extern ssize_t secure_read(Port *port, void *ptr, size_t len);
+extern ssize_t secure_write(Port *port, void *ptr, size_t len);
+
#endif /* LIBPQ_H */
* Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
- * $Id: pqcomm.h,v 1.76 2003/04/17 22:26:01 tgl Exp $
+ * $Id: pqcomm.h,v 1.77 2003/04/19 00:02:29 tgl Exp $
*
*-------------------------------------------------------------------------
*/
/* The earliest and latest frontend/backend protocol version supported. */
#define PG_PROTOCOL_EARLIEST PG_PROTOCOL(1,0)
-#define PG_PROTOCOL_LATEST PG_PROTOCOL(3,100) /* XXX temporary value */
+#define PG_PROTOCOL_LATEST PG_PROTOCOL(3,101) /* XXX temporary value */
typedef uint32 ProtocolVersion; /* FE/BE protocol version number */
* Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
- * $Id: pqformat.h,v 1.13 2002/09/04 23:31:35 tgl Exp $
+ * $Id: pqformat.h,v 1.14 2003/04/19 00:02:29 tgl Exp $
*
*-------------------------------------------------------------------------
*/
extern int pq_puttextmessage(char msgtype, const char *str);
-extern int pq_getint(int *result, int b);
-extern int pq_getstr_bounded(StringInfo s, int maxlen);
-
-#define pq_getstr(s) pq_getstr_bounded(s, 0)
+extern int pq_getmsgbyte(StringInfo msg);
+extern unsigned int pq_getmsgint(StringInfo msg, int b);
+extern const char *pq_getmsgbytes(StringInfo msg, int datalen);
+extern void pq_copymsgbytes(StringInfo msg, char *buf, int datalen);
+extern const char *pq_getmsgstring(StringInfo msg);
+extern void pq_getmsgend(StringInfo msg);
#endif /* PQFORMAT_H */
* Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
- * $Id: dest.h,v 1.33 2003/03/27 16:51:29 momjian Exp $
+ * $Id: dest.h,v 1.34 2003/04/19 00:02:30 tgl Exp $
*
*-------------------------------------------------------------------------
*/
/* Additional functions that go with destination management, more or less. */
-extern void SendCopyBegin(void);
-extern void ReceiveCopyBegin(void);
extern void NullCommand(CommandDest dest);
extern void ReadyForQuery(CommandDest dest);
* Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
- * $Id: fastpath.h,v 1.13 2002/06/20 20:29:52 momjian Exp $
+ * $Id: fastpath.h,v 1.14 2003/04/19 00:02:30 tgl Exp $
*
*-------------------------------------------------------------------------
*/
#ifndef FASTPATH_H
#define FASTPATH_H
-extern int HandleFunctionRequest(void);
+#include "lib/stringinfo.h"
+
+extern int HandleFunctionRequest(StringInfo msgBuf);
#endif /* FASTPATH_H */
* exceed INITIAL_EXPBUFFER_SIZE (currently 256 bytes).
*
* IDENTIFICATION
- * $Header: /cvsroot/pgsql/src/interfaces/libpq/fe-auth.c,v 1.75 2003/04/17 22:26:01 tgl Exp $
+ * $Header: /cvsroot/pgsql/src/interfaces/libpq/fe-auth.c,v 1.76 2003/04/19 00:02:30 tgl Exp $
*
*-------------------------------------------------------------------------
*/
default:
return STATUS_ERROR;
}
- ret = pqPacketSend(conn, 0, crypt_pwd, strlen(crypt_pwd) + 1);
+ ret = pqPacketSend(conn, 'p', crypt_pwd, strlen(crypt_pwd) + 1);
if (areq == AUTH_REQ_MD5)
free(crypt_pwd);
return ret;
*
*
* IDENTIFICATION
- * $Header: /cvsroot/pgsql/src/interfaces/libpq/fe-connect.c,v 1.232 2003/04/17 22:26:02 tgl Exp $
+ * $Header: /cvsroot/pgsql/src/interfaces/libpq/fe-connect.c,v 1.233 2003/04/19 00:02:30 tgl Exp $
*
*-------------------------------------------------------------------------
*/
#endif
/*
- * The output buffer size is set to 8K, which is the usual size of
- * pipe buffers on Unix systems. That way, when we are sending a
+ * We try to send at least 8K at a time, which is the usual size
+ * of pipe buffers on Unix systems. That way, when we are sending a
* large amount of data, we avoid incurring extra kernel context swaps
- * for partial bufferloads. Note that we currently don't ever enlarge
- * the output buffer.
+ * for partial bufferloads. The output buffer is initially made 16K
+ * in size, and we try to dump it after accumulating 8K.
*
* With the same goal of minimizing context swaps, the input buffer will
* be enlarged anytime it has less than 8K free, so we initially
*/
conn->inBufSize = 16 * 1024;
conn->inBuffer = (char *) malloc(conn->inBufSize);
- conn->outBufSize = 8 * 1024;
+ conn->outBufSize = 16 * 1024;
conn->outBuffer = (char *) malloc(conn->outBufSize);
conn->nonblocking = FALSE;
initPQExpBuffer(&conn->errorMessage);
{
/*
* Try to send "close connection" message to backend. Ignore any
- * error. Note: this routine used to go to substantial lengths to
- * avoid getting SIGPIPE'd if the connection were already closed.
- * Now we rely on pqFlush to avoid the signal.
+ * error.
*/
- pqPutc('X', conn);
+ pqPutMsgStart('X', conn);
+ pqPutMsgEnd(conn);
pqFlush(conn);
}
/*
- * pqPacketSend() -- send a single-packet message.
+ * pqPacketSend() -- convenience routine to send a message to server.
*
* pack_type: the single-byte message type code. (Pass zero for startup
* packets, which have no message type code.)
pqPacketSend(PGconn *conn, char pack_type,
const void *buf, size_t buf_len)
{
- /* Send the message type. */
- if (pack_type != 0)
- if (pqPutc(pack_type, conn))
- return STATUS_ERROR;
-
- /* Send the (self-inclusive) message length word. */
- if (pqPutInt(buf_len + 4, 4, conn))
+ /* Start the message. */
+ if (pqPutMsgStart(pack_type, conn))
return STATUS_ERROR;
/* Send the message body. */
if (pqPutnchar(buf, buf_len, conn))
return STATUS_ERROR;
+ /* Finish the message. */
+ if (pqPutMsgEnd(conn))
+ return STATUS_ERROR;
+
/* Flush to ensure backend gets it. */
if (pqFlush(conn))
return STATUS_ERROR;
packet_len += sizeof(ProtocolVersion);
/* Add user name, database name, options */
- if (conn->pguser)
+ if (conn->pguser && conn->pguser[0])
{
if (packet)
strcpy(packet + packet_len, "user");
strcpy(packet + packet_len, conn->pguser);
packet_len += strlen(conn->pguser) + 1;
}
- if (conn->dbName)
+ if (conn->dbName && conn->dbName[0])
{
if (packet)
strcpy(packet + packet_len, "database");
strcpy(packet + packet_len, conn->dbName);
packet_len += strlen(conn->dbName) + 1;
}
- if (conn->pgoptions)
+ if (conn->pgoptions && conn->pgoptions[0])
{
if (packet)
strcpy(packet + packet_len, "options");
*
*
* IDENTIFICATION
- * $Header: /cvsroot/pgsql/src/interfaces/libpq/fe-exec.c,v 1.128 2003/03/25 02:44:36 momjian Exp $
+ * $Header: /cvsroot/pgsql/src/interfaces/libpq/fe-exec.c,v 1.129 2003/04/19 00:02:30 tgl Exp $
*
*-------------------------------------------------------------------------
*/
* Returns: 1 if successfully submitted
* 0 if error (conn->errorMessage is set)
*/
-
int
PQsendQuery(PGconn *conn, const char *query)
{
conn->result = NULL;
conn->curTuple = NULL;
- /* send the query to the backend; */
+ /* construct the outgoing Query message */
+ if (pqPutMsgStart('Q', conn) < 0 ||
+ pqPuts(query, conn) < 0 ||
+ pqPutMsgEnd(conn) < 0)
+ {
+ handleSendFailure(conn);
+ return 0;
+ }
/*
- * in order to guarantee that we don't send a partial query where we
- * would become out of sync with the backend and/or block during a
- * non-blocking connection we must first flush the send buffer before
- * sending more data
- *
- * an alternative is to implement 'queue reservations' where we are able
- * to roll up a transaction (the 'Q' along with our query) and make
- * sure we have enough space for it all in the send buffer.
+ * Give the data a push. In nonblock mode, don't complain if we're
+ * unable to send it all; PQconsumeInput() will do any additional flushing
+ * needed.
*/
- if (pqIsnonblocking(conn))
+ if (pqFlush(conn) < 0)
{
- /*
- * the buffer must have emptied completely before we allow a new
- * query to be buffered
- */
- if (pqFlush(conn))
- return 0;
- /* 'Q' == queries */
- /* XXX: if we fail here we really ought to not block */
- if (pqPutc('Q', conn) != 0 || pqPuts(query, conn) != 0)
- {
- handleSendFailure(conn);
- return 0;
- }
-
- /*
- * give the data a push, ignore the return value as ConsumeInput()
- * will do any additional flushing if needed
- */
- pqFlush(conn);
- }
- else
- {
- /*
- * the frontend-backend protocol uses 'Q' to designate queries
- */
- if (pqPutc('Q', conn) != 0 || pqPuts(query, conn) != 0 ||
- pqFlush(conn) != 0)
- {
- handleSendFailure(conn);
- return 0;
- }
+ handleSendFailure(conn);
+ return 0;
}
/* OK, it's launched! */
*
* NOTE: this routine should only be called in PGASYNC_IDLE state.
*/
-
static void
handleSendFailure(PGconn *conn)
{
* 0 return: some kind of trouble
* 1 return: no problem
*/
-
int
PQconsumeInput(PGconn *conn)
{
if (!conn)
return 0;
+ /*
+ * for non-blocking connections try to flush the send-queue,
+ * otherwise we may never get a response for something that may
+ * not have already been sent because it's in our write buffer!
+ */
+ if (pqIsnonblocking(conn))
+ {
+ if (pqFlush(conn) < 0)
+ return 0;
+ }
+
/*
* Load more data, if available. We do this no matter what state we
* are in, since we are probably getting called because the
* we will NOT block waiting for more input.
*/
if (pqReadData(conn) < 0)
- {
- /*
- * for non-blocking connections try to flush the send-queue
- * otherwise we may never get a responce for something that may
- * not have already been sent because it's in our write buffer!
- */
- if (pqIsnonblocking(conn))
- (void) pqFlush(conn);
return 0;
- }
+
/* Parsing of the data waits till later. */
return 1;
}
* PQputline -- sends a string to the backend.
* Returns 0 if OK, EOF if not.
*
- * Chiefly here so that applications can use "COPY <rel> from stdin".
+ * This exists to support "COPY <rel> from stdin". The backend will ignore
+ * the string if not doing COPY.
*/
int
PQputline(PGconn *conn, const char *s)
{
- if (!conn || conn->sock < 0)
- return EOF;
- return pqPutnchar(s, strlen(s), conn);
+ return PQputnbytes(conn, s, strlen(s));
}
/*
{
if (!conn || conn->sock < 0)
return EOF;
- return pqPutnchar(buffer, nbytes, conn);
+ if (nbytes > 0)
+ {
+ if (pqPutMsgStart('d', conn) < 0 ||
+ pqPutnchar(buffer, nbytes, conn) < 0 ||
+ pqPutMsgEnd(conn) < 0)
+ return EOF;
+ }
+ return 0;
}
/*
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
return NULL;
}
- if (pqPuts("F ", conn) != 0 || /* function */
- pqPutInt(fnid, 4, conn) != 0 || /* function id */
- pqPutInt(nargs, 4, conn) != 0) /* # of args */
+ if (pqPutMsgStart('F', conn) < 0 || /* function call msg */
+ pqPuts("", conn) < 0 || /* useless string */
+ pqPutInt(fnid, 4, conn) < 0 || /* function id */
+ pqPutInt(nargs, 4, conn) < 0) /* # of args */
{
handleSendFailure(conn);
return NULL;
}
}
}
- if (pqFlush(conn))
+
+ if (pqPutMsgEnd(conn) < 0 ||
+ pqFlush(conn))
{
handleSendFailure(conn);
return NULL;
int
PQsetnonblocking(PGconn *conn, int arg)
{
-
arg = (arg == TRUE) ? 1 : 0;
/* early out if the socket is already in the state requested */
if (arg == conn->nonblocking)
int
PQisnonblocking(const PGconn *conn)
{
-
return (pqIsnonblocking(conn));
}
int
PQflush(PGconn *conn)
{
-
return (pqFlush(conn));
}
-/* try to force data out, really only useful for non-blocking users.
- * This implementation actually works for non-blocking connections */
-int
-PQsendSome(PGconn *conn)
-{
- return pqSendSome(conn);
-}
-
/*
* PQfreeNotify - free's the memory associated with a PGnotify
*
{
PQfreemem(notify);
}
-
-
* 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.
+ * routines.
+ *
*
* 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-misc.c,v 1.88 2003/04/02 00:49:28 tgl Exp $
+ * $Header: /cvsroot/pgsql/src/interfaces/libpq/fe-misc.c,v 1.89 2003/04/19 00:02:30 tgl Exp $
*
*-------------------------------------------------------------------------
*/
#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);
static int pqSocketCheck(PGconn *conn, int forRead, int forWrite,
time_t end_time);
static int pqSocketPoll(int sock, int forRead, int forWrite, time_t end_time);
-static int pqPutBytes(const char *s, size_t nbytes, PGconn *conn);
/*
- * pqGetc:
- * get a character from the connection
+ * pqGetc: get 1 character from the connection
*
* All these routines return 0 on success, EOF on error.
* Note that for the Get routines, EOF only means there is not enough
/*
- * write 1 char to the connection
+ * pqPutc: write 1 char to the current message
*/
int
pqPutc(char c, PGconn *conn)
{
- if (pqPutBytes(&c, 1, conn) == EOF)
+ if (pqPutMsgBytes(&c, 1, conn))
return EOF;
if (conn->Pfdebug)
}
-/*
- * pqPutBytes: local routine to write N bytes to the connection,
- * with buffering
- */
-static int
-pqPutBytes(const char *s, size_t nbytes, PGconn *conn)
-{
- /*
- * Strategy to handle blocking and non-blocking connections: Fill the
- * output buffer and flush it repeatedly until either all data has
- * been sent or is at least queued in the buffer.
- *
- * For non-blocking connections, grow the buffer if not all data fits
- * into it and the buffer can't be sent because the socket would
- * block.
- */
-
- while (nbytes)
- {
- size_t avail,
- remaining;
-
- /* fill the output buffer */
- avail = Max(conn->outBufSize - conn->outCount, 0);
- remaining = Min(avail, nbytes);
- memcpy(conn->outBuffer + conn->outCount, s, remaining);
- conn->outCount += remaining;
- s += remaining;
- nbytes -= remaining;
-
- /*
- * if the data didn't fit completely into the buffer, try to flush
- * the buffer
- */
- if (nbytes)
- {
- int send_result = pqSendSome(conn);
-
- /* if there were errors, report them */
- if (send_result < 0)
- return EOF;
-
- /*
- * if not all data could be sent, increase the output buffer,
- * put the rest of s into it and return successfully. This
- * case will only happen in a non-blocking connection
- */
- if (send_result > 0)
- {
- /*
- * try to grow the buffer. FIXME: The new size could be
- * chosen more intelligently.
- */
- size_t buflen = (size_t) conn->outCount + nbytes;
-
- if (buflen > (size_t) conn->outBufSize)
- {
- char *newbuf = realloc(conn->outBuffer, buflen);
-
- if (!newbuf)
- {
- /* realloc failed. Probably out of memory */
- printfPQExpBuffer(&conn->errorMessage,
- "cannot allocate memory for output buffer\n");
- return EOF;
- }
- conn->outBuffer = newbuf;
- conn->outBufSize = buflen;
- }
- /* put the data into it */
- memcpy(conn->outBuffer + conn->outCount, s, nbytes);
- conn->outCount += nbytes;
-
- /* report success. */
- return 0;
- }
- }
-
- /*
- * pqSendSome was able to send all data. Continue with the next
- * chunk of s.
- */
- } /* while */
-
- return 0;
-}
-
/*
* pqGets:
* get a null-terminated string from the connection,
}
+/*
+ * pqPuts: write a null-terminated string to the current message
+ */
int
pqPuts(const char *s, PGconn *conn)
{
- if (pqPutBytes(s, strlen(s) + 1, conn))
+ if (pqPutMsgBytes(s, strlen(s) + 1, conn))
return EOF;
if (conn->Pfdebug)
- fprintf(conn->Pfdebug, "To backend> %s\n", s);
+ fprintf(conn->Pfdebug, "To backend> '%s'\n", s);
return 0;
}
/*
* pqPutnchar:
- * send a string of exactly len bytes, no null termination needed
+ * write exactly len bytes to the current message
*/
int
pqPutnchar(const char *s, size_t len, PGconn *conn)
{
- if (pqPutBytes(s, len, conn))
+ if (pqPutMsgBytes(s, len, conn))
return EOF;
if (conn->Pfdebug)
/*
* pgPutInt
- * send an integer of 2 or 4 bytes, converting from host byte order
+ * write an integer of 2 or 4 bytes, converting from host byte order
* to network byte order.
*/
int
{
case 2:
tmp2 = htons((uint16) value);
- if (pqPutBytes((const char *) &tmp2, 2, conn))
+ if (pqPutMsgBytes((const char *) &tmp2, 2, conn))
return EOF;
break;
case 4:
tmp4 = htonl((uint32) value);
- if (pqPutBytes((const char *) &tmp4, 4, conn))
+ if (pqPutMsgBytes((const char *) &tmp4, 4, conn))
return EOF;
break;
default:
}
/*
- * pqReadReady: is select() saying the file is ready to read?
- * JAB: -or- if SSL is enabled and used, is it buffering bytes?
- * Returns -1 on failure, 0 if not ready, 1 if ready.
+ * Make sure conn's output buffer can hold bytes_needed bytes (caller must
+ * include existing outCount into the value!)
+ *
+ * Returns 0 on success, EOF on error
+ */
+static int
+checkOutBufferSpace(int bytes_needed, PGconn *conn)
+{
+ int newsize = conn->outBufSize;
+ char *newbuf;
+
+ if (bytes_needed <= newsize)
+ return 0;
+ /*
+ * If we need to enlarge the buffer, we first try to double it in size;
+ * if that doesn't work, enlarge in multiples of 8K. This avoids
+ * thrashing the malloc pool by repeated small enlargements.
+ *
+ * Note: tests for newsize > 0 are to catch integer overflow.
+ */
+ do {
+ newsize *= 2;
+ } while (bytes_needed > newsize && newsize > 0);
+
+ if (bytes_needed <= newsize)
+ {
+ newbuf = realloc(conn->outBuffer, newsize);
+ if (newbuf)
+ {
+ /* realloc succeeded */
+ conn->outBuffer = newbuf;
+ conn->outBufSize = newsize;
+ return 0;
+ }
+ }
+
+ newsize = conn->outBufSize;
+ do {
+ newsize += 8192;
+ } while (bytes_needed > newsize && newsize > 0);
+
+ if (bytes_needed <= newsize)
+ {
+ newbuf = realloc(conn->outBuffer, newsize);
+ if (newbuf)
+ {
+ /* realloc succeeded */
+ conn->outBuffer = newbuf;
+ conn->outBufSize = newsize;
+ return 0;
+ }
+ }
+
+ /* realloc failed. Probably out of memory */
+ printfPQExpBuffer(&conn->errorMessage,
+ "cannot allocate memory for output buffer\n");
+ return EOF;
+}
+
+/*
+ * pqPutMsgStart: begin construction of a message to the server
+ *
+ * msg_type is the message type byte, or 0 for a message without type byte
+ * (only startup messages have no type byte)
+ *
+ * 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.
*/
int
-pqReadReady(PGconn *conn)
+pqPutMsgStart(char msg_type, PGconn *conn)
{
- return pqSocketCheck(conn, 1, 0, (time_t) 0);
+ int lenPos;
+
+ /* where the message length word will go */
+ if (msg_type)
+ lenPos = conn->outCount + 1;
+ else
+ lenPos = conn->outCount;
+ /* make sure there is room for it */
+ if (checkOutBufferSpace(lenPos + 4, 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 */
+
+ if (conn->Pfdebug)
+ fprintf(conn->Pfdebug, "To backend> Msg %c\n",
+ msg_type ? msg_type : ' ');
+
+ return 0;
}
/*
- * pqWriteReady: is select() saying the file is ready to write?
- * Returns -1 on failure, 0 if not ready, 1 if ready.
+ * pqPutMsgBytes: add bytes to a partially-constructed message
+ *
+ * Returns 0 on success, EOF on error
+ */
+static int
+pqPutMsgBytes(const void *buf, size_t len, PGconn *conn)
+{
+ /* make sure there is room for it */
+ if (checkOutBufferSpace(conn->outMsgEnd + len, conn))
+ return EOF;
+ /* okay, save the data */
+ memcpy(conn->outBuffer + conn->outMsgEnd, buf, len);
+ conn->outMsgEnd += len;
+ /* no Pfdebug call here, caller should do it */
+ return 0;
+}
+
+/*
+ * pqPutMsgEnd: finish constructing a message and possibly send it
+ *
+ * Returns 0 on success, EOF on error
+ *
+ * We don't actually send anything here unless we've accumulated at least
+ * 8K worth of data (the typical size of a pipe buffer on Unix systems).
+ * This avoids sending small partial packets. The caller must use pqFlush
+ * when it's important to flush all the data out to the server.
*/
int
-pqWriteReady(PGconn *conn)
+pqPutMsgEnd(PGconn *conn)
{
- return pqSocketCheck(conn, 0, 1, (time_t) 0);
+ uint32 msgLen = conn->outMsgEnd - conn->outMsgStart;
+
+ if (conn->Pfdebug)
+ fprintf(conn->Pfdebug, "To backend> Msg complete, length %u\n",
+ msgLen);
+
+ msgLen = htonl(msgLen);
+ memcpy(conn->outBuffer + conn->outMsgStart, &msgLen, 4);
+ conn->outCount = conn->outMsgEnd;
+
+ if (conn->outCount >= 8192)
+ {
+ int toSend = conn->outCount - (conn->outCount % 8192);
+
+ if (pqSendSome(conn, toSend) < 0)
+ return EOF;
+ /* in nonblock mode, don't complain if unable to send it all */
+ }
+
+ return 0;
}
/* ----------
}
/*
- * pqSendSome: send any data waiting in the output buffer.
+ * pqSendSome: send data waiting in the output buffer.
+ *
+ * len is how much to try to send (typically equal to outCount, but may
+ * be less).
*
- * Return 0 on sucess, -1 on failure and 1 when data remains because the
- * socket would block and the connection is non-blocking.
+ * Return 0 on success, -1 on failure and 1 when not all data could be sent
+ * because the socket would block and the connection is non-blocking.
*/
-int
-pqSendSome(PGconn *conn)
+static int
+pqSendSome(PGconn *conn, int len)
{
char *ptr = conn->outBuffer;
- int len = conn->outCount;
+ int remaining = conn->outCount;
+ int result = 0;
if (conn->sock < 0)
{
return -1;
}
- /*
- * don't try to send zero data, allows us to use this function without
- * too much worry about overhead
- */
- if (len == 0)
- return (0);
-
/* while there's still data to send */
while (len > 0)
{
* (typically, a NOTICE message from the backend
* telling us it's committing hara-kiri...). Leave
* the socket open until pqReadData finds no more data
- * can be read.
+ * can be read. But abandon attempt to send data.
*/
+ conn->outCount = 0;
return -1;
default:
libpq_gettext("could not send data to server: %s\n"),
SOCK_STRERROR(SOCK_ERRNO));
/* We don't assume it's a fatal error... */
+ conn->outCount = 0;
return -1;
}
}
{
ptr += sent;
len -= sent;
+ remaining -= sent;
}
if (len > 0)
#endif
if (pqIsnonblocking(conn))
{
- /* shift the contents of the buffer */
- memmove(conn->outBuffer, ptr, len);
- conn->outCount = len;
- return 1;
+ result = 1;
+ break;
}
#ifdef USE_SSL
}
#endif
if (pqWait(FALSE, TRUE, conn))
- return -1;
+ {
+ result = -1;
+ break;
+ }
}
}
- conn->outCount = 0;
+ /* shift the remaining contents of the buffer */
+ if (remaining > 0)
+ memmove(conn->outBuffer, ptr, remaining);
+ conn->outCount = remaining;
- if (conn->Pfdebug)
- fflush(conn->Pfdebug);
-
- return 0;
+ return result;
}
-
/*
* pqFlush: send any data waiting in the output buffer
*
- * Implemented in terms of pqSendSome to recreate the old behavior which
- * returned 0 if all data was sent or EOF. EOF was sent regardless of
- * whether an error occurred or not all data was sent on a non-blocking
- * socket.
+ * Return 0 on success, -1 on failure and 1 when not all data could be sent
+ * because the socket would block and the connection is non-blocking.
*/
int
pqFlush(PGconn *conn)
{
- if (pqSendSome(conn))
- return EOF;
+ if (conn->Pfdebug)
+ fflush(conn->Pfdebug);
+
+ if (conn->outCount > 0)
+ return pqSendSome(conn, conn->outCount);
+
return 0;
}
+
/*
* pqWait: wait until we can read or write the connection socket
*
return 0;
}
+/*
+ * pqReadReady: is select() saying the file is ready to read?
+ * Returns -1 on failure, 0 if not ready, 1 if ready.
+ */
+int
+pqReadReady(PGconn *conn)
+{
+ return pqSocketCheck(conn, 1, 0, (time_t) 0);
+}
+
+/*
+ * pqWriteReady: is select() saying the file is ready to write?
+ * Returns -1 on failure, 0 if not ready, 1 if ready.
+ */
+int
+pqWriteReady(PGconn *conn)
+{
+ return pqSocketCheck(conn, 0, 1, (time_t) 0);
+}
+
/*
* Checks a socket, using poll or select, for data to be read, written,
* or both. Returns >0 if one or more conditions are met, 0 if it timed
* out, -1 if an error occurred.
+ *
* If SSL is in use, the SSL buffer is checked prior to checking the socket
* for read data directly.
*/
return -1;
}
-/* JAB: Check for SSL library buffering read bytes */
#ifdef USE_SSL
+ /* Check for SSL library buffering read bytes */
if (forRead && conn->ssl && SSL_pending(conn->ssl) > 0)
{
/* short-circuit the select */
* If neither forRead nor forWrite are set, immediately return a timeout
* condition (without waiting). Return >0 if condition is met, 0
* if a timeout occurred, -1 if an error or interrupt occurred.
+ *
* Timeout is infinite if end_time is -1. Timeout is immediate (no blocking)
* if end_time is 0 (or indeed, any time before now).
*/
struct pollfd input_fd;
int timeout_ms;
+ if (!forRead && !forWrite)
+ return 0;
+
input_fd.fd = sock;
- input_fd.events = 0;
+ input_fd.events = POLLERR;
input_fd.revents = 0;
if (forRead)
input_fd.events |= POLLIN;
if (forWrite)
input_fd.events |= POLLOUT;
- if (!input_fd.events)
- return 0;
/* Compute appropriate timeout interval */
if (end_time == ((time_t) -1))
* 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.91 2003/03/25 02:44:36 momjian Exp $
+ * $Id: libpq-fe.h,v 1.92 2003/04/19 00:02:30 tgl Exp $
*
*-------------------------------------------------------------------------
*/
/* Force the write buffer to be written (or at least try) */
extern int PQflush(PGconn *conn);
-extern int PQsendSome(PGconn *conn);
/*
* "Fast path" interface --- not really recommended for application
* 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.61 2003/04/17 22:26:02 tgl Exp $
+ * $Id: libpq-int.h,v 1.62 2003/04/19 00:02:30 tgl Exp $
*
*-------------------------------------------------------------------------
*/
* pqcomm.h describe what the backend knows, not what libpq knows.
*/
-#define PG_PROTOCOL_LIBPQ PG_PROTOCOL(3,100) /* XXX temporary value */
+#define PG_PROTOCOL_LIBPQ PG_PROTOCOL(3,101) /* XXX temporary value */
/*
* POSTGRES backend dependent Constants.
int outBufSize; /* allocated size of buffer */
int outCount; /* number of chars waiting in buffer */
+ /* State for constructing messages in outBuffer */
+ int outMsgStart; /* offset to msg start (length word) */
+ int outMsgEnd; /* offset to msg end (so far) */
+
/* Status for asynchronous result construction */
PGresult *result; /* result being constructed */
PGresAttValue *curTuple; /* tuple currently being read */
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 pqPutMsgEnd(PGconn *conn);
extern int pqReadData(PGconn *conn);
extern int pqFlush(PGconn *conn);
-extern int pqSendSome(PGconn *conn);
extern int pqWait(int forRead, int forWrite, PGconn *conn);
extern int pqWaitTimed(int forRead, int forWrite, PGconn *conn,
time_t finish_time);