From 969774614d7759436672ecf7fe2b1e2ac0f85dfd Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@otacoo.com>
Date: Thu, 17 Mar 2016 23:01:47 +0900
Subject: [PATCH 3/3] Avoid potential data loss in pg_receivexlog

pg_receivexlog makes use of rename() for timeline history files as well
as for completed WAL segments. However, this is not reliable and may cause
the rename operation to be lost in case of crashes. This commit makes use
of a similar function to backend's durable_name to make the renaming operation
durable on disk.
---
 src/bin/pg_basebackup/receivelog.c | 93 +++++++++++++++++++++++++++++++++++---
 1 file changed, 87 insertions(+), 6 deletions(-)

diff --git a/src/bin/pg_basebackup/receivelog.c b/src/bin/pg_basebackup/receivelog.c
index 595213f..cf9af83 100644
--- a/src/bin/pg_basebackup/receivelog.c
+++ b/src/bin/pg_basebackup/receivelog.c
@@ -50,6 +50,7 @@ static long CalculateCopyStreamSleeptime(int64 now, int standby_message_timeout,
 
 static bool ReadEndOfStreamingResult(PGresult *res, XLogRecPtr *startpos,
 						 uint32 *timeline);
+static int	durable_rename(const char *oldfile, const char *newfile);
 
 static bool
 mark_file_as_archived(const char *basedir, const char *fname)
@@ -217,10 +218,9 @@ close_walfile(StreamCtl *stream, XLogRecPtr pos)
 
 		snprintf(oldfn, sizeof(oldfn), "%s/%s%s", stream->basedir, current_walfile_name, stream->partial_suffix);
 		snprintf(newfn, sizeof(newfn), "%s/%s", stream->basedir, current_walfile_name);
-		if (rename(oldfn, newfn) != 0)
+		if (durable_rename(oldfn, newfn) != 0)
 		{
-			fprintf(stderr, _("%s: could not rename file \"%s\": %s\n"),
-					progname, current_walfile_name, strerror(errno));
+			/* durable_rename produced a log entry */
 			return false;
 		}
 	}
@@ -356,10 +356,9 @@ writeTimeLineHistoryFile(StreamCtl *stream, char *filename, char *content)
 	/*
 	 * Now move the completed history file into place with its final name.
 	 */
-	if (rename(tmppath, path) < 0)
+	if (durable_rename(tmppath, path) < 0)
 	{
-		fprintf(stderr, _("%s: could not rename file \"%s\" to \"%s\": %s\n"),
-				progname, tmppath, path, strerror(errno));
+		/* durable_rename produced a log entry */
 		return false;
 	}
 
@@ -786,6 +785,88 @@ ReadEndOfStreamingResult(PGresult *res, XLogRecPtr *startpos, uint32 *timeline)
 }
 
 /*
+ * Wrapper of rename() similar to the backend version with the same function
+ * name aimed at making the renaming durable on disk. Note that this version
+ * does not fsync the old file before the rename as all the code paths leading
+ * to this function are already doing this operation. The new file is also
+ * normally not present on disk before the renaming so there is no need to
+ * bother about it.
+ */
+static int
+durable_rename(const char *oldfile, const char *newfile)
+{
+	int		fd;
+	char	parentpath[MAXPGPATH];
+
+	if (rename(oldfile, newfile) != 0)
+	{
+		/* durable_rename produced a log entry */
+		fprintf(stderr, _("%s: could not rename file \"%s\": %s\n"),
+				progname, current_walfile_name, strerror(errno));
+		return -1;
+	}
+
+	/*
+	 * To guarantee renaming of the file is persistent, fsync the file with its
+	 * new name, and its containing directory.
+	 */
+	fd = open(newfile, O_RDWR | PG_BINARY, 0);
+	if (fd < 0)
+	{
+		fprintf(stderr, _("%s: could not open file \"%s\": %s\n"),
+				progname, newfile, strerror(errno));
+		return -1;
+	}
+
+	if (fsync(fd) != 0)
+	{
+		close(fd);
+		fprintf(stderr, _("%s: could not fsync file \"%s\": %s\n"),
+				progname, newfile, strerror(errno));
+		return -1;
+	}
+	close(fd);
+
+	strlcpy(parentpath, newfile, MAXPGPATH);
+	get_parent_directory(parentpath);
+
+	/*
+	 * get_parent_directory() returns an empty string if the input argument is
+	 * just a file name (see comments in path.c), so handle that as being the
+	 * current directory.
+	 */
+	if (strlen(parentpath) == 0)
+		strlcpy(parentpath, ".", MAXPGPATH);
+
+	fd = open(parentpath, O_RDONLY | PG_BINARY, 0);
+
+	/*
+	 * Some OSs don't allow us to open directories at all (Windows returns
+	 * EACCES), just ignore the error in that case.  If desired also silently
+	 * ignoring errors about unreadable files. Log others.
+	 */
+	if (fd < 0 && (errno == EISDIR || errno == EACCES))
+		return 0;
+	else if (fd < 0)
+	{
+		fprintf(stderr, _("%s: could not open file \"%s\": %s\n"),
+				progname, parentpath, strerror(errno));
+		return -1;
+	}
+
+	if (fsync(fd) != 0)
+	{
+		close(fd);
+		fprintf(stderr, _("%s: could not fsync file \"%s\": %s\n"),
+				progname, parentpath, strerror(errno));
+		return -1;
+	}
+	close(fd);
+
+	return 0;
+}
+
+/*
  * The main loop of ReceiveXlogStream. Handles the COPY stream after
  * initiating streaming with the START_STREAMING command.
  *
-- 
2.7.3

