Coverage Report

Created: 2026-11-09 07:20

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