| /*------------------------------------------------------------------------- |
| * |
| * pg_backup_files.c |
| * |
| * This file is copied from the 'custom' format file, but dumps data into |
| * separate files, and the TOC into the 'main' file. |
| * |
| * IT IS FOR DEMONSTRATION PURPOSES ONLY. |
| * |
| * (and could probably be used as a basis for writing a tar file) |
| * |
| * 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 |
| * $PostgreSQL: pgsql/src/bin/pg_dump/pg_backup_files.c,v 1.29.2.2 2007/08/06 01:38:24 tgl Exp $ |
| * |
| *------------------------------------------------------------------------- |
| */ |
| |
| #include "pg_backup_archiver.h" |
| |
| static void _ArchiveEntry(ArchiveHandle *AH, TocEntry *te); |
| static void _StartData(ArchiveHandle *AH, TocEntry *te); |
| static size_t _WriteData(ArchiveHandle *AH, const void *data, size_t dLen); |
| static void _EndData(ArchiveHandle *AH, TocEntry *te); |
| static int _WriteByte(ArchiveHandle *AH, const int i); |
| static int _ReadByte(ArchiveHandle *); |
| static size_t _WriteBuf(ArchiveHandle *AH, const void *buf, size_t len); |
| static size_t _ReadBuf(ArchiveHandle *AH, void *buf, size_t 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, Oid oid); |
| static void _EndBlob(ArchiveHandle *AH, TocEntry *te, Oid oid); |
| static void _EndBlobs(ArchiveHandle *AH, TocEntry *te); |
| |
| #define K_STD_BUF_SIZE 1024 |
| |
| typedef struct |
| { |
| int hasSeek; |
| pgoff_t filePos; |
| FILE *blobToc; |
| } lclContext; |
| |
| typedef struct |
| { |
| #ifdef HAVE_LIBZ |
| gzFile *FH; |
| #else |
| FILE *FH; |
| #endif |
| char *filename; |
| } lclTocEntry; |
| |
| static const char *modulename = gettext_noop("file archiver"); |
| static void _LoadBlobs(ArchiveHandle *AH, RestoreOptions *ropt); |
| static void _getBlobTocEntry(ArchiveHandle *AH, Oid *oid, char *fname); |
| |
| /* |
| * Initializer |
| */ |
| void |
| InitArchiveFmt_Files(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 *) calloc(1, sizeof(lclContext)); |
| AH->formatData = (void *) ctx; |
| ctx->filePos = 0; |
| |
| /* Initialize LO buffering */ |
| AH->lo_buf_size = LOBBUFSIZE; |
| AH->lo_buf = (void *) malloc(LOBBUFSIZE); |
| if (AH->lo_buf == NULL) |
| die_horribly(AH, modulename, "out of memory\n"); |
| |
| /* |
| * Now open the TOC file |
| */ |
| if (AH->mode == archModeWrite) |
| { |
| |
| write_msg(modulename, "WARNING:\n" |
| " This format is for demonstration purposes; it is not intended for\n" |
| " normal use. Files will be written in the current working directory.\n"); |
| |
| if (AH->fSpec && strcmp(AH->fSpec, "") != 0) |
| AH->FH = fopen(AH->fSpec, PG_BINARY_W); |
| else |
| AH->FH = stdout; |
| |
| if (AH->FH == NULL) |
| die_horribly(NULL, modulename, "could not open output file: %s\n", strerror(errno)); |
| |
| ctx->hasSeek = checkSeek(AH->FH); |
| |
| if (AH->compression < 0 || AH->compression > 9) |
| AH->compression = Z_DEFAULT_COMPRESSION; |
| |
| |
| } |
| else |
| { /* Read Mode */ |
| |
| if (AH->fSpec && strcmp(AH->fSpec, "") != 0) |
| AH->FH = fopen(AH->fSpec, PG_BINARY_R); |
| else |
| AH->FH = stdin; |
| |
| if (AH->FH == NULL) |
| die_horribly(NULL, modulename, "could not open input file: %s\n", strerror(errno)); |
| |
| ctx->hasSeek = checkSeek(AH->FH); |
| |
| ReadHead(AH); |
| ReadToc(AH); |
| /* Nothing else in the file... */ |
| if (fclose(AH->FH) != 0) |
| die_horribly(AH, modulename, "could not close TOC file: %s\n", strerror(errno)); |
| } |
| } |
| |
| /* |
| * - 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 *) calloc(1, sizeof(lclTocEntry)); |
| if (te->dataDumper) |
| { |
| #ifdef HAVE_LIBZ |
| if (AH->compression == 0) |
| sprintf(fn, "%d.dat", te->dumpId); |
| else |
| sprintf(fn, "%d.dat.gz", te->dumpId); |
| #else |
| sprintf(fn, "%d.dat", te->dumpId); |
| #endif |
| ctx->filename = strdup(fn); |
| } |
| else |
| { |
| ctx->filename = NULL; |
| ctx->FH = 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 *) calloc(1, sizeof(lclTocEntry)); |
| te->formatData = (void *) ctx; |
| } |
| |
| ctx->filename = ReadStr(AH); |
| if (strlen(ctx->filename) == 0) |
| { |
| free(ctx->filename); |
| ctx->filename = NULL; |
| } |
| ctx->FH = NULL; |
| } |
| |
| static void |
| _PrintExtraToc(ArchiveHandle *AH, TocEntry *te) |
| { |
| lclTocEntry *ctx = (lclTocEntry *) te->formatData; |
| |
| if (AH->public.verbose) |
| ahprintf(AH, "-- File: %s\n", ctx->filename); |
| } |
| |
| static void |
| _StartData(ArchiveHandle *AH, TocEntry *te) |
| { |
| lclTocEntry *tctx = (lclTocEntry *) te->formatData; |
| char fmode[10]; |
| |
| sprintf(fmode, "wb%d", AH->compression); |
| |
| #ifdef HAVE_LIBZ |
| tctx->FH = gzopen(tctx->filename, fmode); |
| #else |
| tctx->FH = fopen(tctx->filename, PG_BINARY_W); |
| #endif |
| |
| if (tctx->FH == NULL) |
| die_horribly(AH, modulename, "could not open output file: %s\n", strerror(errno)); |
| } |
| |
| static size_t |
| _WriteData(ArchiveHandle *AH, const void *data, size_t dLen) |
| { |
| lclTocEntry *tctx = (lclTocEntry *) AH->currToc->formatData; |
| |
| GZWRITE((void *) data, 1, dLen, tctx->FH); |
| |
| return dLen; |
| } |
| |
| static void |
| _EndData(ArchiveHandle *AH, TocEntry *te) |
| { |
| lclTocEntry *tctx = (lclTocEntry *) te->formatData; |
| |
| /* Close the file */ |
| if (GZCLOSE(tctx->FH) != 0) |
| die_horribly(AH, modulename, "could not close data file\n"); |
| |
| tctx->FH = NULL; |
| } |
| |
| /* |
| * Print data for a given file |
| */ |
| static void |
| _PrintFileData(ArchiveHandle *AH, char *filename, RestoreOptions *ropt) |
| { |
| char buf[4096]; |
| size_t cnt; |
| |
| if (!filename) |
| return; |
| |
| #ifdef HAVE_LIBZ |
| AH->FH = gzopen(filename, "rb"); |
| #else |
| AH->FH = fopen(filename, PG_BINARY_R); |
| #endif |
| |
| if (AH->FH == NULL) |
| die_horribly(AH, modulename, "could not open input file: %s\n", strerror(errno)); |
| |
| while ((cnt = GZREAD(buf, 1, 4095, AH->FH)) > 0) |
| { |
| buf[cnt] = '\0'; |
| ahwrite(buf, 1, cnt, AH); |
| } |
| |
| if (GZCLOSE(AH->FH) != 0) |
| die_horribly(AH, modulename, "could not close data file after reading\n"); |
| } |
| |
| |
| /* |
| * Print data for a given TOC entry |
| */ |
| static void |
| _PrintTocData(ArchiveHandle *AH, TocEntry *te, RestoreOptions *ropt) |
| { |
| lclTocEntry *tctx = (lclTocEntry *) te->formatData; |
| |
| if (!tctx->filename) |
| return; |
| |
| if (strcmp(te->desc, "BLOBS") == 0) |
| _LoadBlobs(AH, ropt); |
| else |
| _PrintFileData(AH, tctx->filename, ropt); |
| } |
| |
| static void |
| _getBlobTocEntry(ArchiveHandle *AH, Oid *oid, char fname[K_STD_BUF_SIZE]) |
| { |
| lclContext *ctx = (lclContext *) AH->formatData; |
| char blobTe[K_STD_BUF_SIZE]; |
| size_t fpos; |
| size_t eos; |
| |
| if (fgets(&blobTe[0], K_STD_BUF_SIZE - 1, ctx->blobToc) != NULL) |
| { |
| *oid = atooid(blobTe); |
| |
| fpos = strcspn(blobTe, " "); |
| |
| strlcpy(fname, &blobTe[fpos + 1], K_STD_BUF_SIZE); |
| |
| eos = strlen(fname) - 1; |
| |
| if (fname[eos] == '\n') |
| fname[eos] = '\0'; |
| } |
| else |
| { |
| *oid = 0; |
| fname[0] = '\0'; |
| } |
| } |
| |
| static void |
| _LoadBlobs(ArchiveHandle *AH, RestoreOptions *ropt) |
| { |
| Oid oid; |
| lclContext *ctx = (lclContext *) AH->formatData; |
| char fname[K_STD_BUF_SIZE]; |
| |
| StartRestoreBlobs(AH); |
| |
| ctx->blobToc = fopen("blobs.toc", PG_BINARY_R); |
| |
| if (ctx->blobToc == NULL) |
| die_horribly(AH, modulename, "could not open large object TOC for input: %s\n", strerror(errno)); |
| |
| _getBlobTocEntry(AH, &oid, fname); |
| |
| while (oid != 0) |
| { |
| StartRestoreBlob(AH, oid); |
| _PrintFileData(AH, fname, ropt); |
| EndRestoreBlob(AH, oid); |
| _getBlobTocEntry(AH, &oid, fname); |
| } |
| |
| if (fclose(ctx->blobToc) != 0) |
| die_horribly(AH, modulename, "could not close large object TOC file: %s\n", strerror(errno)); |
| |
| EndRestoreBlobs(AH); |
| } |
| |
| |
| static int |
| _WriteByte(ArchiveHandle *AH, const int i) |
| { |
| lclContext *ctx = (lclContext *) AH->formatData; |
| |
| if (fputc(i, AH->FH) == EOF) |
| die_horribly(AH, modulename, "could not write byte\n"); |
| |
| ctx->filePos += 1; |
| |
| return 1; |
| } |
| |
| static int |
| _ReadByte(ArchiveHandle *AH) |
| { |
| lclContext *ctx = (lclContext *) AH->formatData; |
| int res; |
| |
| res = getc(AH->FH); |
| if (res == EOF) |
| die_horribly(AH, modulename, "unexpected end of file\n"); |
| ctx->filePos += 1; |
| return res; |
| } |
| |
| static size_t |
| _WriteBuf(ArchiveHandle *AH, const void *buf, size_t len) |
| { |
| lclContext *ctx = (lclContext *) AH->formatData; |
| size_t res; |
| |
| res = fwrite(buf, 1, len, AH->FH); |
| if (res != len) |
| die_horribly(AH, modulename, "could not write to output file: %s\n", strerror(errno)); |
| |
| ctx->filePos += res; |
| return res; |
| } |
| |
| static size_t |
| _ReadBuf(ArchiveHandle *AH, void *buf, size_t len) |
| { |
| lclContext *ctx = (lclContext *) AH->formatData; |
| size_t res; |
| |
| res = fread(buf, 1, len, AH->FH); |
| ctx->filePos += res; |
| return res; |
| } |
| |
| static void |
| _CloseArchive(ArchiveHandle *AH) |
| { |
| if (AH->mode == archModeWrite) |
| { |
| WriteHead(AH); |
| WriteToc(AH); |
| if (fclose(AH->FH) != 0) |
| die_horribly(AH, modulename, "could not close TOC file: %s\n", strerror(errno)); |
| WriteDataChunks(AH); |
| } |
| |
| AH->FH = NULL; |
| } |
| |
| |
| |
| /* |
| * 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 = fopen(fname, PG_BINARY_W); |
| |
| if (ctx->blobToc == NULL) |
| die_horribly(AH, modulename, |
| "could not open large object TOC for output: %s\n", strerror(errno)); |
| } |
| |
| /* |
| * 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, Oid oid) |
| { |
| lclContext *ctx = (lclContext *) AH->formatData; |
| lclTocEntry *tctx = (lclTocEntry *) te->formatData; |
| char fmode[10]; |
| char fname[255]; |
| char *sfx; |
| |
| if (oid == 0) |
| die_horribly(AH, modulename, "invalid OID for large object (%u)\n", oid); |
| |
| if (AH->compression != 0) |
| sfx = ".gz"; |
| else |
| sfx = ""; |
| |
| sprintf(fmode, "wb%d", AH->compression); |
| sprintf(fname, "blob_%u.dat%s", oid, sfx); |
| |
| fprintf(ctx->blobToc, "%u %s\n", oid, fname); |
| |
| #ifdef HAVE_LIBZ |
| tctx->FH = gzopen(fname, fmode); |
| #else |
| tctx->FH = fopen(fname, PG_BINARY_W); |
| #endif |
| |
| if (tctx->FH == NULL) |
| die_horribly(AH, modulename, "could not open large object file \"%s\" for input: %s\n", |
| fname, strerror(errno)); |
| } |
| |
| /* |
| * Called by the archiver when the dumper calls EndBlob. |
| * |
| * Optional. |
| */ |
| static void |
| _EndBlob(ArchiveHandle *AH, TocEntry *te, Oid oid) |
| { |
| lclTocEntry *tctx = (lclTocEntry *) te->formatData; |
| |
| if (GZCLOSE(tctx->FH) != 0) |
| die_horribly(AH, modulename, "could not close large object file\n"); |
| } |
| |
| /* |
| * 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); */ |
| |
| if (fclose(ctx->blobToc) != 0) |
| die_horribly(AH, modulename, "could not close large object TOC file: %s\n", strerror(errno)); |
| } |