LCOV - code coverage report
Current view: top level - src/bin/pg_combinebackup - backup_label.c (source / functions) Coverage Total Hit
Test: PostgreSQL 19devel Lines: 79.2 % 101 80
Test Date: 2026-03-10 01:14:48 Functions: 100.0 % 6 6
Legend: Lines:     hit not hit

            Line data    Source code
       1              : /*-------------------------------------------------------------------------
       2              :  *
       3              :  * Read and manipulate backup label files
       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/backup_label.c
       9              :  *
      10              :  *-------------------------------------------------------------------------
      11              :  */
      12              : #include "postgres_fe.h"
      13              : 
      14              : #include <unistd.h>
      15              : 
      16              : #include "access/xlogdefs.h"
      17              : #include "backup_label.h"
      18              : #include "common/file_perm.h"
      19              : #include "common/logging.h"
      20              : #include "write_manifest.h"
      21              : 
      22              : static int  get_eol_offset(StringInfo buf);
      23              : static bool line_starts_with(char *s, char *e, char *match, char **sout);
      24              : static bool parse_lsn(char *s, char *e, XLogRecPtr *lsn, char **c);
      25              : static bool parse_tli(char *s, char *e, TimeLineID *tli);
      26              : 
      27              : /*
      28              :  * Parse a backup label file, starting at buf->cursor.
      29              :  *
      30              :  * We expect to find a START WAL LOCATION line, followed by a LSN, followed
      31              :  * by a space; the resulting LSN is stored into *start_lsn.
      32              :  *
      33              :  * We expect to find a START TIMELINE line, followed by a TLI, followed by
      34              :  * a newline; the resulting TLI is stored into *start_tli.
      35              :  *
      36              :  * We expect to find either both INCREMENTAL FROM LSN and INCREMENTAL FROM TLI
      37              :  * or neither. If these are found, they should be followed by an LSN or TLI
      38              :  * respectively and then by a newline, and the values will be stored into
      39              :  * *previous_lsn and *previous_tli, respectively.
      40              :  *
      41              :  * Other lines in the provided backup_label data are ignored. filename is used
      42              :  * for error reporting; errors are fatal.
      43              :  */
      44              : void
      45           39 : parse_backup_label(char *filename, StringInfo buf,
      46              :                    TimeLineID *start_tli, XLogRecPtr *start_lsn,
      47              :                    TimeLineID *previous_tli, XLogRecPtr *previous_lsn)
      48              : {
      49           39 :     int         found = 0;
      50              : 
      51           39 :     *start_tli = 0;
      52           39 :     *start_lsn = InvalidXLogRecPtr;
      53           39 :     *previous_tli = 0;
      54           39 :     *previous_lsn = InvalidXLogRecPtr;
      55              : 
      56          354 :     while (buf->cursor < buf->len)
      57              :     {
      58          315 :         char       *s = &buf->data[buf->cursor];
      59          315 :         int         eo = get_eol_offset(buf);
      60          315 :         char       *e = &buf->data[eo];
      61              :         char       *c;
      62              : 
      63          315 :         if (line_starts_with(s, e, "START WAL LOCATION: ", &s))
      64              :         {
      65           39 :             if (!parse_lsn(s, e, start_lsn, &c))
      66            0 :                 pg_fatal("%s: could not parse %s",
      67              :                          filename, "START WAL LOCATION");
      68           39 :             if (c >= e || *c != ' ')
      69            0 :                 pg_fatal("%s: improper terminator for %s",
      70              :                          filename, "START WAL LOCATION");
      71           39 :             found |= 1;
      72              :         }
      73          276 :         else if (line_starts_with(s, e, "START TIMELINE: ", &s))
      74              :         {
      75           39 :             if (!parse_tli(s, e, start_tli))
      76            0 :                 pg_fatal("%s: could not parse TLI for %s",
      77              :                          filename, "START TIMELINE");
      78           39 :             if (*start_tli == 0)
      79            0 :                 pg_fatal("%s: invalid TLI", filename);
      80           39 :             found |= 2;
      81              :         }
      82          237 :         else if (line_starts_with(s, e, "INCREMENTAL FROM LSN: ", &s))
      83              :         {
      84           21 :             if (!parse_lsn(s, e, previous_lsn, &c))
      85            0 :                 pg_fatal("%s: could not parse %s",
      86              :                          filename, "INCREMENTAL FROM LSN");
      87           21 :             if (c >= e || *c != '\n')
      88            0 :                 pg_fatal("%s: improper terminator for %s",
      89              :                          filename, "INCREMENTAL FROM LSN");
      90           21 :             found |= 4;
      91              :         }
      92          216 :         else if (line_starts_with(s, e, "INCREMENTAL FROM TLI: ", &s))
      93              :         {
      94           21 :             if (!parse_tli(s, e, previous_tli))
      95            0 :                 pg_fatal("%s: could not parse %s",
      96              :                          filename, "INCREMENTAL FROM TLI");
      97           21 :             if (*previous_tli == 0)
      98            0 :                 pg_fatal("%s: invalid TLI", filename);
      99           21 :             found |= 8;
     100              :         }
     101              : 
     102          315 :         buf->cursor = eo;
     103              :     }
     104              : 
     105           39 :     if ((found & 1) == 0)
     106            0 :         pg_fatal("%s: could not find %s", filename, "START WAL LOCATION");
     107           39 :     if ((found & 2) == 0)
     108            0 :         pg_fatal("%s: could not find %s", filename, "START TIMELINE");
     109           39 :     if ((found & 4) != 0 && (found & 8) == 0)
     110            0 :         pg_fatal("%s: %s requires %s", filename,
     111              :                  "INCREMENTAL FROM LSN", "INCREMENTAL FROM TLI");
     112           39 :     if ((found & 8) != 0 && (found & 4) == 0)
     113            0 :         pg_fatal("%s: %s requires %s", filename,
     114              :                  "INCREMENTAL FROM TLI", "INCREMENTAL FROM LSN");
     115           39 : }
     116              : 
     117              : /*
     118              :  * Write a backup label file to the output directory.
     119              :  *
     120              :  * This will be identical to the provided backup_label file, except that the
     121              :  * INCREMENTAL FROM LSN and INCREMENTAL FROM TLI lines will be omitted.
     122              :  *
     123              :  * The new file will be checksummed using the specified algorithm. If
     124              :  * mwriter != NULL, it will be added to the manifest.
     125              :  */
     126              : void
     127           14 : write_backup_label(char *output_directory, StringInfo buf,
     128              :                    pg_checksum_type checksum_type, manifest_writer *mwriter)
     129              : {
     130              :     char        output_filename[MAXPGPATH];
     131              :     int         output_fd;
     132              :     pg_checksum_context checksum_ctx;
     133              :     uint8       checksum_payload[PG_CHECKSUM_MAX_LENGTH];
     134              :     int         checksum_length;
     135              : 
     136           14 :     pg_checksum_init(&checksum_ctx, checksum_type);
     137              : 
     138           14 :     snprintf(output_filename, MAXPGPATH, "%s/backup_label", output_directory);
     139              : 
     140           14 :     if ((output_fd = open(output_filename,
     141              :                           O_WRONLY | O_CREAT | O_EXCL | PG_BINARY,
     142              :                           pg_file_create_mode)) < 0)
     143            0 :         pg_fatal("could not open file \"%s\": %m", output_filename);
     144              : 
     145          134 :     while (buf->cursor < buf->len)
     146              :     {
     147          120 :         char       *s = &buf->data[buf->cursor];
     148          120 :         int         eo = get_eol_offset(buf);
     149          120 :         char       *e = &buf->data[eo];
     150              : 
     151          120 :         if (!line_starts_with(s, e, "INCREMENTAL FROM LSN: ", NULL) &&
     152          109 :             !line_starts_with(s, e, "INCREMENTAL FROM TLI: ", NULL))
     153              :         {
     154              :             ssize_t     wb;
     155              : 
     156           98 :             wb = write(output_fd, s, e - s);
     157           98 :             if (wb != e - s)
     158              :             {
     159            0 :                 if (wb < 0)
     160            0 :                     pg_fatal("could not write file \"%s\": %m", output_filename);
     161              :                 else
     162            0 :                     pg_fatal("could not write file \"%s\": wrote %d of %d",
     163              :                              output_filename, (int) wb, (int) (e - s));
     164              :             }
     165           98 :             if (pg_checksum_update(&checksum_ctx, (uint8 *) s, e - s) < 0)
     166            0 :                 pg_fatal("could not update checksum of file \"%s\"",
     167              :                          output_filename);
     168              :         }
     169              : 
     170          120 :         buf->cursor = eo;
     171              :     }
     172              : 
     173           14 :     if (close(output_fd) != 0)
     174            0 :         pg_fatal("could not close file \"%s\": %m", output_filename);
     175              : 
     176           14 :     checksum_length = pg_checksum_final(&checksum_ctx, checksum_payload);
     177              : 
     178           14 :     if (mwriter != NULL)
     179              :     {
     180              :         struct stat sb;
     181              : 
     182              :         /*
     183              :          * We could track the length ourselves, but must stat() to get the
     184              :          * mtime.
     185              :          */
     186           13 :         if (stat(output_filename, &sb) < 0)
     187            0 :             pg_fatal("could not stat file \"%s\": %m", output_filename);
     188           13 :         add_file_to_manifest(mwriter, "backup_label", sb.st_size,
     189              :                              sb.st_mtime, checksum_type,
     190              :                              checksum_length, checksum_payload);
     191              :     }
     192           14 : }
     193              : 
     194              : /*
     195              :  * Return the offset at which the next line in the buffer starts, or there
     196              :  * is none, the offset at which the buffer ends.
     197              :  *
     198              :  * The search begins at buf->cursor.
     199              :  */
     200              : static int
     201          435 : get_eol_offset(StringInfo buf)
     202              : {
     203          435 :     int         eo = buf->cursor;
     204              : 
     205        13855 :     while (eo < buf->len)
     206              :     {
     207        13855 :         if (buf->data[eo] == '\n')
     208          435 :             return eo + 1;
     209        13420 :         ++eo;
     210              :     }
     211              : 
     212            0 :     return eo;
     213              : }
     214              : 
     215              : /*
     216              :  * Test whether the line that runs from s to e (inclusive of *s, but not
     217              :  * inclusive of *e) starts with the match string provided, and return true
     218              :  * or false according to whether or not this is the case.
     219              :  *
     220              :  * If the function returns true and if *sout != NULL, stores a pointer to the
     221              :  * byte following the match into *sout.
     222              :  */
     223              : static bool
     224         1273 : line_starts_with(char *s, char *e, char *match, char **sout)
     225              : {
     226         5487 :     while (s < e && *match != '\0' && *s == *match)
     227         4214 :         ++s, ++match;
     228              : 
     229         1273 :     if (*match == '\0' && sout != NULL)
     230          120 :         *sout = s;
     231              : 
     232         1273 :     return (*match == '\0');
     233              : }
     234              : 
     235              : /*
     236              :  * Parse an LSN starting at s and not stopping at or before e. The return value
     237              :  * is true on success and otherwise false. On success, stores the result into
     238              :  * *lsn and sets *c to the first character that is not part of the LSN.
     239              :  */
     240              : static bool
     241           60 : parse_lsn(char *s, char *e, XLogRecPtr *lsn, char **c)
     242              : {
     243           60 :     char        save = *e;
     244              :     int         nchars;
     245              :     bool        success;
     246              :     unsigned    hi;
     247              :     unsigned    lo;
     248              : 
     249           60 :     *e = '\0';
     250           60 :     success = (sscanf(s, "%X/%08X%n", &hi, &lo, &nchars) == 2);
     251           60 :     *e = save;
     252              : 
     253           60 :     if (success)
     254              :     {
     255           60 :         *lsn = ((XLogRecPtr) hi) << 32 | (XLogRecPtr) lo;
     256           60 :         *c = s + nchars;
     257              :     }
     258              : 
     259           60 :     return success;
     260              : }
     261              : 
     262              : /*
     263              :  * Parse a TLI starting at s and stopping at or before e. The return value is
     264              :  * true on success and otherwise false. On success, stores the result into
     265              :  * *tli. If the first character that is not part of the TLI is anything other
     266              :  * than a newline, that is deemed a failure.
     267              :  */
     268              : static bool
     269           60 : parse_tli(char *s, char *e, TimeLineID *tli)
     270              : {
     271           60 :     char        save = *e;
     272              :     int         nchars;
     273              :     bool        success;
     274              : 
     275           60 :     *e = '\0';
     276           60 :     success = (sscanf(s, "%u%n", tli, &nchars) == 1);
     277           60 :     *e = save;
     278              : 
     279           60 :     if (success && s[nchars] != '\n')
     280            0 :         success = false;
     281              : 
     282           60 :     return success;
     283              : }
        

Generated by: LCOV version 2.0-1