LLiHengsync code
de223dd1创建于 2022年3月4日历史提交
/* -------------------------------------------------------------------------
 *
 * pg_backup_custom.c
 *
 *	Implements the custom output format.
 *
 *	The comments with the routined in this code are a good place to
 *	understand how to write a new format.
 *
 *	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
 *	and any liability will be limited to the time taken to fix any
 *	related bug.
 *
 *
 * IDENTIFICATION
 *		src/bin/pg_dump/pg_backup_custom.c
 *
 * -------------------------------------------------------------------------
 */

#include "compress_io.h"
#include "dumputils.h"
#include "dumpmem.h"

#ifdef GAUSS_SFT_TEST
#include "gauss_sft.h"
#endif

/* 
 * --------
 * Routines in the format interface
 * --------
 */
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 _ReopenArchive(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 _PrintData(ArchiveHandle* AH);
static void _skipData(ArchiveHandle* AH);
static void _skipBlobs(ArchiveHandle* AH);

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);
static void _LoadBlobs(ArchiveHandle* AH, bool drop);
static void _Clone(ArchiveHandle* AH);
static void _DeClone(ArchiveHandle* AH);

typedef struct {
    CompressorState* cs;
    int hasSeek;
    pgoff_t filePos;
    pgoff_t dataStart;
} lclContext;

typedef struct {
    int dataState;
    pgoff_t dataPos;
} lclTocEntry;

/* ------
 * Static declarations
 * ------
 */
static void _readBlockHeader(ArchiveHandle* AH, int* type, int* id);
static pgoff_t _getFilePos(ArchiveHandle* AH, lclContext* ctx);

static size_t _CustomWriteFunc(ArchiveHandle* AH, const char* buf, size_t len);
static size_t _CustomReadFunc(ArchiveHandle* AH, char** buf, size_t* buflen);

/* translator: this is a module name */
static const char* modulename = gettext_noop("custom archiver");

/*
 *	Init routine required by ALL formats. This is a global routine
 *	and should be declared in pg_backup_archiver.h
 *
 *	It's task is to create any extra archive context (using AH->formatData),
 *	and to initialize the supported function pointers.
 *
 *	It should also prepare whatever it's input source is for reading/writing,
 *	and in the case of a read mode connection, it should load the Header & TOC.
 */
void InitArchiveFmt_Custom(ArchiveHandle* AH)
{
    lclContext* ctx = NULL;

    /* 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->Reopenptr = _ReopenArchive;
    AH->PrintTocDataptr = _PrintTocData;
    AH->ReadExtraTocptr = _ReadExtraToc;
    AH->WriteExtraTocptr = _WriteExtraToc;
    AH->PrintExtraTocptr = _PrintExtraToc;

    AH->StartBlobsptr = _StartBlobs;
    AH->StartBlobptr = _StartBlob;
    AH->EndBlobptr = _EndBlob;
    AH->EndBlobsptr = _EndBlobs;
    AH->Cloneptr = _Clone;
    AH->DeCloneptr = _DeClone;

    /* Set up a private area. */
    ctx = (lclContext*)pg_calloc(1, sizeof(lclContext));
    AH->formatData = (void*)ctx;

    /* Initialize LO buffering */
    AH->lo_buf_size = LOBBUFSIZE;
    AH->lo_buf = (void*)pg_malloc(LOBBUFSIZE);

    ctx->filePos = 0;

    /*
     * Now open the file
     */
    if (AH->mode == archModeWrite) {
        if ((AH->fSpec != NULL) && strcmp(AH->fSpec, "") != 0) {
            AH->FH = fopen(AH->fSpec, PG_BINARY_W);
            if (AH->FH == NULL)
                exit_horribly(modulename, "could not open output file \"%s\": %s\n", AH->fSpec, strerror(errno));

            /* update file permission */
            if (chmod(AH->fSpec, FILE_PERMISSION) == -1) {
                exit_horribly(
                    modulename, "changing permissions for file \"%s\" failed with: %s\n", AH->fSpec, strerror(errno));
            }
        } else {
            AH->FH = stdout;
            if (AH->FH == NULL)
                exit_horribly(modulename, "could not open output file: %s\n", strerror(errno));
        }

        ctx->hasSeek = checkSeek(AH->FH);
    } else {
        if ((AH->fSpec != NULL) && strcmp(AH->fSpec, "") != 0) {
            AH->FH = fopen(AH->fSpec, PG_BINARY_R);
            if (AH->FH == NULL)
                exit_horribly(modulename, "could not open input file \"%s\": %s\n", AH->fSpec, strerror(errno));
        } else {
            AH->FH = stdin;
            if (AH->FH == NULL) {
                exit_horribly(modulename, "could not open input file: %s\n", strerror(errno));
            }
        }

        ctx->hasSeek = checkSeek(AH->FH);

        ReadHead(AH);
        ReadToc(AH);
        ctx->dataStart = _getFilePos(AH, ctx);
    }
}

