- Support for TAR output
authorPhilip Warner
Fri, 21 Jul 2000 11:43:26 +0000 (11:43 +0000)
committerPhilip Warner
Fri, 21 Jul 2000 11:43:26 +0000 (11:43 +0000)
- Support for BLOB output from pg_dump and input via pg_restore
- Support for direct DB connection in pg_restore
- Fixes in support for --insert flag
- pg_dump now outputs in modified OID order

src/bin/pg_dump/pg_backup_db.c [new file with mode: 0644]
src/bin/pg_dump/pg_backup_db.h [new file with mode: 0644]
src/bin/pg_dump/pg_backup_null.c [new file with mode: 0644]
src/bin/pg_dump/pg_backup_tar.c [new file with mode: 0644]
src/bin/pg_dump/pg_backup_tar.h [new file with mode: 0644]

diff --git a/src/bin/pg_dump/pg_backup_db.c b/src/bin/pg_dump/pg_backup_db.c
new file mode 100644 (file)
index 0000000..c98e938
--- /dev/null
@@ -0,0 +1,497 @@
+/*-------------------------------------------------------------------------
+ *
+ *
+*-------------------------------------------------------------------------
+ */
+
+#include                 /* for getopt() */
+#include 
+
+#include "postgres.h"
+
+#ifdef HAVE_TERMIOS_H
+#include 
+#endif
+
+#include "access/attnum.h"
+#include "access/htup.h"
+#include "catalog/pg_index.h"
+#include "catalog/pg_language.h"
+#include "catalog/pg_trigger.h"
+#include "catalog/pg_type.h"
+
+#include "libpq-fe.h"
+#include 
+#ifndef HAVE_STRDUP
+#include "strdup.h"
+#endif
+
+#include "pg_dump.h"
+#include "pg_backup.h"
+#include "pg_backup_archiver.h"
+#include "pg_backup_db.h"
+
+static const char  *progname = "Archiver(db)";
+
+static void _prompt_for_password(char *username, char *password);
+static void _check_database_version(ArchiveHandle *AH, bool ignoreVersion);
+
+
+static void
+_prompt_for_password(char *username, char *password)
+{
+   char        buf[512];
+   int         length;
+
+#ifdef HAVE_TERMIOS_H
+   struct termios t_orig,
+                  t;
+#endif
+
+   fprintf(stderr, "Username: ");
+   fflush(stderr);
+   fgets(username, 100, stdin);
+   length = strlen(username);
+   /* skip rest of the line */
+   if (length > 0 && username[length - 1] != '\n')
+   {
+       do
+       {
+           fgets(buf, 512, stdin);
+       } while (buf[strlen(buf) - 1] != '\n');
+   }
+   if (length > 0 && username[length - 1] == '\n')
+       username[length - 1] = '\0';
+
+#ifdef HAVE_TERMIOS_H
+   tcgetattr(0, &t);
+   t_orig = t;
+   t.c_lflag &= ~ECHO;
+   tcsetattr(0, TCSADRAIN, &t);
+#endif
+   fprintf(stderr, "Password: ");
+   fflush(stderr);
+   fgets(password, 100, stdin);
+#ifdef HAVE_TERMIOS_H
+   tcsetattr(0, TCSADRAIN, &t_orig);
+#endif
+
+   length = strlen(password);
+   /* skip rest of the line */
+   if (length > 0 && password[length - 1] != '\n')
+   {
+       do
+       {
+           fgets(buf, 512, stdin);
+       } while (buf[strlen(buf) - 1] != '\n');
+   }
+   if (length > 0 && password[length - 1] == '\n')
+       password[length - 1] = '\0';
+
+   fprintf(stderr, "\n\n");
+}
+
+
+static void
+_check_database_version(ArchiveHandle *AH, bool ignoreVersion)
+{
+   PGresult   *res;
+   double      myversion;
+   const char *remoteversion_str;
+   double      remoteversion;
+   PGconn      *conn = AH->connection;
+
+   myversion = strtod(PG_VERSION, NULL);
+   res = PQexec(conn, "SELECT version()");
+   if (!res ||
+       PQresultStatus(res) != PGRES_TUPLES_OK ||
+       PQntuples(res) != 1)
+
+       die_horribly(AH, "check_database_version(): command failed.  "
+               "Explanation from backend: '%s'.\n", PQerrorMessage(conn));
+
+   remoteversion_str = PQgetvalue(res, 0, 0);
+   remoteversion = strtod(remoteversion_str + 11, NULL);
+   if (myversion != remoteversion)
+   {
+       fprintf(stderr, "Database version: %s\n%s version: %s\n",
+               progname, remoteversion_str, PG_VERSION);
+       if (ignoreVersion)
+           fprintf(stderr, "Proceeding despite version mismatch.\n");
+       else
+           die_horribly(AH, "Aborting because of version mismatch.\n"
+                   "Use --ignore-version if you think it's safe to proceed anyway.\n");    
+   }
+   PQclear(res);
+}
+
+PGconn* ConnectDatabase(Archive *AHX, 
+       const char*     dbname,
+       const char*     pghost,
+       const char*     pgport,
+       const int   reqPwd,
+       const int   ignoreVersion)
+{
+   ArchiveHandle   *AH = (ArchiveHandle*)AHX;
+   char        connect_string[512] = "";
+   char        tmp_string[128];
+   char        password[100];
+
+   if (AH->connection)
+       die_horribly(AH, "%s: already connected to database\n", progname);
+
+   if (!dbname && !(dbname = getenv("PGDATABASE")) ) 
+       die_horribly(AH, "%s: no database name specified\n", progname);
+
+   AH->dbname = strdup(dbname);
+
+   if (pghost != NULL)
+   {
+       AH->pghost = strdup(pghost);
+       sprintf(tmp_string, "host=%s ", AH->pghost);
+       strcat(connect_string, tmp_string);
+   }
+   else
+       AH->pghost = NULL;
+
+   if (pgport != NULL)
+   {
+       AH->pgport = strdup(pgport);
+       sprintf(tmp_string, "port=%s ", AH->pgport);
+       strcat(connect_string, tmp_string);
+   }
+   else
+       AH->pgport = NULL;
+
+   sprintf(tmp_string, "dbname=%s ", AH->dbname);
+   strcat(connect_string, tmp_string);
+
+   if (reqPwd)
+   {
+       _prompt_for_password(AH->username, password);
+       strcat(connect_string, "authtype=password ");
+       sprintf(tmp_string, "user=%s ", AH->username);
+       strcat(connect_string, tmp_string);
+       sprintf(tmp_string, "password=%s ", password);
+       strcat(connect_string, tmp_string);
+       MemSet(tmp_string, 0, sizeof(tmp_string));
+       MemSet(password, 0, sizeof(password));
+   }
+   AH->connection = PQconnectdb(connect_string);
+   MemSet(connect_string, 0, sizeof(connect_string));
+
+   /* check to see that the backend connection was successfully made */
+   if (PQstatus(AH->connection) == CONNECTION_BAD)
+       die_horribly(AH, "Connection to database '%s' failed.\n%s\n",
+                       AH->dbname, PQerrorMessage(AH->connection));
+
+   /* check for version mismatch */
+   _check_database_version(AH, ignoreVersion);
+
+   return AH->connection;
+}
+
+/* Convenience function to send a query. Monitors result to handle COPY statements */
+int ExecuteSqlCommand(ArchiveHandle* AH, PQExpBuffer qry, char *desc)
+{
+   PGresult        *res;
+
+   /* fprintf(stderr, "Executing: '%s'\n\n", qry->data); */
+   res = PQexec(AH->connection, qry->data);
+   if (!res)
+       die_horribly(AH, "%s: %s. No result from backend.\n", progname, desc);
+
+    if (PQresultStatus(res) != PGRES_COMMAND_OK && PQresultStatus(res) != PGRES_TUPLES_OK)
+   {
+       if (PQresultStatus(res) == PGRES_COPY_IN)
+           AH->pgCopyIn = 1;
+       else 
+           die_horribly(AH, "%s: %s. Code = %d. Explanation from backend: '%s'.\n",
+                       progname, desc, PQresultStatus(res), PQerrorMessage(AH->connection));
+   }
+
+   PQclear(res);
+
+   return strlen(qry->data);
+}
+
+/* Convenience function to send one or more queries. Monitors result to handle COPY statements */
+int ExecuteSqlCommandBuf(ArchiveHandle* AH, void *qryv, int bufLen)
+{
+   int             loc;
+   int             pos = 0;
+   int             sPos = 0;
+   char            *qry = (char*)qryv;
+   int             isEnd = 0;
+   char            *eos = qry + bufLen;
+
+   /* fprintf(stderr, "\n\n*****\n Buffer:\n\n%s\n*******************\n\n", qry); */
+
+   /* If we're in COPY IN mode, then just break it into lines and send... */
+   if (AH->pgCopyIn) {
+       for(;;) {
+
+           /* Find a lf */
+           loc = strcspn(&qry[pos], "\n") + pos;
+           pos = 0;
+
+           /* If no match, then wait */
+           if (loc >= (eos - qry)) /* None found */
+           {
+               appendBinaryPQExpBuffer(AH->pgCopyBuf, qry, (eos - qry));
+               break;
+           };
+
+           /* fprintf(stderr, "Found cr at %d, prev char was %c, next was %c\n", loc, qry[loc-1], qry[loc+1]); */
+   
+           /* Count the number of preceding slashes */
+           sPos = loc;
+           while (sPos > 0 && qry[sPos-1] == '\\')
+               sPos--;
+
+           sPos = loc - sPos;
+
+           /* If an odd number of preceding slashes, then \n was escaped 
+            * so set the next search pos, and restart (if any left).
+            */
+           if ((sPos & 1) == 1)
+           {
+               /* fprintf(stderr, "cr was escaped\n"); */
+               pos = loc + 1;
+               if (pos >= (eos - qry))
+               {
+                   appendBinaryPQExpBuffer(AH->pgCopyBuf, qry, (eos - qry));
+                   break;
+               }
+           }
+           else
+           {
+               /* We got a good cr */
+               qry[loc] = '\0';
+               appendPQExpBuffer(AH->pgCopyBuf, "%s\n", qry);
+               qry += loc + 1; 
+               isEnd = (strcmp(AH->pgCopyBuf->data, "\\.\n") == 0);
+
+               /* fprintf(stderr, "Sending '%s' via COPY (at end = %d)\n\n", AH->pgCopyBuf->data, isEnd); */ 
+               
+               PQputline(AH->connection, AH->pgCopyBuf->data);
+
+               resetPQExpBuffer(AH->pgCopyBuf);
+
+               /* fprintf(stderr, "Buffer is '%s'\n", AH->pgCopyBuf->data); */
+
+               if(isEnd) {
+                   PQendcopy(AH->connection);
+                   AH->pgCopyIn = 0;
+                   break;
+               }
+
+           }
+
+           /* Make sure we're not past the original buffer end */
+           if (qry >= eos)
+               break;
+
+       }
+   }
+
+   /* We may have finished Copy In, and have a non-empty buffer */
+   if (!AH->pgCopyIn) {
+
+       /* 
+        * The following is a mini state machine to assess then of of an SQL statement.
+        * It really only needs to parse good SQL, or at least that's the theory...
+        * End-of-statement is assumed to be an unquoted, un commented semi-colon.
+        */
+
+       /* fprintf(stderr, "Buffer at start is: '%s'\n\n", AH->sqlBuf->data); */
+
+       for(pos=0; pos < (eos - qry); pos++)
+       {
+           appendPQExpBufferChar(AH->sqlBuf, qry[pos]);
+           /* fprintf(stderr, " %c",qry[pos]); */
+
+           switch (AH->sqlparse.state) {
+
+               case SQL_SCAN: /* Default state == 0, set in _allocAH */
+
+                   if (qry[pos] == ';')
+                   {
+                       /* Send It & reset the buffer */
+                       /* fprintf(stderr, "    sending: '%s'\n\n", AH->sqlBuf->data); */
+                       ExecuteSqlCommand(AH, AH->sqlBuf, "Could not execute query");
+                       resetPQExpBuffer(AH->sqlBuf);
+                       AH->sqlparse.lastChar = '\0';
+                   } 
+                   else 
+                   {
+                       if (qry[pos] == '"' || qry[pos] == '\'')
+                       {   
+                           /* fprintf(stderr,"[startquote]\n"); */
+                           AH->sqlparse.state = SQL_IN_QUOTE;
+                           AH->sqlparse.quoteChar = qry[pos];
+                           AH->sqlparse.backSlash = 0;
+                       } 
+                       else if (qry[pos] == '-' && AH->sqlparse.lastChar == '-')
+                       {
+                           AH->sqlparse.state = SQL_IN_SQL_COMMENT;
+                       } 
+                       else if (qry[pos] == '*' && AH->sqlparse.lastChar == '/')
+                       {
+                           AH->sqlparse.state = SQL_IN_EXT_COMMENT;
+                       }
+                       AH->sqlparse.lastChar = qry[pos];
+                   }
+
+                   break;
+
+               case SQL_IN_SQL_COMMENT:
+
+                   if (qry[pos] == '\n')
+                       AH->sqlparse.state = SQL_SCAN;
+                   break;
+
+               case SQL_IN_EXT_COMMENT:
+
+                   if (AH->sqlparse.lastChar == '*' && qry[pos] == '/')
+                       AH->sqlparse.state = SQL_SCAN;
+                   break;
+
+               case SQL_IN_QUOTE:
+
+                   if (!AH->sqlparse.backSlash && AH->sqlparse.quoteChar == qry[pos])
+                   {
+                       /* fprintf(stderr,"[endquote]\n"); */
+                       AH->sqlparse.state = SQL_SCAN;
+                   } 
+                   else 
+                   {
+
+                       if (qry[pos] == '\\')
+                       {
+                           if (AH->sqlparse.lastChar == '\\')
+                               AH->sqlparse.backSlash = !AH->sqlparse.backSlash;
+                           else
+                               AH->sqlparse.backSlash = 1;
+                           } else {
+                               AH->sqlparse.backSlash = 0;
+                       }
+                   }
+                   break;
+
+           }
+           AH->sqlparse.lastChar = qry[pos];
+           /* fprintf(stderr, "\n"); */
+       }
+
+   }
+
+   return 1;
+}
+
+void FixupBlobRefs(ArchiveHandle *AH, char *tablename)
+{
+   PQExpBuffer     tblQry = createPQExpBuffer();
+   PGresult        *res, *uRes;
+   int             i, n;
+   char            *attr;
+
+   for(i=0 ; i < strlen(tablename) ; i++)
+       tablename[i] = tolower(tablename[i]);
+
+   if (strcmp(tablename, BLOB_XREF_TABLE) == 0)
+       return;
+
+   appendPQExpBuffer(tblQry, "SELECT a.attname FROM pg_class c, pg_attribute a, pg_type t "
+                               " WHERE a.attnum > 0 AND a.attrelid = c.oid AND a.atttypid = t.oid "
+                               " AND t.typname = 'oid' AND c.relname = '%s';", tablename);
+
+   res = PQexec(AH->connection, tblQry->data);
+   if (!res)
+       die_horribly(AH, "%s: could not find OID attrs of %s. Explanation from backend '%s'\n",
+                       progname, tablename, PQerrorMessage(AH->connection));
+
+   if ((n = PQntuples(res)) == 0) {
+       /* We're done */
+       ahlog(AH, 1, "No OID attributes in table %s\n", tablename);
+       PQclear(res);
+       return;
+   }
+
+   for (i = 0 ; i < n ; i++)
+   {
+       attr = PQgetvalue(res, i, 0);
+
+       ahlog(AH, 1, " - %s.%s\n", tablename, attr);
+
+       resetPQExpBuffer(tblQry);
+       appendPQExpBuffer(tblQry, "Update \"%s\" Set \"%s\" = x.newOid From %s x "
+                                   "Where x.oldOid = \"%s\".\"%s\";",
+
+                                   tablename, attr, BLOB_XREF_TABLE, tablename, attr);
+
+       ahlog(AH, 10, " - sql = %s\n", tblQry->data);
+
+       uRes = PQexec(AH->connection, tblQry->data);
+       if (!uRes)
+           die_horribly(AH, "%s: could not update attr %s of table %s. Explanation from backend '%s'\n",
+                               progname, attr, tablename, PQerrorMessage(AH->connection));
+
+       if ( PQresultStatus(uRes) != PGRES_COMMAND_OK )
+           die_horribly(AH, "%s: error while updating attr %s of table %s. Explanation from backend '%s'\n",
+                               progname, attr, tablename, PQerrorMessage(AH->connection));
+
+       PQclear(uRes);
+   }
+
+   PQclear(res);
+
+}
+
+/**********
+ * Convenient SQL calls
+ **********/
+void CreateBlobXrefTable(ArchiveHandle* AH)
+{
+   PQExpBuffer     qry = createPQExpBuffer();
+
+   ahlog(AH, 1, "Creating table for BLOBS xrefs\n");
+
+   appendPQExpBuffer(qry, "Create Temporary Table %s(oldOid oid, newOid oid);", BLOB_XREF_TABLE);
+
+   ExecuteSqlCommand(AH, qry, "can not create BLOB xref table '" BLOB_XREF_TABLE "'");
+
+   resetPQExpBuffer(qry);
+
+   appendPQExpBuffer(qry, "Create Unique Index %s_ix on %s(oldOid)", BLOB_XREF_TABLE, BLOB_XREF_TABLE);
+   ExecuteSqlCommand(AH, qry, "can not create index on BLOB xref table '" BLOB_XREF_TABLE "'");
+}
+
+void InsertBlobXref(ArchiveHandle* AH, int old, int new)
+{
+   PQExpBuffer     qry = createPQExpBuffer();
+
+   appendPQExpBuffer(qry, "Insert Into %s(oldOid, newOid) Values (%d, %d);", BLOB_XREF_TABLE, old, new);
+
+   ExecuteSqlCommand(AH, qry, "can not create BLOB xref entry");
+}
+
+void StartTransaction(ArchiveHandle* AH)
+{
+   PQExpBuffer     qry = createPQExpBuffer();
+
+   appendPQExpBuffer(qry, "Begin;");
+
+   ExecuteSqlCommand(AH, qry, "can not start database transaction");
+}
+
+void CommitTransaction(ArchiveHandle* AH)
+{
+    PQExpBuffer     qry = createPQExpBuffer();
+
+    appendPQExpBuffer(qry, "Commit;");
+
+    ExecuteSqlCommand(AH, qry, "can not commit database transaction");
+}
+
+
diff --git a/src/bin/pg_dump/pg_backup_db.h b/src/bin/pg_dump/pg_backup_db.h
new file mode 100644 (file)
index 0000000..5d03967
--- /dev/null
@@ -0,0 +1,16 @@
+/*
+ * Definitions for pg_backup_db.c
+ *
+ */
+
+#define BLOB_XREF_TABLE "dump_blob_xref" /* MUST be lower case */
+
+extern void FixupBlobRefs(ArchiveHandle *AH, char *tablename);
+extern int ExecuteSqlCommand(ArchiveHandle* AH, PQExpBuffer qry, char *desc);
+extern int ExecuteSqlCommandBuf(ArchiveHandle* AH, void *qry, int bufLen);
+
+extern void CreateBlobXrefTable(ArchiveHandle* AH);
+extern void InsertBlobXref(ArchiveHandle* AH, int old, int new);
+extern void StartTransaction(ArchiveHandle* AH);
+extern void CommitTransaction(ArchiveHandle* AH);
+
diff --git a/src/bin/pg_dump/pg_backup_null.c b/src/bin/pg_dump/pg_backup_null.c
new file mode 100644 (file)
index 0000000..e6f81bb
--- /dev/null
@@ -0,0 +1,114 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_backup_null.c
+ *
+ * Implementation of an archive that is never saved; it is used by 
+ * pg_dump to output output a plain text SQL script instead of save
+ * a real archive.
+ *
+ * See the headers to pg_restore for more details.
+ *
+ * Copyright (c) 2000, Philip Warner
+ *      Rights are granted to use this software in any way so long
+ *      as this notice is not removed.
+ *
+ * The author is not responsible for loss or damages that may
+ * result from it's use.
+ *
+ *
+ * IDENTIFICATION
+ *
+ * Modifications - 09-Jul-2000 - [email protected]
+ *
+ * Initial version. 
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include 
+#include 
+#include  /* for dup */
+#include "pg_backup.h"
+#include "pg_backup_archiver.h"
+
+static int _WriteData(ArchiveHandle* AH, const void* data, int dLen);
+static void     _EndData(ArchiveHandle* AH, TocEntry* te);
+static int      _WriteByte(ArchiveHandle* AH, const int i);
+static int      _WriteBuf(ArchiveHandle* AH, const void* buf, int len);
+static void     _CloseArchive(ArchiveHandle* AH);
+static void    _PrintTocData(ArchiveHandle* AH, TocEntry* te, RestoreOptions *ropt);
+
+/*
+ *  Initializer
+ */
+void InitArchiveFmt_Null(ArchiveHandle* AH) 
+{
+    /* Assuming static functions, this can be copied for each format. */
+    AH->WriteDataPtr = _WriteData;
+    AH->EndDataPtr = _EndData;
+    AH->WriteBytePtr = _WriteByte;
+    AH->WriteBufPtr = _WriteBuf;
+    AH->ClosePtr = _CloseArchive;
+    AH->PrintTocDataPtr = _PrintTocData;
+
+    /*
+     * Now prevent reading...
+     */
+    if (AH->mode == archModeRead)
+   die_horribly(AH, "%s: This format can not be read\n");
+
+}
+
+/*
+ * - Start a new TOC entry
+ */
+
+/*------
+ * Called by dumper via archiver from within a data dump routine 
+ * As at V1.3, this is only called for COPY FROM dfata, and BLOB data
+ *------
+ */
+static int _WriteData(ArchiveHandle* AH, const void* data, int dLen)
+{
+    /* Just send it to output */
+    ahwrite(data, 1, dLen, AH);
+    return dLen;
+}
+
+static void    _EndData(ArchiveHandle* AH, TocEntry* te)
+{
+    ahprintf(AH, "\n\n");
+}
+
+/*------
+ * Called as part of a RestoreArchive call; for the NULL archive, this
+ * just sends the data for a given TOC entry to the output.
+ *------
+ */
+static void    _PrintTocData(ArchiveHandle* AH, TocEntry* te, RestoreOptions *ropt)
+{
+    if (*te->dataDumper)
+   {
+       AH->currToc = te;
+       (*te->dataDumper)((Archive*)AH, te->oid, te->dataDumperArg);
+       AH->currToc = NULL;
+   }
+}
+
+static int _WriteByte(ArchiveHandle* AH, const int i)
+{
+    /* Don't do anything */
+    return 0;
+}
+
+static int _WriteBuf(ArchiveHandle* AH, const void* buf, int len)
+{
+    /* Don't do anything */
+    return len;
+}
+
+static void    _CloseArchive(ArchiveHandle* AH)
+{
+    /* Nothing to do */
+}
+
diff --git a/src/bin/pg_dump/pg_backup_tar.c b/src/bin/pg_dump/pg_backup_tar.c
new file mode 100644 (file)
index 0000000..ca1bdf7
--- /dev/null
@@ -0,0 +1,1132 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_backup_tar.c
+ *
+ * This file is copied from the 'files' format file, but dumps data into
+ * one temp file then sends it to the output TAR archive.
+ *
+ * See the headers to pg_backup_files & pg_restore for more details.
+ *
+ * Copyright (c) 2000, Philip Warner
+ *      Rights are granted to use this software in any way so long
+ *      as this notice is not removed.
+ *
+ * The author is not responsible for loss or damages that may
+ * result from it's use.
+ *
+ *
+ * IDENTIFICATION
+ *
+ * Modifications - 28-Jun-2000 - [email protected]
+ *
+ * Initial version. 
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include 
+#include 
+#include 
+#include 
+#include "pg_backup.h"
+#include "pg_backup_archiver.h"
+#include "pg_backup_tar.h"
+
+static void     _ArchiveEntry(ArchiveHandle* AH, TocEntry* te);
+static void        _StartData(ArchiveHandle* AH, TocEntry* te);
+static int     _WriteData(ArchiveHandle* AH, const void* data, int dLen);
+static void     _EndData(ArchiveHandle* AH, TocEntry* te);
+static int      _WriteByte(ArchiveHandle* AH, const int i);
+static int      _ReadByte(ArchiveHandle* );
+static int      _WriteBuf(ArchiveHandle* AH, const void* buf, int len);
+static int     _ReadBuf(ArchiveHandle* AH, void* buf, int len);
+static void     _CloseArchive(ArchiveHandle* AH);
+static void        _PrintTocData(ArchiveHandle* AH, TocEntry* te, RestoreOptions *ropt);
+static void        _WriteExtraToc(ArchiveHandle* AH, TocEntry* te);
+static void        _ReadExtraToc(ArchiveHandle* AH, TocEntry* te);
+static void        _PrintExtraToc(ArchiveHandle* AH, TocEntry* te);
+
+static void    _StartBlobs(ArchiveHandle* AH, TocEntry* te);
+static void        _StartBlob(ArchiveHandle* AH, TocEntry* te, int oid);
+static void        _EndBlob(ArchiveHandle* AH, TocEntry* te, int oid);
+static void        _EndBlobs(ArchiveHandle* AH, TocEntry* te);
+
+#define K_STD_BUF_SIZE 1024
+
+
+#ifdef HAVE_LIBZ
+   //typedef gzFile    ThingFile;
+   typedef FILE    ThingFile;
+#else
+   typedef FILE    ThingFile;
+#endif
+
+typedef struct {
+   ThingFile       *zFH;
+   FILE            *nFH;
+   FILE            *tarFH;
+   FILE            *tmpFH;
+   char            *targetFile;
+   char            mode;
+   int             pos;
+   int             fileLen;
+   ArchiveHandle   *AH;
+} TAR_MEMBER;
+
+typedef struct {
+   int         hasSeek;
+    int            filePos;
+   TAR_MEMBER  *blobToc;
+   FILE        *tarFH;
+   int         tarFHpos;
+   int         tarNextMember;
+   TAR_MEMBER  *FH;
+   int         isSpecialScript;
+   TAR_MEMBER  *scriptTH;
+} lclContext;
+
+typedef struct {
+   TAR_MEMBER  *TH;
+    char       *filename;
+} lclTocEntry;
+
+static char* progname = "Archiver(tar)";
+
+static void            _LoadBlobs(ArchiveHandle* AH, RestoreOptions *ropt);
+
+static TAR_MEMBER*     tarOpen(ArchiveHandle *AH, const char *filename, char mode);
+static void            tarClose(ArchiveHandle *AH, TAR_MEMBER *TH);
+#ifdef __NOT_USED__
+static char*           tarGets(char *buf, int len, TAR_MEMBER* th);
+#endif
+static int                 tarPrintf(ArchiveHandle *AH, TAR_MEMBER *th, const char *fmt, ...);
+
+static void            _tarAddFile(ArchiveHandle *AH, TAR_MEMBER* th);
+static int                 _tarChecksum(char *th);
+static TAR_MEMBER*     _tarPositionTo(ArchiveHandle *AH, const char *filename);
+static int             tarRead(void *buf, int len, TAR_MEMBER *th);
+static int             tarWrite(const void *buf, int len, TAR_MEMBER *th);
+static void                _tarWriteHeader(TAR_MEMBER* th);
+static int             _tarGetHeader(ArchiveHandle *AH, TAR_MEMBER* th);
+static int             _tarReadRaw(ArchiveHandle *AH, void *buf, int len, TAR_MEMBER *th, FILE *fh);
+
+static int             _scriptOut(ArchiveHandle *AH, const void *buf, int len);
+
+/*
+ *  Initializer
+ */
+void InitArchiveFmt_Tar(ArchiveHandle* AH) 
+{
+    lclContext*        ctx;
+
+    /* Assuming static functions, this can be copied for each format. */
+    AH->ArchiveEntryPtr = _ArchiveEntry;
+    AH->StartDataPtr = _StartData;
+    AH->WriteDataPtr = _WriteData;
+    AH->EndDataPtr = _EndData;
+    AH->WriteBytePtr = _WriteByte;
+    AH->ReadBytePtr = _ReadByte;
+    AH->WriteBufPtr = _WriteBuf;
+    AH->ReadBufPtr = _ReadBuf;
+    AH->ClosePtr = _CloseArchive;
+    AH->PrintTocDataPtr = _PrintTocData;
+    AH->ReadExtraTocPtr = _ReadExtraToc;
+    AH->WriteExtraTocPtr = _WriteExtraToc;
+    AH->PrintExtraTocPtr = _PrintExtraToc;
+
+    AH->StartBlobsPtr = _StartBlobs;
+    AH->StartBlobPtr = _StartBlob;
+    AH->EndBlobPtr = _EndBlob;
+    AH->EndBlobsPtr = _EndBlobs;
+
+    /*
+     * Set up some special context used in compressing data.
+    */
+    ctx = (lclContext*)malloc(sizeof(lclContext));
+    AH->formatData = (void*)ctx;
+    ctx->filePos = 0;
+
+    /*
+     * Now open the TOC file
+     */
+    if (AH->mode == archModeWrite) {
+
+       if (AH->fSpec && strcmp(AH->fSpec,"") != 0) {
+           ctx->tarFH = fopen(AH->fSpec, PG_BINARY_W);
+       } else {
+           ctx->tarFH = stdout;
+       }
+       ctx->tarFHpos = 0;
+
+       /* Make unbuffered since we will dup() it, and the buffers screw each other */
+       //setvbuf(ctx->tarFH, NULL, _IONBF, 0);
+
+       ctx->hasSeek = (fseek(ctx->tarFH, 0, SEEK_CUR) == 0);
+
+       if (AH->compression < 0 || AH->compression > 9) {
+           AH->compression = Z_DEFAULT_COMPRESSION;
+       }
+
+       /* Don't compress into tar files unless asked to do so */
+       if (AH->compression == Z_DEFAULT_COMPRESSION)
+           AH->compression = 0;
+
+       /* We don't support compression because reading the files back is not possible since
+        * gzdopen uses buffered IO which totally screws file positioning.
+        */
+       if (AH->compression != 0)
+           die_horribly(NULL, "%s: Compression not supported in TAR output\n", progname);
+
+    } else { /* Read Mode */
+
+       if (AH->fSpec && strcmp(AH->fSpec,"") != 0) {
+           ctx->tarFH = fopen(AH->fSpec, PG_BINARY_R);
+       } else {
+           ctx->tarFH = stdin;
+       }
+
+       /* Make unbuffered since we will dup() it, and the buffers screw each other */
+       //setvbuf(ctx->tarFH, NULL, _IONBF, 0);
+
+       ctx->tarFHpos = 0;
+
+       ctx->hasSeek = (fseek(ctx->tarFH, 0, SEEK_CUR) == 0);
+
+       /* Forcibly unmark the header as read since we use the lookahead buffer */
+       AH->readHeader = 0;
+
+       ctx->FH = (void*)tarOpen(AH, "toc.dat", 'r');
+       ReadHead(AH);
+       ReadToc(AH);
+       tarClose(AH, ctx->FH); /* Nothing else in the file... */
+    }
+
+}
+
+/*
+ * - Start a new TOC entry
+ *   Setup the output file name.
+ */
+static void    _ArchiveEntry(ArchiveHandle* AH, TocEntry* te) 
+{
+    lclTocEntry*   ctx;
+    char           fn[K_STD_BUF_SIZE];
+
+    ctx = (lclTocEntry*)malloc(sizeof(lclTocEntry));
+    if (te->dataDumper) {
+#ifdef HAVE_LIBZ
+       if (AH->compression == 0) {
+           sprintf(fn, "%d.dat", te->id);
+       } else {
+           sprintf(fn, "%d.dat.gz", te->id);
+       }
+#else
+       sprintf(fn, "%d.dat", te->id);
+#endif
+       ctx->filename = strdup(fn);
+    } else {
+       ctx->filename = NULL;
+       ctx->TH = NULL;
+    }
+    te->formatData = (void*)ctx;
+}
+
+static void    _WriteExtraToc(ArchiveHandle* AH, TocEntry* te)
+{
+    lclTocEntry*   ctx = (lclTocEntry*)te->formatData;
+
+    if (ctx->filename) {
+       WriteStr(AH, ctx->filename);
+    } else {
+       WriteStr(AH, "");
+    }
+}
+
+static void    _ReadExtraToc(ArchiveHandle* AH, TocEntry* te)
+{
+    lclTocEntry*   ctx = (lclTocEntry*)te->formatData;
+
+    if (ctx == NULL) {
+       ctx = (lclTocEntry*)malloc(sizeof(lclTocEntry));
+       te->formatData = (void*)ctx;
+    }
+
+    ctx->filename = ReadStr(AH);
+    if (strlen(ctx->filename) == 0) {
+       free(ctx->filename);
+       ctx->filename = NULL;
+    }
+    ctx->TH = NULL;
+}
+
+static void    _PrintExtraToc(ArchiveHandle* AH, TocEntry* te)
+{
+    lclTocEntry*   ctx = (lclTocEntry*)te->formatData;
+
+    ahprintf(AH, "-- File: %s\n", ctx->filename);
+}
+
+static void    _StartData(ArchiveHandle* AH, TocEntry* te)
+{
+    lclTocEntry*   tctx = (lclTocEntry*)te->formatData;
+
+   tctx->TH = tarOpen(AH, tctx->filename, 'w');
+}
+
+static TAR_MEMBER* tarOpen(ArchiveHandle *AH, const char *filename, char mode)
+{
+   lclContext*     ctx = (lclContext*)AH->formatData;
+   TAR_MEMBER      *tm;
+#ifdef HAVE_LIBZ
+   char            fmode[10];
+#endif
+
+   if (mode == 'r')
+   {
+       tm = _tarPositionTo(AH, filename);
+       if (!tm) /* Not found */
+       {
+           if (filename) /* Couldn't find the requested file. Future: DO SEEK(0) and retry. */
+               die_horribly(AH, "%s: unable to find file '%s' in archive\n", progname, filename);
+           else /* Any file OK, non left, so return NULL */
+               return NULL;
+       }
+
+#ifdef HAVE_LIBZ
+
+       if (AH->compression == 0)
+           tm->nFH = ctx->tarFH;
+       else
+           die_horribly(AH, "%s: compression support is disabled in this format\n", progname);
+           /* tm->zFH = gzdopen(dup(fileno(ctx->tarFH)), "rb"); */
+
+#else
+
+       tm->nFH = ctx->tarFH;
+
+#endif
+
+   } else {
+       tm = calloc(1, sizeof(TAR_MEMBER));
+
+       tm->tmpFH = tmpfile();
+
+#ifdef HAVE_LIBZ
+
+       if (AH->compression != 0)
+       {
+           sprintf(fmode, "wb%d", AH->compression);
+           tm->zFH = gzdopen(dup(fileno(tm->tmpFH)), fmode);
+       } else 
+           tm->nFH = tm->tmpFH;
+
+#else
+
+       tm->nFH = tm->tmpFH;
+
+#endif
+
+       tm->AH = AH;
+       tm->targetFile = strdup(filename);
+   }
+
+   tm->mode = mode;
+   tm->tarFH = ctx->tarFH;
+
+   return tm;
+
+}
+
+static void tarClose(ArchiveHandle *AH, TAR_MEMBER* th)
+{
+   /*
+    * Close the GZ file since we dup'd. This will flush the buffers.
+    */
+   if (AH->compression != 0)
+       GZCLOSE(th->zFH);
+
+   if (th->mode == 'w')
+       _tarAddFile(AH, th); /* This will close the temp file */
+   /* else
+    *      Nothing to do for normal read since we don't dup() normal
+    *      file handle, and we don't use temp files.
+    */
+
+   if (th->targetFile) 
+       free(th->targetFile);
+
+   th->nFH = NULL;
+   th->zFH = NULL;
+}
+
+#ifdef __NOT_USED__
+static char* tarGets(char *buf, int len, TAR_MEMBER* th)
+{
+   char            *s;
+   int             cnt = 0;
+   char            c = ' ';
+   int             eof = 0;
+
+   /* Can't read past logical EOF */
+   if (len > (th->fileLen - th->pos))
+       len = th->fileLen - th->pos;
+
+   while (cnt < len && c != '\n')
+   {
+       if (_tarReadRaw(th->AH, &c, 1, th, NULL) <= 0) {
+           eof = 1;
+           break;
+       }
+       buf[cnt++] = c;
+   }
+
+   if (eof && cnt == 0)
+       s = NULL;
+   else
+   {
+       buf[cnt++] = '\0';
+       s = buf;
+   }
+
+   if (s)
+   {
+       len =  strlen(s);
+       th->pos += len;
+   }
+
+   return s;
+}
+#endif
+
+/* 
+ * Just read bytes from the archive. This is the low level read routine
+ * that is used for ALL reads on a tar file.
+ */
+static int _tarReadRaw(ArchiveHandle *AH, void *buf, int len, TAR_MEMBER *th, FILE *fh)
+{
+   lclContext      *ctx = (lclContext*)AH->formatData;
+   int             avail;
+   int             used = 0;
+   int             res = 0;
+
+   avail = AH->lookaheadLen - AH->lookaheadPos;
+   if (avail > 0)
+   {
+       /* We have some lookahead bytes to use */
+       if (avail >= len) /* Just use the lookahead buffer */
+           used = len;
+       else
+           used = avail;
+
+       /* Copy, and adjust buffer pos */
+       memcpy(buf, AH->lookahead, used);
+       AH->lookaheadPos += used;
+
+       /* Adjust required length */
+       len -= used;
+   }
+
+   /* Read the file if len > 0 */
+   if (len > 0)
+   {
+       if (fh)
+           res = fread(&((char*)buf)[used], 1, len, fh);
+       else if (th)
+       {
+           if (th->zFH)
+               res = GZREAD(&((char*)buf)[used], 1, len, th->zFH);
+           else
+               res = fread(&((char*)buf)[used], 1, len, th->nFH);
+       } 
+       else
+           die_horribly(AH, "%s: neither th nor fh specified in tarReadRaw\n",progname);
+   }
+
+   /* 
+     * fprintf(stderr, "%s: requested %d bytes, got %d from lookahead and %d from file\n", progname, reqLen, used, res);
+    */
+
+   ctx->tarFHpos += res + used;
+
+   return (res + used);
+}
+       
+static int tarRead(void *buf, int len, TAR_MEMBER *th)
+{
+   int res;
+
+   if (th->pos + len > th->fileLen)
+       len = th->fileLen - th->pos;
+
+   if (len <= 0)
+       return 0;
+
+   res = _tarReadRaw(th->AH, buf, len, th, NULL);
+
+   th->pos += res;
+
+   return res;
+}
+
+static int tarWrite(const void *buf, int len, TAR_MEMBER *th)
+{
+   int res;
+
+   if (th->zFH != 0)
+       res = GZWRITE((void*)buf, 1, len, th->zFH);
+   else
+       res = fwrite(buf, 1, len, th->nFH);
+
+   th->pos += res;
+   return res;
+}
+
+static int _WriteData(ArchiveHandle* AH, const void* data, int dLen)
+{
+    lclTocEntry*   tctx = (lclTocEntry*)AH->currToc->formatData;
+
+   tarWrite((void*)data, dLen, tctx->TH);
+
+    //GZWRITE((void*)data, 1, dLen, tctx->TH->FH);
+
+    return dLen;
+}
+
+static void    _EndData(ArchiveHandle* AH, TocEntry* te)
+{
+    lclTocEntry*   tctx = (lclTocEntry*) te->formatData;
+
+    /* Close the file */
+    tarClose(AH, tctx->TH);
+    tctx->TH = NULL;
+}
+
+/* 
+ * Print data for a given file 
+ */
+static void    _PrintFileData(ArchiveHandle* AH, char *filename, RestoreOptions *ropt)
+{
+   lclContext*     ctx = (lclContext*)AH->formatData;
+    char           buf[4096];
+    int                cnt;
+   TAR_MEMBER      *th;
+
+    if (!filename) 
+       return;
+
+   th = tarOpen(AH, filename, 'r');
+   ctx->FH = th;
+
+    while ( (cnt = tarRead(buf, 4095, th)) > 0) {
+       buf[cnt] = '\0';
+       ahwrite(buf, 1, cnt, AH);
+    }
+
+    tarClose(AH, th);
+}
+
+
+/*
+ * Print data for a given TOC entry
+*/
+static void    _PrintTocData(ArchiveHandle* AH, TocEntry* te, RestoreOptions *ropt)
+{
+   lclContext*     ctx = (lclContext*)AH->formatData;
+    lclTocEntry*   tctx = (lclTocEntry*) te->formatData;
+   char            *tmpCopy;
+   int             i, pos1, pos2;
+
+    if (!tctx->filename) 
+       return;
+
+   if (ctx->isSpecialScript)
+   {
+       if (!te->copyStmt)
+           return;
+
+       /* Abort the default COPY */
+       ahprintf(AH, "\\.\n");
+
+       /* Get a copy of the COPY statement and clean it up */
+       tmpCopy = strdup(te->copyStmt);
+       for (i=0 ; i < strlen(tmpCopy) ; i++)
+           tmpCopy[i] = tolower(tmpCopy[i]);
+
+       /*
+        * This is very nasty; we don't know if the archive used WITH OIDS, so
+        * we search the string for it in a paranoid sort of way.
+        */
+       if (strncmp(tmpCopy, "copy ", 5) != 0)
+           die_horribly(AH, "%s: COPY statment badly formatted - could not find 'copy' in '%s'\n", progname, tmpCopy);
+
+       pos1 = 5;
+       for (pos1 = 5; pos1 < strlen(tmpCopy); pos1++)
+           if (tmpCopy[pos1] != ' ')
+               break;  
+
+       if (tmpCopy[pos1] == '"')
+           pos1 += 2;
+       
+       pos1 += strlen(te->name);
+
+       for (pos2 = pos1 ; pos2 < strlen(tmpCopy) ; pos2++)
+           if (strncmp(&tmpCopy[pos2], "from stdin", 10) == 0)
+               break;
+
+       if (pos2 >= strlen(tmpCopy))
+           die_horribly(AH, "%s: COPY statment badly formatted - could not find 'from stdin' in '%s' starting at %d\n",
+                           progname, tmpCopy, pos1);
+
+       ahwrite(tmpCopy, 1, pos2, AH); /* 'copy "table" [with oids]' */
+       ahprintf(AH, " from '$$PATH$$/%s' %s", tctx->filename, &tmpCopy[pos2+10]);
+
+       return;
+   }
+
+   if (strcmp(te->desc, "BLOBS") == 0)
+       _LoadBlobs(AH, ropt);
+   else
+   {
+       _PrintFileData(AH, tctx->filename, ropt);
+   }
+}
+
+/* static void _getBlobTocEntry(ArchiveHandle* AH, int *oid, char fname[K_STD_BUF_SIZE])
+ * {
+ * lclContext*     ctx = (lclContext*)AH->formatData;
+ * char            blobTe[K_STD_BUF_SIZE];
+ * int             fpos;
+ * int             eos;
+ *
+ * if (tarGets(&blobTe[0], K_STD_BUF_SIZE - 1, ctx->blobToc) != NULL)
+ * {
+ *     *oid = atoi(blobTe);
+ *
+ *     fpos = strcspn(blobTe, " ");
+ *
+ *     strncpy(fname, &blobTe[fpos+1], K_STD_BUF_SIZE - 1);
+ *
+ *     eos = strlen(fname)-1;
+ *
+ *     if (fname[eos] == '\n')
+ *         fname[eos] = '\0';
+ *
+ * } else {
+ *
+ *     *oid = 0;
+ *     fname[0] = '\0';
+ * }
+ *}
+ */
+
+static void    _LoadBlobs(ArchiveHandle* AH, RestoreOptions *ropt)
+{
+    int                oid;
+   lclContext*     ctx = (lclContext*)AH->formatData;
+   TAR_MEMBER      *th;    
+   int             cnt;
+   char            buf[4096];
+
+   th = tarOpen(AH, NULL, 'r'); /* Open next file */
+   while (th != NULL)
+   {
+       ctx->FH = th;
+
+       oid = atoi(&th->targetFile[5]);
+
+       if (strncmp(th->targetFile, "blob_",5) == 0 && oid != 0)
+       {
+           ahlog(AH, 1, " - Restoring BLOB oid %d\n", oid);
+
+           StartRestoreBlob(AH, oid);
+
+           while ( (cnt = tarRead(buf, 4095, th)) > 0) {
+               buf[cnt] = '\0';
+               ahwrite(buf, 1, cnt, AH);
+           }
+           EndRestoreBlob(AH, oid);
+       }
+
+       tarClose(AH, th);
+
+       th = tarOpen(AH, NULL, 'r');
+   }
+
+   /*
+    * ctx->blobToc = tarOpen(AH, "blobs.toc", 'r');
+    *
+    * _getBlobTocEntry(AH, &oid, fname);
+    *
+     * while(oid != 0)
+     * {
+    *      StartRestoreBlob(AH, oid);
+    *      _PrintFileData(AH, fname, ropt);
+    *      EndRestoreBlob(AH, oid);
+    *      _getBlobTocEntry(AH, &oid, fname);
+     * }
+    *
+    * tarClose(AH, ctx->blobToc);
+    */ 
+}
+
+
+static int _WriteByte(ArchiveHandle* AH, const int i)
+{
+    lclContext*        ctx = (lclContext*)AH->formatData;
+    int            res;
+   int         b = i;
+
+    res = tarWrite(&b, 1, ctx->FH);
+    if (res != EOF) {
+       ctx->filePos += res;
+    }
+    return res;
+}
+
+static int     _ReadByte(ArchiveHandle* AH)
+{
+    lclContext*        ctx = (lclContext*)AH->formatData;
+    int                res;
+   char            c = '\0';
+
+    res = tarRead(&c, 1, ctx->FH);
+    if (res != EOF) {
+       ctx->filePos += res;
+    }
+    return c;
+}
+
+static int _WriteBuf(ArchiveHandle* AH, const void* buf, int len)
+{
+    lclContext*        ctx = (lclContext*)AH->formatData;
+    int                res;
+
+    res = tarWrite((void*)buf, len, ctx->FH);
+    ctx->filePos += res;
+    return res;
+}
+
+static int _ReadBuf(ArchiveHandle* AH, void* buf, int len)
+{
+    lclContext*        ctx = (lclContext*)AH->formatData;
+    int            res;
+
+    res = tarRead(buf, len, ctx->FH);
+    ctx->filePos += res;
+    return res;
+}
+
+static void    _CloseArchive(ArchiveHandle* AH)
+{
+   lclContext*     ctx = (lclContext*)AH->formatData;
+   TAR_MEMBER      *th;
+   RestoreOptions  *ropt;
+   int             savVerbose;
+
+    if (AH->mode == archModeWrite) {
+
+       /*
+        * Write the Header & TOC to the archive FIRST
+        */
+       th = tarOpen(AH, "toc.dat", 'w');
+       ctx->FH = th;
+       WriteHead(AH);
+       WriteToc(AH);
+       tarClose(AH, th); /* Not needed any more */
+
+       /*
+        * Now send the data (tables & blobs)
+        */
+       WriteDataChunks(AH);
+
+       /* 
+        * Now this format wants to append a script which does a full restore
+        * if the files have been extracted.
+        */
+       th = tarOpen(AH, "restore.sql", 'w');
+       tarPrintf(AH, th, "create temporary table pgdump_restore_path(p text);\n");
+       tarPrintf(AH, th,   "--\n"
+                           "-- NOTE:\n"
+                           "--\n"
+                           "-- File paths need to be edited. Search for $$PATH$$ and\n"
+                           "-- replace it with the path to the directory containing\n"
+                           "-- the extracted data files.\n"
+                           "--\n"
+                           "-- Edit the following to match the path where the\n"
+                           "-- tar archive has been extracted.\n"
+                           "--\n");
+       tarPrintf(AH, th, "insert into pgdump_restore_path values('/tmp');\n\n");
+
+       AH->CustomOutPtr = _scriptOut;
+       ctx->isSpecialScript = 1;
+       ctx->scriptTH = th;
+
+       ropt = NewRestoreOptions();
+       ropt->dropSchema = 1;
+       ropt->compression = 0;
+
+       savVerbose = AH->public.verbose;
+       AH->public.verbose = 0;
+
+       RestoreArchive((Archive*)AH, ropt);
+
+       AH->public.verbose = savVerbose;
+
+       tarClose(AH, th);
+    }
+
+    AH->FH = NULL; 
+}
+
+static int _scriptOut(ArchiveHandle *AH, const void *buf, int len)
+{
+   lclContext*         ctx = (lclContext*)AH->formatData;
+   return tarWrite(buf, len, ctx->scriptTH);
+}
+
+/*
+ * BLOB support
+ */
+
+/*
+ * Called by the archiver when starting to save all BLOB DATA (not schema). 
+ * This routine should save whatever format-specific information is needed
+ * to read the BLOBs back into memory. 
+ *
+ * It is called just prior to the dumper's DataDumper routine.
+ *
+ * Optional, but strongly recommended.
+ *
+ */
+static void    _StartBlobs(ArchiveHandle* AH, TocEntry* te)
+{
+    lclContext*        ctx = (lclContext*)AH->formatData;
+   char            fname[K_STD_BUF_SIZE];
+
+   sprintf(fname, "blobs.toc");
+   ctx->blobToc = tarOpen(AH, fname, 'w');
+
+}
+
+/*
+ * Called by the archiver when the dumper calls StartBlob.
+ *
+ * Mandatory.
+ *
+ * Must save the passed OID for retrieval at restore-time.
+ */
+static void    _StartBlob(ArchiveHandle* AH, TocEntry* te, int oid)
+{
+   lclContext*     ctx = (lclContext*)AH->formatData;
+   lclTocEntry*    tctx = (lclTocEntry*)te->formatData;
+   char            fname[255];
+   char            *sfx;
+
+    if (oid == 0) 
+       die_horribly(AH, "%s: illegal OID for BLOB (%d)\n", progname, oid);
+
+   if (AH->compression != 0)
+       sfx = ".gz";
+   else
+       sfx = "";
+
+   sprintf(fname, "blob_%d.dat%s", oid, sfx);
+
+   tarPrintf(AH, ctx->blobToc, "%d %s\n", oid, fname);
+
+   tctx->TH = tarOpen(AH, fname, 'w');
+
+}
+
+/*
+ * Called by the archiver when the dumper calls EndBlob.
+ *
+ * Optional.
+ *
+ */
+static void    _EndBlob(ArchiveHandle* AH, TocEntry* te, int oid)
+{
+   lclTocEntry*    tctx = (lclTocEntry*)te->formatData;
+
+   tarClose(AH, tctx->TH);
+}
+
+/*
+ * Called by the archiver when finishing saving all BLOB DATA. 
+ *
+ * Optional.
+ *
+ */
+static void    _EndBlobs(ArchiveHandle* AH, TocEntry* te)
+{
+   lclContext*     ctx = (lclContext*)AH->formatData;
+   /* Write out a fake zero OID to mark end-of-blobs. */
+    /* WriteInt(AH, 0); */
+
+   tarClose(AH, ctx->blobToc);
+
+}
+
+
+
+/*------------
+ * TAR Support
+ *------------
+ */
+
+static int tarPrintf(ArchiveHandle *AH, TAR_MEMBER *th, const char *fmt, ...)
+{
+   char        *p = NULL;
+   va_list     ap;
+   int         bSize = strlen(fmt) + 256; /* Should be enough */
+   int         cnt = -1;
+
+   va_start(ap, fmt);
+   /* This is paranoid: deal with the possibility that vsnprintf is willing to ignore trailing null */
+   /* or returns > 0 even if string does not fit. It may be the case that it returns cnt = bufsize */
+   while (cnt < 0 || cnt >= (bSize - 1) ) {
+       if (p != NULL) free(p);
+       bSize *= 2;
+       p = (char*)malloc(bSize);
+       if (p == NULL)
+       {
+           va_end(ap);
+           die_horribly(AH, "%s: could not allocate buffer for ahprintf\n", progname);
+       }
+       cnt = vsnprintf(p, bSize, fmt, ap);
+   }
+   va_end(ap);
+
+   cnt = tarWrite(p, cnt, th);
+
+   free(p);
+   return cnt;
+}
+
+static int _tarChecksum(char *header)
+{
+   int i, sum;
+   sum = 0;
+   for(i = 0; i < 512; i++)
+       if (i < 148 || i >= 156)
+           sum += 0xFF & header[i];
+   return sum + 256; /* Assume 8 blanks in checksum field */
+}
+
+int isValidTarHeader(char *header)
+{
+   int sum;
+   int chk = _tarChecksum(header);
+
+   sscanf(&header[148], "%8o", &sum);
+
+   return (sum == chk && strncmp(&header[257], "ustar  ", 7) == 0);
+}
+
+/* Given the member, write the TAR header & copy the file */
+static void _tarAddFile(ArchiveHandle *AH, TAR_MEMBER* th)
+{
+   lclContext  *ctx = (lclContext*)AH->formatData;
+   FILE        *tmp = th->tmpFH; /* Grab it for convenience */
+   char        buf[32768];
+   int         cnt;
+   int         len = 0;
+   int         i, pad;
+
+   /*
+    * Find file len & go back to start.
+    */
+   fseek(tmp, 0, SEEK_END);
+   th->fileLen = ftell(tmp);
+   fseek(tmp, 0, SEEK_SET);
+
+   _tarWriteHeader(th);
+
+   while ( (cnt = fread(&buf[0], 1, 32767, tmp)) > 0)
+   {
+       fwrite(&buf[0], 1, cnt, th->tarFH);
+       len += cnt;
+   }
+
+   fclose(tmp); /* This *should* delete it... */
+
+   if (len != th->fileLen)
+       die_horribly(AH, "%s: Actual file length does not match expected (%d vs. %d)\n",
+                       progname, len, th->pos);
+
+   pad = ((len + 511) & ~511) - len;
+    for (i=0 ; i < pad ; i++)
+       fputc('\0',th->tarFH);  
+
+   ctx->tarFHpos += len + pad;
+}
+
+/* Locate the file in the archive, read header and position to data */
+static TAR_MEMBER* _tarPositionTo(ArchiveHandle *AH, const char *filename)
+{
+   lclContext      *ctx = (lclContext*)AH->formatData;
+   TAR_MEMBER*     th = calloc(1, sizeof(TAR_MEMBER));
+   char            c;
+   char            header[512];
+   int             i, len, blks, id;
+
+   th->AH = AH;
+
+   /* Go to end of current file, if any */
+   if (ctx->tarFHpos != 0)
+   {
+       ahlog(AH, 4, "Moving from %d (%x) to next member at file position %d (%x)\n", 
+               ctx->tarFHpos, ctx->tarFHpos,
+               ctx->tarNextMember, ctx->tarNextMember);
+
+       while (ctx->tarFHpos < ctx->tarNextMember)
+           _tarReadRaw(AH, &c, 1, NULL, ctx->tarFH);
+   }
+
+   ahlog(AH, 4, "Now at file position %d (%x)\n", ctx->tarFHpos, ctx->tarFHpos);
+
+   /* We are at the start of the file. or at the next member */
+
+   /* Get the header */
+   if (!_tarGetHeader(AH, th))
+   {
+       if (filename)
+           die_horribly(AH, "%s: unable to find header for %s\n", progname, filename);
+       else /* We're just scanning the archibe for the next file, so return null */
+       {
+           free(th);
+           return NULL;
+       }
+   }
+
+    while(filename != NULL && strcmp(th->targetFile, filename) != 0)
+   {
+       ahlog(AH, 4, "Skipping member %s\n", th->targetFile);
+
+       id = atoi(th->targetFile);
+       if ((TocIDRequired(AH, id, AH->ropt) & 2) != 0)
+           die_horribly(AH, "%s: dumping data out of order is not supported in this archive format: "
+                               "%s is required, but comes before %s in the archive file.\n",
+                           progname, th->targetFile, filename);
+
+       /* Header doesn't match, so read to next header */
+       len = ((th->fileLen + 511) & ~511); /* Padded length */
+       blks = len >> 9; /* # of 512 byte blocks */
+
+       for(i=0 ; i < blks ; i++)
+           _tarReadRaw(AH, &header[0], 512, NULL, ctx->tarFH);
+
+       if (!_tarGetHeader(AH, th))
+           die_horribly(AH, "%s: unable to find header for %s\n", progname, filename);
+
+   }
+
+   ctx->tarNextMember = ctx->tarFHpos + ((th->fileLen + 511) & ~511);
+   th->pos = 0;
+
+   return th;
+}
+
+/* Read & verify a header */
+static int _tarGetHeader(ArchiveHandle *AH, TAR_MEMBER* th)
+{
+   lclContext      *ctx = (lclContext*)AH->formatData;
+   char            h[512];
+   char            name[100];
+   int             sum, chk;
+   int             len;
+   int             hPos;
+
+   /*
+    * if ( ftell(ctx->tarFH) != ctx->tarFHpos)
+    *      die_horribly(AH, "%s: mismatch in actual vs. predicted file pos - %d vs. %d\n",
+    *                      progname, ftell(ctx->tarFH), ctx->tarFHpos);
+    */
+
+   hPos = ctx->tarFHpos;
+
+   len = _tarReadRaw(AH, &h[0], 512, NULL, ctx->tarFH);
+   if (len == 0) /* EOF */
+       return 0;
+
+   if (len != 512)
+       die_horribly(AH, "%s: incomplete tar header found (%d bytes)\n", progname, len);
+
+   sscanf(&h[0], "%99s", &name[0]);
+   sscanf(&h[124], "%12o", &len);
+   sscanf(&h[148], "%8o", &sum);
+   chk = _tarChecksum(&h[0]);
+
+   ahlog(AH, 3, "TOC Entry %s at %d (len=%d, chk=%d)\n", &name[0], hPos, len, sum);
+
+   if (chk != sum)
+       die_horribly(AH, "%s: corrupt tar header found in %s "
+                       "(expected %d (%o), computed %d (%o)) file position %d (%x)\n", 
+                       progname, &name[0], sum, sum, chk, chk, ftell(ctx->tarFH), ftell(ctx->tarFH));
+
+   th->targetFile = strdup(name);
+   th->fileLen = len;
+
+   return 1;
+}
+
+static void _tarWriteHeader(TAR_MEMBER* th)
+{
+   char        h[512];
+   int         i;
+   int         lastSum = 0;
+   int         sum;
+
+   for (i = 0 ; i < 512 ; i++)
+       h[i] = '\0';
+
+   /* Name 100 */
+   sprintf(&h[0], "%.99s", th->targetFile);
+
+   /* Mode 8 */
+   sprintf(&h[100], "100600 ");
+
+   /* User ID 8 */
+   sprintf(&h[108], "     0 ");
+
+   /* Group 8 */
+   sprintf(&h[116], "     0 ");
+
+   /* File size 12 */
+   sprintf(&h[124], "%12o", th->fileLen);
+
+   /* Mod Time 12 */
+   sprintf(&h[136], "%12o", (int)time(NULL));
+
+   /* Checksum 8 */
+   sprintf(&h[148], "%8o", lastSum);
+
+   /* Link 1 */
+   sprintf(&h[156], "%c", LF_NORMAL);
+
+   /* Link name 100 (NULL) */
+
+   /* Magic 8 */
+   sprintf(&h[257], "ustar  ");
+
+   /* User 32 */
+   sprintf(&h[265], "%.31s", ""); /* How do I get username reliably? Do I need to? */
+
+   /* Group 32 */
+   sprintf(&h[297], "%.31s", ""); /* How do I get group reliably? Do I need to? */
+
+   /* Maj Dev 8 */
+   // sprintf(&h[329], "%8o", 0);
+
+   /* Min Dev */
+   // sprintf(&h[337], "%8o", 0);
+
+
+   while ( (sum = _tarChecksum(h)) != lastSum)
+   {
+       sprintf(&h[148], "%8o", sum);
+       lastSum = sum;
+   }
+
+   fwrite(h, 1, 512, th->tarFH);
+}
diff --git a/src/bin/pg_dump/pg_backup_tar.h b/src/bin/pg_dump/pg_backup_tar.h
new file mode 100644 (file)
index 0000000..9ae150b
--- /dev/null
@@ -0,0 +1,35 @@
+/* Header
+Offset   Length   Contents
+  0    100 bytes  File name ('\0' terminated, 99 maxmum length)
+100      8 bytes  File mode (in octal ascii)
+108      8 bytes  User ID (in octal ascii)
+116      8 bytes  Group ID (in octal ascii)
+124     12 bytes  File size (s) (in octal ascii)
+136     12 bytes  Modify time (in octal ascii)
+148      8 bytes  Header checksum (in octal ascii)
+156      1 bytes  Link flag
+157    100 bytes  Linkname ('\0' terminated, 99 maxmum length)
+257      8 bytes  Magic ("ustar  \0")
+265     32 bytes  User name ('\0' terminated, 31 maxmum length)
+297     32 bytes  Group name ('\0' terminated, 31 maxmum length)
+329      8 bytes  Major device ID (in octal ascii)
+337      8 bytes  Minor device ID (in octal ascii)
+345    167 bytes  Padding
+512   (s+p)bytes  File contents (s+p) := (((s) + 511) & ~511), round up to 512 bytes
+*/
+
+
+
+
+/* The linkflag defines the type of file */
+#define  LF_OLDNORMAL '\0'       /* Normal disk file, Unix compatible */
+#define  LF_NORMAL    '0'        /* Normal disk file */
+#define  LF_LINK      '1'        /* Link to previously dumped file */
+#define  LF_SYMLINK   '2'        /* Symbolic link */
+#define  LF_CHR       '3'        /* Character special file */
+#define  LF_BLK       '4'        /* Block special file */
+#define  LF_DIR       '5'        /* Directory */
+#define  LF_FIFO      '6'        /* FIFO special file */
+#define  LF_CONTIG    '7'        /* Contiguous file */
+
+