Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/Postgres/src/fe_utils/   (Postgres Database Version 18.4©)  Datei vom 11.4.2026 mit Größe 15 kB image not shown  

Quelle  astreamer_tar.c

  Sprache: C
 

/*-------------------------------------------------------------------------
 *
 * astreamer_tar.c
 *
 * This module implements three types of tar processing. A tar parser
 * expects unlabelled chunks of data (e.g. ASTREAMER_UNKNOWN) and splits
 * it into labelled chunks (any other value of astreamer_archive_context).
 * A tar archiver does the reverse: it takes a bunch of labelled chunks
 * and produces a tarfile, optionally replacing member headers and trailers
 * so that upstream astreamer objects can perform surgery on the tarfile
 * contents without knowing the details of the tar format. A tar terminator
 * just adds two blocks of NUL bytes to the end of the file, since older
 * server versions produce files with this terminator omitted.
 *
 * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group
 *
 * IDENTIFICATION
 *    src/fe_utils/astreamer_tar.c
 *-------------------------------------------------------------------------
 */


#include "postgres_fe.h"

#include <time.h>

#include "common/logging.h"
#include "fe_utils/astreamer.h"
#include "pgtar.h"

typedef struct astreamer_tar_parser
{
 astreamer base;
 astreamer_archive_context next_context;
 astreamer_member member;
 size_t  file_bytes_sent;
 size_t  pad_bytes_expected;
} astreamer_tar_parser;

typedef struct astreamer_tar_archiver
{
 astreamer base;
 bool  rearchive_member;
} astreamer_tar_archiver;

static void astreamer_tar_parser_content(astreamer *streamer,
           astreamer_member *member,
           const char *data, int len,
           astreamer_archive_context context);
static void astreamer_tar_parser_finalize(astreamer *streamer);
static void astreamer_tar_parser_free(astreamer *streamer);
static bool astreamer_tar_header(astreamer_tar_parser *mystreamer);

static const astreamer_ops astreamer_tar_parser_ops = {
 .content = astreamer_tar_parser_content,
 .finalize = astreamer_tar_parser_finalize,
 .free = astreamer_tar_parser_free
};

static void astreamer_tar_archiver_content(astreamer *streamer,
             astreamer_member *member,
             const char *data, int len,
             astreamer_archive_context context);
static void astreamer_tar_archiver_finalize(astreamer *streamer);
static void astreamer_tar_archiver_free(astreamer *streamer);

static const astreamer_ops astreamer_tar_archiver_ops = {
 .content = astreamer_tar_archiver_content,
 .finalize = astreamer_tar_archiver_finalize,
 .free = astreamer_tar_archiver_free
};

static void astreamer_tar_terminator_content(astreamer *streamer,
            astreamer_member *member,
            const char *data, int len,
            astreamer_archive_context context);
static void astreamer_tar_terminator_finalize(astreamer *streamer);
static void astreamer_tar_terminator_free(astreamer *streamer);

static const astreamer_ops astreamer_tar_terminator_ops = {
 .content = astreamer_tar_terminator_content,
 .finalize = astreamer_tar_terminator_finalize,
 .free = astreamer_tar_terminator_free
};

/*
 * Create a astreamer that can parse a stream of content as tar data.
 *
 * The input should be a series of ASTREAMER_UNKNOWN chunks; the astreamer
 * specified by 'next' will receive a series of typed chunks, as per the
 * conventions described in astreamer.h.
 */

astreamer *
astreamer_tar_parser_new(astreamer *next)
{
 astreamer_tar_parser *streamer;

 streamer = palloc0(sizeof(astreamer_tar_parser));
 *((const astreamer_ops **) &streamer->base.bbs_ops) =
  &astreamer_tar_parser_ops;
 streamer->base.bbs_next = next;
 initStringInfo(&streamer->base.bbs_buffer);
 streamer->next_context = ASTREAMER_MEMBER_HEADER;

 return &streamer->base;
}

/*
 * Parse unknown content as tar data.
 */