/*
 * Called by the Archiver when the dumper creates a new TOC entry.
 *
 * Optional.
 *
 * Set up extrac format-related TOC data.
 */
static void _ArchiveEntry(ArchiveHandle* AH, TocEntry* te)
{
    lclTocEntry* ctx = NULL;

    ctx = (lclTocEntry*)pg_calloc(1, sizeof(lclTocEntry));
    if ((te->dataDumper) != NULL) {
        ctx->dataState = K_OFFSET_POS_NOT_SET;
    } else {
        ctx->dataState = K_OFFSET_NO_DATA;
    }
    te->formatData = (void*)ctx;
}

#ifdef ENABLE_UT
void uttest_custom_ArchiveEntry(ArchiveHandle* AH, TocEntry* te)
{
    _ArchiveEntry(AH, te);
}
#endif

/*
 * Called by the Archiver to save any extra format-related TOC entry
 * data.
 *
 * Optional.
 *
 * Use the Archiver routines to write data - they are non-endian, and
 * maintain other important file information.
 */
static void _WriteExtraToc(ArchiveHandle* AH, TocEntry* te)
{
    lclTocEntry* ctx = (lclTocEntry*)te->formatData;

    WriteOffset(AH, ctx->dataPos, ctx->dataState);
}

/*
 * Called by the Archiver to read any extra format-related TOC data.
 *
 * Optional.
 *
 * Needs to match the order defined in _WriteExtraToc, and should also
 * use the Archiver input routines.
 */
static void _ReadExtraToc(ArchiveHandle* AH, TocEntry* te)
{
    lclTocEntry* ctx = (lclTocEntry*)te->formatData;

    if (ctx == NULL) {
        ctx = (lclTocEntry*)pg_calloc(1, sizeof(lclTocEntry));
        te->formatData = (void*)ctx;
    }

    ctx->dataState = ReadOffset(AH, &(ctx->dataPos));

    /*
     * Prior to V1.7 (pg7.3), we dumped the data size as an int now we don't
     * dump it at all.
     */
    if (AH->version < K_VERS_1_7) {
        ReadInt(AH);
    }
}

/*
 * Called by the Archiver when restoring an archive to output a comment
 * that includes useful information about the TOC entry.
 *
 * Optional.
 *
 */
static void _PrintExtraToc(ArchiveHandle* AH, TocEntry* te)
{
    lclTocEntry* ctx = (lclTocEntry*)te->formatData;

    if (AH->publicArc.verbose) {
        ahprintf(AH, "-- Data Pos: " INT64_FORMAT "\n", (int64)ctx->dataPos);
    }
}

/*
 * Called by the archiver when saving TABLE DATA (not schema). This routine
 * should save whatever format-specific information is needed to read
 * the archive back.
 *
 * It is called just prior to the dumper's 'DataDumper' routine being called.
 *
 * Optional, but strongly recommended.
 *
 */
