/*------------------------------------------------------------------------- * * 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 ".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 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 #include #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 }