LCOV - differential code coverage report
Current view: top level - src/bin/pg_combinebackup - backup_label.c (source / functions) Coverage Total Hit UNC UBC GNC CBC DUB DCB
Current: ba12a202ce1b5581dc0ed149cf3f637d7897ad5d vs 2866d8c7dbfc9d882a7d80fef93fbbe763709932 Lines: 80.0 % 105 84 1 20 10 74 1 6
Current Date: 2026-08-27 14:31:44 +0300 Functions: 100.0 % 6 6 2 4
Baseline: lcov-20260827-baseline Branches: 68.3 % 82 56 2 24 2 54
Baseline Date: 2026-08-27 14:31:58 +0300 Line coverage date bins:
Legend: Lines:     hit not hit
Branches: + taken - not taken # not executed
(1,7] days: 100.0 % 6 6 6
(30,360] days: 80.0 % 5 4 1 4
(360..) days: 78.7 % 94 74 20 74
Function coverage date bins:
(360..) days: 100.0 % 6 6 2 4
Branch coverage date bins:
(30,360] days: 50.0 % 4 2 2 2
(360..) days: 69.2 % 78 54 24 54

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

Generated by: LCOV version 2.0-1