static void _StartData(ArchiveHandle* AH, TocEntry* te)
{
    lclContext* ctx = (lclContext*)AH->formatData;
    lclTocEntry* tctx = (lclTocEntry*)te->formatData;

    tctx->dataPos = _getFilePos(AH, ctx);
    tctx->dataState = K_OFFSET_POS_SET;

    _WriteByte(AH, BLK_DATA); /* Block type */
    WriteInt(AH, te->dumpId); /* For sanity check */

    ctx->cs = AllocateCompressor(AH->compression, _CustomWriteFunc);
}

/*
 * Called by archiver when dumper calls WriteData. This routine is
 * called for both BLOB and TABLE data; it is the responsibility of
 * the format to manage each kind of data using StartBlob/StartData.
 *
 * It should only be called from within a DataDumper routine.
 *
 * Mandatory.
 */
static size_t _WriteData(ArchiveHandle* AH, const void* data, size_t dLen)
{
    lclContext* ctx = (lclContext*)AH->formatData;
    CompressorState* cs = ctx->cs;

    if (dLen == 0) {
        return 0;
    }

    return WriteDataToArchive(AH, cs, data, dLen);
}

/*
 * Called by the archiver when a dumper's 'DataDumper' routine has
 * finished.
 *
 * Optional.
 *
 */
static void _EndData(ArchiveHandle* AH, TocEntry* te)
{
    lclContext* ctx = (lclContext*)AH->formatData;

    EndCompressor(AH, ctx->cs);
    /* Send the end marker */
    WriteInt(AH, 0);
}

/*
 * 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;
    lclTocEntry* tctx = (lclTocEntry*)te->formatData;

    tctx->dataPos = _getFilePos(AH, ctx);
    tctx->dataState = K_OFFSET_POS_SET;

    _WriteByte(AH, BLK_BLOBS); /* Block type */
    WriteInt(AH, te->dumpId);  /* For sanity check */
}

/*
 * 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;

    if (oid == 0) {
        exit_horribly(modulename, "invalid OID for large object\n");
    }
    WriteInt(AH, oid);

    ctx->cs = AllocateCompressor(AH->compression, _CustomWriteFunc);
}

/*
 * Called by the archiver when the dumper calls EndBlob.
 *
 * Optional.
 */
static void _EndBlob(ArchiveHandle* AH, TocEntry* te, Oid oid)
{
    lclContext* ctx = (lclContext*)AH->formatData;

    EndCompressor(AH, ctx->cs);
    /* Send the end marker */
    WriteInt(AH, 0);
}

/*
 * Called by the archiver when finishing saving all BLOB DATA.
 *
 * Optional.
 */
static void _EndBlobs(ArchiveHandle* AH, TocEntry* te)
{
    /* Write out a fake zero OID to mark end-of-blobs. */
    WriteInt(AH, 0);
}

/*
 * 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;
    int blkType;
    int id;

    if (tctx->dataState == K_OFFSET_NO_DATA) {
        return;
    }

    if (!ctx->hasSeek || tctx->dataState == K_OFFSET_POS_NOT_SET) {
        /*
         * We cannot seek directly to the desired block.  Instead, skip over
         * block headers until we find the one we want.  This could fail if we
         * are asked to restore items out-of-order.
         */
        _readBlockHeader(AH, &blkType, &id);

        while (blkType != EOF && id != te->dumpId) {
            switch (blkType) {
                case BLK_DATA:
                    _skipData(AH);
                    break;

                case BLK_BLOBS:
                    _skipBlobs(AH);
                    break;

                default: /* Always have a default */
                    exit_horribly(modulename, "unrecognized data block type (%d) while searching archive\n", blkType);
                    break;
            }
            _readBlockHeader(AH, &blkType, &id);
        }
    } else {
        /* We can just seek to the place we need to be. */
        if (fseeko(AH->FH, tctx->dataPos, SEEK_SET) != 0)
            exit_horribly(modulename, "error during file seek: %s\n", strerror(errno));

        _readBlockHeader(AH, &blkType, &id);
    }

    /* Produce suitable failure message if we fell off end of file */
    if (blkType == EOF) {
        if (tctx->dataState == K_OFFSET_POS_NOT_SET) {
            exit_horribly(modulename,
                "could not find block ID %d in archive -- "
                "possibly due to out-of-order restore request, "
                "which cannot be handled due to lack of data offsets in archive\n",
                te->dumpId);
        } else if (!ctx->hasSeek) {
            exit_horribly(modulename,
                "could not find block ID %d in archive -- "
                "possibly due to out-of-order restore request, "
                "which cannot be handled due to non-seekable input file\n",
                te->dumpId);
        } else { /* huh, the dataPos led us to EOF? */
            exit_horribly(modulename,
                "could not find block ID %d in archive -- "
                "possibly corrupt archive\n",
                te->dumpId);
        }
    }

    /* Are we sane? */
    if (id != te->dumpId) {
        exit_horribly(modulename, "found unexpected block ID (%d) when reading data -- expected %d\n", id, te->dumpId);
    }
    switch (blkType) {
        case BLK_DATA:
            _PrintData(AH);
            break;

        case BLK_BLOBS:
            _LoadBlobs(AH, (ropt->dropSchema ? true : false));
            break;

        default: /* Always have a default */
            exit_horribly(modulename, "unrecognized data block type %d while restoring archive\n", blkType);
            break;
    }
}

