Coverage Report

Created: 2025-06-13 06:06

/src/postgres/src/backend/backup/walsummary.c
Line
Count
Source (jump to first uncovered line)
1
/*-------------------------------------------------------------------------
2
 *
3
 * walsummary.c
4
 *    Functions for accessing and managing WAL summary data.
5
 *
6
 * Portions Copyright (c) 2010-2025, 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
GetWalSummaries(TimeLineID tli, XLogRecPtr start_lsn, XLogRecPtr end_lsn)
44
0
{
45
0
  DIR      *sdir;
46
0
  struct dirent *dent;
47
0
  List     *result = NIL;
48
49
0
  sdir = AllocateDir(XLOGDIR "/summaries");
50
0
  while ((dent = ReadDir(sdir, XLOGDIR "/summaries")) != NULL)
51
0
  {
52
0
    WalSummaryFile *ws;
53
0
    uint32    tmp[5];
54
0
    TimeLineID  file_tli;
55
0
    XLogRecPtr  file_start_lsn;
56
0
    XLogRecPtr  file_end_lsn;
57
58
    /* Decode filename, or skip if it's not in the expected format. */
59
0
    if (!IsWalSummaryFilename(dent->d_name))
60
0
      continue;
61
0
    sscanf(dent->d_name, "%08X%08X%08X%08X%08X",
62
0
         &tmp[0], &tmp[1], &tmp[2], &tmp[3], &tmp[4]);
63
0
    file_tli = tmp[0];
64
0
    file_start_lsn = ((uint64) tmp[1]) << 32 | tmp[2];
65
0
    file_end_lsn = ((uint64) tmp[3]) << 32 | tmp[4];
66
67
    /* Skip if it doesn't match the filter criteria. */
68
0
    if (tli != 0 && tli != file_tli)
69
0
      continue;
70
0
    if (!XLogRecPtrIsInvalid(start_lsn) && start_lsn >= file_end_lsn)
71
0
      continue;
72
0
    if (!XLogRecPtrIsInvalid(end_lsn) && end_lsn <= file_start_lsn)
73
0
      continue;
74
75
    /* Add it to the list. */
76
0
    ws = palloc(sizeof(WalSummaryFile));
77
0
    ws->tli = file_tli;
78
0
    ws->start_lsn = file_start_lsn;
79
0
    ws->end_lsn = file_end_lsn;
80
0
    result = lappend(result, ws);
81
0
  }
82
0
  FreeDir(sdir);
83
84
0
  return result;
85
0
}
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
FilterWalSummaries(List *wslist, TimeLineID tli,
101
           XLogRecPtr start_lsn, XLogRecPtr end_lsn)
102
0
{
103
0
  List     *result = NIL;
104
0
  ListCell   *lc;
105
106
  /* Loop over input. */
107
0
  foreach(lc, wslist)
108
0
  {
109
0
    WalSummaryFile *ws = lfirst(lc);
110
111
    /* Skip if it doesn't match the filter criteria. */
112
0
    if (tli != 0 && tli != ws->tli)
113
0
      continue;
114
0
    if (!XLogRecPtrIsInvalid(start_lsn) && start_lsn > ws->end_lsn)
115
0
      continue;
116
0
    if (!XLogRecPtrIsInvalid(end_lsn) && end_lsn < ws->start_lsn)
117
0
      continue;
118
119
    /* Add it to the result list. */
120
0
    result = lappend(result, ws);
121
0
  }
122
123
0
  return result;
124
0
}
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
WalSummariesAreComplete(List *wslist, XLogRecPtr start_lsn,
139
            XLogRecPtr end_lsn, XLogRecPtr *missing_lsn)
140
0
{
141
0
  XLogRecPtr  current_lsn = start_lsn;
142
0
  ListCell   *lc;
143
144
  /* Special case for empty list. */
145
0
  if (wslist == NIL)
146
0
  {
147
0
    *missing_lsn = InvalidXLogRecPtr;
148
0
    return false;
149
0
  }
150
151
  /* Make a private copy of the list and sort it by start LSN. */
152
0
  wslist = list_copy(wslist);
153
0
  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
0
  foreach(lc, wslist)
163
0
  {
164
0
    WalSummaryFile *ws = lfirst(lc);
165
166
0
    if (ws->start_lsn > current_lsn)
167
0
    {
168
      /* We found a gap. */
169
0
      break;
170
0
    }
171
0
    if (ws->end_lsn > current_lsn)
172
0
    {
173
      /*
174
       * Next summary extends beyond end of previous summary, so extend
175
       * the end of the range known to be summarized.
176
       */
177
0
      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
0
      if (current_lsn >= end_lsn)
184
0
        return true;
185
0
    }
186
0
  }
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
0
}
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
OpenWalSummaryFile(WalSummaryFile *ws, bool missing_ok)
206
0
{
207
0
  char    path[MAXPGPATH];
208
0
  File    file;
209
210
0
  snprintf(path, MAXPGPATH,
211
0
       XLOGDIR "/summaries/%08X%08X%08X%08X%08X.summary",
212
0
       ws->tli,
213
0
       LSN_FORMAT_ARGS(ws->start_lsn),
214
0
       LSN_FORMAT_ARGS(ws->end_lsn));
215
216
0
  file = PathNameOpenFile(path, O_RDONLY);
217
0
  if (file < 0 && (errno != EEXIST || !missing_ok))
218
0
    ereport(ERROR,
219
0
        (errcode_for_file_access(),
220
0
         errmsg("could not open file \"%s\": %m", path)));
221
222
0
  return file;
223
0
}
224
225
/*
226
 * Remove a WAL summary file if the last modification time precedes the
227
 * cutoff time.
228
 */
