LCOV - code coverage report
Current view: top level - src/bin/pg_verifybackup - astreamer_verify.c (source / functions) Hit Total Coverage
Test: PostgreSQL 18devel Lines: 121 140 86.4 %
Date: 2024-12-12 20:14:52 Functions: 10 10 100.0 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : /*-------------------------------------------------------------------------
       2             :  *
       3             :  * astreamer_verify.c
       4             :  *
       5             :  * Archive streamer for verification of a tar format backup (including
       6             :  * compressed tar format backups).
       7             :  *
       8             :  * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group
       9             :  *
      10             :  * src/bin/pg_verifybackup/astreamer_verify.c
      11             :  *
      12             :  *-------------------------------------------------------------------------
      13             :  */
      14             : 
      15             : #include "postgres_fe.h"
      16             : 
      17             : #include "catalog/pg_control.h"
      18             : #include "pg_verifybackup.h"
      19             : 
      20             : typedef struct astreamer_verify
      21             : {
      22             :     /* These fields don't change once initialized. */
      23             :     astreamer   base;
      24             :     verifier_context *context;
      25             :     char       *archive_name;
      26             :     Oid         tblspc_oid;
      27             : 
      28             :     /* These fields change for each archive member. */
      29             :     manifest_file *mfile;
      30             :     bool        verify_checksum;
      31             :     bool        verify_control_data;
      32             :     pg_checksum_context *checksum_ctx;
      33             :     uint64      checksum_bytes;
      34             :     ControlFileData control_file;
      35             :     uint64      control_file_bytes;
      36             : } astreamer_verify;
      37             : 
      38             : static void astreamer_verify_content(astreamer *streamer,
      39             :                                      astreamer_member *member,
      40             :                                      const char *data, int len,
      41             :                                      astreamer_archive_context context);
      42             : static void astreamer_verify_finalize(astreamer *streamer);
      43             : static void astreamer_verify_free(astreamer *streamer);
      44             : 
      45             : static void member_verify_header(astreamer *streamer, astreamer_member *member);
      46             : static void member_compute_checksum(astreamer *streamer,
      47             :                                     astreamer_member *member,
      48             :                                     const char *data, int len);
      49             : static void member_verify_checksum(astreamer *streamer);
      50             : static void member_copy_control_data(astreamer *streamer,
      51             :                                      astreamer_member *member,
      52             :                                      const char *data, int len);
      53             : static void member_verify_control_data(astreamer *streamer);
      54             : static void member_reset_info(astreamer *streamer);
      55             : 
      56             : static const astreamer_ops astreamer_verify_ops = {
      57             :     .content = astreamer_verify_content,
      58             :     .finalize = astreamer_verify_finalize,
      59             :     .free = astreamer_verify_free
      60             : };
      61             : 
      62             : /*
      63             :  * Create an astreamer that can verify a tar file.
      64             :  */
      65             : astreamer *
      66          58 : astreamer_verify_content_new(astreamer *next, verifier_context *context,
      67             :                              char *archive_name, Oid tblspc_oid)
      68             : {
      69             :     astreamer_verify *streamer;
      70             : 
      71          58 :     streamer = palloc0(sizeof(astreamer_verify));
      72          58 :     *((const astreamer_ops **) &streamer->base.bbs_ops) =
      73             :         &astreamer_verify_ops;
      74             : 
      75          58 :     streamer->base.bbs_next = next;
      76          58 :     streamer->context = context;
      77          58 :     streamer->archive_name = archive_name;
      78          58 :     streamer->tblspc_oid = tblspc_oid;
      79             : 
      80          58 :     if (!context->skip_checksums)
      81          58 :         streamer->checksum_ctx = pg_malloc(sizeof(pg_checksum_context));
      82             : 
      83          58 :     return &streamer->base;
      84             : }
      85             : 
      86             : /*
      87             :  * Main entry point of the archive streamer for verifying tar members.
      88             :  */
      89             : static void
      90      423678 : astreamer_verify_content(astreamer *streamer, astreamer_member *member,
      91             :                          const char *data, int len,
      92             :                          astreamer_archive_context context)
      93             : {
      94      423678 :     astreamer_verify *mystreamer = (astreamer_verify *) streamer;
      95             : 
      96             :     Assert(context != ASTREAMER_UNKNOWN);
      97             : 
      98      423678 :     switch (context)
      99             :     {
     100       38050 :         case ASTREAMER_MEMBER_HEADER:
     101             :             /* Initial setup plus decide which checks to perform. */
     102       38050 :             member_verify_header(streamer, member);
     103       38050 :             break;
     104             : 
     105      347522 :         case ASTREAMER_MEMBER_CONTENTS:
     106             :             /* Incremental work required to verify file contents. */
     107      347522 :             if (mystreamer->verify_checksum)
     108      213944 :                 member_compute_checksum(streamer, member, data, len);
     109      347522 :             if (mystreamer->verify_control_data)
     110         100 :                 member_copy_control_data(streamer, member, data, len);
     111      347522 :             break;
     112             : 
     113       38050 :         case ASTREAMER_MEMBER_TRAILER:
     114             :             /* Now we've got all the file data. */
     115       38050 :             if (mystreamer->verify_checksum)
     116       35006 :                 member_verify_checksum(streamer);
     117       38050 :             if (mystreamer->verify_control_data)
     118          38 :                 member_verify_control_data(streamer);
     119             : 
     120             :             /* Reset for next archive member. */
     121       38048 :             member_reset_info(streamer);
     122       38048 :             break;
     123             : 
     124          56 :         case ASTREAMER_ARCHIVE_TRAILER:
     125          56 :             break;
     126             : 
     127           0 :         default:
     128             :             /* Shouldn't happen. */
     129           0 :             pg_fatal("unexpected state while parsing tar file");
     130             :     }
     131      423676 : }
     132             : 
     133             : /*
     134             :  * End-of-stream processing for a astreamer_verify stream.
     135             :  */
     136             : static void
     137          56 : astreamer_verify_finalize(astreamer *streamer)
     138             : {
     139             :     Assert(streamer->bbs_next == NULL);
     140          56 : }
     141             : 
     142             : /*
     143             :  * Free memory associated with a astreamer_verify stream.
     144             :  */
     145             : static void
     146          56 : astreamer_verify_free(astreamer *streamer)
     147             : {
     148          56 :     astreamer_verify *mystreamer = (astreamer_verify *) streamer;
     149             : 
     150          56 :     if (mystreamer->checksum_ctx)
     151          56 :         pfree(mystreamer->checksum_ctx);
     152             : 
     153          56 :     pfree(streamer);
     154          56 : }
     155             : 
     156             : /*
     157             :  * Prepare to validate the next archive member.
     158             :  */
     159             : static void
     160       38050 : member_verify_header(astreamer *streamer, astreamer_member *member)
     161             : {
     162       38050 :     astreamer_verify *mystreamer = (astreamer_verify *) streamer;
     163             :     manifest_file *m;
     164             :     char        pathname[MAXPGPATH];
     165             : 
     166             :     /* We are only interested in normal files. */
     167       38050 :     if (member->is_directory || member->is_link)
     168        1104 :         return;
     169             : 
     170             :     /*
     171             :      * The backup manifest stores a relative path to the base directory for
     172             :      * files belonging to a tablespace, while the tablespace backup tar
     173             :      * archive does not include this path.
     174             :      *
     175             :      * The pathname taken from the tar file could contain '.' or '..'
     176             :      * references, which we want to remove, so apply canonicalize_path(). It
     177             :      * could also be an absolute pathname, which we want to treat as a
     178             :      * relative path, so prepend "./" if we're not adding a tablespace prefix
     179             :      * to make sure that canonicalize_path() does what we want.
     180             :      */
     181       37016 :     if (OidIsValid(mystreamer->tblspc_oid))
     182          20 :         snprintf(pathname, MAXPGPATH, "%s/%u/%s",
     183          20 :                  "pg_tblspc", mystreamer->tblspc_oid, member->pathname);
     184             :     else
     185       36996 :         snprintf(pathname, MAXPGPATH, "./%s", member->pathname);
     186       37016 :     canonicalize_path(pathname);
     187             : 
     188             :     /* Ignore any files that are listed in the ignore list. */
     189       37016 :     if (should_ignore_relpath(mystreamer->context, pathname))
     190          62 :         return;
     191             : 
     192             :     /* Check whether there's an entry in the manifest hash. */
     193       36954 :     m = manifest_files_lookup(mystreamer->context->manifest->files, pathname);
     194       36954 :     if (m == NULL)
     195             :     {
     196           4 :         report_backup_error(mystreamer->context,
     197             :                             "\"%s\" is present in \"%s\" but not in the manifest",
     198           4 :                             member->pathname, mystreamer->archive_name);
     199           4 :         return;
     200             :     }
     201       36950 :     mystreamer->mfile = m;
     202             : 
     203             :     /* Flag this entry as having been encountered in a tar archive. */
     204       36950 :     m->matched = true;
     205             : 
     206             :     /* Check that the size matches. */
     207       36950 :     if (m->size != member->size)
     208             :     {
     209           4 :         report_backup_error(mystreamer->context,
     210             :                             "\"%s\" has size %llu in \"%s\" but size %llu in the manifest",
     211           4 :                             member->pathname,
     212           4 :                             (unsigned long long) member->size,
     213             :                             mystreamer->archive_name,
     214           4 :                             (unsigned long long) m->size);
     215           4 :         m->bad = true;
     216           4 :         return;
     217             :     }
     218             : 
     219             :     /*
     220             :      * Decide whether we're going to verify the checksum for this file, and
     221             :      * whether we're going to perform the additional validation that we do
     222             :      * only for the control file.
     223             :      */
     224       36946 :     mystreamer->verify_checksum =
     225       36946 :         (!mystreamer->context->skip_checksums && should_verify_checksum(m));
     226       36946 :     mystreamer->verify_control_data =
     227       73892 :         mystreamer->context->manifest->version != 1 &&
     228       36946 :         !m->bad && strcmp(m->pathname, "global/pg_control") == 0;
     229             : 
     230             :     /* If we're going to verify the checksum, initial a checksum context. */
     231       71952 :     if (mystreamer->verify_checksum &&
     232       35006 :         pg_checksum_init(mystreamer->checksum_ctx, m->checksum_type) < 0)
     233             :     {
     234           0 :         report_backup_error(mystreamer->context,
     235             :                             "%s: could not initialize checksum of file \"%s\"",
     236             :                             mystreamer->archive_name, m->pathname);
     237             : 
     238             :         /*
     239             :          * Checksum verification cannot be performed without proper context
     240             :          * initialization.
     241             :          */
     242           0 :         mystreamer->verify_checksum = false;
     243             :     }
     244             : }
     245             : 
     246             : /*
     247             :  * Computes the checksum incrementally for the received file content.
     248             :  *
     249             :  * Should have a correctly initialized checksum_ctx, which will be used for
     250             :  * incremental checksum computation.
     251             :  */
     252             : static void
     253      213944 : member_compute_checksum(astreamer *streamer, astreamer_member *member,
     254             :                         const char *data, int len)
     255             : {
     256      213944 :     astreamer_verify *mystreamer = (astreamer_verify *) streamer;
     257      213944 :     pg_checksum_context *checksum_ctx = mystreamer->checksum_ctx;
     258      213944 :     manifest_file *m = mystreamer->mfile;
     259             : 
     260             :     Assert(mystreamer->verify_checksum);
     261             :     Assert(m->checksum_type == checksum_ctx->type);
     262             : 
     263             :     /*
     264             :      * Update the total count of computed checksum bytes so that we can
     265             :      * cross-check against the file size.
     266             :      */
     267      213944 :     mystreamer->checksum_bytes += len;
     268             : 
     269             :     /* Feed these bytes to the checksum calculation. */
     270      213944 :     if (pg_checksum_update(checksum_ctx, (uint8 *) data, len) < 0)
     271             :     {
     272           0 :         report_backup_error(mystreamer->context,
     273             :                             "could not update checksum of file \"%s\"",
     274             :                             m->pathname);
     275           0 :         mystreamer->verify_checksum = false;
     276             :     }
     277      213944 : }
     278             : 
     279             : /*
     280             :  * Perform the final computation and checksum verification after the entire
     281             :  * file content has been processed.
     282             :  */
     283             : static void
     284       35006 : member_verify_checksum(astreamer *streamer)
     285             : {
     286       35006 :     astreamer_verify *mystreamer = (astreamer_verify *) streamer;
     287       35006 :     manifest_file *m = mystreamer->mfile;
     288             :     uint8       checksumbuf[PG_CHECKSUM_MAX_LENGTH];
     289             :     int         checksumlen;
     290             : 
     291             :     Assert(mystreamer->verify_checksum);
     292             : 
     293             :     /*
     294             :      * It's unclear how this could fail, but let's check anyway to be safe.
     295             :      */
     296       35006 :     if (mystreamer->checksum_bytes != m->size)
     297             :     {
     298           0 :         report_backup_error(mystreamer->context,
     299             :                             "file \"%s\" in \"%s\" should contain %llu bytes, but read %llu bytes",
     300             :                             m->pathname, mystreamer->archive_name,
     301           0 :                             (unsigned long long) m->size,
     302           0 :                             (unsigned long long) mystreamer->checksum_bytes);
     303           0 :         return;
     304             :     }
     305             : 
     306             :     /* Get the final checksum. */
     307       35006 :     checksumlen = pg_checksum_final(mystreamer->checksum_ctx, checksumbuf);
     308       35006 :     if (checksumlen < 0)
     309             :     {
     310           0 :         report_backup_error(mystreamer->context,
     311             :                             "could not finalize checksum of file \"%s\"",
     312             :                             m->pathname);
     313           0 :         return;
     314             :     }
     315             : 
     316             :     /* And check it against the manifest. */
     317       35006 :     if (checksumlen != m->checksum_length)
     318           0 :         report_backup_error(mystreamer->context,
     319             :                             "file \"%s\" in \"%s\" has checksum of length %d, but expected %d",
     320             :                             m->pathname, mystreamer->archive_name,
     321             :                             m->checksum_length, checksumlen);
     322       35006 :     else if (memcmp(checksumbuf, m->checksum_payload, checksumlen) != 0)
     323          12 :         report_backup_error(mystreamer->context,
     324             :                             "checksum mismatch for file \"%s\" in \"%s\"",
     325             :                             m->pathname, mystreamer->archive_name);
     326             : }
     327             : 
     328             : /*
     329             :  * Stores the pg_control file contents into a local buffer; we need the entire
     330             :  * control file data for verification.
     331             :  */
     332             : static void
     333         100 : member_copy_control_data(astreamer *streamer, astreamer_member *member,
     334             :                          const char *data, int len)
     335             : {
     336         100 :     astreamer_verify *mystreamer = (astreamer_verify *) streamer;
     337             : 
     338             :     /* Should be here only for control file */
     339             :     Assert(mystreamer->verify_control_data);
     340             : 
     341             :     /*
     342             :      * Copy the new data into the control file buffer, but do not overrun the
     343             :      * buffer. Note that the on-disk length of the control file is expected to
     344             :      * be PG_CONTROL_FILE_SIZE, but the part that fits in our buffer is
     345             :      * shorter, just sizeof(ControlFileData).
     346             :      */
     347         100 :     if (mystreamer->control_file_bytes < sizeof(ControlFileData))
     348             :     {
     349             :         size_t      remaining;
     350             : 
     351          38 :         remaining = sizeof(ControlFileData) - mystreamer->control_file_bytes;
     352          38 :         memcpy(((char *) &mystreamer->control_file)
     353          38 :                + mystreamer->control_file_bytes,
     354          38 :                data, Min((size_t) len, remaining));
     355             :     }
     356             : 
     357             :     /* Remember how many bytes we saw, even if we didn't buffer them. */
     358         100 :     mystreamer->control_file_bytes += len;
     359         100 : }
     360             : 
     361             : /*
     362             :  * Performs the CRC calculation of pg_control data and then calls the routines
     363             :  * that execute the final verification of the control file information.
     364             :  */
     365             : static void
     366          38 : member_verify_control_data(astreamer *streamer)
     367             : {
     368          38 :     astreamer_verify *mystreamer = (astreamer_verify *) streamer;
     369          38 :     manifest_data *manifest = mystreamer->context->manifest;
     370             :     pg_crc32c   crc;
     371             : 
     372             :     /* Should be here only for control file */
     373             :     Assert(strcmp(mystreamer->mfile->pathname, "global/pg_control") == 0);
     374             :     Assert(mystreamer->verify_control_data);
     375             : 
     376             :     /*
     377             :      * If the control file is not the right length, that's a big problem.
     378             :      *
     379             :      * NB: There is a theoretical overflow risk here from casting to int, but
     380             :      * it isn't likely to be a real problem and this enables us to match the
     381             :      * same format string that pg_rewind uses for this case. Perhaps both this
     382             :      * and pg_rewind should use an unsigned 64-bit value, but for now we don't
     383             :      * worry about it.
     384             :      */
     385          38 :     if (mystreamer->control_file_bytes != PG_CONTROL_FILE_SIZE)
     386           0 :         report_fatal_error("unexpected control file size %d, expected %d",
     387           0 :                            (int) mystreamer->control_file_bytes,
     388             :                            PG_CONTROL_FILE_SIZE);
     389             : 
     390             :     /* Compute the CRC. */
     391          38 :     INIT_CRC32C(crc);
     392          38 :     COMP_CRC32C(crc, &mystreamer->control_file,
     393             :                 offsetof(ControlFileData, crc));
     394          38 :     FIN_CRC32C(crc);
     395             : 
     396             :     /* Control file contents not meaningful if CRC is bad. */
     397          38 :     if (!EQ_CRC32C(crc, mystreamer->control_file.crc))
     398           0 :         report_fatal_error("%s: %s: CRC is incorrect",
     399             :                            mystreamer->archive_name,
     400           0 :                            mystreamer->mfile->pathname);
     401             : 
     402             :     /* Can't interpret control file if not current version. */
     403          38 :     if (mystreamer->control_file.pg_control_version != PG_CONTROL_VERSION)
     404           0 :         report_fatal_error("%s: %s: unexpected control file version",
     405             :                            mystreamer->archive_name,
     406           0 :                            mystreamer->mfile->pathname);
     407             : 
     408             :     /* System identifiers should match. */
     409          38 :     if (manifest->system_identifier !=
     410          38 :         mystreamer->control_file.system_identifier)
     411           2 :         report_fatal_error("%s: %s: manifest system identifier is %llu, but control file has %llu",
     412             :                            mystreamer->archive_name,
     413           2 :                            mystreamer->mfile->pathname,
     414           2 :                            (unsigned long long) manifest->system_identifier,
     415           2 :                            (unsigned long long) mystreamer->control_file.system_identifier);
     416          36 : }
     417             : 
     418             : /*
     419             :  * Reset flags and free memory allocations for member file verification.
     420             :  */
     421             : static void
     422       38048 : member_reset_info(astreamer *streamer)
     423             : {
     424       38048 :     astreamer_verify *mystreamer = (astreamer_verify *) streamer;
     425             : 
     426       38048 :     mystreamer->mfile = NULL;
     427       38048 :     mystreamer->verify_checksum = false;
     428       38048 :     mystreamer->verify_control_data = false;
     429       38048 :     mystreamer->checksum_bytes = 0;
     430       38048 :     mystreamer->control_file_bytes = 0;
     431       38048 : }

Generated by: LCOV version 1.14