mirror of
https://github.com/postgres/postgres.git
synced 2025-11-28 11:44:57 +03:00
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.
This commit is contained in:
378
src/backend/access/transam/timeline.c
Normal file
378
src/backend/access/transam/timeline.c
Normal file
@@ -0,0 +1,378 @@
|
||||
/*-------------------------------------------------------------------------
|
||||
*
|
||||
* timeline.c
|
||||
* Functions for reading and writing timeline history files.
|
||||
*
|
||||
* A timeline history file lists the timeline changes of the timeline, in
|
||||
* a simple text format. They are archived along with the WAL segments.
|
||||
*
|
||||
* The files are named like "<WAL segment>.history". For example, if the
|
||||
* database starts up and switches to timeline 5, while processing WAL
|
||||
* segment 000000030000002A00000006 (the old timeline was 3), the timeline
|
||||
* history file would be called "000000050000002A00000006.history".
|
||||
*
|
||||
* Each line in the file represents a timeline switch:
|
||||
*
|
||||
* <parentTLI> <xlogfname> <reason>
|
||||
*
|
||||
* parentTLI ID of the parent timeline
|
||||
* xlogfname filename of the WAL segment where the switch happened
|
||||
* reason human-readable explanation of why the timeline was changed
|
||||
*
|
||||
* The fields are separated by tabs. Lines beginning with # are comments, and
|
||||
* are ignored. Empty lines are also ignored.
|
||||
*
|
||||
* Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group
|
||||
* Portions Copyright (c) 1994, Regents of the University of California
|
||||
*
|
||||
* src/backend/access/transam/timeline.c
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
#include "postgres.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "access/timeline.h"
|
||||
#include "access/xlog_internal.h"
|
||||
#include "access/xlogdefs.h"
|
||||
#include "storage/fd.h"
|
||||
|
||||
/*
|
||||
* Try to read a timeline's history file.
|
||||
*
|
||||
* If successful, return the list of component TLIs (the given TLI followed by
|
||||
* its ancestor TLIs). If we can't find the history file, assume that the
|
||||
* timeline has no parents, and return a list of just the specified timeline
|
||||
* ID.
|
||||
*/
|
||||
List *
|
||||
readTimeLineHistory(TimeLineID targetTLI)
|
||||
{
|
||||
List *result;
|
||||
char path[MAXPGPATH];
|
||||
char histfname[MAXFNAMELEN];
|
||||
char fline[MAXPGPATH];
|
||||
FILE *fd;
|
||||
|
||||
/* Timeline 1 does not have a history file, so no need to check */
|
||||
if (targetTLI == 1)
|
||||
return list_make1_int((int) targetTLI);
|
||||
|
||||
if (InArchiveRecovery)
|
||||
{
|
||||
TLHistoryFileName(histfname, targetTLI);
|
||||
RestoreArchivedFile(path, histfname, "RECOVERYHISTORY", 0);
|
||||
}
|
||||
else
|
||||
TLHistoryFilePath(path, targetTLI);
|
||||
|
||||
fd = AllocateFile(path, "r");
|
||||
if (fd == NULL)
|
||||
{
|
||||
if (errno != ENOENT)
|
||||
ereport(FATAL,
|
||||
(errcode_for_file_access(),
|
||||
errmsg("could not open file \"%s\": %m", path)));
|
||||
/* Not there, so assume no parents */
|
||||
return list_make1_int((int) targetTLI);
|
||||
}
|
||||
|
||||
result = NIL;
|
||||
|
||||
/*
|
||||
* Parse the file...
|
||||
*/
|
||||
while (fgets(fline, sizeof(fline), fd) != NULL)
|
||||
{
|
||||
/* skip leading whitespace and check for # comment */
|
||||
char *ptr;
|
||||
char *endptr;
|
||||
TimeLineID tli;
|
||||
|
||||
for (ptr = fline; *ptr; ptr++)
|
||||
{
|
||||
if (!isspace((unsigned char) *ptr))
|
||||
break;
|
||||
}
|
||||
if (*ptr == '\0' || *ptr == '#')
|
||||
continue;
|
||||
|
||||
/* expect a numeric timeline ID as first field of line */
|
||||
tli = (TimeLineID) strtoul(ptr, &endptr, 0);
|
||||
if (endptr == ptr)
|
||||
ereport(FATAL,
|
||||
(errmsg("syntax error in history file: %s", fline),
|
||||
errhint("Expected a numeric timeline ID.")));
|
||||
|
||||
if (result &&
|
||||
tli <= (TimeLineID) linitial_int(result))
|
||||
ereport(FATAL,
|
||||
(errmsg("invalid data in history file: %s", fline),
|
||||
errhint("Timeline IDs must be in increasing sequence.")));
|
||||
|
||||
/* Build list with newest item first */
|
||||
result = lcons_int((int) tli, result);
|
||||
|
||||
/* we ignore the remainder of each line */
|
||||
}
|
||||
|
||||
FreeFile(fd);
|
||||
|
||||
if (result &&
|
||||
targetTLI <= (TimeLineID) linitial_int(result))
|
||||
ereport(FATAL,
|
||||
(errmsg("invalid data in history file \"%s\"", path),
|
||||
errhint("Timeline IDs must be less than child timeline's ID.")));
|
||||
|
||||
result = lcons_int((int) targetTLI, result);
|
||||
|
||||
ereport(DEBUG3,
|
||||
(errmsg_internal("history of timeline %u is %s",
|
||||
targetTLI, nodeToString(result))));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/*
|
||||
* Probe whether a timeline history file exists for the given timeline ID
|
||||
*/
|
||||
bool
|
||||
existsTimeLineHistory(TimeLineID probeTLI)
|
||||
{
|
||||
char path[MAXPGPATH];
|
||||
char histfname[MAXFNAMELEN];
|
||||
FILE *fd;
|
||||
|
||||
/* Timeline 1 does not have a history file, so no need to check */
|
||||
if (probeTLI == 1)
|
||||
return false;
|
||||
|
||||
if (InArchiveRecovery)
|
||||
{
|
||||
TLHistoryFileName(histfname, probeTLI);
|
||||
RestoreArchivedFile(path, histfname, "RECOVERYHISTORY", 0);
|
||||
}
|
||||
else
|
||||
TLHistoryFilePath(path, probeTLI);
|
||||
|
||||
fd = AllocateFile(path, "r");
|
||||
if (fd != NULL)
|
||||
{
|
||||
FreeFile(fd);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (errno != ENOENT)
|
||||
ereport(FATAL,
|
||||
(errcode_for_file_access(),
|
||||
errmsg("could not open file \"%s\": %m", path)));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Find the newest existing timeline, assuming that startTLI exists.
|
||||
*
|
||||
* Note: while this is somewhat heuristic, it does positively guarantee
|
||||
* that (result + 1) is not a known timeline, and therefore it should
|
||||
* be safe to assign that ID to a new timeline.
|
||||
*/
|
||||
TimeLineID
|
||||
findNewestTimeLine(TimeLineID startTLI)
|
||||
{
|
||||
TimeLineID newestTLI;
|
||||
TimeLineID probeTLI;
|
||||
|
||||
/*
|
||||
* The algorithm is just to probe for the existence of timeline history
|
||||
* files. XXX is it useful to allow gaps in the sequence?
|
||||
*/
|
||||
newestTLI = startTLI;
|
||||
|
||||
for (probeTLI = startTLI + 1;; probeTLI++)
|
||||
{
|
||||
if (existsTimeLineHistory(probeTLI))
|
||||
{
|
||||
newestTLI = probeTLI; /* probeTLI exists */
|
||||
}
|
||||
else
|
||||
{
|
||||
/* doesn't exist, assume we're done */
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return newestTLI;
|
||||
}
|
||||
|
||||
/*
|
||||
* Create a new timeline history file.
|
||||
*
|
||||
* newTLI: ID of the new timeline
|
||||
* parentTLI: ID of its immediate parent
|
||||
* endTLI et al: ID of the last used WAL file, for annotation purposes
|
||||
* reason: human-readable explanation of why the timeline was switched
|
||||
*
|
||||
* Currently this is only used at the end recovery, and so there are no locking
|
||||
* considerations. But we should be just as tense as XLogFileInit to avoid
|
||||
* emplacing a bogus file.
|
||||
*/
|
||||
void
|
||||
writeTimeLineHistory(TimeLineID newTLI, TimeLineID parentTLI,
|
||||
TimeLineID endTLI, XLogSegNo endLogSegNo, char *reason)
|
||||
{
|
||||
char path[MAXPGPATH];
|
||||
char tmppath[MAXPGPATH];
|
||||
char histfname[MAXFNAMELEN];
|
||||
char xlogfname[MAXFNAMELEN];
|
||||
char buffer[BLCKSZ];
|
||||
int srcfd;
|
||||
int fd;
|
||||
int nbytes;
|
||||
|
||||
Assert(newTLI > parentTLI); /* else bad selection of newTLI */
|
||||
|
||||
/*
|
||||
* Write into a temp file name.
|
||||
*/
|
||||
snprintf(tmppath, MAXPGPATH, XLOGDIR "/xlogtemp.%d", (int) getpid());
|
||||
|
||||
unlink(tmppath);
|
||||
|
||||
/* do not use get_sync_bit() here --- want to fsync only at end of fill */
|
||||
fd = BasicOpenFile(tmppath, O_RDWR | O_CREAT | O_EXCL,
|
||||
S_IRUSR | S_IWUSR);
|
||||
if (fd < 0)
|
||||
ereport(ERROR,
|
||||
(errcode_for_file_access(),
|
||||
errmsg("could not create file \"%s\": %m", tmppath)));
|
||||
|
||||
/*
|
||||
* If a history file exists for the parent, copy it verbatim
|
||||
*/
|
||||
if (InArchiveRecovery)
|
||||
{
|
||||
TLHistoryFileName(histfname, parentTLI);
|
||||
RestoreArchivedFile(path, histfname, "RECOVERYHISTORY", 0);
|
||||
}
|
||||
else
|
||||
TLHistoryFilePath(path, parentTLI);
|
||||
|
||||
srcfd = BasicOpenFile(path, O_RDONLY, 0);
|
||||
if (srcfd < 0)
|
||||
{
|
||||
if (errno != ENOENT)
|
||||
ereport(ERROR,
|
||||
(errcode_for_file_access(),
|
||||
errmsg("could not open file \"%s\": %m", path)));
|
||||
/* Not there, so assume parent has no parents */
|
||||
}
|
||||
else
|
||||
{
|
||||
for (;;)
|
||||
{
|
||||
errno = 0;
|
||||
nbytes = (int) read(srcfd, buffer, sizeof(buffer));
|
||||
if (nbytes < 0 || errno != 0)
|
||||
ereport(ERROR,
|
||||
(errcode_for_file_access(),
|
||||
errmsg("could not read file \"%s\": %m", path)));
|
||||
if (nbytes == 0)
|
||||
break;
|
||||
errno = 0;
|
||||
if ((int) write(fd, buffer, nbytes) != nbytes)
|
||||
{
|
||||
int save_errno = errno;
|
||||
|
||||
/*
|
||||
* If we fail to make the file, delete it to release disk
|
||||
* space
|
||||
*/
|
||||
unlink(tmppath);
|
||||
|
||||
/*
|
||||
* if write didn't set errno, assume problem is no disk space
|
||||
*/
|
||||
errno = save_errno ? save_errno : ENOSPC;
|
||||
|
||||
ereport(ERROR,
|
||||
(errcode_for_file_access(),
|
||||
errmsg("could not write to file \"%s\": %m", tmppath)));
|
||||
}
|
||||
}
|
||||
close(srcfd);
|
||||
}
|
||||
|
||||
/*
|
||||
* Append one line with the details of this timeline split.
|
||||
*
|
||||
* If we did have a parent file, insert an extra newline just in case the
|
||||
* parent file failed to end with one.
|
||||
*/
|
||||
XLogFileName(xlogfname, endTLI, endLogSegNo);
|
||||
|
||||
snprintf(buffer, sizeof(buffer),
|
||||
"%s%u\t%s\t%s\n",
|
||||
(srcfd < 0) ? "" : "\n",
|
||||
parentTLI,
|
||||
xlogfname,
|
||||
reason);
|
||||
|
||||
nbytes = strlen(buffer);
|
||||
errno = 0;
|
||||
if ((int) write(fd, buffer, nbytes) != nbytes)
|
||||
{
|
||||
int save_errno = errno;
|
||||
|
||||
/*
|
||||
* If we fail to make the file, delete it to release disk space
|
||||
*/
|
||||
unlink(tmppath);
|
||||
/* if write didn't set errno, assume problem is no disk space */
|
||||
errno = save_errno ? save_errno : ENOSPC;
|
||||
|
||||
ereport(ERROR,
|
||||
(errcode_for_file_access(),
|
||||
errmsg("could not write to file \"%s\": %m", tmppath)));
|
||||
}
|
||||
|
||||
if (pg_fsync(fd) != 0)
|
||||
ereport(ERROR,
|
||||
(errcode_for_file_access(),
|
||||
errmsg("could not fsync file \"%s\": %m", tmppath)));
|
||||
|
||||
if (close(fd))
|
||||
ereport(ERROR,
|
||||
(errcode_for_file_access(),
|
||||
errmsg("could not close file \"%s\": %m", tmppath)));
|
||||
|
||||
|
||||
/*
|
||||
* Now move the completed history file into place with its final name.
|
||||
*/
|
||||
TLHistoryFilePath(path, newTLI);
|
||||
|
||||
/*
|
||||
* Prefer link() to rename() here just to be really sure that we don't
|
||||
* overwrite an existing logfile. However, there shouldn't be one, so
|
||||
* rename() is an acceptable substitute except for the truly paranoid.
|
||||
*/
|
||||
#if HAVE_WORKING_LINK
|
||||
if (link(tmppath, path) < 0)
|
||||
ereport(ERROR,
|
||||
(errcode_for_file_access(),
|
||||
errmsg("could not link file \"%s\" to \"%s\": %m",
|
||||
tmppath, path)));
|
||||
unlink(tmppath);
|
||||
#else
|
||||
if (rename(tmppath, path) < 0)
|
||||
ereport(ERROR,
|
||||
(errcode_for_file_access(),
|
||||
errmsg("could not rename file \"%s\" to \"%s\": %m",
|
||||
tmppath, path)));
|
||||
#endif
|
||||
}
|
||||
Reference in New Issue
Block a user