Coverage Report

Created: 2025-09-27 06:52

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/postgres/src/backend/access/transam/timeline.c
Line
Count
Source
1
/*-------------------------------------------------------------------------
2
 *
3
 * timeline.c
4
 *    Functions for reading and writing timeline history files.
5
 *
6
 * A timeline history file lists the timeline changes of the timeline, in
7
 * a simple text format. They are archived along with the WAL segments.
8
 *
9
 * The files are named like "<tli>.history". For example, if the database
10
 * starts up and switches to timeline 5, the timeline history file would be
11
 * called "00000005.history".
12
 *
13
 * Each line in the file represents a timeline switch:
14
 *
15
 * <parentTLI> <switchpoint> <reason>
16
 *
17
 *  parentTLI ID of the parent timeline
18
 *  switchpoint XLogRecPtr of the WAL location where the switch happened
19
 *  reason    human-readable explanation of why the timeline was changed
20
 *
21
 * The fields are separated by tabs. Lines beginning with # are comments, and
22
 * are ignored. Empty lines are also ignored.
23
 *
24
 * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group
25
 * Portions Copyright (c) 1994, Regents of the University of California
26
 *
27
 * src/backend/access/transam/timeline.c
28
 *
29
 *-------------------------------------------------------------------------
30
 */
31
32
#include "postgres.h"
33
34
#include <sys/stat.h>
35
#include <unistd.h>
36
37
#include "access/timeline.h"
38
#include "access/xlog.h"
39
#include "access/xlog_internal.h"
40
#include "access/xlogarchive.h"
41
#include "access/xlogdefs.h"
42
#include "pgstat.h"
43
#include "storage/fd.h"
44
45
/*
46
 * Copies all timeline history files with id's between 'begin' and 'end'
47
 * from archive to pg_wal.
48
 */
49
void
50
restoreTimeLineHistoryFiles(TimeLineID begin, TimeLineID end)
51
0
{
52
0
  char    path[MAXPGPATH];
53
0
  char    histfname[MAXFNAMELEN];
54
0
  TimeLineID  tli;
55
56
0
  for (tli = begin; tli < end; tli++)
57
0
  {
58
0
    if (tli == 1)
59
0
      continue;
60
61
0
    TLHistoryFileName(histfname, tli);
62
0
    if (RestoreArchivedFile(path, histfname, "RECOVERYHISTORY", 0, false))
63
0
      KeepFileRestoredFromArchive(path, histfname);
64
0
  }
65
0
}
66
67
/*
68
 * Try to read a timeline's history file.
69
 *
70
 * If successful, return the list of component TLIs (the given TLI followed by
71
 * its ancestor TLIs).  If we can't find the history file, assume that the
72
 * timeline has no parents, and return a list of just the specified timeline
73
 * ID.
74
 */
