LCOV - code coverage report
Current view: top level - src/bin/pg_combinebackup - write_manifest.c (source / functions) Coverage Total Hit
Test: Code coverage Lines: 0.0 % 135 0
Test Date: 2026-01-26 10:56:24 Functions: 0.0 % 6 0
Legend: Lines:     hit not hit

            Line data    Source code
       1              : /*-------------------------------------------------------------------------
       2              :  *
       3              :  * Write a new backup manifest.
       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/write_manifest.c
       9              :  *
      10              :  *-------------------------------------------------------------------------
      11              :  */
      12              : 
      13              : #include "postgres_fe.h"
      14              : 
      15              : #include <fcntl.h>
      16              : #include <time.h>
      17              : #include <unistd.h>
      18              : 
      19              : #include "common/checksum_helper.h"
      20              : #include "common/file_perm.h"
      21              : #include "common/logging.h"
      22              : #include "lib/stringinfo.h"
      23              : #include "load_manifest.h"
      24              : #include "mb/pg_wchar.h"
      25              : #include "write_manifest.h"
      26              : 
      27              : struct manifest_writer
      28              : {
      29              :         char            pathname[MAXPGPATH];
      30              :         int                     fd;
      31              :         StringInfoData buf;
      32              :         bool            first_file;
      33              :         bool            still_checksumming;
      34              :         pg_checksum_context manifest_ctx;
      35              : };
      36              : 
      37              : static void escape_json(StringInfo buf, const char *str);
      38              : static void flush_manifest(manifest_writer *mwriter);
      39              : static size_t hex_encode(const uint8 *src, size_t len, char *dst);
      40              : 
      41              : /*
      42              :  * Create a new backup manifest writer.
      43              :  *
      44              :  * The backup manifest will be written into a file named backup_manifest
      45              :  * in the specified directory.
      46              :  */
      47              : manifest_writer *
      48            0 : create_manifest_writer(char *directory, uint64 system_identifier)
      49              : {
      50            0 :         manifest_writer *mwriter = pg_malloc(sizeof(manifest_writer));
      51              : 
      52            0 :         snprintf(mwriter->pathname, MAXPGPATH, "%s/backup_manifest", directory);
      53            0 :         mwriter->fd = -1;
      54            0 :         initStringInfo(&mwriter->buf);
      55            0 :         mwriter->first_file = true;
      56            0 :         mwriter->still_checksumming = true;
      57            0 :         pg_checksum_init(&mwriter->manifest_ctx, CHECKSUM_TYPE_SHA256);
      58              : 
      59            0 :         appendStringInfo(&mwriter->buf,
      60              :                                          "{ \"PostgreSQL-Backup-Manifest-Version\": 2,\n"
      61              :                                          "\"System-Identifier\": " UINT64_FORMAT ",\n"
      62              :                                          "\"Files\": [",
      63            0 :                                          system_identifier);
      64              : 
      65            0 :         return mwriter;
      66            0 : }
      67              : 
      68              : /*
      69              :  * Add an entry for a file to a backup manifest.
      70              :  *
      71              :  * This is very similar to the backend's AddFileToBackupManifest, but
      72              :  * various adjustments are required due to frontend/backend differences
      73              :  * and other details.
      74              :  */
      75              : void
      76            0 : add_file_to_manifest(manifest_writer *mwriter, const char *manifest_path,
      77              :                                          uint64 size, time_t mtime,
      78              :                                          pg_checksum_type checksum_type,
      79              :                                          int checksum_length,
      80              :                                          uint8 *checksum_payload)
      81              : {
      82            0 :         int                     pathlen = strlen(manifest_path);
      83              : 
      84            0 :         if (mwriter->first_file)
      85              :         {
      86            0 :                 appendStringInfoChar(&mwriter->buf, '\n');
      87            0 :                 mwriter->first_file = false;
      88            0 :         }
      89              :         else
      90            0 :                 appendStringInfoString(&mwriter->buf, ",\n");
      91              : 
      92            0 :         if (pg_encoding_verifymbstr(PG_UTF8, manifest_path, pathlen) == pathlen)
      93              :         {
      94            0 :                 appendStringInfoString(&mwriter->buf, "{ \"Path\": ");
      95            0 :                 escape_json(&mwriter->buf, manifest_path);
      96            0 :                 appendStringInfoString(&mwriter->buf, ", ");
      97            0 :         }
      98              :         else
      99              :         {
     100            0 :                 appendStringInfoString(&mwriter->buf, "{ \"Encoded-Path\": \"");
     101            0 :                 enlargeStringInfo(&mwriter->buf, 2 * pathlen);
     102            0 :                 mwriter->buf.len += hex_encode((const uint8 *) manifest_path, pathlen,
     103            0 :                                                                            &mwriter->buf.data[mwriter->buf.len]);
     104            0 :                 appendStringInfoString(&mwriter->buf, "\", ");
     105              :         }
     106              : 
     107            0 :         appendStringInfo(&mwriter->buf, "\"Size\": %" PRIu64 ", ", size);
     108              : 
     109            0 :         appendStringInfoString(&mwriter->buf, "\"Last-Modified\": \"");
     110            0 :         enlargeStringInfo(&mwriter->buf, 128);
     111            0 :         mwriter->buf.len += strftime(&mwriter->buf.data[mwriter->buf.len], 128,
     112              :                                                                  "%Y-%m-%d %H:%M:%S %Z",
     113            0 :                                                                  gmtime(&mtime));
     114            0 :         appendStringInfoChar(&mwriter->buf, '"');
     115              : 
     116            0 :         if (mwriter->buf.len > 128 * 1024)
     117            0 :                 flush_manifest(mwriter);
     118              : 
     119            0 :         if (checksum_length > 0)
     120              :         {
     121            0 :                 appendStringInfo(&mwriter->buf,
     122              :                                                  ", \"Checksum-Algorithm\": \"%s\", \"Checksum\": \"",
     123            0 :                                                  pg_checksum_type_name(checksum_type));
     124              : 
     125            0 :                 enlargeStringInfo(&mwriter->buf, 2 * checksum_length);
     126            0 :                 mwriter->buf.len += hex_encode(checksum_payload, checksum_length,
     127            0 :                                                                            &mwriter->buf.data[mwriter->buf.len]);
     128              : 
     129            0 :                 appendStringInfoChar(&mwriter->buf, '"');
     130            0 :         }
     131              : 
     132            0 :         appendStringInfoString(&mwriter->buf, " }");
     133              : 
     134            0 :         if (mwriter->buf.len > 128 * 1024)
     135            0 :                 flush_manifest(mwriter);
     136            0 : }
     137              : 
     138              : /*
     139              :  * Finalize the backup_manifest.
     140              :  */
     141              : void
     142            0 : finalize_manifest(manifest_writer *mwriter,
     143              :                                   manifest_wal_range *first_wal_range)
     144              : {
     145            0 :         uint8           checksumbuf[PG_SHA256_DIGEST_LENGTH];
     146            0 :         int                     len;
     147            0 :         manifest_wal_range *wal_range;
     148              : 
     149              :         /* Terminate the list of files. */
     150            0 :         appendStringInfoString(&mwriter->buf, "\n],\n");
     151              : 
     152              :         /* Start a list of LSN ranges. */
     153            0 :         appendStringInfoString(&mwriter->buf, "\"WAL-Ranges\": [\n");
     154              : 
     155            0 :         for (wal_range = first_wal_range; wal_range != NULL;
     156            0 :                  wal_range = wal_range->next)
     157            0 :                 appendStringInfo(&mwriter->buf,
     158              :                                                  "%s{ \"Timeline\": %u, \"Start-LSN\": \"%X/%08X\", \"End-LSN\": \"%X/%08X\" }",
     159            0 :                                                  wal_range == first_wal_range ? "" : ",\n",
     160            0 :                                                  wal_range->tli,
     161            0 :                                                  LSN_FORMAT_ARGS(wal_range->start_lsn),
     162            0 :                                                  LSN_FORMAT_ARGS(wal_range->end_lsn));
     163              : 
     164              :         /* Terminate the list of WAL ranges. */
     165            0 :         appendStringInfoString(&mwriter->buf, "\n],\n");
     166              : 
     167              :         /* Flush accumulated data and update checksum calculation. */
     168            0 :         flush_manifest(mwriter);
     169              : 
     170              :         /* Checksum only includes data up to this point. */
     171            0 :         mwriter->still_checksumming = false;
     172              : 
     173              :         /* Compute and insert manifest checksum. */
     174            0 :         appendStringInfoString(&mwriter->buf, "\"Manifest-Checksum\": \"");
     175            0 :         enlargeStringInfo(&mwriter->buf, 2 * PG_SHA256_DIGEST_STRING_LENGTH);
     176            0 :         len = pg_checksum_final(&mwriter->manifest_ctx, checksumbuf);
     177            0 :         Assert(len == PG_SHA256_DIGEST_LENGTH);
     178            0 :         mwriter->buf.len +=
     179            0 :                 hex_encode(checksumbuf, len, &mwriter->buf.data[mwriter->buf.len]);
     180            0 :         appendStringInfoString(&mwriter->buf, "\"}\n");
     181              : 
     182              :         /* Flush the last manifest checksum itself. */
     183            0 :         flush_manifest(mwriter);
     184              : 
     185              :         /* Close the file. */
     186            0 :         if (close(mwriter->fd) != 0)
     187            0 :                 pg_fatal("could not close file \"%s\": %m", mwriter->pathname);
     188            0 :         mwriter->fd = -1;
     189            0 : }
     190              : 
     191              : /*
     192              :  * Produce a JSON string literal, properly escaping characters in the text.
     193              :  */
     194              : static void
     195            0 : escape_json(StringInfo buf, const char *str)
     196              : {
     197            0 :         const char *p;
     198              : 
     199            0 :         appendStringInfoCharMacro(buf, '"');
     200            0 :         for (p = str; *p; p++)
     201              :         {
     202            0 :                 switch (*p)
     203              :                 {
     204              :                         case '\b':
     205            0 :                                 appendStringInfoString(buf, "\\b");
     206            0 :                                 break;
     207              :                         case '\f':
     208            0 :                                 appendStringInfoString(buf, "\\f");
     209            0 :                                 break;
     210              :                         case '\n':
     211            0 :                                 appendStringInfoString(buf, "\\n");
     212            0 :                                 break;
     213              :                         case '\r':
     214            0 :                                 appendStringInfoString(buf, "\\r");
     215            0 :                                 break;
     216              :                         case '\t':
     217            0 :                                 appendStringInfoString(buf, "\\t");
     218            0 :                                 break;
     219              :                         case '"':
     220            0 :                                 appendStringInfoString(buf, "\\\"");
     221            0 :                                 break;
     222              :                         case '\\':
     223            0 :                                 appendStringInfoString(buf, "\\\\");
     224            0 :                                 break;
     225              :                         default:
     226            0 :                                 if ((unsigned char) *p < ' ')
     227            0 :                                         appendStringInfo(buf, "\\u%04x", (int) *p);
     228              :                                 else
     229            0 :                                         appendStringInfoCharMacro(buf, *p);
     230            0 :                                 break;
     231              :                 }
     232            0 :         }
     233            0 :         appendStringInfoCharMacro(buf, '"');
     234            0 : }
     235              : 
     236              : /*
     237              :  * Flush whatever portion of the backup manifest we have generated and
     238              :  * buffered in memory out to a file on disk.
     239              :  *
     240              :  * The first call to this function will create the file. After that, we
     241              :  * keep it open and just append more data.
     242              :  */
     243              : static void
     244            0 : flush_manifest(manifest_writer *mwriter)
     245              : {
     246            0 :         if (mwriter->fd == -1 &&
     247            0 :                 (mwriter->fd = open(mwriter->pathname,
     248              :                                                         O_WRONLY | O_CREAT | O_EXCL | PG_BINARY,
     249            0 :                                                         pg_file_create_mode)) < 0)
     250            0 :                 pg_fatal("could not open file \"%s\": %m", mwriter->pathname);
     251              : 
     252            0 :         if (mwriter->buf.len > 0)
     253              :         {
     254            0 :                 ssize_t         wb;
     255              : 
     256            0 :                 wb = write(mwriter->fd, mwriter->buf.data, mwriter->buf.len);
     257            0 :                 if (wb != mwriter->buf.len)
     258              :                 {
     259            0 :                         if (wb < 0)
     260            0 :                                 pg_fatal("could not write file \"%s\": %m", mwriter->pathname);
     261              :                         else
     262            0 :                                 pg_fatal("could not write file \"%s\": wrote %zd of %d",
     263              :                                                  mwriter->pathname, wb, mwriter->buf.len);
     264            0 :                 }
     265              : 
     266            0 :                 if (mwriter->still_checksumming &&
     267            0 :                         pg_checksum_update(&mwriter->manifest_ctx,
     268            0 :                                                            (uint8 *) mwriter->buf.data,
     269            0 :                                                            mwriter->buf.len) < 0)
     270            0 :                         pg_fatal("could not update checksum of file \"%s\"",
     271              :                                          mwriter->pathname);
     272            0 :                 resetStringInfo(&mwriter->buf);
     273            0 :         }
     274            0 : }
     275              : 
     276              : /*
     277              :  * Encode bytes using two hexadecimal digits for each one.
     278              :  */
     279              : static size_t
     280            0 : hex_encode(const uint8 *src, size_t len, char *dst)
     281              : {
     282            0 :         const uint8 *end = src + len;
     283              : 
     284            0 :         while (src < end)
     285              :         {
     286            0 :                 unsigned        n1 = (*src >> 4) & 0xF;
     287            0 :                 unsigned        n2 = *src & 0xF;
     288              : 
     289            0 :                 *dst++ = n1 < 10 ? '0' + n1 : 'a' + n1 - 10;
     290            0 :                 *dst++ = n2 < 10 ? '0' + n2 : 'a' + n2 - 10;
     291            0 :                 ++src;
     292            0 :         }
     293              : 
     294            0 :         return len * 2;
     295            0 : }
        

Generated by: LCOV version 2.3.2-1