static void
astreamer_tar_parser_content(astreamer *streamer, astreamer_member *member,
        const char *data, int len,
        astreamer_archive_context context)
{
 astreamer_tar_parser *mystreamer = (astreamer_tar_parser *) streamer;
 size_t  nbytes;

 /* Expect unparsed input. */
 Assert(member == NULL);
 Assert(context == ASTREAMER_UNKNOWN);

 while (len > 0)
 {
  switch (mystreamer->next_context)
  {
   case ASTREAMER_MEMBER_HEADER:

    /*
     * If we're expecting an archive member header, accumulate a
     * full block of data before doing anything further.
 */

    if (!astreamer_buffer_until(streamer, &data, &len,
           TAR_BLOCK_SIZE))
     return;

    /*
     * Now we can process the header and get ready to process the
     * file contents; however, we might find out that what we
     * thought was the next file header is actually the start of
     * the archive trailer. Switch modes accordingly.
 */

    if (astreamer_tar_header(mystreamer))
    {
     if (mystreamer->member.size == 0)
     {
      /* No content; trailer is zero-length. */
      astreamer_content(mystreamer->base.bbs_next,
            &mystreamer->member,
            NULL, 0,
            ASTREAMER_MEMBER_TRAILER);

      /* Expect next header. */
      mystreamer->next_context = ASTREAMER_MEMBER_HEADER;
     }
     else
     {
      /* Expect contents. */
      mystreamer->next_context = ASTREAMER_MEMBER_CONTENTS;
     }
     mystreamer->base.bbs_buffer.len = 0;
     mystreamer->file_bytes_sent = 0;
    }
    else
     mystreamer->next_context = ASTREAMER_ARCHIVE_TRAILER;
    break;

   case ASTREAMER_MEMBER_CONTENTS:

    /*
     * Send as much content as we have, but not more than the
     * remaining file length.
 */

    Assert(mystreamer->file_bytes_sent < mystreamer->member.size);
    nbytes = mystreamer->member.size - mystreamer->file_bytes_sent;
    nbytes = Min(nbytes, len);
    Assert(nbytes > 0);
    astreamer_content(mystreamer->base.bbs_next,
          &mystreamer->member,
          data, nbytes,
          ASTREAMER_MEMBER_CONTENTS);
    mystreamer->file_bytes_sent += nbytes;
    data += nbytes;
    len -= nbytes;

    /*
     * If we've not yet sent the whole file, then there's more
     * content to come; otherwise, it's time to expect the file
     * trailer.
 */

    Assert(mystreamer->file_bytes_sent <= mystreamer->member.size);
    if (mystreamer->file_bytes_sent == mystreamer->member.size)
    {
     if (mystreamer->pad_bytes_expected == 0)
     {
      /* Trailer is zero-length. */
      astreamer_content(mystreamer->base.bbs_next,
            &mystreamer->member,
            NULL, 0,
            ASTREAMER_MEMBER_TRAILER);

      /* Expect next header. */
      mystreamer->next_context = ASTREAMER_MEMBER_HEADER;
     }
     else
     {
      /* Trailer is not zero-length. */
      mystreamer->next_context = ASTREAMER_MEMBER_TRAILER;
     }
     mystreamer->base.bbs_buffer.len = 0;
    }
    break;

   case ASTREAMER_MEMBER_TRAILER:

    /*
     * If we're expecting an archive member trailer, accumulate
     * the expected number of padding bytes before sending
     * anything onward.
 */

    if (!astreamer_buffer_until(streamer, &data, &len,
           mystreamer->pad_bytes_expected))
     return;

    /* OK, now we can send it. */
    astreamer_content(mystreamer->base.bbs_next,
          &mystreamer->member,
          mystreamer->base.bbs_buffer.data,
          mystreamer->pad_bytes_expected,
          ASTREAMER_MEMBER_TRAILER);

    /* Expect next file header. */
    mystreamer->next_context = ASTREAMER_MEMBER_HEADER;
    mystreamer->base.bbs_buffer.len = 0;
    break;

   case ASTREAMER_ARCHIVE_TRAILER:

    /*
     * We've seen an end-of-archive indicator, so anything more is
     * buffered and sent as part of the archive trailer.
     *
     * Per POSIX, the last physical block of a tar archive is
     * always full-sized, so there may be undefined data after the
     * two zero blocks that mark end-of-archive.  GNU tar, for
     * example, zero-pads to a 10kB boundary by default.  We just
     * buffer whatever we receive and pass it along at finalize
     * time.
 */

    astreamer_buffer_bytes(streamer, &data, &len, len);
    return;

   default:
    /* Shouldn't happen. */
    pg_fatal("unexpected state while parsing tar archive");
  }
 }
}

/*
 * Parse a file header within a tar stream.
 *
 * The return value is true if we found a file header and passed it on to the
 * next astreamer; it is false if we have found the archive trailer.
 * We throw error if we see invalid data.
 */