229
void
230
RemoveWalSummaryIfOlderThan(WalSummaryFile *ws, time_t cutoff_time)
231
0
{
232
0
  char    path[MAXPGPATH];
233
0
  struct stat statbuf;
234
235
0
  snprintf(path, MAXPGPATH,
236
0
       XLOGDIR "/summaries/%08X%08X%08X%08X%08X.summary",
237
0
       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
0
  {
243
0
    if (errno == ENOENT)
244
0
      return;
245
0
    ereport(ERROR,
246
0
        (errcode_for_file_access(),
247
0
         errmsg("could not stat file \"%s\": %m", path)));
248
0
  }
249
0
  if (statbuf.st_mtime >= cutoff_time)
250
0
    return;
251
0
  if (unlink(path) != 0)
252
0
    ereport(ERROR,
253
0
        (errcode_for_file_access(),
254
0
         errmsg("could not stat file \"%s\": %m", path)));
255
0
  ereport(DEBUG2,
256
0
      (errmsg_internal("removing file \"%s\"", path)));
257
0
}
258
259
/*
260
 * Test whether a filename looks like a WAL summary file.
261
 */
262
static bool
263
IsWalSummaryFilename(char *filename)
264
0
{
265
0
  return strspn(filename, "0123456789ABCDEF") == 40 &&
266
0
    strcmp(filename + 40, ".summary") == 0;
267
0
}
268
269
/*
270
 * Data read callback for use with CreateBlockRefTableReader.
271
 */
272
int
273
ReadWalSummary(void *wal_summary_io, void *data, int length)
274
0
{
275
0
  WalSummaryIO *io = wal_summary_io;
276
0
  int     nbytes;
277
278
0
  nbytes = FileRead(io->file, data, length, io->filepos,
279
0
            WAIT_EVENT_WAL_SUMMARY_READ);
280
0
  if (nbytes < 0)
281
0
    ereport(ERROR,
282
0
        (errcode_for_file_access(),
283
0
         errmsg("could not read file \"%s\": %m",
284
0
            FilePathName(io->file))));
285
286
0
  io->filepos += nbytes;
287
0
  return nbytes;
288
0
}
289
290
/*
291
 * Data write callback for use with WriteBlockRefTable.
292
 */
293
int
294
WriteWalSummary(void *wal_summary_io, void *data, int length)
295
0
{
296
0
  WalSummaryIO *io = wal_summary_io;
297
0
  int     nbytes;
298
299
0
  nbytes = FileWrite(io->file, data, length, io->filepos,
300
0
             WAIT_EVENT_WAL_SUMMARY_WRITE);
301
0
  if (nbytes < 0)
302
0
    ereport(ERROR,
303
0
        (errcode_for_file_access(),
304
0
         errmsg("could not write file \"%s\": %m",
305
0
            FilePathName(io->file))));
306
0
  if (nbytes != length)
307
0
    ereport(ERROR,
308
0
        (errcode_for_file_access(),
309
0
         errmsg("could not write file \"%s\": wrote only %d of %d bytes at offset %u",
310
0
            FilePathName(io->file), nbytes,
311
0
            length, (unsigned) io->filepos),
312
0
         errhint("Check free disk space.")));
313
314
0
  io->filepos += nbytes;
315
0
  return nbytes;
316
0
}
317
318
/*
319
 * Error-reporting callback for use with CreateBlockRefTableReader.
320
 */
321
void
322
ReportWalSummaryError(void *callback_arg, char *fmt,...)
323
0
{
324
0
  StringInfoData buf;
325
0
  va_list   ap;
326
0
  int     needed;
327
328
0
  initStringInfo(&buf);
329
0
  for (;;)
330
0
  {
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
0
  }
338
0
  ereport(ERROR,
339
0
      errcode(ERRCODE_DATA_CORRUPTED),
340
0
      errmsg_internal("%s", buf.data));
341
0
}
342
343
/*
344
 * Comparator to sort a List of WalSummaryFile objects by start_lsn.
345
 */
346
static int
347
ListComparatorForWalSummaryFiles(const ListCell *a, const ListCell *b)
348
0
{
349
0
  WalSummaryFile *ws1 = lfirst(a);
350
0
  WalSummaryFile *ws2 = lfirst(b);
351
352
0
  return pg_cmp_u64(ws1->start_lsn, ws2->start_lsn);
353
0
}