From c3e18804ff14f690a6d8c31b452476d0f8fcec28 Mon Sep 17 00:00:00 2001 From: Philip Warner Date: Fri, 21 Jul 2000 11:43:26 +0000 Subject: [PATCH] - Support for TAR output - 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 | 497 +++++++++++++ src/bin/pg_dump/pg_backup_db.h | 16 + src/bin/pg_dump/pg_backup_null.c | 114 +++ src/bin/pg_dump/pg_backup_tar.c | 1132 ++++++++++++++++++++++++++++++ src/bin/pg_dump/pg_backup_tar.h | 35 + 5 files changed, 1794 insertions(+) create mode 100644 src/bin/pg_dump/pg_backup_db.c create mode 100644 src/bin/pg_dump/pg_backup_db.h create mode 100644 src/bin/pg_dump/pg_backup_null.c create mode 100644 src/bin/pg_dump/pg_backup_tar.c create mode 100644 src/bin/pg_dump/pg_backup_tar.h diff --git a/src/bin/pg_dump/pg_backup_db.c b/src/bin/pg_dump/pg_backup_db.c new file mode 100644 index 0000000000..c98e938d4f --- /dev/null +++ b/src/bin/pg_dump/pg_backup_db.c @@ -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 index 0000000000..5d03967f58 --- /dev/null +++ b/src/bin/pg_dump/pg_backup_db.h @@ -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 index 0000000000..e6f81bb31f --- /dev/null +++ b/src/bin/pg_dump/pg_backup_null.c @@ -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 - pjw@rhyme.com.au + * + * 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 index 0000000000..ca1bdf7a61 --- /dev/null +++ b/src/bin/pg_dump/pg_backup_tar.c @@ -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 - pjw@rhyme.com.au + * + * 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 index 0000000000..9ae150b4b5 --- /dev/null +++ b/src/bin/pg_dump/pg_backup_tar.h @@ -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 */ + +