/*
 * Print data from current file position.
 */
static void _PrintData(ArchiveHandle* AH)
{
    ReadDataFromArchive(AH, AH->compression, _CustomReadFunc);
}

static void _LoadBlobs(ArchiveHandle* AH, bool drop)
{
    Oid oid;

    StartRestoreBlobs(AH);

    oid = ReadInt(AH);
    while (oid != 0) {
        StartRestoreBlob(AH, oid, drop);
        _PrintData(AH);
        EndRestoreBlob(AH, oid);
        oid = ReadInt(AH);
    }

    EndRestoreBlobs(AH);
}

/*
 * Skip the BLOBs from the current file position.
 * BLOBS are written sequentially as data blocks (see below).
 * Each BLOB is preceded by it's original OID.
 * A zero OID indicated the end of the BLOBS
 */
static void _skipBlobs(ArchiveHandle* AH)
{
    Oid oid;

    oid = ReadInt(AH);
    while (oid != 0) {
        _skipData(AH);
        oid = ReadInt(AH);
    }
}

/*
 * Skip data from current file position.
 * Data blocks are formatted as an integer length, followed by data.
 * A zero length denoted the end of the block.
 */
static void _skipData(ArchiveHandle* AH)
{
    lclContext* ctx = (lclContext*)AH->formatData;
    char* buf = NULL;
    size_t buflen = 0;
    size_t cnt = 0;

    size_t blkLen = ReadInt(AH);
    while (blkLen != 0) {
        if (blkLen > buflen) {
            if (buf != NULL) {
                free(buf);
            }
            buf = (char*)pg_malloc(blkLen);
            buflen = blkLen;
        }
        cnt = fread(buf, 1, blkLen, AH->FH);
        if (cnt != blkLen) {
            if (feof(AH->FH)) {
                exit_horribly(modulename, "could not read from input file: end of file\n");
            } else {
                exit_horribly(modulename, "could not read from input file: %s\n", strerror(errno));
            }
        }

        ctx->filePos += blkLen;

        blkLen = ReadInt(AH);
    }

    if (buf != NULL) {
        free(buf);
        buf = NULL;
    }
}

/*
 * Write a byte of data to the archive.
 *
 * Mandatory.
 *
 * Called by the archiver to do integer & byte output to the archive.
 */
static int _WriteByte(ArchiveHandle* AH, const int i)
{
    lclContext* ctx = (lclContext*)AH->formatData;
    int res;

    res = fputc(i, AH->FH);
    if (res != EOF) {
        ctx->filePos += 1;
    } else {
        exit_horribly(modulename, "could not write byte: %s\n", strerror(errno));
    }
    return res;
}

/*
 * Read a byte of data from the archive.
 *
 * Mandatory
 *
 * Called by the archiver to read bytes & integers from the archive.
 * EOF should be treated as a fatal error.
 */
