LCOV - code coverage report
Current view: top level - src/bin/pg_verifybackup - pg_verifybackup.c (source / functions) Coverage Total Hit
Test: PostgreSQL 20devel Lines: 90.2 % 480 433
Test Date: 2026-07-19 01:15:46 Functions: 100.0 % 23 23
Legend: Lines:     hit not hit
Branches: + taken - not taken # not executed
Branches: 80.7 % 270 218

             Branch data     Line data    Source code
       1                 :             : /*-------------------------------------------------------------------------
       2                 :             :  *
       3                 :             :  * pg_verifybackup.c
       4                 :             :  *    Verify a backup against a backup manifest.
       5                 :             :  *
       6                 :             :  * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
       7                 :             :  * Portions Copyright (c) 1994, Regents of the University of California
       8                 :             :  *
       9                 :             :  * src/bin/pg_verifybackup/pg_verifybackup.c
      10                 :             :  *
      11                 :             :  *-------------------------------------------------------------------------
      12                 :             :  */
      13                 :             : 
      14                 :             : #include "postgres_fe.h"
      15                 :             : 
      16                 :             : #include <dirent.h>
      17                 :             : #include <fcntl.h>
      18                 :             : #include <limits.h>
      19                 :             : #include <sys/stat.h>
      20                 :             : #include <time.h>
      21                 :             : 
      22                 :             : #include "access/xlog_internal.h"
      23                 :             : #include "common/logging.h"
      24                 :             : #include "common/parse_manifest.h"
      25                 :             : #include "fe_utils/simple_list.h"
      26                 :             : #include "getopt_long.h"
      27                 :             : #include "pg_verifybackup.h"
      28                 :             : #include "pgtime.h"
      29                 :             : 
      30                 :             : /*
      31                 :             :  * For efficiency, we'd like our hash table containing information about the
      32                 :             :  * manifest to start out with approximately the correct number of entries.
      33                 :             :  * There's no way to know the exact number of entries without reading the whole
      34                 :             :  * file, but we can get an estimate by dividing the file size by the estimated
      35                 :             :  * number of bytes per line.
      36                 :             :  *
      37                 :             :  * This could be off by about a factor of two in either direction, because the
      38                 :             :  * checksum algorithm has a big impact on the line lengths; e.g. a SHA512
      39                 :             :  * checksum is 128 hex bytes, whereas a CRC-32C value is only 8, and there
      40                 :             :  * might be no checksum at all.
      41                 :             :  */
      42                 :             : #define ESTIMATED_BYTES_PER_MANIFEST_LINE   100
      43                 :             : 
      44                 :             : /*
      45                 :             :  * How many bytes should we try to read from a file at once?
      46                 :             :  */
      47                 :             : #define READ_CHUNK_SIZE             (128 * 1024)
      48                 :             : 
      49                 :             : /*
      50                 :             :  * Tar file information needed for content verification.
      51                 :             :  */
      52                 :             : typedef struct tar_file
      53                 :             : {
      54                 :             :     char       *relpath;
      55                 :             :     Oid         tblspc_oid;
      56                 :             :     pg_compress_algorithm compress_algorithm;
      57                 :             : } tar_file;
      58                 :             : 
      59                 :             : static manifest_data *parse_manifest_file(char *manifest_path);
      60                 :             : static void verifybackup_version_cb(JsonManifestParseContext *context,
      61                 :             :                                     int manifest_version);
      62                 :             : static void verifybackup_system_identifier(JsonManifestParseContext *context,
      63                 :             :                                            uint64 manifest_system_identifier);
      64                 :             : static void verifybackup_per_file_cb(JsonManifestParseContext *context,
      65                 :             :                                      const char *pathname, uint64 size,
      66                 :             :                                      pg_checksum_type checksum_type,
      67                 :             :                                      int checksum_length,
      68                 :             :                                      uint8 *checksum_payload);
      69                 :             : static void verifybackup_per_wal_range_cb(JsonManifestParseContext *context,
      70                 :             :                                           TimeLineID tli,
      71                 :             :                                           XLogRecPtr start_lsn,
      72                 :             :                                           XLogRecPtr end_lsn);
      73                 :             : pg_noreturn static void report_manifest_error(JsonManifestParseContext *context,
      74                 :             :                                               const char *fmt, ...)
      75                 :             :             pg_attribute_printf(2, 3);
      76                 :             : 
      77                 :             : static void verify_tar_backup(verifier_context *context, DIR *dir,
      78                 :             :                               char **base_archive_path,
      79                 :             :                               char **wal_archive_path);
      80                 :             : static void verify_plain_backup_directory(verifier_context *context,
      81                 :             :                                           char *relpath, char *fullpath,
      82                 :             :                                           DIR *dir);
      83                 :             : static void verify_plain_backup_file(verifier_context *context, char *relpath,
      84                 :             :                                      char *fullpath);
      85                 :             : static void verify_control_file(const char *controlpath,
      86                 :             :                                 uint64 manifest_system_identifier);
      87                 :             : static void precheck_tar_backup_file(verifier_context *context, char *relpath,
      88                 :             :                                      char *fullpath, SimplePtrList *tarfiles,
      89                 :             :                                      char **base_archive_path,
      90                 :             :                                      char **wal_archive_path);
      91                 :             : static void verify_tar_file(verifier_context *context, char *relpath,
      92                 :             :                             char *fullpath, astreamer *streamer);
      93                 :             : static void report_extra_backup_files(verifier_context *context);
      94                 :             : static void verify_backup_checksums(verifier_context *context);
      95                 :             : static void verify_file_checksum(verifier_context *context,
      96                 :             :                                  manifest_file *m, char *fullpath,
      97                 :             :                                  uint8 *buffer);
      98                 :             : static void parse_required_wal(verifier_context *context,
      99                 :             :                                char *pg_waldump_path,
     100                 :             :                                char *wal_path);
     101                 :             : static astreamer *create_archive_verifier(verifier_context *context,
     102                 :             :                                           char *archive_name,
     103                 :             :                                           Oid tblspc_oid,
     104                 :             :                                           pg_compress_algorithm compress_algo);
     105                 :             : 
     106                 :             : static void progress_report(bool finished);
     107                 :             : static void usage(void);
     108                 :             : 
     109                 :             : static const char *progname;
     110                 :             : 
     111                 :             : /* is progress reporting enabled? */
     112                 :             : static bool show_progress = false;
     113                 :             : 
     114                 :             : /* Progress indicators */
     115                 :             : static uint64 total_size = 0;
     116                 :             : static uint64 done_size = 0;
     117                 :             : 
     118                 :             : /*
     119                 :             :  * Main entry point.
     120                 :             :  */
     121                 :             : int
     122                 :         122 : main(int argc, char **argv)
     123                 :             : {
     124                 :             :     static struct option long_options[] = {
     125                 :             :         {"exit-on-error", no_argument, NULL, 'e'},
     126                 :             :         {"ignore", required_argument, NULL, 'i'},
     127                 :             :         {"manifest-path", required_argument, NULL, 'm'},
     128                 :             :         {"format", required_argument, NULL, 'F'},
     129                 :             :         {"no-parse-wal", no_argument, NULL, 'n'},
     130                 :             :         {"progress", no_argument, NULL, 'P'},
     131                 :             :         {"quiet", no_argument, NULL, 'q'},
     132                 :             :         {"skip-checksums", no_argument, NULL, 's'},
     133                 :             :         {"wal-path", required_argument, NULL, 'w'},
     134                 :             :         {"wal-directory", required_argument, NULL, 'w'},  /* deprecated */
     135                 :             :         {NULL, 0, NULL, 0}
     136                 :             :     };
     137                 :             : 
     138                 :             :     int         c;
     139                 :             :     verifier_context context;
     140                 :         122 :     char       *manifest_path = NULL;
     141                 :         122 :     bool        no_parse_wal = false;
     142                 :         122 :     bool        quiet = false;
     143                 :         122 :     char       *wal_path = NULL;
     144                 :         122 :     char       *base_archive_path = NULL;
     145                 :         122 :     char       *wal_archive_path = NULL;
     146                 :         122 :     char       *pg_waldump_path = NULL;
     147                 :             :     DIR        *dir;
     148                 :             : 
     149                 :         122 :     pg_logging_init(argv[0]);
     150                 :         122 :     set_pglocale_pgservice(argv[0], PG_TEXTDOMAIN("pg_verifybackup"));
     151                 :         122 :     progname = get_progname(argv[0]);
     152                 :             : 
     153                 :         122 :     memset(&context, 0, sizeof(context));
     154                 :             : 
     155         [ +  + ]:         122 :     if (argc > 1)
     156                 :             :     {
     157   [ +  +  -  + ]:         121 :         if (strcmp(argv[1], "--help") == 0 || strcmp(argv[1], "-?") == 0)
     158                 :             :         {
     159                 :           1 :             usage();
     160                 :           1 :             exit(0);
     161                 :             :         }
     162   [ +  +  -  + ]:         120 :         if (strcmp(argv[1], "--version") == 0 || strcmp(argv[1], "-V") == 0)
     163                 :             :         {
     164                 :           1 :             puts("pg_verifybackup (PostgreSQL) " PG_VERSION);
     165                 :           1 :             exit(0);
     166                 :             :         }
     167                 :             :     }
     168                 :             : 
     169                 :             :     /*
     170                 :             :      * Skip certain files in the toplevel directory.
     171                 :             :      *
     172                 :             :      * Ignore the backup_manifest file, because it's not included in the
     173                 :             :      * backup manifest.
     174                 :             :      *
     175                 :             :      * Ignore the pg_wal directory, because those files are not included in
     176                 :             :      * the backup manifest either, since they are fetched separately from the
     177                 :             :      * backup itself, and verified via a separate mechanism.
     178                 :             :      *
     179                 :             :      * Ignore postgresql.auto.conf, recovery.signal, and standby.signal,
     180                 :             :      * because we expect that those files may sometimes be created or changed
     181                 :             :      * as part of the backup process. For example, pg_basebackup -R will
     182                 :             :      * modify postgresql.auto.conf and create standby.signal.
     183                 :             :      */
     184                 :         120 :     simple_string_list_append(&context.ignore_list, "backup_manifest");
     185                 :         120 :     simple_string_list_append(&context.ignore_list, "pg_wal");
     186                 :         120 :     simple_string_list_append(&context.ignore_list, "postgresql.auto.conf");
     187                 :         120 :     simple_string_list_append(&context.ignore_list, "recovery.signal");
     188                 :         120 :     simple_string_list_append(&context.ignore_list, "standby.signal");
     189                 :             : 
     190         [ +  + ]:         168 :     while ((c = getopt_long(argc, argv, "eF:i:m:nPqsw:", long_options, NULL)) != -1)
     191                 :             :     {
     192   [ +  +  +  +  :          50 :         switch (c)
          +  +  +  +  +  
                      + ]
     193                 :             :         {
     194                 :          26 :             case 'e':
     195                 :          26 :                 context.exit_on_error = true;
     196                 :          26 :                 break;
     197                 :           4 :             case 'i':
     198                 :             :                 {
     199                 :           4 :                     char       *arg = pstrdup(optarg);
     200                 :             : 
     201                 :           4 :                     canonicalize_path(arg);
     202                 :           4 :                     simple_string_list_append(&context.ignore_list, arg);
     203                 :           4 :                     break;
     204                 :             :                 }
     205                 :           4 :             case 'm':
     206                 :           4 :                 manifest_path = pstrdup(optarg);
     207                 :           4 :                 canonicalize_path(manifest_path);
     208                 :           4 :                 break;
     209                 :           3 :             case 'F':
     210   [ +  -  +  + ]:           3 :                 if (strcmp(optarg, "p") == 0 || strcmp(optarg, "plain") == 0)
     211                 :           1 :                     context.format = 'p';
     212   [ +  -  +  + ]:           2 :                 else if (strcmp(optarg, "t") == 0 || strcmp(optarg, "tar") == 0)
     213                 :           1 :                     context.format = 't';
     214                 :             :                 else
     215                 :           1 :                     pg_fatal("invalid backup format \"%s\", must be \"plain\" or \"tar\"",
     216                 :             :                              optarg);
     217                 :           2 :                 break;
     218                 :           4 :             case 'n':
     219                 :           4 :                 no_parse_wal = true;
     220                 :           4 :                 break;
     221                 :           2 :             case 'P':
     222                 :           2 :                 show_progress = true;
     223                 :           2 :                 break;
     224                 :           3 :             case 'q':
     225                 :           3 :                 quiet = true;
     226                 :           3 :                 break;
     227                 :           2 :             case 's':
     228                 :           2 :                 context.skip_checksums = true;
     229                 :           2 :                 break;
     230                 :           1 :             case 'w':
     231                 :           1 :                 wal_path = pstrdup(optarg);
     232                 :           1 :                 canonicalize_path(wal_path);
     233                 :           1 :                 break;
     234                 :           1 :             default:
     235                 :             :                 /* getopt_long already emitted a complaint */
     236                 :           1 :                 pg_log_error_hint("Try \"%s --help\" for more information.", progname);
     237                 :           1 :                 exit(1);
     238                 :             :         }
     239                 :             :     }
     240                 :             : 
     241                 :             :     /* Get backup directory name */
     242         [ +  + ]:         118 :     if (optind >= argc)
     243                 :             :     {
     244                 :           1 :         pg_log_error("no backup directory specified");
     245                 :           1 :         pg_log_error_hint("Try \"%s --help\" for more information.", progname);
     246                 :           1 :         exit(1);
     247                 :             :     }
     248                 :         117 :     context.backup_directory = pstrdup(argv[optind++]);
     249                 :         117 :     canonicalize_path(context.backup_directory);
     250                 :             : 
     251                 :             :     /* Complain if any arguments remain */
     252         [ +  + ]:         117 :     if (optind < argc)
     253                 :             :     {
     254                 :           1 :         pg_log_error("too many command-line arguments (first is \"%s\")",
     255                 :             :                      argv[optind]);
     256                 :           1 :         pg_log_error_hint("Try \"%s --help\" for more information.", progname);
     257                 :           1 :         exit(1);
     258                 :             :     }
     259                 :             : 
     260                 :             :     /* Complain if the specified arguments conflict */
     261   [ +  +  +  + ]:         116 :     if (show_progress && quiet)
     262                 :           1 :         pg_fatal("cannot specify both %s and %s",
     263                 :             :                  "-P/--progress", "-q/--quiet");
     264                 :             : 
     265                 :             :     /* Unless --no-parse-wal was specified, we will need pg_waldump. */
     266         [ +  + ]:         115 :     if (!no_parse_wal)
     267                 :             :     {
     268                 :             :         int         ret;
     269                 :             : 
     270                 :         111 :         pg_waldump_path = pg_malloc(MAXPGPATH);
     271                 :         111 :         ret = find_other_exec(argv[0], "pg_waldump",
     272                 :             :                               "pg_waldump (PostgreSQL) " PG_VERSION "\n",
     273                 :             :                               pg_waldump_path);
     274         [ -  + ]:         111 :         if (ret < 0)
     275                 :             :         {
     276                 :             :             char        full_path[MAXPGPATH];
     277                 :             : 
     278         [ #  # ]:           0 :             if (find_my_exec(argv[0], full_path) < 0)
     279                 :           0 :                 strlcpy(full_path, progname, sizeof(full_path));
     280                 :             : 
     281         [ #  # ]:           0 :             if (ret == -1)
     282                 :           0 :                 pg_fatal("program \"%s\" is needed by %s but was not found in the same directory as \"%s\"",
     283                 :             :                          "pg_waldump", "pg_verifybackup", full_path);
     284                 :             :             else
     285                 :           0 :                 pg_fatal("program \"%s\" was found by \"%s\" but was not the same version as %s",
     286                 :             :                          "pg_waldump", full_path, "pg_verifybackup");
     287                 :             :         }
     288                 :             :     }
     289                 :             : 
     290                 :             :     /* By default, look for the manifest in the backup directory. */
     291         [ +  + ]:         115 :     if (manifest_path == NULL)
     292                 :         111 :         manifest_path = psprintf("%s/backup_manifest",
     293                 :             :                                  context.backup_directory);
     294                 :             : 
     295                 :             :     /*
     296                 :             :      * Try to read the manifest. We treat any errors encountered while parsing
     297                 :             :      * the manifest as fatal; there doesn't seem to be much point in trying to
     298                 :             :      * verify the backup directory against a corrupted manifest.
     299                 :             :      */
     300                 :         115 :     context.manifest = parse_manifest_file(manifest_path);
     301                 :             : 
     302                 :             :     /*
     303                 :             :      * If the backup directory cannot be found, treat this as a fatal error.
     304                 :             :      */
     305                 :          80 :     dir = opendir(context.backup_directory);
     306         [ +  + ]:          80 :     if (dir == NULL)
     307                 :           1 :         report_fatal_error("could not open directory \"%s\": %m",
     308                 :             :                            context.backup_directory);
     309                 :             : 
     310                 :             :     /*
     311                 :             :      * At this point, we know that the backup directory exists, so it's now
     312                 :             :      * reasonable to check for files immediately inside it. Thus, before going
     313                 :             :      * further, if the user did not specify the backup format, check for
     314                 :             :      * PG_VERSION to distinguish between tar and plain format.
     315                 :             :      */
     316         [ +  + ]:          79 :     if (context.format == '\0')
     317                 :             :     {
     318                 :             :         struct stat sb;
     319                 :             :         char       *path;
     320                 :             : 
     321                 :          77 :         path = psprintf("%s/%s", context.backup_directory, "PG_VERSION");
     322         [ +  + ]:          77 :         if (stat(path, &sb) == 0)
     323                 :          54 :             context.format = 'p';
     324         [ -  + ]:          23 :         else if (errno != ENOENT)
     325                 :             :         {
     326                 :           0 :             pg_log_error("could not stat file \"%s\": %m", path);
     327                 :           0 :             exit(1);
     328                 :             :         }
     329                 :             :         else
     330                 :             :         {
     331                 :             :             /* No PG_VERSION, so assume tar format. */
     332                 :          23 :             context.format = 't';
     333                 :             :         }
     334                 :          77 :         pfree(path);
     335                 :             :     }
     336                 :             : 
     337                 :             :     /*
     338                 :             :      * Perform the appropriate type of verification appropriate based on the
     339                 :             :      * backup format. This will close 'dir'.
     340                 :             :      */
     341         [ +  + ]:          79 :     if (context.format == 'p')
     342                 :          55 :         verify_plain_backup_directory(&context, NULL, context.backup_directory,
     343                 :             :                                       dir);
     344                 :             :     else
     345                 :          24 :         verify_tar_backup(&context, dir, &base_archive_path, &wal_archive_path);
     346                 :             : 
     347                 :             :     /*
     348                 :             :      * The "matched" flag should now be set on every entry in the hash table.
     349                 :             :      * Any entries for which the bit is not set are files mentioned in the
     350                 :             :      * manifest that don't exist on disk (or in the relevant tar files).
     351                 :             :      */
     352                 :          77 :     report_extra_backup_files(&context);
     353                 :             : 
     354                 :             :     /*
     355                 :             :      * If this is a tar-format backup, checksums were already verified above;
     356                 :             :      * but if it's a plain-format backup, we postpone it until this point,
     357                 :             :      * since the earlier checks can be performed just by knowing which files
     358                 :             :      * are present, without needing to read all of them.
     359                 :             :      */
     360   [ +  +  +  + ]:          76 :     if (context.format == 'p' && !context.skip_checksums)
     361                 :          51 :         verify_backup_checksums(&context);
     362                 :             : 
     363                 :             :     /*
     364                 :             :      * By default, WAL files are expected to be found in the backup directory
     365                 :             :      * for plain-format backups. In the case of tar-format backups, if a
     366                 :             :      * separate WAL archive is not found, the WAL files are most likely
     367                 :             :      * included within the main data directory archive.
     368                 :             :      */
     369         [ +  + ]:          76 :     if (wal_path == NULL)
     370                 :             :     {
     371         [ +  + ]:          75 :         if (context.format == 'p')
     372                 :          52 :             wal_path = psprintf("%s/pg_wal", context.backup_directory);
     373         [ +  + ]:          23 :         else if (wal_archive_path)
     374                 :          14 :             wal_path = wal_archive_path;
     375         [ +  + ]:           9 :         else if (base_archive_path)
     376                 :           8 :             wal_path = base_archive_path;
     377                 :             :         else
     378                 :             :         {
     379                 :           1 :             pg_log_error("WAL archive not found");
     380                 :           1 :             pg_log_error_hint("Specify the correct path using the option -w/--wal-path.  "
     381                 :             :                               "Or you must use -n/--no-parse-wal when verifying a tar-format backup.");
     382                 :           1 :             exit(1);
     383                 :             :         }
     384                 :             :     }
     385                 :             : 
     386                 :             :     /*
     387                 :             :      * Try to parse the required ranges of WAL records, unless we were told
     388                 :             :      * not to do so.
     389                 :             :      */
     390         [ +  + ]:          75 :     if (!no_parse_wal)
     391                 :          72 :         parse_required_wal(&context, pg_waldump_path, wal_path);
     392                 :             : 
     393                 :             :     /*
     394                 :             :      * If everything looks OK, tell the user this, unless we were asked to
     395                 :             :      * work quietly.
     396                 :             :      */
     397   [ +  +  +  + ]:          75 :     if (!context.saw_any_error && !quiet)
     398                 :          52 :         printf(_("backup successfully verified\n"));
     399                 :             : 
     400                 :          75 :     return context.saw_any_error ? 1 : 0;
     401                 :             : }
     402                 :             : 
     403                 :             : /*
     404                 :             :  * Parse a manifest file and return a data structure describing the contents.
     405                 :             :  */
     406                 :             : static manifest_data *
     407                 :         115 : parse_manifest_file(char *manifest_path)
     408                 :             : {
     409                 :             :     int         fd;
     410                 :             :     struct stat statbuf;
     411                 :             :     off_t       estimate;
     412                 :             :     uint32      initial_size;
     413                 :             :     manifest_files_hash *ht;
     414                 :             :     char       *buffer;
     415                 :             :     JsonManifestParseContext context;
     416                 :             :     manifest_data *result;
     417                 :             :     size_t      total_size;
     418                 :         115 :     const size_t chunk_size = READ_CHUNK_SIZE;
     419                 :             : 
     420                 :             :     /* Open the manifest file. */
     421         [ +  + ]:         115 :     if ((fd = open(manifest_path, O_RDONLY | PG_BINARY, 0)) < 0)
     422                 :           3 :         report_fatal_error("could not open file \"%s\": %m", manifest_path);
     423                 :             : 
     424                 :             :     /* Figure out how big the manifest is. */
     425         [ -  + ]:         112 :     if (fstat(fd, &statbuf) != 0)
     426                 :           0 :         report_fatal_error("could not stat file \"%s\": %m", manifest_path);
     427                 :             : 
     428                 :             :     /* Guess how large to make the hash table based on the manifest size. */
     429                 :         112 :     estimate = statbuf.st_size / ESTIMATED_BYTES_PER_MANIFEST_LINE;
     430                 :         112 :     initial_size = Min(PG_UINT32_MAX, Max(estimate, 256));
     431                 :             : 
     432                 :             :     /* Create the hash table. */
     433                 :         112 :     ht = manifest_files_create(initial_size, NULL);
     434                 :             : 
     435                 :         112 :     result = pg_malloc0_object(manifest_data);
     436                 :         112 :     result->files = ht;
     437                 :         112 :     context.private_data = result;
     438                 :         112 :     context.version_cb = verifybackup_version_cb;
     439                 :         112 :     context.system_identifier_cb = verifybackup_system_identifier;
     440                 :         112 :     context.per_file_cb = verifybackup_per_file_cb;
     441                 :         112 :     context.per_wal_range_cb = verifybackup_per_wal_range_cb;
     442                 :         112 :     context.error_cb = report_manifest_error;
     443                 :             : 
     444                 :         112 :     total_size = statbuf.st_size;
     445                 :             : 
     446                 :             :     /*
     447                 :             :      * Parse the file, in chunks if necessary.
     448                 :             :      */
     449         [ +  + ]:         112 :     if (total_size <= chunk_size)
     450                 :             :     {
     451                 :             :         ssize_t     rc;
     452                 :             : 
     453                 :          33 :         buffer = pg_malloc(total_size);
     454                 :          33 :         rc = read(fd, buffer, total_size);
     455         [ -  + ]:          33 :         if (rc != total_size)
     456                 :             :         {
     457         [ #  # ]:           0 :             if (rc < 0)
     458                 :           0 :                 pg_fatal("could not read file \"%s\": %m", manifest_path);
     459                 :             :             else
     460                 :           0 :                 pg_fatal("could not read file \"%s\": read %zd of %zu",
     461                 :             :                          manifest_path, rc, total_size);
     462                 :             :         }
     463                 :             : 
     464                 :             :         /* Close the manifest file. */
     465                 :          33 :         close(fd);
     466                 :             : 
     467                 :             :         /* Parse the manifest. */
     468                 :          33 :         json_parse_manifest(&context, buffer, total_size);
     469                 :             :     }
     470                 :             :     else
     471                 :             :     {
     472                 :          79 :         size_t      bytes_left = total_size;
     473                 :             :         JsonManifestParseIncrementalState *inc_state;
     474                 :             : 
     475                 :          79 :         inc_state = json_parse_manifest_incremental_init(&context);
     476                 :             : 
     477                 :          79 :         buffer = pg_malloc(chunk_size + 1);
     478                 :             : 
     479         [ +  + ]:         237 :         while (bytes_left > 0)
     480                 :             :         {
     481                 :             :             ssize_t     rc;
     482                 :         160 :             size_t      bytes_to_read = chunk_size;
     483                 :             : 
     484                 :             :             /*
     485                 :             :              * Make sure that the last chunk is sufficiently large. (i.e. at
     486                 :             :              * least half the chunk size) so that it will contain fully the
     487                 :             :              * piece at the end with the checksum.
     488                 :             :              */
     489         [ +  + ]:         160 :             if (bytes_left < chunk_size)
     490                 :          79 :                 bytes_to_read = bytes_left;
     491         [ +  + ]:          81 :             else if (bytes_left < 2 * chunk_size)
     492                 :          79 :                 bytes_to_read = bytes_left / 2;
     493                 :         160 :             rc = read(fd, buffer, bytes_to_read);
     494         [ -  + ]:         160 :             if (rc != bytes_to_read)
     495                 :             :             {
     496         [ #  # ]:           0 :                 if (rc < 0)
     497                 :           0 :                     pg_fatal("could not read file \"%s\": %m", manifest_path);
     498                 :             :                 else
     499                 :           0 :                     pg_fatal("could not read file \"%s\": read %zu of %zu",
     500                 :             :                              manifest_path,
     501                 :             :                              total_size + rc - bytes_left,
     502                 :             :                              total_size);
     503                 :             :             }
     504                 :         160 :             bytes_left -= rc;
     505                 :         160 :             json_parse_manifest_incremental_chunk(inc_state, buffer, rc,
     506                 :             :                                                   bytes_left == 0);
     507                 :             :         }
     508                 :             : 
     509                 :             :         /* Release the incremental state memory */
     510                 :          77 :         json_parse_manifest_incremental_shutdown(inc_state);
     511                 :             : 
     512                 :          77 :         close(fd);
     513                 :             :     }
     514                 :             : 
     515                 :             :     /* Done with the buffer. */
     516                 :          80 :     pg_free(buffer);
     517                 :             : 
     518                 :          80 :     return result;
     519                 :             : }
     520                 :             : 
     521                 :             : /*
     522                 :             :  * Report an error while parsing the manifest.
     523                 :             :  *
     524                 :             :  * We consider all such errors to be fatal errors. The manifest parser
     525                 :             :  * expects this function not to return.
     526                 :             :  */
     527                 :             : static void
     528                 :          31 : report_manifest_error(JsonManifestParseContext *context, const char *fmt, ...)
     529                 :             : {
     530                 :             :     va_list     ap;
     531                 :             : 
     532                 :          31 :     va_start(ap, fmt);
     533                 :          31 :     pg_log_generic_v(PG_LOG_ERROR, PG_LOG_PRIMARY, gettext(fmt), ap);
     534                 :          31 :     va_end(ap);
     535                 :             : 
     536                 :          31 :     exit(1);
     537                 :             : }
     538                 :             : 
     539                 :             : /*
     540                 :             :  * Record details extracted from the backup manifest.
     541                 :             :  */
     542                 :             : static void
     543                 :         106 : verifybackup_version_cb(JsonManifestParseContext *context,
     544                 :             :                         int manifest_version)
     545                 :             : {
     546                 :         106 :     manifest_data *manifest = context->private_data;
     547                 :             : 
     548                 :             :     /* Validation will be at the later stage */
     549                 :         106 :     manifest->version = manifest_version;
     550                 :         106 : }
     551                 :             : 
     552                 :             : /*
     553                 :             :  * Record details extracted from the backup manifest.
     554                 :             :  */
     555                 :             : static void
     556                 :          82 : verifybackup_system_identifier(JsonManifestParseContext *context,
     557                 :             :                                uint64 manifest_system_identifier)
     558                 :             : {
     559                 :          82 :     manifest_data *manifest = context->private_data;
     560                 :             : 
     561                 :             :     /* Validation will be at the later stage */
     562                 :          82 :     manifest->system_identifier = manifest_system_identifier;
     563                 :          82 : }
     564                 :             : 
     565                 :             : /*
     566                 :             :  * Record details extracted from the backup manifest for one file.
     567                 :             :  */
     568                 :             : static void
     569                 :       84496 : verifybackup_per_file_cb(JsonManifestParseContext *context,
     570                 :             :                          const char *pathname, uint64 size,
     571                 :             :                          pg_checksum_type checksum_type,
     572                 :             :                          int checksum_length, uint8 *checksum_payload)
     573                 :             : {
     574                 :       84496 :     manifest_data *manifest = context->private_data;
     575                 :       84496 :     manifest_files_hash *ht = manifest->files;
     576                 :             :     manifest_file *m;
     577                 :             :     bool        found;
     578                 :             : 
     579                 :             :     /* Make a new entry in the hash table for this file. */
     580                 :       84496 :     m = manifest_files_insert(ht, pathname, &found);
     581         [ +  + ]:       84496 :     if (found)
     582                 :           1 :         report_fatal_error("duplicate path name in backup manifest: \"%s\"",
     583                 :             :                            pathname);
     584                 :             : 
     585                 :             :     /* Initialize the entry. */
     586                 :       84495 :     m->size = size;
     587                 :       84495 :     m->checksum_type = checksum_type;
     588                 :       84495 :     m->checksum_length = checksum_length;
     589                 :       84495 :     m->checksum_payload = checksum_payload;
     590                 :       84495 :     m->matched = false;
     591                 :       84495 :     m->bad = false;
     592                 :       84495 : }
     593                 :             : 
     594                 :             : /*
     595                 :             :  * Record details extracted from the backup manifest for one WAL range.
     596                 :             :  */
     597                 :             : static void
     598                 :          83 : verifybackup_per_wal_range_cb(JsonManifestParseContext *context,
     599                 :             :                               TimeLineID tli,
     600                 :             :                               XLogRecPtr start_lsn, XLogRecPtr end_lsn)
     601                 :             : {
     602                 :          83 :     manifest_data *manifest = context->private_data;
     603                 :             :     manifest_wal_range *range;
     604                 :             : 
     605                 :             :     /* Allocate and initialize a struct describing this WAL range. */
     606                 :          83 :     range = palloc_object(manifest_wal_range);
     607                 :          83 :     range->tli = tli;
     608                 :          83 :     range->start_lsn = start_lsn;
     609                 :          83 :     range->end_lsn = end_lsn;
     610                 :          83 :     range->prev = manifest->last_wal_range;
     611                 :          83 :     range->next = NULL;
     612                 :             : 
     613                 :             :     /* Add it to the end of the list. */
     614         [ +  - ]:          83 :     if (manifest->first_wal_range == NULL)
     615                 :          83 :         manifest->first_wal_range = range;
     616                 :             :     else
     617                 :           0 :         manifest->last_wal_range->next = range;
     618                 :          83 :     manifest->last_wal_range = range;
     619                 :          83 : }
     620                 :             : 
     621                 :             : /*
     622                 :             :  * Verify one directory of a plain-format backup.
     623                 :             :  *
     624                 :             :  * 'relpath' is NULL if we are to verify the top-level backup directory,
     625                 :             :  * and otherwise the relative path to the directory that is to be verified.
     626                 :             :  *
     627                 :             :  * 'fullpath' is the backup directory with 'relpath' appended; i.e. the actual
     628                 :             :  * filesystem path at which it can be found.
     629                 :             :  *
     630                 :             :  * 'dir' is an open directory handle, or NULL if the caller wants us to
     631                 :             :  * open it. If the caller chooses to pass a handle, we'll close it when
     632                 :             :  * we're done with it.
     633                 :             :  */
     634                 :             : static void
     635                 :        1365 : verify_plain_backup_directory(verifier_context *context, char *relpath,
     636                 :             :                               char *fullpath, DIR *dir)
     637                 :             : {
     638                 :             :     struct dirent *dirent;
     639                 :             : 
     640                 :             :     /* Open the directory unless the caller did it. */
     641   [ +  +  +  + ]:        1365 :     if (dir == NULL && ((dir = opendir(fullpath)) == NULL))
     642                 :             :     {
     643                 :           1 :         report_backup_error(context,
     644                 :             :                             "could not open directory \"%s\": %m", fullpath);
     645                 :           1 :         simple_string_list_append(&context->ignore_list, relpath);
     646                 :             : 
     647                 :           1 :         return;
     648                 :             :     }
     649                 :             : 
     650         [ +  + ]:       60197 :     while (errno = 0, (dirent = readdir(dir)) != NULL)
     651                 :             :     {
     652                 :       58835 :         char       *filename = dirent->d_name;
     653                 :       58835 :         char       *newfullpath = psprintf("%s/%s", fullpath, filename);
     654                 :             :         char       *newrelpath;
     655                 :             : 
     656                 :             :         /* Skip "." and ".." */
     657   [ +  +  +  + ]:       58835 :         if (filename[0] == '.' && (filename[1] == '\0'
     658         [ +  - ]:        1362 :                                    || strcmp(filename, "..") == 0))
     659                 :        2725 :             continue;
     660                 :             : 
     661         [ +  + ]:       56110 :         if (relpath == NULL)
     662                 :        1357 :             newrelpath = pstrdup(filename);
     663                 :             :         else
     664                 :       54753 :             newrelpath = psprintf("%s/%s", relpath, filename);
     665                 :             : 
     666         [ +  + ]:       56110 :         if (!should_ignore_relpath(context, newrelpath))
     667                 :       55950 :             verify_plain_backup_file(context, newrelpath, newfullpath);
     668                 :             : 
     669                 :       56108 :         pfree(newfullpath);
     670                 :       56108 :         pfree(newrelpath);
     671                 :             :     }
     672                 :             : 
     673         [ -  + ]:        1362 :     if (closedir(dir))
     674                 :             :     {
     675                 :           0 :         report_backup_error(context,
     676                 :             :                             "could not close directory \"%s\": %m", fullpath);
     677                 :           0 :         return;
     678                 :             :     }
     679                 :             : }
     680                 :             : 
     681                 :             : /*
     682                 :             :  * Verify one file (which might actually be a directory or a symlink).
     683                 :             :  *
     684                 :             :  * The arguments to this function have the same meaning as the similarly named
     685                 :             :  * arguments to verify_plain_backup_directory.
     686                 :             :  */
     687                 :             : static void
     688                 :       55950 : verify_plain_backup_file(verifier_context *context, char *relpath,
     689                 :             :                          char *fullpath)
     690                 :             : {
     691                 :             :     struct stat sb;
     692                 :             :     manifest_file *m;
     693                 :             : 
     694         [ +  + ]:       55950 :     if (stat(fullpath, &sb) != 0)
     695                 :             :     {
     696                 :           3 :         report_backup_error(context,
     697                 :             :                             "could not stat file or directory \"%s\": %m",
     698                 :             :                             relpath);
     699                 :             : 
     700                 :             :         /*
     701                 :             :          * Suppress further errors related to this path name and, if it's a
     702                 :             :          * directory, anything underneath it.
     703                 :             :          */
     704                 :           3 :         simple_string_list_append(&context->ignore_list, relpath);
     705                 :             : 
     706                 :        1314 :         return;
     707                 :             :     }
     708                 :             : 
     709                 :             :     /* If it's a directory, just recurse. */
     710         [ +  + ]:       55947 :     if (S_ISDIR(sb.st_mode))
     711                 :             :     {
     712                 :        1310 :         verify_plain_backup_directory(context, relpath, fullpath, NULL);
     713                 :        1309 :         return;
     714                 :             :     }
     715                 :             : 
     716                 :             :     /* If it's not a directory, it should be a regular file. */
     717         [ -  + ]:       54637 :     if (!S_ISREG(sb.st_mode))
     718                 :             :     {
     719                 :           0 :         report_backup_error(context,
     720                 :             :                             "\"%s\" is not a regular file or directory",
     721                 :             :                             relpath);
     722                 :           0 :         return;
     723                 :             :     }
     724                 :             : 
     725                 :             :     /* Check whether there's an entry in the manifest hash. */
     726                 :       54637 :     m = manifest_files_lookup(context->manifest->files, relpath);
     727         [ +  + ]:       54637 :     if (m == NULL)
     728                 :             :     {
     729                 :           2 :         report_backup_error(context,
     730                 :             :                             "\"%s\" is present on disk but not in the manifest",
     731                 :             :                             relpath);
     732                 :           2 :         return;
     733                 :             :     }
     734                 :             : 
     735                 :             :     /* Flag this entry as having been encountered in the filesystem. */
     736                 :       54635 :     m->matched = true;
     737                 :             : 
     738                 :             :     /* Check that the size matches. */
     739         [ +  + ]:       54635 :     if (m->size != sb.st_size)
     740                 :             :     {
     741                 :           3 :         report_backup_error(context,
     742                 :             :                             "\"%s\" has size %lld on disk but size %" PRIu64 " in the manifest",
     743                 :           3 :                             relpath, (long long) sb.st_size,
     744                 :             :                             m->size);
     745                 :           3 :         m->bad = true;
     746                 :             :     }
     747                 :             : 
     748                 :             :     /*
     749                 :             :      * Validate the manifest system identifier, not available in manifest
     750                 :             :      * version 1.
     751                 :             :      */
     752         [ +  - ]:       54635 :     if (context->manifest->version != 1 &&
     753         [ +  + ]:       54635 :         strcmp(relpath, XLOG_CONTROL_FILE) == 0)
     754                 :          55 :         verify_control_file(fullpath, context->manifest->system_identifier);
     755                 :             : 
     756                 :             :     /* Update statistics for progress report, if necessary */
     757   [ +  +  +  - ]:       54634 :     if (show_progress && !context->skip_checksums &&
     758   [ +  -  +  -  :        1027 :         should_verify_checksum(m))
                   +  - ]
     759                 :        1027 :         total_size += m->size;
     760                 :             : 
     761                 :             :     /*
     762                 :             :      * We don't verify checksums at this stage. We first finish verifying that
     763                 :             :      * we have the expected set of files with the expected sizes, and only
     764                 :             :      * afterwards verify the checksums. That's because computing checksums may
     765                 :             :      * take a while, and we'd like to report more obvious problems quickly.
     766                 :             :      */
     767                 :             : }
     768                 :             : 
     769                 :             : /*
     770                 :             :  * Sanity check control file and validate system identifier against manifest
     771                 :             :  * system identifier.
     772                 :             :  */
     773                 :             : static void
     774                 :          55 : verify_control_file(const char *controlpath, uint64 manifest_system_identifier)
     775                 :             : {
     776                 :             :     ControlFileData *control_file;
     777                 :             :     bool        crc_ok;
     778                 :             : 
     779         [ -  + ]:          55 :     pg_log_debug("reading \"%s\"", controlpath);
     780                 :          55 :     control_file = get_controlfile_by_exact_path(controlpath, &crc_ok);
     781                 :             : 
     782                 :             :     /* Control file contents not meaningful if CRC is bad. */
     783         [ -  + ]:          55 :     if (!crc_ok)
     784                 :           0 :         report_fatal_error("%s: CRC is incorrect", controlpath);
     785                 :             : 
     786                 :             :     /* Can't interpret control file if not current version. */
     787         [ -  + ]:          55 :     if (control_file->pg_control_version != PG_CONTROL_VERSION)
     788                 :           0 :         report_fatal_error("%s: unexpected control file version",
     789                 :             :                            controlpath);
     790                 :             : 
     791                 :             :     /* System identifiers should match. */
     792         [ +  + ]:          55 :     if (manifest_system_identifier != control_file->system_identifier)
     793                 :           1 :         report_fatal_error("%s: manifest system identifier is %" PRIu64 ", but control file has %" PRIu64,
     794                 :             :                            controlpath,
     795                 :             :                            manifest_system_identifier,
     796                 :             :                            control_file->system_identifier);
     797                 :             : 
     798                 :             :     /* Release memory. */
     799                 :          54 :     pfree(control_file);
     800                 :          54 : }
     801                 :             : 
     802                 :             : /*
     803                 :             :  * Verify tar backup.
     804                 :             :  *
     805                 :             :  * The caller should pass a handle to the target directory, which we will
     806                 :             :  * close when we're done with it.
     807                 :             :  */
     808                 :             : static void
     809                 :          24 : verify_tar_backup(verifier_context *context, DIR *dir, char **base_archive_path,
     810                 :             :                   char **wal_archive_path)
     811                 :             : {
     812                 :             :     struct dirent *dirent;
     813                 :          24 :     SimplePtrList tarfiles = {NULL, NULL};
     814                 :             :     SimplePtrListCell *cell;
     815                 :             : 
     816                 :             :     Assert(context->format != 'p');
     817                 :             : 
     818                 :          24 :     progress_report(false);
     819                 :             : 
     820                 :             :     /* First pass: scan the directory for tar files. */
     821         [ +  + ]:         169 :     while (errno = 0, (dirent = readdir(dir)) != NULL)
     822                 :             :     {
     823                 :         145 :         char       *filename = dirent->d_name;
     824                 :             : 
     825                 :             :         /* Skip "." and ".." */
     826   [ +  +  +  + ]:         145 :         if (filename[0] == '.' && (filename[1] == '\0'
     827         [ +  - ]:          24 :                                    || strcmp(filename, "..") == 0))
     828                 :          48 :             continue;
     829                 :             : 
     830                 :             :         /*
     831                 :             :          * Unless it's something we should ignore, perform prechecks and add
     832                 :             :          * it to the list.
     833                 :             :          */
     834         [ +  + ]:          97 :         if (!should_ignore_relpath(context, filename))
     835                 :             :         {
     836                 :             :             char       *fullpath;
     837                 :             : 
     838                 :          71 :             fullpath = psprintf("%s/%s", context->backup_directory, filename);
     839                 :          71 :             precheck_tar_backup_file(context, filename, fullpath, &tarfiles,
     840                 :             :                                      base_archive_path, wal_archive_path);
     841                 :          71 :             pfree(fullpath);
     842                 :             :         }
     843                 :             :     }
     844                 :             : 
     845         [ -  + ]:          24 :     if (closedir(dir))
     846                 :             :     {
     847                 :           0 :         report_backup_error(context,
     848                 :             :                             "could not close directory \"%s\": %m",
     849                 :             :                             context->backup_directory);
     850                 :           0 :         return;
     851                 :             :     }
     852                 :             : 
     853                 :             :     /* Second pass: Perform the final verification of the tar contents. */
     854         [ +  + ]:          56 :     for (cell = tarfiles.head; cell != NULL; cell = cell->next)
     855                 :             :     {
     856                 :          33 :         tar_file   *tar = (tar_file *) cell->ptr;
     857                 :             :         astreamer  *streamer;
     858                 :             :         char       *fullpath;
     859                 :             : 
     860                 :             :         /*
     861                 :             :          * Prepares the archive streamer stack according to the tar
     862                 :             :          * compression format.
     863                 :             :          */
     864                 :          33 :         streamer = create_archive_verifier(context,
     865                 :             :                                            tar->relpath,
     866                 :             :                                            tar->tblspc_oid,
     867                 :             :                                            tar->compress_algorithm);
     868                 :             : 
     869                 :             :         /* Compute the full pathname to the target file. */
     870                 :          33 :         fullpath = psprintf("%s/%s", context->backup_directory,
     871                 :             :                             tar->relpath);
     872                 :             : 
     873                 :             :         /* Invoke the streamer for reading, decompressing, and verifying. */
     874                 :          33 :         verify_tar_file(context, tar->relpath, fullpath, streamer);
     875                 :             : 
     876                 :             :         /* Cleanup. */
     877                 :          32 :         pfree(tar->relpath);
     878                 :          32 :         pfree(tar);
     879                 :          32 :         pfree(fullpath);
     880                 :             : 
     881                 :          32 :         astreamer_finalize(streamer);
     882                 :          32 :         astreamer_free(streamer);
     883                 :             :     }
     884                 :          23 :     simple_ptr_list_destroy(&tarfiles);
     885                 :             : 
     886                 :          23 :     progress_report(true);
     887                 :             : }
     888                 :             : 
     889                 :             : /*
     890                 :             :  * Preparatory steps for verifying files in tar format backups.
     891                 :             :  *
     892                 :             :  * Carries out basic validation of the tar format backup file, detects the
     893                 :             :  * compression type, and appends that information to the tarfiles list. An
     894                 :             :  * error will be reported if the tar file is inaccessible, or if the file type,
     895                 :             :  * name, or compression type is not as expected.
     896                 :             :  *
     897                 :             :  * The arguments to this function are mostly the same as the
     898                 :             :  * verify_plain_backup_file. The additional argument outputs a list of valid
     899                 :             :  * tar files, along with the full paths to the main archive and the WAL
     900                 :             :  * directory archive.
     901                 :             :  */
     902                 :             : static void
     903                 :          71 : precheck_tar_backup_file(verifier_context *context, char *relpath,
     904                 :             :                          char *fullpath, SimplePtrList *tarfiles,
     905                 :             :                          char **base_archive_path, char **wal_archive_path)
     906                 :             : {
     907                 :             :     struct stat sb;
     908                 :          71 :     Oid         tblspc_oid = InvalidOid;
     909                 :             :     pg_compress_algorithm compress_algorithm;
     910                 :             :     tar_file   *tar;
     911                 :          71 :     char       *suffix = NULL;
     912                 :          71 :     bool        is_base_archive = false;
     913                 :          71 :     bool        is_wal_archive = false;
     914                 :             : 
     915                 :             :     /* Should be tar format backup */
     916                 :             :     Assert(context->format == 't');
     917                 :             : 
     918                 :             :     /* Get file information */
     919         [ -  + ]:          71 :     if (stat(fullpath, &sb) != 0)
     920                 :             :     {
     921                 :           0 :         report_backup_error(context,
     922                 :             :                             "could not stat file or directory \"%s\": %m",
     923                 :             :                             relpath);
     924                 :          37 :         return;
     925                 :             :     }
     926                 :             : 
     927                 :             :     /* In a tar format backup, we expect only regular files. */
     928         [ +  + ]:          71 :     if (!S_ISREG(sb.st_mode))
     929                 :             :     {
     930                 :          16 :         report_backup_error(context,
     931                 :             :                             "file \"%s\" is not a regular file",
     932                 :             :                             relpath);
     933                 :          16 :         return;
     934                 :             :     }
     935                 :             : 
     936                 :             :     /*
     937                 :             :      * We expect tar files for backing up the main directory, tablespace, and
     938                 :             :      * pg_wal directory.
     939                 :             :      *
     940                 :             :      * pg_basebackup writes the main data directory to an archive file named
     941                 :             :      * base.tar, the pg_wal directory to pg_wal.tar, and the tablespace
     942                 :             :      * directory to <tablespaceoid>.tar, each followed by a compression type
     943                 :             :      * extension such as .gz, .lz4, or .zst.
     944                 :             :      */
     945         [ +  + ]:          55 :     if (strncmp("base", relpath, 4) == 0)
     946                 :             :     {
     947                 :          23 :         suffix = relpath + 4;
     948                 :          23 :         is_base_archive = true;
     949                 :             :     }
     950         [ +  + ]:          32 :     else if (strncmp("pg_wal", relpath, 6) == 0)
     951                 :             :     {
     952                 :          15 :         suffix = relpath + 6;
     953                 :          15 :         is_wal_archive = true;
     954                 :             :     }
     955                 :             :     else
     956                 :             :     {
     957                 :             :         /* Expected a <tablespaceoid>.tar file here. */
     958                 :          17 :         uint64      num = strtoul(relpath, &suffix, 10);
     959                 :             : 
     960                 :             :         /*
     961                 :             :          * Report an error if we didn't consume at least one character, if the
     962                 :             :          * result is 0, or if the value is too large to be a valid OID.
     963                 :             :          */
     964   [ +  -  +  +  :          17 :         if (suffix == NULL || num <= 0 || num > OID_MAX)
                   -  + ]
     965                 :             :         {
     966                 :           6 :             report_backup_error(context,
     967                 :             :                                 "file \"%s\" is not expected in a tar format backup",
     968                 :             :                                 relpath);
     969                 :           6 :             return;
     970                 :             :         }
     971                 :          11 :         tblspc_oid = (Oid) num;
     972                 :             :     }
     973                 :             : 
     974                 :             :     /* Now, check the compression type of the tar */
     975         [ -  + ]:          49 :     if (!parse_tar_compress_algorithm(suffix, &compress_algorithm))
     976                 :             :     {
     977                 :           0 :         report_backup_error(context,
     978                 :             :                             "file \"%s\" is not expected in a tar format backup",
     979                 :             :                             relpath);
     980                 :           0 :         return;
     981                 :             :     }
     982                 :             : 
     983                 :             :     /*
     984                 :             :      * Ignore WALs, as reading and verification will be handled through
     985                 :             :      * pg_waldump.
     986                 :             :      */
     987         [ +  + ]:          49 :     if (is_wal_archive)
     988                 :             :     {
     989                 :          15 :         *wal_archive_path = pstrdup(fullpath);
     990                 :          15 :         return;
     991                 :             :     }
     992         [ +  + ]:          34 :     else if (is_base_archive)
     993                 :          23 :         *base_archive_path = pstrdup(fullpath);
     994                 :             : 
     995                 :             :     /*
     996                 :             :      * Append the information to the list for complete verification at a later
     997                 :             :      * stage.
     998                 :             :      */
     999                 :          34 :     tar = pg_malloc_object(tar_file);
    1000                 :          34 :     tar->relpath = pstrdup(relpath);
    1001                 :          34 :     tar->tblspc_oid = tblspc_oid;
    1002                 :          34 :     tar->compress_algorithm = compress_algorithm;
    1003                 :             : 
    1004                 :          34 :     simple_ptr_list_append(tarfiles, tar);
    1005                 :             : 
    1006                 :             :     /* Update statistics for progress report, if necessary */
    1007         [ -  + ]:          34 :     if (show_progress)
    1008                 :           0 :         total_size += sb.st_size;
    1009                 :             : }
    1010                 :             : 
    1011                 :             : /*
    1012                 :             :  * Verification of a single tar file content.
    1013                 :             :  *
    1014                 :             :  * It reads a given tar archive in predefined chunks and passes it to the
    1015                 :             :  * streamer, which initiates routines for decompression (if necessary) and then
    1016                 :             :  * verifies each member within the tar file.
    1017                 :             :  */
    1018                 :             : static void
    1019                 :          33 : verify_tar_file(verifier_context *context, char *relpath, char *fullpath,
    1020                 :             :                 astreamer *streamer)
    1021                 :             : {
    1022                 :             :     int         fd;
    1023                 :             :     ssize_t     rc;
    1024                 :             :     char       *buffer;
    1025                 :             : 
    1026         [ -  + ]:          33 :     pg_log_debug("reading \"%s\"", fullpath);
    1027                 :             : 
    1028                 :             :     /* Open the target file. */
    1029         [ -  + ]:          33 :     if ((fd = open(fullpath, O_RDONLY | PG_BINARY, 0)) < 0)
    1030                 :             :     {
    1031                 :           0 :         report_backup_error(context, "could not open file \"%s\": %m",
    1032                 :             :                             relpath);
    1033                 :           0 :         return;
    1034                 :             :     }
    1035                 :             : 
    1036                 :          33 :     buffer = pg_malloc(READ_CHUNK_SIZE * sizeof(uint8));
    1037                 :             : 
    1038                 :             :     /* Perform the reads */
    1039         [ +  + ]:        3573 :     while ((rc = read(fd, buffer, READ_CHUNK_SIZE)) > 0)
    1040                 :             :     {
    1041                 :        3541 :         astreamer_content(streamer, NULL, buffer, rc, ASTREAMER_UNKNOWN);
    1042                 :             : 
    1043                 :             :         /* Report progress */
    1044                 :        3540 :         done_size += rc;
    1045                 :        3540 :         progress_report(false);
    1046                 :             :     }
    1047                 :             : 
    1048                 :          32 :     pg_free(buffer);
    1049                 :             : 
    1050         [ -  + ]:          32 :     if (rc < 0)
    1051                 :           0 :         report_backup_error(context, "could not read file \"%s\": %m",
    1052                 :             :                             relpath);
    1053                 :             : 
    1054                 :             :     /* Close the file. */
    1055         [ -  + ]:          32 :     if (close(fd) != 0)
    1056                 :           0 :         report_backup_error(context, "could not close file \"%s\": %m",
    1057                 :             :                             relpath);
    1058                 :             : }
    1059                 :             : 
    1060                 :             : /*
    1061                 :             :  * Scan the hash table for entries where the 'matched' flag is not set; report
    1062                 :             :  * that such files are present in the manifest but not on disk.
    1063                 :             :  */
    1064                 :             : static void
    1065                 :          77 : report_extra_backup_files(verifier_context *context)
    1066                 :             : {
    1067                 :          77 :     manifest_data *manifest = context->manifest;
    1068                 :             :     manifest_files_iterator it;
    1069                 :             :     manifest_file *m;
    1070                 :             : 
    1071                 :          77 :     manifest_files_start_iterate(manifest->files, &it);
    1072         [ +  + ]:       78870 :     while ((m = manifest_files_iterate(manifest->files, &it)) != NULL)
    1073   [ +  +  +  + ]:       78717 :         if (!m->matched && !should_ignore_relpath(context, m->pathname))
    1074                 :        1035 :             report_backup_error(context,
    1075                 :             :                                 "\"%s\" is present in the manifest but not on disk",
    1076                 :             :                                 m->pathname);
    1077                 :          76 : }
    1078                 :             : 
    1079                 :             : /*
    1080                 :             :  * Verify checksums for hash table entries that are otherwise unproblematic.
    1081                 :             :  * If we've already reported some problem related to a hash table entry, or
    1082                 :             :  * if it has no checksum, just skip it.
    1083                 :             :  */
    1084                 :             : static void
    1085                 :          51 : verify_backup_checksums(verifier_context *context)
    1086                 :             : {
    1087                 :          51 :     manifest_data *manifest = context->manifest;
    1088                 :             :     manifest_files_iterator it;
    1089                 :             :     manifest_file *m;
    1090                 :             :     uint8      *buffer;
    1091                 :             : 
    1092                 :          51 :     progress_report(false);
    1093                 :             : 
    1094                 :          51 :     buffer = pg_malloc_array(uint8, READ_CHUNK_SIZE);
    1095                 :             : 
    1096                 :          51 :     manifest_files_start_iterate(manifest->files, &it);
    1097         [ +  + ]:       52593 :     while ((m = manifest_files_iterate(manifest->files, &it)) != NULL)
    1098                 :             :     {
    1099   [ +  +  +  +  :       52542 :         if (should_verify_checksum(m) &&
                   +  + ]
    1100         [ +  - ]:       49457 :             !should_ignore_relpath(context, m->pathname))
    1101                 :             :         {
    1102                 :             :             char       *fullpath;
    1103                 :             : 
    1104                 :             :             /* Compute the full pathname to the target file. */
    1105                 :       49457 :             fullpath = psprintf("%s/%s", context->backup_directory,
    1106                 :             :                                 m->pathname);
    1107                 :             : 
    1108                 :             :             /* Do the actual checksum verification. */
    1109                 :       49457 :             verify_file_checksum(context, m, fullpath, buffer);
    1110                 :             : 
    1111                 :             :             /* Avoid leaking memory. */
    1112                 :       49457 :             pfree(fullpath);
    1113                 :             :         }
    1114                 :             :     }
    1115                 :             : 
    1116                 :          51 :     pg_free(buffer);
    1117                 :             : 
    1118                 :          51 :     progress_report(true);
    1119                 :          51 : }
    1120                 :             : 
    1121                 :             : /*
    1122                 :             :  * Verify the checksum of a single file.
    1123                 :             :  */
    1124                 :             : static void
    1125                 :       49457 : verify_file_checksum(verifier_context *context, manifest_file *m,
    1126                 :             :                      char *fullpath, uint8 *buffer)
    1127                 :             : {
    1128                 :             :     pg_checksum_context checksum_ctx;
    1129                 :       49457 :     const char *relpath = m->pathname;
    1130                 :             :     int         fd;
    1131                 :             :     ssize_t     rc;
    1132                 :       49457 :     uint64      bytes_read = 0;
    1133                 :             :     uint8       checksumbuf[PG_CHECKSUM_MAX_LENGTH];
    1134                 :             :     int         checksumlen;
    1135                 :             : 
    1136                 :             :     /* Open the target file. */
    1137         [ +  + ]:       49457 :     if ((fd = open(fullpath, O_RDONLY | PG_BINARY, 0)) < 0)
    1138                 :             :     {
    1139                 :           1 :         report_backup_error(context, "could not open file \"%s\": %m",
    1140                 :             :                             relpath);
    1141                 :           1 :         return;
    1142                 :             :     }
    1143                 :             : 
    1144                 :             :     /* Initialize checksum context. */
    1145         [ -  + ]:       49456 :     if (pg_checksum_init(&checksum_ctx, m->checksum_type) < 0)
    1146                 :             :     {
    1147                 :           0 :         report_backup_error(context, "could not initialize checksum of file \"%s\"",
    1148                 :             :                             relpath);
    1149                 :           0 :         close(fd);
    1150                 :           0 :         return;
    1151                 :             :     }
    1152                 :             : 
    1153                 :             :     /* Read the file chunk by chunk, updating the checksum as we go. */
    1154         [ +  + ]:       92375 :     while ((rc = read(fd, buffer, READ_CHUNK_SIZE)) > 0)
    1155                 :             :     {
    1156                 :       42919 :         bytes_read += rc;
    1157         [ -  + ]:       42919 :         if (pg_checksum_update(&checksum_ctx, buffer, rc) < 0)
    1158                 :             :         {
    1159                 :           0 :             report_backup_error(context, "could not update checksum of file \"%s\"",
    1160                 :             :                                 relpath);
    1161                 :           0 :             close(fd);
    1162                 :           0 :             return;
    1163                 :             :         }
    1164                 :             : 
    1165                 :             :         /* Report progress */
    1166                 :       42919 :         done_size += rc;
    1167                 :       42919 :         progress_report(false);
    1168                 :             :     }
    1169         [ -  + ]:       49456 :     if (rc < 0)
    1170                 :           0 :         report_backup_error(context, "could not read file \"%s\": %m",
    1171                 :             :                             relpath);
    1172                 :             : 
    1173                 :             :     /* Close the file. */
    1174         [ -  + ]:       49456 :     if (close(fd) != 0)
    1175                 :             :     {
    1176                 :           0 :         report_backup_error(context, "could not close file \"%s\": %m",
    1177                 :             :                             relpath);
    1178                 :           0 :         return;
    1179                 :             :     }
    1180                 :             : 
    1181                 :             :     /* If we didn't manage to read the whole file, bail out now. */
    1182         [ -  + ]:       49456 :     if (rc < 0)
    1183                 :           0 :         return;
    1184                 :             : 
    1185                 :             :     /*
    1186                 :             :      * Double-check that we read the expected number of bytes from the file.
    1187                 :             :      * Normally, mismatches would be caught in verify_plain_backup_file and
    1188                 :             :      * this check would never be reached, but this provides additional safety
    1189                 :             :      * and clarity in the event of concurrent modifications or filesystem
    1190                 :             :      * misbehavior.
    1191                 :             :      */
    1192         [ -  + ]:       49456 :     if (bytes_read != m->size)
    1193                 :             :     {
    1194                 :           0 :         report_backup_error(context,
    1195                 :             :                             "file \"%s\" should contain %" PRIu64 " bytes, but read %" PRIu64,
    1196                 :             :                             relpath, m->size, bytes_read);
    1197                 :           0 :         return;
    1198                 :             :     }
    1199                 :             : 
    1200                 :             :     /* Get the final checksum. */
    1201                 :       49456 :     checksumlen = pg_checksum_final(&checksum_ctx, checksumbuf);
    1202         [ -  + ]:       49456 :     if (checksumlen < 0)
    1203                 :             :     {
    1204                 :           0 :         report_backup_error(context,
    1205                 :             :                             "could not finalize checksum of file \"%s\"",
    1206                 :             :                             relpath);
    1207                 :           0 :         return;
    1208                 :             :     }
    1209                 :             : 
    1210                 :             :     /* And check it against the manifest. */
    1211         [ -  + ]:       49456 :     if (checksumlen != m->checksum_length)
    1212                 :           0 :         report_backup_error(context,
    1213                 :             :                             "file \"%s\" has checksum of length %d, but expected %d",
    1214                 :             :                             relpath, m->checksum_length, checksumlen);
    1215         [ +  + ]:       49456 :     else if (memcmp(checksumbuf, m->checksum_payload, checksumlen) != 0)
    1216                 :           3 :         report_backup_error(context,
    1217                 :             :                             "checksum mismatch for file \"%s\"",
    1218                 :             :                             relpath);
    1219                 :             : }
    1220                 :             : 
    1221                 :             : /*
    1222                 :             :  * Attempt to parse the WAL files required to restore from backup using
    1223                 :             :  * pg_waldump.
    1224                 :             :  */
    1225                 :             : static void
    1226                 :          72 : parse_required_wal(verifier_context *context, char *pg_waldump_path,
    1227                 :             :                    char *wal_path)
    1228                 :             : {
    1229                 :          72 :     manifest_data *manifest = context->manifest;
    1230                 :          72 :     manifest_wal_range *this_wal_range = manifest->first_wal_range;
    1231                 :             : 
    1232         [ +  + ]:         144 :     while (this_wal_range != NULL)
    1233                 :             :     {
    1234                 :             :         char       *pg_waldump_cmd;
    1235                 :             : 
    1236                 :          72 :         pg_waldump_cmd = psprintf("\"%s\" --quiet --path=\"%s\" --timeline=%u --start=%X/%08X --end=%X/%08X\n",
    1237                 :             :                                   pg_waldump_path, wal_path, this_wal_range->tli,
    1238                 :          72 :                                   LSN_FORMAT_ARGS(this_wal_range->start_lsn),
    1239                 :          72 :                                   LSN_FORMAT_ARGS(this_wal_range->end_lsn));
    1240                 :          72 :         fflush(NULL);
    1241         [ +  + ]:          72 :         if (system(pg_waldump_cmd) != 0)
    1242                 :           2 :             report_backup_error(context,
    1243                 :             :                                 "WAL parsing failed for timeline %u",
    1244                 :             :                                 this_wal_range->tli);
    1245                 :             : 
    1246                 :          72 :         this_wal_range = this_wal_range->next;
    1247                 :             :     }
    1248                 :          72 : }
    1249                 :             : 
    1250                 :             : /*
    1251                 :             :  * Report a problem with the backup.
    1252                 :             :  *
    1253                 :             :  * Update the context to indicate that we saw an error, and exit if the
    1254                 :             :  * context says we should.
    1255                 :             :  */
    1256                 :             : void
    1257                 :        1082 : report_backup_error(verifier_context *context, const char *pg_restrict fmt, ...)
    1258                 :             : {
    1259                 :             :     va_list     ap;
    1260                 :             : 
    1261                 :        1082 :     va_start(ap, fmt);
    1262                 :        1082 :     pg_log_generic_v(PG_LOG_ERROR, PG_LOG_PRIMARY, gettext(fmt), ap);
    1263                 :        1082 :     va_end(ap);
    1264                 :             : 
    1265                 :        1082 :     context->saw_any_error = true;
    1266         [ +  + ]:        1082 :     if (context->exit_on_error)
    1267                 :           1 :         exit(1);
    1268                 :        1081 : }
    1269                 :             : 
    1270                 :             : /*
    1271                 :             :  * Report a fatal error and exit
    1272                 :             :  */
    1273                 :             : void
    1274                 :           7 : report_fatal_error(const char *pg_restrict fmt, ...)
    1275                 :             : {
    1276                 :             :     va_list     ap;
    1277                 :             : 
    1278                 :           7 :     va_start(ap, fmt);
    1279                 :           7 :     pg_log_generic_v(PG_LOG_ERROR, PG_LOG_PRIMARY, gettext(fmt), ap);
    1280                 :           7 :     va_end(ap);
    1281                 :             : 
    1282                 :           7 :     exit(1);
    1283                 :             : }
    1284                 :             : 
    1285                 :             : /*
    1286                 :             :  * Is the specified relative path, or some prefix of it, listed in the set
    1287                 :             :  * of paths to ignore?
    1288                 :             :  *
    1289                 :             :  * Note that by "prefix" we mean a parent directory; for this purpose,
    1290                 :             :  * "aa/bb" is not a prefix of "aa/bbb", but it is a prefix of "aa/bb/cc".
    1291                 :             :  */
    1292                 :             : bool
    1293                 :      130487 : should_ignore_relpath(verifier_context *context, const char *relpath)
    1294                 :             : {
    1295                 :             :     SimpleStringListCell *cell;
    1296                 :             : 
    1297         [ +  + ]:      793246 :     for (cell = context->ignore_list.head; cell != NULL; cell = cell->next)
    1298                 :             :     {
    1299                 :      664039 :         const char *r = relpath;
    1300                 :      664039 :         char       *v = cell->val;
    1301                 :             : 
    1302   [ +  +  +  + ]:      930878 :         while (*v != '\0' && *r == *v)
    1303                 :      266839 :             ++r, ++v;
    1304                 :             : 
    1305   [ +  +  +  +  :      664039 :         if (*v == '\0' && (*r == '\0' || *r == '/'))
                   +  + ]
    1306                 :        1280 :             return true;
    1307                 :             :     }
    1308                 :             : 
    1309                 :      129207 :     return false;
    1310                 :             : }
    1311                 :             : 
    1312                 :             : /*
    1313                 :             :  * Create a chain of archive streamers appropriate for verifying a given
    1314                 :             :  * archive.
    1315                 :             :  */
    1316                 :             : static astreamer *
    1317                 :          33 : create_archive_verifier(verifier_context *context, char *archive_name,
    1318                 :             :                         Oid tblspc_oid, pg_compress_algorithm compress_algo)
    1319                 :             : {
    1320                 :          33 :     astreamer  *streamer = NULL;
    1321                 :             : 
    1322                 :             :     /* Should be here only for tar backup */
    1323                 :             :     Assert(context->format == 't');
    1324                 :             : 
    1325                 :             :     /* Last step is the actual verification. */
    1326                 :          33 :     streamer = astreamer_verify_content_new(streamer, context, archive_name,
    1327                 :             :                                             tblspc_oid);
    1328                 :             : 
    1329                 :             :     /* Before that we must parse the tar file. */
    1330                 :          33 :     streamer = astreamer_tar_parser_new(streamer);
    1331                 :             : 
    1332                 :             :     /* Before that we must decompress, if archive is compressed. */
    1333         [ +  + ]:          33 :     if (compress_algo == PG_COMPRESSION_GZIP)
    1334                 :           3 :         streamer = astreamer_gzip_decompressor_new(streamer);
    1335         [ +  + ]:          30 :     else if (compress_algo == PG_COMPRESSION_LZ4)
    1336                 :           6 :         streamer = astreamer_lz4_decompressor_new(streamer);
    1337         [ -  + ]:          24 :     else if (compress_algo == PG_COMPRESSION_ZSTD)
    1338                 :           0 :         streamer = astreamer_zstd_decompressor_new(streamer);
    1339                 :             : 
    1340                 :          33 :     return streamer;
    1341                 :             : }
    1342                 :             : 
    1343                 :             : /*
    1344                 :             :  * Print a progress report based on the global variables.
    1345                 :             :  *
    1346                 :             :  * Progress report is written at maximum once per second, unless the finished
    1347                 :             :  * parameter is set to true.
    1348                 :             :  *
    1349                 :             :  * If finished is set to true, this is the last progress report. The cursor
    1350                 :             :  * is moved to the next line.
    1351                 :             :  */
    1352                 :             : static void
    1353                 :       46608 : progress_report(bool finished)
    1354                 :             : {
    1355                 :             :     static pg_time_t last_progress_report = 0;
    1356                 :             :     pg_time_t   now;
    1357                 :       46608 :     int         percent_size = 0;
    1358                 :             :     char        totalsize_str[32];
    1359                 :             :     char        donesize_str[32];
    1360                 :             : 
    1361         [ +  + ]:       46608 :     if (!show_progress)
    1362                 :       46606 :         return;
    1363                 :             : 
    1364                 :         896 :     now = time(NULL);
    1365   [ +  +  +  + ]:         896 :     if (now == last_progress_report && !finished)
    1366                 :         894 :         return;                 /* Max once per second */
    1367                 :             : 
    1368                 :           2 :     last_progress_report = now;
    1369         [ +  - ]:           2 :     percent_size = total_size ? (int) ((done_size * 100 / total_size)) : 0;
    1370                 :             : 
    1371                 :           2 :     snprintf(totalsize_str, sizeof(totalsize_str), UINT64_FORMAT,
    1372                 :             :              total_size / 1024);
    1373                 :           2 :     snprintf(donesize_str, sizeof(donesize_str), UINT64_FORMAT,
    1374                 :             :              done_size / 1024);
    1375                 :             : 
    1376                 :           2 :     fprintf(stderr,
    1377                 :           2 :             _("%*s/%s kB (%d%%) verified"),
    1378                 :           2 :             (int) strlen(totalsize_str),
    1379                 :             :             donesize_str, totalsize_str, percent_size);
    1380                 :             : 
    1381                 :             :     /*
    1382                 :             :      * Stay on the same line if reporting to a terminal and we're not done
    1383                 :             :      * yet.
    1384                 :             :      */
    1385   [ +  +  -  + ]:           2 :     fputc((!finished && isatty(fileno(stderr))) ? '\r' : '\n', stderr);
    1386                 :             : }
    1387                 :             : 
    1388                 :             : /*
    1389                 :             :  * Print out usage information and exit.
    1390                 :             :  */
    1391                 :             : static void
    1392                 :           1 : usage(void)
    1393                 :             : {
    1394                 :           1 :     printf(_("%s verifies a backup against the backup manifest.\n\n"), progname);
    1395                 :           1 :     printf(_("Usage:\n  %s [OPTION]... BACKUPDIR\n\n"), progname);
    1396                 :           1 :     printf(_("Options:\n"));
    1397                 :           1 :     printf(_("  -e, --exit-on-error         exit immediately on error\n"));
    1398                 :           1 :     printf(_("  -F, --format=p|t            backup format (plain, tar)\n"));
    1399                 :           1 :     printf(_("  -i, --ignore=RELATIVE_PATH  ignore indicated path\n"));
    1400                 :           1 :     printf(_("  -m, --manifest-path=PATH    use specified path for manifest\n"));
    1401                 :           1 :     printf(_("  -n, --no-parse-wal          do not try to parse WAL files\n"));
    1402                 :           1 :     printf(_("  -P, --progress              show progress information\n"));
    1403                 :           1 :     printf(_("  -q, --quiet                 do not print any output, except for errors\n"));
    1404                 :           1 :     printf(_("  -s, --skip-checksums        skip checksum verification\n"));
    1405                 :           1 :     printf(_("  -w, --wal-path=PATH         use specified path for WAL files\n"));
    1406                 :           1 :     printf(_("      --wal-directory=PATH    (same as --wal-path, deprecated)\n"));
    1407                 :           1 :     printf(_("  -V, --version               output version information, then exit\n"));
    1408                 :           1 :     printf(_("  -?, --help                  show this help, then exit\n"));
    1409                 :           1 :     printf(_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT);
    1410                 :           1 :     printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
    1411                 :           1 : }
        

Generated by: LCOV version 2.0-1