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