static int _ReadByte(ArchiveHandle* AH)
{
    lclContext* ctx = (lclContext*)AH->formatData;
    int res;

    res = getc(AH->FH);
    if (res == EOF) {
        exit_horribly(modulename, "unexpected end of file\n");
    }
    ctx->filePos += 1;
    return res;
}

/*
 * Write a buffer of data to the archive.
 *
 * Mandatory.
 *
 * Called by the archiver to write a block of bytes to the archive.
 */
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) {
        exit_horribly(modulename, "could not write to output file: %s\n", strerror(errno));
    }
    ctx->filePos += res;
    return res;
}

/*
 * Read a block of bytes from the archive.
 *
 * Mandatory.
 *
 * Called by the archiver to read a block of bytes from the archive
 */
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;
}

/*
 * Close the archive.
 *
 * Mandatory.
 *
 * When writing the archive, this is the routine that actually starts
 * the process of saving it to files. No data should be written prior
 * to this point, since the user could sort the TOC after creating it.
 *
 * If an archive is to be written, this toutine must call:
 *		WriteHead			to save the archive header
 *		WriteToc			to save the TOC entries
 *		WriteDataChunks		to save all DATA & BLOBs.
 *
 */
static void _CloseArchive(ArchiveHandle* AH)
{
    lclContext* ctx = (lclContext*)AH->formatData;
    pgoff_t tpos;

    if (AH->mode == archModeWrite) {
        WriteHead(AH);
        tpos = ftello(AH->FH);
        WriteToc(AH);
        ctx->dataStart = _getFilePos(AH, ctx);
        WriteDataChunks(AH);

        /*
         * If possible, re-write the TOC in order to update the data offset
         * information.  This is not essential, as pg_restore can cope in most
         * cases without it; but it can make pg_restore significantly faster
         * in some situations (especially parallel restore).
         */
        if (ctx->hasSeek && tpos >= 0 && fseeko(AH->FH, tpos, SEEK_SET) == 0) {
            WriteToc(AH);
        }
    }

    if (fclose(AH->FH) != 0) {
        exit_horribly(modulename, "could not close archive file: %s\n", strerror(errno));
    }
    AH->FH = NULL;
}

/*
 * Reopen the archive's file handle.
 *
 * We close the original file handle, except on Windows.  (The difference
 * is because on Windows, this is used within a multithreading context,
 * and we don't want a thread closing the parent file handle.)
 */
static void _ReopenArchive(ArchiveHandle* AH)
{
    lclContext* ctx = (lclContext*)AH->formatData;
    pgoff_t tpos;

    if (AH->mode == archModeWrite) {
        exit_horribly(modulename, "can only reopen input archives\n");
    }
    /*
     * These two cases are user-facing errors since they represent unsupported
     * (but not invalid) use-cases.  Word the error messages appropriately.
     */
    if (AH->fSpec == NULL || strcmp(AH->fSpec, "") == 0) {
        exit_horribly(modulename, "parallel restore from standard input is not supported\n");
    }
    if (!ctx->hasSeek) {
        exit_horribly(modulename, "parallel restore from non-seekable file is not supported\n");
    }
    errno = 0;
    tpos = ftello(AH->FH);
    if (errno)
        exit_horribly(modulename, "could not determine seek position in archive file: %s\n", strerror(errno));

#ifndef WIN32
    if (fclose(AH->FH) != 0) {
        exit_horribly(modulename, "could not close archive file: %s\n", strerror(errno));
    }
#endif

    AH->FH = fopen(AH->fSpec, PG_BINARY_R);
    if ((AH->FH) == NULL) {
        exit_horribly(modulename, "could not open input file \"%s\": %s\n", AH->fSpec, strerror(errno));
    }
    if (fseeko(AH->FH, tpos, SEEK_SET) != 0) {
        exit_horribly(modulename, "could not set seek position in archive file: %s\n", strerror(errno));
    }
}


/*
 * Clone format-specific fields during parallel restoration.
 */