75
List *
76
readTimeLineHistory(TimeLineID targetTLI)
77
0
{
78
0
  List     *result;
79
0
  char    path[MAXPGPATH];
80
0
  char    histfname[MAXFNAMELEN];
81
0
  FILE     *fd;
82
0
  TimeLineHistoryEntry *entry;
83
0
  TimeLineID  lasttli = 0;
84
0
  XLogRecPtr  prevend;
85
0
  bool    fromArchive = false;
86
87
  /* Timeline 1 does not have a history file, so no need to check */
88
0
  if (targetTLI == 1)
89
0
  {
90
0
    entry = (TimeLineHistoryEntry *) palloc(sizeof(TimeLineHistoryEntry));
91
0
    entry->tli = targetTLI;
92
0
    entry->begin = entry->end = InvalidXLogRecPtr;
93
0
    return list_make1(entry);
94
0
  }
95
96
0
  if (ArchiveRecoveryRequested)
97
0
  {
98
0
    TLHistoryFileName(histfname, targetTLI);
99
0
    fromArchive =
100
0
      RestoreArchivedFile(path, histfname, "RECOVERYHISTORY", 0, false);
101
0
  }
102
0
  else
103
0
    TLHistoryFilePath(path, targetTLI);
104
105
0
  fd = AllocateFile(path, "r");
106
0
  if (fd == NULL)
107
0
  {
108
0
    if (errno != ENOENT)
109
0
      ereport(FATAL,
110
0
          (errcode_for_file_access(),
111
0
           errmsg("could not open file \"%s\": %m", path)));
112
    /* Not there, so assume no parents */
113
0
    entry = (TimeLineHistoryEntry *) palloc(sizeof(TimeLineHistoryEntry));
114
0
    entry->tli = targetTLI;
115
0
    entry->begin = entry->end = InvalidXLogRecPtr;
116
0
    return list_make1(entry);
117
0
  }
118
119
0
  result = NIL;
120
121
  /*
122
   * Parse the file...
123
   */
124
0
  prevend = InvalidXLogRecPtr;
125
0
  for (;;)
126
0
  {
127
0
    char    fline[MAXPGPATH];
128
0
    char     *res;
129
0
    char     *ptr;
130
0
    TimeLineID  tli;
131
0
    uint32    switchpoint_hi;
132
0
    uint32    switchpoint_lo;
133
0
    int     nfields;
134
135
0
    pgstat_report_wait_start(WAIT_EVENT_TIMELINE_HISTORY_READ);
136
0
    res = fgets(fline, sizeof(fline), fd);
137
0
    pgstat_report_wait_end();
138
0
    if (res == NULL)
139
0
    {
140
0
      if (ferror(fd))
141
0
        ereport(ERROR,
142
0
            (errcode_for_file_access(),
143
0
             errmsg("could not read file \"%s\": %m", path)));
144
145
0
      break;
146
0
    }
147
148
    /* skip leading whitespace and check for # comment */
149
0
    for (ptr = fline; *ptr; ptr++)
150
0
    {
151
0
      if (!isspace((unsigned char) *ptr))
152
0
        break;
153
0
    }
154
0
    if (*ptr == '\0' || *ptr == '#')
155
0
      continue;
156
157
0
    nfields = sscanf(fline, "%u\t%X/%08X", &tli, &switchpoint_hi, &switchpoint_lo);
158
159
0
    if (nfields < 1)
160
0
    {
161
      /* expect a numeric timeline ID as first field of line */
162
0
      ereport(FATAL,
163
0
          (errmsg("syntax error in history file: %s", fline),
164
0
           errhint("Expected a numeric timeline ID.")));
165
0
    }
166
0
    if (nfields != 3)
167
0
      ereport(FATAL,
168
0
          (errmsg("syntax error in history file: %s", fline),
169
0
           errhint("Expected a write-ahead log switchpoint location.")));
170
171
0
    if (result && tli <= lasttli)
172
0
      ereport(FATAL,
173
0
          (errmsg("invalid data in history file: %s", fline),
174
0
           errhint("Timeline IDs must be in increasing sequence.")));
175
176
0
    lasttli = tli;
177
178
0
    entry = (TimeLineHistoryEntry *) palloc(sizeof(TimeLineHistoryEntry));
179
0
    entry->tli = tli;
180
0
    entry->begin = prevend;
181
0
    entry->end = ((uint64) (switchpoint_hi)) << 32 | (uint64) switchpoint_lo;
182
0
    prevend = entry->end;
183
184
    /* Build list with newest item first */
185
0
    result = lcons(entry, result);
186
187
    /* we ignore the remainder of each line */
188
0
  }
189
190
0
  FreeFile(fd);
191
192
0
  if (result && targetTLI <= lasttli)
193
0
    ereport(FATAL,
194
0
        (errmsg("invalid data in history file \"%s\"", path),
195
0
         errhint("Timeline IDs must be less than child timeline's ID.")));
196
197
  /*
198
   * Create one more entry for the "tip" of the timeline, which has no entry
199
   * in the history file.
200
   */
201
0
  entry = (TimeLineHistoryEntry *) palloc(sizeof(TimeLineHistoryEntry));
202
0
  entry->tli = targetTLI;
203
0
  entry->begin = prevend;
204
0
  entry->end = InvalidXLogRecPtr;
205
206
0
  result = lcons(entry, result);
207
208
  /*
209
   * If the history file was fetched from archive, save it in pg_wal for
210
   * future reference.
211
   */
212
0
  if (fromArchive)
213
0
    KeepFileRestoredFromArchive(path, histfname);
214
215
0
  return result;
216
0
}
217
218
/*
219
 * Probe whether a timeline history file exists for the given timeline ID
220
 */