static bool
astreamer_tar_header(astreamer_tar_parser *mystreamer)
{
 bool  has_nonzero_byte = false;
 int   i;
 astreamer_member *member = &mystreamer->member;
 char    *buffer = mystreamer->base.bbs_buffer.data;

 Assert(mystreamer->base.bbs_buffer.len == TAR_BLOCK_SIZE);

 /* Zero out fields of *member, just for consistency. */
 memset(member, 0sizeof(astreamer_member));

 /* Check whether we've got a block of all zero bytes. */
 for (i = 0; i < TAR_BLOCK_SIZE; ++i)
 {
  if (buffer[i] != '\0')
  {
   has_nonzero_byte = true;
   break;
  }
 }

 /*
  * If the entire block was zeros, this is the end of the archive, not the
  * start of the next file.
 */

 if (!has_nonzero_byte)
  return false;

 /*
  * Verify that we have a reasonable-looking header.
 */

 if (!isValidTarHeader(buffer))
  pg_fatal("input file does not appear to be a valid tar archive");

 /*
  * Parse key fields out of the header.
 */

 strlcpy(member->pathname, &buffer[TAR_OFFSET_NAME], MAXPGPATH);
 if (member->pathname[0] == '\0')
  pg_fatal("tar member has empty name");
 if (!path_is_safe_for_extraction(member->pathname))
  pg_fatal("tar member has unsafe path name: \"%s\"",
     member->pathname);

 member->size = read_tar_number(&buffer[TAR_OFFSET_SIZE], 12);
 member->mode = read_tar_number(&buffer[TAR_OFFSET_MODE], 8);
 member->uid = read_tar_number(&buffer[TAR_OFFSET_UID], 8);
 member->gid = read_tar_number(&buffer[TAR_OFFSET_GID], 8);

 switch (buffer[TAR_OFFSET_TYPEFLAG])
 {
  case TAR_FILETYPE_PLAIN:
  case TAR_FILETYPE_PLAIN_OLD:
   member->is_regular = true;
   break;
  case TAR_FILETYPE_DIRECTORY:
   member->is_directory = true;
   break;
  case TAR_FILETYPE_SYMLINK:
   member->is_symlink = true;
   strlcpy(member->linktarget, &buffer[TAR_OFFSET_LINKNAME], 100);
   break;
  case TAR_FILETYPE_PAX_EXTENDED:
  case TAR_FILETYPE_PAX_EXTENDED_GLOBAL:
   pg_fatal("pax extensions to tar format are not supported");
   break;
  default:
   /* For special filetypes, set none of the three is_xxx flags */
   break;
 }

 /* Compute number of padding bytes. */
 mystreamer->pad_bytes_expected = tarPaddingBytesRequired(member->size);

 /* Forward the entire header to the next astreamer. */
 astreamer_content(mystreamer->base.bbs_next, member,
       buffer, TAR_BLOCK_SIZE,
       ASTREAMER_MEMBER_HEADER);

 return true;
}

/*
 * End-of-stream processing for a tar parser.
 */

static void
astreamer_tar_parser_finalize(astreamer *streamer)
{
 astreamer_tar_parser *mystreamer = (astreamer_tar_parser *) streamer;

 if (mystreamer->next_context != ASTREAMER_ARCHIVE_TRAILER &&
  (mystreamer->next_context != ASTREAMER_MEMBER_HEADER ||
   mystreamer->base.bbs_buffer.len > 0))
  pg_fatal("COPY stream ended before last file was finished");

 /* Send the archive trailer, even if empty. */
 astreamer_content(streamer->bbs_next, NULL,
       streamer->bbs_buffer.data, streamer->bbs_buffer.len,
       ASTREAMER_ARCHIVE_TRAILER);

 /* Now finalize successor. */
 astreamer_finalize(streamer->bbs_next);
}

/*
 * Free memory associated with a tar parser.
 */

static void
astreamer_tar_parser_free(astreamer *streamer)
{
 pfree(streamer->bbs_buffer.data);
 astreamer_free(streamer->bbs_next);
 pfree(streamer);
}

/*
 * Create a astreamer that can generate a tar archive.
 *
 * This is intended to be usable either for generating a brand-new tar archive
 * or for modifying one on the fly. The input should be a series of typed
 * chunks (i.e. not ASTREAMER_UNKNOWN). See also the comments for
 * astreamer_tar_parser_content.
 */

astreamer *
astreamer_tar_archiver_new(astreamer *next)
{
 astreamer_tar_archiver *streamer;

 streamer = palloc0(sizeof(astreamer_tar_archiver));
 *((const astreamer_ops **) &streamer->base.bbs_ops) =
  &astreamer_tar_archiver_ops;
 streamer->base.bbs_next = next;

 return &streamer->base;
}

