Skip to content

Commit d5497b9

Browse files
committed
Split off functions related to timeline history files and XLOG archiving.
This is just refactoring, to make the functions accessible outside xlog.c. A followup patch will make use of that, to allow fetching timeline history files over streaming replication.
1 parent 0899556 commit d5497b9

File tree

6 files changed

+1058
-929
lines changed

6 files changed

+1058
-929
lines changed

src/backend/access/transam/Makefile

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@ top_builddir = ../../../..
1313
include $(top_builddir)/src/Makefile.global
1414

1515
OBJS = clog.o transam.o varsup.o xact.o rmgr.o slru.o subtrans.o multixact.o \
16-
twophase.o twophase_rmgr.o xlog.o xlogfuncs.o xlogutils.o
16+
timeline.o twophase.o twophase_rmgr.o xlog.o xlogarchive.o xlogfuncs.o \
17+
xlogutils.o
1718

1819
include $(top_srcdir)/src/backend/common.mk
1920

src/backend/access/transam/timeline.c

Lines changed: 378 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,378 @@
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 "<WAL segment>.history". For example, if the
10+
* database starts up and switches to timeline 5, while processing WAL
11+
* segment 000000030000002A00000006 (the old timeline was 3), the timeline
12+
* history file would be called "000000050000002A00000006.history".
13+
*
14+
* Each line in the file represents a timeline switch:
15+
*
16+
* <parentTLI> <xlogfname> <reason>
17+
*
18+
* parentTLI ID of the parent timeline
19+
* xlogfname filename of the WAL segment where the switch happened
20+
* reason human-readable explanation of why the timeline was changed
21+
*
22+
* The fields are separated by tabs. Lines beginning with # are comments, and
23+
* are ignored. Empty lines are also ignored.
24+
*
25+
* Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group
26+
* Portions Copyright (c) 1994, Regents of the University of California
27+
*
28+
* src/backend/access/transam/timeline.c
29+
*
30+
*-------------------------------------------------------------------------
31+
*/
32+
33+
#include "postgres.h"
34+
35+
#include <stdio.h>
36+
#include <unistd.h>
37+
38+
#include "access/timeline.h"
39+
#include "access/xlog_internal.h"
40+
#include "access/xlogdefs.h"
41+
#include "storage/fd.h"
42+
43+
/*
44+
* Try to read a timeline's history file.
45+
*
46+
* If successful, return the list of component TLIs (the given TLI followed by
47+
* its ancestor TLIs). If we can't find the history file, assume that the
48+
* timeline has no parents, and return a list of just the specified timeline
49+
* ID.
50+
*/
51+
List *
52+
readTimeLineHistory(TimeLineID targetTLI)
53+
{
54+
List *result;
55+
char path[MAXPGPATH];
56+
char histfname[MAXFNAMELEN];
57+
char fline[MAXPGPATH];
58+
FILE *fd;
59+
60+
/* Timeline 1 does not have a history file, so no need to check */
61+
if (targetTLI == 1)
62+
return list_make1_int((int) targetTLI);
63+
64+
if (InArchiveRecovery)
65+
{
66+
TLHistoryFileName(histfname, targetTLI);
67+
RestoreArchivedFile(path, histfname, "RECOVERYHISTORY", 0);
68+
}
69+
else
70+
TLHistoryFilePath(path, targetTLI);
71+
72+
fd = AllocateFile(path, "r");
73+
if (fd == NULL)
74+
{
75+
if (errno != ENOENT)
76+
ereport(FATAL,
77+
(errcode_for_file_access(),
78+
errmsg("could not open file \"%s\": %m", path)));
79+
/* Not there, so assume no parents */
80+
return list_make1_int((int) targetTLI);
81+
}
82+
83+
result = NIL;
84+
85+
/*
86+
* Parse the file...
87+
*/
88+
while (fgets(fline, sizeof(fline), fd) != NULL)
89+
{
90+
/* skip leading whitespace and check for # comment */
91+
char *ptr;
92+
char *endptr;
93+
TimeLineID tli;
94+
95+
for (ptr = fline; *ptr; ptr++)
96+
{
97+
if (!isspace((unsigned char) *ptr))
98+
break;
99+
}
100+
if (*ptr == '\0' || *ptr == '#')
101+
continue;
102+
103+
/* expect a numeric timeline ID as first field of line */
104+
tli = (TimeLineID) strtoul(ptr, &endptr, 0);
105+
if (endptr == ptr)
106+
ereport(FATAL,
107+
(errmsg("syntax error in history file: %s", fline),
108+
errhint("Expected a numeric timeline ID.")));
109+
110+
if (result &&
111+
tli <= (TimeLineID) linitial_int(result))
112+
ereport(FATAL,
113+
(errmsg("invalid data in history file: %s", fline),
114+
errhint("Timeline IDs must be in increasing sequence.")));
115+
116+
/* Build list with newest item first */
117+
result = lcons_int((int) tli, result);
118+
119+
/* we ignore the remainder of each line */
120+
}
121+
122+
FreeFile(fd);
123+
124+
if (result &&
125+
targetTLI <= (TimeLineID) linitial_int(result))
126+
ereport(FATAL,
127+
(errmsg("invalid data in history file \"%s\"", path),
128+
errhint("Timeline IDs must be less than child timeline's ID.")));
129+
130+
result = lcons_int((int) targetTLI, result);
131+
132+
ereport(DEBUG3,
133+
(errmsg_internal("history of timeline %u is %s",
134+
targetTLI, nodeToString(result))));
135+
136+
return result;
137+
}
138+
139+
/*
140+
* Probe whether a timeline history file exists for the given timeline ID
141+
*/
142+
bool
143+
existsTimeLineHistory(TimeLineID probeTLI)
144+
{
145+
char path[MAXPGPATH];
146+
char histfname[MAXFNAMELEN];
147+
FILE *fd;
148+
149+
/* Timeline 1 does not have a history file, so no need to check */
150+
if (probeTLI == 1)
151+
return false;
152+
153+
if (InArchiveRecovery)
154+
{
155+
TLHistoryFileName(histfname, probeTLI);
156+
RestoreArchivedFile(path, histfname, "RECOVERYHISTORY", 0);
157+
}
158+
else
159+
TLHistoryFilePath(path, probeTLI);
160+
161+
fd = AllocateFile(path, "r");
162+
if (fd != NULL)
163+
{
164+
FreeFile(fd);
165+
return true;
166+
}
167+
else
168+
{
169+
if (errno != ENOENT)
170+
ereport(FATAL,
171+
(errcode_for_file_access(),
172+
errmsg("could not open file \"%s\": %m", path)));
173+
return false;
174+
}
175+
}
176+
177+
/*
178+
* Find the newest existing timeline, assuming that startTLI exists.
179+
*
180+
* Note: while this is somewhat heuristic, it does positively guarantee
181+
* that (result + 1) is not a known timeline, and therefore it should
182+
* be safe to assign that ID to a new timeline.
183+
*/
184+
TimeLineID
185+
findNewestTimeLine(TimeLineID startTLI)
186+
{
187+
TimeLineID newestTLI;
188+
TimeLineID probeTLI;
189+
190+
/*
191+
* The algorithm is just to probe for the existence of timeline history
192+
* files. XXX is it useful to allow gaps in the sequence?
193+
*/
194+
newestTLI = startTLI;
195+
196+
for (probeTLI = startTLI + 1;; probeTLI++)
197+
{
198+
if (existsTimeLineHistory(probeTLI))
199+
{
200+
newestTLI = probeTLI; /* probeTLI exists */
201+
}
202+
else
203+
{
204+
/* doesn't exist, assume we're done */
205+
break;
206+
}
207+
}
208+
209+
return newestTLI;
210+
}
211+
212+
/*
213+
* Create a new timeline history file.
214+
*
215+
* newTLI: ID of the new timeline
216+
* parentTLI: ID of its immediate parent
217+
* endTLI et al: ID of the last used WAL file, for annotation purposes
218+
* reason: human-readable explanation of why the timeline was switched
219+
*
220+
* Currently this is only used at the end recovery, and so there are no locking
221+
* considerations. But we should be just as tense as XLogFileInit to avoid
222+
* emplacing a bogus file.
223+
*/
224+
void
225+
writeTimeLineHistory(TimeLineID newTLI, TimeLineID parentTLI,
226+
TimeLineID endTLI, XLogSegNo endLogSegNo, char *reason)
227+
{
228+
char path[MAXPGPATH];
229+
char tmppath[MAXPGPATH];
230+
char histfname[MAXFNAMELEN];
231+
char xlogfname[MAXFNAMELEN];
232+
char buffer[BLCKSZ];
233+
int srcfd;
234+
int fd;
235+
int nbytes;
236+
237+
Assert(newTLI > parentTLI); /* else bad selection of newTLI */
238+
239+
/*
240+
* Write into a temp file name.
241+
*/
242+
snprintf(tmppath, MAXPGPATH, XLOGDIR "/xlogtemp.%d", (int) getpid());
243+
244+
unlink(tmppath);
245+
246+
/* do not use get_sync_bit() here --- want to fsync only at end of fill */
247+
fd = BasicOpenFile(tmppath, O_RDWR | O_CREAT | O_EXCL,
248+
S_IRUSR | S_IWUSR);
249+
if (fd < 0)
250+
ereport(ERROR,
251+
(errcode_for_file_access(),
252+
errmsg("could not create file \"%s\": %m", tmppath)));
253+
254+
/*
255+
* If a history file exists for the parent, copy it verbatim
256+
*/
257+
if (InArchiveRecovery)
258+
{
259+
TLHistoryFileName(histfname, parentTLI);
260+
RestoreArchivedFile(path, histfname, "RECOVERYHISTORY", 0);
261+
}
262+
else
263+
TLHistoryFilePath(path, parentTLI);
264+
265+
srcfd = BasicOpenFile(path, O_RDONLY, 0);
266+
if (srcfd < 0)
267+
{
268+
if (errno != ENOENT)
269+
ereport(ERROR,
270+
(errcode_for_file_access(),
271+
errmsg("could not open file \"%s\": %m", path)));
272+
/* Not there, so assume parent has no parents */
273+
}
274+
else
275+
{
276+
for (;;)
277+
{
278+
errno = 0;
279+
nbytes = (int) read(srcfd, buffer, sizeof(buffer));
280+
if (nbytes < 0 || errno != 0)
281+
ereport(ERROR,
282+
(errcode_for_file_access(),
283+
errmsg("could not read file \"%s\": %m", path)));
284+
if (nbytes == 0)
285+
break;
286+
errno = 0;
287+
if ((int) write(fd, buffer, nbytes) != nbytes)
288+
{
289+
int save_errno = errno;
290+
291+
/*
292+
* If we fail to make the file, delete it to release disk
293+
* space
294+
*/
295+
unlink(tmppath);
296+
297+
/*
298+
* if write didn't set errno, assume problem is no disk space
299+
*/
300+
errno = save_errno ? save_errno : ENOSPC;
301+
302+
ereport(ERROR,
303+
(errcode_for_file_access(),
304+
errmsg("could not write to file \"%s\": %m", tmppath)));
305+
}
306+
}
307+
close(srcfd);
308+
}
309+
310+
/*
311+
* Append one line with the details of this timeline split.
312+
*
313+
* If we did have a parent file, insert an extra newline just in case the
314+
* parent file failed to end with one.
315+
*/
316+
XLogFileName(xlogfname, endTLI, endLogSegNo);
317+
318+
snprintf(buffer, sizeof(buffer),
319+
"%s%u\t%s\t%s\n",
320+
(srcfd < 0) ? "" : "\n",
321+
parentTLI,
322+
xlogfname,
323+
reason);
324+
325+
nbytes = strlen(buffer);
326+
errno = 0;
327+
if ((int) write(fd, buffer, nbytes) != nbytes)
328+
{
329+
int save_errno = errno;
330+
331+
/*
332+
* If we fail to make the file, delete it to release disk space
333+
*/
334+
unlink(tmppath);
335+
/* if write didn't set errno, assume problem is no disk space */
336+
errno = save_errno ? save_errno : ENOSPC;
337+
338+
ereport(ERROR,
339+
(errcode_for_file_access(),
340+
errmsg("could not write to file \"%s\": %m", tmppath)));
341+
}
342+
343+
if (pg_fsync(fd) != 0)
344+
ereport(ERROR,
345+
(errcode_for_file_access(),
346+
errmsg("could not fsync file \"%s\": %m", tmppath)));
347+
348+
if (close(fd))
349+
ereport(ERROR,
350+
(errcode_for_file_access(),
351+
errmsg("could not close file \"%s\": %m", tmppath)));
352+
353+
354+
/*
355+
* Now move the completed history file into place with its final name.
356+
*/
357+
TLHistoryFilePath(path, newTLI);
358+
359+
/*
360+
* Prefer link() to rename() here just to be really sure that we don't
361+
* overwrite an existing logfile. However, there shouldn't be one, so
362+
* rename() is an acceptable substitute except for the truly paranoid.
363+
*/
364+
#if HAVE_WORKING_LINK
365+
if (link(tmppath, path) < 0)
366+
ereport(ERROR,
367+
(errcode_for_file_access(),
368+
errmsg("could not link file \"%s\" to \"%s\": %m",
369+
tmppath, path)));
370+
unlink(tmppath);
371+
#else
372+
if (rename(tmppath, path) < 0)
373+
ereport(ERROR,
374+
(errcode_for_file_access(),
375+
errmsg("could not rename file \"%s\" to \"%s\": %m",
376+
tmppath, path)));
377+
#endif
378+
}

0 commit comments

Comments
 (0)