221
bool
222
existsTimeLineHistory(TimeLineID probeTLI)
223
0
{
224
0
  char    path[MAXPGPATH];
225
0
  char    histfname[MAXFNAMELEN];
226
0
  FILE     *fd;
227
228
  /* Timeline 1 does not have a history file, so no need to check */
229
0
  if (probeTLI == 1)
230
0
    return false;
231
232
0
  if (ArchiveRecoveryRequested)
233
0
  {
234
0
    TLHistoryFileName(histfname, probeTLI);
235
0
    RestoreArchivedFile(path, histfname, "RECOVERYHISTORY", 0, false);
236
0
  }
237
0
  else
238
0
    TLHistoryFilePath(path, probeTLI);
239
240
0
  fd = AllocateFile(path, "r");
241
0
  if (fd != NULL)
242
0
  {
243
0
    FreeFile(fd);
244
0
    return true;
245
0
  }
246
0
  else
247
0
  {
248
0
    if (errno != ENOENT)
249
0
      ereport(FATAL,
250
0
          (errcode_for_file_access(),
251
0
           errmsg("could not open file \"%s\": %m", path)));
252
0
    return false;
253
0
  }
254
0
}
255
256
/*
257
 * Find the newest existing timeline, assuming that startTLI exists.
258
 *
259
 * Note: while this is somewhat heuristic, it does positively guarantee
260
 * that (result + 1) is not a known timeline, and therefore it should
261
 * be safe to assign that ID to a new timeline.
262
 */
263
TimeLineID
264
findNewestTimeLine(TimeLineID startTLI)
265
0
{
266
0
  TimeLineID  newestTLI;
267
0
  TimeLineID  probeTLI;
268
269
  /*
270
   * The algorithm is just to probe for the existence of timeline history
271
   * files.  XXX is it useful to allow gaps in the sequence?
272
   */
273
0
  newestTLI = startTLI;
274
275
0
  for (probeTLI = startTLI + 1;; probeTLI++)
276
0
  {
277
0
    if (existsTimeLineHistory(probeTLI))
278
0
    {
279
0
      newestTLI = probeTLI; /* probeTLI exists */
280
0
    }
281
0
    else
282
0
    {
283
      /* doesn't exist, assume we're done */
284
0
      break;
285
0
    }
286
0
  }
287
288
0
  return newestTLI;
289
0
}
290
291
/*
292
 * Create a new timeline history file.
293
 *
294
 *  newTLI: ID of the new timeline
295
 *  parentTLI: ID of its immediate parent
296
 *  switchpoint: WAL location where the system switched to the new timeline
297
 *  reason: human-readable explanation of why the timeline was switched
298
 *
299
 * Currently this is only used at the end recovery, and so there are no locking
300
 * considerations.  But we should be just as tense as XLogFileInit to avoid
301
 * emplacing a bogus file.
302
 */
303
void
304
writeTimeLineHistory(TimeLineID newTLI, TimeLineID parentTLI,
305
           XLogRecPtr switchpoint, char *reason)
