LCOV - code coverage report
Current view: top level - src/backend/backup - walsummary.c (source / functions) Coverage Total Hit
Test: PostgreSQL 19devel Lines: 66.7 % 105 70
Test Date: 2026-02-27 20:14:49 Functions: 80.0 % 10 8
Legend: Lines:     hit not hit

            Line data    Source code
       1              : /*-------------------------------------------------------------------------
       2              :  *
       3              :  * walsummary.c
       4              :  *    Functions for accessing and managing WAL summary data.
       5              :  *
       6              :  * Portions Copyright (c) 2010-2026, PostgreSQL Global Development Group
       7              :  *
       8              :  * src/backend/backup/walsummary.c
       9              :  *
      10              :  *-------------------------------------------------------------------------
      11              :  */
      12              : 
      13              : #include "postgres.h"
      14              : 
      15              : #include <sys/stat.h>
      16              : #include <unistd.h>
      17              : 
      18              : #include "access/xlog_internal.h"
      19              : #include "backup/walsummary.h"
      20              : #include "common/int.h"
      21              : #include "utils/wait_event.h"
      22              : 
      23              : static bool IsWalSummaryFilename(char *filename);
      24              : static int  ListComparatorForWalSummaryFiles(const ListCell *a,
      25              :                                              const ListCell *b);
      26              : 
      27              : /*
      28              :  * Get a list of WAL summaries.
      29              :  *
      30              :  * If tli != 0, only WAL summaries with the indicated TLI will be included.
      31              :  *
      32              :  * If start_lsn != InvalidXLogRecPtr, only summaries that end after the
      33              :  * indicated LSN will be included.
      34              :  *
      35              :  * If end_lsn != InvalidXLogRecPtr, only summaries that start before the
      36              :  * indicated LSN will be included.
      37              :  *
      38              :  * The intent is that you can call GetWalSummaries(tli, start_lsn, end_lsn)
      39              :  * to get all WAL summaries on the indicated timeline that overlap the
      40              :  * specified LSN range.
      41              :  */
      42              : List *
      43           23 : GetWalSummaries(TimeLineID tli, XLogRecPtr start_lsn, XLogRecPtr end_lsn)
      44              : {
      45              :     DIR        *sdir;
      46              :     struct dirent *dent;
      47           23 :     List       *result = NIL;
      48              : 
      49           23 :     sdir = AllocateDir(XLOGDIR "/summaries");
      50          204 :     while ((dent = ReadDir(sdir, XLOGDIR "/summaries")) != NULL)
      51              :     {
      52              :         WalSummaryFile *ws;
      53              :         uint32      tmp[5];
      54              :         TimeLineID  file_tli;
      55              :         XLogRecPtr  file_start_lsn;
      56              :         XLogRecPtr  file_end_lsn;
      57              : 
      58              :         /* Decode filename, or skip if it's not in the expected format. */
      59          181 :         if (!IsWalSummaryFilename(dent->d_name))
      60          121 :             continue;
      61          135 :         sscanf(dent->d_name, "%08X%08X%08X%08X%08X",
      62              :                &tmp[0], &tmp[1], &tmp[2], &tmp[3], &tmp[4]);
      63          135 :         file_tli = tmp[0];
      64          135 :         file_start_lsn = ((uint64) tmp[1]) << 32 | tmp[2];
      65          135 :         file_end_lsn = ((uint64) tmp[3]) << 32 | tmp[4];
      66              : 
      67              :         /* Skip if it doesn't match the filter criteria. */
      68          135 :         if (tli != 0 && tli != file_tli)
      69            0 :             continue;
      70          135 :         if (XLogRecPtrIsValid(start_lsn) && start_lsn >= file_end_lsn)
      71           75 :             continue;
      72           60 :         if (XLogRecPtrIsValid(end_lsn) && end_lsn <= file_start_lsn)
      73            0 :             continue;
      74              : 
      75              :         /* Add it to the list. */
      76           60 :         ws = palloc_object(WalSummaryFile);
      77           60 :         ws->tli = file_tli;
      78           60 :         ws->start_lsn = file_start_lsn;
      79           60 :         ws->end_lsn = file_end_lsn;
      80           60 :         result = lappend(result, ws);
      81              :     }
      82           23 :     FreeDir(sdir);
      83              : 
      84           23 :     return result;
      85              : }
      86              : 
      87              : /*
      88              :  * Build a new list of WAL summaries based on an existing list, but filtering
      89              :  * out summaries that don't match the search parameters.
      90              :  *
      91              :  * If tli != 0, only WAL summaries with the indicated TLI will be included.
      92              :  *
      93              :  * If start_lsn != InvalidXLogRecPtr, only summaries that end after the
      94              :  * indicated LSN will be included.
      95              :  *
      96              :  * If end_lsn != InvalidXLogRecPtr, only summaries that start before the
      97              :  * indicated LSN will be included.
      98              :  */
      99              : List *
     100           12 : FilterWalSummaries(List *wslist, TimeLineID tli,
     101              :                    XLogRecPtr start_lsn, XLogRecPtr end_lsn)
     102              : {
     103           12 :     List       *result = NIL;
     104              :     ListCell   *lc;
     105              : 
     106              :     /* Loop over input. */
     107           35 :     foreach(lc, wslist)
     108              :     {
     109           23 :         WalSummaryFile *ws = lfirst(lc);
     110              : 
     111              :         /* Skip if it doesn't match the filter criteria. */
     112           23 :         if (tli != 0 && tli != ws->tli)
     113            4 :             continue;
     114           19 :         if (XLogRecPtrIsValid(start_lsn) && start_lsn > ws->end_lsn)
     115            0 :             continue;
     116           19 :         if (XLogRecPtrIsValid(end_lsn) && end_lsn < ws->start_lsn)
     117            0 :             continue;
     118              : 
     119              :         /* Add it to the result list. */
     120           19 :         result = lappend(result, ws);
     121              :     }
     122              : 
     123           12 :     return result;
     124              : }
     125              : 
     126              : /*
     127              :  * Check whether the supplied list of WalSummaryFile objects covers the
     128              :  * whole range of LSNs from start_lsn to end_lsn. This function ignores
     129              :  * timelines, so the caller should probably filter using the appropriate
     130              :  * timeline before calling this.
     131              :  *
     132              :  * If the whole range of LSNs is covered, returns true, otherwise false.
     133              :  * If false is returned, *missing_lsn is set either to InvalidXLogRecPtr
     134              :  * if there are no WAL summary files in the input list, or to the first LSN
     135              :  * in the range that is not covered by a WAL summary file in the input list.
     136              :  */
     137              : bool
     138           12 : WalSummariesAreComplete(List *wslist, XLogRecPtr start_lsn,
     139              :                         XLogRecPtr end_lsn, XLogRecPtr *missing_lsn)
     140              : {
     141           12 :     XLogRecPtr  current_lsn = start_lsn;
     142              :     ListCell   *lc;
     143              : 
     144              :     /* Special case for empty list. */
     145           12 :     if (wslist == NIL)
     146              :     {
     147            0 :         *missing_lsn = InvalidXLogRecPtr;
     148            0 :         return false;
     149              :     }
     150              : 
     151              :     /* Make a private copy of the list and sort it by start LSN. */
     152           12 :     wslist = list_copy(wslist);
     153           12 :     list_sort(wslist, ListComparatorForWalSummaryFiles);
     154              : 
     155              :     /*
     156              :      * Consider summary files in order of increasing start_lsn, advancing the
     157              :      * known-summarized range from start_lsn toward end_lsn.
     158              :      *
     159              :      * Normally, the summary files should cover non-overlapping WAL ranges,
     160              :      * but this algorithm is intended to be correct even in case of overlap.
     161              :      */
     162           19 :     foreach(lc, wslist)
     163              :     {
     164           19 :         WalSummaryFile *ws = lfirst(lc);
     165              : 
     166           19 :         if (ws->start_lsn > current_lsn)
     167              :         {
     168              :             /* We found a gap. */
     169            0 :             break;
     170              :         }
     171           19 :         if (ws->end_lsn > current_lsn)
     172              :         {
     173              :             /*
     174              :              * Next summary extends beyond end of previous summary, so extend
     175              :              * the end of the range known to be summarized.
     176              :              */
     177           19 :             current_lsn = ws->end_lsn;
     178              : 
     179              :             /*
     180              :              * If the range we know to be summarized has reached the required
     181              :              * end LSN, we have proved completeness.
     182              :              */
     183           19 :             if (current_lsn >= end_lsn)
     184           12 :                 return true;
     185              :         }
     186              :     }
     187              : 
     188              :     /*
     189              :      * We either ran out of summary files without reaching the end LSN, or we
     190              :      * hit a gap in the sequence that resulted in us bailing out of the loop
     191              :      * above.
     192              :      */
     193            0 :     *missing_lsn = current_lsn;
     194            0 :     return false;
     195              : }
     196              : 
     197              : /*
     198              :  * Open a WAL summary file.
     199              :  *
     200              :  * This will throw an error in case of trouble. As an exception, if
     201              :  * missing_ok = true and the trouble is specifically that the file does
     202              :  * not exist, it will not throw an error and will return a value less than 0.
     203              :  */
     204              : File
     205           19 : OpenWalSummaryFile(WalSummaryFile *ws, bool missing_ok)
     206              : {
     207              :     char        path[MAXPGPATH];
     208              :     File        file;
     209              : 
     210           19 :     snprintf(path, MAXPGPATH,
     211              :              XLOGDIR "/summaries/%08X%08X%08X%08X%08X.summary",
     212              :              ws->tli,
     213           19 :              LSN_FORMAT_ARGS(ws->start_lsn),
     214           19 :              LSN_FORMAT_ARGS(ws->end_lsn));
     215              : 
     216           19 :     file = PathNameOpenFile(path, O_RDONLY);
     217           19 :     if (file < 0 && (errno != ENOENT || !missing_ok))
     218            0 :         ereport(ERROR,
     219              :                 (errcode_for_file_access(),
     220              :                  errmsg("could not open file \"%s\": %m", path)));
     221              : 
     222           19 :     return file;
     223              : }
     224              : 
     225              : /*
     226              :  * Remove a WAL summary file if the last modification time precedes the
     227              :  * cutoff time.
     228              :  */
     229              : void
     230            0 : RemoveWalSummaryIfOlderThan(WalSummaryFile *ws, time_t cutoff_time)
     231              : {
     232              :     char        path[MAXPGPATH];
     233              :     struct stat statbuf;
     234              : 
     235            0 :     snprintf(path, MAXPGPATH,
     236              :              XLOGDIR "/summaries/%08X%08X%08X%08X%08X.summary",
     237              :              ws->tli,
     238            0 :              LSN_FORMAT_ARGS(ws->start_lsn),
     239            0 :              LSN_FORMAT_ARGS(ws->end_lsn));
     240              : 
     241            0 :     if (lstat(path, &statbuf) != 0)
     242              :     {
     243            0 :         if (errno == ENOENT)
     244            0 :             return;
     245            0 :         ereport(ERROR,
     246              :                 (errcode_for_file_access(),
     247              :                  errmsg("could not stat file \"%s\": %m", path)));
     248              :     }
     249            0 :     if (statbuf.st_mtime >= cutoff_time)
     250            0 :         return;
     251            0 :     if (unlink(path) != 0)
     252            0 :         ereport(ERROR,
     253              :                 (errcode_for_file_access(),
     254              :                  errmsg("could not remove file \"%s\": %m", path)));
     255            0 :     ereport(DEBUG2,
     256              :             (errmsg_internal("removing file \"%s\"", path)));
     257              : }
     258              : 
     259              : /*
     260              :  * Test whether a filename looks like a WAL summary file.
     261              :  */
     262              : static bool
     263          181 : IsWalSummaryFilename(char *filename)
     264              : {
     265          316 :     return strspn(filename, "0123456789ABCDEF") == 40 &&
     266          135 :         strcmp(filename + 40, ".summary") == 0;
     267              : }
     268              : 
     269              : /*
     270              :  * Data read callback for use with CreateBlockRefTableReader.
     271              :  */
     272              : int
     273           19 : ReadWalSummary(void *wal_summary_io, void *data, int length)
     274              : {
     275           19 :     WalSummaryIO *io = wal_summary_io;
     276              :     int         nbytes;
     277              : 
     278           19 :     nbytes = FileRead(io->file, data, length, io->filepos,
     279              :                       WAIT_EVENT_WAL_SUMMARY_READ);
     280           19 :     if (nbytes < 0)
     281            0 :         ereport(ERROR,
     282              :                 (errcode_for_file_access(),
     283              :                  errmsg("could not read file \"%s\": %m",
     284              :                         FilePathName(io->file))));
     285              : 
     286           19 :     io->filepos += nbytes;
     287           19 :     return nbytes;
     288              : }
     289              : 
     290              : /*
     291              :  * Data write callback for use with WriteBlockRefTable.
     292              :  */
     293              : int
     294           18 : WriteWalSummary(void *wal_summary_io, void *data, int length)
     295              : {
     296           18 :     WalSummaryIO *io = wal_summary_io;
     297              :     int         nbytes;
     298              : 
     299           18 :     nbytes = FileWrite(io->file, data, length, io->filepos,
     300              :                        WAIT_EVENT_WAL_SUMMARY_WRITE);
     301           18 :     if (nbytes < 0)
     302            0 :         ereport(ERROR,
     303              :                 (errcode_for_file_access(),
     304              :                  errmsg("could not write file \"%s\": %m",
     305              :                         FilePathName(io->file))));
     306           18 :     if (nbytes != length)
     307            0 :         ereport(ERROR,
     308              :                 (errcode_for_file_access(),
     309              :                  errmsg("could not write file \"%s\": wrote only %d of %d bytes at offset %u",
     310              :                         FilePathName(io->file), nbytes,
     311              :                         length, (unsigned) io->filepos),
     312              :                  errhint("Check free disk space.")));
     313              : 
     314           18 :     io->filepos += nbytes;
     315           18 :     return nbytes;
     316              : }
     317              : 
     318              : /*
     319              :  * Error-reporting callback for use with CreateBlockRefTableReader.
     320              :  */
     321              : void
     322            0 : ReportWalSummaryError(void *callback_arg, char *fmt,...)
     323              : {
     324              :     StringInfoData buf;
     325              :     va_list     ap;
     326              :     int         needed;
     327              : 
     328            0 :     initStringInfo(&buf);
     329              :     for (;;)
     330              :     {
     331            0 :         va_start(ap, fmt);
     332            0 :         needed = appendStringInfoVA(&buf, fmt, ap);
     333            0 :         va_end(ap);
     334            0 :         if (needed == 0)
     335            0 :             break;
     336            0 :         enlargeStringInfo(&buf, needed);
     337              :     }
     338            0 :     ereport(ERROR,
     339              :             errcode(ERRCODE_DATA_CORRUPTED),
     340              :             errmsg_internal("%s", buf.data));
     341              : }
     342              : 
     343              : /*
     344              :  * Comparator to sort a List of WalSummaryFile objects by start_lsn.
     345              :  */
     346              : static int
     347            8 : ListComparatorForWalSummaryFiles(const ListCell *a, const ListCell *b)
     348              : {
     349            8 :     WalSummaryFile *ws1 = lfirst(a);
     350            8 :     WalSummaryFile *ws2 = lfirst(b);
     351              : 
     352            8 :     return pg_cmp_u64(ws1->start_lsn, ws2->start_lsn);
     353              : }
        

Generated by: LCOV version 2.0-1