LCOV - code coverage report
Current view: top level - src/bin/pg_combinebackup - load_manifest.c (source / functions) Coverage Total Hit
Test: PostgreSQL 20devel Lines: 73.9 % 92 68
Test Date: 2026-07-26 09:15:38 Functions: 85.7 % 7 6
Legend: Lines:     hit not hit
Branches: + taken - not taken # not executed
Branches: 46.7 % 30 14

             Branch data     Line data    Source code
       1                 :             : /*-------------------------------------------------------------------------
       2                 :             :  *
       3                 :             :  * Load data from a backup manifest into memory.
       4                 :             :  *
       5                 :             :  * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
       6                 :             :  * Portions Copyright (c) 1994, Regents of the University of California
       7                 :             :  *
       8                 :             :  * src/bin/pg_combinebackup/load_manifest.c
       9                 :             :  *
      10                 :             :  *-------------------------------------------------------------------------
      11                 :             :  */
      12                 :             : 
      13                 :             : #include "postgres_fe.h"
      14                 :             : 
      15                 :             : #include <sys/stat.h>
      16                 :             : #include <unistd.h>
      17                 :             : 
      18                 :             : #include "common/hashfn_unstable.h"
      19                 :             : #include "common/logging.h"
      20                 :             : #include "common/parse_manifest.h"
      21                 :             : #include "load_manifest.h"
      22                 :             : 
      23                 :             : /*
      24                 :             :  * For efficiency, we'd like our hash table containing information about the
      25                 :             :  * manifest to start out with approximately the correct number of entries.
      26                 :             :  * There's no way to know the exact number of entries without reading the whole
      27                 :             :  * file, but we can get an estimate by dividing the file size by the estimated
      28                 :             :  * number of bytes per line.
      29                 :             :  *
      30                 :             :  * This could be off by about a factor of two in either direction, because the
      31                 :             :  * checksum algorithm has a big impact on the line lengths; e.g. a SHA512
      32                 :             :  * checksum is 128 hex bytes, whereas a CRC-32C value is only 8, and there
      33                 :             :  * might be no checksum at all.
      34                 :             :  */
      35                 :             : #define ESTIMATED_BYTES_PER_MANIFEST_LINE   100
      36                 :             : 
      37                 :             : /*
      38                 :             :  * size of json chunk to be read in
      39                 :             :  *
      40                 :             :  */
      41                 :             : #define READ_CHUNK_SIZE (128  * 1024)
      42                 :             : 
      43                 :             : /*
      44                 :             :  * Define a hash table which we can use to store information about the files
      45                 :             :  * mentioned in the backup manifest.
      46                 :             :  */
      47                 :             : #define SH_PREFIX       manifest_files
      48                 :             : #define SH_ELEMENT_TYPE manifest_file
      49                 :             : #define SH_KEY_TYPE     const char *
      50                 :             : #define SH_KEY          pathname
      51                 :             : #define SH_HASH_KEY(tb, key)    hash_string(key)
      52                 :             : #define SH_EQUAL(tb, a, b)      (strcmp(a, b) == 0)
      53                 :             : #define SH_SCOPE        extern
      54                 :             : #define SH_RAW_ALLOCATOR    pg_malloc0
      55                 :             : #define SH_DEFINE
      56                 :             : #include "lib/simplehash.h"
      57                 :             : 
      58                 :             : static void combinebackup_version_cb(JsonManifestParseContext *context,
      59                 :             :                                      int manifest_version);
      60                 :             : static void combinebackup_system_identifier_cb(JsonManifestParseContext *context,
      61                 :             :                                                uint64 manifest_system_identifier);
      62                 :             : static void combinebackup_per_file_cb(JsonManifestParseContext *context,
      63                 :             :                                       const char *pathname, uint64 size,
      64                 :             :                                       pg_checksum_type checksum_type,
      65                 :             :                                       int checksum_length,
      66                 :             :                                       uint8 *checksum_payload);
      67                 :             : static void combinebackup_per_wal_range_cb(JsonManifestParseContext *context,
      68                 :             :                                            TimeLineID tli,
      69                 :             :                                            XLogRecPtr start_lsn,
      70                 :             :                                            XLogRecPtr end_lsn);
      71                 :             : pg_noreturn static void report_manifest_error(JsonManifestParseContext *context,
      72                 :             :                                               const char *fmt, ...)
      73                 :             :             pg_attribute_printf(2, 3);
      74                 :             : 
      75                 :             : /*
      76                 :             :  * Load backup_manifest files from an array of backups and produces an array
      77                 :             :  * of manifest_data objects.
      78                 :             :  *
      79                 :             :  * NB: Since load_backup_manifest() can return NULL, the resulting array could
      80                 :             :  * contain NULL entries.
      81                 :             :  */
      82                 :             : manifest_data **
      83                 :          16 : load_backup_manifests(int n_backups, char **backup_directories)
      84                 :             : {
      85                 :             :     manifest_data **result;
      86                 :             :     int         i;
      87                 :             : 
      88                 :          16 :     result = pg_malloc_array(manifest_data *, n_backups);
      89         [ +  + ]:          48 :     for (i = 0; i < n_backups; ++i)
      90                 :          32 :         result[i] = load_backup_manifest(backup_directories[i]);
      91                 :             : 
      92                 :          16 :     return result;
      93                 :             : }
      94                 :             : 
      95                 :             : /*
      96                 :             :  * Parse the backup_manifest file in the named backup directory. Construct a
      97                 :             :  * hash table with information about all the files it mentions, and a linked
      98                 :             :  * list of all the WAL ranges it mentions.
      99                 :             :  *
     100                 :             :  * If the backup_manifest file simply doesn't exist, logs a warning and returns
     101                 :             :  * NULL. Any other error, or any error parsing the contents of the file, is
     102                 :             :  * fatal.
     103                 :             :  */
     104                 :             : manifest_data *
     105                 :          32 : load_backup_manifest(char *backup_directory)
     106                 :             : {
     107                 :             :     char        pathname[MAXPGPATH];
     108                 :             :     int         fd;
     109                 :             :     struct stat statbuf;
     110                 :             :     off_t       estimate;
     111                 :             :     uint32      initial_size;
     112                 :             :     manifest_files_hash *ht;
     113                 :             :     char       *buffer;
     114                 :             :     JsonManifestParseContext context;
     115                 :             :     manifest_data *result;
     116                 :             :     size_t      total_size;
     117                 :          32 :     const size_t chunk_size = READ_CHUNK_SIZE;
     118                 :             : 
     119                 :             :     /* Open the manifest file. */
     120                 :          32 :     snprintf(pathname, MAXPGPATH, "%s/backup_manifest", backup_directory);
     121         [ -  + ]:          32 :     if ((fd = open(pathname, O_RDONLY | PG_BINARY, 0)) < 0)
     122                 :             :     {
     123         [ #  # ]:           0 :         if (errno == ENOENT)
     124                 :             :         {
     125                 :           0 :             pg_log_warning("file \"%s\" does not exist", pathname);
     126                 :           0 :             return NULL;
     127                 :             :         }
     128                 :           0 :         pg_fatal("could not open file \"%s\": %m", pathname);
     129                 :             :     }
     130                 :             : 
     131                 :             :     /* Figure out how big the manifest is. */
     132         [ -  + ]:          32 :     if (fstat(fd, &statbuf) != 0)
     133                 :           0 :         pg_fatal("could not stat file \"%s\": %m", pathname);
     134                 :             : 
     135                 :             :     /* Guess how large to make the hash table based on the manifest size. */
     136                 :          32 :     estimate = statbuf.st_size / ESTIMATED_BYTES_PER_MANIFEST_LINE;
     137                 :          32 :     initial_size = Min(PG_UINT32_MAX, Max(estimate, 256));
     138                 :             : 
     139                 :             :     /* Create the hash table. */
     140                 :          32 :     ht = manifest_files_create(initial_size, NULL);
     141                 :             : 
     142                 :          32 :     result = pg_malloc0_object(manifest_data);
     143                 :          32 :     result->files = ht;
     144                 :          32 :     context.private_data = result;
     145                 :          32 :     context.version_cb = combinebackup_version_cb;
     146                 :          32 :     context.system_identifier_cb = combinebackup_system_identifier_cb;
     147                 :          32 :     context.per_file_cb = combinebackup_per_file_cb;
     148                 :          32 :     context.per_wal_range_cb = combinebackup_per_wal_range_cb;
     149                 :          32 :     context.error_cb = report_manifest_error;
     150                 :             : 
     151                 :          32 :     total_size = statbuf.st_size;
     152                 :             : 
     153                 :             :     /*
     154                 :             :      * Parse the file, in chunks if necessary.
     155                 :             :      */
     156         [ -  + ]:          32 :     if (total_size <= chunk_size)
     157                 :             :     {
     158                 :             :         ssize_t     rc;
     159                 :             : 
     160                 :           0 :         buffer = pg_malloc(total_size);
     161                 :           0 :         rc = read(fd, buffer, total_size);
     162         [ #  # ]:           0 :         if (rc != total_size)
     163                 :             :         {
     164         [ #  # ]:           0 :             if (rc < 0)
     165                 :           0 :                 pg_fatal("could not read file \"%s\": %m", pathname);
     166                 :             :             else
     167                 :           0 :                 pg_fatal("could not read file \"%s\": read %zd of %zu",
     168                 :             :                          pathname, rc, total_size);
     169                 :             :         }
     170                 :             : 
     171                 :             :         /* Close the manifest file. */
     172                 :           0 :         close(fd);
     173                 :             : 
     174                 :             :         /* Parse the manifest. */
     175                 :           0 :         json_parse_manifest(&context, buffer, total_size);
     176                 :             :     }
     177                 :             :     else
     178                 :             :     {
     179                 :          32 :         size_t      bytes_left = total_size;
     180                 :             :         JsonManifestParseIncrementalState *inc_state;
     181                 :             : 
     182                 :          32 :         inc_state = json_parse_manifest_incremental_init(&context);
     183                 :             : 
     184                 :          32 :         buffer = pg_malloc(chunk_size + 1);
     185                 :             : 
     186         [ +  + ]:          96 :         while (bytes_left > 0)
     187                 :             :         {
     188                 :             :             ssize_t     rc;
     189                 :          64 :             size_t      bytes_to_read = chunk_size;
     190                 :             : 
     191                 :             :             /*
     192                 :             :              * Make sure that the last chunk is sufficiently large. (i.e. at
     193                 :             :              * least half the chunk size) so that it will contain fully the
     194                 :             :              * piece at the end with the checksum.
     195                 :             :              */
     196         [ +  + ]:          64 :             if (bytes_left < chunk_size)
     197                 :          32 :                 bytes_to_read = bytes_left;
     198         [ +  - ]:          32 :             else if (bytes_left < 2 * chunk_size)
     199                 :          32 :                 bytes_to_read = bytes_left / 2;
     200                 :          64 :             rc = read(fd, buffer, bytes_to_read);
     201         [ -  + ]:          64 :             if (rc != bytes_to_read)
     202                 :             :             {
     203         [ #  # ]:           0 :                 if (rc < 0)
     204                 :           0 :                     pg_fatal("could not read file \"%s\": %m", pathname);
     205                 :             :                 else
     206                 :           0 :                     pg_fatal("could not read file \"%s\": read %zu of %zu",
     207                 :             :                              pathname,
     208                 :             :                              total_size + rc - bytes_left,
     209                 :             :                              total_size);
     210                 :             :             }
     211                 :          64 :             bytes_left -= rc;
     212                 :          64 :             json_parse_manifest_incremental_chunk(inc_state, buffer, rc, bytes_left == 0);
     213                 :             :         }
     214                 :             : 
     215                 :             :         /* Release the incremental state memory */
     216                 :          32 :         json_parse_manifest_incremental_shutdown(inc_state);
     217                 :             : 
     218                 :          32 :         close(fd);
     219                 :             :     }
     220                 :             : 
     221                 :             :     /* All done. */
     222                 :          32 :     pg_free(buffer);
     223                 :          32 :     return result;
     224                 :             : }
     225                 :             : 
     226                 :             : /*
     227                 :             :  * Report an error while parsing the manifest.
     228                 :             :  *
     229                 :             :  * We consider all such errors to be fatal errors. The manifest parser
     230                 :             :  * expects this function not to return.
     231                 :             :  */
     232                 :             : static void
     233                 :           0 : report_manifest_error(JsonManifestParseContext *context, const char *fmt, ...)
     234                 :             : {
     235                 :             :     va_list     ap;
     236                 :             : 
     237                 :           0 :     va_start(ap, fmt);
     238                 :           0 :     pg_log_generic_v(PG_LOG_ERROR, PG_LOG_PRIMARY, gettext(fmt), ap);
     239                 :           0 :     va_end(ap);
     240                 :             : 
     241                 :           0 :     exit(1);
     242                 :             : }
     243                 :             : 
     244                 :             : /*
     245                 :             :  * This callback to validate the manifest version number for incremental backup.
     246                 :             :  */
     247                 :             : static void
     248                 :          32 : combinebackup_version_cb(JsonManifestParseContext *context,
     249                 :             :                          int manifest_version)
     250                 :             : {
     251                 :             :     /* Incremental backups supported on manifest version 2 or later */
     252         [ -  + ]:          32 :     if (manifest_version == 1)
     253                 :           0 :         pg_fatal("backup manifest version 1 does not support incremental backup");
     254                 :          32 : }
     255                 :             : 
     256                 :             : /*
     257                 :             :  * Record system identifier extracted from the backup manifest.
     258                 :             :  */
     259                 :             : static void
     260                 :          32 : combinebackup_system_identifier_cb(JsonManifestParseContext *context,
     261                 :             :                                    uint64 manifest_system_identifier)
     262                 :             : {
     263                 :          32 :     manifest_data *manifest = context->private_data;
     264                 :             : 
     265                 :             :     /* Validation will be at the later stage */
     266                 :          32 :     manifest->system_identifier = manifest_system_identifier;
     267                 :          32 : }
     268                 :             : 
     269                 :             : /*
     270                 :             :  * Record details extracted from the backup manifest for one file.
     271                 :             :  */
     272                 :             : static void
     273                 :       34352 : combinebackup_per_file_cb(JsonManifestParseContext *context,
     274                 :             :                           const char *pathname, uint64 size,
     275                 :             :                           pg_checksum_type checksum_type,
     276                 :             :                           int checksum_length, uint8 *checksum_payload)
     277                 :             : {
     278                 :       34352 :     manifest_data *manifest = context->private_data;
     279                 :             :     manifest_file *m;
     280                 :             :     bool        found;
     281                 :             : 
     282                 :             :     /* Make a new entry in the hash table for this file. */
     283                 :       34352 :     m = manifest_files_insert(manifest->files, pathname, &found);
     284         [ -  + ]:       34352 :     if (found)
     285                 :           0 :         pg_fatal("duplicate path name in backup manifest: \"%s\"", pathname);
     286                 :             : 
     287                 :             :     /* Initialize the entry. */
     288                 :       34352 :     m->size = size;
     289                 :       34352 :     m->checksum_type = checksum_type;
     290                 :       34352 :     m->checksum_length = checksum_length;
     291                 :       34352 :     m->checksum_payload = checksum_payload;
     292                 :       34352 : }
     293                 :             : 
     294                 :             : /*
     295                 :             :  * Record details extracted from the backup manifest for one WAL range.
     296                 :             :  */
     297                 :             : static void
     298                 :          32 : combinebackup_per_wal_range_cb(JsonManifestParseContext *context,
     299                 :             :                                TimeLineID tli,
     300                 :             :                                XLogRecPtr start_lsn, XLogRecPtr end_lsn)
     301                 :             : {
     302                 :          32 :     manifest_data *manifest = context->private_data;
     303                 :             :     manifest_wal_range *range;
     304                 :             : 
     305                 :             :     /* Allocate and initialize a struct describing this WAL range. */
     306                 :          32 :     range = palloc_object(manifest_wal_range);
     307                 :          32 :     range->tli = tli;
     308                 :          32 :     range->start_lsn = start_lsn;
     309                 :          32 :     range->end_lsn = end_lsn;
     310                 :          32 :     range->prev = manifest->last_wal_range;
     311                 :          32 :     range->next = NULL;
     312                 :             : 
     313                 :             :     /* Add it to the end of the list. */
     314         [ +  - ]:          32 :     if (manifest->first_wal_range == NULL)
     315                 :          32 :         manifest->first_wal_range = range;
     316                 :             :     else
     317                 :           0 :         manifest->last_wal_range->next = range;
     318                 :          32 :     manifest->last_wal_range = range;
     319                 :          32 : }
        

Generated by: LCOV version 2.0-1