306
0
{
307
0
  char    path[MAXPGPATH];
308
0
  char    tmppath[MAXPGPATH];
309
0
  char    histfname[MAXFNAMELEN];
310
0
  char    buffer[BLCKSZ];
311
0
  int     srcfd;
312
0
  int     fd;
313
0
  int     nbytes;
314
315
0
  Assert(newTLI > parentTLI); /* else bad selection of newTLI */
316
317
  /*
318
   * Write into a temp file name.
319
   */
320
0
  snprintf(tmppath, MAXPGPATH, XLOGDIR "/xlogtemp.%d", (int) getpid());
321
322
0
  unlink(tmppath);
323
324
  /* do not use get_sync_bit() here --- want to fsync only at end of fill */
325
0
  fd = OpenTransientFile(tmppath, O_RDWR | O_CREAT | O_EXCL);
326
0
  if (fd < 0)
327
0
    ereport(ERROR,
328
0
        (errcode_for_file_access(),
329
0
         errmsg("could not create file \"%s\": %m", tmppath)));
330
331
  /*
332
   * If a history file exists for the parent, copy it verbatim
333
   */
334
0
  if (ArchiveRecoveryRequested)
335
0
  {
336
0
    TLHistoryFileName(histfname, parentTLI);
337
0
    RestoreArchivedFile(path, histfname, "RECOVERYHISTORY", 0, false);
338
0
  }
339
0
  else
340
0
    TLHistoryFilePath(path, parentTLI);
341
342
0
  srcfd = OpenTransientFile(path, O_RDONLY);
343
0
  if (srcfd < 0)
344
0
  {
345
0
    if (errno != ENOENT)
346
0
      ereport(ERROR,
347
0
          (errcode_for_file_access(),
348
0
           errmsg("could not open file \"%s\": %m", path)));
349
    /* Not there, so assume parent has no parents */
350
0
  }
351
0
  else
352
0
  {
353
0
    for (;;)
354
0
    {
355
0
      errno = 0;
356
0
      pgstat_report_wait_start(WAIT_EVENT_TIMELINE_HISTORY_READ);
357
0
      nbytes = (int) read(srcfd, buffer, sizeof(buffer));
358
0
      pgstat_report_wait_end();
359
0
      if (nbytes < 0 || errno != 0)
360
0
        ereport(ERROR,
361
0
            (errcode_for_file_access(),
362
0
             errmsg("could not read file \"%s\": %m", path)));
363
0
      if (nbytes == 0)
364
0
        break;
365
0
      errno = 0;
366
0
      pgstat_report_wait_start(WAIT_EVENT_TIMELINE_HISTORY_WRITE);
367
0
      if ((int) write(fd, buffer, nbytes) != nbytes)
368
0
      {
369
0
        int     save_errno = errno;
370
371
        /*
372
         * If we fail to make the file, delete it to release disk
373
         * space
374
         */
375
0
        unlink(tmppath);
376
377
        /*
378
         * if write didn't set errno, assume problem is no disk space
379
         */
380
0
        errno = save_errno ? save_errno : ENOSPC;
381
382
0
        ereport(ERROR,
383
0
            (errcode_for_file_access(),
384
0
             errmsg("could not write to file \"%s\": %m", tmppath)));
385
0
      }
386
0
      pgstat_report_wait_end();
387
0
    }
388
389
0
    if (CloseTransientFile(srcfd) != 0)
390
0
      ereport(ERROR,
391
0
          (errcode_for_file_access(),
392
0
           errmsg("could not close file \"%s\": %m", path)));
393
0
  }
394
395
  /*
396
   * Append one line with the details of this timeline split.
397
   *
398
   * If we did have a parent file, insert an extra newline just in case the
399
   * parent file failed to end with one.
400
   */
401
0
  snprintf(buffer, sizeof(buffer),
402
0
       "%s%u\t%X/%08X\t%s\n",
403
0
       (srcfd < 0) ? "" : "\n",
404
0
       parentTLI,
405
0
       LSN_FORMAT_ARGS(switchpoint),
406
0
       reason);
407
408
0
  nbytes = strlen(buffer);
409
0
  errno = 0;
410
0
  pgstat_report_wait_start(WAIT_EVENT_TIMELINE_HISTORY_WRITE);
411
0
  if ((int) write(fd, buffer, nbytes) != nbytes)
412
0
  {
413
0
    int     save_errno = errno;
414
415
    /*
416
     * If we fail to make the file, delete it to release disk space
417
     */
418
0
    unlink(tmppath);
419
    /* if write didn't set errno, assume problem is no disk space */
420
0
    errno = save_errno ? save_errno : ENOSPC;
421
422
0
    ereport(ERROR,
423
0
        (errcode_for_file_access(),
424
0
         errmsg("could not write to file \"%s\": %m", tmppath)));
425
0
  }
426
0
  pgstat_report_wait_end();
427
428
0
  pgstat_report_wait_start(WAIT_EVENT_TIMELINE_HISTORY_SYNC);
429
0
  if (pg_fsync(fd) != 0)
430
0
    ereport(data_sync_elevel(ERROR),
431
0
        (errcode_for_file_access(),
432
0
         errmsg("could not fsync file \"%s\": %m", tmppath)));
433
0
  pgstat_report_wait_end();
434
435
0
  if (CloseTransientFile(fd) != 0)
436
0
    ereport(ERROR,
437
0
        (errcode_for_file_access(),
438
0
         errmsg("could not close file \"%s\": %m", tmppath)));
439
440
  /*
441
   * Now move the completed history file into place with its final name.
442
   */
443
0
  TLHistoryFilePath(path, newTLI);
444
0
  Assert(access(path, F_OK) != 0 && errno == ENOENT);
445
0
  durable_rename(tmppath, path, ERROR);
446
447
  /* The history file can be archived immediately. */
448
0
  if (XLogArchivingActive())
449
0
  {
450
0
    TLHistoryFileName(histfname, newTLI);
451
0
    XLogArchiveNotify(histfname);
452
0
  }
453
0
}
454
455
/*
456
 * Writes a history file for given timeline and contents.
457
 *
458
 * Currently this is only used in the walreceiver process, and so there are
459
 * no locking considerations.  But we should be just as tense as XLogFileInit
460
 * to avoid emplacing a bogus file.
461
 */