/*
 * Fix up the stream of input chunks to create a valid tar file.
 *
 * If a ASTREAMER_MEMBER_HEADER chunk is of size 0, it is replaced with a
 * newly-constructed tar header. If it is of size TAR_BLOCK_SIZE, it is
 * passed through without change. Any other size is a fatal error (and
 * indicates a bug).
 *
 * Whenever a new ASTREAMER_MEMBER_HEADER chunk is constructed, the
 * corresponding ASTREAMER_MEMBER_TRAILER chunk is also constructed from
 * scratch. Specifically, we construct a block of zero bytes sufficient to
 * pad out to a block boundary, as required by the tar format. Other
 * ASTREAMER_MEMBER_TRAILER chunks are passed through without change.
 *
 * Any ASTREAMER_MEMBER_CONTENTS chunks are passed through without change.
 *
 * The ASTREAMER_ARCHIVE_TRAILER chunk is replaced with two
 * blocks of zero bytes. Not all tar programs require this, but apparently
 * some do. The server does not supply this trailer. If no archive trailer is
 * present, one will be added by astreamer_tar_parser_finalize.
 */

static void
astreamer_tar_archiver_content(astreamer *streamer,
          astreamer_member *member,
          const char *data, int len,
          astreamer_archive_context context)
{
 astreamer_tar_archiver *mystreamer = (astreamer_tar_archiver *) streamer;
 char  buffer[2 * TAR_BLOCK_SIZE];

 Assert(context != ASTREAMER_UNKNOWN);

 if (context == ASTREAMER_MEMBER_HEADER && len != TAR_BLOCK_SIZE)
 {
  Assert(len == 0);

  /* Replace zero-length tar header with a newly constructed one. */
  tarCreateHeader(buffer, member->pathname, NULL,
      member->size, member->mode, member->uid, member->gid,
      time(NULL));
  data = buffer;
  len = TAR_BLOCK_SIZE;

  /* Also make a note to replace padding, in case size changed. */
  mystreamer->rearchive_member = true;
 }
 else if (context == ASTREAMER_MEMBER_TRAILER &&
    mystreamer->rearchive_member)
 {
  int   pad_bytes = tarPaddingBytesRequired(member->size);

  /* Also replace padding, if we regenerated the header. */
  memset(buffer, 0, pad_bytes);
  data = buffer;
  len = pad_bytes;

  /* Don't do this again unless we replace another header. */
  mystreamer->rearchive_member = false;
 }
 else if (context == ASTREAMER_ARCHIVE_TRAILER)
 {
  /* Trailer should always be two blocks of zero bytes. */
  memset(buffer, 02 * TAR_BLOCK_SIZE);
  data = buffer;
  len = 2 * TAR_BLOCK_SIZE;
 }

 astreamer_content(streamer->bbs_next, member, data, len, context);
}

/*
 * End-of-stream processing for a tar archiver.
 */

static void
astreamer_tar_archiver_finalize(astreamer *streamer)
{
 astreamer_finalize(streamer->bbs_next);
}

/*
 * Free memory associated with a tar archiver.
 */

static void
astreamer_tar_archiver_free(astreamer *streamer)
{
 astreamer_free(streamer->bbs_next);
 pfree(streamer);
}

/*
 * Create a astreamer that blindly adds two blocks of NUL bytes to the
 * end of an incomplete tarfile that the server might send us.
 */

astreamer *
astreamer_tar_terminator_new(astreamer *next)
{
 astreamer  *streamer;

 streamer = palloc0(sizeof(astreamer));
 *((const astreamer_ops **) &streamer->bbs_ops) =
  &astreamer_tar_terminator_ops;
 streamer->bbs_next = next;

 return streamer;
}

/*
 * Pass all the content through without change.
 */

static void
astreamer_tar_terminator_content(astreamer *streamer,
         astreamer_member *member,
         const char *data, int len,
         astreamer_archive_context context)
{
 /* Expect unparsed input. */
 Assert(member == NULL);
 Assert(context == ASTREAMER_UNKNOWN);

 /* Just forward it. */
 astreamer_content(streamer->bbs_next, member, data, len, context);
}

/*
 * At the end, blindly add the two blocks of NUL bytes which the server fails
 * to supply.
 */

static void
astreamer_tar_terminator_finalize(astreamer *streamer)
{
 char  buffer[2 * TAR_BLOCK_SIZE];

 memset(buffer, 02 * TAR_BLOCK_SIZE);
 astreamer_content(streamer->bbs_next, NULL, buffer,
       2 * TAR_BLOCK_SIZE, ASTREAMER_UNKNOWN);
 astreamer_finalize(streamer->bbs_next);
}

/*
 * Free memory associated with a tar terminator.
 */

static void
astreamer_tar_terminator_free(astreamer *streamer)
{
 astreamer_free(streamer->bbs_next);
 pfree(streamer);
}

Messung V0.5 in Prozent
C=92 H=98 G=94

¤ Dauer der Verarbeitung: 0.13 Sekunden  (vorverarbeitet am  2026-08-04) ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

PVS Prover

Isabelle Prover

NIST Cobol Testsuite

Cephes Mathematical Library

Vienna Development Method

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.