static void _Clone(ArchiveHandle* AH)
{
    lclContext* ctx = (lclContext*)AH->formatData;

    AH->formatData = (lclContext*)pg_malloc(sizeof(lclContext));
    errno_t rc = memcpy_s(AH->formatData, sizeof(lclContext), ctx, sizeof(lclContext));
    securec_check_c(rc, "\0", "\0");
    ctx = (lclContext*)AH->formatData;

    /* sanity check, shouldn't happen */
    if (ctx->cs != NULL) {
        exit_horribly(modulename, "compressor active\n");
    }
    /*
     * Note: we do not make a local lo_buf because we expect at most one BLOBS
     * entry per archive, so no parallelism is possible.  Likewise,
     * TOC-entry-local state isn't an issue because any one TOC entry is
     * touched by just one worker child.
     */
}

static void _DeClone(ArchiveHandle* AH)
{
    lclContext* ctx = (lclContext*)AH->formatData;

    free(ctx);
    ctx = NULL;
}

/* --------------------------------------------------
 * END OF FORMAT CALLBACKS
 * --------------------------------------------------
 */

/*
 * Get the current position in the archive file.
 */
static pgoff_t _getFilePos(ArchiveHandle* AH, lclContext* ctx)
{
    pgoff_t pos;

    if (ctx->hasSeek) {
        pos = ftello(AH->FH);
        if (pos != ctx->filePos) {
            write_msg(modulename, "WARNING: ftell mismatch with expected position -- ftell used\n");

            /*
             * Prior to 1.7 (pg7.3) we relied on the internally maintained
             * pointer. Now we rely on ftello() always, unless the file has
             * been found to not support it.
             */
        }
    } else {
        pos = ctx->filePos;
    }
    return pos;
}

/*
 * Read a data block header. The format changed in V1.3, so we
 * centralize the code here for simplicity.  Returns *type = EOF
 * if at EOF.
 */
static void _readBlockHeader(ArchiveHandle* AH, int* type, int* id)
{
    lclContext* ctx = (lclContext*)AH->formatData;
    int byt;

    /*
     * Note: if we are at EOF with a pre-1.3 input file, we'll exit_horribly
     * inside ReadInt rather than returning EOF.  It doesn't seem worth
     * jumping through hoops to deal with that case better, because no such
     * files are likely to exist in the wild: only some 7.1 development
     * versions of pg_dump ever generated such files.
     */
    if (AH->version < K_VERS_1_3) {
        *type = BLK_DATA;
    } else {
        byt = getc(AH->FH);
        *type = byt;
        if (byt == EOF) {
            *id = 0; /* don't return an uninitialized value */
            return;
        }
        ctx->filePos += 1;
    }

    *id = ReadInt(AH);
}

/*
 * Callback function for WriteDataToArchive. Writes one block of (compressed)
 * data to the archive.
 */
static size_t _CustomWriteFunc(ArchiveHandle* AH, const char* buf, size_t len)
{
    /* never write 0-byte blocks (this should not happen) */
    if (len == 0) {
        return 0;
    }

    WriteInt(AH, len);
    return _WriteBuf(AH, buf, len);
}

#ifdef ENABLE_UT
size_t uttest_CustomWriteFunc(ArchiveHandle* AH, const char* buf, size_t len)
{
    return _CustomWriteFunc(AH, buf, len);
}
#endif

/*
 * Callback function for ReadDataFromArchive. To keep things simple, we
 * always read one compressed block at a time.
 */
static size_t _CustomReadFunc(ArchiveHandle* AH, char** buf, size_t* buflen)
{
    size_t blkLen;
    size_t cnt;

    /* Read length */
    blkLen = ReadInt(AH);
    if (blkLen == 0) {
        return 0;
    }
    /* If the caller's buffer is not large enough, allocate a bigger one */
    if (blkLen > *buflen) {
        free(*buf);
        *buf = (char*)pg_malloc(blkLen);
        *buflen = blkLen;
    }

    cnt = _ReadBuf(AH, *buf, blkLen);
    if (cnt != blkLen) {
        if (feof(AH->FH)) {
            exit_horribly(modulename, "could not read from input file: end of file\n");
        } else {
            exit_horribly(modulename, "could not read from input file: %s\n", strerror(errno));
        }
    }
    return cnt;
}