462
void
463
writeTimeLineHistoryFile(TimeLineID tli, char *content, int size)
464
0
{
465
0
  char    path[MAXPGPATH];
466
0
  char    tmppath[MAXPGPATH];
467
0
  int     fd;
468
469
  /*
470
   * Write into a temp file name.
471
   */
472
0
  snprintf(tmppath, MAXPGPATH, XLOGDIR "/xlogtemp.%d", (int) getpid());
473
474
0
  unlink(tmppath);
475
476
  /* do not use get_sync_bit() here --- want to fsync only at end of fill */
477
0
  fd = OpenTransientFile(tmppath, O_RDWR | O_CREAT | O_EXCL);
478
0
  if (fd < 0)
479
0
    ereport(ERROR,
480
0
        (errcode_for_file_access(),
481
0
         errmsg("could not create file \"%s\": %m", tmppath)));
482
483
0
  errno = 0;
484
0
  pgstat_report_wait_start(WAIT_EVENT_TIMELINE_HISTORY_FILE_WRITE);
485
0
  if ((int) write(fd, content, size) != size)
486
0
  {
487
0
    int     save_errno = errno;
488
489
    /*
490
     * If we fail to make the file, delete it to release disk space
491
     */
492
0
    unlink(tmppath);
493
    /* if write didn't set errno, assume problem is no disk space */
494
0
    errno = save_errno ? save_errno : ENOSPC;
495
496
0
    ereport(ERROR,
497
0
        (errcode_for_file_access(),
498
0
         errmsg("could not write to file \"%s\": %m", tmppath)));
499
0
  }
500
0
  pgstat_report_wait_end();
501
502
0
  pgstat_report_wait_start(WAIT_EVENT_TIMELINE_HISTORY_FILE_SYNC);
503
0
  if (pg_fsync(fd) != 0)
504
0
    ereport(data_sync_elevel(ERROR),
505
0
        (errcode_for_file_access(),
506
0
         errmsg("could not fsync file \"%s\": %m", tmppath)));
507
0
  pgstat_report_wait_end();
508
509
0
  if (CloseTransientFile(fd) != 0)
510
0
    ereport(ERROR,
511
0
        (errcode_for_file_access(),
512
0
         errmsg("could not close file \"%s\": %m", tmppath)));
513
514
  /*
515
   * Now move the completed history file into place with its final name,
516
   * replacing any existing file with the same name.
517
   */
518
0
  TLHistoryFilePath(path, tli);
519
0
  durable_rename(tmppath, path, ERROR);
520
0
}
521
522
/*
523
 * Returns true if 'expectedTLEs' contains a timeline with id 'tli'
524
 */
525
bool
526
tliInHistory(TimeLineID tli, List *expectedTLEs)
527
0
{
528
0
  ListCell   *cell;
529
530
0
  foreach(cell, expectedTLEs)
531
0
  {
532
0
    if (((TimeLineHistoryEntry *) lfirst(cell))->tli == tli)
533
0
      return true;
534
0
  }
535
536
0
  return false;
537
0
}
538
539
/*
540
 * Returns the ID of the timeline in use at a particular point in time, in
541
 * the given timeline history.
542
 */
543
TimeLineID
544
tliOfPointInHistory(XLogRecPtr ptr, List *history)
545
0
{
546
0
  ListCell   *cell;
547
548
0
  foreach(cell, history)
549
0
  {
550
0
    TimeLineHistoryEntry *tle = (TimeLineHistoryEntry *) lfirst(cell);
551
552
0
    if ((XLogRecPtrIsInvalid(tle->begin) || tle->begin <= ptr) &&
553
0
      (XLogRecPtrIsInvalid(tle->end) || ptr < tle->end))
554
0
    {
555
      /* found it */
556
0
      return tle->tli;
557
0
    }
558
0
  }
559
560
  /* shouldn't happen. */
561
0
  elog(ERROR, "timeline history was not contiguous");
562
0
  return 0;         /* keep compiler quiet */
563
0
}
564
565
/*
566
 * Returns the point in history where we branched off the given timeline,
567
 * and the timeline we branched to (*nextTLI). Returns InvalidXLogRecPtr if
568
 * the timeline is current, ie. we have not branched off from it, and throws
569
 * an error if the timeline is not part of this server's history.
570
 */
571
XLogRecPtr
572
tliSwitchPoint(TimeLineID tli, List *history, TimeLineID *nextTLI)
573
0
{
574
0
  ListCell   *cell;
575
576
0
  if (nextTLI)
577
0
    *nextTLI = 0;
578
0
  foreach(cell, history)
579
0
  {
580
0
    TimeLineHistoryEntry *tle = (TimeLineHistoryEntry *) lfirst(cell);
581
582
0
    if (tle->tli == tli)
583
0
      return tle->end;
584
0
    if (nextTLI)
585
0
      *nextTLI = tle->tli;
586
0
  }
587
588
0
  ereport(ERROR,
589
0
      (errmsg("requested timeline %u is not in this server's history",
590
0
          tli)));
591
0
  return InvalidXLogRecPtr; /* keep compiler quiet */
592
0
}