($INBOX_DIR/description missing)  
help / color / mirror / Atom feed
[PATCH v2 2/5] Refactor pg_rewind for more clear decision making.
41+ messages / 2 participants
[nested] [flat]

* [PATCH v2 2/5] Refactor pg_rewind for more clear decision making.
@ 2020-08-19 12:34 Heikki Linnakangas <[email protected]>
  0 siblings, 0 replies; 41+ messages in thread

From: Heikki Linnakangas @ 2020-08-19 12:34 UTC (permalink / raw)

Deciding what to do with each file is now a separate step after all the
necessary information has been gathered. It is more clear that way.
Previously, the decision-making was divided between process_source_file()
and process_target_file(), and it was a bit hard to piece together what the
overall rules were.
---
 src/bin/pg_rewind/copy_fetch.c  |  14 +-
 src/bin/pg_rewind/file_ops.c    |  16 +-
 src/bin/pg_rewind/filemap.c     | 569 ++++++++++++++++----------------
 src/bin/pg_rewind/filemap.h     |  67 ++--
 src/bin/pg_rewind/libpq_fetch.c |  12 +-
 src/bin/pg_rewind/parsexlog.c   |   2 +-
 src/bin/pg_rewind/pg_rewind.c   |   8 +-
 7 files changed, 371 insertions(+), 317 deletions(-)

diff --git a/src/bin/pg_rewind/copy_fetch.c b/src/bin/pg_rewind/copy_fetch.c
index 1edab5f1867..18fad32600e 100644
--- a/src/bin/pg_rewind/copy_fetch.c
+++ b/src/bin/pg_rewind/copy_fetch.c
@@ -210,7 +210,7 @@ copy_executeFileMap(filemap_t *map)
 	for (i = 0; i < map->narray; i++)
 	{
 		entry = map->array[i];
-		execute_pagemap(&entry->pagemap, entry->path);
+		execute_pagemap(&entry->target_modified_pages, entry->path);
 
 		switch (entry->action)
 		{
@@ -219,16 +219,16 @@ copy_executeFileMap(filemap_t *map)
 				break;
 
 			case FILE_ACTION_COPY:
-				rewind_copy_file_range(entry->path, 0, entry->newsize, true);
+				rewind_copy_file_range(entry->path, 0, entry->source_size, true);
 				break;
 
 			case FILE_ACTION_TRUNCATE:
-				truncate_target_file(entry->path, entry->newsize);
+				truncate_target_file(entry->path, entry->source_size);
 				break;
 
 			case FILE_ACTION_COPY_TAIL:
-				rewind_copy_file_range(entry->path, entry->oldsize,
-									   entry->newsize, false);
+				rewind_copy_file_range(entry->path, entry->target_size,
+									   entry->source_size, false);
 				break;
 
 			case FILE_ACTION_CREATE:
@@ -238,6 +238,10 @@ copy_executeFileMap(filemap_t *map)
 			case FILE_ACTION_REMOVE:
 				remove_target(entry);
 				break;
+
+			case FILE_ACTION_UNDECIDED:
+				pg_fatal("no action decided for \"%s\"", entry->path);
+				break;
 		}
 	}
 
diff --git a/src/bin/pg_rewind/file_ops.c b/src/bin/pg_rewind/file_ops.c
index 55439db20ba..ec37d0b2e0d 100644
--- a/src/bin/pg_rewind/file_ops.c
+++ b/src/bin/pg_rewind/file_ops.c
@@ -126,8 +126,9 @@ void
 remove_target(file_entry_t *entry)
 {
 	Assert(entry->action == FILE_ACTION_REMOVE);
+	Assert(entry->target_exists);
 
-	switch (entry->type)
+	switch (entry->target_type)
 	{
 		case FILE_TYPE_DIRECTORY:
 			remove_target_dir(entry->path);
@@ -140,6 +141,10 @@ remove_target(file_entry_t *entry)
 		case FILE_TYPE_SYMLINK:
 			remove_target_symlink(entry->path);
 			break;
+
+		case FILE_TYPE_UNDEFINED:
+			pg_fatal("undefined file type for \"%s\"", entry->path);
+			break;
 	}
 }
 
@@ -147,21 +152,26 @@ void
 create_target(file_entry_t *entry)
 {
 	Assert(entry->action == FILE_ACTION_CREATE);
+	Assert(!entry->target_exists);
 
-	switch (entry->type)
+	switch (entry->source_type)
 	{
 		case FILE_TYPE_DIRECTORY:
 			create_target_dir(entry->path);
 			break;
 
 		case FILE_TYPE_SYMLINK:
-			create_target_symlink(entry->path, entry->link_target);
+			create_target_symlink(entry->path, entry->source_link_target);
 			break;
 
 		case FILE_TYPE_REGULAR:
 			/* can't happen. Regular files are created with open_target_file. */
 			pg_fatal("invalid action (CREATE) for regular file");
 			break;
+
+		case FILE_TYPE_UNDEFINED:
+			pg_fatal("undefined file type for \"%s\"", entry->path);
+			break;
 	}
 }
 
diff --git a/src/bin/pg_rewind/filemap.c b/src/bin/pg_rewind/filemap.c
index 1879257b66a..7971daeda5e 100644
--- a/src/bin/pg_rewind/filemap.c
+++ b/src/bin/pg_rewind/filemap.c
@@ -26,6 +26,8 @@ static bool isRelDataFile(const char *path);
 static char *datasegpath(RelFileNode rnode, ForkNumber forknum,
 						 BlockNumber segno);
 static int	path_cmp(const void *a, const void *b);
+
+static file_entry_t *get_filemap_entry(const char *path, bool create);
 static int	final_filemap_cmp(const void *a, const void *b);
 static void filemap_list_to_array(filemap_t *map);
 static bool check_file_excluded(const char *path, bool is_source);
@@ -146,6 +148,65 @@ filemap_create(void)
 	filemap = map;
 }
 
+/* Look up or create entry for 'path' */
+static file_entry_t *
+get_filemap_entry(const char *path, bool create)
+{
+	filemap_t  *map = filemap;
+	file_entry_t *entry;
+	file_entry_t **e;
+	file_entry_t key;
+	file_entry_t *key_ptr;
+
+	if (map->array)
+	{
+		key.path = (char *) path;
+		key_ptr = &key;
+		e = bsearch(&key_ptr, map->array, map->narray, sizeof(file_entry_t *),
+					path_cmp);
+	}
+	else
+		e = NULL;
+
+	if (e)
+		entry = *e;
+	else if (!create)
+		entry = NULL;
+	else
+	{
+		/* Create a new entry for this file */
+		entry = pg_malloc(sizeof(file_entry_t));
+		entry->path = pg_strdup(path);
+		entry->isrelfile = isRelDataFile(path);
+		entry->action = FILE_ACTION_UNDECIDED;
+
+		entry->target_exists = false;
+		entry->target_type = FILE_TYPE_UNDEFINED;
+		entry->target_size = 0;
+		entry->target_link_target = NULL;
+		entry->target_modified_pages.bitmap = NULL;
+		entry->target_modified_pages.bitmapsize = 0;
+
+		entry->source_exists = false;
+		entry->source_type = FILE_TYPE_UNDEFINED;
+		entry->source_size = 0;
+		entry->source_link_target = NULL;
+
+		entry->next = NULL;
+
+		if (map->last)
+		{
+			map->last->next = entry;
+			map->last = entry;
+		}
+		else
+			map->first = map->last = entry;
+		map->nlist++;
+	}
+
+	return entry;
+}
+
 /*
  * Callback for processing source file list.
  *
@@ -154,25 +215,12 @@ filemap_create(void)
  * exists in the target and whether the size matches.
  */
 void
-process_source_file(const char *path, file_type_t type, size_t newsize,
+process_source_file(const char *path, file_type_t type, size_t size,
 					const char *link_target)
 {
-	bool		exists;
-	char		localpath[MAXPGPATH];
-	struct stat statbuf;
-	filemap_t  *map = filemap;
-	file_action_t action = FILE_ACTION_NONE;
-	size_t		oldsize = 0;
 	file_entry_t *entry;
 
-	Assert(map->array == NULL);
-
-	/*
-	 * Skip any files matching the exclusion filters. This has the effect to
-	 * remove all those files on the target.
-	 */
-	if (check_file_excluded(path, true))
-		return;
+	Assert(filemap->array == NULL);
 
 	/*
 	 * Pretend that pg_wal is a directory, even if it's really a symlink. We
@@ -182,16 +230,6 @@ process_source_file(const char *path, file_type_t type, size_t newsize,
 	if (strcmp(path, "pg_wal") == 0 && type == FILE_TYPE_SYMLINK)
 		type = FILE_TYPE_DIRECTORY;
 
-	/*
-	 * Skip temporary files, .../pgsql_tmp/... and .../pgsql_tmp.* in source.
-	 * This has the effect that all temporary files in the destination will be
-	 * removed.
-	 */
-	if (strstr(path, "/" PG_TEMP_FILE_PREFIX) != NULL)
-		return;
-	if (strstr(path, "/" PG_TEMP_FILES_DIR "/") != NULL)
-		return;
-
 	/*
 	 * sanity check: a filename that looks like a data file better be a
 	 * regular file
@@ -199,142 +237,12 @@ process_source_file(const char *path, file_type_t type, size_t newsize,
 	if (type != FILE_TYPE_REGULAR && isRelDataFile(path))
 		pg_fatal("data file \"%s\" in source is not a regular file", path);
 
-	snprintf(localpath, sizeof(localpath), "%s/%s", datadir_target, path);
-
-	/* Does the corresponding file exist in the target data dir? */
-	if (lstat(localpath, &statbuf) < 0)
-	{
-		if (errno != ENOENT)
-			pg_fatal("could not stat file \"%s\": %m",
-					 localpath);
-
-		exists = false;
-	}
-	else
-		exists = true;
-
-	switch (type)
-	{
-		case FILE_TYPE_DIRECTORY:
-			if (exists && !S_ISDIR(statbuf.st_mode) && strcmp(path, "pg_wal") != 0)
-			{
-				/* it's a directory in source, but not in target. Strange.. */
-				pg_fatal("\"%s\" is not a directory", localpath);
-			}
-
-			if (!exists)
-				action = FILE_ACTION_CREATE;
-			else
-				action = FILE_ACTION_NONE;
-			oldsize = 0;
-			break;
-
-		case FILE_TYPE_SYMLINK:
-			if (exists &&
-#ifndef WIN32
-				!S_ISLNK(statbuf.st_mode)
-#else
-				!pgwin32_is_junction(localpath)
-#endif
-				)
-			{
-				/*
-				 * It's a symbolic link in source, but not in target.
-				 * Strange..
-				 */
-				pg_fatal("\"%s\" is not a symbolic link", localpath);
-			}
-
-			if (!exists)
-				action = FILE_ACTION_CREATE;
-			else
-				action = FILE_ACTION_NONE;
-			oldsize = 0;
-			break;
-
-		case FILE_TYPE_REGULAR:
-			if (exists && !S_ISREG(statbuf.st_mode))
-				pg_fatal("\"%s\" is not a regular file", localpath);
-
-			if (!exists || !isRelDataFile(path))
-			{
-				/*
-				 * File exists in source, but not in target. Or it's a
-				 * non-data file that we have no special processing for. Copy
-				 * it in toto.
-				 *
-				 * An exception: PG_VERSIONs should be identical, but avoid
-				 * overwriting it for paranoia.
-				 */
-				if (pg_str_endswith(path, "PG_VERSION"))
-				{
-					action = FILE_ACTION_NONE;
-					oldsize = statbuf.st_size;
-				}
-				else
-				{
-					action = FILE_ACTION_COPY;
-					oldsize = 0;
-				}
-			}
-			else
-			{
-				/*
-				 * It's a data file that exists in both.
-				 *
-				 * If it's larger in target, we can truncate it. There will
-				 * also be a WAL record of the truncation in the source
-				 * system, so WAL replay would eventually truncate the target
-				 * too, but we might as well do it now.
-				 *
-				 * If it's smaller in the target, it means that it has been
-				 * truncated in the target, or enlarged in the source, or
-				 * both. If it was truncated in the target, we need to copy
-				 * the missing tail from the source system. If it was enlarged
-				 * in the source system, there will be WAL records in the
-				 * source system for the new blocks, so we wouldn't need to
-				 * copy them here. But we don't know which scenario we're
-				 * dealing with, and there's no harm in copying the missing
-				 * blocks now, so do it now.
-				 *
-				 * If it's the same size, do nothing here. Any blocks modified
-				 * in the target will be copied based on parsing the target
-				 * system's WAL, and any blocks modified in the source will be
-				 * updated after rewinding, when the source system's WAL is
-				 * replayed.
-				 */
-				oldsize = statbuf.st_size;
-				if (oldsize < newsize)
-					action = FILE_ACTION_COPY_TAIL;
-				else if (oldsize > newsize)
-					action = FILE_ACTION_TRUNCATE;
-				else
-					action = FILE_ACTION_NONE;
-			}
-			break;
-	}
-
-	/* Create a new entry for this file */
-	entry = pg_malloc(sizeof(file_entry_t));
-	entry->path = pg_strdup(path);
-	entry->type = type;
-	entry->action = action;
-	entry->oldsize = oldsize;
-	entry->newsize = newsize;
-	entry->link_target = link_target ? pg_strdup(link_target) : NULL;
-	entry->next = NULL;
-	entry->pagemap.bitmap = NULL;
-	entry->pagemap.bitmapsize = 0;
-	entry->isrelfile = isRelDataFile(path);
-
-	if (map->last)
-	{
-		map->last->next = entry;
-		map->last = entry;
-	}
-	else
-		map->first = map->last = entry;
-	map->nlist++;
+	/* Remember this source file */
+	entry = get_filemap_entry(path, true);
+	entry->source_exists = true;
+	entry->source_type = type;
+	entry->source_size = size;
+	entry->source_link_target = link_target ? pg_strdup(link_target) : NULL;
 }
 
 /*
@@ -345,14 +253,9 @@ process_source_file(const char *path, file_type_t type, size_t newsize,
  * deletion.
  */
 void
-process_target_file(const char *path, file_type_t type, size_t oldsize,
+process_target_file(const char *path, file_type_t type, size_t size,
 					const char *link_target)
 {
-	bool		exists;
-	char		localpath[MAXPGPATH];
-	struct stat statbuf;
-	file_entry_t key;
-	file_entry_t *key_ptr;
 	filemap_t  *map = filemap;
 	file_entry_t *entry;
 
@@ -361,17 +264,6 @@ process_target_file(const char *path, file_type_t type, size_t oldsize,
 	 * from the target data folder all paths which have been filtered out from
 	 * the source data folder when processing the source files.
 	 */
-
-	snprintf(localpath, sizeof(localpath), "%s/%s", datadir_target, path);
-	if (lstat(localpath, &statbuf) < 0)
-	{
-		if (errno != ENOENT)
-			pg_fatal("could not stat file \"%s\": %m",
-					 localpath);
-
-		exists = false;
-	}
-
 	if (map->array == NULL)
 	{
 		/* on first call, initialize lookup array */
@@ -389,120 +281,76 @@ process_target_file(const char *path, file_type_t type, size_t oldsize,
 	}
 
 	/*
-	 * Like in process_source_file, pretend that xlog is always a  directory.
+	 * Like in process_source_file, pretend that pg_wal is always a directory.
 	 */
 	if (strcmp(path, "pg_wal") == 0 && type == FILE_TYPE_SYMLINK)
 		type = FILE_TYPE_DIRECTORY;
 
-	key.path = (char *) path;
-	key_ptr = &key;
-	exists = (bsearch(&key_ptr, map->array, map->narray, sizeof(file_entry_t *),
-					  path_cmp) != NULL);
-
-	/* Remove any file or folder that doesn't exist in the source system. */
-	if (!exists)
-	{
-		entry = pg_malloc(sizeof(file_entry_t));
-		entry->path = pg_strdup(path);
-		entry->type = type;
-		entry->action = FILE_ACTION_REMOVE;
-		entry->oldsize = oldsize;
-		entry->newsize = 0;
-		entry->link_target = link_target ? pg_strdup(link_target) : NULL;
-		entry->next = NULL;
-		entry->pagemap.bitmap = NULL;
-		entry->pagemap.bitmapsize = 0;
-		entry->isrelfile = isRelDataFile(path);
-
-		if (map->last == NULL)
-			map->first = entry;
-		else
-			map->last->next = entry;
-		map->last = entry;
-		map->nlist++;
-	}
-	else
-	{
-		/*
-		 * We already handled all files that exist in the source system in
-		 * process_source_file().
-		 */
-	}
+	/* Remember this target file */
+	entry = get_filemap_entry(path, true);
+	entry->target_exists = true;
+	entry->target_type = type;
+	entry->target_size = size;
+	entry->target_link_target = link_target ? pg_strdup(link_target) : NULL;
 }
 
 /*
  * This callback gets called while we read the WAL in the target, for every
- * block that have changed in the target system. It makes note of all the
+ * block that have changed in the target system.  It makes note of all the
  * changed blocks in the pagemap of the file.
+ *
+ * NOTE: All the files on both systems must have already been added to the
+ * file map!
  */
 void
-process_block_change(ForkNumber forknum, RelFileNode rnode, BlockNumber blkno)
+process_target_wal_block_change(ForkNumber forknum, RelFileNode rnode,
+								BlockNumber blkno)
 {
 	char	   *path;
-	file_entry_t key;
-	file_entry_t *key_ptr;
 	file_entry_t *entry;
 	BlockNumber blkno_inseg;
 	int			segno;
-	filemap_t  *map = filemap;
-	file_entry_t **e;
 
-	Assert(map->array);
+	Assert(filemap->array);
 
 	segno = blkno / RELSEG_SIZE;
 	blkno_inseg = blkno % RELSEG_SIZE;
 
 	path = datasegpath(rnode, forknum, segno);
-
-	key.path = (char *) path;
-	key_ptr = &key;
-
-	e = bsearch(&key_ptr, map->array, map->narray, sizeof(file_entry_t *),
-				path_cmp);
-	if (e)
-		entry = *e;
-	else
-		entry = NULL;
+	entry = get_filemap_entry(path, false);
 	pfree(path);
 
 	if (entry)
 	{
-		Assert(entry->isrelfile);
-
-		switch (entry->action)
-		{
-			case FILE_ACTION_NONE:
-			case FILE_ACTION_TRUNCATE:
-				/* skip if we're truncating away the modified block anyway */
-				if ((blkno_inseg + 1) * BLCKSZ <= entry->newsize)
-					datapagemap_add(&entry->pagemap, blkno_inseg);
-				break;
+		int64		end_offset;
 
-			case FILE_ACTION_COPY_TAIL:
-
-				/*
-				 * skip the modified block if it is part of the "tail" that
-				 * we're copying anyway.
-				 */
-				if ((blkno_inseg + 1) * BLCKSZ <= entry->oldsize)
-					datapagemap_add(&entry->pagemap, blkno_inseg);
-				break;
+		Assert(entry->isrelfile);
 
-			case FILE_ACTION_COPY:
-			case FILE_ACTION_REMOVE:
-				break;
+		if (entry->target_type != FILE_TYPE_REGULAR)
+			pg_fatal("unexpected page modification for directory or symbolic link \"%s\"",
+					 entry->path);
 
-			case FILE_ACTION_CREATE:
-				pg_fatal("unexpected page modification for directory or symbolic link \"%s\"", entry->path);
-		}
+		/*
+		 * If the block beyond the EOF in the source system, no need to
+		 * remember it now, because we're going to truncate it away from the
+		 * target anyway. Also no need to remember the block if it's beyond
+		 * the current EOF in the target system; we will copy it over with the
+		 * "tail" from the source system, anyway.
+		 */
+		end_offset = (blkno_inseg + 1) * BLCKSZ;
+		if (end_offset <= entry->source_size &&
+			end_offset <= entry->target_size)
+			datapagemap_add(&entry->target_modified_pages, blkno_inseg);
 	}
 	else
 	{
 		/*
 		 * If we don't have any record of this file in the file map, it means
-		 * that it's a relation that doesn't exist in the source system, and
-		 * it was subsequently removed in the target system, too. We can
-		 * safely ignore it.
+		 * that it's a relation that doesn't exist in the source system.  It
+		 * could exist in the target system; we haven't moved the target-only
+		 * entries from the linked list to the array yet!  But in any case, if
+		 * it doesn't exist in the source it will be removed from the target
+		 * too, and we can safely ignore it.
 		 */
 	}
 }
@@ -593,16 +441,6 @@ filemap_list_to_array(filemap_t *map)
 	map->first = map->last = NULL;
 }
 
-void
-filemap_finalize(void)
-{
-	filemap_t  *map = filemap;
-
-	filemap_list_to_array(map);
-	qsort(map->array, map->narray, sizeof(file_entry_t *),
-		  final_filemap_cmp);
-}
-
 static const char *
 action_to_str(file_action_t action)
 {
@@ -643,26 +481,26 @@ calculate_totals(void)
 	{
 		entry = map->array[i];
 
-		if (entry->type != FILE_TYPE_REGULAR)
+		if (entry->source_type != FILE_TYPE_REGULAR)
 			continue;
 
-		map->total_size += entry->newsize;
+		map->total_size += entry->source_size;
 
 		if (entry->action == FILE_ACTION_COPY)
 		{
-			map->fetch_size += entry->newsize;
+			map->fetch_size += entry->source_size;
 			continue;
 		}
 
 		if (entry->action == FILE_ACTION_COPY_TAIL)
-			map->fetch_size += (entry->newsize - entry->oldsize);
+			map->fetch_size += (entry->source_size - entry->target_size);
 
-		if (entry->pagemap.bitmapsize > 0)
+		if (entry->target_modified_pages.bitmapsize > 0)
 		{
 			datapagemap_iterator_t *iter;
 			BlockNumber blk;
 
-			iter = datapagemap_iterate(&entry->pagemap);
+			iter = datapagemap_iterate(&entry->target_modified_pages);
 			while (datapagemap_next(iter, &blk))
 				map->fetch_size += BLCKSZ;
 
@@ -682,13 +520,13 @@ print_filemap(void)
 	{
 		entry = map->array[i];
 		if (entry->action != FILE_ACTION_NONE ||
-			entry->pagemap.bitmapsize > 0)
+			entry->target_modified_pages.bitmapsize > 0)
 		{
 			pg_log_debug("%s (%s)", entry->path,
 						 action_to_str(entry->action));
 
-			if (entry->pagemap.bitmapsize > 0)
-				datapagemap_print(&entry->pagemap);
+			if (entry->target_modified_pages.bitmapsize > 0)
+				datapagemap_print(&entry->target_modified_pages);
 		}
 	}
 	fflush(stdout);
@@ -837,3 +675,180 @@ final_filemap_cmp(const void *a, const void *b)
 	else
 		return strcmp(fa->path, fb->path);
 }
+
+/*
+ * Decide what action to perform to a file.
+ */
+static file_action_t
+decide_file_action(file_entry_t *entry)
+{
+	const char *path = entry->path;
+
+	/*
+	 * Don't touch the control file. It is handled specially, after copying
+	 * all the other files.
+	 */
+	if (strcmp(path, "global/pg_control") == 0)
+		return FILE_ACTION_NONE;
+
+	/*
+	 * Remove all files matching the exclusion filters in the target.
+	 */
+	if (check_file_excluded(path, true))
+	{
+		if (entry->target_exists)
+			return FILE_ACTION_REMOVE;
+		else
+			return FILE_ACTION_NONE;
+	}
+
+	/*
+	 * Also remove all temporary files, .../pgsql_tmp/... and .../pgsql_tmp.*
+	 * in the target.
+	 */
+	if (strstr(path, "/" PG_TEMP_FILE_PREFIX) != NULL ||
+		strstr(path, "/" PG_TEMP_FILES_DIR "/") != NULL)
+	{
+		if (entry->target_exists)
+			return FILE_ACTION_REMOVE;
+		else
+			return FILE_ACTION_NONE;
+	}
+
+	/*
+	 * Handle cases where the file is missing from one of the systems.
+	 */
+	if (!entry->target_exists && entry->source_exists)
+	{
+		/*
+		 * File exists in source, but not in target. Copy it in toto. (If it's
+		 * a relation data file, WAL replay after rewinding should re-create
+		 * it anyway. But there's no harm in copying it now.)
+		 */
+		switch (entry->source_type)
+		{
+			case FILE_TYPE_DIRECTORY:
+			case FILE_TYPE_SYMLINK:
+				return FILE_ACTION_CREATE;
+			case FILE_TYPE_REGULAR:
+				return FILE_ACTION_COPY;
+			case FILE_TYPE_UNDEFINED:
+				pg_fatal("unknown file type for \"%s\"", entry->path);
+				break;
+		}
+	}
+	else if (entry->target_exists && !entry->source_exists)
+	{
+		/* File exists in target, but not source. Remove it. */
+		return FILE_ACTION_REMOVE;
+	}
+	else if (!entry->target_exists && !entry->source_exists)
+	{
+		/*
+		 * Doesn't exist in either server. Why does it have an entry in the
+		 * first place??
+		 */
+		return FILE_ACTION_NONE;
+	}
+
+	/*
+	 * Otherwise, the file exists on both systems
+	 */
+	Assert(entry->target_exists && entry->source_exists);
+
+	if (entry->source_type != entry->target_type)
+	{
+		/* But it's a different kind of object. Strange.. */
+		pg_fatal("file \"%s\" is of different type in source and target", entry->path);
+	}
+
+	/*
+	 * PG_VERSION files should be identical on both systems, but avoid
+	 * overwriting them for paranoia.
+	 */
+	if (pg_str_endswith(entry->path, "PG_VERSION"))
+		return FILE_ACTION_NONE;
+
+	switch (entry->source_type)
+	{
+		case FILE_TYPE_DIRECTORY:
+			return FILE_ACTION_NONE;
+
+		case FILE_TYPE_SYMLINK:
+			/* FIXME: Check if it points to the same target? */
+			return FILE_ACTION_NONE;
+
+		case FILE_TYPE_REGULAR:
+			if (!entry->isrelfile)
+			{
+				/*
+				 * It's a non-data file that we have no special processing
+				 * for. Copy it in toto.
+				 */
+				return FILE_ACTION_COPY;
+			}
+			else
+			{
+				/*
+				 * It's a data file that exists in both systems.
+				 *
+				 * If it's larger in target, we can truncate it. There will
+				 * also be a WAL record of the truncation in the source
+				 * system, so WAL replay would eventually truncate the target
+				 * too, but we might as well do it now.
+				 *
+				 * If it's smaller in the target, it means that it has been
+				 * truncated in the target, or enlarged in the source, or
+				 * both. If it was truncated in the target, we need to copy
+				 * the missing tail from the source system. If it was enlarged
+				 * in the source system, there will be WAL records in the
+				 * source system for the new blocks, so we wouldn't need to
+				 * copy them here. But we don't know which scenario we're
+				 * dealing with, and there's no harm in copying the missing
+				 * blocks now, so do it now.
+				 *
+				 * If it's the same size, do nothing here. Any blocks modified
+				 * in the target will be copied based on parsing the target
+				 * system's WAL, and any blocks modified in the source will be
+				 * updated after rewinding, when the source system's WAL is
+				 * replayed.
+				 */
+				if (entry->target_size < entry->source_size)
+					return FILE_ACTION_COPY_TAIL;
+				else if (entry->target_size > entry->source_size)
+					return FILE_ACTION_TRUNCATE;
+				else
+					return FILE_ACTION_NONE;
+			}
+			break;
+
+		case FILE_TYPE_UNDEFINED:
+			pg_fatal("unknown file type for \"%s\"", path);
+			break;
+	}
+
+	/* unreachable */
+	pg_fatal("could not decide what to do with file \"%s\"", path);
+}
+
+/*
+ * Decide what to do with each file.
+ */
+void
+filemap_finalize()
+{
+	int			i;
+
+	filemap_list_to_array(filemap);
+
+	for (i = 0; i < filemap->narray; i++)
+	{
+		file_entry_t *entry = filemap->array[i];
+
+		entry->action = decide_file_action(entry);
+	}
+
+	/* Sort the actions to the order that they should be performed */
+	qsort(filemap->array, filemap->narray, sizeof(file_entry_t *),
+		  final_filemap_cmp);
+}
diff --git a/src/bin/pg_rewind/filemap.h b/src/bin/pg_rewind/filemap.h
index 0cb7425170c..a5e8df57f40 100644
--- a/src/bin/pg_rewind/filemap.h
+++ b/src/bin/pg_rewind/filemap.h
@@ -14,17 +14,21 @@
 
 /*
  * For every file found in the local or remote system, we have a file entry
- * which says what we are going to do with the file. For relation files,
- * there is also a page map, marking pages in the file that were changed
- * locally.
- *
- * The enum values are sorted in the order we want actions to be processed.
+ * that contains information about the file on both systems.  For relation
+ * files, there is also a page map that marks pages in the file that were
+ * changed in the target after the last common checkpoint.  Each entry also
+ * contains an 'action' field, which says what we are going to do with the
+ * file.
  */
+
+/* these enum values are sorted in the order we want actions to be processed */
 typedef enum
 {
+	FILE_ACTION_UNDECIDED = 0,	/* not decided yet */
+
 	FILE_ACTION_CREATE,			/* create local directory or symbolic link */
 	FILE_ACTION_COPY,			/* copy whole file, overwriting if exists */
-	FILE_ACTION_COPY_TAIL,		/* copy tail from 'oldsize' to 'newsize' */
+	FILE_ACTION_COPY_TAIL,		/* copy tail from 'source_size' to 'target_size' */
 	FILE_ACTION_NONE,			/* no action (we might still copy modified
 								 * blocks based on the parsed WAL) */
 	FILE_ACTION_TRUNCATE,		/* truncate local file to 'newsize' bytes */
@@ -33,6 +37,8 @@ typedef enum
 
 typedef enum
 {
+	FILE_TYPE_UNDEFINED = 0,
+
 	FILE_TYPE_REGULAR,
 	FILE_TYPE_DIRECTORY,
 	FILE_TYPE_SYMLINK
@@ -41,19 +47,30 @@ typedef enum
 typedef struct file_entry_t
 {
 	char	   *path;
-	file_type_t type;
+	bool		isrelfile;		/* is it a relation data file? */
 
-	file_action_t action;
+	/*
+	 * Status of the file in the target.
+	 */
+	bool		target_exists;
+	file_type_t target_type;
+	size_t		target_size; /* for a regular file */
+	char	   *target_link_target; /* for a symlink */
 
-	/* for a regular file */
-	size_t		oldsize;
-	size_t		newsize;
-	bool		isrelfile;		/* is it a relation data file? */
+	datapagemap_t target_modified_pages;
 
-	datapagemap_t pagemap;
+	/*
+	 * Status of the file in the source.
+	 */
+	bool		source_exists;
+	file_type_t source_type;
+	size_t		source_size;
+	char	   *source_link_target; /* for a symlink */
 
-	/* for a symlink */
-	char	   *link_target;
+	/*
+	 * What will we do to the file?
+	 */
+	file_action_t action;
 
 	struct file_entry_t *next;
 } file_entry_t;
@@ -70,20 +87,19 @@ typedef struct filemap_t
 
 	/*
 	 * After processing all the remote files, the entries in the linked list
-	 * are moved to this array. After processing local files, too, all the
+	 * are moved to this array.  After processing local files, too, all the
 	 * local entries are added to the array by filemap_finalize, and sorted in
-	 * the final order. After filemap_finalize, all the entries are in the
+	 * the final order.  After filemap_finalize, all the entries are in the
 	 * array, and the linked list is empty.
 	 */
 	file_entry_t **array;
 	int			narray;			/* current length of array */
 
 	/*
-	 * Summary information. total_size is the total size of the source
-	 * cluster, and fetch_size is the number of bytes that needs to be copied.
+	 * Summary information.
 	 */
-	uint64		total_size;
-	uint64		fetch_size;
+	uint64		total_size;		/* total size of the source cluster */
+	uint64		fetch_size;		/* number of bytes that needs to be copied */
 } filemap_t;
 
 extern filemap_t *filemap;
@@ -94,11 +110,12 @@ extern void print_filemap(void);
 
 /* Functions for populating the filemap */
 extern void process_source_file(const char *path, file_type_t type,
-								size_t newsize, const char *link_target);
+								size_t size, const char *link_target);
 extern void process_target_file(const char *path, file_type_t type,
-								size_t newsize, const char *link_target);
-extern void process_block_change(ForkNumber forknum, RelFileNode rnode,
-								 BlockNumber blkno);
+								size_t size, const char *link_target);
+extern void process_target_wal_block_change(ForkNumber forknum,
+											RelFileNode rnode,
+											BlockNumber blkno);
 extern void filemap_finalize(void);
 
 #endif							/* FILEMAP_H */
diff --git a/src/bin/pg_rewind/libpq_fetch.c b/src/bin/pg_rewind/libpq_fetch.c
index bf4dfc23b96..7fc9161b8c8 100644
--- a/src/bin/pg_rewind/libpq_fetch.c
+++ b/src/bin/pg_rewind/libpq_fetch.c
@@ -465,7 +465,7 @@ libpq_executeFileMap(filemap_t *map)
 		entry = map->array[i];
 
 		/* If this is a relation file, copy the modified blocks */
-		execute_pagemap(&entry->pagemap, entry->path);
+		execute_pagemap(&entry->target_modified_pages, entry->path);
 
 		switch (entry->action)
 		{
@@ -476,15 +476,15 @@ libpq_executeFileMap(filemap_t *map)
 			case FILE_ACTION_COPY:
 				/* Truncate the old file out of the way, if any */
 				open_target_file(entry->path, true);
-				fetch_file_range(entry->path, 0, entry->newsize);
+				fetch_file_range(entry->path, 0, entry->source_size);
 				break;
 
 			case FILE_ACTION_TRUNCATE:
-				truncate_target_file(entry->path, entry->newsize);
+				truncate_target_file(entry->path, entry->source_size);
 				break;
 
 			case FILE_ACTION_COPY_TAIL:
-				fetch_file_range(entry->path, entry->oldsize, entry->newsize);
+				fetch_file_range(entry->path, entry->target_size, entry->source_size);
 				break;
 
 			case FILE_ACTION_REMOVE:
@@ -494,6 +494,10 @@ libpq_executeFileMap(filemap_t *map)
 			case FILE_ACTION_CREATE:
 				create_target(entry);
 				break;
+
+			case FILE_ACTION_UNDECIDED:
+				pg_fatal("no action decided for \"%s\"", entry->path);
+				break;
 		}
 	}
 
diff --git a/src/bin/pg_rewind/parsexlog.c b/src/bin/pg_rewind/parsexlog.c
index 2229c86f9af..2baeb74ae93 100644
--- a/src/bin/pg_rewind/parsexlog.c
+++ b/src/bin/pg_rewind/parsexlog.c
@@ -436,6 +436,6 @@ extractPageInfo(XLogReaderState *record)
 		if (forknum != MAIN_FORKNUM)
 			continue;
 
-		process_block_change(forknum, rnode, blkno);
+		process_target_wal_block_change(forknum, rnode, blkno);
 	}
 }
diff --git a/src/bin/pg_rewind/pg_rewind.c b/src/bin/pg_rewind/pg_rewind.c
index c9b9e480c0f..210984d302b 100644
--- a/src/bin/pg_rewind/pg_rewind.c
+++ b/src/bin/pg_rewind/pg_rewind.c
@@ -369,7 +369,7 @@ main(int argc, char **argv)
 				chkpttli);
 
 	/*
-	 * Build the filemap, by comparing the source and target data directories.
+	 * Collect information about all files in the target and source systems.
 	 */
 	filemap_create();
 	if (showprogress)
@@ -390,8 +390,12 @@ main(int argc, char **argv)
 		pg_log_info("reading WAL in target");
 	extractPageMap(datadir_target, chkptrec, lastcommontliIndex,
 				   ControlFile_target.checkPoint, restore_command);
-	filemap_finalize();
 
+	/*
+	 * We have collected all information we need from both systems. Decide
+	 * what to do with each file.
+	 */
+	filemap_finalize();
 	if (showprogress)
 		calculate_totals();
 
-- 
2.20.1


--------------BF34D0120055DC3839060F92
Content-Type: text/x-patch; charset=UTF-8;
 name="v2-0003-pg_rewind-Replace-the-hybrid-list-array-data-stru.patch"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
 filename*0="v2-0003-pg_rewind-Replace-the-hybrid-list-array-data-stru.pa";
 filename*1="tch"



^ permalink  raw  reply  [nested|flat] 41+ messages in thread

* [PATCH 2/5] Refactor pg_rewind for more clear decision making.
@ 2020-08-19 12:34 Heikki Linnakangas <[email protected]>
  0 siblings, 0 replies; 41+ messages in thread

From: Heikki Linnakangas @ 2020-08-19 12:34 UTC (permalink / raw)

Deciding what to do with each file is now a separate step after all the
necessary information has been gathered. It is more clear that way.
Previously, the decision-making was divided between process_source_file()
and process_target_file(), and it was a bit hard to piece together what the
overall rules were.
---
 src/bin/pg_rewind/copy_fetch.c  |  14 +-
 src/bin/pg_rewind/file_ops.c    |  16 +-
 src/bin/pg_rewind/filemap.c     | 514 ++++++++++++++++----------------
 src/bin/pg_rewind/filemap.h     |  67 +++--
 src/bin/pg_rewind/libpq_fetch.c |  12 +-
 src/bin/pg_rewind/parsexlog.c   |   2 +-
 src/bin/pg_rewind/pg_rewind.c   |   8 +-
 7 files changed, 332 insertions(+), 301 deletions(-)

diff --git a/src/bin/pg_rewind/copy_fetch.c b/src/bin/pg_rewind/copy_fetch.c
index 1edab5f1867..18fad32600e 100644
--- a/src/bin/pg_rewind/copy_fetch.c
+++ b/src/bin/pg_rewind/copy_fetch.c
@@ -210,7 +210,7 @@ copy_executeFileMap(filemap_t *map)
 	for (i = 0; i < map->narray; i++)
 	{
 		entry = map->array[i];
-		execute_pagemap(&entry->pagemap, entry->path);
+		execute_pagemap(&entry->target_modified_pages, entry->path);
 
 		switch (entry->action)
 		{
@@ -219,16 +219,16 @@ copy_executeFileMap(filemap_t *map)
 				break;
 
 			case FILE_ACTION_COPY:
-				rewind_copy_file_range(entry->path, 0, entry->newsize, true);
+				rewind_copy_file_range(entry->path, 0, entry->source_size, true);
 				break;
 
 			case FILE_ACTION_TRUNCATE:
-				truncate_target_file(entry->path, entry->newsize);
+				truncate_target_file(entry->path, entry->source_size);
 				break;
 
 			case FILE_ACTION_COPY_TAIL:
-				rewind_copy_file_range(entry->path, entry->oldsize,
-									   entry->newsize, false);
+				rewind_copy_file_range(entry->path, entry->target_size,
+									   entry->source_size, false);
 				break;
 
 			case FILE_ACTION_CREATE:
@@ -238,6 +238,10 @@ copy_executeFileMap(filemap_t *map)
 			case FILE_ACTION_REMOVE:
 				remove_target(entry);
 				break;
+
+			case FILE_ACTION_UNDECIDED:
+				pg_fatal("no action decided for \"%s\"", entry->path);
+				break;
 		}
 	}
 
diff --git a/src/bin/pg_rewind/file_ops.c b/src/bin/pg_rewind/file_ops.c
index 55439db20ba..ec37d0b2e0d 100644
--- a/src/bin/pg_rewind/file_ops.c
+++ b/src/bin/pg_rewind/file_ops.c
@@ -126,8 +126,9 @@ void
 remove_target(file_entry_t *entry)
 {
 	Assert(entry->action == FILE_ACTION_REMOVE);
+	Assert(entry->target_exists);
 
-	switch (entry->type)
+	switch (entry->target_type)
 	{
 		case FILE_TYPE_DIRECTORY:
 			remove_target_dir(entry->path);
@@ -140,6 +141,10 @@ remove_target(file_entry_t *entry)
 		case FILE_TYPE_SYMLINK:
 			remove_target_symlink(entry->path);
 			break;
+
+		case FILE_TYPE_UNDEFINED:
+			pg_fatal("undefined file type for \"%s\"", entry->path);
+			break;
 	}
 }
 
@@ -147,21 +152,26 @@ void
 create_target(file_entry_t *entry)
 {
 	Assert(entry->action == FILE_ACTION_CREATE);
+	Assert(!entry->target_exists);
 
-	switch (entry->type)
+	switch (entry->source_type)
 	{
 		case FILE_TYPE_DIRECTORY:
 			create_target_dir(entry->path);
 			break;
 
 		case FILE_TYPE_SYMLINK:
-			create_target_symlink(entry->path, entry->link_target);
+			create_target_symlink(entry->path, entry->source_link_target);
 			break;
 
 		case FILE_TYPE_REGULAR:
 			/* can't happen. Regular files are created with open_target_file. */
 			pg_fatal("invalid action (CREATE) for regular file");
 			break;
+
+		case FILE_TYPE_UNDEFINED:
+			pg_fatal("undefined file type for \"%s\"", entry->path);
+			break;
 	}
 }
 
diff --git a/src/bin/pg_rewind/filemap.c b/src/bin/pg_rewind/filemap.c
index 1879257b66a..431e8e760e0 100644
--- a/src/bin/pg_rewind/filemap.c
+++ b/src/bin/pg_rewind/filemap.c
@@ -26,6 +26,8 @@ static bool isRelDataFile(const char *path);
 static char *datasegpath(RelFileNode rnode, ForkNumber forknum,
 						 BlockNumber segno);
 static int	path_cmp(const void *a, const void *b);
+
+static file_entry_t *get_filemap_entry(const char *path, bool create);
 static int	final_filemap_cmp(const void *a, const void *b);
 static void filemap_list_to_array(filemap_t *map);
 static bool check_file_excluded(const char *path, bool is_source);
@@ -146,6 +148,65 @@ filemap_create(void)
 	filemap = map;
 }
 
+/* Look up or create entry for 'path' */
+static file_entry_t *
+get_filemap_entry(const char *path, bool create)
+{
+	filemap_t  *map = filemap;
+	file_entry_t *entry;
+	file_entry_t **e;
+	file_entry_t key;
+	file_entry_t *key_ptr;
+
+	if (map->array)
+	{
+		key.path = (char *) path;
+		key_ptr = &key;
+		e = bsearch(&key_ptr, map->array, map->narray, sizeof(file_entry_t *),
+					path_cmp);
+	}
+	else
+		e = NULL;
+
+	if (e)
+		entry = *e;
+	else if (!create)
+		entry = NULL;
+	else
+	{
+		/* Create a new entry for this file */
+		entry = pg_malloc(sizeof(file_entry_t));
+		entry->path = pg_strdup(path);
+		entry->isrelfile = isRelDataFile(path);
+		entry->action = FILE_ACTION_UNDECIDED;
+
+		entry->target_exists = false;
+		entry->target_type = FILE_TYPE_UNDEFINED;
+		entry->target_size = 0;
+		entry->target_link_target = NULL;
+		entry->target_modified_pages.bitmap = NULL;
+		entry->target_modified_pages.bitmapsize = 0;
+
+		entry->source_exists = false;
+		entry->source_type = FILE_TYPE_UNDEFINED;
+		entry->source_size = 0;
+		entry->source_link_target = NULL;
+
+		entry->next = NULL;
+
+		if (map->last)
+		{
+			map->last->next = entry;
+			map->last = entry;
+		}
+		else
+			map->first = map->last = entry;
+		map->nlist++;
+	}
+
+	return entry;
+}
+
 /*
  * Callback for processing source file list.
  *
@@ -154,18 +215,19 @@ filemap_create(void)
  * exists in the target and whether the size matches.
  */
 void
-process_source_file(const char *path, file_type_t type, size_t newsize,
+process_source_file(const char *path, file_type_t type, size_t size,
 					const char *link_target)
 {
-	bool		exists;
-	char		localpath[MAXPGPATH];
-	struct stat statbuf;
-	filemap_t  *map = filemap;
-	file_action_t action = FILE_ACTION_NONE;
-	size_t		oldsize = 0;
 	file_entry_t *entry;
 
-	Assert(map->array == NULL);
+	Assert(filemap->array == NULL);
+
+	/*
+	 * Skip the control file. It is handled specially, after copying all the
+	 * other files.
+	 */
+	if (strcmp(path, "global/pg_control") == 0)
+		return;
 
 	/*
 	 * Skip any files matching the exclusion filters. This has the effect to
@@ -199,142 +261,12 @@ process_source_file(const char *path, file_type_t type, size_t newsize,
 	if (type != FILE_TYPE_REGULAR && isRelDataFile(path))
 		pg_fatal("data file \"%s\" in source is not a regular file", path);
 
-	snprintf(localpath, sizeof(localpath), "%s/%s", datadir_target, path);
-
-	/* Does the corresponding file exist in the target data dir? */
-	if (lstat(localpath, &statbuf) < 0)
-	{
-		if (errno != ENOENT)
-			pg_fatal("could not stat file \"%s\": %m",
-					 localpath);
-
-		exists = false;
-	}
-	else
-		exists = true;
-
-	switch (type)
-	{
-		case FILE_TYPE_DIRECTORY:
-			if (exists && !S_ISDIR(statbuf.st_mode) && strcmp(path, "pg_wal") != 0)
-			{
-				/* it's a directory in source, but not in target. Strange.. */
-				pg_fatal("\"%s\" is not a directory", localpath);
-			}
-
-			if (!exists)
-				action = FILE_ACTION_CREATE;
-			else
-				action = FILE_ACTION_NONE;
-			oldsize = 0;
-			break;
-
-		case FILE_TYPE_SYMLINK:
-			if (exists &&
-#ifndef WIN32
-				!S_ISLNK(statbuf.st_mode)
-#else
-				!pgwin32_is_junction(localpath)
-#endif
-				)
-			{
-				/*
-				 * It's a symbolic link in source, but not in target.
-				 * Strange..
-				 */
-				pg_fatal("\"%s\" is not a symbolic link", localpath);
-			}
-
-			if (!exists)
-				action = FILE_ACTION_CREATE;
-			else
-				action = FILE_ACTION_NONE;
-			oldsize = 0;
-			break;
-
-		case FILE_TYPE_REGULAR:
-			if (exists && !S_ISREG(statbuf.st_mode))
-				pg_fatal("\"%s\" is not a regular file", localpath);
-
-			if (!exists || !isRelDataFile(path))
-			{
-				/*
-				 * File exists in source, but not in target. Or it's a
-				 * non-data file that we have no special processing for. Copy
-				 * it in toto.
-				 *
-				 * An exception: PG_VERSIONs should be identical, but avoid
-				 * overwriting it for paranoia.
-				 */
-				if (pg_str_endswith(path, "PG_VERSION"))
-				{
-					action = FILE_ACTION_NONE;
-					oldsize = statbuf.st_size;
-				}
-				else
-				{
-					action = FILE_ACTION_COPY;
-					oldsize = 0;
-				}
-			}
-			else
-			{
-				/*
-				 * It's a data file that exists in both.
-				 *
-				 * If it's larger in target, we can truncate it. There will
-				 * also be a WAL record of the truncation in the source
-				 * system, so WAL replay would eventually truncate the target
-				 * too, but we might as well do it now.
-				 *
-				 * If it's smaller in the target, it means that it has been
-				 * truncated in the target, or enlarged in the source, or
-				 * both. If it was truncated in the target, we need to copy
-				 * the missing tail from the source system. If it was enlarged
-				 * in the source system, there will be WAL records in the
-				 * source system for the new blocks, so we wouldn't need to
-				 * copy them here. But we don't know which scenario we're
-				 * dealing with, and there's no harm in copying the missing
-				 * blocks now, so do it now.
-				 *
-				 * If it's the same size, do nothing here. Any blocks modified
-				 * in the target will be copied based on parsing the target
-				 * system's WAL, and any blocks modified in the source will be
-				 * updated after rewinding, when the source system's WAL is
-				 * replayed.
-				 */
-				oldsize = statbuf.st_size;
-				if (oldsize < newsize)
-					action = FILE_ACTION_COPY_TAIL;
-				else if (oldsize > newsize)
-					action = FILE_ACTION_TRUNCATE;
-				else
-					action = FILE_ACTION_NONE;
-			}
-			break;
-	}
-
-	/* Create a new entry for this file */
-	entry = pg_malloc(sizeof(file_entry_t));
-	entry->path = pg_strdup(path);
-	entry->type = type;
-	entry->action = action;
-	entry->oldsize = oldsize;
-	entry->newsize = newsize;
-	entry->link_target = link_target ? pg_strdup(link_target) : NULL;
-	entry->next = NULL;
-	entry->pagemap.bitmap = NULL;
-	entry->pagemap.bitmapsize = 0;
-	entry->isrelfile = isRelDataFile(path);
-
-	if (map->last)
-	{
-		map->last->next = entry;
-		map->last = entry;
-	}
-	else
-		map->first = map->last = entry;
-	map->nlist++;
+	/* Remember this source file */
+	entry = get_filemap_entry(path, true);
+	entry->source_exists = true;
+	entry->source_type = type;
+	entry->source_size = size;
+	entry->source_link_target = link_target ? pg_strdup(link_target) : NULL;
 }
 
 /*
@@ -345,33 +277,24 @@ process_source_file(const char *path, file_type_t type, size_t newsize,
  * deletion.
  */
 void
-process_target_file(const char *path, file_type_t type, size_t oldsize,
+process_target_file(const char *path, file_type_t type, size_t size,
 					const char *link_target)
 {
-	bool		exists;
-	char		localpath[MAXPGPATH];
-	struct stat statbuf;
-	file_entry_t key;
-	file_entry_t *key_ptr;
 	filemap_t  *map = filemap;
 	file_entry_t *entry;
 
+	/*
+	 * Skip the control file. It is handled specially, after copying all the
+	 * other files.
+	 */
+	if (strcmp(path, "global/pg_control") == 0)
+		return;
+
 	/*
 	 * Do not apply any exclusion filters here.  This has advantage to remove
 	 * from the target data folder all paths which have been filtered out from
 	 * the source data folder when processing the source files.
 	 */
-
-	snprintf(localpath, sizeof(localpath), "%s/%s", datadir_target, path);
-	if (lstat(localpath, &statbuf) < 0)
-	{
-		if (errno != ENOENT)
-			pg_fatal("could not stat file \"%s\": %m",
-					 localpath);
-
-		exists = false;
-	}
-
 	if (map->array == NULL)
 	{
 		/* on first call, initialize lookup array */
@@ -389,120 +312,76 @@ process_target_file(const char *path, file_type_t type, size_t oldsize,
 	}
 
 	/*
-	 * Like in process_source_file, pretend that xlog is always a  directory.
+	 * Like in process_source_file, pretend that pg_wal is always a directory.
 	 */
 	if (strcmp(path, "pg_wal") == 0 && type == FILE_TYPE_SYMLINK)
 		type = FILE_TYPE_DIRECTORY;
 
-	key.path = (char *) path;
-	key_ptr = &key;
-	exists = (bsearch(&key_ptr, map->array, map->narray, sizeof(file_entry_t *),
-					  path_cmp) != NULL);
-
-	/* Remove any file or folder that doesn't exist in the source system. */
-	if (!exists)
-	{
-		entry = pg_malloc(sizeof(file_entry_t));
-		entry->path = pg_strdup(path);
-		entry->type = type;
-		entry->action = FILE_ACTION_REMOVE;
-		entry->oldsize = oldsize;
-		entry->newsize = 0;
-		entry->link_target = link_target ? pg_strdup(link_target) : NULL;
-		entry->next = NULL;
-		entry->pagemap.bitmap = NULL;
-		entry->pagemap.bitmapsize = 0;
-		entry->isrelfile = isRelDataFile(path);
-
-		if (map->last == NULL)
-			map->first = entry;
-		else
-			map->last->next = entry;
-		map->last = entry;
-		map->nlist++;
-	}
-	else
-	{
-		/*
-		 * We already handled all files that exist in the source system in
-		 * process_source_file().
-		 */
-	}
+	/* Remember this target file */
+	entry = get_filemap_entry(path, true);
+	entry->target_exists = true;
+	entry->target_type = type;
+	entry->target_size = size;
+	entry->target_link_target = link_target ? pg_strdup(link_target) : NULL;
 }
 
 /*
  * This callback gets called while we read the WAL in the target, for every
- * block that have changed in the target system. It makes note of all the
+ * block that have changed in the target system.  It makes note of all the
  * changed blocks in the pagemap of the file.
+ *
+ * NOTE: All the files on both systems must have already been added to the
+ * file map!
  */
 void
-process_block_change(ForkNumber forknum, RelFileNode rnode, BlockNumber blkno)
+process_target_wal_block_change(ForkNumber forknum, RelFileNode rnode,
+								BlockNumber blkno)
 {
 	char	   *path;
-	file_entry_t key;
-	file_entry_t *key_ptr;
 	file_entry_t *entry;
 	BlockNumber blkno_inseg;
 	int			segno;
-	filemap_t  *map = filemap;
-	file_entry_t **e;
 
-	Assert(map->array);
+	Assert(filemap->array);
 
 	segno = blkno / RELSEG_SIZE;
 	blkno_inseg = blkno % RELSEG_SIZE;
 
 	path = datasegpath(rnode, forknum, segno);
-
-	key.path = (char *) path;
-	key_ptr = &key;
-
-	e = bsearch(&key_ptr, map->array, map->narray, sizeof(file_entry_t *),
-				path_cmp);
-	if (e)
-		entry = *e;
-	else
-		entry = NULL;
+	entry = get_filemap_entry(path, false);
 	pfree(path);
 
 	if (entry)
 	{
+		int64		end_offset;
+
 		Assert(entry->isrelfile);
 
-		switch (entry->action)
-		{
-			case FILE_ACTION_NONE:
-			case FILE_ACTION_TRUNCATE:
-				/* skip if we're truncating away the modified block anyway */
-				if ((blkno_inseg + 1) * BLCKSZ <= entry->newsize)
-					datapagemap_add(&entry->pagemap, blkno_inseg);
-				break;
-
-			case FILE_ACTION_COPY_TAIL:
-
-				/*
-				 * skip the modified block if it is part of the "tail" that
-				 * we're copying anyway.
-				 */
-				if ((blkno_inseg + 1) * BLCKSZ <= entry->oldsize)
-					datapagemap_add(&entry->pagemap, blkno_inseg);
-				break;
-
-			case FILE_ACTION_COPY:
-			case FILE_ACTION_REMOVE:
-				break;
-
-			case FILE_ACTION_CREATE:
-				pg_fatal("unexpected page modification for directory or symbolic link \"%s\"", entry->path);
-		}
+		if (entry->target_type != FILE_TYPE_REGULAR)
+			pg_fatal("unexpected page modification for directory or symbolic link \"%s\"",
+					 entry->path);
+
+		/*
+		 * If the block beyond the EOF in the source system, no need to
+		 * remember it now, because we're going to truncate it away from the
+		 * target anyway. Also no need to remember the block if it's beyond
+		 * the current EOF in the target system; we will copy it over with the
+		 * "tail" from the source system, anyway.
+		 */
+		end_offset = (blkno_inseg + 1) * BLCKSZ;
+		if (end_offset <= entry->source_size &&
+			end_offset <= entry->target_size)
+			datapagemap_add(&entry->target_modified_pages, blkno_inseg);
 	}
 	else
 	{
 		/*
 		 * If we don't have any record of this file in the file map, it means
-		 * that it's a relation that doesn't exist in the source system, and
-		 * it was subsequently removed in the target system, too. We can
-		 * safely ignore it.
+		 * that it's a relation that doesn't exist in the source system.  It
+		 * could exist in the target system; we haven't moved the target-only
+		 * entries from the linked list to the array yet!  But in any case, if
+		 * it doesn't exist in the source it will be removed from the target
+		 * too, and we can safely ignore it.
 		 */
 	}
 }
@@ -593,16 +472,6 @@ filemap_list_to_array(filemap_t *map)
 	map->first = map->last = NULL;
 }
 
-void
-filemap_finalize(void)
-{
-	filemap_t  *map = filemap;
-
-	filemap_list_to_array(map);
-	qsort(map->array, map->narray, sizeof(file_entry_t *),
-		  final_filemap_cmp);
-}
-
 static const char *
 action_to_str(file_action_t action)
 {
@@ -643,26 +512,26 @@ calculate_totals(void)
 	{
 		entry = map->array[i];
 
-		if (entry->type != FILE_TYPE_REGULAR)
+		if (entry->source_type != FILE_TYPE_REGULAR)
 			continue;
 
-		map->total_size += entry->newsize;
+		map->total_size += entry->source_size;
 
 		if (entry->action == FILE_ACTION_COPY)
 		{
-			map->fetch_size += entry->newsize;
+			map->fetch_size += entry->source_size;
 			continue;
 		}
 
 		if (entry->action == FILE_ACTION_COPY_TAIL)
-			map->fetch_size += (entry->newsize - entry->oldsize);
+			map->fetch_size += (entry->source_size - entry->target_size);
 
-		if (entry->pagemap.bitmapsize > 0)
+		if (entry->target_modified_pages.bitmapsize > 0)
 		{
 			datapagemap_iterator_t *iter;
 			BlockNumber blk;
 
-			iter = datapagemap_iterate(&entry->pagemap);
+			iter = datapagemap_iterate(&entry->target_modified_pages);
 			while (datapagemap_next(iter, &blk))
 				map->fetch_size += BLCKSZ;
 
@@ -682,13 +551,13 @@ print_filemap(void)
 	{
 		entry = map->array[i];
 		if (entry->action != FILE_ACTION_NONE ||
-			entry->pagemap.bitmapsize > 0)
+			entry->target_modified_pages.bitmapsize > 0)
 		{
 			pg_log_debug("%s (%s)", entry->path,
 						 action_to_str(entry->action));
 
-			if (entry->pagemap.bitmapsize > 0)
-				datapagemap_print(&entry->pagemap);
+			if (entry->target_modified_pages.bitmapsize > 0)
+				datapagemap_print(&entry->target_modified_pages);
 		}
 	}
 	fflush(stdout);
@@ -837,3 +706,126 @@ final_filemap_cmp(const void *a, const void *b)
 	else
 		return strcmp(fa->path, fb->path);
 }
+
+/*
+ * Decide what to do with each file.
+ */
+void
+filemap_finalize()
+{
+	int			i;
+
+	filemap_list_to_array(filemap);
+
+	for (i = 0; i < filemap->narray; i++)
+	{
+		file_entry_t *entry = filemap->array[i];
+		file_action_t action;
+
+		if (!entry->target_exists && entry->source_exists)
+		{
+			/*
+			 * File exists in source, but not in target. Copy
+			 * it in toto. (If it's a relation data file, WAL replay
+			 * after rewinding should re-create it anyway. But there's
+			 * no harm in copying it now.)
+			 */
+			if (entry->source_type == FILE_TYPE_DIRECTORY ||
+				entry->source_type == FILE_TYPE_SYMLINK)
+			{
+				action = FILE_ACTION_CREATE;
+			}
+			else
+				action = FILE_ACTION_COPY;
+		}
+		else if (entry->target_exists && !entry->source_exists)
+		{
+			/* Delete file from target */
+			action = FILE_ACTION_REMOVE;
+		}
+		else if (entry->target_exists && entry->source_exists)
+		{
+			/* File exists in both systems */
+			if (entry->source_type != entry->target_type)
+			{
+				/* But it's a different kind of object. Strange.. */
+				pg_fatal("file \"%s\" is of different type in source and target", entry->path);
+			}
+
+			switch (entry->source_type)
+			{
+				case FILE_TYPE_DIRECTORY:
+					action = FILE_ACTION_NONE;
+					break;
+
+				case FILE_TYPE_SYMLINK:
+					// FIXME: Check if it points to the same target?
+					action = FILE_ACTION_NONE;
+					break;
+
+				case FILE_TYPE_REGULAR:
+					if (!entry->isrelfile)
+					{
+						/*
+						 * It's a non-data file that we have no special processing for. Copy
+						 * it in toto.
+						 *
+						 * An exception: PG_VERSIONs should be identical, but avoid
+						 * overwriting it for paranoia.
+						 */
+						if (!pg_str_endswith(entry->path, "PG_VERSION"))
+							action = FILE_ACTION_COPY;
+						else
+							action = FILE_ACTION_NONE;
+					}
+					else
+					{
+						/*
+						 * It's a data file that exists in both systems.
+						 *
+						 * If it's larger in target, we can truncate it. There will
+						 * also be a WAL record of the truncation in the source
+						 * system, so WAL replay would eventually truncate the target
+						 * too, but we might as well do it now.
+						 *
+						 * If it's smaller in the target, it means that it has been
+						 * truncated in the target, or enlarged in the source, or
+						 * both. If it was truncated in the target, we need to copy
+						 * the missing tail from the source system. If it was enlarged
+						 * in the source system, there will be WAL records in the
+						 * source system for the new blocks, so we wouldn't need to
+						 * copy them here. But we don't know which scenario we're
+						 * dealing with, and there's no harm in copying the missing
+						 * blocks now, so do it now.
+						 *
+						 * If it's the same size, do nothing here. Any blocks modified
+						 * in the target will be copied based on parsing the target
+						 * system's WAL, and any blocks modified in the source will be
+						 * updated after rewinding, when the source system's WAL is
+						 * replayed.
+						 */
+						if (entry->target_size < entry->source_size)
+							action = FILE_ACTION_COPY_TAIL;
+						else if (entry->target_size > entry->source_size)
+							action = FILE_ACTION_TRUNCATE;
+						else
+							action = FILE_ACTION_NONE;
+					}
+					break;
+				case FILE_TYPE_UNDEFINED:
+					pg_fatal("unknown file type for \"%s\"", entry->path);
+					break;
+			}
+		}
+		else
+		{
+			/* Doesn't exist in either server. Why does it have an entry at all?? */
+			action = FILE_ACTION_NONE;
+		}
+		entry->action = action;
+	}
+
+	/* Sort the actions to the order that they should be performed */
+	qsort(filemap->array, filemap->narray, sizeof(file_entry_t *),
+		  final_filemap_cmp);
+}
diff --git a/src/bin/pg_rewind/filemap.h b/src/bin/pg_rewind/filemap.h
index 0cb7425170c..a5e8df57f40 100644
--- a/src/bin/pg_rewind/filemap.h
+++ b/src/bin/pg_rewind/filemap.h
@@ -14,17 +14,21 @@
 
 /*
  * For every file found in the local or remote system, we have a file entry
- * which says what we are going to do with the file. For relation files,
- * there is also a page map, marking pages in the file that were changed
- * locally.
- *
- * The enum values are sorted in the order we want actions to be processed.
+ * that contains information about the file on both systems.  For relation
+ * files, there is also a page map that marks pages in the file that were
+ * changed in the target after the last common checkpoint.  Each entry also
+ * contains an 'action' field, which says what we are going to do with the
+ * file.
  */
+
+/* these enum values are sorted in the order we want actions to be processed */
 typedef enum
 {
+	FILE_ACTION_UNDECIDED = 0,	/* not decided yet */
+
 	FILE_ACTION_CREATE,			/* create local directory or symbolic link */
 	FILE_ACTION_COPY,			/* copy whole file, overwriting if exists */
-	FILE_ACTION_COPY_TAIL,		/* copy tail from 'oldsize' to 'newsize' */
+	FILE_ACTION_COPY_TAIL,		/* copy tail from 'source_size' to 'target_size' */
 	FILE_ACTION_NONE,			/* no action (we might still copy modified
 								 * blocks based on the parsed WAL) */
 	FILE_ACTION_TRUNCATE,		/* truncate local file to 'newsize' bytes */
@@ -33,6 +37,8 @@ typedef enum
 
 typedef enum
 {
+	FILE_TYPE_UNDEFINED = 0,
+
 	FILE_TYPE_REGULAR,
 	FILE_TYPE_DIRECTORY,
 	FILE_TYPE_SYMLINK
@@ -41,19 +47,30 @@ typedef enum
 typedef struct file_entry_t
 {
 	char	   *path;
-	file_type_t type;
+	bool		isrelfile;		/* is it a relation data file? */
 
-	file_action_t action;
+	/*
+	 * Status of the file in the target.
+	 */
+	bool		target_exists;
+	file_type_t target_type;
+	size_t		target_size; /* for a regular file */
+	char	   *target_link_target; /* for a symlink */
 
-	/* for a regular file */
-	size_t		oldsize;
-	size_t		newsize;
-	bool		isrelfile;		/* is it a relation data file? */
+	datapagemap_t target_modified_pages;
 
-	datapagemap_t pagemap;
+	/*
+	 * Status of the file in the source.
+	 */
+	bool		source_exists;
+	file_type_t source_type;
+	size_t		source_size;
+	char	   *source_link_target; /* for a symlink */
 
-	/* for a symlink */
-	char	   *link_target;
+	/*
+	 * What will we do to the file?
+	 */
+	file_action_t action;
 
 	struct file_entry_t *next;
 } file_entry_t;
@@ -70,20 +87,19 @@ typedef struct filemap_t
 
 	/*
 	 * After processing all the remote files, the entries in the linked list
-	 * are moved to this array. After processing local files, too, all the
+	 * are moved to this array.  After processing local files, too, all the
 	 * local entries are added to the array by filemap_finalize, and sorted in
-	 * the final order. After filemap_finalize, all the entries are in the
+	 * the final order.  After filemap_finalize, all the entries are in the
 	 * array, and the linked list is empty.
 	 */
 	file_entry_t **array;
 	int			narray;			/* current length of array */
 
 	/*
-	 * Summary information. total_size is the total size of the source
-	 * cluster, and fetch_size is the number of bytes that needs to be copied.
+	 * Summary information.
 	 */
-	uint64		total_size;
-	uint64		fetch_size;
+	uint64		total_size;		/* total size of the source cluster */
+	uint64		fetch_size;		/* number of bytes that needs to be copied */
 } filemap_t;
 
 extern filemap_t *filemap;
@@ -94,11 +110,12 @@ extern void print_filemap(void);
 
 /* Functions for populating the filemap */
 extern void process_source_file(const char *path, file_type_t type,
-								size_t newsize, const char *link_target);
+								size_t size, const char *link_target);
 extern void process_target_file(const char *path, file_type_t type,
-								size_t newsize, const char *link_target);
-extern void process_block_change(ForkNumber forknum, RelFileNode rnode,
-								 BlockNumber blkno);
+								size_t size, const char *link_target);
+extern void process_target_wal_block_change(ForkNumber forknum,
+											RelFileNode rnode,
+											BlockNumber blkno);
 extern void filemap_finalize(void);
 
 #endif							/* FILEMAP_H */
diff --git a/src/bin/pg_rewind/libpq_fetch.c b/src/bin/pg_rewind/libpq_fetch.c
index bf4dfc23b96..7fc9161b8c8 100644
--- a/src/bin/pg_rewind/libpq_fetch.c
+++ b/src/bin/pg_rewind/libpq_fetch.c
@@ -465,7 +465,7 @@ libpq_executeFileMap(filemap_t *map)
 		entry = map->array[i];
 
 		/* If this is a relation file, copy the modified blocks */
-		execute_pagemap(&entry->pagemap, entry->path);
+		execute_pagemap(&entry->target_modified_pages, entry->path);
 
 		switch (entry->action)
 		{
@@ -476,15 +476,15 @@ libpq_executeFileMap(filemap_t *map)
 			case FILE_ACTION_COPY:
 				/* Truncate the old file out of the way, if any */
 				open_target_file(entry->path, true);
-				fetch_file_range(entry->path, 0, entry->newsize);
+				fetch_file_range(entry->path, 0, entry->source_size);
 				break;
 
 			case FILE_ACTION_TRUNCATE:
-				truncate_target_file(entry->path, entry->newsize);
+				truncate_target_file(entry->path, entry->source_size);
 				break;
 
 			case FILE_ACTION_COPY_TAIL:
-				fetch_file_range(entry->path, entry->oldsize, entry->newsize);
+				fetch_file_range(entry->path, entry->target_size, entry->source_size);
 				break;
 
 			case FILE_ACTION_REMOVE:
@@ -494,6 +494,10 @@ libpq_executeFileMap(filemap_t *map)
 			case FILE_ACTION_CREATE:
 				create_target(entry);
 				break;
+
+			case FILE_ACTION_UNDECIDED:
+				pg_fatal("no action decided for \"%s\"", entry->path);
+				break;
 		}
 	}
 
diff --git a/src/bin/pg_rewind/parsexlog.c b/src/bin/pg_rewind/parsexlog.c
index 2229c86f9af..2baeb74ae93 100644
--- a/src/bin/pg_rewind/parsexlog.c
+++ b/src/bin/pg_rewind/parsexlog.c
@@ -436,6 +436,6 @@ extractPageInfo(XLogReaderState *record)
 		if (forknum != MAIN_FORKNUM)
 			continue;
 
-		process_block_change(forknum, rnode, blkno);
+		process_target_wal_block_change(forknum, rnode, blkno);
 	}
 }
diff --git a/src/bin/pg_rewind/pg_rewind.c b/src/bin/pg_rewind/pg_rewind.c
index c9b9e480c0f..210984d302b 100644
--- a/src/bin/pg_rewind/pg_rewind.c
+++ b/src/bin/pg_rewind/pg_rewind.c
@@ -369,7 +369,7 @@ main(int argc, char **argv)
 				chkpttli);
 
 	/*
-	 * Build the filemap, by comparing the source and target data directories.
+	 * Collect information about all files in the target and source systems.
 	 */
 	filemap_create();
 	if (showprogress)
@@ -390,8 +390,12 @@ main(int argc, char **argv)
 		pg_log_info("reading WAL in target");
 	extractPageMap(datadir_target, chkptrec, lastcommontliIndex,
 				   ControlFile_target.checkPoint, restore_command);
-	filemap_finalize();
 
+	/*
+	 * We have collected all information we need from both systems. Decide
+	 * what to do with each file.
+	 */
+	filemap_finalize();
 	if (showprogress)
 		calculate_totals();
 
-- 
2.20.1


--------------D93EDEBFB124D563B723F4BD
Content-Type: text/x-patch; charset=UTF-8;
 name="0003-pg_rewind-Replace-the-hybrid-list-array-data-structu.patch"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
 filename*0="0003-pg_rewind-Replace-the-hybrid-list-array-data-structu.pa";
 filename*1="tch"



^ permalink  raw  reply  [nested|flat] 41+ messages in thread

* [PATCH 2/5] Refactor pg_rewind for more clear decision making.
@ 2020-08-19 12:34 Heikki Linnakangas <[email protected]>
  0 siblings, 0 replies; 41+ messages in thread

From: Heikki Linnakangas @ 2020-08-19 12:34 UTC (permalink / raw)

Deciding what to do with each file is now a separate step after all the
necessary information has been gathered. It is more clear that way.
Previously, the decision-making was divided between process_source_file()
and process_target_file(), and it was a bit hard to piece together what the
overall rules were.
---
 src/bin/pg_rewind/copy_fetch.c  |  14 +-
 src/bin/pg_rewind/file_ops.c    |  16 +-
 src/bin/pg_rewind/filemap.c     | 514 ++++++++++++++++----------------
 src/bin/pg_rewind/filemap.h     |  67 +++--
 src/bin/pg_rewind/libpq_fetch.c |  12 +-
 src/bin/pg_rewind/parsexlog.c   |   2 +-
 src/bin/pg_rewind/pg_rewind.c   |   8 +-
 7 files changed, 332 insertions(+), 301 deletions(-)

diff --git a/src/bin/pg_rewind/copy_fetch.c b/src/bin/pg_rewind/copy_fetch.c
index 1edab5f1867..18fad32600e 100644
--- a/src/bin/pg_rewind/copy_fetch.c
+++ b/src/bin/pg_rewind/copy_fetch.c
@@ -210,7 +210,7 @@ copy_executeFileMap(filemap_t *map)
 	for (i = 0; i < map->narray; i++)
 	{
 		entry = map->array[i];
-		execute_pagemap(&entry->pagemap, entry->path);
+		execute_pagemap(&entry->target_modified_pages, entry->path);
 
 		switch (entry->action)
 		{
@@ -219,16 +219,16 @@ copy_executeFileMap(filemap_t *map)
 				break;
 
 			case FILE_ACTION_COPY:
-				rewind_copy_file_range(entry->path, 0, entry->newsize, true);
+				rewind_copy_file_range(entry->path, 0, entry->source_size, true);
 				break;
 
 			case FILE_ACTION_TRUNCATE:
-				truncate_target_file(entry->path, entry->newsize);
+				truncate_target_file(entry->path, entry->source_size);
 				break;
 
 			case FILE_ACTION_COPY_TAIL:
-				rewind_copy_file_range(entry->path, entry->oldsize,
-									   entry->newsize, false);
+				rewind_copy_file_range(entry->path, entry->target_size,
+									   entry->source_size, false);
 				break;
 
 			case FILE_ACTION_CREATE:
@@ -238,6 +238,10 @@ copy_executeFileMap(filemap_t *map)
 			case FILE_ACTION_REMOVE:
 				remove_target(entry);
 				break;
+
+			case FILE_ACTION_UNDECIDED:
+				pg_fatal("no action decided for \"%s\"", entry->path);
+				break;
 		}
 	}
 
diff --git a/src/bin/pg_rewind/file_ops.c b/src/bin/pg_rewind/file_ops.c
index 55439db20ba..ec37d0b2e0d 100644
--- a/src/bin/pg_rewind/file_ops.c
+++ b/src/bin/pg_rewind/file_ops.c
@@ -126,8 +126,9 @@ void
 remove_target(file_entry_t *entry)
 {
 	Assert(entry->action == FILE_ACTION_REMOVE);
+	Assert(entry->target_exists);
 
-	switch (entry->type)
+	switch (entry->target_type)
 	{
 		case FILE_TYPE_DIRECTORY:
 			remove_target_dir(entry->path);
@@ -140,6 +141,10 @@ remove_target(file_entry_t *entry)
 		case FILE_TYPE_SYMLINK:
 			remove_target_symlink(entry->path);
 			break;
+
+		case FILE_TYPE_UNDEFINED:
+			pg_fatal("undefined file type for \"%s\"", entry->path);
+			break;
 	}
 }
 
@@ -147,21 +152,26 @@ void
 create_target(file_entry_t *entry)
 {
 	Assert(entry->action == FILE_ACTION_CREATE);
+	Assert(!entry->target_exists);
 
-	switch (entry->type)
+	switch (entry->source_type)
 	{
 		case FILE_TYPE_DIRECTORY:
 			create_target_dir(entry->path);
 			break;
 
 		case FILE_TYPE_SYMLINK:
-			create_target_symlink(entry->path, entry->link_target);
+			create_target_symlink(entry->path, entry->source_link_target);
 			break;
 
 		case FILE_TYPE_REGULAR:
 			/* can't happen. Regular files are created with open_target_file. */
 			pg_fatal("invalid action (CREATE) for regular file");
 			break;
+
+		case FILE_TYPE_UNDEFINED:
+			pg_fatal("undefined file type for \"%s\"", entry->path);
+			break;
 	}
 }
 
diff --git a/src/bin/pg_rewind/filemap.c b/src/bin/pg_rewind/filemap.c
index 1879257b66a..431e8e760e0 100644
--- a/src/bin/pg_rewind/filemap.c
+++ b/src/bin/pg_rewind/filemap.c
@@ -26,6 +26,8 @@ static bool isRelDataFile(const char *path);
 static char *datasegpath(RelFileNode rnode, ForkNumber forknum,
 						 BlockNumber segno);
 static int	path_cmp(const void *a, const void *b);
+
+static file_entry_t *get_filemap_entry(const char *path, bool create);
 static int	final_filemap_cmp(const void *a, const void *b);
 static void filemap_list_to_array(filemap_t *map);
 static bool check_file_excluded(const char *path, bool is_source);
@@ -146,6 +148,65 @@ filemap_create(void)
 	filemap = map;
 }
 
+/* Look up or create entry for 'path' */
+static file_entry_t *
+get_filemap_entry(const char *path, bool create)
+{
+	filemap_t  *map = filemap;
+	file_entry_t *entry;
+	file_entry_t **e;
+	file_entry_t key;
+	file_entry_t *key_ptr;
+
+	if (map->array)
+	{
+		key.path = (char *) path;
+		key_ptr = &key;
+		e = bsearch(&key_ptr, map->array, map->narray, sizeof(file_entry_t *),
+					path_cmp);
+	}
+	else
+		e = NULL;
+
+	if (e)
+		entry = *e;
+	else if (!create)
+		entry = NULL;
+	else
+	{
+		/* Create a new entry for this file */
+		entry = pg_malloc(sizeof(file_entry_t));
+		entry->path = pg_strdup(path);
+		entry->isrelfile = isRelDataFile(path);
+		entry->action = FILE_ACTION_UNDECIDED;
+
+		entry->target_exists = false;
+		entry->target_type = FILE_TYPE_UNDEFINED;
+		entry->target_size = 0;
+		entry->target_link_target = NULL;
+		entry->target_modified_pages.bitmap = NULL;
+		entry->target_modified_pages.bitmapsize = 0;
+
+		entry->source_exists = false;
+		entry->source_type = FILE_TYPE_UNDEFINED;
+		entry->source_size = 0;
+		entry->source_link_target = NULL;
+
+		entry->next = NULL;
+
+		if (map->last)
+		{
+			map->last->next = entry;
+			map->last = entry;
+		}
+		else
+			map->first = map->last = entry;
+		map->nlist++;
+	}
+
+	return entry;
+}
+
 /*
  * Callback for processing source file list.
  *
@@ -154,18 +215,19 @@ filemap_create(void)
  * exists in the target and whether the size matches.
  */
 void
-process_source_file(const char *path, file_type_t type, size_t newsize,
+process_source_file(const char *path, file_type_t type, size_t size,
 					const char *link_target)
 {
-	bool		exists;
-	char		localpath[MAXPGPATH];
-	struct stat statbuf;
-	filemap_t  *map = filemap;
-	file_action_t action = FILE_ACTION_NONE;
-	size_t		oldsize = 0;
 	file_entry_t *entry;
 
-	Assert(map->array == NULL);
+	Assert(filemap->array == NULL);
+
+	/*
+	 * Skip the control file. It is handled specially, after copying all the
+	 * other files.
+	 */
+	if (strcmp(path, "global/pg_control") == 0)
+		return;
 
 	/*
 	 * Skip any files matching the exclusion filters. This has the effect to
@@ -199,142 +261,12 @@ process_source_file(const char *path, file_type_t type, size_t newsize,
 	if (type != FILE_TYPE_REGULAR && isRelDataFile(path))
 		pg_fatal("data file \"%s\" in source is not a regular file", path);
 
-	snprintf(localpath, sizeof(localpath), "%s/%s", datadir_target, path);
-
-	/* Does the corresponding file exist in the target data dir? */
-	if (lstat(localpath, &statbuf) < 0)
-	{
-		if (errno != ENOENT)
-			pg_fatal("could not stat file \"%s\": %m",
-					 localpath);
-
-		exists = false;
-	}
-	else
-		exists = true;
-
-	switch (type)
-	{
-		case FILE_TYPE_DIRECTORY:
-			if (exists && !S_ISDIR(statbuf.st_mode) && strcmp(path, "pg_wal") != 0)
-			{
-				/* it's a directory in source, but not in target. Strange.. */
-				pg_fatal("\"%s\" is not a directory", localpath);
-			}
-
-			if (!exists)
-				action = FILE_ACTION_CREATE;
-			else
-				action = FILE_ACTION_NONE;
-			oldsize = 0;
-			break;
-
-		case FILE_TYPE_SYMLINK:
-			if (exists &&
-#ifndef WIN32
-				!S_ISLNK(statbuf.st_mode)
-#else
-				!pgwin32_is_junction(localpath)
-#endif
-				)
-			{
-				/*
-				 * It's a symbolic link in source, but not in target.
-				 * Strange..
-				 */
-				pg_fatal("\"%s\" is not a symbolic link", localpath);
-			}
-
-			if (!exists)
-				action = FILE_ACTION_CREATE;
-			else
-				action = FILE_ACTION_NONE;
-			oldsize = 0;
-			break;
-
-		case FILE_TYPE_REGULAR:
-			if (exists && !S_ISREG(statbuf.st_mode))
-				pg_fatal("\"%s\" is not a regular file", localpath);
-
-			if (!exists || !isRelDataFile(path))
-			{
-				/*
-				 * File exists in source, but not in target. Or it's a
-				 * non-data file that we have no special processing for. Copy
-				 * it in toto.
-				 *
-				 * An exception: PG_VERSIONs should be identical, but avoid
-				 * overwriting it for paranoia.
-				 */
-				if (pg_str_endswith(path, "PG_VERSION"))
-				{
-					action = FILE_ACTION_NONE;
-					oldsize = statbuf.st_size;
-				}
-				else
-				{
-					action = FILE_ACTION_COPY;
-					oldsize = 0;
-				}
-			}
-			else
-			{
-				/*
-				 * It's a data file that exists in both.
-				 *
-				 * If it's larger in target, we can truncate it. There will
-				 * also be a WAL record of the truncation in the source
-				 * system, so WAL replay would eventually truncate the target
-				 * too, but we might as well do it now.
-				 *
-				 * If it's smaller in the target, it means that it has been
-				 * truncated in the target, or enlarged in the source, or
-				 * both. If it was truncated in the target, we need to copy
-				 * the missing tail from the source system. If it was enlarged
-				 * in the source system, there will be WAL records in the
-				 * source system for the new blocks, so we wouldn't need to
-				 * copy them here. But we don't know which scenario we're
-				 * dealing with, and there's no harm in copying the missing
-				 * blocks now, so do it now.
-				 *
-				 * If it's the same size, do nothing here. Any blocks modified
-				 * in the target will be copied based on parsing the target
-				 * system's WAL, and any blocks modified in the source will be
-				 * updated after rewinding, when the source system's WAL is
-				 * replayed.
-				 */
-				oldsize = statbuf.st_size;
-				if (oldsize < newsize)
-					action = FILE_ACTION_COPY_TAIL;
-				else if (oldsize > newsize)
-					action = FILE_ACTION_TRUNCATE;
-				else
-					action = FILE_ACTION_NONE;
-			}
-			break;
-	}
-
-	/* Create a new entry for this file */
-	entry = pg_malloc(sizeof(file_entry_t));
-	entry->path = pg_strdup(path);
-	entry->type = type;
-	entry->action = action;
-	entry->oldsize = oldsize;
-	entry->newsize = newsize;
-	entry->link_target = link_target ? pg_strdup(link_target) : NULL;
-	entry->next = NULL;
-	entry->pagemap.bitmap = NULL;
-	entry->pagemap.bitmapsize = 0;
-	entry->isrelfile = isRelDataFile(path);
-
-	if (map->last)
-	{
-		map->last->next = entry;
-		map->last = entry;
-	}
-	else
-		map->first = map->last = entry;
-	map->nlist++;
+	/* Remember this source file */
+	entry = get_filemap_entry(path, true);
+	entry->source_exists = true;
+	entry->source_type = type;
+	entry->source_size = size;
+	entry->source_link_target = link_target ? pg_strdup(link_target) : NULL;
 }
 
 /*
@@ -345,33 +277,24 @@ process_source_file(const char *path, file_type_t type, size_t newsize,
  * deletion.
  */
 void
-process_target_file(const char *path, file_type_t type, size_t oldsize,
+process_target_file(const char *path, file_type_t type, size_t size,
 					const char *link_target)
 {
-	bool		exists;
-	char		localpath[MAXPGPATH];
-	struct stat statbuf;
-	file_entry_t key;
-	file_entry_t *key_ptr;
 	filemap_t  *map = filemap;
 	file_entry_t *entry;
 
+	/*
+	 * Skip the control file. It is handled specially, after copying all the
+	 * other files.
+	 */
+	if (strcmp(path, "global/pg_control") == 0)
+		return;
+
 	/*
 	 * Do not apply any exclusion filters here.  This has advantage to remove
 	 * from the target data folder all paths which have been filtered out from
 	 * the source data folder when processing the source files.
 	 */
-
-	snprintf(localpath, sizeof(localpath), "%s/%s", datadir_target, path);
-	if (lstat(localpath, &statbuf) < 0)
-	{
-		if (errno != ENOENT)
-			pg_fatal("could not stat file \"%s\": %m",
-					 localpath);
-
-		exists = false;
-	}
-
 	if (map->array == NULL)
 	{
 		/* on first call, initialize lookup array */
@@ -389,120 +312,76 @@ process_target_file(const char *path, file_type_t type, size_t oldsize,
 	}
 
 	/*
-	 * Like in process_source_file, pretend that xlog is always a  directory.
+	 * Like in process_source_file, pretend that pg_wal is always a directory.
 	 */
 	if (strcmp(path, "pg_wal") == 0 && type == FILE_TYPE_SYMLINK)
 		type = FILE_TYPE_DIRECTORY;
 
-	key.path = (char *) path;
-	key_ptr = &key;
-	exists = (bsearch(&key_ptr, map->array, map->narray, sizeof(file_entry_t *),
-					  path_cmp) != NULL);
-
-	/* Remove any file or folder that doesn't exist in the source system. */
-	if (!exists)
-	{
-		entry = pg_malloc(sizeof(file_entry_t));
-		entry->path = pg_strdup(path);
-		entry->type = type;
-		entry->action = FILE_ACTION_REMOVE;
-		entry->oldsize = oldsize;
-		entry->newsize = 0;
-		entry->link_target = link_target ? pg_strdup(link_target) : NULL;
-		entry->next = NULL;
-		entry->pagemap.bitmap = NULL;
-		entry->pagemap.bitmapsize = 0;
-		entry->isrelfile = isRelDataFile(path);
-
-		if (map->last == NULL)
-			map->first = entry;
-		else
-			map->last->next = entry;
-		map->last = entry;
-		map->nlist++;
-	}
-	else
-	{
-		/*
-		 * We already handled all files that exist in the source system in
-		 * process_source_file().
-		 */
-	}
+	/* Remember this target file */
+	entry = get_filemap_entry(path, true);
+	entry->target_exists = true;
+	entry->target_type = type;
+	entry->target_size = size;
+	entry->target_link_target = link_target ? pg_strdup(link_target) : NULL;
 }
 
 /*
  * This callback gets called while we read the WAL in the target, for every
- * block that have changed in the target system. It makes note of all the
+ * block that have changed in the target system.  It makes note of all the
  * changed blocks in the pagemap of the file.
+ *
+ * NOTE: All the files on both systems must have already been added to the
+ * file map!
  */
 void
-process_block_change(ForkNumber forknum, RelFileNode rnode, BlockNumber blkno)
+process_target_wal_block_change(ForkNumber forknum, RelFileNode rnode,
+								BlockNumber blkno)
 {
 	char	   *path;
-	file_entry_t key;
-	file_entry_t *key_ptr;
 	file_entry_t *entry;
 	BlockNumber blkno_inseg;
 	int			segno;
-	filemap_t  *map = filemap;
-	file_entry_t **e;
 
-	Assert(map->array);
+	Assert(filemap->array);
 
 	segno = blkno / RELSEG_SIZE;
 	blkno_inseg = blkno % RELSEG_SIZE;
 
 	path = datasegpath(rnode, forknum, segno);
-
-	key.path = (char *) path;
-	key_ptr = &key;
-
-	e = bsearch(&key_ptr, map->array, map->narray, sizeof(file_entry_t *),
-				path_cmp);
-	if (e)
-		entry = *e;
-	else
-		entry = NULL;
+	entry = get_filemap_entry(path, false);
 	pfree(path);
 
 	if (entry)
 	{
+		int64		end_offset;
+
 		Assert(entry->isrelfile);
 
-		switch (entry->action)
-		{
-			case FILE_ACTION_NONE:
-			case FILE_ACTION_TRUNCATE:
-				/* skip if we're truncating away the modified block anyway */
-				if ((blkno_inseg + 1) * BLCKSZ <= entry->newsize)
-					datapagemap_add(&entry->pagemap, blkno_inseg);
-				break;
-
-			case FILE_ACTION_COPY_TAIL:
-
-				/*
-				 * skip the modified block if it is part of the "tail" that
-				 * we're copying anyway.
-				 */
-				if ((blkno_inseg + 1) * BLCKSZ <= entry->oldsize)
-					datapagemap_add(&entry->pagemap, blkno_inseg);
-				break;
-
-			case FILE_ACTION_COPY:
-			case FILE_ACTION_REMOVE:
-				break;
-
-			case FILE_ACTION_CREATE:
-				pg_fatal("unexpected page modification for directory or symbolic link \"%s\"", entry->path);
-		}
+		if (entry->target_type != FILE_TYPE_REGULAR)
+			pg_fatal("unexpected page modification for directory or symbolic link \"%s\"",
+					 entry->path);
+
+		/*
+		 * If the block beyond the EOF in the source system, no need to
+		 * remember it now, because we're going to truncate it away from the
+		 * target anyway. Also no need to remember the block if it's beyond
+		 * the current EOF in the target system; we will copy it over with the
+		 * "tail" from the source system, anyway.
+		 */
+		end_offset = (blkno_inseg + 1) * BLCKSZ;
+		if (end_offset <= entry->source_size &&
+			end_offset <= entry->target_size)
+			datapagemap_add(&entry->target_modified_pages, blkno_inseg);
 	}
 	else
 	{
 		/*
 		 * If we don't have any record of this file in the file map, it means
-		 * that it's a relation that doesn't exist in the source system, and
-		 * it was subsequently removed in the target system, too. We can
-		 * safely ignore it.
+		 * that it's a relation that doesn't exist in the source system.  It
+		 * could exist in the target system; we haven't moved the target-only
+		 * entries from the linked list to the array yet!  But in any case, if
+		 * it doesn't exist in the source it will be removed from the target
+		 * too, and we can safely ignore it.
 		 */
 	}
 }
@@ -593,16 +472,6 @@ filemap_list_to_array(filemap_t *map)
 	map->first = map->last = NULL;
 }
 
-void
-filemap_finalize(void)
-{
-	filemap_t  *map = filemap;
-
-	filemap_list_to_array(map);
-	qsort(map->array, map->narray, sizeof(file_entry_t *),
-		  final_filemap_cmp);
-}
-
 static const char *
 action_to_str(file_action_t action)
 {
@@ -643,26 +512,26 @@ calculate_totals(void)
 	{
 		entry = map->array[i];
 
-		if (entry->type != FILE_TYPE_REGULAR)
+		if (entry->source_type != FILE_TYPE_REGULAR)
 			continue;
 
-		map->total_size += entry->newsize;
+		map->total_size += entry->source_size;
 
 		if (entry->action == FILE_ACTION_COPY)
 		{
-			map->fetch_size += entry->newsize;
+			map->fetch_size += entry->source_size;
 			continue;
 		}
 
 		if (entry->action == FILE_ACTION_COPY_TAIL)
-			map->fetch_size += (entry->newsize - entry->oldsize);
+			map->fetch_size += (entry->source_size - entry->target_size);
 
-		if (entry->pagemap.bitmapsize > 0)
+		if (entry->target_modified_pages.bitmapsize > 0)
 		{
 			datapagemap_iterator_t *iter;
 			BlockNumber blk;
 
-			iter = datapagemap_iterate(&entry->pagemap);
+			iter = datapagemap_iterate(&entry->target_modified_pages);
 			while (datapagemap_next(iter, &blk))
 				map->fetch_size += BLCKSZ;
 
@@ -682,13 +551,13 @@ print_filemap(void)
 	{
 		entry = map->array[i];
 		if (entry->action != FILE_ACTION_NONE ||
-			entry->pagemap.bitmapsize > 0)
+			entry->target_modified_pages.bitmapsize > 0)
 		{
 			pg_log_debug("%s (%s)", entry->path,
 						 action_to_str(entry->action));
 
-			if (entry->pagemap.bitmapsize > 0)
-				datapagemap_print(&entry->pagemap);
+			if (entry->target_modified_pages.bitmapsize > 0)
+				datapagemap_print(&entry->target_modified_pages);
 		}
 	}
 	fflush(stdout);
@@ -837,3 +706,126 @@ final_filemap_cmp(const void *a, const void *b)
 	else
 		return strcmp(fa->path, fb->path);
 }
+
+/*
+ * Decide what to do with each file.
+ */
+void
+filemap_finalize()
+{
+	int			i;
+
+	filemap_list_to_array(filemap);
+
+	for (i = 0; i < filemap->narray; i++)
+	{
+		file_entry_t *entry = filemap->array[i];
+		file_action_t action;
+
+		if (!entry->target_exists && entry->source_exists)
+		{
+			/*
+			 * File exists in source, but not in target. Copy
+			 * it in toto. (If it's a relation data file, WAL replay
+			 * after rewinding should re-create it anyway. But there's
+			 * no harm in copying it now.)
+			 */
+			if (entry->source_type == FILE_TYPE_DIRECTORY ||
+				entry->source_type == FILE_TYPE_SYMLINK)
+			{
+				action = FILE_ACTION_CREATE;
+			}
+			else
+				action = FILE_ACTION_COPY;
+		}
+		else if (entry->target_exists && !entry->source_exists)
+		{
+			/* Delete file from target */
+			action = FILE_ACTION_REMOVE;
+		}
+		else if (entry->target_exists && entry->source_exists)
+		{
+			/* File exists in both systems */
+			if (entry->source_type != entry->target_type)
+			{
+				/* But it's a different kind of object. Strange.. */
+				pg_fatal("file \"%s\" is of different type in source and target", entry->path);
+			}
+
+			switch (entry->source_type)
+			{
+				case FILE_TYPE_DIRECTORY:
+					action = FILE_ACTION_NONE;
+					break;
+
+				case FILE_TYPE_SYMLINK:
+					// FIXME: Check if it points to the same target?
+					action = FILE_ACTION_NONE;
+					break;
+
+				case FILE_TYPE_REGULAR:
+					if (!entry->isrelfile)
+					{
+						/*
+						 * It's a non-data file that we have no special processing for. Copy
+						 * it in toto.
+						 *
+						 * An exception: PG_VERSIONs should be identical, but avoid
+						 * overwriting it for paranoia.
+						 */
+						if (!pg_str_endswith(entry->path, "PG_VERSION"))
+							action = FILE_ACTION_COPY;
+						else
+							action = FILE_ACTION_NONE;
+					}
+					else
+					{
+						/*
+						 * It's a data file that exists in both systems.
+						 *
+						 * If it's larger in target, we can truncate it. There will
+						 * also be a WAL record of the truncation in the source
+						 * system, so WAL replay would eventually truncate the target
+						 * too, but we might as well do it now.
+						 *
+						 * If it's smaller in the target, it means that it has been
+						 * truncated in the target, or enlarged in the source, or
+						 * both. If it was truncated in the target, we need to copy
+						 * the missing tail from the source system. If it was enlarged
+						 * in the source system, there will be WAL records in the
+						 * source system for the new blocks, so we wouldn't need to
+						 * copy them here. But we don't know which scenario we're
+						 * dealing with, and there's no harm in copying the missing
+						 * blocks now, so do it now.
+						 *
+						 * If it's the same size, do nothing here. Any blocks modified
+						 * in the target will be copied based on parsing the target
+						 * system's WAL, and any blocks modified in the source will be
+						 * updated after rewinding, when the source system's WAL is
+						 * replayed.
+						 */
+						if (entry->target_size < entry->source_size)
+							action = FILE_ACTION_COPY_TAIL;
+						else if (entry->target_size > entry->source_size)
+							action = FILE_ACTION_TRUNCATE;
+						else
+							action = FILE_ACTION_NONE;
+					}
+					break;
+				case FILE_TYPE_UNDEFINED:
+					pg_fatal("unknown file type for \"%s\"", entry->path);
+					break;
+			}
+		}
+		else
+		{
+			/* Doesn't exist in either server. Why does it have an entry at all?? */
+			action = FILE_ACTION_NONE;
+		}
+		entry->action = action;
+	}
+
+	/* Sort the actions to the order that they should be performed */
+	qsort(filemap->array, filemap->narray, sizeof(file_entry_t *),
+		  final_filemap_cmp);
+}
diff --git a/src/bin/pg_rewind/filemap.h b/src/bin/pg_rewind/filemap.h
index 0cb7425170c..a5e8df57f40 100644
--- a/src/bin/pg_rewind/filemap.h
+++ b/src/bin/pg_rewind/filemap.h
@@ -14,17 +14,21 @@
 
 /*
  * For every file found in the local or remote system, we have a file entry
- * which says what we are going to do with the file. For relation files,
- * there is also a page map, marking pages in the file that were changed
- * locally.
- *
- * The enum values are sorted in the order we want actions to be processed.
+ * that contains information about the file on both systems.  For relation
+ * files, there is also a page map that marks pages in the file that were
+ * changed in the target after the last common checkpoint.  Each entry also
+ * contains an 'action' field, which says what we are going to do with the
+ * file.
  */
+
+/* these enum values are sorted in the order we want actions to be processed */
 typedef enum
 {
+	FILE_ACTION_UNDECIDED = 0,	/* not decided yet */
+
 	FILE_ACTION_CREATE,			/* create local directory or symbolic link */
 	FILE_ACTION_COPY,			/* copy whole file, overwriting if exists */
-	FILE_ACTION_COPY_TAIL,		/* copy tail from 'oldsize' to 'newsize' */
+	FILE_ACTION_COPY_TAIL,		/* copy tail from 'source_size' to 'target_size' */
 	FILE_ACTION_NONE,			/* no action (we might still copy modified
 								 * blocks based on the parsed WAL) */
 	FILE_ACTION_TRUNCATE,		/* truncate local file to 'newsize' bytes */
@@ -33,6 +37,8 @@ typedef enum
 
 typedef enum
 {
+	FILE_TYPE_UNDEFINED = 0,
+
 	FILE_TYPE_REGULAR,
 	FILE_TYPE_DIRECTORY,
 	FILE_TYPE_SYMLINK
@@ -41,19 +47,30 @@ typedef enum
 typedef struct file_entry_t
 {
 	char	   *path;
-	file_type_t type;
+	bool		isrelfile;		/* is it a relation data file? */
 
-	file_action_t action;
+	/*
+	 * Status of the file in the target.
+	 */
+	bool		target_exists;
+	file_type_t target_type;
+	size_t		target_size; /* for a regular file */
+	char	   *target_link_target; /* for a symlink */
 
-	/* for a regular file */
-	size_t		oldsize;
-	size_t		newsize;
-	bool		isrelfile;		/* is it a relation data file? */
+	datapagemap_t target_modified_pages;
 
-	datapagemap_t pagemap;
+	/*
+	 * Status of the file in the source.
+	 */
+	bool		source_exists;
+	file_type_t source_type;
+	size_t		source_size;
+	char	   *source_link_target; /* for a symlink */
 
-	/* for a symlink */
-	char	   *link_target;
+	/*
+	 * What will we do to the file?
+	 */
+	file_action_t action;
 
 	struct file_entry_t *next;
 } file_entry_t;
@@ -70,20 +87,19 @@ typedef struct filemap_t
 
 	/*
 	 * After processing all the remote files, the entries in the linked list
-	 * are moved to this array. After processing local files, too, all the
+	 * are moved to this array.  After processing local files, too, all the
 	 * local entries are added to the array by filemap_finalize, and sorted in
-	 * the final order. After filemap_finalize, all the entries are in the
+	 * the final order.  After filemap_finalize, all the entries are in the
 	 * array, and the linked list is empty.
 	 */
 	file_entry_t **array;
 	int			narray;			/* current length of array */
 
 	/*
-	 * Summary information. total_size is the total size of the source
-	 * cluster, and fetch_size is the number of bytes that needs to be copied.
+	 * Summary information.
 	 */
-	uint64		total_size;
-	uint64		fetch_size;
+	uint64		total_size;		/* total size of the source cluster */
+	uint64		fetch_size;		/* number of bytes that needs to be copied */
 } filemap_t;
 
 extern filemap_t *filemap;
@@ -94,11 +110,12 @@ extern void print_filemap(void);
 
 /* Functions for populating the filemap */
 extern void process_source_file(const char *path, file_type_t type,
-								size_t newsize, const char *link_target);
+								size_t size, const char *link_target);
 extern void process_target_file(const char *path, file_type_t type,
-								size_t newsize, const char *link_target);
-extern void process_block_change(ForkNumber forknum, RelFileNode rnode,
-								 BlockNumber blkno);
+								size_t size, const char *link_target);
+extern void process_target_wal_block_change(ForkNumber forknum,
+											RelFileNode rnode,
+											BlockNumber blkno);
 extern void filemap_finalize(void);
 
 #endif							/* FILEMAP_H */
diff --git a/src/bin/pg_rewind/libpq_fetch.c b/src/bin/pg_rewind/libpq_fetch.c
index bf4dfc23b96..7fc9161b8c8 100644
--- a/src/bin/pg_rewind/libpq_fetch.c
+++ b/src/bin/pg_rewind/libpq_fetch.c
@@ -465,7 +465,7 @@ libpq_executeFileMap(filemap_t *map)
 		entry = map->array[i];
 
 		/* If this is a relation file, copy the modified blocks */
-		execute_pagemap(&entry->pagemap, entry->path);
+		execute_pagemap(&entry->target_modified_pages, entry->path);
 
 		switch (entry->action)
 		{
@@ -476,15 +476,15 @@ libpq_executeFileMap(filemap_t *map)
 			case FILE_ACTION_COPY:
 				/* Truncate the old file out of the way, if any */
 				open_target_file(entry->path, true);
-				fetch_file_range(entry->path, 0, entry->newsize);
+				fetch_file_range(entry->path, 0, entry->source_size);
 				break;
 
 			case FILE_ACTION_TRUNCATE:
-				truncate_target_file(entry->path, entry->newsize);
+				truncate_target_file(entry->path, entry->source_size);
 				break;
 
 			case FILE_ACTION_COPY_TAIL:
-				fetch_file_range(entry->path, entry->oldsize, entry->newsize);
+				fetch_file_range(entry->path, entry->target_size, entry->source_size);
 				break;
 
 			case FILE_ACTION_REMOVE:
@@ -494,6 +494,10 @@ libpq_executeFileMap(filemap_t *map)
 			case FILE_ACTION_CREATE:
 				create_target(entry);
 				break;
+
+			case FILE_ACTION_UNDECIDED:
+				pg_fatal("no action decided for \"%s\"", entry->path);
+				break;
 		}
 	}
 
diff --git a/src/bin/pg_rewind/parsexlog.c b/src/bin/pg_rewind/parsexlog.c
index 2229c86f9af..2baeb74ae93 100644
--- a/src/bin/pg_rewind/parsexlog.c
+++ b/src/bin/pg_rewind/parsexlog.c
@@ -436,6 +436,6 @@ extractPageInfo(XLogReaderState *record)
 		if (forknum != MAIN_FORKNUM)
 			continue;
 
-		process_block_change(forknum, rnode, blkno);
+		process_target_wal_block_change(forknum, rnode, blkno);
 	}
 }
diff --git a/src/bin/pg_rewind/pg_rewind.c b/src/bin/pg_rewind/pg_rewind.c
index c9b9e480c0f..210984d302b 100644
--- a/src/bin/pg_rewind/pg_rewind.c
+++ b/src/bin/pg_rewind/pg_rewind.c
@@ -369,7 +369,7 @@ main(int argc, char **argv)
 				chkpttli);
 
 	/*
-	 * Build the filemap, by comparing the source and target data directories.
+	 * Collect information about all files in the target and source systems.
 	 */
 	filemap_create();
 	if (showprogress)
@@ -390,8 +390,12 @@ main(int argc, char **argv)
 		pg_log_info("reading WAL in target");
 	extractPageMap(datadir_target, chkptrec, lastcommontliIndex,
 				   ControlFile_target.checkPoint, restore_command);
-	filemap_finalize();
 
+	/*
+	 * We have collected all information we need from both systems. Decide
+	 * what to do with each file.
+	 */
+	filemap_finalize();
 	if (showprogress)
 		calculate_totals();
 
-- 
2.20.1


--------------D93EDEBFB124D563B723F4BD
Content-Type: text/x-patch; charset=UTF-8;
 name="0003-pg_rewind-Replace-the-hybrid-list-array-data-structu.patch"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
 filename*0="0003-pg_rewind-Replace-the-hybrid-list-array-data-structu.pa";
 filename*1="tch"



^ permalink  raw  reply  [nested|flat] 41+ messages in thread

* [PATCH v2 2/5] Refactor pg_rewind for more clear decision making.
@ 2020-08-19 12:34 Heikki Linnakangas <[email protected]>
  0 siblings, 0 replies; 41+ messages in thread

From: Heikki Linnakangas @ 2020-08-19 12:34 UTC (permalink / raw)

Deciding what to do with each file is now a separate step after all the
necessary information has been gathered. It is more clear that way.
Previously, the decision-making was divided between process_source_file()
and process_target_file(), and it was a bit hard to piece together what the
overall rules were.
---
 src/bin/pg_rewind/copy_fetch.c  |  14 +-
 src/bin/pg_rewind/file_ops.c    |  16 +-
 src/bin/pg_rewind/filemap.c     | 569 ++++++++++++++++----------------
 src/bin/pg_rewind/filemap.h     |  67 ++--
 src/bin/pg_rewind/libpq_fetch.c |  12 +-
 src/bin/pg_rewind/parsexlog.c   |   2 +-
 src/bin/pg_rewind/pg_rewind.c   |   8 +-
 7 files changed, 371 insertions(+), 317 deletions(-)

diff --git a/src/bin/pg_rewind/copy_fetch.c b/src/bin/pg_rewind/copy_fetch.c
index 1edab5f1867..18fad32600e 100644
--- a/src/bin/pg_rewind/copy_fetch.c
+++ b/src/bin/pg_rewind/copy_fetch.c
@@ -210,7 +210,7 @@ copy_executeFileMap(filemap_t *map)
 	for (i = 0; i < map->narray; i++)
 	{
 		entry = map->array[i];
-		execute_pagemap(&entry->pagemap, entry->path);
+		execute_pagemap(&entry->target_modified_pages, entry->path);
 
 		switch (entry->action)
 		{
@@ -219,16 +219,16 @@ copy_executeFileMap(filemap_t *map)
 				break;
 
 			case FILE_ACTION_COPY:
-				rewind_copy_file_range(entry->path, 0, entry->newsize, true);
+				rewind_copy_file_range(entry->path, 0, entry->source_size, true);
 				break;
 
 			case FILE_ACTION_TRUNCATE:
-				truncate_target_file(entry->path, entry->newsize);
+				truncate_target_file(entry->path, entry->source_size);
 				break;
 
 			case FILE_ACTION_COPY_TAIL:
-				rewind_copy_file_range(entry->path, entry->oldsize,
-									   entry->newsize, false);
+				rewind_copy_file_range(entry->path, entry->target_size,
+									   entry->source_size, false);
 				break;
 
 			case FILE_ACTION_CREATE:
@@ -238,6 +238,10 @@ copy_executeFileMap(filemap_t *map)
 			case FILE_ACTION_REMOVE:
 				remove_target(entry);
 				break;
+
+			case FILE_ACTION_UNDECIDED:
+				pg_fatal("no action decided for \"%s\"", entry->path);
+				break;
 		}
 	}
 
diff --git a/src/bin/pg_rewind/file_ops.c b/src/bin/pg_rewind/file_ops.c
index 55439db20ba..ec37d0b2e0d 100644
--- a/src/bin/pg_rewind/file_ops.c
+++ b/src/bin/pg_rewind/file_ops.c
@@ -126,8 +126,9 @@ void
 remove_target(file_entry_t *entry)
 {
 	Assert(entry->action == FILE_ACTION_REMOVE);
+	Assert(entry->target_exists);
 
-	switch (entry->type)
+	switch (entry->target_type)
 	{
 		case FILE_TYPE_DIRECTORY:
 			remove_target_dir(entry->path);
@@ -140,6 +141,10 @@ remove_target(file_entry_t *entry)
 		case FILE_TYPE_SYMLINK:
 			remove_target_symlink(entry->path);
 			break;
+
+		case FILE_TYPE_UNDEFINED:
+			pg_fatal("undefined file type for \"%s\"", entry->path);
+			break;
 	}
 }
 
@@ -147,21 +152,26 @@ void
 create_target(file_entry_t *entry)
 {
 	Assert(entry->action == FILE_ACTION_CREATE);
+	Assert(!entry->target_exists);
 
-	switch (entry->type)
+	switch (entry->source_type)
 	{
 		case FILE_TYPE_DIRECTORY:
 			create_target_dir(entry->path);
 			break;
 
 		case FILE_TYPE_SYMLINK:
-			create_target_symlink(entry->path, entry->link_target);
+			create_target_symlink(entry->path, entry->source_link_target);
 			break;
 
 		case FILE_TYPE_REGULAR:
 			/* can't happen. Regular files are created with open_target_file. */
 			pg_fatal("invalid action (CREATE) for regular file");
 			break;
+
+		case FILE_TYPE_UNDEFINED:
+			pg_fatal("undefined file type for \"%s\"", entry->path);
+			break;
 	}
 }
 
diff --git a/src/bin/pg_rewind/filemap.c b/src/bin/pg_rewind/filemap.c
index 1879257b66a..7971daeda5e 100644
--- a/src/bin/pg_rewind/filemap.c
+++ b/src/bin/pg_rewind/filemap.c
@@ -26,6 +26,8 @@ static bool isRelDataFile(const char *path);
 static char *datasegpath(RelFileNode rnode, ForkNumber forknum,
 						 BlockNumber segno);
 static int	path_cmp(const void *a, const void *b);
+
+static file_entry_t *get_filemap_entry(const char *path, bool create);
 static int	final_filemap_cmp(const void *a, const void *b);
 static void filemap_list_to_array(filemap_t *map);
 static bool check_file_excluded(const char *path, bool is_source);
@@ -146,6 +148,65 @@ filemap_create(void)
 	filemap = map;
 }
 
+/* Look up or create entry for 'path' */
+static file_entry_t *
+get_filemap_entry(const char *path, bool create)
+{
+	filemap_t  *map = filemap;
+	file_entry_t *entry;
+	file_entry_t **e;
+	file_entry_t key;
+	file_entry_t *key_ptr;
+
+	if (map->array)
+	{
+		key.path = (char *) path;
+		key_ptr = &key;
+		e = bsearch(&key_ptr, map->array, map->narray, sizeof(file_entry_t *),
+					path_cmp);
+	}
+	else
+		e = NULL;
+
+	if (e)
+		entry = *e;
+	else if (!create)
+		entry = NULL;
+	else
+	{
+		/* Create a new entry for this file */
+		entry = pg_malloc(sizeof(file_entry_t));
+		entry->path = pg_strdup(path);
+		entry->isrelfile = isRelDataFile(path);
+		entry->action = FILE_ACTION_UNDECIDED;
+
+		entry->target_exists = false;
+		entry->target_type = FILE_TYPE_UNDEFINED;
+		entry->target_size = 0;
+		entry->target_link_target = NULL;
+		entry->target_modified_pages.bitmap = NULL;
+		entry->target_modified_pages.bitmapsize = 0;
+
+		entry->source_exists = false;
+		entry->source_type = FILE_TYPE_UNDEFINED;
+		entry->source_size = 0;
+		entry->source_link_target = NULL;
+
+		entry->next = NULL;
+
+		if (map->last)
+		{
+			map->last->next = entry;
+			map->last = entry;
+		}
+		else
+			map->first = map->last = entry;
+		map->nlist++;
+	}
+
+	return entry;
+}
+
 /*
  * Callback for processing source file list.
  *
@@ -154,25 +215,12 @@ filemap_create(void)
  * exists in the target and whether the size matches.
  */
 void
-process_source_file(const char *path, file_type_t type, size_t newsize,
+process_source_file(const char *path, file_type_t type, size_t size,
 					const char *link_target)
 {
-	bool		exists;
-	char		localpath[MAXPGPATH];
-	struct stat statbuf;
-	filemap_t  *map = filemap;
-	file_action_t action = FILE_ACTION_NONE;
-	size_t		oldsize = 0;
 	file_entry_t *entry;
 
-	Assert(map->array == NULL);
-
-	/*
-	 * Skip any files matching the exclusion filters. This has the effect to
-	 * remove all those files on the target.
-	 */
-	if (check_file_excluded(path, true))
-		return;
+	Assert(filemap->array == NULL);
 
 	/*
 	 * Pretend that pg_wal is a directory, even if it's really a symlink. We
@@ -182,16 +230,6 @@ process_source_file(const char *path, file_type_t type, size_t newsize,
 	if (strcmp(path, "pg_wal") == 0 && type == FILE_TYPE_SYMLINK)
 		type = FILE_TYPE_DIRECTORY;
 
-	/*
-	 * Skip temporary files, .../pgsql_tmp/... and .../pgsql_tmp.* in source.
-	 * This has the effect that all temporary files in the destination will be
-	 * removed.
-	 */
-	if (strstr(path, "/" PG_TEMP_FILE_PREFIX) != NULL)
-		return;
-	if (strstr(path, "/" PG_TEMP_FILES_DIR "/") != NULL)
-		return;
-
 	/*
 	 * sanity check: a filename that looks like a data file better be a
 	 * regular file
@@ -199,142 +237,12 @@ process_source_file(const char *path, file_type_t type, size_t newsize,
 	if (type != FILE_TYPE_REGULAR && isRelDataFile(path))
 		pg_fatal("data file \"%s\" in source is not a regular file", path);
 
-	snprintf(localpath, sizeof(localpath), "%s/%s", datadir_target, path);
-
-	/* Does the corresponding file exist in the target data dir? */
-	if (lstat(localpath, &statbuf) < 0)
-	{
-		if (errno != ENOENT)
-			pg_fatal("could not stat file \"%s\": %m",
-					 localpath);
-
-		exists = false;
-	}
-	else
-		exists = true;
-
-	switch (type)
-	{
-		case FILE_TYPE_DIRECTORY:
-			if (exists && !S_ISDIR(statbuf.st_mode) && strcmp(path, "pg_wal") != 0)
-			{
-				/* it's a directory in source, but not in target. Strange.. */
-				pg_fatal("\"%s\" is not a directory", localpath);
-			}
-
-			if (!exists)
-				action = FILE_ACTION_CREATE;
-			else
-				action = FILE_ACTION_NONE;
-			oldsize = 0;
-			break;
-
-		case FILE_TYPE_SYMLINK:
-			if (exists &&
-#ifndef WIN32
-				!S_ISLNK(statbuf.st_mode)
-#else
-				!pgwin32_is_junction(localpath)
-#endif
-				)
-			{
-				/*
-				 * It's a symbolic link in source, but not in target.
-				 * Strange..
-				 */
-				pg_fatal("\"%s\" is not a symbolic link", localpath);
-			}
-
-			if (!exists)
-				action = FILE_ACTION_CREATE;
-			else
-				action = FILE_ACTION_NONE;
-			oldsize = 0;
-			break;
-
-		case FILE_TYPE_REGULAR:
-			if (exists && !S_ISREG(statbuf.st_mode))
-				pg_fatal("\"%s\" is not a regular file", localpath);
-
-			if (!exists || !isRelDataFile(path))
-			{
-				/*
-				 * File exists in source, but not in target. Or it's a
-				 * non-data file that we have no special processing for. Copy
-				 * it in toto.
-				 *
-				 * An exception: PG_VERSIONs should be identical, but avoid
-				 * overwriting it for paranoia.
-				 */
-				if (pg_str_endswith(path, "PG_VERSION"))
-				{
-					action = FILE_ACTION_NONE;
-					oldsize = statbuf.st_size;
-				}
-				else
-				{
-					action = FILE_ACTION_COPY;
-					oldsize = 0;
-				}
-			}
-			else
-			{
-				/*
-				 * It's a data file that exists in both.
-				 *
-				 * If it's larger in target, we can truncate it. There will
-				 * also be a WAL record of the truncation in the source
-				 * system, so WAL replay would eventually truncate the target
-				 * too, but we might as well do it now.
-				 *
-				 * If it's smaller in the target, it means that it has been
-				 * truncated in the target, or enlarged in the source, or
-				 * both. If it was truncated in the target, we need to copy
-				 * the missing tail from the source system. If it was enlarged
-				 * in the source system, there will be WAL records in the
-				 * source system for the new blocks, so we wouldn't need to
-				 * copy them here. But we don't know which scenario we're
-				 * dealing with, and there's no harm in copying the missing
-				 * blocks now, so do it now.
-				 *
-				 * If it's the same size, do nothing here. Any blocks modified
-				 * in the target will be copied based on parsing the target
-				 * system's WAL, and any blocks modified in the source will be
-				 * updated after rewinding, when the source system's WAL is
-				 * replayed.
-				 */
-				oldsize = statbuf.st_size;
-				if (oldsize < newsize)
-					action = FILE_ACTION_COPY_TAIL;
-				else if (oldsize > newsize)
-					action = FILE_ACTION_TRUNCATE;
-				else
-					action = FILE_ACTION_NONE;
-			}
-			break;
-	}
-
-	/* Create a new entry for this file */
-	entry = pg_malloc(sizeof(file_entry_t));
-	entry->path = pg_strdup(path);
-	entry->type = type;
-	entry->action = action;
-	entry->oldsize = oldsize;
-	entry->newsize = newsize;
-	entry->link_target = link_target ? pg_strdup(link_target) : NULL;
-	entry->next = NULL;
-	entry->pagemap.bitmap = NULL;
-	entry->pagemap.bitmapsize = 0;
-	entry->isrelfile = isRelDataFile(path);
-
-	if (map->last)
-	{
-		map->last->next = entry;
-		map->last = entry;
-	}
-	else
-		map->first = map->last = entry;
-	map->nlist++;
+	/* Remember this source file */
+	entry = get_filemap_entry(path, true);
+	entry->source_exists = true;
+	entry->source_type = type;
+	entry->source_size = size;
+	entry->source_link_target = link_target ? pg_strdup(link_target) : NULL;
 }
 
 /*
@@ -345,14 +253,9 @@ process_source_file(const char *path, file_type_t type, size_t newsize,
  * deletion.
  */
 void
-process_target_file(const char *path, file_type_t type, size_t oldsize,
+process_target_file(const char *path, file_type_t type, size_t size,
 					const char *link_target)
 {
-	bool		exists;
-	char		localpath[MAXPGPATH];
-	struct stat statbuf;
-	file_entry_t key;
-	file_entry_t *key_ptr;
 	filemap_t  *map = filemap;
 	file_entry_t *entry;
 
@@ -361,17 +264,6 @@ process_target_file(const char *path, file_type_t type, size_t oldsize,
 	 * from the target data folder all paths which have been filtered out from
 	 * the source data folder when processing the source files.
 	 */
-
-	snprintf(localpath, sizeof(localpath), "%s/%s", datadir_target, path);
-	if (lstat(localpath, &statbuf) < 0)
-	{
-		if (errno != ENOENT)
-			pg_fatal("could not stat file \"%s\": %m",
-					 localpath);
-
-		exists = false;
-	}
-
 	if (map->array == NULL)
 	{
 		/* on first call, initialize lookup array */
@@ -389,120 +281,76 @@ process_target_file(const char *path, file_type_t type, size_t oldsize,
 	}
 
 	/*
-	 * Like in process_source_file, pretend that xlog is always a  directory.
+	 * Like in process_source_file, pretend that pg_wal is always a directory.
 	 */
 	if (strcmp(path, "pg_wal") == 0 && type == FILE_TYPE_SYMLINK)
 		type = FILE_TYPE_DIRECTORY;
 
-	key.path = (char *) path;
-	key_ptr = &key;
-	exists = (bsearch(&key_ptr, map->array, map->narray, sizeof(file_entry_t *),
-					  path_cmp) != NULL);
-
-	/* Remove any file or folder that doesn't exist in the source system. */
-	if (!exists)
-	{
-		entry = pg_malloc(sizeof(file_entry_t));
-		entry->path = pg_strdup(path);
-		entry->type = type;
-		entry->action = FILE_ACTION_REMOVE;
-		entry->oldsize = oldsize;
-		entry->newsize = 0;
-		entry->link_target = link_target ? pg_strdup(link_target) : NULL;
-		entry->next = NULL;
-		entry->pagemap.bitmap = NULL;
-		entry->pagemap.bitmapsize = 0;
-		entry->isrelfile = isRelDataFile(path);
-
-		if (map->last == NULL)
-			map->first = entry;
-		else
-			map->last->next = entry;
-		map->last = entry;
-		map->nlist++;
-	}
-	else
-	{
-		/*
-		 * We already handled all files that exist in the source system in
-		 * process_source_file().
-		 */
-	}
+	/* Remember this target file */
+	entry = get_filemap_entry(path, true);
+	entry->target_exists = true;
+	entry->target_type = type;
+	entry->target_size = size;
+	entry->target_link_target = link_target ? pg_strdup(link_target) : NULL;
 }
 
 /*
  * This callback gets called while we read the WAL in the target, for every
- * block that have changed in the target system. It makes note of all the
+ * block that have changed in the target system.  It makes note of all the
  * changed blocks in the pagemap of the file.
+ *
+ * NOTE: All the files on both systems must have already been added to the
+ * file map!
  */
 void
-process_block_change(ForkNumber forknum, RelFileNode rnode, BlockNumber blkno)
+process_target_wal_block_change(ForkNumber forknum, RelFileNode rnode,
+								BlockNumber blkno)
 {
 	char	   *path;
-	file_entry_t key;
-	file_entry_t *key_ptr;
 	file_entry_t *entry;
 	BlockNumber blkno_inseg;
 	int			segno;
-	filemap_t  *map = filemap;
-	file_entry_t **e;
 
-	Assert(map->array);
+	Assert(filemap->array);
 
 	segno = blkno / RELSEG_SIZE;
 	blkno_inseg = blkno % RELSEG_SIZE;
 
 	path = datasegpath(rnode, forknum, segno);
-
-	key.path = (char *) path;
-	key_ptr = &key;
-
-	e = bsearch(&key_ptr, map->array, map->narray, sizeof(file_entry_t *),
-				path_cmp);
-	if (e)
-		entry = *e;
-	else
-		entry = NULL;
+	entry = get_filemap_entry(path, false);
 	pfree(path);
 
 	if (entry)
 	{
-		Assert(entry->isrelfile);
-
-		switch (entry->action)
-		{
-			case FILE_ACTION_NONE:
-			case FILE_ACTION_TRUNCATE:
-				/* skip if we're truncating away the modified block anyway */
-				if ((blkno_inseg + 1) * BLCKSZ <= entry->newsize)
-					datapagemap_add(&entry->pagemap, blkno_inseg);
-				break;
+		int64		end_offset;
 
-			case FILE_ACTION_COPY_TAIL:
-
-				/*
-				 * skip the modified block if it is part of the "tail" that
-				 * we're copying anyway.
-				 */
-				if ((blkno_inseg + 1) * BLCKSZ <= entry->oldsize)
-					datapagemap_add(&entry->pagemap, blkno_inseg);
-				break;
+		Assert(entry->isrelfile);
 
-			case FILE_ACTION_COPY:
-			case FILE_ACTION_REMOVE:
-				break;
+		if (entry->target_type != FILE_TYPE_REGULAR)
+			pg_fatal("unexpected page modification for directory or symbolic link \"%s\"",
+					 entry->path);
 
-			case FILE_ACTION_CREATE:
-				pg_fatal("unexpected page modification for directory or symbolic link \"%s\"", entry->path);
-		}
+		/*
+		 * If the block beyond the EOF in the source system, no need to
+		 * remember it now, because we're going to truncate it away from the
+		 * target anyway. Also no need to remember the block if it's beyond
+		 * the current EOF in the target system; we will copy it over with the
+		 * "tail" from the source system, anyway.
+		 */
+		end_offset = (blkno_inseg + 1) * BLCKSZ;
+		if (end_offset <= entry->source_size &&
+			end_offset <= entry->target_size)
+			datapagemap_add(&entry->target_modified_pages, blkno_inseg);
 	}
 	else
 	{
 		/*
 		 * If we don't have any record of this file in the file map, it means
-		 * that it's a relation that doesn't exist in the source system, and
-		 * it was subsequently removed in the target system, too. We can
-		 * safely ignore it.
+		 * that it's a relation that doesn't exist in the source system.  It
+		 * could exist in the target system; we haven't moved the target-only
+		 * entries from the linked list to the array yet!  But in any case, if
+		 * it doesn't exist in the source it will be removed from the target
+		 * too, and we can safely ignore it.
 		 */
 	}
 }
@@ -593,16 +441,6 @@ filemap_list_to_array(filemap_t *map)
 	map->first = map->last = NULL;
 }
 
-void
-filemap_finalize(void)
-{
-	filemap_t  *map = filemap;
-
-	filemap_list_to_array(map);
-	qsort(map->array, map->narray, sizeof(file_entry_t *),
-		  final_filemap_cmp);
-}
-
 static const char *
 action_to_str(file_action_t action)
 {
@@ -643,26 +481,26 @@ calculate_totals(void)
 	{
 		entry = map->array[i];
 
-		if (entry->type != FILE_TYPE_REGULAR)
+		if (entry->source_type != FILE_TYPE_REGULAR)
 			continue;
 
-		map->total_size += entry->newsize;
+		map->total_size += entry->source_size;
 
 		if (entry->action == FILE_ACTION_COPY)
 		{
-			map->fetch_size += entry->newsize;
+			map->fetch_size += entry->source_size;
 			continue;
 		}
 
 		if (entry->action == FILE_ACTION_COPY_TAIL)
-			map->fetch_size += (entry->newsize - entry->oldsize);
+			map->fetch_size += (entry->source_size - entry->target_size);
 
-		if (entry->pagemap.bitmapsize > 0)
+		if (entry->target_modified_pages.bitmapsize > 0)
 		{
 			datapagemap_iterator_t *iter;
 			BlockNumber blk;
 
-			iter = datapagemap_iterate(&entry->pagemap);
+			iter = datapagemap_iterate(&entry->target_modified_pages);
 			while (datapagemap_next(iter, &blk))
 				map->fetch_size += BLCKSZ;
 
@@ -682,13 +520,13 @@ print_filemap(void)
 	{
 		entry = map->array[i];
 		if (entry->action != FILE_ACTION_NONE ||
-			entry->pagemap.bitmapsize > 0)
+			entry->target_modified_pages.bitmapsize > 0)
 		{
 			pg_log_debug("%s (%s)", entry->path,
 						 action_to_str(entry->action));
 
-			if (entry->pagemap.bitmapsize > 0)
-				datapagemap_print(&entry->pagemap);
+			if (entry->target_modified_pages.bitmapsize > 0)
+				datapagemap_print(&entry->target_modified_pages);
 		}
 	}
 	fflush(stdout);
@@ -837,3 +675,180 @@ final_filemap_cmp(const void *a, const void *b)
 	else
 		return strcmp(fa->path, fb->path);
 }
+
+/*
+ * Decide what action to perform to a file.
+ */
+static file_action_t
+decide_file_action(file_entry_t *entry)
+{
+	const char *path = entry->path;
+
+	/*
+	 * Don't touch the control file. It is handled specially, after copying
+	 * all the other files.
+	 */
+	if (strcmp(path, "global/pg_control") == 0)
+		return FILE_ACTION_NONE;
+
+	/*
+	 * Remove all files matching the exclusion filters in the target.
+	 */
+	if (check_file_excluded(path, true))
+	{
+		if (entry->target_exists)
+			return FILE_ACTION_REMOVE;
+		else
+			return FILE_ACTION_NONE;
+	}
+
+	/*
+	 * Also remove all temporary files, .../pgsql_tmp/... and .../pgsql_tmp.*
+	 * in the target.
+	 */
+	if (strstr(path, "/" PG_TEMP_FILE_PREFIX) != NULL ||
+		strstr(path, "/" PG_TEMP_FILES_DIR "/") != NULL)
+	{
+		if (entry->target_exists)
+			return FILE_ACTION_REMOVE;
+		else
+			return FILE_ACTION_NONE;
+	}
+
+	/*
+	 * Handle cases where the file is missing from one of the systems.
+	 */
+	if (!entry->target_exists && entry->source_exists)
+	{
+		/*
+		 * File exists in source, but not in target. Copy it in toto. (If it's
+		 * a relation data file, WAL replay after rewinding should re-create
+		 * it anyway. But there's no harm in copying it now.)
+		 */
+		switch (entry->source_type)
+		{
+			case FILE_TYPE_DIRECTORY:
+			case FILE_TYPE_SYMLINK:
+				return FILE_ACTION_CREATE;
+			case FILE_TYPE_REGULAR:
+				return FILE_ACTION_COPY;
+			case FILE_TYPE_UNDEFINED:
+				pg_fatal("unknown file type for \"%s\"", entry->path);
+				break;
+		}
+	}
+	else if (entry->target_exists && !entry->source_exists)
+	{
+		/* File exists in target, but not source. Remove it. */
+		return FILE_ACTION_REMOVE;
+	}
+	else if (!entry->target_exists && !entry->source_exists)
+	{
+		/*
+		 * Doesn't exist in either server. Why does it have an entry in the
+		 * first place??
+		 */
+		return FILE_ACTION_NONE;
+	}
+
+	/*
+	 * Otherwise, the file exists on both systems
+	 */
+	Assert(entry->target_exists && entry->source_exists);
+
+	if (entry->source_type != entry->target_type)
+	{
+		/* But it's a different kind of object. Strange.. */
+		pg_fatal("file \"%s\" is of different type in source and target", entry->path);
+	}
+
+	/*
+	 * PG_VERSION files should be identical on both systems, but avoid
+	 * overwriting them for paranoia.
+	 */
+	if (pg_str_endswith(entry->path, "PG_VERSION"))
+		return FILE_ACTION_NONE;
+
+	switch (entry->source_type)
+	{
+		case FILE_TYPE_DIRECTORY:
+			return FILE_ACTION_NONE;
+
+		case FILE_TYPE_SYMLINK:
+			/* FIXME: Check if it points to the same target? */
+			return FILE_ACTION_NONE;
+
+		case FILE_TYPE_REGULAR:
+			if (!entry->isrelfile)
+			{
+				/*
+				 * It's a non-data file that we have no special processing
+				 * for. Copy it in toto.
+				 */
+				return FILE_ACTION_COPY;
+			}
+			else
+			{
+				/*
+				 * It's a data file that exists in both systems.
+				 *
+				 * If it's larger in target, we can truncate it. There will
+				 * also be a WAL record of the truncation in the source
+				 * system, so WAL replay would eventually truncate the target
+				 * too, but we might as well do it now.
+				 *
+				 * If it's smaller in the target, it means that it has been
+				 * truncated in the target, or enlarged in the source, or
+				 * both. If it was truncated in the target, we need to copy
+				 * the missing tail from the source system. If it was enlarged
+				 * in the source system, there will be WAL records in the
+				 * source system for the new blocks, so we wouldn't need to
+				 * copy them here. But we don't know which scenario we're
+				 * dealing with, and there's no harm in copying the missing
+				 * blocks now, so do it now.
+				 *
+				 * If it's the same size, do nothing here. Any blocks modified
+				 * in the target will be copied based on parsing the target
+				 * system's WAL, and any blocks modified in the source will be
+				 * updated after rewinding, when the source system's WAL is
+				 * replayed.
+				 */
+				if (entry->target_size < entry->source_size)
+					return FILE_ACTION_COPY_TAIL;
+				else if (entry->target_size > entry->source_size)
+					return FILE_ACTION_TRUNCATE;
+				else
+					return FILE_ACTION_NONE;
+			}
+			break;
+
+		case FILE_TYPE_UNDEFINED:
+			pg_fatal("unknown file type for \"%s\"", path);
+			break;
+	}
+
+	/* unreachable */
+	pg_fatal("could not decide what to do with file \"%s\"", path);
+}
+
+/*
+ * Decide what to do with each file.
+ */
+void
+filemap_finalize()
+{
+	int			i;
+
+	filemap_list_to_array(filemap);
+
+	for (i = 0; i < filemap->narray; i++)
+	{
+		file_entry_t *entry = filemap->array[i];
+
+		entry->action = decide_file_action(entry);
+	}
+
+	/* Sort the actions to the order that they should be performed */
+	qsort(filemap->array, filemap->narray, sizeof(file_entry_t *),
+		  final_filemap_cmp);
+}
diff --git a/src/bin/pg_rewind/filemap.h b/src/bin/pg_rewind/filemap.h
index 0cb7425170c..a5e8df57f40 100644
--- a/src/bin/pg_rewind/filemap.h
+++ b/src/bin/pg_rewind/filemap.h
@@ -14,17 +14,21 @@
 
 /*
  * For every file found in the local or remote system, we have a file entry
- * which says what we are going to do with the file. For relation files,
- * there is also a page map, marking pages in the file that were changed
- * locally.
- *
- * The enum values are sorted in the order we want actions to be processed.
+ * that contains information about the file on both systems.  For relation
+ * files, there is also a page map that marks pages in the file that were
+ * changed in the target after the last common checkpoint.  Each entry also
+ * contains an 'action' field, which says what we are going to do with the
+ * file.
  */
+
+/* these enum values are sorted in the order we want actions to be processed */
 typedef enum
 {
+	FILE_ACTION_UNDECIDED = 0,	/* not decided yet */
+
 	FILE_ACTION_CREATE,			/* create local directory or symbolic link */
 	FILE_ACTION_COPY,			/* copy whole file, overwriting if exists */
-	FILE_ACTION_COPY_TAIL,		/* copy tail from 'oldsize' to 'newsize' */
+	FILE_ACTION_COPY_TAIL,		/* copy tail from 'source_size' to 'target_size' */
 	FILE_ACTION_NONE,			/* no action (we might still copy modified
 								 * blocks based on the parsed WAL) */
 	FILE_ACTION_TRUNCATE,		/* truncate local file to 'newsize' bytes */
@@ -33,6 +37,8 @@ typedef enum
 
 typedef enum
 {
+	FILE_TYPE_UNDEFINED = 0,
+
 	FILE_TYPE_REGULAR,
 	FILE_TYPE_DIRECTORY,
 	FILE_TYPE_SYMLINK
@@ -41,19 +47,30 @@ typedef enum
 typedef struct file_entry_t
 {
 	char	   *path;
-	file_type_t type;
+	bool		isrelfile;		/* is it a relation data file? */
 
-	file_action_t action;
+	/*
+	 * Status of the file in the target.
+	 */
+	bool		target_exists;
+	file_type_t target_type;
+	size_t		target_size; /* for a regular file */
+	char	   *target_link_target; /* for a symlink */
 
-	/* for a regular file */
-	size_t		oldsize;
-	size_t		newsize;
-	bool		isrelfile;		/* is it a relation data file? */
+	datapagemap_t target_modified_pages;
 
-	datapagemap_t pagemap;
+	/*
+	 * Status of the file in the source.
+	 */
+	bool		source_exists;
+	file_type_t source_type;
+	size_t		source_size;
+	char	   *source_link_target; /* for a symlink */
 
-	/* for a symlink */
-	char	   *link_target;
+	/*
+	 * What will we do to the file?
+	 */
+	file_action_t action;
 
 	struct file_entry_t *next;
 } file_entry_t;
@@ -70,20 +87,19 @@ typedef struct filemap_t
 
 	/*
 	 * After processing all the remote files, the entries in the linked list
-	 * are moved to this array. After processing local files, too, all the
+	 * are moved to this array.  After processing local files, too, all the
 	 * local entries are added to the array by filemap_finalize, and sorted in
-	 * the final order. After filemap_finalize, all the entries are in the
+	 * the final order.  After filemap_finalize, all the entries are in the
 	 * array, and the linked list is empty.
 	 */
 	file_entry_t **array;
 	int			narray;			/* current length of array */
 
 	/*
-	 * Summary information. total_size is the total size of the source
-	 * cluster, and fetch_size is the number of bytes that needs to be copied.
+	 * Summary information.
 	 */
-	uint64		total_size;
-	uint64		fetch_size;
+	uint64		total_size;		/* total size of the source cluster */
+	uint64		fetch_size;		/* number of bytes that needs to be copied */
 } filemap_t;
 
 extern filemap_t *filemap;
@@ -94,11 +110,12 @@ extern void print_filemap(void);
 
 /* Functions for populating the filemap */
 extern void process_source_file(const char *path, file_type_t type,
-								size_t newsize, const char *link_target);
+								size_t size, const char *link_target);
 extern void process_target_file(const char *path, file_type_t type,
-								size_t newsize, const char *link_target);
-extern void process_block_change(ForkNumber forknum, RelFileNode rnode,
-								 BlockNumber blkno);
+								size_t size, const char *link_target);
+extern void process_target_wal_block_change(ForkNumber forknum,
+											RelFileNode rnode,
+											BlockNumber blkno);
 extern void filemap_finalize(void);
 
 #endif							/* FILEMAP_H */
diff --git a/src/bin/pg_rewind/libpq_fetch.c b/src/bin/pg_rewind/libpq_fetch.c
index bf4dfc23b96..7fc9161b8c8 100644
--- a/src/bin/pg_rewind/libpq_fetch.c
+++ b/src/bin/pg_rewind/libpq_fetch.c
@@ -465,7 +465,7 @@ libpq_executeFileMap(filemap_t *map)
 		entry = map->array[i];
 
 		/* If this is a relation file, copy the modified blocks */
-		execute_pagemap(&entry->pagemap, entry->path);
+		execute_pagemap(&entry->target_modified_pages, entry->path);
 
 		switch (entry->action)
 		{
@@ -476,15 +476,15 @@ libpq_executeFileMap(filemap_t *map)
 			case FILE_ACTION_COPY:
 				/* Truncate the old file out of the way, if any */
 				open_target_file(entry->path, true);
-				fetch_file_range(entry->path, 0, entry->newsize);
+				fetch_file_range(entry->path, 0, entry->source_size);
 				break;
 
 			case FILE_ACTION_TRUNCATE:
-				truncate_target_file(entry->path, entry->newsize);
+				truncate_target_file(entry->path, entry->source_size);
 				break;
 
 			case FILE_ACTION_COPY_TAIL:
-				fetch_file_range(entry->path, entry->oldsize, entry->newsize);
+				fetch_file_range(entry->path, entry->target_size, entry->source_size);
 				break;
 
 			case FILE_ACTION_REMOVE:
@@ -494,6 +494,10 @@ libpq_executeFileMap(filemap_t *map)
 			case FILE_ACTION_CREATE:
 				create_target(entry);
 				break;
+
+			case FILE_ACTION_UNDECIDED:
+				pg_fatal("no action decided for \"%s\"", entry->path);
+				break;
 		}
 	}
 
diff --git a/src/bin/pg_rewind/parsexlog.c b/src/bin/pg_rewind/parsexlog.c
index 2229c86f9af..2baeb74ae93 100644
--- a/src/bin/pg_rewind/parsexlog.c
+++ b/src/bin/pg_rewind/parsexlog.c
@@ -436,6 +436,6 @@ extractPageInfo(XLogReaderState *record)
 		if (forknum != MAIN_FORKNUM)
 			continue;
 
-		process_block_change(forknum, rnode, blkno);
+		process_target_wal_block_change(forknum, rnode, blkno);
 	}
 }
diff --git a/src/bin/pg_rewind/pg_rewind.c b/src/bin/pg_rewind/pg_rewind.c
index c9b9e480c0f..210984d302b 100644
--- a/src/bin/pg_rewind/pg_rewind.c
+++ b/src/bin/pg_rewind/pg_rewind.c
@@ -369,7 +369,7 @@ main(int argc, char **argv)
 				chkpttli);
 
 	/*
-	 * Build the filemap, by comparing the source and target data directories.
+	 * Collect information about all files in the target and source systems.
 	 */
 	filemap_create();
 	if (showprogress)
@@ -390,8 +390,12 @@ main(int argc, char **argv)
 		pg_log_info("reading WAL in target");
 	extractPageMap(datadir_target, chkptrec, lastcommontliIndex,
 				   ControlFile_target.checkPoint, restore_command);
-	filemap_finalize();
 
+	/*
+	 * We have collected all information we need from both systems. Decide
+	 * what to do with each file.
+	 */
+	filemap_finalize();
 	if (showprogress)
 		calculate_totals();
 
-- 
2.20.1


--------------BF34D0120055DC3839060F92
Content-Type: text/x-patch; charset=UTF-8;
 name="v2-0003-pg_rewind-Replace-the-hybrid-list-array-data-stru.patch"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
 filename*0="v2-0003-pg_rewind-Replace-the-hybrid-list-array-data-stru.pa";
 filename*1="tch"



^ permalink  raw  reply  [nested|flat] 41+ messages in thread

* [PATCH v2 2/5] Refactor pg_rewind for more clear decision making.
@ 2020-08-19 12:34 Heikki Linnakangas <[email protected]>
  0 siblings, 0 replies; 41+ messages in thread

From: Heikki Linnakangas @ 2020-08-19 12:34 UTC (permalink / raw)

Deciding what to do with each file is now a separate step after all the
necessary information has been gathered. It is more clear that way.
Previously, the decision-making was divided between process_source_file()
and process_target_file(), and it was a bit hard to piece together what the
overall rules were.
---
 src/bin/pg_rewind/copy_fetch.c  |  14 +-
 src/bin/pg_rewind/file_ops.c    |  16 +-
 src/bin/pg_rewind/filemap.c     | 569 ++++++++++++++++----------------
 src/bin/pg_rewind/filemap.h     |  67 ++--
 src/bin/pg_rewind/libpq_fetch.c |  12 +-
 src/bin/pg_rewind/parsexlog.c   |   2 +-
 src/bin/pg_rewind/pg_rewind.c   |   8 +-
 7 files changed, 371 insertions(+), 317 deletions(-)

diff --git a/src/bin/pg_rewind/copy_fetch.c b/src/bin/pg_rewind/copy_fetch.c
index 1edab5f1867..18fad32600e 100644
--- a/src/bin/pg_rewind/copy_fetch.c
+++ b/src/bin/pg_rewind/copy_fetch.c
@@ -210,7 +210,7 @@ copy_executeFileMap(filemap_t *map)
 	for (i = 0; i < map->narray; i++)
 	{
 		entry = map->array[i];
-		execute_pagemap(&entry->pagemap, entry->path);
+		execute_pagemap(&entry->target_modified_pages, entry->path);
 
 		switch (entry->action)
 		{
@@ -219,16 +219,16 @@ copy_executeFileMap(filemap_t *map)
 				break;
 
 			case FILE_ACTION_COPY:
-				rewind_copy_file_range(entry->path, 0, entry->newsize, true);
+				rewind_copy_file_range(entry->path, 0, entry->source_size, true);
 				break;
 
 			case FILE_ACTION_TRUNCATE:
-				truncate_target_file(entry->path, entry->newsize);
+				truncate_target_file(entry->path, entry->source_size);
 				break;
 
 			case FILE_ACTION_COPY_TAIL:
-				rewind_copy_file_range(entry->path, entry->oldsize,
-									   entry->newsize, false);
+				rewind_copy_file_range(entry->path, entry->target_size,
+									   entry->source_size, false);
 				break;
 
 			case FILE_ACTION_CREATE:
@@ -238,6 +238,10 @@ copy_executeFileMap(filemap_t *map)
 			case FILE_ACTION_REMOVE:
 				remove_target(entry);
 				break;
+
+			case FILE_ACTION_UNDECIDED:
+				pg_fatal("no action decided for \"%s\"", entry->path);
+				break;
 		}
 	}
 
diff --git a/src/bin/pg_rewind/file_ops.c b/src/bin/pg_rewind/file_ops.c
index 55439db20ba..ec37d0b2e0d 100644
--- a/src/bin/pg_rewind/file_ops.c
+++ b/src/bin/pg_rewind/file_ops.c
@@ -126,8 +126,9 @@ void
 remove_target(file_entry_t *entry)
 {
 	Assert(entry->action == FILE_ACTION_REMOVE);
+	Assert(entry->target_exists);
 
-	switch (entry->type)
+	switch (entry->target_type)
 	{
 		case FILE_TYPE_DIRECTORY:
 			remove_target_dir(entry->path);
@@ -140,6 +141,10 @@ remove_target(file_entry_t *entry)
 		case FILE_TYPE_SYMLINK:
 			remove_target_symlink(entry->path);
 			break;
+
+		case FILE_TYPE_UNDEFINED:
+			pg_fatal("undefined file type for \"%s\"", entry->path);
+			break;
 	}
 }
 
@@ -147,21 +152,26 @@ void
 create_target(file_entry_t *entry)
 {
 	Assert(entry->action == FILE_ACTION_CREATE);
+	Assert(!entry->target_exists);
 
-	switch (entry->type)
+	switch (entry->source_type)
 	{
 		case FILE_TYPE_DIRECTORY:
 			create_target_dir(entry->path);
 			break;
 
 		case FILE_TYPE_SYMLINK:
-			create_target_symlink(entry->path, entry->link_target);
+			create_target_symlink(entry->path, entry->source_link_target);
 			break;
 
 		case FILE_TYPE_REGULAR:
 			/* can't happen. Regular files are created with open_target_file. */
 			pg_fatal("invalid action (CREATE) for regular file");
 			break;
+
+		case FILE_TYPE_UNDEFINED:
+			pg_fatal("undefined file type for \"%s\"", entry->path);
+			break;
 	}
 }
 
diff --git a/src/bin/pg_rewind/filemap.c b/src/bin/pg_rewind/filemap.c
index 1879257b66a..7971daeda5e 100644
--- a/src/bin/pg_rewind/filemap.c
+++ b/src/bin/pg_rewind/filemap.c
@@ -26,6 +26,8 @@ static bool isRelDataFile(const char *path);
 static char *datasegpath(RelFileNode rnode, ForkNumber forknum,
 						 BlockNumber segno);
 static int	path_cmp(const void *a, const void *b);
+
+static file_entry_t *get_filemap_entry(const char *path, bool create);
 static int	final_filemap_cmp(const void *a, const void *b);
 static void filemap_list_to_array(filemap_t *map);
 static bool check_file_excluded(const char *path, bool is_source);
@@ -146,6 +148,65 @@ filemap_create(void)
 	filemap = map;
 }
 
+/* Look up or create entry for 'path' */
+static file_entry_t *
+get_filemap_entry(const char *path, bool create)
+{
+	filemap_t  *map = filemap;
+	file_entry_t *entry;
+	file_entry_t **e;
+	file_entry_t key;
+	file_entry_t *key_ptr;
+
+	if (map->array)
+	{
+		key.path = (char *) path;
+		key_ptr = &key;
+		e = bsearch(&key_ptr, map->array, map->narray, sizeof(file_entry_t *),
+					path_cmp);
+	}
+	else
+		e = NULL;
+
+	if (e)
+		entry = *e;
+	else if (!create)
+		entry = NULL;
+	else
+	{
+		/* Create a new entry for this file */
+		entry = pg_malloc(sizeof(file_entry_t));
+		entry->path = pg_strdup(path);
+		entry->isrelfile = isRelDataFile(path);
+		entry->action = FILE_ACTION_UNDECIDED;
+
+		entry->target_exists = false;
+		entry->target_type = FILE_TYPE_UNDEFINED;
+		entry->target_size = 0;
+		entry->target_link_target = NULL;
+		entry->target_modified_pages.bitmap = NULL;
+		entry->target_modified_pages.bitmapsize = 0;
+
+		entry->source_exists = false;
+		entry->source_type = FILE_TYPE_UNDEFINED;
+		entry->source_size = 0;
+		entry->source_link_target = NULL;
+
+		entry->next = NULL;
+
+		if (map->last)
+		{
+			map->last->next = entry;
+			map->last = entry;
+		}
+		else
+			map->first = map->last = entry;
+		map->nlist++;
+	}
+
+	return entry;
+}
+
 /*
  * Callback for processing source file list.
  *
@@ -154,25 +215,12 @@ filemap_create(void)
  * exists in the target and whether the size matches.
  */
 void
-process_source_file(const char *path, file_type_t type, size_t newsize,
+process_source_file(const char *path, file_type_t type, size_t size,
 					const char *link_target)
 {
-	bool		exists;
-	char		localpath[MAXPGPATH];
-	struct stat statbuf;
-	filemap_t  *map = filemap;
-	file_action_t action = FILE_ACTION_NONE;
-	size_t		oldsize = 0;
 	file_entry_t *entry;
 
-	Assert(map->array == NULL);
-
-	/*
-	 * Skip any files matching the exclusion filters. This has the effect to
-	 * remove all those files on the target.
-	 */
-	if (check_file_excluded(path, true))
-		return;
+	Assert(filemap->array == NULL);
 
 	/*
 	 * Pretend that pg_wal is a directory, even if it's really a symlink. We
@@ -182,16 +230,6 @@ process_source_file(const char *path, file_type_t type, size_t newsize,
 	if (strcmp(path, "pg_wal") == 0 && type == FILE_TYPE_SYMLINK)
 		type = FILE_TYPE_DIRECTORY;
 
-	/*
-	 * Skip temporary files, .../pgsql_tmp/... and .../pgsql_tmp.* in source.
-	 * This has the effect that all temporary files in the destination will be
-	 * removed.
-	 */
-	if (strstr(path, "/" PG_TEMP_FILE_PREFIX) != NULL)
-		return;
-	if (strstr(path, "/" PG_TEMP_FILES_DIR "/") != NULL)
-		return;
-
 	/*
 	 * sanity check: a filename that looks like a data file better be a
 	 * regular file
@@ -199,142 +237,12 @@ process_source_file(const char *path, file_type_t type, size_t newsize,
 	if (type != FILE_TYPE_REGULAR && isRelDataFile(path))
 		pg_fatal("data file \"%s\" in source is not a regular file", path);
 
-	snprintf(localpath, sizeof(localpath), "%s/%s", datadir_target, path);
-
-	/* Does the corresponding file exist in the target data dir? */
-	if (lstat(localpath, &statbuf) < 0)
-	{
-		if (errno != ENOENT)
-			pg_fatal("could not stat file \"%s\": %m",
-					 localpath);
-
-		exists = false;
-	}
-	else
-		exists = true;
-
-	switch (type)
-	{
-		case FILE_TYPE_DIRECTORY:
-			if (exists && !S_ISDIR(statbuf.st_mode) && strcmp(path, "pg_wal") != 0)
-			{
-				/* it's a directory in source, but not in target. Strange.. */
-				pg_fatal("\"%s\" is not a directory", localpath);
-			}
-
-			if (!exists)
-				action = FILE_ACTION_CREATE;
-			else
-				action = FILE_ACTION_NONE;
-			oldsize = 0;
-			break;
-
-		case FILE_TYPE_SYMLINK:
-			if (exists &&
-#ifndef WIN32
-				!S_ISLNK(statbuf.st_mode)
-#else
-				!pgwin32_is_junction(localpath)
-#endif
-				)
-			{
-				/*
-				 * It's a symbolic link in source, but not in target.
-				 * Strange..
-				 */
-				pg_fatal("\"%s\" is not a symbolic link", localpath);
-			}
-
-			if (!exists)
-				action = FILE_ACTION_CREATE;
-			else
-				action = FILE_ACTION_NONE;
-			oldsize = 0;
-			break;
-
-		case FILE_TYPE_REGULAR:
-			if (exists && !S_ISREG(statbuf.st_mode))
-				pg_fatal("\"%s\" is not a regular file", localpath);
-
-			if (!exists || !isRelDataFile(path))
-			{
-				/*
-				 * File exists in source, but not in target. Or it's a
-				 * non-data file that we have no special processing for. Copy
-				 * it in toto.
-				 *
-				 * An exception: PG_VERSIONs should be identical, but avoid
-				 * overwriting it for paranoia.
-				 */
-				if (pg_str_endswith(path, "PG_VERSION"))
-				{
-					action = FILE_ACTION_NONE;
-					oldsize = statbuf.st_size;
-				}
-				else
-				{
-					action = FILE_ACTION_COPY;
-					oldsize = 0;
-				}
-			}
-			else
-			{
-				/*
-				 * It's a data file that exists in both.
-				 *
-				 * If it's larger in target, we can truncate it. There will
-				 * also be a WAL record of the truncation in the source
-				 * system, so WAL replay would eventually truncate the target
-				 * too, but we might as well do it now.
-				 *
-				 * If it's smaller in the target, it means that it has been
-				 * truncated in the target, or enlarged in the source, or
-				 * both. If it was truncated in the target, we need to copy
-				 * the missing tail from the source system. If it was enlarged
-				 * in the source system, there will be WAL records in the
-				 * source system for the new blocks, so we wouldn't need to
-				 * copy them here. But we don't know which scenario we're
-				 * dealing with, and there's no harm in copying the missing
-				 * blocks now, so do it now.
-				 *
-				 * If it's the same size, do nothing here. Any blocks modified
-				 * in the target will be copied based on parsing the target
-				 * system's WAL, and any blocks modified in the source will be
-				 * updated after rewinding, when the source system's WAL is
-				 * replayed.
-				 */
-				oldsize = statbuf.st_size;
-				if (oldsize < newsize)
-					action = FILE_ACTION_COPY_TAIL;
-				else if (oldsize > newsize)
-					action = FILE_ACTION_TRUNCATE;
-				else
-					action = FILE_ACTION_NONE;
-			}
-			break;
-	}
-
-	/* Create a new entry for this file */
-	entry = pg_malloc(sizeof(file_entry_t));
-	entry->path = pg_strdup(path);
-	entry->type = type;
-	entry->action = action;
-	entry->oldsize = oldsize;
-	entry->newsize = newsize;
-	entry->link_target = link_target ? pg_strdup(link_target) : NULL;
-	entry->next = NULL;
-	entry->pagemap.bitmap = NULL;
-	entry->pagemap.bitmapsize = 0;
-	entry->isrelfile = isRelDataFile(path);
-
-	if (map->last)
-	{
-		map->last->next = entry;
-		map->last = entry;
-	}
-	else
-		map->first = map->last = entry;
-	map->nlist++;
+	/* Remember this source file */
+	entry = get_filemap_entry(path, true);
+	entry->source_exists = true;
+	entry->source_type = type;
+	entry->source_size = size;
+	entry->source_link_target = link_target ? pg_strdup(link_target) : NULL;
 }
 
 /*
@@ -345,14 +253,9 @@ process_source_file(const char *path, file_type_t type, size_t newsize,
  * deletion.
  */
 void
-process_target_file(const char *path, file_type_t type, size_t oldsize,
+process_target_file(const char *path, file_type_t type, size_t size,
 					const char *link_target)
 {
-	bool		exists;
-	char		localpath[MAXPGPATH];
-	struct stat statbuf;
-	file_entry_t key;
-	file_entry_t *key_ptr;
 	filemap_t  *map = filemap;
 	file_entry_t *entry;
 
@@ -361,17 +264,6 @@ process_target_file(const char *path, file_type_t type, size_t oldsize,
 	 * from the target data folder all paths which have been filtered out from
 	 * the source data folder when processing the source files.
 	 */
-
-	snprintf(localpath, sizeof(localpath), "%s/%s", datadir_target, path);
-	if (lstat(localpath, &statbuf) < 0)
-	{
-		if (errno != ENOENT)
-			pg_fatal("could not stat file \"%s\": %m",
-					 localpath);
-
-		exists = false;
-	}
-
 	if (map->array == NULL)
 	{
 		/* on first call, initialize lookup array */
@@ -389,120 +281,76 @@ process_target_file(const char *path, file_type_t type, size_t oldsize,
 	}
 
 	/*
-	 * Like in process_source_file, pretend that xlog is always a  directory.
+	 * Like in process_source_file, pretend that pg_wal is always a directory.
 	 */
 	if (strcmp(path, "pg_wal") == 0 && type == FILE_TYPE_SYMLINK)
 		type = FILE_TYPE_DIRECTORY;
 
-	key.path = (char *) path;
-	key_ptr = &key;
-	exists = (bsearch(&key_ptr, map->array, map->narray, sizeof(file_entry_t *),
-					  path_cmp) != NULL);
-
-	/* Remove any file or folder that doesn't exist in the source system. */
-	if (!exists)
-	{
-		entry = pg_malloc(sizeof(file_entry_t));
-		entry->path = pg_strdup(path);
-		entry->type = type;
-		entry->action = FILE_ACTION_REMOVE;
-		entry->oldsize = oldsize;
-		entry->newsize = 0;
-		entry->link_target = link_target ? pg_strdup(link_target) : NULL;
-		entry->next = NULL;
-		entry->pagemap.bitmap = NULL;
-		entry->pagemap.bitmapsize = 0;
-		entry->isrelfile = isRelDataFile(path);
-
-		if (map->last == NULL)
-			map->first = entry;
-		else
-			map->last->next = entry;
-		map->last = entry;
-		map->nlist++;
-	}
-	else
-	{
-		/*
-		 * We already handled all files that exist in the source system in
-		 * process_source_file().
-		 */
-	}
+	/* Remember this target file */
+	entry = get_filemap_entry(path, true);
+	entry->target_exists = true;
+	entry->target_type = type;
+	entry->target_size = size;
+	entry->target_link_target = link_target ? pg_strdup(link_target) : NULL;
 }
 
 /*
  * This callback gets called while we read the WAL in the target, for every
- * block that have changed in the target system. It makes note of all the
+ * block that have changed in the target system.  It makes note of all the
  * changed blocks in the pagemap of the file.
+ *
+ * NOTE: All the files on both systems must have already been added to the
+ * file map!
  */
 void
-process_block_change(ForkNumber forknum, RelFileNode rnode, BlockNumber blkno)
+process_target_wal_block_change(ForkNumber forknum, RelFileNode rnode,
+								BlockNumber blkno)
 {
 	char	   *path;
-	file_entry_t key;
-	file_entry_t *key_ptr;
 	file_entry_t *entry;
 	BlockNumber blkno_inseg;
 	int			segno;
-	filemap_t  *map = filemap;
-	file_entry_t **e;
 
-	Assert(map->array);
+	Assert(filemap->array);
 
 	segno = blkno / RELSEG_SIZE;
 	blkno_inseg = blkno % RELSEG_SIZE;
 
 	path = datasegpath(rnode, forknum, segno);
-
-	key.path = (char *) path;
-	key_ptr = &key;
-
-	e = bsearch(&key_ptr, map->array, map->narray, sizeof(file_entry_t *),
-				path_cmp);
-	if (e)
-		entry = *e;
-	else
-		entry = NULL;
+	entry = get_filemap_entry(path, false);
 	pfree(path);
 
 	if (entry)
 	{
-		Assert(entry->isrelfile);
-
-		switch (entry->action)
-		{
-			case FILE_ACTION_NONE:
-			case FILE_ACTION_TRUNCATE:
-				/* skip if we're truncating away the modified block anyway */
-				if ((blkno_inseg + 1) * BLCKSZ <= entry->newsize)
-					datapagemap_add(&entry->pagemap, blkno_inseg);
-				break;
+		int64		end_offset;
 
-			case FILE_ACTION_COPY_TAIL:
-
-				/*
-				 * skip the modified block if it is part of the "tail" that
-				 * we're copying anyway.
-				 */
-				if ((blkno_inseg + 1) * BLCKSZ <= entry->oldsize)
-					datapagemap_add(&entry->pagemap, blkno_inseg);
-				break;
+		Assert(entry->isrelfile);
 
-			case FILE_ACTION_COPY:
-			case FILE_ACTION_REMOVE:
-				break;
+		if (entry->target_type != FILE_TYPE_REGULAR)
+			pg_fatal("unexpected page modification for directory or symbolic link \"%s\"",
+					 entry->path);
 
-			case FILE_ACTION_CREATE:
-				pg_fatal("unexpected page modification for directory or symbolic link \"%s\"", entry->path);
-		}
+		/*
+		 * If the block beyond the EOF in the source system, no need to
+		 * remember it now, because we're going to truncate it away from the
+		 * target anyway. Also no need to remember the block if it's beyond
+		 * the current EOF in the target system; we will copy it over with the
+		 * "tail" from the source system, anyway.
+		 */
+		end_offset = (blkno_inseg + 1) * BLCKSZ;
+		if (end_offset <= entry->source_size &&
+			end_offset <= entry->target_size)
+			datapagemap_add(&entry->target_modified_pages, blkno_inseg);
 	}
 	else
 	{
 		/*
 		 * If we don't have any record of this file in the file map, it means
-		 * that it's a relation that doesn't exist in the source system, and
-		 * it was subsequently removed in the target system, too. We can
-		 * safely ignore it.
+		 * that it's a relation that doesn't exist in the source system.  It
+		 * could exist in the target system; we haven't moved the target-only
+		 * entries from the linked list to the array yet!  But in any case, if
+		 * it doesn't exist in the source it will be removed from the target
+		 * too, and we can safely ignore it.
 		 */
 	}
 }
@@ -593,16 +441,6 @@ filemap_list_to_array(filemap_t *map)
 	map->first = map->last = NULL;
 }
 
-void
-filemap_finalize(void)
-{
-	filemap_t  *map = filemap;
-
-	filemap_list_to_array(map);
-	qsort(map->array, map->narray, sizeof(file_entry_t *),
-		  final_filemap_cmp);
-}
-
 static const char *
 action_to_str(file_action_t action)
 {
@@ -643,26 +481,26 @@ calculate_totals(void)
 	{
 		entry = map->array[i];
 
-		if (entry->type != FILE_TYPE_REGULAR)
+		if (entry->source_type != FILE_TYPE_REGULAR)
 			continue;
 
-		map->total_size += entry->newsize;
+		map->total_size += entry->source_size;
 
 		if (entry->action == FILE_ACTION_COPY)
 		{
-			map->fetch_size += entry->newsize;
+			map->fetch_size += entry->source_size;
 			continue;
 		}
 
 		if (entry->action == FILE_ACTION_COPY_TAIL)
-			map->fetch_size += (entry->newsize - entry->oldsize);
+			map->fetch_size += (entry->source_size - entry->target_size);
 
-		if (entry->pagemap.bitmapsize > 0)
+		if (entry->target_modified_pages.bitmapsize > 0)
 		{
 			datapagemap_iterator_t *iter;
 			BlockNumber blk;
 
-			iter = datapagemap_iterate(&entry->pagemap);
+			iter = datapagemap_iterate(&entry->target_modified_pages);
 			while (datapagemap_next(iter, &blk))
 				map->fetch_size += BLCKSZ;
 
@@ -682,13 +520,13 @@ print_filemap(void)
 	{
 		entry = map->array[i];
 		if (entry->action != FILE_ACTION_NONE ||
-			entry->pagemap.bitmapsize > 0)
+			entry->target_modified_pages.bitmapsize > 0)
 		{
 			pg_log_debug("%s (%s)", entry->path,
 						 action_to_str(entry->action));
 
-			if (entry->pagemap.bitmapsize > 0)
-				datapagemap_print(&entry->pagemap);
+			if (entry->target_modified_pages.bitmapsize > 0)
+				datapagemap_print(&entry->target_modified_pages);
 		}
 	}
 	fflush(stdout);
@@ -837,3 +675,180 @@ final_filemap_cmp(const void *a, const void *b)
 	else
 		return strcmp(fa->path, fb->path);
 }
+
+/*
+ * Decide what action to perform to a file.
+ */
+static file_action_t
+decide_file_action(file_entry_t *entry)
+{
+	const char *path = entry->path;
+
+	/*
+	 * Don't touch the control file. It is handled specially, after copying
+	 * all the other files.
+	 */
+	if (strcmp(path, "global/pg_control") == 0)
+		return FILE_ACTION_NONE;
+
+	/*
+	 * Remove all files matching the exclusion filters in the target.
+	 */
+	if (check_file_excluded(path, true))
+	{
+		if (entry->target_exists)
+			return FILE_ACTION_REMOVE;
+		else
+			return FILE_ACTION_NONE;
+	}
+
+	/*
+	 * Also remove all temporary files, .../pgsql_tmp/... and .../pgsql_tmp.*
+	 * in the target.
+	 */
+	if (strstr(path, "/" PG_TEMP_FILE_PREFIX) != NULL ||
+		strstr(path, "/" PG_TEMP_FILES_DIR "/") != NULL)
+	{
+		if (entry->target_exists)
+			return FILE_ACTION_REMOVE;
+		else
+			return FILE_ACTION_NONE;
+	}
+
+	/*
+	 * Handle cases where the file is missing from one of the systems.
+	 */
+	if (!entry->target_exists && entry->source_exists)
+	{
+		/*
+		 * File exists in source, but not in target. Copy it in toto. (If it's
+		 * a relation data file, WAL replay after rewinding should re-create
+		 * it anyway. But there's no harm in copying it now.)
+		 */
+		switch (entry->source_type)
+		{
+			case FILE_TYPE_DIRECTORY:
+			case FILE_TYPE_SYMLINK:
+				return FILE_ACTION_CREATE;
+			case FILE_TYPE_REGULAR:
+				return FILE_ACTION_COPY;
+			case FILE_TYPE_UNDEFINED:
+				pg_fatal("unknown file type for \"%s\"", entry->path);
+				break;
+		}
+	}
+	else if (entry->target_exists && !entry->source_exists)
+	{
+		/* File exists in target, but not source. Remove it. */
+		return FILE_ACTION_REMOVE;
+	}
+	else if (!entry->target_exists && !entry->source_exists)
+	{
+		/*
+		 * Doesn't exist in either server. Why does it have an entry in the
+		 * first place??
+		 */
+		return FILE_ACTION_NONE;
+	}
+
+	/*
+	 * Otherwise, the file exists on both systems
+	 */
+	Assert(entry->target_exists && entry->source_exists);
+
+	if (entry->source_type != entry->target_type)
+	{
+		/* But it's a different kind of object. Strange.. */
+		pg_fatal("file \"%s\" is of different type in source and target", entry->path);
+	}
+
+	/*
+	 * PG_VERSION files should be identical on both systems, but avoid
+	 * overwriting them for paranoia.
+	 */
+	if (pg_str_endswith(entry->path, "PG_VERSION"))
+		return FILE_ACTION_NONE;
+
+	switch (entry->source_type)
+	{
+		case FILE_TYPE_DIRECTORY:
+			return FILE_ACTION_NONE;
+
+		case FILE_TYPE_SYMLINK:
+			/* FIXME: Check if it points to the same target? */
+			return FILE_ACTION_NONE;
+
+		case FILE_TYPE_REGULAR:
+			if (!entry->isrelfile)
+			{
+				/*
+				 * It's a non-data file that we have no special processing
+				 * for. Copy it in toto.
+				 */
+				return FILE_ACTION_COPY;
+			}
+			else
+			{
+				/*
+				 * It's a data file that exists in both systems.
+				 *
+				 * If it's larger in target, we can truncate it. There will
+				 * also be a WAL record of the truncation in the source
+				 * system, so WAL replay would eventually truncate the target
+				 * too, but we might as well do it now.
+				 *
+				 * If it's smaller in the target, it means that it has been
+				 * truncated in the target, or enlarged in the source, or
+				 * both. If it was truncated in the target, we need to copy
+				 * the missing tail from the source system. If it was enlarged
+				 * in the source system, there will be WAL records in the
+				 * source system for the new blocks, so we wouldn't need to
+				 * copy them here. But we don't know which scenario we're
+				 * dealing with, and there's no harm in copying the missing
+				 * blocks now, so do it now.
+				 *
+				 * If it's the same size, do nothing here. Any blocks modified
+				 * in the target will be copied based on parsing the target
+				 * system's WAL, and any blocks modified in the source will be
+				 * updated after rewinding, when the source system's WAL is
+				 * replayed.
+				 */
+				if (entry->target_size < entry->source_size)
+					return FILE_ACTION_COPY_TAIL;
+				else if (entry->target_size > entry->source_size)
+					return FILE_ACTION_TRUNCATE;
+				else
+					return FILE_ACTION_NONE;
+			}
+			break;
+
+		case FILE_TYPE_UNDEFINED:
+			pg_fatal("unknown file type for \"%s\"", path);
+			break;
+	}
+
+	/* unreachable */
+	pg_fatal("could not decide what to do with file \"%s\"", path);
+}
+
+/*
+ * Decide what to do with each file.
+ */
+void
+filemap_finalize()
+{
+	int			i;
+
+	filemap_list_to_array(filemap);
+
+	for (i = 0; i < filemap->narray; i++)
+	{
+		file_entry_t *entry = filemap->array[i];
+
+		entry->action = decide_file_action(entry);
+	}
+
+	/* Sort the actions to the order that they should be performed */
+	qsort(filemap->array, filemap->narray, sizeof(file_entry_t *),
+		  final_filemap_cmp);
+}
diff --git a/src/bin/pg_rewind/filemap.h b/src/bin/pg_rewind/filemap.h
index 0cb7425170c..a5e8df57f40 100644
--- a/src/bin/pg_rewind/filemap.h
+++ b/src/bin/pg_rewind/filemap.h
@@ -14,17 +14,21 @@
 
 /*
  * For every file found in the local or remote system, we have a file entry
- * which says what we are going to do with the file. For relation files,
- * there is also a page map, marking pages in the file that were changed
- * locally.
- *
- * The enum values are sorted in the order we want actions to be processed.
+ * that contains information about the file on both systems.  For relation
+ * files, there is also a page map that marks pages in the file that were
+ * changed in the target after the last common checkpoint.  Each entry also
+ * contains an 'action' field, which says what we are going to do with the
+ * file.
  */
+
+/* these enum values are sorted in the order we want actions to be processed */
 typedef enum
 {
+	FILE_ACTION_UNDECIDED = 0,	/* not decided yet */
+
 	FILE_ACTION_CREATE,			/* create local directory or symbolic link */
 	FILE_ACTION_COPY,			/* copy whole file, overwriting if exists */
-	FILE_ACTION_COPY_TAIL,		/* copy tail from 'oldsize' to 'newsize' */
+	FILE_ACTION_COPY_TAIL,		/* copy tail from 'source_size' to 'target_size' */
 	FILE_ACTION_NONE,			/* no action (we might still copy modified
 								 * blocks based on the parsed WAL) */
 	FILE_ACTION_TRUNCATE,		/* truncate local file to 'newsize' bytes */
@@ -33,6 +37,8 @@ typedef enum
 
 typedef enum
 {
+	FILE_TYPE_UNDEFINED = 0,
+
 	FILE_TYPE_REGULAR,
 	FILE_TYPE_DIRECTORY,
 	FILE_TYPE_SYMLINK
@@ -41,19 +47,30 @@ typedef enum
 typedef struct file_entry_t
 {
 	char	   *path;
-	file_type_t type;
+	bool		isrelfile;		/* is it a relation data file? */
 
-	file_action_t action;
+	/*
+	 * Status of the file in the target.
+	 */
+	bool		target_exists;
+	file_type_t target_type;
+	size_t		target_size; /* for a regular file */
+	char	   *target_link_target; /* for a symlink */
 
-	/* for a regular file */
-	size_t		oldsize;
-	size_t		newsize;
-	bool		isrelfile;		/* is it a relation data file? */
+	datapagemap_t target_modified_pages;
 
-	datapagemap_t pagemap;
+	/*
+	 * Status of the file in the source.
+	 */
+	bool		source_exists;
+	file_type_t source_type;
+	size_t		source_size;
+	char	   *source_link_target; /* for a symlink */
 
-	/* for a symlink */
-	char	   *link_target;
+	/*
+	 * What will we do to the file?
+	 */
+	file_action_t action;
 
 	struct file_entry_t *next;
 } file_entry_t;
@@ -70,20 +87,19 @@ typedef struct filemap_t
 
 	/*
 	 * After processing all the remote files, the entries in the linked list
-	 * are moved to this array. After processing local files, too, all the
+	 * are moved to this array.  After processing local files, too, all the
 	 * local entries are added to the array by filemap_finalize, and sorted in
-	 * the final order. After filemap_finalize, all the entries are in the
+	 * the final order.  After filemap_finalize, all the entries are in the
 	 * array, and the linked list is empty.
 	 */
 	file_entry_t **array;
 	int			narray;			/* current length of array */
 
 	/*
-	 * Summary information. total_size is the total size of the source
-	 * cluster, and fetch_size is the number of bytes that needs to be copied.
+	 * Summary information.
 	 */
-	uint64		total_size;
-	uint64		fetch_size;
+	uint64		total_size;		/* total size of the source cluster */
+	uint64		fetch_size;		/* number of bytes that needs to be copied */
 } filemap_t;
 
 extern filemap_t *filemap;
@@ -94,11 +110,12 @@ extern void print_filemap(void);
 
 /* Functions for populating the filemap */
 extern void process_source_file(const char *path, file_type_t type,
-								size_t newsize, const char *link_target);
+								size_t size, const char *link_target);
 extern void process_target_file(const char *path, file_type_t type,
-								size_t newsize, const char *link_target);
-extern void process_block_change(ForkNumber forknum, RelFileNode rnode,
-								 BlockNumber blkno);
+								size_t size, const char *link_target);
+extern void process_target_wal_block_change(ForkNumber forknum,
+											RelFileNode rnode,
+											BlockNumber blkno);
 extern void filemap_finalize(void);
 
 #endif							/* FILEMAP_H */
diff --git a/src/bin/pg_rewind/libpq_fetch.c b/src/bin/pg_rewind/libpq_fetch.c
index bf4dfc23b96..7fc9161b8c8 100644
--- a/src/bin/pg_rewind/libpq_fetch.c
+++ b/src/bin/pg_rewind/libpq_fetch.c
@@ -465,7 +465,7 @@ libpq_executeFileMap(filemap_t *map)
 		entry = map->array[i];
 
 		/* If this is a relation file, copy the modified blocks */
-		execute_pagemap(&entry->pagemap, entry->path);
+		execute_pagemap(&entry->target_modified_pages, entry->path);
 
 		switch (entry->action)
 		{
@@ -476,15 +476,15 @@ libpq_executeFileMap(filemap_t *map)
 			case FILE_ACTION_COPY:
 				/* Truncate the old file out of the way, if any */
 				open_target_file(entry->path, true);
-				fetch_file_range(entry->path, 0, entry->newsize);
+				fetch_file_range(entry->path, 0, entry->source_size);
 				break;
 
 			case FILE_ACTION_TRUNCATE:
-				truncate_target_file(entry->path, entry->newsize);
+				truncate_target_file(entry->path, entry->source_size);
 				break;
 
 			case FILE_ACTION_COPY_TAIL:
-				fetch_file_range(entry->path, entry->oldsize, entry->newsize);
+				fetch_file_range(entry->path, entry->target_size, entry->source_size);
 				break;
 
 			case FILE_ACTION_REMOVE:
@@ -494,6 +494,10 @@ libpq_executeFileMap(filemap_t *map)
 			case FILE_ACTION_CREATE:
 				create_target(entry);
 				break;
+
+			case FILE_ACTION_UNDECIDED:
+				pg_fatal("no action decided for \"%s\"", entry->path);
+				break;
 		}
 	}
 
diff --git a/src/bin/pg_rewind/parsexlog.c b/src/bin/pg_rewind/parsexlog.c
index 2229c86f9af..2baeb74ae93 100644
--- a/src/bin/pg_rewind/parsexlog.c
+++ b/src/bin/pg_rewind/parsexlog.c
@@ -436,6 +436,6 @@ extractPageInfo(XLogReaderState *record)
 		if (forknum != MAIN_FORKNUM)
 			continue;
 
-		process_block_change(forknum, rnode, blkno);
+		process_target_wal_block_change(forknum, rnode, blkno);
 	}
 }
diff --git a/src/bin/pg_rewind/pg_rewind.c b/src/bin/pg_rewind/pg_rewind.c
index c9b9e480c0f..210984d302b 100644
--- a/src/bin/pg_rewind/pg_rewind.c
+++ b/src/bin/pg_rewind/pg_rewind.c
@@ -369,7 +369,7 @@ main(int argc, char **argv)
 				chkpttli);
 
 	/*
-	 * Build the filemap, by comparing the source and target data directories.
+	 * Collect information about all files in the target and source systems.
 	 */
 	filemap_create();
 	if (showprogress)
@@ -390,8 +390,12 @@ main(int argc, char **argv)
 		pg_log_info("reading WAL in target");
 	extractPageMap(datadir_target, chkptrec, lastcommontliIndex,
 				   ControlFile_target.checkPoint, restore_command);
-	filemap_finalize();
 
+	/*
+	 * We have collected all information we need from both systems. Decide
+	 * what to do with each file.
+	 */
+	filemap_finalize();
 	if (showprogress)
 		calculate_totals();
 
-- 
2.20.1


--------------BF34D0120055DC3839060F92
Content-Type: text/x-patch; charset=UTF-8;
 name="v2-0003-pg_rewind-Replace-the-hybrid-list-array-data-stru.patch"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
 filename*0="v2-0003-pg_rewind-Replace-the-hybrid-list-array-data-stru.pa";
 filename*1="tch"



^ permalink  raw  reply  [nested|flat] 41+ messages in thread

* [PATCH 2/5] Refactor pg_rewind for more clear decision making.
@ 2020-08-19 12:34 Heikki Linnakangas <[email protected]>
  0 siblings, 0 replies; 41+ messages in thread

From: Heikki Linnakangas @ 2020-08-19 12:34 UTC (permalink / raw)

Deciding what to do with each file is now a separate step after all the
necessary information has been gathered. It is more clear that way.
Previously, the decision-making was divided between process_source_file()
and process_target_file(), and it was a bit hard to piece together what the
overall rules were.
---
 src/bin/pg_rewind/copy_fetch.c  |  14 +-
 src/bin/pg_rewind/file_ops.c    |  16 +-
 src/bin/pg_rewind/filemap.c     | 514 ++++++++++++++++----------------
 src/bin/pg_rewind/filemap.h     |  67 +++--
 src/bin/pg_rewind/libpq_fetch.c |  12 +-
 src/bin/pg_rewind/parsexlog.c   |   2 +-
 src/bin/pg_rewind/pg_rewind.c   |   8 +-
 7 files changed, 332 insertions(+), 301 deletions(-)

diff --git a/src/bin/pg_rewind/copy_fetch.c b/src/bin/pg_rewind/copy_fetch.c
index 1edab5f1867..18fad32600e 100644
--- a/src/bin/pg_rewind/copy_fetch.c
+++ b/src/bin/pg_rewind/copy_fetch.c
@@ -210,7 +210,7 @@ copy_executeFileMap(filemap_t *map)
 	for (i = 0; i < map->narray; i++)
 	{
 		entry = map->array[i];
-		execute_pagemap(&entry->pagemap, entry->path);
+		execute_pagemap(&entry->target_modified_pages, entry->path);
 
 		switch (entry->action)
 		{
@@ -219,16 +219,16 @@ copy_executeFileMap(filemap_t *map)
 				break;
 
 			case FILE_ACTION_COPY:
-				rewind_copy_file_range(entry->path, 0, entry->newsize, true);
+				rewind_copy_file_range(entry->path, 0, entry->source_size, true);
 				break;
 
 			case FILE_ACTION_TRUNCATE:
-				truncate_target_file(entry->path, entry->newsize);
+				truncate_target_file(entry->path, entry->source_size);
 				break;
 
 			case FILE_ACTION_COPY_TAIL:
-				rewind_copy_file_range(entry->path, entry->oldsize,
-									   entry->newsize, false);
+				rewind_copy_file_range(entry->path, entry->target_size,
+									   entry->source_size, false);
 				break;
 
 			case FILE_ACTION_CREATE:
@@ -238,6 +238,10 @@ copy_executeFileMap(filemap_t *map)
 			case FILE_ACTION_REMOVE:
 				remove_target(entry);
 				break;
+
+			case FILE_ACTION_UNDECIDED:
+				pg_fatal("no action decided for \"%s\"", entry->path);
+				break;
 		}
 	}
 
diff --git a/src/bin/pg_rewind/file_ops.c b/src/bin/pg_rewind/file_ops.c
index 55439db20ba..ec37d0b2e0d 100644
--- a/src/bin/pg_rewind/file_ops.c
+++ b/src/bin/pg_rewind/file_ops.c
@@ -126,8 +126,9 @@ void
 remove_target(file_entry_t *entry)
 {
 	Assert(entry->action == FILE_ACTION_REMOVE);
+	Assert(entry->target_exists);
 
-	switch (entry->type)
+	switch (entry->target_type)
 	{
 		case FILE_TYPE_DIRECTORY:
 			remove_target_dir(entry->path);
@@ -140,6 +141,10 @@ remove_target(file_entry_t *entry)
 		case FILE_TYPE_SYMLINK:
 			remove_target_symlink(entry->path);
 			break;
+
+		case FILE_TYPE_UNDEFINED:
+			pg_fatal("undefined file type for \"%s\"", entry->path);
+			break;
 	}
 }
 
@@ -147,21 +152,26 @@ void
 create_target(file_entry_t *entry)
 {
 	Assert(entry->action == FILE_ACTION_CREATE);
+	Assert(!entry->target_exists);
 
-	switch (entry->type)
+	switch (entry->source_type)
 	{
 		case FILE_TYPE_DIRECTORY:
 			create_target_dir(entry->path);
 			break;
 
 		case FILE_TYPE_SYMLINK:
-			create_target_symlink(entry->path, entry->link_target);
+			create_target_symlink(entry->path, entry->source_link_target);
 			break;
 
 		case FILE_TYPE_REGULAR:
 			/* can't happen. Regular files are created with open_target_file. */
 			pg_fatal("invalid action (CREATE) for regular file");
 			break;
+
+		case FILE_TYPE_UNDEFINED:
+			pg_fatal("undefined file type for \"%s\"", entry->path);
+			break;
 	}
 }
 
diff --git a/src/bin/pg_rewind/filemap.c b/src/bin/pg_rewind/filemap.c
index 1879257b66a..431e8e760e0 100644
--- a/src/bin/pg_rewind/filemap.c
+++ b/src/bin/pg_rewind/filemap.c
@@ -26,6 +26,8 @@ static bool isRelDataFile(const char *path);
 static char *datasegpath(RelFileNode rnode, ForkNumber forknum,
 						 BlockNumber segno);
 static int	path_cmp(const void *a, const void *b);
+
+static file_entry_t *get_filemap_entry(const char *path, bool create);
 static int	final_filemap_cmp(const void *a, const void *b);
 static void filemap_list_to_array(filemap_t *map);
 static bool check_file_excluded(const char *path, bool is_source);
@@ -146,6 +148,65 @@ filemap_create(void)
 	filemap = map;
 }
 
+/* Look up or create entry for 'path' */
+static file_entry_t *
+get_filemap_entry(const char *path, bool create)
+{
+	filemap_t  *map = filemap;
+	file_entry_t *entry;
+	file_entry_t **e;
+	file_entry_t key;
+	file_entry_t *key_ptr;
+
+	if (map->array)
+	{
+		key.path = (char *) path;
+		key_ptr = &key;
+		e = bsearch(&key_ptr, map->array, map->narray, sizeof(file_entry_t *),
+					path_cmp);
+	}
+	else
+		e = NULL;
+
+	if (e)
+		entry = *e;
+	else if (!create)
+		entry = NULL;
+	else
+	{
+		/* Create a new entry for this file */
+		entry = pg_malloc(sizeof(file_entry_t));
+		entry->path = pg_strdup(path);
+		entry->isrelfile = isRelDataFile(path);
+		entry->action = FILE_ACTION_UNDECIDED;
+
+		entry->target_exists = false;
+		entry->target_type = FILE_TYPE_UNDEFINED;
+		entry->target_size = 0;
+		entry->target_link_target = NULL;
+		entry->target_modified_pages.bitmap = NULL;
+		entry->target_modified_pages.bitmapsize = 0;
+
+		entry->source_exists = false;
+		entry->source_type = FILE_TYPE_UNDEFINED;
+		entry->source_size = 0;
+		entry->source_link_target = NULL;
+
+		entry->next = NULL;
+
+		if (map->last)
+		{
+			map->last->next = entry;
+			map->last = entry;
+		}
+		else
+			map->first = map->last = entry;
+		map->nlist++;
+	}
+
+	return entry;
+}
+
 /*
  * Callback for processing source file list.
  *
@@ -154,18 +215,19 @@ filemap_create(void)
  * exists in the target and whether the size matches.
  */
 void
-process_source_file(const char *path, file_type_t type, size_t newsize,
+process_source_file(const char *path, file_type_t type, size_t size,
 					const char *link_target)
 {
-	bool		exists;
-	char		localpath[MAXPGPATH];
-	struct stat statbuf;
-	filemap_t  *map = filemap;
-	file_action_t action = FILE_ACTION_NONE;
-	size_t		oldsize = 0;
 	file_entry_t *entry;
 
-	Assert(map->array == NULL);
+	Assert(filemap->array == NULL);
+
+	/*
+	 * Skip the control file. It is handled specially, after copying all the
+	 * other files.
+	 */
+	if (strcmp(path, "global/pg_control") == 0)
+		return;
 
 	/*
 	 * Skip any files matching the exclusion filters. This has the effect to
@@ -199,142 +261,12 @@ process_source_file(const char *path, file_type_t type, size_t newsize,
 	if (type != FILE_TYPE_REGULAR && isRelDataFile(path))
 		pg_fatal("data file \"%s\" in source is not a regular file", path);
 
-	snprintf(localpath, sizeof(localpath), "%s/%s", datadir_target, path);
-
-	/* Does the corresponding file exist in the target data dir? */
-	if (lstat(localpath, &statbuf) < 0)
-	{
-		if (errno != ENOENT)
-			pg_fatal("could not stat file \"%s\": %m",
-					 localpath);
-
-		exists = false;
-	}
-	else
-		exists = true;
-
-	switch (type)
-	{
-		case FILE_TYPE_DIRECTORY:
-			if (exists && !S_ISDIR(statbuf.st_mode) && strcmp(path, "pg_wal") != 0)
-			{
-				/* it's a directory in source, but not in target. Strange.. */
-				pg_fatal("\"%s\" is not a directory", localpath);
-			}
-
-			if (!exists)
-				action = FILE_ACTION_CREATE;
-			else
-				action = FILE_ACTION_NONE;
-			oldsize = 0;
-			break;
-
-		case FILE_TYPE_SYMLINK:
-			if (exists &&
-#ifndef WIN32
-				!S_ISLNK(statbuf.st_mode)
-#else
-				!pgwin32_is_junction(localpath)
-#endif
-				)
-			{
-				/*
-				 * It's a symbolic link in source, but not in target.
-				 * Strange..
-				 */
-				pg_fatal("\"%s\" is not a symbolic link", localpath);
-			}
-
-			if (!exists)
-				action = FILE_ACTION_CREATE;
-			else
-				action = FILE_ACTION_NONE;
-			oldsize = 0;
-			break;
-
-		case FILE_TYPE_REGULAR:
-			if (exists && !S_ISREG(statbuf.st_mode))
-				pg_fatal("\"%s\" is not a regular file", localpath);
-
-			if (!exists || !isRelDataFile(path))
-			{
-				/*
-				 * File exists in source, but not in target. Or it's a
-				 * non-data file that we have no special processing for. Copy
-				 * it in toto.
-				 *
-				 * An exception: PG_VERSIONs should be identical, but avoid
-				 * overwriting it for paranoia.
-				 */
-				if (pg_str_endswith(path, "PG_VERSION"))
-				{
-					action = FILE_ACTION_NONE;
-					oldsize = statbuf.st_size;
-				}
-				else
-				{
-					action = FILE_ACTION_COPY;
-					oldsize = 0;
-				}
-			}
-			else
-			{
-				/*
-				 * It's a data file that exists in both.
-				 *
-				 * If it's larger in target, we can truncate it. There will
-				 * also be a WAL record of the truncation in the source
-				 * system, so WAL replay would eventually truncate the target
-				 * too, but we might as well do it now.
-				 *
-				 * If it's smaller in the target, it means that it has been
-				 * truncated in the target, or enlarged in the source, or
-				 * both. If it was truncated in the target, we need to copy
-				 * the missing tail from the source system. If it was enlarged
-				 * in the source system, there will be WAL records in the
-				 * source system for the new blocks, so we wouldn't need to
-				 * copy them here. But we don't know which scenario we're
-				 * dealing with, and there's no harm in copying the missing
-				 * blocks now, so do it now.
-				 *
-				 * If it's the same size, do nothing here. Any blocks modified
-				 * in the target will be copied based on parsing the target
-				 * system's WAL, and any blocks modified in the source will be
-				 * updated after rewinding, when the source system's WAL is
-				 * replayed.
-				 */
-				oldsize = statbuf.st_size;
-				if (oldsize < newsize)
-					action = FILE_ACTION_COPY_TAIL;
-				else if (oldsize > newsize)
-					action = FILE_ACTION_TRUNCATE;
-				else
-					action = FILE_ACTION_NONE;
-			}
-			break;
-	}
-
-	/* Create a new entry for this file */
-	entry = pg_malloc(sizeof(file_entry_t));
-	entry->path = pg_strdup(path);
-	entry->type = type;
-	entry->action = action;
-	entry->oldsize = oldsize;
-	entry->newsize = newsize;
-	entry->link_target = link_target ? pg_strdup(link_target) : NULL;
-	entry->next = NULL;
-	entry->pagemap.bitmap = NULL;
-	entry->pagemap.bitmapsize = 0;
-	entry->isrelfile = isRelDataFile(path);
-
-	if (map->last)
-	{
-		map->last->next = entry;
-		map->last = entry;
-	}
-	else
-		map->first = map->last = entry;
-	map->nlist++;
+	/* Remember this source file */
+	entry = get_filemap_entry(path, true);
+	entry->source_exists = true;
+	entry->source_type = type;
+	entry->source_size = size;
+	entry->source_link_target = link_target ? pg_strdup(link_target) : NULL;
 }
 
 /*
@@ -345,33 +277,24 @@ process_source_file(const char *path, file_type_t type, size_t newsize,
  * deletion.
  */
 void
-process_target_file(const char *path, file_type_t type, size_t oldsize,
+process_target_file(const char *path, file_type_t type, size_t size,
 					const char *link_target)
 {
-	bool		exists;
-	char		localpath[MAXPGPATH];
-	struct stat statbuf;
-	file_entry_t key;
-	file_entry_t *key_ptr;
 	filemap_t  *map = filemap;
 	file_entry_t *entry;
 
+	/*
+	 * Skip the control file. It is handled specially, after copying all the
+	 * other files.
+	 */
+	if (strcmp(path, "global/pg_control") == 0)
+		return;
+
 	/*
 	 * Do not apply any exclusion filters here.  This has advantage to remove
 	 * from the target data folder all paths which have been filtered out from
 	 * the source data folder when processing the source files.
 	 */
-
-	snprintf(localpath, sizeof(localpath), "%s/%s", datadir_target, path);
-	if (lstat(localpath, &statbuf) < 0)
-	{
-		if (errno != ENOENT)
-			pg_fatal("could not stat file \"%s\": %m",
-					 localpath);
-
-		exists = false;
-	}
-
 	if (map->array == NULL)
 	{
 		/* on first call, initialize lookup array */
@@ -389,120 +312,76 @@ process_target_file(const char *path, file_type_t type, size_t oldsize,
 	}
 
 	/*
-	 * Like in process_source_file, pretend that xlog is always a  directory.
+	 * Like in process_source_file, pretend that pg_wal is always a directory.
 	 */
 	if (strcmp(path, "pg_wal") == 0 && type == FILE_TYPE_SYMLINK)
 		type = FILE_TYPE_DIRECTORY;
 
-	key.path = (char *) path;
-	key_ptr = &key;
-	exists = (bsearch(&key_ptr, map->array, map->narray, sizeof(file_entry_t *),
-					  path_cmp) != NULL);
-
-	/* Remove any file or folder that doesn't exist in the source system. */
-	if (!exists)
-	{
-		entry = pg_malloc(sizeof(file_entry_t));
-		entry->path = pg_strdup(path);
-		entry->type = type;
-		entry->action = FILE_ACTION_REMOVE;
-		entry->oldsize = oldsize;
-		entry->newsize = 0;
-		entry->link_target = link_target ? pg_strdup(link_target) : NULL;
-		entry->next = NULL;
-		entry->pagemap.bitmap = NULL;
-		entry->pagemap.bitmapsize = 0;
-		entry->isrelfile = isRelDataFile(path);
-
-		if (map->last == NULL)
-			map->first = entry;
-		else
-			map->last->next = entry;
-		map->last = entry;
-		map->nlist++;
-	}
-	else
-	{
-		/*
-		 * We already handled all files that exist in the source system in
-		 * process_source_file().
-		 */
-	}
+	/* Remember this target file */
+	entry = get_filemap_entry(path, true);
+	entry->target_exists = true;
+	entry->target_type = type;
+	entry->target_size = size;
+	entry->target_link_target = link_target ? pg_strdup(link_target) : NULL;
 }
 
 /*
  * This callback gets called while we read the WAL in the target, for every
- * block that have changed in the target system. It makes note of all the
+ * block that have changed in the target system.  It makes note of all the
  * changed blocks in the pagemap of the file.
+ *
+ * NOTE: All the files on both systems must have already been added to the
+ * file map!
  */
 void
-process_block_change(ForkNumber forknum, RelFileNode rnode, BlockNumber blkno)
+process_target_wal_block_change(ForkNumber forknum, RelFileNode rnode,
+								BlockNumber blkno)
 {
 	char	   *path;
-	file_entry_t key;
-	file_entry_t *key_ptr;
 	file_entry_t *entry;
 	BlockNumber blkno_inseg;
 	int			segno;
-	filemap_t  *map = filemap;
-	file_entry_t **e;
 
-	Assert(map->array);
+	Assert(filemap->array);
 
 	segno = blkno / RELSEG_SIZE;
 	blkno_inseg = blkno % RELSEG_SIZE;
 
 	path = datasegpath(rnode, forknum, segno);
-
-	key.path = (char *) path;
-	key_ptr = &key;
-
-	e = bsearch(&key_ptr, map->array, map->narray, sizeof(file_entry_t *),
-				path_cmp);
-	if (e)
-		entry = *e;
-	else
-		entry = NULL;
+	entry = get_filemap_entry(path, false);
 	pfree(path);
 
 	if (entry)
 	{
+		int64		end_offset;
+
 		Assert(entry->isrelfile);
 
-		switch (entry->action)
-		{
-			case FILE_ACTION_NONE:
-			case FILE_ACTION_TRUNCATE:
-				/* skip if we're truncating away the modified block anyway */
-				if ((blkno_inseg + 1) * BLCKSZ <= entry->newsize)
-					datapagemap_add(&entry->pagemap, blkno_inseg);
-				break;
-
-			case FILE_ACTION_COPY_TAIL:
-
-				/*
-				 * skip the modified block if it is part of the "tail" that
-				 * we're copying anyway.
-				 */
-				if ((blkno_inseg + 1) * BLCKSZ <= entry->oldsize)
-					datapagemap_add(&entry->pagemap, blkno_inseg);
-				break;
-
-			case FILE_ACTION_COPY:
-			case FILE_ACTION_REMOVE:
-				break;
-
-			case FILE_ACTION_CREATE:
-				pg_fatal("unexpected page modification for directory or symbolic link \"%s\"", entry->path);
-		}
+		if (entry->target_type != FILE_TYPE_REGULAR)
+			pg_fatal("unexpected page modification for directory or symbolic link \"%s\"",
+					 entry->path);
+
+		/*
+		 * If the block beyond the EOF in the source system, no need to
+		 * remember it now, because we're going to truncate it away from the
+		 * target anyway. Also no need to remember the block if it's beyond
+		 * the current EOF in the target system; we will copy it over with the
+		 * "tail" from the source system, anyway.
+		 */
+		end_offset = (blkno_inseg + 1) * BLCKSZ;
+		if (end_offset <= entry->source_size &&
+			end_offset <= entry->target_size)
+			datapagemap_add(&entry->target_modified_pages, blkno_inseg);
 	}
 	else
 	{
 		/*
 		 * If we don't have any record of this file in the file map, it means
-		 * that it's a relation that doesn't exist in the source system, and
-		 * it was subsequently removed in the target system, too. We can
-		 * safely ignore it.
+		 * that it's a relation that doesn't exist in the source system.  It
+		 * could exist in the target system; we haven't moved the target-only
+		 * entries from the linked list to the array yet!  But in any case, if
+		 * it doesn't exist in the source it will be removed from the target
+		 * too, and we can safely ignore it.
 		 */
 	}
 }
@@ -593,16 +472,6 @@ filemap_list_to_array(filemap_t *map)
 	map->first = map->last = NULL;
 }
 
-void
-filemap_finalize(void)
-{
-	filemap_t  *map = filemap;
-
-	filemap_list_to_array(map);
-	qsort(map->array, map->narray, sizeof(file_entry_t *),
-		  final_filemap_cmp);
-}
-
 static const char *
 action_to_str(file_action_t action)
 {
@@ -643,26 +512,26 @@ calculate_totals(void)
 	{
 		entry = map->array[i];
 
-		if (entry->type != FILE_TYPE_REGULAR)
+		if (entry->source_type != FILE_TYPE_REGULAR)
 			continue;
 
-		map->total_size += entry->newsize;
+		map->total_size += entry->source_size;
 
 		if (entry->action == FILE_ACTION_COPY)
 		{
-			map->fetch_size += entry->newsize;
+			map->fetch_size += entry->source_size;
 			continue;
 		}
 
 		if (entry->action == FILE_ACTION_COPY_TAIL)
-			map->fetch_size += (entry->newsize - entry->oldsize);
+			map->fetch_size += (entry->source_size - entry->target_size);
 
-		if (entry->pagemap.bitmapsize > 0)
+		if (entry->target_modified_pages.bitmapsize > 0)
 		{
 			datapagemap_iterator_t *iter;
 			BlockNumber blk;
 
-			iter = datapagemap_iterate(&entry->pagemap);
+			iter = datapagemap_iterate(&entry->target_modified_pages);
 			while (datapagemap_next(iter, &blk))
 				map->fetch_size += BLCKSZ;
 
@@ -682,13 +551,13 @@ print_filemap(void)
 	{
 		entry = map->array[i];
 		if (entry->action != FILE_ACTION_NONE ||
-			entry->pagemap.bitmapsize > 0)
+			entry->target_modified_pages.bitmapsize > 0)
 		{
 			pg_log_debug("%s (%s)", entry->path,
 						 action_to_str(entry->action));
 
-			if (entry->pagemap.bitmapsize > 0)
-				datapagemap_print(&entry->pagemap);
+			if (entry->target_modified_pages.bitmapsize > 0)
+				datapagemap_print(&entry->target_modified_pages);
 		}
 	}
 	fflush(stdout);
@@ -837,3 +706,126 @@ final_filemap_cmp(const void *a, const void *b)
 	else
 		return strcmp(fa->path, fb->path);
 }
+
+/*
+ * Decide what to do with each file.
+ */
+void
+filemap_finalize()
+{
+	int			i;
+
+	filemap_list_to_array(filemap);
+
+	for (i = 0; i < filemap->narray; i++)
+	{
+		file_entry_t *entry = filemap->array[i];
+		file_action_t action;
+
+		if (!entry->target_exists && entry->source_exists)
+		{
+			/*
+			 * File exists in source, but not in target. Copy
+			 * it in toto. (If it's a relation data file, WAL replay
+			 * after rewinding should re-create it anyway. But there's
+			 * no harm in copying it now.)
+			 */
+			if (entry->source_type == FILE_TYPE_DIRECTORY ||
+				entry->source_type == FILE_TYPE_SYMLINK)
+			{
+				action = FILE_ACTION_CREATE;
+			}
+			else
+				action = FILE_ACTION_COPY;
+		}
+		else if (entry->target_exists && !entry->source_exists)
+		{
+			/* Delete file from target */
+			action = FILE_ACTION_REMOVE;
+		}
+		else if (entry->target_exists && entry->source_exists)
+		{
+			/* File exists in both systems */
+			if (entry->source_type != entry->target_type)
+			{
+				/* But it's a different kind of object. Strange.. */
+				pg_fatal("file \"%s\" is of different type in source and target", entry->path);
+			}
+
+			switch (entry->source_type)
+			{
+				case FILE_TYPE_DIRECTORY:
+					action = FILE_ACTION_NONE;
+					break;
+
+				case FILE_TYPE_SYMLINK:
+					// FIXME: Check if it points to the same target?
+					action = FILE_ACTION_NONE;
+					break;
+
+				case FILE_TYPE_REGULAR:
+					if (!entry->isrelfile)
+					{
+						/*
+						 * It's a non-data file that we have no special processing for. Copy
+						 * it in toto.
+						 *
+						 * An exception: PG_VERSIONs should be identical, but avoid
+						 * overwriting it for paranoia.
+						 */
+						if (!pg_str_endswith(entry->path, "PG_VERSION"))
+							action = FILE_ACTION_COPY;
+						else
+							action = FILE_ACTION_NONE;
+					}
+					else
+					{
+						/*
+						 * It's a data file that exists in both systems.
+						 *
+						 * If it's larger in target, we can truncate it. There will
+						 * also be a WAL record of the truncation in the source
+						 * system, so WAL replay would eventually truncate the target
+						 * too, but we might as well do it now.
+						 *
+						 * If it's smaller in the target, it means that it has been
+						 * truncated in the target, or enlarged in the source, or
+						 * both. If it was truncated in the target, we need to copy
+						 * the missing tail from the source system. If it was enlarged
+						 * in the source system, there will be WAL records in the
+						 * source system for the new blocks, so we wouldn't need to
+						 * copy them here. But we don't know which scenario we're
+						 * dealing with, and there's no harm in copying the missing
+						 * blocks now, so do it now.
+						 *
+						 * If it's the same size, do nothing here. Any blocks modified
+						 * in the target will be copied based on parsing the target
+						 * system's WAL, and any blocks modified in the source will be
+						 * updated after rewinding, when the source system's WAL is
+						 * replayed.
+						 */
+						if (entry->target_size < entry->source_size)
+							action = FILE_ACTION_COPY_TAIL;
+						else if (entry->target_size > entry->source_size)
+							action = FILE_ACTION_TRUNCATE;
+						else
+							action = FILE_ACTION_NONE;
+					}
+					break;
+				case FILE_TYPE_UNDEFINED:
+					pg_fatal("unknown file type for \"%s\"", entry->path);
+					break;
+			}
+		}
+		else
+		{
+			/* Doesn't exist in either server. Why does it have an entry at all?? */
+			action = FILE_ACTION_NONE;
+		}
+		entry->action = action;
+	}
+
+	/* Sort the actions to the order that they should be performed */
+	qsort(filemap->array, filemap->narray, sizeof(file_entry_t *),
+		  final_filemap_cmp);
+}
diff --git a/src/bin/pg_rewind/filemap.h b/src/bin/pg_rewind/filemap.h
index 0cb7425170c..a5e8df57f40 100644
--- a/src/bin/pg_rewind/filemap.h
+++ b/src/bin/pg_rewind/filemap.h
@@ -14,17 +14,21 @@
 
 /*
  * For every file found in the local or remote system, we have a file entry
- * which says what we are going to do with the file. For relation files,
- * there is also a page map, marking pages in the file that were changed
- * locally.
- *
- * The enum values are sorted in the order we want actions to be processed.
+ * that contains information about the file on both systems.  For relation
+ * files, there is also a page map that marks pages in the file that were
+ * changed in the target after the last common checkpoint.  Each entry also
+ * contains an 'action' field, which says what we are going to do with the
+ * file.
  */
+
+/* these enum values are sorted in the order we want actions to be processed */
 typedef enum
 {
+	FILE_ACTION_UNDECIDED = 0,	/* not decided yet */
+
 	FILE_ACTION_CREATE,			/* create local directory or symbolic link */
 	FILE_ACTION_COPY,			/* copy whole file, overwriting if exists */
-	FILE_ACTION_COPY_TAIL,		/* copy tail from 'oldsize' to 'newsize' */
+	FILE_ACTION_COPY_TAIL,		/* copy tail from 'source_size' to 'target_size' */
 	FILE_ACTION_NONE,			/* no action (we might still copy modified
 								 * blocks based on the parsed WAL) */
 	FILE_ACTION_TRUNCATE,		/* truncate local file to 'newsize' bytes */
@@ -33,6 +37,8 @@ typedef enum
 
 typedef enum
 {
+	FILE_TYPE_UNDEFINED = 0,
+
 	FILE_TYPE_REGULAR,
 	FILE_TYPE_DIRECTORY,
 	FILE_TYPE_SYMLINK
@@ -41,19 +47,30 @@ typedef enum
 typedef struct file_entry_t
 {
 	char	   *path;
-	file_type_t type;
+	bool		isrelfile;		/* is it a relation data file? */
 
-	file_action_t action;
+	/*
+	 * Status of the file in the target.
+	 */
+	bool		target_exists;
+	file_type_t target_type;
+	size_t		target_size; /* for a regular file */
+	char	   *target_link_target; /* for a symlink */
 
-	/* for a regular file */
-	size_t		oldsize;
-	size_t		newsize;
-	bool		isrelfile;		/* is it a relation data file? */
+	datapagemap_t target_modified_pages;
 
-	datapagemap_t pagemap;
+	/*
+	 * Status of the file in the source.
+	 */
+	bool		source_exists;
+	file_type_t source_type;
+	size_t		source_size;
+	char	   *source_link_target; /* for a symlink */
 
-	/* for a symlink */
-	char	   *link_target;
+	/*
+	 * What will we do to the file?
+	 */
+	file_action_t action;
 
 	struct file_entry_t *next;
 } file_entry_t;
@@ -70,20 +87,19 @@ typedef struct filemap_t
 
 	/*
 	 * After processing all the remote files, the entries in the linked list
-	 * are moved to this array. After processing local files, too, all the
+	 * are moved to this array.  After processing local files, too, all the
 	 * local entries are added to the array by filemap_finalize, and sorted in
-	 * the final order. After filemap_finalize, all the entries are in the
+	 * the final order.  After filemap_finalize, all the entries are in the
 	 * array, and the linked list is empty.
 	 */
 	file_entry_t **array;
 	int			narray;			/* current length of array */
 
 	/*
-	 * Summary information. total_size is the total size of the source
-	 * cluster, and fetch_size is the number of bytes that needs to be copied.
+	 * Summary information.
 	 */
-	uint64		total_size;
-	uint64		fetch_size;
+	uint64		total_size;		/* total size of the source cluster */
+	uint64		fetch_size;		/* number of bytes that needs to be copied */
 } filemap_t;
 
 extern filemap_t *filemap;
@@ -94,11 +110,12 @@ extern void print_filemap(void);
 
 /* Functions for populating the filemap */
 extern void process_source_file(const char *path, file_type_t type,
-								size_t newsize, const char *link_target);
+								size_t size, const char *link_target);
 extern void process_target_file(const char *path, file_type_t type,
-								size_t newsize, const char *link_target);
-extern void process_block_change(ForkNumber forknum, RelFileNode rnode,
-								 BlockNumber blkno);
+								size_t size, const char *link_target);
+extern void process_target_wal_block_change(ForkNumber forknum,
+											RelFileNode rnode,
+											BlockNumber blkno);
 extern void filemap_finalize(void);
 
 #endif							/* FILEMAP_H */
diff --git a/src/bin/pg_rewind/libpq_fetch.c b/src/bin/pg_rewind/libpq_fetch.c
index bf4dfc23b96..7fc9161b8c8 100644
--- a/src/bin/pg_rewind/libpq_fetch.c
+++ b/src/bin/pg_rewind/libpq_fetch.c
@@ -465,7 +465,7 @@ libpq_executeFileMap(filemap_t *map)
 		entry = map->array[i];
 
 		/* If this is a relation file, copy the modified blocks */
-		execute_pagemap(&entry->pagemap, entry->path);
+		execute_pagemap(&entry->target_modified_pages, entry->path);
 
 		switch (entry->action)
 		{
@@ -476,15 +476,15 @@ libpq_executeFileMap(filemap_t *map)
 			case FILE_ACTION_COPY:
 				/* Truncate the old file out of the way, if any */
 				open_target_file(entry->path, true);
-				fetch_file_range(entry->path, 0, entry->newsize);
+				fetch_file_range(entry->path, 0, entry->source_size);
 				break;
 
 			case FILE_ACTION_TRUNCATE:
-				truncate_target_file(entry->path, entry->newsize);
+				truncate_target_file(entry->path, entry->source_size);
 				break;
 
 			case FILE_ACTION_COPY_TAIL:
-				fetch_file_range(entry->path, entry->oldsize, entry->newsize);
+				fetch_file_range(entry->path, entry->target_size, entry->source_size);
 				break;
 
 			case FILE_ACTION_REMOVE:
@@ -494,6 +494,10 @@ libpq_executeFileMap(filemap_t *map)
 			case FILE_ACTION_CREATE:
 				create_target(entry);
 				break;
+
+			case FILE_ACTION_UNDECIDED:
+				pg_fatal("no action decided for \"%s\"", entry->path);
+				break;
 		}
 	}
 
diff --git a/src/bin/pg_rewind/parsexlog.c b/src/bin/pg_rewind/parsexlog.c
index 2229c86f9af..2baeb74ae93 100644
--- a/src/bin/pg_rewind/parsexlog.c
+++ b/src/bin/pg_rewind/parsexlog.c
@@ -436,6 +436,6 @@ extractPageInfo(XLogReaderState *record)
 		if (forknum != MAIN_FORKNUM)
 			continue;
 
-		process_block_change(forknum, rnode, blkno);
+		process_target_wal_block_change(forknum, rnode, blkno);
 	}
 }
diff --git a/src/bin/pg_rewind/pg_rewind.c b/src/bin/pg_rewind/pg_rewind.c
index c9b9e480c0f..210984d302b 100644
--- a/src/bin/pg_rewind/pg_rewind.c
+++ b/src/bin/pg_rewind/pg_rewind.c
@@ -369,7 +369,7 @@ main(int argc, char **argv)
 				chkpttli);
 
 	/*
-	 * Build the filemap, by comparing the source and target data directories.
+	 * Collect information about all files in the target and source systems.
 	 */
 	filemap_create();
 	if (showprogress)
@@ -390,8 +390,12 @@ main(int argc, char **argv)
 		pg_log_info("reading WAL in target");
 	extractPageMap(datadir_target, chkptrec, lastcommontliIndex,
 				   ControlFile_target.checkPoint, restore_command);
-	filemap_finalize();
 
+	/*
+	 * We have collected all information we need from both systems. Decide
+	 * what to do with each file.
+	 */
+	filemap_finalize();
 	if (showprogress)
 		calculate_totals();
 
-- 
2.20.1


--------------D93EDEBFB124D563B723F4BD
Content-Type: text/x-patch; charset=UTF-8;
 name="0003-pg_rewind-Replace-the-hybrid-list-array-data-structu.patch"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
 filename*0="0003-pg_rewind-Replace-the-hybrid-list-array-data-structu.pa";
 filename*1="tch"



^ permalink  raw  reply  [nested|flat] 41+ messages in thread

* [PATCH v2 2/5] Refactor pg_rewind for more clear decision making.
@ 2020-08-19 12:34 Heikki Linnakangas <[email protected]>
  0 siblings, 0 replies; 41+ messages in thread

From: Heikki Linnakangas @ 2020-08-19 12:34 UTC (permalink / raw)

Deciding what to do with each file is now a separate step after all the
necessary information has been gathered. It is more clear that way.
Previously, the decision-making was divided between process_source_file()
and process_target_file(), and it was a bit hard to piece together what the
overall rules were.
---
 src/bin/pg_rewind/copy_fetch.c  |  14 +-
 src/bin/pg_rewind/file_ops.c    |  16 +-
 src/bin/pg_rewind/filemap.c     | 569 ++++++++++++++++----------------
 src/bin/pg_rewind/filemap.h     |  67 ++--
 src/bin/pg_rewind/libpq_fetch.c |  12 +-
 src/bin/pg_rewind/parsexlog.c   |   2 +-
 src/bin/pg_rewind/pg_rewind.c   |   8 +-
 7 files changed, 371 insertions(+), 317 deletions(-)

diff --git a/src/bin/pg_rewind/copy_fetch.c b/src/bin/pg_rewind/copy_fetch.c
index 1edab5f1867..18fad32600e 100644
--- a/src/bin/pg_rewind/copy_fetch.c
+++ b/src/bin/pg_rewind/copy_fetch.c
@@ -210,7 +210,7 @@ copy_executeFileMap(filemap_t *map)
 	for (i = 0; i < map->narray; i++)
 	{
 		entry = map->array[i];
-		execute_pagemap(&entry->pagemap, entry->path);
+		execute_pagemap(&entry->target_modified_pages, entry->path);
 
 		switch (entry->action)
 		{
@@ -219,16 +219,16 @@ copy_executeFileMap(filemap_t *map)
 				break;
 
 			case FILE_ACTION_COPY:
-				rewind_copy_file_range(entry->path, 0, entry->newsize, true);
+				rewind_copy_file_range(entry->path, 0, entry->source_size, true);
 				break;
 
 			case FILE_ACTION_TRUNCATE:
-				truncate_target_file(entry->path, entry->newsize);
+				truncate_target_file(entry->path, entry->source_size);
 				break;
 
 			case FILE_ACTION_COPY_TAIL:
-				rewind_copy_file_range(entry->path, entry->oldsize,
-									   entry->newsize, false);
+				rewind_copy_file_range(entry->path, entry->target_size,
+									   entry->source_size, false);
 				break;
 
 			case FILE_ACTION_CREATE:
@@ -238,6 +238,10 @@ copy_executeFileMap(filemap_t *map)
 			case FILE_ACTION_REMOVE:
 				remove_target(entry);
 				break;
+
+			case FILE_ACTION_UNDECIDED:
+				pg_fatal("no action decided for \"%s\"", entry->path);
+				break;
 		}
 	}
 
diff --git a/src/bin/pg_rewind/file_ops.c b/src/bin/pg_rewind/file_ops.c
index 55439db20ba..ec37d0b2e0d 100644
--- a/src/bin/pg_rewind/file_ops.c
+++ b/src/bin/pg_rewind/file_ops.c
@@ -126,8 +126,9 @@ void
 remove_target(file_entry_t *entry)
 {
 	Assert(entry->action == FILE_ACTION_REMOVE);
+	Assert(entry->target_exists);
 
-	switch (entry->type)
+	switch (entry->target_type)
 	{
 		case FILE_TYPE_DIRECTORY:
 			remove_target_dir(entry->path);
@@ -140,6 +141,10 @@ remove_target(file_entry_t *entry)
 		case FILE_TYPE_SYMLINK:
 			remove_target_symlink(entry->path);
 			break;
+
+		case FILE_TYPE_UNDEFINED:
+			pg_fatal("undefined file type for \"%s\"", entry->path);
+			break;
 	}
 }
 
@@ -147,21 +152,26 @@ void
 create_target(file_entry_t *entry)
 {
 	Assert(entry->action == FILE_ACTION_CREATE);
+	Assert(!entry->target_exists);
 
-	switch (entry->type)
+	switch (entry->source_type)
 	{
 		case FILE_TYPE_DIRECTORY:
 			create_target_dir(entry->path);
 			break;
 
 		case FILE_TYPE_SYMLINK:
-			create_target_symlink(entry->path, entry->link_target);
+			create_target_symlink(entry->path, entry->source_link_target);
 			break;
 
 		case FILE_TYPE_REGULAR:
 			/* can't happen. Regular files are created with open_target_file. */
 			pg_fatal("invalid action (CREATE) for regular file");
 			break;
+
+		case FILE_TYPE_UNDEFINED:
+			pg_fatal("undefined file type for \"%s\"", entry->path);
+			break;
 	}
 }
 
diff --git a/src/bin/pg_rewind/filemap.c b/src/bin/pg_rewind/filemap.c
index 1879257b66a..7971daeda5e 100644
--- a/src/bin/pg_rewind/filemap.c
+++ b/src/bin/pg_rewind/filemap.c
@@ -26,6 +26,8 @@ static bool isRelDataFile(const char *path);
 static char *datasegpath(RelFileNode rnode, ForkNumber forknum,
 						 BlockNumber segno);
 static int	path_cmp(const void *a, const void *b);
+
+static file_entry_t *get_filemap_entry(const char *path, bool create);
 static int	final_filemap_cmp(const void *a, const void *b);
 static void filemap_list_to_array(filemap_t *map);
 static bool check_file_excluded(const char *path, bool is_source);
@@ -146,6 +148,65 @@ filemap_create(void)
 	filemap = map;
 }
 
+/* Look up or create entry for 'path' */
+static file_entry_t *
+get_filemap_entry(const char *path, bool create)
+{
+	filemap_t  *map = filemap;
+	file_entry_t *entry;
+	file_entry_t **e;
+	file_entry_t key;
+	file_entry_t *key_ptr;
+
+	if (map->array)
+	{
+		key.path = (char *) path;
+		key_ptr = &key;
+		e = bsearch(&key_ptr, map->array, map->narray, sizeof(file_entry_t *),
+					path_cmp);
+	}
+	else
+		e = NULL;
+
+	if (e)
+		entry = *e;
+	else if (!create)
+		entry = NULL;
+	else
+	{
+		/* Create a new entry for this file */
+		entry = pg_malloc(sizeof(file_entry_t));
+		entry->path = pg_strdup(path);
+		entry->isrelfile = isRelDataFile(path);
+		entry->action = FILE_ACTION_UNDECIDED;
+
+		entry->target_exists = false;
+		entry->target_type = FILE_TYPE_UNDEFINED;
+		entry->target_size = 0;
+		entry->target_link_target = NULL;
+		entry->target_modified_pages.bitmap = NULL;
+		entry->target_modified_pages.bitmapsize = 0;
+
+		entry->source_exists = false;
+		entry->source_type = FILE_TYPE_UNDEFINED;
+		entry->source_size = 0;
+		entry->source_link_target = NULL;
+
+		entry->next = NULL;
+
+		if (map->last)
+		{
+			map->last->next = entry;
+			map->last = entry;
+		}
+		else
+			map->first = map->last = entry;
+		map->nlist++;
+	}
+
+	return entry;
+}
+
 /*
  * Callback for processing source file list.
  *
@@ -154,25 +215,12 @@ filemap_create(void)
  * exists in the target and whether the size matches.
  */
 void
-process_source_file(const char *path, file_type_t type, size_t newsize,
+process_source_file(const char *path, file_type_t type, size_t size,
 					const char *link_target)
 {
-	bool		exists;
-	char		localpath[MAXPGPATH];
-	struct stat statbuf;
-	filemap_t  *map = filemap;
-	file_action_t action = FILE_ACTION_NONE;
-	size_t		oldsize = 0;
 	file_entry_t *entry;
 
-	Assert(map->array == NULL);
-
-	/*
-	 * Skip any files matching the exclusion filters. This has the effect to
-	 * remove all those files on the target.
-	 */
-	if (check_file_excluded(path, true))
-		return;
+	Assert(filemap->array == NULL);
 
 	/*
 	 * Pretend that pg_wal is a directory, even if it's really a symlink. We
@@ -182,16 +230,6 @@ process_source_file(const char *path, file_type_t type, size_t newsize,
 	if (strcmp(path, "pg_wal") == 0 && type == FILE_TYPE_SYMLINK)
 		type = FILE_TYPE_DIRECTORY;
 
-	/*
-	 * Skip temporary files, .../pgsql_tmp/... and .../pgsql_tmp.* in source.
-	 * This has the effect that all temporary files in the destination will be
-	 * removed.
-	 */
-	if (strstr(path, "/" PG_TEMP_FILE_PREFIX) != NULL)
-		return;
-	if (strstr(path, "/" PG_TEMP_FILES_DIR "/") != NULL)
-		return;
-
 	/*
 	 * sanity check: a filename that looks like a data file better be a
 	 * regular file
@@ -199,142 +237,12 @@ process_source_file(const char *path, file_type_t type, size_t newsize,
 	if (type != FILE_TYPE_REGULAR && isRelDataFile(path))
 		pg_fatal("data file \"%s\" in source is not a regular file", path);
 
-	snprintf(localpath, sizeof(localpath), "%s/%s", datadir_target, path);
-
-	/* Does the corresponding file exist in the target data dir? */
-	if (lstat(localpath, &statbuf) < 0)
-	{
-		if (errno != ENOENT)
-			pg_fatal("could not stat file \"%s\": %m",
-					 localpath);
-
-		exists = false;
-	}
-	else
-		exists = true;
-
-	switch (type)
-	{
-		case FILE_TYPE_DIRECTORY:
-			if (exists && !S_ISDIR(statbuf.st_mode) && strcmp(path, "pg_wal") != 0)
-			{
-				/* it's a directory in source, but not in target. Strange.. */
-				pg_fatal("\"%s\" is not a directory", localpath);
-			}
-
-			if (!exists)
-				action = FILE_ACTION_CREATE;
-			else
-				action = FILE_ACTION_NONE;
-			oldsize = 0;
-			break;
-
-		case FILE_TYPE_SYMLINK:
-			if (exists &&
-#ifndef WIN32
-				!S_ISLNK(statbuf.st_mode)
-#else
-				!pgwin32_is_junction(localpath)
-#endif
-				)
-			{
-				/*
-				 * It's a symbolic link in source, but not in target.
-				 * Strange..
-				 */
-				pg_fatal("\"%s\" is not a symbolic link", localpath);
-			}
-
-			if (!exists)
-				action = FILE_ACTION_CREATE;
-			else
-				action = FILE_ACTION_NONE;
-			oldsize = 0;
-			break;
-
-		case FILE_TYPE_REGULAR:
-			if (exists && !S_ISREG(statbuf.st_mode))
-				pg_fatal("\"%s\" is not a regular file", localpath);
-
-			if (!exists || !isRelDataFile(path))
-			{
-				/*
-				 * File exists in source, but not in target. Or it's a
-				 * non-data file that we have no special processing for. Copy
-				 * it in toto.
-				 *
-				 * An exception: PG_VERSIONs should be identical, but avoid
-				 * overwriting it for paranoia.
-				 */
-				if (pg_str_endswith(path, "PG_VERSION"))
-				{
-					action = FILE_ACTION_NONE;
-					oldsize = statbuf.st_size;
-				}
-				else
-				{
-					action = FILE_ACTION_COPY;
-					oldsize = 0;
-				}
-			}
-			else
-			{
-				/*
-				 * It's a data file that exists in both.
-				 *
-				 * If it's larger in target, we can truncate it. There will
-				 * also be a WAL record of the truncation in the source
-				 * system, so WAL replay would eventually truncate the target
-				 * too, but we might as well do it now.
-				 *
-				 * If it's smaller in the target, it means that it has been
-				 * truncated in the target, or enlarged in the source, or
-				 * both. If it was truncated in the target, we need to copy
-				 * the missing tail from the source system. If it was enlarged
-				 * in the source system, there will be WAL records in the
-				 * source system for the new blocks, so we wouldn't need to
-				 * copy them here. But we don't know which scenario we're
-				 * dealing with, and there's no harm in copying the missing
-				 * blocks now, so do it now.
-				 *
-				 * If it's the same size, do nothing here. Any blocks modified
-				 * in the target will be copied based on parsing the target
-				 * system's WAL, and any blocks modified in the source will be
-				 * updated after rewinding, when the source system's WAL is
-				 * replayed.
-				 */
-				oldsize = statbuf.st_size;
-				if (oldsize < newsize)
-					action = FILE_ACTION_COPY_TAIL;
-				else if (oldsize > newsize)
-					action = FILE_ACTION_TRUNCATE;
-				else
-					action = FILE_ACTION_NONE;
-			}
-			break;
-	}
-
-	/* Create a new entry for this file */
-	entry = pg_malloc(sizeof(file_entry_t));
-	entry->path = pg_strdup(path);
-	entry->type = type;
-	entry->action = action;
-	entry->oldsize = oldsize;
-	entry->newsize = newsize;
-	entry->link_target = link_target ? pg_strdup(link_target) : NULL;
-	entry->next = NULL;
-	entry->pagemap.bitmap = NULL;
-	entry->pagemap.bitmapsize = 0;
-	entry->isrelfile = isRelDataFile(path);
-
-	if (map->last)
-	{
-		map->last->next = entry;
-		map->last = entry;
-	}
-	else
-		map->first = map->last = entry;
-	map->nlist++;
+	/* Remember this source file */
+	entry = get_filemap_entry(path, true);
+	entry->source_exists = true;
+	entry->source_type = type;
+	entry->source_size = size;
+	entry->source_link_target = link_target ? pg_strdup(link_target) : NULL;
 }
 
 /*
@@ -345,14 +253,9 @@ process_source_file(const char *path, file_type_t type, size_t newsize,
  * deletion.
  */
 void
-process_target_file(const char *path, file_type_t type, size_t oldsize,
+process_target_file(const char *path, file_type_t type, size_t size,
 					const char *link_target)
 {
-	bool		exists;
-	char		localpath[MAXPGPATH];
-	struct stat statbuf;
-	file_entry_t key;
-	file_entry_t *key_ptr;
 	filemap_t  *map = filemap;
 	file_entry_t *entry;
 
@@ -361,17 +264,6 @@ process_target_file(const char *path, file_type_t type, size_t oldsize,
 	 * from the target data folder all paths which have been filtered out from
 	 * the source data folder when processing the source files.
 	 */
-
-	snprintf(localpath, sizeof(localpath), "%s/%s", datadir_target, path);
-	if (lstat(localpath, &statbuf) < 0)
-	{
-		if (errno != ENOENT)
-			pg_fatal("could not stat file \"%s\": %m",
-					 localpath);
-
-		exists = false;
-	}
-
 	if (map->array == NULL)
 	{
 		/* on first call, initialize lookup array */
@@ -389,120 +281,76 @@ process_target_file(const char *path, file_type_t type, size_t oldsize,
 	}
 
 	/*
-	 * Like in process_source_file, pretend that xlog is always a  directory.
+	 * Like in process_source_file, pretend that pg_wal is always a directory.
 	 */
 	if (strcmp(path, "pg_wal") == 0 && type == FILE_TYPE_SYMLINK)
 		type = FILE_TYPE_DIRECTORY;
 
-	key.path = (char *) path;
-	key_ptr = &key;
-	exists = (bsearch(&key_ptr, map->array, map->narray, sizeof(file_entry_t *),
-					  path_cmp) != NULL);
-
-	/* Remove any file or folder that doesn't exist in the source system. */
-	if (!exists)
-	{
-		entry = pg_malloc(sizeof(file_entry_t));
-		entry->path = pg_strdup(path);
-		entry->type = type;
-		entry->action = FILE_ACTION_REMOVE;
-		entry->oldsize = oldsize;
-		entry->newsize = 0;
-		entry->link_target = link_target ? pg_strdup(link_target) : NULL;
-		entry->next = NULL;
-		entry->pagemap.bitmap = NULL;
-		entry->pagemap.bitmapsize = 0;
-		entry->isrelfile = isRelDataFile(path);
-
-		if (map->last == NULL)
-			map->first = entry;
-		else
-			map->last->next = entry;
-		map->last = entry;
-		map->nlist++;
-	}
-	else
-	{
-		/*
-		 * We already handled all files that exist in the source system in
-		 * process_source_file().
-		 */
-	}
+	/* Remember this target file */
+	entry = get_filemap_entry(path, true);
+	entry->target_exists = true;
+	entry->target_type = type;
+	entry->target_size = size;
+	entry->target_link_target = link_target ? pg_strdup(link_target) : NULL;
 }
 
 /*
  * This callback gets called while we read the WAL in the target, for every
- * block that have changed in the target system. It makes note of all the
+ * block that have changed in the target system.  It makes note of all the
  * changed blocks in the pagemap of the file.
+ *
+ * NOTE: All the files on both systems must have already been added to the
+ * file map!
  */
 void
-process_block_change(ForkNumber forknum, RelFileNode rnode, BlockNumber blkno)
+process_target_wal_block_change(ForkNumber forknum, RelFileNode rnode,
+								BlockNumber blkno)
 {
 	char	   *path;
-	file_entry_t key;
-	file_entry_t *key_ptr;
 	file_entry_t *entry;
 	BlockNumber blkno_inseg;
 	int			segno;
-	filemap_t  *map = filemap;
-	file_entry_t **e;
 
-	Assert(map->array);
+	Assert(filemap->array);
 
 	segno = blkno / RELSEG_SIZE;
 	blkno_inseg = blkno % RELSEG_SIZE;
 
 	path = datasegpath(rnode, forknum, segno);
-
-	key.path = (char *) path;
-	key_ptr = &key;
-
-	e = bsearch(&key_ptr, map->array, map->narray, sizeof(file_entry_t *),
-				path_cmp);
-	if (e)
-		entry = *e;
-	else
-		entry = NULL;
+	entry = get_filemap_entry(path, false);
 	pfree(path);
 
 	if (entry)
 	{
-		Assert(entry->isrelfile);
-
-		switch (entry->action)
-		{
-			case FILE_ACTION_NONE:
-			case FILE_ACTION_TRUNCATE:
-				/* skip if we're truncating away the modified block anyway */
-				if ((blkno_inseg + 1) * BLCKSZ <= entry->newsize)
-					datapagemap_add(&entry->pagemap, blkno_inseg);
-				break;
+		int64		end_offset;
 
-			case FILE_ACTION_COPY_TAIL:
-
-				/*
-				 * skip the modified block if it is part of the "tail" that
-				 * we're copying anyway.
-				 */
-				if ((blkno_inseg + 1) * BLCKSZ <= entry->oldsize)
-					datapagemap_add(&entry->pagemap, blkno_inseg);
-				break;
+		Assert(entry->isrelfile);
 
-			case FILE_ACTION_COPY:
-			case FILE_ACTION_REMOVE:
-				break;
+		if (entry->target_type != FILE_TYPE_REGULAR)
+			pg_fatal("unexpected page modification for directory or symbolic link \"%s\"",
+					 entry->path);
 
-			case FILE_ACTION_CREATE:
-				pg_fatal("unexpected page modification for directory or symbolic link \"%s\"", entry->path);
-		}
+		/*
+		 * If the block beyond the EOF in the source system, no need to
+		 * remember it now, because we're going to truncate it away from the
+		 * target anyway. Also no need to remember the block if it's beyond
+		 * the current EOF in the target system; we will copy it over with the
+		 * "tail" from the source system, anyway.
+		 */
+		end_offset = (blkno_inseg + 1) * BLCKSZ;
+		if (end_offset <= entry->source_size &&
+			end_offset <= entry->target_size)
+			datapagemap_add(&entry->target_modified_pages, blkno_inseg);
 	}
 	else
 	{
 		/*
 		 * If we don't have any record of this file in the file map, it means
-		 * that it's a relation that doesn't exist in the source system, and
-		 * it was subsequently removed in the target system, too. We can
-		 * safely ignore it.
+		 * that it's a relation that doesn't exist in the source system.  It
+		 * could exist in the target system; we haven't moved the target-only
+		 * entries from the linked list to the array yet!  But in any case, if
+		 * it doesn't exist in the source it will be removed from the target
+		 * too, and we can safely ignore it.
 		 */
 	}
 }
@@ -593,16 +441,6 @@ filemap_list_to_array(filemap_t *map)
 	map->first = map->last = NULL;
 }
 
-void
-filemap_finalize(void)
-{
-	filemap_t  *map = filemap;
-
-	filemap_list_to_array(map);
-	qsort(map->array, map->narray, sizeof(file_entry_t *),
-		  final_filemap_cmp);
-}
-
 static const char *
 action_to_str(file_action_t action)
 {
@@ -643,26 +481,26 @@ calculate_totals(void)
 	{
 		entry = map->array[i];
 
-		if (entry->type != FILE_TYPE_REGULAR)
+		if (entry->source_type != FILE_TYPE_REGULAR)
 			continue;
 
-		map->total_size += entry->newsize;
+		map->total_size += entry->source_size;
 
 		if (entry->action == FILE_ACTION_COPY)
 		{
-			map->fetch_size += entry->newsize;
+			map->fetch_size += entry->source_size;
 			continue;
 		}
 
 		if (entry->action == FILE_ACTION_COPY_TAIL)
-			map->fetch_size += (entry->newsize - entry->oldsize);
+			map->fetch_size += (entry->source_size - entry->target_size);
 
-		if (entry->pagemap.bitmapsize > 0)
+		if (entry->target_modified_pages.bitmapsize > 0)
 		{
 			datapagemap_iterator_t *iter;
 			BlockNumber blk;
 
-			iter = datapagemap_iterate(&entry->pagemap);
+			iter = datapagemap_iterate(&entry->target_modified_pages);
 			while (datapagemap_next(iter, &blk))
 				map->fetch_size += BLCKSZ;
 
@@ -682,13 +520,13 @@ print_filemap(void)
 	{
 		entry = map->array[i];
 		if (entry->action != FILE_ACTION_NONE ||
-			entry->pagemap.bitmapsize > 0)
+			entry->target_modified_pages.bitmapsize > 0)
 		{
 			pg_log_debug("%s (%s)", entry->path,
 						 action_to_str(entry->action));
 
-			if (entry->pagemap.bitmapsize > 0)
-				datapagemap_print(&entry->pagemap);
+			if (entry->target_modified_pages.bitmapsize > 0)
+				datapagemap_print(&entry->target_modified_pages);
 		}
 	}
 	fflush(stdout);
@@ -837,3 +675,180 @@ final_filemap_cmp(const void *a, const void *b)
 	else
 		return strcmp(fa->path, fb->path);
 }
+
+/*
+ * Decide what action to perform to a file.
+ */
+static file_action_t
+decide_file_action(file_entry_t *entry)
+{
+	const char *path = entry->path;
+
+	/*
+	 * Don't touch the control file. It is handled specially, after copying
+	 * all the other files.
+	 */
+	if (strcmp(path, "global/pg_control") == 0)
+		return FILE_ACTION_NONE;
+
+	/*
+	 * Remove all files matching the exclusion filters in the target.
+	 */
+	if (check_file_excluded(path, true))
+	{
+		if (entry->target_exists)
+			return FILE_ACTION_REMOVE;
+		else
+			return FILE_ACTION_NONE;
+	}
+
+	/*
+	 * Also remove all temporary files, .../pgsql_tmp/... and .../pgsql_tmp.*
+	 * in the target.
+	 */
+	if (strstr(path, "/" PG_TEMP_FILE_PREFIX) != NULL ||
+		strstr(path, "/" PG_TEMP_FILES_DIR "/") != NULL)
+	{
+		if (entry->target_exists)
+			return FILE_ACTION_REMOVE;
+		else
+			return FILE_ACTION_NONE;
+	}
+
+	/*
+	 * Handle cases where the file is missing from one of the systems.
+	 */
+	if (!entry->target_exists && entry->source_exists)
+	{
+		/*
+		 * File exists in source, but not in target. Copy it in toto. (If it's
+		 * a relation data file, WAL replay after rewinding should re-create
+		 * it anyway. But there's no harm in copying it now.)
+		 */
+		switch (entry->source_type)
+		{
+			case FILE_TYPE_DIRECTORY:
+			case FILE_TYPE_SYMLINK:
+				return FILE_ACTION_CREATE;
+			case FILE_TYPE_REGULAR:
+				return FILE_ACTION_COPY;
+			case FILE_TYPE_UNDEFINED:
+				pg_fatal("unknown file type for \"%s\"", entry->path);
+				break;
+		}
+	}
+	else if (entry->target_exists && !entry->source_exists)
+	{
+		/* File exists in target, but not source. Remove it. */
+		return FILE_ACTION_REMOVE;
+	}
+	else if (!entry->target_exists && !entry->source_exists)
+	{
+		/*
+		 * Doesn't exist in either server. Why does it have an entry in the
+		 * first place??
+		 */
+		return FILE_ACTION_NONE;
+	}
+
+	/*
+	 * Otherwise, the file exists on both systems
+	 */
+	Assert(entry->target_exists && entry->source_exists);
+
+	if (entry->source_type != entry->target_type)
+	{
+		/* But it's a different kind of object. Strange.. */
+		pg_fatal("file \"%s\" is of different type in source and target", entry->path);
+	}
+
+	/*
+	 * PG_VERSION files should be identical on both systems, but avoid
+	 * overwriting them for paranoia.
+	 */
+	if (pg_str_endswith(entry->path, "PG_VERSION"))
+		return FILE_ACTION_NONE;
+
+	switch (entry->source_type)
+	{
+		case FILE_TYPE_DIRECTORY:
+			return FILE_ACTION_NONE;
+
+		case FILE_TYPE_SYMLINK:
+			/* FIXME: Check if it points to the same target? */
+			return FILE_ACTION_NONE;
+
+		case FILE_TYPE_REGULAR:
+			if (!entry->isrelfile)
+			{
+				/*
+				 * It's a non-data file that we have no special processing
+				 * for. Copy it in toto.
+				 */
+				return FILE_ACTION_COPY;
+			}
+			else
+			{
+				/*
+				 * It's a data file that exists in both systems.
+				 *
+				 * If it's larger in target, we can truncate it. There will
+				 * also be a WAL record of the truncation in the source
+				 * system, so WAL replay would eventually truncate the target
+				 * too, but we might as well do it now.
+				 *
+				 * If it's smaller in the target, it means that it has been
+				 * truncated in the target, or enlarged in the source, or
+				 * both. If it was truncated in the target, we need to copy
+				 * the missing tail from the source system. If it was enlarged
+				 * in the source system, there will be WAL records in the
+				 * source system for the new blocks, so we wouldn't need to
+				 * copy them here. But we don't know which scenario we're
+				 * dealing with, and there's no harm in copying the missing
+				 * blocks now, so do it now.
+				 *
+				 * If it's the same size, do nothing here. Any blocks modified
+				 * in the target will be copied based on parsing the target
+				 * system's WAL, and any blocks modified in the source will be
+				 * updated after rewinding, when the source system's WAL is
+				 * replayed.
+				 */
+				if (entry->target_size < entry->source_size)
+					return FILE_ACTION_COPY_TAIL;
+				else if (entry->target_size > entry->source_size)
+					return FILE_ACTION_TRUNCATE;
+				else
+					return FILE_ACTION_NONE;
+			}
+			break;
+
+		case FILE_TYPE_UNDEFINED:
+			pg_fatal("unknown file type for \"%s\"", path);
+			break;
+	}
+
+	/* unreachable */
+	pg_fatal("could not decide what to do with file \"%s\"", path);
+}
+
+/*
+ * Decide what to do with each file.
+ */
+void
+filemap_finalize()
+{
+	int			i;
+
+	filemap_list_to_array(filemap);
+
+	for (i = 0; i < filemap->narray; i++)
+	{
+		file_entry_t *entry = filemap->array[i];
+
+		entry->action = decide_file_action(entry);
+	}
+
+	/* Sort the actions to the order that they should be performed */
+	qsort(filemap->array, filemap->narray, sizeof(file_entry_t *),
+		  final_filemap_cmp);
+}
diff --git a/src/bin/pg_rewind/filemap.h b/src/bin/pg_rewind/filemap.h
index 0cb7425170c..a5e8df57f40 100644
--- a/src/bin/pg_rewind/filemap.h
+++ b/src/bin/pg_rewind/filemap.h
@@ -14,17 +14,21 @@
 
 /*
  * For every file found in the local or remote system, we have a file entry
- * which says what we are going to do with the file. For relation files,
- * there is also a page map, marking pages in the file that were changed
- * locally.
- *
- * The enum values are sorted in the order we want actions to be processed.
+ * that contains information about the file on both systems.  For relation
+ * files, there is also a page map that marks pages in the file that were
+ * changed in the target after the last common checkpoint.  Each entry also
+ * contains an 'action' field, which says what we are going to do with the
+ * file.
  */
+
+/* these enum values are sorted in the order we want actions to be processed */
 typedef enum
 {
+	FILE_ACTION_UNDECIDED = 0,	/* not decided yet */
+
 	FILE_ACTION_CREATE,			/* create local directory or symbolic link */
 	FILE_ACTION_COPY,			/* copy whole file, overwriting if exists */
-	FILE_ACTION_COPY_TAIL,		/* copy tail from 'oldsize' to 'newsize' */
+	FILE_ACTION_COPY_TAIL,		/* copy tail from 'source_size' to 'target_size' */
 	FILE_ACTION_NONE,			/* no action (we might still copy modified
 								 * blocks based on the parsed WAL) */
 	FILE_ACTION_TRUNCATE,		/* truncate local file to 'newsize' bytes */
@@ -33,6 +37,8 @@ typedef enum
 
 typedef enum
 {
+	FILE_TYPE_UNDEFINED = 0,
+
 	FILE_TYPE_REGULAR,
 	FILE_TYPE_DIRECTORY,
 	FILE_TYPE_SYMLINK
@@ -41,19 +47,30 @@ typedef enum
 typedef struct file_entry_t
 {
 	char	   *path;
-	file_type_t type;
+	bool		isrelfile;		/* is it a relation data file? */
 
-	file_action_t action;
+	/*
+	 * Status of the file in the target.
+	 */
+	bool		target_exists;
+	file_type_t target_type;
+	size_t		target_size; /* for a regular file */
+	char	   *target_link_target; /* for a symlink */
 
-	/* for a regular file */
-	size_t		oldsize;
-	size_t		newsize;
-	bool		isrelfile;		/* is it a relation data file? */
+	datapagemap_t target_modified_pages;
 
-	datapagemap_t pagemap;
+	/*
+	 * Status of the file in the source.
+	 */
+	bool		source_exists;
+	file_type_t source_type;
+	size_t		source_size;
+	char	   *source_link_target; /* for a symlink */
 
-	/* for a symlink */
-	char	   *link_target;
+	/*
+	 * What will we do to the file?
+	 */
+	file_action_t action;
 
 	struct file_entry_t *next;
 } file_entry_t;
@@ -70,20 +87,19 @@ typedef struct filemap_t
 
 	/*
 	 * After processing all the remote files, the entries in the linked list
-	 * are moved to this array. After processing local files, too, all the
+	 * are moved to this array.  After processing local files, too, all the
 	 * local entries are added to the array by filemap_finalize, and sorted in
-	 * the final order. After filemap_finalize, all the entries are in the
+	 * the final order.  After filemap_finalize, all the entries are in the
 	 * array, and the linked list is empty.
 	 */
 	file_entry_t **array;
 	int			narray;			/* current length of array */
 
 	/*
-	 * Summary information. total_size is the total size of the source
-	 * cluster, and fetch_size is the number of bytes that needs to be copied.
+	 * Summary information.
 	 */
-	uint64		total_size;
-	uint64		fetch_size;
+	uint64		total_size;		/* total size of the source cluster */
+	uint64		fetch_size;		/* number of bytes that needs to be copied */
 } filemap_t;
 
 extern filemap_t *filemap;
@@ -94,11 +110,12 @@ extern void print_filemap(void);
 
 /* Functions for populating the filemap */
 extern void process_source_file(const char *path, file_type_t type,
-								size_t newsize, const char *link_target);
+								size_t size, const char *link_target);
 extern void process_target_file(const char *path, file_type_t type,
-								size_t newsize, const char *link_target);
-extern void process_block_change(ForkNumber forknum, RelFileNode rnode,
-								 BlockNumber blkno);
+								size_t size, const char *link_target);
+extern void process_target_wal_block_change(ForkNumber forknum,
+											RelFileNode rnode,
+											BlockNumber blkno);
 extern void filemap_finalize(void);
 
 #endif							/* FILEMAP_H */
diff --git a/src/bin/pg_rewind/libpq_fetch.c b/src/bin/pg_rewind/libpq_fetch.c
index bf4dfc23b96..7fc9161b8c8 100644
--- a/src/bin/pg_rewind/libpq_fetch.c
+++ b/src/bin/pg_rewind/libpq_fetch.c
@@ -465,7 +465,7 @@ libpq_executeFileMap(filemap_t *map)
 		entry = map->array[i];
 
 		/* If this is a relation file, copy the modified blocks */
-		execute_pagemap(&entry->pagemap, entry->path);
+		execute_pagemap(&entry->target_modified_pages, entry->path);
 
 		switch (entry->action)
 		{
@@ -476,15 +476,15 @@ libpq_executeFileMap(filemap_t *map)
 			case FILE_ACTION_COPY:
 				/* Truncate the old file out of the way, if any */
 				open_target_file(entry->path, true);
-				fetch_file_range(entry->path, 0, entry->newsize);
+				fetch_file_range(entry->path, 0, entry->source_size);
 				break;
 
 			case FILE_ACTION_TRUNCATE:
-				truncate_target_file(entry->path, entry->newsize);
+				truncate_target_file(entry->path, entry->source_size);
 				break;
 
 			case FILE_ACTION_COPY_TAIL:
-				fetch_file_range(entry->path, entry->oldsize, entry->newsize);
+				fetch_file_range(entry->path, entry->target_size, entry->source_size);
 				break;
 
 			case FILE_ACTION_REMOVE:
@@ -494,6 +494,10 @@ libpq_executeFileMap(filemap_t *map)
 			case FILE_ACTION_CREATE:
 				create_target(entry);
 				break;
+
+			case FILE_ACTION_UNDECIDED:
+				pg_fatal("no action decided for \"%s\"", entry->path);
+				break;
 		}
 	}
 
diff --git a/src/bin/pg_rewind/parsexlog.c b/src/bin/pg_rewind/parsexlog.c
index 2229c86f9af..2baeb74ae93 100644
--- a/src/bin/pg_rewind/parsexlog.c
+++ b/src/bin/pg_rewind/parsexlog.c
@@ -436,6 +436,6 @@ extractPageInfo(XLogReaderState *record)
 		if (forknum != MAIN_FORKNUM)
 			continue;
 
-		process_block_change(forknum, rnode, blkno);
+		process_target_wal_block_change(forknum, rnode, blkno);
 	}
 }
diff --git a/src/bin/pg_rewind/pg_rewind.c b/src/bin/pg_rewind/pg_rewind.c
index c9b9e480c0f..210984d302b 100644
--- a/src/bin/pg_rewind/pg_rewind.c
+++ b/src/bin/pg_rewind/pg_rewind.c
@@ -369,7 +369,7 @@ main(int argc, char **argv)
 				chkpttli);
 
 	/*
-	 * Build the filemap, by comparing the source and target data directories.
+	 * Collect information about all files in the target and source systems.
 	 */
 	filemap_create();
 	if (showprogress)
@@ -390,8 +390,12 @@ main(int argc, char **argv)
 		pg_log_info("reading WAL in target");
 	extractPageMap(datadir_target, chkptrec, lastcommontliIndex,
 				   ControlFile_target.checkPoint, restore_command);
-	filemap_finalize();
 
+	/*
+	 * We have collected all information we need from both systems. Decide
+	 * what to do with each file.
+	 */
+	filemap_finalize();
 	if (showprogress)
 		calculate_totals();
 
-- 
2.20.1


--------------BF34D0120055DC3839060F92
Content-Type: text/x-patch; charset=UTF-8;
 name="v2-0003-pg_rewind-Replace-the-hybrid-list-array-data-stru.patch"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
 filename*0="v2-0003-pg_rewind-Replace-the-hybrid-list-array-data-stru.pa";
 filename*1="tch"



^ permalink  raw  reply  [nested|flat] 41+ messages in thread

* [PATCH 2/5] Refactor pg_rewind for more clear decision making.
@ 2020-08-19 12:34 Heikki Linnakangas <[email protected]>
  0 siblings, 0 replies; 41+ messages in thread

From: Heikki Linnakangas @ 2020-08-19 12:34 UTC (permalink / raw)

Deciding what to do with each file is now a separate step after all the
necessary information has been gathered. It is more clear that way.
Previously, the decision-making was divided between process_source_file()
and process_target_file(), and it was a bit hard to piece together what the
overall rules were.
---
 src/bin/pg_rewind/copy_fetch.c  |  14 +-
 src/bin/pg_rewind/file_ops.c    |  16 +-
 src/bin/pg_rewind/filemap.c     | 514 ++++++++++++++++----------------
 src/bin/pg_rewind/filemap.h     |  67 +++--
 src/bin/pg_rewind/libpq_fetch.c |  12 +-
 src/bin/pg_rewind/parsexlog.c   |   2 +-
 src/bin/pg_rewind/pg_rewind.c   |   8 +-
 7 files changed, 332 insertions(+), 301 deletions(-)

diff --git a/src/bin/pg_rewind/copy_fetch.c b/src/bin/pg_rewind/copy_fetch.c
index 1edab5f1867..18fad32600e 100644
--- a/src/bin/pg_rewind/copy_fetch.c
+++ b/src/bin/pg_rewind/copy_fetch.c
@@ -210,7 +210,7 @@ copy_executeFileMap(filemap_t *map)
 	for (i = 0; i < map->narray; i++)
 	{
 		entry = map->array[i];
-		execute_pagemap(&entry->pagemap, entry->path);
+		execute_pagemap(&entry->target_modified_pages, entry->path);
 
 		switch (entry->action)
 		{
@@ -219,16 +219,16 @@ copy_executeFileMap(filemap_t *map)
 				break;
 
 			case FILE_ACTION_COPY:
-				rewind_copy_file_range(entry->path, 0, entry->newsize, true);
+				rewind_copy_file_range(entry->path, 0, entry->source_size, true);
 				break;
 
 			case FILE_ACTION_TRUNCATE:
-				truncate_target_file(entry->path, entry->newsize);
+				truncate_target_file(entry->path, entry->source_size);
 				break;
 
 			case FILE_ACTION_COPY_TAIL:
-				rewind_copy_file_range(entry->path, entry->oldsize,
-									   entry->newsize, false);
+				rewind_copy_file_range(entry->path, entry->target_size,
+									   entry->source_size, false);
 				break;
 
 			case FILE_ACTION_CREATE:
@@ -238,6 +238,10 @@ copy_executeFileMap(filemap_t *map)
 			case FILE_ACTION_REMOVE:
 				remove_target(entry);
 				break;
+
+			case FILE_ACTION_UNDECIDED:
+				pg_fatal("no action decided for \"%s\"", entry->path);
+				break;
 		}
 	}
 
diff --git a/src/bin/pg_rewind/file_ops.c b/src/bin/pg_rewind/file_ops.c
index 55439db20ba..ec37d0b2e0d 100644
--- a/src/bin/pg_rewind/file_ops.c
+++ b/src/bin/pg_rewind/file_ops.c
@@ -126,8 +126,9 @@ void
 remove_target(file_entry_t *entry)
 {
 	Assert(entry->action == FILE_ACTION_REMOVE);
+	Assert(entry->target_exists);
 
-	switch (entry->type)
+	switch (entry->target_type)
 	{
 		case FILE_TYPE_DIRECTORY:
 			remove_target_dir(entry->path);
@@ -140,6 +141,10 @@ remove_target(file_entry_t *entry)
 		case FILE_TYPE_SYMLINK:
 			remove_target_symlink(entry->path);
 			break;
+
+		case FILE_TYPE_UNDEFINED:
+			pg_fatal("undefined file type for \"%s\"", entry->path);
+			break;
 	}
 }
 
@@ -147,21 +152,26 @@ void
 create_target(file_entry_t *entry)
 {
 	Assert(entry->action == FILE_ACTION_CREATE);
+	Assert(!entry->target_exists);
 
-	switch (entry->type)
+	switch (entry->source_type)
 	{
 		case FILE_TYPE_DIRECTORY:
 			create_target_dir(entry->path);
 			break;
 
 		case FILE_TYPE_SYMLINK:
-			create_target_symlink(entry->path, entry->link_target);
+			create_target_symlink(entry->path, entry->source_link_target);
 			break;
 
 		case FILE_TYPE_REGULAR:
 			/* can't happen. Regular files are created with open_target_file. */
 			pg_fatal("invalid action (CREATE) for regular file");
 			break;
+
+		case FILE_TYPE_UNDEFINED:
+			pg_fatal("undefined file type for \"%s\"", entry->path);
+			break;
 	}
 }
 
diff --git a/src/bin/pg_rewind/filemap.c b/src/bin/pg_rewind/filemap.c
index 1879257b66a..431e8e760e0 100644
--- a/src/bin/pg_rewind/filemap.c
+++ b/src/bin/pg_rewind/filemap.c
@@ -26,6 +26,8 @@ static bool isRelDataFile(const char *path);
 static char *datasegpath(RelFileNode rnode, ForkNumber forknum,
 						 BlockNumber segno);
 static int	path_cmp(const void *a, const void *b);
+
+static file_entry_t *get_filemap_entry(const char *path, bool create);
 static int	final_filemap_cmp(const void *a, const void *b);
 static void filemap_list_to_array(filemap_t *map);
 static bool check_file_excluded(const char *path, bool is_source);
@@ -146,6 +148,65 @@ filemap_create(void)
 	filemap = map;
 }
 
+/* Look up or create entry for 'path' */
+static file_entry_t *
+get_filemap_entry(const char *path, bool create)
+{
+	filemap_t  *map = filemap;
+	file_entry_t *entry;
+	file_entry_t **e;
+	file_entry_t key;
+	file_entry_t *key_ptr;
+
+	if (map->array)
+	{
+		key.path = (char *) path;
+		key_ptr = &key;
+		e = bsearch(&key_ptr, map->array, map->narray, sizeof(file_entry_t *),
+					path_cmp);
+	}
+	else
+		e = NULL;
+
+	if (e)
+		entry = *e;
+	else if (!create)
+		entry = NULL;
+	else
+	{
+		/* Create a new entry for this file */
+		entry = pg_malloc(sizeof(file_entry_t));
+		entry->path = pg_strdup(path);
+		entry->isrelfile = isRelDataFile(path);
+		entry->action = FILE_ACTION_UNDECIDED;
+
+		entry->target_exists = false;
+		entry->target_type = FILE_TYPE_UNDEFINED;
+		entry->target_size = 0;
+		entry->target_link_target = NULL;
+		entry->target_modified_pages.bitmap = NULL;
+		entry->target_modified_pages.bitmapsize = 0;
+
+		entry->source_exists = false;
+		entry->source_type = FILE_TYPE_UNDEFINED;
+		entry->source_size = 0;
+		entry->source_link_target = NULL;
+
+		entry->next = NULL;
+
+		if (map->last)
+		{
+			map->last->next = entry;
+			map->last = entry;
+		}
+		else
+			map->first = map->last = entry;
+		map->nlist++;
+	}
+
+	return entry;
+}
+
 /*
  * Callback for processing source file list.
  *
@@ -154,18 +215,19 @@ filemap_create(void)
  * exists in the target and whether the size matches.
  */
 void
-process_source_file(const char *path, file_type_t type, size_t newsize,
+process_source_file(const char *path, file_type_t type, size_t size,
 					const char *link_target)
 {
-	bool		exists;
-	char		localpath[MAXPGPATH];
-	struct stat statbuf;
-	filemap_t  *map = filemap;
-	file_action_t action = FILE_ACTION_NONE;
-	size_t		oldsize = 0;
 	file_entry_t *entry;
 
-	Assert(map->array == NULL);
+	Assert(filemap->array == NULL);
+
+	/*
+	 * Skip the control file. It is handled specially, after copying all the
+	 * other files.
+	 */
+	if (strcmp(path, "global/pg_control") == 0)
+		return;
 
 	/*
 	 * Skip any files matching the exclusion filters. This has the effect to
@@ -199,142 +261,12 @@ process_source_file(const char *path, file_type_t type, size_t newsize,
 	if (type != FILE_TYPE_REGULAR && isRelDataFile(path))
 		pg_fatal("data file \"%s\" in source is not a regular file", path);
 
-	snprintf(localpath, sizeof(localpath), "%s/%s", datadir_target, path);
-
-	/* Does the corresponding file exist in the target data dir? */
-	if (lstat(localpath, &statbuf) < 0)
-	{
-		if (errno != ENOENT)
-			pg_fatal("could not stat file \"%s\": %m",
-					 localpath);
-
-		exists = false;
-	}
-	else
-		exists = true;
-
-	switch (type)
-	{
-		case FILE_TYPE_DIRECTORY:
-			if (exists && !S_ISDIR(statbuf.st_mode) && strcmp(path, "pg_wal") != 0)
-			{
-				/* it's a directory in source, but not in target. Strange.. */
-				pg_fatal("\"%s\" is not a directory", localpath);
-			}
-
-			if (!exists)
-				action = FILE_ACTION_CREATE;
-			else
-				action = FILE_ACTION_NONE;
-			oldsize = 0;
-			break;
-
-		case FILE_TYPE_SYMLINK:
-			if (exists &&
-#ifndef WIN32
-				!S_ISLNK(statbuf.st_mode)
-#else
-				!pgwin32_is_junction(localpath)
-#endif
-				)
-			{
-				/*
-				 * It's a symbolic link in source, but not in target.
-				 * Strange..
-				 */
-				pg_fatal("\"%s\" is not a symbolic link", localpath);
-			}
-
-			if (!exists)
-				action = FILE_ACTION_CREATE;
-			else
-				action = FILE_ACTION_NONE;
-			oldsize = 0;
-			break;
-
-		case FILE_TYPE_REGULAR:
-			if (exists && !S_ISREG(statbuf.st_mode))
-				pg_fatal("\"%s\" is not a regular file", localpath);
-
-			if (!exists || !isRelDataFile(path))
-			{
-				/*
-				 * File exists in source, but not in target. Or it's a
-				 * non-data file that we have no special processing for. Copy
-				 * it in toto.
-				 *
-				 * An exception: PG_VERSIONs should be identical, but avoid
-				 * overwriting it for paranoia.
-				 */
-				if (pg_str_endswith(path, "PG_VERSION"))
-				{
-					action = FILE_ACTION_NONE;
-					oldsize = statbuf.st_size;
-				}
-				else
-				{
-					action = FILE_ACTION_COPY;
-					oldsize = 0;
-				}
-			}
-			else
-			{
-				/*
-				 * It's a data file that exists in both.
-				 *
-				 * If it's larger in target, we can truncate it. There will
-				 * also be a WAL record of the truncation in the source
-				 * system, so WAL replay would eventually truncate the target
-				 * too, but we might as well do it now.
-				 *
-				 * If it's smaller in the target, it means that it has been
-				 * truncated in the target, or enlarged in the source, or
-				 * both. If it was truncated in the target, we need to copy
-				 * the missing tail from the source system. If it was enlarged
-				 * in the source system, there will be WAL records in the
-				 * source system for the new blocks, so we wouldn't need to
-				 * copy them here. But we don't know which scenario we're
-				 * dealing with, and there's no harm in copying the missing
-				 * blocks now, so do it now.
-				 *
-				 * If it's the same size, do nothing here. Any blocks modified
-				 * in the target will be copied based on parsing the target
-				 * system's WAL, and any blocks modified in the source will be
-				 * updated after rewinding, when the source system's WAL is
-				 * replayed.
-				 */
-				oldsize = statbuf.st_size;
-				if (oldsize < newsize)
-					action = FILE_ACTION_COPY_TAIL;
-				else if (oldsize > newsize)
-					action = FILE_ACTION_TRUNCATE;
-				else
-					action = FILE_ACTION_NONE;
-			}
-			break;
-	}
-
-	/* Create a new entry for this file */
-	entry = pg_malloc(sizeof(file_entry_t));
-	entry->path = pg_strdup(path);
-	entry->type = type;
-	entry->action = action;
-	entry->oldsize = oldsize;
-	entry->newsize = newsize;
-	entry->link_target = link_target ? pg_strdup(link_target) : NULL;
-	entry->next = NULL;
-	entry->pagemap.bitmap = NULL;
-	entry->pagemap.bitmapsize = 0;
-	entry->isrelfile = isRelDataFile(path);
-
-	if (map->last)
-	{
-		map->last->next = entry;
-		map->last = entry;
-	}
-	else
-		map->first = map->last = entry;
-	map->nlist++;
+	/* Remember this source file */
+	entry = get_filemap_entry(path, true);
+	entry->source_exists = true;
+	entry->source_type = type;
+	entry->source_size = size;
+	entry->source_link_target = link_target ? pg_strdup(link_target) : NULL;
 }
 
 /*
@@ -345,33 +277,24 @@ process_source_file(const char *path, file_type_t type, size_t newsize,
  * deletion.
  */
 void
-process_target_file(const char *path, file_type_t type, size_t oldsize,
+process_target_file(const char *path, file_type_t type, size_t size,
 					const char *link_target)
 {
-	bool		exists;
-	char		localpath[MAXPGPATH];
-	struct stat statbuf;
-	file_entry_t key;
-	file_entry_t *key_ptr;
 	filemap_t  *map = filemap;
 	file_entry_t *entry;
 
+	/*
+	 * Skip the control file. It is handled specially, after copying all the
+	 * other files.
+	 */
+	if (strcmp(path, "global/pg_control") == 0)
+		return;
+
 	/*
 	 * Do not apply any exclusion filters here.  This has advantage to remove
 	 * from the target data folder all paths which have been filtered out from
 	 * the source data folder when processing the source files.
 	 */
-
-	snprintf(localpath, sizeof(localpath), "%s/%s", datadir_target, path);
-	if (lstat(localpath, &statbuf) < 0)
-	{
-		if (errno != ENOENT)
-			pg_fatal("could not stat file \"%s\": %m",
-					 localpath);
-
-		exists = false;
-	}
-
 	if (map->array == NULL)
 	{
 		/* on first call, initialize lookup array */
@@ -389,120 +312,76 @@ process_target_file(const char *path, file_type_t type, size_t oldsize,
 	}
 
 	/*
-	 * Like in process_source_file, pretend that xlog is always a  directory.
+	 * Like in process_source_file, pretend that pg_wal is always a directory.
 	 */
 	if (strcmp(path, "pg_wal") == 0 && type == FILE_TYPE_SYMLINK)
 		type = FILE_TYPE_DIRECTORY;
 
-	key.path = (char *) path;
-	key_ptr = &key;
-	exists = (bsearch(&key_ptr, map->array, map->narray, sizeof(file_entry_t *),
-					  path_cmp) != NULL);
-
-	/* Remove any file or folder that doesn't exist in the source system. */
-	if (!exists)
-	{
-		entry = pg_malloc(sizeof(file_entry_t));
-		entry->path = pg_strdup(path);
-		entry->type = type;
-		entry->action = FILE_ACTION_REMOVE;
-		entry->oldsize = oldsize;
-		entry->newsize = 0;
-		entry->link_target = link_target ? pg_strdup(link_target) : NULL;
-		entry->next = NULL;
-		entry->pagemap.bitmap = NULL;
-		entry->pagemap.bitmapsize = 0;
-		entry->isrelfile = isRelDataFile(path);
-
-		if (map->last == NULL)
-			map->first = entry;
-		else
-			map->last->next = entry;
-		map->last = entry;
-		map->nlist++;
-	}
-	else
-	{
-		/*
-		 * We already handled all files that exist in the source system in
-		 * process_source_file().
-		 */
-	}
+	/* Remember this target file */
+	entry = get_filemap_entry(path, true);
+	entry->target_exists = true;
+	entry->target_type = type;
+	entry->target_size = size;
+	entry->target_link_target = link_target ? pg_strdup(link_target) : NULL;
 }
 
 /*
  * This callback gets called while we read the WAL in the target, for every
- * block that have changed in the target system. It makes note of all the
+ * block that have changed in the target system.  It makes note of all the
  * changed blocks in the pagemap of the file.
+ *
+ * NOTE: All the files on both systems must have already been added to the
+ * file map!
  */
 void
-process_block_change(ForkNumber forknum, RelFileNode rnode, BlockNumber blkno)
+process_target_wal_block_change(ForkNumber forknum, RelFileNode rnode,
+								BlockNumber blkno)
 {
 	char	   *path;
-	file_entry_t key;
-	file_entry_t *key_ptr;
 	file_entry_t *entry;
 	BlockNumber blkno_inseg;
 	int			segno;
-	filemap_t  *map = filemap;
-	file_entry_t **e;
 
-	Assert(map->array);
+	Assert(filemap->array);
 
 	segno = blkno / RELSEG_SIZE;
 	blkno_inseg = blkno % RELSEG_SIZE;
 
 	path = datasegpath(rnode, forknum, segno);
-
-	key.path = (char *) path;
-	key_ptr = &key;
-
-	e = bsearch(&key_ptr, map->array, map->narray, sizeof(file_entry_t *),
-				path_cmp);
-	if (e)
-		entry = *e;
-	else
-		entry = NULL;
+	entry = get_filemap_entry(path, false);
 	pfree(path);
 
 	if (entry)
 	{
+		int64		end_offset;
+
 		Assert(entry->isrelfile);
 
-		switch (entry->action)
-		{
-			case FILE_ACTION_NONE:
-			case FILE_ACTION_TRUNCATE:
-				/* skip if we're truncating away the modified block anyway */
-				if ((blkno_inseg + 1) * BLCKSZ <= entry->newsize)
-					datapagemap_add(&entry->pagemap, blkno_inseg);
-				break;
-
-			case FILE_ACTION_COPY_TAIL:
-
-				/*
-				 * skip the modified block if it is part of the "tail" that
-				 * we're copying anyway.
-				 */
-				if ((blkno_inseg + 1) * BLCKSZ <= entry->oldsize)
-					datapagemap_add(&entry->pagemap, blkno_inseg);
-				break;
-
-			case FILE_ACTION_COPY:
-			case FILE_ACTION_REMOVE:
-				break;
-
-			case FILE_ACTION_CREATE:
-				pg_fatal("unexpected page modification for directory or symbolic link \"%s\"", entry->path);
-		}
+		if (entry->target_type != FILE_TYPE_REGULAR)
+			pg_fatal("unexpected page modification for directory or symbolic link \"%s\"",
+					 entry->path);
+
+		/*
+		 * If the block beyond the EOF in the source system, no need to
+		 * remember it now, because we're going to truncate it away from the
+		 * target anyway. Also no need to remember the block if it's beyond
+		 * the current EOF in the target system; we will copy it over with the
+		 * "tail" from the source system, anyway.
+		 */
+		end_offset = (blkno_inseg + 1) * BLCKSZ;
+		if (end_offset <= entry->source_size &&
+			end_offset <= entry->target_size)
+			datapagemap_add(&entry->target_modified_pages, blkno_inseg);
 	}
 	else
 	{
 		/*
 		 * If we don't have any record of this file in the file map, it means
-		 * that it's a relation that doesn't exist in the source system, and
-		 * it was subsequently removed in the target system, too. We can
-		 * safely ignore it.
+		 * that it's a relation that doesn't exist in the source system.  It
+		 * could exist in the target system; we haven't moved the target-only
+		 * entries from the linked list to the array yet!  But in any case, if
+		 * it doesn't exist in the source it will be removed from the target
+		 * too, and we can safely ignore it.
 		 */
 	}
 }
@@ -593,16 +472,6 @@ filemap_list_to_array(filemap_t *map)
 	map->first = map->last = NULL;
 }
 
-void
-filemap_finalize(void)
-{
-	filemap_t  *map = filemap;
-
-	filemap_list_to_array(map);
-	qsort(map->array, map->narray, sizeof(file_entry_t *),
-		  final_filemap_cmp);
-}
-
 static const char *
 action_to_str(file_action_t action)
 {
@@ -643,26 +512,26 @@ calculate_totals(void)
 	{
 		entry = map->array[i];
 
-		if (entry->type != FILE_TYPE_REGULAR)
+		if (entry->source_type != FILE_TYPE_REGULAR)
 			continue;
 
-		map->total_size += entry->newsize;
+		map->total_size += entry->source_size;
 
 		if (entry->action == FILE_ACTION_COPY)
 		{
-			map->fetch_size += entry->newsize;
+			map->fetch_size += entry->source_size;
 			continue;
 		}
 
 		if (entry->action == FILE_ACTION_COPY_TAIL)
-			map->fetch_size += (entry->newsize - entry->oldsize);
+			map->fetch_size += (entry->source_size - entry->target_size);
 
-		if (entry->pagemap.bitmapsize > 0)
+		if (entry->target_modified_pages.bitmapsize > 0)
 		{
 			datapagemap_iterator_t *iter;
 			BlockNumber blk;
 
-			iter = datapagemap_iterate(&entry->pagemap);
+			iter = datapagemap_iterate(&entry->target_modified_pages);
 			while (datapagemap_next(iter, &blk))
 				map->fetch_size += BLCKSZ;
 
@@ -682,13 +551,13 @@ print_filemap(void)
 	{
 		entry = map->array[i];
 		if (entry->action != FILE_ACTION_NONE ||
-			entry->pagemap.bitmapsize > 0)
+			entry->target_modified_pages.bitmapsize > 0)
 		{
 			pg_log_debug("%s (%s)", entry->path,
 						 action_to_str(entry->action));
 
-			if (entry->pagemap.bitmapsize > 0)
-				datapagemap_print(&entry->pagemap);
+			if (entry->target_modified_pages.bitmapsize > 0)
+				datapagemap_print(&entry->target_modified_pages);
 		}
 	}
 	fflush(stdout);
@@ -837,3 +706,126 @@ final_filemap_cmp(const void *a, const void *b)
 	else
 		return strcmp(fa->path, fb->path);
 }
+
+/*
+ * Decide what to do with each file.
+ */
+void
+filemap_finalize()
+{
+	int			i;
+
+	filemap_list_to_array(filemap);
+
+	for (i = 0; i < filemap->narray; i++)
+	{
+		file_entry_t *entry = filemap->array[i];
+		file_action_t action;
+
+		if (!entry->target_exists && entry->source_exists)
+		{
+			/*
+			 * File exists in source, but not in target. Copy
+			 * it in toto. (If it's a relation data file, WAL replay
+			 * after rewinding should re-create it anyway. But there's
+			 * no harm in copying it now.)
+			 */
+			if (entry->source_type == FILE_TYPE_DIRECTORY ||
+				entry->source_type == FILE_TYPE_SYMLINK)
+			{
+				action = FILE_ACTION_CREATE;
+			}
+			else
+				action = FILE_ACTION_COPY;
+		}
+		else if (entry->target_exists && !entry->source_exists)
+		{
+			/* Delete file from target */
+			action = FILE_ACTION_REMOVE;
+		}
+		else if (entry->target_exists && entry->source_exists)
+		{
+			/* File exists in both systems */
+			if (entry->source_type != entry->target_type)
+			{
+				/* But it's a different kind of object. Strange.. */
+				pg_fatal("file \"%s\" is of different type in source and target", entry->path);
+			}
+
+			switch (entry->source_type)
+			{
+				case FILE_TYPE_DIRECTORY:
+					action = FILE_ACTION_NONE;
+					break;
+
+				case FILE_TYPE_SYMLINK:
+					// FIXME: Check if it points to the same target?
+					action = FILE_ACTION_NONE;
+					break;
+
+				case FILE_TYPE_REGULAR:
+					if (!entry->isrelfile)
+					{
+						/*
+						 * It's a non-data file that we have no special processing for. Copy
+						 * it in toto.
+						 *
+						 * An exception: PG_VERSIONs should be identical, but avoid
+						 * overwriting it for paranoia.
+						 */
+						if (!pg_str_endswith(entry->path, "PG_VERSION"))
+							action = FILE_ACTION_COPY;
+						else
+							action = FILE_ACTION_NONE;
+					}
+					else
+					{
+						/*
+						 * It's a data file that exists in both systems.
+						 *
+						 * If it's larger in target, we can truncate it. There will
+						 * also be a WAL record of the truncation in the source
+						 * system, so WAL replay would eventually truncate the target
+						 * too, but we might as well do it now.
+						 *
+						 * If it's smaller in the target, it means that it has been
+						 * truncated in the target, or enlarged in the source, or
+						 * both. If it was truncated in the target, we need to copy
+						 * the missing tail from the source system. If it was enlarged
+						 * in the source system, there will be WAL records in the
+						 * source system for the new blocks, so we wouldn't need to
+						 * copy them here. But we don't know which scenario we're
+						 * dealing with, and there's no harm in copying the missing
+						 * blocks now, so do it now.
+						 *
+						 * If it's the same size, do nothing here. Any blocks modified
+						 * in the target will be copied based on parsing the target
+						 * system's WAL, and any blocks modified in the source will be
+						 * updated after rewinding, when the source system's WAL is
+						 * replayed.
+						 */
+						if (entry->target_size < entry->source_size)
+							action = FILE_ACTION_COPY_TAIL;
+						else if (entry->target_size > entry->source_size)
+							action = FILE_ACTION_TRUNCATE;
+						else
+							action = FILE_ACTION_NONE;
+					}
+					break;
+				case FILE_TYPE_UNDEFINED:
+					pg_fatal("unknown file type for \"%s\"", entry->path);
+					break;
+			}
+		}
+		else
+		{
+			/* Doesn't exist in either server. Why does it have an entry at all?? */
+			action = FILE_ACTION_NONE;
+		}
+		entry->action = action;
+	}
+
+	/* Sort the actions to the order that they should be performed */
+	qsort(filemap->array, filemap->narray, sizeof(file_entry_t *),
+		  final_filemap_cmp);
+}
diff --git a/src/bin/pg_rewind/filemap.h b/src/bin/pg_rewind/filemap.h
index 0cb7425170c..a5e8df57f40 100644
--- a/src/bin/pg_rewind/filemap.h
+++ b/src/bin/pg_rewind/filemap.h
@@ -14,17 +14,21 @@
 
 /*
  * For every file found in the local or remote system, we have a file entry
- * which says what we are going to do with the file. For relation files,
- * there is also a page map, marking pages in the file that were changed
- * locally.
- *
- * The enum values are sorted in the order we want actions to be processed.
+ * that contains information about the file on both systems.  For relation
+ * files, there is also a page map that marks pages in the file that were
+ * changed in the target after the last common checkpoint.  Each entry also
+ * contains an 'action' field, which says what we are going to do with the
+ * file.
  */
+
+/* these enum values are sorted in the order we want actions to be processed */
 typedef enum
 {
+	FILE_ACTION_UNDECIDED = 0,	/* not decided yet */
+
 	FILE_ACTION_CREATE,			/* create local directory or symbolic link */
 	FILE_ACTION_COPY,			/* copy whole file, overwriting if exists */
-	FILE_ACTION_COPY_TAIL,		/* copy tail from 'oldsize' to 'newsize' */
+	FILE_ACTION_COPY_TAIL,		/* copy tail from 'source_size' to 'target_size' */
 	FILE_ACTION_NONE,			/* no action (we might still copy modified
 								 * blocks based on the parsed WAL) */
 	FILE_ACTION_TRUNCATE,		/* truncate local file to 'newsize' bytes */
@@ -33,6 +37,8 @@ typedef enum
 
 typedef enum
 {
+	FILE_TYPE_UNDEFINED = 0,
+
 	FILE_TYPE_REGULAR,
 	FILE_TYPE_DIRECTORY,
 	FILE_TYPE_SYMLINK
@@ -41,19 +47,30 @@ typedef enum
 typedef struct file_entry_t
 {
 	char	   *path;
-	file_type_t type;
+	bool		isrelfile;		/* is it a relation data file? */
 
-	file_action_t action;
+	/*
+	 * Status of the file in the target.
+	 */
+	bool		target_exists;
+	file_type_t target_type;
+	size_t		target_size; /* for a regular file */
+	char	   *target_link_target; /* for a symlink */
 
-	/* for a regular file */
-	size_t		oldsize;
-	size_t		newsize;
-	bool		isrelfile;		/* is it a relation data file? */
+	datapagemap_t target_modified_pages;
 
-	datapagemap_t pagemap;
+	/*
+	 * Status of the file in the source.
+	 */
+	bool		source_exists;
+	file_type_t source_type;
+	size_t		source_size;
+	char	   *source_link_target; /* for a symlink */
 
-	/* for a symlink */
-	char	   *link_target;
+	/*
+	 * What will we do to the file?
+	 */
+	file_action_t action;
 
 	struct file_entry_t *next;
 } file_entry_t;
@@ -70,20 +87,19 @@ typedef struct filemap_t
 
 	/*
 	 * After processing all the remote files, the entries in the linked list
-	 * are moved to this array. After processing local files, too, all the
+	 * are moved to this array.  After processing local files, too, all the
 	 * local entries are added to the array by filemap_finalize, and sorted in
-	 * the final order. After filemap_finalize, all the entries are in the
+	 * the final order.  After filemap_finalize, all the entries are in the
 	 * array, and the linked list is empty.
 	 */
 	file_entry_t **array;
 	int			narray;			/* current length of array */
 
 	/*
-	 * Summary information. total_size is the total size of the source
-	 * cluster, and fetch_size is the number of bytes that needs to be copied.
+	 * Summary information.
 	 */
-	uint64		total_size;
-	uint64		fetch_size;
+	uint64		total_size;		/* total size of the source cluster */
+	uint64		fetch_size;		/* number of bytes that needs to be copied */
 } filemap_t;
 
 extern filemap_t *filemap;
@@ -94,11 +110,12 @@ extern void print_filemap(void);
 
 /* Functions for populating the filemap */
 extern void process_source_file(const char *path, file_type_t type,
-								size_t newsize, const char *link_target);
+								size_t size, const char *link_target);
 extern void process_target_file(const char *path, file_type_t type,
-								size_t newsize, const char *link_target);
-extern void process_block_change(ForkNumber forknum, RelFileNode rnode,
-								 BlockNumber blkno);
+								size_t size, const char *link_target);
+extern void process_target_wal_block_change(ForkNumber forknum,
+											RelFileNode rnode,
+											BlockNumber blkno);
 extern void filemap_finalize(void);
 
 #endif							/* FILEMAP_H */
diff --git a/src/bin/pg_rewind/libpq_fetch.c b/src/bin/pg_rewind/libpq_fetch.c
index bf4dfc23b96..7fc9161b8c8 100644
--- a/src/bin/pg_rewind/libpq_fetch.c
+++ b/src/bin/pg_rewind/libpq_fetch.c
@@ -465,7 +465,7 @@ libpq_executeFileMap(filemap_t *map)
 		entry = map->array[i];
 
 		/* If this is a relation file, copy the modified blocks */
-		execute_pagemap(&entry->pagemap, entry->path);
+		execute_pagemap(&entry->target_modified_pages, entry->path);
 
 		switch (entry->action)
 		{
@@ -476,15 +476,15 @@ libpq_executeFileMap(filemap_t *map)
 			case FILE_ACTION_COPY:
 				/* Truncate the old file out of the way, if any */
 				open_target_file(entry->path, true);
-				fetch_file_range(entry->path, 0, entry->newsize);
+				fetch_file_range(entry->path, 0, entry->source_size);
 				break;
 
 			case FILE_ACTION_TRUNCATE:
-				truncate_target_file(entry->path, entry->newsize);
+				truncate_target_file(entry->path, entry->source_size);
 				break;
 
 			case FILE_ACTION_COPY_TAIL:
-				fetch_file_range(entry->path, entry->oldsize, entry->newsize);
+				fetch_file_range(entry->path, entry->target_size, entry->source_size);
 				break;
 
 			case FILE_ACTION_REMOVE:
@@ -494,6 +494,10 @@ libpq_executeFileMap(filemap_t *map)
 			case FILE_ACTION_CREATE:
 				create_target(entry);
 				break;
+
+			case FILE_ACTION_UNDECIDED:
+				pg_fatal("no action decided for \"%s\"", entry->path);
+				break;
 		}
 	}
 
diff --git a/src/bin/pg_rewind/parsexlog.c b/src/bin/pg_rewind/parsexlog.c
index 2229c86f9af..2baeb74ae93 100644
--- a/src/bin/pg_rewind/parsexlog.c
+++ b/src/bin/pg_rewind/parsexlog.c
@@ -436,6 +436,6 @@ extractPageInfo(XLogReaderState *record)
 		if (forknum != MAIN_FORKNUM)
 			continue;
 
-		process_block_change(forknum, rnode, blkno);
+		process_target_wal_block_change(forknum, rnode, blkno);
 	}
 }
diff --git a/src/bin/pg_rewind/pg_rewind.c b/src/bin/pg_rewind/pg_rewind.c
index c9b9e480c0f..210984d302b 100644
--- a/src/bin/pg_rewind/pg_rewind.c
+++ b/src/bin/pg_rewind/pg_rewind.c
@@ -369,7 +369,7 @@ main(int argc, char **argv)
 				chkpttli);
 
 	/*
-	 * Build the filemap, by comparing the source and target data directories.
+	 * Collect information about all files in the target and source systems.
 	 */
 	filemap_create();
 	if (showprogress)
@@ -390,8 +390,12 @@ main(int argc, char **argv)
 		pg_log_info("reading WAL in target");
 	extractPageMap(datadir_target, chkptrec, lastcommontliIndex,
 				   ControlFile_target.checkPoint, restore_command);
-	filemap_finalize();
 
+	/*
+	 * We have collected all information we need from both systems. Decide
+	 * what to do with each file.
+	 */
+	filemap_finalize();
 	if (showprogress)
 		calculate_totals();
 
-- 
2.20.1


--------------D93EDEBFB124D563B723F4BD
Content-Type: text/x-patch; charset=UTF-8;
 name="0003-pg_rewind-Replace-the-hybrid-list-array-data-structu.patch"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
 filename*0="0003-pg_rewind-Replace-the-hybrid-list-array-data-structu.pa";
 filename*1="tch"



^ permalink  raw  reply  [nested|flat] 41+ messages in thread

* [PATCH v2 2/5] Refactor pg_rewind for more clear decision making.
@ 2020-08-19 12:34 Heikki Linnakangas <[email protected]>
  0 siblings, 0 replies; 41+ messages in thread

From: Heikki Linnakangas @ 2020-08-19 12:34 UTC (permalink / raw)

Deciding what to do with each file is now a separate step after all the
necessary information has been gathered. It is more clear that way.
Previously, the decision-making was divided between process_source_file()
and process_target_file(), and it was a bit hard to piece together what the
overall rules were.
---
 src/bin/pg_rewind/copy_fetch.c  |  14 +-
 src/bin/pg_rewind/file_ops.c    |  16 +-
 src/bin/pg_rewind/filemap.c     | 569 ++++++++++++++++----------------
 src/bin/pg_rewind/filemap.h     |  67 ++--
 src/bin/pg_rewind/libpq_fetch.c |  12 +-
 src/bin/pg_rewind/parsexlog.c   |   2 +-
 src/bin/pg_rewind/pg_rewind.c   |   8 +-
 7 files changed, 371 insertions(+), 317 deletions(-)

diff --git a/src/bin/pg_rewind/copy_fetch.c b/src/bin/pg_rewind/copy_fetch.c
index 1edab5f1867..18fad32600e 100644
--- a/src/bin/pg_rewind/copy_fetch.c
+++ b/src/bin/pg_rewind/copy_fetch.c
@@ -210,7 +210,7 @@ copy_executeFileMap(filemap_t *map)
 	for (i = 0; i < map->narray; i++)
 	{
 		entry = map->array[i];
-		execute_pagemap(&entry->pagemap, entry->path);
+		execute_pagemap(&entry->target_modified_pages, entry->path);
 
 		switch (entry->action)
 		{
@@ -219,16 +219,16 @@ copy_executeFileMap(filemap_t *map)
 				break;
 
 			case FILE_ACTION_COPY:
-				rewind_copy_file_range(entry->path, 0, entry->newsize, true);
+				rewind_copy_file_range(entry->path, 0, entry->source_size, true);
 				break;
 
 			case FILE_ACTION_TRUNCATE:
-				truncate_target_file(entry->path, entry->newsize);
+				truncate_target_file(entry->path, entry->source_size);
 				break;
 
 			case FILE_ACTION_COPY_TAIL:
-				rewind_copy_file_range(entry->path, entry->oldsize,
-									   entry->newsize, false);
+				rewind_copy_file_range(entry->path, entry->target_size,
+									   entry->source_size, false);
 				break;
 
 			case FILE_ACTION_CREATE:
@@ -238,6 +238,10 @@ copy_executeFileMap(filemap_t *map)
 			case FILE_ACTION_REMOVE:
 				remove_target(entry);
 				break;
+
+			case FILE_ACTION_UNDECIDED:
+				pg_fatal("no action decided for \"%s\"", entry->path);
+				break;
 		}
 	}
 
diff --git a/src/bin/pg_rewind/file_ops.c b/src/bin/pg_rewind/file_ops.c
index 55439db20ba..ec37d0b2e0d 100644
--- a/src/bin/pg_rewind/file_ops.c
+++ b/src/bin/pg_rewind/file_ops.c
@@ -126,8 +126,9 @@ void
 remove_target(file_entry_t *entry)
 {
 	Assert(entry->action == FILE_ACTION_REMOVE);
+	Assert(entry->target_exists);
 
-	switch (entry->type)
+	switch (entry->target_type)
 	{
 		case FILE_TYPE_DIRECTORY:
 			remove_target_dir(entry->path);
@@ -140,6 +141,10 @@ remove_target(file_entry_t *entry)
 		case FILE_TYPE_SYMLINK:
 			remove_target_symlink(entry->path);
 			break;
+
+		case FILE_TYPE_UNDEFINED:
+			pg_fatal("undefined file type for \"%s\"", entry->path);
+			break;
 	}
 }
 
@@ -147,21 +152,26 @@ void
 create_target(file_entry_t *entry)
 {
 	Assert(entry->action == FILE_ACTION_CREATE);
+	Assert(!entry->target_exists);
 
-	switch (entry->type)
+	switch (entry->source_type)
 	{
 		case FILE_TYPE_DIRECTORY:
 			create_target_dir(entry->path);
 			break;
 
 		case FILE_TYPE_SYMLINK:
-			create_target_symlink(entry->path, entry->link_target);
+			create_target_symlink(entry->path, entry->source_link_target);
 			break;
 
 		case FILE_TYPE_REGULAR:
 			/* can't happen. Regular files are created with open_target_file. */
 			pg_fatal("invalid action (CREATE) for regular file");
 			break;
+
+		case FILE_TYPE_UNDEFINED:
+			pg_fatal("undefined file type for \"%s\"", entry->path);
+			break;
 	}
 }
 
diff --git a/src/bin/pg_rewind/filemap.c b/src/bin/pg_rewind/filemap.c
index 1879257b66a..7971daeda5e 100644
--- a/src/bin/pg_rewind/filemap.c
+++ b/src/bin/pg_rewind/filemap.c
@@ -26,6 +26,8 @@ static bool isRelDataFile(const char *path);
 static char *datasegpath(RelFileNode rnode, ForkNumber forknum,
 						 BlockNumber segno);
 static int	path_cmp(const void *a, const void *b);
+
+static file_entry_t *get_filemap_entry(const char *path, bool create);
 static int	final_filemap_cmp(const void *a, const void *b);
 static void filemap_list_to_array(filemap_t *map);
 static bool check_file_excluded(const char *path, bool is_source);
@@ -146,6 +148,65 @@ filemap_create(void)
 	filemap = map;
 }
 
+/* Look up or create entry for 'path' */
+static file_entry_t *
+get_filemap_entry(const char *path, bool create)
+{
+	filemap_t  *map = filemap;
+	file_entry_t *entry;
+	file_entry_t **e;
+	file_entry_t key;
+	file_entry_t *key_ptr;
+
+	if (map->array)
+	{
+		key.path = (char *) path;
+		key_ptr = &key;
+		e = bsearch(&key_ptr, map->array, map->narray, sizeof(file_entry_t *),
+					path_cmp);
+	}
+	else
+		e = NULL;
+
+	if (e)
+		entry = *e;
+	else if (!create)
+		entry = NULL;
+	else
+	{
+		/* Create a new entry for this file */
+		entry = pg_malloc(sizeof(file_entry_t));
+		entry->path = pg_strdup(path);
+		entry->isrelfile = isRelDataFile(path);
+		entry->action = FILE_ACTION_UNDECIDED;
+
+		entry->target_exists = false;
+		entry->target_type = FILE_TYPE_UNDEFINED;
+		entry->target_size = 0;
+		entry->target_link_target = NULL;
+		entry->target_modified_pages.bitmap = NULL;
+		entry->target_modified_pages.bitmapsize = 0;
+
+		entry->source_exists = false;
+		entry->source_type = FILE_TYPE_UNDEFINED;
+		entry->source_size = 0;
+		entry->source_link_target = NULL;
+
+		entry->next = NULL;
+
+		if (map->last)
+		{
+			map->last->next = entry;
+			map->last = entry;
+		}
+		else
+			map->first = map->last = entry;
+		map->nlist++;
+	}
+
+	return entry;
+}
+
 /*
  * Callback for processing source file list.
  *
@@ -154,25 +215,12 @@ filemap_create(void)
  * exists in the target and whether the size matches.
  */
 void
-process_source_file(const char *path, file_type_t type, size_t newsize,
+process_source_file(const char *path, file_type_t type, size_t size,
 					const char *link_target)
 {
-	bool		exists;
-	char		localpath[MAXPGPATH];
-	struct stat statbuf;
-	filemap_t  *map = filemap;
-	file_action_t action = FILE_ACTION_NONE;
-	size_t		oldsize = 0;
 	file_entry_t *entry;
 
-	Assert(map->array == NULL);
-
-	/*
-	 * Skip any files matching the exclusion filters. This has the effect to
-	 * remove all those files on the target.
-	 */
-	if (check_file_excluded(path, true))
-		return;
+	Assert(filemap->array == NULL);
 
 	/*
 	 * Pretend that pg_wal is a directory, even if it's really a symlink. We
@@ -182,16 +230,6 @@ process_source_file(const char *path, file_type_t type, size_t newsize,
 	if (strcmp(path, "pg_wal") == 0 && type == FILE_TYPE_SYMLINK)
 		type = FILE_TYPE_DIRECTORY;
 
-	/*
-	 * Skip temporary files, .../pgsql_tmp/... and .../pgsql_tmp.* in source.
-	 * This has the effect that all temporary files in the destination will be
-	 * removed.
-	 */
-	if (strstr(path, "/" PG_TEMP_FILE_PREFIX) != NULL)
-		return;
-	if (strstr(path, "/" PG_TEMP_FILES_DIR "/") != NULL)
-		return;
-
 	/*
 	 * sanity check: a filename that looks like a data file better be a
 	 * regular file
@@ -199,142 +237,12 @@ process_source_file(const char *path, file_type_t type, size_t newsize,
 	if (type != FILE_TYPE_REGULAR && isRelDataFile(path))
 		pg_fatal("data file \"%s\" in source is not a regular file", path);
 
-	snprintf(localpath, sizeof(localpath), "%s/%s", datadir_target, path);
-
-	/* Does the corresponding file exist in the target data dir? */
-	if (lstat(localpath, &statbuf) < 0)
-	{
-		if (errno != ENOENT)
-			pg_fatal("could not stat file \"%s\": %m",
-					 localpath);
-
-		exists = false;
-	}
-	else
-		exists = true;
-
-	switch (type)
-	{
-		case FILE_TYPE_DIRECTORY:
-			if (exists && !S_ISDIR(statbuf.st_mode) && strcmp(path, "pg_wal") != 0)
-			{
-				/* it's a directory in source, but not in target. Strange.. */
-				pg_fatal("\"%s\" is not a directory", localpath);
-			}
-
-			if (!exists)
-				action = FILE_ACTION_CREATE;
-			else
-				action = FILE_ACTION_NONE;
-			oldsize = 0;
-			break;
-
-		case FILE_TYPE_SYMLINK:
-			if (exists &&
-#ifndef WIN32
-				!S_ISLNK(statbuf.st_mode)
-#else
-				!pgwin32_is_junction(localpath)
-#endif
-				)
-			{
-				/*
-				 * It's a symbolic link in source, but not in target.
-				 * Strange..
-				 */
-				pg_fatal("\"%s\" is not a symbolic link", localpath);
-			}
-
-			if (!exists)
-				action = FILE_ACTION_CREATE;
-			else
-				action = FILE_ACTION_NONE;
-			oldsize = 0;
-			break;
-
-		case FILE_TYPE_REGULAR:
-			if (exists && !S_ISREG(statbuf.st_mode))
-				pg_fatal("\"%s\" is not a regular file", localpath);
-
-			if (!exists || !isRelDataFile(path))
-			{
-				/*
-				 * File exists in source, but not in target. Or it's a
-				 * non-data file that we have no special processing for. Copy
-				 * it in toto.
-				 *
-				 * An exception: PG_VERSIONs should be identical, but avoid
-				 * overwriting it for paranoia.
-				 */
-				if (pg_str_endswith(path, "PG_VERSION"))
-				{
-					action = FILE_ACTION_NONE;
-					oldsize = statbuf.st_size;
-				}
-				else
-				{
-					action = FILE_ACTION_COPY;
-					oldsize = 0;
-				}
-			}
-			else
-			{
-				/*
-				 * It's a data file that exists in both.
-				 *
-				 * If it's larger in target, we can truncate it. There will
-				 * also be a WAL record of the truncation in the source
-				 * system, so WAL replay would eventually truncate the target
-				 * too, but we might as well do it now.
-				 *
-				 * If it's smaller in the target, it means that it has been
-				 * truncated in the target, or enlarged in the source, or
-				 * both. If it was truncated in the target, we need to copy
-				 * the missing tail from the source system. If it was enlarged
-				 * in the source system, there will be WAL records in the
-				 * source system for the new blocks, so we wouldn't need to
-				 * copy them here. But we don't know which scenario we're
-				 * dealing with, and there's no harm in copying the missing
-				 * blocks now, so do it now.
-				 *
-				 * If it's the same size, do nothing here. Any blocks modified
-				 * in the target will be copied based on parsing the target
-				 * system's WAL, and any blocks modified in the source will be
-				 * updated after rewinding, when the source system's WAL is
-				 * replayed.
-				 */
-				oldsize = statbuf.st_size;
-				if (oldsize < newsize)
-					action = FILE_ACTION_COPY_TAIL;
-				else if (oldsize > newsize)
-					action = FILE_ACTION_TRUNCATE;
-				else
-					action = FILE_ACTION_NONE;
-			}
-			break;
-	}
-
-	/* Create a new entry for this file */
-	entry = pg_malloc(sizeof(file_entry_t));
-	entry->path = pg_strdup(path);
-	entry->type = type;
-	entry->action = action;
-	entry->oldsize = oldsize;
-	entry->newsize = newsize;
-	entry->link_target = link_target ? pg_strdup(link_target) : NULL;
-	entry->next = NULL;
-	entry->pagemap.bitmap = NULL;
-	entry->pagemap.bitmapsize = 0;
-	entry->isrelfile = isRelDataFile(path);
-
-	if (map->last)
-	{
-		map->last->next = entry;
-		map->last = entry;
-	}
-	else
-		map->first = map->last = entry;
-	map->nlist++;
+	/* Remember this source file */
+	entry = get_filemap_entry(path, true);
+	entry->source_exists = true;
+	entry->source_type = type;
+	entry->source_size = size;
+	entry->source_link_target = link_target ? pg_strdup(link_target) : NULL;
 }
 
 /*
@@ -345,14 +253,9 @@ process_source_file(const char *path, file_type_t type, size_t newsize,
  * deletion.
  */
 void
-process_target_file(const char *path, file_type_t type, size_t oldsize,
+process_target_file(const char *path, file_type_t type, size_t size,
 					const char *link_target)
 {
-	bool		exists;
-	char		localpath[MAXPGPATH];
-	struct stat statbuf;
-	file_entry_t key;
-	file_entry_t *key_ptr;
 	filemap_t  *map = filemap;
 	file_entry_t *entry;
 
@@ -361,17 +264,6 @@ process_target_file(const char *path, file_type_t type, size_t oldsize,
 	 * from the target data folder all paths which have been filtered out from
 	 * the source data folder when processing the source files.
 	 */
-
-	snprintf(localpath, sizeof(localpath), "%s/%s", datadir_target, path);
-	if (lstat(localpath, &statbuf) < 0)
-	{
-		if (errno != ENOENT)
-			pg_fatal("could not stat file \"%s\": %m",
-					 localpath);
-
-		exists = false;
-	}
-
 	if (map->array == NULL)
 	{
 		/* on first call, initialize lookup array */
@@ -389,120 +281,76 @@ process_target_file(const char *path, file_type_t type, size_t oldsize,
 	}
 
 	/*
-	 * Like in process_source_file, pretend that xlog is always a  directory.
+	 * Like in process_source_file, pretend that pg_wal is always a directory.
 	 */
 	if (strcmp(path, "pg_wal") == 0 && type == FILE_TYPE_SYMLINK)
 		type = FILE_TYPE_DIRECTORY;
 
-	key.path = (char *) path;
-	key_ptr = &key;
-	exists = (bsearch(&key_ptr, map->array, map->narray, sizeof(file_entry_t *),
-					  path_cmp) != NULL);
-
-	/* Remove any file or folder that doesn't exist in the source system. */
-	if (!exists)
-	{
-		entry = pg_malloc(sizeof(file_entry_t));
-		entry->path = pg_strdup(path);
-		entry->type = type;
-		entry->action = FILE_ACTION_REMOVE;
-		entry->oldsize = oldsize;
-		entry->newsize = 0;
-		entry->link_target = link_target ? pg_strdup(link_target) : NULL;
-		entry->next = NULL;
-		entry->pagemap.bitmap = NULL;
-		entry->pagemap.bitmapsize = 0;
-		entry->isrelfile = isRelDataFile(path);
-
-		if (map->last == NULL)
-			map->first = entry;
-		else
-			map->last->next = entry;
-		map->last = entry;
-		map->nlist++;
-	}
-	else
-	{
-		/*
-		 * We already handled all files that exist in the source system in
-		 * process_source_file().
-		 */
-	}
+	/* Remember this target file */
+	entry = get_filemap_entry(path, true);
+	entry->target_exists = true;
+	entry->target_type = type;
+	entry->target_size = size;
+	entry->target_link_target = link_target ? pg_strdup(link_target) : NULL;
 }
 
 /*
  * This callback gets called while we read the WAL in the target, for every
- * block that have changed in the target system. It makes note of all the
+ * block that have changed in the target system.  It makes note of all the
  * changed blocks in the pagemap of the file.
+ *
+ * NOTE: All the files on both systems must have already been added to the
+ * file map!
  */
 void
-process_block_change(ForkNumber forknum, RelFileNode rnode, BlockNumber blkno)
+process_target_wal_block_change(ForkNumber forknum, RelFileNode rnode,
+								BlockNumber blkno)
 {
 	char	   *path;
-	file_entry_t key;
-	file_entry_t *key_ptr;
 	file_entry_t *entry;
 	BlockNumber blkno_inseg;
 	int			segno;
-	filemap_t  *map = filemap;
-	file_entry_t **e;
 
-	Assert(map->array);
+	Assert(filemap->array);
 
 	segno = blkno / RELSEG_SIZE;
 	blkno_inseg = blkno % RELSEG_SIZE;
 
 	path = datasegpath(rnode, forknum, segno);
-
-	key.path = (char *) path;
-	key_ptr = &key;
-
-	e = bsearch(&key_ptr, map->array, map->narray, sizeof(file_entry_t *),
-				path_cmp);
-	if (e)
-		entry = *e;
-	else
-		entry = NULL;
+	entry = get_filemap_entry(path, false);
 	pfree(path);
 
 	if (entry)
 	{
-		Assert(entry->isrelfile);
-
-		switch (entry->action)
-		{
-			case FILE_ACTION_NONE:
-			case FILE_ACTION_TRUNCATE:
-				/* skip if we're truncating away the modified block anyway */
-				if ((blkno_inseg + 1) * BLCKSZ <= entry->newsize)
-					datapagemap_add(&entry->pagemap, blkno_inseg);
-				break;
+		int64		end_offset;
 
-			case FILE_ACTION_COPY_TAIL:
-
-				/*
-				 * skip the modified block if it is part of the "tail" that
-				 * we're copying anyway.
-				 */
-				if ((blkno_inseg + 1) * BLCKSZ <= entry->oldsize)
-					datapagemap_add(&entry->pagemap, blkno_inseg);
-				break;
+		Assert(entry->isrelfile);
 
-			case FILE_ACTION_COPY:
-			case FILE_ACTION_REMOVE:
-				break;
+		if (entry->target_type != FILE_TYPE_REGULAR)
+			pg_fatal("unexpected page modification for directory or symbolic link \"%s\"",
+					 entry->path);
 
-			case FILE_ACTION_CREATE:
-				pg_fatal("unexpected page modification for directory or symbolic link \"%s\"", entry->path);
-		}
+		/*
+		 * If the block beyond the EOF in the source system, no need to
+		 * remember it now, because we're going to truncate it away from the
+		 * target anyway. Also no need to remember the block if it's beyond
+		 * the current EOF in the target system; we will copy it over with the
+		 * "tail" from the source system, anyway.
+		 */
+		end_offset = (blkno_inseg + 1) * BLCKSZ;
+		if (end_offset <= entry->source_size &&
+			end_offset <= entry->target_size)
+			datapagemap_add(&entry->target_modified_pages, blkno_inseg);
 	}
 	else
 	{
 		/*
 		 * If we don't have any record of this file in the file map, it means
-		 * that it's a relation that doesn't exist in the source system, and
-		 * it was subsequently removed in the target system, too. We can
-		 * safely ignore it.
+		 * that it's a relation that doesn't exist in the source system.  It
+		 * could exist in the target system; we haven't moved the target-only
+		 * entries from the linked list to the array yet!  But in any case, if
+		 * it doesn't exist in the source it will be removed from the target
+		 * too, and we can safely ignore it.
 		 */
 	}
 }
@@ -593,16 +441,6 @@ filemap_list_to_array(filemap_t *map)
 	map->first = map->last = NULL;
 }
 
-void
-filemap_finalize(void)
-{
-	filemap_t  *map = filemap;
-
-	filemap_list_to_array(map);
-	qsort(map->array, map->narray, sizeof(file_entry_t *),
-		  final_filemap_cmp);
-}
-
 static const char *
 action_to_str(file_action_t action)
 {
@@ -643,26 +481,26 @@ calculate_totals(void)
 	{
 		entry = map->array[i];
 
-		if (entry->type != FILE_TYPE_REGULAR)
+		if (entry->source_type != FILE_TYPE_REGULAR)
 			continue;
 
-		map->total_size += entry->newsize;
+		map->total_size += entry->source_size;
 
 		if (entry->action == FILE_ACTION_COPY)
 		{
-			map->fetch_size += entry->newsize;
+			map->fetch_size += entry->source_size;
 			continue;
 		}
 
 		if (entry->action == FILE_ACTION_COPY_TAIL)
-			map->fetch_size += (entry->newsize - entry->oldsize);
+			map->fetch_size += (entry->source_size - entry->target_size);
 
-		if (entry->pagemap.bitmapsize > 0)
+		if (entry->target_modified_pages.bitmapsize > 0)
 		{
 			datapagemap_iterator_t *iter;
 			BlockNumber blk;
 
-			iter = datapagemap_iterate(&entry->pagemap);
+			iter = datapagemap_iterate(&entry->target_modified_pages);
 			while (datapagemap_next(iter, &blk))
 				map->fetch_size += BLCKSZ;
 
@@ -682,13 +520,13 @@ print_filemap(void)
 	{
 		entry = map->array[i];
 		if (entry->action != FILE_ACTION_NONE ||
-			entry->pagemap.bitmapsize > 0)
+			entry->target_modified_pages.bitmapsize > 0)
 		{
 			pg_log_debug("%s (%s)", entry->path,
 						 action_to_str(entry->action));
 
-			if (entry->pagemap.bitmapsize > 0)
-				datapagemap_print(&entry->pagemap);
+			if (entry->target_modified_pages.bitmapsize > 0)
+				datapagemap_print(&entry->target_modified_pages);
 		}
 	}
 	fflush(stdout);
@@ -837,3 +675,180 @@ final_filemap_cmp(const void *a, const void *b)
 	else
 		return strcmp(fa->path, fb->path);
 }
+
+/*
+ * Decide what action to perform to a file.
+ */
+static file_action_t
+decide_file_action(file_entry_t *entry)
+{
+	const char *path = entry->path;
+
+	/*
+	 * Don't touch the control file. It is handled specially, after copying
+	 * all the other files.
+	 */
+	if (strcmp(path, "global/pg_control") == 0)
+		return FILE_ACTION_NONE;
+
+	/*
+	 * Remove all files matching the exclusion filters in the target.
+	 */
+	if (check_file_excluded(path, true))
+	{
+		if (entry->target_exists)
+			return FILE_ACTION_REMOVE;
+		else
+			return FILE_ACTION_NONE;
+	}
+
+	/*
+	 * Also remove all temporary files, .../pgsql_tmp/... and .../pgsql_tmp.*
+	 * in the target.
+	 */
+	if (strstr(path, "/" PG_TEMP_FILE_PREFIX) != NULL ||
+		strstr(path, "/" PG_TEMP_FILES_DIR "/") != NULL)
+	{
+		if (entry->target_exists)
+			return FILE_ACTION_REMOVE;
+		else
+			return FILE_ACTION_NONE;
+	}
+
+	/*
+	 * Handle cases where the file is missing from one of the systems.
+	 */
+	if (!entry->target_exists && entry->source_exists)
+	{
+		/*
+		 * File exists in source, but not in target. Copy it in toto. (If it's
+		 * a relation data file, WAL replay after rewinding should re-create
+		 * it anyway. But there's no harm in copying it now.)
+		 */
+		switch (entry->source_type)
+		{
+			case FILE_TYPE_DIRECTORY:
+			case FILE_TYPE_SYMLINK:
+				return FILE_ACTION_CREATE;
+			case FILE_TYPE_REGULAR:
+				return FILE_ACTION_COPY;
+			case FILE_TYPE_UNDEFINED:
+				pg_fatal("unknown file type for \"%s\"", entry->path);
+				break;
+		}
+	}
+	else if (entry->target_exists && !entry->source_exists)
+	{
+		/* File exists in target, but not source. Remove it. */
+		return FILE_ACTION_REMOVE;
+	}
+	else if (!entry->target_exists && !entry->source_exists)
+	{
+		/*
+		 * Doesn't exist in either server. Why does it have an entry in the
+		 * first place??
+		 */
+		return FILE_ACTION_NONE;
+	}
+
+	/*
+	 * Otherwise, the file exists on both systems
+	 */
+	Assert(entry->target_exists && entry->source_exists);
+
+	if (entry->source_type != entry->target_type)
+	{
+		/* But it's a different kind of object. Strange.. */
+		pg_fatal("file \"%s\" is of different type in source and target", entry->path);
+	}
+
+	/*
+	 * PG_VERSION files should be identical on both systems, but avoid
+	 * overwriting them for paranoia.
+	 */
+	if (pg_str_endswith(entry->path, "PG_VERSION"))
+		return FILE_ACTION_NONE;
+
+	switch (entry->source_type)
+	{
+		case FILE_TYPE_DIRECTORY:
+			return FILE_ACTION_NONE;
+
+		case FILE_TYPE_SYMLINK:
+			/* FIXME: Check if it points to the same target? */
+			return FILE_ACTION_NONE;
+
+		case FILE_TYPE_REGULAR:
+			if (!entry->isrelfile)
+			{
+				/*
+				 * It's a non-data file that we have no special processing
+				 * for. Copy it in toto.
+				 */
+				return FILE_ACTION_COPY;
+			}
+			else
+			{
+				/*
+				 * It's a data file that exists in both systems.
+				 *
+				 * If it's larger in target, we can truncate it. There will
+				 * also be a WAL record of the truncation in the source
+				 * system, so WAL replay would eventually truncate the target
+				 * too, but we might as well do it now.
+				 *
+				 * If it's smaller in the target, it means that it has been
+				 * truncated in the target, or enlarged in the source, or
+				 * both. If it was truncated in the target, we need to copy
+				 * the missing tail from the source system. If it was enlarged
+				 * in the source system, there will be WAL records in the
+				 * source system for the new blocks, so we wouldn't need to
+				 * copy them here. But we don't know which scenario we're
+				 * dealing with, and there's no harm in copying the missing
+				 * blocks now, so do it now.
+				 *
+				 * If it's the same size, do nothing here. Any blocks modified
+				 * in the target will be copied based on parsing the target
+				 * system's WAL, and any blocks modified in the source will be
+				 * updated after rewinding, when the source system's WAL is
+				 * replayed.
+				 */
+				if (entry->target_size < entry->source_size)
+					return FILE_ACTION_COPY_TAIL;
+				else if (entry->target_size > entry->source_size)
+					return FILE_ACTION_TRUNCATE;
+				else
+					return FILE_ACTION_NONE;
+			}
+			break;
+
+		case FILE_TYPE_UNDEFINED:
+			pg_fatal("unknown file type for \"%s\"", path);
+			break;
+	}
+
+	/* unreachable */
+	pg_fatal("could not decide what to do with file \"%s\"", path);
+}
+
+/*
+ * Decide what to do with each file.
+ */
+void
+filemap_finalize()
+{
+	int			i;
+
+	filemap_list_to_array(filemap);
+
+	for (i = 0; i < filemap->narray; i++)
+	{
+		file_entry_t *entry = filemap->array[i];
+
+		entry->action = decide_file_action(entry);
+	}
+
+	/* Sort the actions to the order that they should be performed */
+	qsort(filemap->array, filemap->narray, sizeof(file_entry_t *),
+		  final_filemap_cmp);
+}
diff --git a/src/bin/pg_rewind/filemap.h b/src/bin/pg_rewind/filemap.h
index 0cb7425170c..a5e8df57f40 100644
--- a/src/bin/pg_rewind/filemap.h
+++ b/src/bin/pg_rewind/filemap.h
@@ -14,17 +14,21 @@
 
 /*
  * For every file found in the local or remote system, we have a file entry
- * which says what we are going to do with the file. For relation files,
- * there is also a page map, marking pages in the file that were changed
- * locally.
- *
- * The enum values are sorted in the order we want actions to be processed.
+ * that contains information about the file on both systems.  For relation
+ * files, there is also a page map that marks pages in the file that were
+ * changed in the target after the last common checkpoint.  Each entry also
+ * contains an 'action' field, which says what we are going to do with the
+ * file.
  */
+
+/* these enum values are sorted in the order we want actions to be processed */
 typedef enum
 {
+	FILE_ACTION_UNDECIDED = 0,	/* not decided yet */
+
 	FILE_ACTION_CREATE,			/* create local directory or symbolic link */
 	FILE_ACTION_COPY,			/* copy whole file, overwriting if exists */
-	FILE_ACTION_COPY_TAIL,		/* copy tail from 'oldsize' to 'newsize' */
+	FILE_ACTION_COPY_TAIL,		/* copy tail from 'source_size' to 'target_size' */
 	FILE_ACTION_NONE,			/* no action (we might still copy modified
 								 * blocks based on the parsed WAL) */
 	FILE_ACTION_TRUNCATE,		/* truncate local file to 'newsize' bytes */
@@ -33,6 +37,8 @@ typedef enum
 
 typedef enum
 {
+	FILE_TYPE_UNDEFINED = 0,
+
 	FILE_TYPE_REGULAR,
 	FILE_TYPE_DIRECTORY,
 	FILE_TYPE_SYMLINK
@@ -41,19 +47,30 @@ typedef enum
 typedef struct file_entry_t
 {
 	char	   *path;
-	file_type_t type;
+	bool		isrelfile;		/* is it a relation data file? */
 
-	file_action_t action;
+	/*
+	 * Status of the file in the target.
+	 */
+	bool		target_exists;
+	file_type_t target_type;
+	size_t		target_size; /* for a regular file */
+	char	   *target_link_target; /* for a symlink */
 
-	/* for a regular file */
-	size_t		oldsize;
-	size_t		newsize;
-	bool		isrelfile;		/* is it a relation data file? */
+	datapagemap_t target_modified_pages;
 
-	datapagemap_t pagemap;
+	/*
+	 * Status of the file in the source.
+	 */
+	bool		source_exists;
+	file_type_t source_type;
+	size_t		source_size;
+	char	   *source_link_target; /* for a symlink */
 
-	/* for a symlink */
-	char	   *link_target;
+	/*
+	 * What will we do to the file?
+	 */
+	file_action_t action;
 
 	struct file_entry_t *next;
 } file_entry_t;
@@ -70,20 +87,19 @@ typedef struct filemap_t
 
 	/*
 	 * After processing all the remote files, the entries in the linked list
-	 * are moved to this array. After processing local files, too, all the
+	 * are moved to this array.  After processing local files, too, all the
 	 * local entries are added to the array by filemap_finalize, and sorted in
-	 * the final order. After filemap_finalize, all the entries are in the
+	 * the final order.  After filemap_finalize, all the entries are in the
 	 * array, and the linked list is empty.
 	 */
 	file_entry_t **array;
 	int			narray;			/* current length of array */
 
 	/*
-	 * Summary information. total_size is the total size of the source
-	 * cluster, and fetch_size is the number of bytes that needs to be copied.
+	 * Summary information.
 	 */
-	uint64		total_size;
-	uint64		fetch_size;
+	uint64		total_size;		/* total size of the source cluster */
+	uint64		fetch_size;		/* number of bytes that needs to be copied */
 } filemap_t;
 
 extern filemap_t *filemap;
@@ -94,11 +110,12 @@ extern void print_filemap(void);
 
 /* Functions for populating the filemap */
 extern void process_source_file(const char *path, file_type_t type,
-								size_t newsize, const char *link_target);
+								size_t size, const char *link_target);
 extern void process_target_file(const char *path, file_type_t type,
-								size_t newsize, const char *link_target);
-extern void process_block_change(ForkNumber forknum, RelFileNode rnode,
-								 BlockNumber blkno);
+								size_t size, const char *link_target);
+extern void process_target_wal_block_change(ForkNumber forknum,
+											RelFileNode rnode,
+											BlockNumber blkno);
 extern void filemap_finalize(void);
 
 #endif							/* FILEMAP_H */
diff --git a/src/bin/pg_rewind/libpq_fetch.c b/src/bin/pg_rewind/libpq_fetch.c
index bf4dfc23b96..7fc9161b8c8 100644
--- a/src/bin/pg_rewind/libpq_fetch.c
+++ b/src/bin/pg_rewind/libpq_fetch.c
@@ -465,7 +465,7 @@ libpq_executeFileMap(filemap_t *map)
 		entry = map->array[i];
 
 		/* If this is a relation file, copy the modified blocks */
-		execute_pagemap(&entry->pagemap, entry->path);
+		execute_pagemap(&entry->target_modified_pages, entry->path);
 
 		switch (entry->action)
 		{
@@ -476,15 +476,15 @@ libpq_executeFileMap(filemap_t *map)
 			case FILE_ACTION_COPY:
 				/* Truncate the old file out of the way, if any */
 				open_target_file(entry->path, true);
-				fetch_file_range(entry->path, 0, entry->newsize);
+				fetch_file_range(entry->path, 0, entry->source_size);
 				break;
 
 			case FILE_ACTION_TRUNCATE:
-				truncate_target_file(entry->path, entry->newsize);
+				truncate_target_file(entry->path, entry->source_size);
 				break;
 
 			case FILE_ACTION_COPY_TAIL:
-				fetch_file_range(entry->path, entry->oldsize, entry->newsize);
+				fetch_file_range(entry->path, entry->target_size, entry->source_size);
 				break;
 
 			case FILE_ACTION_REMOVE:
@@ -494,6 +494,10 @@ libpq_executeFileMap(filemap_t *map)
 			case FILE_ACTION_CREATE:
 				create_target(entry);
 				break;
+
+			case FILE_ACTION_UNDECIDED:
+				pg_fatal("no action decided for \"%s\"", entry->path);
+				break;
 		}
 	}
 
diff --git a/src/bin/pg_rewind/parsexlog.c b/src/bin/pg_rewind/parsexlog.c
index 2229c86f9af..2baeb74ae93 100644
--- a/src/bin/pg_rewind/parsexlog.c
+++ b/src/bin/pg_rewind/parsexlog.c
@@ -436,6 +436,6 @@ extractPageInfo(XLogReaderState *record)
 		if (forknum != MAIN_FORKNUM)
 			continue;
 
-		process_block_change(forknum, rnode, blkno);
+		process_target_wal_block_change(forknum, rnode, blkno);
 	}
 }
diff --git a/src/bin/pg_rewind/pg_rewind.c b/src/bin/pg_rewind/pg_rewind.c
index c9b9e480c0f..210984d302b 100644
--- a/src/bin/pg_rewind/pg_rewind.c
+++ b/src/bin/pg_rewind/pg_rewind.c
@@ -369,7 +369,7 @@ main(int argc, char **argv)
 				chkpttli);
 
 	/*
-	 * Build the filemap, by comparing the source and target data directories.
+	 * Collect information about all files in the target and source systems.
 	 */
 	filemap_create();
 	if (showprogress)
@@ -390,8 +390,12 @@ main(int argc, char **argv)
 		pg_log_info("reading WAL in target");
 	extractPageMap(datadir_target, chkptrec, lastcommontliIndex,
 				   ControlFile_target.checkPoint, restore_command);
-	filemap_finalize();
 
+	/*
+	 * We have collected all information we need from both systems. Decide
+	 * what to do with each file.
+	 */
+	filemap_finalize();
 	if (showprogress)
 		calculate_totals();
 
-- 
2.20.1


--------------BF34D0120055DC3839060F92
Content-Type: text/x-patch; charset=UTF-8;
 name="v2-0003-pg_rewind-Replace-the-hybrid-list-array-data-stru.patch"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
 filename*0="v2-0003-pg_rewind-Replace-the-hybrid-list-array-data-stru.pa";
 filename*1="tch"



^ permalink  raw  reply  [nested|flat] 41+ messages in thread

* [PATCH 2/5] Refactor pg_rewind for more clear decision making.
@ 2020-08-19 12:34 Heikki Linnakangas <[email protected]>
  0 siblings, 0 replies; 41+ messages in thread

From: Heikki Linnakangas @ 2020-08-19 12:34 UTC (permalink / raw)

Deciding what to do with each file is now a separate step after all the
necessary information has been gathered. It is more clear that way.
Previously, the decision-making was divided between process_source_file()
and process_target_file(), and it was a bit hard to piece together what the
overall rules were.
---
 src/bin/pg_rewind/copy_fetch.c  |  14 +-
 src/bin/pg_rewind/file_ops.c    |  16 +-
 src/bin/pg_rewind/filemap.c     | 514 ++++++++++++++++----------------
 src/bin/pg_rewind/filemap.h     |  67 +++--
 src/bin/pg_rewind/libpq_fetch.c |  12 +-
 src/bin/pg_rewind/parsexlog.c   |   2 +-
 src/bin/pg_rewind/pg_rewind.c   |   8 +-
 7 files changed, 332 insertions(+), 301 deletions(-)

diff --git a/src/bin/pg_rewind/copy_fetch.c b/src/bin/pg_rewind/copy_fetch.c
index 1edab5f1867..18fad32600e 100644
--- a/src/bin/pg_rewind/copy_fetch.c
+++ b/src/bin/pg_rewind/copy_fetch.c
@@ -210,7 +210,7 @@ copy_executeFileMap(filemap_t *map)
 	for (i = 0; i < map->narray; i++)
 	{
 		entry = map->array[i];
-		execute_pagemap(&entry->pagemap, entry->path);
+		execute_pagemap(&entry->target_modified_pages, entry->path);
 
 		switch (entry->action)
 		{
@@ -219,16 +219,16 @@ copy_executeFileMap(filemap_t *map)
 				break;
 
 			case FILE_ACTION_COPY:
-				rewind_copy_file_range(entry->path, 0, entry->newsize, true);
+				rewind_copy_file_range(entry->path, 0, entry->source_size, true);
 				break;
 
 			case FILE_ACTION_TRUNCATE:
-				truncate_target_file(entry->path, entry->newsize);
+				truncate_target_file(entry->path, entry->source_size);
 				break;
 
 			case FILE_ACTION_COPY_TAIL:
-				rewind_copy_file_range(entry->path, entry->oldsize,
-									   entry->newsize, false);
+				rewind_copy_file_range(entry->path, entry->target_size,
+									   entry->source_size, false);
 				break;
 
 			case FILE_ACTION_CREATE:
@@ -238,6 +238,10 @@ copy_executeFileMap(filemap_t *map)
 			case FILE_ACTION_REMOVE:
 				remove_target(entry);
 				break;
+
+			case FILE_ACTION_UNDECIDED:
+				pg_fatal("no action decided for \"%s\"", entry->path);
+				break;
 		}
 	}
 
diff --git a/src/bin/pg_rewind/file_ops.c b/src/bin/pg_rewind/file_ops.c
index 55439db20ba..ec37d0b2e0d 100644
--- a/src/bin/pg_rewind/file_ops.c
+++ b/src/bin/pg_rewind/file_ops.c
@@ -126,8 +126,9 @@ void
 remove_target(file_entry_t *entry)
 {
 	Assert(entry->action == FILE_ACTION_REMOVE);
+	Assert(entry->target_exists);
 
-	switch (entry->type)
+	switch (entry->target_type)
 	{
 		case FILE_TYPE_DIRECTORY:
 			remove_target_dir(entry->path);
@@ -140,6 +141,10 @@ remove_target(file_entry_t *entry)
 		case FILE_TYPE_SYMLINK:
 			remove_target_symlink(entry->path);
 			break;
+
+		case FILE_TYPE_UNDEFINED:
+			pg_fatal("undefined file type for \"%s\"", entry->path);
+			break;
 	}
 }
 
@@ -147,21 +152,26 @@ void
 create_target(file_entry_t *entry)
 {
 	Assert(entry->action == FILE_ACTION_CREATE);
+	Assert(!entry->target_exists);
 
-	switch (entry->type)
+	switch (entry->source_type)
 	{
 		case FILE_TYPE_DIRECTORY:
 			create_target_dir(entry->path);
 			break;
 
 		case FILE_TYPE_SYMLINK:
-			create_target_symlink(entry->path, entry->link_target);
+			create_target_symlink(entry->path, entry->source_link_target);
 			break;
 
 		case FILE_TYPE_REGULAR:
 			/* can't happen. Regular files are created with open_target_file. */
 			pg_fatal("invalid action (CREATE) for regular file");
 			break;
+
+		case FILE_TYPE_UNDEFINED:
+			pg_fatal("undefined file type for \"%s\"", entry->path);
+			break;
 	}
 }
 
diff --git a/src/bin/pg_rewind/filemap.c b/src/bin/pg_rewind/filemap.c
index 1879257b66a..431e8e760e0 100644
--- a/src/bin/pg_rewind/filemap.c
+++ b/src/bin/pg_rewind/filemap.c
@@ -26,6 +26,8 @@ static bool isRelDataFile(const char *path);
 static char *datasegpath(RelFileNode rnode, ForkNumber forknum,
 						 BlockNumber segno);
 static int	path_cmp(const void *a, const void *b);
+
+static file_entry_t *get_filemap_entry(const char *path, bool create);
 static int	final_filemap_cmp(const void *a, const void *b);
 static void filemap_list_to_array(filemap_t *map);
 static bool check_file_excluded(const char *path, bool is_source);
@@ -146,6 +148,65 @@ filemap_create(void)
 	filemap = map;
 }
 
+/* Look up or create entry for 'path' */
+static file_entry_t *
+get_filemap_entry(const char *path, bool create)
+{
+	filemap_t  *map = filemap;
+	file_entry_t *entry;
+	file_entry_t **e;
+	file_entry_t key;
+	file_entry_t *key_ptr;
+
+	if (map->array)
+	{
+		key.path = (char *) path;
+		key_ptr = &key;
+		e = bsearch(&key_ptr, map->array, map->narray, sizeof(file_entry_t *),
+					path_cmp);
+	}
+	else
+		e = NULL;
+
+	if (e)
+		entry = *e;
+	else if (!create)
+		entry = NULL;
+	else
+	{
+		/* Create a new entry for this file */
+		entry = pg_malloc(sizeof(file_entry_t));
+		entry->path = pg_strdup(path);
+		entry->isrelfile = isRelDataFile(path);
+		entry->action = FILE_ACTION_UNDECIDED;
+
+		entry->target_exists = false;
+		entry->target_type = FILE_TYPE_UNDEFINED;
+		entry->target_size = 0;
+		entry->target_link_target = NULL;
+		entry->target_modified_pages.bitmap = NULL;
+		entry->target_modified_pages.bitmapsize = 0;
+
+		entry->source_exists = false;
+		entry->source_type = FILE_TYPE_UNDEFINED;
+		entry->source_size = 0;
+		entry->source_link_target = NULL;
+
+		entry->next = NULL;
+
+		if (map->last)
+		{
+			map->last->next = entry;
+			map->last = entry;
+		}
+		else
+			map->first = map->last = entry;
+		map->nlist++;
+	}
+
+	return entry;
+}
+
 /*
  * Callback for processing source file list.
  *
@@ -154,18 +215,19 @@ filemap_create(void)
  * exists in the target and whether the size matches.
  */
 void
-process_source_file(const char *path, file_type_t type, size_t newsize,
+process_source_file(const char *path, file_type_t type, size_t size,
 					const char *link_target)
 {
-	bool		exists;
-	char		localpath[MAXPGPATH];
-	struct stat statbuf;
-	filemap_t  *map = filemap;
-	file_action_t action = FILE_ACTION_NONE;
-	size_t		oldsize = 0;
 	file_entry_t *entry;
 
-	Assert(map->array == NULL);
+	Assert(filemap->array == NULL);
+
+	/*
+	 * Skip the control file. It is handled specially, after copying all the
+	 * other files.
+	 */
+	if (strcmp(path, "global/pg_control") == 0)
+		return;
 
 	/*
 	 * Skip any files matching the exclusion filters. This has the effect to
@@ -199,142 +261,12 @@ process_source_file(const char *path, file_type_t type, size_t newsize,
 	if (type != FILE_TYPE_REGULAR && isRelDataFile(path))
 		pg_fatal("data file \"%s\" in source is not a regular file", path);
 
-	snprintf(localpath, sizeof(localpath), "%s/%s", datadir_target, path);
-
-	/* Does the corresponding file exist in the target data dir? */
-	if (lstat(localpath, &statbuf) < 0)
-	{
-		if (errno != ENOENT)
-			pg_fatal("could not stat file \"%s\": %m",
-					 localpath);
-
-		exists = false;
-	}
-	else
-		exists = true;
-
-	switch (type)
-	{
-		case FILE_TYPE_DIRECTORY:
-			if (exists && !S_ISDIR(statbuf.st_mode) && strcmp(path, "pg_wal") != 0)
-			{
-				/* it's a directory in source, but not in target. Strange.. */
-				pg_fatal("\"%s\" is not a directory", localpath);
-			}
-
-			if (!exists)
-				action = FILE_ACTION_CREATE;
-			else
-				action = FILE_ACTION_NONE;
-			oldsize = 0;
-			break;
-
-		case FILE_TYPE_SYMLINK:
-			if (exists &&
-#ifndef WIN32
-				!S_ISLNK(statbuf.st_mode)
-#else
-				!pgwin32_is_junction(localpath)
-#endif
-				)
-			{
-				/*
-				 * It's a symbolic link in source, but not in target.
-				 * Strange..
-				 */
-				pg_fatal("\"%s\" is not a symbolic link", localpath);
-			}
-
-			if (!exists)
-				action = FILE_ACTION_CREATE;
-			else
-				action = FILE_ACTION_NONE;
-			oldsize = 0;
-			break;
-
-		case FILE_TYPE_REGULAR:
-			if (exists && !S_ISREG(statbuf.st_mode))
-				pg_fatal("\"%s\" is not a regular file", localpath);
-
-			if (!exists || !isRelDataFile(path))
-			{
-				/*
-				 * File exists in source, but not in target. Or it's a
-				 * non-data file that we have no special processing for. Copy
-				 * it in toto.
-				 *
-				 * An exception: PG_VERSIONs should be identical, but avoid
-				 * overwriting it for paranoia.
-				 */
-				if (pg_str_endswith(path, "PG_VERSION"))
-				{
-					action = FILE_ACTION_NONE;
-					oldsize = statbuf.st_size;
-				}
-				else
-				{
-					action = FILE_ACTION_COPY;
-					oldsize = 0;
-				}
-			}
-			else
-			{
-				/*
-				 * It's a data file that exists in both.
-				 *
-				 * If it's larger in target, we can truncate it. There will
-				 * also be a WAL record of the truncation in the source
-				 * system, so WAL replay would eventually truncate the target
-				 * too, but we might as well do it now.
-				 *
-				 * If it's smaller in the target, it means that it has been
-				 * truncated in the target, or enlarged in the source, or
-				 * both. If it was truncated in the target, we need to copy
-				 * the missing tail from the source system. If it was enlarged
-				 * in the source system, there will be WAL records in the
-				 * source system for the new blocks, so we wouldn't need to
-				 * copy them here. But we don't know which scenario we're
-				 * dealing with, and there's no harm in copying the missing
-				 * blocks now, so do it now.
-				 *
-				 * If it's the same size, do nothing here. Any blocks modified
-				 * in the target will be copied based on parsing the target
-				 * system's WAL, and any blocks modified in the source will be
-				 * updated after rewinding, when the source system's WAL is
-				 * replayed.
-				 */
-				oldsize = statbuf.st_size;
-				if (oldsize < newsize)
-					action = FILE_ACTION_COPY_TAIL;
-				else if (oldsize > newsize)
-					action = FILE_ACTION_TRUNCATE;
-				else
-					action = FILE_ACTION_NONE;
-			}
-			break;
-	}
-
-	/* Create a new entry for this file */
-	entry = pg_malloc(sizeof(file_entry_t));
-	entry->path = pg_strdup(path);
-	entry->type = type;
-	entry->action = action;
-	entry->oldsize = oldsize;
-	entry->newsize = newsize;
-	entry->link_target = link_target ? pg_strdup(link_target) : NULL;
-	entry->next = NULL;
-	entry->pagemap.bitmap = NULL;
-	entry->pagemap.bitmapsize = 0;
-	entry->isrelfile = isRelDataFile(path);
-
-	if (map->last)
-	{
-		map->last->next = entry;
-		map->last = entry;
-	}
-	else
-		map->first = map->last = entry;
-	map->nlist++;
+	/* Remember this source file */
+	entry = get_filemap_entry(path, true);
+	entry->source_exists = true;
+	entry->source_type = type;
+	entry->source_size = size;
+	entry->source_link_target = link_target ? pg_strdup(link_target) : NULL;
 }
 
 /*
@@ -345,33 +277,24 @@ process_source_file(const char *path, file_type_t type, size_t newsize,
  * deletion.
  */
 void
-process_target_file(const char *path, file_type_t type, size_t oldsize,
+process_target_file(const char *path, file_type_t type, size_t size,
 					const char *link_target)
 {
-	bool		exists;
-	char		localpath[MAXPGPATH];
-	struct stat statbuf;
-	file_entry_t key;
-	file_entry_t *key_ptr;
 	filemap_t  *map = filemap;
 	file_entry_t *entry;
 
+	/*
+	 * Skip the control file. It is handled specially, after copying all the
+	 * other files.
+	 */
+	if (strcmp(path, "global/pg_control") == 0)
+		return;
+
 	/*
 	 * Do not apply any exclusion filters here.  This has advantage to remove
 	 * from the target data folder all paths which have been filtered out from
 	 * the source data folder when processing the source files.
 	 */
-
-	snprintf(localpath, sizeof(localpath), "%s/%s", datadir_target, path);
-	if (lstat(localpath, &statbuf) < 0)
-	{
-		if (errno != ENOENT)
-			pg_fatal("could not stat file \"%s\": %m",
-					 localpath);
-
-		exists = false;
-	}
-
 	if (map->array == NULL)
 	{
 		/* on first call, initialize lookup array */
@@ -389,120 +312,76 @@ process_target_file(const char *path, file_type_t type, size_t oldsize,
 	}
 
 	/*
-	 * Like in process_source_file, pretend that xlog is always a  directory.
+	 * Like in process_source_file, pretend that pg_wal is always a directory.
 	 */
 	if (strcmp(path, "pg_wal") == 0 && type == FILE_TYPE_SYMLINK)
 		type = FILE_TYPE_DIRECTORY;
 
-	key.path = (char *) path;
-	key_ptr = &key;
-	exists = (bsearch(&key_ptr, map->array, map->narray, sizeof(file_entry_t *),
-					  path_cmp) != NULL);
-
-	/* Remove any file or folder that doesn't exist in the source system. */
-	if (!exists)
-	{
-		entry = pg_malloc(sizeof(file_entry_t));
-		entry->path = pg_strdup(path);
-		entry->type = type;
-		entry->action = FILE_ACTION_REMOVE;
-		entry->oldsize = oldsize;
-		entry->newsize = 0;
-		entry->link_target = link_target ? pg_strdup(link_target) : NULL;
-		entry->next = NULL;
-		entry->pagemap.bitmap = NULL;
-		entry->pagemap.bitmapsize = 0;
-		entry->isrelfile = isRelDataFile(path);
-
-		if (map->last == NULL)
-			map->first = entry;
-		else
-			map->last->next = entry;
-		map->last = entry;
-		map->nlist++;
-	}
-	else
-	{
-		/*
-		 * We already handled all files that exist in the source system in
-		 * process_source_file().
-		 */
-	}
+	/* Remember this target file */
+	entry = get_filemap_entry(path, true);
+	entry->target_exists = true;
+	entry->target_type = type;
+	entry->target_size = size;
+	entry->target_link_target = link_target ? pg_strdup(link_target) : NULL;
 }
 
 /*
  * This callback gets called while we read the WAL in the target, for every
- * block that have changed in the target system. It makes note of all the
+ * block that have changed in the target system.  It makes note of all the
  * changed blocks in the pagemap of the file.
+ *
+ * NOTE: All the files on both systems must have already been added to the
+ * file map!
  */
 void
-process_block_change(ForkNumber forknum, RelFileNode rnode, BlockNumber blkno)
+process_target_wal_block_change(ForkNumber forknum, RelFileNode rnode,
+								BlockNumber blkno)
 {
 	char	   *path;
-	file_entry_t key;
-	file_entry_t *key_ptr;
 	file_entry_t *entry;
 	BlockNumber blkno_inseg;
 	int			segno;
-	filemap_t  *map = filemap;
-	file_entry_t **e;
 
-	Assert(map->array);
+	Assert(filemap->array);
 
 	segno = blkno / RELSEG_SIZE;
 	blkno_inseg = blkno % RELSEG_SIZE;
 
 	path = datasegpath(rnode, forknum, segno);
-
-	key.path = (char *) path;
-	key_ptr = &key;
-
-	e = bsearch(&key_ptr, map->array, map->narray, sizeof(file_entry_t *),
-				path_cmp);
-	if (e)
-		entry = *e;
-	else
-		entry = NULL;
+	entry = get_filemap_entry(path, false);
 	pfree(path);
 
 	if (entry)
 	{
+		int64		end_offset;
+
 		Assert(entry->isrelfile);
 
-		switch (entry->action)
-		{
-			case FILE_ACTION_NONE:
-			case FILE_ACTION_TRUNCATE:
-				/* skip if we're truncating away the modified block anyway */
-				if ((blkno_inseg + 1) * BLCKSZ <= entry->newsize)
-					datapagemap_add(&entry->pagemap, blkno_inseg);
-				break;
-
-			case FILE_ACTION_COPY_TAIL:
-
-				/*
-				 * skip the modified block if it is part of the "tail" that
-				 * we're copying anyway.
-				 */
-				if ((blkno_inseg + 1) * BLCKSZ <= entry->oldsize)
-					datapagemap_add(&entry->pagemap, blkno_inseg);
-				break;
-
-			case FILE_ACTION_COPY:
-			case FILE_ACTION_REMOVE:
-				break;
-
-			case FILE_ACTION_CREATE:
-				pg_fatal("unexpected page modification for directory or symbolic link \"%s\"", entry->path);
-		}
+		if (entry->target_type != FILE_TYPE_REGULAR)
+			pg_fatal("unexpected page modification for directory or symbolic link \"%s\"",
+					 entry->path);
+
+		/*
+		 * If the block beyond the EOF in the source system, no need to
+		 * remember it now, because we're going to truncate it away from the
+		 * target anyway. Also no need to remember the block if it's beyond
+		 * the current EOF in the target system; we will copy it over with the
+		 * "tail" from the source system, anyway.
+		 */
+		end_offset = (blkno_inseg + 1) * BLCKSZ;
+		if (end_offset <= entry->source_size &&
+			end_offset <= entry->target_size)
+			datapagemap_add(&entry->target_modified_pages, blkno_inseg);
 	}
 	else
 	{
 		/*
 		 * If we don't have any record of this file in the file map, it means
-		 * that it's a relation that doesn't exist in the source system, and
-		 * it was subsequently removed in the target system, too. We can
-		 * safely ignore it.
+		 * that it's a relation that doesn't exist in the source system.  It
+		 * could exist in the target system; we haven't moved the target-only
+		 * entries from the linked list to the array yet!  But in any case, if
+		 * it doesn't exist in the source it will be removed from the target
+		 * too, and we can safely ignore it.
 		 */
 	}
 }
@@ -593,16 +472,6 @@ filemap_list_to_array(filemap_t *map)
 	map->first = map->last = NULL;
 }
 
-void
-filemap_finalize(void)
-{
-	filemap_t  *map = filemap;
-
-	filemap_list_to_array(map);
-	qsort(map->array, map->narray, sizeof(file_entry_t *),
-		  final_filemap_cmp);
-}
-
 static const char *
 action_to_str(file_action_t action)
 {
@@ -643,26 +512,26 @@ calculate_totals(void)
 	{
 		entry = map->array[i];
 
-		if (entry->type != FILE_TYPE_REGULAR)
+		if (entry->source_type != FILE_TYPE_REGULAR)
 			continue;
 
-		map->total_size += entry->newsize;
+		map->total_size += entry->source_size;
 
 		if (entry->action == FILE_ACTION_COPY)
 		{
-			map->fetch_size += entry->newsize;
+			map->fetch_size += entry->source_size;
 			continue;
 		}
 
 		if (entry->action == FILE_ACTION_COPY_TAIL)
-			map->fetch_size += (entry->newsize - entry->oldsize);
+			map->fetch_size += (entry->source_size - entry->target_size);
 
-		if (entry->pagemap.bitmapsize > 0)
+		if (entry->target_modified_pages.bitmapsize > 0)
 		{
 			datapagemap_iterator_t *iter;
 			BlockNumber blk;
 
-			iter = datapagemap_iterate(&entry->pagemap);
+			iter = datapagemap_iterate(&entry->target_modified_pages);
 			while (datapagemap_next(iter, &blk))
 				map->fetch_size += BLCKSZ;
 
@@ -682,13 +551,13 @@ print_filemap(void)
 	{
 		entry = map->array[i];
 		if (entry->action != FILE_ACTION_NONE ||
-			entry->pagemap.bitmapsize > 0)
+			entry->target_modified_pages.bitmapsize > 0)
 		{
 			pg_log_debug("%s (%s)", entry->path,
 						 action_to_str(entry->action));
 
-			if (entry->pagemap.bitmapsize > 0)
-				datapagemap_print(&entry->pagemap);
+			if (entry->target_modified_pages.bitmapsize > 0)
+				datapagemap_print(&entry->target_modified_pages);
 		}
 	}
 	fflush(stdout);
@@ -837,3 +706,126 @@ final_filemap_cmp(const void *a, const void *b)
 	else
 		return strcmp(fa->path, fb->path);
 }
+
+/*
+ * Decide what to do with each file.
+ */
+void
+filemap_finalize()
+{
+	int			i;
+
+	filemap_list_to_array(filemap);
+
+	for (i = 0; i < filemap->narray; i++)
+	{
+		file_entry_t *entry = filemap->array[i];
+		file_action_t action;
+
+		if (!entry->target_exists && entry->source_exists)
+		{
+			/*
+			 * File exists in source, but not in target. Copy
+			 * it in toto. (If it's a relation data file, WAL replay
+			 * after rewinding should re-create it anyway. But there's
+			 * no harm in copying it now.)
+			 */
+			if (entry->source_type == FILE_TYPE_DIRECTORY ||
+				entry->source_type == FILE_TYPE_SYMLINK)
+			{
+				action = FILE_ACTION_CREATE;
+			}
+			else
+				action = FILE_ACTION_COPY;
+		}
+		else if (entry->target_exists && !entry->source_exists)
+		{
+			/* Delete file from target */
+			action = FILE_ACTION_REMOVE;
+		}
+		else if (entry->target_exists && entry->source_exists)
+		{
+			/* File exists in both systems */
+			if (entry->source_type != entry->target_type)
+			{
+				/* But it's a different kind of object. Strange.. */
+				pg_fatal("file \"%s\" is of different type in source and target", entry->path);
+			}
+
+			switch (entry->source_type)
+			{
+				case FILE_TYPE_DIRECTORY:
+					action = FILE_ACTION_NONE;
+					break;
+
+				case FILE_TYPE_SYMLINK:
+					// FIXME: Check if it points to the same target?
+					action = FILE_ACTION_NONE;
+					break;
+
+				case FILE_TYPE_REGULAR:
+					if (!entry->isrelfile)
+					{
+						/*
+						 * It's a non-data file that we have no special processing for. Copy
+						 * it in toto.
+						 *
+						 * An exception: PG_VERSIONs should be identical, but avoid
+						 * overwriting it for paranoia.
+						 */
+						if (!pg_str_endswith(entry->path, "PG_VERSION"))
+							action = FILE_ACTION_COPY;
+						else
+							action = FILE_ACTION_NONE;
+					}
+					else
+					{
+						/*
+						 * It's a data file that exists in both systems.
+						 *
+						 * If it's larger in target, we can truncate it. There will
+						 * also be a WAL record of the truncation in the source
+						 * system, so WAL replay would eventually truncate the target
+						 * too, but we might as well do it now.
+						 *
+						 * If it's smaller in the target, it means that it has been
+						 * truncated in the target, or enlarged in the source, or
+						 * both. If it was truncated in the target, we need to copy
+						 * the missing tail from the source system. If it was enlarged
+						 * in the source system, there will be WAL records in the
+						 * source system for the new blocks, so we wouldn't need to
+						 * copy them here. But we don't know which scenario we're
+						 * dealing with, and there's no harm in copying the missing
+						 * blocks now, so do it now.
+						 *
+						 * If it's the same size, do nothing here. Any blocks modified
+						 * in the target will be copied based on parsing the target
+						 * system's WAL, and any blocks modified in the source will be
+						 * updated after rewinding, when the source system's WAL is
+						 * replayed.
+						 */
+						if (entry->target_size < entry->source_size)
+							action = FILE_ACTION_COPY_TAIL;
+						else if (entry->target_size > entry->source_size)
+							action = FILE_ACTION_TRUNCATE;
+						else
+							action = FILE_ACTION_NONE;
+					}
+					break;
+				case FILE_TYPE_UNDEFINED:
+					pg_fatal("unknown file type for \"%s\"", entry->path);
+					break;
+			}
+		}
+		else
+		{
+			/* Doesn't exist in either server. Why does it have an entry at all?? */
+			action = FILE_ACTION_NONE;
+		}
+		entry->action = action;
+	}
+
+	/* Sort the actions to the order that they should be performed */
+	qsort(filemap->array, filemap->narray, sizeof(file_entry_t *),
+		  final_filemap_cmp);
+}
diff --git a/src/bin/pg_rewind/filemap.h b/src/bin/pg_rewind/filemap.h
index 0cb7425170c..a5e8df57f40 100644
--- a/src/bin/pg_rewind/filemap.h
+++ b/src/bin/pg_rewind/filemap.h
@@ -14,17 +14,21 @@
 
 /*
  * For every file found in the local or remote system, we have a file entry
- * which says what we are going to do with the file. For relation files,
- * there is also a page map, marking pages in the file that were changed
- * locally.
- *
- * The enum values are sorted in the order we want actions to be processed.
+ * that contains information about the file on both systems.  For relation
+ * files, there is also a page map that marks pages in the file that were
+ * changed in the target after the last common checkpoint.  Each entry also
+ * contains an 'action' field, which says what we are going to do with the
+ * file.
  */
+
+/* these enum values are sorted in the order we want actions to be processed */
 typedef enum
 {
+	FILE_ACTION_UNDECIDED = 0,	/* not decided yet */
+
 	FILE_ACTION_CREATE,			/* create local directory or symbolic link */
 	FILE_ACTION_COPY,			/* copy whole file, overwriting if exists */
-	FILE_ACTION_COPY_TAIL,		/* copy tail from 'oldsize' to 'newsize' */
+	FILE_ACTION_COPY_TAIL,		/* copy tail from 'source_size' to 'target_size' */
 	FILE_ACTION_NONE,			/* no action (we might still copy modified
 								 * blocks based on the parsed WAL) */
 	FILE_ACTION_TRUNCATE,		/* truncate local file to 'newsize' bytes */
@@ -33,6 +37,8 @@ typedef enum
 
 typedef enum
 {
+	FILE_TYPE_UNDEFINED = 0,
+
 	FILE_TYPE_REGULAR,
 	FILE_TYPE_DIRECTORY,
 	FILE_TYPE_SYMLINK
@@ -41,19 +47,30 @@ typedef enum
 typedef struct file_entry_t
 {
 	char	   *path;
-	file_type_t type;
+	bool		isrelfile;		/* is it a relation data file? */
 
-	file_action_t action;
+	/*
+	 * Status of the file in the target.
+	 */
+	bool		target_exists;
+	file_type_t target_type;
+	size_t		target_size; /* for a regular file */
+	char	   *target_link_target; /* for a symlink */
 
-	/* for a regular file */
-	size_t		oldsize;
-	size_t		newsize;
-	bool		isrelfile;		/* is it a relation data file? */
+	datapagemap_t target_modified_pages;
 
-	datapagemap_t pagemap;
+	/*
+	 * Status of the file in the source.
+	 */
+	bool		source_exists;
+	file_type_t source_type;
+	size_t		source_size;
+	char	   *source_link_target; /* for a symlink */
 
-	/* for a symlink */
-	char	   *link_target;
+	/*
+	 * What will we do to the file?
+	 */
+	file_action_t action;
 
 	struct file_entry_t *next;
 } file_entry_t;
@@ -70,20 +87,19 @@ typedef struct filemap_t
 
 	/*
 	 * After processing all the remote files, the entries in the linked list
-	 * are moved to this array. After processing local files, too, all the
+	 * are moved to this array.  After processing local files, too, all the
 	 * local entries are added to the array by filemap_finalize, and sorted in
-	 * the final order. After filemap_finalize, all the entries are in the
+	 * the final order.  After filemap_finalize, all the entries are in the
 	 * array, and the linked list is empty.
 	 */
 	file_entry_t **array;
 	int			narray;			/* current length of array */
 
 	/*
-	 * Summary information. total_size is the total size of the source
-	 * cluster, and fetch_size is the number of bytes that needs to be copied.
+	 * Summary information.
 	 */
-	uint64		total_size;
-	uint64		fetch_size;
+	uint64		total_size;		/* total size of the source cluster */
+	uint64		fetch_size;		/* number of bytes that needs to be copied */
 } filemap_t;
 
 extern filemap_t *filemap;
@@ -94,11 +110,12 @@ extern void print_filemap(void);
 
 /* Functions for populating the filemap */
 extern void process_source_file(const char *path, file_type_t type,
-								size_t newsize, const char *link_target);
+								size_t size, const char *link_target);
 extern void process_target_file(const char *path, file_type_t type,
-								size_t newsize, const char *link_target);
-extern void process_block_change(ForkNumber forknum, RelFileNode rnode,
-								 BlockNumber blkno);
+								size_t size, const char *link_target);
+extern void process_target_wal_block_change(ForkNumber forknum,
+											RelFileNode rnode,
+											BlockNumber blkno);
 extern void filemap_finalize(void);
 
 #endif							/* FILEMAP_H */
diff --git a/src/bin/pg_rewind/libpq_fetch.c b/src/bin/pg_rewind/libpq_fetch.c
index bf4dfc23b96..7fc9161b8c8 100644
--- a/src/bin/pg_rewind/libpq_fetch.c
+++ b/src/bin/pg_rewind/libpq_fetch.c
@@ -465,7 +465,7 @@ libpq_executeFileMap(filemap_t *map)
 		entry = map->array[i];
 
 		/* If this is a relation file, copy the modified blocks */
-		execute_pagemap(&entry->pagemap, entry->path);
+		execute_pagemap(&entry->target_modified_pages, entry->path);
 
 		switch (entry->action)
 		{
@@ -476,15 +476,15 @@ libpq_executeFileMap(filemap_t *map)
 			case FILE_ACTION_COPY:
 				/* Truncate the old file out of the way, if any */
 				open_target_file(entry->path, true);
-				fetch_file_range(entry->path, 0, entry->newsize);
+				fetch_file_range(entry->path, 0, entry->source_size);
 				break;
 
 			case FILE_ACTION_TRUNCATE:
-				truncate_target_file(entry->path, entry->newsize);
+				truncate_target_file(entry->path, entry->source_size);
 				break;
 
 			case FILE_ACTION_COPY_TAIL:
-				fetch_file_range(entry->path, entry->oldsize, entry->newsize);
+				fetch_file_range(entry->path, entry->target_size, entry->source_size);
 				break;
 
 			case FILE_ACTION_REMOVE:
@@ -494,6 +494,10 @@ libpq_executeFileMap(filemap_t *map)
 			case FILE_ACTION_CREATE:
 				create_target(entry);
 				break;
+
+			case FILE_ACTION_UNDECIDED:
+				pg_fatal("no action decided for \"%s\"", entry->path);
+				break;
 		}
 	}
 
diff --git a/src/bin/pg_rewind/parsexlog.c b/src/bin/pg_rewind/parsexlog.c
index 2229c86f9af..2baeb74ae93 100644
--- a/src/bin/pg_rewind/parsexlog.c
+++ b/src/bin/pg_rewind/parsexlog.c
@@ -436,6 +436,6 @@ extractPageInfo(XLogReaderState *record)
 		if (forknum != MAIN_FORKNUM)
 			continue;
 
-		process_block_change(forknum, rnode, blkno);
+		process_target_wal_block_change(forknum, rnode, blkno);
 	}
 }
diff --git a/src/bin/pg_rewind/pg_rewind.c b/src/bin/pg_rewind/pg_rewind.c
index c9b9e480c0f..210984d302b 100644
--- a/src/bin/pg_rewind/pg_rewind.c
+++ b/src/bin/pg_rewind/pg_rewind.c
@@ -369,7 +369,7 @@ main(int argc, char **argv)
 				chkpttli);
 
 	/*
-	 * Build the filemap, by comparing the source and target data directories.
+	 * Collect information about all files in the target and source systems.
 	 */
 	filemap_create();
 	if (showprogress)
@@ -390,8 +390,12 @@ main(int argc, char **argv)
 		pg_log_info("reading WAL in target");
 	extractPageMap(datadir_target, chkptrec, lastcommontliIndex,
 				   ControlFile_target.checkPoint, restore_command);
-	filemap_finalize();
 
+	/*
+	 * We have collected all information we need from both systems. Decide
+	 * what to do with each file.
+	 */
+	filemap_finalize();
 	if (showprogress)
 		calculate_totals();
 
-- 
2.20.1


--------------D93EDEBFB124D563B723F4BD
Content-Type: text/x-patch; charset=UTF-8;
 name="0003-pg_rewind-Replace-the-hybrid-list-array-data-structu.patch"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
 filename*0="0003-pg_rewind-Replace-the-hybrid-list-array-data-structu.pa";
 filename*1="tch"



^ permalink  raw  reply  [nested|flat] 41+ messages in thread

* [PATCH 2/5] Refactor pg_rewind for more clear decision making.
@ 2020-08-19 12:34 Heikki Linnakangas <[email protected]>
  0 siblings, 0 replies; 41+ messages in thread

From: Heikki Linnakangas @ 2020-08-19 12:34 UTC (permalink / raw)

Deciding what to do with each file is now a separate step after all the
necessary information has been gathered. It is more clear that way.
Previously, the decision-making was divided between process_source_file()
and process_target_file(), and it was a bit hard to piece together what the
overall rules were.
---
 src/bin/pg_rewind/copy_fetch.c  |  14 +-
 src/bin/pg_rewind/file_ops.c    |  16 +-
 src/bin/pg_rewind/filemap.c     | 514 ++++++++++++++++----------------
 src/bin/pg_rewind/filemap.h     |  67 +++--
 src/bin/pg_rewind/libpq_fetch.c |  12 +-
 src/bin/pg_rewind/parsexlog.c   |   2 +-
 src/bin/pg_rewind/pg_rewind.c   |   8 +-
 7 files changed, 332 insertions(+), 301 deletions(-)

diff --git a/src/bin/pg_rewind/copy_fetch.c b/src/bin/pg_rewind/copy_fetch.c
index 1edab5f1867..18fad32600e 100644
--- a/src/bin/pg_rewind/copy_fetch.c
+++ b/src/bin/pg_rewind/copy_fetch.c
@@ -210,7 +210,7 @@ copy_executeFileMap(filemap_t *map)
 	for (i = 0; i < map->narray; i++)
 	{
 		entry = map->array[i];
-		execute_pagemap(&entry->pagemap, entry->path);
+		execute_pagemap(&entry->target_modified_pages, entry->path);
 
 		switch (entry->action)
 		{
@@ -219,16 +219,16 @@ copy_executeFileMap(filemap_t *map)
 				break;
 
 			case FILE_ACTION_COPY:
-				rewind_copy_file_range(entry->path, 0, entry->newsize, true);
+				rewind_copy_file_range(entry->path, 0, entry->source_size, true);
 				break;
 
 			case FILE_ACTION_TRUNCATE:
-				truncate_target_file(entry->path, entry->newsize);
+				truncate_target_file(entry->path, entry->source_size);
 				break;
 
 			case FILE_ACTION_COPY_TAIL:
-				rewind_copy_file_range(entry->path, entry->oldsize,
-									   entry->newsize, false);
+				rewind_copy_file_range(entry->path, entry->target_size,
+									   entry->source_size, false);
 				break;
 
 			case FILE_ACTION_CREATE:
@@ -238,6 +238,10 @@ copy_executeFileMap(filemap_t *map)
 			case FILE_ACTION_REMOVE:
 				remove_target(entry);
 				break;
+
+			case FILE_ACTION_UNDECIDED:
+				pg_fatal("no action decided for \"%s\"", entry->path);
+				break;
 		}
 	}
 
diff --git a/src/bin/pg_rewind/file_ops.c b/src/bin/pg_rewind/file_ops.c
index 55439db20ba..ec37d0b2e0d 100644
--- a/src/bin/pg_rewind/file_ops.c
+++ b/src/bin/pg_rewind/file_ops.c
@@ -126,8 +126,9 @@ void
 remove_target(file_entry_t *entry)
 {
 	Assert(entry->action == FILE_ACTION_REMOVE);
+	Assert(entry->target_exists);
 
-	switch (entry->type)
+	switch (entry->target_type)
 	{
 		case FILE_TYPE_DIRECTORY:
 			remove_target_dir(entry->path);
@@ -140,6 +141,10 @@ remove_target(file_entry_t *entry)
 		case FILE_TYPE_SYMLINK:
 			remove_target_symlink(entry->path);
 			break;
+
+		case FILE_TYPE_UNDEFINED:
+			pg_fatal("undefined file type for \"%s\"", entry->path);
+			break;
 	}
 }
 
@@ -147,21 +152,26 @@ void
 create_target(file_entry_t *entry)
 {
 	Assert(entry->action == FILE_ACTION_CREATE);
+	Assert(!entry->target_exists);
 
-	switch (entry->type)
+	switch (entry->source_type)
 	{
 		case FILE_TYPE_DIRECTORY:
 			create_target_dir(entry->path);
 			break;
 
 		case FILE_TYPE_SYMLINK:
-			create_target_symlink(entry->path, entry->link_target);
+			create_target_symlink(entry->path, entry->source_link_target);
 			break;
 
 		case FILE_TYPE_REGULAR:
 			/* can't happen. Regular files are created with open_target_file. */
 			pg_fatal("invalid action (CREATE) for regular file");
 			break;
+
+		case FILE_TYPE_UNDEFINED:
+			pg_fatal("undefined file type for \"%s\"", entry->path);
+			break;
 	}
 }
 
diff --git a/src/bin/pg_rewind/filemap.c b/src/bin/pg_rewind/filemap.c
index 1879257b66a..431e8e760e0 100644
--- a/src/bin/pg_rewind/filemap.c
+++ b/src/bin/pg_rewind/filemap.c
@@ -26,6 +26,8 @@ static bool isRelDataFile(const char *path);
 static char *datasegpath(RelFileNode rnode, ForkNumber forknum,
 						 BlockNumber segno);
 static int	path_cmp(const void *a, const void *b);
+
+static file_entry_t *get_filemap_entry(const char *path, bool create);
 static int	final_filemap_cmp(const void *a, const void *b);
 static void filemap_list_to_array(filemap_t *map);
 static bool check_file_excluded(const char *path, bool is_source);
@@ -146,6 +148,65 @@ filemap_create(void)
 	filemap = map;
 }
 
+/* Look up or create entry for 'path' */
+static file_entry_t *
+get_filemap_entry(const char *path, bool create)
+{
+	filemap_t  *map = filemap;
+	file_entry_t *entry;
+	file_entry_t **e;
+	file_entry_t key;
+	file_entry_t *key_ptr;
+
+	if (map->array)
+	{
+		key.path = (char *) path;
+		key_ptr = &key;
+		e = bsearch(&key_ptr, map->array, map->narray, sizeof(file_entry_t *),
+					path_cmp);
+	}
+	else
+		e = NULL;
+
+	if (e)
+		entry = *e;
+	else if (!create)
+		entry = NULL;
+	else
+	{
+		/* Create a new entry for this file */
+		entry = pg_malloc(sizeof(file_entry_t));
+		entry->path = pg_strdup(path);
+		entry->isrelfile = isRelDataFile(path);
+		entry->action = FILE_ACTION_UNDECIDED;
+
+		entry->target_exists = false;
+		entry->target_type = FILE_TYPE_UNDEFINED;
+		entry->target_size = 0;
+		entry->target_link_target = NULL;
+		entry->target_modified_pages.bitmap = NULL;
+		entry->target_modified_pages.bitmapsize = 0;
+
+		entry->source_exists = false;
+		entry->source_type = FILE_TYPE_UNDEFINED;
+		entry->source_size = 0;
+		entry->source_link_target = NULL;
+
+		entry->next = NULL;
+
+		if (map->last)
+		{
+			map->last->next = entry;
+			map->last = entry;
+		}
+		else
+			map->first = map->last = entry;
+		map->nlist++;
+	}
+
+	return entry;
+}
+
 /*
  * Callback for processing source file list.
  *
@@ -154,18 +215,19 @@ filemap_create(void)
  * exists in the target and whether the size matches.
  */
 void
-process_source_file(const char *path, file_type_t type, size_t newsize,
+process_source_file(const char *path, file_type_t type, size_t size,
 					const char *link_target)
 {
-	bool		exists;
-	char		localpath[MAXPGPATH];
-	struct stat statbuf;
-	filemap_t  *map = filemap;
-	file_action_t action = FILE_ACTION_NONE;
-	size_t		oldsize = 0;
 	file_entry_t *entry;
 
-	Assert(map->array == NULL);
+	Assert(filemap->array == NULL);
+
+	/*
+	 * Skip the control file. It is handled specially, after copying all the
+	 * other files.
+	 */
+	if (strcmp(path, "global/pg_control") == 0)
+		return;
 
 	/*
 	 * Skip any files matching the exclusion filters. This has the effect to
@@ -199,142 +261,12 @@ process_source_file(const char *path, file_type_t type, size_t newsize,
 	if (type != FILE_TYPE_REGULAR && isRelDataFile(path))
 		pg_fatal("data file \"%s\" in source is not a regular file", path);
 
-	snprintf(localpath, sizeof(localpath), "%s/%s", datadir_target, path);
-
-	/* Does the corresponding file exist in the target data dir? */
-	if (lstat(localpath, &statbuf) < 0)
-	{
-		if (errno != ENOENT)
-			pg_fatal("could not stat file \"%s\": %m",
-					 localpath);
-
-		exists = false;
-	}
-	else
-		exists = true;
-
-	switch (type)
-	{
-		case FILE_TYPE_DIRECTORY:
-			if (exists && !S_ISDIR(statbuf.st_mode) && strcmp(path, "pg_wal") != 0)
-			{
-				/* it's a directory in source, but not in target. Strange.. */
-				pg_fatal("\"%s\" is not a directory", localpath);
-			}
-
-			if (!exists)
-				action = FILE_ACTION_CREATE;
-			else
-				action = FILE_ACTION_NONE;
-			oldsize = 0;
-			break;
-
-		case FILE_TYPE_SYMLINK:
-			if (exists &&
-#ifndef WIN32
-				!S_ISLNK(statbuf.st_mode)
-#else
-				!pgwin32_is_junction(localpath)
-#endif
-				)
-			{
-				/*
-				 * It's a symbolic link in source, but not in target.
-				 * Strange..
-				 */
-				pg_fatal("\"%s\" is not a symbolic link", localpath);
-			}
-
-			if (!exists)
-				action = FILE_ACTION_CREATE;
-			else
-				action = FILE_ACTION_NONE;
-			oldsize = 0;
-			break;
-
-		case FILE_TYPE_REGULAR:
-			if (exists && !S_ISREG(statbuf.st_mode))
-				pg_fatal("\"%s\" is not a regular file", localpath);
-
-			if (!exists || !isRelDataFile(path))
-			{
-				/*
-				 * File exists in source, but not in target. Or it's a
-				 * non-data file that we have no special processing for. Copy
-				 * it in toto.
-				 *
-				 * An exception: PG_VERSIONs should be identical, but avoid
-				 * overwriting it for paranoia.
-				 */
-				if (pg_str_endswith(path, "PG_VERSION"))
-				{
-					action = FILE_ACTION_NONE;
-					oldsize = statbuf.st_size;
-				}
-				else
-				{
-					action = FILE_ACTION_COPY;
-					oldsize = 0;
-				}
-			}
-			else
-			{
-				/*
-				 * It's a data file that exists in both.
-				 *
-				 * If it's larger in target, we can truncate it. There will
-				 * also be a WAL record of the truncation in the source
-				 * system, so WAL replay would eventually truncate the target
-				 * too, but we might as well do it now.
-				 *
-				 * If it's smaller in the target, it means that it has been
-				 * truncated in the target, or enlarged in the source, or
-				 * both. If it was truncated in the target, we need to copy
-				 * the missing tail from the source system. If it was enlarged
-				 * in the source system, there will be WAL records in the
-				 * source system for the new blocks, so we wouldn't need to
-				 * copy them here. But we don't know which scenario we're
-				 * dealing with, and there's no harm in copying the missing
-				 * blocks now, so do it now.
-				 *
-				 * If it's the same size, do nothing here. Any blocks modified
-				 * in the target will be copied based on parsing the target
-				 * system's WAL, and any blocks modified in the source will be
-				 * updated after rewinding, when the source system's WAL is
-				 * replayed.
-				 */
-				oldsize = statbuf.st_size;
-				if (oldsize < newsize)
-					action = FILE_ACTION_COPY_TAIL;
-				else if (oldsize > newsize)
-					action = FILE_ACTION_TRUNCATE;
-				else
-					action = FILE_ACTION_NONE;
-			}
-			break;
-	}
-
-	/* Create a new entry for this file */
-	entry = pg_malloc(sizeof(file_entry_t));
-	entry->path = pg_strdup(path);
-	entry->type = type;
-	entry->action = action;
-	entry->oldsize = oldsize;
-	entry->newsize = newsize;
-	entry->link_target = link_target ? pg_strdup(link_target) : NULL;
-	entry->next = NULL;
-	entry->pagemap.bitmap = NULL;
-	entry->pagemap.bitmapsize = 0;
-	entry->isrelfile = isRelDataFile(path);
-
-	if (map->last)
-	{
-		map->last->next = entry;
-		map->last = entry;
-	}
-	else
-		map->first = map->last = entry;
-	map->nlist++;
+	/* Remember this source file */
+	entry = get_filemap_entry(path, true);
+	entry->source_exists = true;
+	entry->source_type = type;
+	entry->source_size = size;
+	entry->source_link_target = link_target ? pg_strdup(link_target) : NULL;
 }
 
 /*
@@ -345,33 +277,24 @@ process_source_file(const char *path, file_type_t type, size_t newsize,
  * deletion.
  */
 void
-process_target_file(const char *path, file_type_t type, size_t oldsize,
+process_target_file(const char *path, file_type_t type, size_t size,
 					const char *link_target)
 {
-	bool		exists;
-	char		localpath[MAXPGPATH];
-	struct stat statbuf;
-	file_entry_t key;
-	file_entry_t *key_ptr;
 	filemap_t  *map = filemap;
 	file_entry_t *entry;
 
+	/*
+	 * Skip the control file. It is handled specially, after copying all the
+	 * other files.
+	 */
+	if (strcmp(path, "global/pg_control") == 0)
+		return;
+
 	/*
 	 * Do not apply any exclusion filters here.  This has advantage to remove
 	 * from the target data folder all paths which have been filtered out from
 	 * the source data folder when processing the source files.
 	 */
-
-	snprintf(localpath, sizeof(localpath), "%s/%s", datadir_target, path);
-	if (lstat(localpath, &statbuf) < 0)
-	{
-		if (errno != ENOENT)
-			pg_fatal("could not stat file \"%s\": %m",
-					 localpath);
-
-		exists = false;
-	}
-
 	if (map->array == NULL)
 	{
 		/* on first call, initialize lookup array */
@@ -389,120 +312,76 @@ process_target_file(const char *path, file_type_t type, size_t oldsize,
 	}
 
 	/*
-	 * Like in process_source_file, pretend that xlog is always a  directory.
+	 * Like in process_source_file, pretend that pg_wal is always a directory.
 	 */
 	if (strcmp(path, "pg_wal") == 0 && type == FILE_TYPE_SYMLINK)
 		type = FILE_TYPE_DIRECTORY;
 
-	key.path = (char *) path;
-	key_ptr = &key;
-	exists = (bsearch(&key_ptr, map->array, map->narray, sizeof(file_entry_t *),
-					  path_cmp) != NULL);
-
-	/* Remove any file or folder that doesn't exist in the source system. */
-	if (!exists)
-	{
-		entry = pg_malloc(sizeof(file_entry_t));
-		entry->path = pg_strdup(path);
-		entry->type = type;
-		entry->action = FILE_ACTION_REMOVE;
-		entry->oldsize = oldsize;
-		entry->newsize = 0;
-		entry->link_target = link_target ? pg_strdup(link_target) : NULL;
-		entry->next = NULL;
-		entry->pagemap.bitmap = NULL;
-		entry->pagemap.bitmapsize = 0;
-		entry->isrelfile = isRelDataFile(path);
-
-		if (map->last == NULL)
-			map->first = entry;
-		else
-			map->last->next = entry;
-		map->last = entry;
-		map->nlist++;
-	}
-	else
-	{
-		/*
-		 * We already handled all files that exist in the source system in
-		 * process_source_file().
-		 */
-	}
+	/* Remember this target file */
+	entry = get_filemap_entry(path, true);
+	entry->target_exists = true;
+	entry->target_type = type;
+	entry->target_size = size;
+	entry->target_link_target = link_target ? pg_strdup(link_target) : NULL;
 }
 
 /*
  * This callback gets called while we read the WAL in the target, for every
- * block that have changed in the target system. It makes note of all the
+ * block that have changed in the target system.  It makes note of all the
  * changed blocks in the pagemap of the file.
+ *
+ * NOTE: All the files on both systems must have already been added to the
+ * file map!
  */
 void
-process_block_change(ForkNumber forknum, RelFileNode rnode, BlockNumber blkno)
+process_target_wal_block_change(ForkNumber forknum, RelFileNode rnode,
+								BlockNumber blkno)
 {
 	char	   *path;
-	file_entry_t key;
-	file_entry_t *key_ptr;
 	file_entry_t *entry;
 	BlockNumber blkno_inseg;
 	int			segno;
-	filemap_t  *map = filemap;
-	file_entry_t **e;
 
-	Assert(map->array);
+	Assert(filemap->array);
 
 	segno = blkno / RELSEG_SIZE;
 	blkno_inseg = blkno % RELSEG_SIZE;
 
 	path = datasegpath(rnode, forknum, segno);
-
-	key.path = (char *) path;
-	key_ptr = &key;
-
-	e = bsearch(&key_ptr, map->array, map->narray, sizeof(file_entry_t *),
-				path_cmp);
-	if (e)
-		entry = *e;
-	else
-		entry = NULL;
+	entry = get_filemap_entry(path, false);
 	pfree(path);
 
 	if (entry)
 	{
+		int64		end_offset;
+
 		Assert(entry->isrelfile);
 
-		switch (entry->action)
-		{
-			case FILE_ACTION_NONE:
-			case FILE_ACTION_TRUNCATE:
-				/* skip if we're truncating away the modified block anyway */
-				if ((blkno_inseg + 1) * BLCKSZ <= entry->newsize)
-					datapagemap_add(&entry->pagemap, blkno_inseg);
-				break;
-
-			case FILE_ACTION_COPY_TAIL:
-
-				/*
-				 * skip the modified block if it is part of the "tail" that
-				 * we're copying anyway.
-				 */
-				if ((blkno_inseg + 1) * BLCKSZ <= entry->oldsize)
-					datapagemap_add(&entry->pagemap, blkno_inseg);
-				break;
-
-			case FILE_ACTION_COPY:
-			case FILE_ACTION_REMOVE:
-				break;
-
-			case FILE_ACTION_CREATE:
-				pg_fatal("unexpected page modification for directory or symbolic link \"%s\"", entry->path);
-		}
+		if (entry->target_type != FILE_TYPE_REGULAR)
+			pg_fatal("unexpected page modification for directory or symbolic link \"%s\"",
+					 entry->path);
+
+		/*
+		 * If the block beyond the EOF in the source system, no need to
+		 * remember it now, because we're going to truncate it away from the
+		 * target anyway. Also no need to remember the block if it's beyond
+		 * the current EOF in the target system; we will copy it over with the
+		 * "tail" from the source system, anyway.
+		 */
+		end_offset = (blkno_inseg + 1) * BLCKSZ;
+		if (end_offset <= entry->source_size &&
+			end_offset <= entry->target_size)
+			datapagemap_add(&entry->target_modified_pages, blkno_inseg);
 	}
 	else
 	{
 		/*
 		 * If we don't have any record of this file in the file map, it means
-		 * that it's a relation that doesn't exist in the source system, and
-		 * it was subsequently removed in the target system, too. We can
-		 * safely ignore it.
+		 * that it's a relation that doesn't exist in the source system.  It
+		 * could exist in the target system; we haven't moved the target-only
+		 * entries from the linked list to the array yet!  But in any case, if
+		 * it doesn't exist in the source it will be removed from the target
+		 * too, and we can safely ignore it.
 		 */
 	}
 }
@@ -593,16 +472,6 @@ filemap_list_to_array(filemap_t *map)
 	map->first = map->last = NULL;
 }
 
-void
-filemap_finalize(void)
-{
-	filemap_t  *map = filemap;
-
-	filemap_list_to_array(map);
-	qsort(map->array, map->narray, sizeof(file_entry_t *),
-		  final_filemap_cmp);
-}
-
 static const char *
 action_to_str(file_action_t action)
 {
@@ -643,26 +512,26 @@ calculate_totals(void)
 	{
 		entry = map->array[i];
 
-		if (entry->type != FILE_TYPE_REGULAR)
+		if (entry->source_type != FILE_TYPE_REGULAR)
 			continue;
 
-		map->total_size += entry->newsize;
+		map->total_size += entry->source_size;
 
 		if (entry->action == FILE_ACTION_COPY)
 		{
-			map->fetch_size += entry->newsize;
+			map->fetch_size += entry->source_size;
 			continue;
 		}
 
 		if (entry->action == FILE_ACTION_COPY_TAIL)
-			map->fetch_size += (entry->newsize - entry->oldsize);
+			map->fetch_size += (entry->source_size - entry->target_size);
 
-		if (entry->pagemap.bitmapsize > 0)
+		if (entry->target_modified_pages.bitmapsize > 0)
 		{
 			datapagemap_iterator_t *iter;
 			BlockNumber blk;
 
-			iter = datapagemap_iterate(&entry->pagemap);
+			iter = datapagemap_iterate(&entry->target_modified_pages);
 			while (datapagemap_next(iter, &blk))
 				map->fetch_size += BLCKSZ;
 
@@ -682,13 +551,13 @@ print_filemap(void)
 	{
 		entry = map->array[i];
 		if (entry->action != FILE_ACTION_NONE ||
-			entry->pagemap.bitmapsize > 0)
+			entry->target_modified_pages.bitmapsize > 0)
 		{
 			pg_log_debug("%s (%s)", entry->path,
 						 action_to_str(entry->action));
 
-			if (entry->pagemap.bitmapsize > 0)
-				datapagemap_print(&entry->pagemap);
+			if (entry->target_modified_pages.bitmapsize > 0)
+				datapagemap_print(&entry->target_modified_pages);
 		}
 	}
 	fflush(stdout);
@@ -837,3 +706,126 @@ final_filemap_cmp(const void *a, const void *b)
 	else
 		return strcmp(fa->path, fb->path);
 }
+
+/*
+ * Decide what to do with each file.
+ */
+void
+filemap_finalize()
+{
+	int			i;
+
+	filemap_list_to_array(filemap);
+
+	for (i = 0; i < filemap->narray; i++)
+	{
+		file_entry_t *entry = filemap->array[i];
+		file_action_t action;
+
+		if (!entry->target_exists && entry->source_exists)
+		{
+			/*
+			 * File exists in source, but not in target. Copy
+			 * it in toto. (If it's a relation data file, WAL replay
+			 * after rewinding should re-create it anyway. But there's
+			 * no harm in copying it now.)
+			 */
+			if (entry->source_type == FILE_TYPE_DIRECTORY ||
+				entry->source_type == FILE_TYPE_SYMLINK)
+			{
+				action = FILE_ACTION_CREATE;
+			}
+			else
+				action = FILE_ACTION_COPY;
+		}
+		else if (entry->target_exists && !entry->source_exists)
+		{
+			/* Delete file from target */
+			action = FILE_ACTION_REMOVE;
+		}
+		else if (entry->target_exists && entry->source_exists)
+		{
+			/* File exists in both systems */
+			if (entry->source_type != entry->target_type)
+			{
+				/* But it's a different kind of object. Strange.. */
+				pg_fatal("file \"%s\" is of different type in source and target", entry->path);
+			}
+
+			switch (entry->source_type)
+			{
+				case FILE_TYPE_DIRECTORY:
+					action = FILE_ACTION_NONE;
+					break;
+
+				case FILE_TYPE_SYMLINK:
+					// FIXME: Check if it points to the same target?
+					action = FILE_ACTION_NONE;
+					break;
+
+				case FILE_TYPE_REGULAR:
+					if (!entry->isrelfile)
+					{
+						/*
+						 * It's a non-data file that we have no special processing for. Copy
+						 * it in toto.
+						 *
+						 * An exception: PG_VERSIONs should be identical, but avoid
+						 * overwriting it for paranoia.
+						 */
+						if (!pg_str_endswith(entry->path, "PG_VERSION"))
+							action = FILE_ACTION_COPY;
+						else
+							action = FILE_ACTION_NONE;
+					}
+					else
+					{
+						/*
+						 * It's a data file that exists in both systems.
+						 *
+						 * If it's larger in target, we can truncate it. There will
+						 * also be a WAL record of the truncation in the source
+						 * system, so WAL replay would eventually truncate the target
+						 * too, but we might as well do it now.
+						 *
+						 * If it's smaller in the target, it means that it has been
+						 * truncated in the target, or enlarged in the source, or
+						 * both. If it was truncated in the target, we need to copy
+						 * the missing tail from the source system. If it was enlarged
+						 * in the source system, there will be WAL records in the
+						 * source system for the new blocks, so we wouldn't need to
+						 * copy them here. But we don't know which scenario we're
+						 * dealing with, and there's no harm in copying the missing
+						 * blocks now, so do it now.
+						 *
+						 * If it's the same size, do nothing here. Any blocks modified
+						 * in the target will be copied based on parsing the target
+						 * system's WAL, and any blocks modified in the source will be
+						 * updated after rewinding, when the source system's WAL is
+						 * replayed.
+						 */
+						if (entry->target_size < entry->source_size)
+							action = FILE_ACTION_COPY_TAIL;
+						else if (entry->target_size > entry->source_size)
+							action = FILE_ACTION_TRUNCATE;
+						else
+							action = FILE_ACTION_NONE;
+					}
+					break;
+				case FILE_TYPE_UNDEFINED:
+					pg_fatal("unknown file type for \"%s\"", entry->path);
+					break;
+			}
+		}
+		else
+		{
+			/* Doesn't exist in either server. Why does it have an entry at all?? */
+			action = FILE_ACTION_NONE;
+		}
+		entry->action = action;
+	}
+
+	/* Sort the actions to the order that they should be performed */
+	qsort(filemap->array, filemap->narray, sizeof(file_entry_t *),
+		  final_filemap_cmp);
+}
diff --git a/src/bin/pg_rewind/filemap.h b/src/bin/pg_rewind/filemap.h
index 0cb7425170c..a5e8df57f40 100644
--- a/src/bin/pg_rewind/filemap.h
+++ b/src/bin/pg_rewind/filemap.h
@@ -14,17 +14,21 @@
 
 /*
  * For every file found in the local or remote system, we have a file entry
- * which says what we are going to do with the file. For relation files,
- * there is also a page map, marking pages in the file that were changed
- * locally.
- *
- * The enum values are sorted in the order we want actions to be processed.
+ * that contains information about the file on both systems.  For relation
+ * files, there is also a page map that marks pages in the file that were
+ * changed in the target after the last common checkpoint.  Each entry also
+ * contains an 'action' field, which says what we are going to do with the
+ * file.
  */
+
+/* these enum values are sorted in the order we want actions to be processed */
 typedef enum
 {
+	FILE_ACTION_UNDECIDED = 0,	/* not decided yet */
+
 	FILE_ACTION_CREATE,			/* create local directory or symbolic link */
 	FILE_ACTION_COPY,			/* copy whole file, overwriting if exists */
-	FILE_ACTION_COPY_TAIL,		/* copy tail from 'oldsize' to 'newsize' */
+	FILE_ACTION_COPY_TAIL,		/* copy tail from 'source_size' to 'target_size' */
 	FILE_ACTION_NONE,			/* no action (we might still copy modified
 								 * blocks based on the parsed WAL) */
 	FILE_ACTION_TRUNCATE,		/* truncate local file to 'newsize' bytes */
@@ -33,6 +37,8 @@ typedef enum
 
 typedef enum
 {
+	FILE_TYPE_UNDEFINED = 0,
+
 	FILE_TYPE_REGULAR,
 	FILE_TYPE_DIRECTORY,
 	FILE_TYPE_SYMLINK
@@ -41,19 +47,30 @@ typedef enum
 typedef struct file_entry_t
 {
 	char	   *path;
-	file_type_t type;
+	bool		isrelfile;		/* is it a relation data file? */
 
-	file_action_t action;
+	/*
+	 * Status of the file in the target.
+	 */
+	bool		target_exists;
+	file_type_t target_type;
+	size_t		target_size; /* for a regular file */
+	char	   *target_link_target; /* for a symlink */
 
-	/* for a regular file */
-	size_t		oldsize;
-	size_t		newsize;
-	bool		isrelfile;		/* is it a relation data file? */
+	datapagemap_t target_modified_pages;
 
-	datapagemap_t pagemap;
+	/*
+	 * Status of the file in the source.
+	 */
+	bool		source_exists;
+	file_type_t source_type;
+	size_t		source_size;
+	char	   *source_link_target; /* for a symlink */
 
-	/* for a symlink */
-	char	   *link_target;
+	/*
+	 * What will we do to the file?
+	 */
+	file_action_t action;
 
 	struct file_entry_t *next;
 } file_entry_t;
@@ -70,20 +87,19 @@ typedef struct filemap_t
 
 	/*
 	 * After processing all the remote files, the entries in the linked list
-	 * are moved to this array. After processing local files, too, all the
+	 * are moved to this array.  After processing local files, too, all the
 	 * local entries are added to the array by filemap_finalize, and sorted in
-	 * the final order. After filemap_finalize, all the entries are in the
+	 * the final order.  After filemap_finalize, all the entries are in the
 	 * array, and the linked list is empty.
 	 */
 	file_entry_t **array;
 	int			narray;			/* current length of array */
 
 	/*
-	 * Summary information. total_size is the total size of the source
-	 * cluster, and fetch_size is the number of bytes that needs to be copied.
+	 * Summary information.
 	 */
-	uint64		total_size;
-	uint64		fetch_size;
+	uint64		total_size;		/* total size of the source cluster */
+	uint64		fetch_size;		/* number of bytes that needs to be copied */
 } filemap_t;
 
 extern filemap_t *filemap;
@@ -94,11 +110,12 @@ extern void print_filemap(void);
 
 /* Functions for populating the filemap */
 extern void process_source_file(const char *path, file_type_t type,
-								size_t newsize, const char *link_target);
+								size_t size, const char *link_target);
 extern void process_target_file(const char *path, file_type_t type,
-								size_t newsize, const char *link_target);
-extern void process_block_change(ForkNumber forknum, RelFileNode rnode,
-								 BlockNumber blkno);
+								size_t size, const char *link_target);
+extern void process_target_wal_block_change(ForkNumber forknum,
+											RelFileNode rnode,
+											BlockNumber blkno);
 extern void filemap_finalize(void);
 
 #endif							/* FILEMAP_H */
diff --git a/src/bin/pg_rewind/libpq_fetch.c b/src/bin/pg_rewind/libpq_fetch.c
index bf4dfc23b96..7fc9161b8c8 100644
--- a/src/bin/pg_rewind/libpq_fetch.c
+++ b/src/bin/pg_rewind/libpq_fetch.c
@@ -465,7 +465,7 @@ libpq_executeFileMap(filemap_t *map)
 		entry = map->array[i];
 
 		/* If this is a relation file, copy the modified blocks */
-		execute_pagemap(&entry->pagemap, entry->path);
+		execute_pagemap(&entry->target_modified_pages, entry->path);
 
 		switch (entry->action)
 		{
@@ -476,15 +476,15 @@ libpq_executeFileMap(filemap_t *map)
 			case FILE_ACTION_COPY:
 				/* Truncate the old file out of the way, if any */
 				open_target_file(entry->path, true);
-				fetch_file_range(entry->path, 0, entry->newsize);
+				fetch_file_range(entry->path, 0, entry->source_size);
 				break;
 
 			case FILE_ACTION_TRUNCATE:
-				truncate_target_file(entry->path, entry->newsize);
+				truncate_target_file(entry->path, entry->source_size);
 				break;
 
 			case FILE_ACTION_COPY_TAIL:
-				fetch_file_range(entry->path, entry->oldsize, entry->newsize);
+				fetch_file_range(entry->path, entry->target_size, entry->source_size);
 				break;
 
 			case FILE_ACTION_REMOVE:
@@ -494,6 +494,10 @@ libpq_executeFileMap(filemap_t *map)
 			case FILE_ACTION_CREATE:
 				create_target(entry);
 				break;
+
+			case FILE_ACTION_UNDECIDED:
+				pg_fatal("no action decided for \"%s\"", entry->path);
+				break;
 		}
 	}
 
diff --git a/src/bin/pg_rewind/parsexlog.c b/src/bin/pg_rewind/parsexlog.c
index 2229c86f9af..2baeb74ae93 100644
--- a/src/bin/pg_rewind/parsexlog.c
+++ b/src/bin/pg_rewind/parsexlog.c
@@ -436,6 +436,6 @@ extractPageInfo(XLogReaderState *record)
 		if (forknum != MAIN_FORKNUM)
 			continue;
 
-		process_block_change(forknum, rnode, blkno);
+		process_target_wal_block_change(forknum, rnode, blkno);
 	}
 }
diff --git a/src/bin/pg_rewind/pg_rewind.c b/src/bin/pg_rewind/pg_rewind.c
index c9b9e480c0f..210984d302b 100644
--- a/src/bin/pg_rewind/pg_rewind.c
+++ b/src/bin/pg_rewind/pg_rewind.c
@@ -369,7 +369,7 @@ main(int argc, char **argv)
 				chkpttli);
 
 	/*
-	 * Build the filemap, by comparing the source and target data directories.
+	 * Collect information about all files in the target and source systems.
 	 */
 	filemap_create();
 	if (showprogress)
@@ -390,8 +390,12 @@ main(int argc, char **argv)
 		pg_log_info("reading WAL in target");
 	extractPageMap(datadir_target, chkptrec, lastcommontliIndex,
 				   ControlFile_target.checkPoint, restore_command);
-	filemap_finalize();
 
+	/*
+	 * We have collected all information we need from both systems. Decide
+	 * what to do with each file.
+	 */
+	filemap_finalize();
 	if (showprogress)
 		calculate_totals();
 
-- 
2.20.1


--------------D93EDEBFB124D563B723F4BD
Content-Type: text/x-patch; charset=UTF-8;
 name="0003-pg_rewind-Replace-the-hybrid-list-array-data-structu.patch"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
 filename*0="0003-pg_rewind-Replace-the-hybrid-list-array-data-structu.pa";
 filename*1="tch"



^ permalink  raw  reply  [nested|flat] 41+ messages in thread

* [PATCH v2 2/5] Refactor pg_rewind for more clear decision making.
@ 2020-08-19 12:34 Heikki Linnakangas <[email protected]>
  0 siblings, 0 replies; 41+ messages in thread

From: Heikki Linnakangas @ 2020-08-19 12:34 UTC (permalink / raw)

Deciding what to do with each file is now a separate step after all the
necessary information has been gathered. It is more clear that way.
Previously, the decision-making was divided between process_source_file()
and process_target_file(), and it was a bit hard to piece together what the
overall rules were.
---
 src/bin/pg_rewind/copy_fetch.c  |  14 +-
 src/bin/pg_rewind/file_ops.c    |  16 +-
 src/bin/pg_rewind/filemap.c     | 569 ++++++++++++++++----------------
 src/bin/pg_rewind/filemap.h     |  67 ++--
 src/bin/pg_rewind/libpq_fetch.c |  12 +-
 src/bin/pg_rewind/parsexlog.c   |   2 +-
 src/bin/pg_rewind/pg_rewind.c   |   8 +-
 7 files changed, 371 insertions(+), 317 deletions(-)

diff --git a/src/bin/pg_rewind/copy_fetch.c b/src/bin/pg_rewind/copy_fetch.c
index 1edab5f1867..18fad32600e 100644
--- a/src/bin/pg_rewind/copy_fetch.c
+++ b/src/bin/pg_rewind/copy_fetch.c
@@ -210,7 +210,7 @@ copy_executeFileMap(filemap_t *map)
 	for (i = 0; i < map->narray; i++)
 	{
 		entry = map->array[i];
-		execute_pagemap(&entry->pagemap, entry->path);
+		execute_pagemap(&entry->target_modified_pages, entry->path);
 
 		switch (entry->action)
 		{
@@ -219,16 +219,16 @@ copy_executeFileMap(filemap_t *map)
 				break;
 
 			case FILE_ACTION_COPY:
-				rewind_copy_file_range(entry->path, 0, entry->newsize, true);
+				rewind_copy_file_range(entry->path, 0, entry->source_size, true);
 				break;
 
 			case FILE_ACTION_TRUNCATE:
-				truncate_target_file(entry->path, entry->newsize);
+				truncate_target_file(entry->path, entry->source_size);
 				break;
 
 			case FILE_ACTION_COPY_TAIL:
-				rewind_copy_file_range(entry->path, entry->oldsize,
-									   entry->newsize, false);
+				rewind_copy_file_range(entry->path, entry->target_size,
+									   entry->source_size, false);
 				break;
 
 			case FILE_ACTION_CREATE:
@@ -238,6 +238,10 @@ copy_executeFileMap(filemap_t *map)
 			case FILE_ACTION_REMOVE:
 				remove_target(entry);
 				break;
+
+			case FILE_ACTION_UNDECIDED:
+				pg_fatal("no action decided for \"%s\"", entry->path);
+				break;
 		}
 	}
 
diff --git a/src/bin/pg_rewind/file_ops.c b/src/bin/pg_rewind/file_ops.c
index 55439db20ba..ec37d0b2e0d 100644
--- a/src/bin/pg_rewind/file_ops.c
+++ b/src/bin/pg_rewind/file_ops.c
@@ -126,8 +126,9 @@ void
 remove_target(file_entry_t *entry)
 {
 	Assert(entry->action == FILE_ACTION_REMOVE);
+	Assert(entry->target_exists);
 
-	switch (entry->type)
+	switch (entry->target_type)
 	{
 		case FILE_TYPE_DIRECTORY:
 			remove_target_dir(entry->path);
@@ -140,6 +141,10 @@ remove_target(file_entry_t *entry)
 		case FILE_TYPE_SYMLINK:
 			remove_target_symlink(entry->path);
 			break;
+
+		case FILE_TYPE_UNDEFINED:
+			pg_fatal("undefined file type for \"%s\"", entry->path);
+			break;
 	}
 }
 
@@ -147,21 +152,26 @@ void
 create_target(file_entry_t *entry)
 {
 	Assert(entry->action == FILE_ACTION_CREATE);
+	Assert(!entry->target_exists);
 
-	switch (entry->type)
+	switch (entry->source_type)
 	{
 		case FILE_TYPE_DIRECTORY:
 			create_target_dir(entry->path);
 			break;
 
 		case FILE_TYPE_SYMLINK:
-			create_target_symlink(entry->path, entry->link_target);
+			create_target_symlink(entry->path, entry->source_link_target);
 			break;
 
 		case FILE_TYPE_REGULAR:
 			/* can't happen. Regular files are created with open_target_file. */
 			pg_fatal("invalid action (CREATE) for regular file");
 			break;
+
+		case FILE_TYPE_UNDEFINED:
+			pg_fatal("undefined file type for \"%s\"", entry->path);
+			break;
 	}
 }
 
diff --git a/src/bin/pg_rewind/filemap.c b/src/bin/pg_rewind/filemap.c
index 1879257b66a..7971daeda5e 100644
--- a/src/bin/pg_rewind/filemap.c
+++ b/src/bin/pg_rewind/filemap.c
@@ -26,6 +26,8 @@ static bool isRelDataFile(const char *path);
 static char *datasegpath(RelFileNode rnode, ForkNumber forknum,
 						 BlockNumber segno);
 static int	path_cmp(const void *a, const void *b);
+
+static file_entry_t *get_filemap_entry(const char *path, bool create);
 static int	final_filemap_cmp(const void *a, const void *b);
 static void filemap_list_to_array(filemap_t *map);
 static bool check_file_excluded(const char *path, bool is_source);
@@ -146,6 +148,65 @@ filemap_create(void)
 	filemap = map;
 }
 
+/* Look up or create entry for 'path' */
+static file_entry_t *
+get_filemap_entry(const char *path, bool create)
+{
+	filemap_t  *map = filemap;
+	file_entry_t *entry;
+	file_entry_t **e;
+	file_entry_t key;
+	file_entry_t *key_ptr;
+
+	if (map->array)
+	{
+		key.path = (char *) path;
+		key_ptr = &key;
+		e = bsearch(&key_ptr, map->array, map->narray, sizeof(file_entry_t *),
+					path_cmp);
+	}
+	else
+		e = NULL;
+
+	if (e)
+		entry = *e;
+	else if (!create)
+		entry = NULL;
+	else
+	{
+		/* Create a new entry for this file */
+		entry = pg_malloc(sizeof(file_entry_t));
+		entry->path = pg_strdup(path);
+		entry->isrelfile = isRelDataFile(path);
+		entry->action = FILE_ACTION_UNDECIDED;
+
+		entry->target_exists = false;
+		entry->target_type = FILE_TYPE_UNDEFINED;
+		entry->target_size = 0;
+		entry->target_link_target = NULL;
+		entry->target_modified_pages.bitmap = NULL;
+		entry->target_modified_pages.bitmapsize = 0;
+
+		entry->source_exists = false;
+		entry->source_type = FILE_TYPE_UNDEFINED;
+		entry->source_size = 0;
+		entry->source_link_target = NULL;
+
+		entry->next = NULL;
+
+		if (map->last)
+		{
+			map->last->next = entry;
+			map->last = entry;
+		}
+		else
+			map->first = map->last = entry;
+		map->nlist++;
+	}
+
+	return entry;
+}
+
 /*
  * Callback for processing source file list.
  *
@@ -154,25 +215,12 @@ filemap_create(void)
  * exists in the target and whether the size matches.
  */
 void
-process_source_file(const char *path, file_type_t type, size_t newsize,
+process_source_file(const char *path, file_type_t type, size_t size,
 					const char *link_target)
 {
-	bool		exists;
-	char		localpath[MAXPGPATH];
-	struct stat statbuf;
-	filemap_t  *map = filemap;
-	file_action_t action = FILE_ACTION_NONE;
-	size_t		oldsize = 0;
 	file_entry_t *entry;
 
-	Assert(map->array == NULL);
-
-	/*
-	 * Skip any files matching the exclusion filters. This has the effect to
-	 * remove all those files on the target.
-	 */
-	if (check_file_excluded(path, true))
-		return;
+	Assert(filemap->array == NULL);
 
 	/*
 	 * Pretend that pg_wal is a directory, even if it's really a symlink. We
@@ -182,16 +230,6 @@ process_source_file(const char *path, file_type_t type, size_t newsize,
 	if (strcmp(path, "pg_wal") == 0 && type == FILE_TYPE_SYMLINK)
 		type = FILE_TYPE_DIRECTORY;
 
-	/*
-	 * Skip temporary files, .../pgsql_tmp/... and .../pgsql_tmp.* in source.
-	 * This has the effect that all temporary files in the destination will be
-	 * removed.
-	 */
-	if (strstr(path, "/" PG_TEMP_FILE_PREFIX) != NULL)
-		return;
-	if (strstr(path, "/" PG_TEMP_FILES_DIR "/") != NULL)
-		return;
-
 	/*
 	 * sanity check: a filename that looks like a data file better be a
 	 * regular file
@@ -199,142 +237,12 @@ process_source_file(const char *path, file_type_t type, size_t newsize,
 	if (type != FILE_TYPE_REGULAR && isRelDataFile(path))
 		pg_fatal("data file \"%s\" in source is not a regular file", path);
 
-	snprintf(localpath, sizeof(localpath), "%s/%s", datadir_target, path);
-
-	/* Does the corresponding file exist in the target data dir? */
-	if (lstat(localpath, &statbuf) < 0)
-	{
-		if (errno != ENOENT)
-			pg_fatal("could not stat file \"%s\": %m",
-					 localpath);
-
-		exists = false;
-	}
-	else
-		exists = true;
-
-	switch (type)
-	{
-		case FILE_TYPE_DIRECTORY:
-			if (exists && !S_ISDIR(statbuf.st_mode) && strcmp(path, "pg_wal") != 0)
-			{
-				/* it's a directory in source, but not in target. Strange.. */
-				pg_fatal("\"%s\" is not a directory", localpath);
-			}
-
-			if (!exists)
-				action = FILE_ACTION_CREATE;
-			else
-				action = FILE_ACTION_NONE;
-			oldsize = 0;
-			break;
-
-		case FILE_TYPE_SYMLINK:
-			if (exists &&
-#ifndef WIN32
-				!S_ISLNK(statbuf.st_mode)
-#else
-				!pgwin32_is_junction(localpath)
-#endif
-				)
-			{
-				/*
-				 * It's a symbolic link in source, but not in target.
-				 * Strange..
-				 */
-				pg_fatal("\"%s\" is not a symbolic link", localpath);
-			}
-
-			if (!exists)
-				action = FILE_ACTION_CREATE;
-			else
-				action = FILE_ACTION_NONE;
-			oldsize = 0;
-			break;
-
-		case FILE_TYPE_REGULAR:
-			if (exists && !S_ISREG(statbuf.st_mode))
-				pg_fatal("\"%s\" is not a regular file", localpath);
-
-			if (!exists || !isRelDataFile(path))
-			{
-				/*
-				 * File exists in source, but not in target. Or it's a
-				 * non-data file that we have no special processing for. Copy
-				 * it in toto.
-				 *
-				 * An exception: PG_VERSIONs should be identical, but avoid
-				 * overwriting it for paranoia.
-				 */
-				if (pg_str_endswith(path, "PG_VERSION"))
-				{
-					action = FILE_ACTION_NONE;
-					oldsize = statbuf.st_size;
-				}
-				else
-				{
-					action = FILE_ACTION_COPY;
-					oldsize = 0;
-				}
-			}
-			else
-			{
-				/*
-				 * It's a data file that exists in both.
-				 *
-				 * If it's larger in target, we can truncate it. There will
-				 * also be a WAL record of the truncation in the source
-				 * system, so WAL replay would eventually truncate the target
-				 * too, but we might as well do it now.
-				 *
-				 * If it's smaller in the target, it means that it has been
-				 * truncated in the target, or enlarged in the source, or
-				 * both. If it was truncated in the target, we need to copy
-				 * the missing tail from the source system. If it was enlarged
-				 * in the source system, there will be WAL records in the
-				 * source system for the new blocks, so we wouldn't need to
-				 * copy them here. But we don't know which scenario we're
-				 * dealing with, and there's no harm in copying the missing
-				 * blocks now, so do it now.
-				 *
-				 * If it's the same size, do nothing here. Any blocks modified
-				 * in the target will be copied based on parsing the target
-				 * system's WAL, and any blocks modified in the source will be
-				 * updated after rewinding, when the source system's WAL is
-				 * replayed.
-				 */
-				oldsize = statbuf.st_size;
-				if (oldsize < newsize)
-					action = FILE_ACTION_COPY_TAIL;
-				else if (oldsize > newsize)
-					action = FILE_ACTION_TRUNCATE;
-				else
-					action = FILE_ACTION_NONE;
-			}
-			break;
-	}
-
-	/* Create a new entry for this file */
-	entry = pg_malloc(sizeof(file_entry_t));
-	entry->path = pg_strdup(path);
-	entry->type = type;
-	entry->action = action;
-	entry->oldsize = oldsize;
-	entry->newsize = newsize;
-	entry->link_target = link_target ? pg_strdup(link_target) : NULL;
-	entry->next = NULL;
-	entry->pagemap.bitmap = NULL;
-	entry->pagemap.bitmapsize = 0;
-	entry->isrelfile = isRelDataFile(path);
-
-	if (map->last)
-	{
-		map->last->next = entry;
-		map->last = entry;
-	}
-	else
-		map->first = map->last = entry;
-	map->nlist++;
+	/* Remember this source file */
+	entry = get_filemap_entry(path, true);
+	entry->source_exists = true;
+	entry->source_type = type;
+	entry->source_size = size;
+	entry->source_link_target = link_target ? pg_strdup(link_target) : NULL;
 }
 
 /*
@@ -345,14 +253,9 @@ process_source_file(const char *path, file_type_t type, size_t newsize,
  * deletion.
  */
 void
-process_target_file(const char *path, file_type_t type, size_t oldsize,
+process_target_file(const char *path, file_type_t type, size_t size,
 					const char *link_target)
 {
-	bool		exists;
-	char		localpath[MAXPGPATH];
-	struct stat statbuf;
-	file_entry_t key;
-	file_entry_t *key_ptr;
 	filemap_t  *map = filemap;
 	file_entry_t *entry;
 
@@ -361,17 +264,6 @@ process_target_file(const char *path, file_type_t type, size_t oldsize,
 	 * from the target data folder all paths which have been filtered out from
 	 * the source data folder when processing the source files.
 	 */
-
-	snprintf(localpath, sizeof(localpath), "%s/%s", datadir_target, path);
-	if (lstat(localpath, &statbuf) < 0)
-	{
-		if (errno != ENOENT)
-			pg_fatal("could not stat file \"%s\": %m",
-					 localpath);
-
-		exists = false;
-	}
-
 	if (map->array == NULL)
 	{
 		/* on first call, initialize lookup array */
@@ -389,120 +281,76 @@ process_target_file(const char *path, file_type_t type, size_t oldsize,
 	}
 
 	/*
-	 * Like in process_source_file, pretend that xlog is always a  directory.
+	 * Like in process_source_file, pretend that pg_wal is always a directory.
 	 */
 	if (strcmp(path, "pg_wal") == 0 && type == FILE_TYPE_SYMLINK)
 		type = FILE_TYPE_DIRECTORY;
 
-	key.path = (char *) path;
-	key_ptr = &key;
-	exists = (bsearch(&key_ptr, map->array, map->narray, sizeof(file_entry_t *),
-					  path_cmp) != NULL);
-
-	/* Remove any file or folder that doesn't exist in the source system. */
-	if (!exists)
-	{
-		entry = pg_malloc(sizeof(file_entry_t));
-		entry->path = pg_strdup(path);
-		entry->type = type;
-		entry->action = FILE_ACTION_REMOVE;
-		entry->oldsize = oldsize;
-		entry->newsize = 0;
-		entry->link_target = link_target ? pg_strdup(link_target) : NULL;
-		entry->next = NULL;
-		entry->pagemap.bitmap = NULL;
-		entry->pagemap.bitmapsize = 0;
-		entry->isrelfile = isRelDataFile(path);
-
-		if (map->last == NULL)
-			map->first = entry;
-		else
-			map->last->next = entry;
-		map->last = entry;
-		map->nlist++;
-	}
-	else
-	{
-		/*
-		 * We already handled all files that exist in the source system in
-		 * process_source_file().
-		 */
-	}
+	/* Remember this target file */
+	entry = get_filemap_entry(path, true);
+	entry->target_exists = true;
+	entry->target_type = type;
+	entry->target_size = size;
+	entry->target_link_target = link_target ? pg_strdup(link_target) : NULL;
 }
 
 /*
  * This callback gets called while we read the WAL in the target, for every
- * block that have changed in the target system. It makes note of all the
+ * block that have changed in the target system.  It makes note of all the
  * changed blocks in the pagemap of the file.
+ *
+ * NOTE: All the files on both systems must have already been added to the
+ * file map!
  */
 void
-process_block_change(ForkNumber forknum, RelFileNode rnode, BlockNumber blkno)
+process_target_wal_block_change(ForkNumber forknum, RelFileNode rnode,
+								BlockNumber blkno)
 {
 	char	   *path;
-	file_entry_t key;
-	file_entry_t *key_ptr;
 	file_entry_t *entry;
 	BlockNumber blkno_inseg;
 	int			segno;
-	filemap_t  *map = filemap;
-	file_entry_t **e;
 
-	Assert(map->array);
+	Assert(filemap->array);
 
 	segno = blkno / RELSEG_SIZE;
 	blkno_inseg = blkno % RELSEG_SIZE;
 
 	path = datasegpath(rnode, forknum, segno);
-
-	key.path = (char *) path;
-	key_ptr = &key;
-
-	e = bsearch(&key_ptr, map->array, map->narray, sizeof(file_entry_t *),
-				path_cmp);
-	if (e)
-		entry = *e;
-	else
-		entry = NULL;
+	entry = get_filemap_entry(path, false);
 	pfree(path);
 
 	if (entry)
 	{
-		Assert(entry->isrelfile);
-
-		switch (entry->action)
-		{
-			case FILE_ACTION_NONE:
-			case FILE_ACTION_TRUNCATE:
-				/* skip if we're truncating away the modified block anyway */
-				if ((blkno_inseg + 1) * BLCKSZ <= entry->newsize)
-					datapagemap_add(&entry->pagemap, blkno_inseg);
-				break;
+		int64		end_offset;
 
-			case FILE_ACTION_COPY_TAIL:
-
-				/*
-				 * skip the modified block if it is part of the "tail" that
-				 * we're copying anyway.
-				 */
-				if ((blkno_inseg + 1) * BLCKSZ <= entry->oldsize)
-					datapagemap_add(&entry->pagemap, blkno_inseg);
-				break;
+		Assert(entry->isrelfile);
 
-			case FILE_ACTION_COPY:
-			case FILE_ACTION_REMOVE:
-				break;
+		if (entry->target_type != FILE_TYPE_REGULAR)
+			pg_fatal("unexpected page modification for directory or symbolic link \"%s\"",
+					 entry->path);
 
-			case FILE_ACTION_CREATE:
-				pg_fatal("unexpected page modification for directory or symbolic link \"%s\"", entry->path);
-		}
+		/*
+		 * If the block beyond the EOF in the source system, no need to
+		 * remember it now, because we're going to truncate it away from the
+		 * target anyway. Also no need to remember the block if it's beyond
+		 * the current EOF in the target system; we will copy it over with the
+		 * "tail" from the source system, anyway.
+		 */
+		end_offset = (blkno_inseg + 1) * BLCKSZ;
+		if (end_offset <= entry->source_size &&
+			end_offset <= entry->target_size)
+			datapagemap_add(&entry->target_modified_pages, blkno_inseg);
 	}
 	else
 	{
 		/*
 		 * If we don't have any record of this file in the file map, it means
-		 * that it's a relation that doesn't exist in the source system, and
-		 * it was subsequently removed in the target system, too. We can
-		 * safely ignore it.
+		 * that it's a relation that doesn't exist in the source system.  It
+		 * could exist in the target system; we haven't moved the target-only
+		 * entries from the linked list to the array yet!  But in any case, if
+		 * it doesn't exist in the source it will be removed from the target
+		 * too, and we can safely ignore it.
 		 */
 	}
 }
@@ -593,16 +441,6 @@ filemap_list_to_array(filemap_t *map)
 	map->first = map->last = NULL;
 }
 
-void
-filemap_finalize(void)
-{
-	filemap_t  *map = filemap;
-
-	filemap_list_to_array(map);
-	qsort(map->array, map->narray, sizeof(file_entry_t *),
-		  final_filemap_cmp);
-}
-
 static const char *
 action_to_str(file_action_t action)
 {
@@ -643,26 +481,26 @@ calculate_totals(void)
 	{
 		entry = map->array[i];
 
-		if (entry->type != FILE_TYPE_REGULAR)
+		if (entry->source_type != FILE_TYPE_REGULAR)
 			continue;
 
-		map->total_size += entry->newsize;
+		map->total_size += entry->source_size;
 
 		if (entry->action == FILE_ACTION_COPY)
 		{
-			map->fetch_size += entry->newsize;
+			map->fetch_size += entry->source_size;
 			continue;
 		}
 
 		if (entry->action == FILE_ACTION_COPY_TAIL)
-			map->fetch_size += (entry->newsize - entry->oldsize);
+			map->fetch_size += (entry->source_size - entry->target_size);
 
-		if (entry->pagemap.bitmapsize > 0)
+		if (entry->target_modified_pages.bitmapsize > 0)
 		{
 			datapagemap_iterator_t *iter;
 			BlockNumber blk;
 
-			iter = datapagemap_iterate(&entry->pagemap);
+			iter = datapagemap_iterate(&entry->target_modified_pages);
 			while (datapagemap_next(iter, &blk))
 				map->fetch_size += BLCKSZ;
 
@@ -682,13 +520,13 @@ print_filemap(void)
 	{
 		entry = map->array[i];
 		if (entry->action != FILE_ACTION_NONE ||
-			entry->pagemap.bitmapsize > 0)
+			entry->target_modified_pages.bitmapsize > 0)
 		{
 			pg_log_debug("%s (%s)", entry->path,
 						 action_to_str(entry->action));
 
-			if (entry->pagemap.bitmapsize > 0)
-				datapagemap_print(&entry->pagemap);
+			if (entry->target_modified_pages.bitmapsize > 0)
+				datapagemap_print(&entry->target_modified_pages);
 		}
 	}
 	fflush(stdout);
@@ -837,3 +675,180 @@ final_filemap_cmp(const void *a, const void *b)
 	else
 		return strcmp(fa->path, fb->path);
 }
+
+/*
+ * Decide what action to perform to a file.
+ */
+static file_action_t
+decide_file_action(file_entry_t *entry)
+{
+	const char *path = entry->path;
+
+	/*
+	 * Don't touch the control file. It is handled specially, after copying
+	 * all the other files.
+	 */
+	if (strcmp(path, "global/pg_control") == 0)
+		return FILE_ACTION_NONE;
+
+	/*
+	 * Remove all files matching the exclusion filters in the target.
+	 */
+	if (check_file_excluded(path, true))
+	{
+		if (entry->target_exists)
+			return FILE_ACTION_REMOVE;
+		else
+			return FILE_ACTION_NONE;
+	}
+
+	/*
+	 * Also remove all temporary files, .../pgsql_tmp/... and .../pgsql_tmp.*
+	 * in the target.
+	 */
+	if (strstr(path, "/" PG_TEMP_FILE_PREFIX) != NULL ||
+		strstr(path, "/" PG_TEMP_FILES_DIR "/") != NULL)
+	{
+		if (entry->target_exists)
+			return FILE_ACTION_REMOVE;
+		else
+			return FILE_ACTION_NONE;
+	}
+
+	/*
+	 * Handle cases where the file is missing from one of the systems.
+	 */
+	if (!entry->target_exists && entry->source_exists)
+	{
+		/*
+		 * File exists in source, but not in target. Copy it in toto. (If it's
+		 * a relation data file, WAL replay after rewinding should re-create
+		 * it anyway. But there's no harm in copying it now.)
+		 */
+		switch (entry->source_type)
+		{
+			case FILE_TYPE_DIRECTORY:
+			case FILE_TYPE_SYMLINK:
+				return FILE_ACTION_CREATE;
+			case FILE_TYPE_REGULAR:
+				return FILE_ACTION_COPY;
+			case FILE_TYPE_UNDEFINED:
+				pg_fatal("unknown file type for \"%s\"", entry->path);
+				break;
+		}
+	}
+	else if (entry->target_exists && !entry->source_exists)
+	{
+		/* File exists in target, but not source. Remove it. */
+		return FILE_ACTION_REMOVE;
+	}
+	else if (!entry->target_exists && !entry->source_exists)
+	{
+		/*
+		 * Doesn't exist in either server. Why does it have an entry in the
+		 * first place??
+		 */
+		return FILE_ACTION_NONE;
+	}
+
+	/*
+	 * Otherwise, the file exists on both systems
+	 */
+	Assert(entry->target_exists && entry->source_exists);
+
+	if (entry->source_type != entry->target_type)
+	{
+		/* But it's a different kind of object. Strange.. */
+		pg_fatal("file \"%s\" is of different type in source and target", entry->path);
+	}
+
+	/*
+	 * PG_VERSION files should be identical on both systems, but avoid
+	 * overwriting them for paranoia.
+	 */
+	if (pg_str_endswith(entry->path, "PG_VERSION"))
+		return FILE_ACTION_NONE;
+
+	switch (entry->source_type)
+	{
+		case FILE_TYPE_DIRECTORY:
+			return FILE_ACTION_NONE;
+
+		case FILE_TYPE_SYMLINK:
+			/* FIXME: Check if it points to the same target? */
+			return FILE_ACTION_NONE;
+
+		case FILE_TYPE_REGULAR:
+			if (!entry->isrelfile)
+			{
+				/*
+				 * It's a non-data file that we have no special processing
+				 * for. Copy it in toto.
+				 */
+				return FILE_ACTION_COPY;
+			}
+			else
+			{
+				/*
+				 * It's a data file that exists in both systems.
+				 *
+				 * If it's larger in target, we can truncate it. There will
+				 * also be a WAL record of the truncation in the source
+				 * system, so WAL replay would eventually truncate the target
+				 * too, but we might as well do it now.
+				 *
+				 * If it's smaller in the target, it means that it has been
+				 * truncated in the target, or enlarged in the source, or
+				 * both. If it was truncated in the target, we need to copy
+				 * the missing tail from the source system. If it was enlarged
+				 * in the source system, there will be WAL records in the
+				 * source system for the new blocks, so we wouldn't need to
+				 * copy them here. But we don't know which scenario we're
+				 * dealing with, and there's no harm in copying the missing
+				 * blocks now, so do it now.
+				 *
+				 * If it's the same size, do nothing here. Any blocks modified
+				 * in the target will be copied based on parsing the target
+				 * system's WAL, and any blocks modified in the source will be
+				 * updated after rewinding, when the source system's WAL is
+				 * replayed.
+				 */
+				if (entry->target_size < entry->source_size)
+					return FILE_ACTION_COPY_TAIL;
+				else if (entry->target_size > entry->source_size)
+					return FILE_ACTION_TRUNCATE;
+				else
+					return FILE_ACTION_NONE;
+			}
+			break;
+
+		case FILE_TYPE_UNDEFINED:
+			pg_fatal("unknown file type for \"%s\"", path);
+			break;
+	}
+
+	/* unreachable */
+	pg_fatal("could not decide what to do with file \"%s\"", path);
+}
+
+/*
+ * Decide what to do with each file.
+ */
+void
+filemap_finalize()
+{
+	int			i;
+
+	filemap_list_to_array(filemap);
+
+	for (i = 0; i < filemap->narray; i++)
+	{
+		file_entry_t *entry = filemap->array[i];
+
+		entry->action = decide_file_action(entry);
+	}
+
+	/* Sort the actions to the order that they should be performed */
+	qsort(filemap->array, filemap->narray, sizeof(file_entry_t *),
+		  final_filemap_cmp);
+}
diff --git a/src/bin/pg_rewind/filemap.h b/src/bin/pg_rewind/filemap.h
index 0cb7425170c..a5e8df57f40 100644
--- a/src/bin/pg_rewind/filemap.h
+++ b/src/bin/pg_rewind/filemap.h
@@ -14,17 +14,21 @@
 
 /*
  * For every file found in the local or remote system, we have a file entry
- * which says what we are going to do with the file. For relation files,
- * there is also a page map, marking pages in the file that were changed
- * locally.
- *
- * The enum values are sorted in the order we want actions to be processed.
+ * that contains information about the file on both systems.  For relation
+ * files, there is also a page map that marks pages in the file that were
+ * changed in the target after the last common checkpoint.  Each entry also
+ * contains an 'action' field, which says what we are going to do with the
+ * file.
  */
+
+/* these enum values are sorted in the order we want actions to be processed */
 typedef enum
 {
+	FILE_ACTION_UNDECIDED = 0,	/* not decided yet */
+
 	FILE_ACTION_CREATE,			/* create local directory or symbolic link */
 	FILE_ACTION_COPY,			/* copy whole file, overwriting if exists */
-	FILE_ACTION_COPY_TAIL,		/* copy tail from 'oldsize' to 'newsize' */
+	FILE_ACTION_COPY_TAIL,		/* copy tail from 'source_size' to 'target_size' */
 	FILE_ACTION_NONE,			/* no action (we might still copy modified
 								 * blocks based on the parsed WAL) */
 	FILE_ACTION_TRUNCATE,		/* truncate local file to 'newsize' bytes */
@@ -33,6 +37,8 @@ typedef enum
 
 typedef enum
 {
+	FILE_TYPE_UNDEFINED = 0,
+
 	FILE_TYPE_REGULAR,
 	FILE_TYPE_DIRECTORY,
 	FILE_TYPE_SYMLINK
@@ -41,19 +47,30 @@ typedef enum
 typedef struct file_entry_t
 {
 	char	   *path;
-	file_type_t type;
+	bool		isrelfile;		/* is it a relation data file? */
 
-	file_action_t action;
+	/*
+	 * Status of the file in the target.
+	 */
+	bool		target_exists;
+	file_type_t target_type;
+	size_t		target_size; /* for a regular file */
+	char	   *target_link_target; /* for a symlink */
 
-	/* for a regular file */
-	size_t		oldsize;
-	size_t		newsize;
-	bool		isrelfile;		/* is it a relation data file? */
+	datapagemap_t target_modified_pages;
 
-	datapagemap_t pagemap;
+	/*
+	 * Status of the file in the source.
+	 */
+	bool		source_exists;
+	file_type_t source_type;
+	size_t		source_size;
+	char	   *source_link_target; /* for a symlink */
 
-	/* for a symlink */
-	char	   *link_target;
+	/*
+	 * What will we do to the file?
+	 */
+	file_action_t action;
 
 	struct file_entry_t *next;
 } file_entry_t;
@@ -70,20 +87,19 @@ typedef struct filemap_t
 
 	/*
 	 * After processing all the remote files, the entries in the linked list
-	 * are moved to this array. After processing local files, too, all the
+	 * are moved to this array.  After processing local files, too, all the
 	 * local entries are added to the array by filemap_finalize, and sorted in
-	 * the final order. After filemap_finalize, all the entries are in the
+	 * the final order.  After filemap_finalize, all the entries are in the
 	 * array, and the linked list is empty.
 	 */
 	file_entry_t **array;
 	int			narray;			/* current length of array */
 
 	/*
-	 * Summary information. total_size is the total size of the source
-	 * cluster, and fetch_size is the number of bytes that needs to be copied.
+	 * Summary information.
 	 */
-	uint64		total_size;
-	uint64		fetch_size;
+	uint64		total_size;		/* total size of the source cluster */
+	uint64		fetch_size;		/* number of bytes that needs to be copied */
 } filemap_t;
 
 extern filemap_t *filemap;
@@ -94,11 +110,12 @@ extern void print_filemap(void);
 
 /* Functions for populating the filemap */
 extern void process_source_file(const char *path, file_type_t type,
-								size_t newsize, const char *link_target);
+								size_t size, const char *link_target);
 extern void process_target_file(const char *path, file_type_t type,
-								size_t newsize, const char *link_target);
-extern void process_block_change(ForkNumber forknum, RelFileNode rnode,
-								 BlockNumber blkno);
+								size_t size, const char *link_target);
+extern void process_target_wal_block_change(ForkNumber forknum,
+											RelFileNode rnode,
+											BlockNumber blkno);
 extern void filemap_finalize(void);
 
 #endif							/* FILEMAP_H */
diff --git a/src/bin/pg_rewind/libpq_fetch.c b/src/bin/pg_rewind/libpq_fetch.c
index bf4dfc23b96..7fc9161b8c8 100644
--- a/src/bin/pg_rewind/libpq_fetch.c
+++ b/src/bin/pg_rewind/libpq_fetch.c
@@ -465,7 +465,7 @@ libpq_executeFileMap(filemap_t *map)
 		entry = map->array[i];
 
 		/* If this is a relation file, copy the modified blocks */
-		execute_pagemap(&entry->pagemap, entry->path);
+		execute_pagemap(&entry->target_modified_pages, entry->path);
 
 		switch (entry->action)
 		{
@@ -476,15 +476,15 @@ libpq_executeFileMap(filemap_t *map)
 			case FILE_ACTION_COPY:
 				/* Truncate the old file out of the way, if any */
 				open_target_file(entry->path, true);
-				fetch_file_range(entry->path, 0, entry->newsize);
+				fetch_file_range(entry->path, 0, entry->source_size);
 				break;
 
 			case FILE_ACTION_TRUNCATE:
-				truncate_target_file(entry->path, entry->newsize);
+				truncate_target_file(entry->path, entry->source_size);
 				break;
 
 			case FILE_ACTION_COPY_TAIL:
-				fetch_file_range(entry->path, entry->oldsize, entry->newsize);
+				fetch_file_range(entry->path, entry->target_size, entry->source_size);
 				break;
 
 			case FILE_ACTION_REMOVE:
@@ -494,6 +494,10 @@ libpq_executeFileMap(filemap_t *map)
 			case FILE_ACTION_CREATE:
 				create_target(entry);
 				break;
+
+			case FILE_ACTION_UNDECIDED:
+				pg_fatal("no action decided for \"%s\"", entry->path);
+				break;
 		}
 	}
 
diff --git a/src/bin/pg_rewind/parsexlog.c b/src/bin/pg_rewind/parsexlog.c
index 2229c86f9af..2baeb74ae93 100644
--- a/src/bin/pg_rewind/parsexlog.c
+++ b/src/bin/pg_rewind/parsexlog.c
@@ -436,6 +436,6 @@ extractPageInfo(XLogReaderState *record)
 		if (forknum != MAIN_FORKNUM)
 			continue;
 
-		process_block_change(forknum, rnode, blkno);
+		process_target_wal_block_change(forknum, rnode, blkno);
 	}
 }
diff --git a/src/bin/pg_rewind/pg_rewind.c b/src/bin/pg_rewind/pg_rewind.c
index c9b9e480c0f..210984d302b 100644
--- a/src/bin/pg_rewind/pg_rewind.c
+++ b/src/bin/pg_rewind/pg_rewind.c
@@ -369,7 +369,7 @@ main(int argc, char **argv)
 				chkpttli);
 
 	/*
-	 * Build the filemap, by comparing the source and target data directories.
+	 * Collect information about all files in the target and source systems.
 	 */
 	filemap_create();
 	if (showprogress)
@@ -390,8 +390,12 @@ main(int argc, char **argv)
 		pg_log_info("reading WAL in target");
 	extractPageMap(datadir_target, chkptrec, lastcommontliIndex,
 				   ControlFile_target.checkPoint, restore_command);
-	filemap_finalize();
 
+	/*
+	 * We have collected all information we need from both systems. Decide
+	 * what to do with each file.
+	 */
+	filemap_finalize();
 	if (showprogress)
 		calculate_totals();
 
-- 
2.20.1


--------------BF34D0120055DC3839060F92
Content-Type: text/x-patch; charset=UTF-8;
 name="v2-0003-pg_rewind-Replace-the-hybrid-list-array-data-stru.patch"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
 filename*0="v2-0003-pg_rewind-Replace-the-hybrid-list-array-data-stru.pa";
 filename*1="tch"



^ permalink  raw  reply  [nested|flat] 41+ messages in thread

* [PATCH v3 2/5] Refactor pg_rewind for more clear decision making.
@ 2020-08-19 12:34 Heikki Linnakangas <[email protected]>
  0 siblings, 0 replies; 41+ messages in thread

From: Heikki Linnakangas @ 2020-08-19 12:34 UTC (permalink / raw)

Deciding what to do with each file is now a separate step after all the
necessary information has been gathered. It is more clear that way.
Previously, the decision-making was divided between process_source_file()
and process_target_file(), and it was a bit hard to piece together what the
overall rules were.
---
 src/bin/pg_rewind/copy_fetch.c  |  14 +-
 src/bin/pg_rewind/file_ops.c    |  16 +-
 src/bin/pg_rewind/filemap.c     | 558 +++++++++++++++++---------------
 src/bin/pg_rewind/filemap.h     |  69 ++--
 src/bin/pg_rewind/libpq_fetch.c |  12 +-
 src/bin/pg_rewind/parsexlog.c   |   2 +-
 src/bin/pg_rewind/pg_rewind.c   |   8 +-
 7 files changed, 373 insertions(+), 306 deletions(-)

diff --git a/src/bin/pg_rewind/copy_fetch.c b/src/bin/pg_rewind/copy_fetch.c
index 1edab5f186..18fad32600 100644
--- a/src/bin/pg_rewind/copy_fetch.c
+++ b/src/bin/pg_rewind/copy_fetch.c
@@ -210,7 +210,7 @@ copy_executeFileMap(filemap_t *map)
 	for (i = 0; i < map->narray; i++)
 	{
 		entry = map->array[i];
-		execute_pagemap(&entry->pagemap, entry->path);
+		execute_pagemap(&entry->target_modified_pages, entry->path);
 
 		switch (entry->action)
 		{
@@ -219,16 +219,16 @@ copy_executeFileMap(filemap_t *map)
 				break;
 
 			case FILE_ACTION_COPY:
-				rewind_copy_file_range(entry->path, 0, entry->newsize, true);
+				rewind_copy_file_range(entry->path, 0, entry->source_size, true);
 				break;
 
 			case FILE_ACTION_TRUNCATE:
-				truncate_target_file(entry->path, entry->newsize);
+				truncate_target_file(entry->path, entry->source_size);
 				break;
 
 			case FILE_ACTION_COPY_TAIL:
-				rewind_copy_file_range(entry->path, entry->oldsize,
-									   entry->newsize, false);
+				rewind_copy_file_range(entry->path, entry->target_size,
+									   entry->source_size, false);
 				break;
 
 			case FILE_ACTION_CREATE:
@@ -238,6 +238,10 @@ copy_executeFileMap(filemap_t *map)
 			case FILE_ACTION_REMOVE:
 				remove_target(entry);
 				break;
+
+			case FILE_ACTION_UNDECIDED:
+				pg_fatal("no action decided for \"%s\"", entry->path);
+				break;
 		}
 	}
 
diff --git a/src/bin/pg_rewind/file_ops.c b/src/bin/pg_rewind/file_ops.c
index 55439db20b..ec37d0b2e0 100644
--- a/src/bin/pg_rewind/file_ops.c
+++ b/src/bin/pg_rewind/file_ops.c
@@ -126,8 +126,9 @@ void
 remove_target(file_entry_t *entry)
 {
 	Assert(entry->action == FILE_ACTION_REMOVE);
+	Assert(entry->target_exists);
 
-	switch (entry->type)
+	switch (entry->target_type)
 	{
 		case FILE_TYPE_DIRECTORY:
 			remove_target_dir(entry->path);
@@ -140,6 +141,10 @@ remove_target(file_entry_t *entry)
 		case FILE_TYPE_SYMLINK:
 			remove_target_symlink(entry->path);
 			break;
+
+		case FILE_TYPE_UNDEFINED:
+			pg_fatal("undefined file type for \"%s\"", entry->path);
+			break;
 	}
 }
 
@@ -147,21 +152,26 @@ void
 create_target(file_entry_t *entry)
 {
 	Assert(entry->action == FILE_ACTION_CREATE);
+	Assert(!entry->target_exists);
 
-	switch (entry->type)
+	switch (entry->source_type)
 	{
 		case FILE_TYPE_DIRECTORY:
 			create_target_dir(entry->path);
 			break;
 
 		case FILE_TYPE_SYMLINK:
-			create_target_symlink(entry->path, entry->link_target);
+			create_target_symlink(entry->path, entry->source_link_target);
 			break;
 
 		case FILE_TYPE_REGULAR:
 			/* can't happen. Regular files are created with open_target_file. */
 			pg_fatal("invalid action (CREATE) for regular file");
 			break;
+
+		case FILE_TYPE_UNDEFINED:
+			pg_fatal("undefined file type for \"%s\"", entry->path);
+			break;
 	}
 }
 
diff --git a/src/bin/pg_rewind/filemap.c b/src/bin/pg_rewind/filemap.c
index 1abc257177..6d19d2be61 100644
--- a/src/bin/pg_rewind/filemap.c
+++ b/src/bin/pg_rewind/filemap.c
@@ -26,6 +26,8 @@ static bool isRelDataFile(const char *path);
 static char *datasegpath(RelFileNode rnode, ForkNumber forknum,
 						 BlockNumber segno);
 static int	path_cmp(const void *a, const void *b);
+
+static file_entry_t *get_filemap_entry(const char *path, bool create);
 static int	final_filemap_cmp(const void *a, const void *b);
 static void filemap_list_to_array(filemap_t *map);
 static bool check_file_excluded(const char *path, bool is_source);
@@ -146,6 +148,65 @@ filemap_create(void)
 	filemap = map;
 }
 
+/* Look up or create entry for 'path' */
+static file_entry_t *
+get_filemap_entry(const char *path, bool create)
+{
+	filemap_t  *map = filemap;
+	file_entry_t *entry;
+	file_entry_t **e;
+	file_entry_t key;
+	file_entry_t *key_ptr;
+
+	if (map->array)
+	{
+		key.path = (char *) path;
+		key_ptr = &key;
+		e = bsearch(&key_ptr, map->array, map->narray, sizeof(file_entry_t *),
+					path_cmp);
+	}
+	else
+		e = NULL;
+
+	if (e)
+		entry = *e;
+	else if (!create)
+		entry = NULL;
+	else
+	{
+		/* Create a new entry for this file */
+		entry = pg_malloc(sizeof(file_entry_t));
+		entry->path = pg_strdup(path);
+		entry->isrelfile = isRelDataFile(path);
+		entry->action = FILE_ACTION_UNDECIDED;
+
+		entry->target_exists = false;
+		entry->target_type = FILE_TYPE_UNDEFINED;
+		entry->target_size = 0;
+		entry->target_link_target = NULL;
+		entry->target_modified_pages.bitmap = NULL;
+		entry->target_modified_pages.bitmapsize = 0;
+
+		entry->source_exists = false;
+		entry->source_type = FILE_TYPE_UNDEFINED;
+		entry->source_size = 0;
+		entry->source_link_target = NULL;
+
+		entry->next = NULL;
+
+		if (map->last)
+		{
+			map->last->next = entry;
+			map->last = entry;
+		}
+		else
+			map->first = map->last = entry;
+		map->nlist++;
+	}
+
+	return entry;
+}
+
 /*
  * Callback for processing source file list.
  *
@@ -154,25 +215,12 @@ filemap_create(void)
  * exists in the target and whether the size matches.
  */
 void
-process_source_file(const char *path, file_type_t type, size_t newsize,
+process_source_file(const char *path, file_type_t type, size_t size,
 					const char *link_target)
 {
-	bool		exists;
-	char		localpath[MAXPGPATH];
-	struct stat statbuf;
-	filemap_t  *map = filemap;
-	file_action_t action = FILE_ACTION_NONE;
-	size_t		oldsize = 0;
 	file_entry_t *entry;
 
-	Assert(map->array == NULL);
-
-	/*
-	 * Skip any files matching the exclusion filters. This has the effect to
-	 * remove all those files on the target.
-	 */
-	if (check_file_excluded(path, true))
-		return;
+	Assert(filemap->array == NULL);
 
 	/*
 	 * Pretend that pg_wal is a directory, even if it's really a symlink. We
@@ -182,16 +230,6 @@ process_source_file(const char *path, file_type_t type, size_t newsize,
 	if (strcmp(path, "pg_wal") == 0 && type == FILE_TYPE_SYMLINK)
 		type = FILE_TYPE_DIRECTORY;
 
-	/*
-	 * Skip temporary files, .../pgsql_tmp/... and .../pgsql_tmp.* in source.
-	 * This has the effect that all temporary files in the destination will be
-	 * removed.
-	 */
-	if (strstr(path, "/" PG_TEMP_FILE_PREFIX) != NULL)
-		return;
-	if (strstr(path, "/" PG_TEMP_FILES_DIR "/") != NULL)
-		return;
-
 	/*
 	 * sanity check: a filename that looks like a data file better be a
 	 * regular file
@@ -199,142 +237,12 @@ process_source_file(const char *path, file_type_t type, size_t newsize,
 	if (type != FILE_TYPE_REGULAR && isRelDataFile(path))
 		pg_fatal("data file \"%s\" in source is not a regular file", path);
 
-	snprintf(localpath, sizeof(localpath), "%s/%s", datadir_target, path);
-
-	/* Does the corresponding file exist in the target data dir? */
-	if (lstat(localpath, &statbuf) < 0)
-	{
-		if (errno != ENOENT)
-			pg_fatal("could not stat file \"%s\": %m",
-					 localpath);
-
-		exists = false;
-	}
-	else
-		exists = true;
-
-	switch (type)
-	{
-		case FILE_TYPE_DIRECTORY:
-			if (exists && !S_ISDIR(statbuf.st_mode) && strcmp(path, "pg_wal") != 0)
-			{
-				/* it's a directory in source, but not in target. Strange.. */
-				pg_fatal("\"%s\" is not a directory", localpath);
-			}
-
-			if (!exists)
-				action = FILE_ACTION_CREATE;
-			else
-				action = FILE_ACTION_NONE;
-			oldsize = 0;
-			break;
-
-		case FILE_TYPE_SYMLINK:
-			if (exists &&
-#ifndef WIN32
-				!S_ISLNK(statbuf.st_mode)
-#else
-				!pgwin32_is_junction(localpath)
-#endif
-				)
-			{
-				/*
-				 * It's a symbolic link in source, but not in target.
-				 * Strange..
-				 */
-				pg_fatal("\"%s\" is not a symbolic link", localpath);
-			}
-
-			if (!exists)
-				action = FILE_ACTION_CREATE;
-			else
-				action = FILE_ACTION_NONE;
-			oldsize = 0;
-			break;
-
-		case FILE_TYPE_REGULAR:
-			if (exists && !S_ISREG(statbuf.st_mode))
-				pg_fatal("\"%s\" is not a regular file", localpath);
-
-			if (!exists || !isRelDataFile(path))
-			{
-				/*
-				 * File exists in source, but not in target. Or it's a
-				 * non-data file that we have no special processing for. Copy
-				 * it in toto.
-				 *
-				 * An exception: PG_VERSIONs should be identical, but avoid
-				 * overwriting it for paranoia.
-				 */
-				if (pg_str_endswith(path, "PG_VERSION"))
-				{
-					action = FILE_ACTION_NONE;
-					oldsize = statbuf.st_size;
-				}
-				else
-				{
-					action = FILE_ACTION_COPY;
-					oldsize = 0;
-				}
-			}
-			else
-			{
-				/*
-				 * It's a data file that exists in both.
-				 *
-				 * If it's larger in target, we can truncate it. There will
-				 * also be a WAL record of the truncation in the source
-				 * system, so WAL replay would eventually truncate the target
-				 * too, but we might as well do it now.
-				 *
-				 * If it's smaller in the target, it means that it has been
-				 * truncated in the target, or enlarged in the source, or
-				 * both. If it was truncated in the target, we need to copy
-				 * the missing tail from the source system. If it was enlarged
-				 * in the source system, there will be WAL records in the
-				 * source system for the new blocks, so we wouldn't need to
-				 * copy them here. But we don't know which scenario we're
-				 * dealing with, and there's no harm in copying the missing
-				 * blocks now, so do it now.
-				 *
-				 * If it's the same size, do nothing here. Any blocks modified
-				 * in the target will be copied based on parsing the target
-				 * system's WAL, and any blocks modified in the source will be
-				 * updated after rewinding, when the source system's WAL is
-				 * replayed.
-				 */
-				oldsize = statbuf.st_size;
-				if (oldsize < newsize)
-					action = FILE_ACTION_COPY_TAIL;
-				else if (oldsize > newsize)
-					action = FILE_ACTION_TRUNCATE;
-				else
-					action = FILE_ACTION_NONE;
-			}
-			break;
-	}
-
-	/* Create a new entry for this file */
-	entry = pg_malloc(sizeof(file_entry_t));
-	entry->path = pg_strdup(path);
-	entry->type = type;
-	entry->action = action;
-	entry->oldsize = oldsize;
-	entry->newsize = newsize;
-	entry->link_target = link_target ? pg_strdup(link_target) : NULL;
-	entry->next = NULL;
-	entry->pagemap.bitmap = NULL;
-	entry->pagemap.bitmapsize = 0;
-	entry->isrelfile = isRelDataFile(path);
-
-	if (map->last)
-	{
-		map->last->next = entry;
-		map->last = entry;
-	}
-	else
-		map->first = map->last = entry;
-	map->nlist++;
+	/* Remember this source file */
+	entry = get_filemap_entry(path, true);
+	entry->source_exists = true;
+	entry->source_type = type;
+	entry->source_size = size;
+	entry->source_link_target = link_target ? pg_strdup(link_target) : NULL;
 }
 
 /*
@@ -345,12 +253,9 @@ process_source_file(const char *path, file_type_t type, size_t newsize,
  * deletion.
  */
 void
-process_target_file(const char *path, file_type_t type, size_t oldsize,
+process_target_file(const char *path, file_type_t type, size_t size,
 					const char *link_target)
 {
-	bool		exists;
-	file_entry_t key;
-	file_entry_t *key_ptr;
 	filemap_t  *map = filemap;
 	file_entry_t *entry;
 
@@ -377,120 +282,76 @@ process_target_file(const char *path, file_type_t type, size_t oldsize,
 	}
 
 	/*
-	 * Like in process_source_file, pretend that xlog is always a  directory.
+	 * Like in process_source_file, pretend that pg_wal is always a directory.
 	 */
 	if (strcmp(path, "pg_wal") == 0 && type == FILE_TYPE_SYMLINK)
 		type = FILE_TYPE_DIRECTORY;
 
-	key.path = (char *) path;
-	key_ptr = &key;
-	exists = (bsearch(&key_ptr, map->array, map->narray, sizeof(file_entry_t *),
-					  path_cmp) != NULL);
-
-	/* Remove any file or folder that doesn't exist in the source system. */
-	if (!exists)
-	{
-		entry = pg_malloc(sizeof(file_entry_t));
-		entry->path = pg_strdup(path);
-		entry->type = type;
-		entry->action = FILE_ACTION_REMOVE;
-		entry->oldsize = oldsize;
-		entry->newsize = 0;
-		entry->link_target = link_target ? pg_strdup(link_target) : NULL;
-		entry->next = NULL;
-		entry->pagemap.bitmap = NULL;
-		entry->pagemap.bitmapsize = 0;
-		entry->isrelfile = isRelDataFile(path);
-
-		if (map->last == NULL)
-			map->first = entry;
-		else
-			map->last->next = entry;
-		map->last = entry;
-		map->nlist++;
-	}
-	else
-	{
-		/*
-		 * We already handled all files that exist in the source system in
-		 * process_source_file().
-		 */
-	}
+	/* Remember this target file */
+	entry = get_filemap_entry(path, true);
+	entry->target_exists = true;
+	entry->target_type = type;
+	entry->target_size = size;
+	entry->target_link_target = link_target ? pg_strdup(link_target) : NULL;
 }
 
 /*
  * This callback gets called while we read the WAL in the target, for every
- * block that have changed in the target system. It makes note of all the
+ * block that have changed in the target system.  It makes note of all the
  * changed blocks in the pagemap of the file.
+ *
+ * NOTE: All the files on both systems must have already been added to the
+ * file map!
  */
 void
-process_block_change(ForkNumber forknum, RelFileNode rnode, BlockNumber blkno)
+process_target_wal_block_change(ForkNumber forknum, RelFileNode rnode,
+								BlockNumber blkno)
 {
 	char	   *path;
-	file_entry_t key;
-	file_entry_t *key_ptr;
 	file_entry_t *entry;
 	BlockNumber blkno_inseg;
 	int			segno;
-	filemap_t  *map = filemap;
-	file_entry_t **e;
 
-	Assert(map->array);
+	Assert(filemap->array);
 
 	segno = blkno / RELSEG_SIZE;
 	blkno_inseg = blkno % RELSEG_SIZE;
 
 	path = datasegpath(rnode, forknum, segno);
-
-	key.path = (char *) path;
-	key_ptr = &key;
-
-	e = bsearch(&key_ptr, map->array, map->narray, sizeof(file_entry_t *),
-				path_cmp);
-	if (e)
-		entry = *e;
-	else
-		entry = NULL;
+	entry = get_filemap_entry(path, false);
 	pfree(path);
 
 	if (entry)
 	{
+		int64		end_offset;
+
 		Assert(entry->isrelfile);
 
-		switch (entry->action)
-		{
-			case FILE_ACTION_NONE:
-			case FILE_ACTION_TRUNCATE:
-				/* skip if we're truncating away the modified block anyway */
-				if ((blkno_inseg + 1) * BLCKSZ <= entry->newsize)
-					datapagemap_add(&entry->pagemap, blkno_inseg);
-				break;
-
-			case FILE_ACTION_COPY_TAIL:
-
-				/*
-				 * skip the modified block if it is part of the "tail" that
-				 * we're copying anyway.
-				 */
-				if ((blkno_inseg + 1) * BLCKSZ <= entry->oldsize)
-					datapagemap_add(&entry->pagemap, blkno_inseg);
-				break;
-
-			case FILE_ACTION_COPY:
-			case FILE_ACTION_REMOVE:
-				break;
-
-			case FILE_ACTION_CREATE:
-				pg_fatal("unexpected page modification for directory or symbolic link \"%s\"", entry->path);
-		}
+		if (entry->target_type != FILE_TYPE_REGULAR)
+			pg_fatal("unexpected page modification for directory or symbolic link \"%s\"",
+					 entry->path);
+
+		/*
+		 * If the block beyond the EOF in the source system, no need to
+		 * remember it now, because we're going to truncate it away from the
+		 * target anyway. Also no need to remember the block if it's beyond
+		 * the current EOF in the target system; we will copy it over with the
+		 * "tail" from the source system, anyway.
+		 */
+		end_offset = (blkno_inseg + 1) * BLCKSZ;
+		if (end_offset <= entry->source_size &&
+			end_offset <= entry->target_size)
+			datapagemap_add(&entry->target_modified_pages, blkno_inseg);
 	}
 	else
 	{
 		/*
 		 * If we don't have any record of this file in the file map, it means
-		 * that it's a relation that doesn't exist in the source system, and
-		 * it was subsequently removed in the target system, too. We can
-		 * safely ignore it.
+		 * that it's a relation that doesn't exist in the source system.  It
+		 * could exist in the target system; we haven't moved the target-only
+		 * entries from the linked list to the array yet!  But in any case, if
+		 * it doesn't exist in the source it will be removed from the target
+		 * too, and we can safely ignore it.
 		 */
 	}
 }
@@ -581,16 +442,6 @@ filemap_list_to_array(filemap_t *map)
 	map->first = map->last = NULL;
 }
 
-void
-filemap_finalize(void)
-{
-	filemap_t  *map = filemap;
-
-	filemap_list_to_array(map);
-	qsort(map->array, map->narray, sizeof(file_entry_t *),
-		  final_filemap_cmp);
-}
-
 static const char *
 action_to_str(file_action_t action)
 {
@@ -631,26 +482,26 @@ calculate_totals(void)
 	{
 		entry = map->array[i];
 
-		if (entry->type != FILE_TYPE_REGULAR)
+		if (entry->source_type != FILE_TYPE_REGULAR)
 			continue;
 
-		map->total_size += entry->newsize;
+		map->total_size += entry->source_size;
 
 		if (entry->action == FILE_ACTION_COPY)
 		{
-			map->fetch_size += entry->newsize;
+			map->fetch_size += entry->source_size;
 			continue;
 		}
 
 		if (entry->action == FILE_ACTION_COPY_TAIL)
-			map->fetch_size += (entry->newsize - entry->oldsize);
+			map->fetch_size += (entry->source_size - entry->target_size);
 
-		if (entry->pagemap.bitmapsize > 0)
+		if (entry->target_modified_pages.bitmapsize > 0)
 		{
 			datapagemap_iterator_t *iter;
 			BlockNumber blk;
 
-			iter = datapagemap_iterate(&entry->pagemap);
+			iter = datapagemap_iterate(&entry->target_modified_pages);
 			while (datapagemap_next(iter, &blk))
 				map->fetch_size += BLCKSZ;
 
@@ -670,13 +521,13 @@ print_filemap(void)
 	{
 		entry = map->array[i];
 		if (entry->action != FILE_ACTION_NONE ||
-			entry->pagemap.bitmapsize > 0)
+			entry->target_modified_pages.bitmapsize > 0)
 		{
 			pg_log_debug("%s (%s)", entry->path,
 						 action_to_str(entry->action));
 
-			if (entry->pagemap.bitmapsize > 0)
-				datapagemap_print(&entry->pagemap);
+			if (entry->target_modified_pages.bitmapsize > 0)
+				datapagemap_print(&entry->target_modified_pages);
 		}
 	}
 	fflush(stdout);
@@ -825,3 +676,180 @@ final_filemap_cmp(const void *a, const void *b)
 	else
 		return strcmp(fa->path, fb->path);
 }
+
+/*
+ * Decide what action to perform to a file.
+ */
+static file_action_t
+decide_file_action(file_entry_t *entry)
+{
+	const char *path = entry->path;
+
+	/*
+	 * Don't touch the control file. It is handled specially, after copying
+	 * all the other files.
+	 */
+	if (strcmp(path, "global/pg_control") == 0)
+		return FILE_ACTION_NONE;
+
+	/*
+	 * Remove all files matching the exclusion filters in the target.
+	 */
+	if (check_file_excluded(path, true))
+	{
+		if (entry->target_exists)
+			return FILE_ACTION_REMOVE;
+		else
+			return FILE_ACTION_NONE;
+	}
+
+	/*
+	 * Also remove all temporary files, .../pgsql_tmp/... and .../pgsql_tmp.*
+	 * in the target.
+	 */
+	if (strstr(path, "/" PG_TEMP_FILE_PREFIX) != NULL ||
+		strstr(path, "/" PG_TEMP_FILES_DIR "/") != NULL)
+	{
+		if (entry->target_exists)
+			return FILE_ACTION_REMOVE;
+		else
+			return FILE_ACTION_NONE;
+	}
+
+	/*
+	 * Handle cases where the file is missing from one of the systems.
+	 */
+	if (!entry->target_exists && entry->source_exists)
+	{
+		/*
+		 * File exists in source, but not in target. Copy it in toto. (If it's
+		 * a relation data file, WAL replay after rewinding should re-create
+		 * it anyway. But there's no harm in copying it now.)
+		 */
+		switch (entry->source_type)
+		{
+			case FILE_TYPE_DIRECTORY:
+			case FILE_TYPE_SYMLINK:
+				return FILE_ACTION_CREATE;
+			case FILE_TYPE_REGULAR:
+				return FILE_ACTION_COPY;
+			case FILE_TYPE_UNDEFINED:
+				pg_fatal("unknown file type for \"%s\"", entry->path);
+				break;
+		}
+	}
+	else if (entry->target_exists && !entry->source_exists)
+	{
+		/* File exists in target, but not source. Remove it. */
+		return FILE_ACTION_REMOVE;
+	}
+	else if (!entry->target_exists && !entry->source_exists)
+	{
+		/*
+		 * Doesn't exist in either server. Why does it have an entry in the
+		 * first place??
+		 */
+		return FILE_ACTION_NONE;
+	}
+
+	/*
+	 * Otherwise, the file exists on both systems
+	 */
+	Assert(entry->target_exists && entry->source_exists);
+
+	if (entry->source_type != entry->target_type)
+	{
+		/* But it's a different kind of object. Strange.. */
+		pg_fatal("file \"%s\" is of different type in source and target", entry->path);
+	}
+
+	/*
+	 * PG_VERSION files should be identical on both systems, but avoid
+	 * overwriting them for paranoia.
+	 */
+	if (pg_str_endswith(entry->path, "PG_VERSION"))
+		return FILE_ACTION_NONE;
+
+	switch (entry->source_type)
+	{
+		case FILE_TYPE_DIRECTORY:
+			return FILE_ACTION_NONE;
+
+		case FILE_TYPE_SYMLINK:
+			/* FIXME: Check if it points to the same target? */
+			return FILE_ACTION_NONE;
+
+		case FILE_TYPE_REGULAR:
+			if (!entry->isrelfile)
+			{
+				/*
+				 * It's a non-data file that we have no special processing
+				 * for. Copy it in toto.
+				 */
+				return FILE_ACTION_COPY;
+			}
+			else
+			{
+				/*
+				 * It's a data file that exists in both systems.
+				 *
+				 * If it's larger in target, we can truncate it. There will
+				 * also be a WAL record of the truncation in the source
+				 * system, so WAL replay would eventually truncate the target
+				 * too, but we might as well do it now.
+				 *
+				 * If it's smaller in the target, it means that it has been
+				 * truncated in the target, or enlarged in the source, or
+				 * both. If it was truncated in the target, we need to copy
+				 * the missing tail from the source system. If it was enlarged
+				 * in the source system, there will be WAL records in the
+				 * source system for the new blocks, so we wouldn't need to
+				 * copy them here. But we don't know which scenario we're
+				 * dealing with, and there's no harm in copying the missing
+				 * blocks now, so do it now.
+				 *
+				 * If it's the same size, do nothing here. Any blocks modified
+				 * in the target will be copied based on parsing the target
+				 * system's WAL, and any blocks modified in the source will be
+				 * updated after rewinding, when the source system's WAL is
+				 * replayed.
+				 */
+				if (entry->target_size < entry->source_size)
+					return FILE_ACTION_COPY_TAIL;
+				else if (entry->target_size > entry->source_size)
+					return FILE_ACTION_TRUNCATE;
+				else
+					return FILE_ACTION_NONE;
+			}
+			break;
+
+		case FILE_TYPE_UNDEFINED:
+			pg_fatal("unknown file type for \"%s\"", path);
+			break;
+	}
+
+	/* unreachable */
+	pg_fatal("could not decide what to do with file \"%s\"", path);
+}
+
+/*
+ * Decide what to do with each file.
+ */
+void
+filemap_finalize()
+{
+	int			i;
+
+	filemap_list_to_array(filemap);
+
+	for (i = 0; i < filemap->narray; i++)
+	{
+		file_entry_t *entry = filemap->array[i];
+
+		entry->action = decide_file_action(entry);
+	}
+
+	/* Sort the actions to the order that they should be performed */
+	qsort(filemap->array, filemap->narray, sizeof(file_entry_t *),
+		  final_filemap_cmp);
+}
diff --git a/src/bin/pg_rewind/filemap.h b/src/bin/pg_rewind/filemap.h
index 0cb7425170..a5e8df57f4 100644
--- a/src/bin/pg_rewind/filemap.h
+++ b/src/bin/pg_rewind/filemap.h
@@ -14,17 +14,21 @@
 
 /*
  * For every file found in the local or remote system, we have a file entry
- * which says what we are going to do with the file. For relation files,
- * there is also a page map, marking pages in the file that were changed
- * locally.
- *
- * The enum values are sorted in the order we want actions to be processed.
+ * that contains information about the file on both systems.  For relation
+ * files, there is also a page map that marks pages in the file that were
+ * changed in the target after the last common checkpoint.  Each entry also
+ * contains an 'action' field, which says what we are going to do with the
+ * file.
  */
+
+/* these enum values are sorted in the order we want actions to be processed */
 typedef enum
 {
+	FILE_ACTION_UNDECIDED = 0,	/* not decided yet */
+
 	FILE_ACTION_CREATE,			/* create local directory or symbolic link */
 	FILE_ACTION_COPY,			/* copy whole file, overwriting if exists */
-	FILE_ACTION_COPY_TAIL,		/* copy tail from 'oldsize' to 'newsize' */
+	FILE_ACTION_COPY_TAIL,		/* copy tail from 'source_size' to 'target_size' */
 	FILE_ACTION_NONE,			/* no action (we might still copy modified
 								 * blocks based on the parsed WAL) */
 	FILE_ACTION_TRUNCATE,		/* truncate local file to 'newsize' bytes */
@@ -33,6 +37,8 @@ typedef enum
 
 typedef enum
 {
+	FILE_TYPE_UNDEFINED = 0,
+
 	FILE_TYPE_REGULAR,
 	FILE_TYPE_DIRECTORY,
 	FILE_TYPE_SYMLINK
@@ -41,19 +47,30 @@ typedef enum
 typedef struct file_entry_t
 {
 	char	   *path;
-	file_type_t type;
-
-	file_action_t action;
-
-	/* for a regular file */
-	size_t		oldsize;
-	size_t		newsize;
 	bool		isrelfile;		/* is it a relation data file? */
 
-	datapagemap_t pagemap;
+	/*
+	 * Status of the file in the target.
+	 */
+	bool		target_exists;
+	file_type_t target_type;
+	size_t		target_size; /* for a regular file */
+	char	   *target_link_target; /* for a symlink */
 
-	/* for a symlink */
-	char	   *link_target;
+	datapagemap_t target_modified_pages;
+
+	/*
+	 * Status of the file in the source.
+	 */
+	bool		source_exists;
+	file_type_t source_type;
+	size_t		source_size;
+	char	   *source_link_target; /* for a symlink */
+
+	/*
+	 * What will we do to the file?
+	 */
+	file_action_t action;
 
 	struct file_entry_t *next;
 } file_entry_t;
@@ -70,20 +87,19 @@ typedef struct filemap_t
 
 	/*
 	 * After processing all the remote files, the entries in the linked list
-	 * are moved to this array. After processing local files, too, all the
+	 * are moved to this array.  After processing local files, too, all the
 	 * local entries are added to the array by filemap_finalize, and sorted in
-	 * the final order. After filemap_finalize, all the entries are in the
+	 * the final order.  After filemap_finalize, all the entries are in the
 	 * array, and the linked list is empty.
 	 */
 	file_entry_t **array;
 	int			narray;			/* current length of array */
 
 	/*
-	 * Summary information. total_size is the total size of the source
-	 * cluster, and fetch_size is the number of bytes that needs to be copied.
+	 * Summary information.
 	 */
-	uint64		total_size;
-	uint64		fetch_size;
+	uint64		total_size;		/* total size of the source cluster */
+	uint64		fetch_size;		/* number of bytes that needs to be copied */
 } filemap_t;
 
 extern filemap_t *filemap;
@@ -94,11 +110,12 @@ extern void print_filemap(void);
 
 /* Functions for populating the filemap */
 extern void process_source_file(const char *path, file_type_t type,
-								size_t newsize, const char *link_target);
+								size_t size, const char *link_target);
 extern void process_target_file(const char *path, file_type_t type,
-								size_t newsize, const char *link_target);
-extern void process_block_change(ForkNumber forknum, RelFileNode rnode,
-								 BlockNumber blkno);
+								size_t size, const char *link_target);
+extern void process_target_wal_block_change(ForkNumber forknum,
+											RelFileNode rnode,
+											BlockNumber blkno);
 extern void filemap_finalize(void);
 
 #endif							/* FILEMAP_H */
diff --git a/src/bin/pg_rewind/libpq_fetch.c b/src/bin/pg_rewind/libpq_fetch.c
index bf4dfc23b9..7fc9161b8c 100644
--- a/src/bin/pg_rewind/libpq_fetch.c
+++ b/src/bin/pg_rewind/libpq_fetch.c
@@ -465,7 +465,7 @@ libpq_executeFileMap(filemap_t *map)
 		entry = map->array[i];
 
 		/* If this is a relation file, copy the modified blocks */
-		execute_pagemap(&entry->pagemap, entry->path);
+		execute_pagemap(&entry->target_modified_pages, entry->path);
 
 		switch (entry->action)
 		{
@@ -476,15 +476,15 @@ libpq_executeFileMap(filemap_t *map)
 			case FILE_ACTION_COPY:
 				/* Truncate the old file out of the way, if any */
 				open_target_file(entry->path, true);
-				fetch_file_range(entry->path, 0, entry->newsize);
+				fetch_file_range(entry->path, 0, entry->source_size);
 				break;
 
 			case FILE_ACTION_TRUNCATE:
-				truncate_target_file(entry->path, entry->newsize);
+				truncate_target_file(entry->path, entry->source_size);
 				break;
 
 			case FILE_ACTION_COPY_TAIL:
-				fetch_file_range(entry->path, entry->oldsize, entry->newsize);
+				fetch_file_range(entry->path, entry->target_size, entry->source_size);
 				break;
 
 			case FILE_ACTION_REMOVE:
@@ -494,6 +494,10 @@ libpq_executeFileMap(filemap_t *map)
 			case FILE_ACTION_CREATE:
 				create_target(entry);
 				break;
+
+			case FILE_ACTION_UNDECIDED:
+				pg_fatal("no action decided for \"%s\"", entry->path);
+				break;
 		}
 	}
 
diff --git a/src/bin/pg_rewind/parsexlog.c b/src/bin/pg_rewind/parsexlog.c
index 2229c86f9a..2baeb74ae9 100644
--- a/src/bin/pg_rewind/parsexlog.c
+++ b/src/bin/pg_rewind/parsexlog.c
@@ -436,6 +436,6 @@ extractPageInfo(XLogReaderState *record)
 		if (forknum != MAIN_FORKNUM)
 			continue;
 
-		process_block_change(forknum, rnode, blkno);
+		process_target_wal_block_change(forknum, rnode, blkno);
 	}
 }
diff --git a/src/bin/pg_rewind/pg_rewind.c b/src/bin/pg_rewind/pg_rewind.c
index 5a7ab764db..e0ed1759cb 100644
--- a/src/bin/pg_rewind/pg_rewind.c
+++ b/src/bin/pg_rewind/pg_rewind.c
@@ -369,7 +369,7 @@ main(int argc, char **argv)
 				chkpttli);
 
 	/*
-	 * Build the filemap, by comparing the source and target data directories.
+	 * Collect information about all files in the target and source systems.
 	 */
 	filemap_create();
 	if (showprogress)
@@ -390,8 +390,12 @@ main(int argc, char **argv)
 		pg_log_info("reading WAL in target");
 	extractPageMap(datadir_target, chkptrec, lastcommontliIndex,
 				   ControlFile_target.checkPoint, restore_command);
+
+	/*
+	 * We have collected all information we need from both systems. Decide
+	 * what to do with each file.
+	 */
 	filemap_finalize();
-
 	if (showprogress)
 		calculate_totals();
 
-- 
2.18.4


----Next_Part(Fri_Sep_18_16_41_50_2020_369)--
Content-Type: Text/X-Patch; charset=us-ascii
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
 filename="v2_5-0003-pg_rewind-Replace-the-hybrid-list-array-data-stru.patch"



^ permalink  raw  reply  [nested|flat] 41+ messages in thread

* [PATCH v2 2/5] Refactor pg_rewind for more clear decision making.
@ 2020-08-19 12:34 Heikki Linnakangas <[email protected]>
  0 siblings, 0 replies; 41+ messages in thread

From: Heikki Linnakangas @ 2020-08-19 12:34 UTC (permalink / raw)

Deciding what to do with each file is now a separate step after all the
necessary information has been gathered. It is more clear that way.
Previously, the decision-making was divided between process_source_file()
and process_target_file(), and it was a bit hard to piece together what the
overall rules were.
---
 src/bin/pg_rewind/copy_fetch.c  |  14 +-
 src/bin/pg_rewind/file_ops.c    |  16 +-
 src/bin/pg_rewind/filemap.c     | 569 ++++++++++++++++----------------
 src/bin/pg_rewind/filemap.h     |  67 ++--
 src/bin/pg_rewind/libpq_fetch.c |  12 +-
 src/bin/pg_rewind/parsexlog.c   |   2 +-
 src/bin/pg_rewind/pg_rewind.c   |   8 +-
 7 files changed, 371 insertions(+), 317 deletions(-)

diff --git a/src/bin/pg_rewind/copy_fetch.c b/src/bin/pg_rewind/copy_fetch.c
index 1edab5f1867..18fad32600e 100644
--- a/src/bin/pg_rewind/copy_fetch.c
+++ b/src/bin/pg_rewind/copy_fetch.c
@@ -210,7 +210,7 @@ copy_executeFileMap(filemap_t *map)
 	for (i = 0; i < map->narray; i++)
 	{
 		entry = map->array[i];
-		execute_pagemap(&entry->pagemap, entry->path);
+		execute_pagemap(&entry->target_modified_pages, entry->path);
 
 		switch (entry->action)
 		{
@@ -219,16 +219,16 @@ copy_executeFileMap(filemap_t *map)
 				break;
 
 			case FILE_ACTION_COPY:
-				rewind_copy_file_range(entry->path, 0, entry->newsize, true);
+				rewind_copy_file_range(entry->path, 0, entry->source_size, true);
 				break;
 
 			case FILE_ACTION_TRUNCATE:
-				truncate_target_file(entry->path, entry->newsize);
+				truncate_target_file(entry->path, entry->source_size);
 				break;
 
 			case FILE_ACTION_COPY_TAIL:
-				rewind_copy_file_range(entry->path, entry->oldsize,
-									   entry->newsize, false);
+				rewind_copy_file_range(entry->path, entry->target_size,
+									   entry->source_size, false);
 				break;
 
 			case FILE_ACTION_CREATE:
@@ -238,6 +238,10 @@ copy_executeFileMap(filemap_t *map)
 			case FILE_ACTION_REMOVE:
 				remove_target(entry);
 				break;
+
+			case FILE_ACTION_UNDECIDED:
+				pg_fatal("no action decided for \"%s\"", entry->path);
+				break;
 		}
 	}
 
diff --git a/src/bin/pg_rewind/file_ops.c b/src/bin/pg_rewind/file_ops.c
index 55439db20ba..ec37d0b2e0d 100644
--- a/src/bin/pg_rewind/file_ops.c
+++ b/src/bin/pg_rewind/file_ops.c
@@ -126,8 +126,9 @@ void
 remove_target(file_entry_t *entry)
 {
 	Assert(entry->action == FILE_ACTION_REMOVE);
+	Assert(entry->target_exists);
 
-	switch (entry->type)
+	switch (entry->target_type)
 	{
 		case FILE_TYPE_DIRECTORY:
 			remove_target_dir(entry->path);
@@ -140,6 +141,10 @@ remove_target(file_entry_t *entry)
 		case FILE_TYPE_SYMLINK:
 			remove_target_symlink(entry->path);
 			break;
+
+		case FILE_TYPE_UNDEFINED:
+			pg_fatal("undefined file type for \"%s\"", entry->path);
+			break;
 	}
 }
 
@@ -147,21 +152,26 @@ void
 create_target(file_entry_t *entry)
 {
 	Assert(entry->action == FILE_ACTION_CREATE);
+	Assert(!entry->target_exists);
 
-	switch (entry->type)
+	switch (entry->source_type)
 	{
 		case FILE_TYPE_DIRECTORY:
 			create_target_dir(entry->path);
 			break;
 
 		case FILE_TYPE_SYMLINK:
-			create_target_symlink(entry->path, entry->link_target);
+			create_target_symlink(entry->path, entry->source_link_target);
 			break;
 
 		case FILE_TYPE_REGULAR:
 			/* can't happen. Regular files are created with open_target_file. */
 			pg_fatal("invalid action (CREATE) for regular file");
 			break;
+
+		case FILE_TYPE_UNDEFINED:
+			pg_fatal("undefined file type for \"%s\"", entry->path);
+			break;
 	}
 }
 
diff --git a/src/bin/pg_rewind/filemap.c b/src/bin/pg_rewind/filemap.c
index 1879257b66a..7971daeda5e 100644
--- a/src/bin/pg_rewind/filemap.c
+++ b/src/bin/pg_rewind/filemap.c
@@ -26,6 +26,8 @@ static bool isRelDataFile(const char *path);
 static char *datasegpath(RelFileNode rnode, ForkNumber forknum,
 						 BlockNumber segno);
 static int	path_cmp(const void *a, const void *b);
+
+static file_entry_t *get_filemap_entry(const char *path, bool create);
 static int	final_filemap_cmp(const void *a, const void *b);
 static void filemap_list_to_array(filemap_t *map);
 static bool check_file_excluded(const char *path, bool is_source);
@@ -146,6 +148,65 @@ filemap_create(void)
 	filemap = map;
 }
 
+/* Look up or create entry for 'path' */
+static file_entry_t *
+get_filemap_entry(const char *path, bool create)
+{
+	filemap_t  *map = filemap;
+	file_entry_t *entry;
+	file_entry_t **e;
+	file_entry_t key;
+	file_entry_t *key_ptr;
+
+	if (map->array)
+	{
+		key.path = (char *) path;
+		key_ptr = &key;
+		e = bsearch(&key_ptr, map->array, map->narray, sizeof(file_entry_t *),
+					path_cmp);
+	}
+	else
+		e = NULL;
+
+	if (e)
+		entry = *e;
+	else if (!create)
+		entry = NULL;
+	else
+	{
+		/* Create a new entry for this file */
+		entry = pg_malloc(sizeof(file_entry_t));
+		entry->path = pg_strdup(path);
+		entry->isrelfile = isRelDataFile(path);
+		entry->action = FILE_ACTION_UNDECIDED;
+
+		entry->target_exists = false;
+		entry->target_type = FILE_TYPE_UNDEFINED;
+		entry->target_size = 0;
+		entry->target_link_target = NULL;
+		entry->target_modified_pages.bitmap = NULL;
+		entry->target_modified_pages.bitmapsize = 0;
+
+		entry->source_exists = false;
+		entry->source_type = FILE_TYPE_UNDEFINED;
+		entry->source_size = 0;
+		entry->source_link_target = NULL;
+
+		entry->next = NULL;
+
+		if (map->last)
+		{
+			map->last->next = entry;
+			map->last = entry;
+		}
+		else
+			map->first = map->last = entry;
+		map->nlist++;
+	}
+
+	return entry;
+}
+
 /*
  * Callback for processing source file list.
  *
@@ -154,25 +215,12 @@ filemap_create(void)
  * exists in the target and whether the size matches.
  */
 void
-process_source_file(const char *path, file_type_t type, size_t newsize,
+process_source_file(const char *path, file_type_t type, size_t size,
 					const char *link_target)
 {
-	bool		exists;
-	char		localpath[MAXPGPATH];
-	struct stat statbuf;
-	filemap_t  *map = filemap;
-	file_action_t action = FILE_ACTION_NONE;
-	size_t		oldsize = 0;
 	file_entry_t *entry;
 
-	Assert(map->array == NULL);
-
-	/*
-	 * Skip any files matching the exclusion filters. This has the effect to
-	 * remove all those files on the target.
-	 */
-	if (check_file_excluded(path, true))
-		return;
+	Assert(filemap->array == NULL);
 
 	/*
 	 * Pretend that pg_wal is a directory, even if it's really a symlink. We
@@ -182,16 +230,6 @@ process_source_file(const char *path, file_type_t type, size_t newsize,
 	if (strcmp(path, "pg_wal") == 0 && type == FILE_TYPE_SYMLINK)
 		type = FILE_TYPE_DIRECTORY;
 
-	/*
-	 * Skip temporary files, .../pgsql_tmp/... and .../pgsql_tmp.* in source.
-	 * This has the effect that all temporary files in the destination will be
-	 * removed.
-	 */
-	if (strstr(path, "/" PG_TEMP_FILE_PREFIX) != NULL)
-		return;
-	if (strstr(path, "/" PG_TEMP_FILES_DIR "/") != NULL)
-		return;
-
 	/*
 	 * sanity check: a filename that looks like a data file better be a
 	 * regular file
@@ -199,142 +237,12 @@ process_source_file(const char *path, file_type_t type, size_t newsize,
 	if (type != FILE_TYPE_REGULAR && isRelDataFile(path))
 		pg_fatal("data file \"%s\" in source is not a regular file", path);
 
-	snprintf(localpath, sizeof(localpath), "%s/%s", datadir_target, path);
-
-	/* Does the corresponding file exist in the target data dir? */
-	if (lstat(localpath, &statbuf) < 0)
-	{
-		if (errno != ENOENT)
-			pg_fatal("could not stat file \"%s\": %m",
-					 localpath);
-
-		exists = false;
-	}
-	else
-		exists = true;
-
-	switch (type)
-	{
-		case FILE_TYPE_DIRECTORY:
-			if (exists && !S_ISDIR(statbuf.st_mode) && strcmp(path, "pg_wal") != 0)
-			{
-				/* it's a directory in source, but not in target. Strange.. */
-				pg_fatal("\"%s\" is not a directory", localpath);
-			}
-
-			if (!exists)
-				action = FILE_ACTION_CREATE;
-			else
-				action = FILE_ACTION_NONE;
-			oldsize = 0;
-			break;
-
-		case FILE_TYPE_SYMLINK:
-			if (exists &&
-#ifndef WIN32
-				!S_ISLNK(statbuf.st_mode)
-#else
-				!pgwin32_is_junction(localpath)
-#endif
-				)
-			{
-				/*
-				 * It's a symbolic link in source, but not in target.
-				 * Strange..
-				 */
-				pg_fatal("\"%s\" is not a symbolic link", localpath);
-			}
-
-			if (!exists)
-				action = FILE_ACTION_CREATE;
-			else
-				action = FILE_ACTION_NONE;
-			oldsize = 0;
-			break;
-
-		case FILE_TYPE_REGULAR:
-			if (exists && !S_ISREG(statbuf.st_mode))
-				pg_fatal("\"%s\" is not a regular file", localpath);
-
-			if (!exists || !isRelDataFile(path))
-			{
-				/*
-				 * File exists in source, but not in target. Or it's a
-				 * non-data file that we have no special processing for. Copy
-				 * it in toto.
-				 *
-				 * An exception: PG_VERSIONs should be identical, but avoid
-				 * overwriting it for paranoia.
-				 */
-				if (pg_str_endswith(path, "PG_VERSION"))
-				{
-					action = FILE_ACTION_NONE;
-					oldsize = statbuf.st_size;
-				}
-				else
-				{
-					action = FILE_ACTION_COPY;
-					oldsize = 0;
-				}
-			}
-			else
-			{
-				/*
-				 * It's a data file that exists in both.
-				 *
-				 * If it's larger in target, we can truncate it. There will
-				 * also be a WAL record of the truncation in the source
-				 * system, so WAL replay would eventually truncate the target
-				 * too, but we might as well do it now.
-				 *
-				 * If it's smaller in the target, it means that it has been
-				 * truncated in the target, or enlarged in the source, or
-				 * both. If it was truncated in the target, we need to copy
-				 * the missing tail from the source system. If it was enlarged
-				 * in the source system, there will be WAL records in the
-				 * source system for the new blocks, so we wouldn't need to
-				 * copy them here. But we don't know which scenario we're
-				 * dealing with, and there's no harm in copying the missing
-				 * blocks now, so do it now.
-				 *
-				 * If it's the same size, do nothing here. Any blocks modified
-				 * in the target will be copied based on parsing the target
-				 * system's WAL, and any blocks modified in the source will be
-				 * updated after rewinding, when the source system's WAL is
-				 * replayed.
-				 */
-				oldsize = statbuf.st_size;
-				if (oldsize < newsize)
-					action = FILE_ACTION_COPY_TAIL;
-				else if (oldsize > newsize)
-					action = FILE_ACTION_TRUNCATE;
-				else
-					action = FILE_ACTION_NONE;
-			}
-			break;
-	}
-
-	/* Create a new entry for this file */
-	entry = pg_malloc(sizeof(file_entry_t));
-	entry->path = pg_strdup(path);
-	entry->type = type;
-	entry->action = action;
-	entry->oldsize = oldsize;
-	entry->newsize = newsize;
-	entry->link_target = link_target ? pg_strdup(link_target) : NULL;
-	entry->next = NULL;
-	entry->pagemap.bitmap = NULL;
-	entry->pagemap.bitmapsize = 0;
-	entry->isrelfile = isRelDataFile(path);
-
-	if (map->last)
-	{
-		map->last->next = entry;
-		map->last = entry;
-	}
-	else
-		map->first = map->last = entry;
-	map->nlist++;
+	/* Remember this source file */
+	entry = get_filemap_entry(path, true);
+	entry->source_exists = true;
+	entry->source_type = type;
+	entry->source_size = size;
+	entry->source_link_target = link_target ? pg_strdup(link_target) : NULL;
 }
 
 /*
@@ -345,14 +253,9 @@ process_source_file(const char *path, file_type_t type, size_t newsize,
  * deletion.
  */
 void
-process_target_file(const char *path, file_type_t type, size_t oldsize,
+process_target_file(const char *path, file_type_t type, size_t size,
 					const char *link_target)
 {
-	bool		exists;
-	char		localpath[MAXPGPATH];
-	struct stat statbuf;
-	file_entry_t key;
-	file_entry_t *key_ptr;
 	filemap_t  *map = filemap;
 	file_entry_t *entry;
 
@@ -361,17 +264,6 @@ process_target_file(const char *path, file_type_t type, size_t oldsize,
 	 * from the target data folder all paths which have been filtered out from
 	 * the source data folder when processing the source files.
 	 */
-
-	snprintf(localpath, sizeof(localpath), "%s/%s", datadir_target, path);
-	if (lstat(localpath, &statbuf) < 0)
-	{
-		if (errno != ENOENT)
-			pg_fatal("could not stat file \"%s\": %m",
-					 localpath);
-
-		exists = false;
-	}
-
 	if (map->array == NULL)
 	{
 		/* on first call, initialize lookup array */
@@ -389,120 +281,76 @@ process_target_file(const char *path, file_type_t type, size_t oldsize,
 	}
 
 	/*
-	 * Like in process_source_file, pretend that xlog is always a  directory.
+	 * Like in process_source_file, pretend that pg_wal is always a directory.
 	 */
 	if (strcmp(path, "pg_wal") == 0 && type == FILE_TYPE_SYMLINK)
 		type = FILE_TYPE_DIRECTORY;
 
-	key.path = (char *) path;
-	key_ptr = &key;
-	exists = (bsearch(&key_ptr, map->array, map->narray, sizeof(file_entry_t *),
-					  path_cmp) != NULL);
-
-	/* Remove any file or folder that doesn't exist in the source system. */
-	if (!exists)
-	{
-		entry = pg_malloc(sizeof(file_entry_t));
-		entry->path = pg_strdup(path);
-		entry->type = type;
-		entry->action = FILE_ACTION_REMOVE;
-		entry->oldsize = oldsize;
-		entry->newsize = 0;
-		entry->link_target = link_target ? pg_strdup(link_target) : NULL;
-		entry->next = NULL;
-		entry->pagemap.bitmap = NULL;
-		entry->pagemap.bitmapsize = 0;
-		entry->isrelfile = isRelDataFile(path);
-
-		if (map->last == NULL)
-			map->first = entry;
-		else
-			map->last->next = entry;
-		map->last = entry;
-		map->nlist++;
-	}
-	else
-	{
-		/*
-		 * We already handled all files that exist in the source system in
-		 * process_source_file().
-		 */
-	}
+	/* Remember this target file */
+	entry = get_filemap_entry(path, true);
+	entry->target_exists = true;
+	entry->target_type = type;
+	entry->target_size = size;
+	entry->target_link_target = link_target ? pg_strdup(link_target) : NULL;
 }
 
 /*
  * This callback gets called while we read the WAL in the target, for every
- * block that have changed in the target system. It makes note of all the
+ * block that have changed in the target system.  It makes note of all the
  * changed blocks in the pagemap of the file.
+ *
+ * NOTE: All the files on both systems must have already been added to the
+ * file map!
  */
 void
-process_block_change(ForkNumber forknum, RelFileNode rnode, BlockNumber blkno)
+process_target_wal_block_change(ForkNumber forknum, RelFileNode rnode,
+								BlockNumber blkno)
 {
 	char	   *path;
-	file_entry_t key;
-	file_entry_t *key_ptr;
 	file_entry_t *entry;
 	BlockNumber blkno_inseg;
 	int			segno;
-	filemap_t  *map = filemap;
-	file_entry_t **e;
 
-	Assert(map->array);
+	Assert(filemap->array);
 
 	segno = blkno / RELSEG_SIZE;
 	blkno_inseg = blkno % RELSEG_SIZE;
 
 	path = datasegpath(rnode, forknum, segno);
-
-	key.path = (char *) path;
-	key_ptr = &key;
-
-	e = bsearch(&key_ptr, map->array, map->narray, sizeof(file_entry_t *),
-				path_cmp);
-	if (e)
-		entry = *e;
-	else
-		entry = NULL;
+	entry = get_filemap_entry(path, false);
 	pfree(path);
 
 	if (entry)
 	{
-		Assert(entry->isrelfile);
-
-		switch (entry->action)
-		{
-			case FILE_ACTION_NONE:
-			case FILE_ACTION_TRUNCATE:
-				/* skip if we're truncating away the modified block anyway */
-				if ((blkno_inseg + 1) * BLCKSZ <= entry->newsize)
-					datapagemap_add(&entry->pagemap, blkno_inseg);
-				break;
+		int64		end_offset;
 
-			case FILE_ACTION_COPY_TAIL:
-
-				/*
-				 * skip the modified block if it is part of the "tail" that
-				 * we're copying anyway.
-				 */
-				if ((blkno_inseg + 1) * BLCKSZ <= entry->oldsize)
-					datapagemap_add(&entry->pagemap, blkno_inseg);
-				break;
+		Assert(entry->isrelfile);
 
-			case FILE_ACTION_COPY:
-			case FILE_ACTION_REMOVE:
-				break;
+		if (entry->target_type != FILE_TYPE_REGULAR)
+			pg_fatal("unexpected page modification for directory or symbolic link \"%s\"",
+					 entry->path);
 
-			case FILE_ACTION_CREATE:
-				pg_fatal("unexpected page modification for directory or symbolic link \"%s\"", entry->path);
-		}
+		/*
+		 * If the block beyond the EOF in the source system, no need to
+		 * remember it now, because we're going to truncate it away from the
+		 * target anyway. Also no need to remember the block if it's beyond
+		 * the current EOF in the target system; we will copy it over with the
+		 * "tail" from the source system, anyway.
+		 */
+		end_offset = (blkno_inseg + 1) * BLCKSZ;
+		if (end_offset <= entry->source_size &&
+			end_offset <= entry->target_size)
+			datapagemap_add(&entry->target_modified_pages, blkno_inseg);
 	}
 	else
 	{
 		/*
 		 * If we don't have any record of this file in the file map, it means
-		 * that it's a relation that doesn't exist in the source system, and
-		 * it was subsequently removed in the target system, too. We can
-		 * safely ignore it.
+		 * that it's a relation that doesn't exist in the source system.  It
+		 * could exist in the target system; we haven't moved the target-only
+		 * entries from the linked list to the array yet!  But in any case, if
+		 * it doesn't exist in the source it will be removed from the target
+		 * too, and we can safely ignore it.
 		 */
 	}
 }
@@ -593,16 +441,6 @@ filemap_list_to_array(filemap_t *map)
 	map->first = map->last = NULL;
 }
 
-void
-filemap_finalize(void)
-{
-	filemap_t  *map = filemap;
-
-	filemap_list_to_array(map);
-	qsort(map->array, map->narray, sizeof(file_entry_t *),
-		  final_filemap_cmp);
-}
-
 static const char *
 action_to_str(file_action_t action)
 {
@@ -643,26 +481,26 @@ calculate_totals(void)
 	{
 		entry = map->array[i];
 
-		if (entry->type != FILE_TYPE_REGULAR)
+		if (entry->source_type != FILE_TYPE_REGULAR)
 			continue;
 
-		map->total_size += entry->newsize;
+		map->total_size += entry->source_size;
 
 		if (entry->action == FILE_ACTION_COPY)
 		{
-			map->fetch_size += entry->newsize;
+			map->fetch_size += entry->source_size;
 			continue;
 		}
 
 		if (entry->action == FILE_ACTION_COPY_TAIL)
-			map->fetch_size += (entry->newsize - entry->oldsize);
+			map->fetch_size += (entry->source_size - entry->target_size);
 
-		if (entry->pagemap.bitmapsize > 0)
+		if (entry->target_modified_pages.bitmapsize > 0)
 		{
 			datapagemap_iterator_t *iter;
 			BlockNumber blk;
 
-			iter = datapagemap_iterate(&entry->pagemap);
+			iter = datapagemap_iterate(&entry->target_modified_pages);
 			while (datapagemap_next(iter, &blk))
 				map->fetch_size += BLCKSZ;
 
@@ -682,13 +520,13 @@ print_filemap(void)
 	{
 		entry = map->array[i];
 		if (entry->action != FILE_ACTION_NONE ||
-			entry->pagemap.bitmapsize > 0)
+			entry->target_modified_pages.bitmapsize > 0)
 		{
 			pg_log_debug("%s (%s)", entry->path,
 						 action_to_str(entry->action));
 
-			if (entry->pagemap.bitmapsize > 0)
-				datapagemap_print(&entry->pagemap);
+			if (entry->target_modified_pages.bitmapsize > 0)
+				datapagemap_print(&entry->target_modified_pages);
 		}
 	}
 	fflush(stdout);
@@ -837,3 +675,180 @@ final_filemap_cmp(const void *a, const void *b)
 	else
 		return strcmp(fa->path, fb->path);
 }
+
+/*
+ * Decide what action to perform to a file.
+ */
+static file_action_t
+decide_file_action(file_entry_t *entry)
+{
+	const char *path = entry->path;
+
+	/*
+	 * Don't touch the control file. It is handled specially, after copying
+	 * all the other files.
+	 */
+	if (strcmp(path, "global/pg_control") == 0)
+		return FILE_ACTION_NONE;
+
+	/*
+	 * Remove all files matching the exclusion filters in the target.
+	 */
+	if (check_file_excluded(path, true))
+	{
+		if (entry->target_exists)
+			return FILE_ACTION_REMOVE;
+		else
+			return FILE_ACTION_NONE;
+	}
+
+	/*
+	 * Also remove all temporary files, .../pgsql_tmp/... and .../pgsql_tmp.*
+	 * in the target.
+	 */
+	if (strstr(path, "/" PG_TEMP_FILE_PREFIX) != NULL ||
+		strstr(path, "/" PG_TEMP_FILES_DIR "/") != NULL)
+	{
+		if (entry->target_exists)
+			return FILE_ACTION_REMOVE;
+		else
+			return FILE_ACTION_NONE;
+	}
+
+	/*
+	 * Handle cases where the file is missing from one of the systems.
+	 */
+	if (!entry->target_exists && entry->source_exists)
+	{
+		/*
+		 * File exists in source, but not in target. Copy it in toto. (If it's
+		 * a relation data file, WAL replay after rewinding should re-create
+		 * it anyway. But there's no harm in copying it now.)
+		 */
+		switch (entry->source_type)
+		{
+			case FILE_TYPE_DIRECTORY:
+			case FILE_TYPE_SYMLINK:
+				return FILE_ACTION_CREATE;
+			case FILE_TYPE_REGULAR:
+				return FILE_ACTION_COPY;
+			case FILE_TYPE_UNDEFINED:
+				pg_fatal("unknown file type for \"%s\"", entry->path);
+				break;
+		}
+	}
+	else if (entry->target_exists && !entry->source_exists)
+	{
+		/* File exists in target, but not source. Remove it. */
+		return FILE_ACTION_REMOVE;
+	}
+	else if (!entry->target_exists && !entry->source_exists)
+	{
+		/*
+		 * Doesn't exist in either server. Why does it have an entry in the
+		 * first place??
+		 */
+		return FILE_ACTION_NONE;
+	}
+
+	/*
+	 * Otherwise, the file exists on both systems
+	 */
+	Assert(entry->target_exists && entry->source_exists);
+
+	if (entry->source_type != entry->target_type)
+	{
+		/* But it's a different kind of object. Strange.. */
+		pg_fatal("file \"%s\" is of different type in source and target", entry->path);
+	}
+
+	/*
+	 * PG_VERSION files should be identical on both systems, but avoid
+	 * overwriting them for paranoia.
+	 */
+	if (pg_str_endswith(entry->path, "PG_VERSION"))
+		return FILE_ACTION_NONE;
+
+	switch (entry->source_type)
+	{
+		case FILE_TYPE_DIRECTORY:
+			return FILE_ACTION_NONE;
+
+		case FILE_TYPE_SYMLINK:
+			/* FIXME: Check if it points to the same target? */
+			return FILE_ACTION_NONE;
+
+		case FILE_TYPE_REGULAR:
+			if (!entry->isrelfile)
+			{
+				/*
+				 * It's a non-data file that we have no special processing
+				 * for. Copy it in toto.
+				 */
+				return FILE_ACTION_COPY;
+			}
+			else
+			{
+				/*
+				 * It's a data file that exists in both systems.
+				 *
+				 * If it's larger in target, we can truncate it. There will
+				 * also be a WAL record of the truncation in the source
+				 * system, so WAL replay would eventually truncate the target
+				 * too, but we might as well do it now.
+				 *
+				 * If it's smaller in the target, it means that it has been
+				 * truncated in the target, or enlarged in the source, or
+				 * both. If it was truncated in the target, we need to copy
+				 * the missing tail from the source system. If it was enlarged
+				 * in the source system, there will be WAL records in the
+				 * source system for the new blocks, so we wouldn't need to
+				 * copy them here. But we don't know which scenario we're
+				 * dealing with, and there's no harm in copying the missing
+				 * blocks now, so do it now.
+				 *
+				 * If it's the same size, do nothing here. Any blocks modified
+				 * in the target will be copied based on parsing the target
+				 * system's WAL, and any blocks modified in the source will be
+				 * updated after rewinding, when the source system's WAL is
+				 * replayed.
+				 */
+				if (entry->target_size < entry->source_size)
+					return FILE_ACTION_COPY_TAIL;
+				else if (entry->target_size > entry->source_size)
+					return FILE_ACTION_TRUNCATE;
+				else
+					return FILE_ACTION_NONE;
+			}
+			break;
+
+		case FILE_TYPE_UNDEFINED:
+			pg_fatal("unknown file type for \"%s\"", path);
+			break;
+	}
+
+	/* unreachable */
+	pg_fatal("could not decide what to do with file \"%s\"", path);
+}
+
+/*
+ * Decide what to do with each file.
+ */
+void
+filemap_finalize()
+{
+	int			i;
+
+	filemap_list_to_array(filemap);
+
+	for (i = 0; i < filemap->narray; i++)
+	{
+		file_entry_t *entry = filemap->array[i];
+
+		entry->action = decide_file_action(entry);
+	}
+
+	/* Sort the actions to the order that they should be performed */
+	qsort(filemap->array, filemap->narray, sizeof(file_entry_t *),
+		  final_filemap_cmp);
+}
diff --git a/src/bin/pg_rewind/filemap.h b/src/bin/pg_rewind/filemap.h
index 0cb7425170c..a5e8df57f40 100644
--- a/src/bin/pg_rewind/filemap.h
+++ b/src/bin/pg_rewind/filemap.h
@@ -14,17 +14,21 @@
 
 /*
  * For every file found in the local or remote system, we have a file entry
- * which says what we are going to do with the file. For relation files,
- * there is also a page map, marking pages in the file that were changed
- * locally.
- *
- * The enum values are sorted in the order we want actions to be processed.
+ * that contains information about the file on both systems.  For relation
+ * files, there is also a page map that marks pages in the file that were
+ * changed in the target after the last common checkpoint.  Each entry also
+ * contains an 'action' field, which says what we are going to do with the
+ * file.
  */
+
+/* these enum values are sorted in the order we want actions to be processed */
 typedef enum
 {
+	FILE_ACTION_UNDECIDED = 0,	/* not decided yet */
+
 	FILE_ACTION_CREATE,			/* create local directory or symbolic link */
 	FILE_ACTION_COPY,			/* copy whole file, overwriting if exists */
-	FILE_ACTION_COPY_TAIL,		/* copy tail from 'oldsize' to 'newsize' */
+	FILE_ACTION_COPY_TAIL,		/* copy tail from 'source_size' to 'target_size' */
 	FILE_ACTION_NONE,			/* no action (we might still copy modified
 								 * blocks based on the parsed WAL) */
 	FILE_ACTION_TRUNCATE,		/* truncate local file to 'newsize' bytes */
@@ -33,6 +37,8 @@ typedef enum
 
 typedef enum
 {
+	FILE_TYPE_UNDEFINED = 0,
+
 	FILE_TYPE_REGULAR,
 	FILE_TYPE_DIRECTORY,
 	FILE_TYPE_SYMLINK
@@ -41,19 +47,30 @@ typedef enum
 typedef struct file_entry_t
 {
 	char	   *path;
-	file_type_t type;
+	bool		isrelfile;		/* is it a relation data file? */
 
-	file_action_t action;
+	/*
+	 * Status of the file in the target.
+	 */
+	bool		target_exists;
+	file_type_t target_type;
+	size_t		target_size; /* for a regular file */
+	char	   *target_link_target; /* for a symlink */
 
-	/* for a regular file */
-	size_t		oldsize;
-	size_t		newsize;
-	bool		isrelfile;		/* is it a relation data file? */
+	datapagemap_t target_modified_pages;
 
-	datapagemap_t pagemap;
+	/*
+	 * Status of the file in the source.
+	 */
+	bool		source_exists;
+	file_type_t source_type;
+	size_t		source_size;
+	char	   *source_link_target; /* for a symlink */
 
-	/* for a symlink */
-	char	   *link_target;
+	/*
+	 * What will we do to the file?
+	 */
+	file_action_t action;
 
 	struct file_entry_t *next;
 } file_entry_t;
@@ -70,20 +87,19 @@ typedef struct filemap_t
 
 	/*
 	 * After processing all the remote files, the entries in the linked list
-	 * are moved to this array. After processing local files, too, all the
+	 * are moved to this array.  After processing local files, too, all the
 	 * local entries are added to the array by filemap_finalize, and sorted in
-	 * the final order. After filemap_finalize, all the entries are in the
+	 * the final order.  After filemap_finalize, all the entries are in the
 	 * array, and the linked list is empty.
 	 */
 	file_entry_t **array;
 	int			narray;			/* current length of array */
 
 	/*
-	 * Summary information. total_size is the total size of the source
-	 * cluster, and fetch_size is the number of bytes that needs to be copied.
+	 * Summary information.
 	 */
-	uint64		total_size;
-	uint64		fetch_size;
+	uint64		total_size;		/* total size of the source cluster */
+	uint64		fetch_size;		/* number of bytes that needs to be copied */
 } filemap_t;
 
 extern filemap_t *filemap;
@@ -94,11 +110,12 @@ extern void print_filemap(void);
 
 /* Functions for populating the filemap */
 extern void process_source_file(const char *path, file_type_t type,
-								size_t newsize, const char *link_target);
+								size_t size, const char *link_target);
 extern void process_target_file(const char *path, file_type_t type,
-								size_t newsize, const char *link_target);
-extern void process_block_change(ForkNumber forknum, RelFileNode rnode,
-								 BlockNumber blkno);
+								size_t size, const char *link_target);
+extern void process_target_wal_block_change(ForkNumber forknum,
+											RelFileNode rnode,
+											BlockNumber blkno);
 extern void filemap_finalize(void);
 
 #endif							/* FILEMAP_H */
diff --git a/src/bin/pg_rewind/libpq_fetch.c b/src/bin/pg_rewind/libpq_fetch.c
index bf4dfc23b96..7fc9161b8c8 100644
--- a/src/bin/pg_rewind/libpq_fetch.c
+++ b/src/bin/pg_rewind/libpq_fetch.c
@@ -465,7 +465,7 @@ libpq_executeFileMap(filemap_t *map)
 		entry = map->array[i];
 
 		/* If this is a relation file, copy the modified blocks */
-		execute_pagemap(&entry->pagemap, entry->path);
+		execute_pagemap(&entry->target_modified_pages, entry->path);
 
 		switch (entry->action)
 		{
@@ -476,15 +476,15 @@ libpq_executeFileMap(filemap_t *map)
 			case FILE_ACTION_COPY:
 				/* Truncate the old file out of the way, if any */
 				open_target_file(entry->path, true);
-				fetch_file_range(entry->path, 0, entry->newsize);
+				fetch_file_range(entry->path, 0, entry->source_size);
 				break;
 
 			case FILE_ACTION_TRUNCATE:
-				truncate_target_file(entry->path, entry->newsize);
+				truncate_target_file(entry->path, entry->source_size);
 				break;
 
 			case FILE_ACTION_COPY_TAIL:
-				fetch_file_range(entry->path, entry->oldsize, entry->newsize);
+				fetch_file_range(entry->path, entry->target_size, entry->source_size);
 				break;
 
 			case FILE_ACTION_REMOVE:
@@ -494,6 +494,10 @@ libpq_executeFileMap(filemap_t *map)
 			case FILE_ACTION_CREATE:
 				create_target(entry);
 				break;
+
+			case FILE_ACTION_UNDECIDED:
+				pg_fatal("no action decided for \"%s\"", entry->path);
+				break;
 		}
 	}
 
diff --git a/src/bin/pg_rewind/parsexlog.c b/src/bin/pg_rewind/parsexlog.c
index 2229c86f9af..2baeb74ae93 100644
--- a/src/bin/pg_rewind/parsexlog.c
+++ b/src/bin/pg_rewind/parsexlog.c
@@ -436,6 +436,6 @@ extractPageInfo(XLogReaderState *record)
 		if (forknum != MAIN_FORKNUM)
 			continue;
 
-		process_block_change(forknum, rnode, blkno);
+		process_target_wal_block_change(forknum, rnode, blkno);
 	}
 }
diff --git a/src/bin/pg_rewind/pg_rewind.c b/src/bin/pg_rewind/pg_rewind.c
index c9b9e480c0f..210984d302b 100644
--- a/src/bin/pg_rewind/pg_rewind.c
+++ b/src/bin/pg_rewind/pg_rewind.c
@@ -369,7 +369,7 @@ main(int argc, char **argv)
 				chkpttli);
 
 	/*
-	 * Build the filemap, by comparing the source and target data directories.
+	 * Collect information about all files in the target and source systems.
 	 */
 	filemap_create();
 	if (showprogress)
@@ -390,8 +390,12 @@ main(int argc, char **argv)
 		pg_log_info("reading WAL in target");
 	extractPageMap(datadir_target, chkptrec, lastcommontliIndex,
 				   ControlFile_target.checkPoint, restore_command);
-	filemap_finalize();
 
+	/*
+	 * We have collected all information we need from both systems. Decide
+	 * what to do with each file.
+	 */
+	filemap_finalize();
 	if (showprogress)
 		calculate_totals();
 
-- 
2.20.1


--------------BF34D0120055DC3839060F92
Content-Type: text/x-patch; charset=UTF-8;
 name="v2-0003-pg_rewind-Replace-the-hybrid-list-array-data-stru.patch"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
 filename*0="v2-0003-pg_rewind-Replace-the-hybrid-list-array-data-stru.pa";
 filename*1="tch"



^ permalink  raw  reply  [nested|flat] 41+ messages in thread

* [PATCH 2/5] Refactor pg_rewind for more clear decision making.
@ 2020-08-19 12:34 Heikki Linnakangas <[email protected]>
  0 siblings, 0 replies; 41+ messages in thread

From: Heikki Linnakangas @ 2020-08-19 12:34 UTC (permalink / raw)

Deciding what to do with each file is now a separate step after all the
necessary information has been gathered. It is more clear that way.
Previously, the decision-making was divided between process_source_file()
and process_target_file(), and it was a bit hard to piece together what the
overall rules were.
---
 src/bin/pg_rewind/copy_fetch.c  |  14 +-
 src/bin/pg_rewind/file_ops.c    |  16 +-
 src/bin/pg_rewind/filemap.c     | 514 ++++++++++++++++----------------
 src/bin/pg_rewind/filemap.h     |  67 +++--
 src/bin/pg_rewind/libpq_fetch.c |  12 +-
 src/bin/pg_rewind/parsexlog.c   |   2 +-
 src/bin/pg_rewind/pg_rewind.c   |   8 +-
 7 files changed, 332 insertions(+), 301 deletions(-)

diff --git a/src/bin/pg_rewind/copy_fetch.c b/src/bin/pg_rewind/copy_fetch.c
index 1edab5f1867..18fad32600e 100644
--- a/src/bin/pg_rewind/copy_fetch.c
+++ b/src/bin/pg_rewind/copy_fetch.c
@@ -210,7 +210,7 @@ copy_executeFileMap(filemap_t *map)
 	for (i = 0; i < map->narray; i++)
 	{
 		entry = map->array[i];
-		execute_pagemap(&entry->pagemap, entry->path);
+		execute_pagemap(&entry->target_modified_pages, entry->path);
 
 		switch (entry->action)
 		{
@@ -219,16 +219,16 @@ copy_executeFileMap(filemap_t *map)
 				break;
 
 			case FILE_ACTION_COPY:
-				rewind_copy_file_range(entry->path, 0, entry->newsize, true);
+				rewind_copy_file_range(entry->path, 0, entry->source_size, true);
 				break;
 
 			case FILE_ACTION_TRUNCATE:
-				truncate_target_file(entry->path, entry->newsize);
+				truncate_target_file(entry->path, entry->source_size);
 				break;
 
 			case FILE_ACTION_COPY_TAIL:
-				rewind_copy_file_range(entry->path, entry->oldsize,
-									   entry->newsize, false);
+				rewind_copy_file_range(entry->path, entry->target_size,
+									   entry->source_size, false);
 				break;
 
 			case FILE_ACTION_CREATE:
@@ -238,6 +238,10 @@ copy_executeFileMap(filemap_t *map)
 			case FILE_ACTION_REMOVE:
 				remove_target(entry);
 				break;
+
+			case FILE_ACTION_UNDECIDED:
+				pg_fatal("no action decided for \"%s\"", entry->path);
+				break;
 		}
 	}
 
diff --git a/src/bin/pg_rewind/file_ops.c b/src/bin/pg_rewind/file_ops.c
index 55439db20ba..ec37d0b2e0d 100644
--- a/src/bin/pg_rewind/file_ops.c
+++ b/src/bin/pg_rewind/file_ops.c
@@ -126,8 +126,9 @@ void
 remove_target(file_entry_t *entry)
 {
 	Assert(entry->action == FILE_ACTION_REMOVE);
+	Assert(entry->target_exists);
 
-	switch (entry->type)
+	switch (entry->target_type)
 	{
 		case FILE_TYPE_DIRECTORY:
 			remove_target_dir(entry->path);
@@ -140,6 +141,10 @@ remove_target(file_entry_t *entry)
 		case FILE_TYPE_SYMLINK:
 			remove_target_symlink(entry->path);
 			break;
+
+		case FILE_TYPE_UNDEFINED:
+			pg_fatal("undefined file type for \"%s\"", entry->path);
+			break;
 	}
 }
 
@@ -147,21 +152,26 @@ void
 create_target(file_entry_t *entry)
 {
 	Assert(entry->action == FILE_ACTION_CREATE);
+	Assert(!entry->target_exists);
 
-	switch (entry->type)
+	switch (entry->source_type)
 	{
 		case FILE_TYPE_DIRECTORY:
 			create_target_dir(entry->path);
 			break;
 
 		case FILE_TYPE_SYMLINK:
-			create_target_symlink(entry->path, entry->link_target);
+			create_target_symlink(entry->path, entry->source_link_target);
 			break;
 
 		case FILE_TYPE_REGULAR:
 			/* can't happen. Regular files are created with open_target_file. */
 			pg_fatal("invalid action (CREATE) for regular file");
 			break;
+
+		case FILE_TYPE_UNDEFINED:
+			pg_fatal("undefined file type for \"%s\"", entry->path);
+			break;
 	}
 }
 
diff --git a/src/bin/pg_rewind/filemap.c b/src/bin/pg_rewind/filemap.c
index 1879257b66a..431e8e760e0 100644
--- a/src/bin/pg_rewind/filemap.c
+++ b/src/bin/pg_rewind/filemap.c
@@ -26,6 +26,8 @@ static bool isRelDataFile(const char *path);
 static char *datasegpath(RelFileNode rnode, ForkNumber forknum,
 						 BlockNumber segno);
 static int	path_cmp(const void *a, const void *b);
+
+static file_entry_t *get_filemap_entry(const char *path, bool create);
 static int	final_filemap_cmp(const void *a, const void *b);
 static void filemap_list_to_array(filemap_t *map);
 static bool check_file_excluded(const char *path, bool is_source);
@@ -146,6 +148,65 @@ filemap_create(void)
 	filemap = map;
 }
 
+/* Look up or create entry for 'path' */
+static file_entry_t *
+get_filemap_entry(const char *path, bool create)
+{
+	filemap_t  *map = filemap;
+	file_entry_t *entry;
+	file_entry_t **e;
+	file_entry_t key;
+	file_entry_t *key_ptr;
+
+	if (map->array)
+	{
+		key.path = (char *) path;
+		key_ptr = &key;
+		e = bsearch(&key_ptr, map->array, map->narray, sizeof(file_entry_t *),
+					path_cmp);
+	}
+	else
+		e = NULL;
+
+	if (e)
+		entry = *e;
+	else if (!create)
+		entry = NULL;
+	else
+	{
+		/* Create a new entry for this file */
+		entry = pg_malloc(sizeof(file_entry_t));
+		entry->path = pg_strdup(path);
+		entry->isrelfile = isRelDataFile(path);
+		entry->action = FILE_ACTION_UNDECIDED;
+
+		entry->target_exists = false;
+		entry->target_type = FILE_TYPE_UNDEFINED;
+		entry->target_size = 0;
+		entry->target_link_target = NULL;
+		entry->target_modified_pages.bitmap = NULL;
+		entry->target_modified_pages.bitmapsize = 0;
+
+		entry->source_exists = false;
+		entry->source_type = FILE_TYPE_UNDEFINED;
+		entry->source_size = 0;
+		entry->source_link_target = NULL;
+
+		entry->next = NULL;
+
+		if (map->last)
+		{
+			map->last->next = entry;
+			map->last = entry;
+		}
+		else
+			map->first = map->last = entry;
+		map->nlist++;
+	}
+
+	return entry;
+}
+
 /*
  * Callback for processing source file list.
  *
@@ -154,18 +215,19 @@ filemap_create(void)
  * exists in the target and whether the size matches.
  */
 void
-process_source_file(const char *path, file_type_t type, size_t newsize,
+process_source_file(const char *path, file_type_t type, size_t size,
 					const char *link_target)
 {
-	bool		exists;
-	char		localpath[MAXPGPATH];
-	struct stat statbuf;
-	filemap_t  *map = filemap;
-	file_action_t action = FILE_ACTION_NONE;
-	size_t		oldsize = 0;
 	file_entry_t *entry;
 
-	Assert(map->array == NULL);
+	Assert(filemap->array == NULL);
+
+	/*
+	 * Skip the control file. It is handled specially, after copying all the
+	 * other files.
+	 */
+	if (strcmp(path, "global/pg_control") == 0)
+		return;
 
 	/*
 	 * Skip any files matching the exclusion filters. This has the effect to
@@ -199,142 +261,12 @@ process_source_file(const char *path, file_type_t type, size_t newsize,
 	if (type != FILE_TYPE_REGULAR && isRelDataFile(path))
 		pg_fatal("data file \"%s\" in source is not a regular file", path);
 
-	snprintf(localpath, sizeof(localpath), "%s/%s", datadir_target, path);
-
-	/* Does the corresponding file exist in the target data dir? */
-	if (lstat(localpath, &statbuf) < 0)
-	{
-		if (errno != ENOENT)
-			pg_fatal("could not stat file \"%s\": %m",
-					 localpath);
-
-		exists = false;
-	}
-	else
-		exists = true;
-
-	switch (type)
-	{
-		case FILE_TYPE_DIRECTORY:
-			if (exists && !S_ISDIR(statbuf.st_mode) && strcmp(path, "pg_wal") != 0)
-			{
-				/* it's a directory in source, but not in target. Strange.. */
-				pg_fatal("\"%s\" is not a directory", localpath);
-			}
-
-			if (!exists)
-				action = FILE_ACTION_CREATE;
-			else
-				action = FILE_ACTION_NONE;
-			oldsize = 0;
-			break;
-
-		case FILE_TYPE_SYMLINK:
-			if (exists &&
-#ifndef WIN32
-				!S_ISLNK(statbuf.st_mode)
-#else
-				!pgwin32_is_junction(localpath)
-#endif
-				)
-			{
-				/*
-				 * It's a symbolic link in source, but not in target.
-				 * Strange..
-				 */
-				pg_fatal("\"%s\" is not a symbolic link", localpath);
-			}
-
-			if (!exists)
-				action = FILE_ACTION_CREATE;
-			else
-				action = FILE_ACTION_NONE;
-			oldsize = 0;
-			break;
-
-		case FILE_TYPE_REGULAR:
-			if (exists && !S_ISREG(statbuf.st_mode))
-				pg_fatal("\"%s\" is not a regular file", localpath);
-
-			if (!exists || !isRelDataFile(path))
-			{
-				/*
-				 * File exists in source, but not in target. Or it's a
-				 * non-data file that we have no special processing for. Copy
-				 * it in toto.
-				 *
-				 * An exception: PG_VERSIONs should be identical, but avoid
-				 * overwriting it for paranoia.
-				 */
-				if (pg_str_endswith(path, "PG_VERSION"))
-				{
-					action = FILE_ACTION_NONE;
-					oldsize = statbuf.st_size;
-				}
-				else
-				{
-					action = FILE_ACTION_COPY;
-					oldsize = 0;
-				}
-			}
-			else
-			{
-				/*
-				 * It's a data file that exists in both.
-				 *
-				 * If it's larger in target, we can truncate it. There will
-				 * also be a WAL record of the truncation in the source
-				 * system, so WAL replay would eventually truncate the target
-				 * too, but we might as well do it now.
-				 *
-				 * If it's smaller in the target, it means that it has been
-				 * truncated in the target, or enlarged in the source, or
-				 * both. If it was truncated in the target, we need to copy
-				 * the missing tail from the source system. If it was enlarged
-				 * in the source system, there will be WAL records in the
-				 * source system for the new blocks, so we wouldn't need to
-				 * copy them here. But we don't know which scenario we're
-				 * dealing with, and there's no harm in copying the missing
-				 * blocks now, so do it now.
-				 *
-				 * If it's the same size, do nothing here. Any blocks modified
-				 * in the target will be copied based on parsing the target
-				 * system's WAL, and any blocks modified in the source will be
-				 * updated after rewinding, when the source system's WAL is
-				 * replayed.
-				 */
-				oldsize = statbuf.st_size;
-				if (oldsize < newsize)
-					action = FILE_ACTION_COPY_TAIL;
-				else if (oldsize > newsize)
-					action = FILE_ACTION_TRUNCATE;
-				else
-					action = FILE_ACTION_NONE;
-			}
-			break;
-	}
-
-	/* Create a new entry for this file */
-	entry = pg_malloc(sizeof(file_entry_t));
-	entry->path = pg_strdup(path);
-	entry->type = type;
-	entry->action = action;
-	entry->oldsize = oldsize;
-	entry->newsize = newsize;
-	entry->link_target = link_target ? pg_strdup(link_target) : NULL;
-	entry->next = NULL;
-	entry->pagemap.bitmap = NULL;
-	entry->pagemap.bitmapsize = 0;
-	entry->isrelfile = isRelDataFile(path);
-
-	if (map->last)
-	{
-		map->last->next = entry;
-		map->last = entry;
-	}
-	else
-		map->first = map->last = entry;
-	map->nlist++;
+	/* Remember this source file */
+	entry = get_filemap_entry(path, true);
+	entry->source_exists = true;
+	entry->source_type = type;
+	entry->source_size = size;
+	entry->source_link_target = link_target ? pg_strdup(link_target) : NULL;
 }
 
 /*
@@ -345,33 +277,24 @@ process_source_file(const char *path, file_type_t type, size_t newsize,
  * deletion.
  */
 void
-process_target_file(const char *path, file_type_t type, size_t oldsize,
+process_target_file(const char *path, file_type_t type, size_t size,
 					const char *link_target)
 {
-	bool		exists;
-	char		localpath[MAXPGPATH];
-	struct stat statbuf;
-	file_entry_t key;
-	file_entry_t *key_ptr;
 	filemap_t  *map = filemap;
 	file_entry_t *entry;
 
+	/*
+	 * Skip the control file. It is handled specially, after copying all the
+	 * other files.
+	 */
+	if (strcmp(path, "global/pg_control") == 0)
+		return;
+
 	/*
 	 * Do not apply any exclusion filters here.  This has advantage to remove
 	 * from the target data folder all paths which have been filtered out from
 	 * the source data folder when processing the source files.
 	 */
-
-	snprintf(localpath, sizeof(localpath), "%s/%s", datadir_target, path);
-	if (lstat(localpath, &statbuf) < 0)
-	{
-		if (errno != ENOENT)
-			pg_fatal("could not stat file \"%s\": %m",
-					 localpath);
-
-		exists = false;
-	}
-
 	if (map->array == NULL)
 	{
 		/* on first call, initialize lookup array */
@@ -389,120 +312,76 @@ process_target_file(const char *path, file_type_t type, size_t oldsize,
 	}
 
 	/*
-	 * Like in process_source_file, pretend that xlog is always a  directory.
+	 * Like in process_source_file, pretend that pg_wal is always a directory.
 	 */
 	if (strcmp(path, "pg_wal") == 0 && type == FILE_TYPE_SYMLINK)
 		type = FILE_TYPE_DIRECTORY;
 
-	key.path = (char *) path;
-	key_ptr = &key;
-	exists = (bsearch(&key_ptr, map->array, map->narray, sizeof(file_entry_t *),
-					  path_cmp) != NULL);
-
-	/* Remove any file or folder that doesn't exist in the source system. */
-	if (!exists)
-	{
-		entry = pg_malloc(sizeof(file_entry_t));
-		entry->path = pg_strdup(path);
-		entry->type = type;
-		entry->action = FILE_ACTION_REMOVE;
-		entry->oldsize = oldsize;
-		entry->newsize = 0;
-		entry->link_target = link_target ? pg_strdup(link_target) : NULL;
-		entry->next = NULL;
-		entry->pagemap.bitmap = NULL;
-		entry->pagemap.bitmapsize = 0;
-		entry->isrelfile = isRelDataFile(path);
-
-		if (map->last == NULL)
-			map->first = entry;
-		else
-			map->last->next = entry;
-		map->last = entry;
-		map->nlist++;
-	}
-	else
-	{
-		/*
-		 * We already handled all files that exist in the source system in
-		 * process_source_file().
-		 */
-	}
+	/* Remember this target file */
+	entry = get_filemap_entry(path, true);
+	entry->target_exists = true;
+	entry->target_type = type;
+	entry->target_size = size;
+	entry->target_link_target = link_target ? pg_strdup(link_target) : NULL;
 }
 
 /*
  * This callback gets called while we read the WAL in the target, for every
- * block that have changed in the target system. It makes note of all the
+ * block that have changed in the target system.  It makes note of all the
  * changed blocks in the pagemap of the file.
+ *
+ * NOTE: All the files on both systems must have already been added to the
+ * file map!
  */
 void
-process_block_change(ForkNumber forknum, RelFileNode rnode, BlockNumber blkno)
+process_target_wal_block_change(ForkNumber forknum, RelFileNode rnode,
+								BlockNumber blkno)
 {
 	char	   *path;
-	file_entry_t key;
-	file_entry_t *key_ptr;
 	file_entry_t *entry;
 	BlockNumber blkno_inseg;
 	int			segno;
-	filemap_t  *map = filemap;
-	file_entry_t **e;
 
-	Assert(map->array);
+	Assert(filemap->array);
 
 	segno = blkno / RELSEG_SIZE;
 	blkno_inseg = blkno % RELSEG_SIZE;
 
 	path = datasegpath(rnode, forknum, segno);
-
-	key.path = (char *) path;
-	key_ptr = &key;
-
-	e = bsearch(&key_ptr, map->array, map->narray, sizeof(file_entry_t *),
-				path_cmp);
-	if (e)
-		entry = *e;
-	else
-		entry = NULL;
+	entry = get_filemap_entry(path, false);
 	pfree(path);
 
 	if (entry)
 	{
+		int64		end_offset;
+
 		Assert(entry->isrelfile);
 
-		switch (entry->action)
-		{
-			case FILE_ACTION_NONE:
-			case FILE_ACTION_TRUNCATE:
-				/* skip if we're truncating away the modified block anyway */
-				if ((blkno_inseg + 1) * BLCKSZ <= entry->newsize)
-					datapagemap_add(&entry->pagemap, blkno_inseg);
-				break;
-
-			case FILE_ACTION_COPY_TAIL:
-
-				/*
-				 * skip the modified block if it is part of the "tail" that
-				 * we're copying anyway.
-				 */
-				if ((blkno_inseg + 1) * BLCKSZ <= entry->oldsize)
-					datapagemap_add(&entry->pagemap, blkno_inseg);
-				break;
-
-			case FILE_ACTION_COPY:
-			case FILE_ACTION_REMOVE:
-				break;
-
-			case FILE_ACTION_CREATE:
-				pg_fatal("unexpected page modification for directory or symbolic link \"%s\"", entry->path);
-		}
+		if (entry->target_type != FILE_TYPE_REGULAR)
+			pg_fatal("unexpected page modification for directory or symbolic link \"%s\"",
+					 entry->path);
+
+		/*
+		 * If the block beyond the EOF in the source system, no need to
+		 * remember it now, because we're going to truncate it away from the
+		 * target anyway. Also no need to remember the block if it's beyond
+		 * the current EOF in the target system; we will copy it over with the
+		 * "tail" from the source system, anyway.
+		 */
+		end_offset = (blkno_inseg + 1) * BLCKSZ;
+		if (end_offset <= entry->source_size &&
+			end_offset <= entry->target_size)
+			datapagemap_add(&entry->target_modified_pages, blkno_inseg);
 	}
 	else
 	{
 		/*
 		 * If we don't have any record of this file in the file map, it means
-		 * that it's a relation that doesn't exist in the source system, and
-		 * it was subsequently removed in the target system, too. We can
-		 * safely ignore it.
+		 * that it's a relation that doesn't exist in the source system.  It
+		 * could exist in the target system; we haven't moved the target-only
+		 * entries from the linked list to the array yet!  But in any case, if
+		 * it doesn't exist in the source it will be removed from the target
+		 * too, and we can safely ignore it.
 		 */
 	}
 }
@@ -593,16 +472,6 @@ filemap_list_to_array(filemap_t *map)
 	map->first = map->last = NULL;
 }
 
-void
-filemap_finalize(void)
-{
-	filemap_t  *map = filemap;
-
-	filemap_list_to_array(map);
-	qsort(map->array, map->narray, sizeof(file_entry_t *),
-		  final_filemap_cmp);
-}
-
 static const char *
 action_to_str(file_action_t action)
 {
@@ -643,26 +512,26 @@ calculate_totals(void)
 	{
 		entry = map->array[i];
 
-		if (entry->type != FILE_TYPE_REGULAR)
+		if (entry->source_type != FILE_TYPE_REGULAR)
 			continue;
 
-		map->total_size += entry->newsize;
+		map->total_size += entry->source_size;
 
 		if (entry->action == FILE_ACTION_COPY)
 		{
-			map->fetch_size += entry->newsize;
+			map->fetch_size += entry->source_size;
 			continue;
 		}
 
 		if (entry->action == FILE_ACTION_COPY_TAIL)
-			map->fetch_size += (entry->newsize - entry->oldsize);
+			map->fetch_size += (entry->source_size - entry->target_size);
 
-		if (entry->pagemap.bitmapsize > 0)
+		if (entry->target_modified_pages.bitmapsize > 0)
 		{
 			datapagemap_iterator_t *iter;
 			BlockNumber blk;
 
-			iter = datapagemap_iterate(&entry->pagemap);
+			iter = datapagemap_iterate(&entry->target_modified_pages);
 			while (datapagemap_next(iter, &blk))
 				map->fetch_size += BLCKSZ;
 
@@ -682,13 +551,13 @@ print_filemap(void)
 	{
 		entry = map->array[i];
 		if (entry->action != FILE_ACTION_NONE ||
-			entry->pagemap.bitmapsize > 0)
+			entry->target_modified_pages.bitmapsize > 0)
 		{
 			pg_log_debug("%s (%s)", entry->path,
 						 action_to_str(entry->action));
 
-			if (entry->pagemap.bitmapsize > 0)
-				datapagemap_print(&entry->pagemap);
+			if (entry->target_modified_pages.bitmapsize > 0)
+				datapagemap_print(&entry->target_modified_pages);
 		}
 	}
 	fflush(stdout);
@@ -837,3 +706,126 @@ final_filemap_cmp(const void *a, const void *b)
 	else
 		return strcmp(fa->path, fb->path);
 }
+
+/*
+ * Decide what to do with each file.
+ */
+void
+filemap_finalize()
+{
+	int			i;
+
+	filemap_list_to_array(filemap);
+
+	for (i = 0; i < filemap->narray; i++)
+	{
+		file_entry_t *entry = filemap->array[i];
+		file_action_t action;
+
+		if (!entry->target_exists && entry->source_exists)
+		{
+			/*
+			 * File exists in source, but not in target. Copy
+			 * it in toto. (If it's a relation data file, WAL replay
+			 * after rewinding should re-create it anyway. But there's
+			 * no harm in copying it now.)
+			 */
+			if (entry->source_type == FILE_TYPE_DIRECTORY ||
+				entry->source_type == FILE_TYPE_SYMLINK)
+			{
+				action = FILE_ACTION_CREATE;
+			}
+			else
+				action = FILE_ACTION_COPY;
+		}
+		else if (entry->target_exists && !entry->source_exists)
+		{
+			/* Delete file from target */
+			action = FILE_ACTION_REMOVE;
+		}
+		else if (entry->target_exists && entry->source_exists)
+		{
+			/* File exists in both systems */
+			if (entry->source_type != entry->target_type)
+			{
+				/* But it's a different kind of object. Strange.. */
+				pg_fatal("file \"%s\" is of different type in source and target", entry->path);
+			}
+
+			switch (entry->source_type)
+			{
+				case FILE_TYPE_DIRECTORY:
+					action = FILE_ACTION_NONE;
+					break;
+
+				case FILE_TYPE_SYMLINK:
+					// FIXME: Check if it points to the same target?
+					action = FILE_ACTION_NONE;
+					break;
+
+				case FILE_TYPE_REGULAR:
+					if (!entry->isrelfile)
+					{
+						/*
+						 * It's a non-data file that we have no special processing for. Copy
+						 * it in toto.
+						 *
+						 * An exception: PG_VERSIONs should be identical, but avoid
+						 * overwriting it for paranoia.
+						 */
+						if (!pg_str_endswith(entry->path, "PG_VERSION"))
+							action = FILE_ACTION_COPY;
+						else
+							action = FILE_ACTION_NONE;
+					}
+					else
+					{
+						/*
+						 * It's a data file that exists in both systems.
+						 *
+						 * If it's larger in target, we can truncate it. There will
+						 * also be a WAL record of the truncation in the source
+						 * system, so WAL replay would eventually truncate the target
+						 * too, but we might as well do it now.
+						 *
+						 * If it's smaller in the target, it means that it has been
+						 * truncated in the target, or enlarged in the source, or
+						 * both. If it was truncated in the target, we need to copy
+						 * the missing tail from the source system. If it was enlarged
+						 * in the source system, there will be WAL records in the
+						 * source system for the new blocks, so we wouldn't need to
+						 * copy them here. But we don't know which scenario we're
+						 * dealing with, and there's no harm in copying the missing
+						 * blocks now, so do it now.
+						 *
+						 * If it's the same size, do nothing here. Any blocks modified
+						 * in the target will be copied based on parsing the target
+						 * system's WAL, and any blocks modified in the source will be
+						 * updated after rewinding, when the source system's WAL is
+						 * replayed.
+						 */
+						if (entry->target_size < entry->source_size)
+							action = FILE_ACTION_COPY_TAIL;
+						else if (entry->target_size > entry->source_size)
+							action = FILE_ACTION_TRUNCATE;
+						else
+							action = FILE_ACTION_NONE;
+					}
+					break;
+				case FILE_TYPE_UNDEFINED:
+					pg_fatal("unknown file type for \"%s\"", entry->path);
+					break;
+			}
+		}
+		else
+		{
+			/* Doesn't exist in either server. Why does it have an entry at all?? */
+			action = FILE_ACTION_NONE;
+		}
+		entry->action = action;
+	}
+
+	/* Sort the actions to the order that they should be performed */
+	qsort(filemap->array, filemap->narray, sizeof(file_entry_t *),
+		  final_filemap_cmp);
+}
diff --git a/src/bin/pg_rewind/filemap.h b/src/bin/pg_rewind/filemap.h
index 0cb7425170c..a5e8df57f40 100644
--- a/src/bin/pg_rewind/filemap.h
+++ b/src/bin/pg_rewind/filemap.h
@@ -14,17 +14,21 @@
 
 /*
  * For every file found in the local or remote system, we have a file entry
- * which says what we are going to do with the file. For relation files,
- * there is also a page map, marking pages in the file that were changed
- * locally.
- *
- * The enum values are sorted in the order we want actions to be processed.
+ * that contains information about the file on both systems.  For relation
+ * files, there is also a page map that marks pages in the file that were
+ * changed in the target after the last common checkpoint.  Each entry also
+ * contains an 'action' field, which says what we are going to do with the
+ * file.
  */
+
+/* these enum values are sorted in the order we want actions to be processed */
 typedef enum
 {
+	FILE_ACTION_UNDECIDED = 0,	/* not decided yet */
+
 	FILE_ACTION_CREATE,			/* create local directory or symbolic link */
 	FILE_ACTION_COPY,			/* copy whole file, overwriting if exists */
-	FILE_ACTION_COPY_TAIL,		/* copy tail from 'oldsize' to 'newsize' */
+	FILE_ACTION_COPY_TAIL,		/* copy tail from 'source_size' to 'target_size' */
 	FILE_ACTION_NONE,			/* no action (we might still copy modified
 								 * blocks based on the parsed WAL) */
 	FILE_ACTION_TRUNCATE,		/* truncate local file to 'newsize' bytes */
@@ -33,6 +37,8 @@ typedef enum
 
 typedef enum
 {
+	FILE_TYPE_UNDEFINED = 0,
+
 	FILE_TYPE_REGULAR,
 	FILE_TYPE_DIRECTORY,
 	FILE_TYPE_SYMLINK
@@ -41,19 +47,30 @@ typedef enum
 typedef struct file_entry_t
 {
 	char	   *path;
-	file_type_t type;
+	bool		isrelfile;		/* is it a relation data file? */
 
-	file_action_t action;
+	/*
+	 * Status of the file in the target.
+	 */
+	bool		target_exists;
+	file_type_t target_type;
+	size_t		target_size; /* for a regular file */
+	char	   *target_link_target; /* for a symlink */
 
-	/* for a regular file */
-	size_t		oldsize;
-	size_t		newsize;
-	bool		isrelfile;		/* is it a relation data file? */
+	datapagemap_t target_modified_pages;
 
-	datapagemap_t pagemap;
+	/*
+	 * Status of the file in the source.
+	 */
+	bool		source_exists;
+	file_type_t source_type;
+	size_t		source_size;
+	char	   *source_link_target; /* for a symlink */
 
-	/* for a symlink */
-	char	   *link_target;
+	/*
+	 * What will we do to the file?
+	 */
+	file_action_t action;
 
 	struct file_entry_t *next;
 } file_entry_t;
@@ -70,20 +87,19 @@ typedef struct filemap_t
 
 	/*
 	 * After processing all the remote files, the entries in the linked list
-	 * are moved to this array. After processing local files, too, all the
+	 * are moved to this array.  After processing local files, too, all the
 	 * local entries are added to the array by filemap_finalize, and sorted in
-	 * the final order. After filemap_finalize, all the entries are in the
+	 * the final order.  After filemap_finalize, all the entries are in the
 	 * array, and the linked list is empty.
 	 */
 	file_entry_t **array;
 	int			narray;			/* current length of array */
 
 	/*
-	 * Summary information. total_size is the total size of the source
-	 * cluster, and fetch_size is the number of bytes that needs to be copied.
+	 * Summary information.
 	 */
-	uint64		total_size;
-	uint64		fetch_size;
+	uint64		total_size;		/* total size of the source cluster */
+	uint64		fetch_size;		/* number of bytes that needs to be copied */
 } filemap_t;
 
 extern filemap_t *filemap;
@@ -94,11 +110,12 @@ extern void print_filemap(void);
 
 /* Functions for populating the filemap */
 extern void process_source_file(const char *path, file_type_t type,
-								size_t newsize, const char *link_target);
+								size_t size, const char *link_target);
 extern void process_target_file(const char *path, file_type_t type,
-								size_t newsize, const char *link_target);
-extern void process_block_change(ForkNumber forknum, RelFileNode rnode,
-								 BlockNumber blkno);
+								size_t size, const char *link_target);
+extern void process_target_wal_block_change(ForkNumber forknum,
+											RelFileNode rnode,
+											BlockNumber blkno);
 extern void filemap_finalize(void);
 
 #endif							/* FILEMAP_H */
diff --git a/src/bin/pg_rewind/libpq_fetch.c b/src/bin/pg_rewind/libpq_fetch.c
index bf4dfc23b96..7fc9161b8c8 100644
--- a/src/bin/pg_rewind/libpq_fetch.c
+++ b/src/bin/pg_rewind/libpq_fetch.c
@@ -465,7 +465,7 @@ libpq_executeFileMap(filemap_t *map)
 		entry = map->array[i];
 
 		/* If this is a relation file, copy the modified blocks */
-		execute_pagemap(&entry->pagemap, entry->path);
+		execute_pagemap(&entry->target_modified_pages, entry->path);
 
 		switch (entry->action)
 		{
@@ -476,15 +476,15 @@ libpq_executeFileMap(filemap_t *map)
 			case FILE_ACTION_COPY:
 				/* Truncate the old file out of the way, if any */
 				open_target_file(entry->path, true);
-				fetch_file_range(entry->path, 0, entry->newsize);
+				fetch_file_range(entry->path, 0, entry->source_size);
 				break;
 
 			case FILE_ACTION_TRUNCATE:
-				truncate_target_file(entry->path, entry->newsize);
+				truncate_target_file(entry->path, entry->source_size);
 				break;
 
 			case FILE_ACTION_COPY_TAIL:
-				fetch_file_range(entry->path, entry->oldsize, entry->newsize);
+				fetch_file_range(entry->path, entry->target_size, entry->source_size);
 				break;
 
 			case FILE_ACTION_REMOVE:
@@ -494,6 +494,10 @@ libpq_executeFileMap(filemap_t *map)
 			case FILE_ACTION_CREATE:
 				create_target(entry);
 				break;
+
+			case FILE_ACTION_UNDECIDED:
+				pg_fatal("no action decided for \"%s\"", entry->path);
+				break;
 		}
 	}
 
diff --git a/src/bin/pg_rewind/parsexlog.c b/src/bin/pg_rewind/parsexlog.c
index 2229c86f9af..2baeb74ae93 100644
--- a/src/bin/pg_rewind/parsexlog.c
+++ b/src/bin/pg_rewind/parsexlog.c
@@ -436,6 +436,6 @@ extractPageInfo(XLogReaderState *record)
 		if (forknum != MAIN_FORKNUM)
 			continue;
 
-		process_block_change(forknum, rnode, blkno);
+		process_target_wal_block_change(forknum, rnode, blkno);
 	}
 }
diff --git a/src/bin/pg_rewind/pg_rewind.c b/src/bin/pg_rewind/pg_rewind.c
index c9b9e480c0f..210984d302b 100644
--- a/src/bin/pg_rewind/pg_rewind.c
+++ b/src/bin/pg_rewind/pg_rewind.c
@@ -369,7 +369,7 @@ main(int argc, char **argv)
 				chkpttli);
 
 	/*
-	 * Build the filemap, by comparing the source and target data directories.
+	 * Collect information about all files in the target and source systems.
 	 */
 	filemap_create();
 	if (showprogress)
@@ -390,8 +390,12 @@ main(int argc, char **argv)
 		pg_log_info("reading WAL in target");
 	extractPageMap(datadir_target, chkptrec, lastcommontliIndex,
 				   ControlFile_target.checkPoint, restore_command);
-	filemap_finalize();
 
+	/*
+	 * We have collected all information we need from both systems. Decide
+	 * what to do with each file.
+	 */
+	filemap_finalize();
 	if (showprogress)
 		calculate_totals();
 
-- 
2.20.1


--------------D93EDEBFB124D563B723F4BD
Content-Type: text/x-patch; charset=UTF-8;
 name="0003-pg_rewind-Replace-the-hybrid-list-array-data-structu.patch"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
 filename*0="0003-pg_rewind-Replace-the-hybrid-list-array-data-structu.pa";
 filename*1="tch"



^ permalink  raw  reply  [nested|flat] 41+ messages in thread

* [PATCH 2/5] Refactor pg_rewind for more clear decision making.
@ 2020-08-19 12:34 Heikki Linnakangas <[email protected]>
  0 siblings, 0 replies; 41+ messages in thread

From: Heikki Linnakangas @ 2020-08-19 12:34 UTC (permalink / raw)

Deciding what to do with each file is now a separate step after all the
necessary information has been gathered. It is more clear that way.
Previously, the decision-making was divided between process_source_file()
and process_target_file(), and it was a bit hard to piece together what the
overall rules were.
---
 src/bin/pg_rewind/copy_fetch.c  |  14 +-
 src/bin/pg_rewind/file_ops.c    |  16 +-
 src/bin/pg_rewind/filemap.c     | 514 ++++++++++++++++----------------
 src/bin/pg_rewind/filemap.h     |  67 +++--
 src/bin/pg_rewind/libpq_fetch.c |  12 +-
 src/bin/pg_rewind/parsexlog.c   |   2 +-
 src/bin/pg_rewind/pg_rewind.c   |   8 +-
 7 files changed, 332 insertions(+), 301 deletions(-)

diff --git a/src/bin/pg_rewind/copy_fetch.c b/src/bin/pg_rewind/copy_fetch.c
index 1edab5f1867..18fad32600e 100644
--- a/src/bin/pg_rewind/copy_fetch.c
+++ b/src/bin/pg_rewind/copy_fetch.c
@@ -210,7 +210,7 @@ copy_executeFileMap(filemap_t *map)
 	for (i = 0; i < map->narray; i++)
 	{
 		entry = map->array[i];
-		execute_pagemap(&entry->pagemap, entry->path);
+		execute_pagemap(&entry->target_modified_pages, entry->path);
 
 		switch (entry->action)
 		{
@@ -219,16 +219,16 @@ copy_executeFileMap(filemap_t *map)
 				break;
 
 			case FILE_ACTION_COPY:
-				rewind_copy_file_range(entry->path, 0, entry->newsize, true);
+				rewind_copy_file_range(entry->path, 0, entry->source_size, true);
 				break;
 
 			case FILE_ACTION_TRUNCATE:
-				truncate_target_file(entry->path, entry->newsize);
+				truncate_target_file(entry->path, entry->source_size);
 				break;
 
 			case FILE_ACTION_COPY_TAIL:
-				rewind_copy_file_range(entry->path, entry->oldsize,
-									   entry->newsize, false);
+				rewind_copy_file_range(entry->path, entry->target_size,
+									   entry->source_size, false);
 				break;
 
 			case FILE_ACTION_CREATE:
@@ -238,6 +238,10 @@ copy_executeFileMap(filemap_t *map)
 			case FILE_ACTION_REMOVE:
 				remove_target(entry);
 				break;
+
+			case FILE_ACTION_UNDECIDED:
+				pg_fatal("no action decided for \"%s\"", entry->path);
+				break;
 		}
 	}
 
diff --git a/src/bin/pg_rewind/file_ops.c b/src/bin/pg_rewind/file_ops.c
index 55439db20ba..ec37d0b2e0d 100644
--- a/src/bin/pg_rewind/file_ops.c
+++ b/src/bin/pg_rewind/file_ops.c
@@ -126,8 +126,9 @@ void
 remove_target(file_entry_t *entry)
 {
 	Assert(entry->action == FILE_ACTION_REMOVE);
+	Assert(entry->target_exists);
 
-	switch (entry->type)
+	switch (entry->target_type)
 	{
 		case FILE_TYPE_DIRECTORY:
 			remove_target_dir(entry->path);
@@ -140,6 +141,10 @@ remove_target(file_entry_t *entry)
 		case FILE_TYPE_SYMLINK:
 			remove_target_symlink(entry->path);
 			break;
+
+		case FILE_TYPE_UNDEFINED:
+			pg_fatal("undefined file type for \"%s\"", entry->path);
+			break;
 	}
 }
 
@@ -147,21 +152,26 @@ void
 create_target(file_entry_t *entry)
 {
 	Assert(entry->action == FILE_ACTION_CREATE);
+	Assert(!entry->target_exists);
 
-	switch (entry->type)
+	switch (entry->source_type)
 	{
 		case FILE_TYPE_DIRECTORY:
 			create_target_dir(entry->path);
 			break;
 
 		case FILE_TYPE_SYMLINK:
-			create_target_symlink(entry->path, entry->link_target);
+			create_target_symlink(entry->path, entry->source_link_target);
 			break;
 
 		case FILE_TYPE_REGULAR:
 			/* can't happen. Regular files are created with open_target_file. */
 			pg_fatal("invalid action (CREATE) for regular file");
 			break;
+
+		case FILE_TYPE_UNDEFINED:
+			pg_fatal("undefined file type for \"%s\"", entry->path);
+			break;
 	}
 }
 
diff --git a/src/bin/pg_rewind/filemap.c b/src/bin/pg_rewind/filemap.c
index 1879257b66a..431e8e760e0 100644
--- a/src/bin/pg_rewind/filemap.c
+++ b/src/bin/pg_rewind/filemap.c
@@ -26,6 +26,8 @@ static bool isRelDataFile(const char *path);
 static char *datasegpath(RelFileNode rnode, ForkNumber forknum,
 						 BlockNumber segno);
 static int	path_cmp(const void *a, const void *b);
+
+static file_entry_t *get_filemap_entry(const char *path, bool create);
 static int	final_filemap_cmp(const void *a, const void *b);
 static void filemap_list_to_array(filemap_t *map);
 static bool check_file_excluded(const char *path, bool is_source);
@@ -146,6 +148,65 @@ filemap_create(void)
 	filemap = map;
 }
 
+/* Look up or create entry for 'path' */
+static file_entry_t *
+get_filemap_entry(const char *path, bool create)
+{
+	filemap_t  *map = filemap;
+	file_entry_t *entry;
+	file_entry_t **e;
+	file_entry_t key;
+	file_entry_t *key_ptr;
+
+	if (map->array)
+	{
+		key.path = (char *) path;
+		key_ptr = &key;
+		e = bsearch(&key_ptr, map->array, map->narray, sizeof(file_entry_t *),
+					path_cmp);
+	}
+	else
+		e = NULL;
+
+	if (e)
+		entry = *e;
+	else if (!create)
+		entry = NULL;
+	else
+	{
+		/* Create a new entry for this file */
+		entry = pg_malloc(sizeof(file_entry_t));
+		entry->path = pg_strdup(path);
+		entry->isrelfile = isRelDataFile(path);
+		entry->action = FILE_ACTION_UNDECIDED;
+
+		entry->target_exists = false;
+		entry->target_type = FILE_TYPE_UNDEFINED;
+		entry->target_size = 0;
+		entry->target_link_target = NULL;
+		entry->target_modified_pages.bitmap = NULL;
+		entry->target_modified_pages.bitmapsize = 0;
+
+		entry->source_exists = false;
+		entry->source_type = FILE_TYPE_UNDEFINED;
+		entry->source_size = 0;
+		entry->source_link_target = NULL;
+
+		entry->next = NULL;
+
+		if (map->last)
+		{
+			map->last->next = entry;
+			map->last = entry;
+		}
+		else
+			map->first = map->last = entry;
+		map->nlist++;
+	}
+
+	return entry;
+}
+
 /*
  * Callback for processing source file list.
  *
@@ -154,18 +215,19 @@ filemap_create(void)
  * exists in the target and whether the size matches.
  */
 void
-process_source_file(const char *path, file_type_t type, size_t newsize,
+process_source_file(const char *path, file_type_t type, size_t size,
 					const char *link_target)
 {
-	bool		exists;
-	char		localpath[MAXPGPATH];
-	struct stat statbuf;
-	filemap_t  *map = filemap;
-	file_action_t action = FILE_ACTION_NONE;
-	size_t		oldsize = 0;
 	file_entry_t *entry;
 
-	Assert(map->array == NULL);
+	Assert(filemap->array == NULL);
+
+	/*
+	 * Skip the control file. It is handled specially, after copying all the
+	 * other files.
+	 */
+	if (strcmp(path, "global/pg_control") == 0)
+		return;
 
 	/*
 	 * Skip any files matching the exclusion filters. This has the effect to
@@ -199,142 +261,12 @@ process_source_file(const char *path, file_type_t type, size_t newsize,
 	if (type != FILE_TYPE_REGULAR && isRelDataFile(path))
 		pg_fatal("data file \"%s\" in source is not a regular file", path);
 
-	snprintf(localpath, sizeof(localpath), "%s/%s", datadir_target, path);
-
-	/* Does the corresponding file exist in the target data dir? */
-	if (lstat(localpath, &statbuf) < 0)
-	{
-		if (errno != ENOENT)
-			pg_fatal("could not stat file \"%s\": %m",
-					 localpath);
-
-		exists = false;
-	}
-	else
-		exists = true;
-
-	switch (type)
-	{
-		case FILE_TYPE_DIRECTORY:
-			if (exists && !S_ISDIR(statbuf.st_mode) && strcmp(path, "pg_wal") != 0)
-			{
-				/* it's a directory in source, but not in target. Strange.. */
-				pg_fatal("\"%s\" is not a directory", localpath);
-			}
-
-			if (!exists)
-				action = FILE_ACTION_CREATE;
-			else
-				action = FILE_ACTION_NONE;
-			oldsize = 0;
-			break;
-
-		case FILE_TYPE_SYMLINK:
-			if (exists &&
-#ifndef WIN32
-				!S_ISLNK(statbuf.st_mode)
-#else
-				!pgwin32_is_junction(localpath)
-#endif
-				)
-			{
-				/*
-				 * It's a symbolic link in source, but not in target.
-				 * Strange..
-				 */
-				pg_fatal("\"%s\" is not a symbolic link", localpath);
-			}
-
-			if (!exists)
-				action = FILE_ACTION_CREATE;
-			else
-				action = FILE_ACTION_NONE;
-			oldsize = 0;
-			break;
-
-		case FILE_TYPE_REGULAR:
-			if (exists && !S_ISREG(statbuf.st_mode))
-				pg_fatal("\"%s\" is not a regular file", localpath);
-
-			if (!exists || !isRelDataFile(path))
-			{
-				/*
-				 * File exists in source, but not in target. Or it's a
-				 * non-data file that we have no special processing for. Copy
-				 * it in toto.
-				 *
-				 * An exception: PG_VERSIONs should be identical, but avoid
-				 * overwriting it for paranoia.
-				 */
-				if (pg_str_endswith(path, "PG_VERSION"))
-				{
-					action = FILE_ACTION_NONE;
-					oldsize = statbuf.st_size;
-				}
-				else
-				{
-					action = FILE_ACTION_COPY;
-					oldsize = 0;
-				}
-			}
-			else
-			{
-				/*
-				 * It's a data file that exists in both.
-				 *
-				 * If it's larger in target, we can truncate it. There will
-				 * also be a WAL record of the truncation in the source
-				 * system, so WAL replay would eventually truncate the target
-				 * too, but we might as well do it now.
-				 *
-				 * If it's smaller in the target, it means that it has been
-				 * truncated in the target, or enlarged in the source, or
-				 * both. If it was truncated in the target, we need to copy
-				 * the missing tail from the source system. If it was enlarged
-				 * in the source system, there will be WAL records in the
-				 * source system for the new blocks, so we wouldn't need to
-				 * copy them here. But we don't know which scenario we're
-				 * dealing with, and there's no harm in copying the missing
-				 * blocks now, so do it now.
-				 *
-				 * If it's the same size, do nothing here. Any blocks modified
-				 * in the target will be copied based on parsing the target
-				 * system's WAL, and any blocks modified in the source will be
-				 * updated after rewinding, when the source system's WAL is
-				 * replayed.
-				 */
-				oldsize = statbuf.st_size;
-				if (oldsize < newsize)
-					action = FILE_ACTION_COPY_TAIL;
-				else if (oldsize > newsize)
-					action = FILE_ACTION_TRUNCATE;
-				else
-					action = FILE_ACTION_NONE;
-			}
-			break;
-	}
-
-	/* Create a new entry for this file */
-	entry = pg_malloc(sizeof(file_entry_t));
-	entry->path = pg_strdup(path);
-	entry->type = type;
-	entry->action = action;
-	entry->oldsize = oldsize;
-	entry->newsize = newsize;
-	entry->link_target = link_target ? pg_strdup(link_target) : NULL;
-	entry->next = NULL;
-	entry->pagemap.bitmap = NULL;
-	entry->pagemap.bitmapsize = 0;
-	entry->isrelfile = isRelDataFile(path);
-
-	if (map->last)
-	{
-		map->last->next = entry;
-		map->last = entry;
-	}
-	else
-		map->first = map->last = entry;
-	map->nlist++;
+	/* Remember this source file */
+	entry = get_filemap_entry(path, true);
+	entry->source_exists = true;
+	entry->source_type = type;
+	entry->source_size = size;
+	entry->source_link_target = link_target ? pg_strdup(link_target) : NULL;
 }
 
 /*
@@ -345,33 +277,24 @@ process_source_file(const char *path, file_type_t type, size_t newsize,
  * deletion.
  */
 void
-process_target_file(const char *path, file_type_t type, size_t oldsize,
+process_target_file(const char *path, file_type_t type, size_t size,
 					const char *link_target)
 {
-	bool		exists;
-	char		localpath[MAXPGPATH];
-	struct stat statbuf;
-	file_entry_t key;
-	file_entry_t *key_ptr;
 	filemap_t  *map = filemap;
 	file_entry_t *entry;
 
+	/*
+	 * Skip the control file. It is handled specially, after copying all the
+	 * other files.
+	 */
+	if (strcmp(path, "global/pg_control") == 0)
+		return;
+
 	/*
 	 * Do not apply any exclusion filters here.  This has advantage to remove
 	 * from the target data folder all paths which have been filtered out from
 	 * the source data folder when processing the source files.
 	 */
-
-	snprintf(localpath, sizeof(localpath), "%s/%s", datadir_target, path);
-	if (lstat(localpath, &statbuf) < 0)
-	{
-		if (errno != ENOENT)
-			pg_fatal("could not stat file \"%s\": %m",
-					 localpath);
-
-		exists = false;
-	}
-
 	if (map->array == NULL)
 	{
 		/* on first call, initialize lookup array */
@@ -389,120 +312,76 @@ process_target_file(const char *path, file_type_t type, size_t oldsize,
 	}
 
 	/*
-	 * Like in process_source_file, pretend that xlog is always a  directory.
+	 * Like in process_source_file, pretend that pg_wal is always a directory.
 	 */
 	if (strcmp(path, "pg_wal") == 0 && type == FILE_TYPE_SYMLINK)
 		type = FILE_TYPE_DIRECTORY;
 
-	key.path = (char *) path;
-	key_ptr = &key;
-	exists = (bsearch(&key_ptr, map->array, map->narray, sizeof(file_entry_t *),
-					  path_cmp) != NULL);
-
-	/* Remove any file or folder that doesn't exist in the source system. */
-	if (!exists)
-	{
-		entry = pg_malloc(sizeof(file_entry_t));
-		entry->path = pg_strdup(path);
-		entry->type = type;
-		entry->action = FILE_ACTION_REMOVE;
-		entry->oldsize = oldsize;
-		entry->newsize = 0;
-		entry->link_target = link_target ? pg_strdup(link_target) : NULL;
-		entry->next = NULL;
-		entry->pagemap.bitmap = NULL;
-		entry->pagemap.bitmapsize = 0;
-		entry->isrelfile = isRelDataFile(path);
-
-		if (map->last == NULL)
-			map->first = entry;
-		else
-			map->last->next = entry;
-		map->last = entry;
-		map->nlist++;
-	}
-	else
-	{
-		/*
-		 * We already handled all files that exist in the source system in
-		 * process_source_file().
-		 */
-	}
+	/* Remember this target file */
+	entry = get_filemap_entry(path, true);
+	entry->target_exists = true;
+	entry->target_type = type;
+	entry->target_size = size;
+	entry->target_link_target = link_target ? pg_strdup(link_target) : NULL;
 }
 
 /*
  * This callback gets called while we read the WAL in the target, for every
- * block that have changed in the target system. It makes note of all the
+ * block that have changed in the target system.  It makes note of all the
  * changed blocks in the pagemap of the file.
+ *
+ * NOTE: All the files on both systems must have already been added to the
+ * file map!
  */
 void
-process_block_change(ForkNumber forknum, RelFileNode rnode, BlockNumber blkno)
+process_target_wal_block_change(ForkNumber forknum, RelFileNode rnode,
+								BlockNumber blkno)
 {
 	char	   *path;
-	file_entry_t key;
-	file_entry_t *key_ptr;
 	file_entry_t *entry;
 	BlockNumber blkno_inseg;
 	int			segno;
-	filemap_t  *map = filemap;
-	file_entry_t **e;
 
-	Assert(map->array);
+	Assert(filemap->array);
 
 	segno = blkno / RELSEG_SIZE;
 	blkno_inseg = blkno % RELSEG_SIZE;
 
 	path = datasegpath(rnode, forknum, segno);
-
-	key.path = (char *) path;
-	key_ptr = &key;
-
-	e = bsearch(&key_ptr, map->array, map->narray, sizeof(file_entry_t *),
-				path_cmp);
-	if (e)
-		entry = *e;
-	else
-		entry = NULL;
+	entry = get_filemap_entry(path, false);
 	pfree(path);
 
 	if (entry)
 	{
+		int64		end_offset;
+
 		Assert(entry->isrelfile);
 
-		switch (entry->action)
-		{
-			case FILE_ACTION_NONE:
-			case FILE_ACTION_TRUNCATE:
-				/* skip if we're truncating away the modified block anyway */
-				if ((blkno_inseg + 1) * BLCKSZ <= entry->newsize)
-					datapagemap_add(&entry->pagemap, blkno_inseg);
-				break;
-
-			case FILE_ACTION_COPY_TAIL:
-
-				/*
-				 * skip the modified block if it is part of the "tail" that
-				 * we're copying anyway.
-				 */
-				if ((blkno_inseg + 1) * BLCKSZ <= entry->oldsize)
-					datapagemap_add(&entry->pagemap, blkno_inseg);
-				break;
-
-			case FILE_ACTION_COPY:
-			case FILE_ACTION_REMOVE:
-				break;
-
-			case FILE_ACTION_CREATE:
-				pg_fatal("unexpected page modification for directory or symbolic link \"%s\"", entry->path);
-		}
+		if (entry->target_type != FILE_TYPE_REGULAR)
+			pg_fatal("unexpected page modification for directory or symbolic link \"%s\"",
+					 entry->path);
+
+		/*
+		 * If the block beyond the EOF in the source system, no need to
+		 * remember it now, because we're going to truncate it away from the
+		 * target anyway. Also no need to remember the block if it's beyond
+		 * the current EOF in the target system; we will copy it over with the
+		 * "tail" from the source system, anyway.
+		 */
+		end_offset = (blkno_inseg + 1) * BLCKSZ;
+		if (end_offset <= entry->source_size &&
+			end_offset <= entry->target_size)
+			datapagemap_add(&entry->target_modified_pages, blkno_inseg);
 	}
 	else
 	{
 		/*
 		 * If we don't have any record of this file in the file map, it means
-		 * that it's a relation that doesn't exist in the source system, and
-		 * it was subsequently removed in the target system, too. We can
-		 * safely ignore it.
+		 * that it's a relation that doesn't exist in the source system.  It
+		 * could exist in the target system; we haven't moved the target-only
+		 * entries from the linked list to the array yet!  But in any case, if
+		 * it doesn't exist in the source it will be removed from the target
+		 * too, and we can safely ignore it.
 		 */
 	}
 }
@@ -593,16 +472,6 @@ filemap_list_to_array(filemap_t *map)
 	map->first = map->last = NULL;
 }
 
-void
-filemap_finalize(void)
-{
-	filemap_t  *map = filemap;
-
-	filemap_list_to_array(map);
-	qsort(map->array, map->narray, sizeof(file_entry_t *),
-		  final_filemap_cmp);
-}
-
 static const char *
 action_to_str(file_action_t action)
 {
@@ -643,26 +512,26 @@ calculate_totals(void)
 	{
 		entry = map->array[i];
 
-		if (entry->type != FILE_TYPE_REGULAR)
+		if (entry->source_type != FILE_TYPE_REGULAR)
 			continue;
 
-		map->total_size += entry->newsize;
+		map->total_size += entry->source_size;
 
 		if (entry->action == FILE_ACTION_COPY)
 		{
-			map->fetch_size += entry->newsize;
+			map->fetch_size += entry->source_size;
 			continue;
 		}
 
 		if (entry->action == FILE_ACTION_COPY_TAIL)
-			map->fetch_size += (entry->newsize - entry->oldsize);
+			map->fetch_size += (entry->source_size - entry->target_size);
 
-		if (entry->pagemap.bitmapsize > 0)
+		if (entry->target_modified_pages.bitmapsize > 0)
 		{
 			datapagemap_iterator_t *iter;
 			BlockNumber blk;
 
-			iter = datapagemap_iterate(&entry->pagemap);
+			iter = datapagemap_iterate(&entry->target_modified_pages);
 			while (datapagemap_next(iter, &blk))
 				map->fetch_size += BLCKSZ;
 
@@ -682,13 +551,13 @@ print_filemap(void)
 	{
 		entry = map->array[i];
 		if (entry->action != FILE_ACTION_NONE ||
-			entry->pagemap.bitmapsize > 0)
+			entry->target_modified_pages.bitmapsize > 0)
 		{
 			pg_log_debug("%s (%s)", entry->path,
 						 action_to_str(entry->action));
 
-			if (entry->pagemap.bitmapsize > 0)
-				datapagemap_print(&entry->pagemap);
+			if (entry->target_modified_pages.bitmapsize > 0)
+				datapagemap_print(&entry->target_modified_pages);
 		}
 	}
 	fflush(stdout);
@@ -837,3 +706,126 @@ final_filemap_cmp(const void *a, const void *b)
 	else
 		return strcmp(fa->path, fb->path);
 }
+
+/*
+ * Decide what to do with each file.
+ */
+void
+filemap_finalize()
+{
+	int			i;
+
+	filemap_list_to_array(filemap);
+
+	for (i = 0; i < filemap->narray; i++)
+	{
+		file_entry_t *entry = filemap->array[i];
+		file_action_t action;
+
+		if (!entry->target_exists && entry->source_exists)
+		{
+			/*
+			 * File exists in source, but not in target. Copy
+			 * it in toto. (If it's a relation data file, WAL replay
+			 * after rewinding should re-create it anyway. But there's
+			 * no harm in copying it now.)
+			 */
+			if (entry->source_type == FILE_TYPE_DIRECTORY ||
+				entry->source_type == FILE_TYPE_SYMLINK)
+			{
+				action = FILE_ACTION_CREATE;
+			}
+			else
+				action = FILE_ACTION_COPY;
+		}
+		else if (entry->target_exists && !entry->source_exists)
+		{
+			/* Delete file from target */
+			action = FILE_ACTION_REMOVE;
+		}
+		else if (entry->target_exists && entry->source_exists)
+		{
+			/* File exists in both systems */
+			if (entry->source_type != entry->target_type)
+			{
+				/* But it's a different kind of object. Strange.. */
+				pg_fatal("file \"%s\" is of different type in source and target", entry->path);
+			}
+
+			switch (entry->source_type)
+			{
+				case FILE_TYPE_DIRECTORY:
+					action = FILE_ACTION_NONE;
+					break;
+
+				case FILE_TYPE_SYMLINK:
+					// FIXME: Check if it points to the same target?
+					action = FILE_ACTION_NONE;
+					break;
+
+				case FILE_TYPE_REGULAR:
+					if (!entry->isrelfile)
+					{
+						/*
+						 * It's a non-data file that we have no special processing for. Copy
+						 * it in toto.
+						 *
+						 * An exception: PG_VERSIONs should be identical, but avoid
+						 * overwriting it for paranoia.
+						 */
+						if (!pg_str_endswith(entry->path, "PG_VERSION"))
+							action = FILE_ACTION_COPY;
+						else
+							action = FILE_ACTION_NONE;
+					}
+					else
+					{
+						/*
+						 * It's a data file that exists in both systems.
+						 *
+						 * If it's larger in target, we can truncate it. There will
+						 * also be a WAL record of the truncation in the source
+						 * system, so WAL replay would eventually truncate the target
+						 * too, but we might as well do it now.
+						 *
+						 * If it's smaller in the target, it means that it has been
+						 * truncated in the target, or enlarged in the source, or
+						 * both. If it was truncated in the target, we need to copy
+						 * the missing tail from the source system. If it was enlarged
+						 * in the source system, there will be WAL records in the
+						 * source system for the new blocks, so we wouldn't need to
+						 * copy them here. But we don't know which scenario we're
+						 * dealing with, and there's no harm in copying the missing
+						 * blocks now, so do it now.
+						 *
+						 * If it's the same size, do nothing here. Any blocks modified
+						 * in the target will be copied based on parsing the target
+						 * system's WAL, and any blocks modified in the source will be
+						 * updated after rewinding, when the source system's WAL is
+						 * replayed.
+						 */
+						if (entry->target_size < entry->source_size)
+							action = FILE_ACTION_COPY_TAIL;
+						else if (entry->target_size > entry->source_size)
+							action = FILE_ACTION_TRUNCATE;
+						else
+							action = FILE_ACTION_NONE;
+					}
+					break;
+				case FILE_TYPE_UNDEFINED:
+					pg_fatal("unknown file type for \"%s\"", entry->path);
+					break;
+			}
+		}
+		else
+		{
+			/* Doesn't exist in either server. Why does it have an entry at all?? */
+			action = FILE_ACTION_NONE;
+		}
+		entry->action = action;
+	}
+
+	/* Sort the actions to the order that they should be performed */
+	qsort(filemap->array, filemap->narray, sizeof(file_entry_t *),
+		  final_filemap_cmp);
+}
diff --git a/src/bin/pg_rewind/filemap.h b/src/bin/pg_rewind/filemap.h
index 0cb7425170c..a5e8df57f40 100644
--- a/src/bin/pg_rewind/filemap.h
+++ b/src/bin/pg_rewind/filemap.h
@@ -14,17 +14,21 @@
 
 /*
  * For every file found in the local or remote system, we have a file entry
- * which says what we are going to do with the file. For relation files,
- * there is also a page map, marking pages in the file that were changed
- * locally.
- *
- * The enum values are sorted in the order we want actions to be processed.
+ * that contains information about the file on both systems.  For relation
+ * files, there is also a page map that marks pages in the file that were
+ * changed in the target after the last common checkpoint.  Each entry also
+ * contains an 'action' field, which says what we are going to do with the
+ * file.
  */
+
+/* these enum values are sorted in the order we want actions to be processed */
 typedef enum
 {
+	FILE_ACTION_UNDECIDED = 0,	/* not decided yet */
+
 	FILE_ACTION_CREATE,			/* create local directory or symbolic link */
 	FILE_ACTION_COPY,			/* copy whole file, overwriting if exists */
-	FILE_ACTION_COPY_TAIL,		/* copy tail from 'oldsize' to 'newsize' */
+	FILE_ACTION_COPY_TAIL,		/* copy tail from 'source_size' to 'target_size' */
 	FILE_ACTION_NONE,			/* no action (we might still copy modified
 								 * blocks based on the parsed WAL) */
 	FILE_ACTION_TRUNCATE,		/* truncate local file to 'newsize' bytes */
@@ -33,6 +37,8 @@ typedef enum
 
 typedef enum
 {
+	FILE_TYPE_UNDEFINED = 0,
+
 	FILE_TYPE_REGULAR,
 	FILE_TYPE_DIRECTORY,
 	FILE_TYPE_SYMLINK
@@ -41,19 +47,30 @@ typedef enum
 typedef struct file_entry_t
 {
 	char	   *path;
-	file_type_t type;
+	bool		isrelfile;		/* is it a relation data file? */
 
-	file_action_t action;
+	/*
+	 * Status of the file in the target.
+	 */
+	bool		target_exists;
+	file_type_t target_type;
+	size_t		target_size; /* for a regular file */
+	char	   *target_link_target; /* for a symlink */
 
-	/* for a regular file */
-	size_t		oldsize;
-	size_t		newsize;
-	bool		isrelfile;		/* is it a relation data file? */
+	datapagemap_t target_modified_pages;
 
-	datapagemap_t pagemap;
+	/*
+	 * Status of the file in the source.
+	 */
+	bool		source_exists;
+	file_type_t source_type;
+	size_t		source_size;
+	char	   *source_link_target; /* for a symlink */
 
-	/* for a symlink */
-	char	   *link_target;
+	/*
+	 * What will we do to the file?
+	 */
+	file_action_t action;
 
 	struct file_entry_t *next;
 } file_entry_t;
@@ -70,20 +87,19 @@ typedef struct filemap_t
 
 	/*
 	 * After processing all the remote files, the entries in the linked list
-	 * are moved to this array. After processing local files, too, all the
+	 * are moved to this array.  After processing local files, too, all the
 	 * local entries are added to the array by filemap_finalize, and sorted in
-	 * the final order. After filemap_finalize, all the entries are in the
+	 * the final order.  After filemap_finalize, all the entries are in the
 	 * array, and the linked list is empty.
 	 */
 	file_entry_t **array;
 	int			narray;			/* current length of array */
 
 	/*
-	 * Summary information. total_size is the total size of the source
-	 * cluster, and fetch_size is the number of bytes that needs to be copied.
+	 * Summary information.
 	 */
-	uint64		total_size;
-	uint64		fetch_size;
+	uint64		total_size;		/* total size of the source cluster */
+	uint64		fetch_size;		/* number of bytes that needs to be copied */
 } filemap_t;
 
 extern filemap_t *filemap;
@@ -94,11 +110,12 @@ extern void print_filemap(void);
 
 /* Functions for populating the filemap */
 extern void process_source_file(const char *path, file_type_t type,
-								size_t newsize, const char *link_target);
+								size_t size, const char *link_target);
 extern void process_target_file(const char *path, file_type_t type,
-								size_t newsize, const char *link_target);
-extern void process_block_change(ForkNumber forknum, RelFileNode rnode,
-								 BlockNumber blkno);
+								size_t size, const char *link_target);
+extern void process_target_wal_block_change(ForkNumber forknum,
+											RelFileNode rnode,
+											BlockNumber blkno);
 extern void filemap_finalize(void);
 
 #endif							/* FILEMAP_H */
diff --git a/src/bin/pg_rewind/libpq_fetch.c b/src/bin/pg_rewind/libpq_fetch.c
index bf4dfc23b96..7fc9161b8c8 100644
--- a/src/bin/pg_rewind/libpq_fetch.c
+++ b/src/bin/pg_rewind/libpq_fetch.c
@@ -465,7 +465,7 @@ libpq_executeFileMap(filemap_t *map)
 		entry = map->array[i];
 
 		/* If this is a relation file, copy the modified blocks */
-		execute_pagemap(&entry->pagemap, entry->path);
+		execute_pagemap(&entry->target_modified_pages, entry->path);
 
 		switch (entry->action)
 		{
@@ -476,15 +476,15 @@ libpq_executeFileMap(filemap_t *map)
 			case FILE_ACTION_COPY:
 				/* Truncate the old file out of the way, if any */
 				open_target_file(entry->path, true);
-				fetch_file_range(entry->path, 0, entry->newsize);
+				fetch_file_range(entry->path, 0, entry->source_size);
 				break;
 
 			case FILE_ACTION_TRUNCATE:
-				truncate_target_file(entry->path, entry->newsize);
+				truncate_target_file(entry->path, entry->source_size);
 				break;
 
 			case FILE_ACTION_COPY_TAIL:
-				fetch_file_range(entry->path, entry->oldsize, entry->newsize);
+				fetch_file_range(entry->path, entry->target_size, entry->source_size);
 				break;
 
 			case FILE_ACTION_REMOVE:
@@ -494,6 +494,10 @@ libpq_executeFileMap(filemap_t *map)
 			case FILE_ACTION_CREATE:
 				create_target(entry);
 				break;
+
+			case FILE_ACTION_UNDECIDED:
+				pg_fatal("no action decided for \"%s\"", entry->path);
+				break;
 		}
 	}
 
diff --git a/src/bin/pg_rewind/parsexlog.c b/src/bin/pg_rewind/parsexlog.c
index 2229c86f9af..2baeb74ae93 100644
--- a/src/bin/pg_rewind/parsexlog.c
+++ b/src/bin/pg_rewind/parsexlog.c
@@ -436,6 +436,6 @@ extractPageInfo(XLogReaderState *record)
 		if (forknum != MAIN_FORKNUM)
 			continue;
 
-		process_block_change(forknum, rnode, blkno);
+		process_target_wal_block_change(forknum, rnode, blkno);
 	}
 }
diff --git a/src/bin/pg_rewind/pg_rewind.c b/src/bin/pg_rewind/pg_rewind.c
index c9b9e480c0f..210984d302b 100644
--- a/src/bin/pg_rewind/pg_rewind.c
+++ b/src/bin/pg_rewind/pg_rewind.c
@@ -369,7 +369,7 @@ main(int argc, char **argv)
 				chkpttli);
 
 	/*
-	 * Build the filemap, by comparing the source and target data directories.
+	 * Collect information about all files in the target and source systems.
 	 */
 	filemap_create();
 	if (showprogress)
@@ -390,8 +390,12 @@ main(int argc, char **argv)
 		pg_log_info("reading WAL in target");
 	extractPageMap(datadir_target, chkptrec, lastcommontliIndex,
 				   ControlFile_target.checkPoint, restore_command);
-	filemap_finalize();
 
+	/*
+	 * We have collected all information we need from both systems. Decide
+	 * what to do with each file.
+	 */
+	filemap_finalize();
 	if (showprogress)
 		calculate_totals();
 
-- 
2.20.1


--------------D93EDEBFB124D563B723F4BD
Content-Type: text/x-patch; charset=UTF-8;
 name="0003-pg_rewind-Replace-the-hybrid-list-array-data-structu.patch"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
 filename*0="0003-pg_rewind-Replace-the-hybrid-list-array-data-structu.pa";
 filename*1="tch"



^ permalink  raw  reply  [nested|flat] 41+ messages in thread

* [PATCH 2/5] Refactor pg_rewind for more clear decision making.
@ 2020-08-19 12:34 Heikki Linnakangas <[email protected]>
  0 siblings, 0 replies; 41+ messages in thread

From: Heikki Linnakangas @ 2020-08-19 12:34 UTC (permalink / raw)

Deciding what to do with each file is now a separate step after all the
necessary information has been gathered. It is more clear that way.
Previously, the decision-making was divided between process_source_file()
and process_target_file(), and it was a bit hard to piece together what the
overall rules were.
---
 src/bin/pg_rewind/copy_fetch.c  |  14 +-
 src/bin/pg_rewind/file_ops.c    |  16 +-
 src/bin/pg_rewind/filemap.c     | 514 ++++++++++++++++----------------
 src/bin/pg_rewind/filemap.h     |  67 +++--
 src/bin/pg_rewind/libpq_fetch.c |  12 +-
 src/bin/pg_rewind/parsexlog.c   |   2 +-
 src/bin/pg_rewind/pg_rewind.c   |   8 +-
 7 files changed, 332 insertions(+), 301 deletions(-)

diff --git a/src/bin/pg_rewind/copy_fetch.c b/src/bin/pg_rewind/copy_fetch.c
index 1edab5f1867..18fad32600e 100644
--- a/src/bin/pg_rewind/copy_fetch.c
+++ b/src/bin/pg_rewind/copy_fetch.c
@@ -210,7 +210,7 @@ copy_executeFileMap(filemap_t *map)
 	for (i = 0; i < map->narray; i++)
 	{
 		entry = map->array[i];
-		execute_pagemap(&entry->pagemap, entry->path);
+		execute_pagemap(&entry->target_modified_pages, entry->path);
 
 		switch (entry->action)
 		{
@@ -219,16 +219,16 @@ copy_executeFileMap(filemap_t *map)
 				break;
 
 			case FILE_ACTION_COPY:
-				rewind_copy_file_range(entry->path, 0, entry->newsize, true);
+				rewind_copy_file_range(entry->path, 0, entry->source_size, true);
 				break;
 
 			case FILE_ACTION_TRUNCATE:
-				truncate_target_file(entry->path, entry->newsize);
+				truncate_target_file(entry->path, entry->source_size);
 				break;
 
 			case FILE_ACTION_COPY_TAIL:
-				rewind_copy_file_range(entry->path, entry->oldsize,
-									   entry->newsize, false);
+				rewind_copy_file_range(entry->path, entry->target_size,
+									   entry->source_size, false);
 				break;
 
 			case FILE_ACTION_CREATE:
@@ -238,6 +238,10 @@ copy_executeFileMap(filemap_t *map)
 			case FILE_ACTION_REMOVE:
 				remove_target(entry);
 				break;
+
+			case FILE_ACTION_UNDECIDED:
+				pg_fatal("no action decided for \"%s\"", entry->path);
+				break;
 		}
 	}
 
diff --git a/src/bin/pg_rewind/file_ops.c b/src/bin/pg_rewind/file_ops.c
index 55439db20ba..ec37d0b2e0d 100644
--- a/src/bin/pg_rewind/file_ops.c
+++ b/src/bin/pg_rewind/file_ops.c
@@ -126,8 +126,9 @@ void
 remove_target(file_entry_t *entry)
 {
 	Assert(entry->action == FILE_ACTION_REMOVE);
+	Assert(entry->target_exists);
 
-	switch (entry->type)
+	switch (entry->target_type)
 	{
 		case FILE_TYPE_DIRECTORY:
 			remove_target_dir(entry->path);
@@ -140,6 +141,10 @@ remove_target(file_entry_t *entry)
 		case FILE_TYPE_SYMLINK:
 			remove_target_symlink(entry->path);
 			break;
+
+		case FILE_TYPE_UNDEFINED:
+			pg_fatal("undefined file type for \"%s\"", entry->path);
+			break;
 	}
 }
 
@@ -147,21 +152,26 @@ void
 create_target(file_entry_t *entry)
 {
 	Assert(entry->action == FILE_ACTION_CREATE);
+	Assert(!entry->target_exists);
 
-	switch (entry->type)
+	switch (entry->source_type)
 	{
 		case FILE_TYPE_DIRECTORY:
 			create_target_dir(entry->path);
 			break;
 
 		case FILE_TYPE_SYMLINK:
-			create_target_symlink(entry->path, entry->link_target);
+			create_target_symlink(entry->path, entry->source_link_target);
 			break;
 
 		case FILE_TYPE_REGULAR:
 			/* can't happen. Regular files are created with open_target_file. */
 			pg_fatal("invalid action (CREATE) for regular file");
 			break;
+
+		case FILE_TYPE_UNDEFINED:
+			pg_fatal("undefined file type for \"%s\"", entry->path);
+			break;
 	}
 }
 
diff --git a/src/bin/pg_rewind/filemap.c b/src/bin/pg_rewind/filemap.c
index 1879257b66a..431e8e760e0 100644
--- a/src/bin/pg_rewind/filemap.c
+++ b/src/bin/pg_rewind/filemap.c
@@ -26,6 +26,8 @@ static bool isRelDataFile(const char *path);
 static char *datasegpath(RelFileNode rnode, ForkNumber forknum,
 						 BlockNumber segno);
 static int	path_cmp(const void *a, const void *b);
+
+static file_entry_t *get_filemap_entry(const char *path, bool create);
 static int	final_filemap_cmp(const void *a, const void *b);
 static void filemap_list_to_array(filemap_t *map);
 static bool check_file_excluded(const char *path, bool is_source);
@@ -146,6 +148,65 @@ filemap_create(void)
 	filemap = map;
 }
 
+/* Look up or create entry for 'path' */
+static file_entry_t *
+get_filemap_entry(const char *path, bool create)
+{
+	filemap_t  *map = filemap;
+	file_entry_t *entry;
+	file_entry_t **e;
+	file_entry_t key;
+	file_entry_t *key_ptr;
+
+	if (map->array)
+	{
+		key.path = (char *) path;
+		key_ptr = &key;
+		e = bsearch(&key_ptr, map->array, map->narray, sizeof(file_entry_t *),
+					path_cmp);
+	}
+	else
+		e = NULL;
+
+	if (e)
+		entry = *e;
+	else if (!create)
+		entry = NULL;
+	else
+	{
+		/* Create a new entry for this file */
+		entry = pg_malloc(sizeof(file_entry_t));
+		entry->path = pg_strdup(path);
+		entry->isrelfile = isRelDataFile(path);
+		entry->action = FILE_ACTION_UNDECIDED;
+
+		entry->target_exists = false;
+		entry->target_type = FILE_TYPE_UNDEFINED;
+		entry->target_size = 0;
+		entry->target_link_target = NULL;
+		entry->target_modified_pages.bitmap = NULL;
+		entry->target_modified_pages.bitmapsize = 0;
+
+		entry->source_exists = false;
+		entry->source_type = FILE_TYPE_UNDEFINED;
+		entry->source_size = 0;
+		entry->source_link_target = NULL;
+
+		entry->next = NULL;
+
+		if (map->last)
+		{
+			map->last->next = entry;
+			map->last = entry;
+		}
+		else
+			map->first = map->last = entry;
+		map->nlist++;
+	}
+
+	return entry;
+}
+
 /*
  * Callback for processing source file list.
  *
@@ -154,18 +215,19 @@ filemap_create(void)
  * exists in the target and whether the size matches.
  */
 void
-process_source_file(const char *path, file_type_t type, size_t newsize,
+process_source_file(const char *path, file_type_t type, size_t size,
 					const char *link_target)
 {
-	bool		exists;
-	char		localpath[MAXPGPATH];
-	struct stat statbuf;
-	filemap_t  *map = filemap;
-	file_action_t action = FILE_ACTION_NONE;
-	size_t		oldsize = 0;
 	file_entry_t *entry;
 
-	Assert(map->array == NULL);
+	Assert(filemap->array == NULL);
+
+	/*
+	 * Skip the control file. It is handled specially, after copying all the
+	 * other files.
+	 */
+	if (strcmp(path, "global/pg_control") == 0)
+		return;
 
 	/*
 	 * Skip any files matching the exclusion filters. This has the effect to
@@ -199,142 +261,12 @@ process_source_file(const char *path, file_type_t type, size_t newsize,
 	if (type != FILE_TYPE_REGULAR && isRelDataFile(path))
 		pg_fatal("data file \"%s\" in source is not a regular file", path);
 
-	snprintf(localpath, sizeof(localpath), "%s/%s", datadir_target, path);
-
-	/* Does the corresponding file exist in the target data dir? */
-	if (lstat(localpath, &statbuf) < 0)
-	{
-		if (errno != ENOENT)
-			pg_fatal("could not stat file \"%s\": %m",
-					 localpath);
-
-		exists = false;
-	}
-	else
-		exists = true;
-
-	switch (type)
-	{
-		case FILE_TYPE_DIRECTORY:
-			if (exists && !S_ISDIR(statbuf.st_mode) && strcmp(path, "pg_wal") != 0)
-			{
-				/* it's a directory in source, but not in target. Strange.. */
-				pg_fatal("\"%s\" is not a directory", localpath);
-			}
-
-			if (!exists)
-				action = FILE_ACTION_CREATE;
-			else
-				action = FILE_ACTION_NONE;
-			oldsize = 0;
-			break;
-
-		case FILE_TYPE_SYMLINK:
-			if (exists &&
-#ifndef WIN32
-				!S_ISLNK(statbuf.st_mode)
-#else
-				!pgwin32_is_junction(localpath)
-#endif
-				)
-			{
-				/*
-				 * It's a symbolic link in source, but not in target.
-				 * Strange..
-				 */
-				pg_fatal("\"%s\" is not a symbolic link", localpath);
-			}
-
-			if (!exists)
-				action = FILE_ACTION_CREATE;
-			else
-				action = FILE_ACTION_NONE;
-			oldsize = 0;
-			break;
-
-		case FILE_TYPE_REGULAR:
-			if (exists && !S_ISREG(statbuf.st_mode))
-				pg_fatal("\"%s\" is not a regular file", localpath);
-
-			if (!exists || !isRelDataFile(path))
-			{
-				/*
-				 * File exists in source, but not in target. Or it's a
-				 * non-data file that we have no special processing for. Copy
-				 * it in toto.
-				 *
-				 * An exception: PG_VERSIONs should be identical, but avoid
-				 * overwriting it for paranoia.
-				 */
-				if (pg_str_endswith(path, "PG_VERSION"))
-				{
-					action = FILE_ACTION_NONE;
-					oldsize = statbuf.st_size;
-				}
-				else
-				{
-					action = FILE_ACTION_COPY;
-					oldsize = 0;
-				}
-			}
-			else
-			{
-				/*
-				 * It's a data file that exists in both.
-				 *
-				 * If it's larger in target, we can truncate it. There will
-				 * also be a WAL record of the truncation in the source
-				 * system, so WAL replay would eventually truncate the target
-				 * too, but we might as well do it now.
-				 *
-				 * If it's smaller in the target, it means that it has been
-				 * truncated in the target, or enlarged in the source, or
-				 * both. If it was truncated in the target, we need to copy
-				 * the missing tail from the source system. If it was enlarged
-				 * in the source system, there will be WAL records in the
-				 * source system for the new blocks, so we wouldn't need to
-				 * copy them here. But we don't know which scenario we're
-				 * dealing with, and there's no harm in copying the missing
-				 * blocks now, so do it now.
-				 *
-				 * If it's the same size, do nothing here. Any blocks modified
-				 * in the target will be copied based on parsing the target
-				 * system's WAL, and any blocks modified in the source will be
-				 * updated after rewinding, when the source system's WAL is
-				 * replayed.
-				 */
-				oldsize = statbuf.st_size;
-				if (oldsize < newsize)
-					action = FILE_ACTION_COPY_TAIL;
-				else if (oldsize > newsize)
-					action = FILE_ACTION_TRUNCATE;
-				else
-					action = FILE_ACTION_NONE;
-			}
-			break;
-	}
-
-	/* Create a new entry for this file */
-	entry = pg_malloc(sizeof(file_entry_t));
-	entry->path = pg_strdup(path);
-	entry->type = type;
-	entry->action = action;
-	entry->oldsize = oldsize;
-	entry->newsize = newsize;
-	entry->link_target = link_target ? pg_strdup(link_target) : NULL;
-	entry->next = NULL;
-	entry->pagemap.bitmap = NULL;
-	entry->pagemap.bitmapsize = 0;
-	entry->isrelfile = isRelDataFile(path);
-
-	if (map->last)
-	{
-		map->last->next = entry;
-		map->last = entry;
-	}
-	else
-		map->first = map->last = entry;
-	map->nlist++;
+	/* Remember this source file */
+	entry = get_filemap_entry(path, true);
+	entry->source_exists = true;
+	entry->source_type = type;
+	entry->source_size = size;
+	entry->source_link_target = link_target ? pg_strdup(link_target) : NULL;
 }
 
 /*
@@ -345,33 +277,24 @@ process_source_file(const char *path, file_type_t type, size_t newsize,
  * deletion.
  */
 void
-process_target_file(const char *path, file_type_t type, size_t oldsize,
+process_target_file(const char *path, file_type_t type, size_t size,
 					const char *link_target)
 {
-	bool		exists;
-	char		localpath[MAXPGPATH];
-	struct stat statbuf;
-	file_entry_t key;
-	file_entry_t *key_ptr;
 	filemap_t  *map = filemap;
 	file_entry_t *entry;
 
+	/*
+	 * Skip the control file. It is handled specially, after copying all the
+	 * other files.
+	 */
+	if (strcmp(path, "global/pg_control") == 0)
+		return;
+
 	/*
 	 * Do not apply any exclusion filters here.  This has advantage to remove
 	 * from the target data folder all paths which have been filtered out from
 	 * the source data folder when processing the source files.
 	 */
-
-	snprintf(localpath, sizeof(localpath), "%s/%s", datadir_target, path);
-	if (lstat(localpath, &statbuf) < 0)
-	{
-		if (errno != ENOENT)
-			pg_fatal("could not stat file \"%s\": %m",
-					 localpath);
-
-		exists = false;
-	}
-
 	if (map->array == NULL)
 	{
 		/* on first call, initialize lookup array */
@@ -389,120 +312,76 @@ process_target_file(const char *path, file_type_t type, size_t oldsize,
 	}
 
 	/*
-	 * Like in process_source_file, pretend that xlog is always a  directory.
+	 * Like in process_source_file, pretend that pg_wal is always a directory.
 	 */
 	if (strcmp(path, "pg_wal") == 0 && type == FILE_TYPE_SYMLINK)
 		type = FILE_TYPE_DIRECTORY;
 
-	key.path = (char *) path;
-	key_ptr = &key;
-	exists = (bsearch(&key_ptr, map->array, map->narray, sizeof(file_entry_t *),
-					  path_cmp) != NULL);
-
-	/* Remove any file or folder that doesn't exist in the source system. */
-	if (!exists)
-	{
-		entry = pg_malloc(sizeof(file_entry_t));
-		entry->path = pg_strdup(path);
-		entry->type = type;
-		entry->action = FILE_ACTION_REMOVE;
-		entry->oldsize = oldsize;
-		entry->newsize = 0;
-		entry->link_target = link_target ? pg_strdup(link_target) : NULL;
-		entry->next = NULL;
-		entry->pagemap.bitmap = NULL;
-		entry->pagemap.bitmapsize = 0;
-		entry->isrelfile = isRelDataFile(path);
-
-		if (map->last == NULL)
-			map->first = entry;
-		else
-			map->last->next = entry;
-		map->last = entry;
-		map->nlist++;
-	}
-	else
-	{
-		/*
-		 * We already handled all files that exist in the source system in
-		 * process_source_file().
-		 */
-	}
+	/* Remember this target file */
+	entry = get_filemap_entry(path, true);
+	entry->target_exists = true;
+	entry->target_type = type;
+	entry->target_size = size;
+	entry->target_link_target = link_target ? pg_strdup(link_target) : NULL;
 }
 
 /*
  * This callback gets called while we read the WAL in the target, for every
- * block that have changed in the target system. It makes note of all the
+ * block that have changed in the target system.  It makes note of all the
  * changed blocks in the pagemap of the file.
+ *
+ * NOTE: All the files on both systems must have already been added to the
+ * file map!
  */
 void
-process_block_change(ForkNumber forknum, RelFileNode rnode, BlockNumber blkno)
+process_target_wal_block_change(ForkNumber forknum, RelFileNode rnode,
+								BlockNumber blkno)
 {
 	char	   *path;
-	file_entry_t key;
-	file_entry_t *key_ptr;
 	file_entry_t *entry;
 	BlockNumber blkno_inseg;
 	int			segno;
-	filemap_t  *map = filemap;
-	file_entry_t **e;
 
-	Assert(map->array);
+	Assert(filemap->array);
 
 	segno = blkno / RELSEG_SIZE;
 	blkno_inseg = blkno % RELSEG_SIZE;
 
 	path = datasegpath(rnode, forknum, segno);
-
-	key.path = (char *) path;
-	key_ptr = &key;
-
-	e = bsearch(&key_ptr, map->array, map->narray, sizeof(file_entry_t *),
-				path_cmp);
-	if (e)
-		entry = *e;
-	else
-		entry = NULL;
+	entry = get_filemap_entry(path, false);
 	pfree(path);
 
 	if (entry)
 	{
+		int64		end_offset;
+
 		Assert(entry->isrelfile);
 
-		switch (entry->action)
-		{
-			case FILE_ACTION_NONE:
-			case FILE_ACTION_TRUNCATE:
-				/* skip if we're truncating away the modified block anyway */
-				if ((blkno_inseg + 1) * BLCKSZ <= entry->newsize)
-					datapagemap_add(&entry->pagemap, blkno_inseg);
-				break;
-
-			case FILE_ACTION_COPY_TAIL:
-
-				/*
-				 * skip the modified block if it is part of the "tail" that
-				 * we're copying anyway.
-				 */
-				if ((blkno_inseg + 1) * BLCKSZ <= entry->oldsize)
-					datapagemap_add(&entry->pagemap, blkno_inseg);
-				break;
-
-			case FILE_ACTION_COPY:
-			case FILE_ACTION_REMOVE:
-				break;
-
-			case FILE_ACTION_CREATE:
-				pg_fatal("unexpected page modification for directory or symbolic link \"%s\"", entry->path);
-		}
+		if (entry->target_type != FILE_TYPE_REGULAR)
+			pg_fatal("unexpected page modification for directory or symbolic link \"%s\"",
+					 entry->path);
+
+		/*
+		 * If the block beyond the EOF in the source system, no need to
+		 * remember it now, because we're going to truncate it away from the
+		 * target anyway. Also no need to remember the block if it's beyond
+		 * the current EOF in the target system; we will copy it over with the
+		 * "tail" from the source system, anyway.
+		 */
+		end_offset = (blkno_inseg + 1) * BLCKSZ;
+		if (end_offset <= entry->source_size &&
+			end_offset <= entry->target_size)
+			datapagemap_add(&entry->target_modified_pages, blkno_inseg);
 	}
 	else
 	{
 		/*
 		 * If we don't have any record of this file in the file map, it means
-		 * that it's a relation that doesn't exist in the source system, and
-		 * it was subsequently removed in the target system, too. We can
-		 * safely ignore it.
+		 * that it's a relation that doesn't exist in the source system.  It
+		 * could exist in the target system; we haven't moved the target-only
+		 * entries from the linked list to the array yet!  But in any case, if
+		 * it doesn't exist in the source it will be removed from the target
+		 * too, and we can safely ignore it.
 		 */
 	}
 }
@@ -593,16 +472,6 @@ filemap_list_to_array(filemap_t *map)
 	map->first = map->last = NULL;
 }
 
-void
-filemap_finalize(void)
-{
-	filemap_t  *map = filemap;
-
-	filemap_list_to_array(map);
-	qsort(map->array, map->narray, sizeof(file_entry_t *),
-		  final_filemap_cmp);
-}
-
 static const char *
 action_to_str(file_action_t action)
 {
@@ -643,26 +512,26 @@ calculate_totals(void)
 	{
 		entry = map->array[i];
 
-		if (entry->type != FILE_TYPE_REGULAR)
+		if (entry->source_type != FILE_TYPE_REGULAR)
 			continue;
 
-		map->total_size += entry->newsize;
+		map->total_size += entry->source_size;
 
 		if (entry->action == FILE_ACTION_COPY)
 		{
-			map->fetch_size += entry->newsize;
+			map->fetch_size += entry->source_size;
 			continue;
 		}
 
 		if (entry->action == FILE_ACTION_COPY_TAIL)
-			map->fetch_size += (entry->newsize - entry->oldsize);
+			map->fetch_size += (entry->source_size - entry->target_size);
 
-		if (entry->pagemap.bitmapsize > 0)
+		if (entry->target_modified_pages.bitmapsize > 0)
 		{
 			datapagemap_iterator_t *iter;
 			BlockNumber blk;
 
-			iter = datapagemap_iterate(&entry->pagemap);
+			iter = datapagemap_iterate(&entry->target_modified_pages);
 			while (datapagemap_next(iter, &blk))
 				map->fetch_size += BLCKSZ;
 
@@ -682,13 +551,13 @@ print_filemap(void)
 	{
 		entry = map->array[i];
 		if (entry->action != FILE_ACTION_NONE ||
-			entry->pagemap.bitmapsize > 0)
+			entry->target_modified_pages.bitmapsize > 0)
 		{
 			pg_log_debug("%s (%s)", entry->path,
 						 action_to_str(entry->action));
 
-			if (entry->pagemap.bitmapsize > 0)
-				datapagemap_print(&entry->pagemap);
+			if (entry->target_modified_pages.bitmapsize > 0)
+				datapagemap_print(&entry->target_modified_pages);
 		}
 	}
 	fflush(stdout);
@@ -837,3 +706,126 @@ final_filemap_cmp(const void *a, const void *b)
 	else
 		return strcmp(fa->path, fb->path);
 }
+
+/*
+ * Decide what to do with each file.
+ */
+void
+filemap_finalize()
+{
+	int			i;
+
+	filemap_list_to_array(filemap);
+
+	for (i = 0; i < filemap->narray; i++)
+	{
+		file_entry_t *entry = filemap->array[i];
+		file_action_t action;
+
+		if (!entry->target_exists && entry->source_exists)
+		{
+			/*
+			 * File exists in source, but not in target. Copy
+			 * it in toto. (If it's a relation data file, WAL replay
+			 * after rewinding should re-create it anyway. But there's
+			 * no harm in copying it now.)
+			 */
+			if (entry->source_type == FILE_TYPE_DIRECTORY ||
+				entry->source_type == FILE_TYPE_SYMLINK)
+			{
+				action = FILE_ACTION_CREATE;
+			}
+			else
+				action = FILE_ACTION_COPY;
+		}
+		else if (entry->target_exists && !entry->source_exists)
+		{
+			/* Delete file from target */
+			action = FILE_ACTION_REMOVE;
+		}
+		else if (entry->target_exists && entry->source_exists)
+		{
+			/* File exists in both systems */
+			if (entry->source_type != entry->target_type)
+			{
+				/* But it's a different kind of object. Strange.. */
+				pg_fatal("file \"%s\" is of different type in source and target", entry->path);
+			}
+
+			switch (entry->source_type)
+			{
+				case FILE_TYPE_DIRECTORY:
+					action = FILE_ACTION_NONE;
+					break;
+
+				case FILE_TYPE_SYMLINK:
+					// FIXME: Check if it points to the same target?
+					action = FILE_ACTION_NONE;
+					break;
+
+				case FILE_TYPE_REGULAR:
+					if (!entry->isrelfile)
+					{
+						/*
+						 * It's a non-data file that we have no special processing for. Copy
+						 * it in toto.
+						 *
+						 * An exception: PG_VERSIONs should be identical, but avoid
+						 * overwriting it for paranoia.
+						 */
+						if (!pg_str_endswith(entry->path, "PG_VERSION"))
+							action = FILE_ACTION_COPY;
+						else
+							action = FILE_ACTION_NONE;
+					}
+					else
+					{
+						/*
+						 * It's a data file that exists in both systems.
+						 *
+						 * If it's larger in target, we can truncate it. There will
+						 * also be a WAL record of the truncation in the source
+						 * system, so WAL replay would eventually truncate the target
+						 * too, but we might as well do it now.
+						 *
+						 * If it's smaller in the target, it means that it has been
+						 * truncated in the target, or enlarged in the source, or
+						 * both. If it was truncated in the target, we need to copy
+						 * the missing tail from the source system. If it was enlarged
+						 * in the source system, there will be WAL records in the
+						 * source system for the new blocks, so we wouldn't need to
+						 * copy them here. But we don't know which scenario we're
+						 * dealing with, and there's no harm in copying the missing
+						 * blocks now, so do it now.
+						 *
+						 * If it's the same size, do nothing here. Any blocks modified
+						 * in the target will be copied based on parsing the target
+						 * system's WAL, and any blocks modified in the source will be
+						 * updated after rewinding, when the source system's WAL is
+						 * replayed.
+						 */
+						if (entry->target_size < entry->source_size)
+							action = FILE_ACTION_COPY_TAIL;
+						else if (entry->target_size > entry->source_size)
+							action = FILE_ACTION_TRUNCATE;
+						else
+							action = FILE_ACTION_NONE;
+					}
+					break;
+				case FILE_TYPE_UNDEFINED:
+					pg_fatal("unknown file type for \"%s\"", entry->path);
+					break;
+			}
+		}
+		else
+		{
+			/* Doesn't exist in either server. Why does it have an entry at all?? */
+			action = FILE_ACTION_NONE;
+		}
+		entry->action = action;
+	}
+
+	/* Sort the actions to the order that they should be performed */
+	qsort(filemap->array, filemap->narray, sizeof(file_entry_t *),
+		  final_filemap_cmp);
+}
diff --git a/src/bin/pg_rewind/filemap.h b/src/bin/pg_rewind/filemap.h
index 0cb7425170c..a5e8df57f40 100644
--- a/src/bin/pg_rewind/filemap.h
+++ b/src/bin/pg_rewind/filemap.h
@@ -14,17 +14,21 @@
 
 /*
  * For every file found in the local or remote system, we have a file entry
- * which says what we are going to do with the file. For relation files,
- * there is also a page map, marking pages in the file that were changed
- * locally.
- *
- * The enum values are sorted in the order we want actions to be processed.
+ * that contains information about the file on both systems.  For relation
+ * files, there is also a page map that marks pages in the file that were
+ * changed in the target after the last common checkpoint.  Each entry also
+ * contains an 'action' field, which says what we are going to do with the
+ * file.
  */
+
+/* these enum values are sorted in the order we want actions to be processed */
 typedef enum
 {
+	FILE_ACTION_UNDECIDED = 0,	/* not decided yet */
+
 	FILE_ACTION_CREATE,			/* create local directory or symbolic link */
 	FILE_ACTION_COPY,			/* copy whole file, overwriting if exists */
-	FILE_ACTION_COPY_TAIL,		/* copy tail from 'oldsize' to 'newsize' */
+	FILE_ACTION_COPY_TAIL,		/* copy tail from 'source_size' to 'target_size' */
 	FILE_ACTION_NONE,			/* no action (we might still copy modified
 								 * blocks based on the parsed WAL) */
 	FILE_ACTION_TRUNCATE,		/* truncate local file to 'newsize' bytes */
@@ -33,6 +37,8 @@ typedef enum
 
 typedef enum
 {
+	FILE_TYPE_UNDEFINED = 0,
+
 	FILE_TYPE_REGULAR,
 	FILE_TYPE_DIRECTORY,
 	FILE_TYPE_SYMLINK
@@ -41,19 +47,30 @@ typedef enum
 typedef struct file_entry_t
 {
 	char	   *path;
-	file_type_t type;
+	bool		isrelfile;		/* is it a relation data file? */
 
-	file_action_t action;
+	/*
+	 * Status of the file in the target.
+	 */
+	bool		target_exists;
+	file_type_t target_type;
+	size_t		target_size; /* for a regular file */
+	char	   *target_link_target; /* for a symlink */
 
-	/* for a regular file */
-	size_t		oldsize;
-	size_t		newsize;
-	bool		isrelfile;		/* is it a relation data file? */
+	datapagemap_t target_modified_pages;
 
-	datapagemap_t pagemap;
+	/*
+	 * Status of the file in the source.
+	 */
+	bool		source_exists;
+	file_type_t source_type;
+	size_t		source_size;
+	char	   *source_link_target; /* for a symlink */
 
-	/* for a symlink */
-	char	   *link_target;
+	/*
+	 * What will we do to the file?
+	 */
+	file_action_t action;
 
 	struct file_entry_t *next;
 } file_entry_t;
@@ -70,20 +87,19 @@ typedef struct filemap_t
 
 	/*
 	 * After processing all the remote files, the entries in the linked list
-	 * are moved to this array. After processing local files, too, all the
+	 * are moved to this array.  After processing local files, too, all the
 	 * local entries are added to the array by filemap_finalize, and sorted in
-	 * the final order. After filemap_finalize, all the entries are in the
+	 * the final order.  After filemap_finalize, all the entries are in the
 	 * array, and the linked list is empty.
 	 */
 	file_entry_t **array;
 	int			narray;			/* current length of array */
 
 	/*
-	 * Summary information. total_size is the total size of the source
-	 * cluster, and fetch_size is the number of bytes that needs to be copied.
+	 * Summary information.
 	 */
-	uint64		total_size;
-	uint64		fetch_size;
+	uint64		total_size;		/* total size of the source cluster */
+	uint64		fetch_size;		/* number of bytes that needs to be copied */
 } filemap_t;
 
 extern filemap_t *filemap;
@@ -94,11 +110,12 @@ extern void print_filemap(void);
 
 /* Functions for populating the filemap */
 extern void process_source_file(const char *path, file_type_t type,
-								size_t newsize, const char *link_target);
+								size_t size, const char *link_target);
 extern void process_target_file(const char *path, file_type_t type,
-								size_t newsize, const char *link_target);
-extern void process_block_change(ForkNumber forknum, RelFileNode rnode,
-								 BlockNumber blkno);
+								size_t size, const char *link_target);
+extern void process_target_wal_block_change(ForkNumber forknum,
+											RelFileNode rnode,
+											BlockNumber blkno);
 extern void filemap_finalize(void);
 
 #endif							/* FILEMAP_H */
diff --git a/src/bin/pg_rewind/libpq_fetch.c b/src/bin/pg_rewind/libpq_fetch.c
index bf4dfc23b96..7fc9161b8c8 100644
--- a/src/bin/pg_rewind/libpq_fetch.c
+++ b/src/bin/pg_rewind/libpq_fetch.c
@@ -465,7 +465,7 @@ libpq_executeFileMap(filemap_t *map)
 		entry = map->array[i];
 
 		/* If this is a relation file, copy the modified blocks */
-		execute_pagemap(&entry->pagemap, entry->path);
+		execute_pagemap(&entry->target_modified_pages, entry->path);
 
 		switch (entry->action)
 		{
@@ -476,15 +476,15 @@ libpq_executeFileMap(filemap_t *map)
 			case FILE_ACTION_COPY:
 				/* Truncate the old file out of the way, if any */
 				open_target_file(entry->path, true);
-				fetch_file_range(entry->path, 0, entry->newsize);
+				fetch_file_range(entry->path, 0, entry->source_size);
 				break;
 
 			case FILE_ACTION_TRUNCATE:
-				truncate_target_file(entry->path, entry->newsize);
+				truncate_target_file(entry->path, entry->source_size);
 				break;
 
 			case FILE_ACTION_COPY_TAIL:
-				fetch_file_range(entry->path, entry->oldsize, entry->newsize);
+				fetch_file_range(entry->path, entry->target_size, entry->source_size);
 				break;
 
 			case FILE_ACTION_REMOVE:
@@ -494,6 +494,10 @@ libpq_executeFileMap(filemap_t *map)
 			case FILE_ACTION_CREATE:
 				create_target(entry);
 				break;
+
+			case FILE_ACTION_UNDECIDED:
+				pg_fatal("no action decided for \"%s\"", entry->path);
+				break;
 		}
 	}
 
diff --git a/src/bin/pg_rewind/parsexlog.c b/src/bin/pg_rewind/parsexlog.c
index 2229c86f9af..2baeb74ae93 100644
--- a/src/bin/pg_rewind/parsexlog.c
+++ b/src/bin/pg_rewind/parsexlog.c
@@ -436,6 +436,6 @@ extractPageInfo(XLogReaderState *record)
 		if (forknum != MAIN_FORKNUM)
 			continue;
 
-		process_block_change(forknum, rnode, blkno);
+		process_target_wal_block_change(forknum, rnode, blkno);
 	}
 }
diff --git a/src/bin/pg_rewind/pg_rewind.c b/src/bin/pg_rewind/pg_rewind.c
index c9b9e480c0f..210984d302b 100644
--- a/src/bin/pg_rewind/pg_rewind.c
+++ b/src/bin/pg_rewind/pg_rewind.c
@@ -369,7 +369,7 @@ main(int argc, char **argv)
 				chkpttli);
 
 	/*
-	 * Build the filemap, by comparing the source and target data directories.
+	 * Collect information about all files in the target and source systems.
 	 */
 	filemap_create();
 	if (showprogress)
@@ -390,8 +390,12 @@ main(int argc, char **argv)
 		pg_log_info("reading WAL in target");
 	extractPageMap(datadir_target, chkptrec, lastcommontliIndex,
 				   ControlFile_target.checkPoint, restore_command);
-	filemap_finalize();
 
+	/*
+	 * We have collected all information we need from both systems. Decide
+	 * what to do with each file.
+	 */
+	filemap_finalize();
 	if (showprogress)
 		calculate_totals();
 
-- 
2.20.1


--------------D93EDEBFB124D563B723F4BD
Content-Type: text/x-patch; charset=UTF-8;
 name="0003-pg_rewind-Replace-the-hybrid-list-array-data-structu.patch"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
 filename*0="0003-pg_rewind-Replace-the-hybrid-list-array-data-structu.pa";
 filename*1="tch"



^ permalink  raw  reply  [nested|flat] 41+ messages in thread

* [PATCH 2/5] Refactor pg_rewind for more clear decision making.
@ 2020-08-19 12:34 Heikki Linnakangas <[email protected]>
  0 siblings, 0 replies; 41+ messages in thread

From: Heikki Linnakangas @ 2020-08-19 12:34 UTC (permalink / raw)

Deciding what to do with each file is now a separate step after all the
necessary information has been gathered. It is more clear that way.
Previously, the decision-making was divided between process_source_file()
and process_target_file(), and it was a bit hard to piece together what the
overall rules were.
---
 src/bin/pg_rewind/copy_fetch.c  |  14 +-
 src/bin/pg_rewind/file_ops.c    |  16 +-
 src/bin/pg_rewind/filemap.c     | 514 ++++++++++++++++----------------
 src/bin/pg_rewind/filemap.h     |  67 +++--
 src/bin/pg_rewind/libpq_fetch.c |  12 +-
 src/bin/pg_rewind/parsexlog.c   |   2 +-
 src/bin/pg_rewind/pg_rewind.c   |   8 +-
 7 files changed, 332 insertions(+), 301 deletions(-)

diff --git a/src/bin/pg_rewind/copy_fetch.c b/src/bin/pg_rewind/copy_fetch.c
index 1edab5f1867..18fad32600e 100644
--- a/src/bin/pg_rewind/copy_fetch.c
+++ b/src/bin/pg_rewind/copy_fetch.c
@@ -210,7 +210,7 @@ copy_executeFileMap(filemap_t *map)
 	for (i = 0; i < map->narray; i++)
 	{
 		entry = map->array[i];
-		execute_pagemap(&entry->pagemap, entry->path);
+		execute_pagemap(&entry->target_modified_pages, entry->path);
 
 		switch (entry->action)
 		{
@@ -219,16 +219,16 @@ copy_executeFileMap(filemap_t *map)
 				break;
 
 			case FILE_ACTION_COPY:
-				rewind_copy_file_range(entry->path, 0, entry->newsize, true);
+				rewind_copy_file_range(entry->path, 0, entry->source_size, true);
 				break;
 
 			case FILE_ACTION_TRUNCATE:
-				truncate_target_file(entry->path, entry->newsize);
+				truncate_target_file(entry->path, entry->source_size);
 				break;
 
 			case FILE_ACTION_COPY_TAIL:
-				rewind_copy_file_range(entry->path, entry->oldsize,
-									   entry->newsize, false);
+				rewind_copy_file_range(entry->path, entry->target_size,
+									   entry->source_size, false);
 				break;
 
 			case FILE_ACTION_CREATE:
@@ -238,6 +238,10 @@ copy_executeFileMap(filemap_t *map)
 			case FILE_ACTION_REMOVE:
 				remove_target(entry);
 				break;
+
+			case FILE_ACTION_UNDECIDED:
+				pg_fatal("no action decided for \"%s\"", entry->path);
+				break;
 		}
 	}
 
diff --git a/src/bin/pg_rewind/file_ops.c b/src/bin/pg_rewind/file_ops.c
index 55439db20ba..ec37d0b2e0d 100644
--- a/src/bin/pg_rewind/file_ops.c
+++ b/src/bin/pg_rewind/file_ops.c
@@ -126,8 +126,9 @@ void
 remove_target(file_entry_t *entry)
 {
 	Assert(entry->action == FILE_ACTION_REMOVE);
+	Assert(entry->target_exists);
 
-	switch (entry->type)
+	switch (entry->target_type)
 	{
 		case FILE_TYPE_DIRECTORY:
 			remove_target_dir(entry->path);
@@ -140,6 +141,10 @@ remove_target(file_entry_t *entry)
 		case FILE_TYPE_SYMLINK:
 			remove_target_symlink(entry->path);
 			break;
+
+		case FILE_TYPE_UNDEFINED:
+			pg_fatal("undefined file type for \"%s\"", entry->path);
+			break;
 	}
 }
 
@@ -147,21 +152,26 @@ void
 create_target(file_entry_t *entry)
 {
 	Assert(entry->action == FILE_ACTION_CREATE);
+	Assert(!entry->target_exists);
 
-	switch (entry->type)
+	switch (entry->source_type)
 	{
 		case FILE_TYPE_DIRECTORY:
 			create_target_dir(entry->path);
 			break;
 
 		case FILE_TYPE_SYMLINK:
-			create_target_symlink(entry->path, entry->link_target);
+			create_target_symlink(entry->path, entry->source_link_target);
 			break;
 
 		case FILE_TYPE_REGULAR:
 			/* can't happen. Regular files are created with open_target_file. */
 			pg_fatal("invalid action (CREATE) for regular file");
 			break;
+
+		case FILE_TYPE_UNDEFINED:
+			pg_fatal("undefined file type for \"%s\"", entry->path);
+			break;
 	}
 }
 
diff --git a/src/bin/pg_rewind/filemap.c b/src/bin/pg_rewind/filemap.c
index 1879257b66a..431e8e760e0 100644
--- a/src/bin/pg_rewind/filemap.c
+++ b/src/bin/pg_rewind/filemap.c
@@ -26,6 +26,8 @@ static bool isRelDataFile(const char *path);
 static char *datasegpath(RelFileNode rnode, ForkNumber forknum,
 						 BlockNumber segno);
 static int	path_cmp(const void *a, const void *b);
+
+static file_entry_t *get_filemap_entry(const char *path, bool create);
 static int	final_filemap_cmp(const void *a, const void *b);
 static void filemap_list_to_array(filemap_t *map);
 static bool check_file_excluded(const char *path, bool is_source);
@@ -146,6 +148,65 @@ filemap_create(void)
 	filemap = map;
 }
 
+/* Look up or create entry for 'path' */
+static file_entry_t *
+get_filemap_entry(const char *path, bool create)
+{
+	filemap_t  *map = filemap;
+	file_entry_t *entry;
+	file_entry_t **e;
+	file_entry_t key;
+	file_entry_t *key_ptr;
+
+	if (map->array)
+	{
+		key.path = (char *) path;
+		key_ptr = &key;
+		e = bsearch(&key_ptr, map->array, map->narray, sizeof(file_entry_t *),
+					path_cmp);
+	}
+	else
+		e = NULL;
+
+	if (e)
+		entry = *e;
+	else if (!create)
+		entry = NULL;
+	else
+	{
+		/* Create a new entry for this file */
+		entry = pg_malloc(sizeof(file_entry_t));
+		entry->path = pg_strdup(path);
+		entry->isrelfile = isRelDataFile(path);
+		entry->action = FILE_ACTION_UNDECIDED;
+
+		entry->target_exists = false;
+		entry->target_type = FILE_TYPE_UNDEFINED;
+		entry->target_size = 0;
+		entry->target_link_target = NULL;
+		entry->target_modified_pages.bitmap = NULL;
+		entry->target_modified_pages.bitmapsize = 0;
+
+		entry->source_exists = false;
+		entry->source_type = FILE_TYPE_UNDEFINED;
+		entry->source_size = 0;
+		entry->source_link_target = NULL;
+
+		entry->next = NULL;
+
+		if (map->last)
+		{
+			map->last->next = entry;
+			map->last = entry;
+		}
+		else
+			map->first = map->last = entry;
+		map->nlist++;
+	}
+
+	return entry;
+}
+
 /*
  * Callback for processing source file list.
  *
@@ -154,18 +215,19 @@ filemap_create(void)
  * exists in the target and whether the size matches.
  */
 void
-process_source_file(const char *path, file_type_t type, size_t newsize,
+process_source_file(const char *path, file_type_t type, size_t size,
 					const char *link_target)
 {
-	bool		exists;
-	char		localpath[MAXPGPATH];
-	struct stat statbuf;
-	filemap_t  *map = filemap;
-	file_action_t action = FILE_ACTION_NONE;
-	size_t		oldsize = 0;
 	file_entry_t *entry;
 
-	Assert(map->array == NULL);
+	Assert(filemap->array == NULL);
+
+	/*
+	 * Skip the control file. It is handled specially, after copying all the
+	 * other files.
+	 */
+	if (strcmp(path, "global/pg_control") == 0)
+		return;
 
 	/*
 	 * Skip any files matching the exclusion filters. This has the effect to
@@ -199,142 +261,12 @@ process_source_file(const char *path, file_type_t type, size_t newsize,
 	if (type != FILE_TYPE_REGULAR && isRelDataFile(path))
 		pg_fatal("data file \"%s\" in source is not a regular file", path);
 
-	snprintf(localpath, sizeof(localpath), "%s/%s", datadir_target, path);
-
-	/* Does the corresponding file exist in the target data dir? */
-	if (lstat(localpath, &statbuf) < 0)
-	{
-		if (errno != ENOENT)
-			pg_fatal("could not stat file \"%s\": %m",
-					 localpath);
-
-		exists = false;
-	}
-	else
-		exists = true;
-
-	switch (type)
-	{
-		case FILE_TYPE_DIRECTORY:
-			if (exists && !S_ISDIR(statbuf.st_mode) && strcmp(path, "pg_wal") != 0)
-			{
-				/* it's a directory in source, but not in target. Strange.. */
-				pg_fatal("\"%s\" is not a directory", localpath);
-			}
-
-			if (!exists)
-				action = FILE_ACTION_CREATE;
-			else
-				action = FILE_ACTION_NONE;
-			oldsize = 0;
-			break;
-
-		case FILE_TYPE_SYMLINK:
-			if (exists &&
-#ifndef WIN32
-				!S_ISLNK(statbuf.st_mode)
-#else
-				!pgwin32_is_junction(localpath)
-#endif
-				)
-			{
-				/*
-				 * It's a symbolic link in source, but not in target.
-				 * Strange..
-				 */
-				pg_fatal("\"%s\" is not a symbolic link", localpath);
-			}
-
-			if (!exists)
-				action = FILE_ACTION_CREATE;
-			else
-				action = FILE_ACTION_NONE;
-			oldsize = 0;
-			break;
-
-		case FILE_TYPE_REGULAR:
-			if (exists && !S_ISREG(statbuf.st_mode))
-				pg_fatal("\"%s\" is not a regular file", localpath);
-
-			if (!exists || !isRelDataFile(path))
-			{
-				/*
-				 * File exists in source, but not in target. Or it's a
-				 * non-data file that we have no special processing for. Copy
-				 * it in toto.
-				 *
-				 * An exception: PG_VERSIONs should be identical, but avoid
-				 * overwriting it for paranoia.
-				 */
-				if (pg_str_endswith(path, "PG_VERSION"))
-				{
-					action = FILE_ACTION_NONE;
-					oldsize = statbuf.st_size;
-				}
-				else
-				{
-					action = FILE_ACTION_COPY;
-					oldsize = 0;
-				}
-			}
-			else
-			{
-				/*
-				 * It's a data file that exists in both.
-				 *
-				 * If it's larger in target, we can truncate it. There will
-				 * also be a WAL record of the truncation in the source
-				 * system, so WAL replay would eventually truncate the target
-				 * too, but we might as well do it now.
-				 *
-				 * If it's smaller in the target, it means that it has been
-				 * truncated in the target, or enlarged in the source, or
-				 * both. If it was truncated in the target, we need to copy
-				 * the missing tail from the source system. If it was enlarged
-				 * in the source system, there will be WAL records in the
-				 * source system for the new blocks, so we wouldn't need to
-				 * copy them here. But we don't know which scenario we're
-				 * dealing with, and there's no harm in copying the missing
-				 * blocks now, so do it now.
-				 *
-				 * If it's the same size, do nothing here. Any blocks modified
-				 * in the target will be copied based on parsing the target
-				 * system's WAL, and any blocks modified in the source will be
-				 * updated after rewinding, when the source system's WAL is
-				 * replayed.
-				 */
-				oldsize = statbuf.st_size;
-				if (oldsize < newsize)
-					action = FILE_ACTION_COPY_TAIL;
-				else if (oldsize > newsize)
-					action = FILE_ACTION_TRUNCATE;
-				else
-					action = FILE_ACTION_NONE;
-			}
-			break;
-	}
-
-	/* Create a new entry for this file */
-	entry = pg_malloc(sizeof(file_entry_t));
-	entry->path = pg_strdup(path);
-	entry->type = type;
-	entry->action = action;
-	entry->oldsize = oldsize;
-	entry->newsize = newsize;
-	entry->link_target = link_target ? pg_strdup(link_target) : NULL;
-	entry->next = NULL;
-	entry->pagemap.bitmap = NULL;
-	entry->pagemap.bitmapsize = 0;
-	entry->isrelfile = isRelDataFile(path);
-
-	if (map->last)
-	{
-		map->last->next = entry;
-		map->last = entry;
-	}
-	else
-		map->first = map->last = entry;
-	map->nlist++;
+	/* Remember this source file */
+	entry = get_filemap_entry(path, true);
+	entry->source_exists = true;
+	entry->source_type = type;
+	entry->source_size = size;
+	entry->source_link_target = link_target ? pg_strdup(link_target) : NULL;
 }
 
 /*
@@ -345,33 +277,24 @@ process_source_file(const char *path, file_type_t type, size_t newsize,
  * deletion.
  */
 void
-process_target_file(const char *path, file_type_t type, size_t oldsize,
+process_target_file(const char *path, file_type_t type, size_t size,
 					const char *link_target)
 {
-	bool		exists;
-	char		localpath[MAXPGPATH];
-	struct stat statbuf;
-	file_entry_t key;
-	file_entry_t *key_ptr;
 	filemap_t  *map = filemap;
 	file_entry_t *entry;
 
+	/*
+	 * Skip the control file. It is handled specially, after copying all the
+	 * other files.
+	 */
+	if (strcmp(path, "global/pg_control") == 0)
+		return;
+
 	/*
 	 * Do not apply any exclusion filters here.  This has advantage to remove
 	 * from the target data folder all paths which have been filtered out from
 	 * the source data folder when processing the source files.
 	 */
-
-	snprintf(localpath, sizeof(localpath), "%s/%s", datadir_target, path);
-	if (lstat(localpath, &statbuf) < 0)
-	{
-		if (errno != ENOENT)
-			pg_fatal("could not stat file \"%s\": %m",
-					 localpath);
-
-		exists = false;
-	}
-
 	if (map->array == NULL)
 	{
 		/* on first call, initialize lookup array */
@@ -389,120 +312,76 @@ process_target_file(const char *path, file_type_t type, size_t oldsize,
 	}
 
 	/*
-	 * Like in process_source_file, pretend that xlog is always a  directory.
+	 * Like in process_source_file, pretend that pg_wal is always a directory.
 	 */
 	if (strcmp(path, "pg_wal") == 0 && type == FILE_TYPE_SYMLINK)
 		type = FILE_TYPE_DIRECTORY;
 
-	key.path = (char *) path;
-	key_ptr = &key;
-	exists = (bsearch(&key_ptr, map->array, map->narray, sizeof(file_entry_t *),
-					  path_cmp) != NULL);
-
-	/* Remove any file or folder that doesn't exist in the source system. */
-	if (!exists)
-	{
-		entry = pg_malloc(sizeof(file_entry_t));
-		entry->path = pg_strdup(path);
-		entry->type = type;
-		entry->action = FILE_ACTION_REMOVE;
-		entry->oldsize = oldsize;
-		entry->newsize = 0;
-		entry->link_target = link_target ? pg_strdup(link_target) : NULL;
-		entry->next = NULL;
-		entry->pagemap.bitmap = NULL;
-		entry->pagemap.bitmapsize = 0;
-		entry->isrelfile = isRelDataFile(path);
-
-		if (map->last == NULL)
-			map->first = entry;
-		else
-			map->last->next = entry;
-		map->last = entry;
-		map->nlist++;
-	}
-	else
-	{
-		/*
-		 * We already handled all files that exist in the source system in
-		 * process_source_file().
-		 */
-	}
+	/* Remember this target file */
+	entry = get_filemap_entry(path, true);
+	entry->target_exists = true;
+	entry->target_type = type;
+	entry->target_size = size;
+	entry->target_link_target = link_target ? pg_strdup(link_target) : NULL;
 }
 
 /*
  * This callback gets called while we read the WAL in the target, for every
- * block that have changed in the target system. It makes note of all the
+ * block that have changed in the target system.  It makes note of all the
  * changed blocks in the pagemap of the file.
+ *
+ * NOTE: All the files on both systems must have already been added to the
+ * file map!
  */
 void
-process_block_change(ForkNumber forknum, RelFileNode rnode, BlockNumber blkno)
+process_target_wal_block_change(ForkNumber forknum, RelFileNode rnode,
+								BlockNumber blkno)
 {
 	char	   *path;
-	file_entry_t key;
-	file_entry_t *key_ptr;
 	file_entry_t *entry;
 	BlockNumber blkno_inseg;
 	int			segno;
-	filemap_t  *map = filemap;
-	file_entry_t **e;
 
-	Assert(map->array);
+	Assert(filemap->array);
 
 	segno = blkno / RELSEG_SIZE;
 	blkno_inseg = blkno % RELSEG_SIZE;
 
 	path = datasegpath(rnode, forknum, segno);
-
-	key.path = (char *) path;
-	key_ptr = &key;
-
-	e = bsearch(&key_ptr, map->array, map->narray, sizeof(file_entry_t *),
-				path_cmp);
-	if (e)
-		entry = *e;
-	else
-		entry = NULL;
+	entry = get_filemap_entry(path, false);
 	pfree(path);
 
 	if (entry)
 	{
+		int64		end_offset;
+
 		Assert(entry->isrelfile);
 
-		switch (entry->action)
-		{
-			case FILE_ACTION_NONE:
-			case FILE_ACTION_TRUNCATE:
-				/* skip if we're truncating away the modified block anyway */
-				if ((blkno_inseg + 1) * BLCKSZ <= entry->newsize)
-					datapagemap_add(&entry->pagemap, blkno_inseg);
-				break;
-
-			case FILE_ACTION_COPY_TAIL:
-
-				/*
-				 * skip the modified block if it is part of the "tail" that
-				 * we're copying anyway.
-				 */
-				if ((blkno_inseg + 1) * BLCKSZ <= entry->oldsize)
-					datapagemap_add(&entry->pagemap, blkno_inseg);
-				break;
-
-			case FILE_ACTION_COPY:
-			case FILE_ACTION_REMOVE:
-				break;
-
-			case FILE_ACTION_CREATE:
-				pg_fatal("unexpected page modification for directory or symbolic link \"%s\"", entry->path);
-		}
+		if (entry->target_type != FILE_TYPE_REGULAR)
+			pg_fatal("unexpected page modification for directory or symbolic link \"%s\"",
+					 entry->path);
+
+		/*
+		 * If the block beyond the EOF in the source system, no need to
+		 * remember it now, because we're going to truncate it away from the
+		 * target anyway. Also no need to remember the block if it's beyond
+		 * the current EOF in the target system; we will copy it over with the
+		 * "tail" from the source system, anyway.
+		 */
+		end_offset = (blkno_inseg + 1) * BLCKSZ;
+		if (end_offset <= entry->source_size &&
+			end_offset <= entry->target_size)
+			datapagemap_add(&entry->target_modified_pages, blkno_inseg);
 	}
 	else
 	{
 		/*
 		 * If we don't have any record of this file in the file map, it means
-		 * that it's a relation that doesn't exist in the source system, and
-		 * it was subsequently removed in the target system, too. We can
-		 * safely ignore it.
+		 * that it's a relation that doesn't exist in the source system.  It
+		 * could exist in the target system; we haven't moved the target-only
+		 * entries from the linked list to the array yet!  But in any case, if
+		 * it doesn't exist in the source it will be removed from the target
+		 * too, and we can safely ignore it.
 		 */
 	}
 }
@@ -593,16 +472,6 @@ filemap_list_to_array(filemap_t *map)
 	map->first = map->last = NULL;
 }
 
-void
-filemap_finalize(void)
-{
-	filemap_t  *map = filemap;
-
-	filemap_list_to_array(map);
-	qsort(map->array, map->narray, sizeof(file_entry_t *),
-		  final_filemap_cmp);
-}
-
 static const char *
 action_to_str(file_action_t action)
 {
@@ -643,26 +512,26 @@ calculate_totals(void)
 	{
 		entry = map->array[i];
 
-		if (entry->type != FILE_TYPE_REGULAR)
+		if (entry->source_type != FILE_TYPE_REGULAR)
 			continue;
 
-		map->total_size += entry->newsize;
+		map->total_size += entry->source_size;
 
 		if (entry->action == FILE_ACTION_COPY)
 		{
-			map->fetch_size += entry->newsize;
+			map->fetch_size += entry->source_size;
 			continue;
 		}
 
 		if (entry->action == FILE_ACTION_COPY_TAIL)
-			map->fetch_size += (entry->newsize - entry->oldsize);
+			map->fetch_size += (entry->source_size - entry->target_size);
 
-		if (entry->pagemap.bitmapsize > 0)
+		if (entry->target_modified_pages.bitmapsize > 0)
 		{
 			datapagemap_iterator_t *iter;
 			BlockNumber blk;
 
-			iter = datapagemap_iterate(&entry->pagemap);
+			iter = datapagemap_iterate(&entry->target_modified_pages);
 			while (datapagemap_next(iter, &blk))
 				map->fetch_size += BLCKSZ;
 
@@ -682,13 +551,13 @@ print_filemap(void)
 	{
 		entry = map->array[i];
 		if (entry->action != FILE_ACTION_NONE ||
-			entry->pagemap.bitmapsize > 0)
+			entry->target_modified_pages.bitmapsize > 0)
 		{
 			pg_log_debug("%s (%s)", entry->path,
 						 action_to_str(entry->action));
 
-			if (entry->pagemap.bitmapsize > 0)
-				datapagemap_print(&entry->pagemap);
+			if (entry->target_modified_pages.bitmapsize > 0)
+				datapagemap_print(&entry->target_modified_pages);
 		}
 	}
 	fflush(stdout);
@@ -837,3 +706,126 @@ final_filemap_cmp(const void *a, const void *b)
 	else
 		return strcmp(fa->path, fb->path);
 }
+
+/*
+ * Decide what to do with each file.
+ */
+void
+filemap_finalize()
+{
+	int			i;
+
+	filemap_list_to_array(filemap);
+
+	for (i = 0; i < filemap->narray; i++)
+	{
+		file_entry_t *entry = filemap->array[i];
+		file_action_t action;
+
+		if (!entry->target_exists && entry->source_exists)
+		{
+			/*
+			 * File exists in source, but not in target. Copy
+			 * it in toto. (If it's a relation data file, WAL replay
+			 * after rewinding should re-create it anyway. But there's
+			 * no harm in copying it now.)
+			 */
+			if (entry->source_type == FILE_TYPE_DIRECTORY ||
+				entry->source_type == FILE_TYPE_SYMLINK)
+			{
+				action = FILE_ACTION_CREATE;
+			}
+			else
+				action = FILE_ACTION_COPY;
+		}
+		else if (entry->target_exists && !entry->source_exists)
+		{
+			/* Delete file from target */
+			action = FILE_ACTION_REMOVE;
+		}
+		else if (entry->target_exists && entry->source_exists)
+		{
+			/* File exists in both systems */
+			if (entry->source_type != entry->target_type)
+			{
+				/* But it's a different kind of object. Strange.. */
+				pg_fatal("file \"%s\" is of different type in source and target", entry->path);
+			}
+
+			switch (entry->source_type)
+			{
+				case FILE_TYPE_DIRECTORY:
+					action = FILE_ACTION_NONE;
+					break;
+
+				case FILE_TYPE_SYMLINK:
+					// FIXME: Check if it points to the same target?
+					action = FILE_ACTION_NONE;
+					break;
+
+				case FILE_TYPE_REGULAR:
+					if (!entry->isrelfile)
+					{
+						/*
+						 * It's a non-data file that we have no special processing for. Copy
+						 * it in toto.
+						 *
+						 * An exception: PG_VERSIONs should be identical, but avoid
+						 * overwriting it for paranoia.
+						 */
+						if (!pg_str_endswith(entry->path, "PG_VERSION"))
+							action = FILE_ACTION_COPY;
+						else
+							action = FILE_ACTION_NONE;
+					}
+					else
+					{
+						/*
+						 * It's a data file that exists in both systems.
+						 *
+						 * If it's larger in target, we can truncate it. There will
+						 * also be a WAL record of the truncation in the source
+						 * system, so WAL replay would eventually truncate the target
+						 * too, but we might as well do it now.
+						 *
+						 * If it's smaller in the target, it means that it has been
+						 * truncated in the target, or enlarged in the source, or
+						 * both. If it was truncated in the target, we need to copy
+						 * the missing tail from the source system. If it was enlarged
+						 * in the source system, there will be WAL records in the
+						 * source system for the new blocks, so we wouldn't need to
+						 * copy them here. But we don't know which scenario we're
+						 * dealing with, and there's no harm in copying the missing
+						 * blocks now, so do it now.
+						 *
+						 * If it's the same size, do nothing here. Any blocks modified
+						 * in the target will be copied based on parsing the target
+						 * system's WAL, and any blocks modified in the source will be
+						 * updated after rewinding, when the source system's WAL is
+						 * replayed.
+						 */
+						if (entry->target_size < entry->source_size)
+							action = FILE_ACTION_COPY_TAIL;
+						else if (entry->target_size > entry->source_size)
+							action = FILE_ACTION_TRUNCATE;
+						else
+							action = FILE_ACTION_NONE;
+					}
+					break;
+				case FILE_TYPE_UNDEFINED:
+					pg_fatal("unknown file type for \"%s\"", entry->path);
+					break;
+			}
+		}
+		else
+		{
+			/* Doesn't exist in either server. Why does it have an entry at all?? */
+			action = FILE_ACTION_NONE;
+		}
+		entry->action = action;
+	}
+
+	/* Sort the actions to the order that they should be performed */
+	qsort(filemap->array, filemap->narray, sizeof(file_entry_t *),
+		  final_filemap_cmp);
+}
diff --git a/src/bin/pg_rewind/filemap.h b/src/bin/pg_rewind/filemap.h
index 0cb7425170c..a5e8df57f40 100644
--- a/src/bin/pg_rewind/filemap.h
+++ b/src/bin/pg_rewind/filemap.h
@@ -14,17 +14,21 @@
 
 /*
  * For every file found in the local or remote system, we have a file entry
- * which says what we are going to do with the file. For relation files,
- * there is also a page map, marking pages in the file that were changed
- * locally.
- *
- * The enum values are sorted in the order we want actions to be processed.
+ * that contains information about the file on both systems.  For relation
+ * files, there is also a page map that marks pages in the file that were
+ * changed in the target after the last common checkpoint.  Each entry also
+ * contains an 'action' field, which says what we are going to do with the
+ * file.
  */
+
+/* these enum values are sorted in the order we want actions to be processed */
 typedef enum
 {
+	FILE_ACTION_UNDECIDED = 0,	/* not decided yet */
+
 	FILE_ACTION_CREATE,			/* create local directory or symbolic link */
 	FILE_ACTION_COPY,			/* copy whole file, overwriting if exists */
-	FILE_ACTION_COPY_TAIL,		/* copy tail from 'oldsize' to 'newsize' */
+	FILE_ACTION_COPY_TAIL,		/* copy tail from 'source_size' to 'target_size' */
 	FILE_ACTION_NONE,			/* no action (we might still copy modified
 								 * blocks based on the parsed WAL) */
 	FILE_ACTION_TRUNCATE,		/* truncate local file to 'newsize' bytes */
@@ -33,6 +37,8 @@ typedef enum
 
 typedef enum
 {
+	FILE_TYPE_UNDEFINED = 0,
+
 	FILE_TYPE_REGULAR,
 	FILE_TYPE_DIRECTORY,
 	FILE_TYPE_SYMLINK
@@ -41,19 +47,30 @@ typedef enum
 typedef struct file_entry_t
 {
 	char	   *path;
-	file_type_t type;
+	bool		isrelfile;		/* is it a relation data file? */
 
-	file_action_t action;
+	/*
+	 * Status of the file in the target.
+	 */
+	bool		target_exists;
+	file_type_t target_type;
+	size_t		target_size; /* for a regular file */
+	char	   *target_link_target; /* for a symlink */
 
-	/* for a regular file */
-	size_t		oldsize;
-	size_t		newsize;
-	bool		isrelfile;		/* is it a relation data file? */
+	datapagemap_t target_modified_pages;
 
-	datapagemap_t pagemap;
+	/*
+	 * Status of the file in the source.
+	 */
+	bool		source_exists;
+	file_type_t source_type;
+	size_t		source_size;
+	char	   *source_link_target; /* for a symlink */
 
-	/* for a symlink */
-	char	   *link_target;
+	/*
+	 * What will we do to the file?
+	 */
+	file_action_t action;
 
 	struct file_entry_t *next;
 } file_entry_t;
@@ -70,20 +87,19 @@ typedef struct filemap_t
 
 	/*
 	 * After processing all the remote files, the entries in the linked list
-	 * are moved to this array. After processing local files, too, all the
+	 * are moved to this array.  After processing local files, too, all the
 	 * local entries are added to the array by filemap_finalize, and sorted in
-	 * the final order. After filemap_finalize, all the entries are in the
+	 * the final order.  After filemap_finalize, all the entries are in the
 	 * array, and the linked list is empty.
 	 */
 	file_entry_t **array;
 	int			narray;			/* current length of array */
 
 	/*
-	 * Summary information. total_size is the total size of the source
-	 * cluster, and fetch_size is the number of bytes that needs to be copied.
+	 * Summary information.
 	 */
-	uint64		total_size;
-	uint64		fetch_size;
+	uint64		total_size;		/* total size of the source cluster */
+	uint64		fetch_size;		/* number of bytes that needs to be copied */
 } filemap_t;
 
 extern filemap_t *filemap;
@@ -94,11 +110,12 @@ extern void print_filemap(void);
 
 /* Functions for populating the filemap */
 extern void process_source_file(const char *path, file_type_t type,
-								size_t newsize, const char *link_target);
+								size_t size, const char *link_target);
 extern void process_target_file(const char *path, file_type_t type,
-								size_t newsize, const char *link_target);
-extern void process_block_change(ForkNumber forknum, RelFileNode rnode,
-								 BlockNumber blkno);
+								size_t size, const char *link_target);
+extern void process_target_wal_block_change(ForkNumber forknum,
+											RelFileNode rnode,
+											BlockNumber blkno);
 extern void filemap_finalize(void);
 
 #endif							/* FILEMAP_H */
diff --git a/src/bin/pg_rewind/libpq_fetch.c b/src/bin/pg_rewind/libpq_fetch.c
index bf4dfc23b96..7fc9161b8c8 100644
--- a/src/bin/pg_rewind/libpq_fetch.c
+++ b/src/bin/pg_rewind/libpq_fetch.c
@@ -465,7 +465,7 @@ libpq_executeFileMap(filemap_t *map)
 		entry = map->array[i];
 
 		/* If this is a relation file, copy the modified blocks */
-		execute_pagemap(&entry->pagemap, entry->path);
+		execute_pagemap(&entry->target_modified_pages, entry->path);
 
 		switch (entry->action)
 		{
@@ -476,15 +476,15 @@ libpq_executeFileMap(filemap_t *map)
 			case FILE_ACTION_COPY:
 				/* Truncate the old file out of the way, if any */
 				open_target_file(entry->path, true);
-				fetch_file_range(entry->path, 0, entry->newsize);
+				fetch_file_range(entry->path, 0, entry->source_size);
 				break;
 
 			case FILE_ACTION_TRUNCATE:
-				truncate_target_file(entry->path, entry->newsize);
+				truncate_target_file(entry->path, entry->source_size);
 				break;
 
 			case FILE_ACTION_COPY_TAIL:
-				fetch_file_range(entry->path, entry->oldsize, entry->newsize);
+				fetch_file_range(entry->path, entry->target_size, entry->source_size);
 				break;
 
 			case FILE_ACTION_REMOVE:
@@ -494,6 +494,10 @@ libpq_executeFileMap(filemap_t *map)
 			case FILE_ACTION_CREATE:
 				create_target(entry);
 				break;
+
+			case FILE_ACTION_UNDECIDED:
+				pg_fatal("no action decided for \"%s\"", entry->path);
+				break;
 		}
 	}
 
diff --git a/src/bin/pg_rewind/parsexlog.c b/src/bin/pg_rewind/parsexlog.c
index 2229c86f9af..2baeb74ae93 100644
--- a/src/bin/pg_rewind/parsexlog.c
+++ b/src/bin/pg_rewind/parsexlog.c
@@ -436,6 +436,6 @@ extractPageInfo(XLogReaderState *record)
 		if (forknum != MAIN_FORKNUM)
 			continue;
 
-		process_block_change(forknum, rnode, blkno);
+		process_target_wal_block_change(forknum, rnode, blkno);
 	}
 }
diff --git a/src/bin/pg_rewind/pg_rewind.c b/src/bin/pg_rewind/pg_rewind.c
index c9b9e480c0f..210984d302b 100644
--- a/src/bin/pg_rewind/pg_rewind.c
+++ b/src/bin/pg_rewind/pg_rewind.c
@@ -369,7 +369,7 @@ main(int argc, char **argv)
 				chkpttli);
 
 	/*
-	 * Build the filemap, by comparing the source and target data directories.
+	 * Collect information about all files in the target and source systems.
 	 */
 	filemap_create();
 	if (showprogress)
@@ -390,8 +390,12 @@ main(int argc, char **argv)
 		pg_log_info("reading WAL in target");
 	extractPageMap(datadir_target, chkptrec, lastcommontliIndex,
 				   ControlFile_target.checkPoint, restore_command);
-	filemap_finalize();
 
+	/*
+	 * We have collected all information we need from both systems. Decide
+	 * what to do with each file.
+	 */
+	filemap_finalize();
 	if (showprogress)
 		calculate_totals();
 
-- 
2.20.1


--------------D93EDEBFB124D563B723F4BD
Content-Type: text/x-patch; charset=UTF-8;
 name="0003-pg_rewind-Replace-the-hybrid-list-array-data-structu.patch"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
 filename*0="0003-pg_rewind-Replace-the-hybrid-list-array-data-structu.pa";
 filename*1="tch"



^ permalink  raw  reply  [nested|flat] 41+ messages in thread

* [PATCH v2 2/5] Refactor pg_rewind for more clear decision making.
@ 2020-08-19 12:34 Heikki Linnakangas <[email protected]>
  0 siblings, 0 replies; 41+ messages in thread

From: Heikki Linnakangas @ 2020-08-19 12:34 UTC (permalink / raw)

Deciding what to do with each file is now a separate step after all the
necessary information has been gathered. It is more clear that way.
Previously, the decision-making was divided between process_source_file()
and process_target_file(), and it was a bit hard to piece together what the
overall rules were.
---
 src/bin/pg_rewind/copy_fetch.c  |  14 +-
 src/bin/pg_rewind/file_ops.c    |  16 +-
 src/bin/pg_rewind/filemap.c     | 569 ++++++++++++++++----------------
 src/bin/pg_rewind/filemap.h     |  67 ++--
 src/bin/pg_rewind/libpq_fetch.c |  12 +-
 src/bin/pg_rewind/parsexlog.c   |   2 +-
 src/bin/pg_rewind/pg_rewind.c   |   8 +-
 7 files changed, 371 insertions(+), 317 deletions(-)

diff --git a/src/bin/pg_rewind/copy_fetch.c b/src/bin/pg_rewind/copy_fetch.c
index 1edab5f1867..18fad32600e 100644
--- a/src/bin/pg_rewind/copy_fetch.c
+++ b/src/bin/pg_rewind/copy_fetch.c
@@ -210,7 +210,7 @@ copy_executeFileMap(filemap_t *map)
 	for (i = 0; i < map->narray; i++)
 	{
 		entry = map->array[i];
-		execute_pagemap(&entry->pagemap, entry->path);
+		execute_pagemap(&entry->target_modified_pages, entry->path);
 
 		switch (entry->action)
 		{
@@ -219,16 +219,16 @@ copy_executeFileMap(filemap_t *map)
 				break;
 
 			case FILE_ACTION_COPY:
-				rewind_copy_file_range(entry->path, 0, entry->newsize, true);
+				rewind_copy_file_range(entry->path, 0, entry->source_size, true);
 				break;
 
 			case FILE_ACTION_TRUNCATE:
-				truncate_target_file(entry->path, entry->newsize);
+				truncate_target_file(entry->path, entry->source_size);
 				break;
 
 			case FILE_ACTION_COPY_TAIL:
-				rewind_copy_file_range(entry->path, entry->oldsize,
-									   entry->newsize, false);
+				rewind_copy_file_range(entry->path, entry->target_size,
+									   entry->source_size, false);
 				break;
 
 			case FILE_ACTION_CREATE:
@@ -238,6 +238,10 @@ copy_executeFileMap(filemap_t *map)
 			case FILE_ACTION_REMOVE:
 				remove_target(entry);
 				break;
+
+			case FILE_ACTION_UNDECIDED:
+				pg_fatal("no action decided for \"%s\"", entry->path);
+				break;
 		}
 	}
 
diff --git a/src/bin/pg_rewind/file_ops.c b/src/bin/pg_rewind/file_ops.c
index 55439db20ba..ec37d0b2e0d 100644
--- a/src/bin/pg_rewind/file_ops.c
+++ b/src/bin/pg_rewind/file_ops.c
@@ -126,8 +126,9 @@ void
 remove_target(file_entry_t *entry)
 {
 	Assert(entry->action == FILE_ACTION_REMOVE);
+	Assert(entry->target_exists);
 
-	switch (entry->type)
+	switch (entry->target_type)
 	{
 		case FILE_TYPE_DIRECTORY:
 			remove_target_dir(entry->path);
@@ -140,6 +141,10 @@ remove_target(file_entry_t *entry)
 		case FILE_TYPE_SYMLINK:
 			remove_target_symlink(entry->path);
 			break;
+
+		case FILE_TYPE_UNDEFINED:
+			pg_fatal("undefined file type for \"%s\"", entry->path);
+			break;
 	}
 }
 
@@ -147,21 +152,26 @@ void
 create_target(file_entry_t *entry)
 {
 	Assert(entry->action == FILE_ACTION_CREATE);
+	Assert(!entry->target_exists);
 
-	switch (entry->type)
+	switch (entry->source_type)
 	{
 		case FILE_TYPE_DIRECTORY:
 			create_target_dir(entry->path);
 			break;
 
 		case FILE_TYPE_SYMLINK:
-			create_target_symlink(entry->path, entry->link_target);
+			create_target_symlink(entry->path, entry->source_link_target);
 			break;
 
 		case FILE_TYPE_REGULAR:
 			/* can't happen. Regular files are created with open_target_file. */
 			pg_fatal("invalid action (CREATE) for regular file");
 			break;
+
+		case FILE_TYPE_UNDEFINED:
+			pg_fatal("undefined file type for \"%s\"", entry->path);
+			break;
 	}
 }
 
diff --git a/src/bin/pg_rewind/filemap.c b/src/bin/pg_rewind/filemap.c
index 1879257b66a..7971daeda5e 100644
--- a/src/bin/pg_rewind/filemap.c
+++ b/src/bin/pg_rewind/filemap.c
@@ -26,6 +26,8 @@ static bool isRelDataFile(const char *path);
 static char *datasegpath(RelFileNode rnode, ForkNumber forknum,
 						 BlockNumber segno);
 static int	path_cmp(const void *a, const void *b);
+
+static file_entry_t *get_filemap_entry(const char *path, bool create);
 static int	final_filemap_cmp(const void *a, const void *b);
 static void filemap_list_to_array(filemap_t *map);
 static bool check_file_excluded(const char *path, bool is_source);
@@ -146,6 +148,65 @@ filemap_create(void)
 	filemap = map;
 }
 
+/* Look up or create entry for 'path' */
+static file_entry_t *
+get_filemap_entry(const char *path, bool create)
+{
+	filemap_t  *map = filemap;
+	file_entry_t *entry;
+	file_entry_t **e;
+	file_entry_t key;
+	file_entry_t *key_ptr;
+
+	if (map->array)
+	{
+		key.path = (char *) path;
+		key_ptr = &key;
+		e = bsearch(&key_ptr, map->array, map->narray, sizeof(file_entry_t *),
+					path_cmp);
+	}
+	else
+		e = NULL;
+
+	if (e)
+		entry = *e;
+	else if (!create)
+		entry = NULL;
+	else
+	{
+		/* Create a new entry for this file */
+		entry = pg_malloc(sizeof(file_entry_t));
+		entry->path = pg_strdup(path);
+		entry->isrelfile = isRelDataFile(path);
+		entry->action = FILE_ACTION_UNDECIDED;
+
+		entry->target_exists = false;
+		entry->target_type = FILE_TYPE_UNDEFINED;
+		entry->target_size = 0;
+		entry->target_link_target = NULL;
+		entry->target_modified_pages.bitmap = NULL;
+		entry->target_modified_pages.bitmapsize = 0;
+
+		entry->source_exists = false;
+		entry->source_type = FILE_TYPE_UNDEFINED;
+		entry->source_size = 0;
+		entry->source_link_target = NULL;
+
+		entry->next = NULL;
+
+		if (map->last)
+		{
+			map->last->next = entry;
+			map->last = entry;
+		}
+		else
+			map->first = map->last = entry;
+		map->nlist++;
+	}
+
+	return entry;
+}
+
 /*
  * Callback for processing source file list.
  *
@@ -154,25 +215,12 @@ filemap_create(void)
  * exists in the target and whether the size matches.
  */
 void
-process_source_file(const char *path, file_type_t type, size_t newsize,
+process_source_file(const char *path, file_type_t type, size_t size,
 					const char *link_target)
 {
-	bool		exists;
-	char		localpath[MAXPGPATH];
-	struct stat statbuf;
-	filemap_t  *map = filemap;
-	file_action_t action = FILE_ACTION_NONE;
-	size_t		oldsize = 0;
 	file_entry_t *entry;
 
-	Assert(map->array == NULL);
-
-	/*
-	 * Skip any files matching the exclusion filters. This has the effect to
-	 * remove all those files on the target.
-	 */
-	if (check_file_excluded(path, true))
-		return;
+	Assert(filemap->array == NULL);
 
 	/*
 	 * Pretend that pg_wal is a directory, even if it's really a symlink. We
@@ -182,16 +230,6 @@ process_source_file(const char *path, file_type_t type, size_t newsize,
 	if (strcmp(path, "pg_wal") == 0 && type == FILE_TYPE_SYMLINK)
 		type = FILE_TYPE_DIRECTORY;
 
-	/*
-	 * Skip temporary files, .../pgsql_tmp/... and .../pgsql_tmp.* in source.
-	 * This has the effect that all temporary files in the destination will be
-	 * removed.
-	 */
-	if (strstr(path, "/" PG_TEMP_FILE_PREFIX) != NULL)
-		return;
-	if (strstr(path, "/" PG_TEMP_FILES_DIR "/") != NULL)
-		return;
-
 	/*
 	 * sanity check: a filename that looks like a data file better be a
 	 * regular file
@@ -199,142 +237,12 @@ process_source_file(const char *path, file_type_t type, size_t newsize,
 	if (type != FILE_TYPE_REGULAR && isRelDataFile(path))
 		pg_fatal("data file \"%s\" in source is not a regular file", path);
 
-	snprintf(localpath, sizeof(localpath), "%s/%s", datadir_target, path);
-
-	/* Does the corresponding file exist in the target data dir? */
-	if (lstat(localpath, &statbuf) < 0)
-	{
-		if (errno != ENOENT)
-			pg_fatal("could not stat file \"%s\": %m",
-					 localpath);
-
-		exists = false;
-	}
-	else
-		exists = true;
-
-	switch (type)
-	{
-		case FILE_TYPE_DIRECTORY:
-			if (exists && !S_ISDIR(statbuf.st_mode) && strcmp(path, "pg_wal") != 0)
-			{
-				/* it's a directory in source, but not in target. Strange.. */
-				pg_fatal("\"%s\" is not a directory", localpath);
-			}
-
-			if (!exists)
-				action = FILE_ACTION_CREATE;
-			else
-				action = FILE_ACTION_NONE;
-			oldsize = 0;
-			break;
-
-		case FILE_TYPE_SYMLINK:
-			if (exists &&
-#ifndef WIN32
-				!S_ISLNK(statbuf.st_mode)
-#else
-				!pgwin32_is_junction(localpath)
-#endif
-				)
-			{
-				/*
-				 * It's a symbolic link in source, but not in target.
-				 * Strange..
-				 */
-				pg_fatal("\"%s\" is not a symbolic link", localpath);
-			}
-
-			if (!exists)
-				action = FILE_ACTION_CREATE;
-			else
-				action = FILE_ACTION_NONE;
-			oldsize = 0;
-			break;
-
-		case FILE_TYPE_REGULAR:
-			if (exists && !S_ISREG(statbuf.st_mode))
-				pg_fatal("\"%s\" is not a regular file", localpath);
-
-			if (!exists || !isRelDataFile(path))
-			{
-				/*
-				 * File exists in source, but not in target. Or it's a
-				 * non-data file that we have no special processing for. Copy
-				 * it in toto.
-				 *
-				 * An exception: PG_VERSIONs should be identical, but avoid
-				 * overwriting it for paranoia.
-				 */
-				if (pg_str_endswith(path, "PG_VERSION"))
-				{
-					action = FILE_ACTION_NONE;
-					oldsize = statbuf.st_size;
-				}
-				else
-				{
-					action = FILE_ACTION_COPY;
-					oldsize = 0;
-				}
-			}
-			else
-			{
-				/*
-				 * It's a data file that exists in both.
-				 *
-				 * If it's larger in target, we can truncate it. There will
-				 * also be a WAL record of the truncation in the source
-				 * system, so WAL replay would eventually truncate the target
-				 * too, but we might as well do it now.
-				 *
-				 * If it's smaller in the target, it means that it has been
-				 * truncated in the target, or enlarged in the source, or
-				 * both. If it was truncated in the target, we need to copy
-				 * the missing tail from the source system. If it was enlarged
-				 * in the source system, there will be WAL records in the
-				 * source system for the new blocks, so we wouldn't need to
-				 * copy them here. But we don't know which scenario we're
-				 * dealing with, and there's no harm in copying the missing
-				 * blocks now, so do it now.
-				 *
-				 * If it's the same size, do nothing here. Any blocks modified
-				 * in the target will be copied based on parsing the target
-				 * system's WAL, and any blocks modified in the source will be
-				 * updated after rewinding, when the source system's WAL is
-				 * replayed.
-				 */
-				oldsize = statbuf.st_size;
-				if (oldsize < newsize)
-					action = FILE_ACTION_COPY_TAIL;
-				else if (oldsize > newsize)
-					action = FILE_ACTION_TRUNCATE;
-				else
-					action = FILE_ACTION_NONE;
-			}
-			break;
-	}
-
-	/* Create a new entry for this file */
-	entry = pg_malloc(sizeof(file_entry_t));
-	entry->path = pg_strdup(path);
-	entry->type = type;
-	entry->action = action;
-	entry->oldsize = oldsize;
-	entry->newsize = newsize;
-	entry->link_target = link_target ? pg_strdup(link_target) : NULL;
-	entry->next = NULL;
-	entry->pagemap.bitmap = NULL;
-	entry->pagemap.bitmapsize = 0;
-	entry->isrelfile = isRelDataFile(path);
-
-	if (map->last)
-	{
-		map->last->next = entry;
-		map->last = entry;
-	}
-	else
-		map->first = map->last = entry;
-	map->nlist++;
+	/* Remember this source file */
+	entry = get_filemap_entry(path, true);
+	entry->source_exists = true;
+	entry->source_type = type;
+	entry->source_size = size;
+	entry->source_link_target = link_target ? pg_strdup(link_target) : NULL;
 }
 
 /*
@@ -345,14 +253,9 @@ process_source_file(const char *path, file_type_t type, size_t newsize,
  * deletion.
  */
 void
-process_target_file(const char *path, file_type_t type, size_t oldsize,
+process_target_file(const char *path, file_type_t type, size_t size,
 					const char *link_target)
 {
-	bool		exists;
-	char		localpath[MAXPGPATH];
-	struct stat statbuf;
-	file_entry_t key;
-	file_entry_t *key_ptr;
 	filemap_t  *map = filemap;
 	file_entry_t *entry;
 
@@ -361,17 +264,6 @@ process_target_file(const char *path, file_type_t type, size_t oldsize,
 	 * from the target data folder all paths which have been filtered out from
 	 * the source data folder when processing the source files.
 	 */
-
-	snprintf(localpath, sizeof(localpath), "%s/%s", datadir_target, path);
-	if (lstat(localpath, &statbuf) < 0)
-	{
-		if (errno != ENOENT)
-			pg_fatal("could not stat file \"%s\": %m",
-					 localpath);
-
-		exists = false;
-	}
-
 	if (map->array == NULL)
 	{
 		/* on first call, initialize lookup array */
@@ -389,120 +281,76 @@ process_target_file(const char *path, file_type_t type, size_t oldsize,
 	}
 
 	/*
-	 * Like in process_source_file, pretend that xlog is always a  directory.
+	 * Like in process_source_file, pretend that pg_wal is always a directory.
 	 */
 	if (strcmp(path, "pg_wal") == 0 && type == FILE_TYPE_SYMLINK)
 		type = FILE_TYPE_DIRECTORY;
 
-	key.path = (char *) path;
-	key_ptr = &key;
-	exists = (bsearch(&key_ptr, map->array, map->narray, sizeof(file_entry_t *),
-					  path_cmp) != NULL);
-
-	/* Remove any file or folder that doesn't exist in the source system. */
-	if (!exists)
-	{
-		entry = pg_malloc(sizeof(file_entry_t));
-		entry->path = pg_strdup(path);
-		entry->type = type;
-		entry->action = FILE_ACTION_REMOVE;
-		entry->oldsize = oldsize;
-		entry->newsize = 0;
-		entry->link_target = link_target ? pg_strdup(link_target) : NULL;
-		entry->next = NULL;
-		entry->pagemap.bitmap = NULL;
-		entry->pagemap.bitmapsize = 0;
-		entry->isrelfile = isRelDataFile(path);
-
-		if (map->last == NULL)
-			map->first = entry;
-		else
-			map->last->next = entry;
-		map->last = entry;
-		map->nlist++;
-	}
-	else
-	{
-		/*
-		 * We already handled all files that exist in the source system in
-		 * process_source_file().
-		 */
-	}
+	/* Remember this target file */
+	entry = get_filemap_entry(path, true);
+	entry->target_exists = true;
+	entry->target_type = type;
+	entry->target_size = size;
+	entry->target_link_target = link_target ? pg_strdup(link_target) : NULL;
 }
 
 /*
  * This callback gets called while we read the WAL in the target, for every
- * block that have changed in the target system. It makes note of all the
+ * block that have changed in the target system.  It makes note of all the
  * changed blocks in the pagemap of the file.
+ *
+ * NOTE: All the files on both systems must have already been added to the
+ * file map!
  */
 void
-process_block_change(ForkNumber forknum, RelFileNode rnode, BlockNumber blkno)
+process_target_wal_block_change(ForkNumber forknum, RelFileNode rnode,
+								BlockNumber blkno)
 {
 	char	   *path;
-	file_entry_t key;
-	file_entry_t *key_ptr;
 	file_entry_t *entry;
 	BlockNumber blkno_inseg;
 	int			segno;
-	filemap_t  *map = filemap;
-	file_entry_t **e;
 
-	Assert(map->array);
+	Assert(filemap->array);
 
 	segno = blkno / RELSEG_SIZE;
 	blkno_inseg = blkno % RELSEG_SIZE;
 
 	path = datasegpath(rnode, forknum, segno);
-
-	key.path = (char *) path;
-	key_ptr = &key;
-
-	e = bsearch(&key_ptr, map->array, map->narray, sizeof(file_entry_t *),
-				path_cmp);
-	if (e)
-		entry = *e;
-	else
-		entry = NULL;
+	entry = get_filemap_entry(path, false);
 	pfree(path);
 
 	if (entry)
 	{
-		Assert(entry->isrelfile);
-
-		switch (entry->action)
-		{
-			case FILE_ACTION_NONE:
-			case FILE_ACTION_TRUNCATE:
-				/* skip if we're truncating away the modified block anyway */
-				if ((blkno_inseg + 1) * BLCKSZ <= entry->newsize)
-					datapagemap_add(&entry->pagemap, blkno_inseg);
-				break;
+		int64		end_offset;
 
-			case FILE_ACTION_COPY_TAIL:
-
-				/*
-				 * skip the modified block if it is part of the "tail" that
-				 * we're copying anyway.
-				 */
-				if ((blkno_inseg + 1) * BLCKSZ <= entry->oldsize)
-					datapagemap_add(&entry->pagemap, blkno_inseg);
-				break;
+		Assert(entry->isrelfile);
 
-			case FILE_ACTION_COPY:
-			case FILE_ACTION_REMOVE:
-				break;
+		if (entry->target_type != FILE_TYPE_REGULAR)
+			pg_fatal("unexpected page modification for directory or symbolic link \"%s\"",
+					 entry->path);
 
-			case FILE_ACTION_CREATE:
-				pg_fatal("unexpected page modification for directory or symbolic link \"%s\"", entry->path);
-		}
+		/*
+		 * If the block beyond the EOF in the source system, no need to
+		 * remember it now, because we're going to truncate it away from the
+		 * target anyway. Also no need to remember the block if it's beyond
+		 * the current EOF in the target system; we will copy it over with the
+		 * "tail" from the source system, anyway.
+		 */
+		end_offset = (blkno_inseg + 1) * BLCKSZ;
+		if (end_offset <= entry->source_size &&
+			end_offset <= entry->target_size)
+			datapagemap_add(&entry->target_modified_pages, blkno_inseg);
 	}
 	else
 	{
 		/*
 		 * If we don't have any record of this file in the file map, it means
-		 * that it's a relation that doesn't exist in the source system, and
-		 * it was subsequently removed in the target system, too. We can
-		 * safely ignore it.
+		 * that it's a relation that doesn't exist in the source system.  It
+		 * could exist in the target system; we haven't moved the target-only
+		 * entries from the linked list to the array yet!  But in any case, if
+		 * it doesn't exist in the source it will be removed from the target
+		 * too, and we can safely ignore it.
 		 */
 	}
 }
@@ -593,16 +441,6 @@ filemap_list_to_array(filemap_t *map)
 	map->first = map->last = NULL;
 }
 
-void
-filemap_finalize(void)
-{
-	filemap_t  *map = filemap;
-
-	filemap_list_to_array(map);
-	qsort(map->array, map->narray, sizeof(file_entry_t *),
-		  final_filemap_cmp);
-}
-
 static const char *
 action_to_str(file_action_t action)
 {
@@ -643,26 +481,26 @@ calculate_totals(void)
 	{
 		entry = map->array[i];
 
-		if (entry->type != FILE_TYPE_REGULAR)
+		if (entry->source_type != FILE_TYPE_REGULAR)
 			continue;
 
-		map->total_size += entry->newsize;
+		map->total_size += entry->source_size;
 
 		if (entry->action == FILE_ACTION_COPY)
 		{
-			map->fetch_size += entry->newsize;
+			map->fetch_size += entry->source_size;
 			continue;
 		}
 
 		if (entry->action == FILE_ACTION_COPY_TAIL)
-			map->fetch_size += (entry->newsize - entry->oldsize);
+			map->fetch_size += (entry->source_size - entry->target_size);
 
-		if (entry->pagemap.bitmapsize > 0)
+		if (entry->target_modified_pages.bitmapsize > 0)
 		{
 			datapagemap_iterator_t *iter;
 			BlockNumber blk;
 
-			iter = datapagemap_iterate(&entry->pagemap);
+			iter = datapagemap_iterate(&entry->target_modified_pages);
 			while (datapagemap_next(iter, &blk))
 				map->fetch_size += BLCKSZ;
 
@@ -682,13 +520,13 @@ print_filemap(void)
 	{
 		entry = map->array[i];
 		if (entry->action != FILE_ACTION_NONE ||
-			entry->pagemap.bitmapsize > 0)
+			entry->target_modified_pages.bitmapsize > 0)
 		{
 			pg_log_debug("%s (%s)", entry->path,
 						 action_to_str(entry->action));
 
-			if (entry->pagemap.bitmapsize > 0)
-				datapagemap_print(&entry->pagemap);
+			if (entry->target_modified_pages.bitmapsize > 0)
+				datapagemap_print(&entry->target_modified_pages);
 		}
 	}
 	fflush(stdout);
@@ -837,3 +675,180 @@ final_filemap_cmp(const void *a, const void *b)
 	else
 		return strcmp(fa->path, fb->path);
 }
+
+/*
+ * Decide what action to perform to a file.
+ */
+static file_action_t
+decide_file_action(file_entry_t *entry)
+{
+	const char *path = entry->path;
+
+	/*
+	 * Don't touch the control file. It is handled specially, after copying
+	 * all the other files.
+	 */
+	if (strcmp(path, "global/pg_control") == 0)
+		return FILE_ACTION_NONE;
+
+	/*
+	 * Remove all files matching the exclusion filters in the target.
+	 */
+	if (check_file_excluded(path, true))
+	{
+		if (entry->target_exists)
+			return FILE_ACTION_REMOVE;
+		else
+			return FILE_ACTION_NONE;
+	}
+
+	/*
+	 * Also remove all temporary files, .../pgsql_tmp/... and .../pgsql_tmp.*
+	 * in the target.
+	 */
+	if (strstr(path, "/" PG_TEMP_FILE_PREFIX) != NULL ||
+		strstr(path, "/" PG_TEMP_FILES_DIR "/") != NULL)
+	{
+		if (entry->target_exists)
+			return FILE_ACTION_REMOVE;
+		else
+			return FILE_ACTION_NONE;
+	}
+
+	/*
+	 * Handle cases where the file is missing from one of the systems.
+	 */
+	if (!entry->target_exists && entry->source_exists)
+	{
+		/*
+		 * File exists in source, but not in target. Copy it in toto. (If it's
+		 * a relation data file, WAL replay after rewinding should re-create
+		 * it anyway. But there's no harm in copying it now.)
+		 */
+		switch (entry->source_type)
+		{
+			case FILE_TYPE_DIRECTORY:
+			case FILE_TYPE_SYMLINK:
+				return FILE_ACTION_CREATE;
+			case FILE_TYPE_REGULAR:
+				return FILE_ACTION_COPY;
+			case FILE_TYPE_UNDEFINED:
+				pg_fatal("unknown file type for \"%s\"", entry->path);
+				break;
+		}
+	}
+	else if (entry->target_exists && !entry->source_exists)
+	{
+		/* File exists in target, but not source. Remove it. */
+		return FILE_ACTION_REMOVE;
+	}
+	else if (!entry->target_exists && !entry->source_exists)
+	{
+		/*
+		 * Doesn't exist in either server. Why does it have an entry in the
+		 * first place??
+		 */
+		return FILE_ACTION_NONE;
+	}
+
+	/*
+	 * Otherwise, the file exists on both systems
+	 */
+	Assert(entry->target_exists && entry->source_exists);
+
+	if (entry->source_type != entry->target_type)
+	{
+		/* But it's a different kind of object. Strange.. */
+		pg_fatal("file \"%s\" is of different type in source and target", entry->path);
+	}
+
+	/*
+	 * PG_VERSION files should be identical on both systems, but avoid
+	 * overwriting them for paranoia.
+	 */
+	if (pg_str_endswith(entry->path, "PG_VERSION"))
+		return FILE_ACTION_NONE;
+
+	switch (entry->source_type)
+	{
+		case FILE_TYPE_DIRECTORY:
+			return FILE_ACTION_NONE;
+
+		case FILE_TYPE_SYMLINK:
+			/* FIXME: Check if it points to the same target? */
+			return FILE_ACTION_NONE;
+
+		case FILE_TYPE_REGULAR:
+			if (!entry->isrelfile)
+			{
+				/*
+				 * It's a non-data file that we have no special processing
+				 * for. Copy it in toto.
+				 */
+				return FILE_ACTION_COPY;
+			}
+			else
+			{
+				/*
+				 * It's a data file that exists in both systems.
+				 *
+				 * If it's larger in target, we can truncate it. There will
+				 * also be a WAL record of the truncation in the source
+				 * system, so WAL replay would eventually truncate the target
+				 * too, but we might as well do it now.
+				 *
+				 * If it's smaller in the target, it means that it has been
+				 * truncated in the target, or enlarged in the source, or
+				 * both. If it was truncated in the target, we need to copy
+				 * the missing tail from the source system. If it was enlarged
+				 * in the source system, there will be WAL records in the
+				 * source system for the new blocks, so we wouldn't need to
+				 * copy them here. But we don't know which scenario we're
+				 * dealing with, and there's no harm in copying the missing
+				 * blocks now, so do it now.
+				 *
+				 * If it's the same size, do nothing here. Any blocks modified
+				 * in the target will be copied based on parsing the target
+				 * system's WAL, and any blocks modified in the source will be
+				 * updated after rewinding, when the source system's WAL is
+				 * replayed.
+				 */
+				if (entry->target_size < entry->source_size)
+					return FILE_ACTION_COPY_TAIL;
+				else if (entry->target_size > entry->source_size)
+					return FILE_ACTION_TRUNCATE;
+				else
+					return FILE_ACTION_NONE;
+			}
+			break;
+
+		case FILE_TYPE_UNDEFINED:
+			pg_fatal("unknown file type for \"%s\"", path);
+			break;
+	}
+
+	/* unreachable */
+	pg_fatal("could not decide what to do with file \"%s\"", path);
+}
+
+/*
+ * Decide what to do with each file.
+ */
+void
+filemap_finalize()
+{
+	int			i;
+
+	filemap_list_to_array(filemap);
+
+	for (i = 0; i < filemap->narray; i++)
+	{
+		file_entry_t *entry = filemap->array[i];
+
+		entry->action = decide_file_action(entry);
+	}
+
+	/* Sort the actions to the order that they should be performed */
+	qsort(filemap->array, filemap->narray, sizeof(file_entry_t *),
+		  final_filemap_cmp);
+}
diff --git a/src/bin/pg_rewind/filemap.h b/src/bin/pg_rewind/filemap.h
index 0cb7425170c..a5e8df57f40 100644
--- a/src/bin/pg_rewind/filemap.h
+++ b/src/bin/pg_rewind/filemap.h
@@ -14,17 +14,21 @@
 
 /*
  * For every file found in the local or remote system, we have a file entry
- * which says what we are going to do with the file. For relation files,
- * there is also a page map, marking pages in the file that were changed
- * locally.
- *
- * The enum values are sorted in the order we want actions to be processed.
+ * that contains information about the file on both systems.  For relation
+ * files, there is also a page map that marks pages in the file that were
+ * changed in the target after the last common checkpoint.  Each entry also
+ * contains an 'action' field, which says what we are going to do with the
+ * file.
  */
+
+/* these enum values are sorted in the order we want actions to be processed */
 typedef enum
 {
+	FILE_ACTION_UNDECIDED = 0,	/* not decided yet */
+
 	FILE_ACTION_CREATE,			/* create local directory or symbolic link */
 	FILE_ACTION_COPY,			/* copy whole file, overwriting if exists */
-	FILE_ACTION_COPY_TAIL,		/* copy tail from 'oldsize' to 'newsize' */
+	FILE_ACTION_COPY_TAIL,		/* copy tail from 'source_size' to 'target_size' */
 	FILE_ACTION_NONE,			/* no action (we might still copy modified
 								 * blocks based on the parsed WAL) */
 	FILE_ACTION_TRUNCATE,		/* truncate local file to 'newsize' bytes */
@@ -33,6 +37,8 @@ typedef enum
 
 typedef enum
 {
+	FILE_TYPE_UNDEFINED = 0,
+
 	FILE_TYPE_REGULAR,
 	FILE_TYPE_DIRECTORY,
 	FILE_TYPE_SYMLINK
@@ -41,19 +47,30 @@ typedef enum
 typedef struct file_entry_t
 {
 	char	   *path;
-	file_type_t type;
+	bool		isrelfile;		/* is it a relation data file? */
 
-	file_action_t action;
+	/*
+	 * Status of the file in the target.
+	 */
+	bool		target_exists;
+	file_type_t target_type;
+	size_t		target_size; /* for a regular file */
+	char	   *target_link_target; /* for a symlink */
 
-	/* for a regular file */
-	size_t		oldsize;
-	size_t		newsize;
-	bool		isrelfile;		/* is it a relation data file? */
+	datapagemap_t target_modified_pages;
 
-	datapagemap_t pagemap;
+	/*
+	 * Status of the file in the source.
+	 */
+	bool		source_exists;
+	file_type_t source_type;
+	size_t		source_size;
+	char	   *source_link_target; /* for a symlink */
 
-	/* for a symlink */
-	char	   *link_target;
+	/*
+	 * What will we do to the file?
+	 */
+	file_action_t action;
 
 	struct file_entry_t *next;
 } file_entry_t;
@@ -70,20 +87,19 @@ typedef struct filemap_t
 
 	/*
 	 * After processing all the remote files, the entries in the linked list
-	 * are moved to this array. After processing local files, too, all the
+	 * are moved to this array.  After processing local files, too, all the
 	 * local entries are added to the array by filemap_finalize, and sorted in
-	 * the final order. After filemap_finalize, all the entries are in the
+	 * the final order.  After filemap_finalize, all the entries are in the
 	 * array, and the linked list is empty.
 	 */
 	file_entry_t **array;
 	int			narray;			/* current length of array */
 
 	/*
-	 * Summary information. total_size is the total size of the source
-	 * cluster, and fetch_size is the number of bytes that needs to be copied.
+	 * Summary information.
 	 */
-	uint64		total_size;
-	uint64		fetch_size;
+	uint64		total_size;		/* total size of the source cluster */
+	uint64		fetch_size;		/* number of bytes that needs to be copied */
 } filemap_t;
 
 extern filemap_t *filemap;
@@ -94,11 +110,12 @@ extern void print_filemap(void);
 
 /* Functions for populating the filemap */
 extern void process_source_file(const char *path, file_type_t type,
-								size_t newsize, const char *link_target);
+								size_t size, const char *link_target);
 extern void process_target_file(const char *path, file_type_t type,
-								size_t newsize, const char *link_target);
-extern void process_block_change(ForkNumber forknum, RelFileNode rnode,
-								 BlockNumber blkno);
+								size_t size, const char *link_target);
+extern void process_target_wal_block_change(ForkNumber forknum,
+											RelFileNode rnode,
+											BlockNumber blkno);
 extern void filemap_finalize(void);
 
 #endif							/* FILEMAP_H */
diff --git a/src/bin/pg_rewind/libpq_fetch.c b/src/bin/pg_rewind/libpq_fetch.c
index bf4dfc23b96..7fc9161b8c8 100644
--- a/src/bin/pg_rewind/libpq_fetch.c
+++ b/src/bin/pg_rewind/libpq_fetch.c
@@ -465,7 +465,7 @@ libpq_executeFileMap(filemap_t *map)
 		entry = map->array[i];
 
 		/* If this is a relation file, copy the modified blocks */
-		execute_pagemap(&entry->pagemap, entry->path);
+		execute_pagemap(&entry->target_modified_pages, entry->path);
 
 		switch (entry->action)
 		{
@@ -476,15 +476,15 @@ libpq_executeFileMap(filemap_t *map)
 			case FILE_ACTION_COPY:
 				/* Truncate the old file out of the way, if any */
 				open_target_file(entry->path, true);
-				fetch_file_range(entry->path, 0, entry->newsize);
+				fetch_file_range(entry->path, 0, entry->source_size);
 				break;
 
 			case FILE_ACTION_TRUNCATE:
-				truncate_target_file(entry->path, entry->newsize);
+				truncate_target_file(entry->path, entry->source_size);
 				break;
 
 			case FILE_ACTION_COPY_TAIL:
-				fetch_file_range(entry->path, entry->oldsize, entry->newsize);
+				fetch_file_range(entry->path, entry->target_size, entry->source_size);
 				break;
 
 			case FILE_ACTION_REMOVE:
@@ -494,6 +494,10 @@ libpq_executeFileMap(filemap_t *map)
 			case FILE_ACTION_CREATE:
 				create_target(entry);
 				break;
+
+			case FILE_ACTION_UNDECIDED:
+				pg_fatal("no action decided for \"%s\"", entry->path);
+				break;
 		}
 	}
 
diff --git a/src/bin/pg_rewind/parsexlog.c b/src/bin/pg_rewind/parsexlog.c
index 2229c86f9af..2baeb74ae93 100644
--- a/src/bin/pg_rewind/parsexlog.c
+++ b/src/bin/pg_rewind/parsexlog.c
@@ -436,6 +436,6 @@ extractPageInfo(XLogReaderState *record)
 		if (forknum != MAIN_FORKNUM)
 			continue;
 
-		process_block_change(forknum, rnode, blkno);
+		process_target_wal_block_change(forknum, rnode, blkno);
 	}
 }
diff --git a/src/bin/pg_rewind/pg_rewind.c b/src/bin/pg_rewind/pg_rewind.c
index c9b9e480c0f..210984d302b 100644
--- a/src/bin/pg_rewind/pg_rewind.c
+++ b/src/bin/pg_rewind/pg_rewind.c
@@ -369,7 +369,7 @@ main(int argc, char **argv)
 				chkpttli);
 
 	/*
-	 * Build the filemap, by comparing the source and target data directories.
+	 * Collect information about all files in the target and source systems.
 	 */
 	filemap_create();
 	if (showprogress)
@@ -390,8 +390,12 @@ main(int argc, char **argv)
 		pg_log_info("reading WAL in target");
 	extractPageMap(datadir_target, chkptrec, lastcommontliIndex,
 				   ControlFile_target.checkPoint, restore_command);
-	filemap_finalize();
 
+	/*
+	 * We have collected all information we need from both systems. Decide
+	 * what to do with each file.
+	 */
+	filemap_finalize();
 	if (showprogress)
 		calculate_totals();
 
-- 
2.20.1


--------------BF34D0120055DC3839060F92
Content-Type: text/x-patch; charset=UTF-8;
 name="v2-0003-pg_rewind-Replace-the-hybrid-list-array-data-stru.patch"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
 filename*0="v2-0003-pg_rewind-Replace-the-hybrid-list-array-data-stru.pa";
 filename*1="tch"



^ permalink  raw  reply  [nested|flat] 41+ messages in thread

* [PATCH 2/5] Refactor pg_rewind for more clear decision making.
@ 2020-08-19 12:34 Heikki Linnakangas <[email protected]>
  0 siblings, 0 replies; 41+ messages in thread

From: Heikki Linnakangas @ 2020-08-19 12:34 UTC (permalink / raw)

Deciding what to do with each file is now a separate step after all the
necessary information has been gathered. It is more clear that way.
Previously, the decision-making was divided between process_source_file()
and process_target_file(), and it was a bit hard to piece together what the
overall rules were.
---
 src/bin/pg_rewind/copy_fetch.c  |  14 +-
 src/bin/pg_rewind/file_ops.c    |  16 +-
 src/bin/pg_rewind/filemap.c     | 514 ++++++++++++++++----------------
 src/bin/pg_rewind/filemap.h     |  67 +++--
 src/bin/pg_rewind/libpq_fetch.c |  12 +-
 src/bin/pg_rewind/parsexlog.c   |   2 +-
 src/bin/pg_rewind/pg_rewind.c   |   8 +-
 7 files changed, 332 insertions(+), 301 deletions(-)

diff --git a/src/bin/pg_rewind/copy_fetch.c b/src/bin/pg_rewind/copy_fetch.c
index 1edab5f1867..18fad32600e 100644
--- a/src/bin/pg_rewind/copy_fetch.c
+++ b/src/bin/pg_rewind/copy_fetch.c
@@ -210,7 +210,7 @@ copy_executeFileMap(filemap_t *map)
 	for (i = 0; i < map->narray; i++)
 	{
 		entry = map->array[i];
-		execute_pagemap(&entry->pagemap, entry->path);
+		execute_pagemap(&entry->target_modified_pages, entry->path);
 
 		switch (entry->action)
 		{
@@ -219,16 +219,16 @@ copy_executeFileMap(filemap_t *map)
 				break;
 
 			case FILE_ACTION_COPY:
-				rewind_copy_file_range(entry->path, 0, entry->newsize, true);
+				rewind_copy_file_range(entry->path, 0, entry->source_size, true);
 				break;
 
 			case FILE_ACTION_TRUNCATE:
-				truncate_target_file(entry->path, entry->newsize);
+				truncate_target_file(entry->path, entry->source_size);
 				break;
 
 			case FILE_ACTION_COPY_TAIL:
-				rewind_copy_file_range(entry->path, entry->oldsize,
-									   entry->newsize, false);
+				rewind_copy_file_range(entry->path, entry->target_size,
+									   entry->source_size, false);
 				break;
 
 			case FILE_ACTION_CREATE:
@@ -238,6 +238,10 @@ copy_executeFileMap(filemap_t *map)
 			case FILE_ACTION_REMOVE:
 				remove_target(entry);
 				break;
+
+			case FILE_ACTION_UNDECIDED:
+				pg_fatal("no action decided for \"%s\"", entry->path);
+				break;
 		}
 	}
 
diff --git a/src/bin/pg_rewind/file_ops.c b/src/bin/pg_rewind/file_ops.c
index 55439db20ba..ec37d0b2e0d 100644
--- a/src/bin/pg_rewind/file_ops.c
+++ b/src/bin/pg_rewind/file_ops.c
@@ -126,8 +126,9 @@ void
 remove_target(file_entry_t *entry)
 {
 	Assert(entry->action == FILE_ACTION_REMOVE);
+	Assert(entry->target_exists);
 
-	switch (entry->type)
+	switch (entry->target_type)
 	{
 		case FILE_TYPE_DIRECTORY:
 			remove_target_dir(entry->path);
@@ -140,6 +141,10 @@ remove_target(file_entry_t *entry)
 		case FILE_TYPE_SYMLINK:
 			remove_target_symlink(entry->path);
 			break;
+
+		case FILE_TYPE_UNDEFINED:
+			pg_fatal("undefined file type for \"%s\"", entry->path);
+			break;
 	}
 }
 
@@ -147,21 +152,26 @@ void
 create_target(file_entry_t *entry)
 {
 	Assert(entry->action == FILE_ACTION_CREATE);
+	Assert(!entry->target_exists);
 
-	switch (entry->type)
+	switch (entry->source_type)
 	{
 		case FILE_TYPE_DIRECTORY:
 			create_target_dir(entry->path);
 			break;
 
 		case FILE_TYPE_SYMLINK:
-			create_target_symlink(entry->path, entry->link_target);
+			create_target_symlink(entry->path, entry->source_link_target);
 			break;
 
 		case FILE_TYPE_REGULAR:
 			/* can't happen. Regular files are created with open_target_file. */
 			pg_fatal("invalid action (CREATE) for regular file");
 			break;
+
+		case FILE_TYPE_UNDEFINED:
+			pg_fatal("undefined file type for \"%s\"", entry->path);
+			break;
 	}
 }
 
diff --git a/src/bin/pg_rewind/filemap.c b/src/bin/pg_rewind/filemap.c
index 1879257b66a..431e8e760e0 100644
--- a/src/bin/pg_rewind/filemap.c
+++ b/src/bin/pg_rewind/filemap.c
@@ -26,6 +26,8 @@ static bool isRelDataFile(const char *path);
 static char *datasegpath(RelFileNode rnode, ForkNumber forknum,
 						 BlockNumber segno);
 static int	path_cmp(const void *a, const void *b);
+
+static file_entry_t *get_filemap_entry(const char *path, bool create);
 static int	final_filemap_cmp(const void *a, const void *b);
 static void filemap_list_to_array(filemap_t *map);
 static bool check_file_excluded(const char *path, bool is_source);
@@ -146,6 +148,65 @@ filemap_create(void)
 	filemap = map;
 }
 
+/* Look up or create entry for 'path' */
+static file_entry_t *
+get_filemap_entry(const char *path, bool create)
+{
+	filemap_t  *map = filemap;
+	file_entry_t *entry;
+	file_entry_t **e;
+	file_entry_t key;
+	file_entry_t *key_ptr;
+
+	if (map->array)
+	{
+		key.path = (char *) path;
+		key_ptr = &key;
+		e = bsearch(&key_ptr, map->array, map->narray, sizeof(file_entry_t *),
+					path_cmp);
+	}
+	else
+		e = NULL;
+
+	if (e)
+		entry = *e;
+	else if (!create)
+		entry = NULL;
+	else
+	{
+		/* Create a new entry for this file */
+		entry = pg_malloc(sizeof(file_entry_t));
+		entry->path = pg_strdup(path);
+		entry->isrelfile = isRelDataFile(path);
+		entry->action = FILE_ACTION_UNDECIDED;
+
+		entry->target_exists = false;
+		entry->target_type = FILE_TYPE_UNDEFINED;
+		entry->target_size = 0;
+		entry->target_link_target = NULL;
+		entry->target_modified_pages.bitmap = NULL;
+		entry->target_modified_pages.bitmapsize = 0;
+
+		entry->source_exists = false;
+		entry->source_type = FILE_TYPE_UNDEFINED;
+		entry->source_size = 0;
+		entry->source_link_target = NULL;
+
+		entry->next = NULL;
+
+		if (map->last)
+		{
+			map->last->next = entry;
+			map->last = entry;
+		}
+		else
+			map->first = map->last = entry;
+		map->nlist++;
+	}
+
+	return entry;
+}
+
 /*
  * Callback for processing source file list.
  *
@@ -154,18 +215,19 @@ filemap_create(void)
  * exists in the target and whether the size matches.
  */
 void
-process_source_file(const char *path, file_type_t type, size_t newsize,
+process_source_file(const char *path, file_type_t type, size_t size,
 					const char *link_target)
 {
-	bool		exists;
-	char		localpath[MAXPGPATH];
-	struct stat statbuf;
-	filemap_t  *map = filemap;
-	file_action_t action = FILE_ACTION_NONE;
-	size_t		oldsize = 0;
 	file_entry_t *entry;
 
-	Assert(map->array == NULL);
+	Assert(filemap->array == NULL);
+
+	/*
+	 * Skip the control file. It is handled specially, after copying all the
+	 * other files.
+	 */
+	if (strcmp(path, "global/pg_control") == 0)
+		return;
 
 	/*
 	 * Skip any files matching the exclusion filters. This has the effect to
@@ -199,142 +261,12 @@ process_source_file(const char *path, file_type_t type, size_t newsize,
 	if (type != FILE_TYPE_REGULAR && isRelDataFile(path))
 		pg_fatal("data file \"%s\" in source is not a regular file", path);
 
-	snprintf(localpath, sizeof(localpath), "%s/%s", datadir_target, path);
-
-	/* Does the corresponding file exist in the target data dir? */
-	if (lstat(localpath, &statbuf) < 0)
-	{
-		if (errno != ENOENT)
-			pg_fatal("could not stat file \"%s\": %m",
-					 localpath);
-
-		exists = false;
-	}
-	else
-		exists = true;
-
-	switch (type)
-	{
-		case FILE_TYPE_DIRECTORY:
-			if (exists && !S_ISDIR(statbuf.st_mode) && strcmp(path, "pg_wal") != 0)
-			{
-				/* it's a directory in source, but not in target. Strange.. */
-				pg_fatal("\"%s\" is not a directory", localpath);
-			}
-
-			if (!exists)
-				action = FILE_ACTION_CREATE;
-			else
-				action = FILE_ACTION_NONE;
-			oldsize = 0;
-			break;
-
-		case FILE_TYPE_SYMLINK:
-			if (exists &&
-#ifndef WIN32
-				!S_ISLNK(statbuf.st_mode)
-#else
-				!pgwin32_is_junction(localpath)
-#endif
-				)
-			{
-				/*
-				 * It's a symbolic link in source, but not in target.
-				 * Strange..
-				 */
-				pg_fatal("\"%s\" is not a symbolic link", localpath);
-			}
-
-			if (!exists)
-				action = FILE_ACTION_CREATE;
-			else
-				action = FILE_ACTION_NONE;
-			oldsize = 0;
-			break;
-
-		case FILE_TYPE_REGULAR:
-			if (exists && !S_ISREG(statbuf.st_mode))
-				pg_fatal("\"%s\" is not a regular file", localpath);
-
-			if (!exists || !isRelDataFile(path))
-			{
-				/*
-				 * File exists in source, but not in target. Or it's a
-				 * non-data file that we have no special processing for. Copy
-				 * it in toto.
-				 *
-				 * An exception: PG_VERSIONs should be identical, but avoid
-				 * overwriting it for paranoia.
-				 */
-				if (pg_str_endswith(path, "PG_VERSION"))
-				{
-					action = FILE_ACTION_NONE;
-					oldsize = statbuf.st_size;
-				}
-				else
-				{
-					action = FILE_ACTION_COPY;
-					oldsize = 0;
-				}
-			}
-			else
-			{
-				/*
-				 * It's a data file that exists in both.
-				 *
-				 * If it's larger in target, we can truncate it. There will
-				 * also be a WAL record of the truncation in the source
-				 * system, so WAL replay would eventually truncate the target
-				 * too, but we might as well do it now.
-				 *
-				 * If it's smaller in the target, it means that it has been
-				 * truncated in the target, or enlarged in the source, or
-				 * both. If it was truncated in the target, we need to copy
-				 * the missing tail from the source system. If it was enlarged
-				 * in the source system, there will be WAL records in the
-				 * source system for the new blocks, so we wouldn't need to
-				 * copy them here. But we don't know which scenario we're
-				 * dealing with, and there's no harm in copying the missing
-				 * blocks now, so do it now.
-				 *
-				 * If it's the same size, do nothing here. Any blocks modified
-				 * in the target will be copied based on parsing the target
-				 * system's WAL, and any blocks modified in the source will be
-				 * updated after rewinding, when the source system's WAL is
-				 * replayed.
-				 */
-				oldsize = statbuf.st_size;
-				if (oldsize < newsize)
-					action = FILE_ACTION_COPY_TAIL;
-				else if (oldsize > newsize)
-					action = FILE_ACTION_TRUNCATE;
-				else
-					action = FILE_ACTION_NONE;
-			}
-			break;
-	}
-
-	/* Create a new entry for this file */
-	entry = pg_malloc(sizeof(file_entry_t));
-	entry->path = pg_strdup(path);
-	entry->type = type;
-	entry->action = action;
-	entry->oldsize = oldsize;
-	entry->newsize = newsize;
-	entry->link_target = link_target ? pg_strdup(link_target) : NULL;
-	entry->next = NULL;
-	entry->pagemap.bitmap = NULL;
-	entry->pagemap.bitmapsize = 0;
-	entry->isrelfile = isRelDataFile(path);
-
-	if (map->last)
-	{
-		map->last->next = entry;
-		map->last = entry;
-	}
-	else
-		map->first = map->last = entry;
-	map->nlist++;
+	/* Remember this source file */
+	entry = get_filemap_entry(path, true);
+	entry->source_exists = true;
+	entry->source_type = type;
+	entry->source_size = size;
+	entry->source_link_target = link_target ? pg_strdup(link_target) : NULL;
 }
 
 /*
@@ -345,33 +277,24 @@ process_source_file(const char *path, file_type_t type, size_t newsize,
  * deletion.
  */
 void
-process_target_file(const char *path, file_type_t type, size_t oldsize,
+process_target_file(const char *path, file_type_t type, size_t size,
 					const char *link_target)
 {
-	bool		exists;
-	char		localpath[MAXPGPATH];
-	struct stat statbuf;
-	file_entry_t key;
-	file_entry_t *key_ptr;
 	filemap_t  *map = filemap;
 	file_entry_t *entry;
 
+	/*
+	 * Skip the control file. It is handled specially, after copying all the
+	 * other files.
+	 */
+	if (strcmp(path, "global/pg_control") == 0)
+		return;
+
 	/*
 	 * Do not apply any exclusion filters here.  This has advantage to remove
 	 * from the target data folder all paths which have been filtered out from
 	 * the source data folder when processing the source files.
 	 */
-
-	snprintf(localpath, sizeof(localpath), "%s/%s", datadir_target, path);
-	if (lstat(localpath, &statbuf) < 0)
-	{
-		if (errno != ENOENT)
-			pg_fatal("could not stat file \"%s\": %m",
-					 localpath);
-
-		exists = false;
-	}
-
 	if (map->array == NULL)
 	{
 		/* on first call, initialize lookup array */
@@ -389,120 +312,76 @@ process_target_file(const char *path, file_type_t type, size_t oldsize,
 	}
 
 	/*
-	 * Like in process_source_file, pretend that xlog is always a  directory.
+	 * Like in process_source_file, pretend that pg_wal is always a directory.
 	 */
 	if (strcmp(path, "pg_wal") == 0 && type == FILE_TYPE_SYMLINK)
 		type = FILE_TYPE_DIRECTORY;
 
-	key.path = (char *) path;
-	key_ptr = &key;
-	exists = (bsearch(&key_ptr, map->array, map->narray, sizeof(file_entry_t *),
-					  path_cmp) != NULL);
-
-	/* Remove any file or folder that doesn't exist in the source system. */
-	if (!exists)
-	{
-		entry = pg_malloc(sizeof(file_entry_t));
-		entry->path = pg_strdup(path);
-		entry->type = type;
-		entry->action = FILE_ACTION_REMOVE;
-		entry->oldsize = oldsize;
-		entry->newsize = 0;
-		entry->link_target = link_target ? pg_strdup(link_target) : NULL;
-		entry->next = NULL;
-		entry->pagemap.bitmap = NULL;
-		entry->pagemap.bitmapsize = 0;
-		entry->isrelfile = isRelDataFile(path);
-
-		if (map->last == NULL)
-			map->first = entry;
-		else
-			map->last->next = entry;
-		map->last = entry;
-		map->nlist++;
-	}
-	else
-	{
-		/*
-		 * We already handled all files that exist in the source system in
-		 * process_source_file().
-		 */
-	}
+	/* Remember this target file */
+	entry = get_filemap_entry(path, true);
+	entry->target_exists = true;
+	entry->target_type = type;
+	entry->target_size = size;
+	entry->target_link_target = link_target ? pg_strdup(link_target) : NULL;
 }
 
 /*
  * This callback gets called while we read the WAL in the target, for every
- * block that have changed in the target system. It makes note of all the
+ * block that have changed in the target system.  It makes note of all the
  * changed blocks in the pagemap of the file.
+ *
+ * NOTE: All the files on both systems must have already been added to the
+ * file map!
  */
 void
-process_block_change(ForkNumber forknum, RelFileNode rnode, BlockNumber blkno)
+process_target_wal_block_change(ForkNumber forknum, RelFileNode rnode,
+								BlockNumber blkno)
 {
 	char	   *path;
-	file_entry_t key;
-	file_entry_t *key_ptr;
 	file_entry_t *entry;
 	BlockNumber blkno_inseg;
 	int			segno;
-	filemap_t  *map = filemap;
-	file_entry_t **e;
 
-	Assert(map->array);
+	Assert(filemap->array);
 
 	segno = blkno / RELSEG_SIZE;
 	blkno_inseg = blkno % RELSEG_SIZE;
 
 	path = datasegpath(rnode, forknum, segno);
-
-	key.path = (char *) path;
-	key_ptr = &key;
-
-	e = bsearch(&key_ptr, map->array, map->narray, sizeof(file_entry_t *),
-				path_cmp);
-	if (e)
-		entry = *e;
-	else
-		entry = NULL;
+	entry = get_filemap_entry(path, false);
 	pfree(path);
 
 	if (entry)
 	{
+		int64		end_offset;
+
 		Assert(entry->isrelfile);
 
-		switch (entry->action)
-		{
-			case FILE_ACTION_NONE:
-			case FILE_ACTION_TRUNCATE:
-				/* skip if we're truncating away the modified block anyway */
-				if ((blkno_inseg + 1) * BLCKSZ <= entry->newsize)
-					datapagemap_add(&entry->pagemap, blkno_inseg);
-				break;
-
-			case FILE_ACTION_COPY_TAIL:
-
-				/*
-				 * skip the modified block if it is part of the "tail" that
-				 * we're copying anyway.
-				 */
-				if ((blkno_inseg + 1) * BLCKSZ <= entry->oldsize)
-					datapagemap_add(&entry->pagemap, blkno_inseg);
-				break;
-
-			case FILE_ACTION_COPY:
-			case FILE_ACTION_REMOVE:
-				break;
-
-			case FILE_ACTION_CREATE:
-				pg_fatal("unexpected page modification for directory or symbolic link \"%s\"", entry->path);
-		}
+		if (entry->target_type != FILE_TYPE_REGULAR)
+			pg_fatal("unexpected page modification for directory or symbolic link \"%s\"",
+					 entry->path);
+
+		/*
+		 * If the block beyond the EOF in the source system, no need to
+		 * remember it now, because we're going to truncate it away from the
+		 * target anyway. Also no need to remember the block if it's beyond
+		 * the current EOF in the target system; we will copy it over with the
+		 * "tail" from the source system, anyway.
+		 */
+		end_offset = (blkno_inseg + 1) * BLCKSZ;
+		if (end_offset <= entry->source_size &&
+			end_offset <= entry->target_size)
+			datapagemap_add(&entry->target_modified_pages, blkno_inseg);
 	}
 	else
 	{
 		/*
 		 * If we don't have any record of this file in the file map, it means
-		 * that it's a relation that doesn't exist in the source system, and
-		 * it was subsequently removed in the target system, too. We can
-		 * safely ignore it.
+		 * that it's a relation that doesn't exist in the source system.  It
+		 * could exist in the target system; we haven't moved the target-only
+		 * entries from the linked list to the array yet!  But in any case, if
+		 * it doesn't exist in the source it will be removed from the target
+		 * too, and we can safely ignore it.
 		 */
 	}
 }
@@ -593,16 +472,6 @@ filemap_list_to_array(filemap_t *map)
 	map->first = map->last = NULL;
 }
 
-void
-filemap_finalize(void)
-{
-	filemap_t  *map = filemap;
-
-	filemap_list_to_array(map);
-	qsort(map->array, map->narray, sizeof(file_entry_t *),
-		  final_filemap_cmp);
-}
-
 static const char *
 action_to_str(file_action_t action)
 {
@@ -643,26 +512,26 @@ calculate_totals(void)
 	{
 		entry = map->array[i];
 
-		if (entry->type != FILE_TYPE_REGULAR)
+		if (entry->source_type != FILE_TYPE_REGULAR)
 			continue;
 
-		map->total_size += entry->newsize;
+		map->total_size += entry->source_size;
 
 		if (entry->action == FILE_ACTION_COPY)
 		{
-			map->fetch_size += entry->newsize;
+			map->fetch_size += entry->source_size;
 			continue;
 		}
 
 		if (entry->action == FILE_ACTION_COPY_TAIL)
-			map->fetch_size += (entry->newsize - entry->oldsize);
+			map->fetch_size += (entry->source_size - entry->target_size);
 
-		if (entry->pagemap.bitmapsize > 0)
+		if (entry->target_modified_pages.bitmapsize > 0)
 		{
 			datapagemap_iterator_t *iter;
 			BlockNumber blk;
 
-			iter = datapagemap_iterate(&entry->pagemap);
+			iter = datapagemap_iterate(&entry->target_modified_pages);
 			while (datapagemap_next(iter, &blk))
 				map->fetch_size += BLCKSZ;
 
@@ -682,13 +551,13 @@ print_filemap(void)
 	{
 		entry = map->array[i];
 		if (entry->action != FILE_ACTION_NONE ||
-			entry->pagemap.bitmapsize > 0)
+			entry->target_modified_pages.bitmapsize > 0)
 		{
 			pg_log_debug("%s (%s)", entry->path,
 						 action_to_str(entry->action));
 
-			if (entry->pagemap.bitmapsize > 0)
-				datapagemap_print(&entry->pagemap);
+			if (entry->target_modified_pages.bitmapsize > 0)
+				datapagemap_print(&entry->target_modified_pages);
 		}
 	}
 	fflush(stdout);
@@ -837,3 +706,126 @@ final_filemap_cmp(const void *a, const void *b)
 	else
 		return strcmp(fa->path, fb->path);
 }
+
+/*
+ * Decide what to do with each file.
+ */
+void
+filemap_finalize()
+{
+	int			i;
+
+	filemap_list_to_array(filemap);
+
+	for (i = 0; i < filemap->narray; i++)
+	{
+		file_entry_t *entry = filemap->array[i];
+		file_action_t action;
+
+		if (!entry->target_exists && entry->source_exists)
+		{
+			/*
+			 * File exists in source, but not in target. Copy
+			 * it in toto. (If it's a relation data file, WAL replay
+			 * after rewinding should re-create it anyway. But there's
+			 * no harm in copying it now.)
+			 */
+			if (entry->source_type == FILE_TYPE_DIRECTORY ||
+				entry->source_type == FILE_TYPE_SYMLINK)
+			{
+				action = FILE_ACTION_CREATE;
+			}
+			else
+				action = FILE_ACTION_COPY;
+		}
+		else if (entry->target_exists && !entry->source_exists)
+		{
+			/* Delete file from target */
+			action = FILE_ACTION_REMOVE;
+		}
+		else if (entry->target_exists && entry->source_exists)
+		{
+			/* File exists in both systems */
+			if (entry->source_type != entry->target_type)
+			{
+				/* But it's a different kind of object. Strange.. */
+				pg_fatal("file \"%s\" is of different type in source and target", entry->path);
+			}
+
+			switch (entry->source_type)
+			{
+				case FILE_TYPE_DIRECTORY:
+					action = FILE_ACTION_NONE;
+					break;
+
+				case FILE_TYPE_SYMLINK:
+					// FIXME: Check if it points to the same target?
+					action = FILE_ACTION_NONE;
+					break;
+
+				case FILE_TYPE_REGULAR:
+					if (!entry->isrelfile)
+					{
+						/*
+						 * It's a non-data file that we have no special processing for. Copy
+						 * it in toto.
+						 *
+						 * An exception: PG_VERSIONs should be identical, but avoid
+						 * overwriting it for paranoia.
+						 */
+						if (!pg_str_endswith(entry->path, "PG_VERSION"))
+							action = FILE_ACTION_COPY;
+						else
+							action = FILE_ACTION_NONE;
+					}
+					else
+					{
+						/*
+						 * It's a data file that exists in both systems.
+						 *
+						 * If it's larger in target, we can truncate it. There will
+						 * also be a WAL record of the truncation in the source
+						 * system, so WAL replay would eventually truncate the target
+						 * too, but we might as well do it now.
+						 *
+						 * If it's smaller in the target, it means that it has been
+						 * truncated in the target, or enlarged in the source, or
+						 * both. If it was truncated in the target, we need to copy
+						 * the missing tail from the source system. If it was enlarged
+						 * in the source system, there will be WAL records in the
+						 * source system for the new blocks, so we wouldn't need to
+						 * copy them here. But we don't know which scenario we're
+						 * dealing with, and there's no harm in copying the missing
+						 * blocks now, so do it now.
+						 *
+						 * If it's the same size, do nothing here. Any blocks modified
+						 * in the target will be copied based on parsing the target
+						 * system's WAL, and any blocks modified in the source will be
+						 * updated after rewinding, when the source system's WAL is
+						 * replayed.
+						 */
+						if (entry->target_size < entry->source_size)
+							action = FILE_ACTION_COPY_TAIL;
+						else if (entry->target_size > entry->source_size)
+							action = FILE_ACTION_TRUNCATE;
+						else
+							action = FILE_ACTION_NONE;
+					}
+					break;
+				case FILE_TYPE_UNDEFINED:
+					pg_fatal("unknown file type for \"%s\"", entry->path);
+					break;
+			}
+		}
+		else
+		{
+			/* Doesn't exist in either server. Why does it have an entry at all?? */
+			action = FILE_ACTION_NONE;
+		}
+		entry->action = action;
+	}
+
+	/* Sort the actions to the order that they should be performed */
+	qsort(filemap->array, filemap->narray, sizeof(file_entry_t *),
+		  final_filemap_cmp);
+}
diff --git a/src/bin/pg_rewind/filemap.h b/src/bin/pg_rewind/filemap.h
index 0cb7425170c..a5e8df57f40 100644
--- a/src/bin/pg_rewind/filemap.h
+++ b/src/bin/pg_rewind/filemap.h
@@ -14,17 +14,21 @@
 
 /*
  * For every file found in the local or remote system, we have a file entry
- * which says what we are going to do with the file. For relation files,
- * there is also a page map, marking pages in the file that were changed
- * locally.
- *
- * The enum values are sorted in the order we want actions to be processed.
+ * that contains information about the file on both systems.  For relation
+ * files, there is also a page map that marks pages in the file that were
+ * changed in the target after the last common checkpoint.  Each entry also
+ * contains an 'action' field, which says what we are going to do with the
+ * file.
  */
+
+/* these enum values are sorted in the order we want actions to be processed */
 typedef enum
 {
+	FILE_ACTION_UNDECIDED = 0,	/* not decided yet */
+
 	FILE_ACTION_CREATE,			/* create local directory or symbolic link */
 	FILE_ACTION_COPY,			/* copy whole file, overwriting if exists */
-	FILE_ACTION_COPY_TAIL,		/* copy tail from 'oldsize' to 'newsize' */
+	FILE_ACTION_COPY_TAIL,		/* copy tail from 'source_size' to 'target_size' */
 	FILE_ACTION_NONE,			/* no action (we might still copy modified
 								 * blocks based on the parsed WAL) */
 	FILE_ACTION_TRUNCATE,		/* truncate local file to 'newsize' bytes */
@@ -33,6 +37,8 @@ typedef enum
 
 typedef enum
 {
+	FILE_TYPE_UNDEFINED = 0,
+
 	FILE_TYPE_REGULAR,
 	FILE_TYPE_DIRECTORY,
 	FILE_TYPE_SYMLINK
@@ -41,19 +47,30 @@ typedef enum
 typedef struct file_entry_t
 {
 	char	   *path;
-	file_type_t type;
+	bool		isrelfile;		/* is it a relation data file? */
 
-	file_action_t action;
+	/*
+	 * Status of the file in the target.
+	 */
+	bool		target_exists;
+	file_type_t target_type;
+	size_t		target_size; /* for a regular file */
+	char	   *target_link_target; /* for a symlink */
 
-	/* for a regular file */
-	size_t		oldsize;
-	size_t		newsize;
-	bool		isrelfile;		/* is it a relation data file? */
+	datapagemap_t target_modified_pages;
 
-	datapagemap_t pagemap;
+	/*
+	 * Status of the file in the source.
+	 */
+	bool		source_exists;
+	file_type_t source_type;
+	size_t		source_size;
+	char	   *source_link_target; /* for a symlink */
 
-	/* for a symlink */
-	char	   *link_target;
+	/*
+	 * What will we do to the file?
+	 */
+	file_action_t action;
 
 	struct file_entry_t *next;
 } file_entry_t;
@@ -70,20 +87,19 @@ typedef struct filemap_t
 
 	/*
 	 * After processing all the remote files, the entries in the linked list
-	 * are moved to this array. After processing local files, too, all the
+	 * are moved to this array.  After processing local files, too, all the
 	 * local entries are added to the array by filemap_finalize, and sorted in
-	 * the final order. After filemap_finalize, all the entries are in the
+	 * the final order.  After filemap_finalize, all the entries are in the
 	 * array, and the linked list is empty.
 	 */
 	file_entry_t **array;
 	int			narray;			/* current length of array */
 
 	/*
-	 * Summary information. total_size is the total size of the source
-	 * cluster, and fetch_size is the number of bytes that needs to be copied.
+	 * Summary information.
 	 */
-	uint64		total_size;
-	uint64		fetch_size;
+	uint64		total_size;		/* total size of the source cluster */
+	uint64		fetch_size;		/* number of bytes that needs to be copied */
 } filemap_t;
 
 extern filemap_t *filemap;
@@ -94,11 +110,12 @@ extern void print_filemap(void);
 
 /* Functions for populating the filemap */
 extern void process_source_file(const char *path, file_type_t type,
-								size_t newsize, const char *link_target);
+								size_t size, const char *link_target);
 extern void process_target_file(const char *path, file_type_t type,
-								size_t newsize, const char *link_target);
-extern void process_block_change(ForkNumber forknum, RelFileNode rnode,
-								 BlockNumber blkno);
+								size_t size, const char *link_target);
+extern void process_target_wal_block_change(ForkNumber forknum,
+											RelFileNode rnode,
+											BlockNumber blkno);
 extern void filemap_finalize(void);
 
 #endif							/* FILEMAP_H */
diff --git a/src/bin/pg_rewind/libpq_fetch.c b/src/bin/pg_rewind/libpq_fetch.c
index bf4dfc23b96..7fc9161b8c8 100644
--- a/src/bin/pg_rewind/libpq_fetch.c
+++ b/src/bin/pg_rewind/libpq_fetch.c
@@ -465,7 +465,7 @@ libpq_executeFileMap(filemap_t *map)
 		entry = map->array[i];
 
 		/* If this is a relation file, copy the modified blocks */
-		execute_pagemap(&entry->pagemap, entry->path);
+		execute_pagemap(&entry->target_modified_pages, entry->path);
 
 		switch (entry->action)
 		{
@@ -476,15 +476,15 @@ libpq_executeFileMap(filemap_t *map)
 			case FILE_ACTION_COPY:
 				/* Truncate the old file out of the way, if any */
 				open_target_file(entry->path, true);
-				fetch_file_range(entry->path, 0, entry->newsize);
+				fetch_file_range(entry->path, 0, entry->source_size);
 				break;
 
 			case FILE_ACTION_TRUNCATE:
-				truncate_target_file(entry->path, entry->newsize);
+				truncate_target_file(entry->path, entry->source_size);
 				break;
 
 			case FILE_ACTION_COPY_TAIL:
-				fetch_file_range(entry->path, entry->oldsize, entry->newsize);
+				fetch_file_range(entry->path, entry->target_size, entry->source_size);
 				break;
 
 			case FILE_ACTION_REMOVE:
@@ -494,6 +494,10 @@ libpq_executeFileMap(filemap_t *map)
 			case FILE_ACTION_CREATE:
 				create_target(entry);
 				break;
+
+			case FILE_ACTION_UNDECIDED:
+				pg_fatal("no action decided for \"%s\"", entry->path);
+				break;
 		}
 	}
 
diff --git a/src/bin/pg_rewind/parsexlog.c b/src/bin/pg_rewind/parsexlog.c
index 2229c86f9af..2baeb74ae93 100644
--- a/src/bin/pg_rewind/parsexlog.c
+++ b/src/bin/pg_rewind/parsexlog.c
@@ -436,6 +436,6 @@ extractPageInfo(XLogReaderState *record)
 		if (forknum != MAIN_FORKNUM)
 			continue;
 
-		process_block_change(forknum, rnode, blkno);
+		process_target_wal_block_change(forknum, rnode, blkno);
 	}
 }
diff --git a/src/bin/pg_rewind/pg_rewind.c b/src/bin/pg_rewind/pg_rewind.c
index c9b9e480c0f..210984d302b 100644
--- a/src/bin/pg_rewind/pg_rewind.c
+++ b/src/bin/pg_rewind/pg_rewind.c
@@ -369,7 +369,7 @@ main(int argc, char **argv)
 				chkpttli);
 
 	/*
-	 * Build the filemap, by comparing the source and target data directories.
+	 * Collect information about all files in the target and source systems.
 	 */
 	filemap_create();
 	if (showprogress)
@@ -390,8 +390,12 @@ main(int argc, char **argv)
 		pg_log_info("reading WAL in target");
 	extractPageMap(datadir_target, chkptrec, lastcommontliIndex,
 				   ControlFile_target.checkPoint, restore_command);
-	filemap_finalize();
 
+	/*
+	 * We have collected all information we need from both systems. Decide
+	 * what to do with each file.
+	 */
+	filemap_finalize();
 	if (showprogress)
 		calculate_totals();
 
-- 
2.20.1


--------------D93EDEBFB124D563B723F4BD
Content-Type: text/x-patch; charset=UTF-8;
 name="0003-pg_rewind-Replace-the-hybrid-list-array-data-structu.patch"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
 filename*0="0003-pg_rewind-Replace-the-hybrid-list-array-data-structu.pa";
 filename*1="tch"



^ permalink  raw  reply  [nested|flat] 41+ messages in thread

* [PATCH v2 2/5] Refactor pg_rewind for more clear decision making.
@ 2020-08-19 12:34 Heikki Linnakangas <[email protected]>
  0 siblings, 0 replies; 41+ messages in thread

From: Heikki Linnakangas @ 2020-08-19 12:34 UTC (permalink / raw)

Deciding what to do with each file is now a separate step after all the
necessary information has been gathered. It is more clear that way.
Previously, the decision-making was divided between process_source_file()
and process_target_file(), and it was a bit hard to piece together what the
overall rules were.
---
 src/bin/pg_rewind/copy_fetch.c  |  14 +-
 src/bin/pg_rewind/file_ops.c    |  16 +-
 src/bin/pg_rewind/filemap.c     | 569 ++++++++++++++++----------------
 src/bin/pg_rewind/filemap.h     |  67 ++--
 src/bin/pg_rewind/libpq_fetch.c |  12 +-
 src/bin/pg_rewind/parsexlog.c   |   2 +-
 src/bin/pg_rewind/pg_rewind.c   |   8 +-
 7 files changed, 371 insertions(+), 317 deletions(-)

diff --git a/src/bin/pg_rewind/copy_fetch.c b/src/bin/pg_rewind/copy_fetch.c
index 1edab5f1867..18fad32600e 100644
--- a/src/bin/pg_rewind/copy_fetch.c
+++ b/src/bin/pg_rewind/copy_fetch.c
@@ -210,7 +210,7 @@ copy_executeFileMap(filemap_t *map)
 	for (i = 0; i < map->narray; i++)
 	{
 		entry = map->array[i];
-		execute_pagemap(&entry->pagemap, entry->path);
+		execute_pagemap(&entry->target_modified_pages, entry->path);
 
 		switch (entry->action)
 		{
@@ -219,16 +219,16 @@ copy_executeFileMap(filemap_t *map)
 				break;
 
 			case FILE_ACTION_COPY:
-				rewind_copy_file_range(entry->path, 0, entry->newsize, true);
+				rewind_copy_file_range(entry->path, 0, entry->source_size, true);
 				break;
 
 			case FILE_ACTION_TRUNCATE:
-				truncate_target_file(entry->path, entry->newsize);
+				truncate_target_file(entry->path, entry->source_size);
 				break;
 
 			case FILE_ACTION_COPY_TAIL:
-				rewind_copy_file_range(entry->path, entry->oldsize,
-									   entry->newsize, false);
+				rewind_copy_file_range(entry->path, entry->target_size,
+									   entry->source_size, false);
 				break;
 
 			case FILE_ACTION_CREATE:
@@ -238,6 +238,10 @@ copy_executeFileMap(filemap_t *map)
 			case FILE_ACTION_REMOVE:
 				remove_target(entry);
 				break;
+
+			case FILE_ACTION_UNDECIDED:
+				pg_fatal("no action decided for \"%s\"", entry->path);
+				break;
 		}
 	}
 
diff --git a/src/bin/pg_rewind/file_ops.c b/src/bin/pg_rewind/file_ops.c
index 55439db20ba..ec37d0b2e0d 100644
--- a/src/bin/pg_rewind/file_ops.c
+++ b/src/bin/pg_rewind/file_ops.c
@@ -126,8 +126,9 @@ void
 remove_target(file_entry_t *entry)
 {
 	Assert(entry->action == FILE_ACTION_REMOVE);
+	Assert(entry->target_exists);
 
-	switch (entry->type)
+	switch (entry->target_type)
 	{
 		case FILE_TYPE_DIRECTORY:
 			remove_target_dir(entry->path);
@@ -140,6 +141,10 @@ remove_target(file_entry_t *entry)
 		case FILE_TYPE_SYMLINK:
 			remove_target_symlink(entry->path);
 			break;
+
+		case FILE_TYPE_UNDEFINED:
+			pg_fatal("undefined file type for \"%s\"", entry->path);
+			break;
 	}
 }
 
@@ -147,21 +152,26 @@ void
 create_target(file_entry_t *entry)
 {
 	Assert(entry->action == FILE_ACTION_CREATE);
+	Assert(!entry->target_exists);
 
-	switch (entry->type)
+	switch (entry->source_type)
 	{
 		case FILE_TYPE_DIRECTORY:
 			create_target_dir(entry->path);
 			break;
 
 		case FILE_TYPE_SYMLINK:
-			create_target_symlink(entry->path, entry->link_target);
+			create_target_symlink(entry->path, entry->source_link_target);
 			break;
 
 		case FILE_TYPE_REGULAR:
 			/* can't happen. Regular files are created with open_target_file. */
 			pg_fatal("invalid action (CREATE) for regular file");
 			break;
+
+		case FILE_TYPE_UNDEFINED:
+			pg_fatal("undefined file type for \"%s\"", entry->path);
+			break;
 	}
 }
 
diff --git a/src/bin/pg_rewind/filemap.c b/src/bin/pg_rewind/filemap.c
index 1879257b66a..7971daeda5e 100644
--- a/src/bin/pg_rewind/filemap.c
+++ b/src/bin/pg_rewind/filemap.c
@@ -26,6 +26,8 @@ static bool isRelDataFile(const char *path);
 static char *datasegpath(RelFileNode rnode, ForkNumber forknum,
 						 BlockNumber segno);
 static int	path_cmp(const void *a, const void *b);
+
+static file_entry_t *get_filemap_entry(const char *path, bool create);
 static int	final_filemap_cmp(const void *a, const void *b);
 static void filemap_list_to_array(filemap_t *map);
 static bool check_file_excluded(const char *path, bool is_source);
@@ -146,6 +148,65 @@ filemap_create(void)
 	filemap = map;
 }
 
+/* Look up or create entry for 'path' */
+static file_entry_t *
+get_filemap_entry(const char *path, bool create)
+{
+	filemap_t  *map = filemap;
+	file_entry_t *entry;
+	file_entry_t **e;
+	file_entry_t key;
+	file_entry_t *key_ptr;
+
+	if (map->array)
+	{
+		key.path = (char *) path;
+		key_ptr = &key;
+		e = bsearch(&key_ptr, map->array, map->narray, sizeof(file_entry_t *),
+					path_cmp);
+	}
+	else
+		e = NULL;
+
+	if (e)
+		entry = *e;
+	else if (!create)
+		entry = NULL;
+	else
+	{
+		/* Create a new entry for this file */
+		entry = pg_malloc(sizeof(file_entry_t));
+		entry->path = pg_strdup(path);
+		entry->isrelfile = isRelDataFile(path);
+		entry->action = FILE_ACTION_UNDECIDED;
+
+		entry->target_exists = false;
+		entry->target_type = FILE_TYPE_UNDEFINED;
+		entry->target_size = 0;
+		entry->target_link_target = NULL;
+		entry->target_modified_pages.bitmap = NULL;
+		entry->target_modified_pages.bitmapsize = 0;
+
+		entry->source_exists = false;
+		entry->source_type = FILE_TYPE_UNDEFINED;
+		entry->source_size = 0;
+		entry->source_link_target = NULL;
+
+		entry->next = NULL;
+
+		if (map->last)
+		{
+			map->last->next = entry;
+			map->last = entry;
+		}
+		else
+			map->first = map->last = entry;
+		map->nlist++;
+	}
+
+	return entry;
+}
+
 /*
  * Callback for processing source file list.
  *
@@ -154,25 +215,12 @@ filemap_create(void)
  * exists in the target and whether the size matches.
  */
 void
-process_source_file(const char *path, file_type_t type, size_t newsize,
+process_source_file(const char *path, file_type_t type, size_t size,
 					const char *link_target)
 {
-	bool		exists;
-	char		localpath[MAXPGPATH];
-	struct stat statbuf;
-	filemap_t  *map = filemap;
-	file_action_t action = FILE_ACTION_NONE;
-	size_t		oldsize = 0;
 	file_entry_t *entry;
 
-	Assert(map->array == NULL);
-
-	/*
-	 * Skip any files matching the exclusion filters. This has the effect to
-	 * remove all those files on the target.
-	 */
-	if (check_file_excluded(path, true))
-		return;
+	Assert(filemap->array == NULL);
 
 	/*
 	 * Pretend that pg_wal is a directory, even if it's really a symlink. We
@@ -182,16 +230,6 @@ process_source_file(const char *path, file_type_t type, size_t newsize,
 	if (strcmp(path, "pg_wal") == 0 && type == FILE_TYPE_SYMLINK)
 		type = FILE_TYPE_DIRECTORY;
 
-	/*
-	 * Skip temporary files, .../pgsql_tmp/... and .../pgsql_tmp.* in source.
-	 * This has the effect that all temporary files in the destination will be
-	 * removed.
-	 */
-	if (strstr(path, "/" PG_TEMP_FILE_PREFIX) != NULL)
-		return;
-	if (strstr(path, "/" PG_TEMP_FILES_DIR "/") != NULL)
-		return;
-
 	/*
 	 * sanity check: a filename that looks like a data file better be a
 	 * regular file
@@ -199,142 +237,12 @@ process_source_file(const char *path, file_type_t type, size_t newsize,
 	if (type != FILE_TYPE_REGULAR && isRelDataFile(path))
 		pg_fatal("data file \"%s\" in source is not a regular file", path);
 
-	snprintf(localpath, sizeof(localpath), "%s/%s", datadir_target, path);
-
-	/* Does the corresponding file exist in the target data dir? */
-	if (lstat(localpath, &statbuf) < 0)
-	{
-		if (errno != ENOENT)
-			pg_fatal("could not stat file \"%s\": %m",
-					 localpath);
-
-		exists = false;
-	}
-	else
-		exists = true;
-
-	switch (type)
-	{
-		case FILE_TYPE_DIRECTORY:
-			if (exists && !S_ISDIR(statbuf.st_mode) && strcmp(path, "pg_wal") != 0)
-			{
-				/* it's a directory in source, but not in target. Strange.. */
-				pg_fatal("\"%s\" is not a directory", localpath);
-			}
-
-			if (!exists)
-				action = FILE_ACTION_CREATE;
-			else
-				action = FILE_ACTION_NONE;
-			oldsize = 0;
-			break;
-
-		case FILE_TYPE_SYMLINK:
-			if (exists &&
-#ifndef WIN32
-				!S_ISLNK(statbuf.st_mode)
-#else
-				!pgwin32_is_junction(localpath)
-#endif
-				)
-			{
-				/*
-				 * It's a symbolic link in source, but not in target.
-				 * Strange..
-				 */
-				pg_fatal("\"%s\" is not a symbolic link", localpath);
-			}
-
-			if (!exists)
-				action = FILE_ACTION_CREATE;
-			else
-				action = FILE_ACTION_NONE;
-			oldsize = 0;
-			break;
-
-		case FILE_TYPE_REGULAR:
-			if (exists && !S_ISREG(statbuf.st_mode))
-				pg_fatal("\"%s\" is not a regular file", localpath);
-
-			if (!exists || !isRelDataFile(path))
-			{
-				/*
-				 * File exists in source, but not in target. Or it's a
-				 * non-data file that we have no special processing for. Copy
-				 * it in toto.
-				 *
-				 * An exception: PG_VERSIONs should be identical, but avoid
-				 * overwriting it for paranoia.
-				 */
-				if (pg_str_endswith(path, "PG_VERSION"))
-				{
-					action = FILE_ACTION_NONE;
-					oldsize = statbuf.st_size;
-				}
-				else
-				{
-					action = FILE_ACTION_COPY;
-					oldsize = 0;
-				}
-			}
-			else
-			{
-				/*
-				 * It's a data file that exists in both.
-				 *
-				 * If it's larger in target, we can truncate it. There will
-				 * also be a WAL record of the truncation in the source
-				 * system, so WAL replay would eventually truncate the target
-				 * too, but we might as well do it now.
-				 *
-				 * If it's smaller in the target, it means that it has been
-				 * truncated in the target, or enlarged in the source, or
-				 * both. If it was truncated in the target, we need to copy
-				 * the missing tail from the source system. If it was enlarged
-				 * in the source system, there will be WAL records in the
-				 * source system for the new blocks, so we wouldn't need to
-				 * copy them here. But we don't know which scenario we're
-				 * dealing with, and there's no harm in copying the missing
-				 * blocks now, so do it now.
-				 *
-				 * If it's the same size, do nothing here. Any blocks modified
-				 * in the target will be copied based on parsing the target
-				 * system's WAL, and any blocks modified in the source will be
-				 * updated after rewinding, when the source system's WAL is
-				 * replayed.
-				 */
-				oldsize = statbuf.st_size;
-				if (oldsize < newsize)
-					action = FILE_ACTION_COPY_TAIL;
-				else if (oldsize > newsize)
-					action = FILE_ACTION_TRUNCATE;
-				else
-					action = FILE_ACTION_NONE;
-			}
-			break;
-	}
-
-	/* Create a new entry for this file */
-	entry = pg_malloc(sizeof(file_entry_t));
-	entry->path = pg_strdup(path);
-	entry->type = type;
-	entry->action = action;
-	entry->oldsize = oldsize;
-	entry->newsize = newsize;
-	entry->link_target = link_target ? pg_strdup(link_target) : NULL;
-	entry->next = NULL;
-	entry->pagemap.bitmap = NULL;
-	entry->pagemap.bitmapsize = 0;
-	entry->isrelfile = isRelDataFile(path);
-
-	if (map->last)
-	{
-		map->last->next = entry;
-		map->last = entry;
-	}
-	else
-		map->first = map->last = entry;
-	map->nlist++;
+	/* Remember this source file */
+	entry = get_filemap_entry(path, true);
+	entry->source_exists = true;
+	entry->source_type = type;
+	entry->source_size = size;
+	entry->source_link_target = link_target ? pg_strdup(link_target) : NULL;
 }
 
 /*
@@ -345,14 +253,9 @@ process_source_file(const char *path, file_type_t type, size_t newsize,
  * deletion.
  */
 void
-process_target_file(const char *path, file_type_t type, size_t oldsize,
+process_target_file(const char *path, file_type_t type, size_t size,
 					const char *link_target)
 {
-	bool		exists;
-	char		localpath[MAXPGPATH];
-	struct stat statbuf;
-	file_entry_t key;
-	file_entry_t *key_ptr;
 	filemap_t  *map = filemap;
 	file_entry_t *entry;
 
@@ -361,17 +264,6 @@ process_target_file(const char *path, file_type_t type, size_t oldsize,
 	 * from the target data folder all paths which have been filtered out from
 	 * the source data folder when processing the source files.
 	 */
-
-	snprintf(localpath, sizeof(localpath), "%s/%s", datadir_target, path);
-	if (lstat(localpath, &statbuf) < 0)
-	{
-		if (errno != ENOENT)
-			pg_fatal("could not stat file \"%s\": %m",
-					 localpath);
-
-		exists = false;
-	}
-
 	if (map->array == NULL)
 	{
 		/* on first call, initialize lookup array */
@@ -389,120 +281,76 @@ process_target_file(const char *path, file_type_t type, size_t oldsize,
 	}
 
 	/*
-	 * Like in process_source_file, pretend that xlog is always a  directory.
+	 * Like in process_source_file, pretend that pg_wal is always a directory.
 	 */
 	if (strcmp(path, "pg_wal") == 0 && type == FILE_TYPE_SYMLINK)
 		type = FILE_TYPE_DIRECTORY;
 
-	key.path = (char *) path;
-	key_ptr = &key;
-	exists = (bsearch(&key_ptr, map->array, map->narray, sizeof(file_entry_t *),
-					  path_cmp) != NULL);
-
-	/* Remove any file or folder that doesn't exist in the source system. */
-	if (!exists)
-	{
-		entry = pg_malloc(sizeof(file_entry_t));
-		entry->path = pg_strdup(path);
-		entry->type = type;
-		entry->action = FILE_ACTION_REMOVE;
-		entry->oldsize = oldsize;
-		entry->newsize = 0;
-		entry->link_target = link_target ? pg_strdup(link_target) : NULL;
-		entry->next = NULL;
-		entry->pagemap.bitmap = NULL;
-		entry->pagemap.bitmapsize = 0;
-		entry->isrelfile = isRelDataFile(path);
-
-		if (map->last == NULL)
-			map->first = entry;
-		else
-			map->last->next = entry;
-		map->last = entry;
-		map->nlist++;
-	}
-	else
-	{
-		/*
-		 * We already handled all files that exist in the source system in
-		 * process_source_file().
-		 */
-	}
+	/* Remember this target file */
+	entry = get_filemap_entry(path, true);
+	entry->target_exists = true;
+	entry->target_type = type;
+	entry->target_size = size;
+	entry->target_link_target = link_target ? pg_strdup(link_target) : NULL;
 }
 
 /*
  * This callback gets called while we read the WAL in the target, for every
- * block that have changed in the target system. It makes note of all the
+ * block that have changed in the target system.  It makes note of all the
  * changed blocks in the pagemap of the file.
+ *
+ * NOTE: All the files on both systems must have already been added to the
+ * file map!
  */
 void
-process_block_change(ForkNumber forknum, RelFileNode rnode, BlockNumber blkno)
+process_target_wal_block_change(ForkNumber forknum, RelFileNode rnode,
+								BlockNumber blkno)
 {
 	char	   *path;
-	file_entry_t key;
-	file_entry_t *key_ptr;
 	file_entry_t *entry;
 	BlockNumber blkno_inseg;
 	int			segno;
-	filemap_t  *map = filemap;
-	file_entry_t **e;
 
-	Assert(map->array);
+	Assert(filemap->array);
 
 	segno = blkno / RELSEG_SIZE;
 	blkno_inseg = blkno % RELSEG_SIZE;
 
 	path = datasegpath(rnode, forknum, segno);
-
-	key.path = (char *) path;
-	key_ptr = &key;
-
-	e = bsearch(&key_ptr, map->array, map->narray, sizeof(file_entry_t *),
-				path_cmp);
-	if (e)
-		entry = *e;
-	else
-		entry = NULL;
+	entry = get_filemap_entry(path, false);
 	pfree(path);
 
 	if (entry)
 	{
-		Assert(entry->isrelfile);
-
-		switch (entry->action)
-		{
-			case FILE_ACTION_NONE:
-			case FILE_ACTION_TRUNCATE:
-				/* skip if we're truncating away the modified block anyway */
-				if ((blkno_inseg + 1) * BLCKSZ <= entry->newsize)
-					datapagemap_add(&entry->pagemap, blkno_inseg);
-				break;
+		int64		end_offset;
 
-			case FILE_ACTION_COPY_TAIL:
-
-				/*
-				 * skip the modified block if it is part of the "tail" that
-				 * we're copying anyway.
-				 */
-				if ((blkno_inseg + 1) * BLCKSZ <= entry->oldsize)
-					datapagemap_add(&entry->pagemap, blkno_inseg);
-				break;
+		Assert(entry->isrelfile);
 
-			case FILE_ACTION_COPY:
-			case FILE_ACTION_REMOVE:
-				break;
+		if (entry->target_type != FILE_TYPE_REGULAR)
+			pg_fatal("unexpected page modification for directory or symbolic link \"%s\"",
+					 entry->path);
 
-			case FILE_ACTION_CREATE:
-				pg_fatal("unexpected page modification for directory or symbolic link \"%s\"", entry->path);
-		}
+		/*
+		 * If the block beyond the EOF in the source system, no need to
+		 * remember it now, because we're going to truncate it away from the
+		 * target anyway. Also no need to remember the block if it's beyond
+		 * the current EOF in the target system; we will copy it over with the
+		 * "tail" from the source system, anyway.
+		 */
+		end_offset = (blkno_inseg + 1) * BLCKSZ;
+		if (end_offset <= entry->source_size &&
+			end_offset <= entry->target_size)
+			datapagemap_add(&entry->target_modified_pages, blkno_inseg);
 	}
 	else
 	{
 		/*
 		 * If we don't have any record of this file in the file map, it means
-		 * that it's a relation that doesn't exist in the source system, and
-		 * it was subsequently removed in the target system, too. We can
-		 * safely ignore it.
+		 * that it's a relation that doesn't exist in the source system.  It
+		 * could exist in the target system; we haven't moved the target-only
+		 * entries from the linked list to the array yet!  But in any case, if
+		 * it doesn't exist in the source it will be removed from the target
+		 * too, and we can safely ignore it.
 		 */
 	}
 }
@@ -593,16 +441,6 @@ filemap_list_to_array(filemap_t *map)
 	map->first = map->last = NULL;
 }
 
-void
-filemap_finalize(void)
-{
-	filemap_t  *map = filemap;
-
-	filemap_list_to_array(map);
-	qsort(map->array, map->narray, sizeof(file_entry_t *),
-		  final_filemap_cmp);
-}
-
 static const char *
 action_to_str(file_action_t action)
 {
@@ -643,26 +481,26 @@ calculate_totals(void)
 	{
 		entry = map->array[i];
 
-		if (entry->type != FILE_TYPE_REGULAR)
+		if (entry->source_type != FILE_TYPE_REGULAR)
 			continue;
 
-		map->total_size += entry->newsize;
+		map->total_size += entry->source_size;
 
 		if (entry->action == FILE_ACTION_COPY)
 		{
-			map->fetch_size += entry->newsize;
+			map->fetch_size += entry->source_size;
 			continue;
 		}
 
 		if (entry->action == FILE_ACTION_COPY_TAIL)
-			map->fetch_size += (entry->newsize - entry->oldsize);
+			map->fetch_size += (entry->source_size - entry->target_size);
 
-		if (entry->pagemap.bitmapsize > 0)
+		if (entry->target_modified_pages.bitmapsize > 0)
 		{
 			datapagemap_iterator_t *iter;
 			BlockNumber blk;
 
-			iter = datapagemap_iterate(&entry->pagemap);
+			iter = datapagemap_iterate(&entry->target_modified_pages);
 			while (datapagemap_next(iter, &blk))
 				map->fetch_size += BLCKSZ;
 
@@ -682,13 +520,13 @@ print_filemap(void)
 	{
 		entry = map->array[i];
 		if (entry->action != FILE_ACTION_NONE ||
-			entry->pagemap.bitmapsize > 0)
+			entry->target_modified_pages.bitmapsize > 0)
 		{
 			pg_log_debug("%s (%s)", entry->path,
 						 action_to_str(entry->action));
 
-			if (entry->pagemap.bitmapsize > 0)
-				datapagemap_print(&entry->pagemap);
+			if (entry->target_modified_pages.bitmapsize > 0)
+				datapagemap_print(&entry->target_modified_pages);
 		}
 	}
 	fflush(stdout);
@@ -837,3 +675,180 @@ final_filemap_cmp(const void *a, const void *b)
 	else
 		return strcmp(fa->path, fb->path);
 }
+
+/*
+ * Decide what action to perform to a file.
+ */
+static file_action_t
+decide_file_action(file_entry_t *entry)
+{
+	const char *path = entry->path;
+
+	/*
+	 * Don't touch the control file. It is handled specially, after copying
+	 * all the other files.
+	 */
+	if (strcmp(path, "global/pg_control") == 0)
+		return FILE_ACTION_NONE;
+
+	/*
+	 * Remove all files matching the exclusion filters in the target.
+	 */
+	if (check_file_excluded(path, true))
+	{
+		if (entry->target_exists)
+			return FILE_ACTION_REMOVE;
+		else
+			return FILE_ACTION_NONE;
+	}
+
+	/*
+	 * Also remove all temporary files, .../pgsql_tmp/... and .../pgsql_tmp.*
+	 * in the target.
+	 */
+	if (strstr(path, "/" PG_TEMP_FILE_PREFIX) != NULL ||
+		strstr(path, "/" PG_TEMP_FILES_DIR "/") != NULL)
+	{
+		if (entry->target_exists)
+			return FILE_ACTION_REMOVE;
+		else
+			return FILE_ACTION_NONE;
+	}
+
+	/*
+	 * Handle cases where the file is missing from one of the systems.
+	 */
+	if (!entry->target_exists && entry->source_exists)
+	{
+		/*
+		 * File exists in source, but not in target. Copy it in toto. (If it's
+		 * a relation data file, WAL replay after rewinding should re-create
+		 * it anyway. But there's no harm in copying it now.)
+		 */
+		switch (entry->source_type)
+		{
+			case FILE_TYPE_DIRECTORY:
+			case FILE_TYPE_SYMLINK:
+				return FILE_ACTION_CREATE;
+			case FILE_TYPE_REGULAR:
+				return FILE_ACTION_COPY;
+			case FILE_TYPE_UNDEFINED:
+				pg_fatal("unknown file type for \"%s\"", entry->path);
+				break;
+		}
+	}
+	else if (entry->target_exists && !entry->source_exists)
+	{
+		/* File exists in target, but not source. Remove it. */
+		return FILE_ACTION_REMOVE;
+	}
+	else if (!entry->target_exists && !entry->source_exists)
+	{
+		/*
+		 * Doesn't exist in either server. Why does it have an entry in the
+		 * first place??
+		 */
+		return FILE_ACTION_NONE;
+	}
+
+	/*
+	 * Otherwise, the file exists on both systems
+	 */
+	Assert(entry->target_exists && entry->source_exists);
+
+	if (entry->source_type != entry->target_type)
+	{
+		/* But it's a different kind of object. Strange.. */
+		pg_fatal("file \"%s\" is of different type in source and target", entry->path);
+	}
+
+	/*
+	 * PG_VERSION files should be identical on both systems, but avoid
+	 * overwriting them for paranoia.
+	 */
+	if (pg_str_endswith(entry->path, "PG_VERSION"))
+		return FILE_ACTION_NONE;
+
+	switch (entry->source_type)
+	{
+		case FILE_TYPE_DIRECTORY:
+			return FILE_ACTION_NONE;
+
+		case FILE_TYPE_SYMLINK:
+			/* FIXME: Check if it points to the same target? */
+			return FILE_ACTION_NONE;
+
+		case FILE_TYPE_REGULAR:
+			if (!entry->isrelfile)
+			{
+				/*
+				 * It's a non-data file that we have no special processing
+				 * for. Copy it in toto.
+				 */
+				return FILE_ACTION_COPY;
+			}
+			else
+			{
+				/*
+				 * It's a data file that exists in both systems.
+				 *
+				 * If it's larger in target, we can truncate it. There will
+				 * also be a WAL record of the truncation in the source
+				 * system, so WAL replay would eventually truncate the target
+				 * too, but we might as well do it now.
+				 *
+				 * If it's smaller in the target, it means that it has been
+				 * truncated in the target, or enlarged in the source, or
+				 * both. If it was truncated in the target, we need to copy
+				 * the missing tail from the source system. If it was enlarged
+				 * in the source system, there will be WAL records in the
+				 * source system for the new blocks, so we wouldn't need to
+				 * copy them here. But we don't know which scenario we're
+				 * dealing with, and there's no harm in copying the missing
+				 * blocks now, so do it now.
+				 *
+				 * If it's the same size, do nothing here. Any blocks modified
+				 * in the target will be copied based on parsing the target
+				 * system's WAL, and any blocks modified in the source will be
+				 * updated after rewinding, when the source system's WAL is
+				 * replayed.
+				 */
+				if (entry->target_size < entry->source_size)
+					return FILE_ACTION_COPY_TAIL;
+				else if (entry->target_size > entry->source_size)
+					return FILE_ACTION_TRUNCATE;
+				else
+					return FILE_ACTION_NONE;
+			}
+			break;
+
+		case FILE_TYPE_UNDEFINED:
+			pg_fatal("unknown file type for \"%s\"", path);
+			break;
+	}
+
+	/* unreachable */
+	pg_fatal("could not decide what to do with file \"%s\"", path);
+}
+
+/*
+ * Decide what to do with each file.
+ */
+void
+filemap_finalize()
+{
+	int			i;
+
+	filemap_list_to_array(filemap);
+
+	for (i = 0; i < filemap->narray; i++)
+	{
+		file_entry_t *entry = filemap->array[i];
+
+		entry->action = decide_file_action(entry);
+	}
+
+	/* Sort the actions to the order that they should be performed */
+	qsort(filemap->array, filemap->narray, sizeof(file_entry_t *),
+		  final_filemap_cmp);
+}
diff --git a/src/bin/pg_rewind/filemap.h b/src/bin/pg_rewind/filemap.h
index 0cb7425170c..a5e8df57f40 100644
--- a/src/bin/pg_rewind/filemap.h
+++ b/src/bin/pg_rewind/filemap.h
@@ -14,17 +14,21 @@
 
 /*
  * For every file found in the local or remote system, we have a file entry
- * which says what we are going to do with the file. For relation files,
- * there is also a page map, marking pages in the file that were changed
- * locally.
- *
- * The enum values are sorted in the order we want actions to be processed.
+ * that contains information about the file on both systems.  For relation
+ * files, there is also a page map that marks pages in the file that were
+ * changed in the target after the last common checkpoint.  Each entry also
+ * contains an 'action' field, which says what we are going to do with the
+ * file.
  */
+
+/* these enum values are sorted in the order we want actions to be processed */
 typedef enum
 {
+	FILE_ACTION_UNDECIDED = 0,	/* not decided yet */
+
 	FILE_ACTION_CREATE,			/* create local directory or symbolic link */
 	FILE_ACTION_COPY,			/* copy whole file, overwriting if exists */
-	FILE_ACTION_COPY_TAIL,		/* copy tail from 'oldsize' to 'newsize' */
+	FILE_ACTION_COPY_TAIL,		/* copy tail from 'source_size' to 'target_size' */
 	FILE_ACTION_NONE,			/* no action (we might still copy modified
 								 * blocks based on the parsed WAL) */
 	FILE_ACTION_TRUNCATE,		/* truncate local file to 'newsize' bytes */
@@ -33,6 +37,8 @@ typedef enum
 
 typedef enum
 {
+	FILE_TYPE_UNDEFINED = 0,
+
 	FILE_TYPE_REGULAR,
 	FILE_TYPE_DIRECTORY,
 	FILE_TYPE_SYMLINK
@@ -41,19 +47,30 @@ typedef enum
 typedef struct file_entry_t
 {
 	char	   *path;
-	file_type_t type;
+	bool		isrelfile;		/* is it a relation data file? */
 
-	file_action_t action;
+	/*
+	 * Status of the file in the target.
+	 */
+	bool		target_exists;
+	file_type_t target_type;
+	size_t		target_size; /* for a regular file */
+	char	   *target_link_target; /* for a symlink */
 
-	/* for a regular file */
-	size_t		oldsize;
-	size_t		newsize;
-	bool		isrelfile;		/* is it a relation data file? */
+	datapagemap_t target_modified_pages;
 
-	datapagemap_t pagemap;
+	/*
+	 * Status of the file in the source.
+	 */
+	bool		source_exists;
+	file_type_t source_type;
+	size_t		source_size;
+	char	   *source_link_target; /* for a symlink */
 
-	/* for a symlink */
-	char	   *link_target;
+	/*
+	 * What will we do to the file?
+	 */
+	file_action_t action;
 
 	struct file_entry_t *next;
 } file_entry_t;
@@ -70,20 +87,19 @@ typedef struct filemap_t
 
 	/*
 	 * After processing all the remote files, the entries in the linked list
-	 * are moved to this array. After processing local files, too, all the
+	 * are moved to this array.  After processing local files, too, all the
 	 * local entries are added to the array by filemap_finalize, and sorted in
-	 * the final order. After filemap_finalize, all the entries are in the
+	 * the final order.  After filemap_finalize, all the entries are in the
 	 * array, and the linked list is empty.
 	 */
 	file_entry_t **array;
 	int			narray;			/* current length of array */
 
 	/*
-	 * Summary information. total_size is the total size of the source
-	 * cluster, and fetch_size is the number of bytes that needs to be copied.
+	 * Summary information.
 	 */
-	uint64		total_size;
-	uint64		fetch_size;
+	uint64		total_size;		/* total size of the source cluster */
+	uint64		fetch_size;		/* number of bytes that needs to be copied */
 } filemap_t;
 
 extern filemap_t *filemap;
@@ -94,11 +110,12 @@ extern void print_filemap(void);
 
 /* Functions for populating the filemap */
 extern void process_source_file(const char *path, file_type_t type,
-								size_t newsize, const char *link_target);
+								size_t size, const char *link_target);
 extern void process_target_file(const char *path, file_type_t type,
-								size_t newsize, const char *link_target);
-extern void process_block_change(ForkNumber forknum, RelFileNode rnode,
-								 BlockNumber blkno);
+								size_t size, const char *link_target);
+extern void process_target_wal_block_change(ForkNumber forknum,
+											RelFileNode rnode,
+											BlockNumber blkno);
 extern void filemap_finalize(void);
 
 #endif							/* FILEMAP_H */
diff --git a/src/bin/pg_rewind/libpq_fetch.c b/src/bin/pg_rewind/libpq_fetch.c
index bf4dfc23b96..7fc9161b8c8 100644
--- a/src/bin/pg_rewind/libpq_fetch.c
+++ b/src/bin/pg_rewind/libpq_fetch.c
@@ -465,7 +465,7 @@ libpq_executeFileMap(filemap_t *map)
 		entry = map->array[i];
 
 		/* If this is a relation file, copy the modified blocks */
-		execute_pagemap(&entry->pagemap, entry->path);
+		execute_pagemap(&entry->target_modified_pages, entry->path);
 
 		switch (entry->action)
 		{
@@ -476,15 +476,15 @@ libpq_executeFileMap(filemap_t *map)
 			case FILE_ACTION_COPY:
 				/* Truncate the old file out of the way, if any */
 				open_target_file(entry->path, true);
-				fetch_file_range(entry->path, 0, entry->newsize);
+				fetch_file_range(entry->path, 0, entry->source_size);
 				break;
 
 			case FILE_ACTION_TRUNCATE:
-				truncate_target_file(entry->path, entry->newsize);
+				truncate_target_file(entry->path, entry->source_size);
 				break;
 
 			case FILE_ACTION_COPY_TAIL:
-				fetch_file_range(entry->path, entry->oldsize, entry->newsize);
+				fetch_file_range(entry->path, entry->target_size, entry->source_size);
 				break;
 
 			case FILE_ACTION_REMOVE:
@@ -494,6 +494,10 @@ libpq_executeFileMap(filemap_t *map)
 			case FILE_ACTION_CREATE:
 				create_target(entry);
 				break;
+
+			case FILE_ACTION_UNDECIDED:
+				pg_fatal("no action decided for \"%s\"", entry->path);
+				break;
 		}
 	}
 
diff --git a/src/bin/pg_rewind/parsexlog.c b/src/bin/pg_rewind/parsexlog.c
index 2229c86f9af..2baeb74ae93 100644
--- a/src/bin/pg_rewind/parsexlog.c
+++ b/src/bin/pg_rewind/parsexlog.c
@@ -436,6 +436,6 @@ extractPageInfo(XLogReaderState *record)
 		if (forknum != MAIN_FORKNUM)
 			continue;
 
-		process_block_change(forknum, rnode, blkno);
+		process_target_wal_block_change(forknum, rnode, blkno);
 	}
 }
diff --git a/src/bin/pg_rewind/pg_rewind.c b/src/bin/pg_rewind/pg_rewind.c
index c9b9e480c0f..210984d302b 100644
--- a/src/bin/pg_rewind/pg_rewind.c
+++ b/src/bin/pg_rewind/pg_rewind.c
@@ -369,7 +369,7 @@ main(int argc, char **argv)
 				chkpttli);
 
 	/*
-	 * Build the filemap, by comparing the source and target data directories.
+	 * Collect information about all files in the target and source systems.
 	 */
 	filemap_create();
 	if (showprogress)
@@ -390,8 +390,12 @@ main(int argc, char **argv)
 		pg_log_info("reading WAL in target");
 	extractPageMap(datadir_target, chkptrec, lastcommontliIndex,
 				   ControlFile_target.checkPoint, restore_command);
-	filemap_finalize();
 
+	/*
+	 * We have collected all information we need from both systems. Decide
+	 * what to do with each file.
+	 */
+	filemap_finalize();
 	if (showprogress)
 		calculate_totals();
 
-- 
2.20.1


--------------BF34D0120055DC3839060F92
Content-Type: text/x-patch; charset=UTF-8;
 name="v2-0003-pg_rewind-Replace-the-hybrid-list-array-data-stru.patch"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
 filename*0="v2-0003-pg_rewind-Replace-the-hybrid-list-array-data-stru.pa";
 filename*1="tch"



^ permalink  raw  reply  [nested|flat] 41+ messages in thread

* [PATCH v2 2/5] Refactor pg_rewind for more clear decision making.
@ 2020-08-19 12:34 Heikki Linnakangas <[email protected]>
  0 siblings, 0 replies; 41+ messages in thread

From: Heikki Linnakangas @ 2020-08-19 12:34 UTC (permalink / raw)

Deciding what to do with each file is now a separate step after all the
necessary information has been gathered. It is more clear that way.
Previously, the decision-making was divided between process_source_file()
and process_target_file(), and it was a bit hard to piece together what the
overall rules were.
---
 src/bin/pg_rewind/copy_fetch.c  |  14 +-
 src/bin/pg_rewind/file_ops.c    |  16 +-
 src/bin/pg_rewind/filemap.c     | 569 ++++++++++++++++----------------
 src/bin/pg_rewind/filemap.h     |  67 ++--
 src/bin/pg_rewind/libpq_fetch.c |  12 +-
 src/bin/pg_rewind/parsexlog.c   |   2 +-
 src/bin/pg_rewind/pg_rewind.c   |   8 +-
 7 files changed, 371 insertions(+), 317 deletions(-)

diff --git a/src/bin/pg_rewind/copy_fetch.c b/src/bin/pg_rewind/copy_fetch.c
index 1edab5f1867..18fad32600e 100644
--- a/src/bin/pg_rewind/copy_fetch.c
+++ b/src/bin/pg_rewind/copy_fetch.c
@@ -210,7 +210,7 @@ copy_executeFileMap(filemap_t *map)
 	for (i = 0; i < map->narray; i++)
 	{
 		entry = map->array[i];
-		execute_pagemap(&entry->pagemap, entry->path);
+		execute_pagemap(&entry->target_modified_pages, entry->path);
 
 		switch (entry->action)
 		{
@@ -219,16 +219,16 @@ copy_executeFileMap(filemap_t *map)
 				break;
 
 			case FILE_ACTION_COPY:
-				rewind_copy_file_range(entry->path, 0, entry->newsize, true);
+				rewind_copy_file_range(entry->path, 0, entry->source_size, true);
 				break;
 
 			case FILE_ACTION_TRUNCATE:
-				truncate_target_file(entry->path, entry->newsize);
+				truncate_target_file(entry->path, entry->source_size);
 				break;
 
 			case FILE_ACTION_COPY_TAIL:
-				rewind_copy_file_range(entry->path, entry->oldsize,
-									   entry->newsize, false);
+				rewind_copy_file_range(entry->path, entry->target_size,
+									   entry->source_size, false);
 				break;
 
 			case FILE_ACTION_CREATE:
@@ -238,6 +238,10 @@ copy_executeFileMap(filemap_t *map)
 			case FILE_ACTION_REMOVE:
 				remove_target(entry);
 				break;
+
+			case FILE_ACTION_UNDECIDED:
+				pg_fatal("no action decided for \"%s\"", entry->path);
+				break;
 		}
 	}
 
diff --git a/src/bin/pg_rewind/file_ops.c b/src/bin/pg_rewind/file_ops.c
index 55439db20ba..ec37d0b2e0d 100644
--- a/src/bin/pg_rewind/file_ops.c
+++ b/src/bin/pg_rewind/file_ops.c
@@ -126,8 +126,9 @@ void
 remove_target(file_entry_t *entry)
 {
 	Assert(entry->action == FILE_ACTION_REMOVE);
+	Assert(entry->target_exists);
 
-	switch (entry->type)
+	switch (entry->target_type)
 	{
 		case FILE_TYPE_DIRECTORY:
 			remove_target_dir(entry->path);
@@ -140,6 +141,10 @@ remove_target(file_entry_t *entry)
 		case FILE_TYPE_SYMLINK:
 			remove_target_symlink(entry->path);
 			break;
+
+		case FILE_TYPE_UNDEFINED:
+			pg_fatal("undefined file type for \"%s\"", entry->path);
+			break;
 	}
 }
 
@@ -147,21 +152,26 @@ void
 create_target(file_entry_t *entry)
 {
 	Assert(entry->action == FILE_ACTION_CREATE);
+	Assert(!entry->target_exists);
 
-	switch (entry->type)
+	switch (entry->source_type)
 	{
 		case FILE_TYPE_DIRECTORY:
 			create_target_dir(entry->path);
 			break;
 
 		case FILE_TYPE_SYMLINK:
-			create_target_symlink(entry->path, entry->link_target);
+			create_target_symlink(entry->path, entry->source_link_target);
 			break;
 
 		case FILE_TYPE_REGULAR:
 			/* can't happen. Regular files are created with open_target_file. */
 			pg_fatal("invalid action (CREATE) for regular file");
 			break;
+
+		case FILE_TYPE_UNDEFINED:
+			pg_fatal("undefined file type for \"%s\"", entry->path);
+			break;
 	}
 }
 
diff --git a/src/bin/pg_rewind/filemap.c b/src/bin/pg_rewind/filemap.c
index 1879257b66a..7971daeda5e 100644
--- a/src/bin/pg_rewind/filemap.c
+++ b/src/bin/pg_rewind/filemap.c
@@ -26,6 +26,8 @@ static bool isRelDataFile(const char *path);
 static char *datasegpath(RelFileNode rnode, ForkNumber forknum,
 						 BlockNumber segno);
 static int	path_cmp(const void *a, const void *b);
+
+static file_entry_t *get_filemap_entry(const char *path, bool create);
 static int	final_filemap_cmp(const void *a, const void *b);
 static void filemap_list_to_array(filemap_t *map);
 static bool check_file_excluded(const char *path, bool is_source);
@@ -146,6 +148,65 @@ filemap_create(void)
 	filemap = map;
 }
 
+/* Look up or create entry for 'path' */
+static file_entry_t *
+get_filemap_entry(const char *path, bool create)
+{
+	filemap_t  *map = filemap;
+	file_entry_t *entry;
+	file_entry_t **e;
+	file_entry_t key;
+	file_entry_t *key_ptr;
+
+	if (map->array)
+	{
+		key.path = (char *) path;
+		key_ptr = &key;
+		e = bsearch(&key_ptr, map->array, map->narray, sizeof(file_entry_t *),
+					path_cmp);
+	}
+	else
+		e = NULL;
+
+	if (e)
+		entry = *e;
+	else if (!create)
+		entry = NULL;
+	else
+	{
+		/* Create a new entry for this file */
+		entry = pg_malloc(sizeof(file_entry_t));
+		entry->path = pg_strdup(path);
+		entry->isrelfile = isRelDataFile(path);
+		entry->action = FILE_ACTION_UNDECIDED;
+
+		entry->target_exists = false;
+		entry->target_type = FILE_TYPE_UNDEFINED;
+		entry->target_size = 0;
+		entry->target_link_target = NULL;
+		entry->target_modified_pages.bitmap = NULL;
+		entry->target_modified_pages.bitmapsize = 0;
+
+		entry->source_exists = false;
+		entry->source_type = FILE_TYPE_UNDEFINED;
+		entry->source_size = 0;
+		entry->source_link_target = NULL;
+
+		entry->next = NULL;
+
+		if (map->last)
+		{
+			map->last->next = entry;
+			map->last = entry;
+		}
+		else
+			map->first = map->last = entry;
+		map->nlist++;
+	}
+
+	return entry;
+}
+
 /*
  * Callback for processing source file list.
  *
@@ -154,25 +215,12 @@ filemap_create(void)
  * exists in the target and whether the size matches.
  */
 void
-process_source_file(const char *path, file_type_t type, size_t newsize,
+process_source_file(const char *path, file_type_t type, size_t size,
 					const char *link_target)
 {
-	bool		exists;
-	char		localpath[MAXPGPATH];
-	struct stat statbuf;
-	filemap_t  *map = filemap;
-	file_action_t action = FILE_ACTION_NONE;
-	size_t		oldsize = 0;
 	file_entry_t *entry;
 
-	Assert(map->array == NULL);
-
-	/*
-	 * Skip any files matching the exclusion filters. This has the effect to
-	 * remove all those files on the target.
-	 */
-	if (check_file_excluded(path, true))
-		return;
+	Assert(filemap->array == NULL);
 
 	/*
 	 * Pretend that pg_wal is a directory, even if it's really a symlink. We
@@ -182,16 +230,6 @@ process_source_file(const char *path, file_type_t type, size_t newsize,
 	if (strcmp(path, "pg_wal") == 0 && type == FILE_TYPE_SYMLINK)
 		type = FILE_TYPE_DIRECTORY;
 
-	/*
-	 * Skip temporary files, .../pgsql_tmp/... and .../pgsql_tmp.* in source.
-	 * This has the effect that all temporary files in the destination will be
-	 * removed.
-	 */
-	if (strstr(path, "/" PG_TEMP_FILE_PREFIX) != NULL)
-		return;
-	if (strstr(path, "/" PG_TEMP_FILES_DIR "/") != NULL)
-		return;
-
 	/*
 	 * sanity check: a filename that looks like a data file better be a
 	 * regular file
@@ -199,142 +237,12 @@ process_source_file(const char *path, file_type_t type, size_t newsize,
 	if (type != FILE_TYPE_REGULAR && isRelDataFile(path))
 		pg_fatal("data file \"%s\" in source is not a regular file", path);
 
-	snprintf(localpath, sizeof(localpath), "%s/%s", datadir_target, path);
-
-	/* Does the corresponding file exist in the target data dir? */
-	if (lstat(localpath, &statbuf) < 0)
-	{
-		if (errno != ENOENT)
-			pg_fatal("could not stat file \"%s\": %m",
-					 localpath);
-
-		exists = false;
-	}
-	else
-		exists = true;
-
-	switch (type)
-	{
-		case FILE_TYPE_DIRECTORY:
-			if (exists && !S_ISDIR(statbuf.st_mode) && strcmp(path, "pg_wal") != 0)
-			{
-				/* it's a directory in source, but not in target. Strange.. */
-				pg_fatal("\"%s\" is not a directory", localpath);
-			}
-
-			if (!exists)
-				action = FILE_ACTION_CREATE;
-			else
-				action = FILE_ACTION_NONE;
-			oldsize = 0;
-			break;
-
-		case FILE_TYPE_SYMLINK:
-			if (exists &&
-#ifndef WIN32
-				!S_ISLNK(statbuf.st_mode)
-#else
-				!pgwin32_is_junction(localpath)
-#endif
-				)
-			{
-				/*
-				 * It's a symbolic link in source, but not in target.
-				 * Strange..
-				 */
-				pg_fatal("\"%s\" is not a symbolic link", localpath);
-			}
-
-			if (!exists)
-				action = FILE_ACTION_CREATE;
-			else
-				action = FILE_ACTION_NONE;
-			oldsize = 0;
-			break;
-
-		case FILE_TYPE_REGULAR:
-			if (exists && !S_ISREG(statbuf.st_mode))
-				pg_fatal("\"%s\" is not a regular file", localpath);
-
-			if (!exists || !isRelDataFile(path))
-			{
-				/*
-				 * File exists in source, but not in target. Or it's a
-				 * non-data file that we have no special processing for. Copy
-				 * it in toto.
-				 *
-				 * An exception: PG_VERSIONs should be identical, but avoid
-				 * overwriting it for paranoia.
-				 */
-				if (pg_str_endswith(path, "PG_VERSION"))
-				{
-					action = FILE_ACTION_NONE;
-					oldsize = statbuf.st_size;
-				}
-				else
-				{
-					action = FILE_ACTION_COPY;
-					oldsize = 0;
-				}
-			}
-			else
-			{
-				/*
-				 * It's a data file that exists in both.
-				 *
-				 * If it's larger in target, we can truncate it. There will
-				 * also be a WAL record of the truncation in the source
-				 * system, so WAL replay would eventually truncate the target
-				 * too, but we might as well do it now.
-				 *
-				 * If it's smaller in the target, it means that it has been
-				 * truncated in the target, or enlarged in the source, or
-				 * both. If it was truncated in the target, we need to copy
-				 * the missing tail from the source system. If it was enlarged
-				 * in the source system, there will be WAL records in the
-				 * source system for the new blocks, so we wouldn't need to
-				 * copy them here. But we don't know which scenario we're
-				 * dealing with, and there's no harm in copying the missing
-				 * blocks now, so do it now.
-				 *
-				 * If it's the same size, do nothing here. Any blocks modified
-				 * in the target will be copied based on parsing the target
-				 * system's WAL, and any blocks modified in the source will be
-				 * updated after rewinding, when the source system's WAL is
-				 * replayed.
-				 */
-				oldsize = statbuf.st_size;
-				if (oldsize < newsize)
-					action = FILE_ACTION_COPY_TAIL;
-				else if (oldsize > newsize)
-					action = FILE_ACTION_TRUNCATE;
-				else
-					action = FILE_ACTION_NONE;
-			}
-			break;
-	}
-
-	/* Create a new entry for this file */
-	entry = pg_malloc(sizeof(file_entry_t));
-	entry->path = pg_strdup(path);
-	entry->type = type;
-	entry->action = action;
-	entry->oldsize = oldsize;
-	entry->newsize = newsize;
-	entry->link_target = link_target ? pg_strdup(link_target) : NULL;
-	entry->next = NULL;
-	entry->pagemap.bitmap = NULL;
-	entry->pagemap.bitmapsize = 0;
-	entry->isrelfile = isRelDataFile(path);
-
-	if (map->last)
-	{
-		map->last->next = entry;
-		map->last = entry;
-	}
-	else
-		map->first = map->last = entry;
-	map->nlist++;
+	/* Remember this source file */
+	entry = get_filemap_entry(path, true);
+	entry->source_exists = true;
+	entry->source_type = type;
+	entry->source_size = size;
+	entry->source_link_target = link_target ? pg_strdup(link_target) : NULL;
 }
 
 /*
@@ -345,14 +253,9 @@ process_source_file(const char *path, file_type_t type, size_t newsize,
  * deletion.
  */
 void
-process_target_file(const char *path, file_type_t type, size_t oldsize,
+process_target_file(const char *path, file_type_t type, size_t size,
 					const char *link_target)
 {
-	bool		exists;
-	char		localpath[MAXPGPATH];
-	struct stat statbuf;
-	file_entry_t key;
-	file_entry_t *key_ptr;
 	filemap_t  *map = filemap;
 	file_entry_t *entry;
 
@@ -361,17 +264,6 @@ process_target_file(const char *path, file_type_t type, size_t oldsize,
 	 * from the target data folder all paths which have been filtered out from
 	 * the source data folder when processing the source files.
 	 */
-
-	snprintf(localpath, sizeof(localpath), "%s/%s", datadir_target, path);
-	if (lstat(localpath, &statbuf) < 0)
-	{
-		if (errno != ENOENT)
-			pg_fatal("could not stat file \"%s\": %m",
-					 localpath);
-
-		exists = false;
-	}
-
 	if (map->array == NULL)
 	{
 		/* on first call, initialize lookup array */
@@ -389,120 +281,76 @@ process_target_file(const char *path, file_type_t type, size_t oldsize,
 	}
 
 	/*
-	 * Like in process_source_file, pretend that xlog is always a  directory.
+	 * Like in process_source_file, pretend that pg_wal is always a directory.
 	 */
 	if (strcmp(path, "pg_wal") == 0 && type == FILE_TYPE_SYMLINK)
 		type = FILE_TYPE_DIRECTORY;
 
-	key.path = (char *) path;
-	key_ptr = &key;
-	exists = (bsearch(&key_ptr, map->array, map->narray, sizeof(file_entry_t *),
-					  path_cmp) != NULL);
-
-	/* Remove any file or folder that doesn't exist in the source system. */
-	if (!exists)
-	{
-		entry = pg_malloc(sizeof(file_entry_t));
-		entry->path = pg_strdup(path);
-		entry->type = type;
-		entry->action = FILE_ACTION_REMOVE;
-		entry->oldsize = oldsize;
-		entry->newsize = 0;
-		entry->link_target = link_target ? pg_strdup(link_target) : NULL;
-		entry->next = NULL;
-		entry->pagemap.bitmap = NULL;
-		entry->pagemap.bitmapsize = 0;
-		entry->isrelfile = isRelDataFile(path);
-
-		if (map->last == NULL)
-			map->first = entry;
-		else
-			map->last->next = entry;
-		map->last = entry;
-		map->nlist++;
-	}
-	else
-	{
-		/*
-		 * We already handled all files that exist in the source system in
-		 * process_source_file().
-		 */
-	}
+	/* Remember this target file */
+	entry = get_filemap_entry(path, true);
+	entry->target_exists = true;
+	entry->target_type = type;
+	entry->target_size = size;
+	entry->target_link_target = link_target ? pg_strdup(link_target) : NULL;
 }
 
 /*
  * This callback gets called while we read the WAL in the target, for every
- * block that have changed in the target system. It makes note of all the
+ * block that have changed in the target system.  It makes note of all the
  * changed blocks in the pagemap of the file.
+ *
+ * NOTE: All the files on both systems must have already been added to the
+ * file map!
  */
 void
-process_block_change(ForkNumber forknum, RelFileNode rnode, BlockNumber blkno)
+process_target_wal_block_change(ForkNumber forknum, RelFileNode rnode,
+								BlockNumber blkno)
 {
 	char	   *path;
-	file_entry_t key;
-	file_entry_t *key_ptr;
 	file_entry_t *entry;
 	BlockNumber blkno_inseg;
 	int			segno;
-	filemap_t  *map = filemap;
-	file_entry_t **e;
 
-	Assert(map->array);
+	Assert(filemap->array);
 
 	segno = blkno / RELSEG_SIZE;
 	blkno_inseg = blkno % RELSEG_SIZE;
 
 	path = datasegpath(rnode, forknum, segno);
-
-	key.path = (char *) path;
-	key_ptr = &key;
-
-	e = bsearch(&key_ptr, map->array, map->narray, sizeof(file_entry_t *),
-				path_cmp);
-	if (e)
-		entry = *e;
-	else
-		entry = NULL;
+	entry = get_filemap_entry(path, false);
 	pfree(path);
 
 	if (entry)
 	{
-		Assert(entry->isrelfile);
-
-		switch (entry->action)
-		{
-			case FILE_ACTION_NONE:
-			case FILE_ACTION_TRUNCATE:
-				/* skip if we're truncating away the modified block anyway */
-				if ((blkno_inseg + 1) * BLCKSZ <= entry->newsize)
-					datapagemap_add(&entry->pagemap, blkno_inseg);
-				break;
+		int64		end_offset;
 
-			case FILE_ACTION_COPY_TAIL:
-
-				/*
-				 * skip the modified block if it is part of the "tail" that
-				 * we're copying anyway.
-				 */
-				if ((blkno_inseg + 1) * BLCKSZ <= entry->oldsize)
-					datapagemap_add(&entry->pagemap, blkno_inseg);
-				break;
+		Assert(entry->isrelfile);
 
-			case FILE_ACTION_COPY:
-			case FILE_ACTION_REMOVE:
-				break;
+		if (entry->target_type != FILE_TYPE_REGULAR)
+			pg_fatal("unexpected page modification for directory or symbolic link \"%s\"",
+					 entry->path);
 
-			case FILE_ACTION_CREATE:
-				pg_fatal("unexpected page modification for directory or symbolic link \"%s\"", entry->path);
-		}
+		/*
+		 * If the block beyond the EOF in the source system, no need to
+		 * remember it now, because we're going to truncate it away from the
+		 * target anyway. Also no need to remember the block if it's beyond
+		 * the current EOF in the target system; we will copy it over with the
+		 * "tail" from the source system, anyway.
+		 */
+		end_offset = (blkno_inseg + 1) * BLCKSZ;
+		if (end_offset <= entry->source_size &&
+			end_offset <= entry->target_size)
+			datapagemap_add(&entry->target_modified_pages, blkno_inseg);
 	}
 	else
 	{
 		/*
 		 * If we don't have any record of this file in the file map, it means
-		 * that it's a relation that doesn't exist in the source system, and
-		 * it was subsequently removed in the target system, too. We can
-		 * safely ignore it.
+		 * that it's a relation that doesn't exist in the source system.  It
+		 * could exist in the target system; we haven't moved the target-only
+		 * entries from the linked list to the array yet!  But in any case, if
+		 * it doesn't exist in the source it will be removed from the target
+		 * too, and we can safely ignore it.
 		 */
 	}
 }
@@ -593,16 +441,6 @@ filemap_list_to_array(filemap_t *map)
 	map->first = map->last = NULL;
 }
 
-void
-filemap_finalize(void)
-{
-	filemap_t  *map = filemap;
-
-	filemap_list_to_array(map);
-	qsort(map->array, map->narray, sizeof(file_entry_t *),
-		  final_filemap_cmp);
-}
-
 static const char *
 action_to_str(file_action_t action)
 {
@@ -643,26 +481,26 @@ calculate_totals(void)
 	{
 		entry = map->array[i];
 
-		if (entry->type != FILE_TYPE_REGULAR)
+		if (entry->source_type != FILE_TYPE_REGULAR)
 			continue;
 
-		map->total_size += entry->newsize;
+		map->total_size += entry->source_size;
 
 		if (entry->action == FILE_ACTION_COPY)
 		{
-			map->fetch_size += entry->newsize;
+			map->fetch_size += entry->source_size;
 			continue;
 		}
 
 		if (entry->action == FILE_ACTION_COPY_TAIL)
-			map->fetch_size += (entry->newsize - entry->oldsize);
+			map->fetch_size += (entry->source_size - entry->target_size);
 
-		if (entry->pagemap.bitmapsize > 0)
+		if (entry->target_modified_pages.bitmapsize > 0)
 		{
 			datapagemap_iterator_t *iter;
 			BlockNumber blk;
 
-			iter = datapagemap_iterate(&entry->pagemap);
+			iter = datapagemap_iterate(&entry->target_modified_pages);
 			while (datapagemap_next(iter, &blk))
 				map->fetch_size += BLCKSZ;
 
@@ -682,13 +520,13 @@ print_filemap(void)
 	{
 		entry = map->array[i];
 		if (entry->action != FILE_ACTION_NONE ||
-			entry->pagemap.bitmapsize > 0)
+			entry->target_modified_pages.bitmapsize > 0)
 		{
 			pg_log_debug("%s (%s)", entry->path,
 						 action_to_str(entry->action));
 
-			if (entry->pagemap.bitmapsize > 0)
-				datapagemap_print(&entry->pagemap);
+			if (entry->target_modified_pages.bitmapsize > 0)
+				datapagemap_print(&entry->target_modified_pages);
 		}
 	}
 	fflush(stdout);
@@ -837,3 +675,180 @@ final_filemap_cmp(const void *a, const void *b)
 	else
 		return strcmp(fa->path, fb->path);
 }
+
+/*
+ * Decide what action to perform to a file.
+ */
+static file_action_t
+decide_file_action(file_entry_t *entry)
+{
+	const char *path = entry->path;
+
+	/*
+	 * Don't touch the control file. It is handled specially, after copying
+	 * all the other files.
+	 */
+	if (strcmp(path, "global/pg_control") == 0)
+		return FILE_ACTION_NONE;
+
+	/*
+	 * Remove all files matching the exclusion filters in the target.
+	 */
+	if (check_file_excluded(path, true))
+	{
+		if (entry->target_exists)
+			return FILE_ACTION_REMOVE;
+		else
+			return FILE_ACTION_NONE;
+	}
+
+	/*
+	 * Also remove all temporary files, .../pgsql_tmp/... and .../pgsql_tmp.*
+	 * in the target.
+	 */
+	if (strstr(path, "/" PG_TEMP_FILE_PREFIX) != NULL ||
+		strstr(path, "/" PG_TEMP_FILES_DIR "/") != NULL)
+	{
+		if (entry->target_exists)
+			return FILE_ACTION_REMOVE;
+		else
+			return FILE_ACTION_NONE;
+	}
+
+	/*
+	 * Handle cases where the file is missing from one of the systems.
+	 */
+	if (!entry->target_exists && entry->source_exists)
+	{
+		/*
+		 * File exists in source, but not in target. Copy it in toto. (If it's
+		 * a relation data file, WAL replay after rewinding should re-create
+		 * it anyway. But there's no harm in copying it now.)
+		 */
+		switch (entry->source_type)
+		{
+			case FILE_TYPE_DIRECTORY:
+			case FILE_TYPE_SYMLINK:
+				return FILE_ACTION_CREATE;
+			case FILE_TYPE_REGULAR:
+				return FILE_ACTION_COPY;
+			case FILE_TYPE_UNDEFINED:
+				pg_fatal("unknown file type for \"%s\"", entry->path);
+				break;
+		}
+	}
+	else if (entry->target_exists && !entry->source_exists)
+	{
+		/* File exists in target, but not source. Remove it. */
+		return FILE_ACTION_REMOVE;
+	}
+	else if (!entry->target_exists && !entry->source_exists)
+	{
+		/*
+		 * Doesn't exist in either server. Why does it have an entry in the
+		 * first place??
+		 */
+		return FILE_ACTION_NONE;
+	}
+
+	/*
+	 * Otherwise, the file exists on both systems
+	 */
+	Assert(entry->target_exists && entry->source_exists);
+
+	if (entry->source_type != entry->target_type)
+	{
+		/* But it's a different kind of object. Strange.. */
+		pg_fatal("file \"%s\" is of different type in source and target", entry->path);
+	}
+
+	/*
+	 * PG_VERSION files should be identical on both systems, but avoid
+	 * overwriting them for paranoia.
+	 */
+	if (pg_str_endswith(entry->path, "PG_VERSION"))
+		return FILE_ACTION_NONE;
+
+	switch (entry->source_type)
+	{
+		case FILE_TYPE_DIRECTORY:
+			return FILE_ACTION_NONE;
+
+		case FILE_TYPE_SYMLINK:
+			/* FIXME: Check if it points to the same target? */
+			return FILE_ACTION_NONE;
+
+		case FILE_TYPE_REGULAR:
+			if (!entry->isrelfile)
+			{
+				/*
+				 * It's a non-data file that we have no special processing
+				 * for. Copy it in toto.
+				 */
+				return FILE_ACTION_COPY;
+			}
+			else
+			{
+				/*
+				 * It's a data file that exists in both systems.
+				 *
+				 * If it's larger in target, we can truncate it. There will
+				 * also be a WAL record of the truncation in the source
+				 * system, so WAL replay would eventually truncate the target
+				 * too, but we might as well do it now.
+				 *
+				 * If it's smaller in the target, it means that it has been
+				 * truncated in the target, or enlarged in the source, or
+				 * both. If it was truncated in the target, we need to copy
+				 * the missing tail from the source system. If it was enlarged
+				 * in the source system, there will be WAL records in the
+				 * source system for the new blocks, so we wouldn't need to
+				 * copy them here. But we don't know which scenario we're
+				 * dealing with, and there's no harm in copying the missing
+				 * blocks now, so do it now.
+				 *
+				 * If it's the same size, do nothing here. Any blocks modified
+				 * in the target will be copied based on parsing the target
+				 * system's WAL, and any blocks modified in the source will be
+				 * updated after rewinding, when the source system's WAL is
+				 * replayed.
+				 */
+				if (entry->target_size < entry->source_size)
+					return FILE_ACTION_COPY_TAIL;
+				else if (entry->target_size > entry->source_size)
+					return FILE_ACTION_TRUNCATE;
+				else
+					return FILE_ACTION_NONE;
+			}
+			break;
+
+		case FILE_TYPE_UNDEFINED:
+			pg_fatal("unknown file type for \"%s\"", path);
+			break;
+	}
+
+	/* unreachable */
+	pg_fatal("could not decide what to do with file \"%s\"", path);
+}
+
+/*
+ * Decide what to do with each file.
+ */
+void
+filemap_finalize()
+{
+	int			i;
+
+	filemap_list_to_array(filemap);
+
+	for (i = 0; i < filemap->narray; i++)
+	{
+		file_entry_t *entry = filemap->array[i];
+
+		entry->action = decide_file_action(entry);
+	}
+
+	/* Sort the actions to the order that they should be performed */
+	qsort(filemap->array, filemap->narray, sizeof(file_entry_t *),
+		  final_filemap_cmp);
+}
diff --git a/src/bin/pg_rewind/filemap.h b/src/bin/pg_rewind/filemap.h
index 0cb7425170c..a5e8df57f40 100644
--- a/src/bin/pg_rewind/filemap.h
+++ b/src/bin/pg_rewind/filemap.h
@@ -14,17 +14,21 @@
 
 /*
  * For every file found in the local or remote system, we have a file entry
- * which says what we are going to do with the file. For relation files,
- * there is also a page map, marking pages in the file that were changed
- * locally.
- *
- * The enum values are sorted in the order we want actions to be processed.
+ * that contains information about the file on both systems.  For relation
+ * files, there is also a page map that marks pages in the file that were
+ * changed in the target after the last common checkpoint.  Each entry also
+ * contains an 'action' field, which says what we are going to do with the
+ * file.
  */
+
+/* these enum values are sorted in the order we want actions to be processed */
 typedef enum
 {
+	FILE_ACTION_UNDECIDED = 0,	/* not decided yet */
+
 	FILE_ACTION_CREATE,			/* create local directory or symbolic link */
 	FILE_ACTION_COPY,			/* copy whole file, overwriting if exists */
-	FILE_ACTION_COPY_TAIL,		/* copy tail from 'oldsize' to 'newsize' */
+	FILE_ACTION_COPY_TAIL,		/* copy tail from 'source_size' to 'target_size' */
 	FILE_ACTION_NONE,			/* no action (we might still copy modified
 								 * blocks based on the parsed WAL) */
 	FILE_ACTION_TRUNCATE,		/* truncate local file to 'newsize' bytes */
@@ -33,6 +37,8 @@ typedef enum
 
 typedef enum
 {
+	FILE_TYPE_UNDEFINED = 0,
+
 	FILE_TYPE_REGULAR,
 	FILE_TYPE_DIRECTORY,
 	FILE_TYPE_SYMLINK
@@ -41,19 +47,30 @@ typedef enum
 typedef struct file_entry_t
 {
 	char	   *path;
-	file_type_t type;
+	bool		isrelfile;		/* is it a relation data file? */
 
-	file_action_t action;
+	/*
+	 * Status of the file in the target.
+	 */
+	bool		target_exists;
+	file_type_t target_type;
+	size_t		target_size; /* for a regular file */
+	char	   *target_link_target; /* for a symlink */
 
-	/* for a regular file */
-	size_t		oldsize;
-	size_t		newsize;
-	bool		isrelfile;		/* is it a relation data file? */
+	datapagemap_t target_modified_pages;
 
-	datapagemap_t pagemap;
+	/*
+	 * Status of the file in the source.
+	 */
+	bool		source_exists;
+	file_type_t source_type;
+	size_t		source_size;
+	char	   *source_link_target; /* for a symlink */
 
-	/* for a symlink */
-	char	   *link_target;
+	/*
+	 * What will we do to the file?
+	 */
+	file_action_t action;
 
 	struct file_entry_t *next;
 } file_entry_t;
@@ -70,20 +87,19 @@ typedef struct filemap_t
 
 	/*
 	 * After processing all the remote files, the entries in the linked list
-	 * are moved to this array. After processing local files, too, all the
+	 * are moved to this array.  After processing local files, too, all the
 	 * local entries are added to the array by filemap_finalize, and sorted in
-	 * the final order. After filemap_finalize, all the entries are in the
+	 * the final order.  After filemap_finalize, all the entries are in the
 	 * array, and the linked list is empty.
 	 */
 	file_entry_t **array;
 	int			narray;			/* current length of array */
 
 	/*
-	 * Summary information. total_size is the total size of the source
-	 * cluster, and fetch_size is the number of bytes that needs to be copied.
+	 * Summary information.
 	 */
-	uint64		total_size;
-	uint64		fetch_size;
+	uint64		total_size;		/* total size of the source cluster */
+	uint64		fetch_size;		/* number of bytes that needs to be copied */
 } filemap_t;
 
 extern filemap_t *filemap;
@@ -94,11 +110,12 @@ extern void print_filemap(void);
 
 /* Functions for populating the filemap */
 extern void process_source_file(const char *path, file_type_t type,
-								size_t newsize, const char *link_target);
+								size_t size, const char *link_target);
 extern void process_target_file(const char *path, file_type_t type,
-								size_t newsize, const char *link_target);
-extern void process_block_change(ForkNumber forknum, RelFileNode rnode,
-								 BlockNumber blkno);
+								size_t size, const char *link_target);
+extern void process_target_wal_block_change(ForkNumber forknum,
+											RelFileNode rnode,
+											BlockNumber blkno);
 extern void filemap_finalize(void);
 
 #endif							/* FILEMAP_H */
diff --git a/src/bin/pg_rewind/libpq_fetch.c b/src/bin/pg_rewind/libpq_fetch.c
index bf4dfc23b96..7fc9161b8c8 100644
--- a/src/bin/pg_rewind/libpq_fetch.c
+++ b/src/bin/pg_rewind/libpq_fetch.c
@@ -465,7 +465,7 @@ libpq_executeFileMap(filemap_t *map)
 		entry = map->array[i];
 
 		/* If this is a relation file, copy the modified blocks */
-		execute_pagemap(&entry->pagemap, entry->path);
+		execute_pagemap(&entry->target_modified_pages, entry->path);
 
 		switch (entry->action)
 		{
@@ -476,15 +476,15 @@ libpq_executeFileMap(filemap_t *map)
 			case FILE_ACTION_COPY:
 				/* Truncate the old file out of the way, if any */
 				open_target_file(entry->path, true);
-				fetch_file_range(entry->path, 0, entry->newsize);
+				fetch_file_range(entry->path, 0, entry->source_size);
 				break;
 
 			case FILE_ACTION_TRUNCATE:
-				truncate_target_file(entry->path, entry->newsize);
+				truncate_target_file(entry->path, entry->source_size);
 				break;
 
 			case FILE_ACTION_COPY_TAIL:
-				fetch_file_range(entry->path, entry->oldsize, entry->newsize);
+				fetch_file_range(entry->path, entry->target_size, entry->source_size);
 				break;
 
 			case FILE_ACTION_REMOVE:
@@ -494,6 +494,10 @@ libpq_executeFileMap(filemap_t *map)
 			case FILE_ACTION_CREATE:
 				create_target(entry);
 				break;
+
+			case FILE_ACTION_UNDECIDED:
+				pg_fatal("no action decided for \"%s\"", entry->path);
+				break;
 		}
 	}
 
diff --git a/src/bin/pg_rewind/parsexlog.c b/src/bin/pg_rewind/parsexlog.c
index 2229c86f9af..2baeb74ae93 100644
--- a/src/bin/pg_rewind/parsexlog.c
+++ b/src/bin/pg_rewind/parsexlog.c
@@ -436,6 +436,6 @@ extractPageInfo(XLogReaderState *record)
 		if (forknum != MAIN_FORKNUM)
 			continue;
 
-		process_block_change(forknum, rnode, blkno);
+		process_target_wal_block_change(forknum, rnode, blkno);
 	}
 }
diff --git a/src/bin/pg_rewind/pg_rewind.c b/src/bin/pg_rewind/pg_rewind.c
index c9b9e480c0f..210984d302b 100644
--- a/src/bin/pg_rewind/pg_rewind.c
+++ b/src/bin/pg_rewind/pg_rewind.c
@@ -369,7 +369,7 @@ main(int argc, char **argv)
 				chkpttli);
 
 	/*
-	 * Build the filemap, by comparing the source and target data directories.
+	 * Collect information about all files in the target and source systems.
 	 */
 	filemap_create();
 	if (showprogress)
@@ -390,8 +390,12 @@ main(int argc, char **argv)
 		pg_log_info("reading WAL in target");
 	extractPageMap(datadir_target, chkptrec, lastcommontliIndex,
 				   ControlFile_target.checkPoint, restore_command);
-	filemap_finalize();
 
+	/*
+	 * We have collected all information we need from both systems. Decide
+	 * what to do with each file.
+	 */
+	filemap_finalize();
 	if (showprogress)
 		calculate_totals();
 
-- 
2.20.1


--------------BF34D0120055DC3839060F92
Content-Type: text/x-patch; charset=UTF-8;
 name="v2-0003-pg_rewind-Replace-the-hybrid-list-array-data-stru.patch"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
 filename*0="v2-0003-pg_rewind-Replace-the-hybrid-list-array-data-stru.pa";
 filename*1="tch"



^ permalink  raw  reply  [nested|flat] 41+ messages in thread

* [PATCH v2 2/5] Refactor pg_rewind for more clear decision making.
@ 2020-08-19 12:34 Heikki Linnakangas <[email protected]>
  0 siblings, 0 replies; 41+ messages in thread

From: Heikki Linnakangas @ 2020-08-19 12:34 UTC (permalink / raw)

Deciding what to do with each file is now a separate step after all the
necessary information has been gathered. It is more clear that way.
Previously, the decision-making was divided between process_source_file()
and process_target_file(), and it was a bit hard to piece together what the
overall rules were.
---
 src/bin/pg_rewind/copy_fetch.c  |  14 +-
 src/bin/pg_rewind/file_ops.c    |  16 +-
 src/bin/pg_rewind/filemap.c     | 569 ++++++++++++++++----------------
 src/bin/pg_rewind/filemap.h     |  67 ++--
 src/bin/pg_rewind/libpq_fetch.c |  12 +-
 src/bin/pg_rewind/parsexlog.c   |   2 +-
 src/bin/pg_rewind/pg_rewind.c   |   8 +-
 7 files changed, 371 insertions(+), 317 deletions(-)

diff --git a/src/bin/pg_rewind/copy_fetch.c b/src/bin/pg_rewind/copy_fetch.c
index 1edab5f1867..18fad32600e 100644
--- a/src/bin/pg_rewind/copy_fetch.c
+++ b/src/bin/pg_rewind/copy_fetch.c
@@ -210,7 +210,7 @@ copy_executeFileMap(filemap_t *map)
 	for (i = 0; i < map->narray; i++)
 	{
 		entry = map->array[i];
-		execute_pagemap(&entry->pagemap, entry->path);
+		execute_pagemap(&entry->target_modified_pages, entry->path);
 
 		switch (entry->action)
 		{
@@ -219,16 +219,16 @@ copy_executeFileMap(filemap_t *map)
 				break;
 
 			case FILE_ACTION_COPY:
-				rewind_copy_file_range(entry->path, 0, entry->newsize, true);
+				rewind_copy_file_range(entry->path, 0, entry->source_size, true);
 				break;
 
 			case FILE_ACTION_TRUNCATE:
-				truncate_target_file(entry->path, entry->newsize);
+				truncate_target_file(entry->path, entry->source_size);
 				break;
 
 			case FILE_ACTION_COPY_TAIL:
-				rewind_copy_file_range(entry->path, entry->oldsize,
-									   entry->newsize, false);
+				rewind_copy_file_range(entry->path, entry->target_size,
+									   entry->source_size, false);
 				break;
 
 			case FILE_ACTION_CREATE:
@@ -238,6 +238,10 @@ copy_executeFileMap(filemap_t *map)
 			case FILE_ACTION_REMOVE:
 				remove_target(entry);
 				break;
+
+			case FILE_ACTION_UNDECIDED:
+				pg_fatal("no action decided for \"%s\"", entry->path);
+				break;
 		}
 	}
 
diff --git a/src/bin/pg_rewind/file_ops.c b/src/bin/pg_rewind/file_ops.c
index 55439db20ba..ec37d0b2e0d 100644
--- a/src/bin/pg_rewind/file_ops.c
+++ b/src/bin/pg_rewind/file_ops.c
@@ -126,8 +126,9 @@ void
 remove_target(file_entry_t *entry)
 {
 	Assert(entry->action == FILE_ACTION_REMOVE);
+	Assert(entry->target_exists);
 
-	switch (entry->type)
+	switch (entry->target_type)
 	{
 		case FILE_TYPE_DIRECTORY:
 			remove_target_dir(entry->path);
@@ -140,6 +141,10 @@ remove_target(file_entry_t *entry)
 		case FILE_TYPE_SYMLINK:
 			remove_target_symlink(entry->path);
 			break;
+
+		case FILE_TYPE_UNDEFINED:
+			pg_fatal("undefined file type for \"%s\"", entry->path);
+			break;
 	}
 }
 
@@ -147,21 +152,26 @@ void
 create_target(file_entry_t *entry)
 {
 	Assert(entry->action == FILE_ACTION_CREATE);
+	Assert(!entry->target_exists);
 
-	switch (entry->type)
+	switch (entry->source_type)
 	{
 		case FILE_TYPE_DIRECTORY:
 			create_target_dir(entry->path);
 			break;
 
 		case FILE_TYPE_SYMLINK:
-			create_target_symlink(entry->path, entry->link_target);
+			create_target_symlink(entry->path, entry->source_link_target);
 			break;
 
 		case FILE_TYPE_REGULAR:
 			/* can't happen. Regular files are created with open_target_file. */
 			pg_fatal("invalid action (CREATE) for regular file");
 			break;
+
+		case FILE_TYPE_UNDEFINED:
+			pg_fatal("undefined file type for \"%s\"", entry->path);
+			break;
 	}
 }
 
diff --git a/src/bin/pg_rewind/filemap.c b/src/bin/pg_rewind/filemap.c
index 1879257b66a..7971daeda5e 100644
--- a/src/bin/pg_rewind/filemap.c
+++ b/src/bin/pg_rewind/filemap.c
@@ -26,6 +26,8 @@ static bool isRelDataFile(const char *path);
 static char *datasegpath(RelFileNode rnode, ForkNumber forknum,
 						 BlockNumber segno);
 static int	path_cmp(const void *a, const void *b);
+
+static file_entry_t *get_filemap_entry(const char *path, bool create);
 static int	final_filemap_cmp(const void *a, const void *b);
 static void filemap_list_to_array(filemap_t *map);
 static bool check_file_excluded(const char *path, bool is_source);
@@ -146,6 +148,65 @@ filemap_create(void)
 	filemap = map;
 }
 
+/* Look up or create entry for 'path' */
+static file_entry_t *
+get_filemap_entry(const char *path, bool create)
+{
+	filemap_t  *map = filemap;
+	file_entry_t *entry;
+	file_entry_t **e;
+	file_entry_t key;
+	file_entry_t *key_ptr;
+
+	if (map->array)
+	{
+		key.path = (char *) path;
+		key_ptr = &key;
+		e = bsearch(&key_ptr, map->array, map->narray, sizeof(file_entry_t *),
+					path_cmp);
+	}
+	else
+		e = NULL;
+
+	if (e)
+		entry = *e;
+	else if (!create)
+		entry = NULL;
+	else
+	{
+		/* Create a new entry for this file */
+		entry = pg_malloc(sizeof(file_entry_t));
+		entry->path = pg_strdup(path);
+		entry->isrelfile = isRelDataFile(path);
+		entry->action = FILE_ACTION_UNDECIDED;
+
+		entry->target_exists = false;
+		entry->target_type = FILE_TYPE_UNDEFINED;
+		entry->target_size = 0;
+		entry->target_link_target = NULL;
+		entry->target_modified_pages.bitmap = NULL;
+		entry->target_modified_pages.bitmapsize = 0;
+
+		entry->source_exists = false;
+		entry->source_type = FILE_TYPE_UNDEFINED;
+		entry->source_size = 0;
+		entry->source_link_target = NULL;
+
+		entry->next = NULL;
+
+		if (map->last)
+		{
+			map->last->next = entry;
+			map->last = entry;
+		}
+		else
+			map->first = map->last = entry;
+		map->nlist++;
+	}
+
+	return entry;
+}
+
 /*
  * Callback for processing source file list.
  *
@@ -154,25 +215,12 @@ filemap_create(void)
  * exists in the target and whether the size matches.
  */
 void
-process_source_file(const char *path, file_type_t type, size_t newsize,
+process_source_file(const char *path, file_type_t type, size_t size,
 					const char *link_target)
 {
-	bool		exists;
-	char		localpath[MAXPGPATH];
-	struct stat statbuf;
-	filemap_t  *map = filemap;
-	file_action_t action = FILE_ACTION_NONE;
-	size_t		oldsize = 0;
 	file_entry_t *entry;
 
-	Assert(map->array == NULL);
-
-	/*
-	 * Skip any files matching the exclusion filters. This has the effect to
-	 * remove all those files on the target.
-	 */
-	if (check_file_excluded(path, true))
-		return;
+	Assert(filemap->array == NULL);
 
 	/*
 	 * Pretend that pg_wal is a directory, even if it's really a symlink. We
@@ -182,16 +230,6 @@ process_source_file(const char *path, file_type_t type, size_t newsize,
 	if (strcmp(path, "pg_wal") == 0 && type == FILE_TYPE_SYMLINK)
 		type = FILE_TYPE_DIRECTORY;
 
-	/*
-	 * Skip temporary files, .../pgsql_tmp/... and .../pgsql_tmp.* in source.
-	 * This has the effect that all temporary files in the destination will be
-	 * removed.
-	 */
-	if (strstr(path, "/" PG_TEMP_FILE_PREFIX) != NULL)
-		return;
-	if (strstr(path, "/" PG_TEMP_FILES_DIR "/") != NULL)
-		return;
-
 	/*
 	 * sanity check: a filename that looks like a data file better be a
 	 * regular file
@@ -199,142 +237,12 @@ process_source_file(const char *path, file_type_t type, size_t newsize,
 	if (type != FILE_TYPE_REGULAR && isRelDataFile(path))
 		pg_fatal("data file \"%s\" in source is not a regular file", path);
 
-	snprintf(localpath, sizeof(localpath), "%s/%s", datadir_target, path);
-
-	/* Does the corresponding file exist in the target data dir? */
-	if (lstat(localpath, &statbuf) < 0)
-	{
-		if (errno != ENOENT)
-			pg_fatal("could not stat file \"%s\": %m",
-					 localpath);
-
-		exists = false;
-	}
-	else
-		exists = true;
-
-	switch (type)
-	{
-		case FILE_TYPE_DIRECTORY:
-			if (exists && !S_ISDIR(statbuf.st_mode) && strcmp(path, "pg_wal") != 0)
-			{
-				/* it's a directory in source, but not in target. Strange.. */
-				pg_fatal("\"%s\" is not a directory", localpath);
-			}
-
-			if (!exists)
-				action = FILE_ACTION_CREATE;
-			else
-				action = FILE_ACTION_NONE;
-			oldsize = 0;
-			break;
-
-		case FILE_TYPE_SYMLINK:
-			if (exists &&
-#ifndef WIN32
-				!S_ISLNK(statbuf.st_mode)
-#else
-				!pgwin32_is_junction(localpath)
-#endif
-				)
-			{
-				/*
-				 * It's a symbolic link in source, but not in target.
-				 * Strange..
-				 */
-				pg_fatal("\"%s\" is not a symbolic link", localpath);
-			}
-
-			if (!exists)
-				action = FILE_ACTION_CREATE;
-			else
-				action = FILE_ACTION_NONE;
-			oldsize = 0;
-			break;
-
-		case FILE_TYPE_REGULAR:
-			if (exists && !S_ISREG(statbuf.st_mode))
-				pg_fatal("\"%s\" is not a regular file", localpath);
-
-			if (!exists || !isRelDataFile(path))
-			{
-				/*
-				 * File exists in source, but not in target. Or it's a
-				 * non-data file that we have no special processing for. Copy
-				 * it in toto.
-				 *
-				 * An exception: PG_VERSIONs should be identical, but avoid
-				 * overwriting it for paranoia.
-				 */
-				if (pg_str_endswith(path, "PG_VERSION"))
-				{
-					action = FILE_ACTION_NONE;
-					oldsize = statbuf.st_size;
-				}
-				else
-				{
-					action = FILE_ACTION_COPY;
-					oldsize = 0;
-				}
-			}
-			else
-			{
-				/*
-				 * It's a data file that exists in both.
-				 *
-				 * If it's larger in target, we can truncate it. There will
-				 * also be a WAL record of the truncation in the source
-				 * system, so WAL replay would eventually truncate the target
-				 * too, but we might as well do it now.
-				 *
-				 * If it's smaller in the target, it means that it has been
-				 * truncated in the target, or enlarged in the source, or
-				 * both. If it was truncated in the target, we need to copy
-				 * the missing tail from the source system. If it was enlarged
-				 * in the source system, there will be WAL records in the
-				 * source system for the new blocks, so we wouldn't need to
-				 * copy them here. But we don't know which scenario we're
-				 * dealing with, and there's no harm in copying the missing
-				 * blocks now, so do it now.
-				 *
-				 * If it's the same size, do nothing here. Any blocks modified
-				 * in the target will be copied based on parsing the target
-				 * system's WAL, and any blocks modified in the source will be
-				 * updated after rewinding, when the source system's WAL is
-				 * replayed.
-				 */
-				oldsize = statbuf.st_size;
-				if (oldsize < newsize)
-					action = FILE_ACTION_COPY_TAIL;
-				else if (oldsize > newsize)
-					action = FILE_ACTION_TRUNCATE;
-				else
-					action = FILE_ACTION_NONE;
-			}
-			break;
-	}
-
-	/* Create a new entry for this file */
-	entry = pg_malloc(sizeof(file_entry_t));
-	entry->path = pg_strdup(path);
-	entry->type = type;
-	entry->action = action;
-	entry->oldsize = oldsize;
-	entry->newsize = newsize;
-	entry->link_target = link_target ? pg_strdup(link_target) : NULL;
-	entry->next = NULL;
-	entry->pagemap.bitmap = NULL;
-	entry->pagemap.bitmapsize = 0;
-	entry->isrelfile = isRelDataFile(path);
-
-	if (map->last)
-	{
-		map->last->next = entry;
-		map->last = entry;
-	}
-	else
-		map->first = map->last = entry;
-	map->nlist++;
+	/* Remember this source file */
+	entry = get_filemap_entry(path, true);
+	entry->source_exists = true;
+	entry->source_type = type;
+	entry->source_size = size;
+	entry->source_link_target = link_target ? pg_strdup(link_target) : NULL;
 }
 
 /*
@@ -345,14 +253,9 @@ process_source_file(const char *path, file_type_t type, size_t newsize,
  * deletion.
  */
 void
-process_target_file(const char *path, file_type_t type, size_t oldsize,
+process_target_file(const char *path, file_type_t type, size_t size,
 					const char *link_target)
 {
-	bool		exists;
-	char		localpath[MAXPGPATH];
-	struct stat statbuf;
-	file_entry_t key;
-	file_entry_t *key_ptr;
 	filemap_t  *map = filemap;
 	file_entry_t *entry;
 
@@ -361,17 +264,6 @@ process_target_file(const char *path, file_type_t type, size_t oldsize,
 	 * from the target data folder all paths which have been filtered out from
 	 * the source data folder when processing the source files.
 	 */
-
-	snprintf(localpath, sizeof(localpath), "%s/%s", datadir_target, path);
-	if (lstat(localpath, &statbuf) < 0)
-	{
-		if (errno != ENOENT)
-			pg_fatal("could not stat file \"%s\": %m",
-					 localpath);
-
-		exists = false;
-	}
-
 	if (map->array == NULL)
 	{
 		/* on first call, initialize lookup array */
@@ -389,120 +281,76 @@ process_target_file(const char *path, file_type_t type, size_t oldsize,
 	}
 
 	/*
-	 * Like in process_source_file, pretend that xlog is always a  directory.
+	 * Like in process_source_file, pretend that pg_wal is always a directory.
 	 */
 	if (strcmp(path, "pg_wal") == 0 && type == FILE_TYPE_SYMLINK)
 		type = FILE_TYPE_DIRECTORY;
 
-	key.path = (char *) path;
-	key_ptr = &key;
-	exists = (bsearch(&key_ptr, map->array, map->narray, sizeof(file_entry_t *),
-					  path_cmp) != NULL);
-
-	/* Remove any file or folder that doesn't exist in the source system. */
-	if (!exists)
-	{
-		entry = pg_malloc(sizeof(file_entry_t));
-		entry->path = pg_strdup(path);
-		entry->type = type;
-		entry->action = FILE_ACTION_REMOVE;
-		entry->oldsize = oldsize;
-		entry->newsize = 0;
-		entry->link_target = link_target ? pg_strdup(link_target) : NULL;
-		entry->next = NULL;
-		entry->pagemap.bitmap = NULL;
-		entry->pagemap.bitmapsize = 0;
-		entry->isrelfile = isRelDataFile(path);
-
-		if (map->last == NULL)
-			map->first = entry;
-		else
-			map->last->next = entry;
-		map->last = entry;
-		map->nlist++;
-	}
-	else
-	{
-		/*
-		 * We already handled all files that exist in the source system in
-		 * process_source_file().
-		 */
-	}
+	/* Remember this target file */
+	entry = get_filemap_entry(path, true);
+	entry->target_exists = true;
+	entry->target_type = type;
+	entry->target_size = size;
+	entry->target_link_target = link_target ? pg_strdup(link_target) : NULL;
 }
 
 /*
  * This callback gets called while we read the WAL in the target, for every
- * block that have changed in the target system. It makes note of all the
+ * block that have changed in the target system.  It makes note of all the
  * changed blocks in the pagemap of the file.
+ *
+ * NOTE: All the files on both systems must have already been added to the
+ * file map!
  */
 void
-process_block_change(ForkNumber forknum, RelFileNode rnode, BlockNumber blkno)
+process_target_wal_block_change(ForkNumber forknum, RelFileNode rnode,
+								BlockNumber blkno)
 {
 	char	   *path;
-	file_entry_t key;
-	file_entry_t *key_ptr;
 	file_entry_t *entry;
 	BlockNumber blkno_inseg;
 	int			segno;
-	filemap_t  *map = filemap;
-	file_entry_t **e;
 
-	Assert(map->array);
+	Assert(filemap->array);
 
 	segno = blkno / RELSEG_SIZE;
 	blkno_inseg = blkno % RELSEG_SIZE;
 
 	path = datasegpath(rnode, forknum, segno);
-
-	key.path = (char *) path;
-	key_ptr = &key;
-
-	e = bsearch(&key_ptr, map->array, map->narray, sizeof(file_entry_t *),
-				path_cmp);
-	if (e)
-		entry = *e;
-	else
-		entry = NULL;
+	entry = get_filemap_entry(path, false);
 	pfree(path);
 
 	if (entry)
 	{
-		Assert(entry->isrelfile);
-
-		switch (entry->action)
-		{
-			case FILE_ACTION_NONE:
-			case FILE_ACTION_TRUNCATE:
-				/* skip if we're truncating away the modified block anyway */
-				if ((blkno_inseg + 1) * BLCKSZ <= entry->newsize)
-					datapagemap_add(&entry->pagemap, blkno_inseg);
-				break;
+		int64		end_offset;
 
-			case FILE_ACTION_COPY_TAIL:
-
-				/*
-				 * skip the modified block if it is part of the "tail" that
-				 * we're copying anyway.
-				 */
-				if ((blkno_inseg + 1) * BLCKSZ <= entry->oldsize)
-					datapagemap_add(&entry->pagemap, blkno_inseg);
-				break;
+		Assert(entry->isrelfile);
 
-			case FILE_ACTION_COPY:
-			case FILE_ACTION_REMOVE:
-				break;
+		if (entry->target_type != FILE_TYPE_REGULAR)
+			pg_fatal("unexpected page modification for directory or symbolic link \"%s\"",
+					 entry->path);
 
-			case FILE_ACTION_CREATE:
-				pg_fatal("unexpected page modification for directory or symbolic link \"%s\"", entry->path);
-		}
+		/*
+		 * If the block beyond the EOF in the source system, no need to
+		 * remember it now, because we're going to truncate it away from the
+		 * target anyway. Also no need to remember the block if it's beyond
+		 * the current EOF in the target system; we will copy it over with the
+		 * "tail" from the source system, anyway.
+		 */
+		end_offset = (blkno_inseg + 1) * BLCKSZ;
+		if (end_offset <= entry->source_size &&
+			end_offset <= entry->target_size)
+			datapagemap_add(&entry->target_modified_pages, blkno_inseg);
 	}
 	else
 	{
 		/*
 		 * If we don't have any record of this file in the file map, it means
-		 * that it's a relation that doesn't exist in the source system, and
-		 * it was subsequently removed in the target system, too. We can
-		 * safely ignore it.
+		 * that it's a relation that doesn't exist in the source system.  It
+		 * could exist in the target system; we haven't moved the target-only
+		 * entries from the linked list to the array yet!  But in any case, if
+		 * it doesn't exist in the source it will be removed from the target
+		 * too, and we can safely ignore it.
 		 */
 	}
 }
@@ -593,16 +441,6 @@ filemap_list_to_array(filemap_t *map)
 	map->first = map->last = NULL;
 }
 
-void
-filemap_finalize(void)
-{
-	filemap_t  *map = filemap;
-
-	filemap_list_to_array(map);
-	qsort(map->array, map->narray, sizeof(file_entry_t *),
-		  final_filemap_cmp);
-}
-
 static const char *
 action_to_str(file_action_t action)
 {
@@ -643,26 +481,26 @@ calculate_totals(void)
 	{
 		entry = map->array[i];
 
-		if (entry->type != FILE_TYPE_REGULAR)
+		if (entry->source_type != FILE_TYPE_REGULAR)
 			continue;
 
-		map->total_size += entry->newsize;
+		map->total_size += entry->source_size;
 
 		if (entry->action == FILE_ACTION_COPY)
 		{
-			map->fetch_size += entry->newsize;
+			map->fetch_size += entry->source_size;
 			continue;
 		}
 
 		if (entry->action == FILE_ACTION_COPY_TAIL)
-			map->fetch_size += (entry->newsize - entry->oldsize);
+			map->fetch_size += (entry->source_size - entry->target_size);
 
-		if (entry->pagemap.bitmapsize > 0)
+		if (entry->target_modified_pages.bitmapsize > 0)
 		{
 			datapagemap_iterator_t *iter;
 			BlockNumber blk;
 
-			iter = datapagemap_iterate(&entry->pagemap);
+			iter = datapagemap_iterate(&entry->target_modified_pages);
 			while (datapagemap_next(iter, &blk))
 				map->fetch_size += BLCKSZ;
 
@@ -682,13 +520,13 @@ print_filemap(void)
 	{
 		entry = map->array[i];
 		if (entry->action != FILE_ACTION_NONE ||
-			entry->pagemap.bitmapsize > 0)
+			entry->target_modified_pages.bitmapsize > 0)
 		{
 			pg_log_debug("%s (%s)", entry->path,
 						 action_to_str(entry->action));
 
-			if (entry->pagemap.bitmapsize > 0)
-				datapagemap_print(&entry->pagemap);
+			if (entry->target_modified_pages.bitmapsize > 0)
+				datapagemap_print(&entry->target_modified_pages);
 		}
 	}
 	fflush(stdout);
@@ -837,3 +675,180 @@ final_filemap_cmp(const void *a, const void *b)
 	else
 		return strcmp(fa->path, fb->path);
 }
+
+/*
+ * Decide what action to perform to a file.
+ */
+static file_action_t
+decide_file_action(file_entry_t *entry)
+{
+	const char *path = entry->path;
+
+	/*
+	 * Don't touch the control file. It is handled specially, after copying
+	 * all the other files.
+	 */
+	if (strcmp(path, "global/pg_control") == 0)
+		return FILE_ACTION_NONE;
+
+	/*
+	 * Remove all files matching the exclusion filters in the target.
+	 */
+	if (check_file_excluded(path, true))
+	{
+		if (entry->target_exists)
+			return FILE_ACTION_REMOVE;
+		else
+			return FILE_ACTION_NONE;
+	}
+
+	/*
+	 * Also remove all temporary files, .../pgsql_tmp/... and .../pgsql_tmp.*
+	 * in the target.
+	 */
+	if (strstr(path, "/" PG_TEMP_FILE_PREFIX) != NULL ||
+		strstr(path, "/" PG_TEMP_FILES_DIR "/") != NULL)
+	{
+		if (entry->target_exists)
+			return FILE_ACTION_REMOVE;
+		else
+			return FILE_ACTION_NONE;
+	}
+
+	/*
+	 * Handle cases where the file is missing from one of the systems.
+	 */
+	if (!entry->target_exists && entry->source_exists)
+	{
+		/*
+		 * File exists in source, but not in target. Copy it in toto. (If it's
+		 * a relation data file, WAL replay after rewinding should re-create
+		 * it anyway. But there's no harm in copying it now.)
+		 */
+		switch (entry->source_type)
+		{
+			case FILE_TYPE_DIRECTORY:
+			case FILE_TYPE_SYMLINK:
+				return FILE_ACTION_CREATE;
+			case FILE_TYPE_REGULAR:
+				return FILE_ACTION_COPY;
+			case FILE_TYPE_UNDEFINED:
+				pg_fatal("unknown file type for \"%s\"", entry->path);
+				break;
+		}
+	}
+	else if (entry->target_exists && !entry->source_exists)
+	{
+		/* File exists in target, but not source. Remove it. */
+		return FILE_ACTION_REMOVE;
+	}
+	else if (!entry->target_exists && !entry->source_exists)
+	{
+		/*
+		 * Doesn't exist in either server. Why does it have an entry in the
+		 * first place??
+		 */
+		return FILE_ACTION_NONE;
+	}
+
+	/*
+	 * Otherwise, the file exists on both systems
+	 */
+	Assert(entry->target_exists && entry->source_exists);
+
+	if (entry->source_type != entry->target_type)
+	{
+		/* But it's a different kind of object. Strange.. */
+		pg_fatal("file \"%s\" is of different type in source and target", entry->path);
+	}
+
+	/*
+	 * PG_VERSION files should be identical on both systems, but avoid
+	 * overwriting them for paranoia.
+	 */
+	if (pg_str_endswith(entry->path, "PG_VERSION"))
+		return FILE_ACTION_NONE;
+
+	switch (entry->source_type)
+	{
+		case FILE_TYPE_DIRECTORY:
+			return FILE_ACTION_NONE;
+
+		case FILE_TYPE_SYMLINK:
+			/* FIXME: Check if it points to the same target? */
+			return FILE_ACTION_NONE;
+
+		case FILE_TYPE_REGULAR:
+			if (!entry->isrelfile)
+			{
+				/*
+				 * It's a non-data file that we have no special processing
+				 * for. Copy it in toto.
+				 */
+				return FILE_ACTION_COPY;
+			}
+			else
+			{
+				/*
+				 * It's a data file that exists in both systems.
+				 *
+				 * If it's larger in target, we can truncate it. There will
+				 * also be a WAL record of the truncation in the source
+				 * system, so WAL replay would eventually truncate the target
+				 * too, but we might as well do it now.
+				 *
+				 * If it's smaller in the target, it means that it has been
+				 * truncated in the target, or enlarged in the source, or
+				 * both. If it was truncated in the target, we need to copy
+				 * the missing tail from the source system. If it was enlarged
+				 * in the source system, there will be WAL records in the
+				 * source system for the new blocks, so we wouldn't need to
+				 * copy them here. But we don't know which scenario we're
+				 * dealing with, and there's no harm in copying the missing
+				 * blocks now, so do it now.
+				 *
+				 * If it's the same size, do nothing here. Any blocks modified
+				 * in the target will be copied based on parsing the target
+				 * system's WAL, and any blocks modified in the source will be
+				 * updated after rewinding, when the source system's WAL is
+				 * replayed.
+				 */
+				if (entry->target_size < entry->source_size)
+					return FILE_ACTION_COPY_TAIL;
+				else if (entry->target_size > entry->source_size)
+					return FILE_ACTION_TRUNCATE;
+				else
+					return FILE_ACTION_NONE;
+			}
+			break;
+
+		case FILE_TYPE_UNDEFINED:
+			pg_fatal("unknown file type for \"%s\"", path);
+			break;
+	}
+
+	/* unreachable */
+	pg_fatal("could not decide what to do with file \"%s\"", path);
+}
+
+/*
+ * Decide what to do with each file.
+ */
+void
+filemap_finalize()
+{
+	int			i;
+
+	filemap_list_to_array(filemap);
+
+	for (i = 0; i < filemap->narray; i++)
+	{
+		file_entry_t *entry = filemap->array[i];
+
+		entry->action = decide_file_action(entry);
+	}
+
+	/* Sort the actions to the order that they should be performed */
+	qsort(filemap->array, filemap->narray, sizeof(file_entry_t *),
+		  final_filemap_cmp);
+}
diff --git a/src/bin/pg_rewind/filemap.h b/src/bin/pg_rewind/filemap.h
index 0cb7425170c..a5e8df57f40 100644
--- a/src/bin/pg_rewind/filemap.h
+++ b/src/bin/pg_rewind/filemap.h
@@ -14,17 +14,21 @@
 
 /*
  * For every file found in the local or remote system, we have a file entry
- * which says what we are going to do with the file. For relation files,
- * there is also a page map, marking pages in the file that were changed
- * locally.
- *
- * The enum values are sorted in the order we want actions to be processed.
+ * that contains information about the file on both systems.  For relation
+ * files, there is also a page map that marks pages in the file that were
+ * changed in the target after the last common checkpoint.  Each entry also
+ * contains an 'action' field, which says what we are going to do with the
+ * file.
  */
+
+/* these enum values are sorted in the order we want actions to be processed */
 typedef enum
 {
+	FILE_ACTION_UNDECIDED = 0,	/* not decided yet */
+
 	FILE_ACTION_CREATE,			/* create local directory or symbolic link */
 	FILE_ACTION_COPY,			/* copy whole file, overwriting if exists */
-	FILE_ACTION_COPY_TAIL,		/* copy tail from 'oldsize' to 'newsize' */
+	FILE_ACTION_COPY_TAIL,		/* copy tail from 'source_size' to 'target_size' */
 	FILE_ACTION_NONE,			/* no action (we might still copy modified
 								 * blocks based on the parsed WAL) */
 	FILE_ACTION_TRUNCATE,		/* truncate local file to 'newsize' bytes */
@@ -33,6 +37,8 @@ typedef enum
 
 typedef enum
 {
+	FILE_TYPE_UNDEFINED = 0,
+
 	FILE_TYPE_REGULAR,
 	FILE_TYPE_DIRECTORY,
 	FILE_TYPE_SYMLINK
@@ -41,19 +47,30 @@ typedef enum
 typedef struct file_entry_t
 {
 	char	   *path;
-	file_type_t type;
+	bool		isrelfile;		/* is it a relation data file? */
 
-	file_action_t action;
+	/*
+	 * Status of the file in the target.
+	 */
+	bool		target_exists;
+	file_type_t target_type;
+	size_t		target_size; /* for a regular file */
+	char	   *target_link_target; /* for a symlink */
 
-	/* for a regular file */
-	size_t		oldsize;
-	size_t		newsize;
-	bool		isrelfile;		/* is it a relation data file? */
+	datapagemap_t target_modified_pages;
 
-	datapagemap_t pagemap;
+	/*
+	 * Status of the file in the source.
+	 */
+	bool		source_exists;
+	file_type_t source_type;
+	size_t		source_size;
+	char	   *source_link_target; /* for a symlink */
 
-	/* for a symlink */
-	char	   *link_target;
+	/*
+	 * What will we do to the file?
+	 */
+	file_action_t action;
 
 	struct file_entry_t *next;
 } file_entry_t;
@@ -70,20 +87,19 @@ typedef struct filemap_t
 
 	/*
 	 * After processing all the remote files, the entries in the linked list
-	 * are moved to this array. After processing local files, too, all the
+	 * are moved to this array.  After processing local files, too, all the
 	 * local entries are added to the array by filemap_finalize, and sorted in
-	 * the final order. After filemap_finalize, all the entries are in the
+	 * the final order.  After filemap_finalize, all the entries are in the
 	 * array, and the linked list is empty.
 	 */
 	file_entry_t **array;
 	int			narray;			/* current length of array */
 
 	/*
-	 * Summary information. total_size is the total size of the source
-	 * cluster, and fetch_size is the number of bytes that needs to be copied.
+	 * Summary information.
 	 */
-	uint64		total_size;
-	uint64		fetch_size;
+	uint64		total_size;		/* total size of the source cluster */
+	uint64		fetch_size;		/* number of bytes that needs to be copied */
 } filemap_t;
 
 extern filemap_t *filemap;
@@ -94,11 +110,12 @@ extern void print_filemap(void);
 
 /* Functions for populating the filemap */
 extern void process_source_file(const char *path, file_type_t type,
-								size_t newsize, const char *link_target);
+								size_t size, const char *link_target);
 extern void process_target_file(const char *path, file_type_t type,
-								size_t newsize, const char *link_target);
-extern void process_block_change(ForkNumber forknum, RelFileNode rnode,
-								 BlockNumber blkno);
+								size_t size, const char *link_target);
+extern void process_target_wal_block_change(ForkNumber forknum,
+											RelFileNode rnode,
+											BlockNumber blkno);
 extern void filemap_finalize(void);
 
 #endif							/* FILEMAP_H */
diff --git a/src/bin/pg_rewind/libpq_fetch.c b/src/bin/pg_rewind/libpq_fetch.c
index bf4dfc23b96..7fc9161b8c8 100644
--- a/src/bin/pg_rewind/libpq_fetch.c
+++ b/src/bin/pg_rewind/libpq_fetch.c
@@ -465,7 +465,7 @@ libpq_executeFileMap(filemap_t *map)
 		entry = map->array[i];
 
 		/* If this is a relation file, copy the modified blocks */
-		execute_pagemap(&entry->pagemap, entry->path);
+		execute_pagemap(&entry->target_modified_pages, entry->path);
 
 		switch (entry->action)
 		{
@@ -476,15 +476,15 @@ libpq_executeFileMap(filemap_t *map)
 			case FILE_ACTION_COPY:
 				/* Truncate the old file out of the way, if any */
 				open_target_file(entry->path, true);
-				fetch_file_range(entry->path, 0, entry->newsize);
+				fetch_file_range(entry->path, 0, entry->source_size);
 				break;
 
 			case FILE_ACTION_TRUNCATE:
-				truncate_target_file(entry->path, entry->newsize);
+				truncate_target_file(entry->path, entry->source_size);
 				break;
 
 			case FILE_ACTION_COPY_TAIL:
-				fetch_file_range(entry->path, entry->oldsize, entry->newsize);
+				fetch_file_range(entry->path, entry->target_size, entry->source_size);
 				break;
 
 			case FILE_ACTION_REMOVE:
@@ -494,6 +494,10 @@ libpq_executeFileMap(filemap_t *map)
 			case FILE_ACTION_CREATE:
 				create_target(entry);
 				break;
+
+			case FILE_ACTION_UNDECIDED:
+				pg_fatal("no action decided for \"%s\"", entry->path);
+				break;
 		}
 	}
 
diff --git a/src/bin/pg_rewind/parsexlog.c b/src/bin/pg_rewind/parsexlog.c
index 2229c86f9af..2baeb74ae93 100644
--- a/src/bin/pg_rewind/parsexlog.c
+++ b/src/bin/pg_rewind/parsexlog.c
@@ -436,6 +436,6 @@ extractPageInfo(XLogReaderState *record)
 		if (forknum != MAIN_FORKNUM)
 			continue;
 
-		process_block_change(forknum, rnode, blkno);
+		process_target_wal_block_change(forknum, rnode, blkno);
 	}
 }
diff --git a/src/bin/pg_rewind/pg_rewind.c b/src/bin/pg_rewind/pg_rewind.c
index c9b9e480c0f..210984d302b 100644
--- a/src/bin/pg_rewind/pg_rewind.c
+++ b/src/bin/pg_rewind/pg_rewind.c
@@ -369,7 +369,7 @@ main(int argc, char **argv)
 				chkpttli);
 
 	/*
-	 * Build the filemap, by comparing the source and target data directories.
+	 * Collect information about all files in the target and source systems.
 	 */
 	filemap_create();
 	if (showprogress)
@@ -390,8 +390,12 @@ main(int argc, char **argv)
 		pg_log_info("reading WAL in target");
 	extractPageMap(datadir_target, chkptrec, lastcommontliIndex,
 				   ControlFile_target.checkPoint, restore_command);
-	filemap_finalize();
 
+	/*
+	 * We have collected all information we need from both systems. Decide
+	 * what to do with each file.
+	 */
+	filemap_finalize();
 	if (showprogress)
 		calculate_totals();
 
-- 
2.20.1


--------------BF34D0120055DC3839060F92
Content-Type: text/x-patch; charset=UTF-8;
 name="v2-0003-pg_rewind-Replace-the-hybrid-list-array-data-stru.patch"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
 filename*0="v2-0003-pg_rewind-Replace-the-hybrid-list-array-data-stru.pa";
 filename*1="tch"



^ permalink  raw  reply  [nested|flat] 41+ messages in thread

* [PATCH v2 2/5] Refactor pg_rewind for more clear decision making.
@ 2020-08-19 12:34 Heikki Linnakangas <[email protected]>
  0 siblings, 0 replies; 41+ messages in thread

From: Heikki Linnakangas @ 2020-08-19 12:34 UTC (permalink / raw)

Deciding what to do with each file is now a separate step after all the
necessary information has been gathered. It is more clear that way.
Previously, the decision-making was divided between process_source_file()
and process_target_file(), and it was a bit hard to piece together what the
overall rules were.
---
 src/bin/pg_rewind/copy_fetch.c  |  14 +-
 src/bin/pg_rewind/file_ops.c    |  16 +-
 src/bin/pg_rewind/filemap.c     | 569 ++++++++++++++++----------------
 src/bin/pg_rewind/filemap.h     |  67 ++--
 src/bin/pg_rewind/libpq_fetch.c |  12 +-
 src/bin/pg_rewind/parsexlog.c   |   2 +-
 src/bin/pg_rewind/pg_rewind.c   |   8 +-
 7 files changed, 371 insertions(+), 317 deletions(-)

diff --git a/src/bin/pg_rewind/copy_fetch.c b/src/bin/pg_rewind/copy_fetch.c
index 1edab5f1867..18fad32600e 100644
--- a/src/bin/pg_rewind/copy_fetch.c
+++ b/src/bin/pg_rewind/copy_fetch.c
@@ -210,7 +210,7 @@ copy_executeFileMap(filemap_t *map)
 	for (i = 0; i < map->narray; i++)
 	{
 		entry = map->array[i];
-		execute_pagemap(&entry->pagemap, entry->path);
+		execute_pagemap(&entry->target_modified_pages, entry->path);
 
 		switch (entry->action)
 		{
@@ -219,16 +219,16 @@ copy_executeFileMap(filemap_t *map)
 				break;
 
 			case FILE_ACTION_COPY:
-				rewind_copy_file_range(entry->path, 0, entry->newsize, true);
+				rewind_copy_file_range(entry->path, 0, entry->source_size, true);
 				break;
 
 			case FILE_ACTION_TRUNCATE:
-				truncate_target_file(entry->path, entry->newsize);
+				truncate_target_file(entry->path, entry->source_size);
 				break;
 
 			case FILE_ACTION_COPY_TAIL:
-				rewind_copy_file_range(entry->path, entry->oldsize,
-									   entry->newsize, false);
+				rewind_copy_file_range(entry->path, entry->target_size,
+									   entry->source_size, false);
 				break;
 
 			case FILE_ACTION_CREATE:
@@ -238,6 +238,10 @@ copy_executeFileMap(filemap_t *map)
 			case FILE_ACTION_REMOVE:
 				remove_target(entry);
 				break;
+
+			case FILE_ACTION_UNDECIDED:
+				pg_fatal("no action decided for \"%s\"", entry->path);
+				break;
 		}
 	}
 
diff --git a/src/bin/pg_rewind/file_ops.c b/src/bin/pg_rewind/file_ops.c
index 55439db20ba..ec37d0b2e0d 100644
--- a/src/bin/pg_rewind/file_ops.c
+++ b/src/bin/pg_rewind/file_ops.c
@@ -126,8 +126,9 @@ void
 remove_target(file_entry_t *entry)
 {
 	Assert(entry->action == FILE_ACTION_REMOVE);
+	Assert(entry->target_exists);
 
-	switch (entry->type)
+	switch (entry->target_type)
 	{
 		case FILE_TYPE_DIRECTORY:
 			remove_target_dir(entry->path);
@@ -140,6 +141,10 @@ remove_target(file_entry_t *entry)
 		case FILE_TYPE_SYMLINK:
 			remove_target_symlink(entry->path);
 			break;
+
+		case FILE_TYPE_UNDEFINED:
+			pg_fatal("undefined file type for \"%s\"", entry->path);
+			break;
 	}
 }
 
@@ -147,21 +152,26 @@ void
 create_target(file_entry_t *entry)
 {
 	Assert(entry->action == FILE_ACTION_CREATE);
+	Assert(!entry->target_exists);
 
-	switch (entry->type)
+	switch (entry->source_type)
 	{
 		case FILE_TYPE_DIRECTORY:
 			create_target_dir(entry->path);
 			break;
 
 		case FILE_TYPE_SYMLINK:
-			create_target_symlink(entry->path, entry->link_target);
+			create_target_symlink(entry->path, entry->source_link_target);
 			break;
 
 		case FILE_TYPE_REGULAR:
 			/* can't happen. Regular files are created with open_target_file. */
 			pg_fatal("invalid action (CREATE) for regular file");
 			break;
+
+		case FILE_TYPE_UNDEFINED:
+			pg_fatal("undefined file type for \"%s\"", entry->path);
+			break;
 	}
 }
 
diff --git a/src/bin/pg_rewind/filemap.c b/src/bin/pg_rewind/filemap.c
index 1879257b66a..7971daeda5e 100644
--- a/src/bin/pg_rewind/filemap.c
+++ b/src/bin/pg_rewind/filemap.c
@@ -26,6 +26,8 @@ static bool isRelDataFile(const char *path);
 static char *datasegpath(RelFileNode rnode, ForkNumber forknum,
 						 BlockNumber segno);
 static int	path_cmp(const void *a, const void *b);
+
+static file_entry_t *get_filemap_entry(const char *path, bool create);
 static int	final_filemap_cmp(const void *a, const void *b);
 static void filemap_list_to_array(filemap_t *map);
 static bool check_file_excluded(const char *path, bool is_source);
@@ -146,6 +148,65 @@ filemap_create(void)
 	filemap = map;
 }
 
+/* Look up or create entry for 'path' */
+static file_entry_t *
+get_filemap_entry(const char *path, bool create)
+{
+	filemap_t  *map = filemap;
+	file_entry_t *entry;
+	file_entry_t **e;
+	file_entry_t key;
+	file_entry_t *key_ptr;
+
+	if (map->array)
+	{
+		key.path = (char *) path;
+		key_ptr = &key;
+		e = bsearch(&key_ptr, map->array, map->narray, sizeof(file_entry_t *),
+					path_cmp);
+	}
+	else
+		e = NULL;
+
+	if (e)
+		entry = *e;
+	else if (!create)
+		entry = NULL;
+	else
+	{
+		/* Create a new entry for this file */
+		entry = pg_malloc(sizeof(file_entry_t));
+		entry->path = pg_strdup(path);
+		entry->isrelfile = isRelDataFile(path);
+		entry->action = FILE_ACTION_UNDECIDED;
+
+		entry->target_exists = false;
+		entry->target_type = FILE_TYPE_UNDEFINED;
+		entry->target_size = 0;
+		entry->target_link_target = NULL;
+		entry->target_modified_pages.bitmap = NULL;
+		entry->target_modified_pages.bitmapsize = 0;
+
+		entry->source_exists = false;
+		entry->source_type = FILE_TYPE_UNDEFINED;
+		entry->source_size = 0;
+		entry->source_link_target = NULL;
+
+		entry->next = NULL;
+
+		if (map->last)
+		{
+			map->last->next = entry;
+			map->last = entry;
+		}
+		else
+			map->first = map->last = entry;
+		map->nlist++;
+	}
+
+	return entry;
+}
+
 /*
  * Callback for processing source file list.
  *
@@ -154,25 +215,12 @@ filemap_create(void)
  * exists in the target and whether the size matches.
  */
 void
-process_source_file(const char *path, file_type_t type, size_t newsize,
+process_source_file(const char *path, file_type_t type, size_t size,
 					const char *link_target)
 {
-	bool		exists;
-	char		localpath[MAXPGPATH];
-	struct stat statbuf;
-	filemap_t  *map = filemap;
-	file_action_t action = FILE_ACTION_NONE;
-	size_t		oldsize = 0;
 	file_entry_t *entry;
 
-	Assert(map->array == NULL);
-
-	/*
-	 * Skip any files matching the exclusion filters. This has the effect to
-	 * remove all those files on the target.
-	 */
-	if (check_file_excluded(path, true))
-		return;
+	Assert(filemap->array == NULL);
 
 	/*
 	 * Pretend that pg_wal is a directory, even if it's really a symlink. We
@@ -182,16 +230,6 @@ process_source_file(const char *path, file_type_t type, size_t newsize,
 	if (strcmp(path, "pg_wal") == 0 && type == FILE_TYPE_SYMLINK)
 		type = FILE_TYPE_DIRECTORY;
 
-	/*
-	 * Skip temporary files, .../pgsql_tmp/... and .../pgsql_tmp.* in source.
-	 * This has the effect that all temporary files in the destination will be
-	 * removed.
-	 */
-	if (strstr(path, "/" PG_TEMP_FILE_PREFIX) != NULL)
-		return;
-	if (strstr(path, "/" PG_TEMP_FILES_DIR "/") != NULL)
-		return;
-
 	/*
 	 * sanity check: a filename that looks like a data file better be a
 	 * regular file
@@ -199,142 +237,12 @@ process_source_file(const char *path, file_type_t type, size_t newsize,
 	if (type != FILE_TYPE_REGULAR && isRelDataFile(path))
 		pg_fatal("data file \"%s\" in source is not a regular file", path);
 
-	snprintf(localpath, sizeof(localpath), "%s/%s", datadir_target, path);
-
-	/* Does the corresponding file exist in the target data dir? */
-	if (lstat(localpath, &statbuf) < 0)
-	{
-		if (errno != ENOENT)
-			pg_fatal("could not stat file \"%s\": %m",
-					 localpath);
-
-		exists = false;
-	}
-	else
-		exists = true;
-
-	switch (type)
-	{
-		case FILE_TYPE_DIRECTORY:
-			if (exists && !S_ISDIR(statbuf.st_mode) && strcmp(path, "pg_wal") != 0)
-			{
-				/* it's a directory in source, but not in target. Strange.. */
-				pg_fatal("\"%s\" is not a directory", localpath);
-			}
-
-			if (!exists)
-				action = FILE_ACTION_CREATE;
-			else
-				action = FILE_ACTION_NONE;
-			oldsize = 0;
-			break;
-
-		case FILE_TYPE_SYMLINK:
-			if (exists &&
-#ifndef WIN32
-				!S_ISLNK(statbuf.st_mode)
-#else
-				!pgwin32_is_junction(localpath)
-#endif
-				)
-			{
-				/*
-				 * It's a symbolic link in source, but not in target.
-				 * Strange..
-				 */
-				pg_fatal("\"%s\" is not a symbolic link", localpath);
-			}
-
-			if (!exists)
-				action = FILE_ACTION_CREATE;
-			else
-				action = FILE_ACTION_NONE;
-			oldsize = 0;
-			break;
-
-		case FILE_TYPE_REGULAR:
-			if (exists && !S_ISREG(statbuf.st_mode))
-				pg_fatal("\"%s\" is not a regular file", localpath);
-
-			if (!exists || !isRelDataFile(path))
-			{
-				/*
-				 * File exists in source, but not in target. Or it's a
-				 * non-data file that we have no special processing for. Copy
-				 * it in toto.
-				 *
-				 * An exception: PG_VERSIONs should be identical, but avoid
-				 * overwriting it for paranoia.
-				 */
-				if (pg_str_endswith(path, "PG_VERSION"))
-				{
-					action = FILE_ACTION_NONE;
-					oldsize = statbuf.st_size;
-				}
-				else
-				{
-					action = FILE_ACTION_COPY;
-					oldsize = 0;
-				}
-			}
-			else
-			{
-				/*
-				 * It's a data file that exists in both.
-				 *
-				 * If it's larger in target, we can truncate it. There will
-				 * also be a WAL record of the truncation in the source
-				 * system, so WAL replay would eventually truncate the target
-				 * too, but we might as well do it now.
-				 *
-				 * If it's smaller in the target, it means that it has been
-				 * truncated in the target, or enlarged in the source, or
-				 * both. If it was truncated in the target, we need to copy
-				 * the missing tail from the source system. If it was enlarged
-				 * in the source system, there will be WAL records in the
-				 * source system for the new blocks, so we wouldn't need to
-				 * copy them here. But we don't know which scenario we're
-				 * dealing with, and there's no harm in copying the missing
-				 * blocks now, so do it now.
-				 *
-				 * If it's the same size, do nothing here. Any blocks modified
-				 * in the target will be copied based on parsing the target
-				 * system's WAL, and any blocks modified in the source will be
-				 * updated after rewinding, when the source system's WAL is
-				 * replayed.
-				 */
-				oldsize = statbuf.st_size;
-				if (oldsize < newsize)
-					action = FILE_ACTION_COPY_TAIL;
-				else if (oldsize > newsize)
-					action = FILE_ACTION_TRUNCATE;
-				else
-					action = FILE_ACTION_NONE;
-			}
-			break;
-	}
-
-	/* Create a new entry for this file */
-	entry = pg_malloc(sizeof(file_entry_t));
-	entry->path = pg_strdup(path);
-	entry->type = type;
-	entry->action = action;
-	entry->oldsize = oldsize;
-	entry->newsize = newsize;
-	entry->link_target = link_target ? pg_strdup(link_target) : NULL;
-	entry->next = NULL;
-	entry->pagemap.bitmap = NULL;
-	entry->pagemap.bitmapsize = 0;
-	entry->isrelfile = isRelDataFile(path);
-
-	if (map->last)
-	{
-		map->last->next = entry;
-		map->last = entry;
-	}
-	else
-		map->first = map->last = entry;
-	map->nlist++;
+	/* Remember this source file */
+	entry = get_filemap_entry(path, true);
+	entry->source_exists = true;
+	entry->source_type = type;
+	entry->source_size = size;
+	entry->source_link_target = link_target ? pg_strdup(link_target) : NULL;
 }
 
 /*
@@ -345,14 +253,9 @@ process_source_file(const char *path, file_type_t type, size_t newsize,
  * deletion.
  */
 void
-process_target_file(const char *path, file_type_t type, size_t oldsize,
+process_target_file(const char *path, file_type_t type, size_t size,
 					const char *link_target)
 {
-	bool		exists;
-	char		localpath[MAXPGPATH];
-	struct stat statbuf;
-	file_entry_t key;
-	file_entry_t *key_ptr;
 	filemap_t  *map = filemap;
 	file_entry_t *entry;
 
@@ -361,17 +264,6 @@ process_target_file(const char *path, file_type_t type, size_t oldsize,
 	 * from the target data folder all paths which have been filtered out from
 	 * the source data folder when processing the source files.
 	 */
-
-	snprintf(localpath, sizeof(localpath), "%s/%s", datadir_target, path);
-	if (lstat(localpath, &statbuf) < 0)
-	{
-		if (errno != ENOENT)
-			pg_fatal("could not stat file \"%s\": %m",
-					 localpath);
-
-		exists = false;
-	}
-
 	if (map->array == NULL)
 	{
 		/* on first call, initialize lookup array */
@@ -389,120 +281,76 @@ process_target_file(const char *path, file_type_t type, size_t oldsize,
 	}
 
 	/*
-	 * Like in process_source_file, pretend that xlog is always a  directory.
+	 * Like in process_source_file, pretend that pg_wal is always a directory.
 	 */
 	if (strcmp(path, "pg_wal") == 0 && type == FILE_TYPE_SYMLINK)
 		type = FILE_TYPE_DIRECTORY;
 
-	key.path = (char *) path;
-	key_ptr = &key;
-	exists = (bsearch(&key_ptr, map->array, map->narray, sizeof(file_entry_t *),
-					  path_cmp) != NULL);
-
-	/* Remove any file or folder that doesn't exist in the source system. */
-	if (!exists)
-	{
-		entry = pg_malloc(sizeof(file_entry_t));
-		entry->path = pg_strdup(path);
-		entry->type = type;
-		entry->action = FILE_ACTION_REMOVE;
-		entry->oldsize = oldsize;
-		entry->newsize = 0;
-		entry->link_target = link_target ? pg_strdup(link_target) : NULL;
-		entry->next = NULL;
-		entry->pagemap.bitmap = NULL;
-		entry->pagemap.bitmapsize = 0;
-		entry->isrelfile = isRelDataFile(path);
-
-		if (map->last == NULL)
-			map->first = entry;
-		else
-			map->last->next = entry;
-		map->last = entry;
-		map->nlist++;
-	}
-	else
-	{
-		/*
-		 * We already handled all files that exist in the source system in
-		 * process_source_file().
-		 */
-	}
+	/* Remember this target file */
+	entry = get_filemap_entry(path, true);
+	entry->target_exists = true;
+	entry->target_type = type;
+	entry->target_size = size;
+	entry->target_link_target = link_target ? pg_strdup(link_target) : NULL;
 }
 
 /*
  * This callback gets called while we read the WAL in the target, for every
- * block that have changed in the target system. It makes note of all the
+ * block that have changed in the target system.  It makes note of all the
  * changed blocks in the pagemap of the file.
+ *
+ * NOTE: All the files on both systems must have already been added to the
+ * file map!
  */
 void
-process_block_change(ForkNumber forknum, RelFileNode rnode, BlockNumber blkno)
+process_target_wal_block_change(ForkNumber forknum, RelFileNode rnode,
+								BlockNumber blkno)
 {
 	char	   *path;
-	file_entry_t key;
-	file_entry_t *key_ptr;
 	file_entry_t *entry;
 	BlockNumber blkno_inseg;
 	int			segno;
-	filemap_t  *map = filemap;
-	file_entry_t **e;
 
-	Assert(map->array);
+	Assert(filemap->array);
 
 	segno = blkno / RELSEG_SIZE;
 	blkno_inseg = blkno % RELSEG_SIZE;
 
 	path = datasegpath(rnode, forknum, segno);
-
-	key.path = (char *) path;
-	key_ptr = &key;
-
-	e = bsearch(&key_ptr, map->array, map->narray, sizeof(file_entry_t *),
-				path_cmp);
-	if (e)
-		entry = *e;
-	else
-		entry = NULL;
+	entry = get_filemap_entry(path, false);
 	pfree(path);
 
 	if (entry)
 	{
-		Assert(entry->isrelfile);
-
-		switch (entry->action)
-		{
-			case FILE_ACTION_NONE:
-			case FILE_ACTION_TRUNCATE:
-				/* skip if we're truncating away the modified block anyway */
-				if ((blkno_inseg + 1) * BLCKSZ <= entry->newsize)
-					datapagemap_add(&entry->pagemap, blkno_inseg);
-				break;
+		int64		end_offset;
 
-			case FILE_ACTION_COPY_TAIL:
-
-				/*
-				 * skip the modified block if it is part of the "tail" that
-				 * we're copying anyway.
-				 */
-				if ((blkno_inseg + 1) * BLCKSZ <= entry->oldsize)
-					datapagemap_add(&entry->pagemap, blkno_inseg);
-				break;
+		Assert(entry->isrelfile);
 
-			case FILE_ACTION_COPY:
-			case FILE_ACTION_REMOVE:
-				break;
+		if (entry->target_type != FILE_TYPE_REGULAR)
+			pg_fatal("unexpected page modification for directory or symbolic link \"%s\"",
+					 entry->path);
 
-			case FILE_ACTION_CREATE:
-				pg_fatal("unexpected page modification for directory or symbolic link \"%s\"", entry->path);
-		}
+		/*
+		 * If the block beyond the EOF in the source system, no need to
+		 * remember it now, because we're going to truncate it away from the
+		 * target anyway. Also no need to remember the block if it's beyond
+		 * the current EOF in the target system; we will copy it over with the
+		 * "tail" from the source system, anyway.
+		 */
+		end_offset = (blkno_inseg + 1) * BLCKSZ;
+		if (end_offset <= entry->source_size &&
+			end_offset <= entry->target_size)
+			datapagemap_add(&entry->target_modified_pages, blkno_inseg);
 	}
 	else
 	{
 		/*
 		 * If we don't have any record of this file in the file map, it means
-		 * that it's a relation that doesn't exist in the source system, and
-		 * it was subsequently removed in the target system, too. We can
-		 * safely ignore it.
+		 * that it's a relation that doesn't exist in the source system.  It
+		 * could exist in the target system; we haven't moved the target-only
+		 * entries from the linked list to the array yet!  But in any case, if
+		 * it doesn't exist in the source it will be removed from the target
+		 * too, and we can safely ignore it.
 		 */
 	}
 }
@@ -593,16 +441,6 @@ filemap_list_to_array(filemap_t *map)
 	map->first = map->last = NULL;
 }
 
-void
-filemap_finalize(void)
-{
-	filemap_t  *map = filemap;
-
-	filemap_list_to_array(map);
-	qsort(map->array, map->narray, sizeof(file_entry_t *),
-		  final_filemap_cmp);
-}
-
 static const char *
 action_to_str(file_action_t action)
 {
@@ -643,26 +481,26 @@ calculate_totals(void)
 	{
 		entry = map->array[i];
 
-		if (entry->type != FILE_TYPE_REGULAR)
+		if (entry->source_type != FILE_TYPE_REGULAR)
 			continue;
 
-		map->total_size += entry->newsize;
+		map->total_size += entry->source_size;
 
 		if (entry->action == FILE_ACTION_COPY)
 		{
-			map->fetch_size += entry->newsize;
+			map->fetch_size += entry->source_size;
 			continue;
 		}
 
 		if (entry->action == FILE_ACTION_COPY_TAIL)
-			map->fetch_size += (entry->newsize - entry->oldsize);
+			map->fetch_size += (entry->source_size - entry->target_size);
 
-		if (entry->pagemap.bitmapsize > 0)
+		if (entry->target_modified_pages.bitmapsize > 0)
 		{
 			datapagemap_iterator_t *iter;
 			BlockNumber blk;
 
-			iter = datapagemap_iterate(&entry->pagemap);
+			iter = datapagemap_iterate(&entry->target_modified_pages);
 			while (datapagemap_next(iter, &blk))
 				map->fetch_size += BLCKSZ;
 
@@ -682,13 +520,13 @@ print_filemap(void)
 	{
 		entry = map->array[i];
 		if (entry->action != FILE_ACTION_NONE ||
-			entry->pagemap.bitmapsize > 0)
+			entry->target_modified_pages.bitmapsize > 0)
 		{
 			pg_log_debug("%s (%s)", entry->path,
 						 action_to_str(entry->action));
 
-			if (entry->pagemap.bitmapsize > 0)
-				datapagemap_print(&entry->pagemap);
+			if (entry->target_modified_pages.bitmapsize > 0)
+				datapagemap_print(&entry->target_modified_pages);
 		}
 	}
 	fflush(stdout);
@@ -837,3 +675,180 @@ final_filemap_cmp(const void *a, const void *b)
 	else
 		return strcmp(fa->path, fb->path);
 }
+
+/*
+ * Decide what action to perform to a file.
+ */
+static file_action_t
+decide_file_action(file_entry_t *entry)
+{
+	const char *path = entry->path;
+
+	/*
+	 * Don't touch the control file. It is handled specially, after copying
+	 * all the other files.
+	 */
+	if (strcmp(path, "global/pg_control") == 0)
+		return FILE_ACTION_NONE;
+
+	/*
+	 * Remove all files matching the exclusion filters in the target.
+	 */
+	if (check_file_excluded(path, true))
+	{
+		if (entry->target_exists)
+			return FILE_ACTION_REMOVE;
+		else
+			return FILE_ACTION_NONE;
+	}
+
+	/*
+	 * Also remove all temporary files, .../pgsql_tmp/... and .../pgsql_tmp.*
+	 * in the target.
+	 */
+	if (strstr(path, "/" PG_TEMP_FILE_PREFIX) != NULL ||
+		strstr(path, "/" PG_TEMP_FILES_DIR "/") != NULL)
+	{
+		if (entry->target_exists)
+			return FILE_ACTION_REMOVE;
+		else
+			return FILE_ACTION_NONE;
+	}
+
+	/*
+	 * Handle cases where the file is missing from one of the systems.
+	 */
+	if (!entry->target_exists && entry->source_exists)
+	{
+		/*
+		 * File exists in source, but not in target. Copy it in toto. (If it's
+		 * a relation data file, WAL replay after rewinding should re-create
+		 * it anyway. But there's no harm in copying it now.)
+		 */
+		switch (entry->source_type)
+		{
+			case FILE_TYPE_DIRECTORY:
+			case FILE_TYPE_SYMLINK:
+				return FILE_ACTION_CREATE;
+			case FILE_TYPE_REGULAR:
+				return FILE_ACTION_COPY;
+			case FILE_TYPE_UNDEFINED:
+				pg_fatal("unknown file type for \"%s\"", entry->path);
+				break;
+		}
+	}
+	else if (entry->target_exists && !entry->source_exists)
+	{
+		/* File exists in target, but not source. Remove it. */
+		return FILE_ACTION_REMOVE;
+	}
+	else if (!entry->target_exists && !entry->source_exists)
+	{
+		/*
+		 * Doesn't exist in either server. Why does it have an entry in the
+		 * first place??
+		 */
+		return FILE_ACTION_NONE;
+	}
+
+	/*
+	 * Otherwise, the file exists on both systems
+	 */
+	Assert(entry->target_exists && entry->source_exists);
+
+	if (entry->source_type != entry->target_type)
+	{
+		/* But it's a different kind of object. Strange.. */
+		pg_fatal("file \"%s\" is of different type in source and target", entry->path);
+	}
+
+	/*
+	 * PG_VERSION files should be identical on both systems, but avoid
+	 * overwriting them for paranoia.
+	 */
+	if (pg_str_endswith(entry->path, "PG_VERSION"))
+		return FILE_ACTION_NONE;
+
+	switch (entry->source_type)
+	{
+		case FILE_TYPE_DIRECTORY:
+			return FILE_ACTION_NONE;
+
+		case FILE_TYPE_SYMLINK:
+			/* FIXME: Check if it points to the same target? */
+			return FILE_ACTION_NONE;
+
+		case FILE_TYPE_REGULAR:
+			if (!entry->isrelfile)
+			{
+				/*
+				 * It's a non-data file that we have no special processing
+				 * for. Copy it in toto.
+				 */
+				return FILE_ACTION_COPY;
+			}
+			else
+			{
+				/*
+				 * It's a data file that exists in both systems.
+				 *
+				 * If it's larger in target, we can truncate it. There will
+				 * also be a WAL record of the truncation in the source
+				 * system, so WAL replay would eventually truncate the target
+				 * too, but we might as well do it now.
+				 *
+				 * If it's smaller in the target, it means that it has been
+				 * truncated in the target, or enlarged in the source, or
+				 * both. If it was truncated in the target, we need to copy
+				 * the missing tail from the source system. If it was enlarged
+				 * in the source system, there will be WAL records in the
+				 * source system for the new blocks, so we wouldn't need to
+				 * copy them here. But we don't know which scenario we're
+				 * dealing with, and there's no harm in copying the missing
+				 * blocks now, so do it now.
+				 *
+				 * If it's the same size, do nothing here. Any blocks modified
+				 * in the target will be copied based on parsing the target
+				 * system's WAL, and any blocks modified in the source will be
+				 * updated after rewinding, when the source system's WAL is
+				 * replayed.
+				 */
+				if (entry->target_size < entry->source_size)
+					return FILE_ACTION_COPY_TAIL;
+				else if (entry->target_size > entry->source_size)
+					return FILE_ACTION_TRUNCATE;
+				else
+					return FILE_ACTION_NONE;
+			}
+			break;
+
+		case FILE_TYPE_UNDEFINED:
+			pg_fatal("unknown file type for \"%s\"", path);
+			break;
+	}
+
+	/* unreachable */
+	pg_fatal("could not decide what to do with file \"%s\"", path);
+}
+
+/*
+ * Decide what to do with each file.
+ */
+void
+filemap_finalize()
+{
+	int			i;
+
+	filemap_list_to_array(filemap);
+
+	for (i = 0; i < filemap->narray; i++)
+	{
+		file_entry_t *entry = filemap->array[i];
+
+		entry->action = decide_file_action(entry);
+	}
+
+	/* Sort the actions to the order that they should be performed */
+	qsort(filemap->array, filemap->narray, sizeof(file_entry_t *),
+		  final_filemap_cmp);
+}
diff --git a/src/bin/pg_rewind/filemap.h b/src/bin/pg_rewind/filemap.h
index 0cb7425170c..a5e8df57f40 100644
--- a/src/bin/pg_rewind/filemap.h
+++ b/src/bin/pg_rewind/filemap.h
@@ -14,17 +14,21 @@
 
 /*
  * For every file found in the local or remote system, we have a file entry
- * which says what we are going to do with the file. For relation files,
- * there is also a page map, marking pages in the file that were changed
- * locally.
- *
- * The enum values are sorted in the order we want actions to be processed.
+ * that contains information about the file on both systems.  For relation
+ * files, there is also a page map that marks pages in the file that were
+ * changed in the target after the last common checkpoint.  Each entry also
+ * contains an 'action' field, which says what we are going to do with the
+ * file.
  */
+
+/* these enum values are sorted in the order we want actions to be processed */
 typedef enum
 {
+	FILE_ACTION_UNDECIDED = 0,	/* not decided yet */
+
 	FILE_ACTION_CREATE,			/* create local directory or symbolic link */
 	FILE_ACTION_COPY,			/* copy whole file, overwriting if exists */
-	FILE_ACTION_COPY_TAIL,		/* copy tail from 'oldsize' to 'newsize' */
+	FILE_ACTION_COPY_TAIL,		/* copy tail from 'source_size' to 'target_size' */
 	FILE_ACTION_NONE,			/* no action (we might still copy modified
 								 * blocks based on the parsed WAL) */
 	FILE_ACTION_TRUNCATE,		/* truncate local file to 'newsize' bytes */
@@ -33,6 +37,8 @@ typedef enum
 
 typedef enum
 {
+	FILE_TYPE_UNDEFINED = 0,
+
 	FILE_TYPE_REGULAR,
 	FILE_TYPE_DIRECTORY,
 	FILE_TYPE_SYMLINK
@@ -41,19 +47,30 @@ typedef enum
 typedef struct file_entry_t
 {
 	char	   *path;
-	file_type_t type;
+	bool		isrelfile;		/* is it a relation data file? */
 
-	file_action_t action;
+	/*
+	 * Status of the file in the target.
+	 */
+	bool		target_exists;
+	file_type_t target_type;
+	size_t		target_size; /* for a regular file */
+	char	   *target_link_target; /* for a symlink */
 
-	/* for a regular file */
-	size_t		oldsize;
-	size_t		newsize;
-	bool		isrelfile;		/* is it a relation data file? */
+	datapagemap_t target_modified_pages;
 
-	datapagemap_t pagemap;
+	/*
+	 * Status of the file in the source.
+	 */
+	bool		source_exists;
+	file_type_t source_type;
+	size_t		source_size;
+	char	   *source_link_target; /* for a symlink */
 
-	/* for a symlink */
-	char	   *link_target;
+	/*
+	 * What will we do to the file?
+	 */
+	file_action_t action;
 
 	struct file_entry_t *next;
 } file_entry_t;
@@ -70,20 +87,19 @@ typedef struct filemap_t
 
 	/*
 	 * After processing all the remote files, the entries in the linked list
-	 * are moved to this array. After processing local files, too, all the
+	 * are moved to this array.  After processing local files, too, all the
 	 * local entries are added to the array by filemap_finalize, and sorted in
-	 * the final order. After filemap_finalize, all the entries are in the
+	 * the final order.  After filemap_finalize, all the entries are in the
 	 * array, and the linked list is empty.
 	 */
 	file_entry_t **array;
 	int			narray;			/* current length of array */
 
 	/*
-	 * Summary information. total_size is the total size of the source
-	 * cluster, and fetch_size is the number of bytes that needs to be copied.
+	 * Summary information.
 	 */
-	uint64		total_size;
-	uint64		fetch_size;
+	uint64		total_size;		/* total size of the source cluster */
+	uint64		fetch_size;		/* number of bytes that needs to be copied */
 } filemap_t;
 
 extern filemap_t *filemap;
@@ -94,11 +110,12 @@ extern void print_filemap(void);
 
 /* Functions for populating the filemap */
 extern void process_source_file(const char *path, file_type_t type,
-								size_t newsize, const char *link_target);
+								size_t size, const char *link_target);
 extern void process_target_file(const char *path, file_type_t type,
-								size_t newsize, const char *link_target);
-extern void process_block_change(ForkNumber forknum, RelFileNode rnode,
-								 BlockNumber blkno);
+								size_t size, const char *link_target);
+extern void process_target_wal_block_change(ForkNumber forknum,
+											RelFileNode rnode,
+											BlockNumber blkno);
 extern void filemap_finalize(void);
 
 #endif							/* FILEMAP_H */
diff --git a/src/bin/pg_rewind/libpq_fetch.c b/src/bin/pg_rewind/libpq_fetch.c
index bf4dfc23b96..7fc9161b8c8 100644
--- a/src/bin/pg_rewind/libpq_fetch.c
+++ b/src/bin/pg_rewind/libpq_fetch.c
@@ -465,7 +465,7 @@ libpq_executeFileMap(filemap_t *map)
 		entry = map->array[i];
 
 		/* If this is a relation file, copy the modified blocks */
-		execute_pagemap(&entry->pagemap, entry->path);
+		execute_pagemap(&entry->target_modified_pages, entry->path);
 
 		switch (entry->action)
 		{
@@ -476,15 +476,15 @@ libpq_executeFileMap(filemap_t *map)
 			case FILE_ACTION_COPY:
 				/* Truncate the old file out of the way, if any */
 				open_target_file(entry->path, true);
-				fetch_file_range(entry->path, 0, entry->newsize);
+				fetch_file_range(entry->path, 0, entry->source_size);
 				break;
 
 			case FILE_ACTION_TRUNCATE:
-				truncate_target_file(entry->path, entry->newsize);
+				truncate_target_file(entry->path, entry->source_size);
 				break;
 
 			case FILE_ACTION_COPY_TAIL:
-				fetch_file_range(entry->path, entry->oldsize, entry->newsize);
+				fetch_file_range(entry->path, entry->target_size, entry->source_size);
 				break;
 
 			case FILE_ACTION_REMOVE:
@@ -494,6 +494,10 @@ libpq_executeFileMap(filemap_t *map)
 			case FILE_ACTION_CREATE:
 				create_target(entry);
 				break;
+
+			case FILE_ACTION_UNDECIDED:
+				pg_fatal("no action decided for \"%s\"", entry->path);
+				break;
 		}
 	}
 
diff --git a/src/bin/pg_rewind/parsexlog.c b/src/bin/pg_rewind/parsexlog.c
index 2229c86f9af..2baeb74ae93 100644
--- a/src/bin/pg_rewind/parsexlog.c
+++ b/src/bin/pg_rewind/parsexlog.c
@@ -436,6 +436,6 @@ extractPageInfo(XLogReaderState *record)
 		if (forknum != MAIN_FORKNUM)
 			continue;
 
-		process_block_change(forknum, rnode, blkno);
+		process_target_wal_block_change(forknum, rnode, blkno);
 	}
 }
diff --git a/src/bin/pg_rewind/pg_rewind.c b/src/bin/pg_rewind/pg_rewind.c
index c9b9e480c0f..210984d302b 100644
--- a/src/bin/pg_rewind/pg_rewind.c
+++ b/src/bin/pg_rewind/pg_rewind.c
@@ -369,7 +369,7 @@ main(int argc, char **argv)
 				chkpttli);
 
 	/*
-	 * Build the filemap, by comparing the source and target data directories.
+	 * Collect information about all files in the target and source systems.
 	 */
 	filemap_create();
 	if (showprogress)
@@ -390,8 +390,12 @@ main(int argc, char **argv)
 		pg_log_info("reading WAL in target");
 	extractPageMap(datadir_target, chkptrec, lastcommontliIndex,
 				   ControlFile_target.checkPoint, restore_command);
-	filemap_finalize();
 
+	/*
+	 * We have collected all information we need from both systems. Decide
+	 * what to do with each file.
+	 */
+	filemap_finalize();
 	if (showprogress)
 		calculate_totals();
 
-- 
2.20.1


--------------BF34D0120055DC3839060F92
Content-Type: text/x-patch; charset=UTF-8;
 name="v2-0003-pg_rewind-Replace-the-hybrid-list-array-data-stru.patch"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
 filename*0="v2-0003-pg_rewind-Replace-the-hybrid-list-array-data-stru.pa";
 filename*1="tch"



^ permalink  raw  reply  [nested|flat] 41+ messages in thread

* [PATCH v2 2/5] Refactor pg_rewind for more clear decision making.
@ 2020-08-19 12:34 Heikki Linnakangas <[email protected]>
  0 siblings, 0 replies; 41+ messages in thread

From: Heikki Linnakangas @ 2020-08-19 12:34 UTC (permalink / raw)

Deciding what to do with each file is now a separate step after all the
necessary information has been gathered. It is more clear that way.
Previously, the decision-making was divided between process_source_file()
and process_target_file(), and it was a bit hard to piece together what the
overall rules were.
---
 src/bin/pg_rewind/copy_fetch.c  |  14 +-
 src/bin/pg_rewind/file_ops.c    |  16 +-
 src/bin/pg_rewind/filemap.c     | 569 ++++++++++++++++----------------
 src/bin/pg_rewind/filemap.h     |  67 ++--
 src/bin/pg_rewind/libpq_fetch.c |  12 +-
 src/bin/pg_rewind/parsexlog.c   |   2 +-
 src/bin/pg_rewind/pg_rewind.c   |   8 +-
 7 files changed, 371 insertions(+), 317 deletions(-)

diff --git a/src/bin/pg_rewind/copy_fetch.c b/src/bin/pg_rewind/copy_fetch.c
index 1edab5f1867..18fad32600e 100644
--- a/src/bin/pg_rewind/copy_fetch.c
+++ b/src/bin/pg_rewind/copy_fetch.c
@@ -210,7 +210,7 @@ copy_executeFileMap(filemap_t *map)
 	for (i = 0; i < map->narray; i++)
 	{
 		entry = map->array[i];
-		execute_pagemap(&entry->pagemap, entry->path);
+		execute_pagemap(&entry->target_modified_pages, entry->path);
 
 		switch (entry->action)
 		{
@@ -219,16 +219,16 @@ copy_executeFileMap(filemap_t *map)
 				break;
 
 			case FILE_ACTION_COPY:
-				rewind_copy_file_range(entry->path, 0, entry->newsize, true);
+				rewind_copy_file_range(entry->path, 0, entry->source_size, true);
 				break;
 
 			case FILE_ACTION_TRUNCATE:
-				truncate_target_file(entry->path, entry->newsize);
+				truncate_target_file(entry->path, entry->source_size);
 				break;
 
 			case FILE_ACTION_COPY_TAIL:
-				rewind_copy_file_range(entry->path, entry->oldsize,
-									   entry->newsize, false);
+				rewind_copy_file_range(entry->path, entry->target_size,
+									   entry->source_size, false);
 				break;
 
 			case FILE_ACTION_CREATE:
@@ -238,6 +238,10 @@ copy_executeFileMap(filemap_t *map)
 			case FILE_ACTION_REMOVE:
 				remove_target(entry);
 				break;
+
+			case FILE_ACTION_UNDECIDED:
+				pg_fatal("no action decided for \"%s\"", entry->path);
+				break;
 		}
 	}
 
diff --git a/src/bin/pg_rewind/file_ops.c b/src/bin/pg_rewind/file_ops.c
index 55439db20ba..ec37d0b2e0d 100644
--- a/src/bin/pg_rewind/file_ops.c
+++ b/src/bin/pg_rewind/file_ops.c
@@ -126,8 +126,9 @@ void
 remove_target(file_entry_t *entry)
 {
 	Assert(entry->action == FILE_ACTION_REMOVE);
+	Assert(entry->target_exists);
 
-	switch (entry->type)
+	switch (entry->target_type)
 	{
 		case FILE_TYPE_DIRECTORY:
 			remove_target_dir(entry->path);
@@ -140,6 +141,10 @@ remove_target(file_entry_t *entry)
 		case FILE_TYPE_SYMLINK:
 			remove_target_symlink(entry->path);
 			break;
+
+		case FILE_TYPE_UNDEFINED:
+			pg_fatal("undefined file type for \"%s\"", entry->path);
+			break;
 	}
 }
 
@@ -147,21 +152,26 @@ void
 create_target(file_entry_t *entry)
 {
 	Assert(entry->action == FILE_ACTION_CREATE);
+	Assert(!entry->target_exists);
 
-	switch (entry->type)
+	switch (entry->source_type)
 	{
 		case FILE_TYPE_DIRECTORY:
 			create_target_dir(entry->path);
 			break;
 
 		case FILE_TYPE_SYMLINK:
-			create_target_symlink(entry->path, entry->link_target);
+			create_target_symlink(entry->path, entry->source_link_target);
 			break;
 
 		case FILE_TYPE_REGULAR:
 			/* can't happen. Regular files are created with open_target_file. */
 			pg_fatal("invalid action (CREATE) for regular file");
 			break;
+
+		case FILE_TYPE_UNDEFINED:
+			pg_fatal("undefined file type for \"%s\"", entry->path);
+			break;
 	}
 }
 
diff --git a/src/bin/pg_rewind/filemap.c b/src/bin/pg_rewind/filemap.c
index 1879257b66a..7971daeda5e 100644
--- a/src/bin/pg_rewind/filemap.c
+++ b/src/bin/pg_rewind/filemap.c
@@ -26,6 +26,8 @@ static bool isRelDataFile(const char *path);
 static char *datasegpath(RelFileNode rnode, ForkNumber forknum,
 						 BlockNumber segno);
 static int	path_cmp(const void *a, const void *b);
+
+static file_entry_t *get_filemap_entry(const char *path, bool create);
 static int	final_filemap_cmp(const void *a, const void *b);
 static void filemap_list_to_array(filemap_t *map);
 static bool check_file_excluded(const char *path, bool is_source);
@@ -146,6 +148,65 @@ filemap_create(void)
 	filemap = map;
 }
 
+/* Look up or create entry for 'path' */
+static file_entry_t *
+get_filemap_entry(const char *path, bool create)
+{
+	filemap_t  *map = filemap;
+	file_entry_t *entry;
+	file_entry_t **e;
+	file_entry_t key;
+	file_entry_t *key_ptr;
+
+	if (map->array)
+	{
+		key.path = (char *) path;
+		key_ptr = &key;
+		e = bsearch(&key_ptr, map->array, map->narray, sizeof(file_entry_t *),
+					path_cmp);
+	}
+	else
+		e = NULL;
+
+	if (e)
+		entry = *e;
+	else if (!create)
+		entry = NULL;
+	else
+	{
+		/* Create a new entry for this file */
+		entry = pg_malloc(sizeof(file_entry_t));
+		entry->path = pg_strdup(path);
+		entry->isrelfile = isRelDataFile(path);
+		entry->action = FILE_ACTION_UNDECIDED;
+
+		entry->target_exists = false;
+		entry->target_type = FILE_TYPE_UNDEFINED;
+		entry->target_size = 0;
+		entry->target_link_target = NULL;
+		entry->target_modified_pages.bitmap = NULL;
+		entry->target_modified_pages.bitmapsize = 0;
+
+		entry->source_exists = false;
+		entry->source_type = FILE_TYPE_UNDEFINED;
+		entry->source_size = 0;
+		entry->source_link_target = NULL;
+
+		entry->next = NULL;
+
+		if (map->last)
+		{
+			map->last->next = entry;
+			map->last = entry;
+		}
+		else
+			map->first = map->last = entry;
+		map->nlist++;
+	}
+
+	return entry;
+}
+
 /*
  * Callback for processing source file list.
  *
@@ -154,25 +215,12 @@ filemap_create(void)
  * exists in the target and whether the size matches.
  */
 void
-process_source_file(const char *path, file_type_t type, size_t newsize,
+process_source_file(const char *path, file_type_t type, size_t size,
 					const char *link_target)
 {
-	bool		exists;
-	char		localpath[MAXPGPATH];
-	struct stat statbuf;
-	filemap_t  *map = filemap;
-	file_action_t action = FILE_ACTION_NONE;
-	size_t		oldsize = 0;
 	file_entry_t *entry;
 
-	Assert(map->array == NULL);
-
-	/*
-	 * Skip any files matching the exclusion filters. This has the effect to
-	 * remove all those files on the target.
-	 */
-	if (check_file_excluded(path, true))
-		return;
+	Assert(filemap->array == NULL);
 
 	/*
 	 * Pretend that pg_wal is a directory, even if it's really a symlink. We
@@ -182,16 +230,6 @@ process_source_file(const char *path, file_type_t type, size_t newsize,
 	if (strcmp(path, "pg_wal") == 0 && type == FILE_TYPE_SYMLINK)
 		type = FILE_TYPE_DIRECTORY;
 
-	/*
-	 * Skip temporary files, .../pgsql_tmp/... and .../pgsql_tmp.* in source.
-	 * This has the effect that all temporary files in the destination will be
-	 * removed.
-	 */
-	if (strstr(path, "/" PG_TEMP_FILE_PREFIX) != NULL)
-		return;
-	if (strstr(path, "/" PG_TEMP_FILES_DIR "/") != NULL)
-		return;
-
 	/*
 	 * sanity check: a filename that looks like a data file better be a
 	 * regular file
@@ -199,142 +237,12 @@ process_source_file(const char *path, file_type_t type, size_t newsize,
 	if (type != FILE_TYPE_REGULAR && isRelDataFile(path))
 		pg_fatal("data file \"%s\" in source is not a regular file", path);
 
-	snprintf(localpath, sizeof(localpath), "%s/%s", datadir_target, path);
-
-	/* Does the corresponding file exist in the target data dir? */
-	if (lstat(localpath, &statbuf) < 0)
-	{
-		if (errno != ENOENT)
-			pg_fatal("could not stat file \"%s\": %m",
-					 localpath);
-
-		exists = false;
-	}
-	else
-		exists = true;
-
-	switch (type)
-	{
-		case FILE_TYPE_DIRECTORY:
-			if (exists && !S_ISDIR(statbuf.st_mode) && strcmp(path, "pg_wal") != 0)
-			{
-				/* it's a directory in source, but not in target. Strange.. */
-				pg_fatal("\"%s\" is not a directory", localpath);
-			}
-
-			if (!exists)
-				action = FILE_ACTION_CREATE;
-			else
-				action = FILE_ACTION_NONE;
-			oldsize = 0;
-			break;
-
-		case FILE_TYPE_SYMLINK:
-			if (exists &&
-#ifndef WIN32
-				!S_ISLNK(statbuf.st_mode)
-#else
-				!pgwin32_is_junction(localpath)
-#endif
-				)
-			{
-				/*
-				 * It's a symbolic link in source, but not in target.
-				 * Strange..
-				 */
-				pg_fatal("\"%s\" is not a symbolic link", localpath);
-			}
-
-			if (!exists)
-				action = FILE_ACTION_CREATE;
-			else
-				action = FILE_ACTION_NONE;
-			oldsize = 0;
-			break;
-
-		case FILE_TYPE_REGULAR:
-			if (exists && !S_ISREG(statbuf.st_mode))
-				pg_fatal("\"%s\" is not a regular file", localpath);
-
-			if (!exists || !isRelDataFile(path))
-			{
-				/*
-				 * File exists in source, but not in target. Or it's a
-				 * non-data file that we have no special processing for. Copy
-				 * it in toto.
-				 *
-				 * An exception: PG_VERSIONs should be identical, but avoid
-				 * overwriting it for paranoia.
-				 */
-				if (pg_str_endswith(path, "PG_VERSION"))
-				{
-					action = FILE_ACTION_NONE;
-					oldsize = statbuf.st_size;
-				}
-				else
-				{
-					action = FILE_ACTION_COPY;
-					oldsize = 0;
-				}
-			}
-			else
-			{
-				/*
-				 * It's a data file that exists in both.
-				 *
-				 * If it's larger in target, we can truncate it. There will
-				 * also be a WAL record of the truncation in the source
-				 * system, so WAL replay would eventually truncate the target
-				 * too, but we might as well do it now.
-				 *
-				 * If it's smaller in the target, it means that it has been
-				 * truncated in the target, or enlarged in the source, or
-				 * both. If it was truncated in the target, we need to copy
-				 * the missing tail from the source system. If it was enlarged
-				 * in the source system, there will be WAL records in the
-				 * source system for the new blocks, so we wouldn't need to
-				 * copy them here. But we don't know which scenario we're
-				 * dealing with, and there's no harm in copying the missing
-				 * blocks now, so do it now.
-				 *
-				 * If it's the same size, do nothing here. Any blocks modified
-				 * in the target will be copied based on parsing the target
-				 * system's WAL, and any blocks modified in the source will be
-				 * updated after rewinding, when the source system's WAL is
-				 * replayed.
-				 */
-				oldsize = statbuf.st_size;
-				if (oldsize < newsize)
-					action = FILE_ACTION_COPY_TAIL;
-				else if (oldsize > newsize)
-					action = FILE_ACTION_TRUNCATE;
-				else
-					action = FILE_ACTION_NONE;
-			}
-			break;
-	}
-
-	/* Create a new entry for this file */
-	entry = pg_malloc(sizeof(file_entry_t));
-	entry->path = pg_strdup(path);
-	entry->type = type;
-	entry->action = action;
-	entry->oldsize = oldsize;
-	entry->newsize = newsize;
-	entry->link_target = link_target ? pg_strdup(link_target) : NULL;
-	entry->next = NULL;
-	entry->pagemap.bitmap = NULL;
-	entry->pagemap.bitmapsize = 0;
-	entry->isrelfile = isRelDataFile(path);
-
-	if (map->last)
-	{
-		map->last->next = entry;
-		map->last = entry;
-	}
-	else
-		map->first = map->last = entry;
-	map->nlist++;
+	/* Remember this source file */
+	entry = get_filemap_entry(path, true);
+	entry->source_exists = true;
+	entry->source_type = type;
+	entry->source_size = size;
+	entry->source_link_target = link_target ? pg_strdup(link_target) : NULL;
 }
 
 /*
@@ -345,14 +253,9 @@ process_source_file(const char *path, file_type_t type, size_t newsize,
  * deletion.
  */
 void
-process_target_file(const char *path, file_type_t type, size_t oldsize,
+process_target_file(const char *path, file_type_t type, size_t size,
 					const char *link_target)
 {
-	bool		exists;
-	char		localpath[MAXPGPATH];
-	struct stat statbuf;
-	file_entry_t key;
-	file_entry_t *key_ptr;
 	filemap_t  *map = filemap;
 	file_entry_t *entry;
 
@@ -361,17 +264,6 @@ process_target_file(const char *path, file_type_t type, size_t oldsize,
 	 * from the target data folder all paths which have been filtered out from
 	 * the source data folder when processing the source files.
 	 */
-
-	snprintf(localpath, sizeof(localpath), "%s/%s", datadir_target, path);
-	if (lstat(localpath, &statbuf) < 0)
-	{
-		if (errno != ENOENT)
-			pg_fatal("could not stat file \"%s\": %m",
-					 localpath);
-
-		exists = false;
-	}
-
 	if (map->array == NULL)
 	{
 		/* on first call, initialize lookup array */
@@ -389,120 +281,76 @@ process_target_file(const char *path, file_type_t type, size_t oldsize,
 	}
 
 	/*
-	 * Like in process_source_file, pretend that xlog is always a  directory.
+	 * Like in process_source_file, pretend that pg_wal is always a directory.
 	 */
 	if (strcmp(path, "pg_wal") == 0 && type == FILE_TYPE_SYMLINK)
 		type = FILE_TYPE_DIRECTORY;
 
-	key.path = (char *) path;
-	key_ptr = &key;
-	exists = (bsearch(&key_ptr, map->array, map->narray, sizeof(file_entry_t *),
-					  path_cmp) != NULL);
-
-	/* Remove any file or folder that doesn't exist in the source system. */
-	if (!exists)
-	{
-		entry = pg_malloc(sizeof(file_entry_t));
-		entry->path = pg_strdup(path);
-		entry->type = type;
-		entry->action = FILE_ACTION_REMOVE;
-		entry->oldsize = oldsize;
-		entry->newsize = 0;
-		entry->link_target = link_target ? pg_strdup(link_target) : NULL;
-		entry->next = NULL;
-		entry->pagemap.bitmap = NULL;
-		entry->pagemap.bitmapsize = 0;
-		entry->isrelfile = isRelDataFile(path);
-
-		if (map->last == NULL)
-			map->first = entry;
-		else
-			map->last->next = entry;
-		map->last = entry;
-		map->nlist++;
-	}
-	else
-	{
-		/*
-		 * We already handled all files that exist in the source system in
-		 * process_source_file().
-		 */
-	}
+	/* Remember this target file */
+	entry = get_filemap_entry(path, true);
+	entry->target_exists = true;
+	entry->target_type = type;
+	entry->target_size = size;
+	entry->target_link_target = link_target ? pg_strdup(link_target) : NULL;
 }
 
 /*
  * This callback gets called while we read the WAL in the target, for every
- * block that have changed in the target system. It makes note of all the
+ * block that have changed in the target system.  It makes note of all the
  * changed blocks in the pagemap of the file.
+ *
+ * NOTE: All the files on both systems must have already been added to the
+ * file map!
  */
 void
-process_block_change(ForkNumber forknum, RelFileNode rnode, BlockNumber blkno)
+process_target_wal_block_change(ForkNumber forknum, RelFileNode rnode,
+								BlockNumber blkno)
 {
 	char	   *path;
-	file_entry_t key;
-	file_entry_t *key_ptr;
 	file_entry_t *entry;
 	BlockNumber blkno_inseg;
 	int			segno;
-	filemap_t  *map = filemap;
-	file_entry_t **e;
 
-	Assert(map->array);
+	Assert(filemap->array);
 
 	segno = blkno / RELSEG_SIZE;
 	blkno_inseg = blkno % RELSEG_SIZE;
 
 	path = datasegpath(rnode, forknum, segno);
-
-	key.path = (char *) path;
-	key_ptr = &key;
-
-	e = bsearch(&key_ptr, map->array, map->narray, sizeof(file_entry_t *),
-				path_cmp);
-	if (e)
-		entry = *e;
-	else
-		entry = NULL;
+	entry = get_filemap_entry(path, false);
 	pfree(path);
 
 	if (entry)
 	{
-		Assert(entry->isrelfile);
-
-		switch (entry->action)
-		{
-			case FILE_ACTION_NONE:
-			case FILE_ACTION_TRUNCATE:
-				/* skip if we're truncating away the modified block anyway */
-				if ((blkno_inseg + 1) * BLCKSZ <= entry->newsize)
-					datapagemap_add(&entry->pagemap, blkno_inseg);
-				break;
+		int64		end_offset;
 
-			case FILE_ACTION_COPY_TAIL:
-
-				/*
-				 * skip the modified block if it is part of the "tail" that
-				 * we're copying anyway.
-				 */
-				if ((blkno_inseg + 1) * BLCKSZ <= entry->oldsize)
-					datapagemap_add(&entry->pagemap, blkno_inseg);
-				break;
+		Assert(entry->isrelfile);
 
-			case FILE_ACTION_COPY:
-			case FILE_ACTION_REMOVE:
-				break;
+		if (entry->target_type != FILE_TYPE_REGULAR)
+			pg_fatal("unexpected page modification for directory or symbolic link \"%s\"",
+					 entry->path);
 
-			case FILE_ACTION_CREATE:
-				pg_fatal("unexpected page modification for directory or symbolic link \"%s\"", entry->path);
-		}
+		/*
+		 * If the block beyond the EOF in the source system, no need to
+		 * remember it now, because we're going to truncate it away from the
+		 * target anyway. Also no need to remember the block if it's beyond
+		 * the current EOF in the target system; we will copy it over with the
+		 * "tail" from the source system, anyway.
+		 */
+		end_offset = (blkno_inseg + 1) * BLCKSZ;
+		if (end_offset <= entry->source_size &&
+			end_offset <= entry->target_size)
+			datapagemap_add(&entry->target_modified_pages, blkno_inseg);
 	}
 	else
 	{
 		/*
 		 * If we don't have any record of this file in the file map, it means
-		 * that it's a relation that doesn't exist in the source system, and
-		 * it was subsequently removed in the target system, too. We can
-		 * safely ignore it.
+		 * that it's a relation that doesn't exist in the source system.  It
+		 * could exist in the target system; we haven't moved the target-only
+		 * entries from the linked list to the array yet!  But in any case, if
+		 * it doesn't exist in the source it will be removed from the target
+		 * too, and we can safely ignore it.
 		 */
 	}
 }
@@ -593,16 +441,6 @@ filemap_list_to_array(filemap_t *map)
 	map->first = map->last = NULL;
 }
 
-void
-filemap_finalize(void)
-{
-	filemap_t  *map = filemap;
-
-	filemap_list_to_array(map);
-	qsort(map->array, map->narray, sizeof(file_entry_t *),
-		  final_filemap_cmp);
-}
-
 static const char *
 action_to_str(file_action_t action)
 {
@@ -643,26 +481,26 @@ calculate_totals(void)
 	{
 		entry = map->array[i];
 
-		if (entry->type != FILE_TYPE_REGULAR)
+		if (entry->source_type != FILE_TYPE_REGULAR)
 			continue;
 
-		map->total_size += entry->newsize;
+		map->total_size += entry->source_size;
 
 		if (entry->action == FILE_ACTION_COPY)
 		{
-			map->fetch_size += entry->newsize;
+			map->fetch_size += entry->source_size;
 			continue;
 		}
 
 		if (entry->action == FILE_ACTION_COPY_TAIL)
-			map->fetch_size += (entry->newsize - entry->oldsize);
+			map->fetch_size += (entry->source_size - entry->target_size);
 
-		if (entry->pagemap.bitmapsize > 0)
+		if (entry->target_modified_pages.bitmapsize > 0)
 		{
 			datapagemap_iterator_t *iter;
 			BlockNumber blk;
 
-			iter = datapagemap_iterate(&entry->pagemap);
+			iter = datapagemap_iterate(&entry->target_modified_pages);
 			while (datapagemap_next(iter, &blk))
 				map->fetch_size += BLCKSZ;
 
@@ -682,13 +520,13 @@ print_filemap(void)
 	{
 		entry = map->array[i];
 		if (entry->action != FILE_ACTION_NONE ||
-			entry->pagemap.bitmapsize > 0)
+			entry->target_modified_pages.bitmapsize > 0)
 		{
 			pg_log_debug("%s (%s)", entry->path,
 						 action_to_str(entry->action));
 
-			if (entry->pagemap.bitmapsize > 0)
-				datapagemap_print(&entry->pagemap);
+			if (entry->target_modified_pages.bitmapsize > 0)
+				datapagemap_print(&entry->target_modified_pages);
 		}
 	}
 	fflush(stdout);
@@ -837,3 +675,180 @@ final_filemap_cmp(const void *a, const void *b)
 	else
 		return strcmp(fa->path, fb->path);
 }
+
+/*
+ * Decide what action to perform to a file.
+ */
+static file_action_t
+decide_file_action(file_entry_t *entry)
+{
+	const char *path = entry->path;
+
+	/*
+	 * Don't touch the control file. It is handled specially, after copying
+	 * all the other files.
+	 */
+	if (strcmp(path, "global/pg_control") == 0)
+		return FILE_ACTION_NONE;
+
+	/*
+	 * Remove all files matching the exclusion filters in the target.
+	 */
+	if (check_file_excluded(path, true))
+	{
+		if (entry->target_exists)
+			return FILE_ACTION_REMOVE;
+		else
+			return FILE_ACTION_NONE;
+	}
+
+	/*
+	 * Also remove all temporary files, .../pgsql_tmp/... and .../pgsql_tmp.*
+	 * in the target.
+	 */
+	if (strstr(path, "/" PG_TEMP_FILE_PREFIX) != NULL ||
+		strstr(path, "/" PG_TEMP_FILES_DIR "/") != NULL)
+	{
+		if (entry->target_exists)
+			return FILE_ACTION_REMOVE;
+		else
+			return FILE_ACTION_NONE;
+	}
+
+	/*
+	 * Handle cases where the file is missing from one of the systems.
+	 */
+	if (!entry->target_exists && entry->source_exists)
+	{
+		/*
+		 * File exists in source, but not in target. Copy it in toto. (If it's
+		 * a relation data file, WAL replay after rewinding should re-create
+		 * it anyway. But there's no harm in copying it now.)
+		 */
+		switch (entry->source_type)
+		{
+			case FILE_TYPE_DIRECTORY:
+			case FILE_TYPE_SYMLINK:
+				return FILE_ACTION_CREATE;
+			case FILE_TYPE_REGULAR:
+				return FILE_ACTION_COPY;
+			case FILE_TYPE_UNDEFINED:
+				pg_fatal("unknown file type for \"%s\"", entry->path);
+				break;
+		}
+	}
+	else if (entry->target_exists && !entry->source_exists)
+	{
+		/* File exists in target, but not source. Remove it. */
+		return FILE_ACTION_REMOVE;
+	}
+	else if (!entry->target_exists && !entry->source_exists)
+	{
+		/*
+		 * Doesn't exist in either server. Why does it have an entry in the
+		 * first place??
+		 */
+		return FILE_ACTION_NONE;
+	}
+
+	/*
+	 * Otherwise, the file exists on both systems
+	 */
+	Assert(entry->target_exists && entry->source_exists);
+
+	if (entry->source_type != entry->target_type)
+	{
+		/* But it's a different kind of object. Strange.. */
+		pg_fatal("file \"%s\" is of different type in source and target", entry->path);
+	}
+
+	/*
+	 * PG_VERSION files should be identical on both systems, but avoid
+	 * overwriting them for paranoia.
+	 */
+	if (pg_str_endswith(entry->path, "PG_VERSION"))
+		return FILE_ACTION_NONE;
+
+	switch (entry->source_type)
+	{
+		case FILE_TYPE_DIRECTORY:
+			return FILE_ACTION_NONE;
+
+		case FILE_TYPE_SYMLINK:
+			/* FIXME: Check if it points to the same target? */
+			return FILE_ACTION_NONE;
+
+		case FILE_TYPE_REGULAR:
+			if (!entry->isrelfile)
+			{
+				/*
+				 * It's a non-data file that we have no special processing
+				 * for. Copy it in toto.
+				 */
+				return FILE_ACTION_COPY;
+			}
+			else
+			{
+				/*
+				 * It's a data file that exists in both systems.
+				 *
+				 * If it's larger in target, we can truncate it. There will
+				 * also be a WAL record of the truncation in the source
+				 * system, so WAL replay would eventually truncate the target
+				 * too, but we might as well do it now.
+				 *
+				 * If it's smaller in the target, it means that it has been
+				 * truncated in the target, or enlarged in the source, or
+				 * both. If it was truncated in the target, we need to copy
+				 * the missing tail from the source system. If it was enlarged
+				 * in the source system, there will be WAL records in the
+				 * source system for the new blocks, so we wouldn't need to
+				 * copy them here. But we don't know which scenario we're
+				 * dealing with, and there's no harm in copying the missing
+				 * blocks now, so do it now.
+				 *
+				 * If it's the same size, do nothing here. Any blocks modified
+				 * in the target will be copied based on parsing the target
+				 * system's WAL, and any blocks modified in the source will be
+				 * updated after rewinding, when the source system's WAL is
+				 * replayed.
+				 */
+				if (entry->target_size < entry->source_size)
+					return FILE_ACTION_COPY_TAIL;
+				else if (entry->target_size > entry->source_size)
+					return FILE_ACTION_TRUNCATE;
+				else
+					return FILE_ACTION_NONE;
+			}
+			break;
+
+		case FILE_TYPE_UNDEFINED:
+			pg_fatal("unknown file type for \"%s\"", path);
+			break;
+	}
+
+	/* unreachable */
+	pg_fatal("could not decide what to do with file \"%s\"", path);
+}
+
+/*
+ * Decide what to do with each file.
+ */
+void
+filemap_finalize()
+{
+	int			i;
+
+	filemap_list_to_array(filemap);
+
+	for (i = 0; i < filemap->narray; i++)
+	{
+		file_entry_t *entry = filemap->array[i];
+
+		entry->action = decide_file_action(entry);
+	}
+
+	/* Sort the actions to the order that they should be performed */
+	qsort(filemap->array, filemap->narray, sizeof(file_entry_t *),
+		  final_filemap_cmp);
+}
diff --git a/src/bin/pg_rewind/filemap.h b/src/bin/pg_rewind/filemap.h
index 0cb7425170c..a5e8df57f40 100644
--- a/src/bin/pg_rewind/filemap.h
+++ b/src/bin/pg_rewind/filemap.h
@@ -14,17 +14,21 @@
 
 /*
  * For every file found in the local or remote system, we have a file entry
- * which says what we are going to do with the file. For relation files,
- * there is also a page map, marking pages in the file that were changed
- * locally.
- *
- * The enum values are sorted in the order we want actions to be processed.
+ * that contains information about the file on both systems.  For relation
+ * files, there is also a page map that marks pages in the file that were
+ * changed in the target after the last common checkpoint.  Each entry also
+ * contains an 'action' field, which says what we are going to do with the
+ * file.
  */
+
+/* these enum values are sorted in the order we want actions to be processed */
 typedef enum
 {
+	FILE_ACTION_UNDECIDED = 0,	/* not decided yet */
+
 	FILE_ACTION_CREATE,			/* create local directory or symbolic link */
 	FILE_ACTION_COPY,			/* copy whole file, overwriting if exists */
-	FILE_ACTION_COPY_TAIL,		/* copy tail from 'oldsize' to 'newsize' */
+	FILE_ACTION_COPY_TAIL,		/* copy tail from 'source_size' to 'target_size' */
 	FILE_ACTION_NONE,			/* no action (we might still copy modified
 								 * blocks based on the parsed WAL) */
 	FILE_ACTION_TRUNCATE,		/* truncate local file to 'newsize' bytes */
@@ -33,6 +37,8 @@ typedef enum
 
 typedef enum
 {
+	FILE_TYPE_UNDEFINED = 0,
+
 	FILE_TYPE_REGULAR,
 	FILE_TYPE_DIRECTORY,
 	FILE_TYPE_SYMLINK
@@ -41,19 +47,30 @@ typedef enum
 typedef struct file_entry_t
 {
 	char	   *path;
-	file_type_t type;
+	bool		isrelfile;		/* is it a relation data file? */
 
-	file_action_t action;
+	/*
+	 * Status of the file in the target.
+	 */
+	bool		target_exists;
+	file_type_t target_type;
+	size_t		target_size; /* for a regular file */
+	char	   *target_link_target; /* for a symlink */
 
-	/* for a regular file */
-	size_t		oldsize;
-	size_t		newsize;
-	bool		isrelfile;		/* is it a relation data file? */
+	datapagemap_t target_modified_pages;
 
-	datapagemap_t pagemap;
+	/*
+	 * Status of the file in the source.
+	 */
+	bool		source_exists;
+	file_type_t source_type;
+	size_t		source_size;
+	char	   *source_link_target; /* for a symlink */
 
-	/* for a symlink */
-	char	   *link_target;
+	/*
+	 * What will we do to the file?
+	 */
+	file_action_t action;
 
 	struct file_entry_t *next;
 } file_entry_t;
@@ -70,20 +87,19 @@ typedef struct filemap_t
 
 	/*
 	 * After processing all the remote files, the entries in the linked list
-	 * are moved to this array. After processing local files, too, all the
+	 * are moved to this array.  After processing local files, too, all the
 	 * local entries are added to the array by filemap_finalize, and sorted in
-	 * the final order. After filemap_finalize, all the entries are in the
+	 * the final order.  After filemap_finalize, all the entries are in the
 	 * array, and the linked list is empty.
 	 */
 	file_entry_t **array;
 	int			narray;			/* current length of array */
 
 	/*
-	 * Summary information. total_size is the total size of the source
-	 * cluster, and fetch_size is the number of bytes that needs to be copied.
+	 * Summary information.
 	 */
-	uint64		total_size;
-	uint64		fetch_size;
+	uint64		total_size;		/* total size of the source cluster */
+	uint64		fetch_size;		/* number of bytes that needs to be copied */
 } filemap_t;
 
 extern filemap_t *filemap;
@@ -94,11 +110,12 @@ extern void print_filemap(void);
 
 /* Functions for populating the filemap */
 extern void process_source_file(const char *path, file_type_t type,
-								size_t newsize, const char *link_target);
+								size_t size, const char *link_target);
 extern void process_target_file(const char *path, file_type_t type,
-								size_t newsize, const char *link_target);
-extern void process_block_change(ForkNumber forknum, RelFileNode rnode,
-								 BlockNumber blkno);
+								size_t size, const char *link_target);
+extern void process_target_wal_block_change(ForkNumber forknum,
+											RelFileNode rnode,
+											BlockNumber blkno);
 extern void filemap_finalize(void);
 
 #endif							/* FILEMAP_H */
diff --git a/src/bin/pg_rewind/libpq_fetch.c b/src/bin/pg_rewind/libpq_fetch.c
index bf4dfc23b96..7fc9161b8c8 100644
--- a/src/bin/pg_rewind/libpq_fetch.c
+++ b/src/bin/pg_rewind/libpq_fetch.c
@@ -465,7 +465,7 @@ libpq_executeFileMap(filemap_t *map)
 		entry = map->array[i];
 
 		/* If this is a relation file, copy the modified blocks */
-		execute_pagemap(&entry->pagemap, entry->path);
+		execute_pagemap(&entry->target_modified_pages, entry->path);
 
 		switch (entry->action)
 		{
@@ -476,15 +476,15 @@ libpq_executeFileMap(filemap_t *map)
 			case FILE_ACTION_COPY:
 				/* Truncate the old file out of the way, if any */
 				open_target_file(entry->path, true);
-				fetch_file_range(entry->path, 0, entry->newsize);
+				fetch_file_range(entry->path, 0, entry->source_size);
 				break;
 
 			case FILE_ACTION_TRUNCATE:
-				truncate_target_file(entry->path, entry->newsize);
+				truncate_target_file(entry->path, entry->source_size);
 				break;
 
 			case FILE_ACTION_COPY_TAIL:
-				fetch_file_range(entry->path, entry->oldsize, entry->newsize);
+				fetch_file_range(entry->path, entry->target_size, entry->source_size);
 				break;
 
 			case FILE_ACTION_REMOVE:
@@ -494,6 +494,10 @@ libpq_executeFileMap(filemap_t *map)
 			case FILE_ACTION_CREATE:
 				create_target(entry);
 				break;
+
+			case FILE_ACTION_UNDECIDED:
+				pg_fatal("no action decided for \"%s\"", entry->path);
+				break;
 		}
 	}
 
diff --git a/src/bin/pg_rewind/parsexlog.c b/src/bin/pg_rewind/parsexlog.c
index 2229c86f9af..2baeb74ae93 100644
--- a/src/bin/pg_rewind/parsexlog.c
+++ b/src/bin/pg_rewind/parsexlog.c
@@ -436,6 +436,6 @@ extractPageInfo(XLogReaderState *record)
 		if (forknum != MAIN_FORKNUM)
 			continue;
 
-		process_block_change(forknum, rnode, blkno);
+		process_target_wal_block_change(forknum, rnode, blkno);
 	}
 }
diff --git a/src/bin/pg_rewind/pg_rewind.c b/src/bin/pg_rewind/pg_rewind.c
index c9b9e480c0f..210984d302b 100644
--- a/src/bin/pg_rewind/pg_rewind.c
+++ b/src/bin/pg_rewind/pg_rewind.c
@@ -369,7 +369,7 @@ main(int argc, char **argv)
 				chkpttli);
 
 	/*
-	 * Build the filemap, by comparing the source and target data directories.
+	 * Collect information about all files in the target and source systems.
 	 */
 	filemap_create();
 	if (showprogress)
@@ -390,8 +390,12 @@ main(int argc, char **argv)
 		pg_log_info("reading WAL in target");
 	extractPageMap(datadir_target, chkptrec, lastcommontliIndex,
 				   ControlFile_target.checkPoint, restore_command);
-	filemap_finalize();
 
+	/*
+	 * We have collected all information we need from both systems. Decide
+	 * what to do with each file.
+	 */
+	filemap_finalize();
 	if (showprogress)
 		calculate_totals();
 
-- 
2.20.1


--------------BF34D0120055DC3839060F92
Content-Type: text/x-patch; charset=UTF-8;
 name="v2-0003-pg_rewind-Replace-the-hybrid-list-array-data-stru.patch"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
 filename*0="v2-0003-pg_rewind-Replace-the-hybrid-list-array-data-stru.pa";
 filename*1="tch"



^ permalink  raw  reply  [nested|flat] 41+ messages in thread

* [PATCH 2/5] Refactor pg_rewind for more clear decision making.
@ 2020-08-19 12:34 Heikki Linnakangas <[email protected]>
  0 siblings, 0 replies; 41+ messages in thread

From: Heikki Linnakangas @ 2020-08-19 12:34 UTC (permalink / raw)

Deciding what to do with each file is now a separate step after all the
necessary information has been gathered. It is more clear that way.
Previously, the decision-making was divided between process_source_file()
and process_target_file(), and it was a bit hard to piece together what the
overall rules were.
---
 src/bin/pg_rewind/copy_fetch.c  |  14 +-
 src/bin/pg_rewind/file_ops.c    |  16 +-
 src/bin/pg_rewind/filemap.c     | 514 ++++++++++++++++----------------
 src/bin/pg_rewind/filemap.h     |  67 +++--
 src/bin/pg_rewind/libpq_fetch.c |  12 +-
 src/bin/pg_rewind/parsexlog.c   |   2 +-
 src/bin/pg_rewind/pg_rewind.c   |   8 +-
 7 files changed, 332 insertions(+), 301 deletions(-)

diff --git a/src/bin/pg_rewind/copy_fetch.c b/src/bin/pg_rewind/copy_fetch.c
index 1edab5f1867..18fad32600e 100644
--- a/src/bin/pg_rewind/copy_fetch.c
+++ b/src/bin/pg_rewind/copy_fetch.c
@@ -210,7 +210,7 @@ copy_executeFileMap(filemap_t *map)
 	for (i = 0; i < map->narray; i++)
 	{
 		entry = map->array[i];
-		execute_pagemap(&entry->pagemap, entry->path);
+		execute_pagemap(&entry->target_modified_pages, entry->path);
 
 		switch (entry->action)
 		{
@@ -219,16 +219,16 @@ copy_executeFileMap(filemap_t *map)
 				break;
 
 			case FILE_ACTION_COPY:
-				rewind_copy_file_range(entry->path, 0, entry->newsize, true);
+				rewind_copy_file_range(entry->path, 0, entry->source_size, true);
 				break;
 
 			case FILE_ACTION_TRUNCATE:
-				truncate_target_file(entry->path, entry->newsize);
+				truncate_target_file(entry->path, entry->source_size);
 				break;
 
 			case FILE_ACTION_COPY_TAIL:
-				rewind_copy_file_range(entry->path, entry->oldsize,
-									   entry->newsize, false);
+				rewind_copy_file_range(entry->path, entry->target_size,
+									   entry->source_size, false);
 				break;
 
 			case FILE_ACTION_CREATE:
@@ -238,6 +238,10 @@ copy_executeFileMap(filemap_t *map)
 			case FILE_ACTION_REMOVE:
 				remove_target(entry);
 				break;
+
+			case FILE_ACTION_UNDECIDED:
+				pg_fatal("no action decided for \"%s\"", entry->path);
+				break;
 		}
 	}
 
diff --git a/src/bin/pg_rewind/file_ops.c b/src/bin/pg_rewind/file_ops.c
index 55439db20ba..ec37d0b2e0d 100644
--- a/src/bin/pg_rewind/file_ops.c
+++ b/src/bin/pg_rewind/file_ops.c
@@ -126,8 +126,9 @@ void
 remove_target(file_entry_t *entry)
 {
 	Assert(entry->action == FILE_ACTION_REMOVE);
+	Assert(entry->target_exists);
 
-	switch (entry->type)
+	switch (entry->target_type)
 	{
 		case FILE_TYPE_DIRECTORY:
 			remove_target_dir(entry->path);
@@ -140,6 +141,10 @@ remove_target(file_entry_t *entry)
 		case FILE_TYPE_SYMLINK:
 			remove_target_symlink(entry->path);
 			break;
+
+		case FILE_TYPE_UNDEFINED:
+			pg_fatal("undefined file type for \"%s\"", entry->path);
+			break;
 	}
 }
 
@@ -147,21 +152,26 @@ void
 create_target(file_entry_t *entry)
 {
 	Assert(entry->action == FILE_ACTION_CREATE);
+	Assert(!entry->target_exists);
 
-	switch (entry->type)
+	switch (entry->source_type)
 	{
 		case FILE_TYPE_DIRECTORY:
 			create_target_dir(entry->path);
 			break;
 
 		case FILE_TYPE_SYMLINK:
-			create_target_symlink(entry->path, entry->link_target);
+			create_target_symlink(entry->path, entry->source_link_target);
 			break;
 
 		case FILE_TYPE_REGULAR:
 			/* can't happen. Regular files are created with open_target_file. */
 			pg_fatal("invalid action (CREATE) for regular file");
 			break;
+
+		case FILE_TYPE_UNDEFINED:
+			pg_fatal("undefined file type for \"%s\"", entry->path);
+			break;
 	}
 }
 
diff --git a/src/bin/pg_rewind/filemap.c b/src/bin/pg_rewind/filemap.c
index 1879257b66a..431e8e760e0 100644
--- a/src/bin/pg_rewind/filemap.c
+++ b/src/bin/pg_rewind/filemap.c
@@ -26,6 +26,8 @@ static bool isRelDataFile(const char *path);
 static char *datasegpath(RelFileNode rnode, ForkNumber forknum,
 						 BlockNumber segno);
 static int	path_cmp(const void *a, const void *b);
+
+static file_entry_t *get_filemap_entry(const char *path, bool create);
 static int	final_filemap_cmp(const void *a, const void *b);
 static void filemap_list_to_array(filemap_t *map);
 static bool check_file_excluded(const char *path, bool is_source);
@@ -146,6 +148,65 @@ filemap_create(void)
 	filemap = map;
 }
 
+/* Look up or create entry for 'path' */
+static file_entry_t *
+get_filemap_entry(const char *path, bool create)
+{
+	filemap_t  *map = filemap;
+	file_entry_t *entry;
+	file_entry_t **e;
+	file_entry_t key;
+	file_entry_t *key_ptr;
+
+	if (map->array)
+	{
+		key.path = (char *) path;
+		key_ptr = &key;
+		e = bsearch(&key_ptr, map->array, map->narray, sizeof(file_entry_t *),
+					path_cmp);
+	}
+	else
+		e = NULL;
+
+	if (e)
+		entry = *e;
+	else if (!create)
+		entry = NULL;
+	else
+	{
+		/* Create a new entry for this file */
+		entry = pg_malloc(sizeof(file_entry_t));
+		entry->path = pg_strdup(path);
+		entry->isrelfile = isRelDataFile(path);
+		entry->action = FILE_ACTION_UNDECIDED;
+
+		entry->target_exists = false;
+		entry->target_type = FILE_TYPE_UNDEFINED;
+		entry->target_size = 0;
+		entry->target_link_target = NULL;
+		entry->target_modified_pages.bitmap = NULL;
+		entry->target_modified_pages.bitmapsize = 0;
+
+		entry->source_exists = false;
+		entry->source_type = FILE_TYPE_UNDEFINED;
+		entry->source_size = 0;
+		entry->source_link_target = NULL;
+
+		entry->next = NULL;
+
+		if (map->last)
+		{
+			map->last->next = entry;
+			map->last = entry;
+		}
+		else
+			map->first = map->last = entry;
+		map->nlist++;
+	}
+
+	return entry;
+}
+
 /*
  * Callback for processing source file list.
  *
@@ -154,18 +215,19 @@ filemap_create(void)
  * exists in the target and whether the size matches.
  */
 void
-process_source_file(const char *path, file_type_t type, size_t newsize,
+process_source_file(const char *path, file_type_t type, size_t size,
 					const char *link_target)
 {
-	bool		exists;
-	char		localpath[MAXPGPATH];
-	struct stat statbuf;
-	filemap_t  *map = filemap;
-	file_action_t action = FILE_ACTION_NONE;
-	size_t		oldsize = 0;
 	file_entry_t *entry;
 
-	Assert(map->array == NULL);
+	Assert(filemap->array == NULL);
+
+	/*
+	 * Skip the control file. It is handled specially, after copying all the
+	 * other files.
+	 */
+	if (strcmp(path, "global/pg_control") == 0)
+		return;
 
 	/*
 	 * Skip any files matching the exclusion filters. This has the effect to
@@ -199,142 +261,12 @@ process_source_file(const char *path, file_type_t type, size_t newsize,
 	if (type != FILE_TYPE_REGULAR && isRelDataFile(path))
 		pg_fatal("data file \"%s\" in source is not a regular file", path);
 
-	snprintf(localpath, sizeof(localpath), "%s/%s", datadir_target, path);
-
-	/* Does the corresponding file exist in the target data dir? */
-	if (lstat(localpath, &statbuf) < 0)
-	{
-		if (errno != ENOENT)
-			pg_fatal("could not stat file \"%s\": %m",
-					 localpath);
-
-		exists = false;
-	}
-	else
-		exists = true;
-
-	switch (type)
-	{
-		case FILE_TYPE_DIRECTORY:
-			if (exists && !S_ISDIR(statbuf.st_mode) && strcmp(path, "pg_wal") != 0)
-			{
-				/* it's a directory in source, but not in target. Strange.. */
-				pg_fatal("\"%s\" is not a directory", localpath);
-			}
-
-			if (!exists)
-				action = FILE_ACTION_CREATE;
-			else
-				action = FILE_ACTION_NONE;
-			oldsize = 0;
-			break;
-
-		case FILE_TYPE_SYMLINK:
-			if (exists &&
-#ifndef WIN32
-				!S_ISLNK(statbuf.st_mode)
-#else
-				!pgwin32_is_junction(localpath)
-#endif
-				)
-			{
-				/*
-				 * It's a symbolic link in source, but not in target.
-				 * Strange..
-				 */
-				pg_fatal("\"%s\" is not a symbolic link", localpath);
-			}
-
-			if (!exists)
-				action = FILE_ACTION_CREATE;
-			else
-				action = FILE_ACTION_NONE;
-			oldsize = 0;
-			break;
-
-		case FILE_TYPE_REGULAR:
-			if (exists && !S_ISREG(statbuf.st_mode))
-				pg_fatal("\"%s\" is not a regular file", localpath);
-
-			if (!exists || !isRelDataFile(path))
-			{
-				/*
-				 * File exists in source, but not in target. Or it's a
-				 * non-data file that we have no special processing for. Copy
-				 * it in toto.
-				 *
-				 * An exception: PG_VERSIONs should be identical, but avoid
-				 * overwriting it for paranoia.
-				 */
-				if (pg_str_endswith(path, "PG_VERSION"))
-				{
-					action = FILE_ACTION_NONE;
-					oldsize = statbuf.st_size;
-				}
-				else
-				{
-					action = FILE_ACTION_COPY;
-					oldsize = 0;
-				}
-			}
-			else
-			{
-				/*
-				 * It's a data file that exists in both.
-				 *
-				 * If it's larger in target, we can truncate it. There will
-				 * also be a WAL record of the truncation in the source
-				 * system, so WAL replay would eventually truncate the target
-				 * too, but we might as well do it now.
-				 *
-				 * If it's smaller in the target, it means that it has been
-				 * truncated in the target, or enlarged in the source, or
-				 * both. If it was truncated in the target, we need to copy
-				 * the missing tail from the source system. If it was enlarged
-				 * in the source system, there will be WAL records in the
-				 * source system for the new blocks, so we wouldn't need to
-				 * copy them here. But we don't know which scenario we're
-				 * dealing with, and there's no harm in copying the missing
-				 * blocks now, so do it now.
-				 *
-				 * If it's the same size, do nothing here. Any blocks modified
-				 * in the target will be copied based on parsing the target
-				 * system's WAL, and any blocks modified in the source will be
-				 * updated after rewinding, when the source system's WAL is
-				 * replayed.
-				 */
-				oldsize = statbuf.st_size;
-				if (oldsize < newsize)
-					action = FILE_ACTION_COPY_TAIL;
-				else if (oldsize > newsize)
-					action = FILE_ACTION_TRUNCATE;
-				else
-					action = FILE_ACTION_NONE;
-			}
-			break;
-	}
-
-	/* Create a new entry for this file */
-	entry = pg_malloc(sizeof(file_entry_t));
-	entry->path = pg_strdup(path);
-	entry->type = type;
-	entry->action = action;
-	entry->oldsize = oldsize;
-	entry->newsize = newsize;
-	entry->link_target = link_target ? pg_strdup(link_target) : NULL;
-	entry->next = NULL;
-	entry->pagemap.bitmap = NULL;
-	entry->pagemap.bitmapsize = 0;
-	entry->isrelfile = isRelDataFile(path);
-
-	if (map->last)
-	{
-		map->last->next = entry;
-		map->last = entry;
-	}
-	else
-		map->first = map->last = entry;
-	map->nlist++;
+	/* Remember this source file */
+	entry = get_filemap_entry(path, true);
+	entry->source_exists = true;
+	entry->source_type = type;
+	entry->source_size = size;
+	entry->source_link_target = link_target ? pg_strdup(link_target) : NULL;
 }
 
 /*
@@ -345,33 +277,24 @@ process_source_file(const char *path, file_type_t type, size_t newsize,
  * deletion.
  */
 void
-process_target_file(const char *path, file_type_t type, size_t oldsize,
+process_target_file(const char *path, file_type_t type, size_t size,
 					const char *link_target)
 {
-	bool		exists;
-	char		localpath[MAXPGPATH];
-	struct stat statbuf;
-	file_entry_t key;
-	file_entry_t *key_ptr;
 	filemap_t  *map = filemap;
 	file_entry_t *entry;
 
+	/*
+	 * Skip the control file. It is handled specially, after copying all the
+	 * other files.
+	 */
+	if (strcmp(path, "global/pg_control") == 0)
+		return;
+
 	/*
 	 * Do not apply any exclusion filters here.  This has advantage to remove
 	 * from the target data folder all paths which have been filtered out from
 	 * the source data folder when processing the source files.
 	 */
-
-	snprintf(localpath, sizeof(localpath), "%s/%s", datadir_target, path);
-	if (lstat(localpath, &statbuf) < 0)
-	{
-		if (errno != ENOENT)
-			pg_fatal("could not stat file \"%s\": %m",
-					 localpath);
-
-		exists = false;
-	}
-
 	if (map->array == NULL)
 	{
 		/* on first call, initialize lookup array */
@@ -389,120 +312,76 @@ process_target_file(const char *path, file_type_t type, size_t oldsize,
 	}
 
 	/*
-	 * Like in process_source_file, pretend that xlog is always a  directory.
+	 * Like in process_source_file, pretend that pg_wal is always a directory.
 	 */
 	if (strcmp(path, "pg_wal") == 0 && type == FILE_TYPE_SYMLINK)
 		type = FILE_TYPE_DIRECTORY;
 
-	key.path = (char *) path;
-	key_ptr = &key;
-	exists = (bsearch(&key_ptr, map->array, map->narray, sizeof(file_entry_t *),
-					  path_cmp) != NULL);
-
-	/* Remove any file or folder that doesn't exist in the source system. */
-	if (!exists)
-	{
-		entry = pg_malloc(sizeof(file_entry_t));
-		entry->path = pg_strdup(path);
-		entry->type = type;
-		entry->action = FILE_ACTION_REMOVE;
-		entry->oldsize = oldsize;
-		entry->newsize = 0;
-		entry->link_target = link_target ? pg_strdup(link_target) : NULL;
-		entry->next = NULL;
-		entry->pagemap.bitmap = NULL;
-		entry->pagemap.bitmapsize = 0;
-		entry->isrelfile = isRelDataFile(path);
-
-		if (map->last == NULL)
-			map->first = entry;
-		else
-			map->last->next = entry;
-		map->last = entry;
-		map->nlist++;
-	}
-	else
-	{
-		/*
-		 * We already handled all files that exist in the source system in
-		 * process_source_file().
-		 */
-	}
+	/* Remember this target file */
+	entry = get_filemap_entry(path, true);
+	entry->target_exists = true;
+	entry->target_type = type;
+	entry->target_size = size;
+	entry->target_link_target = link_target ? pg_strdup(link_target) : NULL;
 }
 
 /*
  * This callback gets called while we read the WAL in the target, for every
- * block that have changed in the target system. It makes note of all the
+ * block that have changed in the target system.  It makes note of all the
  * changed blocks in the pagemap of the file.
+ *
+ * NOTE: All the files on both systems must have already been added to the
+ * file map!
  */
 void
-process_block_change(ForkNumber forknum, RelFileNode rnode, BlockNumber blkno)
+process_target_wal_block_change(ForkNumber forknum, RelFileNode rnode,
+								BlockNumber blkno)
 {
 	char	   *path;
-	file_entry_t key;
-	file_entry_t *key_ptr;
 	file_entry_t *entry;
 	BlockNumber blkno_inseg;
 	int			segno;
-	filemap_t  *map = filemap;
-	file_entry_t **e;
 
-	Assert(map->array);
+	Assert(filemap->array);
 
 	segno = blkno / RELSEG_SIZE;
 	blkno_inseg = blkno % RELSEG_SIZE;
 
 	path = datasegpath(rnode, forknum, segno);
-
-	key.path = (char *) path;
-	key_ptr = &key;
-
-	e = bsearch(&key_ptr, map->array, map->narray, sizeof(file_entry_t *),
-				path_cmp);
-	if (e)
-		entry = *e;
-	else
-		entry = NULL;
+	entry = get_filemap_entry(path, false);
 	pfree(path);
 
 	if (entry)
 	{
+		int64		end_offset;
+
 		Assert(entry->isrelfile);
 
-		switch (entry->action)
-		{
-			case FILE_ACTION_NONE:
-			case FILE_ACTION_TRUNCATE:
-				/* skip if we're truncating away the modified block anyway */
-				if ((blkno_inseg + 1) * BLCKSZ <= entry->newsize)
-					datapagemap_add(&entry->pagemap, blkno_inseg);
-				break;
-
-			case FILE_ACTION_COPY_TAIL:
-
-				/*
-				 * skip the modified block if it is part of the "tail" that
-				 * we're copying anyway.
-				 */
-				if ((blkno_inseg + 1) * BLCKSZ <= entry->oldsize)
-					datapagemap_add(&entry->pagemap, blkno_inseg);
-				break;
-
-			case FILE_ACTION_COPY:
-			case FILE_ACTION_REMOVE:
-				break;
-
-			case FILE_ACTION_CREATE:
-				pg_fatal("unexpected page modification for directory or symbolic link \"%s\"", entry->path);
-		}
+		if (entry->target_type != FILE_TYPE_REGULAR)
+			pg_fatal("unexpected page modification for directory or symbolic link \"%s\"",
+					 entry->path);
+
+		/*
+		 * If the block beyond the EOF in the source system, no need to
+		 * remember it now, because we're going to truncate it away from the
+		 * target anyway. Also no need to remember the block if it's beyond
+		 * the current EOF in the target system; we will copy it over with the
+		 * "tail" from the source system, anyway.
+		 */
+		end_offset = (blkno_inseg + 1) * BLCKSZ;
+		if (end_offset <= entry->source_size &&
+			end_offset <= entry->target_size)
+			datapagemap_add(&entry->target_modified_pages, blkno_inseg);
 	}
 	else
 	{
 		/*
 		 * If we don't have any record of this file in the file map, it means
-		 * that it's a relation that doesn't exist in the source system, and
-		 * it was subsequently removed in the target system, too. We can
-		 * safely ignore it.
+		 * that it's a relation that doesn't exist in the source system.  It
+		 * could exist in the target system; we haven't moved the target-only
+		 * entries from the linked list to the array yet!  But in any case, if
+		 * it doesn't exist in the source it will be removed from the target
+		 * too, and we can safely ignore it.
 		 */
 	}
 }
@@ -593,16 +472,6 @@ filemap_list_to_array(filemap_t *map)
 	map->first = map->last = NULL;
 }
 
-void
-filemap_finalize(void)
-{
-	filemap_t  *map = filemap;
-
-	filemap_list_to_array(map);
-	qsort(map->array, map->narray, sizeof(file_entry_t *),
-		  final_filemap_cmp);
-}
-
 static const char *
 action_to_str(file_action_t action)
 {
@@ -643,26 +512,26 @@ calculate_totals(void)
 	{
 		entry = map->array[i];
 
-		if (entry->type != FILE_TYPE_REGULAR)
+		if (entry->source_type != FILE_TYPE_REGULAR)
 			continue;
 
-		map->total_size += entry->newsize;
+		map->total_size += entry->source_size;
 
 		if (entry->action == FILE_ACTION_COPY)
 		{
-			map->fetch_size += entry->newsize;
+			map->fetch_size += entry->source_size;
 			continue;
 		}
 
 		if (entry->action == FILE_ACTION_COPY_TAIL)
-			map->fetch_size += (entry->newsize - entry->oldsize);
+			map->fetch_size += (entry->source_size - entry->target_size);
 
-		if (entry->pagemap.bitmapsize > 0)
+		if (entry->target_modified_pages.bitmapsize > 0)
 		{
 			datapagemap_iterator_t *iter;
 			BlockNumber blk;
 
-			iter = datapagemap_iterate(&entry->pagemap);
+			iter = datapagemap_iterate(&entry->target_modified_pages);
 			while (datapagemap_next(iter, &blk))
 				map->fetch_size += BLCKSZ;
 
@@ -682,13 +551,13 @@ print_filemap(void)
 	{
 		entry = map->array[i];
 		if (entry->action != FILE_ACTION_NONE ||
-			entry->pagemap.bitmapsize > 0)
+			entry->target_modified_pages.bitmapsize > 0)
 		{
 			pg_log_debug("%s (%s)", entry->path,
 						 action_to_str(entry->action));
 
-			if (entry->pagemap.bitmapsize > 0)
-				datapagemap_print(&entry->pagemap);
+			if (entry->target_modified_pages.bitmapsize > 0)
+				datapagemap_print(&entry->target_modified_pages);
 		}
 	}
 	fflush(stdout);
@@ -837,3 +706,126 @@ final_filemap_cmp(const void *a, const void *b)
 	else
 		return strcmp(fa->path, fb->path);
 }
+
+/*
+ * Decide what to do with each file.
+ */
+void
+filemap_finalize()
+{
+	int			i;
+
+	filemap_list_to_array(filemap);
+
+	for (i = 0; i < filemap->narray; i++)
+	{
+		file_entry_t *entry = filemap->array[i];
+		file_action_t action;
+
+		if (!entry->target_exists && entry->source_exists)
+		{
+			/*
+			 * File exists in source, but not in target. Copy
+			 * it in toto. (If it's a relation data file, WAL replay
+			 * after rewinding should re-create it anyway. But there's
+			 * no harm in copying it now.)
+			 */
+			if (entry->source_type == FILE_TYPE_DIRECTORY ||
+				entry->source_type == FILE_TYPE_SYMLINK)
+			{
+				action = FILE_ACTION_CREATE;
+			}
+			else
+				action = FILE_ACTION_COPY;
+		}
+		else if (entry->target_exists && !entry->source_exists)
+		{
+			/* Delete file from target */
+			action = FILE_ACTION_REMOVE;
+		}
+		else if (entry->target_exists && entry->source_exists)
+		{
+			/* File exists in both systems */
+			if (entry->source_type != entry->target_type)
+			{
+				/* But it's a different kind of object. Strange.. */
+				pg_fatal("file \"%s\" is of different type in source and target", entry->path);
+			}
+
+			switch (entry->source_type)
+			{
+				case FILE_TYPE_DIRECTORY:
+					action = FILE_ACTION_NONE;
+					break;
+
+				case FILE_TYPE_SYMLINK:
+					// FIXME: Check if it points to the same target?
+					action = FILE_ACTION_NONE;
+					break;
+
+				case FILE_TYPE_REGULAR:
+					if (!entry->isrelfile)
+					{
+						/*
+						 * It's a non-data file that we have no special processing for. Copy
+						 * it in toto.
+						 *
+						 * An exception: PG_VERSIONs should be identical, but avoid
+						 * overwriting it for paranoia.
+						 */
+						if (!pg_str_endswith(entry->path, "PG_VERSION"))
+							action = FILE_ACTION_COPY;
+						else
+							action = FILE_ACTION_NONE;
+					}
+					else
+					{
+						/*
+						 * It's a data file that exists in both systems.
+						 *
+						 * If it's larger in target, we can truncate it. There will
+						 * also be a WAL record of the truncation in the source
+						 * system, so WAL replay would eventually truncate the target
+						 * too, but we might as well do it now.
+						 *
+						 * If it's smaller in the target, it means that it has been
+						 * truncated in the target, or enlarged in the source, or
+						 * both. If it was truncated in the target, we need to copy
+						 * the missing tail from the source system. If it was enlarged
+						 * in the source system, there will be WAL records in the
+						 * source system for the new blocks, so we wouldn't need to
+						 * copy them here. But we don't know which scenario we're
+						 * dealing with, and there's no harm in copying the missing
+						 * blocks now, so do it now.
+						 *
+						 * If it's the same size, do nothing here. Any blocks modified
+						 * in the target will be copied based on parsing the target
+						 * system's WAL, and any blocks modified in the source will be
+						 * updated after rewinding, when the source system's WAL is
+						 * replayed.
+						 */
+						if (entry->target_size < entry->source_size)
+							action = FILE_ACTION_COPY_TAIL;
+						else if (entry->target_size > entry->source_size)
+							action = FILE_ACTION_TRUNCATE;
+						else
+							action = FILE_ACTION_NONE;
+					}
+					break;
+				case FILE_TYPE_UNDEFINED:
+					pg_fatal("unknown file type for \"%s\"", entry->path);
+					break;
+			}
+		}
+		else
+		{
+			/* Doesn't exist in either server. Why does it have an entry at all?? */
+			action = FILE_ACTION_NONE;
+		}
+		entry->action = action;
+	}
+
+	/* Sort the actions to the order that they should be performed */
+	qsort(filemap->array, filemap->narray, sizeof(file_entry_t *),
+		  final_filemap_cmp);
+}
diff --git a/src/bin/pg_rewind/filemap.h b/src/bin/pg_rewind/filemap.h
index 0cb7425170c..a5e8df57f40 100644
--- a/src/bin/pg_rewind/filemap.h
+++ b/src/bin/pg_rewind/filemap.h
@@ -14,17 +14,21 @@
 
 /*
  * For every file found in the local or remote system, we have a file entry
- * which says what we are going to do with the file. For relation files,
- * there is also a page map, marking pages in the file that were changed
- * locally.
- *
- * The enum values are sorted in the order we want actions to be processed.
+ * that contains information about the file on both systems.  For relation
+ * files, there is also a page map that marks pages in the file that were
+ * changed in the target after the last common checkpoint.  Each entry also
+ * contains an 'action' field, which says what we are going to do with the
+ * file.
  */
+
+/* these enum values are sorted in the order we want actions to be processed */
 typedef enum
 {
+	FILE_ACTION_UNDECIDED = 0,	/* not decided yet */
+
 	FILE_ACTION_CREATE,			/* create local directory or symbolic link */
 	FILE_ACTION_COPY,			/* copy whole file, overwriting if exists */
-	FILE_ACTION_COPY_TAIL,		/* copy tail from 'oldsize' to 'newsize' */
+	FILE_ACTION_COPY_TAIL,		/* copy tail from 'source_size' to 'target_size' */
 	FILE_ACTION_NONE,			/* no action (we might still copy modified
 								 * blocks based on the parsed WAL) */
 	FILE_ACTION_TRUNCATE,		/* truncate local file to 'newsize' bytes */
@@ -33,6 +37,8 @@ typedef enum
 
 typedef enum
 {
+	FILE_TYPE_UNDEFINED = 0,
+
 	FILE_TYPE_REGULAR,
 	FILE_TYPE_DIRECTORY,
 	FILE_TYPE_SYMLINK
@@ -41,19 +47,30 @@ typedef enum
 typedef struct file_entry_t
 {
 	char	   *path;
-	file_type_t type;
+	bool		isrelfile;		/* is it a relation data file? */
 
-	file_action_t action;
+	/*
+	 * Status of the file in the target.
+	 */
+	bool		target_exists;
+	file_type_t target_type;
+	size_t		target_size; /* for a regular file */
+	char	   *target_link_target; /* for a symlink */
 
-	/* for a regular file */
-	size_t		oldsize;
-	size_t		newsize;
-	bool		isrelfile;		/* is it a relation data file? */
+	datapagemap_t target_modified_pages;
 
-	datapagemap_t pagemap;
+	/*
+	 * Status of the file in the source.
+	 */
+	bool		source_exists;
+	file_type_t source_type;
+	size_t		source_size;
+	char	   *source_link_target; /* for a symlink */
 
-	/* for a symlink */
-	char	   *link_target;
+	/*
+	 * What will we do to the file?
+	 */
+	file_action_t action;
 
 	struct file_entry_t *next;
 } file_entry_t;
@@ -70,20 +87,19 @@ typedef struct filemap_t
 
 	/*
 	 * After processing all the remote files, the entries in the linked list
-	 * are moved to this array. After processing local files, too, all the
+	 * are moved to this array.  After processing local files, too, all the
 	 * local entries are added to the array by filemap_finalize, and sorted in
-	 * the final order. After filemap_finalize, all the entries are in the
+	 * the final order.  After filemap_finalize, all the entries are in the
 	 * array, and the linked list is empty.
 	 */
 	file_entry_t **array;
 	int			narray;			/* current length of array */
 
 	/*
-	 * Summary information. total_size is the total size of the source
-	 * cluster, and fetch_size is the number of bytes that needs to be copied.
+	 * Summary information.
 	 */
-	uint64		total_size;
-	uint64		fetch_size;
+	uint64		total_size;		/* total size of the source cluster */
+	uint64		fetch_size;		/* number of bytes that needs to be copied */
 } filemap_t;
 
 extern filemap_t *filemap;
@@ -94,11 +110,12 @@ extern void print_filemap(void);
 
 /* Functions for populating the filemap */
 extern void process_source_file(const char *path, file_type_t type,
-								size_t newsize, const char *link_target);
+								size_t size, const char *link_target);
 extern void process_target_file(const char *path, file_type_t type,
-								size_t newsize, const char *link_target);
-extern void process_block_change(ForkNumber forknum, RelFileNode rnode,
-								 BlockNumber blkno);
+								size_t size, const char *link_target);
+extern void process_target_wal_block_change(ForkNumber forknum,
+											RelFileNode rnode,
+											BlockNumber blkno);
 extern void filemap_finalize(void);
 
 #endif							/* FILEMAP_H */
diff --git a/src/bin/pg_rewind/libpq_fetch.c b/src/bin/pg_rewind/libpq_fetch.c
index bf4dfc23b96..7fc9161b8c8 100644
--- a/src/bin/pg_rewind/libpq_fetch.c
+++ b/src/bin/pg_rewind/libpq_fetch.c
@@ -465,7 +465,7 @@ libpq_executeFileMap(filemap_t *map)
 		entry = map->array[i];
 
 		/* If this is a relation file, copy the modified blocks */
-		execute_pagemap(&entry->pagemap, entry->path);
+		execute_pagemap(&entry->target_modified_pages, entry->path);
 
 		switch (entry->action)
 		{
@@ -476,15 +476,15 @@ libpq_executeFileMap(filemap_t *map)
 			case FILE_ACTION_COPY:
 				/* Truncate the old file out of the way, if any */
 				open_target_file(entry->path, true);
-				fetch_file_range(entry->path, 0, entry->newsize);
+				fetch_file_range(entry->path, 0, entry->source_size);
 				break;
 
 			case FILE_ACTION_TRUNCATE:
-				truncate_target_file(entry->path, entry->newsize);
+				truncate_target_file(entry->path, entry->source_size);
 				break;
 
 			case FILE_ACTION_COPY_TAIL:
-				fetch_file_range(entry->path, entry->oldsize, entry->newsize);
+				fetch_file_range(entry->path, entry->target_size, entry->source_size);
 				break;
 
 			case FILE_ACTION_REMOVE:
@@ -494,6 +494,10 @@ libpq_executeFileMap(filemap_t *map)
 			case FILE_ACTION_CREATE:
 				create_target(entry);
 				break;
+
+			case FILE_ACTION_UNDECIDED:
+				pg_fatal("no action decided for \"%s\"", entry->path);
+				break;
 		}
 	}
 
diff --git a/src/bin/pg_rewind/parsexlog.c b/src/bin/pg_rewind/parsexlog.c
index 2229c86f9af..2baeb74ae93 100644
--- a/src/bin/pg_rewind/parsexlog.c
+++ b/src/bin/pg_rewind/parsexlog.c
@@ -436,6 +436,6 @@ extractPageInfo(XLogReaderState *record)
 		if (forknum != MAIN_FORKNUM)
 			continue;
 
-		process_block_change(forknum, rnode, blkno);
+		process_target_wal_block_change(forknum, rnode, blkno);
 	}
 }
diff --git a/src/bin/pg_rewind/pg_rewind.c b/src/bin/pg_rewind/pg_rewind.c
index c9b9e480c0f..210984d302b 100644
--- a/src/bin/pg_rewind/pg_rewind.c
+++ b/src/bin/pg_rewind/pg_rewind.c
@@ -369,7 +369,7 @@ main(int argc, char **argv)
 				chkpttli);
 
 	/*
-	 * Build the filemap, by comparing the source and target data directories.
+	 * Collect information about all files in the target and source systems.
 	 */
 	filemap_create();
 	if (showprogress)
@@ -390,8 +390,12 @@ main(int argc, char **argv)
 		pg_log_info("reading WAL in target");
 	extractPageMap(datadir_target, chkptrec, lastcommontliIndex,
 				   ControlFile_target.checkPoint, restore_command);
-	filemap_finalize();
 
+	/*
+	 * We have collected all information we need from both systems. Decide
+	 * what to do with each file.
+	 */
+	filemap_finalize();
 	if (showprogress)
 		calculate_totals();
 
-- 
2.20.1


--------------D93EDEBFB124D563B723F4BD
Content-Type: text/x-patch; charset=UTF-8;
 name="0003-pg_rewind-Replace-the-hybrid-list-array-data-structu.patch"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
 filename*0="0003-pg_rewind-Replace-the-hybrid-list-array-data-structu.pa";
 filename*1="tch"



^ permalink  raw  reply  [nested|flat] 41+ messages in thread

* [PATCH 2/5] Refactor pg_rewind for more clear decision making.
@ 2020-08-19 12:34 Heikki Linnakangas <[email protected]>
  0 siblings, 0 replies; 41+ messages in thread

From: Heikki Linnakangas @ 2020-08-19 12:34 UTC (permalink / raw)

Deciding what to do with each file is now a separate step after all the
necessary information has been gathered. It is more clear that way.
Previously, the decision-making was divided between process_source_file()
and process_target_file(), and it was a bit hard to piece together what the
overall rules were.
---
 src/bin/pg_rewind/copy_fetch.c  |  14 +-
 src/bin/pg_rewind/file_ops.c    |  16 +-
 src/bin/pg_rewind/filemap.c     | 514 ++++++++++++++++----------------
 src/bin/pg_rewind/filemap.h     |  67 +++--
 src/bin/pg_rewind/libpq_fetch.c |  12 +-
 src/bin/pg_rewind/parsexlog.c   |   2 +-
 src/bin/pg_rewind/pg_rewind.c   |   8 +-
 7 files changed, 332 insertions(+), 301 deletions(-)

diff --git a/src/bin/pg_rewind/copy_fetch.c b/src/bin/pg_rewind/copy_fetch.c
index 1edab5f1867..18fad32600e 100644
--- a/src/bin/pg_rewind/copy_fetch.c
+++ b/src/bin/pg_rewind/copy_fetch.c
@@ -210,7 +210,7 @@ copy_executeFileMap(filemap_t *map)
 	for (i = 0; i < map->narray; i++)
 	{
 		entry = map->array[i];
-		execute_pagemap(&entry->pagemap, entry->path);
+		execute_pagemap(&entry->target_modified_pages, entry->path);
 
 		switch (entry->action)
 		{
@@ -219,16 +219,16 @@ copy_executeFileMap(filemap_t *map)
 				break;
 
 			case FILE_ACTION_COPY:
-				rewind_copy_file_range(entry->path, 0, entry->newsize, true);
+				rewind_copy_file_range(entry->path, 0, entry->source_size, true);
 				break;
 
 			case FILE_ACTION_TRUNCATE:
-				truncate_target_file(entry->path, entry->newsize);
+				truncate_target_file(entry->path, entry->source_size);
 				break;
 
 			case FILE_ACTION_COPY_TAIL:
-				rewind_copy_file_range(entry->path, entry->oldsize,
-									   entry->newsize, false);
+				rewind_copy_file_range(entry->path, entry->target_size,
+									   entry->source_size, false);
 				break;
 
 			case FILE_ACTION_CREATE:
@@ -238,6 +238,10 @@ copy_executeFileMap(filemap_t *map)
 			case FILE_ACTION_REMOVE:
 				remove_target(entry);
 				break;
+
+			case FILE_ACTION_UNDECIDED:
+				pg_fatal("no action decided for \"%s\"", entry->path);
+				break;
 		}
 	}
 
diff --git a/src/bin/pg_rewind/file_ops.c b/src/bin/pg_rewind/file_ops.c
index 55439db20ba..ec37d0b2e0d 100644
--- a/src/bin/pg_rewind/file_ops.c
+++ b/src/bin/pg_rewind/file_ops.c
@@ -126,8 +126,9 @@ void
 remove_target(file_entry_t *entry)
 {
 	Assert(entry->action == FILE_ACTION_REMOVE);
+	Assert(entry->target_exists);
 
-	switch (entry->type)
+	switch (entry->target_type)
 	{
 		case FILE_TYPE_DIRECTORY:
 			remove_target_dir(entry->path);
@@ -140,6 +141,10 @@ remove_target(file_entry_t *entry)
 		case FILE_TYPE_SYMLINK:
 			remove_target_symlink(entry->path);
 			break;
+
+		case FILE_TYPE_UNDEFINED:
+			pg_fatal("undefined file type for \"%s\"", entry->path);
+			break;
 	}
 }
 
@@ -147,21 +152,26 @@ void
 create_target(file_entry_t *entry)
 {
 	Assert(entry->action == FILE_ACTION_CREATE);
+	Assert(!entry->target_exists);
 
-	switch (entry->type)
+	switch (entry->source_type)
 	{
 		case FILE_TYPE_DIRECTORY:
 			create_target_dir(entry->path);
 			break;
 
 		case FILE_TYPE_SYMLINK:
-			create_target_symlink(entry->path, entry->link_target);
+			create_target_symlink(entry->path, entry->source_link_target);
 			break;
 
 		case FILE_TYPE_REGULAR:
 			/* can't happen. Regular files are created with open_target_file. */
 			pg_fatal("invalid action (CREATE) for regular file");
 			break;
+
+		case FILE_TYPE_UNDEFINED:
+			pg_fatal("undefined file type for \"%s\"", entry->path);
+			break;
 	}
 }
 
diff --git a/src/bin/pg_rewind/filemap.c b/src/bin/pg_rewind/filemap.c
index 1879257b66a..431e8e760e0 100644
--- a/src/bin/pg_rewind/filemap.c
+++ b/src/bin/pg_rewind/filemap.c
@@ -26,6 +26,8 @@ static bool isRelDataFile(const char *path);
 static char *datasegpath(RelFileNode rnode, ForkNumber forknum,
 						 BlockNumber segno);
 static int	path_cmp(const void *a, const void *b);
+
+static file_entry_t *get_filemap_entry(const char *path, bool create);
 static int	final_filemap_cmp(const void *a, const void *b);
 static void filemap_list_to_array(filemap_t *map);
 static bool check_file_excluded(const char *path, bool is_source);
@@ -146,6 +148,65 @@ filemap_create(void)
 	filemap = map;
 }
 
+/* Look up or create entry for 'path' */
+static file_entry_t *
+get_filemap_entry(const char *path, bool create)
+{
+	filemap_t  *map = filemap;
+	file_entry_t *entry;
+	file_entry_t **e;
+	file_entry_t key;
+	file_entry_t *key_ptr;
+
+	if (map->array)
+	{
+		key.path = (char *) path;
+		key_ptr = &key;
+		e = bsearch(&key_ptr, map->array, map->narray, sizeof(file_entry_t *),
+					path_cmp);
+	}
+	else
+		e = NULL;
+
+	if (e)
+		entry = *e;
+	else if (!create)
+		entry = NULL;
+	else
+	{
+		/* Create a new entry for this file */
+		entry = pg_malloc(sizeof(file_entry_t));
+		entry->path = pg_strdup(path);
+		entry->isrelfile = isRelDataFile(path);
+		entry->action = FILE_ACTION_UNDECIDED;
+
+		entry->target_exists = false;
+		entry->target_type = FILE_TYPE_UNDEFINED;
+		entry->target_size = 0;
+		entry->target_link_target = NULL;
+		entry->target_modified_pages.bitmap = NULL;
+		entry->target_modified_pages.bitmapsize = 0;
+
+		entry->source_exists = false;
+		entry->source_type = FILE_TYPE_UNDEFINED;
+		entry->source_size = 0;
+		entry->source_link_target = NULL;
+
+		entry->next = NULL;
+
+		if (map->last)
+		{
+			map->last->next = entry;
+			map->last = entry;
+		}
+		else
+			map->first = map->last = entry;
+		map->nlist++;
+	}
+
+	return entry;
+}
+
 /*
  * Callback for processing source file list.
  *
@@ -154,18 +215,19 @@ filemap_create(void)
  * exists in the target and whether the size matches.
  */
 void
-process_source_file(const char *path, file_type_t type, size_t newsize,
+process_source_file(const char *path, file_type_t type, size_t size,
 					const char *link_target)
 {
-	bool		exists;
-	char		localpath[MAXPGPATH];
-	struct stat statbuf;
-	filemap_t  *map = filemap;
-	file_action_t action = FILE_ACTION_NONE;
-	size_t		oldsize = 0;
 	file_entry_t *entry;
 
-	Assert(map->array == NULL);
+	Assert(filemap->array == NULL);
+
+	/*
+	 * Skip the control file. It is handled specially, after copying all the
+	 * other files.
+	 */
+	if (strcmp(path, "global/pg_control") == 0)
+		return;
 
 	/*
 	 * Skip any files matching the exclusion filters. This has the effect to
@@ -199,142 +261,12 @@ process_source_file(const char *path, file_type_t type, size_t newsize,
 	if (type != FILE_TYPE_REGULAR && isRelDataFile(path))
 		pg_fatal("data file \"%s\" in source is not a regular file", path);
 
-	snprintf(localpath, sizeof(localpath), "%s/%s", datadir_target, path);
-
-	/* Does the corresponding file exist in the target data dir? */
-	if (lstat(localpath, &statbuf) < 0)
-	{
-		if (errno != ENOENT)
-			pg_fatal("could not stat file \"%s\": %m",
-					 localpath);
-
-		exists = false;
-	}
-	else
-		exists = true;
-
-	switch (type)
-	{
-		case FILE_TYPE_DIRECTORY:
-			if (exists && !S_ISDIR(statbuf.st_mode) && strcmp(path, "pg_wal") != 0)
-			{
-				/* it's a directory in source, but not in target. Strange.. */
-				pg_fatal("\"%s\" is not a directory", localpath);
-			}
-
-			if (!exists)
-				action = FILE_ACTION_CREATE;
-			else
-				action = FILE_ACTION_NONE;
-			oldsize = 0;
-			break;
-
-		case FILE_TYPE_SYMLINK:
-			if (exists &&
-#ifndef WIN32
-				!S_ISLNK(statbuf.st_mode)
-#else
-				!pgwin32_is_junction(localpath)
-#endif
-				)
-			{
-				/*
-				 * It's a symbolic link in source, but not in target.
-				 * Strange..
-				 */
-				pg_fatal("\"%s\" is not a symbolic link", localpath);
-			}
-
-			if (!exists)
-				action = FILE_ACTION_CREATE;
-			else
-				action = FILE_ACTION_NONE;
-			oldsize = 0;
-			break;
-
-		case FILE_TYPE_REGULAR:
-			if (exists && !S_ISREG(statbuf.st_mode))
-				pg_fatal("\"%s\" is not a regular file", localpath);
-
-			if (!exists || !isRelDataFile(path))
-			{
-				/*
-				 * File exists in source, but not in target. Or it's a
-				 * non-data file that we have no special processing for. Copy
-				 * it in toto.
-				 *
-				 * An exception: PG_VERSIONs should be identical, but avoid
-				 * overwriting it for paranoia.
-				 */
-				if (pg_str_endswith(path, "PG_VERSION"))
-				{
-					action = FILE_ACTION_NONE;
-					oldsize = statbuf.st_size;
-				}
-				else
-				{
-					action = FILE_ACTION_COPY;
-					oldsize = 0;
-				}
-			}
-			else
-			{
-				/*
-				 * It's a data file that exists in both.
-				 *
-				 * If it's larger in target, we can truncate it. There will
-				 * also be a WAL record of the truncation in the source
-				 * system, so WAL replay would eventually truncate the target
-				 * too, but we might as well do it now.
-				 *
-				 * If it's smaller in the target, it means that it has been
-				 * truncated in the target, or enlarged in the source, or
-				 * both. If it was truncated in the target, we need to copy
-				 * the missing tail from the source system. If it was enlarged
-				 * in the source system, there will be WAL records in the
-				 * source system for the new blocks, so we wouldn't need to
-				 * copy them here. But we don't know which scenario we're
-				 * dealing with, and there's no harm in copying the missing
-				 * blocks now, so do it now.
-				 *
-				 * If it's the same size, do nothing here. Any blocks modified
-				 * in the target will be copied based on parsing the target
-				 * system's WAL, and any blocks modified in the source will be
-				 * updated after rewinding, when the source system's WAL is
-				 * replayed.
-				 */
-				oldsize = statbuf.st_size;
-				if (oldsize < newsize)
-					action = FILE_ACTION_COPY_TAIL;
-				else if (oldsize > newsize)
-					action = FILE_ACTION_TRUNCATE;
-				else
-					action = FILE_ACTION_NONE;
-			}
-			break;
-	}
-
-	/* Create a new entry for this file */
-	entry = pg_malloc(sizeof(file_entry_t));
-	entry->path = pg_strdup(path);
-	entry->type = type;
-	entry->action = action;
-	entry->oldsize = oldsize;
-	entry->newsize = newsize;
-	entry->link_target = link_target ? pg_strdup(link_target) : NULL;
-	entry->next = NULL;
-	entry->pagemap.bitmap = NULL;
-	entry->pagemap.bitmapsize = 0;
-	entry->isrelfile = isRelDataFile(path);
-
-	if (map->last)
-	{
-		map->last->next = entry;
-		map->last = entry;
-	}
-	else
-		map->first = map->last = entry;
-	map->nlist++;
+	/* Remember this source file */
+	entry = get_filemap_entry(path, true);
+	entry->source_exists = true;
+	entry->source_type = type;
+	entry->source_size = size;
+	entry->source_link_target = link_target ? pg_strdup(link_target) : NULL;
 }
 
 /*
@@ -345,33 +277,24 @@ process_source_file(const char *path, file_type_t type, size_t newsize,
  * deletion.
  */
 void
-process_target_file(const char *path, file_type_t type, size_t oldsize,
+process_target_file(const char *path, file_type_t type, size_t size,
 					const char *link_target)
 {
-	bool		exists;
-	char		localpath[MAXPGPATH];
-	struct stat statbuf;
-	file_entry_t key;
-	file_entry_t *key_ptr;
 	filemap_t  *map = filemap;
 	file_entry_t *entry;
 
+	/*
+	 * Skip the control file. It is handled specially, after copying all the
+	 * other files.
+	 */
+	if (strcmp(path, "global/pg_control") == 0)
+		return;
+
 	/*
 	 * Do not apply any exclusion filters here.  This has advantage to remove
 	 * from the target data folder all paths which have been filtered out from
 	 * the source data folder when processing the source files.
 	 */
-
-	snprintf(localpath, sizeof(localpath), "%s/%s", datadir_target, path);
-	if (lstat(localpath, &statbuf) < 0)
-	{
-		if (errno != ENOENT)
-			pg_fatal("could not stat file \"%s\": %m",
-					 localpath);
-
-		exists = false;
-	}
-
 	if (map->array == NULL)
 	{
 		/* on first call, initialize lookup array */
@@ -389,120 +312,76 @@ process_target_file(const char *path, file_type_t type, size_t oldsize,
 	}
 
 	/*
-	 * Like in process_source_file, pretend that xlog is always a  directory.
+	 * Like in process_source_file, pretend that pg_wal is always a directory.
 	 */
 	if (strcmp(path, "pg_wal") == 0 && type == FILE_TYPE_SYMLINK)
 		type = FILE_TYPE_DIRECTORY;
 
-	key.path = (char *) path;
-	key_ptr = &key;
-	exists = (bsearch(&key_ptr, map->array, map->narray, sizeof(file_entry_t *),
-					  path_cmp) != NULL);
-
-	/* Remove any file or folder that doesn't exist in the source system. */
-	if (!exists)
-	{
-		entry = pg_malloc(sizeof(file_entry_t));
-		entry->path = pg_strdup(path);
-		entry->type = type;
-		entry->action = FILE_ACTION_REMOVE;
-		entry->oldsize = oldsize;
-		entry->newsize = 0;
-		entry->link_target = link_target ? pg_strdup(link_target) : NULL;
-		entry->next = NULL;
-		entry->pagemap.bitmap = NULL;
-		entry->pagemap.bitmapsize = 0;
-		entry->isrelfile = isRelDataFile(path);
-
-		if (map->last == NULL)
-			map->first = entry;
-		else
-			map->last->next = entry;
-		map->last = entry;
-		map->nlist++;
-	}
-	else
-	{
-		/*
-		 * We already handled all files that exist in the source system in
-		 * process_source_file().
-		 */
-	}
+	/* Remember this target file */
+	entry = get_filemap_entry(path, true);
+	entry->target_exists = true;
+	entry->target_type = type;
+	entry->target_size = size;
+	entry->target_link_target = link_target ? pg_strdup(link_target) : NULL;
 }
 
 /*
  * This callback gets called while we read the WAL in the target, for every
- * block that have changed in the target system. It makes note of all the
+ * block that have changed in the target system.  It makes note of all the
  * changed blocks in the pagemap of the file.
+ *
+ * NOTE: All the files on both systems must have already been added to the
+ * file map!
  */
 void
-process_block_change(ForkNumber forknum, RelFileNode rnode, BlockNumber blkno)
+process_target_wal_block_change(ForkNumber forknum, RelFileNode rnode,
+								BlockNumber blkno)
 {
 	char	   *path;
-	file_entry_t key;
-	file_entry_t *key_ptr;
 	file_entry_t *entry;
 	BlockNumber blkno_inseg;
 	int			segno;
-	filemap_t  *map = filemap;
-	file_entry_t **e;
 
-	Assert(map->array);
+	Assert(filemap->array);
 
 	segno = blkno / RELSEG_SIZE;
 	blkno_inseg = blkno % RELSEG_SIZE;
 
 	path = datasegpath(rnode, forknum, segno);
-
-	key.path = (char *) path;
-	key_ptr = &key;
-
-	e = bsearch(&key_ptr, map->array, map->narray, sizeof(file_entry_t *),
-				path_cmp);
-	if (e)
-		entry = *e;
-	else
-		entry = NULL;
+	entry = get_filemap_entry(path, false);
 	pfree(path);
 
 	if (entry)
 	{
+		int64		end_offset;
+
 		Assert(entry->isrelfile);
 
-		switch (entry->action)
-		{
-			case FILE_ACTION_NONE:
-			case FILE_ACTION_TRUNCATE:
-				/* skip if we're truncating away the modified block anyway */
-				if ((blkno_inseg + 1) * BLCKSZ <= entry->newsize)
-					datapagemap_add(&entry->pagemap, blkno_inseg);
-				break;
-
-			case FILE_ACTION_COPY_TAIL:
-
-				/*
-				 * skip the modified block if it is part of the "tail" that
-				 * we're copying anyway.
-				 */
-				if ((blkno_inseg + 1) * BLCKSZ <= entry->oldsize)
-					datapagemap_add(&entry->pagemap, blkno_inseg);
-				break;
-
-			case FILE_ACTION_COPY:
-			case FILE_ACTION_REMOVE:
-				break;
-
-			case FILE_ACTION_CREATE:
-				pg_fatal("unexpected page modification for directory or symbolic link \"%s\"", entry->path);
-		}
+		if (entry->target_type != FILE_TYPE_REGULAR)
+			pg_fatal("unexpected page modification for directory or symbolic link \"%s\"",
+					 entry->path);
+
+		/*
+		 * If the block beyond the EOF in the source system, no need to
+		 * remember it now, because we're going to truncate it away from the
+		 * target anyway. Also no need to remember the block if it's beyond
+		 * the current EOF in the target system; we will copy it over with the
+		 * "tail" from the source system, anyway.
+		 */
+		end_offset = (blkno_inseg + 1) * BLCKSZ;
+		if (end_offset <= entry->source_size &&
+			end_offset <= entry->target_size)
+			datapagemap_add(&entry->target_modified_pages, blkno_inseg);
 	}
 	else
 	{
 		/*
 		 * If we don't have any record of this file in the file map, it means
-		 * that it's a relation that doesn't exist in the source system, and
-		 * it was subsequently removed in the target system, too. We can
-		 * safely ignore it.
+		 * that it's a relation that doesn't exist in the source system.  It
+		 * could exist in the target system; we haven't moved the target-only
+		 * entries from the linked list to the array yet!  But in any case, if
+		 * it doesn't exist in the source it will be removed from the target
+		 * too, and we can safely ignore it.
 		 */
 	}
 }
@@ -593,16 +472,6 @@ filemap_list_to_array(filemap_t *map)
 	map->first = map->last = NULL;
 }
 
-void
-filemap_finalize(void)
-{
-	filemap_t  *map = filemap;
-
-	filemap_list_to_array(map);
-	qsort(map->array, map->narray, sizeof(file_entry_t *),
-		  final_filemap_cmp);
-}
-
 static const char *
 action_to_str(file_action_t action)
 {
@@ -643,26 +512,26 @@ calculate_totals(void)
 	{
 		entry = map->array[i];
 
-		if (entry->type != FILE_TYPE_REGULAR)
+		if (entry->source_type != FILE_TYPE_REGULAR)
 			continue;
 
-		map->total_size += entry->newsize;
+		map->total_size += entry->source_size;
 
 		if (entry->action == FILE_ACTION_COPY)
 		{
-			map->fetch_size += entry->newsize;
+			map->fetch_size += entry->source_size;
 			continue;
 		}
 
 		if (entry->action == FILE_ACTION_COPY_TAIL)
-			map->fetch_size += (entry->newsize - entry->oldsize);
+			map->fetch_size += (entry->source_size - entry->target_size);
 
-		if (entry->pagemap.bitmapsize > 0)
+		if (entry->target_modified_pages.bitmapsize > 0)
 		{
 			datapagemap_iterator_t *iter;
 			BlockNumber blk;
 
-			iter = datapagemap_iterate(&entry->pagemap);
+			iter = datapagemap_iterate(&entry->target_modified_pages);
 			while (datapagemap_next(iter, &blk))
 				map->fetch_size += BLCKSZ;
 
@@ -682,13 +551,13 @@ print_filemap(void)
 	{
 		entry = map->array[i];
 		if (entry->action != FILE_ACTION_NONE ||
-			entry->pagemap.bitmapsize > 0)
+			entry->target_modified_pages.bitmapsize > 0)
 		{
 			pg_log_debug("%s (%s)", entry->path,
 						 action_to_str(entry->action));
 
-			if (entry->pagemap.bitmapsize > 0)
-				datapagemap_print(&entry->pagemap);
+			if (entry->target_modified_pages.bitmapsize > 0)
+				datapagemap_print(&entry->target_modified_pages);
 		}
 	}
 	fflush(stdout);
@@ -837,3 +706,126 @@ final_filemap_cmp(const void *a, const void *b)
 	else
 		return strcmp(fa->path, fb->path);
 }
+
+/*
+ * Decide what to do with each file.
+ */
+void
+filemap_finalize()
+{
+	int			i;
+
+	filemap_list_to_array(filemap);
+
+	for (i = 0; i < filemap->narray; i++)
+	{
+		file_entry_t *entry = filemap->array[i];
+		file_action_t action;
+
+		if (!entry->target_exists && entry->source_exists)
+		{
+			/*
+			 * File exists in source, but not in target. Copy
+			 * it in toto. (If it's a relation data file, WAL replay
+			 * after rewinding should re-create it anyway. But there's
+			 * no harm in copying it now.)
+			 */
+			if (entry->source_type == FILE_TYPE_DIRECTORY ||
+				entry->source_type == FILE_TYPE_SYMLINK)
+			{
+				action = FILE_ACTION_CREATE;
+			}
+			else
+				action = FILE_ACTION_COPY;
+		}
+		else if (entry->target_exists && !entry->source_exists)
+		{
+			/* Delete file from target */
+			action = FILE_ACTION_REMOVE;
+		}
+		else if (entry->target_exists && entry->source_exists)
+		{
+			/* File exists in both systems */
+			if (entry->source_type != entry->target_type)
+			{
+				/* But it's a different kind of object. Strange.. */
+				pg_fatal("file \"%s\" is of different type in source and target", entry->path);
+			}
+
+			switch (entry->source_type)
+			{
+				case FILE_TYPE_DIRECTORY:
+					action = FILE_ACTION_NONE;
+					break;
+
+				case FILE_TYPE_SYMLINK:
+					// FIXME: Check if it points to the same target?
+					action = FILE_ACTION_NONE;
+					break;
+
+				case FILE_TYPE_REGULAR:
+					if (!entry->isrelfile)
+					{
+						/*
+						 * It's a non-data file that we have no special processing for. Copy
+						 * it in toto.
+						 *
+						 * An exception: PG_VERSIONs should be identical, but avoid
+						 * overwriting it for paranoia.
+						 */
+						if (!pg_str_endswith(entry->path, "PG_VERSION"))
+							action = FILE_ACTION_COPY;
+						else
+							action = FILE_ACTION_NONE;
+					}
+					else
+					{
+						/*
+						 * It's a data file that exists in both systems.
+						 *
+						 * If it's larger in target, we can truncate it. There will
+						 * also be a WAL record of the truncation in the source
+						 * system, so WAL replay would eventually truncate the target
+						 * too, but we might as well do it now.
+						 *
+						 * If it's smaller in the target, it means that it has been
+						 * truncated in the target, or enlarged in the source, or
+						 * both. If it was truncated in the target, we need to copy
+						 * the missing tail from the source system. If it was enlarged
+						 * in the source system, there will be WAL records in the
+						 * source system for the new blocks, so we wouldn't need to
+						 * copy them here. But we don't know which scenario we're
+						 * dealing with, and there's no harm in copying the missing
+						 * blocks now, so do it now.
+						 *
+						 * If it's the same size, do nothing here. Any blocks modified
+						 * in the target will be copied based on parsing the target
+						 * system's WAL, and any blocks modified in the source will be
+						 * updated after rewinding, when the source system's WAL is
+						 * replayed.
+						 */
+						if (entry->target_size < entry->source_size)
+							action = FILE_ACTION_COPY_TAIL;
+						else if (entry->target_size > entry->source_size)
+							action = FILE_ACTION_TRUNCATE;
+						else
+							action = FILE_ACTION_NONE;
+					}
+					break;
+				case FILE_TYPE_UNDEFINED:
+					pg_fatal("unknown file type for \"%s\"", entry->path);
+					break;
+			}
+		}
+		else
+		{
+			/* Doesn't exist in either server. Why does it have an entry at all?? */
+			action = FILE_ACTION_NONE;
+		}
+		entry->action = action;
+	}
+
+	/* Sort the actions to the order that they should be performed */
+	qsort(filemap->array, filemap->narray, sizeof(file_entry_t *),
+		  final_filemap_cmp);
+}
diff --git a/src/bin/pg_rewind/filemap.h b/src/bin/pg_rewind/filemap.h
index 0cb7425170c..a5e8df57f40 100644
--- a/src/bin/pg_rewind/filemap.h
+++ b/src/bin/pg_rewind/filemap.h
@@ -14,17 +14,21 @@
 
 /*
  * For every file found in the local or remote system, we have a file entry
- * which says what we are going to do with the file. For relation files,
- * there is also a page map, marking pages in the file that were changed
- * locally.
- *
- * The enum values are sorted in the order we want actions to be processed.
+ * that contains information about the file on both systems.  For relation
+ * files, there is also a page map that marks pages in the file that were
+ * changed in the target after the last common checkpoint.  Each entry also
+ * contains an 'action' field, which says what we are going to do with the
+ * file.
  */
+
+/* these enum values are sorted in the order we want actions to be processed */
 typedef enum
 {
+	FILE_ACTION_UNDECIDED = 0,	/* not decided yet */
+
 	FILE_ACTION_CREATE,			/* create local directory or symbolic link */
 	FILE_ACTION_COPY,			/* copy whole file, overwriting if exists */
-	FILE_ACTION_COPY_TAIL,		/* copy tail from 'oldsize' to 'newsize' */
+	FILE_ACTION_COPY_TAIL,		/* copy tail from 'source_size' to 'target_size' */
 	FILE_ACTION_NONE,			/* no action (we might still copy modified
 								 * blocks based on the parsed WAL) */
 	FILE_ACTION_TRUNCATE,		/* truncate local file to 'newsize' bytes */
@@ -33,6 +37,8 @@ typedef enum
 
 typedef enum
 {
+	FILE_TYPE_UNDEFINED = 0,
+
 	FILE_TYPE_REGULAR,
 	FILE_TYPE_DIRECTORY,
 	FILE_TYPE_SYMLINK
@@ -41,19 +47,30 @@ typedef enum
 typedef struct file_entry_t
 {
 	char	   *path;
-	file_type_t type;
+	bool		isrelfile;		/* is it a relation data file? */
 
-	file_action_t action;
+	/*
+	 * Status of the file in the target.
+	 */
+	bool		target_exists;
+	file_type_t target_type;
+	size_t		target_size; /* for a regular file */
+	char	   *target_link_target; /* for a symlink */
 
-	/* for a regular file */
-	size_t		oldsize;
-	size_t		newsize;
-	bool		isrelfile;		/* is it a relation data file? */
+	datapagemap_t target_modified_pages;
 
-	datapagemap_t pagemap;
+	/*
+	 * Status of the file in the source.
+	 */
+	bool		source_exists;
+	file_type_t source_type;
+	size_t		source_size;
+	char	   *source_link_target; /* for a symlink */
 
-	/* for a symlink */
-	char	   *link_target;
+	/*
+	 * What will we do to the file?
+	 */
+	file_action_t action;
 
 	struct file_entry_t *next;
 } file_entry_t;
@@ -70,20 +87,19 @@ typedef struct filemap_t
 
 	/*
 	 * After processing all the remote files, the entries in the linked list
-	 * are moved to this array. After processing local files, too, all the
+	 * are moved to this array.  After processing local files, too, all the
 	 * local entries are added to the array by filemap_finalize, and sorted in
-	 * the final order. After filemap_finalize, all the entries are in the
+	 * the final order.  After filemap_finalize, all the entries are in the
 	 * array, and the linked list is empty.
 	 */
 	file_entry_t **array;
 	int			narray;			/* current length of array */
 
 	/*
-	 * Summary information. total_size is the total size of the source
-	 * cluster, and fetch_size is the number of bytes that needs to be copied.
+	 * Summary information.
 	 */
-	uint64		total_size;
-	uint64		fetch_size;
+	uint64		total_size;		/* total size of the source cluster */
+	uint64		fetch_size;		/* number of bytes that needs to be copied */
 } filemap_t;
 
 extern filemap_t *filemap;
@@ -94,11 +110,12 @@ extern void print_filemap(void);
 
 /* Functions for populating the filemap */
 extern void process_source_file(const char *path, file_type_t type,
-								size_t newsize, const char *link_target);
+								size_t size, const char *link_target);
 extern void process_target_file(const char *path, file_type_t type,
-								size_t newsize, const char *link_target);
-extern void process_block_change(ForkNumber forknum, RelFileNode rnode,
-								 BlockNumber blkno);
+								size_t size, const char *link_target);
+extern void process_target_wal_block_change(ForkNumber forknum,
+											RelFileNode rnode,
+											BlockNumber blkno);
 extern void filemap_finalize(void);
 
 #endif							/* FILEMAP_H */
diff --git a/src/bin/pg_rewind/libpq_fetch.c b/src/bin/pg_rewind/libpq_fetch.c
index bf4dfc23b96..7fc9161b8c8 100644
--- a/src/bin/pg_rewind/libpq_fetch.c
+++ b/src/bin/pg_rewind/libpq_fetch.c
@@ -465,7 +465,7 @@ libpq_executeFileMap(filemap_t *map)
 		entry = map->array[i];
 
 		/* If this is a relation file, copy the modified blocks */
-		execute_pagemap(&entry->pagemap, entry->path);
+		execute_pagemap(&entry->target_modified_pages, entry->path);
 
 		switch (entry->action)
 		{
@@ -476,15 +476,15 @@ libpq_executeFileMap(filemap_t *map)
 			case FILE_ACTION_COPY:
 				/* Truncate the old file out of the way, if any */
 				open_target_file(entry->path, true);
-				fetch_file_range(entry->path, 0, entry->newsize);
+				fetch_file_range(entry->path, 0, entry->source_size);
 				break;
 
 			case FILE_ACTION_TRUNCATE:
-				truncate_target_file(entry->path, entry->newsize);
+				truncate_target_file(entry->path, entry->source_size);
 				break;
 
 			case FILE_ACTION_COPY_TAIL:
-				fetch_file_range(entry->path, entry->oldsize, entry->newsize);
+				fetch_file_range(entry->path, entry->target_size, entry->source_size);
 				break;
 
 			case FILE_ACTION_REMOVE:
@@ -494,6 +494,10 @@ libpq_executeFileMap(filemap_t *map)
 			case FILE_ACTION_CREATE:
 				create_target(entry);
 				break;
+
+			case FILE_ACTION_UNDECIDED:
+				pg_fatal("no action decided for \"%s\"", entry->path);
+				break;
 		}
 	}
 
diff --git a/src/bin/pg_rewind/parsexlog.c b/src/bin/pg_rewind/parsexlog.c
index 2229c86f9af..2baeb74ae93 100644
--- a/src/bin/pg_rewind/parsexlog.c
+++ b/src/bin/pg_rewind/parsexlog.c
@@ -436,6 +436,6 @@ extractPageInfo(XLogReaderState *record)
 		if (forknum != MAIN_FORKNUM)
 			continue;
 
-		process_block_change(forknum, rnode, blkno);
+		process_target_wal_block_change(forknum, rnode, blkno);
 	}
 }
diff --git a/src/bin/pg_rewind/pg_rewind.c b/src/bin/pg_rewind/pg_rewind.c
index c9b9e480c0f..210984d302b 100644
--- a/src/bin/pg_rewind/pg_rewind.c
+++ b/src/bin/pg_rewind/pg_rewind.c
@@ -369,7 +369,7 @@ main(int argc, char **argv)
 				chkpttli);
 
 	/*
-	 * Build the filemap, by comparing the source and target data directories.
+	 * Collect information about all files in the target and source systems.
 	 */
 	filemap_create();
 	if (showprogress)
@@ -390,8 +390,12 @@ main(int argc, char **argv)
 		pg_log_info("reading WAL in target");
 	extractPageMap(datadir_target, chkptrec, lastcommontliIndex,
 				   ControlFile_target.checkPoint, restore_command);
-	filemap_finalize();
 
+	/*
+	 * We have collected all information we need from both systems. Decide
+	 * what to do with each file.
+	 */
+	filemap_finalize();
 	if (showprogress)
 		calculate_totals();
 
-- 
2.20.1


--------------D93EDEBFB124D563B723F4BD
Content-Type: text/x-patch; charset=UTF-8;
 name="0003-pg_rewind-Replace-the-hybrid-list-array-data-structu.patch"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
 filename*0="0003-pg_rewind-Replace-the-hybrid-list-array-data-structu.pa";
 filename*1="tch"



^ permalink  raw  reply  [nested|flat] 41+ messages in thread

* [PATCH 2/5] Refactor pg_rewind for more clear decision making.
@ 2020-08-19 12:34 Heikki Linnakangas <[email protected]>
  0 siblings, 0 replies; 41+ messages in thread

From: Heikki Linnakangas @ 2020-08-19 12:34 UTC (permalink / raw)

Deciding what to do with each file is now a separate step after all the
necessary information has been gathered. It is more clear that way.
Previously, the decision-making was divided between process_source_file()
and process_target_file(), and it was a bit hard to piece together what the
overall rules were.
---
 src/bin/pg_rewind/copy_fetch.c  |  14 +-
 src/bin/pg_rewind/file_ops.c    |  16 +-
 src/bin/pg_rewind/filemap.c     | 514 ++++++++++++++++----------------
 src/bin/pg_rewind/filemap.h     |  67 +++--
 src/bin/pg_rewind/libpq_fetch.c |  12 +-
 src/bin/pg_rewind/parsexlog.c   |   2 +-
 src/bin/pg_rewind/pg_rewind.c   |   8 +-
 7 files changed, 332 insertions(+), 301 deletions(-)

diff --git a/src/bin/pg_rewind/copy_fetch.c b/src/bin/pg_rewind/copy_fetch.c
index 1edab5f1867..18fad32600e 100644
--- a/src/bin/pg_rewind/copy_fetch.c
+++ b/src/bin/pg_rewind/copy_fetch.c
@@ -210,7 +210,7 @@ copy_executeFileMap(filemap_t *map)
 	for (i = 0; i < map->narray; i++)
 	{
 		entry = map->array[i];
-		execute_pagemap(&entry->pagemap, entry->path);
+		execute_pagemap(&entry->target_modified_pages, entry->path);
 
 		switch (entry->action)
 		{
@@ -219,16 +219,16 @@ copy_executeFileMap(filemap_t *map)
 				break;
 
 			case FILE_ACTION_COPY:
-				rewind_copy_file_range(entry->path, 0, entry->newsize, true);
+				rewind_copy_file_range(entry->path, 0, entry->source_size, true);
 				break;
 
 			case FILE_ACTION_TRUNCATE:
-				truncate_target_file(entry->path, entry->newsize);
+				truncate_target_file(entry->path, entry->source_size);
 				break;
 
 			case FILE_ACTION_COPY_TAIL:
-				rewind_copy_file_range(entry->path, entry->oldsize,
-									   entry->newsize, false);
+				rewind_copy_file_range(entry->path, entry->target_size,
+									   entry->source_size, false);
 				break;
 
 			case FILE_ACTION_CREATE:
@@ -238,6 +238,10 @@ copy_executeFileMap(filemap_t *map)
 			case FILE_ACTION_REMOVE:
 				remove_target(entry);
 				break;
+
+			case FILE_ACTION_UNDECIDED:
+				pg_fatal("no action decided for \"%s\"", entry->path);
+				break;
 		}
 	}
 
diff --git a/src/bin/pg_rewind/file_ops.c b/src/bin/pg_rewind/file_ops.c
index 55439db20ba..ec37d0b2e0d 100644
--- a/src/bin/pg_rewind/file_ops.c
+++ b/src/bin/pg_rewind/file_ops.c
@@ -126,8 +126,9 @@ void
 remove_target(file_entry_t *entry)
 {
 	Assert(entry->action == FILE_ACTION_REMOVE);
+	Assert(entry->target_exists);
 
-	switch (entry->type)
+	switch (entry->target_type)
 	{
 		case FILE_TYPE_DIRECTORY:
 			remove_target_dir(entry->path);
@@ -140,6 +141,10 @@ remove_target(file_entry_t *entry)
 		case FILE_TYPE_SYMLINK:
 			remove_target_symlink(entry->path);
 			break;
+
+		case FILE_TYPE_UNDEFINED:
+			pg_fatal("undefined file type for \"%s\"", entry->path);
+			break;
 	}
 }
 
@@ -147,21 +152,26 @@ void
 create_target(file_entry_t *entry)
 {
 	Assert(entry->action == FILE_ACTION_CREATE);
+	Assert(!entry->target_exists);
 
-	switch (entry->type)
+	switch (entry->source_type)
 	{
 		case FILE_TYPE_DIRECTORY:
 			create_target_dir(entry->path);
 			break;
 
 		case FILE_TYPE_SYMLINK:
-			create_target_symlink(entry->path, entry->link_target);
+			create_target_symlink(entry->path, entry->source_link_target);
 			break;
 
 		case FILE_TYPE_REGULAR:
 			/* can't happen. Regular files are created with open_target_file. */
 			pg_fatal("invalid action (CREATE) for regular file");
 			break;
+
+		case FILE_TYPE_UNDEFINED:
+			pg_fatal("undefined file type for \"%s\"", entry->path);
+			break;
 	}
 }
 
diff --git a/src/bin/pg_rewind/filemap.c b/src/bin/pg_rewind/filemap.c
index 1879257b66a..431e8e760e0 100644
--- a/src/bin/pg_rewind/filemap.c
+++ b/src/bin/pg_rewind/filemap.c
@@ -26,6 +26,8 @@ static bool isRelDataFile(const char *path);
 static char *datasegpath(RelFileNode rnode, ForkNumber forknum,
 						 BlockNumber segno);
 static int	path_cmp(const void *a, const void *b);
+
+static file_entry_t *get_filemap_entry(const char *path, bool create);
 static int	final_filemap_cmp(const void *a, const void *b);
 static void filemap_list_to_array(filemap_t *map);
 static bool check_file_excluded(const char *path, bool is_source);
@@ -146,6 +148,65 @@ filemap_create(void)
 	filemap = map;
 }
 
+/* Look up or create entry for 'path' */
+static file_entry_t *
+get_filemap_entry(const char *path, bool create)
+{
+	filemap_t  *map = filemap;
+	file_entry_t *entry;
+	file_entry_t **e;
+	file_entry_t key;
+	file_entry_t *key_ptr;
+
+	if (map->array)
+	{
+		key.path = (char *) path;
+		key_ptr = &key;
+		e = bsearch(&key_ptr, map->array, map->narray, sizeof(file_entry_t *),
+					path_cmp);
+	}
+	else
+		e = NULL;
+
+	if (e)
+		entry = *e;
+	else if (!create)
+		entry = NULL;
+	else
+	{
+		/* Create a new entry for this file */
+		entry = pg_malloc(sizeof(file_entry_t));
+		entry->path = pg_strdup(path);
+		entry->isrelfile = isRelDataFile(path);
+		entry->action = FILE_ACTION_UNDECIDED;
+
+		entry->target_exists = false;
+		entry->target_type = FILE_TYPE_UNDEFINED;
+		entry->target_size = 0;
+		entry->target_link_target = NULL;
+		entry->target_modified_pages.bitmap = NULL;
+		entry->target_modified_pages.bitmapsize = 0;
+
+		entry->source_exists = false;
+		entry->source_type = FILE_TYPE_UNDEFINED;
+		entry->source_size = 0;
+		entry->source_link_target = NULL;
+
+		entry->next = NULL;
+
+		if (map->last)
+		{
+			map->last->next = entry;
+			map->last = entry;
+		}
+		else
+			map->first = map->last = entry;
+		map->nlist++;
+	}
+
+	return entry;
+}
+
 /*
  * Callback for processing source file list.
  *
@@ -154,18 +215,19 @@ filemap_create(void)
  * exists in the target and whether the size matches.
  */
 void
-process_source_file(const char *path, file_type_t type, size_t newsize,
+process_source_file(const char *path, file_type_t type, size_t size,
 					const char *link_target)
 {
-	bool		exists;
-	char		localpath[MAXPGPATH];
-	struct stat statbuf;
-	filemap_t  *map = filemap;
-	file_action_t action = FILE_ACTION_NONE;
-	size_t		oldsize = 0;
 	file_entry_t *entry;
 
-	Assert(map->array == NULL);
+	Assert(filemap->array == NULL);
+
+	/*
+	 * Skip the control file. It is handled specially, after copying all the
+	 * other files.
+	 */
+	if (strcmp(path, "global/pg_control") == 0)
+		return;
 
 	/*
 	 * Skip any files matching the exclusion filters. This has the effect to
@@ -199,142 +261,12 @@ process_source_file(const char *path, file_type_t type, size_t newsize,
 	if (type != FILE_TYPE_REGULAR && isRelDataFile(path))
 		pg_fatal("data file \"%s\" in source is not a regular file", path);
 
-	snprintf(localpath, sizeof(localpath), "%s/%s", datadir_target, path);
-
-	/* Does the corresponding file exist in the target data dir? */
-	if (lstat(localpath, &statbuf) < 0)
-	{
-		if (errno != ENOENT)
-			pg_fatal("could not stat file \"%s\": %m",
-					 localpath);
-
-		exists = false;
-	}
-	else
-		exists = true;
-
-	switch (type)
-	{
-		case FILE_TYPE_DIRECTORY:
-			if (exists && !S_ISDIR(statbuf.st_mode) && strcmp(path, "pg_wal") != 0)
-			{
-				/* it's a directory in source, but not in target. Strange.. */
-				pg_fatal("\"%s\" is not a directory", localpath);
-			}
-
-			if (!exists)
-				action = FILE_ACTION_CREATE;
-			else
-				action = FILE_ACTION_NONE;
-			oldsize = 0;
-			break;
-
-		case FILE_TYPE_SYMLINK:
-			if (exists &&
-#ifndef WIN32
-				!S_ISLNK(statbuf.st_mode)
-#else
-				!pgwin32_is_junction(localpath)
-#endif
-				)
-			{
-				/*
-				 * It's a symbolic link in source, but not in target.
-				 * Strange..
-				 */
-				pg_fatal("\"%s\" is not a symbolic link", localpath);
-			}
-
-			if (!exists)
-				action = FILE_ACTION_CREATE;
-			else
-				action = FILE_ACTION_NONE;
-			oldsize = 0;
-			break;
-
-		case FILE_TYPE_REGULAR:
-			if (exists && !S_ISREG(statbuf.st_mode))
-				pg_fatal("\"%s\" is not a regular file", localpath);
-
-			if (!exists || !isRelDataFile(path))
-			{
-				/*
-				 * File exists in source, but not in target. Or it's a
-				 * non-data file that we have no special processing for. Copy
-				 * it in toto.
-				 *
-				 * An exception: PG_VERSIONs should be identical, but avoid
-				 * overwriting it for paranoia.
-				 */
-				if (pg_str_endswith(path, "PG_VERSION"))
-				{
-					action = FILE_ACTION_NONE;
-					oldsize = statbuf.st_size;
-				}
-				else
-				{
-					action = FILE_ACTION_COPY;
-					oldsize = 0;
-				}
-			}
-			else
-			{
-				/*
-				 * It's a data file that exists in both.
-				 *
-				 * If it's larger in target, we can truncate it. There will
-				 * also be a WAL record of the truncation in the source
-				 * system, so WAL replay would eventually truncate the target
-				 * too, but we might as well do it now.
-				 *
-				 * If it's smaller in the target, it means that it has been
-				 * truncated in the target, or enlarged in the source, or
-				 * both. If it was truncated in the target, we need to copy
-				 * the missing tail from the source system. If it was enlarged
-				 * in the source system, there will be WAL records in the
-				 * source system for the new blocks, so we wouldn't need to
-				 * copy them here. But we don't know which scenario we're
-				 * dealing with, and there's no harm in copying the missing
-				 * blocks now, so do it now.
-				 *
-				 * If it's the same size, do nothing here. Any blocks modified
-				 * in the target will be copied based on parsing the target
-				 * system's WAL, and any blocks modified in the source will be
-				 * updated after rewinding, when the source system's WAL is
-				 * replayed.
-				 */
-				oldsize = statbuf.st_size;
-				if (oldsize < newsize)
-					action = FILE_ACTION_COPY_TAIL;
-				else if (oldsize > newsize)
-					action = FILE_ACTION_TRUNCATE;
-				else
-					action = FILE_ACTION_NONE;
-			}
-			break;
-	}
-
-	/* Create a new entry for this file */
-	entry = pg_malloc(sizeof(file_entry_t));
-	entry->path = pg_strdup(path);
-	entry->type = type;
-	entry->action = action;
-	entry->oldsize = oldsize;
-	entry->newsize = newsize;
-	entry->link_target = link_target ? pg_strdup(link_target) : NULL;
-	entry->next = NULL;
-	entry->pagemap.bitmap = NULL;
-	entry->pagemap.bitmapsize = 0;
-	entry->isrelfile = isRelDataFile(path);
-
-	if (map->last)
-	{
-		map->last->next = entry;
-		map->last = entry;
-	}
-	else
-		map->first = map->last = entry;
-	map->nlist++;
+	/* Remember this source file */
+	entry = get_filemap_entry(path, true);
+	entry->source_exists = true;
+	entry->source_type = type;
+	entry->source_size = size;
+	entry->source_link_target = link_target ? pg_strdup(link_target) : NULL;
 }
 
 /*
@@ -345,33 +277,24 @@ process_source_file(const char *path, file_type_t type, size_t newsize,
  * deletion.
  */
 void
-process_target_file(const char *path, file_type_t type, size_t oldsize,
+process_target_file(const char *path, file_type_t type, size_t size,
 					const char *link_target)
 {
-	bool		exists;
-	char		localpath[MAXPGPATH];
-	struct stat statbuf;
-	file_entry_t key;
-	file_entry_t *key_ptr;
 	filemap_t  *map = filemap;
 	file_entry_t *entry;
 
+	/*
+	 * Skip the control file. It is handled specially, after copying all the
+	 * other files.
+	 */
+	if (strcmp(path, "global/pg_control") == 0)
+		return;
+
 	/*
 	 * Do not apply any exclusion filters here.  This has advantage to remove
 	 * from the target data folder all paths which have been filtered out from
 	 * the source data folder when processing the source files.
 	 */
-
-	snprintf(localpath, sizeof(localpath), "%s/%s", datadir_target, path);
-	if (lstat(localpath, &statbuf) < 0)
-	{
-		if (errno != ENOENT)
-			pg_fatal("could not stat file \"%s\": %m",
-					 localpath);
-
-		exists = false;
-	}
-
 	if (map->array == NULL)
 	{
 		/* on first call, initialize lookup array */
@@ -389,120 +312,76 @@ process_target_file(const char *path, file_type_t type, size_t oldsize,
 	}
 
 	/*
-	 * Like in process_source_file, pretend that xlog is always a  directory.
+	 * Like in process_source_file, pretend that pg_wal is always a directory.
 	 */
 	if (strcmp(path, "pg_wal") == 0 && type == FILE_TYPE_SYMLINK)
 		type = FILE_TYPE_DIRECTORY;
 
-	key.path = (char *) path;
-	key_ptr = &key;
-	exists = (bsearch(&key_ptr, map->array, map->narray, sizeof(file_entry_t *),
-					  path_cmp) != NULL);
-
-	/* Remove any file or folder that doesn't exist in the source system. */
-	if (!exists)
-	{
-		entry = pg_malloc(sizeof(file_entry_t));
-		entry->path = pg_strdup(path);
-		entry->type = type;
-		entry->action = FILE_ACTION_REMOVE;
-		entry->oldsize = oldsize;
-		entry->newsize = 0;
-		entry->link_target = link_target ? pg_strdup(link_target) : NULL;
-		entry->next = NULL;
-		entry->pagemap.bitmap = NULL;
-		entry->pagemap.bitmapsize = 0;
-		entry->isrelfile = isRelDataFile(path);
-
-		if (map->last == NULL)
-			map->first = entry;
-		else
-			map->last->next = entry;
-		map->last = entry;
-		map->nlist++;
-	}
-	else
-	{
-		/*
-		 * We already handled all files that exist in the source system in
-		 * process_source_file().
-		 */
-	}
+	/* Remember this target file */
+	entry = get_filemap_entry(path, true);
+	entry->target_exists = true;
+	entry->target_type = type;
+	entry->target_size = size;
+	entry->target_link_target = link_target ? pg_strdup(link_target) : NULL;
 }
 
 /*
  * This callback gets called while we read the WAL in the target, for every
- * block that have changed in the target system. It makes note of all the
+ * block that have changed in the target system.  It makes note of all the
  * changed blocks in the pagemap of the file.
+ *
+ * NOTE: All the files on both systems must have already been added to the
+ * file map!
  */
 void
-process_block_change(ForkNumber forknum, RelFileNode rnode, BlockNumber blkno)
+process_target_wal_block_change(ForkNumber forknum, RelFileNode rnode,
+								BlockNumber blkno)
 {
 	char	   *path;
-	file_entry_t key;
-	file_entry_t *key_ptr;
 	file_entry_t *entry;
 	BlockNumber blkno_inseg;
 	int			segno;
-	filemap_t  *map = filemap;
-	file_entry_t **e;
 
-	Assert(map->array);
+	Assert(filemap->array);
 
 	segno = blkno / RELSEG_SIZE;
 	blkno_inseg = blkno % RELSEG_SIZE;
 
 	path = datasegpath(rnode, forknum, segno);
-
-	key.path = (char *) path;
-	key_ptr = &key;
-
-	e = bsearch(&key_ptr, map->array, map->narray, sizeof(file_entry_t *),
-				path_cmp);
-	if (e)
-		entry = *e;
-	else
-		entry = NULL;
+	entry = get_filemap_entry(path, false);
 	pfree(path);
 
 	if (entry)
 	{
+		int64		end_offset;
+
 		Assert(entry->isrelfile);
 
-		switch (entry->action)
-		{
-			case FILE_ACTION_NONE:
-			case FILE_ACTION_TRUNCATE:
-				/* skip if we're truncating away the modified block anyway */
-				if ((blkno_inseg + 1) * BLCKSZ <= entry->newsize)
-					datapagemap_add(&entry->pagemap, blkno_inseg);
-				break;
-
-			case FILE_ACTION_COPY_TAIL:
-
-				/*
-				 * skip the modified block if it is part of the "tail" that
-				 * we're copying anyway.
-				 */
-				if ((blkno_inseg + 1) * BLCKSZ <= entry->oldsize)
-					datapagemap_add(&entry->pagemap, blkno_inseg);
-				break;
-
-			case FILE_ACTION_COPY:
-			case FILE_ACTION_REMOVE:
-				break;
-
-			case FILE_ACTION_CREATE:
-				pg_fatal("unexpected page modification for directory or symbolic link \"%s\"", entry->path);
-		}
+		if (entry->target_type != FILE_TYPE_REGULAR)
+			pg_fatal("unexpected page modification for directory or symbolic link \"%s\"",
+					 entry->path);
+
+		/*
+		 * If the block beyond the EOF in the source system, no need to
+		 * remember it now, because we're going to truncate it away from the
+		 * target anyway. Also no need to remember the block if it's beyond
+		 * the current EOF in the target system; we will copy it over with the
+		 * "tail" from the source system, anyway.
+		 */
+		end_offset = (blkno_inseg + 1) * BLCKSZ;
+		if (end_offset <= entry->source_size &&
+			end_offset <= entry->target_size)
+			datapagemap_add(&entry->target_modified_pages, blkno_inseg);
 	}
 	else
 	{
 		/*
 		 * If we don't have any record of this file in the file map, it means
-		 * that it's a relation that doesn't exist in the source system, and
-		 * it was subsequently removed in the target system, too. We can
-		 * safely ignore it.
+		 * that it's a relation that doesn't exist in the source system.  It
+		 * could exist in the target system; we haven't moved the target-only
+		 * entries from the linked list to the array yet!  But in any case, if
+		 * it doesn't exist in the source it will be removed from the target
+		 * too, and we can safely ignore it.
 		 */
 	}
 }
@@ -593,16 +472,6 @@ filemap_list_to_array(filemap_t *map)
 	map->first = map->last = NULL;
 }
 
-void
-filemap_finalize(void)
-{
-	filemap_t  *map = filemap;
-
-	filemap_list_to_array(map);
-	qsort(map->array, map->narray, sizeof(file_entry_t *),
-		  final_filemap_cmp);
-}
-
 static const char *
 action_to_str(file_action_t action)
 {
@@ -643,26 +512,26 @@ calculate_totals(void)
 	{
 		entry = map->array[i];
 
-		if (entry->type != FILE_TYPE_REGULAR)
+		if (entry->source_type != FILE_TYPE_REGULAR)
 			continue;
 
-		map->total_size += entry->newsize;
+		map->total_size += entry->source_size;
 
 		if (entry->action == FILE_ACTION_COPY)
 		{
-			map->fetch_size += entry->newsize;
+			map->fetch_size += entry->source_size;
 			continue;
 		}
 
 		if (entry->action == FILE_ACTION_COPY_TAIL)
-			map->fetch_size += (entry->newsize - entry->oldsize);
+			map->fetch_size += (entry->source_size - entry->target_size);
 
-		if (entry->pagemap.bitmapsize > 0)
+		if (entry->target_modified_pages.bitmapsize > 0)
 		{
 			datapagemap_iterator_t *iter;
 			BlockNumber blk;
 
-			iter = datapagemap_iterate(&entry->pagemap);
+			iter = datapagemap_iterate(&entry->target_modified_pages);
 			while (datapagemap_next(iter, &blk))
 				map->fetch_size += BLCKSZ;
 
@@ -682,13 +551,13 @@ print_filemap(void)
 	{
 		entry = map->array[i];
 		if (entry->action != FILE_ACTION_NONE ||
-			entry->pagemap.bitmapsize > 0)
+			entry->target_modified_pages.bitmapsize > 0)
 		{
 			pg_log_debug("%s (%s)", entry->path,
 						 action_to_str(entry->action));
 
-			if (entry->pagemap.bitmapsize > 0)
-				datapagemap_print(&entry->pagemap);
+			if (entry->target_modified_pages.bitmapsize > 0)
+				datapagemap_print(&entry->target_modified_pages);
 		}
 	}
 	fflush(stdout);
@@ -837,3 +706,126 @@ final_filemap_cmp(const void *a, const void *b)
 	else
 		return strcmp(fa->path, fb->path);
 }
+
+/*
+ * Decide what to do with each file.
+ */
+void
+filemap_finalize()
+{
+	int			i;
+
+	filemap_list_to_array(filemap);
+
+	for (i = 0; i < filemap->narray; i++)
+	{
+		file_entry_t *entry = filemap->array[i];
+		file_action_t action;
+
+		if (!entry->target_exists && entry->source_exists)
+		{
+			/*
+			 * File exists in source, but not in target. Copy
+			 * it in toto. (If it's a relation data file, WAL replay
+			 * after rewinding should re-create it anyway. But there's
+			 * no harm in copying it now.)
+			 */
+			if (entry->source_type == FILE_TYPE_DIRECTORY ||
+				entry->source_type == FILE_TYPE_SYMLINK)
+			{
+				action = FILE_ACTION_CREATE;
+			}
+			else
+				action = FILE_ACTION_COPY;
+		}
+		else if (entry->target_exists && !entry->source_exists)
+		{
+			/* Delete file from target */
+			action = FILE_ACTION_REMOVE;
+		}
+		else if (entry->target_exists && entry->source_exists)
+		{
+			/* File exists in both systems */
+			if (entry->source_type != entry->target_type)
+			{
+				/* But it's a different kind of object. Strange.. */
+				pg_fatal("file \"%s\" is of different type in source and target", entry->path);
+			}
+
+			switch (entry->source_type)
+			{
+				case FILE_TYPE_DIRECTORY:
+					action = FILE_ACTION_NONE;
+					break;
+
+				case FILE_TYPE_SYMLINK:
+					// FIXME: Check if it points to the same target?
+					action = FILE_ACTION_NONE;
+					break;
+
+				case FILE_TYPE_REGULAR:
+					if (!entry->isrelfile)
+					{
+						/*
+						 * It's a non-data file that we have no special processing for. Copy
+						 * it in toto.
+						 *
+						 * An exception: PG_VERSIONs should be identical, but avoid
+						 * overwriting it for paranoia.
+						 */
+						if (!pg_str_endswith(entry->path, "PG_VERSION"))
+							action = FILE_ACTION_COPY;
+						else
+							action = FILE_ACTION_NONE;
+					}
+					else
+					{
+						/*
+						 * It's a data file that exists in both systems.
+						 *
+						 * If it's larger in target, we can truncate it. There will
+						 * also be a WAL record of the truncation in the source
+						 * system, so WAL replay would eventually truncate the target
+						 * too, but we might as well do it now.
+						 *
+						 * If it's smaller in the target, it means that it has been
+						 * truncated in the target, or enlarged in the source, or
+						 * both. If it was truncated in the target, we need to copy
+						 * the missing tail from the source system. If it was enlarged
+						 * in the source system, there will be WAL records in the
+						 * source system for the new blocks, so we wouldn't need to
+						 * copy them here. But we don't know which scenario we're
+						 * dealing with, and there's no harm in copying the missing
+						 * blocks now, so do it now.
+						 *
+						 * If it's the same size, do nothing here. Any blocks modified
+						 * in the target will be copied based on parsing the target
+						 * system's WAL, and any blocks modified in the source will be
+						 * updated after rewinding, when the source system's WAL is
+						 * replayed.
+						 */
+						if (entry->target_size < entry->source_size)
+							action = FILE_ACTION_COPY_TAIL;
+						else if (entry->target_size > entry->source_size)
+							action = FILE_ACTION_TRUNCATE;
+						else
+							action = FILE_ACTION_NONE;
+					}
+					break;
+				case FILE_TYPE_UNDEFINED:
+					pg_fatal("unknown file type for \"%s\"", entry->path);
+					break;
+			}
+		}
+		else
+		{
+			/* Doesn't exist in either server. Why does it have an entry at all?? */
+			action = FILE_ACTION_NONE;
+		}
+		entry->action = action;
+	}
+
+	/* Sort the actions to the order that they should be performed */
+	qsort(filemap->array, filemap->narray, sizeof(file_entry_t *),
+		  final_filemap_cmp);
+}
diff --git a/src/bin/pg_rewind/filemap.h b/src/bin/pg_rewind/filemap.h
index 0cb7425170c..a5e8df57f40 100644
--- a/src/bin/pg_rewind/filemap.h
+++ b/src/bin/pg_rewind/filemap.h
@@ -14,17 +14,21 @@
 
 /*
  * For every file found in the local or remote system, we have a file entry
- * which says what we are going to do with the file. For relation files,
- * there is also a page map, marking pages in the file that were changed
- * locally.
- *
- * The enum values are sorted in the order we want actions to be processed.
+ * that contains information about the file on both systems.  For relation
+ * files, there is also a page map that marks pages in the file that were
+ * changed in the target after the last common checkpoint.  Each entry also
+ * contains an 'action' field, which says what we are going to do with the
+ * file.
  */
+
+/* these enum values are sorted in the order we want actions to be processed */
 typedef enum
 {
+	FILE_ACTION_UNDECIDED = 0,	/* not decided yet */
+
 	FILE_ACTION_CREATE,			/* create local directory or symbolic link */
 	FILE_ACTION_COPY,			/* copy whole file, overwriting if exists */
-	FILE_ACTION_COPY_TAIL,		/* copy tail from 'oldsize' to 'newsize' */
+	FILE_ACTION_COPY_TAIL,		/* copy tail from 'source_size' to 'target_size' */
 	FILE_ACTION_NONE,			/* no action (we might still copy modified
 								 * blocks based on the parsed WAL) */
 	FILE_ACTION_TRUNCATE,		/* truncate local file to 'newsize' bytes */
@@ -33,6 +37,8 @@ typedef enum
 
 typedef enum
 {
+	FILE_TYPE_UNDEFINED = 0,
+
 	FILE_TYPE_REGULAR,
 	FILE_TYPE_DIRECTORY,
 	FILE_TYPE_SYMLINK
@@ -41,19 +47,30 @@ typedef enum
 typedef struct file_entry_t
 {
 	char	   *path;
-	file_type_t type;
+	bool		isrelfile;		/* is it a relation data file? */
 
-	file_action_t action;
+	/*
+	 * Status of the file in the target.
+	 */
+	bool		target_exists;
+	file_type_t target_type;
+	size_t		target_size; /* for a regular file */
+	char	   *target_link_target; /* for a symlink */
 
-	/* for a regular file */
-	size_t		oldsize;
-	size_t		newsize;
-	bool		isrelfile;		/* is it a relation data file? */
+	datapagemap_t target_modified_pages;
 
-	datapagemap_t pagemap;
+	/*
+	 * Status of the file in the source.
+	 */
+	bool		source_exists;
+	file_type_t source_type;
+	size_t		source_size;
+	char	   *source_link_target; /* for a symlink */
 
-	/* for a symlink */
-	char	   *link_target;
+	/*
+	 * What will we do to the file?
+	 */
+	file_action_t action;
 
 	struct file_entry_t *next;
 } file_entry_t;
@@ -70,20 +87,19 @@ typedef struct filemap_t
 
 	/*
 	 * After processing all the remote files, the entries in the linked list
-	 * are moved to this array. After processing local files, too, all the
+	 * are moved to this array.  After processing local files, too, all the
 	 * local entries are added to the array by filemap_finalize, and sorted in
-	 * the final order. After filemap_finalize, all the entries are in the
+	 * the final order.  After filemap_finalize, all the entries are in the
 	 * array, and the linked list is empty.
 	 */
 	file_entry_t **array;
 	int			narray;			/* current length of array */
 
 	/*
-	 * Summary information. total_size is the total size of the source
-	 * cluster, and fetch_size is the number of bytes that needs to be copied.
+	 * Summary information.
 	 */
-	uint64		total_size;
-	uint64		fetch_size;
+	uint64		total_size;		/* total size of the source cluster */
+	uint64		fetch_size;		/* number of bytes that needs to be copied */
 } filemap_t;
 
 extern filemap_t *filemap;
@@ -94,11 +110,12 @@ extern void print_filemap(void);
 
 /* Functions for populating the filemap */
 extern void process_source_file(const char *path, file_type_t type,
-								size_t newsize, const char *link_target);
+								size_t size, const char *link_target);
 extern void process_target_file(const char *path, file_type_t type,
-								size_t newsize, const char *link_target);
-extern void process_block_change(ForkNumber forknum, RelFileNode rnode,
-								 BlockNumber blkno);
+								size_t size, const char *link_target);
+extern void process_target_wal_block_change(ForkNumber forknum,
+											RelFileNode rnode,
+											BlockNumber blkno);
 extern void filemap_finalize(void);
 
 #endif							/* FILEMAP_H */
diff --git a/src/bin/pg_rewind/libpq_fetch.c b/src/bin/pg_rewind/libpq_fetch.c
index bf4dfc23b96..7fc9161b8c8 100644
--- a/src/bin/pg_rewind/libpq_fetch.c
+++ b/src/bin/pg_rewind/libpq_fetch.c
@@ -465,7 +465,7 @@ libpq_executeFileMap(filemap_t *map)
 		entry = map->array[i];
 
 		/* If this is a relation file, copy the modified blocks */
-		execute_pagemap(&entry->pagemap, entry->path);
+		execute_pagemap(&entry->target_modified_pages, entry->path);
 
 		switch (entry->action)
 		{
@@ -476,15 +476,15 @@ libpq_executeFileMap(filemap_t *map)
 			case FILE_ACTION_COPY:
 				/* Truncate the old file out of the way, if any */
 				open_target_file(entry->path, true);
-				fetch_file_range(entry->path, 0, entry->newsize);
+				fetch_file_range(entry->path, 0, entry->source_size);
 				break;
 
 			case FILE_ACTION_TRUNCATE:
-				truncate_target_file(entry->path, entry->newsize);
+				truncate_target_file(entry->path, entry->source_size);
 				break;
 
 			case FILE_ACTION_COPY_TAIL:
-				fetch_file_range(entry->path, entry->oldsize, entry->newsize);
+				fetch_file_range(entry->path, entry->target_size, entry->source_size);
 				break;
 
 			case FILE_ACTION_REMOVE:
@@ -494,6 +494,10 @@ libpq_executeFileMap(filemap_t *map)
 			case FILE_ACTION_CREATE:
 				create_target(entry);
 				break;
+
+			case FILE_ACTION_UNDECIDED:
+				pg_fatal("no action decided for \"%s\"", entry->path);
+				break;
 		}
 	}
 
diff --git a/src/bin/pg_rewind/parsexlog.c b/src/bin/pg_rewind/parsexlog.c
index 2229c86f9af..2baeb74ae93 100644
--- a/src/bin/pg_rewind/parsexlog.c
+++ b/src/bin/pg_rewind/parsexlog.c
@@ -436,6 +436,6 @@ extractPageInfo(XLogReaderState *record)
 		if (forknum != MAIN_FORKNUM)
 			continue;
 
-		process_block_change(forknum, rnode, blkno);
+		process_target_wal_block_change(forknum, rnode, blkno);
 	}
 }
diff --git a/src/bin/pg_rewind/pg_rewind.c b/src/bin/pg_rewind/pg_rewind.c
index c9b9e480c0f..210984d302b 100644
--- a/src/bin/pg_rewind/pg_rewind.c
+++ b/src/bin/pg_rewind/pg_rewind.c
@@ -369,7 +369,7 @@ main(int argc, char **argv)
 				chkpttli);
 
 	/*
-	 * Build the filemap, by comparing the source and target data directories.
+	 * Collect information about all files in the target and source systems.
 	 */
 	filemap_create();
 	if (showprogress)
@@ -390,8 +390,12 @@ main(int argc, char **argv)
 		pg_log_info("reading WAL in target");
 	extractPageMap(datadir_target, chkptrec, lastcommontliIndex,
 				   ControlFile_target.checkPoint, restore_command);
-	filemap_finalize();
 
+	/*
+	 * We have collected all information we need from both systems. Decide
+	 * what to do with each file.
+	 */
+	filemap_finalize();
 	if (showprogress)
 		calculate_totals();
 
-- 
2.20.1


--------------D93EDEBFB124D563B723F4BD
Content-Type: text/x-patch; charset=UTF-8;
 name="0003-pg_rewind-Replace-the-hybrid-list-array-data-structu.patch"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
 filename*0="0003-pg_rewind-Replace-the-hybrid-list-array-data-structu.pa";
 filename*1="tch"



^ permalink  raw  reply  [nested|flat] 41+ messages in thread

* [PATCH v2 2/5] Refactor pg_rewind for more clear decision making.
@ 2020-08-19 12:34 Heikki Linnakangas <[email protected]>
  0 siblings, 0 replies; 41+ messages in thread

From: Heikki Linnakangas @ 2020-08-19 12:34 UTC (permalink / raw)

Deciding what to do with each file is now a separate step after all the
necessary information has been gathered. It is more clear that way.
Previously, the decision-making was divided between process_source_file()
and process_target_file(), and it was a bit hard to piece together what the
overall rules were.
---
 src/bin/pg_rewind/copy_fetch.c  |  14 +-
 src/bin/pg_rewind/file_ops.c    |  16 +-
 src/bin/pg_rewind/filemap.c     | 569 ++++++++++++++++----------------
 src/bin/pg_rewind/filemap.h     |  67 ++--
 src/bin/pg_rewind/libpq_fetch.c |  12 +-
 src/bin/pg_rewind/parsexlog.c   |   2 +-
 src/bin/pg_rewind/pg_rewind.c   |   8 +-
 7 files changed, 371 insertions(+), 317 deletions(-)

diff --git a/src/bin/pg_rewind/copy_fetch.c b/src/bin/pg_rewind/copy_fetch.c
index 1edab5f1867..18fad32600e 100644
--- a/src/bin/pg_rewind/copy_fetch.c
+++ b/src/bin/pg_rewind/copy_fetch.c
@@ -210,7 +210,7 @@ copy_executeFileMap(filemap_t *map)
 	for (i = 0; i < map->narray; i++)
 	{
 		entry = map->array[i];
-		execute_pagemap(&entry->pagemap, entry->path);
+		execute_pagemap(&entry->target_modified_pages, entry->path);
 
 		switch (entry->action)
 		{
@@ -219,16 +219,16 @@ copy_executeFileMap(filemap_t *map)
 				break;
 
 			case FILE_ACTION_COPY:
-				rewind_copy_file_range(entry->path, 0, entry->newsize, true);
+				rewind_copy_file_range(entry->path, 0, entry->source_size, true);
 				break;
 
 			case FILE_ACTION_TRUNCATE:
-				truncate_target_file(entry->path, entry->newsize);
+				truncate_target_file(entry->path, entry->source_size);
 				break;
 
 			case FILE_ACTION_COPY_TAIL:
-				rewind_copy_file_range(entry->path, entry->oldsize,
-									   entry->newsize, false);
+				rewind_copy_file_range(entry->path, entry->target_size,
+									   entry->source_size, false);
 				break;
 
 			case FILE_ACTION_CREATE:
@@ -238,6 +238,10 @@ copy_executeFileMap(filemap_t *map)
 			case FILE_ACTION_REMOVE:
 				remove_target(entry);
 				break;
+
+			case FILE_ACTION_UNDECIDED:
+				pg_fatal("no action decided for \"%s\"", entry->path);
+				break;
 		}
 	}
 
diff --git a/src/bin/pg_rewind/file_ops.c b/src/bin/pg_rewind/file_ops.c
index 55439db20ba..ec37d0b2e0d 100644
--- a/src/bin/pg_rewind/file_ops.c
+++ b/src/bin/pg_rewind/file_ops.c
@@ -126,8 +126,9 @@ void
 remove_target(file_entry_t *entry)
 {
 	Assert(entry->action == FILE_ACTION_REMOVE);
+	Assert(entry->target_exists);
 
-	switch (entry->type)
+	switch (entry->target_type)
 	{
 		case FILE_TYPE_DIRECTORY:
 			remove_target_dir(entry->path);
@@ -140,6 +141,10 @@ remove_target(file_entry_t *entry)
 		case FILE_TYPE_SYMLINK:
 			remove_target_symlink(entry->path);
 			break;
+
+		case FILE_TYPE_UNDEFINED:
+			pg_fatal("undefined file type for \"%s\"", entry->path);
+			break;
 	}
 }
 
@@ -147,21 +152,26 @@ void
 create_target(file_entry_t *entry)
 {
 	Assert(entry->action == FILE_ACTION_CREATE);
+	Assert(!entry->target_exists);
 
-	switch (entry->type)
+	switch (entry->source_type)
 	{
 		case FILE_TYPE_DIRECTORY:
 			create_target_dir(entry->path);
 			break;
 
 		case FILE_TYPE_SYMLINK:
-			create_target_symlink(entry->path, entry->link_target);
+			create_target_symlink(entry->path, entry->source_link_target);
 			break;
 
 		case FILE_TYPE_REGULAR:
 			/* can't happen. Regular files are created with open_target_file. */
 			pg_fatal("invalid action (CREATE) for regular file");
 			break;
+
+		case FILE_TYPE_UNDEFINED:
+			pg_fatal("undefined file type for \"%s\"", entry->path);
+			break;
 	}
 }
 
diff --git a/src/bin/pg_rewind/filemap.c b/src/bin/pg_rewind/filemap.c
index 1879257b66a..7971daeda5e 100644
--- a/src/bin/pg_rewind/filemap.c
+++ b/src/bin/pg_rewind/filemap.c
@@ -26,6 +26,8 @@ static bool isRelDataFile(const char *path);
 static char *datasegpath(RelFileNode rnode, ForkNumber forknum,
 						 BlockNumber segno);
 static int	path_cmp(const void *a, const void *b);
+
+static file_entry_t *get_filemap_entry(const char *path, bool create);
 static int	final_filemap_cmp(const void *a, const void *b);
 static void filemap_list_to_array(filemap_t *map);
 static bool check_file_excluded(const char *path, bool is_source);
@@ -146,6 +148,65 @@ filemap_create(void)
 	filemap = map;
 }
 
+/* Look up or create entry for 'path' */
+static file_entry_t *
+get_filemap_entry(const char *path, bool create)
+{
+	filemap_t  *map = filemap;
+	file_entry_t *entry;
+	file_entry_t **e;
+	file_entry_t key;
+	file_entry_t *key_ptr;
+
+	if (map->array)
+	{
+		key.path = (char *) path;
+		key_ptr = &key;
+		e = bsearch(&key_ptr, map->array, map->narray, sizeof(file_entry_t *),
+					path_cmp);
+	}
+	else
+		e = NULL;
+
+	if (e)
+		entry = *e;
+	else if (!create)
+		entry = NULL;
+	else
+	{
+		/* Create a new entry for this file */
+		entry = pg_malloc(sizeof(file_entry_t));
+		entry->path = pg_strdup(path);
+		entry->isrelfile = isRelDataFile(path);
+		entry->action = FILE_ACTION_UNDECIDED;
+
+		entry->target_exists = false;
+		entry->target_type = FILE_TYPE_UNDEFINED;
+		entry->target_size = 0;
+		entry->target_link_target = NULL;
+		entry->target_modified_pages.bitmap = NULL;
+		entry->target_modified_pages.bitmapsize = 0;
+
+		entry->source_exists = false;
+		entry->source_type = FILE_TYPE_UNDEFINED;
+		entry->source_size = 0;
+		entry->source_link_target = NULL;
+
+		entry->next = NULL;
+
+		if (map->last)
+		{
+			map->last->next = entry;
+			map->last = entry;
+		}
+		else
+			map->first = map->last = entry;
+		map->nlist++;
+	}
+
+	return entry;
+}
+
 /*
  * Callback for processing source file list.
  *
@@ -154,25 +215,12 @@ filemap_create(void)
  * exists in the target and whether the size matches.
  */
 void
-process_source_file(const char *path, file_type_t type, size_t newsize,
+process_source_file(const char *path, file_type_t type, size_t size,
 					const char *link_target)
 {
-	bool		exists;
-	char		localpath[MAXPGPATH];
-	struct stat statbuf;
-	filemap_t  *map = filemap;
-	file_action_t action = FILE_ACTION_NONE;
-	size_t		oldsize = 0;
 	file_entry_t *entry;
 
-	Assert(map->array == NULL);
-
-	/*
-	 * Skip any files matching the exclusion filters. This has the effect to
-	 * remove all those files on the target.
-	 */
-	if (check_file_excluded(path, true))
-		return;
+	Assert(filemap->array == NULL);
 
 	/*
 	 * Pretend that pg_wal is a directory, even if it's really a symlink. We
@@ -182,16 +230,6 @@ process_source_file(const char *path, file_type_t type, size_t newsize,
 	if (strcmp(path, "pg_wal") == 0 && type == FILE_TYPE_SYMLINK)
 		type = FILE_TYPE_DIRECTORY;
 
-	/*
-	 * Skip temporary files, .../pgsql_tmp/... and .../pgsql_tmp.* in source.
-	 * This has the effect that all temporary files in the destination will be
-	 * removed.
-	 */
-	if (strstr(path, "/" PG_TEMP_FILE_PREFIX) != NULL)
-		return;
-	if (strstr(path, "/" PG_TEMP_FILES_DIR "/") != NULL)
-		return;
-
 	/*
 	 * sanity check: a filename that looks like a data file better be a
 	 * regular file
@@ -199,142 +237,12 @@ process_source_file(const char *path, file_type_t type, size_t newsize,
 	if (type != FILE_TYPE_REGULAR && isRelDataFile(path))
 		pg_fatal("data file \"%s\" in source is not a regular file", path);
 
-	snprintf(localpath, sizeof(localpath), "%s/%s", datadir_target, path);
-
-	/* Does the corresponding file exist in the target data dir? */
-	if (lstat(localpath, &statbuf) < 0)
-	{
-		if (errno != ENOENT)
-			pg_fatal("could not stat file \"%s\": %m",
-					 localpath);
-
-		exists = false;
-	}
-	else
-		exists = true;
-
-	switch (type)
-	{
-		case FILE_TYPE_DIRECTORY:
-			if (exists && !S_ISDIR(statbuf.st_mode) && strcmp(path, "pg_wal") != 0)
-			{
-				/* it's a directory in source, but not in target. Strange.. */
-				pg_fatal("\"%s\" is not a directory", localpath);
-			}
-
-			if (!exists)
-				action = FILE_ACTION_CREATE;
-			else
-				action = FILE_ACTION_NONE;
-			oldsize = 0;
-			break;
-
-		case FILE_TYPE_SYMLINK:
-			if (exists &&
-#ifndef WIN32
-				!S_ISLNK(statbuf.st_mode)
-#else
-				!pgwin32_is_junction(localpath)
-#endif
-				)
-			{
-				/*
-				 * It's a symbolic link in source, but not in target.
-				 * Strange..
-				 */
-				pg_fatal("\"%s\" is not a symbolic link", localpath);
-			}
-
-			if (!exists)
-				action = FILE_ACTION_CREATE;
-			else
-				action = FILE_ACTION_NONE;
-			oldsize = 0;
-			break;
-
-		case FILE_TYPE_REGULAR:
-			if (exists && !S_ISREG(statbuf.st_mode))
-				pg_fatal("\"%s\" is not a regular file", localpath);
-
-			if (!exists || !isRelDataFile(path))
-			{
-				/*
-				 * File exists in source, but not in target. Or it's a
-				 * non-data file that we have no special processing for. Copy
-				 * it in toto.
-				 *
-				 * An exception: PG_VERSIONs should be identical, but avoid
-				 * overwriting it for paranoia.
-				 */
-				if (pg_str_endswith(path, "PG_VERSION"))
-				{
-					action = FILE_ACTION_NONE;
-					oldsize = statbuf.st_size;
-				}
-				else
-				{
-					action = FILE_ACTION_COPY;
-					oldsize = 0;
-				}
-			}
-			else
-			{
-				/*
-				 * It's a data file that exists in both.
-				 *
-				 * If it's larger in target, we can truncate it. There will
-				 * also be a WAL record of the truncation in the source
-				 * system, so WAL replay would eventually truncate the target
-				 * too, but we might as well do it now.
-				 *
-				 * If it's smaller in the target, it means that it has been
-				 * truncated in the target, or enlarged in the source, or
-				 * both. If it was truncated in the target, we need to copy
-				 * the missing tail from the source system. If it was enlarged
-				 * in the source system, there will be WAL records in the
-				 * source system for the new blocks, so we wouldn't need to
-				 * copy them here. But we don't know which scenario we're
-				 * dealing with, and there's no harm in copying the missing
-				 * blocks now, so do it now.
-				 *
-				 * If it's the same size, do nothing here. Any blocks modified
-				 * in the target will be copied based on parsing the target
-				 * system's WAL, and any blocks modified in the source will be
-				 * updated after rewinding, when the source system's WAL is
-				 * replayed.
-				 */
-				oldsize = statbuf.st_size;
-				if (oldsize < newsize)
-					action = FILE_ACTION_COPY_TAIL;
-				else if (oldsize > newsize)
-					action = FILE_ACTION_TRUNCATE;
-				else
-					action = FILE_ACTION_NONE;
-			}
-			break;
-	}
-
-	/* Create a new entry for this file */
-	entry = pg_malloc(sizeof(file_entry_t));
-	entry->path = pg_strdup(path);
-	entry->type = type;
-	entry->action = action;
-	entry->oldsize = oldsize;
-	entry->newsize = newsize;
-	entry->link_target = link_target ? pg_strdup(link_target) : NULL;
-	entry->next = NULL;
-	entry->pagemap.bitmap = NULL;
-	entry->pagemap.bitmapsize = 0;
-	entry->isrelfile = isRelDataFile(path);
-
-	if (map->last)
-	{
-		map->last->next = entry;
-		map->last = entry;
-	}
-	else
-		map->first = map->last = entry;
-	map->nlist++;
+	/* Remember this source file */
+	entry = get_filemap_entry(path, true);
+	entry->source_exists = true;
+	entry->source_type = type;
+	entry->source_size = size;
+	entry->source_link_target = link_target ? pg_strdup(link_target) : NULL;
 }
 
 /*
@@ -345,14 +253,9 @@ process_source_file(const char *path, file_type_t type, size_t newsize,
  * deletion.
  */
 void
-process_target_file(const char *path, file_type_t type, size_t oldsize,
+process_target_file(const char *path, file_type_t type, size_t size,
 					const char *link_target)
 {
-	bool		exists;
-	char		localpath[MAXPGPATH];
-	struct stat statbuf;
-	file_entry_t key;
-	file_entry_t *key_ptr;
 	filemap_t  *map = filemap;
 	file_entry_t *entry;
 
@@ -361,17 +264,6 @@ process_target_file(const char *path, file_type_t type, size_t oldsize,
 	 * from the target data folder all paths which have been filtered out from
 	 * the source data folder when processing the source files.
 	 */
-
-	snprintf(localpath, sizeof(localpath), "%s/%s", datadir_target, path);
-	if (lstat(localpath, &statbuf) < 0)
-	{
-		if (errno != ENOENT)
-			pg_fatal("could not stat file \"%s\": %m",
-					 localpath);
-
-		exists = false;
-	}
-
 	if (map->array == NULL)
 	{
 		/* on first call, initialize lookup array */
@@ -389,120 +281,76 @@ process_target_file(const char *path, file_type_t type, size_t oldsize,
 	}
 
 	/*
-	 * Like in process_source_file, pretend that xlog is always a  directory.
+	 * Like in process_source_file, pretend that pg_wal is always a directory.
 	 */
 	if (strcmp(path, "pg_wal") == 0 && type == FILE_TYPE_SYMLINK)
 		type = FILE_TYPE_DIRECTORY;
 
-	key.path = (char *) path;
-	key_ptr = &key;
-	exists = (bsearch(&key_ptr, map->array, map->narray, sizeof(file_entry_t *),
-					  path_cmp) != NULL);
-
-	/* Remove any file or folder that doesn't exist in the source system. */
-	if (!exists)
-	{
-		entry = pg_malloc(sizeof(file_entry_t));
-		entry->path = pg_strdup(path);
-		entry->type = type;
-		entry->action = FILE_ACTION_REMOVE;
-		entry->oldsize = oldsize;
-		entry->newsize = 0;
-		entry->link_target = link_target ? pg_strdup(link_target) : NULL;
-		entry->next = NULL;
-		entry->pagemap.bitmap = NULL;
-		entry->pagemap.bitmapsize = 0;
-		entry->isrelfile = isRelDataFile(path);
-
-		if (map->last == NULL)
-			map->first = entry;
-		else
-			map->last->next = entry;
-		map->last = entry;
-		map->nlist++;
-	}
-	else
-	{
-		/*
-		 * We already handled all files that exist in the source system in
-		 * process_source_file().
-		 */
-	}
+	/* Remember this target file */
+	entry = get_filemap_entry(path, true);
+	entry->target_exists = true;
+	entry->target_type = type;
+	entry->target_size = size;
+	entry->target_link_target = link_target ? pg_strdup(link_target) : NULL;
 }
 
 /*
  * This callback gets called while we read the WAL in the target, for every
- * block that have changed in the target system. It makes note of all the
+ * block that have changed in the target system.  It makes note of all the
  * changed blocks in the pagemap of the file.
+ *
+ * NOTE: All the files on both systems must have already been added to the
+ * file map!
  */
 void
-process_block_change(ForkNumber forknum, RelFileNode rnode, BlockNumber blkno)
+process_target_wal_block_change(ForkNumber forknum, RelFileNode rnode,
+								BlockNumber blkno)
 {
 	char	   *path;
-	file_entry_t key;
-	file_entry_t *key_ptr;
 	file_entry_t *entry;
 	BlockNumber blkno_inseg;
 	int			segno;
-	filemap_t  *map = filemap;
-	file_entry_t **e;
 
-	Assert(map->array);
+	Assert(filemap->array);
 
 	segno = blkno / RELSEG_SIZE;
 	blkno_inseg = blkno % RELSEG_SIZE;
 
 	path = datasegpath(rnode, forknum, segno);
-
-	key.path = (char *) path;
-	key_ptr = &key;
-
-	e = bsearch(&key_ptr, map->array, map->narray, sizeof(file_entry_t *),
-				path_cmp);
-	if (e)
-		entry = *e;
-	else
-		entry = NULL;
+	entry = get_filemap_entry(path, false);
 	pfree(path);
 
 	if (entry)
 	{
-		Assert(entry->isrelfile);
-
-		switch (entry->action)
-		{
-			case FILE_ACTION_NONE:
-			case FILE_ACTION_TRUNCATE:
-				/* skip if we're truncating away the modified block anyway */
-				if ((blkno_inseg + 1) * BLCKSZ <= entry->newsize)
-					datapagemap_add(&entry->pagemap, blkno_inseg);
-				break;
+		int64		end_offset;
 
-			case FILE_ACTION_COPY_TAIL:
-
-				/*
-				 * skip the modified block if it is part of the "tail" that
-				 * we're copying anyway.
-				 */
-				if ((blkno_inseg + 1) * BLCKSZ <= entry->oldsize)
-					datapagemap_add(&entry->pagemap, blkno_inseg);
-				break;
+		Assert(entry->isrelfile);
 
-			case FILE_ACTION_COPY:
-			case FILE_ACTION_REMOVE:
-				break;
+		if (entry->target_type != FILE_TYPE_REGULAR)
+			pg_fatal("unexpected page modification for directory or symbolic link \"%s\"",
+					 entry->path);
 
-			case FILE_ACTION_CREATE:
-				pg_fatal("unexpected page modification for directory or symbolic link \"%s\"", entry->path);
-		}
+		/*
+		 * If the block beyond the EOF in the source system, no need to
+		 * remember it now, because we're going to truncate it away from the
+		 * target anyway. Also no need to remember the block if it's beyond
+		 * the current EOF in the target system; we will copy it over with the
+		 * "tail" from the source system, anyway.
+		 */
+		end_offset = (blkno_inseg + 1) * BLCKSZ;
+		if (end_offset <= entry->source_size &&
+			end_offset <= entry->target_size)
+			datapagemap_add(&entry->target_modified_pages, blkno_inseg);
 	}
 	else
 	{
 		/*
 		 * If we don't have any record of this file in the file map, it means
-		 * that it's a relation that doesn't exist in the source system, and
-		 * it was subsequently removed in the target system, too. We can
-		 * safely ignore it.
+		 * that it's a relation that doesn't exist in the source system.  It
+		 * could exist in the target system; we haven't moved the target-only
+		 * entries from the linked list to the array yet!  But in any case, if
+		 * it doesn't exist in the source it will be removed from the target
+		 * too, and we can safely ignore it.
 		 */
 	}
 }
@@ -593,16 +441,6 @@ filemap_list_to_array(filemap_t *map)
 	map->first = map->last = NULL;
 }
 
-void
-filemap_finalize(void)
-{
-	filemap_t  *map = filemap;
-
-	filemap_list_to_array(map);
-	qsort(map->array, map->narray, sizeof(file_entry_t *),
-		  final_filemap_cmp);
-}
-
 static const char *
 action_to_str(file_action_t action)
 {
@@ -643,26 +481,26 @@ calculate_totals(void)
 	{
 		entry = map->array[i];
 
-		if (entry->type != FILE_TYPE_REGULAR)
+		if (entry->source_type != FILE_TYPE_REGULAR)
 			continue;
 
-		map->total_size += entry->newsize;
+		map->total_size += entry->source_size;
 
 		if (entry->action == FILE_ACTION_COPY)
 		{
-			map->fetch_size += entry->newsize;
+			map->fetch_size += entry->source_size;
 			continue;
 		}
 
 		if (entry->action == FILE_ACTION_COPY_TAIL)
-			map->fetch_size += (entry->newsize - entry->oldsize);
+			map->fetch_size += (entry->source_size - entry->target_size);
 
-		if (entry->pagemap.bitmapsize > 0)
+		if (entry->target_modified_pages.bitmapsize > 0)
 		{
 			datapagemap_iterator_t *iter;
 			BlockNumber blk;
 
-			iter = datapagemap_iterate(&entry->pagemap);
+			iter = datapagemap_iterate(&entry->target_modified_pages);
 			while (datapagemap_next(iter, &blk))
 				map->fetch_size += BLCKSZ;
 
@@ -682,13 +520,13 @@ print_filemap(void)
 	{
 		entry = map->array[i];
 		if (entry->action != FILE_ACTION_NONE ||
-			entry->pagemap.bitmapsize > 0)
+			entry->target_modified_pages.bitmapsize > 0)
 		{
 			pg_log_debug("%s (%s)", entry->path,
 						 action_to_str(entry->action));
 
-			if (entry->pagemap.bitmapsize > 0)
-				datapagemap_print(&entry->pagemap);
+			if (entry->target_modified_pages.bitmapsize > 0)
+				datapagemap_print(&entry->target_modified_pages);
 		}
 	}
 	fflush(stdout);
@@ -837,3 +675,180 @@ final_filemap_cmp(const void *a, const void *b)
 	else
 		return strcmp(fa->path, fb->path);
 }
+
+/*
+ * Decide what action to perform to a file.
+ */
+static file_action_t
+decide_file_action(file_entry_t *entry)
+{
+	const char *path = entry->path;
+
+	/*
+	 * Don't touch the control file. It is handled specially, after copying
+	 * all the other files.
+	 */
+	if (strcmp(path, "global/pg_control") == 0)
+		return FILE_ACTION_NONE;
+
+	/*
+	 * Remove all files matching the exclusion filters in the target.
+	 */
+	if (check_file_excluded(path, true))
+	{
+		if (entry->target_exists)
+			return FILE_ACTION_REMOVE;
+		else
+			return FILE_ACTION_NONE;
+	}
+
+	/*
+	 * Also remove all temporary files, .../pgsql_tmp/... and .../pgsql_tmp.*
+	 * in the target.
+	 */
+	if (strstr(path, "/" PG_TEMP_FILE_PREFIX) != NULL ||
+		strstr(path, "/" PG_TEMP_FILES_DIR "/") != NULL)
+	{
+		if (entry->target_exists)
+			return FILE_ACTION_REMOVE;
+		else
+			return FILE_ACTION_NONE;
+	}
+
+	/*
+	 * Handle cases where the file is missing from one of the systems.
+	 */
+	if (!entry->target_exists && entry->source_exists)
+	{
+		/*
+		 * File exists in source, but not in target. Copy it in toto. (If it's
+		 * a relation data file, WAL replay after rewinding should re-create
+		 * it anyway. But there's no harm in copying it now.)
+		 */
+		switch (entry->source_type)
+		{
+			case FILE_TYPE_DIRECTORY:
+			case FILE_TYPE_SYMLINK:
+				return FILE_ACTION_CREATE;
+			case FILE_TYPE_REGULAR:
+				return FILE_ACTION_COPY;
+			case FILE_TYPE_UNDEFINED:
+				pg_fatal("unknown file type for \"%s\"", entry->path);
+				break;
+		}
+	}
+	else if (entry->target_exists && !entry->source_exists)
+	{
+		/* File exists in target, but not source. Remove it. */
+		return FILE_ACTION_REMOVE;
+	}
+	else if (!entry->target_exists && !entry->source_exists)
+	{
+		/*
+		 * Doesn't exist in either server. Why does it have an entry in the
+		 * first place??
+		 */
+		return FILE_ACTION_NONE;
+	}
+
+	/*
+	 * Otherwise, the file exists on both systems
+	 */
+	Assert(entry->target_exists && entry->source_exists);
+
+	if (entry->source_type != entry->target_type)
+	{
+		/* But it's a different kind of object. Strange.. */
+		pg_fatal("file \"%s\" is of different type in source and target", entry->path);
+	}
+
+	/*
+	 * PG_VERSION files should be identical on both systems, but avoid
+	 * overwriting them for paranoia.
+	 */
+	if (pg_str_endswith(entry->path, "PG_VERSION"))
+		return FILE_ACTION_NONE;
+
+	switch (entry->source_type)
+	{
+		case FILE_TYPE_DIRECTORY:
+			return FILE_ACTION_NONE;
+
+		case FILE_TYPE_SYMLINK:
+			/* FIXME: Check if it points to the same target? */
+			return FILE_ACTION_NONE;
+
+		case FILE_TYPE_REGULAR:
+			if (!entry->isrelfile)
+			{
+				/*
+				 * It's a non-data file that we have no special processing
+				 * for. Copy it in toto.
+				 */
+				return FILE_ACTION_COPY;
+			}
+			else
+			{
+				/*
+				 * It's a data file that exists in both systems.
+				 *
+				 * If it's larger in target, we can truncate it. There will
+				 * also be a WAL record of the truncation in the source
+				 * system, so WAL replay would eventually truncate the target
+				 * too, but we might as well do it now.
+				 *
+				 * If it's smaller in the target, it means that it has been
+				 * truncated in the target, or enlarged in the source, or
+				 * both. If it was truncated in the target, we need to copy
+				 * the missing tail from the source system. If it was enlarged
+				 * in the source system, there will be WAL records in the
+				 * source system for the new blocks, so we wouldn't need to
+				 * copy them here. But we don't know which scenario we're
+				 * dealing with, and there's no harm in copying the missing
+				 * blocks now, so do it now.
+				 *
+				 * If it's the same size, do nothing here. Any blocks modified
+				 * in the target will be copied based on parsing the target
+				 * system's WAL, and any blocks modified in the source will be
+				 * updated after rewinding, when the source system's WAL is
+				 * replayed.
+				 */
+				if (entry->target_size < entry->source_size)
+					return FILE_ACTION_COPY_TAIL;
+				else if (entry->target_size > entry->source_size)
+					return FILE_ACTION_TRUNCATE;
+				else
+					return FILE_ACTION_NONE;
+			}
+			break;
+
+		case FILE_TYPE_UNDEFINED:
+			pg_fatal("unknown file type for \"%s\"", path);
+			break;
+	}
+
+	/* unreachable */
+	pg_fatal("could not decide what to do with file \"%s\"", path);
+}
+
+/*
+ * Decide what to do with each file.
+ */
+void
+filemap_finalize()
+{
+	int			i;
+
+	filemap_list_to_array(filemap);
+
+	for (i = 0; i < filemap->narray; i++)
+	{
+		file_entry_t *entry = filemap->array[i];
+
+		entry->action = decide_file_action(entry);
+	}
+
+	/* Sort the actions to the order that they should be performed */
+	qsort(filemap->array, filemap->narray, sizeof(file_entry_t *),
+		  final_filemap_cmp);
+}
diff --git a/src/bin/pg_rewind/filemap.h b/src/bin/pg_rewind/filemap.h
index 0cb7425170c..a5e8df57f40 100644
--- a/src/bin/pg_rewind/filemap.h
+++ b/src/bin/pg_rewind/filemap.h
@@ -14,17 +14,21 @@
 
 /*
  * For every file found in the local or remote system, we have a file entry
- * which says what we are going to do with the file. For relation files,
- * there is also a page map, marking pages in the file that were changed
- * locally.
- *
- * The enum values are sorted in the order we want actions to be processed.
+ * that contains information about the file on both systems.  For relation
+ * files, there is also a page map that marks pages in the file that were
+ * changed in the target after the last common checkpoint.  Each entry also
+ * contains an 'action' field, which says what we are going to do with the
+ * file.
  */
+
+/* these enum values are sorted in the order we want actions to be processed */
 typedef enum
 {
+	FILE_ACTION_UNDECIDED = 0,	/* not decided yet */
+
 	FILE_ACTION_CREATE,			/* create local directory or symbolic link */
 	FILE_ACTION_COPY,			/* copy whole file, overwriting if exists */
-	FILE_ACTION_COPY_TAIL,		/* copy tail from 'oldsize' to 'newsize' */
+	FILE_ACTION_COPY_TAIL,		/* copy tail from 'source_size' to 'target_size' */
 	FILE_ACTION_NONE,			/* no action (we might still copy modified
 								 * blocks based on the parsed WAL) */
 	FILE_ACTION_TRUNCATE,		/* truncate local file to 'newsize' bytes */
@@ -33,6 +37,8 @@ typedef enum
 
 typedef enum
 {
+	FILE_TYPE_UNDEFINED = 0,
+
 	FILE_TYPE_REGULAR,
 	FILE_TYPE_DIRECTORY,
 	FILE_TYPE_SYMLINK
@@ -41,19 +47,30 @@ typedef enum
 typedef struct file_entry_t
 {
 	char	   *path;
-	file_type_t type;
+	bool		isrelfile;		/* is it a relation data file? */
 
-	file_action_t action;
+	/*
+	 * Status of the file in the target.
+	 */
+	bool		target_exists;
+	file_type_t target_type;
+	size_t		target_size; /* for a regular file */
+	char	   *target_link_target; /* for a symlink */
 
-	/* for a regular file */
-	size_t		oldsize;
-	size_t		newsize;
-	bool		isrelfile;		/* is it a relation data file? */
+	datapagemap_t target_modified_pages;
 
-	datapagemap_t pagemap;
+	/*
+	 * Status of the file in the source.
+	 */
+	bool		source_exists;
+	file_type_t source_type;
+	size_t		source_size;
+	char	   *source_link_target; /* for a symlink */
 
-	/* for a symlink */
-	char	   *link_target;
+	/*
+	 * What will we do to the file?
+	 */
+	file_action_t action;
 
 	struct file_entry_t *next;
 } file_entry_t;
@@ -70,20 +87,19 @@ typedef struct filemap_t
 
 	/*
 	 * After processing all the remote files, the entries in the linked list
-	 * are moved to this array. After processing local files, too, all the
+	 * are moved to this array.  After processing local files, too, all the
 	 * local entries are added to the array by filemap_finalize, and sorted in
-	 * the final order. After filemap_finalize, all the entries are in the
+	 * the final order.  After filemap_finalize, all the entries are in the
 	 * array, and the linked list is empty.
 	 */
 	file_entry_t **array;
 	int			narray;			/* current length of array */
 
 	/*
-	 * Summary information. total_size is the total size of the source
-	 * cluster, and fetch_size is the number of bytes that needs to be copied.
+	 * Summary information.
 	 */
-	uint64		total_size;
-	uint64		fetch_size;
+	uint64		total_size;		/* total size of the source cluster */
+	uint64		fetch_size;		/* number of bytes that needs to be copied */
 } filemap_t;
 
 extern filemap_t *filemap;
@@ -94,11 +110,12 @@ extern void print_filemap(void);
 
 /* Functions for populating the filemap */
 extern void process_source_file(const char *path, file_type_t type,
-								size_t newsize, const char *link_target);
+								size_t size, const char *link_target);
 extern void process_target_file(const char *path, file_type_t type,
-								size_t newsize, const char *link_target);
-extern void process_block_change(ForkNumber forknum, RelFileNode rnode,
-								 BlockNumber blkno);
+								size_t size, const char *link_target);
+extern void process_target_wal_block_change(ForkNumber forknum,
+											RelFileNode rnode,
+											BlockNumber blkno);
 extern void filemap_finalize(void);
 
 #endif							/* FILEMAP_H */
diff --git a/src/bin/pg_rewind/libpq_fetch.c b/src/bin/pg_rewind/libpq_fetch.c
index bf4dfc23b96..7fc9161b8c8 100644
--- a/src/bin/pg_rewind/libpq_fetch.c
+++ b/src/bin/pg_rewind/libpq_fetch.c
@@ -465,7 +465,7 @@ libpq_executeFileMap(filemap_t *map)
 		entry = map->array[i];
 
 		/* If this is a relation file, copy the modified blocks */
-		execute_pagemap(&entry->pagemap, entry->path);
+		execute_pagemap(&entry->target_modified_pages, entry->path);
 
 		switch (entry->action)
 		{
@@ -476,15 +476,15 @@ libpq_executeFileMap(filemap_t *map)
 			case FILE_ACTION_COPY:
 				/* Truncate the old file out of the way, if any */
 				open_target_file(entry->path, true);
-				fetch_file_range(entry->path, 0, entry->newsize);
+				fetch_file_range(entry->path, 0, entry->source_size);
 				break;
 
 			case FILE_ACTION_TRUNCATE:
-				truncate_target_file(entry->path, entry->newsize);
+				truncate_target_file(entry->path, entry->source_size);
 				break;
 
 			case FILE_ACTION_COPY_TAIL:
-				fetch_file_range(entry->path, entry->oldsize, entry->newsize);
+				fetch_file_range(entry->path, entry->target_size, entry->source_size);
 				break;
 
 			case FILE_ACTION_REMOVE:
@@ -494,6 +494,10 @@ libpq_executeFileMap(filemap_t *map)
 			case FILE_ACTION_CREATE:
 				create_target(entry);
 				break;
+
+			case FILE_ACTION_UNDECIDED:
+				pg_fatal("no action decided for \"%s\"", entry->path);
+				break;
 		}
 	}
 
diff --git a/src/bin/pg_rewind/parsexlog.c b/src/bin/pg_rewind/parsexlog.c
index 2229c86f9af..2baeb74ae93 100644
--- a/src/bin/pg_rewind/parsexlog.c
+++ b/src/bin/pg_rewind/parsexlog.c
@@ -436,6 +436,6 @@ extractPageInfo(XLogReaderState *record)
 		if (forknum != MAIN_FORKNUM)
 			continue;
 
-		process_block_change(forknum, rnode, blkno);
+		process_target_wal_block_change(forknum, rnode, blkno);
 	}
 }
diff --git a/src/bin/pg_rewind/pg_rewind.c b/src/bin/pg_rewind/pg_rewind.c
index c9b9e480c0f..210984d302b 100644
--- a/src/bin/pg_rewind/pg_rewind.c
+++ b/src/bin/pg_rewind/pg_rewind.c
@@ -369,7 +369,7 @@ main(int argc, char **argv)
 				chkpttli);
 
 	/*
-	 * Build the filemap, by comparing the source and target data directories.
+	 * Collect information about all files in the target and source systems.
 	 */
 	filemap_create();
 	if (showprogress)
@@ -390,8 +390,12 @@ main(int argc, char **argv)
 		pg_log_info("reading WAL in target");
 	extractPageMap(datadir_target, chkptrec, lastcommontliIndex,
 				   ControlFile_target.checkPoint, restore_command);
-	filemap_finalize();
 
+	/*
+	 * We have collected all information we need from both systems. Decide
+	 * what to do with each file.
+	 */
+	filemap_finalize();
 	if (showprogress)
 		calculate_totals();
 
-- 
2.20.1


--------------BF34D0120055DC3839060F92
Content-Type: text/x-patch; charset=UTF-8;
 name="v2-0003-pg_rewind-Replace-the-hybrid-list-array-data-stru.patch"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
 filename*0="v2-0003-pg_rewind-Replace-the-hybrid-list-array-data-stru.pa";
 filename*1="tch"



^ permalink  raw  reply  [nested|flat] 41+ messages in thread

* [PATCH v2 2/5] Refactor pg_rewind for more clear decision making.
@ 2020-08-19 12:34 Heikki Linnakangas <[email protected]>
  0 siblings, 0 replies; 41+ messages in thread

From: Heikki Linnakangas @ 2020-08-19 12:34 UTC (permalink / raw)

Deciding what to do with each file is now a separate step after all the
necessary information has been gathered. It is more clear that way.
Previously, the decision-making was divided between process_source_file()
and process_target_file(), and it was a bit hard to piece together what the
overall rules were.
---
 src/bin/pg_rewind/copy_fetch.c  |  14 +-
 src/bin/pg_rewind/file_ops.c    |  16 +-
 src/bin/pg_rewind/filemap.c     | 569 ++++++++++++++++----------------
 src/bin/pg_rewind/filemap.h     |  67 ++--
 src/bin/pg_rewind/libpq_fetch.c |  12 +-
 src/bin/pg_rewind/parsexlog.c   |   2 +-
 src/bin/pg_rewind/pg_rewind.c   |   8 +-
 7 files changed, 371 insertions(+), 317 deletions(-)

diff --git a/src/bin/pg_rewind/copy_fetch.c b/src/bin/pg_rewind/copy_fetch.c
index 1edab5f1867..18fad32600e 100644
--- a/src/bin/pg_rewind/copy_fetch.c
+++ b/src/bin/pg_rewind/copy_fetch.c
@@ -210,7 +210,7 @@ copy_executeFileMap(filemap_t *map)
 	for (i = 0; i < map->narray; i++)
 	{
 		entry = map->array[i];
-		execute_pagemap(&entry->pagemap, entry->path);
+		execute_pagemap(&entry->target_modified_pages, entry->path);
 
 		switch (entry->action)
 		{
@@ -219,16 +219,16 @@ copy_executeFileMap(filemap_t *map)
 				break;
 
 			case FILE_ACTION_COPY:
-				rewind_copy_file_range(entry->path, 0, entry->newsize, true);
+				rewind_copy_file_range(entry->path, 0, entry->source_size, true);
 				break;
 
 			case FILE_ACTION_TRUNCATE:
-				truncate_target_file(entry->path, entry->newsize);
+				truncate_target_file(entry->path, entry->source_size);
 				break;
 
 			case FILE_ACTION_COPY_TAIL:
-				rewind_copy_file_range(entry->path, entry->oldsize,
-									   entry->newsize, false);
+				rewind_copy_file_range(entry->path, entry->target_size,
+									   entry->source_size, false);
 				break;
 
 			case FILE_ACTION_CREATE:
@@ -238,6 +238,10 @@ copy_executeFileMap(filemap_t *map)
 			case FILE_ACTION_REMOVE:
 				remove_target(entry);
 				break;
+
+			case FILE_ACTION_UNDECIDED:
+				pg_fatal("no action decided for \"%s\"", entry->path);
+				break;
 		}
 	}
 
diff --git a/src/bin/pg_rewind/file_ops.c b/src/bin/pg_rewind/file_ops.c
index 55439db20ba..ec37d0b2e0d 100644
--- a/src/bin/pg_rewind/file_ops.c
+++ b/src/bin/pg_rewind/file_ops.c
@@ -126,8 +126,9 @@ void
 remove_target(file_entry_t *entry)
 {
 	Assert(entry->action == FILE_ACTION_REMOVE);
+	Assert(entry->target_exists);
 
-	switch (entry->type)
+	switch (entry->target_type)
 	{
 		case FILE_TYPE_DIRECTORY:
 			remove_target_dir(entry->path);
@@ -140,6 +141,10 @@ remove_target(file_entry_t *entry)
 		case FILE_TYPE_SYMLINK:
 			remove_target_symlink(entry->path);
 			break;
+
+		case FILE_TYPE_UNDEFINED:
+			pg_fatal("undefined file type for \"%s\"", entry->path);
+			break;
 	}
 }
 
@@ -147,21 +152,26 @@ void
 create_target(file_entry_t *entry)
 {
 	Assert(entry->action == FILE_ACTION_CREATE);
+	Assert(!entry->target_exists);
 
-	switch (entry->type)
+	switch (entry->source_type)
 	{
 		case FILE_TYPE_DIRECTORY:
 			create_target_dir(entry->path);
 			break;
 
 		case FILE_TYPE_SYMLINK:
-			create_target_symlink(entry->path, entry->link_target);
+			create_target_symlink(entry->path, entry->source_link_target);
 			break;
 
 		case FILE_TYPE_REGULAR:
 			/* can't happen. Regular files are created with open_target_file. */
 			pg_fatal("invalid action (CREATE) for regular file");
 			break;
+
+		case FILE_TYPE_UNDEFINED:
+			pg_fatal("undefined file type for \"%s\"", entry->path);
+			break;
 	}
 }
 
diff --git a/src/bin/pg_rewind/filemap.c b/src/bin/pg_rewind/filemap.c
index 1879257b66a..7971daeda5e 100644
--- a/src/bin/pg_rewind/filemap.c
+++ b/src/bin/pg_rewind/filemap.c
@@ -26,6 +26,8 @@ static bool isRelDataFile(const char *path);
 static char *datasegpath(RelFileNode rnode, ForkNumber forknum,
 						 BlockNumber segno);
 static int	path_cmp(const void *a, const void *b);
+
+static file_entry_t *get_filemap_entry(const char *path, bool create);
 static int	final_filemap_cmp(const void *a, const void *b);
 static void filemap_list_to_array(filemap_t *map);
 static bool check_file_excluded(const char *path, bool is_source);
@@ -146,6 +148,65 @@ filemap_create(void)
 	filemap = map;
 }
 
+/* Look up or create entry for 'path' */
+static file_entry_t *
+get_filemap_entry(const char *path, bool create)
+{
+	filemap_t  *map = filemap;
+	file_entry_t *entry;
+	file_entry_t **e;
+	file_entry_t key;
+	file_entry_t *key_ptr;
+
+	if (map->array)
+	{
+		key.path = (char *) path;
+		key_ptr = &key;
+		e = bsearch(&key_ptr, map->array, map->narray, sizeof(file_entry_t *),
+					path_cmp);
+	}
+	else
+		e = NULL;
+
+	if (e)
+		entry = *e;
+	else if (!create)
+		entry = NULL;
+	else
+	{
+		/* Create a new entry for this file */
+		entry = pg_malloc(sizeof(file_entry_t));
+		entry->path = pg_strdup(path);
+		entry->isrelfile = isRelDataFile(path);
+		entry->action = FILE_ACTION_UNDECIDED;
+
+		entry->target_exists = false;
+		entry->target_type = FILE_TYPE_UNDEFINED;
+		entry->target_size = 0;
+		entry->target_link_target = NULL;
+		entry->target_modified_pages.bitmap = NULL;
+		entry->target_modified_pages.bitmapsize = 0;
+
+		entry->source_exists = false;
+		entry->source_type = FILE_TYPE_UNDEFINED;
+		entry->source_size = 0;
+		entry->source_link_target = NULL;
+
+		entry->next = NULL;
+
+		if (map->last)
+		{
+			map->last->next = entry;
+			map->last = entry;
+		}
+		else
+			map->first = map->last = entry;
+		map->nlist++;
+	}
+
+	return entry;
+}
+
 /*
  * Callback for processing source file list.
  *
@@ -154,25 +215,12 @@ filemap_create(void)
  * exists in the target and whether the size matches.
  */
 void
-process_source_file(const char *path, file_type_t type, size_t newsize,
+process_source_file(const char *path, file_type_t type, size_t size,
 					const char *link_target)
 {
-	bool		exists;
-	char		localpath[MAXPGPATH];
-	struct stat statbuf;
-	filemap_t  *map = filemap;
-	file_action_t action = FILE_ACTION_NONE;
-	size_t		oldsize = 0;
 	file_entry_t *entry;
 
-	Assert(map->array == NULL);
-
-	/*
-	 * Skip any files matching the exclusion filters. This has the effect to
-	 * remove all those files on the target.
-	 */
-	if (check_file_excluded(path, true))
-		return;
+	Assert(filemap->array == NULL);
 
 	/*
 	 * Pretend that pg_wal is a directory, even if it's really a symlink. We
@@ -182,16 +230,6 @@ process_source_file(const char *path, file_type_t type, size_t newsize,
 	if (strcmp(path, "pg_wal") == 0 && type == FILE_TYPE_SYMLINK)
 		type = FILE_TYPE_DIRECTORY;
 
-	/*
-	 * Skip temporary files, .../pgsql_tmp/... and .../pgsql_tmp.* in source.
-	 * This has the effect that all temporary files in the destination will be
-	 * removed.
-	 */
-	if (strstr(path, "/" PG_TEMP_FILE_PREFIX) != NULL)
-		return;
-	if (strstr(path, "/" PG_TEMP_FILES_DIR "/") != NULL)
-		return;
-
 	/*
 	 * sanity check: a filename that looks like a data file better be a
 	 * regular file
@@ -199,142 +237,12 @@ process_source_file(const char *path, file_type_t type, size_t newsize,
 	if (type != FILE_TYPE_REGULAR && isRelDataFile(path))
 		pg_fatal("data file \"%s\" in source is not a regular file", path);
 
-	snprintf(localpath, sizeof(localpath), "%s/%s", datadir_target, path);
-
-	/* Does the corresponding file exist in the target data dir? */
-	if (lstat(localpath, &statbuf) < 0)
-	{
-		if (errno != ENOENT)
-			pg_fatal("could not stat file \"%s\": %m",
-					 localpath);
-
-		exists = false;
-	}
-	else
-		exists = true;
-
-	switch (type)
-	{
-		case FILE_TYPE_DIRECTORY:
-			if (exists && !S_ISDIR(statbuf.st_mode) && strcmp(path, "pg_wal") != 0)
-			{
-				/* it's a directory in source, but not in target. Strange.. */
-				pg_fatal("\"%s\" is not a directory", localpath);
-			}
-
-			if (!exists)
-				action = FILE_ACTION_CREATE;
-			else
-				action = FILE_ACTION_NONE;
-			oldsize = 0;
-			break;
-
-		case FILE_TYPE_SYMLINK:
-			if (exists &&
-#ifndef WIN32
-				!S_ISLNK(statbuf.st_mode)
-#else
-				!pgwin32_is_junction(localpath)
-#endif
-				)
-			{
-				/*
-				 * It's a symbolic link in source, but not in target.
-				 * Strange..
-				 */
-				pg_fatal("\"%s\" is not a symbolic link", localpath);
-			}
-
-			if (!exists)
-				action = FILE_ACTION_CREATE;
-			else
-				action = FILE_ACTION_NONE;
-			oldsize = 0;
-			break;
-
-		case FILE_TYPE_REGULAR:
-			if (exists && !S_ISREG(statbuf.st_mode))
-				pg_fatal("\"%s\" is not a regular file", localpath);
-
-			if (!exists || !isRelDataFile(path))
-			{
-				/*
-				 * File exists in source, but not in target. Or it's a
-				 * non-data file that we have no special processing for. Copy
-				 * it in toto.
-				 *
-				 * An exception: PG_VERSIONs should be identical, but avoid
-				 * overwriting it for paranoia.
-				 */
-				if (pg_str_endswith(path, "PG_VERSION"))
-				{
-					action = FILE_ACTION_NONE;
-					oldsize = statbuf.st_size;
-				}
-				else
-				{
-					action = FILE_ACTION_COPY;
-					oldsize = 0;
-				}
-			}
-			else
-			{
-				/*
-				 * It's a data file that exists in both.
-				 *
-				 * If it's larger in target, we can truncate it. There will
-				 * also be a WAL record of the truncation in the source
-				 * system, so WAL replay would eventually truncate the target
-				 * too, but we might as well do it now.
-				 *
-				 * If it's smaller in the target, it means that it has been
-				 * truncated in the target, or enlarged in the source, or
-				 * both. If it was truncated in the target, we need to copy
-				 * the missing tail from the source system. If it was enlarged
-				 * in the source system, there will be WAL records in the
-				 * source system for the new blocks, so we wouldn't need to
-				 * copy them here. But we don't know which scenario we're
-				 * dealing with, and there's no harm in copying the missing
-				 * blocks now, so do it now.
-				 *
-				 * If it's the same size, do nothing here. Any blocks modified
-				 * in the target will be copied based on parsing the target
-				 * system's WAL, and any blocks modified in the source will be
-				 * updated after rewinding, when the source system's WAL is
-				 * replayed.
-				 */
-				oldsize = statbuf.st_size;
-				if (oldsize < newsize)
-					action = FILE_ACTION_COPY_TAIL;
-				else if (oldsize > newsize)
-					action = FILE_ACTION_TRUNCATE;
-				else
-					action = FILE_ACTION_NONE;
-			}
-			break;
-	}
-
-	/* Create a new entry for this file */
-	entry = pg_malloc(sizeof(file_entry_t));
-	entry->path = pg_strdup(path);
-	entry->type = type;
-	entry->action = action;
-	entry->oldsize = oldsize;
-	entry->newsize = newsize;
-	entry->link_target = link_target ? pg_strdup(link_target) : NULL;
-	entry->next = NULL;
-	entry->pagemap.bitmap = NULL;
-	entry->pagemap.bitmapsize = 0;
-	entry->isrelfile = isRelDataFile(path);
-
-	if (map->last)
-	{
-		map->last->next = entry;
-		map->last = entry;
-	}
-	else
-		map->first = map->last = entry;
-	map->nlist++;
+	/* Remember this source file */
+	entry = get_filemap_entry(path, true);
+	entry->source_exists = true;
+	entry->source_type = type;
+	entry->source_size = size;
+	entry->source_link_target = link_target ? pg_strdup(link_target) : NULL;
 }
 
 /*
@@ -345,14 +253,9 @@ process_source_file(const char *path, file_type_t type, size_t newsize,
  * deletion.
  */
 void
-process_target_file(const char *path, file_type_t type, size_t oldsize,
+process_target_file(const char *path, file_type_t type, size_t size,
 					const char *link_target)
 {
-	bool		exists;
-	char		localpath[MAXPGPATH];
-	struct stat statbuf;
-	file_entry_t key;
-	file_entry_t *key_ptr;
 	filemap_t  *map = filemap;
 	file_entry_t *entry;
 
@@ -361,17 +264,6 @@ process_target_file(const char *path, file_type_t type, size_t oldsize,
 	 * from the target data folder all paths which have been filtered out from
 	 * the source data folder when processing the source files.
 	 */
-
-	snprintf(localpath, sizeof(localpath), "%s/%s", datadir_target, path);
-	if (lstat(localpath, &statbuf) < 0)
-	{
-		if (errno != ENOENT)
-			pg_fatal("could not stat file \"%s\": %m",
-					 localpath);
-
-		exists = false;
-	}
-
 	if (map->array == NULL)
 	{
 		/* on first call, initialize lookup array */
@@ -389,120 +281,76 @@ process_target_file(const char *path, file_type_t type, size_t oldsize,
 	}
 
 	/*
-	 * Like in process_source_file, pretend that xlog is always a  directory.
+	 * Like in process_source_file, pretend that pg_wal is always a directory.
 	 */
 	if (strcmp(path, "pg_wal") == 0 && type == FILE_TYPE_SYMLINK)
 		type = FILE_TYPE_DIRECTORY;
 
-	key.path = (char *) path;
-	key_ptr = &key;
-	exists = (bsearch(&key_ptr, map->array, map->narray, sizeof(file_entry_t *),
-					  path_cmp) != NULL);
-
-	/* Remove any file or folder that doesn't exist in the source system. */
-	if (!exists)
-	{
-		entry = pg_malloc(sizeof(file_entry_t));
-		entry->path = pg_strdup(path);
-		entry->type = type;
-		entry->action = FILE_ACTION_REMOVE;
-		entry->oldsize = oldsize;
-		entry->newsize = 0;
-		entry->link_target = link_target ? pg_strdup(link_target) : NULL;
-		entry->next = NULL;
-		entry->pagemap.bitmap = NULL;
-		entry->pagemap.bitmapsize = 0;
-		entry->isrelfile = isRelDataFile(path);
-
-		if (map->last == NULL)
-			map->first = entry;
-		else
-			map->last->next = entry;
-		map->last = entry;
-		map->nlist++;
-	}
-	else
-	{
-		/*
-		 * We already handled all files that exist in the source system in
-		 * process_source_file().
-		 */
-	}
+	/* Remember this target file */
+	entry = get_filemap_entry(path, true);
+	entry->target_exists = true;
+	entry->target_type = type;
+	entry->target_size = size;
+	entry->target_link_target = link_target ? pg_strdup(link_target) : NULL;
 }
 
 /*
  * This callback gets called while we read the WAL in the target, for every
- * block that have changed in the target system. It makes note of all the
+ * block that have changed in the target system.  It makes note of all the
  * changed blocks in the pagemap of the file.
+ *
+ * NOTE: All the files on both systems must have already been added to the
+ * file map!
  */
 void
-process_block_change(ForkNumber forknum, RelFileNode rnode, BlockNumber blkno)
+process_target_wal_block_change(ForkNumber forknum, RelFileNode rnode,
+								BlockNumber blkno)
 {
 	char	   *path;
-	file_entry_t key;
-	file_entry_t *key_ptr;
 	file_entry_t *entry;
 	BlockNumber blkno_inseg;
 	int			segno;
-	filemap_t  *map = filemap;
-	file_entry_t **e;
 
-	Assert(map->array);
+	Assert(filemap->array);
 
 	segno = blkno / RELSEG_SIZE;
 	blkno_inseg = blkno % RELSEG_SIZE;
 
 	path = datasegpath(rnode, forknum, segno);
-
-	key.path = (char *) path;
-	key_ptr = &key;
-
-	e = bsearch(&key_ptr, map->array, map->narray, sizeof(file_entry_t *),
-				path_cmp);
-	if (e)
-		entry = *e;
-	else
-		entry = NULL;
+	entry = get_filemap_entry(path, false);
 	pfree(path);
 
 	if (entry)
 	{
-		Assert(entry->isrelfile);
-
-		switch (entry->action)
-		{
-			case FILE_ACTION_NONE:
-			case FILE_ACTION_TRUNCATE:
-				/* skip if we're truncating away the modified block anyway */
-				if ((blkno_inseg + 1) * BLCKSZ <= entry->newsize)
-					datapagemap_add(&entry->pagemap, blkno_inseg);
-				break;
+		int64		end_offset;
 
-			case FILE_ACTION_COPY_TAIL:
-
-				/*
-				 * skip the modified block if it is part of the "tail" that
-				 * we're copying anyway.
-				 */
-				if ((blkno_inseg + 1) * BLCKSZ <= entry->oldsize)
-					datapagemap_add(&entry->pagemap, blkno_inseg);
-				break;
+		Assert(entry->isrelfile);
 
-			case FILE_ACTION_COPY:
-			case FILE_ACTION_REMOVE:
-				break;
+		if (entry->target_type != FILE_TYPE_REGULAR)
+			pg_fatal("unexpected page modification for directory or symbolic link \"%s\"",
+					 entry->path);
 
-			case FILE_ACTION_CREATE:
-				pg_fatal("unexpected page modification for directory or symbolic link \"%s\"", entry->path);
-		}
+		/*
+		 * If the block beyond the EOF in the source system, no need to
+		 * remember it now, because we're going to truncate it away from the
+		 * target anyway. Also no need to remember the block if it's beyond
+		 * the current EOF in the target system; we will copy it over with the
+		 * "tail" from the source system, anyway.
+		 */
+		end_offset = (blkno_inseg + 1) * BLCKSZ;
+		if (end_offset <= entry->source_size &&
+			end_offset <= entry->target_size)
+			datapagemap_add(&entry->target_modified_pages, blkno_inseg);
 	}
 	else
 	{
 		/*
 		 * If we don't have any record of this file in the file map, it means
-		 * that it's a relation that doesn't exist in the source system, and
-		 * it was subsequently removed in the target system, too. We can
-		 * safely ignore it.
+		 * that it's a relation that doesn't exist in the source system.  It
+		 * could exist in the target system; we haven't moved the target-only
+		 * entries from the linked list to the array yet!  But in any case, if
+		 * it doesn't exist in the source it will be removed from the target
+		 * too, and we can safely ignore it.
 		 */
 	}
 }
@@ -593,16 +441,6 @@ filemap_list_to_array(filemap_t *map)
 	map->first = map->last = NULL;
 }
 
-void
-filemap_finalize(void)
-{
-	filemap_t  *map = filemap;
-
-	filemap_list_to_array(map);
-	qsort(map->array, map->narray, sizeof(file_entry_t *),
-		  final_filemap_cmp);
-}
-
 static const char *
 action_to_str(file_action_t action)
 {
@@ -643,26 +481,26 @@ calculate_totals(void)
 	{
 		entry = map->array[i];
 
-		if (entry->type != FILE_TYPE_REGULAR)
+		if (entry->source_type != FILE_TYPE_REGULAR)
 			continue;
 
-		map->total_size += entry->newsize;
+		map->total_size += entry->source_size;
 
 		if (entry->action == FILE_ACTION_COPY)
 		{
-			map->fetch_size += entry->newsize;
+			map->fetch_size += entry->source_size;
 			continue;
 		}
 
 		if (entry->action == FILE_ACTION_COPY_TAIL)
-			map->fetch_size += (entry->newsize - entry->oldsize);
+			map->fetch_size += (entry->source_size - entry->target_size);
 
-		if (entry->pagemap.bitmapsize > 0)
+		if (entry->target_modified_pages.bitmapsize > 0)
 		{
 			datapagemap_iterator_t *iter;
 			BlockNumber blk;
 
-			iter = datapagemap_iterate(&entry->pagemap);
+			iter = datapagemap_iterate(&entry->target_modified_pages);
 			while (datapagemap_next(iter, &blk))
 				map->fetch_size += BLCKSZ;
 
@@ -682,13 +520,13 @@ print_filemap(void)
 	{
 		entry = map->array[i];
 		if (entry->action != FILE_ACTION_NONE ||
-			entry->pagemap.bitmapsize > 0)
+			entry->target_modified_pages.bitmapsize > 0)
 		{
 			pg_log_debug("%s (%s)", entry->path,
 						 action_to_str(entry->action));
 
-			if (entry->pagemap.bitmapsize > 0)
-				datapagemap_print(&entry->pagemap);
+			if (entry->target_modified_pages.bitmapsize > 0)
+				datapagemap_print(&entry->target_modified_pages);
 		}
 	}
 	fflush(stdout);
@@ -837,3 +675,180 @@ final_filemap_cmp(const void *a, const void *b)
 	else
 		return strcmp(fa->path, fb->path);
 }
+
+/*
+ * Decide what action to perform to a file.
+ */
+static file_action_t
+decide_file_action(file_entry_t *entry)
+{
+	const char *path = entry->path;
+
+	/*
+	 * Don't touch the control file. It is handled specially, after copying
+	 * all the other files.
+	 */
+	if (strcmp(path, "global/pg_control") == 0)
+		return FILE_ACTION_NONE;
+
+	/*
+	 * Remove all files matching the exclusion filters in the target.
+	 */
+	if (check_file_excluded(path, true))
+	{
+		if (entry->target_exists)
+			return FILE_ACTION_REMOVE;
+		else
+			return FILE_ACTION_NONE;
+	}
+
+	/*
+	 * Also remove all temporary files, .../pgsql_tmp/... and .../pgsql_tmp.*
+	 * in the target.
+	 */
+	if (strstr(path, "/" PG_TEMP_FILE_PREFIX) != NULL ||
+		strstr(path, "/" PG_TEMP_FILES_DIR "/") != NULL)
+	{
+		if (entry->target_exists)
+			return FILE_ACTION_REMOVE;
+		else
+			return FILE_ACTION_NONE;
+	}
+
+	/*
+	 * Handle cases where the file is missing from one of the systems.
+	 */
+	if (!entry->target_exists && entry->source_exists)
+	{
+		/*
+		 * File exists in source, but not in target. Copy it in toto. (If it's
+		 * a relation data file, WAL replay after rewinding should re-create
+		 * it anyway. But there's no harm in copying it now.)
+		 */
+		switch (entry->source_type)
+		{
+			case FILE_TYPE_DIRECTORY:
+			case FILE_TYPE_SYMLINK:
+				return FILE_ACTION_CREATE;
+			case FILE_TYPE_REGULAR:
+				return FILE_ACTION_COPY;
+			case FILE_TYPE_UNDEFINED:
+				pg_fatal("unknown file type for \"%s\"", entry->path);
+				break;
+		}
+	}
+	else if (entry->target_exists && !entry->source_exists)
+	{
+		/* File exists in target, but not source. Remove it. */
+		return FILE_ACTION_REMOVE;
+	}
+	else if (!entry->target_exists && !entry->source_exists)
+	{
+		/*
+		 * Doesn't exist in either server. Why does it have an entry in the
+		 * first place??
+		 */
+		return FILE_ACTION_NONE;
+	}
+
+	/*
+	 * Otherwise, the file exists on both systems
+	 */
+	Assert(entry->target_exists && entry->source_exists);
+
+	if (entry->source_type != entry->target_type)
+	{
+		/* But it's a different kind of object. Strange.. */
+		pg_fatal("file \"%s\" is of different type in source and target", entry->path);
+	}
+
+	/*
+	 * PG_VERSION files should be identical on both systems, but avoid
+	 * overwriting them for paranoia.
+	 */
+	if (pg_str_endswith(entry->path, "PG_VERSION"))
+		return FILE_ACTION_NONE;
+
+	switch (entry->source_type)
+	{
+		case FILE_TYPE_DIRECTORY:
+			return FILE_ACTION_NONE;
+
+		case FILE_TYPE_SYMLINK:
+			/* FIXME: Check if it points to the same target? */
+			return FILE_ACTION_NONE;
+
+		case FILE_TYPE_REGULAR:
+			if (!entry->isrelfile)
+			{
+				/*
+				 * It's a non-data file that we have no special processing
+				 * for. Copy it in toto.
+				 */
+				return FILE_ACTION_COPY;
+			}
+			else
+			{
+				/*
+				 * It's a data file that exists in both systems.
+				 *
+				 * If it's larger in target, we can truncate it. There will
+				 * also be a WAL record of the truncation in the source
+				 * system, so WAL replay would eventually truncate the target
+				 * too, but we might as well do it now.
+				 *
+				 * If it's smaller in the target, it means that it has been
+				 * truncated in the target, or enlarged in the source, or
+				 * both. If it was truncated in the target, we need to copy
+				 * the missing tail from the source system. If it was enlarged
+				 * in the source system, there will be WAL records in the
+				 * source system for the new blocks, so we wouldn't need to
+				 * copy them here. But we don't know which scenario we're
+				 * dealing with, and there's no harm in copying the missing
+				 * blocks now, so do it now.
+				 *
+				 * If it's the same size, do nothing here. Any blocks modified
+				 * in the target will be copied based on parsing the target
+				 * system's WAL, and any blocks modified in the source will be
+				 * updated after rewinding, when the source system's WAL is
+				 * replayed.
+				 */
+				if (entry->target_size < entry->source_size)
+					return FILE_ACTION_COPY_TAIL;
+				else if (entry->target_size > entry->source_size)
+					return FILE_ACTION_TRUNCATE;
+				else
+					return FILE_ACTION_NONE;
+			}
+			break;
+
+		case FILE_TYPE_UNDEFINED:
+			pg_fatal("unknown file type for \"%s\"", path);
+			break;
+	}
+
+	/* unreachable */
+	pg_fatal("could not decide what to do with file \"%s\"", path);
+}
+
+/*
+ * Decide what to do with each file.
+ */
+void
+filemap_finalize()
+{
+	int			i;
+
+	filemap_list_to_array(filemap);
+
+	for (i = 0; i < filemap->narray; i++)
+	{
+		file_entry_t *entry = filemap->array[i];
+
+		entry->action = decide_file_action(entry);
+	}
+
+	/* Sort the actions to the order that they should be performed */
+	qsort(filemap->array, filemap->narray, sizeof(file_entry_t *),
+		  final_filemap_cmp);
+}
diff --git a/src/bin/pg_rewind/filemap.h b/src/bin/pg_rewind/filemap.h
index 0cb7425170c..a5e8df57f40 100644
--- a/src/bin/pg_rewind/filemap.h
+++ b/src/bin/pg_rewind/filemap.h
@@ -14,17 +14,21 @@
 
 /*
  * For every file found in the local or remote system, we have a file entry
- * which says what we are going to do with the file. For relation files,
- * there is also a page map, marking pages in the file that were changed
- * locally.
- *
- * The enum values are sorted in the order we want actions to be processed.
+ * that contains information about the file on both systems.  For relation
+ * files, there is also a page map that marks pages in the file that were
+ * changed in the target after the last common checkpoint.  Each entry also
+ * contains an 'action' field, which says what we are going to do with the
+ * file.
  */
+
+/* these enum values are sorted in the order we want actions to be processed */
 typedef enum
 {
+	FILE_ACTION_UNDECIDED = 0,	/* not decided yet */
+
 	FILE_ACTION_CREATE,			/* create local directory or symbolic link */
 	FILE_ACTION_COPY,			/* copy whole file, overwriting if exists */
-	FILE_ACTION_COPY_TAIL,		/* copy tail from 'oldsize' to 'newsize' */
+	FILE_ACTION_COPY_TAIL,		/* copy tail from 'source_size' to 'target_size' */
 	FILE_ACTION_NONE,			/* no action (we might still copy modified
 								 * blocks based on the parsed WAL) */
 	FILE_ACTION_TRUNCATE,		/* truncate local file to 'newsize' bytes */
@@ -33,6 +37,8 @@ typedef enum
 
 typedef enum
 {
+	FILE_TYPE_UNDEFINED = 0,
+
 	FILE_TYPE_REGULAR,
 	FILE_TYPE_DIRECTORY,
 	FILE_TYPE_SYMLINK
@@ -41,19 +47,30 @@ typedef enum
 typedef struct file_entry_t
 {
 	char	   *path;
-	file_type_t type;
+	bool		isrelfile;		/* is it a relation data file? */
 
-	file_action_t action;
+	/*
+	 * Status of the file in the target.
+	 */
+	bool		target_exists;
+	file_type_t target_type;
+	size_t		target_size; /* for a regular file */
+	char	   *target_link_target; /* for a symlink */
 
-	/* for a regular file */
-	size_t		oldsize;
-	size_t		newsize;
-	bool		isrelfile;		/* is it a relation data file? */
+	datapagemap_t target_modified_pages;
 
-	datapagemap_t pagemap;
+	/*
+	 * Status of the file in the source.
+	 */
+	bool		source_exists;
+	file_type_t source_type;
+	size_t		source_size;
+	char	   *source_link_target; /* for a symlink */
 
-	/* for a symlink */
-	char	   *link_target;
+	/*
+	 * What will we do to the file?
+	 */
+	file_action_t action;
 
 	struct file_entry_t *next;
 } file_entry_t;
@@ -70,20 +87,19 @@ typedef struct filemap_t
 
 	/*
 	 * After processing all the remote files, the entries in the linked list
-	 * are moved to this array. After processing local files, too, all the
+	 * are moved to this array.  After processing local files, too, all the
 	 * local entries are added to the array by filemap_finalize, and sorted in
-	 * the final order. After filemap_finalize, all the entries are in the
+	 * the final order.  After filemap_finalize, all the entries are in the
 	 * array, and the linked list is empty.
 	 */
 	file_entry_t **array;
 	int			narray;			/* current length of array */
 
 	/*
-	 * Summary information. total_size is the total size of the source
-	 * cluster, and fetch_size is the number of bytes that needs to be copied.
+	 * Summary information.
 	 */
-	uint64		total_size;
-	uint64		fetch_size;
+	uint64		total_size;		/* total size of the source cluster */
+	uint64		fetch_size;		/* number of bytes that needs to be copied */
 } filemap_t;
 
 extern filemap_t *filemap;
@@ -94,11 +110,12 @@ extern void print_filemap(void);
 
 /* Functions for populating the filemap */
 extern void process_source_file(const char *path, file_type_t type,
-								size_t newsize, const char *link_target);
+								size_t size, const char *link_target);
 extern void process_target_file(const char *path, file_type_t type,
-								size_t newsize, const char *link_target);
-extern void process_block_change(ForkNumber forknum, RelFileNode rnode,
-								 BlockNumber blkno);
+								size_t size, const char *link_target);
+extern void process_target_wal_block_change(ForkNumber forknum,
+											RelFileNode rnode,
+											BlockNumber blkno);
 extern void filemap_finalize(void);
 
 #endif							/* FILEMAP_H */
diff --git a/src/bin/pg_rewind/libpq_fetch.c b/src/bin/pg_rewind/libpq_fetch.c
index bf4dfc23b96..7fc9161b8c8 100644
--- a/src/bin/pg_rewind/libpq_fetch.c
+++ b/src/bin/pg_rewind/libpq_fetch.c
@@ -465,7 +465,7 @@ libpq_executeFileMap(filemap_t *map)
 		entry = map->array[i];
 
 		/* If this is a relation file, copy the modified blocks */
-		execute_pagemap(&entry->pagemap, entry->path);
+		execute_pagemap(&entry->target_modified_pages, entry->path);
 
 		switch (entry->action)
 		{
@@ -476,15 +476,15 @@ libpq_executeFileMap(filemap_t *map)
 			case FILE_ACTION_COPY:
 				/* Truncate the old file out of the way, if any */
 				open_target_file(entry->path, true);
-				fetch_file_range(entry->path, 0, entry->newsize);
+				fetch_file_range(entry->path, 0, entry->source_size);
 				break;
 
 			case FILE_ACTION_TRUNCATE:
-				truncate_target_file(entry->path, entry->newsize);
+				truncate_target_file(entry->path, entry->source_size);
 				break;
 
 			case FILE_ACTION_COPY_TAIL:
-				fetch_file_range(entry->path, entry->oldsize, entry->newsize);
+				fetch_file_range(entry->path, entry->target_size, entry->source_size);
 				break;
 
 			case FILE_ACTION_REMOVE:
@@ -494,6 +494,10 @@ libpq_executeFileMap(filemap_t *map)
 			case FILE_ACTION_CREATE:
 				create_target(entry);
 				break;
+
+			case FILE_ACTION_UNDECIDED:
+				pg_fatal("no action decided for \"%s\"", entry->path);
+				break;
 		}
 	}
 
diff --git a/src/bin/pg_rewind/parsexlog.c b/src/bin/pg_rewind/parsexlog.c
index 2229c86f9af..2baeb74ae93 100644
--- a/src/bin/pg_rewind/parsexlog.c
+++ b/src/bin/pg_rewind/parsexlog.c
@@ -436,6 +436,6 @@ extractPageInfo(XLogReaderState *record)
 		if (forknum != MAIN_FORKNUM)
 			continue;
 
-		process_block_change(forknum, rnode, blkno);
+		process_target_wal_block_change(forknum, rnode, blkno);
 	}
 }
diff --git a/src/bin/pg_rewind/pg_rewind.c b/src/bin/pg_rewind/pg_rewind.c
index c9b9e480c0f..210984d302b 100644
--- a/src/bin/pg_rewind/pg_rewind.c
+++ b/src/bin/pg_rewind/pg_rewind.c
@@ -369,7 +369,7 @@ main(int argc, char **argv)
 				chkpttli);
 
 	/*
-	 * Build the filemap, by comparing the source and target data directories.
+	 * Collect information about all files in the target and source systems.
 	 */
 	filemap_create();
 	if (showprogress)
@@ -390,8 +390,12 @@ main(int argc, char **argv)
 		pg_log_info("reading WAL in target");
 	extractPageMap(datadir_target, chkptrec, lastcommontliIndex,
 				   ControlFile_target.checkPoint, restore_command);
-	filemap_finalize();
 
+	/*
+	 * We have collected all information we need from both systems. Decide
+	 * what to do with each file.
+	 */
+	filemap_finalize();
 	if (showprogress)
 		calculate_totals();
 
-- 
2.20.1


--------------BF34D0120055DC3839060F92
Content-Type: text/x-patch; charset=UTF-8;
 name="v2-0003-pg_rewind-Replace-the-hybrid-list-array-data-stru.patch"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
 filename*0="v2-0003-pg_rewind-Replace-the-hybrid-list-array-data-stru.pa";
 filename*1="tch"



^ permalink  raw  reply  [nested|flat] 41+ messages in thread

* [PATCH v2 2/5] Refactor pg_rewind for more clear decision making.
@ 2020-08-19 12:34 Heikki Linnakangas <[email protected]>
  0 siblings, 0 replies; 41+ messages in thread

From: Heikki Linnakangas @ 2020-08-19 12:34 UTC (permalink / raw)

Deciding what to do with each file is now a separate step after all the
necessary information has been gathered. It is more clear that way.
Previously, the decision-making was divided between process_source_file()
and process_target_file(), and it was a bit hard to piece together what the
overall rules were.
---
 src/bin/pg_rewind/copy_fetch.c  |  14 +-
 src/bin/pg_rewind/file_ops.c    |  16 +-
 src/bin/pg_rewind/filemap.c     | 569 ++++++++++++++++----------------
 src/bin/pg_rewind/filemap.h     |  67 ++--
 src/bin/pg_rewind/libpq_fetch.c |  12 +-
 src/bin/pg_rewind/parsexlog.c   |   2 +-
 src/bin/pg_rewind/pg_rewind.c   |   8 +-
 7 files changed, 371 insertions(+), 317 deletions(-)

diff --git a/src/bin/pg_rewind/copy_fetch.c b/src/bin/pg_rewind/copy_fetch.c
index 1edab5f1867..18fad32600e 100644
--- a/src/bin/pg_rewind/copy_fetch.c
+++ b/src/bin/pg_rewind/copy_fetch.c
@@ -210,7 +210,7 @@ copy_executeFileMap(filemap_t *map)
 	for (i = 0; i < map->narray; i++)
 	{
 		entry = map->array[i];
-		execute_pagemap(&entry->pagemap, entry->path);
+		execute_pagemap(&entry->target_modified_pages, entry->path);
 
 		switch (entry->action)
 		{
@@ -219,16 +219,16 @@ copy_executeFileMap(filemap_t *map)
 				break;
 
 			case FILE_ACTION_COPY:
-				rewind_copy_file_range(entry->path, 0, entry->newsize, true);
+				rewind_copy_file_range(entry->path, 0, entry->source_size, true);
 				break;
 
 			case FILE_ACTION_TRUNCATE:
-				truncate_target_file(entry->path, entry->newsize);
+				truncate_target_file(entry->path, entry->source_size);
 				break;
 
 			case FILE_ACTION_COPY_TAIL:
-				rewind_copy_file_range(entry->path, entry->oldsize,
-									   entry->newsize, false);
+				rewind_copy_file_range(entry->path, entry->target_size,
+									   entry->source_size, false);
 				break;
 
 			case FILE_ACTION_CREATE:
@@ -238,6 +238,10 @@ copy_executeFileMap(filemap_t *map)
 			case FILE_ACTION_REMOVE:
 				remove_target(entry);
 				break;
+
+			case FILE_ACTION_UNDECIDED:
+				pg_fatal("no action decided for \"%s\"", entry->path);
+				break;
 		}
 	}
 
diff --git a/src/bin/pg_rewind/file_ops.c b/src/bin/pg_rewind/file_ops.c
index 55439db20ba..ec37d0b2e0d 100644
--- a/src/bin/pg_rewind/file_ops.c
+++ b/src/bin/pg_rewind/file_ops.c
@@ -126,8 +126,9 @@ void
 remove_target(file_entry_t *entry)
 {
 	Assert(entry->action == FILE_ACTION_REMOVE);
+	Assert(entry->target_exists);
 
-	switch (entry->type)
+	switch (entry->target_type)
 	{
 		case FILE_TYPE_DIRECTORY:
 			remove_target_dir(entry->path);
@@ -140,6 +141,10 @@ remove_target(file_entry_t *entry)
 		case FILE_TYPE_SYMLINK:
 			remove_target_symlink(entry->path);
 			break;
+
+		case FILE_TYPE_UNDEFINED:
+			pg_fatal("undefined file type for \"%s\"", entry->path);
+			break;
 	}
 }
 
@@ -147,21 +152,26 @@ void
 create_target(file_entry_t *entry)
 {
 	Assert(entry->action == FILE_ACTION_CREATE);
+	Assert(!entry->target_exists);
 
-	switch (entry->type)
+	switch (entry->source_type)
 	{
 		case FILE_TYPE_DIRECTORY:
 			create_target_dir(entry->path);
 			break;
 
 		case FILE_TYPE_SYMLINK:
-			create_target_symlink(entry->path, entry->link_target);
+			create_target_symlink(entry->path, entry->source_link_target);
 			break;
 
 		case FILE_TYPE_REGULAR:
 			/* can't happen. Regular files are created with open_target_file. */
 			pg_fatal("invalid action (CREATE) for regular file");
 			break;
+
+		case FILE_TYPE_UNDEFINED:
+			pg_fatal("undefined file type for \"%s\"", entry->path);
+			break;
 	}
 }
 
diff --git a/src/bin/pg_rewind/filemap.c b/src/bin/pg_rewind/filemap.c
index 1879257b66a..7971daeda5e 100644
--- a/src/bin/pg_rewind/filemap.c
+++ b/src/bin/pg_rewind/filemap.c
@@ -26,6 +26,8 @@ static bool isRelDataFile(const char *path);
 static char *datasegpath(RelFileNode rnode, ForkNumber forknum,
 						 BlockNumber segno);
 static int	path_cmp(const void *a, const void *b);
+
+static file_entry_t *get_filemap_entry(const char *path, bool create);
 static int	final_filemap_cmp(const void *a, const void *b);
 static void filemap_list_to_array(filemap_t *map);
 static bool check_file_excluded(const char *path, bool is_source);
@@ -146,6 +148,65 @@ filemap_create(void)
 	filemap = map;
 }
 
+/* Look up or create entry for 'path' */
+static file_entry_t *
+get_filemap_entry(const char *path, bool create)
+{
+	filemap_t  *map = filemap;
+	file_entry_t *entry;
+	file_entry_t **e;
+	file_entry_t key;
+	file_entry_t *key_ptr;
+
+	if (map->array)
+	{
+		key.path = (char *) path;
+		key_ptr = &key;
+		e = bsearch(&key_ptr, map->array, map->narray, sizeof(file_entry_t *),
+					path_cmp);
+	}
+	else
+		e = NULL;
+
+	if (e)
+		entry = *e;
+	else if (!create)
+		entry = NULL;
+	else
+	{
+		/* Create a new entry for this file */
+		entry = pg_malloc(sizeof(file_entry_t));
+		entry->path = pg_strdup(path);
+		entry->isrelfile = isRelDataFile(path);
+		entry->action = FILE_ACTION_UNDECIDED;
+
+		entry->target_exists = false;
+		entry->target_type = FILE_TYPE_UNDEFINED;
+		entry->target_size = 0;
+		entry->target_link_target = NULL;
+		entry->target_modified_pages.bitmap = NULL;
+		entry->target_modified_pages.bitmapsize = 0;
+
+		entry->source_exists = false;
+		entry->source_type = FILE_TYPE_UNDEFINED;
+		entry->source_size = 0;
+		entry->source_link_target = NULL;
+
+		entry->next = NULL;
+
+		if (map->last)
+		{
+			map->last->next = entry;
+			map->last = entry;
+		}
+		else
+			map->first = map->last = entry;
+		map->nlist++;
+	}
+
+	return entry;
+}
+
 /*
  * Callback for processing source file list.
  *
@@ -154,25 +215,12 @@ filemap_create(void)
  * exists in the target and whether the size matches.
  */
 void
-process_source_file(const char *path, file_type_t type, size_t newsize,
+process_source_file(const char *path, file_type_t type, size_t size,
 					const char *link_target)
 {
-	bool		exists;
-	char		localpath[MAXPGPATH];
-	struct stat statbuf;
-	filemap_t  *map = filemap;
-	file_action_t action = FILE_ACTION_NONE;
-	size_t		oldsize = 0;
 	file_entry_t *entry;
 
-	Assert(map->array == NULL);
-
-	/*
-	 * Skip any files matching the exclusion filters. This has the effect to
-	 * remove all those files on the target.
-	 */
-	if (check_file_excluded(path, true))
-		return;
+	Assert(filemap->array == NULL);
 
 	/*
 	 * Pretend that pg_wal is a directory, even if it's really a symlink. We
@@ -182,16 +230,6 @@ process_source_file(const char *path, file_type_t type, size_t newsize,
 	if (strcmp(path, "pg_wal") == 0 && type == FILE_TYPE_SYMLINK)
 		type = FILE_TYPE_DIRECTORY;
 
-	/*
-	 * Skip temporary files, .../pgsql_tmp/... and .../pgsql_tmp.* in source.
-	 * This has the effect that all temporary files in the destination will be
-	 * removed.
-	 */
-	if (strstr(path, "/" PG_TEMP_FILE_PREFIX) != NULL)
-		return;
-	if (strstr(path, "/" PG_TEMP_FILES_DIR "/") != NULL)
-		return;
-
 	/*
 	 * sanity check: a filename that looks like a data file better be a
 	 * regular file
@@ -199,142 +237,12 @@ process_source_file(const char *path, file_type_t type, size_t newsize,
 	if (type != FILE_TYPE_REGULAR && isRelDataFile(path))
 		pg_fatal("data file \"%s\" in source is not a regular file", path);
 
-	snprintf(localpath, sizeof(localpath), "%s/%s", datadir_target, path);
-
-	/* Does the corresponding file exist in the target data dir? */
-	if (lstat(localpath, &statbuf) < 0)
-	{
-		if (errno != ENOENT)
-			pg_fatal("could not stat file \"%s\": %m",
-					 localpath);
-
-		exists = false;
-	}
-	else
-		exists = true;
-
-	switch (type)
-	{
-		case FILE_TYPE_DIRECTORY:
-			if (exists && !S_ISDIR(statbuf.st_mode) && strcmp(path, "pg_wal") != 0)
-			{
-				/* it's a directory in source, but not in target. Strange.. */
-				pg_fatal("\"%s\" is not a directory", localpath);
-			}
-
-			if (!exists)
-				action = FILE_ACTION_CREATE;
-			else
-				action = FILE_ACTION_NONE;
-			oldsize = 0;
-			break;
-
-		case FILE_TYPE_SYMLINK:
-			if (exists &&
-#ifndef WIN32
-				!S_ISLNK(statbuf.st_mode)
-#else
-				!pgwin32_is_junction(localpath)
-#endif
-				)
-			{
-				/*
-				 * It's a symbolic link in source, but not in target.
-				 * Strange..
-				 */
-				pg_fatal("\"%s\" is not a symbolic link", localpath);
-			}
-
-			if (!exists)
-				action = FILE_ACTION_CREATE;
-			else
-				action = FILE_ACTION_NONE;
-			oldsize = 0;
-			break;
-
-		case FILE_TYPE_REGULAR:
-			if (exists && !S_ISREG(statbuf.st_mode))
-				pg_fatal("\"%s\" is not a regular file", localpath);
-
-			if (!exists || !isRelDataFile(path))
-			{
-				/*
-				 * File exists in source, but not in target. Or it's a
-				 * non-data file that we have no special processing for. Copy
-				 * it in toto.
-				 *
-				 * An exception: PG_VERSIONs should be identical, but avoid
-				 * overwriting it for paranoia.
-				 */
-				if (pg_str_endswith(path, "PG_VERSION"))
-				{
-					action = FILE_ACTION_NONE;
-					oldsize = statbuf.st_size;
-				}
-				else
-				{
-					action = FILE_ACTION_COPY;
-					oldsize = 0;
-				}
-			}
-			else
-			{
-				/*
-				 * It's a data file that exists in both.
-				 *
-				 * If it's larger in target, we can truncate it. There will
-				 * also be a WAL record of the truncation in the source
-				 * system, so WAL replay would eventually truncate the target
-				 * too, but we might as well do it now.
-				 *
-				 * If it's smaller in the target, it means that it has been
-				 * truncated in the target, or enlarged in the source, or
-				 * both. If it was truncated in the target, we need to copy
-				 * the missing tail from the source system. If it was enlarged
-				 * in the source system, there will be WAL records in the
-				 * source system for the new blocks, so we wouldn't need to
-				 * copy them here. But we don't know which scenario we're
-				 * dealing with, and there's no harm in copying the missing
-				 * blocks now, so do it now.
-				 *
-				 * If it's the same size, do nothing here. Any blocks modified
-				 * in the target will be copied based on parsing the target
-				 * system's WAL, and any blocks modified in the source will be
-				 * updated after rewinding, when the source system's WAL is
-				 * replayed.
-				 */
-				oldsize = statbuf.st_size;
-				if (oldsize < newsize)
-					action = FILE_ACTION_COPY_TAIL;
-				else if (oldsize > newsize)
-					action = FILE_ACTION_TRUNCATE;
-				else
-					action = FILE_ACTION_NONE;
-			}
-			break;
-	}
-
-	/* Create a new entry for this file */
-	entry = pg_malloc(sizeof(file_entry_t));
-	entry->path = pg_strdup(path);
-	entry->type = type;
-	entry->action = action;
-	entry->oldsize = oldsize;
-	entry->newsize = newsize;
-	entry->link_target = link_target ? pg_strdup(link_target) : NULL;
-	entry->next = NULL;
-	entry->pagemap.bitmap = NULL;
-	entry->pagemap.bitmapsize = 0;
-	entry->isrelfile = isRelDataFile(path);
-
-	if (map->last)
-	{
-		map->last->next = entry;
-		map->last = entry;
-	}
-	else
-		map->first = map->last = entry;
-	map->nlist++;
+	/* Remember this source file */
+	entry = get_filemap_entry(path, true);
+	entry->source_exists = true;
+	entry->source_type = type;
+	entry->source_size = size;
+	entry->source_link_target = link_target ? pg_strdup(link_target) : NULL;
 }
 
 /*
@@ -345,14 +253,9 @@ process_source_file(const char *path, file_type_t type, size_t newsize,
  * deletion.
  */
 void
-process_target_file(const char *path, file_type_t type, size_t oldsize,
+process_target_file(const char *path, file_type_t type, size_t size,
 					const char *link_target)
 {
-	bool		exists;
-	char		localpath[MAXPGPATH];
-	struct stat statbuf;
-	file_entry_t key;
-	file_entry_t *key_ptr;
 	filemap_t  *map = filemap;
 	file_entry_t *entry;
 
@@ -361,17 +264,6 @@ process_target_file(const char *path, file_type_t type, size_t oldsize,
 	 * from the target data folder all paths which have been filtered out from
 	 * the source data folder when processing the source files.
 	 */
-
-	snprintf(localpath, sizeof(localpath), "%s/%s", datadir_target, path);
-	if (lstat(localpath, &statbuf) < 0)
-	{
-		if (errno != ENOENT)
-			pg_fatal("could not stat file \"%s\": %m",
-					 localpath);
-
-		exists = false;
-	}
-
 	if (map->array == NULL)
 	{
 		/* on first call, initialize lookup array */
@@ -389,120 +281,76 @@ process_target_file(const char *path, file_type_t type, size_t oldsize,
 	}
 
 	/*
-	 * Like in process_source_file, pretend that xlog is always a  directory.
+	 * Like in process_source_file, pretend that pg_wal is always a directory.
 	 */
 	if (strcmp(path, "pg_wal") == 0 && type == FILE_TYPE_SYMLINK)
 		type = FILE_TYPE_DIRECTORY;
 
-	key.path = (char *) path;
-	key_ptr = &key;
-	exists = (bsearch(&key_ptr, map->array, map->narray, sizeof(file_entry_t *),
-					  path_cmp) != NULL);
-
-	/* Remove any file or folder that doesn't exist in the source system. */
-	if (!exists)
-	{
-		entry = pg_malloc(sizeof(file_entry_t));
-		entry->path = pg_strdup(path);
-		entry->type = type;
-		entry->action = FILE_ACTION_REMOVE;
-		entry->oldsize = oldsize;
-		entry->newsize = 0;
-		entry->link_target = link_target ? pg_strdup(link_target) : NULL;
-		entry->next = NULL;
-		entry->pagemap.bitmap = NULL;
-		entry->pagemap.bitmapsize = 0;
-		entry->isrelfile = isRelDataFile(path);
-
-		if (map->last == NULL)
-			map->first = entry;
-		else
-			map->last->next = entry;
-		map->last = entry;
-		map->nlist++;
-	}
-	else
-	{
-		/*
-		 * We already handled all files that exist in the source system in
-		 * process_source_file().
-		 */
-	}
+	/* Remember this target file */
+	entry = get_filemap_entry(path, true);
+	entry->target_exists = true;
+	entry->target_type = type;
+	entry->target_size = size;
+	entry->target_link_target = link_target ? pg_strdup(link_target) : NULL;
 }
 
 /*
  * This callback gets called while we read the WAL in the target, for every
- * block that have changed in the target system. It makes note of all the
+ * block that have changed in the target system.  It makes note of all the
  * changed blocks in the pagemap of the file.
+ *
+ * NOTE: All the files on both systems must have already been added to the
+ * file map!
  */
 void
-process_block_change(ForkNumber forknum, RelFileNode rnode, BlockNumber blkno)
+process_target_wal_block_change(ForkNumber forknum, RelFileNode rnode,
+								BlockNumber blkno)
 {
 	char	   *path;
-	file_entry_t key;
-	file_entry_t *key_ptr;
 	file_entry_t *entry;
 	BlockNumber blkno_inseg;
 	int			segno;
-	filemap_t  *map = filemap;
-	file_entry_t **e;
 
-	Assert(map->array);
+	Assert(filemap->array);
 
 	segno = blkno / RELSEG_SIZE;
 	blkno_inseg = blkno % RELSEG_SIZE;
 
 	path = datasegpath(rnode, forknum, segno);
-
-	key.path = (char *) path;
-	key_ptr = &key;
-
-	e = bsearch(&key_ptr, map->array, map->narray, sizeof(file_entry_t *),
-				path_cmp);
-	if (e)
-		entry = *e;
-	else
-		entry = NULL;
+	entry = get_filemap_entry(path, false);
 	pfree(path);
 
 	if (entry)
 	{
-		Assert(entry->isrelfile);
-
-		switch (entry->action)
-		{
-			case FILE_ACTION_NONE:
-			case FILE_ACTION_TRUNCATE:
-				/* skip if we're truncating away the modified block anyway */
-				if ((blkno_inseg + 1) * BLCKSZ <= entry->newsize)
-					datapagemap_add(&entry->pagemap, blkno_inseg);
-				break;
+		int64		end_offset;
 
-			case FILE_ACTION_COPY_TAIL:
-
-				/*
-				 * skip the modified block if it is part of the "tail" that
-				 * we're copying anyway.
-				 */
-				if ((blkno_inseg + 1) * BLCKSZ <= entry->oldsize)
-					datapagemap_add(&entry->pagemap, blkno_inseg);
-				break;
+		Assert(entry->isrelfile);
 
-			case FILE_ACTION_COPY:
-			case FILE_ACTION_REMOVE:
-				break;
+		if (entry->target_type != FILE_TYPE_REGULAR)
+			pg_fatal("unexpected page modification for directory or symbolic link \"%s\"",
+					 entry->path);
 
-			case FILE_ACTION_CREATE:
-				pg_fatal("unexpected page modification for directory or symbolic link \"%s\"", entry->path);
-		}
+		/*
+		 * If the block beyond the EOF in the source system, no need to
+		 * remember it now, because we're going to truncate it away from the
+		 * target anyway. Also no need to remember the block if it's beyond
+		 * the current EOF in the target system; we will copy it over with the
+		 * "tail" from the source system, anyway.
+		 */
+		end_offset = (blkno_inseg + 1) * BLCKSZ;
+		if (end_offset <= entry->source_size &&
+			end_offset <= entry->target_size)
+			datapagemap_add(&entry->target_modified_pages, blkno_inseg);
 	}
 	else
 	{
 		/*
 		 * If we don't have any record of this file in the file map, it means
-		 * that it's a relation that doesn't exist in the source system, and
-		 * it was subsequently removed in the target system, too. We can
-		 * safely ignore it.
+		 * that it's a relation that doesn't exist in the source system.  It
+		 * could exist in the target system; we haven't moved the target-only
+		 * entries from the linked list to the array yet!  But in any case, if
+		 * it doesn't exist in the source it will be removed from the target
+		 * too, and we can safely ignore it.
 		 */
 	}
 }
@@ -593,16 +441,6 @@ filemap_list_to_array(filemap_t *map)
 	map->first = map->last = NULL;
 }
 
-void
-filemap_finalize(void)
-{
-	filemap_t  *map = filemap;
-
-	filemap_list_to_array(map);
-	qsort(map->array, map->narray, sizeof(file_entry_t *),
-		  final_filemap_cmp);
-}
-
 static const char *
 action_to_str(file_action_t action)
 {
@@ -643,26 +481,26 @@ calculate_totals(void)
 	{
 		entry = map->array[i];
 
-		if (entry->type != FILE_TYPE_REGULAR)
+		if (entry->source_type != FILE_TYPE_REGULAR)
 			continue;
 
-		map->total_size += entry->newsize;
+		map->total_size += entry->source_size;
 
 		if (entry->action == FILE_ACTION_COPY)
 		{
-			map->fetch_size += entry->newsize;
+			map->fetch_size += entry->source_size;
 			continue;
 		}
 
 		if (entry->action == FILE_ACTION_COPY_TAIL)
-			map->fetch_size += (entry->newsize - entry->oldsize);
+			map->fetch_size += (entry->source_size - entry->target_size);
 
-		if (entry->pagemap.bitmapsize > 0)
+		if (entry->target_modified_pages.bitmapsize > 0)
 		{
 			datapagemap_iterator_t *iter;
 			BlockNumber blk;
 
-			iter = datapagemap_iterate(&entry->pagemap);
+			iter = datapagemap_iterate(&entry->target_modified_pages);
 			while (datapagemap_next(iter, &blk))
 				map->fetch_size += BLCKSZ;
 
@@ -682,13 +520,13 @@ print_filemap(void)
 	{
 		entry = map->array[i];
 		if (entry->action != FILE_ACTION_NONE ||
-			entry->pagemap.bitmapsize > 0)
+			entry->target_modified_pages.bitmapsize > 0)
 		{
 			pg_log_debug("%s (%s)", entry->path,
 						 action_to_str(entry->action));
 
-			if (entry->pagemap.bitmapsize > 0)
-				datapagemap_print(&entry->pagemap);
+			if (entry->target_modified_pages.bitmapsize > 0)
+				datapagemap_print(&entry->target_modified_pages);
 		}
 	}
 	fflush(stdout);
@@ -837,3 +675,180 @@ final_filemap_cmp(const void *a, const void *b)
 	else
 		return strcmp(fa->path, fb->path);
 }
+
+/*
+ * Decide what action to perform to a file.
+ */
+static file_action_t
+decide_file_action(file_entry_t *entry)
+{
+	const char *path = entry->path;
+
+	/*
+	 * Don't touch the control file. It is handled specially, after copying
+	 * all the other files.
+	 */
+	if (strcmp(path, "global/pg_control") == 0)
+		return FILE_ACTION_NONE;
+
+	/*
+	 * Remove all files matching the exclusion filters in the target.
+	 */
+	if (check_file_excluded(path, true))
+	{
+		if (entry->target_exists)
+			return FILE_ACTION_REMOVE;
+		else
+			return FILE_ACTION_NONE;
+	}
+
+	/*
+	 * Also remove all temporary files, .../pgsql_tmp/... and .../pgsql_tmp.*
+	 * in the target.
+	 */
+	if (strstr(path, "/" PG_TEMP_FILE_PREFIX) != NULL ||
+		strstr(path, "/" PG_TEMP_FILES_DIR "/") != NULL)
+	{
+		if (entry->target_exists)
+			return FILE_ACTION_REMOVE;
+		else
+			return FILE_ACTION_NONE;
+	}
+
+	/*
+	 * Handle cases where the file is missing from one of the systems.
+	 */
+	if (!entry->target_exists && entry->source_exists)
+	{
+		/*
+		 * File exists in source, but not in target. Copy it in toto. (If it's
+		 * a relation data file, WAL replay after rewinding should re-create
+		 * it anyway. But there's no harm in copying it now.)
+		 */
+		switch (entry->source_type)
+		{
+			case FILE_TYPE_DIRECTORY:
+			case FILE_TYPE_SYMLINK:
+				return FILE_ACTION_CREATE;
+			case FILE_TYPE_REGULAR:
+				return FILE_ACTION_COPY;
+			case FILE_TYPE_UNDEFINED:
+				pg_fatal("unknown file type for \"%s\"", entry->path);
+				break;
+		}
+	}
+	else if (entry->target_exists && !entry->source_exists)
+	{
+		/* File exists in target, but not source. Remove it. */
+		return FILE_ACTION_REMOVE;
+	}
+	else if (!entry->target_exists && !entry->source_exists)
+	{
+		/*
+		 * Doesn't exist in either server. Why does it have an entry in the
+		 * first place??
+		 */
+		return FILE_ACTION_NONE;
+	}
+
+	/*
+	 * Otherwise, the file exists on both systems
+	 */
+	Assert(entry->target_exists && entry->source_exists);
+
+	if (entry->source_type != entry->target_type)
+	{
+		/* But it's a different kind of object. Strange.. */
+		pg_fatal("file \"%s\" is of different type in source and target", entry->path);
+	}
+
+	/*
+	 * PG_VERSION files should be identical on both systems, but avoid
+	 * overwriting them for paranoia.
+	 */
+	if (pg_str_endswith(entry->path, "PG_VERSION"))
+		return FILE_ACTION_NONE;
+
+	switch (entry->source_type)
+	{
+		case FILE_TYPE_DIRECTORY:
+			return FILE_ACTION_NONE;
+
+		case FILE_TYPE_SYMLINK:
+			/* FIXME: Check if it points to the same target? */
+			return FILE_ACTION_NONE;
+
+		case FILE_TYPE_REGULAR:
+			if (!entry->isrelfile)
+			{
+				/*
+				 * It's a non-data file that we have no special processing
+				 * for. Copy it in toto.
+				 */
+				return FILE_ACTION_COPY;
+			}
+			else
+			{
+				/*
+				 * It's a data file that exists in both systems.
+				 *
+				 * If it's larger in target, we can truncate it. There will
+				 * also be a WAL record of the truncation in the source
+				 * system, so WAL replay would eventually truncate the target
+				 * too, but we might as well do it now.
+				 *
+				 * If it's smaller in the target, it means that it has been
+				 * truncated in the target, or enlarged in the source, or
+				 * both. If it was truncated in the target, we need to copy
+				 * the missing tail from the source system. If it was enlarged
+				 * in the source system, there will be WAL records in the
+				 * source system for the new blocks, so we wouldn't need to
+				 * copy them here. But we don't know which scenario we're
+				 * dealing with, and there's no harm in copying the missing
+				 * blocks now, so do it now.
+				 *
+				 * If it's the same size, do nothing here. Any blocks modified
+				 * in the target will be copied based on parsing the target
+				 * system's WAL, and any blocks modified in the source will be
+				 * updated after rewinding, when the source system's WAL is
+				 * replayed.
+				 */
+				if (entry->target_size < entry->source_size)
+					return FILE_ACTION_COPY_TAIL;
+				else if (entry->target_size > entry->source_size)
+					return FILE_ACTION_TRUNCATE;
+				else
+					return FILE_ACTION_NONE;
+			}
+			break;
+
+		case FILE_TYPE_UNDEFINED:
+			pg_fatal("unknown file type for \"%s\"", path);
+			break;
+	}
+
+	/* unreachable */
+	pg_fatal("could not decide what to do with file \"%s\"", path);
+}
+
+/*
+ * Decide what to do with each file.
+ */
+void
+filemap_finalize()
+{
+	int			i;
+
+	filemap_list_to_array(filemap);
+
+	for (i = 0; i < filemap->narray; i++)
+	{
+		file_entry_t *entry = filemap->array[i];
+
+		entry->action = decide_file_action(entry);
+	}
+
+	/* Sort the actions to the order that they should be performed */
+	qsort(filemap->array, filemap->narray, sizeof(file_entry_t *),
+		  final_filemap_cmp);
+}
diff --git a/src/bin/pg_rewind/filemap.h b/src/bin/pg_rewind/filemap.h
index 0cb7425170c..a5e8df57f40 100644
--- a/src/bin/pg_rewind/filemap.h
+++ b/src/bin/pg_rewind/filemap.h
@@ -14,17 +14,21 @@
 
 /*
  * For every file found in the local or remote system, we have a file entry
- * which says what we are going to do with the file. For relation files,
- * there is also a page map, marking pages in the file that were changed
- * locally.
- *
- * The enum values are sorted in the order we want actions to be processed.
+ * that contains information about the file on both systems.  For relation
+ * files, there is also a page map that marks pages in the file that were
+ * changed in the target after the last common checkpoint.  Each entry also
+ * contains an 'action' field, which says what we are going to do with the
+ * file.
  */
+
+/* these enum values are sorted in the order we want actions to be processed */
 typedef enum
 {
+	FILE_ACTION_UNDECIDED = 0,	/* not decided yet */
+
 	FILE_ACTION_CREATE,			/* create local directory or symbolic link */
 	FILE_ACTION_COPY,			/* copy whole file, overwriting if exists */
-	FILE_ACTION_COPY_TAIL,		/* copy tail from 'oldsize' to 'newsize' */
+	FILE_ACTION_COPY_TAIL,		/* copy tail from 'source_size' to 'target_size' */
 	FILE_ACTION_NONE,			/* no action (we might still copy modified
 								 * blocks based on the parsed WAL) */
 	FILE_ACTION_TRUNCATE,		/* truncate local file to 'newsize' bytes */
@@ -33,6 +37,8 @@ typedef enum
 
 typedef enum
 {
+	FILE_TYPE_UNDEFINED = 0,
+
 	FILE_TYPE_REGULAR,
 	FILE_TYPE_DIRECTORY,
 	FILE_TYPE_SYMLINK
@@ -41,19 +47,30 @@ typedef enum
 typedef struct file_entry_t
 {
 	char	   *path;
-	file_type_t type;
+	bool		isrelfile;		/* is it a relation data file? */
 
-	file_action_t action;
+	/*
+	 * Status of the file in the target.
+	 */
+	bool		target_exists;
+	file_type_t target_type;
+	size_t		target_size; /* for a regular file */
+	char	   *target_link_target; /* for a symlink */
 
-	/* for a regular file */
-	size_t		oldsize;
-	size_t		newsize;
-	bool		isrelfile;		/* is it a relation data file? */
+	datapagemap_t target_modified_pages;
 
-	datapagemap_t pagemap;
+	/*
+	 * Status of the file in the source.
+	 */
+	bool		source_exists;
+	file_type_t source_type;
+	size_t		source_size;
+	char	   *source_link_target; /* for a symlink */
 
-	/* for a symlink */
-	char	   *link_target;
+	/*
+	 * What will we do to the file?
+	 */
+	file_action_t action;
 
 	struct file_entry_t *next;
 } file_entry_t;
@@ -70,20 +87,19 @@ typedef struct filemap_t
 
 	/*
 	 * After processing all the remote files, the entries in the linked list
-	 * are moved to this array. After processing local files, too, all the
+	 * are moved to this array.  After processing local files, too, all the
 	 * local entries are added to the array by filemap_finalize, and sorted in
-	 * the final order. After filemap_finalize, all the entries are in the
+	 * the final order.  After filemap_finalize, all the entries are in the
 	 * array, and the linked list is empty.
 	 */
 	file_entry_t **array;
 	int			narray;			/* current length of array */
 
 	/*
-	 * Summary information. total_size is the total size of the source
-	 * cluster, and fetch_size is the number of bytes that needs to be copied.
+	 * Summary information.
 	 */
-	uint64		total_size;
-	uint64		fetch_size;
+	uint64		total_size;		/* total size of the source cluster */
+	uint64		fetch_size;		/* number of bytes that needs to be copied */
 } filemap_t;
 
 extern filemap_t *filemap;
@@ -94,11 +110,12 @@ extern void print_filemap(void);
 
 /* Functions for populating the filemap */
 extern void process_source_file(const char *path, file_type_t type,
-								size_t newsize, const char *link_target);
+								size_t size, const char *link_target);
 extern void process_target_file(const char *path, file_type_t type,
-								size_t newsize, const char *link_target);
-extern void process_block_change(ForkNumber forknum, RelFileNode rnode,
-								 BlockNumber blkno);
+								size_t size, const char *link_target);
+extern void process_target_wal_block_change(ForkNumber forknum,
+											RelFileNode rnode,
+											BlockNumber blkno);
 extern void filemap_finalize(void);
 
 #endif							/* FILEMAP_H */
diff --git a/src/bin/pg_rewind/libpq_fetch.c b/src/bin/pg_rewind/libpq_fetch.c
index bf4dfc23b96..7fc9161b8c8 100644
--- a/src/bin/pg_rewind/libpq_fetch.c
+++ b/src/bin/pg_rewind/libpq_fetch.c
@@ -465,7 +465,7 @@ libpq_executeFileMap(filemap_t *map)
 		entry = map->array[i];
 
 		/* If this is a relation file, copy the modified blocks */
-		execute_pagemap(&entry->pagemap, entry->path);
+		execute_pagemap(&entry->target_modified_pages, entry->path);
 
 		switch (entry->action)
 		{
@@ -476,15 +476,15 @@ libpq_executeFileMap(filemap_t *map)
 			case FILE_ACTION_COPY:
 				/* Truncate the old file out of the way, if any */
 				open_target_file(entry->path, true);
-				fetch_file_range(entry->path, 0, entry->newsize);
+				fetch_file_range(entry->path, 0, entry->source_size);
 				break;
 
 			case FILE_ACTION_TRUNCATE:
-				truncate_target_file(entry->path, entry->newsize);
+				truncate_target_file(entry->path, entry->source_size);
 				break;
 
 			case FILE_ACTION_COPY_TAIL:
-				fetch_file_range(entry->path, entry->oldsize, entry->newsize);
+				fetch_file_range(entry->path, entry->target_size, entry->source_size);
 				break;
 
 			case FILE_ACTION_REMOVE:
@@ -494,6 +494,10 @@ libpq_executeFileMap(filemap_t *map)
 			case FILE_ACTION_CREATE:
 				create_target(entry);
 				break;
+
+			case FILE_ACTION_UNDECIDED:
+				pg_fatal("no action decided for \"%s\"", entry->path);
+				break;
 		}
 	}
 
diff --git a/src/bin/pg_rewind/parsexlog.c b/src/bin/pg_rewind/parsexlog.c
index 2229c86f9af..2baeb74ae93 100644
--- a/src/bin/pg_rewind/parsexlog.c
+++ b/src/bin/pg_rewind/parsexlog.c
@@ -436,6 +436,6 @@ extractPageInfo(XLogReaderState *record)
 		if (forknum != MAIN_FORKNUM)
 			continue;
 
-		process_block_change(forknum, rnode, blkno);
+		process_target_wal_block_change(forknum, rnode, blkno);
 	}
 }
diff --git a/src/bin/pg_rewind/pg_rewind.c b/src/bin/pg_rewind/pg_rewind.c
index c9b9e480c0f..210984d302b 100644
--- a/src/bin/pg_rewind/pg_rewind.c
+++ b/src/bin/pg_rewind/pg_rewind.c
@@ -369,7 +369,7 @@ main(int argc, char **argv)
 				chkpttli);
 
 	/*
-	 * Build the filemap, by comparing the source and target data directories.
+	 * Collect information about all files in the target and source systems.
 	 */
 	filemap_create();
 	if (showprogress)
@@ -390,8 +390,12 @@ main(int argc, char **argv)
 		pg_log_info("reading WAL in target");
 	extractPageMap(datadir_target, chkptrec, lastcommontliIndex,
 				   ControlFile_target.checkPoint, restore_command);
-	filemap_finalize();
 
+	/*
+	 * We have collected all information we need from both systems. Decide
+	 * what to do with each file.
+	 */
+	filemap_finalize();
 	if (showprogress)
 		calculate_totals();
 
-- 
2.20.1


--------------BF34D0120055DC3839060F92
Content-Type: text/x-patch; charset=UTF-8;
 name="v2-0003-pg_rewind-Replace-the-hybrid-list-array-data-stru.patch"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
 filename*0="v2-0003-pg_rewind-Replace-the-hybrid-list-array-data-stru.pa";
 filename*1="tch"



^ permalink  raw  reply  [nested|flat] 41+ messages in thread

* [PATCH v2 2/5] Refactor pg_rewind for more clear decision making.
@ 2020-08-19 12:34 Heikki Linnakangas <[email protected]>
  0 siblings, 0 replies; 41+ messages in thread

From: Heikki Linnakangas @ 2020-08-19 12:34 UTC (permalink / raw)

Deciding what to do with each file is now a separate step after all the
necessary information has been gathered. It is more clear that way.
Previously, the decision-making was divided between process_source_file()
and process_target_file(), and it was a bit hard to piece together what the
overall rules were.
---
 src/bin/pg_rewind/copy_fetch.c  |  14 +-
 src/bin/pg_rewind/file_ops.c    |  16 +-
 src/bin/pg_rewind/filemap.c     | 569 ++++++++++++++++----------------
 src/bin/pg_rewind/filemap.h     |  67 ++--
 src/bin/pg_rewind/libpq_fetch.c |  12 +-
 src/bin/pg_rewind/parsexlog.c   |   2 +-
 src/bin/pg_rewind/pg_rewind.c   |   8 +-
 7 files changed, 371 insertions(+), 317 deletions(-)

diff --git a/src/bin/pg_rewind/copy_fetch.c b/src/bin/pg_rewind/copy_fetch.c
index 1edab5f1867..18fad32600e 100644
--- a/src/bin/pg_rewind/copy_fetch.c
+++ b/src/bin/pg_rewind/copy_fetch.c
@@ -210,7 +210,7 @@ copy_executeFileMap(filemap_t *map)
 	for (i = 0; i < map->narray; i++)
 	{
 		entry = map->array[i];
-		execute_pagemap(&entry->pagemap, entry->path);
+		execute_pagemap(&entry->target_modified_pages, entry->path);
 
 		switch (entry->action)
 		{
@@ -219,16 +219,16 @@ copy_executeFileMap(filemap_t *map)
 				break;
 
 			case FILE_ACTION_COPY:
-				rewind_copy_file_range(entry->path, 0, entry->newsize, true);
+				rewind_copy_file_range(entry->path, 0, entry->source_size, true);
 				break;
 
 			case FILE_ACTION_TRUNCATE:
-				truncate_target_file(entry->path, entry->newsize);
+				truncate_target_file(entry->path, entry->source_size);
 				break;
 
 			case FILE_ACTION_COPY_TAIL:
-				rewind_copy_file_range(entry->path, entry->oldsize,
-									   entry->newsize, false);
+				rewind_copy_file_range(entry->path, entry->target_size,
+									   entry->source_size, false);
 				break;
 
 			case FILE_ACTION_CREATE:
@@ -238,6 +238,10 @@ copy_executeFileMap(filemap_t *map)
 			case FILE_ACTION_REMOVE:
 				remove_target(entry);
 				break;
+
+			case FILE_ACTION_UNDECIDED:
+				pg_fatal("no action decided for \"%s\"", entry->path);
+				break;
 		}
 	}
 
diff --git a/src/bin/pg_rewind/file_ops.c b/src/bin/pg_rewind/file_ops.c
index 55439db20ba..ec37d0b2e0d 100644
--- a/src/bin/pg_rewind/file_ops.c
+++ b/src/bin/pg_rewind/file_ops.c
@@ -126,8 +126,9 @@ void
 remove_target(file_entry_t *entry)
 {
 	Assert(entry->action == FILE_ACTION_REMOVE);
+	Assert(entry->target_exists);
 
-	switch (entry->type)
+	switch (entry->target_type)
 	{
 		case FILE_TYPE_DIRECTORY:
 			remove_target_dir(entry->path);
@@ -140,6 +141,10 @@ remove_target(file_entry_t *entry)
 		case FILE_TYPE_SYMLINK:
 			remove_target_symlink(entry->path);
 			break;
+
+		case FILE_TYPE_UNDEFINED:
+			pg_fatal("undefined file type for \"%s\"", entry->path);
+			break;
 	}
 }
 
@@ -147,21 +152,26 @@ void
 create_target(file_entry_t *entry)
 {
 	Assert(entry->action == FILE_ACTION_CREATE);
+	Assert(!entry->target_exists);
 
-	switch (entry->type)
+	switch (entry->source_type)
 	{
 		case FILE_TYPE_DIRECTORY:
 			create_target_dir(entry->path);
 			break;
 
 		case FILE_TYPE_SYMLINK:
-			create_target_symlink(entry->path, entry->link_target);
+			create_target_symlink(entry->path, entry->source_link_target);
 			break;
 
 		case FILE_TYPE_REGULAR:
 			/* can't happen. Regular files are created with open_target_file. */
 			pg_fatal("invalid action (CREATE) for regular file");
 			break;
+
+		case FILE_TYPE_UNDEFINED:
+			pg_fatal("undefined file type for \"%s\"", entry->path);
+			break;
 	}
 }
 
diff --git a/src/bin/pg_rewind/filemap.c b/src/bin/pg_rewind/filemap.c
index 1879257b66a..7971daeda5e 100644
--- a/src/bin/pg_rewind/filemap.c
+++ b/src/bin/pg_rewind/filemap.c
@@ -26,6 +26,8 @@ static bool isRelDataFile(const char *path);
 static char *datasegpath(RelFileNode rnode, ForkNumber forknum,
 						 BlockNumber segno);
 static int	path_cmp(const void *a, const void *b);
+
+static file_entry_t *get_filemap_entry(const char *path, bool create);
 static int	final_filemap_cmp(const void *a, const void *b);
 static void filemap_list_to_array(filemap_t *map);
 static bool check_file_excluded(const char *path, bool is_source);
@@ -146,6 +148,65 @@ filemap_create(void)
 	filemap = map;
 }
 
+/* Look up or create entry for 'path' */
+static file_entry_t *
+get_filemap_entry(const char *path, bool create)
+{
+	filemap_t  *map = filemap;
+	file_entry_t *entry;
+	file_entry_t **e;
+	file_entry_t key;
+	file_entry_t *key_ptr;
+
+	if (map->array)
+	{
+		key.path = (char *) path;
+		key_ptr = &key;
+		e = bsearch(&key_ptr, map->array, map->narray, sizeof(file_entry_t *),
+					path_cmp);
+	}
+	else
+		e = NULL;
+
+	if (e)
+		entry = *e;
+	else if (!create)
+		entry = NULL;
+	else
+	{
+		/* Create a new entry for this file */
+		entry = pg_malloc(sizeof(file_entry_t));
+		entry->path = pg_strdup(path);
+		entry->isrelfile = isRelDataFile(path);
+		entry->action = FILE_ACTION_UNDECIDED;
+
+		entry->target_exists = false;
+		entry->target_type = FILE_TYPE_UNDEFINED;
+		entry->target_size = 0;
+		entry->target_link_target = NULL;
+		entry->target_modified_pages.bitmap = NULL;
+		entry->target_modified_pages.bitmapsize = 0;
+
+		entry->source_exists = false;
+		entry->source_type = FILE_TYPE_UNDEFINED;
+		entry->source_size = 0;
+		entry->source_link_target = NULL;
+
+		entry->next = NULL;
+
+		if (map->last)
+		{
+			map->last->next = entry;
+			map->last = entry;
+		}
+		else
+			map->first = map->last = entry;
+		map->nlist++;
+	}
+
+	return entry;
+}
+
 /*
  * Callback for processing source file list.
  *
@@ -154,25 +215,12 @@ filemap_create(void)
  * exists in the target and whether the size matches.
  */
 void
-process_source_file(const char *path, file_type_t type, size_t newsize,
+process_source_file(const char *path, file_type_t type, size_t size,
 					const char *link_target)
 {
-	bool		exists;
-	char		localpath[MAXPGPATH];
-	struct stat statbuf;
-	filemap_t  *map = filemap;
-	file_action_t action = FILE_ACTION_NONE;
-	size_t		oldsize = 0;
 	file_entry_t *entry;
 
-	Assert(map->array == NULL);
-
-	/*
-	 * Skip any files matching the exclusion filters. This has the effect to
-	 * remove all those files on the target.
-	 */
-	if (check_file_excluded(path, true))
-		return;
+	Assert(filemap->array == NULL);
 
 	/*
 	 * Pretend that pg_wal is a directory, even if it's really a symlink. We
@@ -182,16 +230,6 @@ process_source_file(const char *path, file_type_t type, size_t newsize,
 	if (strcmp(path, "pg_wal") == 0 && type == FILE_TYPE_SYMLINK)
 		type = FILE_TYPE_DIRECTORY;
 
-	/*
-	 * Skip temporary files, .../pgsql_tmp/... and .../pgsql_tmp.* in source.
-	 * This has the effect that all temporary files in the destination will be
-	 * removed.
-	 */
-	if (strstr(path, "/" PG_TEMP_FILE_PREFIX) != NULL)
-		return;
-	if (strstr(path, "/" PG_TEMP_FILES_DIR "/") != NULL)
-		return;
-
 	/*
 	 * sanity check: a filename that looks like a data file better be a
 	 * regular file
@@ -199,142 +237,12 @@ process_source_file(const char *path, file_type_t type, size_t newsize,
 	if (type != FILE_TYPE_REGULAR && isRelDataFile(path))
 		pg_fatal("data file \"%s\" in source is not a regular file", path);
 
-	snprintf(localpath, sizeof(localpath), "%s/%s", datadir_target, path);
-
-	/* Does the corresponding file exist in the target data dir? */
-	if (lstat(localpath, &statbuf) < 0)
-	{
-		if (errno != ENOENT)
-			pg_fatal("could not stat file \"%s\": %m",
-					 localpath);
-
-		exists = false;
-	}
-	else
-		exists = true;
-
-	switch (type)
-	{
-		case FILE_TYPE_DIRECTORY:
-			if (exists && !S_ISDIR(statbuf.st_mode) && strcmp(path, "pg_wal") != 0)
-			{
-				/* it's a directory in source, but not in target. Strange.. */
-				pg_fatal("\"%s\" is not a directory", localpath);
-			}
-
-			if (!exists)
-				action = FILE_ACTION_CREATE;
-			else
-				action = FILE_ACTION_NONE;
-			oldsize = 0;
-			break;
-
-		case FILE_TYPE_SYMLINK:
-			if (exists &&
-#ifndef WIN32
-				!S_ISLNK(statbuf.st_mode)
-#else
-				!pgwin32_is_junction(localpath)
-#endif
-				)
-			{
-				/*
-				 * It's a symbolic link in source, but not in target.
-				 * Strange..
-				 */
-				pg_fatal("\"%s\" is not a symbolic link", localpath);
-			}
-
-			if (!exists)
-				action = FILE_ACTION_CREATE;
-			else
-				action = FILE_ACTION_NONE;
-			oldsize = 0;
-			break;
-
-		case FILE_TYPE_REGULAR:
-			if (exists && !S_ISREG(statbuf.st_mode))
-				pg_fatal("\"%s\" is not a regular file", localpath);
-
-			if (!exists || !isRelDataFile(path))
-			{
-				/*
-				 * File exists in source, but not in target. Or it's a
-				 * non-data file that we have no special processing for. Copy
-				 * it in toto.
-				 *
-				 * An exception: PG_VERSIONs should be identical, but avoid
-				 * overwriting it for paranoia.
-				 */
-				if (pg_str_endswith(path, "PG_VERSION"))
-				{
-					action = FILE_ACTION_NONE;
-					oldsize = statbuf.st_size;
-				}
-				else
-				{
-					action = FILE_ACTION_COPY;
-					oldsize = 0;
-				}
-			}
-			else
-			{
-				/*
-				 * It's a data file that exists in both.
-				 *
-				 * If it's larger in target, we can truncate it. There will
-				 * also be a WAL record of the truncation in the source
-				 * system, so WAL replay would eventually truncate the target
-				 * too, but we might as well do it now.
-				 *
-				 * If it's smaller in the target, it means that it has been
-				 * truncated in the target, or enlarged in the source, or
-				 * both. If it was truncated in the target, we need to copy
-				 * the missing tail from the source system. If it was enlarged
-				 * in the source system, there will be WAL records in the
-				 * source system for the new blocks, so we wouldn't need to
-				 * copy them here. But we don't know which scenario we're
-				 * dealing with, and there's no harm in copying the missing
-				 * blocks now, so do it now.
-				 *
-				 * If it's the same size, do nothing here. Any blocks modified
-				 * in the target will be copied based on parsing the target
-				 * system's WAL, and any blocks modified in the source will be
-				 * updated after rewinding, when the source system's WAL is
-				 * replayed.
-				 */
-				oldsize = statbuf.st_size;
-				if (oldsize < newsize)
-					action = FILE_ACTION_COPY_TAIL;
-				else if (oldsize > newsize)
-					action = FILE_ACTION_TRUNCATE;
-				else
-					action = FILE_ACTION_NONE;
-			}
-			break;
-	}
-
-	/* Create a new entry for this file */
-	entry = pg_malloc(sizeof(file_entry_t));
-	entry->path = pg_strdup(path);
-	entry->type = type;
-	entry->action = action;
-	entry->oldsize = oldsize;
-	entry->newsize = newsize;
-	entry->link_target = link_target ? pg_strdup(link_target) : NULL;
-	entry->next = NULL;
-	entry->pagemap.bitmap = NULL;
-	entry->pagemap.bitmapsize = 0;
-	entry->isrelfile = isRelDataFile(path);
-
-	if (map->last)
-	{
-		map->last->next = entry;
-		map->last = entry;
-	}
-	else
-		map->first = map->last = entry;
-	map->nlist++;
+	/* Remember this source file */
+	entry = get_filemap_entry(path, true);
+	entry->source_exists = true;
+	entry->source_type = type;
+	entry->source_size = size;
+	entry->source_link_target = link_target ? pg_strdup(link_target) : NULL;
 }
 
 /*
@@ -345,14 +253,9 @@ process_source_file(const char *path, file_type_t type, size_t newsize,
  * deletion.
  */
 void
-process_target_file(const char *path, file_type_t type, size_t oldsize,
+process_target_file(const char *path, file_type_t type, size_t size,
 					const char *link_target)
 {
-	bool		exists;
-	char		localpath[MAXPGPATH];
-	struct stat statbuf;
-	file_entry_t key;
-	file_entry_t *key_ptr;
 	filemap_t  *map = filemap;
 	file_entry_t *entry;
 
@@ -361,17 +264,6 @@ process_target_file(const char *path, file_type_t type, size_t oldsize,
 	 * from the target data folder all paths which have been filtered out from
 	 * the source data folder when processing the source files.
 	 */
-
-	snprintf(localpath, sizeof(localpath), "%s/%s", datadir_target, path);
-	if (lstat(localpath, &statbuf) < 0)
-	{
-		if (errno != ENOENT)
-			pg_fatal("could not stat file \"%s\": %m",
-					 localpath);
-
-		exists = false;
-	}
-
 	if (map->array == NULL)
 	{
 		/* on first call, initialize lookup array */
@@ -389,120 +281,76 @@ process_target_file(const char *path, file_type_t type, size_t oldsize,
 	}
 
 	/*
-	 * Like in process_source_file, pretend that xlog is always a  directory.
+	 * Like in process_source_file, pretend that pg_wal is always a directory.
 	 */
 	if (strcmp(path, "pg_wal") == 0 && type == FILE_TYPE_SYMLINK)
 		type = FILE_TYPE_DIRECTORY;
 
-	key.path = (char *) path;
-	key_ptr = &key;
-	exists = (bsearch(&key_ptr, map->array, map->narray, sizeof(file_entry_t *),
-					  path_cmp) != NULL);
-
-	/* Remove any file or folder that doesn't exist in the source system. */
-	if (!exists)
-	{
-		entry = pg_malloc(sizeof(file_entry_t));
-		entry->path = pg_strdup(path);
-		entry->type = type;
-		entry->action = FILE_ACTION_REMOVE;
-		entry->oldsize = oldsize;
-		entry->newsize = 0;
-		entry->link_target = link_target ? pg_strdup(link_target) : NULL;
-		entry->next = NULL;
-		entry->pagemap.bitmap = NULL;
-		entry->pagemap.bitmapsize = 0;
-		entry->isrelfile = isRelDataFile(path);
-
-		if (map->last == NULL)
-			map->first = entry;
-		else
-			map->last->next = entry;
-		map->last = entry;
-		map->nlist++;
-	}
-	else
-	{
-		/*
-		 * We already handled all files that exist in the source system in
-		 * process_source_file().
-		 */
-	}
+	/* Remember this target file */
+	entry = get_filemap_entry(path, true);
+	entry->target_exists = true;
+	entry->target_type = type;
+	entry->target_size = size;
+	entry->target_link_target = link_target ? pg_strdup(link_target) : NULL;
 }
 
 /*
  * This callback gets called while we read the WAL in the target, for every
- * block that have changed in the target system. It makes note of all the
+ * block that have changed in the target system.  It makes note of all the
  * changed blocks in the pagemap of the file.
+ *
+ * NOTE: All the files on both systems must have already been added to the
+ * file map!
  */
 void
-process_block_change(ForkNumber forknum, RelFileNode rnode, BlockNumber blkno)
+process_target_wal_block_change(ForkNumber forknum, RelFileNode rnode,
+								BlockNumber blkno)
 {
 	char	   *path;
-	file_entry_t key;
-	file_entry_t *key_ptr;
 	file_entry_t *entry;
 	BlockNumber blkno_inseg;
 	int			segno;
-	filemap_t  *map = filemap;
-	file_entry_t **e;
 
-	Assert(map->array);
+	Assert(filemap->array);
 
 	segno = blkno / RELSEG_SIZE;
 	blkno_inseg = blkno % RELSEG_SIZE;
 
 	path = datasegpath(rnode, forknum, segno);
-
-	key.path = (char *) path;
-	key_ptr = &key;
-
-	e = bsearch(&key_ptr, map->array, map->narray, sizeof(file_entry_t *),
-				path_cmp);
-	if (e)
-		entry = *e;
-	else
-		entry = NULL;
+	entry = get_filemap_entry(path, false);
 	pfree(path);
 
 	if (entry)
 	{
-		Assert(entry->isrelfile);
-
-		switch (entry->action)
-		{
-			case FILE_ACTION_NONE:
-			case FILE_ACTION_TRUNCATE:
-				/* skip if we're truncating away the modified block anyway */
-				if ((blkno_inseg + 1) * BLCKSZ <= entry->newsize)
-					datapagemap_add(&entry->pagemap, blkno_inseg);
-				break;
+		int64		end_offset;
 
-			case FILE_ACTION_COPY_TAIL:
-
-				/*
-				 * skip the modified block if it is part of the "tail" that
-				 * we're copying anyway.
-				 */
-				if ((blkno_inseg + 1) * BLCKSZ <= entry->oldsize)
-					datapagemap_add(&entry->pagemap, blkno_inseg);
-				break;
+		Assert(entry->isrelfile);
 
-			case FILE_ACTION_COPY:
-			case FILE_ACTION_REMOVE:
-				break;
+		if (entry->target_type != FILE_TYPE_REGULAR)
+			pg_fatal("unexpected page modification for directory or symbolic link \"%s\"",
+					 entry->path);
 
-			case FILE_ACTION_CREATE:
-				pg_fatal("unexpected page modification for directory or symbolic link \"%s\"", entry->path);
-		}
+		/*
+		 * If the block beyond the EOF in the source system, no need to
+		 * remember it now, because we're going to truncate it away from the
+		 * target anyway. Also no need to remember the block if it's beyond
+		 * the current EOF in the target system; we will copy it over with the
+		 * "tail" from the source system, anyway.
+		 */
+		end_offset = (blkno_inseg + 1) * BLCKSZ;
+		if (end_offset <= entry->source_size &&
+			end_offset <= entry->target_size)
+			datapagemap_add(&entry->target_modified_pages, blkno_inseg);
 	}
 	else
 	{
 		/*
 		 * If we don't have any record of this file in the file map, it means
-		 * that it's a relation that doesn't exist in the source system, and
-		 * it was subsequently removed in the target system, too. We can
-		 * safely ignore it.
+		 * that it's a relation that doesn't exist in the source system.  It
+		 * could exist in the target system; we haven't moved the target-only
+		 * entries from the linked list to the array yet!  But in any case, if
+		 * it doesn't exist in the source it will be removed from the target
+		 * too, and we can safely ignore it.
 		 */
 	}
 }
@@ -593,16 +441,6 @@ filemap_list_to_array(filemap_t *map)
 	map->first = map->last = NULL;
 }
 
-void
-filemap_finalize(void)
-{
-	filemap_t  *map = filemap;
-
-	filemap_list_to_array(map);
-	qsort(map->array, map->narray, sizeof(file_entry_t *),
-		  final_filemap_cmp);
-}
-
 static const char *
 action_to_str(file_action_t action)
 {
@@ -643,26 +481,26 @@ calculate_totals(void)
 	{
 		entry = map->array[i];
 
-		if (entry->type != FILE_TYPE_REGULAR)
+		if (entry->source_type != FILE_TYPE_REGULAR)
 			continue;
 
-		map->total_size += entry->newsize;
+		map->total_size += entry->source_size;
 
 		if (entry->action == FILE_ACTION_COPY)
 		{
-			map->fetch_size += entry->newsize;
+			map->fetch_size += entry->source_size;
 			continue;
 		}
 
 		if (entry->action == FILE_ACTION_COPY_TAIL)
-			map->fetch_size += (entry->newsize - entry->oldsize);
+			map->fetch_size += (entry->source_size - entry->target_size);
 
-		if (entry->pagemap.bitmapsize > 0)
+		if (entry->target_modified_pages.bitmapsize > 0)
 		{
 			datapagemap_iterator_t *iter;
 			BlockNumber blk;
 
-			iter = datapagemap_iterate(&entry->pagemap);
+			iter = datapagemap_iterate(&entry->target_modified_pages);
 			while (datapagemap_next(iter, &blk))
 				map->fetch_size += BLCKSZ;
 
@@ -682,13 +520,13 @@ print_filemap(void)
 	{
 		entry = map->array[i];
 		if (entry->action != FILE_ACTION_NONE ||
-			entry->pagemap.bitmapsize > 0)
+			entry->target_modified_pages.bitmapsize > 0)
 		{
 			pg_log_debug("%s (%s)", entry->path,
 						 action_to_str(entry->action));
 
-			if (entry->pagemap.bitmapsize > 0)
-				datapagemap_print(&entry->pagemap);
+			if (entry->target_modified_pages.bitmapsize > 0)
+				datapagemap_print(&entry->target_modified_pages);
 		}
 	}
 	fflush(stdout);
@@ -837,3 +675,180 @@ final_filemap_cmp(const void *a, const void *b)
 	else
 		return strcmp(fa->path, fb->path);
 }
+
+/*
+ * Decide what action to perform to a file.
+ */
+static file_action_t
+decide_file_action(file_entry_t *entry)
+{
+	const char *path = entry->path;
+
+	/*
+	 * Don't touch the control file. It is handled specially, after copying
+	 * all the other files.
+	 */
+	if (strcmp(path, "global/pg_control") == 0)
+		return FILE_ACTION_NONE;
+
+	/*
+	 * Remove all files matching the exclusion filters in the target.
+	 */
+	if (check_file_excluded(path, true))
+	{
+		if (entry->target_exists)
+			return FILE_ACTION_REMOVE;
+		else
+			return FILE_ACTION_NONE;
+	}
+
+	/*
+	 * Also remove all temporary files, .../pgsql_tmp/... and .../pgsql_tmp.*
+	 * in the target.
+	 */
+	if (strstr(path, "/" PG_TEMP_FILE_PREFIX) != NULL ||
+		strstr(path, "/" PG_TEMP_FILES_DIR "/") != NULL)
+	{
+		if (entry->target_exists)
+			return FILE_ACTION_REMOVE;
+		else
+			return FILE_ACTION_NONE;
+	}
+
+	/*
+	 * Handle cases where the file is missing from one of the systems.
+	 */
+	if (!entry->target_exists && entry->source_exists)
+	{
+		/*
+		 * File exists in source, but not in target. Copy it in toto. (If it's
+		 * a relation data file, WAL replay after rewinding should re-create
+		 * it anyway. But there's no harm in copying it now.)
+		 */
+		switch (entry->source_type)
+		{
+			case FILE_TYPE_DIRECTORY:
+			case FILE_TYPE_SYMLINK:
+				return FILE_ACTION_CREATE;
+			case FILE_TYPE_REGULAR:
+				return FILE_ACTION_COPY;
+			case FILE_TYPE_UNDEFINED:
+				pg_fatal("unknown file type for \"%s\"", entry->path);
+				break;
+		}
+	}
+	else if (entry->target_exists && !entry->source_exists)
+	{
+		/* File exists in target, but not source. Remove it. */
+		return FILE_ACTION_REMOVE;
+	}
+	else if (!entry->target_exists && !entry->source_exists)
+	{
+		/*
+		 * Doesn't exist in either server. Why does it have an entry in the
+		 * first place??
+		 */
+		return FILE_ACTION_NONE;
+	}
+
+	/*
+	 * Otherwise, the file exists on both systems
+	 */
+	Assert(entry->target_exists && entry->source_exists);
+
+	if (entry->source_type != entry->target_type)
+	{
+		/* But it's a different kind of object. Strange.. */
+		pg_fatal("file \"%s\" is of different type in source and target", entry->path);
+	}
+
+	/*
+	 * PG_VERSION files should be identical on both systems, but avoid
+	 * overwriting them for paranoia.
+	 */
+	if (pg_str_endswith(entry->path, "PG_VERSION"))
+		return FILE_ACTION_NONE;
+
+	switch (entry->source_type)
+	{
+		case FILE_TYPE_DIRECTORY:
+			return FILE_ACTION_NONE;
+
+		case FILE_TYPE_SYMLINK:
+			/* FIXME: Check if it points to the same target? */
+			return FILE_ACTION_NONE;
+
+		case FILE_TYPE_REGULAR:
+			if (!entry->isrelfile)
+			{
+				/*
+				 * It's a non-data file that we have no special processing
+				 * for. Copy it in toto.
+				 */
+				return FILE_ACTION_COPY;
+			}
+			else
+			{
+				/*
+				 * It's a data file that exists in both systems.
+				 *
+				 * If it's larger in target, we can truncate it. There will
+				 * also be a WAL record of the truncation in the source
+				 * system, so WAL replay would eventually truncate the target
+				 * too, but we might as well do it now.
+				 *
+				 * If it's smaller in the target, it means that it has been
+				 * truncated in the target, or enlarged in the source, or
+				 * both. If it was truncated in the target, we need to copy
+				 * the missing tail from the source system. If it was enlarged
+				 * in the source system, there will be WAL records in the
+				 * source system for the new blocks, so we wouldn't need to
+				 * copy them here. But we don't know which scenario we're
+				 * dealing with, and there's no harm in copying the missing
+				 * blocks now, so do it now.
+				 *
+				 * If it's the same size, do nothing here. Any blocks modified
+				 * in the target will be copied based on parsing the target
+				 * system's WAL, and any blocks modified in the source will be
+				 * updated after rewinding, when the source system's WAL is
+				 * replayed.
+				 */
+				if (entry->target_size < entry->source_size)
+					return FILE_ACTION_COPY_TAIL;
+				else if (entry->target_size > entry->source_size)
+					return FILE_ACTION_TRUNCATE;
+				else
+					return FILE_ACTION_NONE;
+			}
+			break;
+
+		case FILE_TYPE_UNDEFINED:
+			pg_fatal("unknown file type for \"%s\"", path);
+			break;
+	}
+
+	/* unreachable */
+	pg_fatal("could not decide what to do with file \"%s\"", path);
+}
+
+/*
+ * Decide what to do with each file.
+ */
+void
+filemap_finalize()
+{
+	int			i;
+
+	filemap_list_to_array(filemap);
+
+	for (i = 0; i < filemap->narray; i++)
+	{
+		file_entry_t *entry = filemap->array[i];
+
+		entry->action = decide_file_action(entry);
+	}
+
+	/* Sort the actions to the order that they should be performed */
+	qsort(filemap->array, filemap->narray, sizeof(file_entry_t *),
+		  final_filemap_cmp);
+}
diff --git a/src/bin/pg_rewind/filemap.h b/src/bin/pg_rewind/filemap.h
index 0cb7425170c..a5e8df57f40 100644
--- a/src/bin/pg_rewind/filemap.h
+++ b/src/bin/pg_rewind/filemap.h
@@ -14,17 +14,21 @@
 
 /*
  * For every file found in the local or remote system, we have a file entry
- * which says what we are going to do with the file. For relation files,
- * there is also a page map, marking pages in the file that were changed
- * locally.
- *
- * The enum values are sorted in the order we want actions to be processed.
+ * that contains information about the file on both systems.  For relation
+ * files, there is also a page map that marks pages in the file that were
+ * changed in the target after the last common checkpoint.  Each entry also
+ * contains an 'action' field, which says what we are going to do with the
+ * file.
  */
+
+/* these enum values are sorted in the order we want actions to be processed */
 typedef enum
 {
+	FILE_ACTION_UNDECIDED = 0,	/* not decided yet */
+
 	FILE_ACTION_CREATE,			/* create local directory or symbolic link */
 	FILE_ACTION_COPY,			/* copy whole file, overwriting if exists */
-	FILE_ACTION_COPY_TAIL,		/* copy tail from 'oldsize' to 'newsize' */
+	FILE_ACTION_COPY_TAIL,		/* copy tail from 'source_size' to 'target_size' */
 	FILE_ACTION_NONE,			/* no action (we might still copy modified
 								 * blocks based on the parsed WAL) */
 	FILE_ACTION_TRUNCATE,		/* truncate local file to 'newsize' bytes */
@@ -33,6 +37,8 @@ typedef enum
 
 typedef enum
 {
+	FILE_TYPE_UNDEFINED = 0,
+
 	FILE_TYPE_REGULAR,
 	FILE_TYPE_DIRECTORY,
 	FILE_TYPE_SYMLINK
@@ -41,19 +47,30 @@ typedef enum
 typedef struct file_entry_t
 {
 	char	   *path;
-	file_type_t type;
+	bool		isrelfile;		/* is it a relation data file? */
 
-	file_action_t action;
+	/*
+	 * Status of the file in the target.
+	 */
+	bool		target_exists;
+	file_type_t target_type;
+	size_t		target_size; /* for a regular file */
+	char	   *target_link_target; /* for a symlink */
 
-	/* for a regular file */
-	size_t		oldsize;
-	size_t		newsize;
-	bool		isrelfile;		/* is it a relation data file? */
+	datapagemap_t target_modified_pages;
 
-	datapagemap_t pagemap;
+	/*
+	 * Status of the file in the source.
+	 */
+	bool		source_exists;
+	file_type_t source_type;
+	size_t		source_size;
+	char	   *source_link_target; /* for a symlink */
 
-	/* for a symlink */
-	char	   *link_target;
+	/*
+	 * What will we do to the file?
+	 */
+	file_action_t action;
 
 	struct file_entry_t *next;
 } file_entry_t;
@@ -70,20 +87,19 @@ typedef struct filemap_t
 
 	/*
 	 * After processing all the remote files, the entries in the linked list
-	 * are moved to this array. After processing local files, too, all the
+	 * are moved to this array.  After processing local files, too, all the
 	 * local entries are added to the array by filemap_finalize, and sorted in
-	 * the final order. After filemap_finalize, all the entries are in the
+	 * the final order.  After filemap_finalize, all the entries are in the
 	 * array, and the linked list is empty.
 	 */
 	file_entry_t **array;
 	int			narray;			/* current length of array */
 
 	/*
-	 * Summary information. total_size is the total size of the source
-	 * cluster, and fetch_size is the number of bytes that needs to be copied.
+	 * Summary information.
 	 */
-	uint64		total_size;
-	uint64		fetch_size;
+	uint64		total_size;		/* total size of the source cluster */
+	uint64		fetch_size;		/* number of bytes that needs to be copied */
 } filemap_t;
 
 extern filemap_t *filemap;
@@ -94,11 +110,12 @@ extern void print_filemap(void);
 
 /* Functions for populating the filemap */
 extern void process_source_file(const char *path, file_type_t type,
-								size_t newsize, const char *link_target);
+								size_t size, const char *link_target);
 extern void process_target_file(const char *path, file_type_t type,
-								size_t newsize, const char *link_target);
-extern void process_block_change(ForkNumber forknum, RelFileNode rnode,
-								 BlockNumber blkno);
+								size_t size, const char *link_target);
+extern void process_target_wal_block_change(ForkNumber forknum,
+											RelFileNode rnode,
+											BlockNumber blkno);
 extern void filemap_finalize(void);
 
 #endif							/* FILEMAP_H */
diff --git a/src/bin/pg_rewind/libpq_fetch.c b/src/bin/pg_rewind/libpq_fetch.c
index bf4dfc23b96..7fc9161b8c8 100644
--- a/src/bin/pg_rewind/libpq_fetch.c
+++ b/src/bin/pg_rewind/libpq_fetch.c
@@ -465,7 +465,7 @@ libpq_executeFileMap(filemap_t *map)
 		entry = map->array[i];
 
 		/* If this is a relation file, copy the modified blocks */
-		execute_pagemap(&entry->pagemap, entry->path);
+		execute_pagemap(&entry->target_modified_pages, entry->path);
 
 		switch (entry->action)
 		{
@@ -476,15 +476,15 @@ libpq_executeFileMap(filemap_t *map)
 			case FILE_ACTION_COPY:
 				/* Truncate the old file out of the way, if any */
 				open_target_file(entry->path, true);
-				fetch_file_range(entry->path, 0, entry->newsize);
+				fetch_file_range(entry->path, 0, entry->source_size);
 				break;
 
 			case FILE_ACTION_TRUNCATE:
-				truncate_target_file(entry->path, entry->newsize);
+				truncate_target_file(entry->path, entry->source_size);
 				break;
 
 			case FILE_ACTION_COPY_TAIL:
-				fetch_file_range(entry->path, entry->oldsize, entry->newsize);
+				fetch_file_range(entry->path, entry->target_size, entry->source_size);
 				break;
 
 			case FILE_ACTION_REMOVE:
@@ -494,6 +494,10 @@ libpq_executeFileMap(filemap_t *map)
 			case FILE_ACTION_CREATE:
 				create_target(entry);
 				break;
+
+			case FILE_ACTION_UNDECIDED:
+				pg_fatal("no action decided for \"%s\"", entry->path);
+				break;
 		}
 	}
 
diff --git a/src/bin/pg_rewind/parsexlog.c b/src/bin/pg_rewind/parsexlog.c
index 2229c86f9af..2baeb74ae93 100644
--- a/src/bin/pg_rewind/parsexlog.c
+++ b/src/bin/pg_rewind/parsexlog.c
@@ -436,6 +436,6 @@ extractPageInfo(XLogReaderState *record)
 		if (forknum != MAIN_FORKNUM)
 			continue;
 
-		process_block_change(forknum, rnode, blkno);
+		process_target_wal_block_change(forknum, rnode, blkno);
 	}
 }
diff --git a/src/bin/pg_rewind/pg_rewind.c b/src/bin/pg_rewind/pg_rewind.c
index c9b9e480c0f..210984d302b 100644
--- a/src/bin/pg_rewind/pg_rewind.c
+++ b/src/bin/pg_rewind/pg_rewind.c
@@ -369,7 +369,7 @@ main(int argc, char **argv)
 				chkpttli);
 
 	/*
-	 * Build the filemap, by comparing the source and target data directories.
+	 * Collect information about all files in the target and source systems.
 	 */
 	filemap_create();
 	if (showprogress)
@@ -390,8 +390,12 @@ main(int argc, char **argv)
 		pg_log_info("reading WAL in target");
 	extractPageMap(datadir_target, chkptrec, lastcommontliIndex,
 				   ControlFile_target.checkPoint, restore_command);
-	filemap_finalize();
 
+	/*
+	 * We have collected all information we need from both systems. Decide
+	 * what to do with each file.
+	 */
+	filemap_finalize();
 	if (showprogress)
 		calculate_totals();
 
-- 
2.20.1


--------------BF34D0120055DC3839060F92
Content-Type: text/x-patch; charset=UTF-8;
 name="v2-0003-pg_rewind-Replace-the-hybrid-list-array-data-stru.patch"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
 filename*0="v2-0003-pg_rewind-Replace-the-hybrid-list-array-data-stru.pa";
 filename*1="tch"



^ permalink  raw  reply  [nested|flat] 41+ messages in thread

* [PATCH v2 2/5] Refactor pg_rewind for more clear decision making.
@ 2020-08-19 12:34 Heikki Linnakangas <[email protected]>
  0 siblings, 0 replies; 41+ messages in thread

From: Heikki Linnakangas @ 2020-08-19 12:34 UTC (permalink / raw)

Deciding what to do with each file is now a separate step after all the
necessary information has been gathered. It is more clear that way.
Previously, the decision-making was divided between process_source_file()
and process_target_file(), and it was a bit hard to piece together what the
overall rules were.
---
 src/bin/pg_rewind/copy_fetch.c  |  14 +-
 src/bin/pg_rewind/file_ops.c    |  16 +-
 src/bin/pg_rewind/filemap.c     | 569 ++++++++++++++++----------------
 src/bin/pg_rewind/filemap.h     |  67 ++--
 src/bin/pg_rewind/libpq_fetch.c |  12 +-
 src/bin/pg_rewind/parsexlog.c   |   2 +-
 src/bin/pg_rewind/pg_rewind.c   |   8 +-
 7 files changed, 371 insertions(+), 317 deletions(-)

diff --git a/src/bin/pg_rewind/copy_fetch.c b/src/bin/pg_rewind/copy_fetch.c
index 1edab5f1867..18fad32600e 100644
--- a/src/bin/pg_rewind/copy_fetch.c
+++ b/src/bin/pg_rewind/copy_fetch.c
@@ -210,7 +210,7 @@ copy_executeFileMap(filemap_t *map)
 	for (i = 0; i < map->narray; i++)
 	{
 		entry = map->array[i];
-		execute_pagemap(&entry->pagemap, entry->path);
+		execute_pagemap(&entry->target_modified_pages, entry->path);
 
 		switch (entry->action)
 		{
@@ -219,16 +219,16 @@ copy_executeFileMap(filemap_t *map)
 				break;
 
 			case FILE_ACTION_COPY:
-				rewind_copy_file_range(entry->path, 0, entry->newsize, true);
+				rewind_copy_file_range(entry->path, 0, entry->source_size, true);
 				break;
 
 			case FILE_ACTION_TRUNCATE:
-				truncate_target_file(entry->path, entry->newsize);
+				truncate_target_file(entry->path, entry->source_size);
 				break;
 
 			case FILE_ACTION_COPY_TAIL:
-				rewind_copy_file_range(entry->path, entry->oldsize,
-									   entry->newsize, false);
+				rewind_copy_file_range(entry->path, entry->target_size,
+									   entry->source_size, false);
 				break;
 
 			case FILE_ACTION_CREATE:
@@ -238,6 +238,10 @@ copy_executeFileMap(filemap_t *map)
 			case FILE_ACTION_REMOVE:
 				remove_target(entry);
 				break;
+
+			case FILE_ACTION_UNDECIDED:
+				pg_fatal("no action decided for \"%s\"", entry->path);
+				break;
 		}
 	}
 
diff --git a/src/bin/pg_rewind/file_ops.c b/src/bin/pg_rewind/file_ops.c
index 55439db20ba..ec37d0b2e0d 100644
--- a/src/bin/pg_rewind/file_ops.c
+++ b/src/bin/pg_rewind/file_ops.c
@@ -126,8 +126,9 @@ void
 remove_target(file_entry_t *entry)
 {
 	Assert(entry->action == FILE_ACTION_REMOVE);
+	Assert(entry->target_exists);
 
-	switch (entry->type)
+	switch (entry->target_type)
 	{
 		case FILE_TYPE_DIRECTORY:
 			remove_target_dir(entry->path);
@@ -140,6 +141,10 @@ remove_target(file_entry_t *entry)
 		case FILE_TYPE_SYMLINK:
 			remove_target_symlink(entry->path);
 			break;
+
+		case FILE_TYPE_UNDEFINED:
+			pg_fatal("undefined file type for \"%s\"", entry->path);
+			break;
 	}
 }
 
@@ -147,21 +152,26 @@ void
 create_target(file_entry_t *entry)
 {
 	Assert(entry->action == FILE_ACTION_CREATE);
+	Assert(!entry->target_exists);
 
-	switch (entry->type)
+	switch (entry->source_type)
 	{
 		case FILE_TYPE_DIRECTORY:
 			create_target_dir(entry->path);
 			break;
 
 		case FILE_TYPE_SYMLINK:
-			create_target_symlink(entry->path, entry->link_target);
+			create_target_symlink(entry->path, entry->source_link_target);
 			break;
 
 		case FILE_TYPE_REGULAR:
 			/* can't happen. Regular files are created with open_target_file. */
 			pg_fatal("invalid action (CREATE) for regular file");
 			break;
+
+		case FILE_TYPE_UNDEFINED:
+			pg_fatal("undefined file type for \"%s\"", entry->path);
+			break;
 	}
 }
 
diff --git a/src/bin/pg_rewind/filemap.c b/src/bin/pg_rewind/filemap.c
index 1879257b66a..7971daeda5e 100644
--- a/src/bin/pg_rewind/filemap.c
+++ b/src/bin/pg_rewind/filemap.c
@@ -26,6 +26,8 @@ static bool isRelDataFile(const char *path);
 static char *datasegpath(RelFileNode rnode, ForkNumber forknum,
 						 BlockNumber segno);
 static int	path_cmp(const void *a, const void *b);
+
+static file_entry_t *get_filemap_entry(const char *path, bool create);
 static int	final_filemap_cmp(const void *a, const void *b);
 static void filemap_list_to_array(filemap_t *map);
 static bool check_file_excluded(const char *path, bool is_source);
@@ -146,6 +148,65 @@ filemap_create(void)
 	filemap = map;
 }
 
+/* Look up or create entry for 'path' */
+static file_entry_t *
+get_filemap_entry(const char *path, bool create)
+{
+	filemap_t  *map = filemap;
+	file_entry_t *entry;
+	file_entry_t **e;
+	file_entry_t key;
+	file_entry_t *key_ptr;
+
+	if (map->array)
+	{
+		key.path = (char *) path;
+		key_ptr = &key;
+		e = bsearch(&key_ptr, map->array, map->narray, sizeof(file_entry_t *),
+					path_cmp);
+	}
+	else
+		e = NULL;
+
+	if (e)
+		entry = *e;
+	else if (!create)
+		entry = NULL;
+	else
+	{
+		/* Create a new entry for this file */
+		entry = pg_malloc(sizeof(file_entry_t));
+		entry->path = pg_strdup(path);
+		entry->isrelfile = isRelDataFile(path);
+		entry->action = FILE_ACTION_UNDECIDED;
+
+		entry->target_exists = false;
+		entry->target_type = FILE_TYPE_UNDEFINED;
+		entry->target_size = 0;
+		entry->target_link_target = NULL;
+		entry->target_modified_pages.bitmap = NULL;
+		entry->target_modified_pages.bitmapsize = 0;
+
+		entry->source_exists = false;
+		entry->source_type = FILE_TYPE_UNDEFINED;
+		entry->source_size = 0;
+		entry->source_link_target = NULL;
+
+		entry->next = NULL;
+
+		if (map->last)
+		{
+			map->last->next = entry;
+			map->last = entry;
+		}
+		else
+			map->first = map->last = entry;
+		map->nlist++;
+	}
+
+	return entry;
+}
+
 /*
  * Callback for processing source file list.
  *
@@ -154,25 +215,12 @@ filemap_create(void)
  * exists in the target and whether the size matches.
  */
 void
-process_source_file(const char *path, file_type_t type, size_t newsize,
+process_source_file(const char *path, file_type_t type, size_t size,
 					const char *link_target)
 {
-	bool		exists;
-	char		localpath[MAXPGPATH];
-	struct stat statbuf;
-	filemap_t  *map = filemap;
-	file_action_t action = FILE_ACTION_NONE;
-	size_t		oldsize = 0;
 	file_entry_t *entry;
 
-	Assert(map->array == NULL);
-
-	/*
-	 * Skip any files matching the exclusion filters. This has the effect to
-	 * remove all those files on the target.
-	 */
-	if (check_file_excluded(path, true))
-		return;
+	Assert(filemap->array == NULL);
 
 	/*
 	 * Pretend that pg_wal is a directory, even if it's really a symlink. We
@@ -182,16 +230,6 @@ process_source_file(const char *path, file_type_t type, size_t newsize,
 	if (strcmp(path, "pg_wal") == 0 && type == FILE_TYPE_SYMLINK)
 		type = FILE_TYPE_DIRECTORY;
 
-	/*
-	 * Skip temporary files, .../pgsql_tmp/... and .../pgsql_tmp.* in source.
-	 * This has the effect that all temporary files in the destination will be
-	 * removed.
-	 */
-	if (strstr(path, "/" PG_TEMP_FILE_PREFIX) != NULL)
-		return;
-	if (strstr(path, "/" PG_TEMP_FILES_DIR "/") != NULL)
-		return;
-
 	/*
 	 * sanity check: a filename that looks like a data file better be a
 	 * regular file
@@ -199,142 +237,12 @@ process_source_file(const char *path, file_type_t type, size_t newsize,
 	if (type != FILE_TYPE_REGULAR && isRelDataFile(path))
 		pg_fatal("data file \"%s\" in source is not a regular file", path);
 
-	snprintf(localpath, sizeof(localpath), "%s/%s", datadir_target, path);
-
-	/* Does the corresponding file exist in the target data dir? */
-	if (lstat(localpath, &statbuf) < 0)
-	{
-		if (errno != ENOENT)
-			pg_fatal("could not stat file \"%s\": %m",
-					 localpath);
-
-		exists = false;
-	}
-	else
-		exists = true;
-
-	switch (type)
-	{
-		case FILE_TYPE_DIRECTORY:
-			if (exists && !S_ISDIR(statbuf.st_mode) && strcmp(path, "pg_wal") != 0)
-			{
-				/* it's a directory in source, but not in target. Strange.. */
-				pg_fatal("\"%s\" is not a directory", localpath);
-			}
-
-			if (!exists)
-				action = FILE_ACTION_CREATE;
-			else
-				action = FILE_ACTION_NONE;
-			oldsize = 0;
-			break;
-
-		case FILE_TYPE_SYMLINK:
-			if (exists &&
-#ifndef WIN32
-				!S_ISLNK(statbuf.st_mode)
-#else
-				!pgwin32_is_junction(localpath)
-#endif
-				)
-			{
-				/*
-				 * It's a symbolic link in source, but not in target.
-				 * Strange..
-				 */
-				pg_fatal("\"%s\" is not a symbolic link", localpath);
-			}
-
-			if (!exists)
-				action = FILE_ACTION_CREATE;
-			else
-				action = FILE_ACTION_NONE;
-			oldsize = 0;
-			break;
-
-		case FILE_TYPE_REGULAR:
-			if (exists && !S_ISREG(statbuf.st_mode))
-				pg_fatal("\"%s\" is not a regular file", localpath);
-
-			if (!exists || !isRelDataFile(path))
-			{
-				/*
-				 * File exists in source, but not in target. Or it's a
-				 * non-data file that we have no special processing for. Copy
-				 * it in toto.
-				 *
-				 * An exception: PG_VERSIONs should be identical, but avoid
-				 * overwriting it for paranoia.
-				 */
-				if (pg_str_endswith(path, "PG_VERSION"))
-				{
-					action = FILE_ACTION_NONE;
-					oldsize = statbuf.st_size;
-				}
-				else
-				{
-					action = FILE_ACTION_COPY;
-					oldsize = 0;
-				}
-			}
-			else
-			{
-				/*
-				 * It's a data file that exists in both.
-				 *
-				 * If it's larger in target, we can truncate it. There will
-				 * also be a WAL record of the truncation in the source
-				 * system, so WAL replay would eventually truncate the target
-				 * too, but we might as well do it now.
-				 *
-				 * If it's smaller in the target, it means that it has been
-				 * truncated in the target, or enlarged in the source, or
-				 * both. If it was truncated in the target, we need to copy
-				 * the missing tail from the source system. If it was enlarged
-				 * in the source system, there will be WAL records in the
-				 * source system for the new blocks, so we wouldn't need to
-				 * copy them here. But we don't know which scenario we're
-				 * dealing with, and there's no harm in copying the missing
-				 * blocks now, so do it now.
-				 *
-				 * If it's the same size, do nothing here. Any blocks modified
-				 * in the target will be copied based on parsing the target
-				 * system's WAL, and any blocks modified in the source will be
-				 * updated after rewinding, when the source system's WAL is
-				 * replayed.
-				 */
-				oldsize = statbuf.st_size;
-				if (oldsize < newsize)
-					action = FILE_ACTION_COPY_TAIL;
-				else if (oldsize > newsize)
-					action = FILE_ACTION_TRUNCATE;
-				else
-					action = FILE_ACTION_NONE;
-			}
-			break;
-	}
-
-	/* Create a new entry for this file */
-	entry = pg_malloc(sizeof(file_entry_t));
-	entry->path = pg_strdup(path);
-	entry->type = type;
-	entry->action = action;
-	entry->oldsize = oldsize;
-	entry->newsize = newsize;
-	entry->link_target = link_target ? pg_strdup(link_target) : NULL;
-	entry->next = NULL;
-	entry->pagemap.bitmap = NULL;
-	entry->pagemap.bitmapsize = 0;
-	entry->isrelfile = isRelDataFile(path);
-
-	if (map->last)
-	{
-		map->last->next = entry;
-		map->last = entry;
-	}
-	else
-		map->first = map->last = entry;
-	map->nlist++;
+	/* Remember this source file */
+	entry = get_filemap_entry(path, true);
+	entry->source_exists = true;
+	entry->source_type = type;
+	entry->source_size = size;
+	entry->source_link_target = link_target ? pg_strdup(link_target) : NULL;
 }
 
 /*
@@ -345,14 +253,9 @@ process_source_file(const char *path, file_type_t type, size_t newsize,
  * deletion.
  */
 void
-process_target_file(const char *path, file_type_t type, size_t oldsize,
+process_target_file(const char *path, file_type_t type, size_t size,
 					const char *link_target)
 {
-	bool		exists;
-	char		localpath[MAXPGPATH];
-	struct stat statbuf;
-	file_entry_t key;
-	file_entry_t *key_ptr;
 	filemap_t  *map = filemap;
 	file_entry_t *entry;
 
@@ -361,17 +264,6 @@ process_target_file(const char *path, file_type_t type, size_t oldsize,
 	 * from the target data folder all paths which have been filtered out from
 	 * the source data folder when processing the source files.
 	 */
-
-	snprintf(localpath, sizeof(localpath), "%s/%s", datadir_target, path);
-	if (lstat(localpath, &statbuf) < 0)
-	{
-		if (errno != ENOENT)
-			pg_fatal("could not stat file \"%s\": %m",
-					 localpath);
-
-		exists = false;
-	}
-
 	if (map->array == NULL)
 	{
 		/* on first call, initialize lookup array */
@@ -389,120 +281,76 @@ process_target_file(const char *path, file_type_t type, size_t oldsize,
 	}
 
 	/*
-	 * Like in process_source_file, pretend that xlog is always a  directory.
+	 * Like in process_source_file, pretend that pg_wal is always a directory.
 	 */
 	if (strcmp(path, "pg_wal") == 0 && type == FILE_TYPE_SYMLINK)
 		type = FILE_TYPE_DIRECTORY;
 
-	key.path = (char *) path;
-	key_ptr = &key;
-	exists = (bsearch(&key_ptr, map->array, map->narray, sizeof(file_entry_t *),
-					  path_cmp) != NULL);
-
-	/* Remove any file or folder that doesn't exist in the source system. */
-	if (!exists)
-	{
-		entry = pg_malloc(sizeof(file_entry_t));
-		entry->path = pg_strdup(path);
-		entry->type = type;
-		entry->action = FILE_ACTION_REMOVE;
-		entry->oldsize = oldsize;
-		entry->newsize = 0;
-		entry->link_target = link_target ? pg_strdup(link_target) : NULL;
-		entry->next = NULL;
-		entry->pagemap.bitmap = NULL;
-		entry->pagemap.bitmapsize = 0;
-		entry->isrelfile = isRelDataFile(path);
-
-		if (map->last == NULL)
-			map->first = entry;
-		else
-			map->last->next = entry;
-		map->last = entry;
-		map->nlist++;
-	}
-	else
-	{
-		/*
-		 * We already handled all files that exist in the source system in
-		 * process_source_file().
-		 */
-	}
+	/* Remember this target file */
+	entry = get_filemap_entry(path, true);
+	entry->target_exists = true;
+	entry->target_type = type;
+	entry->target_size = size;
+	entry->target_link_target = link_target ? pg_strdup(link_target) : NULL;
 }
 
 /*
  * This callback gets called while we read the WAL in the target, for every
- * block that have changed in the target system. It makes note of all the
+ * block that have changed in the target system.  It makes note of all the
  * changed blocks in the pagemap of the file.
+ *
+ * NOTE: All the files on both systems must have already been added to the
+ * file map!
  */
 void
-process_block_change(ForkNumber forknum, RelFileNode rnode, BlockNumber blkno)
+process_target_wal_block_change(ForkNumber forknum, RelFileNode rnode,
+								BlockNumber blkno)
 {
 	char	   *path;
-	file_entry_t key;
-	file_entry_t *key_ptr;
 	file_entry_t *entry;
 	BlockNumber blkno_inseg;
 	int			segno;
-	filemap_t  *map = filemap;
-	file_entry_t **e;
 
-	Assert(map->array);
+	Assert(filemap->array);
 
 	segno = blkno / RELSEG_SIZE;
 	blkno_inseg = blkno % RELSEG_SIZE;
 
 	path = datasegpath(rnode, forknum, segno);
-
-	key.path = (char *) path;
-	key_ptr = &key;
-
-	e = bsearch(&key_ptr, map->array, map->narray, sizeof(file_entry_t *),
-				path_cmp);
-	if (e)
-		entry = *e;
-	else
-		entry = NULL;
+	entry = get_filemap_entry(path, false);
 	pfree(path);
 
 	if (entry)
 	{
-		Assert(entry->isrelfile);
-
-		switch (entry->action)
-		{
-			case FILE_ACTION_NONE:
-			case FILE_ACTION_TRUNCATE:
-				/* skip if we're truncating away the modified block anyway */
-				if ((blkno_inseg + 1) * BLCKSZ <= entry->newsize)
-					datapagemap_add(&entry->pagemap, blkno_inseg);
-				break;
+		int64		end_offset;
 
-			case FILE_ACTION_COPY_TAIL:
-
-				/*
-				 * skip the modified block if it is part of the "tail" that
-				 * we're copying anyway.
-				 */
-				if ((blkno_inseg + 1) * BLCKSZ <= entry->oldsize)
-					datapagemap_add(&entry->pagemap, blkno_inseg);
-				break;
+		Assert(entry->isrelfile);
 
-			case FILE_ACTION_COPY:
-			case FILE_ACTION_REMOVE:
-				break;
+		if (entry->target_type != FILE_TYPE_REGULAR)
+			pg_fatal("unexpected page modification for directory or symbolic link \"%s\"",
+					 entry->path);
 
-			case FILE_ACTION_CREATE:
-				pg_fatal("unexpected page modification for directory or symbolic link \"%s\"", entry->path);
-		}
+		/*
+		 * If the block beyond the EOF in the source system, no need to
+		 * remember it now, because we're going to truncate it away from the
+		 * target anyway. Also no need to remember the block if it's beyond
+		 * the current EOF in the target system; we will copy it over with the
+		 * "tail" from the source system, anyway.
+		 */
+		end_offset = (blkno_inseg + 1) * BLCKSZ;
+		if (end_offset <= entry->source_size &&
+			end_offset <= entry->target_size)
+			datapagemap_add(&entry->target_modified_pages, blkno_inseg);
 	}
 	else
 	{
 		/*
 		 * If we don't have any record of this file in the file map, it means
-		 * that it's a relation that doesn't exist in the source system, and
-		 * it was subsequently removed in the target system, too. We can
-		 * safely ignore it.
+		 * that it's a relation that doesn't exist in the source system.  It
+		 * could exist in the target system; we haven't moved the target-only
+		 * entries from the linked list to the array yet!  But in any case, if
+		 * it doesn't exist in the source it will be removed from the target
+		 * too, and we can safely ignore it.
 		 */
 	}
 }
@@ -593,16 +441,6 @@ filemap_list_to_array(filemap_t *map)
 	map->first = map->last = NULL;
 }
 
-void
-filemap_finalize(void)
-{
-	filemap_t  *map = filemap;
-
-	filemap_list_to_array(map);
-	qsort(map->array, map->narray, sizeof(file_entry_t *),
-		  final_filemap_cmp);
-}
-
 static const char *
 action_to_str(file_action_t action)
 {
@@ -643,26 +481,26 @@ calculate_totals(void)
 	{
 		entry = map->array[i];
 
-		if (entry->type != FILE_TYPE_REGULAR)
+		if (entry->source_type != FILE_TYPE_REGULAR)
 			continue;
 
-		map->total_size += entry->newsize;
+		map->total_size += entry->source_size;
 
 		if (entry->action == FILE_ACTION_COPY)
 		{
-			map->fetch_size += entry->newsize;
+			map->fetch_size += entry->source_size;
 			continue;
 		}
 
 		if (entry->action == FILE_ACTION_COPY_TAIL)
-			map->fetch_size += (entry->newsize - entry->oldsize);
+			map->fetch_size += (entry->source_size - entry->target_size);
 
-		if (entry->pagemap.bitmapsize > 0)
+		if (entry->target_modified_pages.bitmapsize > 0)
 		{
 			datapagemap_iterator_t *iter;
 			BlockNumber blk;
 
-			iter = datapagemap_iterate(&entry->pagemap);
+			iter = datapagemap_iterate(&entry->target_modified_pages);
 			while (datapagemap_next(iter, &blk))
 				map->fetch_size += BLCKSZ;
 
@@ -682,13 +520,13 @@ print_filemap(void)
 	{
 		entry = map->array[i];
 		if (entry->action != FILE_ACTION_NONE ||
-			entry->pagemap.bitmapsize > 0)
+			entry->target_modified_pages.bitmapsize > 0)
 		{
 			pg_log_debug("%s (%s)", entry->path,
 						 action_to_str(entry->action));
 
-			if (entry->pagemap.bitmapsize > 0)
-				datapagemap_print(&entry->pagemap);
+			if (entry->target_modified_pages.bitmapsize > 0)
+				datapagemap_print(&entry->target_modified_pages);
 		}
 	}
 	fflush(stdout);
@@ -837,3 +675,180 @@ final_filemap_cmp(const void *a, const void *b)
 	else
 		return strcmp(fa->path, fb->path);
 }
+
+/*
+ * Decide what action to perform to a file.
+ */
+static file_action_t
+decide_file_action(file_entry_t *entry)
+{
+	const char *path = entry->path;
+
+	/*
+	 * Don't touch the control file. It is handled specially, after copying
+	 * all the other files.
+	 */
+	if (strcmp(path, "global/pg_control") == 0)
+		return FILE_ACTION_NONE;
+
+	/*
+	 * Remove all files matching the exclusion filters in the target.
+	 */
+	if (check_file_excluded(path, true))
+	{
+		if (entry->target_exists)
+			return FILE_ACTION_REMOVE;
+		else
+			return FILE_ACTION_NONE;
+	}
+
+	/*
+	 * Also remove all temporary files, .../pgsql_tmp/... and .../pgsql_tmp.*
+	 * in the target.
+	 */
+	if (strstr(path, "/" PG_TEMP_FILE_PREFIX) != NULL ||
+		strstr(path, "/" PG_TEMP_FILES_DIR "/") != NULL)
+	{
+		if (entry->target_exists)
+			return FILE_ACTION_REMOVE;
+		else
+			return FILE_ACTION_NONE;
+	}
+
+	/*
+	 * Handle cases where the file is missing from one of the systems.
+	 */
+	if (!entry->target_exists && entry->source_exists)
+	{
+		/*
+		 * File exists in source, but not in target. Copy it in toto. (If it's
+		 * a relation data file, WAL replay after rewinding should re-create
+		 * it anyway. But there's no harm in copying it now.)
+		 */
+		switch (entry->source_type)
+		{
+			case FILE_TYPE_DIRECTORY:
+			case FILE_TYPE_SYMLINK:
+				return FILE_ACTION_CREATE;
+			case FILE_TYPE_REGULAR:
+				return FILE_ACTION_COPY;
+			case FILE_TYPE_UNDEFINED:
+				pg_fatal("unknown file type for \"%s\"", entry->path);
+				break;
+		}
+	}
+	else if (entry->target_exists && !entry->source_exists)
+	{
+		/* File exists in target, but not source. Remove it. */
+		return FILE_ACTION_REMOVE;
+	}
+	else if (!entry->target_exists && !entry->source_exists)
+	{
+		/*
+		 * Doesn't exist in either server. Why does it have an entry in the
+		 * first place??
+		 */
+		return FILE_ACTION_NONE;
+	}
+
+	/*
+	 * Otherwise, the file exists on both systems
+	 */
+	Assert(entry->target_exists && entry->source_exists);
+
+	if (entry->source_type != entry->target_type)
+	{
+		/* But it's a different kind of object. Strange.. */
+		pg_fatal("file \"%s\" is of different type in source and target", entry->path);
+	}
+
+	/*
+	 * PG_VERSION files should be identical on both systems, but avoid
+	 * overwriting them for paranoia.
+	 */
+	if (pg_str_endswith(entry->path, "PG_VERSION"))
+		return FILE_ACTION_NONE;
+
+	switch (entry->source_type)
+	{
+		case FILE_TYPE_DIRECTORY:
+			return FILE_ACTION_NONE;
+
+		case FILE_TYPE_SYMLINK:
+			/* FIXME: Check if it points to the same target? */
+			return FILE_ACTION_NONE;
+
+		case FILE_TYPE_REGULAR:
+			if (!entry->isrelfile)
+			{
+				/*
+				 * It's a non-data file that we have no special processing
+				 * for. Copy it in toto.
+				 */
+				return FILE_ACTION_COPY;
+			}
+			else
+			{
+				/*
+				 * It's a data file that exists in both systems.
+				 *
+				 * If it's larger in target, we can truncate it. There will
+				 * also be a WAL record of the truncation in the source
+				 * system, so WAL replay would eventually truncate the target
+				 * too, but we might as well do it now.
+				 *
+				 * If it's smaller in the target, it means that it has been
+				 * truncated in the target, or enlarged in the source, or
+				 * both. If it was truncated in the target, we need to copy
+				 * the missing tail from the source system. If it was enlarged
+				 * in the source system, there will be WAL records in the
+				 * source system for the new blocks, so we wouldn't need to
+				 * copy them here. But we don't know which scenario we're
+				 * dealing with, and there's no harm in copying the missing
+				 * blocks now, so do it now.
+				 *
+				 * If it's the same size, do nothing here. Any blocks modified
+				 * in the target will be copied based on parsing the target
+				 * system's WAL, and any blocks modified in the source will be
+				 * updated after rewinding, when the source system's WAL is
+				 * replayed.
+				 */
+				if (entry->target_size < entry->source_size)
+					return FILE_ACTION_COPY_TAIL;
+				else if (entry->target_size > entry->source_size)
+					return FILE_ACTION_TRUNCATE;
+				else
+					return FILE_ACTION_NONE;
+			}
+			break;
+
+		case FILE_TYPE_UNDEFINED:
+			pg_fatal("unknown file type for \"%s\"", path);
+			break;
+	}
+
+	/* unreachable */
+	pg_fatal("could not decide what to do with file \"%s\"", path);
+}
+
+/*
+ * Decide what to do with each file.
+ */
+void
+filemap_finalize()
+{
+	int			i;
+
+	filemap_list_to_array(filemap);
+
+	for (i = 0; i < filemap->narray; i++)
+	{
+		file_entry_t *entry = filemap->array[i];
+
+		entry->action = decide_file_action(entry);
+	}
+
+	/* Sort the actions to the order that they should be performed */
+	qsort(filemap->array, filemap->narray, sizeof(file_entry_t *),
+		  final_filemap_cmp);
+}
diff --git a/src/bin/pg_rewind/filemap.h b/src/bin/pg_rewind/filemap.h
index 0cb7425170c..a5e8df57f40 100644
--- a/src/bin/pg_rewind/filemap.h
+++ b/src/bin/pg_rewind/filemap.h
@@ -14,17 +14,21 @@
 
 /*
  * For every file found in the local or remote system, we have a file entry
- * which says what we are going to do with the file. For relation files,
- * there is also a page map, marking pages in the file that were changed
- * locally.
- *
- * The enum values are sorted in the order we want actions to be processed.
+ * that contains information about the file on both systems.  For relation
+ * files, there is also a page map that marks pages in the file that were
+ * changed in the target after the last common checkpoint.  Each entry also
+ * contains an 'action' field, which says what we are going to do with the
+ * file.
  */
+
+/* these enum values are sorted in the order we want actions to be processed */
 typedef enum
 {
+	FILE_ACTION_UNDECIDED = 0,	/* not decided yet */
+
 	FILE_ACTION_CREATE,			/* create local directory or symbolic link */
 	FILE_ACTION_COPY,			/* copy whole file, overwriting if exists */
-	FILE_ACTION_COPY_TAIL,		/* copy tail from 'oldsize' to 'newsize' */
+	FILE_ACTION_COPY_TAIL,		/* copy tail from 'source_size' to 'target_size' */
 	FILE_ACTION_NONE,			/* no action (we might still copy modified
 								 * blocks based on the parsed WAL) */
 	FILE_ACTION_TRUNCATE,		/* truncate local file to 'newsize' bytes */
@@ -33,6 +37,8 @@ typedef enum
 
 typedef enum
 {
+	FILE_TYPE_UNDEFINED = 0,
+
 	FILE_TYPE_REGULAR,
 	FILE_TYPE_DIRECTORY,
 	FILE_TYPE_SYMLINK
@@ -41,19 +47,30 @@ typedef enum
 typedef struct file_entry_t
 {
 	char	   *path;
-	file_type_t type;
+	bool		isrelfile;		/* is it a relation data file? */
 
-	file_action_t action;
+	/*
+	 * Status of the file in the target.
+	 */
+	bool		target_exists;
+	file_type_t target_type;
+	size_t		target_size; /* for a regular file */
+	char	   *target_link_target; /* for a symlink */
 
-	/* for a regular file */
-	size_t		oldsize;
-	size_t		newsize;
-	bool		isrelfile;		/* is it a relation data file? */
+	datapagemap_t target_modified_pages;
 
-	datapagemap_t pagemap;
+	/*
+	 * Status of the file in the source.
+	 */
+	bool		source_exists;
+	file_type_t source_type;
+	size_t		source_size;
+	char	   *source_link_target; /* for a symlink */
 
-	/* for a symlink */
-	char	   *link_target;
+	/*
+	 * What will we do to the file?
+	 */
+	file_action_t action;
 
 	struct file_entry_t *next;
 } file_entry_t;
@@ -70,20 +87,19 @@ typedef struct filemap_t
 
 	/*
 	 * After processing all the remote files, the entries in the linked list
-	 * are moved to this array. After processing local files, too, all the
+	 * are moved to this array.  After processing local files, too, all the
 	 * local entries are added to the array by filemap_finalize, and sorted in
-	 * the final order. After filemap_finalize, all the entries are in the
+	 * the final order.  After filemap_finalize, all the entries are in the
 	 * array, and the linked list is empty.
 	 */
 	file_entry_t **array;
 	int			narray;			/* current length of array */
 
 	/*
-	 * Summary information. total_size is the total size of the source
-	 * cluster, and fetch_size is the number of bytes that needs to be copied.
+	 * Summary information.
 	 */
-	uint64		total_size;
-	uint64		fetch_size;
+	uint64		total_size;		/* total size of the source cluster */
+	uint64		fetch_size;		/* number of bytes that needs to be copied */
 } filemap_t;
 
 extern filemap_t *filemap;
@@ -94,11 +110,12 @@ extern void print_filemap(void);
 
 /* Functions for populating the filemap */
 extern void process_source_file(const char *path, file_type_t type,
-								size_t newsize, const char *link_target);
+								size_t size, const char *link_target);
 extern void process_target_file(const char *path, file_type_t type,
-								size_t newsize, const char *link_target);
-extern void process_block_change(ForkNumber forknum, RelFileNode rnode,
-								 BlockNumber blkno);
+								size_t size, const char *link_target);
+extern void process_target_wal_block_change(ForkNumber forknum,
+											RelFileNode rnode,
+											BlockNumber blkno);
 extern void filemap_finalize(void);
 
 #endif							/* FILEMAP_H */
diff --git a/src/bin/pg_rewind/libpq_fetch.c b/src/bin/pg_rewind/libpq_fetch.c
index bf4dfc23b96..7fc9161b8c8 100644
--- a/src/bin/pg_rewind/libpq_fetch.c
+++ b/src/bin/pg_rewind/libpq_fetch.c
@@ -465,7 +465,7 @@ libpq_executeFileMap(filemap_t *map)
 		entry = map->array[i];
 
 		/* If this is a relation file, copy the modified blocks */
-		execute_pagemap(&entry->pagemap, entry->path);
+		execute_pagemap(&entry->target_modified_pages, entry->path);
 
 		switch (entry->action)
 		{
@@ -476,15 +476,15 @@ libpq_executeFileMap(filemap_t *map)
 			case FILE_ACTION_COPY:
 				/* Truncate the old file out of the way, if any */
 				open_target_file(entry->path, true);
-				fetch_file_range(entry->path, 0, entry->newsize);
+				fetch_file_range(entry->path, 0, entry->source_size);
 				break;
 
 			case FILE_ACTION_TRUNCATE:
-				truncate_target_file(entry->path, entry->newsize);
+				truncate_target_file(entry->path, entry->source_size);
 				break;
 
 			case FILE_ACTION_COPY_TAIL:
-				fetch_file_range(entry->path, entry->oldsize, entry->newsize);
+				fetch_file_range(entry->path, entry->target_size, entry->source_size);
 				break;
 
 			case FILE_ACTION_REMOVE:
@@ -494,6 +494,10 @@ libpq_executeFileMap(filemap_t *map)
 			case FILE_ACTION_CREATE:
 				create_target(entry);
 				break;
+
+			case FILE_ACTION_UNDECIDED:
+				pg_fatal("no action decided for \"%s\"", entry->path);
+				break;
 		}
 	}
 
diff --git a/src/bin/pg_rewind/parsexlog.c b/src/bin/pg_rewind/parsexlog.c
index 2229c86f9af..2baeb74ae93 100644
--- a/src/bin/pg_rewind/parsexlog.c
+++ b/src/bin/pg_rewind/parsexlog.c
@@ -436,6 +436,6 @@ extractPageInfo(XLogReaderState *record)
 		if (forknum != MAIN_FORKNUM)
 			continue;
 
-		process_block_change(forknum, rnode, blkno);
+		process_target_wal_block_change(forknum, rnode, blkno);
 	}
 }
diff --git a/src/bin/pg_rewind/pg_rewind.c b/src/bin/pg_rewind/pg_rewind.c
index c9b9e480c0f..210984d302b 100644
--- a/src/bin/pg_rewind/pg_rewind.c
+++ b/src/bin/pg_rewind/pg_rewind.c
@@ -369,7 +369,7 @@ main(int argc, char **argv)
 				chkpttli);
 
 	/*
-	 * Build the filemap, by comparing the source and target data directories.
+	 * Collect information about all files in the target and source systems.
 	 */
 	filemap_create();
 	if (showprogress)
@@ -390,8 +390,12 @@ main(int argc, char **argv)
 		pg_log_info("reading WAL in target");
 	extractPageMap(datadir_target, chkptrec, lastcommontliIndex,
 				   ControlFile_target.checkPoint, restore_command);
-	filemap_finalize();
 
+	/*
+	 * We have collected all information we need from both systems. Decide
+	 * what to do with each file.
+	 */
+	filemap_finalize();
 	if (showprogress)
 		calculate_totals();
 
-- 
2.20.1


--------------BF34D0120055DC3839060F92
Content-Type: text/x-patch; charset=UTF-8;
 name="v2-0003-pg_rewind-Replace-the-hybrid-list-array-data-stru.patch"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
 filename*0="v2-0003-pg_rewind-Replace-the-hybrid-list-array-data-stru.pa";
 filename*1="tch"



^ permalink  raw  reply  [nested|flat] 41+ messages in thread

* [PATCH v2 2/5] Refactor pg_rewind for more clear decision making.
@ 2020-08-19 12:34 Heikki Linnakangas <[email protected]>
  0 siblings, 0 replies; 41+ messages in thread

From: Heikki Linnakangas @ 2020-08-19 12:34 UTC (permalink / raw)

Deciding what to do with each file is now a separate step after all the
necessary information has been gathered. It is more clear that way.
Previously, the decision-making was divided between process_source_file()
and process_target_file(), and it was a bit hard to piece together what the
overall rules were.
---
 src/bin/pg_rewind/copy_fetch.c  |  14 +-
 src/bin/pg_rewind/file_ops.c    |  16 +-
 src/bin/pg_rewind/filemap.c     | 569 ++++++++++++++++----------------
 src/bin/pg_rewind/filemap.h     |  67 ++--
 src/bin/pg_rewind/libpq_fetch.c |  12 +-
 src/bin/pg_rewind/parsexlog.c   |   2 +-
 src/bin/pg_rewind/pg_rewind.c   |   8 +-
 7 files changed, 371 insertions(+), 317 deletions(-)

diff --git a/src/bin/pg_rewind/copy_fetch.c b/src/bin/pg_rewind/copy_fetch.c
index 1edab5f1867..18fad32600e 100644
--- a/src/bin/pg_rewind/copy_fetch.c
+++ b/src/bin/pg_rewind/copy_fetch.c
@@ -210,7 +210,7 @@ copy_executeFileMap(filemap_t *map)
 	for (i = 0; i < map->narray; i++)
 	{
 		entry = map->array[i];
-		execute_pagemap(&entry->pagemap, entry->path);
+		execute_pagemap(&entry->target_modified_pages, entry->path);
 
 		switch (entry->action)
 		{
@@ -219,16 +219,16 @@ copy_executeFileMap(filemap_t *map)
 				break;
 
 			case FILE_ACTION_COPY:
-				rewind_copy_file_range(entry->path, 0, entry->newsize, true);
+				rewind_copy_file_range(entry->path, 0, entry->source_size, true);
 				break;
 
 			case FILE_ACTION_TRUNCATE:
-				truncate_target_file(entry->path, entry->newsize);
+				truncate_target_file(entry->path, entry->source_size);
 				break;
 
 			case FILE_ACTION_COPY_TAIL:
-				rewind_copy_file_range(entry->path, entry->oldsize,
-									   entry->newsize, false);
+				rewind_copy_file_range(entry->path, entry->target_size,
+									   entry->source_size, false);
 				break;
 
 			case FILE_ACTION_CREATE:
@@ -238,6 +238,10 @@ copy_executeFileMap(filemap_t *map)
 			case FILE_ACTION_REMOVE:
 				remove_target(entry);
 				break;
+
+			case FILE_ACTION_UNDECIDED:
+				pg_fatal("no action decided for \"%s\"", entry->path);
+				break;
 		}
 	}
 
diff --git a/src/bin/pg_rewind/file_ops.c b/src/bin/pg_rewind/file_ops.c
index 55439db20ba..ec37d0b2e0d 100644
--- a/src/bin/pg_rewind/file_ops.c
+++ b/src/bin/pg_rewind/file_ops.c
@@ -126,8 +126,9 @@ void
 remove_target(file_entry_t *entry)
 {
 	Assert(entry->action == FILE_ACTION_REMOVE);
+	Assert(entry->target_exists);
 
-	switch (entry->type)
+	switch (entry->target_type)
 	{
 		case FILE_TYPE_DIRECTORY:
 			remove_target_dir(entry->path);
@@ -140,6 +141,10 @@ remove_target(file_entry_t *entry)
 		case FILE_TYPE_SYMLINK:
 			remove_target_symlink(entry->path);
 			break;
+
+		case FILE_TYPE_UNDEFINED:
+			pg_fatal("undefined file type for \"%s\"", entry->path);
+			break;
 	}
 }
 
@@ -147,21 +152,26 @@ void
 create_target(file_entry_t *entry)
 {
 	Assert(entry->action == FILE_ACTION_CREATE);
+	Assert(!entry->target_exists);
 
-	switch (entry->type)
+	switch (entry->source_type)
 	{
 		case FILE_TYPE_DIRECTORY:
 			create_target_dir(entry->path);
 			break;
 
 		case FILE_TYPE_SYMLINK:
-			create_target_symlink(entry->path, entry->link_target);
+			create_target_symlink(entry->path, entry->source_link_target);
 			break;
 
 		case FILE_TYPE_REGULAR:
 			/* can't happen. Regular files are created with open_target_file. */
 			pg_fatal("invalid action (CREATE) for regular file");
 			break;
+
+		case FILE_TYPE_UNDEFINED:
+			pg_fatal("undefined file type for \"%s\"", entry->path);
+			break;
 	}
 }
 
diff --git a/src/bin/pg_rewind/filemap.c b/src/bin/pg_rewind/filemap.c
index 1879257b66a..7971daeda5e 100644
--- a/src/bin/pg_rewind/filemap.c
+++ b/src/bin/pg_rewind/filemap.c
@@ -26,6 +26,8 @@ static bool isRelDataFile(const char *path);
 static char *datasegpath(RelFileNode rnode, ForkNumber forknum,
 						 BlockNumber segno);
 static int	path_cmp(const void *a, const void *b);
+
+static file_entry_t *get_filemap_entry(const char *path, bool create);
 static int	final_filemap_cmp(const void *a, const void *b);
 static void filemap_list_to_array(filemap_t *map);
 static bool check_file_excluded(const char *path, bool is_source);
@@ -146,6 +148,65 @@ filemap_create(void)
 	filemap = map;
 }
 
+/* Look up or create entry for 'path' */
+static file_entry_t *
+get_filemap_entry(const char *path, bool create)
+{
+	filemap_t  *map = filemap;
+	file_entry_t *entry;
+	file_entry_t **e;
+	file_entry_t key;
+	file_entry_t *key_ptr;
+
+	if (map->array)
+	{
+		key.path = (char *) path;
+		key_ptr = &key;
+		e = bsearch(&key_ptr, map->array, map->narray, sizeof(file_entry_t *),
+					path_cmp);
+	}
+	else
+		e = NULL;
+
+	if (e)
+		entry = *e;
+	else if (!create)
+		entry = NULL;
+	else
+	{
+		/* Create a new entry for this file */
+		entry = pg_malloc(sizeof(file_entry_t));
+		entry->path = pg_strdup(path);
+		entry->isrelfile = isRelDataFile(path);
+		entry->action = FILE_ACTION_UNDECIDED;
+
+		entry->target_exists = false;
+		entry->target_type = FILE_TYPE_UNDEFINED;
+		entry->target_size = 0;
+		entry->target_link_target = NULL;
+		entry->target_modified_pages.bitmap = NULL;
+		entry->target_modified_pages.bitmapsize = 0;
+
+		entry->source_exists = false;
+		entry->source_type = FILE_TYPE_UNDEFINED;
+		entry->source_size = 0;
+		entry->source_link_target = NULL;
+
+		entry->next = NULL;
+
+		if (map->last)
+		{
+			map->last->next = entry;
+			map->last = entry;
+		}
+		else
+			map->first = map->last = entry;
+		map->nlist++;
+	}
+
+	return entry;
+}
+
 /*
  * Callback for processing source file list.
  *
@@ -154,25 +215,12 @@ filemap_create(void)
  * exists in the target and whether the size matches.
  */
 void
-process_source_file(const char *path, file_type_t type, size_t newsize,
+process_source_file(const char *path, file_type_t type, size_t size,
 					const char *link_target)
 {
-	bool		exists;
-	char		localpath[MAXPGPATH];
-	struct stat statbuf;
-	filemap_t  *map = filemap;
-	file_action_t action = FILE_ACTION_NONE;
-	size_t		oldsize = 0;
 	file_entry_t *entry;
 
-	Assert(map->array == NULL);
-
-	/*
-	 * Skip any files matching the exclusion filters. This has the effect to
-	 * remove all those files on the target.
-	 */
-	if (check_file_excluded(path, true))
-		return;
+	Assert(filemap->array == NULL);
 
 	/*
 	 * Pretend that pg_wal is a directory, even if it's really a symlink. We
@@ -182,16 +230,6 @@ process_source_file(const char *path, file_type_t type, size_t newsize,
 	if (strcmp(path, "pg_wal") == 0 && type == FILE_TYPE_SYMLINK)
 		type = FILE_TYPE_DIRECTORY;
 
-	/*
-	 * Skip temporary files, .../pgsql_tmp/... and .../pgsql_tmp.* in source.
-	 * This has the effect that all temporary files in the destination will be
-	 * removed.
-	 */
-	if (strstr(path, "/" PG_TEMP_FILE_PREFIX) != NULL)
-		return;
-	if (strstr(path, "/" PG_TEMP_FILES_DIR "/") != NULL)
-		return;
-
 	/*
 	 * sanity check: a filename that looks like a data file better be a
 	 * regular file
@@ -199,142 +237,12 @@ process_source_file(const char *path, file_type_t type, size_t newsize,
 	if (type != FILE_TYPE_REGULAR && isRelDataFile(path))
 		pg_fatal("data file \"%s\" in source is not a regular file", path);
 
-	snprintf(localpath, sizeof(localpath), "%s/%s", datadir_target, path);
-
-	/* Does the corresponding file exist in the target data dir? */
-	if (lstat(localpath, &statbuf) < 0)
-	{
-		if (errno != ENOENT)
-			pg_fatal("could not stat file \"%s\": %m",
-					 localpath);
-
-		exists = false;
-	}
-	else
-		exists = true;
-
-	switch (type)
-	{
-		case FILE_TYPE_DIRECTORY:
-			if (exists && !S_ISDIR(statbuf.st_mode) && strcmp(path, "pg_wal") != 0)
-			{
-				/* it's a directory in source, but not in target. Strange.. */
-				pg_fatal("\"%s\" is not a directory", localpath);
-			}
-
-			if (!exists)
-				action = FILE_ACTION_CREATE;
-			else
-				action = FILE_ACTION_NONE;
-			oldsize = 0;
-			break;
-
-		case FILE_TYPE_SYMLINK:
-			if (exists &&
-#ifndef WIN32
-				!S_ISLNK(statbuf.st_mode)
-#else
-				!pgwin32_is_junction(localpath)
-#endif
-				)
-			{
-				/*
-				 * It's a symbolic link in source, but not in target.
-				 * Strange..
-				 */
-				pg_fatal("\"%s\" is not a symbolic link", localpath);
-			}
-
-			if (!exists)
-				action = FILE_ACTION_CREATE;
-			else
-				action = FILE_ACTION_NONE;
-			oldsize = 0;
-			break;
-
-		case FILE_TYPE_REGULAR:
-			if (exists && !S_ISREG(statbuf.st_mode))
-				pg_fatal("\"%s\" is not a regular file", localpath);
-
-			if (!exists || !isRelDataFile(path))
-			{
-				/*
-				 * File exists in source, but not in target. Or it's a
-				 * non-data file that we have no special processing for. Copy
-				 * it in toto.
-				 *
-				 * An exception: PG_VERSIONs should be identical, but avoid
-				 * overwriting it for paranoia.
-				 */
-				if (pg_str_endswith(path, "PG_VERSION"))
-				{
-					action = FILE_ACTION_NONE;
-					oldsize = statbuf.st_size;
-				}
-				else
-				{
-					action = FILE_ACTION_COPY;
-					oldsize = 0;
-				}
-			}
-			else
-			{
-				/*
-				 * It's a data file that exists in both.
-				 *
-				 * If it's larger in target, we can truncate it. There will
-				 * also be a WAL record of the truncation in the source
-				 * system, so WAL replay would eventually truncate the target
-				 * too, but we might as well do it now.
-				 *
-				 * If it's smaller in the target, it means that it has been
-				 * truncated in the target, or enlarged in the source, or
-				 * both. If it was truncated in the target, we need to copy
-				 * the missing tail from the source system. If it was enlarged
-				 * in the source system, there will be WAL records in the
-				 * source system for the new blocks, so we wouldn't need to
-				 * copy them here. But we don't know which scenario we're
-				 * dealing with, and there's no harm in copying the missing
-				 * blocks now, so do it now.
-				 *
-				 * If it's the same size, do nothing here. Any blocks modified
-				 * in the target will be copied based on parsing the target
-				 * system's WAL, and any blocks modified in the source will be
-				 * updated after rewinding, when the source system's WAL is
-				 * replayed.
-				 */
-				oldsize = statbuf.st_size;
-				if (oldsize < newsize)
-					action = FILE_ACTION_COPY_TAIL;
-				else if (oldsize > newsize)
-					action = FILE_ACTION_TRUNCATE;
-				else
-					action = FILE_ACTION_NONE;
-			}
-			break;
-	}
-
-	/* Create a new entry for this file */
-	entry = pg_malloc(sizeof(file_entry_t));
-	entry->path = pg_strdup(path);
-	entry->type = type;
-	entry->action = action;
-	entry->oldsize = oldsize;
-	entry->newsize = newsize;
-	entry->link_target = link_target ? pg_strdup(link_target) : NULL;
-	entry->next = NULL;
-	entry->pagemap.bitmap = NULL;
-	entry->pagemap.bitmapsize = 0;
-	entry->isrelfile = isRelDataFile(path);
-
-	if (map->last)
-	{
-		map->last->next = entry;
-		map->last = entry;
-	}
-	else
-		map->first = map->last = entry;
-	map->nlist++;
+	/* Remember this source file */
+	entry = get_filemap_entry(path, true);
+	entry->source_exists = true;
+	entry->source_type = type;
+	entry->source_size = size;
+	entry->source_link_target = link_target ? pg_strdup(link_target) : NULL;
 }
 
 /*
@@ -345,14 +253,9 @@ process_source_file(const char *path, file_type_t type, size_t newsize,
  * deletion.
  */
 void
-process_target_file(const char *path, file_type_t type, size_t oldsize,
+process_target_file(const char *path, file_type_t type, size_t size,
 					const char *link_target)
 {
-	bool		exists;
-	char		localpath[MAXPGPATH];
-	struct stat statbuf;
-	file_entry_t key;
-	file_entry_t *key_ptr;
 	filemap_t  *map = filemap;
 	file_entry_t *entry;
 
@@ -361,17 +264,6 @@ process_target_file(const char *path, file_type_t type, size_t oldsize,
 	 * from the target data folder all paths which have been filtered out from
 	 * the source data folder when processing the source files.
 	 */
-
-	snprintf(localpath, sizeof(localpath), "%s/%s", datadir_target, path);
-	if (lstat(localpath, &statbuf) < 0)
-	{
-		if (errno != ENOENT)
-			pg_fatal("could not stat file \"%s\": %m",
-					 localpath);
-
-		exists = false;
-	}
-
 	if (map->array == NULL)
 	{
 		/* on first call, initialize lookup array */
@@ -389,120 +281,76 @@ process_target_file(const char *path, file_type_t type, size_t oldsize,
 	}
 
 	/*
-	 * Like in process_source_file, pretend that xlog is always a  directory.
+	 * Like in process_source_file, pretend that pg_wal is always a directory.
 	 */
 	if (strcmp(path, "pg_wal") == 0 && type == FILE_TYPE_SYMLINK)
 		type = FILE_TYPE_DIRECTORY;
 
-	key.path = (char *) path;
-	key_ptr = &key;
-	exists = (bsearch(&key_ptr, map->array, map->narray, sizeof(file_entry_t *),
-					  path_cmp) != NULL);
-
-	/* Remove any file or folder that doesn't exist in the source system. */
-	if (!exists)
-	{
-		entry = pg_malloc(sizeof(file_entry_t));
-		entry->path = pg_strdup(path);
-		entry->type = type;
-		entry->action = FILE_ACTION_REMOVE;
-		entry->oldsize = oldsize;
-		entry->newsize = 0;
-		entry->link_target = link_target ? pg_strdup(link_target) : NULL;
-		entry->next = NULL;
-		entry->pagemap.bitmap = NULL;
-		entry->pagemap.bitmapsize = 0;
-		entry->isrelfile = isRelDataFile(path);
-
-		if (map->last == NULL)
-			map->first = entry;
-		else
-			map->last->next = entry;
-		map->last = entry;
-		map->nlist++;
-	}
-	else
-	{
-		/*
-		 * We already handled all files that exist in the source system in
-		 * process_source_file().
-		 */
-	}
+	/* Remember this target file */
+	entry = get_filemap_entry(path, true);
+	entry->target_exists = true;
+	entry->target_type = type;
+	entry->target_size = size;
+	entry->target_link_target = link_target ? pg_strdup(link_target) : NULL;
 }
 
 /*
  * This callback gets called while we read the WAL in the target, for every
- * block that have changed in the target system. It makes note of all the
+ * block that have changed in the target system.  It makes note of all the
  * changed blocks in the pagemap of the file.
+ *
+ * NOTE: All the files on both systems must have already been added to the
+ * file map!
  */
 void
-process_block_change(ForkNumber forknum, RelFileNode rnode, BlockNumber blkno)
+process_target_wal_block_change(ForkNumber forknum, RelFileNode rnode,
+								BlockNumber blkno)
 {
 	char	   *path;
-	file_entry_t key;
-	file_entry_t *key_ptr;
 	file_entry_t *entry;
 	BlockNumber blkno_inseg;
 	int			segno;
-	filemap_t  *map = filemap;
-	file_entry_t **e;
 
-	Assert(map->array);
+	Assert(filemap->array);
 
 	segno = blkno / RELSEG_SIZE;
 	blkno_inseg = blkno % RELSEG_SIZE;
 
 	path = datasegpath(rnode, forknum, segno);
-
-	key.path = (char *) path;
-	key_ptr = &key;
-
-	e = bsearch(&key_ptr, map->array, map->narray, sizeof(file_entry_t *),
-				path_cmp);
-	if (e)
-		entry = *e;
-	else
-		entry = NULL;
+	entry = get_filemap_entry(path, false);
 	pfree(path);
 
 	if (entry)
 	{
-		Assert(entry->isrelfile);
-
-		switch (entry->action)
-		{
-			case FILE_ACTION_NONE:
-			case FILE_ACTION_TRUNCATE:
-				/* skip if we're truncating away the modified block anyway */
-				if ((blkno_inseg + 1) * BLCKSZ <= entry->newsize)
-					datapagemap_add(&entry->pagemap, blkno_inseg);
-				break;
+		int64		end_offset;
 
-			case FILE_ACTION_COPY_TAIL:
-
-				/*
-				 * skip the modified block if it is part of the "tail" that
-				 * we're copying anyway.
-				 */
-				if ((blkno_inseg + 1) * BLCKSZ <= entry->oldsize)
-					datapagemap_add(&entry->pagemap, blkno_inseg);
-				break;
+		Assert(entry->isrelfile);
 
-			case FILE_ACTION_COPY:
-			case FILE_ACTION_REMOVE:
-				break;
+		if (entry->target_type != FILE_TYPE_REGULAR)
+			pg_fatal("unexpected page modification for directory or symbolic link \"%s\"",
+					 entry->path);
 
-			case FILE_ACTION_CREATE:
-				pg_fatal("unexpected page modification for directory or symbolic link \"%s\"", entry->path);
-		}
+		/*
+		 * If the block beyond the EOF in the source system, no need to
+		 * remember it now, because we're going to truncate it away from the
+		 * target anyway. Also no need to remember the block if it's beyond
+		 * the current EOF in the target system; we will copy it over with the
+		 * "tail" from the source system, anyway.
+		 */
+		end_offset = (blkno_inseg + 1) * BLCKSZ;
+		if (end_offset <= entry->source_size &&
+			end_offset <= entry->target_size)
+			datapagemap_add(&entry->target_modified_pages, blkno_inseg);
 	}
 	else
 	{
 		/*
 		 * If we don't have any record of this file in the file map, it means
-		 * that it's a relation that doesn't exist in the source system, and
-		 * it was subsequently removed in the target system, too. We can
-		 * safely ignore it.
+		 * that it's a relation that doesn't exist in the source system.  It
+		 * could exist in the target system; we haven't moved the target-only
+		 * entries from the linked list to the array yet!  But in any case, if
+		 * it doesn't exist in the source it will be removed from the target
+		 * too, and we can safely ignore it.
 		 */
 	}
 }
@@ -593,16 +441,6 @@ filemap_list_to_array(filemap_t *map)
 	map->first = map->last = NULL;
 }
 
-void
-filemap_finalize(void)
-{
-	filemap_t  *map = filemap;
-
-	filemap_list_to_array(map);
-	qsort(map->array, map->narray, sizeof(file_entry_t *),
-		  final_filemap_cmp);
-}
-
 static const char *
 action_to_str(file_action_t action)
 {
@@ -643,26 +481,26 @@ calculate_totals(void)
 	{
 		entry = map->array[i];
 
-		if (entry->type != FILE_TYPE_REGULAR)
+		if (entry->source_type != FILE_TYPE_REGULAR)
 			continue;
 
-		map->total_size += entry->newsize;
+		map->total_size += entry->source_size;
 
 		if (entry->action == FILE_ACTION_COPY)
 		{
-			map->fetch_size += entry->newsize;
+			map->fetch_size += entry->source_size;
 			continue;
 		}
 
 		if (entry->action == FILE_ACTION_COPY_TAIL)
-			map->fetch_size += (entry->newsize - entry->oldsize);
+			map->fetch_size += (entry->source_size - entry->target_size);
 
-		if (entry->pagemap.bitmapsize > 0)
+		if (entry->target_modified_pages.bitmapsize > 0)
 		{
 			datapagemap_iterator_t *iter;
 			BlockNumber blk;
 
-			iter = datapagemap_iterate(&entry->pagemap);
+			iter = datapagemap_iterate(&entry->target_modified_pages);
 			while (datapagemap_next(iter, &blk))
 				map->fetch_size += BLCKSZ;
 
@@ -682,13 +520,13 @@ print_filemap(void)
 	{
 		entry = map->array[i];
 		if (entry->action != FILE_ACTION_NONE ||
-			entry->pagemap.bitmapsize > 0)
+			entry->target_modified_pages.bitmapsize > 0)
 		{
 			pg_log_debug("%s (%s)", entry->path,
 						 action_to_str(entry->action));
 
-			if (entry->pagemap.bitmapsize > 0)
-				datapagemap_print(&entry->pagemap);
+			if (entry->target_modified_pages.bitmapsize > 0)
+				datapagemap_print(&entry->target_modified_pages);
 		}
 	}
 	fflush(stdout);
@@ -837,3 +675,180 @@ final_filemap_cmp(const void *a, const void *b)
 	else
 		return strcmp(fa->path, fb->path);
 }
+
+/*
+ * Decide what action to perform to a file.
+ */
+static file_action_t
+decide_file_action(file_entry_t *entry)
+{
+	const char *path = entry->path;
+
+	/*
+	 * Don't touch the control file. It is handled specially, after copying
+	 * all the other files.
+	 */
+	if (strcmp(path, "global/pg_control") == 0)
+		return FILE_ACTION_NONE;
+
+	/*
+	 * Remove all files matching the exclusion filters in the target.
+	 */
+	if (check_file_excluded(path, true))
+	{
+		if (entry->target_exists)
+			return FILE_ACTION_REMOVE;
+		else
+			return FILE_ACTION_NONE;
+	}
+
+	/*
+	 * Also remove all temporary files, .../pgsql_tmp/... and .../pgsql_tmp.*
+	 * in the target.
+	 */
+	if (strstr(path, "/" PG_TEMP_FILE_PREFIX) != NULL ||
+		strstr(path, "/" PG_TEMP_FILES_DIR "/") != NULL)
+	{
+		if (entry->target_exists)
+			return FILE_ACTION_REMOVE;
+		else
+			return FILE_ACTION_NONE;
+	}
+
+	/*
+	 * Handle cases where the file is missing from one of the systems.
+	 */
+	if (!entry->target_exists && entry->source_exists)
+	{
+		/*
+		 * File exists in source, but not in target. Copy it in toto. (If it's
+		 * a relation data file, WAL replay after rewinding should re-create
+		 * it anyway. But there's no harm in copying it now.)
+		 */
+		switch (entry->source_type)
+		{
+			case FILE_TYPE_DIRECTORY:
+			case FILE_TYPE_SYMLINK:
+				return FILE_ACTION_CREATE;
+			case FILE_TYPE_REGULAR:
+				return FILE_ACTION_COPY;
+			case FILE_TYPE_UNDEFINED:
+				pg_fatal("unknown file type for \"%s\"", entry->path);
+				break;
+		}
+	}
+	else if (entry->target_exists && !entry->source_exists)
+	{
+		/* File exists in target, but not source. Remove it. */
+		return FILE_ACTION_REMOVE;
+	}
+	else if (!entry->target_exists && !entry->source_exists)
+	{
+		/*
+		 * Doesn't exist in either server. Why does it have an entry in the
+		 * first place??
+		 */
+		return FILE_ACTION_NONE;
+	}
+
+	/*
+	 * Otherwise, the file exists on both systems
+	 */
+	Assert(entry->target_exists && entry->source_exists);
+
+	if (entry->source_type != entry->target_type)
+	{
+		/* But it's a different kind of object. Strange.. */
+		pg_fatal("file \"%s\" is of different type in source and target", entry->path);
+	}
+
+	/*
+	 * PG_VERSION files should be identical on both systems, but avoid
+	 * overwriting them for paranoia.
+	 */
+	if (pg_str_endswith(entry->path, "PG_VERSION"))
+		return FILE_ACTION_NONE;
+
+	switch (entry->source_type)
+	{
+		case FILE_TYPE_DIRECTORY:
+			return FILE_ACTION_NONE;
+
+		case FILE_TYPE_SYMLINK:
+			/* FIXME: Check if it points to the same target? */
+			return FILE_ACTION_NONE;
+
+		case FILE_TYPE_REGULAR:
+			if (!entry->isrelfile)
+			{
+				/*
+				 * It's a non-data file that we have no special processing
+				 * for. Copy it in toto.
+				 */
+				return FILE_ACTION_COPY;
+			}
+			else
+			{
+				/*
+				 * It's a data file that exists in both systems.
+				 *
+				 * If it's larger in target, we can truncate it. There will
+				 * also be a WAL record of the truncation in the source
+				 * system, so WAL replay would eventually truncate the target
+				 * too, but we might as well do it now.
+				 *
+				 * If it's smaller in the target, it means that it has been
+				 * truncated in the target, or enlarged in the source, or
+				 * both. If it was truncated in the target, we need to copy
+				 * the missing tail from the source system. If it was enlarged
+				 * in the source system, there will be WAL records in the
+				 * source system for the new blocks, so we wouldn't need to
+				 * copy them here. But we don't know which scenario we're
+				 * dealing with, and there's no harm in copying the missing
+				 * blocks now, so do it now.
+				 *
+				 * If it's the same size, do nothing here. Any blocks modified
+				 * in the target will be copied based on parsing the target
+				 * system's WAL, and any blocks modified in the source will be
+				 * updated after rewinding, when the source system's WAL is
+				 * replayed.
+				 */
+				if (entry->target_size < entry->source_size)
+					return FILE_ACTION_COPY_TAIL;
+				else if (entry->target_size > entry->source_size)
+					return FILE_ACTION_TRUNCATE;
+				else
+					return FILE_ACTION_NONE;
+			}
+			break;
+
+		case FILE_TYPE_UNDEFINED:
+			pg_fatal("unknown file type for \"%s\"", path);
+			break;
+	}
+
+	/* unreachable */
+	pg_fatal("could not decide what to do with file \"%s\"", path);
+}
+
+/*
+ * Decide what to do with each file.
+ */
+void
+filemap_finalize()
+{
+	int			i;
+
+	filemap_list_to_array(filemap);
+
+	for (i = 0; i < filemap->narray; i++)
+	{
+		file_entry_t *entry = filemap->array[i];
+
+		entry->action = decide_file_action(entry);
+	}
+
+	/* Sort the actions to the order that they should be performed */
+	qsort(filemap->array, filemap->narray, sizeof(file_entry_t *),
+		  final_filemap_cmp);
+}
diff --git a/src/bin/pg_rewind/filemap.h b/src/bin/pg_rewind/filemap.h
index 0cb7425170c..a5e8df57f40 100644
--- a/src/bin/pg_rewind/filemap.h
+++ b/src/bin/pg_rewind/filemap.h
@@ -14,17 +14,21 @@
 
 /*
  * For every file found in the local or remote system, we have a file entry
- * which says what we are going to do with the file. For relation files,
- * there is also a page map, marking pages in the file that were changed
- * locally.
- *
- * The enum values are sorted in the order we want actions to be processed.
+ * that contains information about the file on both systems.  For relation
+ * files, there is also a page map that marks pages in the file that were
+ * changed in the target after the last common checkpoint.  Each entry also
+ * contains an 'action' field, which says what we are going to do with the
+ * file.
  */
+
+/* these enum values are sorted in the order we want actions to be processed */
 typedef enum
 {
+	FILE_ACTION_UNDECIDED = 0,	/* not decided yet */
+
 	FILE_ACTION_CREATE,			/* create local directory or symbolic link */
 	FILE_ACTION_COPY,			/* copy whole file, overwriting if exists */
-	FILE_ACTION_COPY_TAIL,		/* copy tail from 'oldsize' to 'newsize' */
+	FILE_ACTION_COPY_TAIL,		/* copy tail from 'source_size' to 'target_size' */
 	FILE_ACTION_NONE,			/* no action (we might still copy modified
 								 * blocks based on the parsed WAL) */
 	FILE_ACTION_TRUNCATE,		/* truncate local file to 'newsize' bytes */
@@ -33,6 +37,8 @@ typedef enum
 
 typedef enum
 {
+	FILE_TYPE_UNDEFINED = 0,
+
 	FILE_TYPE_REGULAR,
 	FILE_TYPE_DIRECTORY,
 	FILE_TYPE_SYMLINK
@@ -41,19 +47,30 @@ typedef enum
 typedef struct file_entry_t
 {
 	char	   *path;
-	file_type_t type;
+	bool		isrelfile;		/* is it a relation data file? */
 
-	file_action_t action;
+	/*
+	 * Status of the file in the target.
+	 */
+	bool		target_exists;
+	file_type_t target_type;
+	size_t		target_size; /* for a regular file */
+	char	   *target_link_target; /* for a symlink */
 
-	/* for a regular file */
-	size_t		oldsize;
-	size_t		newsize;
-	bool		isrelfile;		/* is it a relation data file? */
+	datapagemap_t target_modified_pages;
 
-	datapagemap_t pagemap;
+	/*
+	 * Status of the file in the source.
+	 */
+	bool		source_exists;
+	file_type_t source_type;
+	size_t		source_size;
+	char	   *source_link_target; /* for a symlink */
 
-	/* for a symlink */
-	char	   *link_target;
+	/*
+	 * What will we do to the file?
+	 */
+	file_action_t action;
 
 	struct file_entry_t *next;
 } file_entry_t;
@@ -70,20 +87,19 @@ typedef struct filemap_t
 
 	/*
 	 * After processing all the remote files, the entries in the linked list
-	 * are moved to this array. After processing local files, too, all the
+	 * are moved to this array.  After processing local files, too, all the
 	 * local entries are added to the array by filemap_finalize, and sorted in
-	 * the final order. After filemap_finalize, all the entries are in the
+	 * the final order.  After filemap_finalize, all the entries are in the
 	 * array, and the linked list is empty.
 	 */
 	file_entry_t **array;
 	int			narray;			/* current length of array */
 
 	/*
-	 * Summary information. total_size is the total size of the source
-	 * cluster, and fetch_size is the number of bytes that needs to be copied.
+	 * Summary information.
 	 */
-	uint64		total_size;
-	uint64		fetch_size;
+	uint64		total_size;		/* total size of the source cluster */
+	uint64		fetch_size;		/* number of bytes that needs to be copied */
 } filemap_t;
 
 extern filemap_t *filemap;
@@ -94,11 +110,12 @@ extern void print_filemap(void);
 
 /* Functions for populating the filemap */
 extern void process_source_file(const char *path, file_type_t type,
-								size_t newsize, const char *link_target);
+								size_t size, const char *link_target);
 extern void process_target_file(const char *path, file_type_t type,
-								size_t newsize, const char *link_target);
-extern void process_block_change(ForkNumber forknum, RelFileNode rnode,
-								 BlockNumber blkno);
+								size_t size, const char *link_target);
+extern void process_target_wal_block_change(ForkNumber forknum,
+											RelFileNode rnode,
+											BlockNumber blkno);
 extern void filemap_finalize(void);
 
 #endif							/* FILEMAP_H */
diff --git a/src/bin/pg_rewind/libpq_fetch.c b/src/bin/pg_rewind/libpq_fetch.c
index bf4dfc23b96..7fc9161b8c8 100644
--- a/src/bin/pg_rewind/libpq_fetch.c
+++ b/src/bin/pg_rewind/libpq_fetch.c
@@ -465,7 +465,7 @@ libpq_executeFileMap(filemap_t *map)
 		entry = map->array[i];
 
 		/* If this is a relation file, copy the modified blocks */
-		execute_pagemap(&entry->pagemap, entry->path);
+		execute_pagemap(&entry->target_modified_pages, entry->path);
 
 		switch (entry->action)
 		{
@@ -476,15 +476,15 @@ libpq_executeFileMap(filemap_t *map)
 			case FILE_ACTION_COPY:
 				/* Truncate the old file out of the way, if any */
 				open_target_file(entry->path, true);
-				fetch_file_range(entry->path, 0, entry->newsize);
+				fetch_file_range(entry->path, 0, entry->source_size);
 				break;
 
 			case FILE_ACTION_TRUNCATE:
-				truncate_target_file(entry->path, entry->newsize);
+				truncate_target_file(entry->path, entry->source_size);
 				break;
 
 			case FILE_ACTION_COPY_TAIL:
-				fetch_file_range(entry->path, entry->oldsize, entry->newsize);
+				fetch_file_range(entry->path, entry->target_size, entry->source_size);
 				break;
 
 			case FILE_ACTION_REMOVE:
@@ -494,6 +494,10 @@ libpq_executeFileMap(filemap_t *map)
 			case FILE_ACTION_CREATE:
 				create_target(entry);
 				break;
+
+			case FILE_ACTION_UNDECIDED:
+				pg_fatal("no action decided for \"%s\"", entry->path);
+				break;
 		}
 	}
 
diff --git a/src/bin/pg_rewind/parsexlog.c b/src/bin/pg_rewind/parsexlog.c
index 2229c86f9af..2baeb74ae93 100644
--- a/src/bin/pg_rewind/parsexlog.c
+++ b/src/bin/pg_rewind/parsexlog.c
@@ -436,6 +436,6 @@ extractPageInfo(XLogReaderState *record)
 		if (forknum != MAIN_FORKNUM)
 			continue;
 
-		process_block_change(forknum, rnode, blkno);
+		process_target_wal_block_change(forknum, rnode, blkno);
 	}
 }
diff --git a/src/bin/pg_rewind/pg_rewind.c b/src/bin/pg_rewind/pg_rewind.c
index c9b9e480c0f..210984d302b 100644
--- a/src/bin/pg_rewind/pg_rewind.c
+++ b/src/bin/pg_rewind/pg_rewind.c
@@ -369,7 +369,7 @@ main(int argc, char **argv)
 				chkpttli);
 
 	/*
-	 * Build the filemap, by comparing the source and target data directories.
+	 * Collect information about all files in the target and source systems.
 	 */
 	filemap_create();
 	if (showprogress)
@@ -390,8 +390,12 @@ main(int argc, char **argv)
 		pg_log_info("reading WAL in target");
 	extractPageMap(datadir_target, chkptrec, lastcommontliIndex,
 				   ControlFile_target.checkPoint, restore_command);
-	filemap_finalize();
 
+	/*
+	 * We have collected all information we need from both systems. Decide
+	 * what to do with each file.
+	 */
+	filemap_finalize();
 	if (showprogress)
 		calculate_totals();
 
-- 
2.20.1


--------------BF34D0120055DC3839060F92
Content-Type: text/x-patch; charset=UTF-8;
 name="v2-0003-pg_rewind-Replace-the-hybrid-list-array-data-stru.patch"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
 filename*0="v2-0003-pg_rewind-Replace-the-hybrid-list-array-data-stru.pa";
 filename*1="tch"



^ permalink  raw  reply  [nested|flat] 41+ messages in thread

* [PATCH 2/5] Refactor pg_rewind for more clear decision making.
@ 2020-08-19 12:34 Heikki Linnakangas <[email protected]>
  0 siblings, 0 replies; 41+ messages in thread

From: Heikki Linnakangas @ 2020-08-19 12:34 UTC (permalink / raw)

Deciding what to do with each file is now a separate step after all the
necessary information has been gathered. It is more clear that way.
Previously, the decision-making was divided between process_source_file()
and process_target_file(), and it was a bit hard to piece together what the
overall rules were.
---
 src/bin/pg_rewind/copy_fetch.c  |  14 +-
 src/bin/pg_rewind/file_ops.c    |  16 +-
 src/bin/pg_rewind/filemap.c     | 514 ++++++++++++++++----------------
 src/bin/pg_rewind/filemap.h     |  67 +++--
 src/bin/pg_rewind/libpq_fetch.c |  12 +-
 src/bin/pg_rewind/parsexlog.c   |   2 +-
 src/bin/pg_rewind/pg_rewind.c   |   8 +-
 7 files changed, 332 insertions(+), 301 deletions(-)

diff --git a/src/bin/pg_rewind/copy_fetch.c b/src/bin/pg_rewind/copy_fetch.c
index 1edab5f1867..18fad32600e 100644
--- a/src/bin/pg_rewind/copy_fetch.c
+++ b/src/bin/pg_rewind/copy_fetch.c
@@ -210,7 +210,7 @@ copy_executeFileMap(filemap_t *map)
 	for (i = 0; i < map->narray; i++)
 	{
 		entry = map->array[i];
-		execute_pagemap(&entry->pagemap, entry->path);
+		execute_pagemap(&entry->target_modified_pages, entry->path);
 
 		switch (entry->action)
 		{
@@ -219,16 +219,16 @@ copy_executeFileMap(filemap_t *map)
 				break;
 
 			case FILE_ACTION_COPY:
-				rewind_copy_file_range(entry->path, 0, entry->newsize, true);
+				rewind_copy_file_range(entry->path, 0, entry->source_size, true);
 				break;
 
 			case FILE_ACTION_TRUNCATE:
-				truncate_target_file(entry->path, entry->newsize);
+				truncate_target_file(entry->path, entry->source_size);
 				break;
 
 			case FILE_ACTION_COPY_TAIL:
-				rewind_copy_file_range(entry->path, entry->oldsize,
-									   entry->newsize, false);
+				rewind_copy_file_range(entry->path, entry->target_size,
+									   entry->source_size, false);
 				break;
 
 			case FILE_ACTION_CREATE:
@@ -238,6 +238,10 @@ copy_executeFileMap(filemap_t *map)
 			case FILE_ACTION_REMOVE:
 				remove_target(entry);
 				break;
+
+			case FILE_ACTION_UNDECIDED:
+				pg_fatal("no action decided for \"%s\"", entry->path);
+				break;
 		}
 	}
 
diff --git a/src/bin/pg_rewind/file_ops.c b/src/bin/pg_rewind/file_ops.c
index 55439db20ba..ec37d0b2e0d 100644
--- a/src/bin/pg_rewind/file_ops.c
+++ b/src/bin/pg_rewind/file_ops.c
@@ -126,8 +126,9 @@ void
 remove_target(file_entry_t *entry)
 {
 	Assert(entry->action == FILE_ACTION_REMOVE);
+	Assert(entry->target_exists);
 
-	switch (entry->type)
+	switch (entry->target_type)
 	{
 		case FILE_TYPE_DIRECTORY:
 			remove_target_dir(entry->path);
@@ -140,6 +141,10 @@ remove_target(file_entry_t *entry)
 		case FILE_TYPE_SYMLINK:
 			remove_target_symlink(entry->path);
 			break;
+
+		case FILE_TYPE_UNDEFINED:
+			pg_fatal("undefined file type for \"%s\"", entry->path);
+			break;
 	}
 }
 
@@ -147,21 +152,26 @@ void
 create_target(file_entry_t *entry)
 {
 	Assert(entry->action == FILE_ACTION_CREATE);
+	Assert(!entry->target_exists);
 
-	switch (entry->type)
+	switch (entry->source_type)
 	{
 		case FILE_TYPE_DIRECTORY:
 			create_target_dir(entry->path);
 			break;
 
 		case FILE_TYPE_SYMLINK:
-			create_target_symlink(entry->path, entry->link_target);
+			create_target_symlink(entry->path, entry->source_link_target);
 			break;
 
 		case FILE_TYPE_REGULAR:
 			/* can't happen. Regular files are created with open_target_file. */
 			pg_fatal("invalid action (CREATE) for regular file");
 			break;
+
+		case FILE_TYPE_UNDEFINED:
+			pg_fatal("undefined file type for \"%s\"", entry->path);
+			break;
 	}
 }
 
diff --git a/src/bin/pg_rewind/filemap.c b/src/bin/pg_rewind/filemap.c
index 1879257b66a..431e8e760e0 100644
--- a/src/bin/pg_rewind/filemap.c
+++ b/src/bin/pg_rewind/filemap.c
@@ -26,6 +26,8 @@ static bool isRelDataFile(const char *path);
 static char *datasegpath(RelFileNode rnode, ForkNumber forknum,
 						 BlockNumber segno);
 static int	path_cmp(const void *a, const void *b);
+
+static file_entry_t *get_filemap_entry(const char *path, bool create);
 static int	final_filemap_cmp(const void *a, const void *b);
 static void filemap_list_to_array(filemap_t *map);
 static bool check_file_excluded(const char *path, bool is_source);
@@ -146,6 +148,65 @@ filemap_create(void)
 	filemap = map;
 }
 
+/* Look up or create entry for 'path' */
+static file_entry_t *
+get_filemap_entry(const char *path, bool create)
+{
+	filemap_t  *map = filemap;
+	file_entry_t *entry;
+	file_entry_t **e;
+	file_entry_t key;
+	file_entry_t *key_ptr;
+
+	if (map->array)
+	{
+		key.path = (char *) path;
+		key_ptr = &key;
+		e = bsearch(&key_ptr, map->array, map->narray, sizeof(file_entry_t *),
+					path_cmp);
+	}
+	else
+		e = NULL;
+
+	if (e)
+		entry = *e;
+	else if (!create)
+		entry = NULL;
+	else
+	{
+		/* Create a new entry for this file */
+		entry = pg_malloc(sizeof(file_entry_t));
+		entry->path = pg_strdup(path);
+		entry->isrelfile = isRelDataFile(path);
+		entry->action = FILE_ACTION_UNDECIDED;
+
+		entry->target_exists = false;
+		entry->target_type = FILE_TYPE_UNDEFINED;
+		entry->target_size = 0;
+		entry->target_link_target = NULL;
+		entry->target_modified_pages.bitmap = NULL;
+		entry->target_modified_pages.bitmapsize = 0;
+
+		entry->source_exists = false;
+		entry->source_type = FILE_TYPE_UNDEFINED;
+		entry->source_size = 0;
+		entry->source_link_target = NULL;
+
+		entry->next = NULL;
+
+		if (map->last)
+		{
+			map->last->next = entry;
+			map->last = entry;
+		}
+		else
+			map->first = map->last = entry;
+		map->nlist++;
+	}
+
+	return entry;
+}
+
 /*
  * Callback for processing source file list.
  *
@@ -154,18 +215,19 @@ filemap_create(void)
  * exists in the target and whether the size matches.
  */
 void
-process_source_file(const char *path, file_type_t type, size_t newsize,
+process_source_file(const char *path, file_type_t type, size_t size,
 					const char *link_target)
 {
-	bool		exists;
-	char		localpath[MAXPGPATH];
-	struct stat statbuf;
-	filemap_t  *map = filemap;
-	file_action_t action = FILE_ACTION_NONE;
-	size_t		oldsize = 0;
 	file_entry_t *entry;
 
-	Assert(map->array == NULL);
+	Assert(filemap->array == NULL);
+
+	/*
+	 * Skip the control file. It is handled specially, after copying all the
+	 * other files.
+	 */
+	if (strcmp(path, "global/pg_control") == 0)
+		return;
 
 	/*
 	 * Skip any files matching the exclusion filters. This has the effect to
@@ -199,142 +261,12 @@ process_source_file(const char *path, file_type_t type, size_t newsize,
 	if (type != FILE_TYPE_REGULAR && isRelDataFile(path))
 		pg_fatal("data file \"%s\" in source is not a regular file", path);
 
-	snprintf(localpath, sizeof(localpath), "%s/%s", datadir_target, path);
-
-	/* Does the corresponding file exist in the target data dir? */
-	if (lstat(localpath, &statbuf) < 0)
-	{
-		if (errno != ENOENT)
-			pg_fatal("could not stat file \"%s\": %m",
-					 localpath);
-
-		exists = false;
-	}
-	else
-		exists = true;
-
-	switch (type)
-	{
-		case FILE_TYPE_DIRECTORY:
-			if (exists && !S_ISDIR(statbuf.st_mode) && strcmp(path, "pg_wal") != 0)
-			{
-				/* it's a directory in source, but not in target. Strange.. */
-				pg_fatal("\"%s\" is not a directory", localpath);
-			}
-
-			if (!exists)
-				action = FILE_ACTION_CREATE;
-			else
-				action = FILE_ACTION_NONE;
-			oldsize = 0;
-			break;
-
-		case FILE_TYPE_SYMLINK:
-			if (exists &&
-#ifndef WIN32
-				!S_ISLNK(statbuf.st_mode)
-#else
-				!pgwin32_is_junction(localpath)
-#endif
-				)
-			{
-				/*
-				 * It's a symbolic link in source, but not in target.
-				 * Strange..
-				 */
-				pg_fatal("\"%s\" is not a symbolic link", localpath);
-			}
-
-			if (!exists)
-				action = FILE_ACTION_CREATE;
-			else
-				action = FILE_ACTION_NONE;
-			oldsize = 0;
-			break;
-
-		case FILE_TYPE_REGULAR:
-			if (exists && !S_ISREG(statbuf.st_mode))
-				pg_fatal("\"%s\" is not a regular file", localpath);
-
-			if (!exists || !isRelDataFile(path))
-			{
-				/*
-				 * File exists in source, but not in target. Or it's a
-				 * non-data file that we have no special processing for. Copy
-				 * it in toto.
-				 *
-				 * An exception: PG_VERSIONs should be identical, but avoid
-				 * overwriting it for paranoia.
-				 */
-				if (pg_str_endswith(path, "PG_VERSION"))
-				{
-					action = FILE_ACTION_NONE;
-					oldsize = statbuf.st_size;
-				}
-				else
-				{
-					action = FILE_ACTION_COPY;
-					oldsize = 0;
-				}
-			}
-			else
-			{
-				/*
-				 * It's a data file that exists in both.
-				 *
-				 * If it's larger in target, we can truncate it. There will
-				 * also be a WAL record of the truncation in the source
-				 * system, so WAL replay would eventually truncate the target
-				 * too, but we might as well do it now.
-				 *
-				 * If it's smaller in the target, it means that it has been
-				 * truncated in the target, or enlarged in the source, or
-				 * both. If it was truncated in the target, we need to copy
-				 * the missing tail from the source system. If it was enlarged
-				 * in the source system, there will be WAL records in the
-				 * source system for the new blocks, so we wouldn't need to
-				 * copy them here. But we don't know which scenario we're
-				 * dealing with, and there's no harm in copying the missing
-				 * blocks now, so do it now.
-				 *
-				 * If it's the same size, do nothing here. Any blocks modified
-				 * in the target will be copied based on parsing the target
-				 * system's WAL, and any blocks modified in the source will be
-				 * updated after rewinding, when the source system's WAL is
-				 * replayed.
-				 */
-				oldsize = statbuf.st_size;
-				if (oldsize < newsize)
-					action = FILE_ACTION_COPY_TAIL;
-				else if (oldsize > newsize)
-					action = FILE_ACTION_TRUNCATE;
-				else
-					action = FILE_ACTION_NONE;
-			}
-			break;
-	}
-
-	/* Create a new entry for this file */
-	entry = pg_malloc(sizeof(file_entry_t));
-	entry->path = pg_strdup(path);
-	entry->type = type;
-	entry->action = action;
-	entry->oldsize = oldsize;
-	entry->newsize = newsize;
-	entry->link_target = link_target ? pg_strdup(link_target) : NULL;
-	entry->next = NULL;
-	entry->pagemap.bitmap = NULL;
-	entry->pagemap.bitmapsize = 0;
-	entry->isrelfile = isRelDataFile(path);
-
-	if (map->last)
-	{
-		map->last->next = entry;
-		map->last = entry;
-	}
-	else
-		map->first = map->last = entry;
-	map->nlist++;
+	/* Remember this source file */
+	entry = get_filemap_entry(path, true);
+	entry->source_exists = true;
+	entry->source_type = type;
+	entry->source_size = size;
+	entry->source_link_target = link_target ? pg_strdup(link_target) : NULL;
 }
 
 /*
@@ -345,33 +277,24 @@ process_source_file(const char *path, file_type_t type, size_t newsize,
  * deletion.
  */
 void
-process_target_file(const char *path, file_type_t type, size_t oldsize,
+process_target_file(const char *path, file_type_t type, size_t size,
 					const char *link_target)
 {
-	bool		exists;
-	char		localpath[MAXPGPATH];
-	struct stat statbuf;
-	file_entry_t key;
-	file_entry_t *key_ptr;
 	filemap_t  *map = filemap;
 	file_entry_t *entry;
 
+	/*
+	 * Skip the control file. It is handled specially, after copying all the
+	 * other files.
+	 */
+	if (strcmp(path, "global/pg_control") == 0)
+		return;
+
 	/*
 	 * Do not apply any exclusion filters here.  This has advantage to remove
 	 * from the target data folder all paths which have been filtered out from
 	 * the source data folder when processing the source files.
 	 */
-
-	snprintf(localpath, sizeof(localpath), "%s/%s", datadir_target, path);
-	if (lstat(localpath, &statbuf) < 0)
-	{
-		if (errno != ENOENT)
-			pg_fatal("could not stat file \"%s\": %m",
-					 localpath);
-
-		exists = false;
-	}
-
 	if (map->array == NULL)
 	{
 		/* on first call, initialize lookup array */
@@ -389,120 +312,76 @@ process_target_file(const char *path, file_type_t type, size_t oldsize,
 	}
 
 	/*
-	 * Like in process_source_file, pretend that xlog is always a  directory.
+	 * Like in process_source_file, pretend that pg_wal is always a directory.
 	 */
 	if (strcmp(path, "pg_wal") == 0 && type == FILE_TYPE_SYMLINK)
 		type = FILE_TYPE_DIRECTORY;
 
-	key.path = (char *) path;
-	key_ptr = &key;
-	exists = (bsearch(&key_ptr, map->array, map->narray, sizeof(file_entry_t *),
-					  path_cmp) != NULL);
-
-	/* Remove any file or folder that doesn't exist in the source system. */
-	if (!exists)
-	{
-		entry = pg_malloc(sizeof(file_entry_t));
-		entry->path = pg_strdup(path);
-		entry->type = type;
-		entry->action = FILE_ACTION_REMOVE;
-		entry->oldsize = oldsize;
-		entry->newsize = 0;
-		entry->link_target = link_target ? pg_strdup(link_target) : NULL;
-		entry->next = NULL;
-		entry->pagemap.bitmap = NULL;
-		entry->pagemap.bitmapsize = 0;
-		entry->isrelfile = isRelDataFile(path);
-
-		if (map->last == NULL)
-			map->first = entry;
-		else
-			map->last->next = entry;
-		map->last = entry;
-		map->nlist++;
-	}
-	else
-	{
-		/*
-		 * We already handled all files that exist in the source system in
-		 * process_source_file().
-		 */
-	}
+	/* Remember this target file */
+	entry = get_filemap_entry(path, true);
+	entry->target_exists = true;
+	entry->target_type = type;
+	entry->target_size = size;
+	entry->target_link_target = link_target ? pg_strdup(link_target) : NULL;
 }
 
 /*
  * This callback gets called while we read the WAL in the target, for every
- * block that have changed in the target system. It makes note of all the
+ * block that have changed in the target system.  It makes note of all the
  * changed blocks in the pagemap of the file.
+ *
+ * NOTE: All the files on both systems must have already been added to the
+ * file map!
  */
 void
-process_block_change(ForkNumber forknum, RelFileNode rnode, BlockNumber blkno)
+process_target_wal_block_change(ForkNumber forknum, RelFileNode rnode,
+								BlockNumber blkno)
 {
 	char	   *path;
-	file_entry_t key;
-	file_entry_t *key_ptr;
 	file_entry_t *entry;
 	BlockNumber blkno_inseg;
 	int			segno;
-	filemap_t  *map = filemap;
-	file_entry_t **e;
 
-	Assert(map->array);
+	Assert(filemap->array);
 
 	segno = blkno / RELSEG_SIZE;
 	blkno_inseg = blkno % RELSEG_SIZE;
 
 	path = datasegpath(rnode, forknum, segno);
-
-	key.path = (char *) path;
-	key_ptr = &key;
-
-	e = bsearch(&key_ptr, map->array, map->narray, sizeof(file_entry_t *),
-				path_cmp);
-	if (e)
-		entry = *e;
-	else
-		entry = NULL;
+	entry = get_filemap_entry(path, false);
 	pfree(path);
 
 	if (entry)
 	{
+		int64		end_offset;
+
 		Assert(entry->isrelfile);
 
-		switch (entry->action)
-		{
-			case FILE_ACTION_NONE:
-			case FILE_ACTION_TRUNCATE:
-				/* skip if we're truncating away the modified block anyway */
-				if ((blkno_inseg + 1) * BLCKSZ <= entry->newsize)
-					datapagemap_add(&entry->pagemap, blkno_inseg);
-				break;
-
-			case FILE_ACTION_COPY_TAIL:
-
-				/*
-				 * skip the modified block if it is part of the "tail" that
-				 * we're copying anyway.
-				 */
-				if ((blkno_inseg + 1) * BLCKSZ <= entry->oldsize)
-					datapagemap_add(&entry->pagemap, blkno_inseg);
-				break;
-
-			case FILE_ACTION_COPY:
-			case FILE_ACTION_REMOVE:
-				break;
-
-			case FILE_ACTION_CREATE:
-				pg_fatal("unexpected page modification for directory or symbolic link \"%s\"", entry->path);
-		}
+		if (entry->target_type != FILE_TYPE_REGULAR)
+			pg_fatal("unexpected page modification for directory or symbolic link \"%s\"",
+					 entry->path);
+
+		/*
+		 * If the block beyond the EOF in the source system, no need to
+		 * remember it now, because we're going to truncate it away from the
+		 * target anyway. Also no need to remember the block if it's beyond
+		 * the current EOF in the target system; we will copy it over with the
+		 * "tail" from the source system, anyway.
+		 */
+		end_offset = (blkno_inseg + 1) * BLCKSZ;
+		if (end_offset <= entry->source_size &&
+			end_offset <= entry->target_size)
+			datapagemap_add(&entry->target_modified_pages, blkno_inseg);
 	}
 	else
 	{
 		/*
 		 * If we don't have any record of this file in the file map, it means
-		 * that it's a relation that doesn't exist in the source system, and
-		 * it was subsequently removed in the target system, too. We can
-		 * safely ignore it.
+		 * that it's a relation that doesn't exist in the source system.  It
+		 * could exist in the target system; we haven't moved the target-only
+		 * entries from the linked list to the array yet!  But in any case, if
+		 * it doesn't exist in the source it will be removed from the target
+		 * too, and we can safely ignore it.
 		 */
 	}
 }
@@ -593,16 +472,6 @@ filemap_list_to_array(filemap_t *map)
 	map->first = map->last = NULL;
 }
 
-void
-filemap_finalize(void)
-{
-	filemap_t  *map = filemap;
-
-	filemap_list_to_array(map);
-	qsort(map->array, map->narray, sizeof(file_entry_t *),
-		  final_filemap_cmp);
-}
-
 static const char *
 action_to_str(file_action_t action)
 {
@@ -643,26 +512,26 @@ calculate_totals(void)
 	{
 		entry = map->array[i];
 
-		if (entry->type != FILE_TYPE_REGULAR)
+		if (entry->source_type != FILE_TYPE_REGULAR)
 			continue;
 
-		map->total_size += entry->newsize;
+		map->total_size += entry->source_size;
 
 		if (entry->action == FILE_ACTION_COPY)
 		{
-			map->fetch_size += entry->newsize;
+			map->fetch_size += entry->source_size;
 			continue;
 		}
 
 		if (entry->action == FILE_ACTION_COPY_TAIL)
-			map->fetch_size += (entry->newsize - entry->oldsize);
+			map->fetch_size += (entry->source_size - entry->target_size);
 
-		if (entry->pagemap.bitmapsize > 0)
+		if (entry->target_modified_pages.bitmapsize > 0)
 		{
 			datapagemap_iterator_t *iter;
 			BlockNumber blk;
 
-			iter = datapagemap_iterate(&entry->pagemap);
+			iter = datapagemap_iterate(&entry->target_modified_pages);
 			while (datapagemap_next(iter, &blk))
 				map->fetch_size += BLCKSZ;
 
@@ -682,13 +551,13 @@ print_filemap(void)
 	{
 		entry = map->array[i];
 		if (entry->action != FILE_ACTION_NONE ||
-			entry->pagemap.bitmapsize > 0)
+			entry->target_modified_pages.bitmapsize > 0)
 		{
 			pg_log_debug("%s (%s)", entry->path,
 						 action_to_str(entry->action));
 
-			if (entry->pagemap.bitmapsize > 0)
-				datapagemap_print(&entry->pagemap);
+			if (entry->target_modified_pages.bitmapsize > 0)
+				datapagemap_print(&entry->target_modified_pages);
 		}
 	}
 	fflush(stdout);
@@ -837,3 +706,126 @@ final_filemap_cmp(const void *a, const void *b)
 	else
 		return strcmp(fa->path, fb->path);
 }
+
+/*
+ * Decide what to do with each file.
+ */
+void
+filemap_finalize()
+{
+	int			i;
+
+	filemap_list_to_array(filemap);
+
+	for (i = 0; i < filemap->narray; i++)
+	{
+		file_entry_t *entry = filemap->array[i];
+		file_action_t action;
+
+		if (!entry->target_exists && entry->source_exists)
+		{
+			/*
+			 * File exists in source, but not in target. Copy
+			 * it in toto. (If it's a relation data file, WAL replay
+			 * after rewinding should re-create it anyway. But there's
+			 * no harm in copying it now.)
+			 */
+			if (entry->source_type == FILE_TYPE_DIRECTORY ||
+				entry->source_type == FILE_TYPE_SYMLINK)
+			{
+				action = FILE_ACTION_CREATE;
+			}
+			else
+				action = FILE_ACTION_COPY;
+		}
+		else if (entry->target_exists && !entry->source_exists)
+		{
+			/* Delete file from target */
+			action = FILE_ACTION_REMOVE;
+		}
+		else if (entry->target_exists && entry->source_exists)
+		{
+			/* File exists in both systems */
+			if (entry->source_type != entry->target_type)
+			{
+				/* But it's a different kind of object. Strange.. */
+				pg_fatal("file \"%s\" is of different type in source and target", entry->path);
+			}
+
+			switch (entry->source_type)
+			{
+				case FILE_TYPE_DIRECTORY:
+					action = FILE_ACTION_NONE;
+					break;
+
+				case FILE_TYPE_SYMLINK:
+					// FIXME: Check if it points to the same target?
+					action = FILE_ACTION_NONE;
+					break;
+
+				case FILE_TYPE_REGULAR:
+					if (!entry->isrelfile)
+					{
+						/*
+						 * It's a non-data file that we have no special processing for. Copy
+						 * it in toto.
+						 *
+						 * An exception: PG_VERSIONs should be identical, but avoid
+						 * overwriting it for paranoia.
+						 */
+						if (!pg_str_endswith(entry->path, "PG_VERSION"))
+							action = FILE_ACTION_COPY;
+						else
+							action = FILE_ACTION_NONE;
+					}
+					else
+					{
+						/*
+						 * It's a data file that exists in both systems.
+						 *
+						 * If it's larger in target, we can truncate it. There will
+						 * also be a WAL record of the truncation in the source
+						 * system, so WAL replay would eventually truncate the target
+						 * too, but we might as well do it now.
+						 *
+						 * If it's smaller in the target, it means that it has been
+						 * truncated in the target, or enlarged in the source, or
+						 * both. If it was truncated in the target, we need to copy
+						 * the missing tail from the source system. If it was enlarged
+						 * in the source system, there will be WAL records in the
+						 * source system for the new blocks, so we wouldn't need to
+						 * copy them here. But we don't know which scenario we're
+						 * dealing with, and there's no harm in copying the missing
+						 * blocks now, so do it now.
+						 *
+						 * If it's the same size, do nothing here. Any blocks modified
+						 * in the target will be copied based on parsing the target
+						 * system's WAL, and any blocks modified in the source will be
+						 * updated after rewinding, when the source system's WAL is
+						 * replayed.
+						 */
+						if (entry->target_size < entry->source_size)
+							action = FILE_ACTION_COPY_TAIL;
+						else if (entry->target_size > entry->source_size)
+							action = FILE_ACTION_TRUNCATE;
+						else
+							action = FILE_ACTION_NONE;
+					}
+					break;
+				case FILE_TYPE_UNDEFINED:
+					pg_fatal("unknown file type for \"%s\"", entry->path);
+					break;
+			}
+		}
+		else
+		{
+			/* Doesn't exist in either server. Why does it have an entry at all?? */
+			action = FILE_ACTION_NONE;
+		}
+		entry->action = action;
+	}
+
+	/* Sort the actions to the order that they should be performed */
+	qsort(filemap->array, filemap->narray, sizeof(file_entry_t *),
+		  final_filemap_cmp);
+}
diff --git a/src/bin/pg_rewind/filemap.h b/src/bin/pg_rewind/filemap.h
index 0cb7425170c..a5e8df57f40 100644
--- a/src/bin/pg_rewind/filemap.h
+++ b/src/bin/pg_rewind/filemap.h
@@ -14,17 +14,21 @@
 
 /*
  * For every file found in the local or remote system, we have a file entry
- * which says what we are going to do with the file. For relation files,
- * there is also a page map, marking pages in the file that were changed
- * locally.
- *
- * The enum values are sorted in the order we want actions to be processed.
+ * that contains information about the file on both systems.  For relation
+ * files, there is also a page map that marks pages in the file that were
+ * changed in the target after the last common checkpoint.  Each entry also
+ * contains an 'action' field, which says what we are going to do with the
+ * file.
  */
+
+/* these enum values are sorted in the order we want actions to be processed */
 typedef enum
 {
+	FILE_ACTION_UNDECIDED = 0,	/* not decided yet */
+
 	FILE_ACTION_CREATE,			/* create local directory or symbolic link */
 	FILE_ACTION_COPY,			/* copy whole file, overwriting if exists */
-	FILE_ACTION_COPY_TAIL,		/* copy tail from 'oldsize' to 'newsize' */
+	FILE_ACTION_COPY_TAIL,		/* copy tail from 'source_size' to 'target_size' */
 	FILE_ACTION_NONE,			/* no action (we might still copy modified
 								 * blocks based on the parsed WAL) */
 	FILE_ACTION_TRUNCATE,		/* truncate local file to 'newsize' bytes */
@@ -33,6 +37,8 @@ typedef enum
 
 typedef enum
 {
+	FILE_TYPE_UNDEFINED = 0,
+
 	FILE_TYPE_REGULAR,
 	FILE_TYPE_DIRECTORY,
 	FILE_TYPE_SYMLINK
@@ -41,19 +47,30 @@ typedef enum
 typedef struct file_entry_t
 {
 	char	   *path;
-	file_type_t type;
+	bool		isrelfile;		/* is it a relation data file? */
 
-	file_action_t action;
+	/*
+	 * Status of the file in the target.
+	 */
+	bool		target_exists;
+	file_type_t target_type;
+	size_t		target_size; /* for a regular file */
+	char	   *target_link_target; /* for a symlink */
 
-	/* for a regular file */
-	size_t		oldsize;
-	size_t		newsize;
-	bool		isrelfile;		/* is it a relation data file? */
+	datapagemap_t target_modified_pages;
 
-	datapagemap_t pagemap;
+	/*
+	 * Status of the file in the source.
+	 */
+	bool		source_exists;
+	file_type_t source_type;
+	size_t		source_size;
+	char	   *source_link_target; /* for a symlink */
 
-	/* for a symlink */
-	char	   *link_target;
+	/*
+	 * What will we do to the file?
+	 */
+	file_action_t action;
 
 	struct file_entry_t *next;
 } file_entry_t;
@@ -70,20 +87,19 @@ typedef struct filemap_t
 
 	/*
 	 * After processing all the remote files, the entries in the linked list
-	 * are moved to this array. After processing local files, too, all the
+	 * are moved to this array.  After processing local files, too, all the
 	 * local entries are added to the array by filemap_finalize, and sorted in
-	 * the final order. After filemap_finalize, all the entries are in the
+	 * the final order.  After filemap_finalize, all the entries are in the
 	 * array, and the linked list is empty.
 	 */
 	file_entry_t **array;
 	int			narray;			/* current length of array */
 
 	/*
-	 * Summary information. total_size is the total size of the source
-	 * cluster, and fetch_size is the number of bytes that needs to be copied.
+	 * Summary information.
 	 */
-	uint64		total_size;
-	uint64		fetch_size;
+	uint64		total_size;		/* total size of the source cluster */
+	uint64		fetch_size;		/* number of bytes that needs to be copied */
 } filemap_t;
 
 extern filemap_t *filemap;
@@ -94,11 +110,12 @@ extern void print_filemap(void);
 
 /* Functions for populating the filemap */
 extern void process_source_file(const char *path, file_type_t type,
-								size_t newsize, const char *link_target);
+								size_t size, const char *link_target);
 extern void process_target_file(const char *path, file_type_t type,
-								size_t newsize, const char *link_target);
-extern void process_block_change(ForkNumber forknum, RelFileNode rnode,
-								 BlockNumber blkno);
+								size_t size, const char *link_target);
+extern void process_target_wal_block_change(ForkNumber forknum,
+											RelFileNode rnode,
+											BlockNumber blkno);
 extern void filemap_finalize(void);
 
 #endif							/* FILEMAP_H */
diff --git a/src/bin/pg_rewind/libpq_fetch.c b/src/bin/pg_rewind/libpq_fetch.c
index bf4dfc23b96..7fc9161b8c8 100644
--- a/src/bin/pg_rewind/libpq_fetch.c
+++ b/src/bin/pg_rewind/libpq_fetch.c
@@ -465,7 +465,7 @@ libpq_executeFileMap(filemap_t *map)
 		entry = map->array[i];
 
 		/* If this is a relation file, copy the modified blocks */
-		execute_pagemap(&entry->pagemap, entry->path);
+		execute_pagemap(&entry->target_modified_pages, entry->path);
 
 		switch (entry->action)
 		{
@@ -476,15 +476,15 @@ libpq_executeFileMap(filemap_t *map)
 			case FILE_ACTION_COPY:
 				/* Truncate the old file out of the way, if any */
 				open_target_file(entry->path, true);
-				fetch_file_range(entry->path, 0, entry->newsize);
+				fetch_file_range(entry->path, 0, entry->source_size);
 				break;
 
 			case FILE_ACTION_TRUNCATE:
-				truncate_target_file(entry->path, entry->newsize);
+				truncate_target_file(entry->path, entry->source_size);
 				break;
 
 			case FILE_ACTION_COPY_TAIL:
-				fetch_file_range(entry->path, entry->oldsize, entry->newsize);
+				fetch_file_range(entry->path, entry->target_size, entry->source_size);
 				break;
 
 			case FILE_ACTION_REMOVE:
@@ -494,6 +494,10 @@ libpq_executeFileMap(filemap_t *map)
 			case FILE_ACTION_CREATE:
 				create_target(entry);
 				break;
+
+			case FILE_ACTION_UNDECIDED:
+				pg_fatal("no action decided for \"%s\"", entry->path);
+				break;
 		}
 	}
 
diff --git a/src/bin/pg_rewind/parsexlog.c b/src/bin/pg_rewind/parsexlog.c
index 2229c86f9af..2baeb74ae93 100644
--- a/src/bin/pg_rewind/parsexlog.c
+++ b/src/bin/pg_rewind/parsexlog.c
@@ -436,6 +436,6 @@ extractPageInfo(XLogReaderState *record)
 		if (forknum != MAIN_FORKNUM)
 			continue;
 
-		process_block_change(forknum, rnode, blkno);
+		process_target_wal_block_change(forknum, rnode, blkno);
 	}
 }
diff --git a/src/bin/pg_rewind/pg_rewind.c b/src/bin/pg_rewind/pg_rewind.c
index c9b9e480c0f..210984d302b 100644
--- a/src/bin/pg_rewind/pg_rewind.c
+++ b/src/bin/pg_rewind/pg_rewind.c
@@ -369,7 +369,7 @@ main(int argc, char **argv)
 				chkpttli);
 
 	/*
-	 * Build the filemap, by comparing the source and target data directories.
+	 * Collect information about all files in the target and source systems.
 	 */
 	filemap_create();
 	if (showprogress)
@@ -390,8 +390,12 @@ main(int argc, char **argv)
 		pg_log_info("reading WAL in target");
 	extractPageMap(datadir_target, chkptrec, lastcommontliIndex,
 				   ControlFile_target.checkPoint, restore_command);
-	filemap_finalize();
 
+	/*
+	 * We have collected all information we need from both systems. Decide
+	 * what to do with each file.
+	 */
+	filemap_finalize();
 	if (showprogress)
 		calculate_totals();
 
-- 
2.20.1


--------------D93EDEBFB124D563B723F4BD
Content-Type: text/x-patch; charset=UTF-8;
 name="0003-pg_rewind-Replace-the-hybrid-list-array-data-structu.patch"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
 filename*0="0003-pg_rewind-Replace-the-hybrid-list-array-data-structu.pa";
 filename*1="tch"



^ permalink  raw  reply  [nested|flat] 41+ messages in thread

* [PATCH 2/5] Refactor pg_rewind for more clear decision making.
@ 2020-08-19 12:34 Heikki Linnakangas <[email protected]>
  0 siblings, 0 replies; 41+ messages in thread

From: Heikki Linnakangas @ 2020-08-19 12:34 UTC (permalink / raw)

Deciding what to do with each file is now a separate step after all the
necessary information has been gathered. It is more clear that way.
Previously, the decision-making was divided between process_source_file()
and process_target_file(), and it was a bit hard to piece together what the
overall rules were.
---
 src/bin/pg_rewind/copy_fetch.c  |  14 +-
 src/bin/pg_rewind/file_ops.c    |  16 +-
 src/bin/pg_rewind/filemap.c     | 514 ++++++++++++++++----------------
 src/bin/pg_rewind/filemap.h     |  67 +++--
 src/bin/pg_rewind/libpq_fetch.c |  12 +-
 src/bin/pg_rewind/parsexlog.c   |   2 +-
 src/bin/pg_rewind/pg_rewind.c   |   8 +-
 7 files changed, 332 insertions(+), 301 deletions(-)

diff --git a/src/bin/pg_rewind/copy_fetch.c b/src/bin/pg_rewind/copy_fetch.c
index 1edab5f1867..18fad32600e 100644
--- a/src/bin/pg_rewind/copy_fetch.c
+++ b/src/bin/pg_rewind/copy_fetch.c
@@ -210,7 +210,7 @@ copy_executeFileMap(filemap_t *map)
 	for (i = 0; i < map->narray; i++)
 	{
 		entry = map->array[i];
-		execute_pagemap(&entry->pagemap, entry->path);
+		execute_pagemap(&entry->target_modified_pages, entry->path);
 
 		switch (entry->action)
 		{
@@ -219,16 +219,16 @@ copy_executeFileMap(filemap_t *map)
 				break;
 
 			case FILE_ACTION_COPY:
-				rewind_copy_file_range(entry->path, 0, entry->newsize, true);
+				rewind_copy_file_range(entry->path, 0, entry->source_size, true);
 				break;
 
 			case FILE_ACTION_TRUNCATE:
-				truncate_target_file(entry->path, entry->newsize);
+				truncate_target_file(entry->path, entry->source_size);
 				break;
 
 			case FILE_ACTION_COPY_TAIL:
-				rewind_copy_file_range(entry->path, entry->oldsize,
-									   entry->newsize, false);
+				rewind_copy_file_range(entry->path, entry->target_size,
+									   entry->source_size, false);
 				break;
 
 			case FILE_ACTION_CREATE:
@@ -238,6 +238,10 @@ copy_executeFileMap(filemap_t *map)
 			case FILE_ACTION_REMOVE:
 				remove_target(entry);
 				break;
+
+			case FILE_ACTION_UNDECIDED:
+				pg_fatal("no action decided for \"%s\"", entry->path);
+				break;
 		}
 	}
 
diff --git a/src/bin/pg_rewind/file_ops.c b/src/bin/pg_rewind/file_ops.c
index 55439db20ba..ec37d0b2e0d 100644
--- a/src/bin/pg_rewind/file_ops.c
+++ b/src/bin/pg_rewind/file_ops.c
@@ -126,8 +126,9 @@ void
 remove_target(file_entry_t *entry)
 {
 	Assert(entry->action == FILE_ACTION_REMOVE);
+	Assert(entry->target_exists);
 
-	switch (entry->type)
+	switch (entry->target_type)
 	{
 		case FILE_TYPE_DIRECTORY:
 			remove_target_dir(entry->path);
@@ -140,6 +141,10 @@ remove_target(file_entry_t *entry)
 		case FILE_TYPE_SYMLINK:
 			remove_target_symlink(entry->path);
 			break;
+
+		case FILE_TYPE_UNDEFINED:
+			pg_fatal("undefined file type for \"%s\"", entry->path);
+			break;
 	}
 }
 
@@ -147,21 +152,26 @@ void
 create_target(file_entry_t *entry)
 {
 	Assert(entry->action == FILE_ACTION_CREATE);
+	Assert(!entry->target_exists);
 
-	switch (entry->type)
+	switch (entry->source_type)
 	{
 		case FILE_TYPE_DIRECTORY:
 			create_target_dir(entry->path);
 			break;
 
 		case FILE_TYPE_SYMLINK:
-			create_target_symlink(entry->path, entry->link_target);
+			create_target_symlink(entry->path, entry->source_link_target);
 			break;
 
 		case FILE_TYPE_REGULAR:
 			/* can't happen. Regular files are created with open_target_file. */
 			pg_fatal("invalid action (CREATE) for regular file");
 			break;
+
+		case FILE_TYPE_UNDEFINED:
+			pg_fatal("undefined file type for \"%s\"", entry->path);
+			break;
 	}
 }
 
diff --git a/src/bin/pg_rewind/filemap.c b/src/bin/pg_rewind/filemap.c
index 1879257b66a..431e8e760e0 100644
--- a/src/bin/pg_rewind/filemap.c
+++ b/src/bin/pg_rewind/filemap.c
@@ -26,6 +26,8 @@ static bool isRelDataFile(const char *path);
 static char *datasegpath(RelFileNode rnode, ForkNumber forknum,
 						 BlockNumber segno);
 static int	path_cmp(const void *a, const void *b);
+
+static file_entry_t *get_filemap_entry(const char *path, bool create);
 static int	final_filemap_cmp(const void *a, const void *b);
 static void filemap_list_to_array(filemap_t *map);
 static bool check_file_excluded(const char *path, bool is_source);
@@ -146,6 +148,65 @@ filemap_create(void)
 	filemap = map;
 }
 
+/* Look up or create entry for 'path' */
+static file_entry_t *
+get_filemap_entry(const char *path, bool create)
+{
+	filemap_t  *map = filemap;
+	file_entry_t *entry;
+	file_entry_t **e;
+	file_entry_t key;
+	file_entry_t *key_ptr;
+
+	if (map->array)
+	{
+		key.path = (char *) path;
+		key_ptr = &key;
+		e = bsearch(&key_ptr, map->array, map->narray, sizeof(file_entry_t *),
+					path_cmp);
+	}
+	else
+		e = NULL;
+
+	if (e)
+		entry = *e;
+	else if (!create)
+		entry = NULL;
+	else
+	{
+		/* Create a new entry for this file */
+		entry = pg_malloc(sizeof(file_entry_t));
+		entry->path = pg_strdup(path);
+		entry->isrelfile = isRelDataFile(path);
+		entry->action = FILE_ACTION_UNDECIDED;
+
+		entry->target_exists = false;
+		entry->target_type = FILE_TYPE_UNDEFINED;
+		entry->target_size = 0;
+		entry->target_link_target = NULL;
+		entry->target_modified_pages.bitmap = NULL;
+		entry->target_modified_pages.bitmapsize = 0;
+
+		entry->source_exists = false;
+		entry->source_type = FILE_TYPE_UNDEFINED;
+		entry->source_size = 0;
+		entry->source_link_target = NULL;
+
+		entry->next = NULL;
+
+		if (map->last)
+		{
+			map->last->next = entry;
+			map->last = entry;
+		}
+		else
+			map->first = map->last = entry;
+		map->nlist++;
+	}
+
+	return entry;
+}
+
 /*
  * Callback for processing source file list.
  *
@@ -154,18 +215,19 @@ filemap_create(void)
  * exists in the target and whether the size matches.
  */
 void
-process_source_file(const char *path, file_type_t type, size_t newsize,
+process_source_file(const char *path, file_type_t type, size_t size,
 					const char *link_target)
 {
-	bool		exists;
-	char		localpath[MAXPGPATH];
-	struct stat statbuf;
-	filemap_t  *map = filemap;
-	file_action_t action = FILE_ACTION_NONE;
-	size_t		oldsize = 0;
 	file_entry_t *entry;
 
-	Assert(map->array == NULL);
+	Assert(filemap->array == NULL);
+
+	/*
+	 * Skip the control file. It is handled specially, after copying all the
+	 * other files.
+	 */
+	if (strcmp(path, "global/pg_control") == 0)
+		return;
 
 	/*
 	 * Skip any files matching the exclusion filters. This has the effect to
@@ -199,142 +261,12 @@ process_source_file(const char *path, file_type_t type, size_t newsize,
 	if (type != FILE_TYPE_REGULAR && isRelDataFile(path))
 		pg_fatal("data file \"%s\" in source is not a regular file", path);
 
-	snprintf(localpath, sizeof(localpath), "%s/%s", datadir_target, path);
-
-	/* Does the corresponding file exist in the target data dir? */
-	if (lstat(localpath, &statbuf) < 0)
-	{
-		if (errno != ENOENT)
-			pg_fatal("could not stat file \"%s\": %m",
-					 localpath);
-
-		exists = false;
-	}
-	else
-		exists = true;
-
-	switch (type)
-	{
-		case FILE_TYPE_DIRECTORY:
-			if (exists && !S_ISDIR(statbuf.st_mode) && strcmp(path, "pg_wal") != 0)
-			{
-				/* it's a directory in source, but not in target. Strange.. */
-				pg_fatal("\"%s\" is not a directory", localpath);
-			}
-
-			if (!exists)
-				action = FILE_ACTION_CREATE;
-			else
-				action = FILE_ACTION_NONE;
-			oldsize = 0;
-			break;
-
-		case FILE_TYPE_SYMLINK:
-			if (exists &&
-#ifndef WIN32
-				!S_ISLNK(statbuf.st_mode)
-#else
-				!pgwin32_is_junction(localpath)
-#endif
-				)
-			{
-				/*
-				 * It's a symbolic link in source, but not in target.
-				 * Strange..
-				 */
-				pg_fatal("\"%s\" is not a symbolic link", localpath);
-			}
-
-			if (!exists)
-				action = FILE_ACTION_CREATE;
-			else
-				action = FILE_ACTION_NONE;
-			oldsize = 0;
-			break;
-
-		case FILE_TYPE_REGULAR:
-			if (exists && !S_ISREG(statbuf.st_mode))
-				pg_fatal("\"%s\" is not a regular file", localpath);
-
-			if (!exists || !isRelDataFile(path))
-			{
-				/*
-				 * File exists in source, but not in target. Or it's a
-				 * non-data file that we have no special processing for. Copy
-				 * it in toto.
-				 *
-				 * An exception: PG_VERSIONs should be identical, but avoid
-				 * overwriting it for paranoia.
-				 */
-				if (pg_str_endswith(path, "PG_VERSION"))
-				{
-					action = FILE_ACTION_NONE;
-					oldsize = statbuf.st_size;
-				}
-				else
-				{
-					action = FILE_ACTION_COPY;
-					oldsize = 0;
-				}
-			}
-			else
-			{
-				/*
-				 * It's a data file that exists in both.
-				 *
-				 * If it's larger in target, we can truncate it. There will
-				 * also be a WAL record of the truncation in the source
-				 * system, so WAL replay would eventually truncate the target
-				 * too, but we might as well do it now.
-				 *
-				 * If it's smaller in the target, it means that it has been
-				 * truncated in the target, or enlarged in the source, or
-				 * both. If it was truncated in the target, we need to copy
-				 * the missing tail from the source system. If it was enlarged
-				 * in the source system, there will be WAL records in the
-				 * source system for the new blocks, so we wouldn't need to
-				 * copy them here. But we don't know which scenario we're
-				 * dealing with, and there's no harm in copying the missing
-				 * blocks now, so do it now.
-				 *
-				 * If it's the same size, do nothing here. Any blocks modified
-				 * in the target will be copied based on parsing the target
-				 * system's WAL, and any blocks modified in the source will be
-				 * updated after rewinding, when the source system's WAL is
-				 * replayed.
-				 */
-				oldsize = statbuf.st_size;
-				if (oldsize < newsize)
-					action = FILE_ACTION_COPY_TAIL;
-				else if (oldsize > newsize)
-					action = FILE_ACTION_TRUNCATE;
-				else
-					action = FILE_ACTION_NONE;
-			}
-			break;
-	}
-
-	/* Create a new entry for this file */
-	entry = pg_malloc(sizeof(file_entry_t));
-	entry->path = pg_strdup(path);
-	entry->type = type;
-	entry->action = action;
-	entry->oldsize = oldsize;
-	entry->newsize = newsize;
-	entry->link_target = link_target ? pg_strdup(link_target) : NULL;
-	entry->next = NULL;
-	entry->pagemap.bitmap = NULL;
-	entry->pagemap.bitmapsize = 0;
-	entry->isrelfile = isRelDataFile(path);
-
-	if (map->last)
-	{
-		map->last->next = entry;
-		map->last = entry;
-	}
-	else
-		map->first = map->last = entry;
-	map->nlist++;
+	/* Remember this source file */
+	entry = get_filemap_entry(path, true);
+	entry->source_exists = true;
+	entry->source_type = type;
+	entry->source_size = size;
+	entry->source_link_target = link_target ? pg_strdup(link_target) : NULL;
 }
 
 /*
@@ -345,33 +277,24 @@ process_source_file(const char *path, file_type_t type, size_t newsize,
  * deletion.
  */
 void
-process_target_file(const char *path, file_type_t type, size_t oldsize,
+process_target_file(const char *path, file_type_t type, size_t size,
 					const char *link_target)
 {
-	bool		exists;
-	char		localpath[MAXPGPATH];
-	struct stat statbuf;
-	file_entry_t key;
-	file_entry_t *key_ptr;
 	filemap_t  *map = filemap;
 	file_entry_t *entry;
 
+	/*
+	 * Skip the control file. It is handled specially, after copying all the
+	 * other files.
+	 */
+	if (strcmp(path, "global/pg_control") == 0)
+		return;
+
 	/*
 	 * Do not apply any exclusion filters here.  This has advantage to remove
 	 * from the target data folder all paths which have been filtered out from
 	 * the source data folder when processing the source files.
 	 */
-
-	snprintf(localpath, sizeof(localpath), "%s/%s", datadir_target, path);
-	if (lstat(localpath, &statbuf) < 0)
-	{
-		if (errno != ENOENT)
-			pg_fatal("could not stat file \"%s\": %m",
-					 localpath);
-
-		exists = false;
-	}
-
 	if (map->array == NULL)
 	{
 		/* on first call, initialize lookup array */
@@ -389,120 +312,76 @@ process_target_file(const char *path, file_type_t type, size_t oldsize,
 	}
 
 	/*
-	 * Like in process_source_file, pretend that xlog is always a  directory.
+	 * Like in process_source_file, pretend that pg_wal is always a directory.
 	 */
 	if (strcmp(path, "pg_wal") == 0 && type == FILE_TYPE_SYMLINK)
 		type = FILE_TYPE_DIRECTORY;
 
-	key.path = (char *) path;
-	key_ptr = &key;
-	exists = (bsearch(&key_ptr, map->array, map->narray, sizeof(file_entry_t *),
-					  path_cmp) != NULL);
-
-	/* Remove any file or folder that doesn't exist in the source system. */
-	if (!exists)
-	{
-		entry = pg_malloc(sizeof(file_entry_t));
-		entry->path = pg_strdup(path);
-		entry->type = type;
-		entry->action = FILE_ACTION_REMOVE;
-		entry->oldsize = oldsize;
-		entry->newsize = 0;
-		entry->link_target = link_target ? pg_strdup(link_target) : NULL;
-		entry->next = NULL;
-		entry->pagemap.bitmap = NULL;
-		entry->pagemap.bitmapsize = 0;
-		entry->isrelfile = isRelDataFile(path);
-
-		if (map->last == NULL)
-			map->first = entry;
-		else
-			map->last->next = entry;
-		map->last = entry;
-		map->nlist++;
-	}
-	else
-	{
-		/*
-		 * We already handled all files that exist in the source system in
-		 * process_source_file().
-		 */
-	}
+	/* Remember this target file */
+	entry = get_filemap_entry(path, true);
+	entry->target_exists = true;
+	entry->target_type = type;
+	entry->target_size = size;
+	entry->target_link_target = link_target ? pg_strdup(link_target) : NULL;
 }
 
 /*
  * This callback gets called while we read the WAL in the target, for every
- * block that have changed in the target system. It makes note of all the
+ * block that have changed in the target system.  It makes note of all the
  * changed blocks in the pagemap of the file.
+ *
+ * NOTE: All the files on both systems must have already been added to the
+ * file map!
  */
 void
-process_block_change(ForkNumber forknum, RelFileNode rnode, BlockNumber blkno)
+process_target_wal_block_change(ForkNumber forknum, RelFileNode rnode,
+								BlockNumber blkno)
 {
 	char	   *path;
-	file_entry_t key;
-	file_entry_t *key_ptr;
 	file_entry_t *entry;
 	BlockNumber blkno_inseg;
 	int			segno;
-	filemap_t  *map = filemap;
-	file_entry_t **e;
 
-	Assert(map->array);
+	Assert(filemap->array);
 
 	segno = blkno / RELSEG_SIZE;
 	blkno_inseg = blkno % RELSEG_SIZE;
 
 	path = datasegpath(rnode, forknum, segno);
-
-	key.path = (char *) path;
-	key_ptr = &key;
-
-	e = bsearch(&key_ptr, map->array, map->narray, sizeof(file_entry_t *),
-				path_cmp);
-	if (e)
-		entry = *e;
-	else
-		entry = NULL;
+	entry = get_filemap_entry(path, false);
 	pfree(path);
 
 	if (entry)
 	{
+		int64		end_offset;
+
 		Assert(entry->isrelfile);
 
-		switch (entry->action)
-		{
-			case FILE_ACTION_NONE:
-			case FILE_ACTION_TRUNCATE:
-				/* skip if we're truncating away the modified block anyway */
-				if ((blkno_inseg + 1) * BLCKSZ <= entry->newsize)
-					datapagemap_add(&entry->pagemap, blkno_inseg);
-				break;
-
-			case FILE_ACTION_COPY_TAIL:
-
-				/*
-				 * skip the modified block if it is part of the "tail" that
-				 * we're copying anyway.
-				 */
-				if ((blkno_inseg + 1) * BLCKSZ <= entry->oldsize)
-					datapagemap_add(&entry->pagemap, blkno_inseg);
-				break;
-
-			case FILE_ACTION_COPY:
-			case FILE_ACTION_REMOVE:
-				break;
-
-			case FILE_ACTION_CREATE:
-				pg_fatal("unexpected page modification for directory or symbolic link \"%s\"", entry->path);
-		}
+		if (entry->target_type != FILE_TYPE_REGULAR)
+			pg_fatal("unexpected page modification for directory or symbolic link \"%s\"",
+					 entry->path);
+
+		/*
+		 * If the block beyond the EOF in the source system, no need to
+		 * remember it now, because we're going to truncate it away from the
+		 * target anyway. Also no need to remember the block if it's beyond
+		 * the current EOF in the target system; we will copy it over with the
+		 * "tail" from the source system, anyway.
+		 */
+		end_offset = (blkno_inseg + 1) * BLCKSZ;
+		if (end_offset <= entry->source_size &&
+			end_offset <= entry->target_size)
+			datapagemap_add(&entry->target_modified_pages, blkno_inseg);
 	}
 	else
 	{
 		/*
 		 * If we don't have any record of this file in the file map, it means
-		 * that it's a relation that doesn't exist in the source system, and
-		 * it was subsequently removed in the target system, too. We can
-		 * safely ignore it.
+		 * that it's a relation that doesn't exist in the source system.  It
+		 * could exist in the target system; we haven't moved the target-only
+		 * entries from the linked list to the array yet!  But in any case, if
+		 * it doesn't exist in the source it will be removed from the target
+		 * too, and we can safely ignore it.
 		 */
 	}
 }
@@ -593,16 +472,6 @@ filemap_list_to_array(filemap_t *map)
 	map->first = map->last = NULL;
 }
 
-void
-filemap_finalize(void)
-{
-	filemap_t  *map = filemap;
-
-	filemap_list_to_array(map);
-	qsort(map->array, map->narray, sizeof(file_entry_t *),
-		  final_filemap_cmp);
-}
-
 static const char *
 action_to_str(file_action_t action)
 {
@@ -643,26 +512,26 @@ calculate_totals(void)
 	{
 		entry = map->array[i];
 
-		if (entry->type != FILE_TYPE_REGULAR)
+		if (entry->source_type != FILE_TYPE_REGULAR)
 			continue;
 
-		map->total_size += entry->newsize;
+		map->total_size += entry->source_size;
 
 		if (entry->action == FILE_ACTION_COPY)
 		{
-			map->fetch_size += entry->newsize;
+			map->fetch_size += entry->source_size;
 			continue;
 		}
 
 		if (entry->action == FILE_ACTION_COPY_TAIL)
-			map->fetch_size += (entry->newsize - entry->oldsize);
+			map->fetch_size += (entry->source_size - entry->target_size);
 
-		if (entry->pagemap.bitmapsize > 0)
+		if (entry->target_modified_pages.bitmapsize > 0)
 		{
 			datapagemap_iterator_t *iter;
 			BlockNumber blk;
 
-			iter = datapagemap_iterate(&entry->pagemap);
+			iter = datapagemap_iterate(&entry->target_modified_pages);
 			while (datapagemap_next(iter, &blk))
 				map->fetch_size += BLCKSZ;
 
@@ -682,13 +551,13 @@ print_filemap(void)
 	{
 		entry = map->array[i];
 		if (entry->action != FILE_ACTION_NONE ||
-			entry->pagemap.bitmapsize > 0)
+			entry->target_modified_pages.bitmapsize > 0)
 		{
 			pg_log_debug("%s (%s)", entry->path,
 						 action_to_str(entry->action));
 
-			if (entry->pagemap.bitmapsize > 0)
-				datapagemap_print(&entry->pagemap);
+			if (entry->target_modified_pages.bitmapsize > 0)
+				datapagemap_print(&entry->target_modified_pages);
 		}
 	}
 	fflush(stdout);
@@ -837,3 +706,126 @@ final_filemap_cmp(const void *a, const void *b)
 	else
 		return strcmp(fa->path, fb->path);
 }
+
+/*
+ * Decide what to do with each file.
+ */
+void
+filemap_finalize()
+{
+	int			i;
+
+	filemap_list_to_array(filemap);
+
+	for (i = 0; i < filemap->narray; i++)
+	{
+		file_entry_t *entry = filemap->array[i];
+		file_action_t action;
+
+		if (!entry->target_exists && entry->source_exists)
+		{
+			/*
+			 * File exists in source, but not in target. Copy
+			 * it in toto. (If it's a relation data file, WAL replay
+			 * after rewinding should re-create it anyway. But there's
+			 * no harm in copying it now.)
+			 */
+			if (entry->source_type == FILE_TYPE_DIRECTORY ||
+				entry->source_type == FILE_TYPE_SYMLINK)
+			{
+				action = FILE_ACTION_CREATE;
+			}
+			else
+				action = FILE_ACTION_COPY;
+		}
+		else if (entry->target_exists && !entry->source_exists)
+		{
+			/* Delete file from target */
+			action = FILE_ACTION_REMOVE;
+		}
+		else if (entry->target_exists && entry->source_exists)
+		{
+			/* File exists in both systems */
+			if (entry->source_type != entry->target_type)
+			{
+				/* But it's a different kind of object. Strange.. */
+				pg_fatal("file \"%s\" is of different type in source and target", entry->path);
+			}
+
+			switch (entry->source_type)
+			{
+				case FILE_TYPE_DIRECTORY:
+					action = FILE_ACTION_NONE;
+					break;
+
+				case FILE_TYPE_SYMLINK:
+					// FIXME: Check if it points to the same target?
+					action = FILE_ACTION_NONE;
+					break;
+
+				case FILE_TYPE_REGULAR:
+					if (!entry->isrelfile)
+					{
+						/*
+						 * It's a non-data file that we have no special processing for. Copy
+						 * it in toto.
+						 *
+						 * An exception: PG_VERSIONs should be identical, but avoid
+						 * overwriting it for paranoia.
+						 */
+						if (!pg_str_endswith(entry->path, "PG_VERSION"))
+							action = FILE_ACTION_COPY;
+						else
+							action = FILE_ACTION_NONE;
+					}
+					else
+					{
+						/*
+						 * It's a data file that exists in both systems.
+						 *
+						 * If it's larger in target, we can truncate it. There will
+						 * also be a WAL record of the truncation in the source
+						 * system, so WAL replay would eventually truncate the target
+						 * too, but we might as well do it now.
+						 *
+						 * If it's smaller in the target, it means that it has been
+						 * truncated in the target, or enlarged in the source, or
+						 * both. If it was truncated in the target, we need to copy
+						 * the missing tail from the source system. If it was enlarged
+						 * in the source system, there will be WAL records in the
+						 * source system for the new blocks, so we wouldn't need to
+						 * copy them here. But we don't know which scenario we're
+						 * dealing with, and there's no harm in copying the missing
+						 * blocks now, so do it now.
+						 *
+						 * If it's the same size, do nothing here. Any blocks modified
+						 * in the target will be copied based on parsing the target
+						 * system's WAL, and any blocks modified in the source will be
+						 * updated after rewinding, when the source system's WAL is
+						 * replayed.
+						 */
+						if (entry->target_size < entry->source_size)
+							action = FILE_ACTION_COPY_TAIL;
+						else if (entry->target_size > entry->source_size)
+							action = FILE_ACTION_TRUNCATE;
+						else
+							action = FILE_ACTION_NONE;
+					}
+					break;
+				case FILE_TYPE_UNDEFINED:
+					pg_fatal("unknown file type for \"%s\"", entry->path);
+					break;
+			}
+		}
+		else
+		{
+			/* Doesn't exist in either server. Why does it have an entry at all?? */
+			action = FILE_ACTION_NONE;
+		}
+		entry->action = action;
+	}
+
+	/* Sort the actions to the order that they should be performed */
+	qsort(filemap->array, filemap->narray, sizeof(file_entry_t *),
+		  final_filemap_cmp);
+}
diff --git a/src/bin/pg_rewind/filemap.h b/src/bin/pg_rewind/filemap.h
index 0cb7425170c..a5e8df57f40 100644
--- a/src/bin/pg_rewind/filemap.h
+++ b/src/bin/pg_rewind/filemap.h
@@ -14,17 +14,21 @@
 
 /*
  * For every file found in the local or remote system, we have a file entry
- * which says what we are going to do with the file. For relation files,
- * there is also a page map, marking pages in the file that were changed
- * locally.
- *
- * The enum values are sorted in the order we want actions to be processed.
+ * that contains information about the file on both systems.  For relation
+ * files, there is also a page map that marks pages in the file that were
+ * changed in the target after the last common checkpoint.  Each entry also
+ * contains an 'action' field, which says what we are going to do with the
+ * file.
  */
+
+/* these enum values are sorted in the order we want actions to be processed */
 typedef enum
 {
+	FILE_ACTION_UNDECIDED = 0,	/* not decided yet */
+
 	FILE_ACTION_CREATE,			/* create local directory or symbolic link */
 	FILE_ACTION_COPY,			/* copy whole file, overwriting if exists */
-	FILE_ACTION_COPY_TAIL,		/* copy tail from 'oldsize' to 'newsize' */
+	FILE_ACTION_COPY_TAIL,		/* copy tail from 'source_size' to 'target_size' */
 	FILE_ACTION_NONE,			/* no action (we might still copy modified
 								 * blocks based on the parsed WAL) */
 	FILE_ACTION_TRUNCATE,		/* truncate local file to 'newsize' bytes */
@@ -33,6 +37,8 @@ typedef enum
 
 typedef enum
 {
+	FILE_TYPE_UNDEFINED = 0,
+
 	FILE_TYPE_REGULAR,
 	FILE_TYPE_DIRECTORY,
 	FILE_TYPE_SYMLINK
@@ -41,19 +47,30 @@ typedef enum
 typedef struct file_entry_t
 {
 	char	   *path;
-	file_type_t type;
+	bool		isrelfile;		/* is it a relation data file? */
 
-	file_action_t action;
+	/*
+	 * Status of the file in the target.
+	 */
+	bool		target_exists;
+	file_type_t target_type;
+	size_t		target_size; /* for a regular file */
+	char	   *target_link_target; /* for a symlink */
 
-	/* for a regular file */
-	size_t		oldsize;
-	size_t		newsize;
-	bool		isrelfile;		/* is it a relation data file? */
+	datapagemap_t target_modified_pages;
 
-	datapagemap_t pagemap;
+	/*
+	 * Status of the file in the source.
+	 */
+	bool		source_exists;
+	file_type_t source_type;
+	size_t		source_size;
+	char	   *source_link_target; /* for a symlink */
 
-	/* for a symlink */
-	char	   *link_target;
+	/*
+	 * What will we do to the file?
+	 */
+	file_action_t action;
 
 	struct file_entry_t *next;
 } file_entry_t;
@@ -70,20 +87,19 @@ typedef struct filemap_t
 
 	/*
 	 * After processing all the remote files, the entries in the linked list
-	 * are moved to this array. After processing local files, too, all the
+	 * are moved to this array.  After processing local files, too, all the
 	 * local entries are added to the array by filemap_finalize, and sorted in
-	 * the final order. After filemap_finalize, all the entries are in the
+	 * the final order.  After filemap_finalize, all the entries are in the
 	 * array, and the linked list is empty.
 	 */
 	file_entry_t **array;
 	int			narray;			/* current length of array */
 
 	/*
-	 * Summary information. total_size is the total size of the source
-	 * cluster, and fetch_size is the number of bytes that needs to be copied.
+	 * Summary information.
 	 */
-	uint64		total_size;
-	uint64		fetch_size;
+	uint64		total_size;		/* total size of the source cluster */
+	uint64		fetch_size;		/* number of bytes that needs to be copied */
 } filemap_t;
 
 extern filemap_t *filemap;
@@ -94,11 +110,12 @@ extern void print_filemap(void);
 
 /* Functions for populating the filemap */
 extern void process_source_file(const char *path, file_type_t type,
-								size_t newsize, const char *link_target);
+								size_t size, const char *link_target);
 extern void process_target_file(const char *path, file_type_t type,
-								size_t newsize, const char *link_target);
-extern void process_block_change(ForkNumber forknum, RelFileNode rnode,
-								 BlockNumber blkno);
+								size_t size, const char *link_target);
+extern void process_target_wal_block_change(ForkNumber forknum,
+											RelFileNode rnode,
+											BlockNumber blkno);
 extern void filemap_finalize(void);
 
 #endif							/* FILEMAP_H */
diff --git a/src/bin/pg_rewind/libpq_fetch.c b/src/bin/pg_rewind/libpq_fetch.c
index bf4dfc23b96..7fc9161b8c8 100644
--- a/src/bin/pg_rewind/libpq_fetch.c
+++ b/src/bin/pg_rewind/libpq_fetch.c
@@ -465,7 +465,7 @@ libpq_executeFileMap(filemap_t *map)
 		entry = map->array[i];
 
 		/* If this is a relation file, copy the modified blocks */
-		execute_pagemap(&entry->pagemap, entry->path);
+		execute_pagemap(&entry->target_modified_pages, entry->path);
 
 		switch (entry->action)
 		{
@@ -476,15 +476,15 @@ libpq_executeFileMap(filemap_t *map)
 			case FILE_ACTION_COPY:
 				/* Truncate the old file out of the way, if any */
 				open_target_file(entry->path, true);
-				fetch_file_range(entry->path, 0, entry->newsize);
+				fetch_file_range(entry->path, 0, entry->source_size);
 				break;
 
 			case FILE_ACTION_TRUNCATE:
-				truncate_target_file(entry->path, entry->newsize);
+				truncate_target_file(entry->path, entry->source_size);
 				break;
 
 			case FILE_ACTION_COPY_TAIL:
-				fetch_file_range(entry->path, entry->oldsize, entry->newsize);
+				fetch_file_range(entry->path, entry->target_size, entry->source_size);
 				break;
 
 			case FILE_ACTION_REMOVE:
@@ -494,6 +494,10 @@ libpq_executeFileMap(filemap_t *map)
 			case FILE_ACTION_CREATE:
 				create_target(entry);
 				break;
+
+			case FILE_ACTION_UNDECIDED:
+				pg_fatal("no action decided for \"%s\"", entry->path);
+				break;
 		}
 	}
 
diff --git a/src/bin/pg_rewind/parsexlog.c b/src/bin/pg_rewind/parsexlog.c
index 2229c86f9af..2baeb74ae93 100644
--- a/src/bin/pg_rewind/parsexlog.c
+++ b/src/bin/pg_rewind/parsexlog.c
@@ -436,6 +436,6 @@ extractPageInfo(XLogReaderState *record)
 		if (forknum != MAIN_FORKNUM)
 			continue;
 
-		process_block_change(forknum, rnode, blkno);
+		process_target_wal_block_change(forknum, rnode, blkno);
 	}
 }
diff --git a/src/bin/pg_rewind/pg_rewind.c b/src/bin/pg_rewind/pg_rewind.c
index c9b9e480c0f..210984d302b 100644
--- a/src/bin/pg_rewind/pg_rewind.c
+++ b/src/bin/pg_rewind/pg_rewind.c
@@ -369,7 +369,7 @@ main(int argc, char **argv)
 				chkpttli);
 
 	/*
-	 * Build the filemap, by comparing the source and target data directories.
+	 * Collect information about all files in the target and source systems.
 	 */
 	filemap_create();
 	if (showprogress)
@@ -390,8 +390,12 @@ main(int argc, char **argv)
 		pg_log_info("reading WAL in target");
 	extractPageMap(datadir_target, chkptrec, lastcommontliIndex,
 				   ControlFile_target.checkPoint, restore_command);
-	filemap_finalize();
 
+	/*
+	 * We have collected all information we need from both systems. Decide
+	 * what to do with each file.
+	 */
+	filemap_finalize();
 	if (showprogress)
 		calculate_totals();
 
-- 
2.20.1


--------------D93EDEBFB124D563B723F4BD
Content-Type: text/x-patch; charset=UTF-8;
 name="0003-pg_rewind-Replace-the-hybrid-list-array-data-structu.patch"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
 filename*0="0003-pg_rewind-Replace-the-hybrid-list-array-data-structu.pa";
 filename*1="tch"



^ permalink  raw  reply  [nested|flat] 41+ messages in thread

* [PATCH 2/5] Refactor pg_rewind for more clear decision making.
@ 2020-08-19 12:34 Heikki Linnakangas <[email protected]>
  0 siblings, 0 replies; 41+ messages in thread

From: Heikki Linnakangas @ 2020-08-19 12:34 UTC (permalink / raw)

Deciding what to do with each file is now a separate step after all the
necessary information has been gathered. It is more clear that way.
Previously, the decision-making was divided between process_source_file()
and process_target_file(), and it was a bit hard to piece together what the
overall rules were.
---
 src/bin/pg_rewind/copy_fetch.c  |  14 +-
 src/bin/pg_rewind/file_ops.c    |  16 +-
 src/bin/pg_rewind/filemap.c     | 514 ++++++++++++++++----------------
 src/bin/pg_rewind/filemap.h     |  67 +++--
 src/bin/pg_rewind/libpq_fetch.c |  12 +-
 src/bin/pg_rewind/parsexlog.c   |   2 +-
 src/bin/pg_rewind/pg_rewind.c   |   8 +-
 7 files changed, 332 insertions(+), 301 deletions(-)

diff --git a/src/bin/pg_rewind/copy_fetch.c b/src/bin/pg_rewind/copy_fetch.c
index 1edab5f1867..18fad32600e 100644
--- a/src/bin/pg_rewind/copy_fetch.c
+++ b/src/bin/pg_rewind/copy_fetch.c
@@ -210,7 +210,7 @@ copy_executeFileMap(filemap_t *map)
 	for (i = 0; i < map->narray; i++)
 	{
 		entry = map->array[i];
-		execute_pagemap(&entry->pagemap, entry->path);
+		execute_pagemap(&entry->target_modified_pages, entry->path);
 
 		switch (entry->action)
 		{
@@ -219,16 +219,16 @@ copy_executeFileMap(filemap_t *map)
 				break;
 
 			case FILE_ACTION_COPY:
-				rewind_copy_file_range(entry->path, 0, entry->newsize, true);
+				rewind_copy_file_range(entry->path, 0, entry->source_size, true);
 				break;
 
 			case FILE_ACTION_TRUNCATE:
-				truncate_target_file(entry->path, entry->newsize);
+				truncate_target_file(entry->path, entry->source_size);
 				break;
 
 			case FILE_ACTION_COPY_TAIL:
-				rewind_copy_file_range(entry->path, entry->oldsize,
-									   entry->newsize, false);
+				rewind_copy_file_range(entry->path, entry->target_size,
+									   entry->source_size, false);
 				break;
 
 			case FILE_ACTION_CREATE:
@@ -238,6 +238,10 @@ copy_executeFileMap(filemap_t *map)
 			case FILE_ACTION_REMOVE:
 				remove_target(entry);
 				break;
+
+			case FILE_ACTION_UNDECIDED:
+				pg_fatal("no action decided for \"%s\"", entry->path);
+				break;
 		}
 	}
 
diff --git a/src/bin/pg_rewind/file_ops.c b/src/bin/pg_rewind/file_ops.c
index 55439db20ba..ec37d0b2e0d 100644
--- a/src/bin/pg_rewind/file_ops.c
+++ b/src/bin/pg_rewind/file_ops.c
@@ -126,8 +126,9 @@ void
 remove_target(file_entry_t *entry)
 {
 	Assert(entry->action == FILE_ACTION_REMOVE);
+	Assert(entry->target_exists);
 
-	switch (entry->type)
+	switch (entry->target_type)
 	{
 		case FILE_TYPE_DIRECTORY:
 			remove_target_dir(entry->path);
@@ -140,6 +141,10 @@ remove_target(file_entry_t *entry)
 		case FILE_TYPE_SYMLINK:
 			remove_target_symlink(entry->path);
 			break;
+
+		case FILE_TYPE_UNDEFINED:
+			pg_fatal("undefined file type for \"%s\"", entry->path);
+			break;
 	}
 }
 
@@ -147,21 +152,26 @@ void
 create_target(file_entry_t *entry)
 {
 	Assert(entry->action == FILE_ACTION_CREATE);
+	Assert(!entry->target_exists);
 
-	switch (entry->type)
+	switch (entry->source_type)
 	{
 		case FILE_TYPE_DIRECTORY:
 			create_target_dir(entry->path);
 			break;
 
 		case FILE_TYPE_SYMLINK:
-			create_target_symlink(entry->path, entry->link_target);
+			create_target_symlink(entry->path, entry->source_link_target);
 			break;
 
 		case FILE_TYPE_REGULAR:
 			/* can't happen. Regular files are created with open_target_file. */
 			pg_fatal("invalid action (CREATE) for regular file");
 			break;
+
+		case FILE_TYPE_UNDEFINED:
+			pg_fatal("undefined file type for \"%s\"", entry->path);
+			break;
 	}
 }
 
diff --git a/src/bin/pg_rewind/filemap.c b/src/bin/pg_rewind/filemap.c
index 1879257b66a..431e8e760e0 100644
--- a/src/bin/pg_rewind/filemap.c
+++ b/src/bin/pg_rewind/filemap.c
@@ -26,6 +26,8 @@ static bool isRelDataFile(const char *path);
 static char *datasegpath(RelFileNode rnode, ForkNumber forknum,
 						 BlockNumber segno);
 static int	path_cmp(const void *a, const void *b);
+
+static file_entry_t *get_filemap_entry(const char *path, bool create);
 static int	final_filemap_cmp(const void *a, const void *b);
 static void filemap_list_to_array(filemap_t *map);
 static bool check_file_excluded(const char *path, bool is_source);
@@ -146,6 +148,65 @@ filemap_create(void)
 	filemap = map;
 }
 
+/* Look up or create entry for 'path' */
+static file_entry_t *
+get_filemap_entry(const char *path, bool create)
+{
+	filemap_t  *map = filemap;
+	file_entry_t *entry;
+	file_entry_t **e;
+	file_entry_t key;
+	file_entry_t *key_ptr;
+
+	if (map->array)
+	{
+		key.path = (char *) path;
+		key_ptr = &key;
+		e = bsearch(&key_ptr, map->array, map->narray, sizeof(file_entry_t *),
+					path_cmp);
+	}
+	else
+		e = NULL;
+
+	if (e)
+		entry = *e;
+	else if (!create)
+		entry = NULL;
+	else
+	{
+		/* Create a new entry for this file */
+		entry = pg_malloc(sizeof(file_entry_t));
+		entry->path = pg_strdup(path);
+		entry->isrelfile = isRelDataFile(path);
+		entry->action = FILE_ACTION_UNDECIDED;
+
+		entry->target_exists = false;
+		entry->target_type = FILE_TYPE_UNDEFINED;
+		entry->target_size = 0;
+		entry->target_link_target = NULL;
+		entry->target_modified_pages.bitmap = NULL;
+		entry->target_modified_pages.bitmapsize = 0;
+
+		entry->source_exists = false;
+		entry->source_type = FILE_TYPE_UNDEFINED;
+		entry->source_size = 0;
+		entry->source_link_target = NULL;
+
+		entry->next = NULL;
+
+		if (map->last)
+		{
+			map->last->next = entry;
+			map->last = entry;
+		}
+		else
+			map->first = map->last = entry;
+		map->nlist++;
+	}
+
+	return entry;
+}
+
 /*
  * Callback for processing source file list.
  *
@@ -154,18 +215,19 @@ filemap_create(void)
  * exists in the target and whether the size matches.
  */
 void
-process_source_file(const char *path, file_type_t type, size_t newsize,
+process_source_file(const char *path, file_type_t type, size_t size,
 					const char *link_target)
 {
-	bool		exists;
-	char		localpath[MAXPGPATH];
-	struct stat statbuf;
-	filemap_t  *map = filemap;
-	file_action_t action = FILE_ACTION_NONE;
-	size_t		oldsize = 0;
 	file_entry_t *entry;
 
-	Assert(map->array == NULL);
+	Assert(filemap->array == NULL);
+
+	/*
+	 * Skip the control file. It is handled specially, after copying all the
+	 * other files.
+	 */
+	if (strcmp(path, "global/pg_control") == 0)
+		return;
 
 	/*
 	 * Skip any files matching the exclusion filters. This has the effect to
@@ -199,142 +261,12 @@ process_source_file(const char *path, file_type_t type, size_t newsize,
 	if (type != FILE_TYPE_REGULAR && isRelDataFile(path))
 		pg_fatal("data file \"%s\" in source is not a regular file", path);
 
-	snprintf(localpath, sizeof(localpath), "%s/%s", datadir_target, path);
-
-	/* Does the corresponding file exist in the target data dir? */
-	if (lstat(localpath, &statbuf) < 0)
-	{
-		if (errno != ENOENT)
-			pg_fatal("could not stat file \"%s\": %m",
-					 localpath);
-
-		exists = false;
-	}
-	else
-		exists = true;
-
-	switch (type)
-	{
-		case FILE_TYPE_DIRECTORY:
-			if (exists && !S_ISDIR(statbuf.st_mode) && strcmp(path, "pg_wal") != 0)
-			{
-				/* it's a directory in source, but not in target. Strange.. */
-				pg_fatal("\"%s\" is not a directory", localpath);
-			}
-
-			if (!exists)
-				action = FILE_ACTION_CREATE;
-			else
-				action = FILE_ACTION_NONE;
-			oldsize = 0;
-			break;
-
-		case FILE_TYPE_SYMLINK:
-			if (exists &&
-#ifndef WIN32
-				!S_ISLNK(statbuf.st_mode)
-#else
-				!pgwin32_is_junction(localpath)
-#endif
-				)
-			{
-				/*
-				 * It's a symbolic link in source, but not in target.
-				 * Strange..
-				 */
-				pg_fatal("\"%s\" is not a symbolic link", localpath);
-			}
-
-			if (!exists)
-				action = FILE_ACTION_CREATE;
-			else
-				action = FILE_ACTION_NONE;
-			oldsize = 0;
-			break;
-
-		case FILE_TYPE_REGULAR:
-			if (exists && !S_ISREG(statbuf.st_mode))
-				pg_fatal("\"%s\" is not a regular file", localpath);
-
-			if (!exists || !isRelDataFile(path))
-			{
-				/*
-				 * File exists in source, but not in target. Or it's a
-				 * non-data file that we have no special processing for. Copy
-				 * it in toto.
-				 *
-				 * An exception: PG_VERSIONs should be identical, but avoid
-				 * overwriting it for paranoia.
-				 */
-				if (pg_str_endswith(path, "PG_VERSION"))
-				{
-					action = FILE_ACTION_NONE;
-					oldsize = statbuf.st_size;
-				}
-				else
-				{
-					action = FILE_ACTION_COPY;
-					oldsize = 0;
-				}
-			}
-			else
-			{
-				/*
-				 * It's a data file that exists in both.
-				 *
-				 * If it's larger in target, we can truncate it. There will
-				 * also be a WAL record of the truncation in the source
-				 * system, so WAL replay would eventually truncate the target
-				 * too, but we might as well do it now.
-				 *
-				 * If it's smaller in the target, it means that it has been
-				 * truncated in the target, or enlarged in the source, or
-				 * both. If it was truncated in the target, we need to copy
-				 * the missing tail from the source system. If it was enlarged
-				 * in the source system, there will be WAL records in the
-				 * source system for the new blocks, so we wouldn't need to
-				 * copy them here. But we don't know which scenario we're
-				 * dealing with, and there's no harm in copying the missing
-				 * blocks now, so do it now.
-				 *
-				 * If it's the same size, do nothing here. Any blocks modified
-				 * in the target will be copied based on parsing the target
-				 * system's WAL, and any blocks modified in the source will be
-				 * updated after rewinding, when the source system's WAL is
-				 * replayed.
-				 */
-				oldsize = statbuf.st_size;
-				if (oldsize < newsize)
-					action = FILE_ACTION_COPY_TAIL;
-				else if (oldsize > newsize)
-					action = FILE_ACTION_TRUNCATE;
-				else
-					action = FILE_ACTION_NONE;
-			}
-			break;
-	}
-
-	/* Create a new entry for this file */
-	entry = pg_malloc(sizeof(file_entry_t));
-	entry->path = pg_strdup(path);
-	entry->type = type;
-	entry->action = action;
-	entry->oldsize = oldsize;
-	entry->newsize = newsize;
-	entry->link_target = link_target ? pg_strdup(link_target) : NULL;
-	entry->next = NULL;
-	entry->pagemap.bitmap = NULL;
-	entry->pagemap.bitmapsize = 0;
-	entry->isrelfile = isRelDataFile(path);
-
-	if (map->last)
-	{
-		map->last->next = entry;
-		map->last = entry;
-	}
-	else
-		map->first = map->last = entry;
-	map->nlist++;
+	/* Remember this source file */
+	entry = get_filemap_entry(path, true);
+	entry->source_exists = true;
+	entry->source_type = type;
+	entry->source_size = size;
+	entry->source_link_target = link_target ? pg_strdup(link_target) : NULL;
 }
 
 /*
@@ -345,33 +277,24 @@ process_source_file(const char *path, file_type_t type, size_t newsize,
  * deletion.
  */
 void
-process_target_file(const char *path, file_type_t type, size_t oldsize,
+process_target_file(const char *path, file_type_t type, size_t size,
 					const char *link_target)
 {
-	bool		exists;
-	char		localpath[MAXPGPATH];
-	struct stat statbuf;
-	file_entry_t key;
-	file_entry_t *key_ptr;
 	filemap_t  *map = filemap;
 	file_entry_t *entry;
 
+	/*
+	 * Skip the control file. It is handled specially, after copying all the
+	 * other files.
+	 */
+	if (strcmp(path, "global/pg_control") == 0)
+		return;
+
 	/*
 	 * Do not apply any exclusion filters here.  This has advantage to remove
 	 * from the target data folder all paths which have been filtered out from
 	 * the source data folder when processing the source files.
 	 */
-
-	snprintf(localpath, sizeof(localpath), "%s/%s", datadir_target, path);
-	if (lstat(localpath, &statbuf) < 0)
-	{
-		if (errno != ENOENT)
-			pg_fatal("could not stat file \"%s\": %m",
-					 localpath);
-
-		exists = false;
-	}
-
 	if (map->array == NULL)
 	{
 		/* on first call, initialize lookup array */
@@ -389,120 +312,76 @@ process_target_file(const char *path, file_type_t type, size_t oldsize,
 	}
 
 	/*
-	 * Like in process_source_file, pretend that xlog is always a  directory.
+	 * Like in process_source_file, pretend that pg_wal is always a directory.
 	 */
 	if (strcmp(path, "pg_wal") == 0 && type == FILE_TYPE_SYMLINK)
 		type = FILE_TYPE_DIRECTORY;
 
-	key.path = (char *) path;
-	key_ptr = &key;
-	exists = (bsearch(&key_ptr, map->array, map->narray, sizeof(file_entry_t *),
-					  path_cmp) != NULL);
-
-	/* Remove any file or folder that doesn't exist in the source system. */
-	if (!exists)
-	{
-		entry = pg_malloc(sizeof(file_entry_t));
-		entry->path = pg_strdup(path);
-		entry->type = type;
-		entry->action = FILE_ACTION_REMOVE;
-		entry->oldsize = oldsize;
-		entry->newsize = 0;
-		entry->link_target = link_target ? pg_strdup(link_target) : NULL;
-		entry->next = NULL;
-		entry->pagemap.bitmap = NULL;
-		entry->pagemap.bitmapsize = 0;
-		entry->isrelfile = isRelDataFile(path);
-
-		if (map->last == NULL)
-			map->first = entry;
-		else
-			map->last->next = entry;
-		map->last = entry;
-		map->nlist++;
-	}
-	else
-	{
-		/*
-		 * We already handled all files that exist in the source system in
-		 * process_source_file().
-		 */
-	}
+	/* Remember this target file */
+	entry = get_filemap_entry(path, true);
+	entry->target_exists = true;
+	entry->target_type = type;
+	entry->target_size = size;
+	entry->target_link_target = link_target ? pg_strdup(link_target) : NULL;
 }
 
 /*
  * This callback gets called while we read the WAL in the target, for every
- * block that have changed in the target system. It makes note of all the
+ * block that have changed in the target system.  It makes note of all the
  * changed blocks in the pagemap of the file.
+ *
+ * NOTE: All the files on both systems must have already been added to the
+ * file map!
  */
 void
-process_block_change(ForkNumber forknum, RelFileNode rnode, BlockNumber blkno)
+process_target_wal_block_change(ForkNumber forknum, RelFileNode rnode,
+								BlockNumber blkno)
 {
 	char	   *path;
-	file_entry_t key;
-	file_entry_t *key_ptr;
 	file_entry_t *entry;
 	BlockNumber blkno_inseg;
 	int			segno;
-	filemap_t  *map = filemap;
-	file_entry_t **e;
 
-	Assert(map->array);
+	Assert(filemap->array);
 
 	segno = blkno / RELSEG_SIZE;
 	blkno_inseg = blkno % RELSEG_SIZE;
 
 	path = datasegpath(rnode, forknum, segno);
-
-	key.path = (char *) path;
-	key_ptr = &key;
-
-	e = bsearch(&key_ptr, map->array, map->narray, sizeof(file_entry_t *),
-				path_cmp);
-	if (e)
-		entry = *e;
-	else
-		entry = NULL;
+	entry = get_filemap_entry(path, false);
 	pfree(path);
 
 	if (entry)
 	{
+		int64		end_offset;
+
 		Assert(entry->isrelfile);
 
-		switch (entry->action)
-		{
-			case FILE_ACTION_NONE:
-			case FILE_ACTION_TRUNCATE:
-				/* skip if we're truncating away the modified block anyway */
-				if ((blkno_inseg + 1) * BLCKSZ <= entry->newsize)
-					datapagemap_add(&entry->pagemap, blkno_inseg);
-				break;
-
-			case FILE_ACTION_COPY_TAIL:
-
-				/*
-				 * skip the modified block if it is part of the "tail" that
-				 * we're copying anyway.
-				 */
-				if ((blkno_inseg + 1) * BLCKSZ <= entry->oldsize)
-					datapagemap_add(&entry->pagemap, blkno_inseg);
-				break;
-
-			case FILE_ACTION_COPY:
-			case FILE_ACTION_REMOVE:
-				break;
-
-			case FILE_ACTION_CREATE:
-				pg_fatal("unexpected page modification for directory or symbolic link \"%s\"", entry->path);
-		}
+		if (entry->target_type != FILE_TYPE_REGULAR)
+			pg_fatal("unexpected page modification for directory or symbolic link \"%s\"",
+					 entry->path);
+
+		/*
+		 * If the block beyond the EOF in the source system, no need to
+		 * remember it now, because we're going to truncate it away from the
+		 * target anyway. Also no need to remember the block if it's beyond
+		 * the current EOF in the target system; we will copy it over with the
+		 * "tail" from the source system, anyway.
+		 */
+		end_offset = (blkno_inseg + 1) * BLCKSZ;
+		if (end_offset <= entry->source_size &&
+			end_offset <= entry->target_size)
+			datapagemap_add(&entry->target_modified_pages, blkno_inseg);
 	}
 	else
 	{
 		/*
 		 * If we don't have any record of this file in the file map, it means
-		 * that it's a relation that doesn't exist in the source system, and
-		 * it was subsequently removed in the target system, too. We can
-		 * safely ignore it.
+		 * that it's a relation that doesn't exist in the source system.  It
+		 * could exist in the target system; we haven't moved the target-only
+		 * entries from the linked list to the array yet!  But in any case, if
+		 * it doesn't exist in the source it will be removed from the target
+		 * too, and we can safely ignore it.
 		 */
 	}
 }
@@ -593,16 +472,6 @@ filemap_list_to_array(filemap_t *map)
 	map->first = map->last = NULL;
 }
 
-void
-filemap_finalize(void)
-{
-	filemap_t  *map = filemap;
-
-	filemap_list_to_array(map);
-	qsort(map->array, map->narray, sizeof(file_entry_t *),
-		  final_filemap_cmp);
-}
-
 static const char *
 action_to_str(file_action_t action)
 {
@@ -643,26 +512,26 @@ calculate_totals(void)
 	{
 		entry = map->array[i];
 
-		if (entry->type != FILE_TYPE_REGULAR)
+		if (entry->source_type != FILE_TYPE_REGULAR)
 			continue;
 
-		map->total_size += entry->newsize;
+		map->total_size += entry->source_size;
 
 		if (entry->action == FILE_ACTION_COPY)
 		{
-			map->fetch_size += entry->newsize;
+			map->fetch_size += entry->source_size;
 			continue;
 		}
 
 		if (entry->action == FILE_ACTION_COPY_TAIL)
-			map->fetch_size += (entry->newsize - entry->oldsize);
+			map->fetch_size += (entry->source_size - entry->target_size);
 
-		if (entry->pagemap.bitmapsize > 0)
+		if (entry->target_modified_pages.bitmapsize > 0)
 		{
 			datapagemap_iterator_t *iter;
 			BlockNumber blk;
 
-			iter = datapagemap_iterate(&entry->pagemap);
+			iter = datapagemap_iterate(&entry->target_modified_pages);
 			while (datapagemap_next(iter, &blk))
 				map->fetch_size += BLCKSZ;
 
@@ -682,13 +551,13 @@ print_filemap(void)
 	{
 		entry = map->array[i];
 		if (entry->action != FILE_ACTION_NONE ||
-			entry->pagemap.bitmapsize > 0)
+			entry->target_modified_pages.bitmapsize > 0)
 		{
 			pg_log_debug("%s (%s)", entry->path,
 						 action_to_str(entry->action));
 
-			if (entry->pagemap.bitmapsize > 0)
-				datapagemap_print(&entry->pagemap);
+			if (entry->target_modified_pages.bitmapsize > 0)
+				datapagemap_print(&entry->target_modified_pages);
 		}
 	}
 	fflush(stdout);
@@ -837,3 +706,126 @@ final_filemap_cmp(const void *a, const void *b)
 	else
 		return strcmp(fa->path, fb->path);
 }
+
+/*
+ * Decide what to do with each file.
+ */
+void
+filemap_finalize()
+{
+	int			i;
+
+	filemap_list_to_array(filemap);
+
+	for (i = 0; i < filemap->narray; i++)
+	{
+		file_entry_t *entry = filemap->array[i];
+		file_action_t action;
+
+		if (!entry->target_exists && entry->source_exists)
+		{
+			/*
+			 * File exists in source, but not in target. Copy
+			 * it in toto. (If it's a relation data file, WAL replay
+			 * after rewinding should re-create it anyway. But there's
+			 * no harm in copying it now.)
+			 */
+			if (entry->source_type == FILE_TYPE_DIRECTORY ||
+				entry->source_type == FILE_TYPE_SYMLINK)
+			{
+				action = FILE_ACTION_CREATE;
+			}
+			else
+				action = FILE_ACTION_COPY;
+		}
+		else if (entry->target_exists && !entry->source_exists)
+		{
+			/* Delete file from target */
+			action = FILE_ACTION_REMOVE;
+		}
+		else if (entry->target_exists && entry->source_exists)
+		{
+			/* File exists in both systems */
+			if (entry->source_type != entry->target_type)
+			{
+				/* But it's a different kind of object. Strange.. */
+				pg_fatal("file \"%s\" is of different type in source and target", entry->path);
+			}
+
+			switch (entry->source_type)
+			{
+				case FILE_TYPE_DIRECTORY:
+					action = FILE_ACTION_NONE;
+					break;
+
+				case FILE_TYPE_SYMLINK:
+					// FIXME: Check if it points to the same target?
+					action = FILE_ACTION_NONE;
+					break;
+
+				case FILE_TYPE_REGULAR:
+					if (!entry->isrelfile)
+					{
+						/*
+						 * It's a non-data file that we have no special processing for. Copy
+						 * it in toto.
+						 *
+						 * An exception: PG_VERSIONs should be identical, but avoid
+						 * overwriting it for paranoia.
+						 */
+						if (!pg_str_endswith(entry->path, "PG_VERSION"))
+							action = FILE_ACTION_COPY;
+						else
+							action = FILE_ACTION_NONE;
+					}
+					else
+					{
+						/*
+						 * It's a data file that exists in both systems.
+						 *
+						 * If it's larger in target, we can truncate it. There will
+						 * also be a WAL record of the truncation in the source
+						 * system, so WAL replay would eventually truncate the target
+						 * too, but we might as well do it now.
+						 *
+						 * If it's smaller in the target, it means that it has been
+						 * truncated in the target, or enlarged in the source, or
+						 * both. If it was truncated in the target, we need to copy
+						 * the missing tail from the source system. If it was enlarged
+						 * in the source system, there will be WAL records in the
+						 * source system for the new blocks, so we wouldn't need to
+						 * copy them here. But we don't know which scenario we're
+						 * dealing with, and there's no harm in copying the missing
+						 * blocks now, so do it now.
+						 *
+						 * If it's the same size, do nothing here. Any blocks modified
+						 * in the target will be copied based on parsing the target
+						 * system's WAL, and any blocks modified in the source will be
+						 * updated after rewinding, when the source system's WAL is
+						 * replayed.
+						 */
+						if (entry->target_size < entry->source_size)
+							action = FILE_ACTION_COPY_TAIL;
+						else if (entry->target_size > entry->source_size)
+							action = FILE_ACTION_TRUNCATE;
+						else
+							action = FILE_ACTION_NONE;
+					}
+					break;
+				case FILE_TYPE_UNDEFINED:
+					pg_fatal("unknown file type for \"%s\"", entry->path);
+					break;
+			}
+		}
+		else
+		{
+			/* Doesn't exist in either server. Why does it have an entry at all?? */
+			action = FILE_ACTION_NONE;
+		}
+		entry->action = action;
+	}
+
+	/* Sort the actions to the order that they should be performed */
+	qsort(filemap->array, filemap->narray, sizeof(file_entry_t *),
+		  final_filemap_cmp);
+}
diff --git a/src/bin/pg_rewind/filemap.h b/src/bin/pg_rewind/filemap.h
index 0cb7425170c..a5e8df57f40 100644
--- a/src/bin/pg_rewind/filemap.h
+++ b/src/bin/pg_rewind/filemap.h
@@ -14,17 +14,21 @@
 
 /*
  * For every file found in the local or remote system, we have a file entry
- * which says what we are going to do with the file. For relation files,
- * there is also a page map, marking pages in the file that were changed
- * locally.
- *
- * The enum values are sorted in the order we want actions to be processed.
+ * that contains information about the file on both systems.  For relation
+ * files, there is also a page map that marks pages in the file that were
+ * changed in the target after the last common checkpoint.  Each entry also
+ * contains an 'action' field, which says what we are going to do with the
+ * file.
  */
+
+/* these enum values are sorted in the order we want actions to be processed */
 typedef enum
 {
+	FILE_ACTION_UNDECIDED = 0,	/* not decided yet */
+
 	FILE_ACTION_CREATE,			/* create local directory or symbolic link */
 	FILE_ACTION_COPY,			/* copy whole file, overwriting if exists */
-	FILE_ACTION_COPY_TAIL,		/* copy tail from 'oldsize' to 'newsize' */
+	FILE_ACTION_COPY_TAIL,		/* copy tail from 'source_size' to 'target_size' */
 	FILE_ACTION_NONE,			/* no action (we might still copy modified
 								 * blocks based on the parsed WAL) */
 	FILE_ACTION_TRUNCATE,		/* truncate local file to 'newsize' bytes */
@@ -33,6 +37,8 @@ typedef enum
 
 typedef enum
 {
+	FILE_TYPE_UNDEFINED = 0,
+
 	FILE_TYPE_REGULAR,
 	FILE_TYPE_DIRECTORY,
 	FILE_TYPE_SYMLINK
@@ -41,19 +47,30 @@ typedef enum
 typedef struct file_entry_t
 {
 	char	   *path;
-	file_type_t type;
+	bool		isrelfile;		/* is it a relation data file? */
 
-	file_action_t action;
+	/*
+	 * Status of the file in the target.
+	 */
+	bool		target_exists;
+	file_type_t target_type;
+	size_t		target_size; /* for a regular file */
+	char	   *target_link_target; /* for a symlink */
 
-	/* for a regular file */
-	size_t		oldsize;
-	size_t		newsize;
-	bool		isrelfile;		/* is it a relation data file? */
+	datapagemap_t target_modified_pages;
 
-	datapagemap_t pagemap;
+	/*
+	 * Status of the file in the source.
+	 */
+	bool		source_exists;
+	file_type_t source_type;
+	size_t		source_size;
+	char	   *source_link_target; /* for a symlink */
 
-	/* for a symlink */
-	char	   *link_target;
+	/*
+	 * What will we do to the file?
+	 */
+	file_action_t action;
 
 	struct file_entry_t *next;
 } file_entry_t;
@@ -70,20 +87,19 @@ typedef struct filemap_t
 
 	/*
 	 * After processing all the remote files, the entries in the linked list
-	 * are moved to this array. After processing local files, too, all the
+	 * are moved to this array.  After processing local files, too, all the
 	 * local entries are added to the array by filemap_finalize, and sorted in
-	 * the final order. After filemap_finalize, all the entries are in the
+	 * the final order.  After filemap_finalize, all the entries are in the
 	 * array, and the linked list is empty.
 	 */
 	file_entry_t **array;
 	int			narray;			/* current length of array */
 
 	/*
-	 * Summary information. total_size is the total size of the source
-	 * cluster, and fetch_size is the number of bytes that needs to be copied.
+	 * Summary information.
 	 */
-	uint64		total_size;
-	uint64		fetch_size;
+	uint64		total_size;		/* total size of the source cluster */
+	uint64		fetch_size;		/* number of bytes that needs to be copied */
 } filemap_t;
 
 extern filemap_t *filemap;
@@ -94,11 +110,12 @@ extern void print_filemap(void);
 
 /* Functions for populating the filemap */
 extern void process_source_file(const char *path, file_type_t type,
-								size_t newsize, const char *link_target);
+								size_t size, const char *link_target);
 extern void process_target_file(const char *path, file_type_t type,
-								size_t newsize, const char *link_target);
-extern void process_block_change(ForkNumber forknum, RelFileNode rnode,
-								 BlockNumber blkno);
+								size_t size, const char *link_target);
+extern void process_target_wal_block_change(ForkNumber forknum,
+											RelFileNode rnode,
+											BlockNumber blkno);
 extern void filemap_finalize(void);
 
 #endif							/* FILEMAP_H */
diff --git a/src/bin/pg_rewind/libpq_fetch.c b/src/bin/pg_rewind/libpq_fetch.c
index bf4dfc23b96..7fc9161b8c8 100644
--- a/src/bin/pg_rewind/libpq_fetch.c
+++ b/src/bin/pg_rewind/libpq_fetch.c
@@ -465,7 +465,7 @@ libpq_executeFileMap(filemap_t *map)
 		entry = map->array[i];
 
 		/* If this is a relation file, copy the modified blocks */
-		execute_pagemap(&entry->pagemap, entry->path);
+		execute_pagemap(&entry->target_modified_pages, entry->path);
 
 		switch (entry->action)
 		{
@@ -476,15 +476,15 @@ libpq_executeFileMap(filemap_t *map)
 			case FILE_ACTION_COPY:
 				/* Truncate the old file out of the way, if any */
 				open_target_file(entry->path, true);
-				fetch_file_range(entry->path, 0, entry->newsize);
+				fetch_file_range(entry->path, 0, entry->source_size);
 				break;
 
 			case FILE_ACTION_TRUNCATE:
-				truncate_target_file(entry->path, entry->newsize);
+				truncate_target_file(entry->path, entry->source_size);
 				break;
 
 			case FILE_ACTION_COPY_TAIL:
-				fetch_file_range(entry->path, entry->oldsize, entry->newsize);
+				fetch_file_range(entry->path, entry->target_size, entry->source_size);
 				break;
 
 			case FILE_ACTION_REMOVE:
@@ -494,6 +494,10 @@ libpq_executeFileMap(filemap_t *map)
 			case FILE_ACTION_CREATE:
 				create_target(entry);
 				break;
+
+			case FILE_ACTION_UNDECIDED:
+				pg_fatal("no action decided for \"%s\"", entry->path);
+				break;
 		}
 	}
 
diff --git a/src/bin/pg_rewind/parsexlog.c b/src/bin/pg_rewind/parsexlog.c
index 2229c86f9af..2baeb74ae93 100644
--- a/src/bin/pg_rewind/parsexlog.c
+++ b/src/bin/pg_rewind/parsexlog.c
@@ -436,6 +436,6 @@ extractPageInfo(XLogReaderState *record)
 		if (forknum != MAIN_FORKNUM)
 			continue;
 
-		process_block_change(forknum, rnode, blkno);
+		process_target_wal_block_change(forknum, rnode, blkno);
 	}
 }
diff --git a/src/bin/pg_rewind/pg_rewind.c b/src/bin/pg_rewind/pg_rewind.c
index c9b9e480c0f..210984d302b 100644
--- a/src/bin/pg_rewind/pg_rewind.c
+++ b/src/bin/pg_rewind/pg_rewind.c
@@ -369,7 +369,7 @@ main(int argc, char **argv)
 				chkpttli);
 
 	/*
-	 * Build the filemap, by comparing the source and target data directories.
+	 * Collect information about all files in the target and source systems.
 	 */
 	filemap_create();
 	if (showprogress)
@@ -390,8 +390,12 @@ main(int argc, char **argv)
 		pg_log_info("reading WAL in target");
 	extractPageMap(datadir_target, chkptrec, lastcommontliIndex,
 				   ControlFile_target.checkPoint, restore_command);
-	filemap_finalize();
 
+	/*
+	 * We have collected all information we need from both systems. Decide
+	 * what to do with each file.
+	 */
+	filemap_finalize();
 	if (showprogress)
 		calculate_totals();
 
-- 
2.20.1


--------------D93EDEBFB124D563B723F4BD
Content-Type: text/x-patch; charset=UTF-8;
 name="0003-pg_rewind-Replace-the-hybrid-list-array-data-structu.patch"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
 filename*0="0003-pg_rewind-Replace-the-hybrid-list-array-data-structu.pa";
 filename*1="tch"



^ permalink  raw  reply  [nested|flat] 41+ messages in thread

* [PATCH 2/5] Refactor pg_rewind for more clear decision making.
@ 2020-08-19 12:34 Heikki Linnakangas <[email protected]>
  0 siblings, 0 replies; 41+ messages in thread

From: Heikki Linnakangas @ 2020-08-19 12:34 UTC (permalink / raw)

Deciding what to do with each file is now a separate step after all the
necessary information has been gathered. It is more clear that way.
Previously, the decision-making was divided between process_source_file()
and process_target_file(), and it was a bit hard to piece together what the
overall rules were.
---
 src/bin/pg_rewind/copy_fetch.c  |  14 +-
 src/bin/pg_rewind/file_ops.c    |  16 +-
 src/bin/pg_rewind/filemap.c     | 514 ++++++++++++++++----------------
 src/bin/pg_rewind/filemap.h     |  67 +++--
 src/bin/pg_rewind/libpq_fetch.c |  12 +-
 src/bin/pg_rewind/parsexlog.c   |   2 +-
 src/bin/pg_rewind/pg_rewind.c   |   8 +-
 7 files changed, 332 insertions(+), 301 deletions(-)

diff --git a/src/bin/pg_rewind/copy_fetch.c b/src/bin/pg_rewind/copy_fetch.c
index 1edab5f1867..18fad32600e 100644
--- a/src/bin/pg_rewind/copy_fetch.c
+++ b/src/bin/pg_rewind/copy_fetch.c
@@ -210,7 +210,7 @@ copy_executeFileMap(filemap_t *map)
 	for (i = 0; i < map->narray; i++)
 	{
 		entry = map->array[i];
-		execute_pagemap(&entry->pagemap, entry->path);
+		execute_pagemap(&entry->target_modified_pages, entry->path);
 
 		switch (entry->action)
 		{
@@ -219,16 +219,16 @@ copy_executeFileMap(filemap_t *map)
 				break;
 
 			case FILE_ACTION_COPY:
-				rewind_copy_file_range(entry->path, 0, entry->newsize, true);
+				rewind_copy_file_range(entry->path, 0, entry->source_size, true);
 				break;
 
 			case FILE_ACTION_TRUNCATE:
-				truncate_target_file(entry->path, entry->newsize);
+				truncate_target_file(entry->path, entry->source_size);
 				break;
 
 			case FILE_ACTION_COPY_TAIL:
-				rewind_copy_file_range(entry->path, entry->oldsize,
-									   entry->newsize, false);
+				rewind_copy_file_range(entry->path, entry->target_size,
+									   entry->source_size, false);
 				break;
 
 			case FILE_ACTION_CREATE:
@@ -238,6 +238,10 @@ copy_executeFileMap(filemap_t *map)
 			case FILE_ACTION_REMOVE:
 				remove_target(entry);
 				break;
+
+			case FILE_ACTION_UNDECIDED:
+				pg_fatal("no action decided for \"%s\"", entry->path);
+				break;
 		}
 	}
 
diff --git a/src/bin/pg_rewind/file_ops.c b/src/bin/pg_rewind/file_ops.c
index 55439db20ba..ec37d0b2e0d 100644
--- a/src/bin/pg_rewind/file_ops.c
+++ b/src/bin/pg_rewind/file_ops.c
@@ -126,8 +126,9 @@ void
 remove_target(file_entry_t *entry)
 {
 	Assert(entry->action == FILE_ACTION_REMOVE);
+	Assert(entry->target_exists);
 
-	switch (entry->type)
+	switch (entry->target_type)
 	{
 		case FILE_TYPE_DIRECTORY:
 			remove_target_dir(entry->path);
@@ -140,6 +141,10 @@ remove_target(file_entry_t *entry)
 		case FILE_TYPE_SYMLINK:
 			remove_target_symlink(entry->path);
 			break;
+
+		case FILE_TYPE_UNDEFINED:
+			pg_fatal("undefined file type for \"%s\"", entry->path);
+			break;
 	}
 }
 
@@ -147,21 +152,26 @@ void
 create_target(file_entry_t *entry)
 {
 	Assert(entry->action == FILE_ACTION_CREATE);
+	Assert(!entry->target_exists);
 
-	switch (entry->type)
+	switch (entry->source_type)
 	{
 		case FILE_TYPE_DIRECTORY:
 			create_target_dir(entry->path);
 			break;
 
 		case FILE_TYPE_SYMLINK:
-			create_target_symlink(entry->path, entry->link_target);
+			create_target_symlink(entry->path, entry->source_link_target);
 			break;
 
 		case FILE_TYPE_REGULAR:
 			/* can't happen. Regular files are created with open_target_file. */
 			pg_fatal("invalid action (CREATE) for regular file");
 			break;
+
+		case FILE_TYPE_UNDEFINED:
+			pg_fatal("undefined file type for \"%s\"", entry->path);
+			break;
 	}
 }
 
diff --git a/src/bin/pg_rewind/filemap.c b/src/bin/pg_rewind/filemap.c
index 1879257b66a..431e8e760e0 100644
--- a/src/bin/pg_rewind/filemap.c
+++ b/src/bin/pg_rewind/filemap.c
@@ -26,6 +26,8 @@ static bool isRelDataFile(const char *path);
 static char *datasegpath(RelFileNode rnode, ForkNumber forknum,
 						 BlockNumber segno);
 static int	path_cmp(const void *a, const void *b);
+
+static file_entry_t *get_filemap_entry(const char *path, bool create);
 static int	final_filemap_cmp(const void *a, const void *b);
 static void filemap_list_to_array(filemap_t *map);
 static bool check_file_excluded(const char *path, bool is_source);
@@ -146,6 +148,65 @@ filemap_create(void)
 	filemap = map;
 }
 
+/* Look up or create entry for 'path' */
+static file_entry_t *
+get_filemap_entry(const char *path, bool create)
+{
+	filemap_t  *map = filemap;
+	file_entry_t *entry;
+	file_entry_t **e;
+	file_entry_t key;
+	file_entry_t *key_ptr;
+
+	if (map->array)
+	{
+		key.path = (char *) path;
+		key_ptr = &key;
+		e = bsearch(&key_ptr, map->array, map->narray, sizeof(file_entry_t *),
+					path_cmp);
+	}
+	else
+		e = NULL;
+
+	if (e)
+		entry = *e;
+	else if (!create)
+		entry = NULL;
+	else
+	{
+		/* Create a new entry for this file */
+		entry = pg_malloc(sizeof(file_entry_t));
+		entry->path = pg_strdup(path);
+		entry->isrelfile = isRelDataFile(path);
+		entry->action = FILE_ACTION_UNDECIDED;
+
+		entry->target_exists = false;
+		entry->target_type = FILE_TYPE_UNDEFINED;
+		entry->target_size = 0;
+		entry->target_link_target = NULL;
+		entry->target_modified_pages.bitmap = NULL;
+		entry->target_modified_pages.bitmapsize = 0;
+
+		entry->source_exists = false;
+		entry->source_type = FILE_TYPE_UNDEFINED;
+		entry->source_size = 0;
+		entry->source_link_target = NULL;
+
+		entry->next = NULL;
+
+		if (map->last)
+		{
+			map->last->next = entry;
+			map->last = entry;
+		}
+		else
+			map->first = map->last = entry;
+		map->nlist++;
+	}
+
+	return entry;
+}
+
 /*
  * Callback for processing source file list.
  *
@@ -154,18 +215,19 @@ filemap_create(void)
  * exists in the target and whether the size matches.
  */
 void
-process_source_file(const char *path, file_type_t type, size_t newsize,
+process_source_file(const char *path, file_type_t type, size_t size,
 					const char *link_target)
 {
-	bool		exists;
-	char		localpath[MAXPGPATH];
-	struct stat statbuf;
-	filemap_t  *map = filemap;
-	file_action_t action = FILE_ACTION_NONE;
-	size_t		oldsize = 0;
 	file_entry_t *entry;
 
-	Assert(map->array == NULL);
+	Assert(filemap->array == NULL);
+
+	/*
+	 * Skip the control file. It is handled specially, after copying all the
+	 * other files.
+	 */
+	if (strcmp(path, "global/pg_control") == 0)
+		return;
 
 	/*
 	 * Skip any files matching the exclusion filters. This has the effect to
@@ -199,142 +261,12 @@ process_source_file(const char *path, file_type_t type, size_t newsize,
 	if (type != FILE_TYPE_REGULAR && isRelDataFile(path))
 		pg_fatal("data file \"%s\" in source is not a regular file", path);
 
-	snprintf(localpath, sizeof(localpath), "%s/%s", datadir_target, path);
-
-	/* Does the corresponding file exist in the target data dir? */
-	if (lstat(localpath, &statbuf) < 0)
-	{
-		if (errno != ENOENT)
-			pg_fatal("could not stat file \"%s\": %m",
-					 localpath);
-
-		exists = false;
-	}
-	else
-		exists = true;
-
-	switch (type)
-	{
-		case FILE_TYPE_DIRECTORY:
-			if (exists && !S_ISDIR(statbuf.st_mode) && strcmp(path, "pg_wal") != 0)
-			{
-				/* it's a directory in source, but not in target. Strange.. */
-				pg_fatal("\"%s\" is not a directory", localpath);
-			}
-
-			if (!exists)
-				action = FILE_ACTION_CREATE;
-			else
-				action = FILE_ACTION_NONE;
-			oldsize = 0;
-			break;
-
-		case FILE_TYPE_SYMLINK:
-			if (exists &&
-#ifndef WIN32
-				!S_ISLNK(statbuf.st_mode)
-#else
-				!pgwin32_is_junction(localpath)
-#endif
-				)
-			{
-				/*
-				 * It's a symbolic link in source, but not in target.
-				 * Strange..
-				 */
-				pg_fatal("\"%s\" is not a symbolic link", localpath);
-			}
-
-			if (!exists)
-				action = FILE_ACTION_CREATE;
-			else
-				action = FILE_ACTION_NONE;
-			oldsize = 0;
-			break;
-
-		case FILE_TYPE_REGULAR:
-			if (exists && !S_ISREG(statbuf.st_mode))
-				pg_fatal("\"%s\" is not a regular file", localpath);
-
-			if (!exists || !isRelDataFile(path))
-			{
-				/*
-				 * File exists in source, but not in target. Or it's a
-				 * non-data file that we have no special processing for. Copy
-				 * it in toto.
-				 *
-				 * An exception: PG_VERSIONs should be identical, but avoid
-				 * overwriting it for paranoia.
-				 */
-				if (pg_str_endswith(path, "PG_VERSION"))
-				{
-					action = FILE_ACTION_NONE;
-					oldsize = statbuf.st_size;
-				}
-				else
-				{
-					action = FILE_ACTION_COPY;
-					oldsize = 0;
-				}
-			}
-			else
-			{
-				/*
-				 * It's a data file that exists in both.
-				 *
-				 * If it's larger in target, we can truncate it. There will
-				 * also be a WAL record of the truncation in the source
-				 * system, so WAL replay would eventually truncate the target
-				 * too, but we might as well do it now.
-				 *
-				 * If it's smaller in the target, it means that it has been
-				 * truncated in the target, or enlarged in the source, or
-				 * both. If it was truncated in the target, we need to copy
-				 * the missing tail from the source system. If it was enlarged
-				 * in the source system, there will be WAL records in the
-				 * source system for the new blocks, so we wouldn't need to
-				 * copy them here. But we don't know which scenario we're
-				 * dealing with, and there's no harm in copying the missing
-				 * blocks now, so do it now.
-				 *
-				 * If it's the same size, do nothing here. Any blocks modified
-				 * in the target will be copied based on parsing the target
-				 * system's WAL, and any blocks modified in the source will be
-				 * updated after rewinding, when the source system's WAL is
-				 * replayed.
-				 */
-				oldsize = statbuf.st_size;
-				if (oldsize < newsize)
-					action = FILE_ACTION_COPY_TAIL;
-				else if (oldsize > newsize)
-					action = FILE_ACTION_TRUNCATE;
-				else
-					action = FILE_ACTION_NONE;
-			}
-			break;
-	}
-
-	/* Create a new entry for this file */
-	entry = pg_malloc(sizeof(file_entry_t));
-	entry->path = pg_strdup(path);
-	entry->type = type;
-	entry->action = action;
-	entry->oldsize = oldsize;
-	entry->newsize = newsize;
-	entry->link_target = link_target ? pg_strdup(link_target) : NULL;
-	entry->next = NULL;
-	entry->pagemap.bitmap = NULL;
-	entry->pagemap.bitmapsize = 0;
-	entry->isrelfile = isRelDataFile(path);
-
-	if (map->last)
-	{
-		map->last->next = entry;
-		map->last = entry;
-	}
-	else
-		map->first = map->last = entry;
-	map->nlist++;
+	/* Remember this source file */
+	entry = get_filemap_entry(path, true);
+	entry->source_exists = true;
+	entry->source_type = type;
+	entry->source_size = size;
+	entry->source_link_target = link_target ? pg_strdup(link_target) : NULL;
 }
 
 /*
@@ -345,33 +277,24 @@ process_source_file(const char *path, file_type_t type, size_t newsize,
  * deletion.
  */
 void
-process_target_file(const char *path, file_type_t type, size_t oldsize,
+process_target_file(const char *path, file_type_t type, size_t size,
 					const char *link_target)
 {
-	bool		exists;
-	char		localpath[MAXPGPATH];
-	struct stat statbuf;
-	file_entry_t key;
-	file_entry_t *key_ptr;
 	filemap_t  *map = filemap;
 	file_entry_t *entry;
 
+	/*
+	 * Skip the control file. It is handled specially, after copying all the
+	 * other files.
+	 */
+	if (strcmp(path, "global/pg_control") == 0)
+		return;
+
 	/*
 	 * Do not apply any exclusion filters here.  This has advantage to remove
 	 * from the target data folder all paths which have been filtered out from
 	 * the source data folder when processing the source files.
 	 */
-
-	snprintf(localpath, sizeof(localpath), "%s/%s", datadir_target, path);
-	if (lstat(localpath, &statbuf) < 0)
-	{
-		if (errno != ENOENT)
-			pg_fatal("could not stat file \"%s\": %m",
-					 localpath);
-
-		exists = false;
-	}
-
 	if (map->array == NULL)
 	{
 		/* on first call, initialize lookup array */
@@ -389,120 +312,76 @@ process_target_file(const char *path, file_type_t type, size_t oldsize,
 	}
 
 	/*
-	 * Like in process_source_file, pretend that xlog is always a  directory.
+	 * Like in process_source_file, pretend that pg_wal is always a directory.
 	 */
 	if (strcmp(path, "pg_wal") == 0 && type == FILE_TYPE_SYMLINK)
 		type = FILE_TYPE_DIRECTORY;
 
-	key.path = (char *) path;
-	key_ptr = &key;
-	exists = (bsearch(&key_ptr, map->array, map->narray, sizeof(file_entry_t *),
-					  path_cmp) != NULL);
-
-	/* Remove any file or folder that doesn't exist in the source system. */
-	if (!exists)
-	{
-		entry = pg_malloc(sizeof(file_entry_t));
-		entry->path = pg_strdup(path);
-		entry->type = type;
-		entry->action = FILE_ACTION_REMOVE;
-		entry->oldsize = oldsize;
-		entry->newsize = 0;
-		entry->link_target = link_target ? pg_strdup(link_target) : NULL;
-		entry->next = NULL;
-		entry->pagemap.bitmap = NULL;
-		entry->pagemap.bitmapsize = 0;
-		entry->isrelfile = isRelDataFile(path);
-
-		if (map->last == NULL)
-			map->first = entry;
-		else
-			map->last->next = entry;
-		map->last = entry;
-		map->nlist++;
-	}
-	else
-	{
-		/*
-		 * We already handled all files that exist in the source system in
-		 * process_source_file().
-		 */
-	}
+	/* Remember this target file */
+	entry = get_filemap_entry(path, true);
+	entry->target_exists = true;
+	entry->target_type = type;
+	entry->target_size = size;
+	entry->target_link_target = link_target ? pg_strdup(link_target) : NULL;
 }
 
 /*
  * This callback gets called while we read the WAL in the target, for every
- * block that have changed in the target system. It makes note of all the
+ * block that have changed in the target system.  It makes note of all the
  * changed blocks in the pagemap of the file.
+ *
+ * NOTE: All the files on both systems must have already been added to the
+ * file map!
  */
 void
-process_block_change(ForkNumber forknum, RelFileNode rnode, BlockNumber blkno)
+process_target_wal_block_change(ForkNumber forknum, RelFileNode rnode,
+								BlockNumber blkno)
 {
 	char	   *path;
-	file_entry_t key;
-	file_entry_t *key_ptr;
 	file_entry_t *entry;
 	BlockNumber blkno_inseg;
 	int			segno;
-	filemap_t  *map = filemap;
-	file_entry_t **e;
 
-	Assert(map->array);
+	Assert(filemap->array);
 
 	segno = blkno / RELSEG_SIZE;
 	blkno_inseg = blkno % RELSEG_SIZE;
 
 	path = datasegpath(rnode, forknum, segno);
-
-	key.path = (char *) path;
-	key_ptr = &key;
-
-	e = bsearch(&key_ptr, map->array, map->narray, sizeof(file_entry_t *),
-				path_cmp);
-	if (e)
-		entry = *e;
-	else
-		entry = NULL;
+	entry = get_filemap_entry(path, false);
 	pfree(path);
 
 	if (entry)
 	{
+		int64		end_offset;
+
 		Assert(entry->isrelfile);
 
-		switch (entry->action)
-		{
-			case FILE_ACTION_NONE:
-			case FILE_ACTION_TRUNCATE:
-				/* skip if we're truncating away the modified block anyway */
-				if ((blkno_inseg + 1) * BLCKSZ <= entry->newsize)
-					datapagemap_add(&entry->pagemap, blkno_inseg);
-				break;
-
-			case FILE_ACTION_COPY_TAIL:
-
-				/*
-				 * skip the modified block if it is part of the "tail" that
-				 * we're copying anyway.
-				 */
-				if ((blkno_inseg + 1) * BLCKSZ <= entry->oldsize)
-					datapagemap_add(&entry->pagemap, blkno_inseg);
-				break;
-
-			case FILE_ACTION_COPY:
-			case FILE_ACTION_REMOVE:
-				break;
-
-			case FILE_ACTION_CREATE:
-				pg_fatal("unexpected page modification for directory or symbolic link \"%s\"", entry->path);
-		}
+		if (entry->target_type != FILE_TYPE_REGULAR)
+			pg_fatal("unexpected page modification for directory or symbolic link \"%s\"",
+					 entry->path);
+
+		/*
+		 * If the block beyond the EOF in the source system, no need to
+		 * remember it now, because we're going to truncate it away from the
+		 * target anyway. Also no need to remember the block if it's beyond
+		 * the current EOF in the target system; we will copy it over with the
+		 * "tail" from the source system, anyway.
+		 */
+		end_offset = (blkno_inseg + 1) * BLCKSZ;
+		if (end_offset <= entry->source_size &&
+			end_offset <= entry->target_size)
+			datapagemap_add(&entry->target_modified_pages, blkno_inseg);
 	}
 	else
 	{
 		/*
 		 * If we don't have any record of this file in the file map, it means
-		 * that it's a relation that doesn't exist in the source system, and
-		 * it was subsequently removed in the target system, too. We can
-		 * safely ignore it.
+		 * that it's a relation that doesn't exist in the source system.  It
+		 * could exist in the target system; we haven't moved the target-only
+		 * entries from the linked list to the array yet!  But in any case, if
+		 * it doesn't exist in the source it will be removed from the target
+		 * too, and we can safely ignore it.
 		 */
 	}
 }
@@ -593,16 +472,6 @@ filemap_list_to_array(filemap_t *map)
 	map->first = map->last = NULL;
 }
 
-void
-filemap_finalize(void)
-{
-	filemap_t  *map = filemap;
-
-	filemap_list_to_array(map);
-	qsort(map->array, map->narray, sizeof(file_entry_t *),
-		  final_filemap_cmp);
-}
-
 static const char *
 action_to_str(file_action_t action)
 {
@@ -643,26 +512,26 @@ calculate_totals(void)
 	{
 		entry = map->array[i];
 
-		if (entry->type != FILE_TYPE_REGULAR)
+		if (entry->source_type != FILE_TYPE_REGULAR)
 			continue;
 
-		map->total_size += entry->newsize;
+		map->total_size += entry->source_size;
 
 		if (entry->action == FILE_ACTION_COPY)
 		{
-			map->fetch_size += entry->newsize;
+			map->fetch_size += entry->source_size;
 			continue;
 		}
 
 		if (entry->action == FILE_ACTION_COPY_TAIL)
-			map->fetch_size += (entry->newsize - entry->oldsize);
+			map->fetch_size += (entry->source_size - entry->target_size);
 
-		if (entry->pagemap.bitmapsize > 0)
+		if (entry->target_modified_pages.bitmapsize > 0)
 		{
 			datapagemap_iterator_t *iter;
 			BlockNumber blk;
 
-			iter = datapagemap_iterate(&entry->pagemap);
+			iter = datapagemap_iterate(&entry->target_modified_pages);
 			while (datapagemap_next(iter, &blk))
 				map->fetch_size += BLCKSZ;
 
@@ -682,13 +551,13 @@ print_filemap(void)
 	{
 		entry = map->array[i];
 		if (entry->action != FILE_ACTION_NONE ||
-			entry->pagemap.bitmapsize > 0)
+			entry->target_modified_pages.bitmapsize > 0)
 		{
 			pg_log_debug("%s (%s)", entry->path,
 						 action_to_str(entry->action));
 
-			if (entry->pagemap.bitmapsize > 0)
-				datapagemap_print(&entry->pagemap);
+			if (entry->target_modified_pages.bitmapsize > 0)
+				datapagemap_print(&entry->target_modified_pages);
 		}
 	}
 	fflush(stdout);
@@ -837,3 +706,126 @@ final_filemap_cmp(const void *a, const void *b)
 	else
 		return strcmp(fa->path, fb->path);
 }
+
+/*
+ * Decide what to do with each file.
+ */
+void
+filemap_finalize()
+{
+	int			i;
+
+	filemap_list_to_array(filemap);
+
+	for (i = 0; i < filemap->narray; i++)
+	{
+		file_entry_t *entry = filemap->array[i];
+		file_action_t action;
+
+		if (!entry->target_exists && entry->source_exists)
+		{
+			/*
+			 * File exists in source, but not in target. Copy
+			 * it in toto. (If it's a relation data file, WAL replay
+			 * after rewinding should re-create it anyway. But there's
+			 * no harm in copying it now.)
+			 */
+			if (entry->source_type == FILE_TYPE_DIRECTORY ||
+				entry->source_type == FILE_TYPE_SYMLINK)
+			{
+				action = FILE_ACTION_CREATE;
+			}
+			else
+				action = FILE_ACTION_COPY;
+		}
+		else if (entry->target_exists && !entry->source_exists)
+		{
+			/* Delete file from target */
+			action = FILE_ACTION_REMOVE;
+		}
+		else if (entry->target_exists && entry->source_exists)
+		{
+			/* File exists in both systems */
+			if (entry->source_type != entry->target_type)
+			{
+				/* But it's a different kind of object. Strange.. */
+				pg_fatal("file \"%s\" is of different type in source and target", entry->path);
+			}
+
+			switch (entry->source_type)
+			{
+				case FILE_TYPE_DIRECTORY:
+					action = FILE_ACTION_NONE;
+					break;
+
+				case FILE_TYPE_SYMLINK:
+					// FIXME: Check if it points to the same target?
+					action = FILE_ACTION_NONE;
+					break;
+
+				case FILE_TYPE_REGULAR:
+					if (!entry->isrelfile)
+					{
+						/*
+						 * It's a non-data file that we have no special processing for. Copy
+						 * it in toto.
+						 *
+						 * An exception: PG_VERSIONs should be identical, but avoid
+						 * overwriting it for paranoia.
+						 */
+						if (!pg_str_endswith(entry->path, "PG_VERSION"))
+							action = FILE_ACTION_COPY;
+						else
+							action = FILE_ACTION_NONE;
+					}
+					else
+					{
+						/*
+						 * It's a data file that exists in both systems.
+						 *
+						 * If it's larger in target, we can truncate it. There will
+						 * also be a WAL record of the truncation in the source
+						 * system, so WAL replay would eventually truncate the target
+						 * too, but we might as well do it now.
+						 *
+						 * If it's smaller in the target, it means that it has been
+						 * truncated in the target, or enlarged in the source, or
+						 * both. If it was truncated in the target, we need to copy
+						 * the missing tail from the source system. If it was enlarged
+						 * in the source system, there will be WAL records in the
+						 * source system for the new blocks, so we wouldn't need to
+						 * copy them here. But we don't know which scenario we're
+						 * dealing with, and there's no harm in copying the missing
+						 * blocks now, so do it now.
+						 *
+						 * If it's the same size, do nothing here. Any blocks modified
+						 * in the target will be copied based on parsing the target
+						 * system's WAL, and any blocks modified in the source will be
+						 * updated after rewinding, when the source system's WAL is
+						 * replayed.
+						 */
+						if (entry->target_size < entry->source_size)
+							action = FILE_ACTION_COPY_TAIL;
+						else if (entry->target_size > entry->source_size)
+							action = FILE_ACTION_TRUNCATE;
+						else
+							action = FILE_ACTION_NONE;
+					}
+					break;
+				case FILE_TYPE_UNDEFINED:
+					pg_fatal("unknown file type for \"%s\"", entry->path);
+					break;
+			}
+		}
+		else
+		{
+			/* Doesn't exist in either server. Why does it have an entry at all?? */
+			action = FILE_ACTION_NONE;
+		}
+		entry->action = action;
+	}
+
+	/* Sort the actions to the order that they should be performed */
+	qsort(filemap->array, filemap->narray, sizeof(file_entry_t *),
+		  final_filemap_cmp);
+}
diff --git a/src/bin/pg_rewind/filemap.h b/src/bin/pg_rewind/filemap.h
index 0cb7425170c..a5e8df57f40 100644
--- a/src/bin/pg_rewind/filemap.h
+++ b/src/bin/pg_rewind/filemap.h
@@ -14,17 +14,21 @@
 
 /*
  * For every file found in the local or remote system, we have a file entry
- * which says what we are going to do with the file. For relation files,
- * there is also a page map, marking pages in the file that were changed
- * locally.
- *
- * The enum values are sorted in the order we want actions to be processed.
+ * that contains information about the file on both systems.  For relation
+ * files, there is also a page map that marks pages in the file that were
+ * changed in the target after the last common checkpoint.  Each entry also
+ * contains an 'action' field, which says what we are going to do with the
+ * file.
  */
+
+/* these enum values are sorted in the order we want actions to be processed */
 typedef enum
 {
+	FILE_ACTION_UNDECIDED = 0,	/* not decided yet */
+
 	FILE_ACTION_CREATE,			/* create local directory or symbolic link */
 	FILE_ACTION_COPY,			/* copy whole file, overwriting if exists */
-	FILE_ACTION_COPY_TAIL,		/* copy tail from 'oldsize' to 'newsize' */
+	FILE_ACTION_COPY_TAIL,		/* copy tail from 'source_size' to 'target_size' */
 	FILE_ACTION_NONE,			/* no action (we might still copy modified
 								 * blocks based on the parsed WAL) */
 	FILE_ACTION_TRUNCATE,		/* truncate local file to 'newsize' bytes */
@@ -33,6 +37,8 @@ typedef enum
 
 typedef enum
 {
+	FILE_TYPE_UNDEFINED = 0,
+
 	FILE_TYPE_REGULAR,
 	FILE_TYPE_DIRECTORY,
 	FILE_TYPE_SYMLINK
@@ -41,19 +47,30 @@ typedef enum
 typedef struct file_entry_t
 {
 	char	   *path;
-	file_type_t type;
+	bool		isrelfile;		/* is it a relation data file? */
 
-	file_action_t action;
+	/*
+	 * Status of the file in the target.
+	 */
+	bool		target_exists;
+	file_type_t target_type;
+	size_t		target_size; /* for a regular file */
+	char	   *target_link_target; /* for a symlink */
 
-	/* for a regular file */
-	size_t		oldsize;
-	size_t		newsize;
-	bool		isrelfile;		/* is it a relation data file? */
+	datapagemap_t target_modified_pages;
 
-	datapagemap_t pagemap;
+	/*
+	 * Status of the file in the source.
+	 */
+	bool		source_exists;
+	file_type_t source_type;
+	size_t		source_size;
+	char	   *source_link_target; /* for a symlink */
 
-	/* for a symlink */
-	char	   *link_target;
+	/*
+	 * What will we do to the file?
+	 */
+	file_action_t action;
 
 	struct file_entry_t *next;
 } file_entry_t;
@@ -70,20 +87,19 @@ typedef struct filemap_t
 
 	/*
 	 * After processing all the remote files, the entries in the linked list
-	 * are moved to this array. After processing local files, too, all the
+	 * are moved to this array.  After processing local files, too, all the
 	 * local entries are added to the array by filemap_finalize, and sorted in
-	 * the final order. After filemap_finalize, all the entries are in the
+	 * the final order.  After filemap_finalize, all the entries are in the
 	 * array, and the linked list is empty.
 	 */
 	file_entry_t **array;
 	int			narray;			/* current length of array */
 
 	/*
-	 * Summary information. total_size is the total size of the source
-	 * cluster, and fetch_size is the number of bytes that needs to be copied.
+	 * Summary information.
 	 */
-	uint64		total_size;
-	uint64		fetch_size;
+	uint64		total_size;		/* total size of the source cluster */
+	uint64		fetch_size;		/* number of bytes that needs to be copied */
 } filemap_t;
 
 extern filemap_t *filemap;
@@ -94,11 +110,12 @@ extern void print_filemap(void);
 
 /* Functions for populating the filemap */
 extern void process_source_file(const char *path, file_type_t type,
-								size_t newsize, const char *link_target);
+								size_t size, const char *link_target);
 extern void process_target_file(const char *path, file_type_t type,
-								size_t newsize, const char *link_target);
-extern void process_block_change(ForkNumber forknum, RelFileNode rnode,
-								 BlockNumber blkno);
+								size_t size, const char *link_target);
+extern void process_target_wal_block_change(ForkNumber forknum,
+											RelFileNode rnode,
+											BlockNumber blkno);
 extern void filemap_finalize(void);
 
 #endif							/* FILEMAP_H */
diff --git a/src/bin/pg_rewind/libpq_fetch.c b/src/bin/pg_rewind/libpq_fetch.c
index bf4dfc23b96..7fc9161b8c8 100644
--- a/src/bin/pg_rewind/libpq_fetch.c
+++ b/src/bin/pg_rewind/libpq_fetch.c
@@ -465,7 +465,7 @@ libpq_executeFileMap(filemap_t *map)
 		entry = map->array[i];
 
 		/* If this is a relation file, copy the modified blocks */
-		execute_pagemap(&entry->pagemap, entry->path);
+		execute_pagemap(&entry->target_modified_pages, entry->path);
 
 		switch (entry->action)
 		{
@@ -476,15 +476,15 @@ libpq_executeFileMap(filemap_t *map)
 			case FILE_ACTION_COPY:
 				/* Truncate the old file out of the way, if any */
 				open_target_file(entry->path, true);
-				fetch_file_range(entry->path, 0, entry->newsize);
+				fetch_file_range(entry->path, 0, entry->source_size);
 				break;
 
 			case FILE_ACTION_TRUNCATE:
-				truncate_target_file(entry->path, entry->newsize);
+				truncate_target_file(entry->path, entry->source_size);
 				break;
 
 			case FILE_ACTION_COPY_TAIL:
-				fetch_file_range(entry->path, entry->oldsize, entry->newsize);
+				fetch_file_range(entry->path, entry->target_size, entry->source_size);
 				break;
 
 			case FILE_ACTION_REMOVE:
@@ -494,6 +494,10 @@ libpq_executeFileMap(filemap_t *map)
 			case FILE_ACTION_CREATE:
 				create_target(entry);
 				break;
+
+			case FILE_ACTION_UNDECIDED:
+				pg_fatal("no action decided for \"%s\"", entry->path);
+				break;
 		}
 	}
 
diff --git a/src/bin/pg_rewind/parsexlog.c b/src/bin/pg_rewind/parsexlog.c
index 2229c86f9af..2baeb74ae93 100644
--- a/src/bin/pg_rewind/parsexlog.c
+++ b/src/bin/pg_rewind/parsexlog.c
@@ -436,6 +436,6 @@ extractPageInfo(XLogReaderState *record)
 		if (forknum != MAIN_FORKNUM)
 			continue;
 
-		process_block_change(forknum, rnode, blkno);
+		process_target_wal_block_change(forknum, rnode, blkno);
 	}
 }
diff --git a/src/bin/pg_rewind/pg_rewind.c b/src/bin/pg_rewind/pg_rewind.c
index c9b9e480c0f..210984d302b 100644
--- a/src/bin/pg_rewind/pg_rewind.c
+++ b/src/bin/pg_rewind/pg_rewind.c
@@ -369,7 +369,7 @@ main(int argc, char **argv)
 				chkpttli);
 
 	/*
-	 * Build the filemap, by comparing the source and target data directories.
+	 * Collect information about all files in the target and source systems.
 	 */
 	filemap_create();
 	if (showprogress)
@@ -390,8 +390,12 @@ main(int argc, char **argv)
 		pg_log_info("reading WAL in target");
 	extractPageMap(datadir_target, chkptrec, lastcommontliIndex,
 				   ControlFile_target.checkPoint, restore_command);
-	filemap_finalize();
 
+	/*
+	 * We have collected all information we need from both systems. Decide
+	 * what to do with each file.
+	 */
+	filemap_finalize();
 	if (showprogress)
 		calculate_totals();
 
-- 
2.20.1


--------------D93EDEBFB124D563B723F4BD
Content-Type: text/x-patch; charset=UTF-8;
 name="0003-pg_rewind-Replace-the-hybrid-list-array-data-structu.patch"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
 filename*0="0003-pg_rewind-Replace-the-hybrid-list-array-data-structu.pa";
 filename*1="tch"



^ permalink  raw  reply  [nested|flat] 41+ messages in thread

* [PATCH 2/5] Refactor pg_rewind for more clear decision making.
@ 2020-08-19 12:34 Heikki Linnakangas <[email protected]>
  0 siblings, 0 replies; 41+ messages in thread

From: Heikki Linnakangas @ 2020-08-19 12:34 UTC (permalink / raw)

Deciding what to do with each file is now a separate step after all the
necessary information has been gathered. It is more clear that way.
Previously, the decision-making was divided between process_source_file()
and process_target_file(), and it was a bit hard to piece together what the
overall rules were.
---
 src/bin/pg_rewind/copy_fetch.c  |  14 +-
 src/bin/pg_rewind/file_ops.c    |  16 +-
 src/bin/pg_rewind/filemap.c     | 514 ++++++++++++++++----------------
 src/bin/pg_rewind/filemap.h     |  67 +++--
 src/bin/pg_rewind/libpq_fetch.c |  12 +-
 src/bin/pg_rewind/parsexlog.c   |   2 +-
 src/bin/pg_rewind/pg_rewind.c   |   8 +-
 7 files changed, 332 insertions(+), 301 deletions(-)

diff --git a/src/bin/pg_rewind/copy_fetch.c b/src/bin/pg_rewind/copy_fetch.c
index 1edab5f1867..18fad32600e 100644
--- a/src/bin/pg_rewind/copy_fetch.c
+++ b/src/bin/pg_rewind/copy_fetch.c
@@ -210,7 +210,7 @@ copy_executeFileMap(filemap_t *map)
 	for (i = 0; i < map->narray; i++)
 	{
 		entry = map->array[i];
-		execute_pagemap(&entry->pagemap, entry->path);
+		execute_pagemap(&entry->target_modified_pages, entry->path);
 
 		switch (entry->action)
 		{
@@ -219,16 +219,16 @@ copy_executeFileMap(filemap_t *map)
 				break;
 
 			case FILE_ACTION_COPY:
-				rewind_copy_file_range(entry->path, 0, entry->newsize, true);
+				rewind_copy_file_range(entry->path, 0, entry->source_size, true);
 				break;
 
 			case FILE_ACTION_TRUNCATE:
-				truncate_target_file(entry->path, entry->newsize);
+				truncate_target_file(entry->path, entry->source_size);
 				break;
 
 			case FILE_ACTION_COPY_TAIL:
-				rewind_copy_file_range(entry->path, entry->oldsize,
-									   entry->newsize, false);
+				rewind_copy_file_range(entry->path, entry->target_size,
+									   entry->source_size, false);
 				break;
 
 			case FILE_ACTION_CREATE:
@@ -238,6 +238,10 @@ copy_executeFileMap(filemap_t *map)
 			case FILE_ACTION_REMOVE:
 				remove_target(entry);
 				break;
+
+			case FILE_ACTION_UNDECIDED:
+				pg_fatal("no action decided for \"%s\"", entry->path);
+				break;
 		}
 	}
 
diff --git a/src/bin/pg_rewind/file_ops.c b/src/bin/pg_rewind/file_ops.c
index 55439db20ba..ec37d0b2e0d 100644
--- a/src/bin/pg_rewind/file_ops.c
+++ b/src/bin/pg_rewind/file_ops.c
@@ -126,8 +126,9 @@ void
 remove_target(file_entry_t *entry)
 {
 	Assert(entry->action == FILE_ACTION_REMOVE);
+	Assert(entry->target_exists);
 
-	switch (entry->type)
+	switch (entry->target_type)
 	{
 		case FILE_TYPE_DIRECTORY:
 			remove_target_dir(entry->path);
@@ -140,6 +141,10 @@ remove_target(file_entry_t *entry)
 		case FILE_TYPE_SYMLINK:
 			remove_target_symlink(entry->path);
 			break;
+
+		case FILE_TYPE_UNDEFINED:
+			pg_fatal("undefined file type for \"%s\"", entry->path);
+			break;
 	}
 }
 
@@ -147,21 +152,26 @@ void
 create_target(file_entry_t *entry)
 {
 	Assert(entry->action == FILE_ACTION_CREATE);
+	Assert(!entry->target_exists);
 
-	switch (entry->type)
+	switch (entry->source_type)
 	{
 		case FILE_TYPE_DIRECTORY:
 			create_target_dir(entry->path);
 			break;
 
 		case FILE_TYPE_SYMLINK:
-			create_target_symlink(entry->path, entry->link_target);
+			create_target_symlink(entry->path, entry->source_link_target);
 			break;
 
 		case FILE_TYPE_REGULAR:
 			/* can't happen. Regular files are created with open_target_file. */
 			pg_fatal("invalid action (CREATE) for regular file");
 			break;
+
+		case FILE_TYPE_UNDEFINED:
+			pg_fatal("undefined file type for \"%s\"", entry->path);
+			break;
 	}
 }
 
diff --git a/src/bin/pg_rewind/filemap.c b/src/bin/pg_rewind/filemap.c
index 1879257b66a..431e8e760e0 100644
--- a/src/bin/pg_rewind/filemap.c
+++ b/src/bin/pg_rewind/filemap.c
@@ -26,6 +26,8 @@ static bool isRelDataFile(const char *path);
 static char *datasegpath(RelFileNode rnode, ForkNumber forknum,
 						 BlockNumber segno);
 static int	path_cmp(const void *a, const void *b);
+
+static file_entry_t *get_filemap_entry(const char *path, bool create);
 static int	final_filemap_cmp(const void *a, const void *b);
 static void filemap_list_to_array(filemap_t *map);
 static bool check_file_excluded(const char *path, bool is_source);
@@ -146,6 +148,65 @@ filemap_create(void)
 	filemap = map;
 }
 
+/* Look up or create entry for 'path' */
+static file_entry_t *
+get_filemap_entry(const char *path, bool create)
+{
+	filemap_t  *map = filemap;
+	file_entry_t *entry;
+	file_entry_t **e;
+	file_entry_t key;
+	file_entry_t *key_ptr;
+
+	if (map->array)
+	{
+		key.path = (char *) path;
+		key_ptr = &key;
+		e = bsearch(&key_ptr, map->array, map->narray, sizeof(file_entry_t *),
+					path_cmp);
+	}
+	else
+		e = NULL;
+
+	if (e)
+		entry = *e;
+	else if (!create)
+		entry = NULL;
+	else
+	{
+		/* Create a new entry for this file */
+		entry = pg_malloc(sizeof(file_entry_t));
+		entry->path = pg_strdup(path);
+		entry->isrelfile = isRelDataFile(path);
+		entry->action = FILE_ACTION_UNDECIDED;
+
+		entry->target_exists = false;
+		entry->target_type = FILE_TYPE_UNDEFINED;
+		entry->target_size = 0;
+		entry->target_link_target = NULL;
+		entry->target_modified_pages.bitmap = NULL;
+		entry->target_modified_pages.bitmapsize = 0;
+
+		entry->source_exists = false;
+		entry->source_type = FILE_TYPE_UNDEFINED;
+		entry->source_size = 0;
+		entry->source_link_target = NULL;
+
+		entry->next = NULL;
+
+		if (map->last)
+		{
+			map->last->next = entry;
+			map->last = entry;
+		}
+		else
+			map->first = map->last = entry;
+		map->nlist++;
+	}
+
+	return entry;
+}
+
 /*
  * Callback for processing source file list.
  *
@@ -154,18 +215,19 @@ filemap_create(void)
  * exists in the target and whether the size matches.
  */
 void
-process_source_file(const char *path, file_type_t type, size_t newsize,
+process_source_file(const char *path, file_type_t type, size_t size,
 					const char *link_target)
 {
-	bool		exists;
-	char		localpath[MAXPGPATH];
-	struct stat statbuf;
-	filemap_t  *map = filemap;
-	file_action_t action = FILE_ACTION_NONE;
-	size_t		oldsize = 0;
 	file_entry_t *entry;
 
-	Assert(map->array == NULL);
+	Assert(filemap->array == NULL);
+
+	/*
+	 * Skip the control file. It is handled specially, after copying all the
+	 * other files.
+	 */
+	if (strcmp(path, "global/pg_control") == 0)
+		return;
 
 	/*
 	 * Skip any files matching the exclusion filters. This has the effect to
@@ -199,142 +261,12 @@ process_source_file(const char *path, file_type_t type, size_t newsize,
 	if (type != FILE_TYPE_REGULAR && isRelDataFile(path))
 		pg_fatal("data file \"%s\" in source is not a regular file", path);
 
-	snprintf(localpath, sizeof(localpath), "%s/%s", datadir_target, path);
-
-	/* Does the corresponding file exist in the target data dir? */
-	if (lstat(localpath, &statbuf) < 0)
-	{
-		if (errno != ENOENT)
-			pg_fatal("could not stat file \"%s\": %m",
-					 localpath);
-
-		exists = false;
-	}
-	else
-		exists = true;
-
-	switch (type)
-	{
-		case FILE_TYPE_DIRECTORY:
-			if (exists && !S_ISDIR(statbuf.st_mode) && strcmp(path, "pg_wal") != 0)
-			{
-				/* it's a directory in source, but not in target. Strange.. */
-				pg_fatal("\"%s\" is not a directory", localpath);
-			}
-
-			if (!exists)
-				action = FILE_ACTION_CREATE;
-			else
-				action = FILE_ACTION_NONE;
-			oldsize = 0;
-			break;
-
-		case FILE_TYPE_SYMLINK:
-			if (exists &&
-#ifndef WIN32
-				!S_ISLNK(statbuf.st_mode)
-#else
-				!pgwin32_is_junction(localpath)
-#endif
-				)
-			{
-				/*
-				 * It's a symbolic link in source, but not in target.
-				 * Strange..
-				 */
-				pg_fatal("\"%s\" is not a symbolic link", localpath);
-			}
-
-			if (!exists)
-				action = FILE_ACTION_CREATE;
-			else
-				action = FILE_ACTION_NONE;
-			oldsize = 0;
-			break;
-
-		case FILE_TYPE_REGULAR:
-			if (exists && !S_ISREG(statbuf.st_mode))
-				pg_fatal("\"%s\" is not a regular file", localpath);
-
-			if (!exists || !isRelDataFile(path))
-			{
-				/*
-				 * File exists in source, but not in target. Or it's a
-				 * non-data file that we have no special processing for. Copy
-				 * it in toto.
-				 *
-				 * An exception: PG_VERSIONs should be identical, but avoid
-				 * overwriting it for paranoia.
-				 */
-				if (pg_str_endswith(path, "PG_VERSION"))
-				{
-					action = FILE_ACTION_NONE;
-					oldsize = statbuf.st_size;
-				}
-				else
-				{
-					action = FILE_ACTION_COPY;
-					oldsize = 0;
-				}
-			}
-			else
-			{
-				/*
-				 * It's a data file that exists in both.
-				 *
-				 * If it's larger in target, we can truncate it. There will
-				 * also be a WAL record of the truncation in the source
-				 * system, so WAL replay would eventually truncate the target
-				 * too, but we might as well do it now.
-				 *
-				 * If it's smaller in the target, it means that it has been
-				 * truncated in the target, or enlarged in the source, or
-				 * both. If it was truncated in the target, we need to copy
-				 * the missing tail from the source system. If it was enlarged
-				 * in the source system, there will be WAL records in the
-				 * source system for the new blocks, so we wouldn't need to
-				 * copy them here. But we don't know which scenario we're
-				 * dealing with, and there's no harm in copying the missing
-				 * blocks now, so do it now.
-				 *
-				 * If it's the same size, do nothing here. Any blocks modified
-				 * in the target will be copied based on parsing the target
-				 * system's WAL, and any blocks modified in the source will be
-				 * updated after rewinding, when the source system's WAL is
-				 * replayed.
-				 */
-				oldsize = statbuf.st_size;
-				if (oldsize < newsize)
-					action = FILE_ACTION_COPY_TAIL;
-				else if (oldsize > newsize)
-					action = FILE_ACTION_TRUNCATE;
-				else
-					action = FILE_ACTION_NONE;
-			}
-			break;
-	}
-
-	/* Create a new entry for this file */
-	entry = pg_malloc(sizeof(file_entry_t));
-	entry->path = pg_strdup(path);
-	entry->type = type;
-	entry->action = action;
-	entry->oldsize = oldsize;
-	entry->newsize = newsize;
-	entry->link_target = link_target ? pg_strdup(link_target) : NULL;
-	entry->next = NULL;
-	entry->pagemap.bitmap = NULL;
-	entry->pagemap.bitmapsize = 0;
-	entry->isrelfile = isRelDataFile(path);
-
-	if (map->last)
-	{
-		map->last->next = entry;
-		map->last = entry;
-	}
-	else
-		map->first = map->last = entry;
-	map->nlist++;
+	/* Remember this source file */
+	entry = get_filemap_entry(path, true);
+	entry->source_exists = true;
+	entry->source_type = type;
+	entry->source_size = size;
+	entry->source_link_target = link_target ? pg_strdup(link_target) : NULL;
 }
 
 /*
@@ -345,33 +277,24 @@ process_source_file(const char *path, file_type_t type, size_t newsize,
  * deletion.
  */
 void
-process_target_file(const char *path, file_type_t type, size_t oldsize,
+process_target_file(const char *path, file_type_t type, size_t size,
 					const char *link_target)
 {
-	bool		exists;
-	char		localpath[MAXPGPATH];
-	struct stat statbuf;
-	file_entry_t key;
-	file_entry_t *key_ptr;
 	filemap_t  *map = filemap;
 	file_entry_t *entry;
 
+	/*
+	 * Skip the control file. It is handled specially, after copying all the
+	 * other files.
+	 */
+	if (strcmp(path, "global/pg_control") == 0)
+		return;
+
 	/*
 	 * Do not apply any exclusion filters here.  This has advantage to remove
 	 * from the target data folder all paths which have been filtered out from
 	 * the source data folder when processing the source files.
 	 */
-
-	snprintf(localpath, sizeof(localpath), "%s/%s", datadir_target, path);
-	if (lstat(localpath, &statbuf) < 0)
-	{
-		if (errno != ENOENT)
-			pg_fatal("could not stat file \"%s\": %m",
-					 localpath);
-
-		exists = false;
-	}
-
 	if (map->array == NULL)
 	{
 		/* on first call, initialize lookup array */
@@ -389,120 +312,76 @@ process_target_file(const char *path, file_type_t type, size_t oldsize,
 	}
 
 	/*
-	 * Like in process_source_file, pretend that xlog is always a  directory.
+	 * Like in process_source_file, pretend that pg_wal is always a directory.
 	 */
 	if (strcmp(path, "pg_wal") == 0 && type == FILE_TYPE_SYMLINK)
 		type = FILE_TYPE_DIRECTORY;
 
-	key.path = (char *) path;
-	key_ptr = &key;
-	exists = (bsearch(&key_ptr, map->array, map->narray, sizeof(file_entry_t *),
-					  path_cmp) != NULL);
-
-	/* Remove any file or folder that doesn't exist in the source system. */
-	if (!exists)
-	{
-		entry = pg_malloc(sizeof(file_entry_t));
-		entry->path = pg_strdup(path);
-		entry->type = type;
-		entry->action = FILE_ACTION_REMOVE;
-		entry->oldsize = oldsize;
-		entry->newsize = 0;
-		entry->link_target = link_target ? pg_strdup(link_target) : NULL;
-		entry->next = NULL;
-		entry->pagemap.bitmap = NULL;
-		entry->pagemap.bitmapsize = 0;
-		entry->isrelfile = isRelDataFile(path);
-
-		if (map->last == NULL)
-			map->first = entry;
-		else
-			map->last->next = entry;
-		map->last = entry;
-		map->nlist++;
-	}
-	else
-	{
-		/*
-		 * We already handled all files that exist in the source system in
-		 * process_source_file().
-		 */
-	}
+	/* Remember this target file */
+	entry = get_filemap_entry(path, true);
+	entry->target_exists = true;
+	entry->target_type = type;
+	entry->target_size = size;
+	entry->target_link_target = link_target ? pg_strdup(link_target) : NULL;
 }
 
 /*
  * This callback gets called while we read the WAL in the target, for every
- * block that have changed in the target system. It makes note of all the
+ * block that have changed in the target system.  It makes note of all the
  * changed blocks in the pagemap of the file.
+ *
+ * NOTE: All the files on both systems must have already been added to the
+ * file map!
  */
 void
-process_block_change(ForkNumber forknum, RelFileNode rnode, BlockNumber blkno)
+process_target_wal_block_change(ForkNumber forknum, RelFileNode rnode,
+								BlockNumber blkno)
 {
 	char	   *path;
-	file_entry_t key;
-	file_entry_t *key_ptr;
 	file_entry_t *entry;
 	BlockNumber blkno_inseg;
 	int			segno;
-	filemap_t  *map = filemap;
-	file_entry_t **e;
 
-	Assert(map->array);
+	Assert(filemap->array);
 
 	segno = blkno / RELSEG_SIZE;
 	blkno_inseg = blkno % RELSEG_SIZE;
 
 	path = datasegpath(rnode, forknum, segno);
-
-	key.path = (char *) path;
-	key_ptr = &key;
-
-	e = bsearch(&key_ptr, map->array, map->narray, sizeof(file_entry_t *),
-				path_cmp);
-	if (e)
-		entry = *e;
-	else
-		entry = NULL;
+	entry = get_filemap_entry(path, false);
 	pfree(path);
 
 	if (entry)
 	{
+		int64		end_offset;
+
 		Assert(entry->isrelfile);
 
-		switch (entry->action)
-		{
-			case FILE_ACTION_NONE:
-			case FILE_ACTION_TRUNCATE:
-				/* skip if we're truncating away the modified block anyway */
-				if ((blkno_inseg + 1) * BLCKSZ <= entry->newsize)
-					datapagemap_add(&entry->pagemap, blkno_inseg);
-				break;
-
-			case FILE_ACTION_COPY_TAIL:
-
-				/*
-				 * skip the modified block if it is part of the "tail" that
-				 * we're copying anyway.
-				 */
-				if ((blkno_inseg + 1) * BLCKSZ <= entry->oldsize)
-					datapagemap_add(&entry->pagemap, blkno_inseg);
-				break;
-
-			case FILE_ACTION_COPY:
-			case FILE_ACTION_REMOVE:
-				break;
-
-			case FILE_ACTION_CREATE:
-				pg_fatal("unexpected page modification for directory or symbolic link \"%s\"", entry->path);
-		}
+		if (entry->target_type != FILE_TYPE_REGULAR)
+			pg_fatal("unexpected page modification for directory or symbolic link \"%s\"",
+					 entry->path);
+
+		/*
+		 * If the block beyond the EOF in the source system, no need to
+		 * remember it now, because we're going to truncate it away from the
+		 * target anyway. Also no need to remember the block if it's beyond
+		 * the current EOF in the target system; we will copy it over with the
+		 * "tail" from the source system, anyway.
+		 */
+		end_offset = (blkno_inseg + 1) * BLCKSZ;
+		if (end_offset <= entry->source_size &&
+			end_offset <= entry->target_size)
+			datapagemap_add(&entry->target_modified_pages, blkno_inseg);
 	}
 	else
 	{
 		/*
 		 * If we don't have any record of this file in the file map, it means
-		 * that it's a relation that doesn't exist in the source system, and
-		 * it was subsequently removed in the target system, too. We can
-		 * safely ignore it.
+		 * that it's a relation that doesn't exist in the source system.  It
+		 * could exist in the target system; we haven't moved the target-only
+		 * entries from the linked list to the array yet!  But in any case, if
+		 * it doesn't exist in the source it will be removed from the target
+		 * too, and we can safely ignore it.
 		 */
 	}
 }
@@ -593,16 +472,6 @@ filemap_list_to_array(filemap_t *map)
 	map->first = map->last = NULL;
 }
 
-void
-filemap_finalize(void)
-{
-	filemap_t  *map = filemap;
-
-	filemap_list_to_array(map);
-	qsort(map->array, map->narray, sizeof(file_entry_t *),
-		  final_filemap_cmp);
-}
-
 static const char *
 action_to_str(file_action_t action)
 {
@@ -643,26 +512,26 @@ calculate_totals(void)
 	{
 		entry = map->array[i];
 
-		if (entry->type != FILE_TYPE_REGULAR)
+		if (entry->source_type != FILE_TYPE_REGULAR)
 			continue;
 
-		map->total_size += entry->newsize;
+		map->total_size += entry->source_size;
 
 		if (entry->action == FILE_ACTION_COPY)
 		{
-			map->fetch_size += entry->newsize;
+			map->fetch_size += entry->source_size;
 			continue;
 		}
 
 		if (entry->action == FILE_ACTION_COPY_TAIL)
-			map->fetch_size += (entry->newsize - entry->oldsize);
+			map->fetch_size += (entry->source_size - entry->target_size);
 
-		if (entry->pagemap.bitmapsize > 0)
+		if (entry->target_modified_pages.bitmapsize > 0)
 		{
 			datapagemap_iterator_t *iter;
 			BlockNumber blk;
 
-			iter = datapagemap_iterate(&entry->pagemap);
+			iter = datapagemap_iterate(&entry->target_modified_pages);
 			while (datapagemap_next(iter, &blk))
 				map->fetch_size += BLCKSZ;
 
@@ -682,13 +551,13 @@ print_filemap(void)
 	{
 		entry = map->array[i];
 		if (entry->action != FILE_ACTION_NONE ||
-			entry->pagemap.bitmapsize > 0)
+			entry->target_modified_pages.bitmapsize > 0)
 		{
 			pg_log_debug("%s (%s)", entry->path,
 						 action_to_str(entry->action));
 
-			if (entry->pagemap.bitmapsize > 0)
-				datapagemap_print(&entry->pagemap);
+			if (entry->target_modified_pages.bitmapsize > 0)
+				datapagemap_print(&entry->target_modified_pages);
 		}
 	}
 	fflush(stdout);
@@ -837,3 +706,126 @@ final_filemap_cmp(const void *a, const void *b)
 	else
 		return strcmp(fa->path, fb->path);
 }
+
+/*
+ * Decide what to do with each file.
+ */
+void
+filemap_finalize()
+{
+	int			i;
+
+	filemap_list_to_array(filemap);
+
+	for (i = 0; i < filemap->narray; i++)
+	{
+		file_entry_t *entry = filemap->array[i];
+		file_action_t action;
+
+		if (!entry->target_exists && entry->source_exists)
+		{
+			/*
+			 * File exists in source, but not in target. Copy
+			 * it in toto. (If it's a relation data file, WAL replay
+			 * after rewinding should re-create it anyway. But there's
+			 * no harm in copying it now.)
+			 */
+			if (entry->source_type == FILE_TYPE_DIRECTORY ||
+				entry->source_type == FILE_TYPE_SYMLINK)
+			{
+				action = FILE_ACTION_CREATE;
+			}
+			else
+				action = FILE_ACTION_COPY;
+		}
+		else if (entry->target_exists && !entry->source_exists)
+		{
+			/* Delete file from target */
+			action = FILE_ACTION_REMOVE;
+		}
+		else if (entry->target_exists && entry->source_exists)
+		{
+			/* File exists in both systems */
+			if (entry->source_type != entry->target_type)
+			{
+				/* But it's a different kind of object. Strange.. */
+				pg_fatal("file \"%s\" is of different type in source and target", entry->path);
+			}
+
+			switch (entry->source_type)
+			{
+				case FILE_TYPE_DIRECTORY:
+					action = FILE_ACTION_NONE;
+					break;
+
+				case FILE_TYPE_SYMLINK:
+					// FIXME: Check if it points to the same target?
+					action = FILE_ACTION_NONE;
+					break;
+
+				case FILE_TYPE_REGULAR:
+					if (!entry->isrelfile)
+					{
+						/*
+						 * It's a non-data file that we have no special processing for. Copy
+						 * it in toto.
+						 *
+						 * An exception: PG_VERSIONs should be identical, but avoid
+						 * overwriting it for paranoia.
+						 */
+						if (!pg_str_endswith(entry->path, "PG_VERSION"))
+							action = FILE_ACTION_COPY;
+						else
+							action = FILE_ACTION_NONE;
+					}
+					else
+					{
+						/*
+						 * It's a data file that exists in both systems.
+						 *
+						 * If it's larger in target, we can truncate it. There will
+						 * also be a WAL record of the truncation in the source
+						 * system, so WAL replay would eventually truncate the target
+						 * too, but we might as well do it now.
+						 *
+						 * If it's smaller in the target, it means that it has been
+						 * truncated in the target, or enlarged in the source, or
+						 * both. If it was truncated in the target, we need to copy
+						 * the missing tail from the source system. If it was enlarged
+						 * in the source system, there will be WAL records in the
+						 * source system for the new blocks, so we wouldn't need to
+						 * copy them here. But we don't know which scenario we're
+						 * dealing with, and there's no harm in copying the missing
+						 * blocks now, so do it now.
+						 *
+						 * If it's the same size, do nothing here. Any blocks modified
+						 * in the target will be copied based on parsing the target
+						 * system's WAL, and any blocks modified in the source will be
+						 * updated after rewinding, when the source system's WAL is
+						 * replayed.
+						 */
+						if (entry->target_size < entry->source_size)
+							action = FILE_ACTION_COPY_TAIL;
+						else if (entry->target_size > entry->source_size)
+							action = FILE_ACTION_TRUNCATE;
+						else
+							action = FILE_ACTION_NONE;
+					}
+					break;
+				case FILE_TYPE_UNDEFINED:
+					pg_fatal("unknown file type for \"%s\"", entry->path);
+					break;
+			}
+		}
+		else
+		{
+			/* Doesn't exist in either server. Why does it have an entry at all?? */
+			action = FILE_ACTION_NONE;
+		}
+		entry->action = action;
+	}
+
+	/* Sort the actions to the order that they should be performed */
+	qsort(filemap->array, filemap->narray, sizeof(file_entry_t *),
+		  final_filemap_cmp);
+}
diff --git a/src/bin/pg_rewind/filemap.h b/src/bin/pg_rewind/filemap.h
index 0cb7425170c..a5e8df57f40 100644
--- a/src/bin/pg_rewind/filemap.h
+++ b/src/bin/pg_rewind/filemap.h
@@ -14,17 +14,21 @@
 
 /*
  * For every file found in the local or remote system, we have a file entry
- * which says what we are going to do with the file. For relation files,
- * there is also a page map, marking pages in the file that were changed
- * locally.
- *
- * The enum values are sorted in the order we want actions to be processed.
+ * that contains information about the file on both systems.  For relation
+ * files, there is also a page map that marks pages in the file that were
+ * changed in the target after the last common checkpoint.  Each entry also
+ * contains an 'action' field, which says what we are going to do with the
+ * file.
  */
+
+/* these enum values are sorted in the order we want actions to be processed */
 typedef enum
 {
+	FILE_ACTION_UNDECIDED = 0,	/* not decided yet */
+
 	FILE_ACTION_CREATE,			/* create local directory or symbolic link */
 	FILE_ACTION_COPY,			/* copy whole file, overwriting if exists */
-	FILE_ACTION_COPY_TAIL,		/* copy tail from 'oldsize' to 'newsize' */
+	FILE_ACTION_COPY_TAIL,		/* copy tail from 'source_size' to 'target_size' */
 	FILE_ACTION_NONE,			/* no action (we might still copy modified
 								 * blocks based on the parsed WAL) */
 	FILE_ACTION_TRUNCATE,		/* truncate local file to 'newsize' bytes */
@@ -33,6 +37,8 @@ typedef enum
 
 typedef enum
 {
+	FILE_TYPE_UNDEFINED = 0,
+
 	FILE_TYPE_REGULAR,
 	FILE_TYPE_DIRECTORY,
 	FILE_TYPE_SYMLINK
@@ -41,19 +47,30 @@ typedef enum
 typedef struct file_entry_t
 {
 	char	   *path;
-	file_type_t type;
+	bool		isrelfile;		/* is it a relation data file? */
 
-	file_action_t action;
+	/*
+	 * Status of the file in the target.
+	 */
+	bool		target_exists;
+	file_type_t target_type;
+	size_t		target_size; /* for a regular file */
+	char	   *target_link_target; /* for a symlink */
 
-	/* for a regular file */
-	size_t		oldsize;
-	size_t		newsize;
-	bool		isrelfile;		/* is it a relation data file? */
+	datapagemap_t target_modified_pages;
 
-	datapagemap_t pagemap;
+	/*
+	 * Status of the file in the source.
+	 */
+	bool		source_exists;
+	file_type_t source_type;
+	size_t		source_size;
+	char	   *source_link_target; /* for a symlink */
 
-	/* for a symlink */
-	char	   *link_target;
+	/*
+	 * What will we do to the file?
+	 */
+	file_action_t action;
 
 	struct file_entry_t *next;
 } file_entry_t;
@@ -70,20 +87,19 @@ typedef struct filemap_t
 
 	/*
 	 * After processing all the remote files, the entries in the linked list
-	 * are moved to this array. After processing local files, too, all the
+	 * are moved to this array.  After processing local files, too, all the
 	 * local entries are added to the array by filemap_finalize, and sorted in
-	 * the final order. After filemap_finalize, all the entries are in the
+	 * the final order.  After filemap_finalize, all the entries are in the
 	 * array, and the linked list is empty.
 	 */
 	file_entry_t **array;
 	int			narray;			/* current length of array */
 
 	/*
-	 * Summary information. total_size is the total size of the source
-	 * cluster, and fetch_size is the number of bytes that needs to be copied.
+	 * Summary information.
 	 */
-	uint64		total_size;
-	uint64		fetch_size;
+	uint64		total_size;		/* total size of the source cluster */
+	uint64		fetch_size;		/* number of bytes that needs to be copied */
 } filemap_t;
 
 extern filemap_t *filemap;
@@ -94,11 +110,12 @@ extern void print_filemap(void);
 
 /* Functions for populating the filemap */
 extern void process_source_file(const char *path, file_type_t type,
-								size_t newsize, const char *link_target);
+								size_t size, const char *link_target);
 extern void process_target_file(const char *path, file_type_t type,
-								size_t newsize, const char *link_target);
-extern void process_block_change(ForkNumber forknum, RelFileNode rnode,
-								 BlockNumber blkno);
+								size_t size, const char *link_target);
+extern void process_target_wal_block_change(ForkNumber forknum,
+											RelFileNode rnode,
+											BlockNumber blkno);
 extern void filemap_finalize(void);
 
 #endif							/* FILEMAP_H */
diff --git a/src/bin/pg_rewind/libpq_fetch.c b/src/bin/pg_rewind/libpq_fetch.c
index bf4dfc23b96..7fc9161b8c8 100644
--- a/src/bin/pg_rewind/libpq_fetch.c
+++ b/src/bin/pg_rewind/libpq_fetch.c
@@ -465,7 +465,7 @@ libpq_executeFileMap(filemap_t *map)
 		entry = map->array[i];
 
 		/* If this is a relation file, copy the modified blocks */
-		execute_pagemap(&entry->pagemap, entry->path);
+		execute_pagemap(&entry->target_modified_pages, entry->path);
 
 		switch (entry->action)
 		{
@@ -476,15 +476,15 @@ libpq_executeFileMap(filemap_t *map)
 			case FILE_ACTION_COPY:
 				/* Truncate the old file out of the way, if any */
 				open_target_file(entry->path, true);
-				fetch_file_range(entry->path, 0, entry->newsize);
+				fetch_file_range(entry->path, 0, entry->source_size);
 				break;
 
 			case FILE_ACTION_TRUNCATE:
-				truncate_target_file(entry->path, entry->newsize);
+				truncate_target_file(entry->path, entry->source_size);
 				break;
 
 			case FILE_ACTION_COPY_TAIL:
-				fetch_file_range(entry->path, entry->oldsize, entry->newsize);
+				fetch_file_range(entry->path, entry->target_size, entry->source_size);
 				break;
 
 			case FILE_ACTION_REMOVE:
@@ -494,6 +494,10 @@ libpq_executeFileMap(filemap_t *map)
 			case FILE_ACTION_CREATE:
 				create_target(entry);
 				break;
+
+			case FILE_ACTION_UNDECIDED:
+				pg_fatal("no action decided for \"%s\"", entry->path);
+				break;
 		}
 	}
 
diff --git a/src/bin/pg_rewind/parsexlog.c b/src/bin/pg_rewind/parsexlog.c
index 2229c86f9af..2baeb74ae93 100644
--- a/src/bin/pg_rewind/parsexlog.c
+++ b/src/bin/pg_rewind/parsexlog.c
@@ -436,6 +436,6 @@ extractPageInfo(XLogReaderState *record)
 		if (forknum != MAIN_FORKNUM)
 			continue;
 
-		process_block_change(forknum, rnode, blkno);
+		process_target_wal_block_change(forknum, rnode, blkno);
 	}
 }
diff --git a/src/bin/pg_rewind/pg_rewind.c b/src/bin/pg_rewind/pg_rewind.c
index c9b9e480c0f..210984d302b 100644
--- a/src/bin/pg_rewind/pg_rewind.c
+++ b/src/bin/pg_rewind/pg_rewind.c
@@ -369,7 +369,7 @@ main(int argc, char **argv)
 				chkpttli);
 
 	/*
-	 * Build the filemap, by comparing the source and target data directories.
+	 * Collect information about all files in the target and source systems.
 	 */
 	filemap_create();
 	if (showprogress)
@@ -390,8 +390,12 @@ main(int argc, char **argv)
 		pg_log_info("reading WAL in target");
 	extractPageMap(datadir_target, chkptrec, lastcommontliIndex,
 				   ControlFile_target.checkPoint, restore_command);
-	filemap_finalize();
 
+	/*
+	 * We have collected all information we need from both systems. Decide
+	 * what to do with each file.
+	 */
+	filemap_finalize();
 	if (showprogress)
 		calculate_totals();
 
-- 
2.20.1


--------------D93EDEBFB124D563B723F4BD
Content-Type: text/x-patch; charset=UTF-8;
 name="0003-pg_rewind-Replace-the-hybrid-list-array-data-structu.patch"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
 filename*0="0003-pg_rewind-Replace-the-hybrid-list-array-data-structu.pa";
 filename*1="tch"



^ permalink  raw  reply  [nested|flat] 41+ messages in thread

* [PATCH v3 2/5] Refactor pg_rewind for more clear decision making.
@ 2020-09-24 17:08 Heikki Linnakangas <[email protected]>
  0 siblings, 0 replies; 41+ messages in thread

From: Heikki Linnakangas @ 2020-09-24 17:08 UTC (permalink / raw)

Deciding what to do with each file is now a separate step after all the
necessary information has been gathered. It is more clear that way.
Previously, the decision-making was divided between process_source_file()
and process_target_file(), and it was a bit hard to piece together what the
overall rules were.

Reviewed-by: Kyotaro Horiguchi, Soumyadeep Chakraborty
Discussion: https://www.postgresql.org/message-id/0c5b3783-af52-3ee5-f8fa-6e794061f70d%40iki.fi
---
 src/bin/pg_rewind/copy_fetch.c  |  14 +-
 src/bin/pg_rewind/file_ops.c    |  16 +-
 src/bin/pg_rewind/filemap.c     | 571 +++++++++++++++++---------------
 src/bin/pg_rewind/filemap.h     |  69 ++--
 src/bin/pg_rewind/libpq_fetch.c |  12 +-
 src/bin/pg_rewind/parsexlog.c   |   2 +-
 src/bin/pg_rewind/pg_rewind.c   |   8 +-
 7 files changed, 381 insertions(+), 311 deletions(-)

diff --git a/src/bin/pg_rewind/copy_fetch.c b/src/bin/pg_rewind/copy_fetch.c
index 1edab5f1867..e4b8ce6aaf4 100644
--- a/src/bin/pg_rewind/copy_fetch.c
+++ b/src/bin/pg_rewind/copy_fetch.c
@@ -210,7 +210,7 @@ copy_executeFileMap(filemap_t *map)
 	for (i = 0; i < map->narray; i++)
 	{
 		entry = map->array[i];
-		execute_pagemap(&entry->pagemap, entry->path);
+		execute_pagemap(&entry->target_pages_to_overwrite, entry->path);
 
 		switch (entry->action)
 		{
@@ -219,16 +219,16 @@ copy_executeFileMap(filemap_t *map)
 				break;
 
 			case FILE_ACTION_COPY:
-				rewind_copy_file_range(entry->path, 0, entry->newsize, true);
+				rewind_copy_file_range(entry->path, 0, entry->source_size, true);
 				break;
 
 			case FILE_ACTION_TRUNCATE:
-				truncate_target_file(entry->path, entry->newsize);
+				truncate_target_file(entry->path, entry->source_size);
 				break;
 
 			case FILE_ACTION_COPY_TAIL:
-				rewind_copy_file_range(entry->path, entry->oldsize,
-									   entry->newsize, false);
+				rewind_copy_file_range(entry->path, entry->target_size,
+									   entry->source_size, false);
 				break;
 
 			case FILE_ACTION_CREATE:
@@ -238,6 +238,10 @@ copy_executeFileMap(filemap_t *map)
 			case FILE_ACTION_REMOVE:
 				remove_target(entry);
 				break;
+
+			case FILE_ACTION_UNDECIDED:
+				pg_fatal("no action decided for \"%s\"", entry->path);
+				break;
 		}
 	}
 
diff --git a/src/bin/pg_rewind/file_ops.c b/src/bin/pg_rewind/file_ops.c
index 55439db20ba..ec37d0b2e0d 100644
--- a/src/bin/pg_rewind/file_ops.c
+++ b/src/bin/pg_rewind/file_ops.c
@@ -126,8 +126,9 @@ void
 remove_target(file_entry_t *entry)
 {
 	Assert(entry->action == FILE_ACTION_REMOVE);
+	Assert(entry->target_exists);
 
-	switch (entry->type)
+	switch (entry->target_type)
 	{
 		case FILE_TYPE_DIRECTORY:
 			remove_target_dir(entry->path);
@@ -140,6 +141,10 @@ remove_target(file_entry_t *entry)
 		case FILE_TYPE_SYMLINK:
 			remove_target_symlink(entry->path);
 			break;
+
+		case FILE_TYPE_UNDEFINED:
+			pg_fatal("undefined file type for \"%s\"", entry->path);
+			break;
 	}
 }
 
@@ -147,21 +152,26 @@ void
 create_target(file_entry_t *entry)
 {
 	Assert(entry->action == FILE_ACTION_CREATE);
+	Assert(!entry->target_exists);
 
-	switch (entry->type)
+	switch (entry->source_type)
 	{
 		case FILE_TYPE_DIRECTORY:
 			create_target_dir(entry->path);
 			break;
 
 		case FILE_TYPE_SYMLINK:
-			create_target_symlink(entry->path, entry->link_target);
+			create_target_symlink(entry->path, entry->source_link_target);
 			break;
 
 		case FILE_TYPE_REGULAR:
 			/* can't happen. Regular files are created with open_target_file. */
 			pg_fatal("invalid action (CREATE) for regular file");
 			break;
+
+		case FILE_TYPE_UNDEFINED:
+			pg_fatal("undefined file type for \"%s\"", entry->path);
+			break;
 	}
 }
 
diff --git a/src/bin/pg_rewind/filemap.c b/src/bin/pg_rewind/filemap.c
index 1abc257177e..79e5bfdc7d1 100644
--- a/src/bin/pg_rewind/filemap.c
+++ b/src/bin/pg_rewind/filemap.c
@@ -26,6 +26,8 @@ static bool isRelDataFile(const char *path);
 static char *datasegpath(RelFileNode rnode, ForkNumber forknum,
 						 BlockNumber segno);
 static int	path_cmp(const void *a, const void *b);
+
+static file_entry_t *get_filemap_entry(const char *path, bool create);
 static int	final_filemap_cmp(const void *a, const void *b);
 static void filemap_list_to_array(filemap_t *map);
 static bool check_file_excluded(const char *path, bool is_source);
@@ -146,33 +148,79 @@ filemap_create(void)
 	filemap = map;
 }
 
+/* Look up or create entry for 'path' */
+static file_entry_t *
+get_filemap_entry(const char *path, bool create)
+{
+	filemap_t  *map = filemap;
+	file_entry_t *entry;
+	file_entry_t **e;
+	file_entry_t key;
+	file_entry_t *key_ptr;
+
+	if (map->array)
+	{
+		key.path = (char *) path;
+		key_ptr = &key;
+		e = bsearch(&key_ptr, map->array, map->narray, sizeof(file_entry_t *),
+					path_cmp);
+	}
+	else
+		e = NULL;
+
+	if (e)
+		entry = *e;
+	else if (!create)
+		entry = NULL;
+	else
+	{
+		/* Create a new entry for this file */
+		entry = pg_malloc(sizeof(file_entry_t));
+		entry->path = pg_strdup(path);
+		entry->isrelfile = isRelDataFile(path);
+		entry->action = FILE_ACTION_UNDECIDED;
+
+		entry->target_exists = false;
+		entry->target_type = FILE_TYPE_UNDEFINED;
+		entry->target_size = 0;
+		entry->target_link_target = NULL;
+		entry->target_pages_to_overwrite.bitmap = NULL;
+		entry->target_pages_to_overwrite.bitmapsize = 0;
+
+		entry->source_exists = false;
+		entry->source_type = FILE_TYPE_UNDEFINED;
+		entry->source_size = 0;
+		entry->source_link_target = NULL;
+
+		entry->next = NULL;
+
+		if (map->last)
+		{
+			map->last->next = entry;
+			map->last = entry;
+		}
+		else
+			map->first = map->last = entry;
+		map->nlist++;
+	}
+
+	return entry;
+}
+
 /*
  * Callback for processing source file list.
  *
- * This is called once for every file in the source server. We decide what
- * action needs to be taken for the file, depending on whether the file
- * exists in the target and whether the size matches.
+ * This is called once for every file in the source server.  We record the
+ * type and size of file, so that decide_file_action() can later decide what
+ * to do with it.
  */
 void
-process_source_file(const char *path, file_type_t type, size_t newsize,
+process_source_file(const char *path, file_type_t type, size_t size,
 					const char *link_target)
 {
-	bool		exists;
-	char		localpath[MAXPGPATH];
-	struct stat statbuf;
-	filemap_t  *map = filemap;
-	file_action_t action = FILE_ACTION_NONE;
-	size_t		oldsize = 0;
 	file_entry_t *entry;
 
-	Assert(map->array == NULL);
-
-	/*
-	 * Skip any files matching the exclusion filters. This has the effect to
-	 * remove all those files on the target.
-	 */
-	if (check_file_excluded(path, true))
-		return;
+	Assert(filemap->array == NULL);
 
 	/*
 	 * Pretend that pg_wal is a directory, even if it's really a symlink. We
@@ -182,16 +230,6 @@ process_source_file(const char *path, file_type_t type, size_t newsize,
 	if (strcmp(path, "pg_wal") == 0 && type == FILE_TYPE_SYMLINK)
 		type = FILE_TYPE_DIRECTORY;
 
-	/*
-	 * Skip temporary files, .../pgsql_tmp/... and .../pgsql_tmp.* in source.
-	 * This has the effect that all temporary files in the destination will be
-	 * removed.
-	 */
-	if (strstr(path, "/" PG_TEMP_FILE_PREFIX) != NULL)
-		return;
-	if (strstr(path, "/" PG_TEMP_FILES_DIR "/") != NULL)
-		return;
-
 	/*
 	 * sanity check: a filename that looks like a data file better be a
 	 * regular file
@@ -199,158 +237,25 @@ process_source_file(const char *path, file_type_t type, size_t newsize,
 	if (type != FILE_TYPE_REGULAR && isRelDataFile(path))
 		pg_fatal("data file \"%s\" in source is not a regular file", path);
 
-	snprintf(localpath, sizeof(localpath), "%s/%s", datadir_target, path);
-
-	/* Does the corresponding file exist in the target data dir? */
-	if (lstat(localpath, &statbuf) < 0)
-	{
-		if (errno != ENOENT)
-			pg_fatal("could not stat file \"%s\": %m",
-					 localpath);
-
-		exists = false;
-	}
-	else
-		exists = true;
-
-	switch (type)
-	{
-		case FILE_TYPE_DIRECTORY:
-			if (exists && !S_ISDIR(statbuf.st_mode) && strcmp(path, "pg_wal") != 0)
-			{
-				/* it's a directory in source, but not in target. Strange.. */
-				pg_fatal("\"%s\" is not a directory", localpath);
-			}
-
-			if (!exists)
-				action = FILE_ACTION_CREATE;
-			else
-				action = FILE_ACTION_NONE;
-			oldsize = 0;
-			break;
-
-		case FILE_TYPE_SYMLINK:
-			if (exists &&
-#ifndef WIN32
-				!S_ISLNK(statbuf.st_mode)
-#else
-				!pgwin32_is_junction(localpath)
-#endif
-				)
-			{
-				/*
-				 * It's a symbolic link in source, but not in target.
-				 * Strange..
-				 */
-				pg_fatal("\"%s\" is not a symbolic link", localpath);
-			}
-
-			if (!exists)
-				action = FILE_ACTION_CREATE;
-			else
-				action = FILE_ACTION_NONE;
-			oldsize = 0;
-			break;
-
-		case FILE_TYPE_REGULAR:
-			if (exists && !S_ISREG(statbuf.st_mode))
-				pg_fatal("\"%s\" is not a regular file", localpath);
-
-			if (!exists || !isRelDataFile(path))
-			{
-				/*
-				 * File exists in source, but not in target. Or it's a
-				 * non-data file that we have no special processing for. Copy
-				 * it in toto.
-				 *
-				 * An exception: PG_VERSIONs should be identical, but avoid
-				 * overwriting it for paranoia.
-				 */
-				if (pg_str_endswith(path, "PG_VERSION"))
-				{
-					action = FILE_ACTION_NONE;
-					oldsize = statbuf.st_size;
-				}
-				else
-				{
-					action = FILE_ACTION_COPY;
-					oldsize = 0;
-				}
-			}
-			else
-			{
-				/*
-				 * It's a data file that exists in both.
-				 *
-				 * If it's larger in target, we can truncate it. There will
-				 * also be a WAL record of the truncation in the source
-				 * system, so WAL replay would eventually truncate the target
-				 * too, but we might as well do it now.
-				 *
-				 * If it's smaller in the target, it means that it has been
-				 * truncated in the target, or enlarged in the source, or
-				 * both. If it was truncated in the target, we need to copy
-				 * the missing tail from the source system. If it was enlarged
-				 * in the source system, there will be WAL records in the
-				 * source system for the new blocks, so we wouldn't need to
-				 * copy them here. But we don't know which scenario we're
-				 * dealing with, and there's no harm in copying the missing
-				 * blocks now, so do it now.
-				 *
-				 * If it's the same size, do nothing here. Any blocks modified
-				 * in the target will be copied based on parsing the target
-				 * system's WAL, and any blocks modified in the source will be
-				 * updated after rewinding, when the source system's WAL is
-				 * replayed.
-				 */
-				oldsize = statbuf.st_size;
-				if (oldsize < newsize)
-					action = FILE_ACTION_COPY_TAIL;
-				else if (oldsize > newsize)
-					action = FILE_ACTION_TRUNCATE;
-				else
-					action = FILE_ACTION_NONE;
-			}
-			break;
-	}
-
-	/* Create a new entry for this file */
-	entry = pg_malloc(sizeof(file_entry_t));
-	entry->path = pg_strdup(path);
-	entry->type = type;
-	entry->action = action;
-	entry->oldsize = oldsize;
-	entry->newsize = newsize;
-	entry->link_target = link_target ? pg_strdup(link_target) : NULL;
-	entry->next = NULL;
-	entry->pagemap.bitmap = NULL;
-	entry->pagemap.bitmapsize = 0;
-	entry->isrelfile = isRelDataFile(path);
-
-	if (map->last)
-	{
-		map->last->next = entry;
-		map->last = entry;
-	}
-	else
-		map->first = map->last = entry;
-	map->nlist++;
+	/* Remember this source file */
+	entry = get_filemap_entry(path, true);
+	entry->source_exists = true;
+	entry->source_type = type;
+	entry->source_size = size;
+	entry->source_link_target = link_target ? pg_strdup(link_target) : NULL;
 }
 
 /*
  * Callback for processing target file list.
  *
- * All source files must be already processed before calling this. This only
- * marks target data directory's files that didn't exist in the source for
- * deletion.
+ * All source files must be already processed before calling this.  We record
+ * the type and size of file, so that decide_file_action() can later decide
+ * what to do with it.
  */
 void
-process_target_file(const char *path, file_type_t type, size_t oldsize,
+process_target_file(const char *path, file_type_t type, size_t size,
 					const char *link_target)
 {
-	bool		exists;
-	file_entry_t key;
-	file_entry_t *key_ptr;
 	filemap_t  *map = filemap;
 	file_entry_t *entry;
 
@@ -359,7 +264,6 @@ process_target_file(const char *path, file_type_t type, size_t oldsize,
 	 * from the target data folder all paths which have been filtered out from
 	 * the source data folder when processing the source files.
 	 */
-
 	if (map->array == NULL)
 	{
 		/* on first call, initialize lookup array */
@@ -377,120 +281,77 @@ process_target_file(const char *path, file_type_t type, size_t oldsize,
 	}
 
 	/*
-	 * Like in process_source_file, pretend that xlog is always a  directory.
+	 * Like in process_source_file, pretend that pg_wal is always a directory.
 	 */
 	if (strcmp(path, "pg_wal") == 0 && type == FILE_TYPE_SYMLINK)
 		type = FILE_TYPE_DIRECTORY;
 
-	key.path = (char *) path;
-	key_ptr = &key;
-	exists = (bsearch(&key_ptr, map->array, map->narray, sizeof(file_entry_t *),
-					  path_cmp) != NULL);
-
-	/* Remove any file or folder that doesn't exist in the source system. */
-	if (!exists)
-	{
-		entry = pg_malloc(sizeof(file_entry_t));
-		entry->path = pg_strdup(path);
-		entry->type = type;
-		entry->action = FILE_ACTION_REMOVE;
-		entry->oldsize = oldsize;
-		entry->newsize = 0;
-		entry->link_target = link_target ? pg_strdup(link_target) : NULL;
-		entry->next = NULL;
-		entry->pagemap.bitmap = NULL;
-		entry->pagemap.bitmapsize = 0;
-		entry->isrelfile = isRelDataFile(path);
-
-		if (map->last == NULL)
-			map->first = entry;
-		else
-			map->last->next = entry;
-		map->last = entry;
-		map->nlist++;
-	}
-	else
-	{
-		/*
-		 * We already handled all files that exist in the source system in
-		 * process_source_file().
-		 */
-	}
+	/* Remember this target file */
+	entry = get_filemap_entry(path, true);
+	entry->target_exists = true;
+	entry->target_type = type;
+	entry->target_size = size;
+	entry->target_link_target = link_target ? pg_strdup(link_target) : NULL;
 }
 
 /*
  * This callback gets called while we read the WAL in the target, for every
- * block that have changed in the target system. It makes note of all the
- * changed blocks in the pagemap of the file.
+ * block that has changed in the target system.  It decides if the given
+ * 'blkno' in the target relfile needs to be overwritten from the source, and
+ * if so, records it in 'target_pages_to_overwrite' bitmap.
+ *
+ * NOTE: All the files on both systems must have already been added to the
+ * file map!
  */
 void
-process_block_change(ForkNumber forknum, RelFileNode rnode, BlockNumber blkno)
+process_target_wal_block_change(ForkNumber forknum, RelFileNode rnode,
+								BlockNumber blkno)
 {
 	char	   *path;
-	file_entry_t key;
-	file_entry_t *key_ptr;
 	file_entry_t *entry;
 	BlockNumber blkno_inseg;
 	int			segno;
-	filemap_t  *map = filemap;
-	file_entry_t **e;
 
-	Assert(map->array);
+	Assert(filemap->array);
 
 	segno = blkno / RELSEG_SIZE;
 	blkno_inseg = blkno % RELSEG_SIZE;
 
 	path = datasegpath(rnode, forknum, segno);
-
-	key.path = (char *) path;
-	key_ptr = &key;
-
-	e = bsearch(&key_ptr, map->array, map->narray, sizeof(file_entry_t *),
-				path_cmp);
-	if (e)
-		entry = *e;
-	else
-		entry = NULL;
+	entry = get_filemap_entry(path, false);
 	pfree(path);
 
 	if (entry)
 	{
-		Assert(entry->isrelfile);
+		int64		end_offset;
 
-		switch (entry->action)
-		{
-			case FILE_ACTION_NONE:
-			case FILE_ACTION_TRUNCATE:
-				/* skip if we're truncating away the modified block anyway */
-				if ((blkno_inseg + 1) * BLCKSZ <= entry->newsize)
-					datapagemap_add(&entry->pagemap, blkno_inseg);
-				break;
-
-			case FILE_ACTION_COPY_TAIL:
-
-				/*
-				 * skip the modified block if it is part of the "tail" that
-				 * we're copying anyway.
-				 */
-				if ((blkno_inseg + 1) * BLCKSZ <= entry->oldsize)
-					datapagemap_add(&entry->pagemap, blkno_inseg);
-				break;
+		Assert(entry->isrelfile);
 
-			case FILE_ACTION_COPY:
-			case FILE_ACTION_REMOVE:
-				break;
+		if (entry->target_type != FILE_TYPE_REGULAR)
+			pg_fatal("unexpected page modification for non-regular file \"%s\"",
+					 entry->path);
 
-			case FILE_ACTION_CREATE:
-				pg_fatal("unexpected page modification for directory or symbolic link \"%s\"", entry->path);
-		}
+		/*
+		 * If the block beyond the EOF in the source system, no need to
+		 * remember it now, because we're going to truncate it away from the
+		 * target anyway. Also no need to remember the block if it's beyond
+		 * the current EOF in the target system; we will copy it over with the
+		 * "tail" from the source system, anyway.
+		 */
+		end_offset = (blkno_inseg + 1) * BLCKSZ;
+		if (end_offset <= entry->source_size &&
+			end_offset <= entry->target_size)
+			datapagemap_add(&entry->target_pages_to_overwrite, blkno_inseg);
 	}
 	else
 	{
 		/*
 		 * If we don't have any record of this file in the file map, it means
-		 * that it's a relation that doesn't exist in the source system, and
-		 * it was subsequently removed in the target system, too. We can
-		 * safely ignore it.
+		 * that it's a relation that doesn't exist in the source system.  It
+		 * could exist in the target system; we haven't moved the target-only
+		 * entries from the linked list to the array yet!  But in any case, if
+		 * it doesn't exist in the source it will be removed from the target
+		 * too, and we can safely ignore it.
 		 */
 	}
 }
@@ -505,6 +366,15 @@ check_file_excluded(const char *path, bool is_source)
 	int			excludeIdx;
 	const char *filename;
 
+	/*
+	 * Skip all temporary files, .../pgsql_tmp/... and .../pgsql_tmp.*
+	 */
+	if (strstr(path, "/" PG_TEMP_FILE_PREFIX) != NULL ||
+		strstr(path, "/" PG_TEMP_FILES_DIR "/") != NULL)
+	{
+		return true;
+	}
+
 	/* check individual files... */
 	for (excludeIdx = 0; excludeFiles[excludeIdx].name != NULL; excludeIdx++)
 	{
@@ -581,16 +451,6 @@ filemap_list_to_array(filemap_t *map)
 	map->first = map->last = NULL;
 }
 
-void
-filemap_finalize(void)
-{
-	filemap_t  *map = filemap;
-
-	filemap_list_to_array(map);
-	qsort(map->array, map->narray, sizeof(file_entry_t *),
-		  final_filemap_cmp);
-}
-
 static const char *
 action_to_str(file_action_t action)
 {
@@ -631,26 +491,26 @@ calculate_totals(void)
 	{
 		entry = map->array[i];
 
-		if (entry->type != FILE_TYPE_REGULAR)
+		if (entry->source_type != FILE_TYPE_REGULAR)
 			continue;
 
-		map->total_size += entry->newsize;
+		map->total_size += entry->source_size;
 
 		if (entry->action == FILE_ACTION_COPY)
 		{
-			map->fetch_size += entry->newsize;
+			map->fetch_size += entry->source_size;
 			continue;
 		}
 
 		if (entry->action == FILE_ACTION_COPY_TAIL)
-			map->fetch_size += (entry->newsize - entry->oldsize);
+			map->fetch_size += (entry->source_size - entry->target_size);
 
-		if (entry->pagemap.bitmapsize > 0)
+		if (entry->target_pages_to_overwrite.bitmapsize > 0)
 		{
 			datapagemap_iterator_t *iter;
 			BlockNumber blk;
 
-			iter = datapagemap_iterate(&entry->pagemap);
+			iter = datapagemap_iterate(&entry->target_pages_to_overwrite);
 			while (datapagemap_next(iter, &blk))
 				map->fetch_size += BLCKSZ;
 
@@ -670,13 +530,13 @@ print_filemap(void)
 	{
 		entry = map->array[i];
 		if (entry->action != FILE_ACTION_NONE ||
-			entry->pagemap.bitmapsize > 0)
+			entry->target_pages_to_overwrite.bitmapsize > 0)
 		{
 			pg_log_debug("%s (%s)", entry->path,
 						 action_to_str(entry->action));
 
-			if (entry->pagemap.bitmapsize > 0)
-				datapagemap_print(&entry->pagemap);
+			if (entry->target_pages_to_overwrite.bitmapsize > 0)
+				datapagemap_print(&entry->target_pages_to_overwrite);
 		}
 	}
 	fflush(stdout);
@@ -825,3 +685,170 @@ final_filemap_cmp(const void *a, const void *b)
 	else
 		return strcmp(fa->path, fb->path);
 }
+
+/*
+ * Decide what action to perform to a file.
+ */
+static file_action_t
+decide_file_action(file_entry_t *entry)
+{
+	const char *path = entry->path;
+
+	/*
+	 * Don't touch the control file. It is handled specially, after copying
+	 * all the other files.
+	 */
+	if (strcmp(path, "global/pg_control") == 0)
+		return FILE_ACTION_NONE;
+
+	/*
+	 * Remove all files matching the exclusion filters in the target.
+	 */
+	if (check_file_excluded(path, true))
+	{
+		if (entry->target_exists)
+			return FILE_ACTION_REMOVE;
+		else
+			return FILE_ACTION_NONE;
+	}
+
+	/*
+	 * Handle cases where the file is missing from one of the systems.
+	 */
+	if (!entry->target_exists && entry->source_exists)
+	{
+		/*
+		 * File exists in source, but not in target. Copy it in toto. (If it's
+		 * a relation data file, WAL replay after rewinding should re-create
+		 * it anyway. But there's no harm in copying it now.)
+		 */
+		switch (entry->source_type)
+		{
+			case FILE_TYPE_DIRECTORY:
+			case FILE_TYPE_SYMLINK:
+				return FILE_ACTION_CREATE;
+			case FILE_TYPE_REGULAR:
+				return FILE_ACTION_COPY;
+			case FILE_TYPE_UNDEFINED:
+				pg_fatal("unknown file type for \"%s\"", entry->path);
+				break;
+		}
+	}
+	else if (entry->target_exists && !entry->source_exists)
+	{
+		/* File exists in target, but not source. Remove it. */
+		return FILE_ACTION_REMOVE;
+	}
+	else if (!entry->target_exists && !entry->source_exists)
+	{
+		/*
+		 * Doesn't exist in either server. Why does it have an entry in the
+		 * first place??
+		 */
+		Assert(false);
+		return FILE_ACTION_NONE;
+	}
+
+	/*
+	 * Otherwise, the file exists on both systems
+	 */
+	Assert(entry->target_exists && entry->source_exists);
+
+	if (entry->source_type != entry->target_type)
+	{
+		/* But it's a different kind of object. Strange.. */
+		pg_fatal("file \"%s\" is of different type in source and target", entry->path);
+	}
+
+	/*
+	 * PG_VERSION files should be identical on both systems, but avoid
+	 * overwriting them for paranoia.
+	 */
+	if (pg_str_endswith(entry->path, "PG_VERSION"))
+		return FILE_ACTION_NONE;
+
+	switch (entry->source_type)
+	{
+		case FILE_TYPE_DIRECTORY:
+			return FILE_ACTION_NONE;
+
+		case FILE_TYPE_SYMLINK:
+			/*
+			 * XXX: Should we check if it points to the same target?
+			 */
+			return FILE_ACTION_NONE;
+
+		case FILE_TYPE_REGULAR:
+			if (!entry->isrelfile)
+			{
+				/*
+				 * It's a non-data file that we have no special processing
+				 * for. Copy it in toto.
+				 */
+				return FILE_ACTION_COPY;
+			}
+			else
+			{
+				/*
+				 * It's a data file that exists in both systems.
+				 *
+				 * If it's larger in target, we can truncate it. There will
+				 * also be a WAL record of the truncation in the source
+				 * system, so WAL replay would eventually truncate the target
+				 * too, but we might as well do it now.
+				 *
+				 * If it's smaller in the target, it means that it has been
+				 * truncated in the target, or enlarged in the source, or
+				 * both. If it was truncated in the target, we need to copy
+				 * the missing tail from the source system. If it was enlarged
+				 * in the source system, there will be WAL records in the
+				 * source system for the new blocks, so we wouldn't need to
+				 * copy them here. But we don't know which scenario we're
+				 * dealing with, and there's no harm in copying the missing
+				 * blocks now, so do it now.
+				 *
+				 * If it's the same size, do nothing here. Any blocks modified
+				 * in the target will be copied based on parsing the target
+				 * system's WAL, and any blocks modified in the source will be
+				 * updated after rewinding, when the source system's WAL is
+				 * replayed.
+				 */
+				if (entry->target_size < entry->source_size)
+					return FILE_ACTION_COPY_TAIL;
+				else if (entry->target_size > entry->source_size)
+					return FILE_ACTION_TRUNCATE;
+				else
+					return FILE_ACTION_NONE;
+			}
+			break;
+
+		case FILE_TYPE_UNDEFINED:
+			pg_fatal("unknown file type for \"%s\"", path);
+			break;
+	}
+
+	/* unreachable */
+	pg_fatal("could not decide what to do with file \"%s\"", path);
+}
+
+/*
+ * Decide what to do with each file.
+ */
+void
+decide_file_actions()
+{
+	int			i;
+
+	filemap_list_to_array(filemap);
+
+	for (i = 0; i < filemap->narray; i++)
+	{
+		file_entry_t *entry = filemap->array[i];
+
+		entry->action = decide_file_action(entry);
+	}
+
+	/* Sort the actions to the order that they should be performed */
+	qsort(filemap->array, filemap->narray, sizeof(file_entry_t *),
+		  final_filemap_cmp);
+}
diff --git a/src/bin/pg_rewind/filemap.h b/src/bin/pg_rewind/filemap.h
index 0cb7425170c..73c687e4e34 100644
--- a/src/bin/pg_rewind/filemap.h
+++ b/src/bin/pg_rewind/filemap.h
@@ -14,17 +14,21 @@
 
 /*
  * For every file found in the local or remote system, we have a file entry
- * which says what we are going to do with the file. For relation files,
- * there is also a page map, marking pages in the file that were changed
- * locally.
- *
- * The enum values are sorted in the order we want actions to be processed.
+ * that contains information about the file on both systems.  For relation
+ * files, there is also a page map that marks pages in the file that were
+ * changed in the target after the last common checkpoint.  Each entry also
+ * contains an 'action' field, which says what we are going to do with the
+ * file.
  */
+
+/* these enum values are sorted in the order we want actions to be processed */
 typedef enum
 {
+	FILE_ACTION_UNDECIDED = 0,	/* not decided yet */
+
 	FILE_ACTION_CREATE,			/* create local directory or symbolic link */
 	FILE_ACTION_COPY,			/* copy whole file, overwriting if exists */
-	FILE_ACTION_COPY_TAIL,		/* copy tail from 'oldsize' to 'newsize' */
+	FILE_ACTION_COPY_TAIL,		/* copy tail from 'source_size' to 'target_size' */
 	FILE_ACTION_NONE,			/* no action (we might still copy modified
 								 * blocks based on the parsed WAL) */
 	FILE_ACTION_TRUNCATE,		/* truncate local file to 'newsize' bytes */
@@ -33,6 +37,8 @@ typedef enum
 
 typedef enum
 {
+	FILE_TYPE_UNDEFINED = 0,
+
 	FILE_TYPE_REGULAR,
 	FILE_TYPE_DIRECTORY,
 	FILE_TYPE_SYMLINK
@@ -41,19 +47,34 @@ typedef enum
 typedef struct file_entry_t
 {
 	char	   *path;
-	file_type_t type;
+	bool		isrelfile;		/* is it a relation data file? */
 
-	file_action_t action;
+	/*
+	 * Status of the file in the target.
+	 */
+	bool		target_exists;
+	file_type_t target_type;
+	size_t		target_size; /* for a regular file */
+	char	   *target_link_target; /* for a symlink */
 
-	/* for a regular file */
-	size_t		oldsize;
-	size_t		newsize;
-	bool		isrelfile;		/* is it a relation data file? */
+	/*
+	 * Pages that were modified in the target and need to be replaced
+	 * from the source.
+	 */
+	datapagemap_t target_pages_to_overwrite;
 
-	datapagemap_t pagemap;
+	/*
+	 * Status of the file in the source.
+	 */
+	bool		source_exists;
+	file_type_t source_type;
+	size_t		source_size;
+	char	   *source_link_target; /* for a symlink */
 
-	/* for a symlink */
-	char	   *link_target;
+	/*
+	 * What will we do to the file?
+	 */
+	file_action_t action;
 
 	struct file_entry_t *next;
 } file_entry_t;
@@ -79,11 +100,10 @@ typedef struct filemap_t
 	int			narray;			/* current length of array */
 
 	/*
-	 * Summary information. total_size is the total size of the source
-	 * cluster, and fetch_size is the number of bytes that needs to be copied.
+	 * Summary information.
 	 */
-	uint64		total_size;
-	uint64		fetch_size;
+	uint64		total_size;		/* total size of the source cluster */
+	uint64		fetch_size;		/* number of bytes that needs to be copied */
 } filemap_t;
 
 extern filemap_t *filemap;
@@ -94,11 +114,12 @@ extern void print_filemap(void);
 
 /* Functions for populating the filemap */
 extern void process_source_file(const char *path, file_type_t type,
-								size_t newsize, const char *link_target);
+								size_t size, const char *link_target);
 extern void process_target_file(const char *path, file_type_t type,
-								size_t newsize, const char *link_target);
-extern void process_block_change(ForkNumber forknum, RelFileNode rnode,
-								 BlockNumber blkno);
-extern void filemap_finalize(void);
+								size_t size, const char *link_target);
+extern void process_target_wal_block_change(ForkNumber forknum,
+											RelFileNode rnode,
+											BlockNumber blkno);
+extern void decide_file_actions(void);
 
 #endif							/* FILEMAP_H */
diff --git a/src/bin/pg_rewind/libpq_fetch.c b/src/bin/pg_rewind/libpq_fetch.c
index bf4dfc23b96..2fc4a784bdb 100644
--- a/src/bin/pg_rewind/libpq_fetch.c
+++ b/src/bin/pg_rewind/libpq_fetch.c
@@ -465,7 +465,7 @@ libpq_executeFileMap(filemap_t *map)
 		entry = map->array[i];
 
 		/* If this is a relation file, copy the modified blocks */
-		execute_pagemap(&entry->pagemap, entry->path);
+		execute_pagemap(&entry->target_pages_to_overwrite, entry->path);
 
 		switch (entry->action)
 		{
@@ -476,15 +476,15 @@ libpq_executeFileMap(filemap_t *map)
 			case FILE_ACTION_COPY:
 				/* Truncate the old file out of the way, if any */
 				open_target_file(entry->path, true);
-				fetch_file_range(entry->path, 0, entry->newsize);
+				fetch_file_range(entry->path, 0, entry->source_size);
 				break;
 
 			case FILE_ACTION_TRUNCATE:
-				truncate_target_file(entry->path, entry->newsize);
+				truncate_target_file(entry->path, entry->source_size);
 				break;
 
 			case FILE_ACTION_COPY_TAIL:
-				fetch_file_range(entry->path, entry->oldsize, entry->newsize);
+				fetch_file_range(entry->path, entry->target_size, entry->source_size);
 				break;
 
 			case FILE_ACTION_REMOVE:
@@ -494,6 +494,10 @@ libpq_executeFileMap(filemap_t *map)
 			case FILE_ACTION_CREATE:
 				create_target(entry);
 				break;
+
+			case FILE_ACTION_UNDECIDED:
+				pg_fatal("no action decided for \"%s\"", entry->path);
+				break;
 		}
 	}
 
diff --git a/src/bin/pg_rewind/parsexlog.c b/src/bin/pg_rewind/parsexlog.c
index 2229c86f9af..2baeb74ae93 100644
--- a/src/bin/pg_rewind/parsexlog.c
+++ b/src/bin/pg_rewind/parsexlog.c
@@ -436,6 +436,6 @@ extractPageInfo(XLogReaderState *record)
 		if (forknum != MAIN_FORKNUM)
 			continue;
 
-		process_block_change(forknum, rnode, blkno);
+		process_target_wal_block_change(forknum, rnode, blkno);
 	}
 }
diff --git a/src/bin/pg_rewind/pg_rewind.c b/src/bin/pg_rewind/pg_rewind.c
index 5a7ab764db4..4760090d06e 100644
--- a/src/bin/pg_rewind/pg_rewind.c
+++ b/src/bin/pg_rewind/pg_rewind.c
@@ -369,7 +369,7 @@ main(int argc, char **argv)
 				chkpttli);
 
 	/*
-	 * Build the filemap, by comparing the source and target data directories.
+	 * Collect information about all files in the target and source systems.
 	 */
 	filemap_create();
 	if (showprogress)
@@ -390,8 +390,12 @@ main(int argc, char **argv)
 		pg_log_info("reading WAL in target");
 	extractPageMap(datadir_target, chkptrec, lastcommontliIndex,
 				   ControlFile_target.checkPoint, restore_command);
-	filemap_finalize();
 
+	/*
+	 * We have collected all information we need from both systems. Decide
+	 * what to do with each file.
+	 */
+	decide_file_actions();
 	if (showprogress)
 		calculate_totals();
 
-- 
2.20.1


--------------E5E97ECB089CAF17EDB11AD4
Content-Type: text/x-patch; charset=UTF-8;
 name="v3-0003-pg_rewind-Replace-the-hybrid-list-array-data-stru.patch"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
 filename*0="v3-0003-pg_rewind-Replace-the-hybrid-list-array-data-stru.pa";
 filename*1="tch"



^ permalink  raw  reply  [nested|flat] 41+ messages in thread

* Re: Using LibPq in TAP tests via FFI
@ 2024-06-16 14:58 Andrew Dunstan <[email protected]>
  0 siblings, 0 replies; 41+ messages in thread

From: Andrew Dunstan @ 2024-06-16 14:58 UTC (permalink / raw)
  To: Andres Freund <[email protected]>; +Cc: PostgreSQL Hackers <[email protected]>

On Fri, Jun 14, 2024 at 11:40 AM Andres Freund <[email protected]> wrote:

> Hi,
>
> On June 14, 2024 8:09:43 AM PDT, Andrew Dunstan <[email protected]>
> wrote:
> >Over at [1] Andres expressed enthusiasm for enabling TAP tests to call
> LibPQ directly via FFI, and there was some support from others as well.
> Attached is a very rough POC for just that.There are two perl modules, one
> which wraps libpq (or almost all of it) in perl, and another which uses
> that module to create a session object that can be used to run SQL. Also in
> the patch is a modification of one TAP test (arbitrarily chosen as
> src/bin/pg_amcheck/t/004_verify_heapam.p) to use the new interface, so it
> doesn't use psql at all.
> >
> >There's a bunch of work to do here, but for a morning's work it's not too
> bad :-) Luckily I had most of the first file already to hand.
>
> Yay!
>
>
> >Next I plan to look at some of the recovery tests and other uses of
> background_psql, which might be more challenging,a dn require extension of
> the session API. Also there's a lot of error checking and documentation
> that need to be added.
>
> I'd suggest trying to convert the various looping constructs first,
> they're responsible for a large number of spawned shells. And I vaguely
> recall that there were none/very few that depend on actually being run via
> psql.
>
>
>
>
Yeah, here's a new version with a few more scripts modified, and also
poll_query_until() adjusted. That seems to be the biggest looping construct.

The biggest remaining unadjusted script users of psql are all in the
subscription and recovery tests.

cheers

andrew


Attachments:

  [text/x-patch] pqffi-poc-2.patch (31.9K, ../../CAD5tBcKwpcXYGbgewQDi8LzdrEZ6td-kVwHDRiirsK2M_wQSXw@mail.gmail.com/3-pqffi-poc-2.patch)
  download | inline diff:
diff --git a/contrib/amcheck/t/001_verify_heapam.pl b/contrib/amcheck/t/001_verify_heapam.pl
index 9de3148277..c259d2ccfd 100644
--- a/contrib/amcheck/t/001_verify_heapam.pl
+++ b/contrib/amcheck/t/001_verify_heapam.pl
@@ -5,6 +5,7 @@ use strict;
 use warnings FATAL => 'all';
 
 use PostgreSQL::Test::Cluster;
+use PostgreSQL::Test::Session;
 use PostgreSQL::Test::Utils;
 
 use Test::More;
@@ -18,7 +19,9 @@ $node = PostgreSQL::Test::Cluster->new('test');
 $node->init;
 $node->append_conf('postgresql.conf', 'autovacuum=off');
 $node->start;
-$node->safe_psql('postgres', q(CREATE EXTENSION amcheck));
+my $session = PostgreSQL::Test::Session->new(node => $node);
+
+$session->do(q(CREATE EXTENSION amcheck));
 
 #
 # Check a table with data loaded but no corruption, freezing, etc.
@@ -49,7 +52,7 @@ detects_heap_corruption(
 # Check a corrupt table with all-frozen data
 #
 fresh_test_table('test');
-$node->safe_psql('postgres', q(VACUUM (FREEZE, DISABLE_PAGE_SKIPPING) test));
+$session->do(q(VACUUM (FREEZE, DISABLE_PAGE_SKIPPING) test));
 detects_no_corruption("verify_heapam('test')",
 	"all-frozen not corrupted table");
 corrupt_first_page('test');
@@ -81,7 +84,7 @@ sub relation_filepath
 	my ($relname) = @_;
 
 	my $pgdata = $node->data_dir;
-	my $rel = $node->safe_psql('postgres',
+	my $rel = $session->query_oneval(
 		qq(SELECT pg_relation_filepath('$relname')));
 	die "path not found for relation $relname" unless defined $rel;
 	return "$pgdata/$rel";
@@ -92,8 +95,8 @@ sub get_toast_for
 {
 	my ($relname) = @_;
 
-	return $node->safe_psql(
-		'postgres', qq(
+	return $session->query_oneval(
+		qq(
 		SELECT 'pg_toast.' || t.relname
 			FROM pg_catalog.pg_class c, pg_catalog.pg_class t
 			WHERE c.relname = '$relname'
@@ -105,8 +108,8 @@ sub fresh_test_table
 {
 	my ($relname) = @_;
 
-	return $node->safe_psql(
-		'postgres', qq(
+	return $session->do(
+		qq(
 		DROP TABLE IF EXISTS $relname CASCADE;
 		CREATE TABLE $relname (a integer, b text);
 		ALTER TABLE $relname SET (autovacuum_enabled=false);
@@ -130,8 +133,8 @@ sub fresh_test_sequence
 {
 	my ($seqname) = @_;
 
-	return $node->safe_psql(
-		'postgres', qq(
+	return $session->do(
+		qq(
 		DROP SEQUENCE IF EXISTS $seqname CASCADE;
 		CREATE SEQUENCE $seqname
 			INCREMENT BY 13
@@ -147,8 +150,8 @@ sub advance_test_sequence
 {
 	my ($seqname) = @_;
 
-	return $node->safe_psql(
-		'postgres', qq(
+	return $session->query_oneval(
+		qq(
 		SELECT nextval('$seqname');
 	));
 }
@@ -158,7 +161,7 @@ sub set_test_sequence
 {
 	my ($seqname) = @_;
 
-	return $node->safe_psql(
+	return $session->query_oneval(
 		'postgres', qq(
 		SELECT setval('$seqname', 102);
 	));
@@ -169,8 +172,8 @@ sub reset_test_sequence
 {
 	my ($seqname) = @_;
 
-	return $node->safe_psql(
-		'postgres', qq(
+	return $session->do(
+		qq(
 		ALTER SEQUENCE $seqname RESTART WITH 51
 	));
 }
@@ -182,6 +185,7 @@ sub corrupt_first_page
 	my ($relname) = @_;
 	my $relpath = relation_filepath($relname);
 
+	$session->close;
 	$node->stop;
 
 	my $fh;
@@ -204,6 +208,7 @@ sub corrupt_first_page
 	  or BAIL_OUT("close failed: $!");
 
 	$node->start;
+	$session->reconnect;
 }
 
 sub detects_heap_corruption
@@ -229,7 +234,7 @@ sub detects_corruption
 
 	my ($function, $testname, @re) = @_;
 
-	my $result = $node->safe_psql('postgres', qq(SELECT * FROM $function));
+	my $result = $session->query_tuples(qq(SELECT * FROM $function));
 	like($result, $_, $testname) for (@re);
 }
 
@@ -239,7 +244,7 @@ sub detects_no_corruption
 
 	my ($function, $testname) = @_;
 
-	my $result = $node->safe_psql('postgres', qq(SELECT * FROM $function));
+	my $result = $session->query_tuples(qq(SELECT * FROM $function));
 	is($result, '', $testname);
 }
 
diff --git a/contrib/bloom/t/001_wal.pl b/contrib/bloom/t/001_wal.pl
index 61f5641d9e..280e95eb2b 100644
--- a/contrib/bloom/t/001_wal.pl
+++ b/contrib/bloom/t/001_wal.pl
@@ -5,11 +5,14 @@
 use strict;
 use warnings FATAL => 'all';
 use PostgreSQL::Test::Cluster;
+use PostgreSQL::Test::Session;
 use PostgreSQL::Test::Utils;
 use Test::More;
 
 my $node_primary;
 my $node_standby;
+my $session_primary;
+my $session_standby;
 
 # Run few queries on both primary and standby and check their results match.
 sub test_index_replay
@@ -21,20 +24,18 @@ sub test_index_replay
 	# Wait for standby to catch up
 	$node_primary->wait_for_catchup($node_standby);
 
-	my $queries = qq(SET enable_seqscan=off;
-SET enable_bitmapscan=on;
-SET enable_indexscan=on;
-SELECT * FROM tst WHERE i = 0;
-SELECT * FROM tst WHERE i = 3;
-SELECT * FROM tst WHERE t = 'b';
-SELECT * FROM tst WHERE t = 'f';
-SELECT * FROM tst WHERE i = 3 AND t = 'c';
-SELECT * FROM tst WHERE i = 7 AND t = 'e';
-);
+	my @queries = (
+		"SELECT * FROM tst WHERE i = 0",
+		"SELECT * FROM tst WHERE i = 3",
+		"SELECT * FROM tst WHERE t = 'b'",
+		"SELECT * FROM tst WHERE t = 'f'",
+		"SELECT * FROM tst WHERE i = 3 AND t = 'c'",
+		"SELECT * FROM tst WHERE i = 7 AND t = 'e'",
+	   );
 
 	# Run test queries and compare their result
-	my $primary_result = $node_primary->safe_psql("postgres", $queries);
-	my $standby_result = $node_standby->safe_psql("postgres", $queries);
+	my $primary_result = $session_primary->query_tuples(@queries);
+	my $standby_result = $session_standby->query_tuples(@queries);
 
 	is($primary_result, $standby_result, "$test_name: query result matches");
 	return;
@@ -55,13 +56,24 @@ $node_standby->init_from_backup($node_primary, $backup_name,
 	has_streaming => 1);
 $node_standby->start;
 
+# Create and initialize the sessions
+$session_primary = PostgreSQL::Test::Session->new(node => $node_primary);
+$session_standby = PostgreSQL::Test::Session->new(node => $node_standby);
+my $initset = q[
+   SET enable_seqscan=off;
+   SET enable_bitmapscan=on;
+   SET enable_indexscan=on;
+];
+$session_primary->do($initset);
+$session_standby->do($initset);
+
 # Create some bloom index on primary
-$node_primary->safe_psql("postgres", "CREATE EXTENSION bloom;");
-$node_primary->safe_psql("postgres", "CREATE TABLE tst (i int4, t text);");
-$node_primary->safe_psql("postgres",
+$session_primary->do("CREATE EXTENSION bloom;");
+$session_primary->do("CREATE TABLE tst (i int4, t text);");
+$session_primary->do(
 	"INSERT INTO tst SELECT i%10, substr(encode(sha256(i::text::bytea), 'hex'), 1, 1) FROM generate_series(1,10000) i;"
 );
-$node_primary->safe_psql("postgres",
+$session_primary->do(
 	"CREATE INDEX bloomidx ON tst USING bloom (i, t) WITH (col1 = 3);");
 
 # Test that queries give same result
diff --git a/src/bin/pg_amcheck/t/004_verify_heapam.pl b/src/bin/pg_amcheck/t/004_verify_heapam.pl
index f6d2c5f787..c8036249ae 100644
--- a/src/bin/pg_amcheck/t/004_verify_heapam.pl
+++ b/src/bin/pg_amcheck/t/004_verify_heapam.pl
@@ -5,6 +5,7 @@ use strict;
 use warnings FATAL => 'all';
 
 use PostgreSQL::Test::Cluster;
+use PostgreSQL::Test::Session;
 use PostgreSQL::Test::Utils;
 
 use Test::More;
@@ -190,16 +191,17 @@ $node->append_conf('postgresql.conf', 'max_prepared_transactions=10');
 $node->start;
 my $port = $node->port;
 my $pgdata = $node->data_dir;
-$node->safe_psql('postgres', "CREATE EXTENSION amcheck");
-$node->safe_psql('postgres', "CREATE EXTENSION pageinspect");
+my $session = PostgreSQL::Test::Session->new(node => $node);
+$session->do("CREATE EXTENSION amcheck");
+$session->do("CREATE EXTENSION pageinspect");
 
 # Get a non-zero datfrozenxid
-$node->safe_psql('postgres', qq(VACUUM FREEZE));
+$session->do(qq(VACUUM FREEZE));
 
 # Create the test table with precisely the schema that our corruption function
 # expects.
-$node->safe_psql(
-	'postgres', qq(
+$session->do(
+	qq(
 		CREATE TABLE public.test (a BIGINT, b TEXT, c TEXT);
 		ALTER TABLE public.test SET (autovacuum_enabled=false);
 		ALTER TABLE public.test ALTER COLUMN c SET STORAGE EXTERNAL;
@@ -209,14 +211,15 @@ $node->safe_psql(
 # We want (0 < datfrozenxid < test.relfrozenxid).  To achieve this, we freeze
 # an otherwise unused table, public.junk, prior to inserting data and freezing
 # public.test
-$node->safe_psql(
-	'postgres', qq(
+$session->do(
+	qq(
 		CREATE TABLE public.junk AS SELECT 'junk'::TEXT AS junk_column;
 		ALTER TABLE public.junk SET (autovacuum_enabled=false);
-		VACUUM FREEZE public.junk
-	));
+	),
+	'VACUUM FREEZE public.junk'
+);
 
-my $rel = $node->safe_psql('postgres',
+my $rel = $session->query_oneval(
 	qq(SELECT pg_relation_filepath('public.test')));
 my $relpath = "$pgdata/$rel";
 
@@ -229,23 +232,24 @@ my $ROWCOUNT_BASIC = 16;
 
 # First insert data needed for tests unrelated to update chain validation.
 # Then freeze the page. These tuples are at offset numbers 1 to 16.
-$node->safe_psql(
-	'postgres', qq(
+$session->do(
+	qq(
 	INSERT INTO public.test (a, b, c)
 		SELECT
 			x'DEADF9F9DEADF9F9'::bigint,
 			'abcdefg',
 			repeat('w', 10000)
 	FROM generate_series(1, $ROWCOUNT_BASIC);
-	VACUUM FREEZE public.test;)
+    ),
+	'VACUUM FREEZE public.test'
 );
 
 # Create some simple HOT update chains for line pointer validation. After
 # the page is HOT pruned, we'll have two redirects line pointers each pointing
 # to a tuple. We'll then change the second redirect to point to the same
 # tuple as the first one and verify that we can detect corruption.
-$node->safe_psql(
-	'postgres', qq(
+$session->do(
+	qq(
 		INSERT INTO public.test (a, b, c)
 			VALUES ( x'DEADF9F9DEADF9F9'::bigint, 'abcdefg',
 					 generate_series(1,2)); -- offset numbers 17 and 18
@@ -254,8 +258,8 @@ $node->safe_psql(
 	));
 
 # Create some more HOT update chains.
-$node->safe_psql(
-	'postgres', qq(
+$session->do(
+	qq(
 		INSERT INTO public.test (a, b, c)
 			VALUES ( x'DEADF9F9DEADF9F9'::bigint, 'abcdefg',
 					 generate_series(3,6)); -- offset numbers 21 through 24
@@ -264,25 +268,30 @@ $node->safe_psql(
 	));
 
 # Negative test case of HOT-pruning with aborted tuple.
-$node->safe_psql(
-	'postgres', qq(
+$session->do(
+	qq(
 		BEGIN;
 		UPDATE public.test SET c = 'a' WHERE c = '5'; -- offset number 27
 		ABORT;
-		VACUUM FREEZE public.test;
-	));
+       ),
+	   'VACUUM FREEZE public.test;',
+	);
 
 # Next update on any tuple will be stored at the same place of tuple inserted
 # by aborted transaction. This should not cause the table to appear corrupt.
-$node->safe_psql(
-	'postgres', qq(
+$session->do(
+	qq(
+        BEGIN;
 		UPDATE public.test SET c = 'a' WHERE c = '6'; -- offset number 27 again
-		VACUUM FREEZE public.test;
-	));
+        COMMIT;
+	),
+	'VACUUM FREEZE public.test;',
+   );
 
 # Data for HOT chain validation, so not calling VACUUM FREEZE.
-$node->safe_psql(
-	'postgres', qq(
+$session->do(
+	qq(
+        BEGIN;
 		INSERT INTO public.test (a, b, c)
 			VALUES ( x'DEADF9F9DEADF9F9'::bigint, 'abcdefg',
 					 generate_series(7,15)); -- offset numbers 28 to 36
@@ -293,11 +302,12 @@ $node->safe_psql(
 		UPDATE public.test SET c = 'a' WHERE c = '13'; -- offset number 41
 		UPDATE public.test SET c = 'a' WHERE c = '14'; -- offset number 42
 		UPDATE public.test SET c = 'a' WHERE c = '15'; -- offset number 43
+        COMMIT;
 	));
 
 # Need one aborted transaction to test corruption in HOT chains.
-$node->safe_psql(
-	'postgres', qq(
+$session->do(
+	qq(
 		BEGIN;
 		UPDATE public.test SET c = 'a' WHERE c = '9'; -- offset number 44
 		ABORT;
@@ -306,19 +316,19 @@ $node->safe_psql(
 # Need one in-progress transaction to test few corruption in HOT chains.
 # We are creating PREPARE TRANSACTION here as these will not be aborted
 # even if we stop the node.
-$node->safe_psql(
-	'postgres', qq(
+$session->do(
+	qq(
 		BEGIN;
 		PREPARE TRANSACTION 'in_progress_tx';
 	));
-my $in_progress_xid = $node->safe_psql(
-	'postgres', qq(
+my $in_progress_xid = $session->query_oneval(
+	qq(
 		SELECT transaction FROM pg_prepared_xacts;
 	));
 
-my $relfrozenxid = $node->safe_psql('postgres',
+my $relfrozenxid = $session->query_oneval(
 	q(select relfrozenxid from pg_class where relname = 'test'));
-my $datfrozenxid = $node->safe_psql('postgres',
+my $datfrozenxid = $session->query_oneval(
 	q(select datfrozenxid from pg_database where datname = 'postgres'));
 
 # Sanity check that our 'test' table has a relfrozenxid newer than the
@@ -326,6 +336,7 @@ my $datfrozenxid = $node->safe_psql('postgres',
 # first normal xid.  We rely on these invariants in some of our tests.
 if ($datfrozenxid <= 3 || $datfrozenxid >= $relfrozenxid)
 {
+	$session->close;
 	$node->clean_node;
 	plan skip_all =>
 	  "Xid thresholds not as expected: got datfrozenxid = $datfrozenxid, relfrozenxid = $relfrozenxid";
@@ -334,17 +345,21 @@ if ($datfrozenxid <= 3 || $datfrozenxid >= $relfrozenxid)
 
 # Find where each of the tuples is located on the page. If a particular
 # line pointer is a redirect rather than a tuple, we record the offset as -1.
-my @lp_off = split '\n', $node->safe_psql(
-	'postgres', qq(
+my $lp_off_res = $session->query(
+	qq(
 	    SELECT CASE WHEN lp_flags = 2 THEN -1 ELSE lp_off END
 	    FROM heap_page_items(get_raw_page('test', 'main', 0))
     )
-);
+   );
+my @lp_off;
+push(@lp_off, $_->[0]) foreach @{$lp_off_res->{rows}};
+
 scalar @lp_off == $ROWCOUNT or BAIL_OUT("row offset counts mismatch");
 
 # Sanity check that our 'test' table on disk layout matches expectations.  If
 # this is not so, we will have to skip the test until somebody updates the test
 # to work on this platform.
+$session->close;
 $node->stop;
 my $file;
 open($file, '+<', $relpath)
@@ -750,17 +765,19 @@ for (my $tupidx = 0; $tupidx < $ROWCOUNT; $tupidx++)
 close($file)
   or BAIL_OUT("close failed: $!");
 $node->start;
+$session->reconnect;
 
 # Run pg_amcheck against the corrupt table with epoch=0, comparing actual
 # corruption messages against the expected messages
 $node->command_checks_all(
 	[ 'pg_amcheck', '--no-dependent-indexes', '-p', $port, 'postgres' ],
 	2, [@expected], [], 'Expected corruption message output');
-$node->safe_psql(
-	'postgres', qq(
+$session->do(
+	qq(
                         COMMIT PREPARED 'in_progress_tx';
         ));
 
+$session->close;
 $node->teardown_node;
 $node->clean_node;
 
diff --git a/src/test/perl/PostgreSQL/PqFFI.pm b/src/test/perl/PostgreSQL/PqFFI.pm
new file mode 100644
index 0000000000..fac544d32c
--- /dev/null
+++ b/src/test/perl/PostgreSQL/PqFFI.pm
@@ -0,0 +1,564 @@
+
+############################################
+#
+# FFI wrapper for libpq
+#
+############################################
+package PostgreSQL::PqFFI;
+
+use strict;
+use warnings FATAL => 'all';
+
+use FFI::Platypus;
+use FFI::CheckLib;
+
+use Exporter qw(import);
+
+our @EXPORT = qw (
+
+  CONNECTION_OK
+  CONNECTION_BAD
+  CONNECTION_STARTED
+  CONNECTION_MADE
+  CONNECTION_AWAITING_RESPONSE
+  CONNECTION_AUTH_OK
+  CONNECTION_SETENV
+  CONNECTION_SSL_STARTUP
+  CONNECTION_NEEDED
+  CONNECTION_CHECK_WRITABLE
+  CONNECTION_CONSUME
+  CONNECTION_GSS_STARTUP
+  CONNECTION_CHECK_TARGET
+  CONNECTION_CHECK_STANDBY
+
+  PGRES_EMPTY_QUERY
+  PGRES_COMMAND_OK
+  PGRES_TUPLES_OK
+  PGRES_COPY_OUT
+  PGRES_COPY_IN
+  PGRES_BAD_RESPONSE
+  PGRES_NONFATAL_ERROR
+  PGRES_FATAL_ERROR
+  PGRES_COPY_BOTH
+  PGRES_SINGLE_TUPLE
+  PGRES_PIPELINE_SYNC
+  PGRES_PIPELINE_ABORTED
+
+  PQPING_OK
+  PQPING_REJECT
+  PQPING_NO_RESPONSE
+  PQPING_NO_ATTEMPT
+
+  PQTRANS_IDLE
+  PQTRANS_ACTIVE
+  PQTRANS_INTRANS
+  PQTRANS_INERROR
+  PQTRANS_UNKNOWN
+
+  BOOLOID
+  BYTEAOID
+  CHAROID
+  NAMEOID
+  INT8OID
+  INT2OID
+  INT2VECTOROID
+  INT4OID
+  TEXTOID
+  OIDOID
+  TIDOID
+  XIDOID
+  CIDOID
+  OIDVECTOROID
+  JSONOID
+  XMLOID
+  XID8OID
+  POINTOID
+  LSEGOID
+  PATHOID
+  BOXOID
+  POLYGONOID
+  LINEOID
+  FLOAT4OID
+  FLOAT8OID
+  UNKNOWNOID
+  CIRCLEOID
+  MONEYOID
+  MACADDROID
+  INETOID
+  CIDROID
+  MACADDR8OID
+  ACLITEMOID
+  BPCHAROID
+  VARCHAROID
+  DATEOID
+  TIMEOID
+  TIMESTAMPOID
+  TIMESTAMPTZOID
+  INTERVALOID
+  TIMETZOID
+  BITOID
+  VARBITOID
+  NUMERICOID
+  REFCURSOROID
+  UUIDOID
+  TSVECTOROID
+  GTSVECTOROID
+  TSQUERYOID
+  JSONBOID
+  JSONPATHOID
+  TXID_SNAPSHOTOID
+  INT4RANGEOID
+  NUMRANGEOID
+  TSRANGEOID
+  TSTZRANGEOID
+  DATERANGEOID
+  INT8RANGEOID
+  INT4MULTIRANGEOID
+  NUMMULTIRANGEOID
+  TSMULTIRANGEOID
+  TSTZMULTIRANGEOID
+  DATEMULTIRANGEOID
+  INT8MULTIRANGEOID
+  RECORDOID
+  RECORDARRAYOID
+  CSTRINGOID
+  VOIDOID
+  TRIGGEROID
+  EVENT_TRIGGEROID
+  BOOLARRAYOID
+  BYTEAARRAYOID
+  CHARARRAYOID
+  NAMEARRAYOID
+  INT8ARRAYOID
+  INT2ARRAYOID
+  INT2VECTORARRAYOID
+  INT4ARRAYOID
+  TEXTARRAYOID
+  OIDARRAYOID
+  TIDARRAYOID
+  XIDARRAYOID
+  CIDARRAYOID
+  OIDVECTORARRAYOID
+  JSONARRAYOID
+  XMLARRAYOID
+  XID8ARRAYOID
+  POINTARRAYOID
+  LSEGARRAYOID
+  PATHARRAYOID
+  BOXARRAYOID
+  POLYGONARRAYOID
+  LINEARRAYOID
+  FLOAT4ARRAYOID
+  FLOAT8ARRAYOID
+  CIRCLEARRAYOID
+  MONEYARRAYOID
+  MACADDRARRAYOID
+  INETARRAYOID
+  CIDRARRAYOID
+  MACADDR8ARRAYOID
+  ACLITEMARRAYOID
+  BPCHARARRAYOID
+  VARCHARARRAYOID
+  DATEARRAYOID
+  TIMEARRAYOID
+  TIMESTAMPARRAYOID
+  TIMESTAMPTZARRAYOID
+  INTERVALARRAYOID
+  TIMETZARRAYOID
+  BITARRAYOID
+  VARBITARRAYOID
+  NUMERICARRAYOID
+  REFCURSORARRAYOID
+  UUIDARRAYOID
+  TSVECTORARRAYOID
+  GTSVECTORARRAYOID
+  TSQUERYARRAYOID
+  JSONBARRAYOID
+  JSONPATHARRAYOID
+  TXID_SNAPSHOTARRAYOID
+  INT4RANGEARRAYOID
+  NUMRANGEARRAYOID
+  TSRANGEARRAYOID
+  TSTZRANGEARRAYOID
+  DATERANGEARRAYOID
+  INT8RANGEARRAYOID
+  INT4MULTIRANGEARRAYOID
+  NUMMULTIRANGEARRAYOID
+  TSMULTIRANGEARRAYOID
+  TSTZMULTIRANGEARRAYOID
+  DATEMULTIRANGEARRAYOID
+  INT8MULTIRANGEARRAYOID
+  CSTRINGARRAYOID
+
+);
+
+# connection status
+
+use constant {
+	CONNECTION_OK => 0,
+	CONNECTION_BAD => 1,
+	# Non-blocking mode only below here
+
+	CONNECTION_STARTED => 2,
+	CONNECTION_MADE => 3,
+	CONNECTION_AWAITING_RESPONSE => 4,
+	CONNECTION_AUTH_OK => 5,
+	CONNECTION_SETENV => 6,
+	CONNECTION_SSL_STARTUP => 7,
+	CONNECTION_NEEDED => 8,
+	CONNECTION_CHECK_WRITABLE => 9,
+	CONNECTION_CONSUME => 10,
+	CONNECTION_GSS_STARTUP => 11,
+	CONNECTION_CHECK_TARGET => 12,
+	CONNECTION_CHECK_STANDBY => 13,
+};
+
+# exec status
+
+use constant {
+	PGRES_EMPTY_QUERY => 0,
+	PGRES_COMMAND_OK => 1,
+	PGRES_TUPLES_OK => 2,
+	PGRES_COPY_OUT => 3,
+	PGRES_COPY_IN => 4,
+	PGRES_BAD_RESPONSE => 5,
+	PGRES_NONFATAL_ERROR => 6,
+	PGRES_FATAL_ERROR => 7,
+	PGRES_COPY_BOTH => 8,
+	PGRES_SINGLE_TUPLE => 9,
+	PGRES_PIPELINE_SYNC => 10,
+	PGRES_PIPELINE_ABORTED => 11,
+};
+
+# ping status
+
+use constant {
+	PQPING_OK => 0,
+	PQPING_REJECT => 1,
+	PQPING_NO_RESPONSE => 2,
+	PQPING_NO_ATTEMPT => 3,
+};
+
+# txn status
+use constant {
+	PQTRANS_IDLE => 0,
+	PQTRANS_ACTIVE => 1,
+	PQTRANS_INTRANS => 2,
+	PQTRANS_INERROR => 3,
+	PQTRANS_UNKNOWN => 4,
+};
+
+# type oids
+use constant {
+	BOOLOID => 16,
+	BYTEAOID => 17,
+	CHAROID => 18,
+	NAMEOID => 19,
+	INT8OID => 20,
+	INT2OID => 21,
+	INT2VECTOROID => 22,
+	INT4OID => 23,
+	TEXTOID => 25,
+	OIDOID => 26,
+	TIDOID => 27,
+	XIDOID => 28,
+	CIDOID => 29,
+	OIDVECTOROID => 30,
+	JSONOID => 114,
+	XMLOID => 142,
+	XID8OID => 5069,
+	POINTOID => 600,
+	LSEGOID => 601,
+	PATHOID => 602,
+	BOXOID => 603,
+	POLYGONOID => 604,
+	LINEOID => 628,
+	FLOAT4OID => 700,
+	FLOAT8OID => 701,
+	UNKNOWNOID => 705,
+	CIRCLEOID => 718,
+	MONEYOID => 790,
+	MACADDROID => 829,
+	INETOID => 869,
+	CIDROID => 650,
+	MACADDR8OID => 774,
+	ACLITEMOID => 1033,
+	BPCHAROID => 1042,
+	VARCHAROID => 1043,
+	DATEOID => 1082,
+	TIMEOID => 1083,
+	TIMESTAMPOID => 1114,
+	TIMESTAMPTZOID => 1184,
+	INTERVALOID => 1186,
+	TIMETZOID => 1266,
+	BITOID => 1560,
+	VARBITOID => 1562,
+	NUMERICOID => 1700,
+	REFCURSOROID => 1790,
+	UUIDOID => 2950,
+	TSVECTOROID => 3614,
+	GTSVECTOROID => 3642,
+	TSQUERYOID => 3615,
+	JSONBOID => 3802,
+	JSONPATHOID => 4072,
+	TXID_SNAPSHOTOID => 2970,
+	INT4RANGEOID => 3904,
+	NUMRANGEOID => 3906,
+	TSRANGEOID => 3908,
+	TSTZRANGEOID => 3910,
+	DATERANGEOID => 3912,
+	INT8RANGEOID => 3926,
+	INT4MULTIRANGEOID => 4451,
+	NUMMULTIRANGEOID => 4532,
+	TSMULTIRANGEOID => 4533,
+	TSTZMULTIRANGEOID => 4534,
+	DATEMULTIRANGEOID => 4535,
+	INT8MULTIRANGEOID => 4536,
+	RECORDOID => 2249,
+	RECORDARRAYOID => 2287,
+	CSTRINGOID => 2275,
+	VOIDOID => 2278,
+	TRIGGEROID => 2279,
+	EVENT_TRIGGEROID => 3838,
+	BOOLARRAYOID => 1000,
+	BYTEAARRAYOID => 1001,
+	CHARARRAYOID => 1002,
+	NAMEARRAYOID => 1003,
+	INT8ARRAYOID => 1016,
+	INT2ARRAYOID => 1005,
+	INT2VECTORARRAYOID => 1006,
+	INT4ARRAYOID => 1007,
+	TEXTARRAYOID => 1009,
+	OIDARRAYOID => 1028,
+	TIDARRAYOID => 1010,
+	XIDARRAYOID => 1011,
+	CIDARRAYOID => 1012,
+	OIDVECTORARRAYOID => 1013,
+	JSONARRAYOID => 199,
+	XMLARRAYOID => 143,
+	XID8ARRAYOID => 271,
+	POINTARRAYOID => 1017,
+	LSEGARRAYOID => 1018,
+	PATHARRAYOID => 1019,
+	BOXARRAYOID => 1020,
+	POLYGONARRAYOID => 1027,
+	LINEARRAYOID => 629,
+	FLOAT4ARRAYOID => 1021,
+	FLOAT8ARRAYOID => 1022,
+	CIRCLEARRAYOID => 719,
+	MONEYARRAYOID => 791,
+	MACADDRARRAYOID => 1040,
+	INETARRAYOID => 1041,
+	CIDRARRAYOID => 651,
+	MACADDR8ARRAYOID => 775,
+	ACLITEMARRAYOID => 1034,
+	BPCHARARRAYOID => 1014,
+	VARCHARARRAYOID => 1015,
+	DATEARRAYOID => 1182,
+	TIMEARRAYOID => 1183,
+	TIMESTAMPARRAYOID => 1115,
+	TIMESTAMPTZARRAYOID => 1185,
+	INTERVALARRAYOID => 1187,
+	TIMETZARRAYOID => 1270,
+	BITARRAYOID => 1561,
+	VARBITARRAYOID => 1563,
+	NUMERICARRAYOID => 1231,
+	REFCURSORARRAYOID => 2201,
+	UUIDARRAYOID => 2951,
+	TSVECTORARRAYOID => 3643,
+	GTSVECTORARRAYOID => 3644,
+	TSQUERYARRAYOID => 3645,
+	JSONBARRAYOID => 3807,
+	JSONPATHARRAYOID => 4073,
+	TXID_SNAPSHOTARRAYOID => 2949,
+	INT4RANGEARRAYOID => 3905,
+	NUMRANGEARRAYOID => 3907,
+	TSRANGEARRAYOID => 3909,
+	TSTZRANGEARRAYOID => 3911,
+	DATERANGEARRAYOID => 3913,
+	INT8RANGEARRAYOID => 3927,
+	INT4MULTIRANGEARRAYOID => 6150,
+	NUMMULTIRANGEARRAYOID => 6151,
+	TSMULTIRANGEARRAYOID => 6152,
+	TSTZMULTIRANGEARRAYOID => 6153,
+	DATEMULTIRANGEARRAYOID => 6155,
+	INT8MULTIRANGEARRAYOID => 6157,
+	CSTRINGARRAYOID => 1263,
+};
+
+
+
+my @procs = qw(
+
+  PQconnectdb
+  PQconnectdbParams
+  PQsetdbLogin
+  PQfinish
+  PQreset
+  PQdb
+  PQuser
+  PQpass
+  PQhost
+  PQhostaddr
+  PQport
+  PQtty
+  PQoptions
+  PQstatus
+  PQtransactionStatus
+  PQparameterStatus
+  PQping
+  PQpingParams
+
+  PQexec
+  PQexecParams
+  PQprepare
+  PQexecPrepared
+
+  PQdescribePrepared
+  PQdescribePortal
+
+  PQclosePrepared
+  PQclosePortal
+  PQclear
+
+  PQprotocolVersion
+  PQserverVersion
+  PQerrorMessage
+  PQsocket
+  PQbackendPID
+  PQconnectionNeedsPassword
+  PQconnectionUsedPassword
+  PQconnectionUsedGSSAPI
+  PQclientEncoding
+  PQsetClientEncoding
+
+  PQresultStatus
+  PQresStatus
+  PQresultErrorMessage
+  PQresultErrorField
+  PQntuples
+  PQnfields
+  PQbinaryTuples
+  PQfname
+  PQfnumber
+  PQftable
+  PQftablecol
+  PQfformat
+  PQftype
+  PQfsize
+  PQfmod
+  PQcmdStatus
+  PQoidValue
+  PQcmdTuples
+  PQgetvalue
+  PQgetlength
+  PQgetisnull
+  PQnparams
+  PQparamtype
+
+);
+
+push(@EXPORT, @procs);
+
+sub setup
+{
+	my $libdir = shift;
+
+	my $ffi = FFI::Platypus->new(api => 1);
+
+	$ffi->type('opaque' => 'PGconn');
+	$ffi->type('opaque' => 'PGresult');
+	$ffi->type('uint32' => 'Oid');
+	$ffi->type('int' => 'ExecStatusType');
+
+	my $lib = find_lib_or_die(
+		lib => 'pq',
+		libpath => [$libdir],
+		systempath => [],);
+	$ffi->lib($lib);
+
+	$ffi->attach('PQconnectdb' => ['string'] => 'PGconn');
+	$ffi->attach(
+		'PQconnectdbParams' => [ 'string[]', 'string[]', 'int' ] => 'PGconn');
+	$ffi->attach(
+		'PQsetdbLogin' => [
+			'string', 'string', 'string', 'string',
+			'string', 'string', 'string',
+		] => 'PGconn');
+	$ffi->attach('PQfinish' => ['PGconn'] => 'void');
+	$ffi->attach('PQreset' => ['PGconn'] => 'void');
+	$ffi->attach('PQdb' => ['PGconn'] => 'string');
+	$ffi->attach('PQuser' => ['PGconn'] => 'string');
+	$ffi->attach('PQpass' => ['PGconn'] => 'string');
+	$ffi->attach('PQhost' => ['PGconn'] => 'string');
+	$ffi->attach('PQhostaddr' => ['PGconn'] => 'string');
+	$ffi->attach('PQport' => ['PGconn'] => 'string');
+	$ffi->attach('PQtty' => ['PGconn'] => 'string');
+	$ffi->attach('PQoptions' => ['PGconn'] => 'string');
+	$ffi->attach('PQstatus' => ['PGconn'] => 'int');
+	$ffi->attach('PQtransactionStatus' => ['PGconn'] => 'int');
+	$ffi->attach('PQparameterStatus' => [ 'PGconn', 'string' ] => 'string');
+	$ffi->attach('PQping' => ['string'] => 'int');
+	$ffi->attach(
+		'PQpingParams' => [ 'string[]', 'string[]', 'int' ] => 'int');
+
+	$ffi->attach('PQprotocolVersion' => ['PGconn'] => 'int');
+	$ffi->attach('PQserverVersion' => ['PGconn'] => 'int');
+	$ffi->attach('PQerrorMessage' => ['PGconn'] => 'string');
+	$ffi->attach('PQsocket' => ['PGconn'] => 'int');
+	$ffi->attach('PQbackendPID' => ['PGconn'] => 'int');
+	$ffi->attach('PQconnectionNeedsPassword' => ['PGconn'] => 'int');
+	$ffi->attach('PQconnectionUsedPassword' => ['PGconn'] => 'int');
+	$ffi->attach('PQconnectionUsedGSSAPI' => ['PGconn'] => 'int');
+	$ffi->attach('PQclientEncoding' => ['PGconn'] => 'int');
+	$ffi->attach('PQsetClientEncoding' => [ 'PGconn', 'string' ] => 'int');
+
+	$ffi->attach('PQexec' => [ 'PGconn', 'string' ] => 'PGresult');
+	$ffi->attach(
+		'PQexecParams' => [
+			'PGconn', 'string', 'int', 'int[]',
+			'string[]', 'int[]', 'int[]', 'int'
+		] => 'PGresult');
+	$ffi->attach(
+		'PQprepare' => [ 'PGconn', 'string', 'string', 'int', 'int[]' ] =>
+		  'PGresult');
+	$ffi->attach(
+		'PQexecPrepared' => [ 'PGconn', 'string', 'int',
+			'string[]', 'int[]', 'int[]', 'int' ] => 'PGresult');
+
+	$ffi->attach('PQresultStatus' => ['PGresult'] => 'ExecStatusType');
+	$ffi->attach('PQresStatus' => ['ExecStatusType'] => 'string');
+	$ffi->attach('PQresultErrorMessage' => ['PGresult'] => 'string');
+	$ffi->attach('PQresultErrorField' => [ 'PGresult', 'int' ] => 'string');
+	$ffi->attach('PQntuples' => ['PGresult'] => 'int');
+	$ffi->attach('PQnfields' => ['PGresult'] => 'int');
+	$ffi->attach('PQbinaryTuples' => ['PGresult'] => 'int');
+	$ffi->attach('PQfname' => [ 'PGresult', 'int' ] => 'string');
+	$ffi->attach('PQfnumber' => [ 'PGresult', 'string' ] => 'int');
+	$ffi->attach('PQftable' => [ 'PGresult', 'int' ] => 'Oid');
+	$ffi->attach('PQftablecol' => [ 'PGresult', 'int' ] => 'int');
+	$ffi->attach('PQfformat' => [ 'PGresult', 'int' ] => 'int');
+	$ffi->attach('PQftype' => [ 'PGresult', 'int' ] => 'Oid');
+	$ffi->attach('PQfsize' => [ 'PGresult', 'int' ] => 'int');
+	$ffi->attach('PQfmod' => [ 'PGresult', 'int' ] => 'int');
+	$ffi->attach('PQcmdStatus' => ['PGresult'] => 'string');
+	$ffi->attach('PQoidValue' => ['PGresult'] => 'Oid');
+	$ffi->attach('PQcmdTuples' => ['PGresult'] => 'string');
+	$ffi->attach('PQgetvalue' => [ 'PGresult', 'int', 'int' ] => 'string');
+	$ffi->attach('PQgetlength' => [ 'PGresult', 'int', 'int' ] => 'int');
+	$ffi->attach('PQgetisnull' => [ 'PGresult', 'int', 'int' ] => 'int');
+	$ffi->attach('PQnparams' => ['PGresult'] => 'int');
+	$ffi->attach('PQparamtype' => [ 'PGresult', 'int' ] => 'Oid');
+
+
+	$ffi->attach(
+		'PQdescribePrepared' => [ 'PGconn', 'string' ] => 'PGresult');
+	$ffi->attach('PQdescribePortal' => [ 'PGconn', 'string' ] => 'PGresult');
+
+	$ffi->attach('PQclosePrepared' => [ 'PGconn', 'string' ] => 'PGresult');
+	$ffi->attach('PQclosePortal' => [ 'PGconn', 'string' ] => 'PGresult');
+	$ffi->attach('PQclear' => ['PGresult'] => 'void');
+}
+
+
+1;
diff --git a/src/test/perl/PostgreSQL/Test/Cluster.pm b/src/test/perl/PostgreSQL/Test/Cluster.pm
index 83f385a487..5427036112 100644
--- a/src/test/perl/PostgreSQL/Test/Cluster.pm
+++ b/src/test/perl/PostgreSQL/Test/Cluster.pm
@@ -111,6 +111,7 @@ use Socket;
 use Test::More;
 use PostgreSQL::Test::Utils          ();
 use PostgreSQL::Test::BackgroundPsql ();
+use PostgreSQL::Test::Session;
 use Text::ParseWords                 qw(shellwords);
 use Time::HiRes                      qw(usleep);
 use Scalar::Util                     qw(blessed);
@@ -1990,6 +1991,8 @@ sub psql
 
 	local %ENV = $self->_get_env();
 
+	# note("counting psql"); 
+
 	my $stdout = $params{stdout};
 	my $stderr = $params{stderr};
 	my $replication = $params{replication};
@@ -2499,26 +2502,18 @@ sub poll_query_until
 
 	$expected = 't' unless defined($expected);    # default value
 
-	my $cmd = [
-		$self->installed_command('psql'), '-XAt',
-		'-d', $self->connstr($dbname)
-	];
-	my ($stdout, $stderr);
+	my $session = PostgreSQL::Test::Session->new(node => $self,
+												 dbname => $dbname);
 	my $max_attempts = 10 * $PostgreSQL::Test::Utils::timeout_default;
 	my $attempts = 0;
 
+	my $query_value;
+
 	while ($attempts < $max_attempts)
 	{
-		my $result = IPC::Run::run $cmd, '<', \$query,
-		  '>', \$stdout, '2>', \$stderr;
+		$query_value = $session->query_tuples($query);
 
-		chomp($stdout);
-		chomp($stderr);
-
-		if ($stdout eq $expected && $stderr eq '')
-		{
-			return 1;
-		}
+		return 1 if ($query_value  // 'no value returned') eq $expected;
 
 		# Wait 0.1 second before retrying.
 		usleep(100_000);
@@ -2533,9 +2528,8 @@ $query
 expecting this output:
 $expected
 last actual query output:
-$stdout
-with stderr:
-$stderr);
+$query_value
+);
 	return 0;
 }
 
diff --git a/src/test/perl/PostgreSQL/Test/Session.pm b/src/test/perl/PostgreSQL/Test/Session.pm
new file mode 100644
index 0000000000..7333c3869c
--- /dev/null
+++ b/src/test/perl/PostgreSQL/Test/Session.pm
@@ -0,0 +1,162 @@
+package PostgreSQL::Test::Session;
+
+use strict;
+use warnings FATAL => 'all';
+
+
+use PostgreSQL::PqFFI;
+
+my $setup_ok;
+
+sub setup
+{
+	return if $setup_ok;
+	my $libdir = shift;
+	PostgreSQL::PqFFI::setup($libdir);
+	$setup_ok = 1;
+}
+
+sub new
+{
+	my $class = shift;
+	my $self = {};
+	bless $self, $class;
+	my %args = @_;
+	my $node = $args{node};
+	my $dbname = $args{dbname} || 'postgres';
+	die "bad node" unless $node->isa("PostgreSQL::Test::Cluster");
+	unless ($setup_ok)
+	{
+		my $libdir = $node->config_data('--libdir');
+		setup($libdir);
+	}
+	$self->{connstr} = $node->connstr($dbname);
+	$self->{conn} = PQconnectdb($self->{connstr});
+	return $self;
+}
+
+sub close
+{
+	my $self = shift;
+	PQfinish($self->{conn});
+	delete $self->{conn};
+}
+
+sub DESTROY
+{
+	my $self = shift;
+	$self->close if $self->{conn};
+}
+
+sub reconnect
+{
+	my $self = shift;
+	$self->close if $self->{conn};
+	$self->{conn} = PQconnectdb($self->{connstr});
+}
+
+# run some sql which doesn't return tuples
+
+sub do
+{
+	my $self = shift;
+	my $conn = $self->{conn};
+	foreach my $sql (@_)
+	{
+		my $result = PQexec($conn, $sql);
+		my $ok = $result && (PQresultStatus($result) == PGRES_COMMAND_OK);
+		PQclear($result);
+		return undef unless $ok;
+	}
+	return 1;
+}
+
+# run some sql that does return tuples
+
+sub query
+{
+	my $self = shift;
+	my $sql = shift;
+	my $conn = $self->{conn};
+	my $result = PQexec($conn, $sql);
+	my $ok = $result && (PQresultStatus($result) == PGRES_TUPLES_OK);
+	unless  ($ok)
+	{
+		PQclear($result) if $result;
+		return undef;
+	}
+	my $ntuples = PQntuples($result);
+	my $nfields = PQnfields($result);
+	my $res = {	names => [], types => [], rows => [], };
+	# assuming here that the strings returned by PQfname and PQgetvalue
+	# are mapped into perl space using setsvpv or similar and thus won't
+	# be affect by us calling PQclear on the result object.
+	foreach my $field (0 .. $nfields-1)
+	{
+		push(@{$res->{names}}, PQfname($result, $field));
+		push(@{$res->{types}}, PQftype($result, $field));
+	}
+	foreach my $nrow (0.. $ntuples - 1)
+	{
+		my $row = [];
+		foreach my $field ( 0 .. $nfields - 1)
+		{
+			my $val = PQgetvalue($result, $nrow, $field);
+			if (($val // "") eq "")
+			{
+				$val = undef if PQgetisnull($result, $nrow, $field);
+			}
+			push(@$row, $val);
+		}
+		push(@{$res->{rows}}, $row);
+	}
+	PQclear($result);
+	return $res;
+}
+
+sub query_oneval
+{
+	my $self = shift;
+	my $sql = shift;
+	my $missing_ok = shift; # default is not ok
+	my $conn = $self->{conn};
+	my $result = PQexec($conn, $sql);
+	my $ok = $result && (PQresultStatus($result) == PGRES_TUPLES_OK);
+	unless  ($ok)
+	{
+		PQclear($result) if $result;
+		return undef;
+	}
+	my $ntuples = PQntuples($result);
+	return undef if ($missing_ok && !$ntuples);
+	my $nfields = PQnfields($result);
+	die "$ntuples tuples != 1 or $nfields fields != 1"
+	  if $ntuples != 1 || $nfields != 1;
+	my $val = PQgetvalue($result, 0, 0);
+	if ($val eq "")
+	{
+		$val = undef if PGgetisnull($result, 0, 0);
+	}
+	PQclear($result);
+	return $val;
+}
+
+# return tuples like psql's -A -t mode.
+
+sub query_tuples
+{
+	my $self = shift;
+	my @results;
+	foreach my $sql (@_)
+	{
+		my $res = $self->query($sql);
+		# join will render undef as an empty string here
+		no warnings qw(uninitialized);
+		my @tuples = map { join('|', @$_); } @{$res->{rows}};
+		push(@results, join("\n",@tuples));
+	}
+	return join("\n",@results);
+}
+
+
+1;


^ permalink  raw  reply  [nested|flat] 41+ messages in thread


end of thread, other threads:[~2024-06-16 14:58 UTC | newest]

Thread overview: 41+ messages (download: mbox mbox.gz follow: Atom feed)
-- links below jump to the message on this page --
2020-08-19 12:34 [PATCH v2 2/5] Refactor pg_rewind for more clear decision making. Heikki Linnakangas <[email protected]>
2020-08-19 12:34 [PATCH 2/5] Refactor pg_rewind for more clear decision making. Heikki Linnakangas <[email protected]>
2020-08-19 12:34 [PATCH 2/5] Refactor pg_rewind for more clear decision making. Heikki Linnakangas <[email protected]>
2020-08-19 12:34 [PATCH v2 2/5] Refactor pg_rewind for more clear decision making. Heikki Linnakangas <[email protected]>
2020-08-19 12:34 [PATCH v2 2/5] Refactor pg_rewind for more clear decision making. Heikki Linnakangas <[email protected]>
2020-08-19 12:34 [PATCH 2/5] Refactor pg_rewind for more clear decision making. Heikki Linnakangas <[email protected]>
2020-08-19 12:34 [PATCH v2 2/5] Refactor pg_rewind for more clear decision making. Heikki Linnakangas <[email protected]>
2020-08-19 12:34 [PATCH 2/5] Refactor pg_rewind for more clear decision making. Heikki Linnakangas <[email protected]>
2020-08-19 12:34 [PATCH v2 2/5] Refactor pg_rewind for more clear decision making. Heikki Linnakangas <[email protected]>
2020-08-19 12:34 [PATCH 2/5] Refactor pg_rewind for more clear decision making. Heikki Linnakangas <[email protected]>
2020-08-19 12:34 [PATCH 2/5] Refactor pg_rewind for more clear decision making. Heikki Linnakangas <[email protected]>
2020-08-19 12:34 [PATCH v2 2/5] Refactor pg_rewind for more clear decision making. Heikki Linnakangas <[email protected]>
2020-08-19 12:34 [PATCH v3 2/5] Refactor pg_rewind for more clear decision making. Heikki Linnakangas <[email protected]>
2020-08-19 12:34 [PATCH v2 2/5] Refactor pg_rewind for more clear decision making. Heikki Linnakangas <[email protected]>
2020-08-19 12:34 [PATCH 2/5] Refactor pg_rewind for more clear decision making. Heikki Linnakangas <[email protected]>
2020-08-19 12:34 [PATCH 2/5] Refactor pg_rewind for more clear decision making. Heikki Linnakangas <[email protected]>
2020-08-19 12:34 [PATCH 2/5] Refactor pg_rewind for more clear decision making. Heikki Linnakangas <[email protected]>
2020-08-19 12:34 [PATCH 2/5] Refactor pg_rewind for more clear decision making. Heikki Linnakangas <[email protected]>
2020-08-19 12:34 [PATCH v2 2/5] Refactor pg_rewind for more clear decision making. Heikki Linnakangas <[email protected]>
2020-08-19 12:34 [PATCH 2/5] Refactor pg_rewind for more clear decision making. Heikki Linnakangas <[email protected]>
2020-08-19 12:34 [PATCH v2 2/5] Refactor pg_rewind for more clear decision making. Heikki Linnakangas <[email protected]>
2020-08-19 12:34 [PATCH v2 2/5] Refactor pg_rewind for more clear decision making. Heikki Linnakangas <[email protected]>
2020-08-19 12:34 [PATCH v2 2/5] Refactor pg_rewind for more clear decision making. Heikki Linnakangas <[email protected]>
2020-08-19 12:34 [PATCH v2 2/5] Refactor pg_rewind for more clear decision making. Heikki Linnakangas <[email protected]>
2020-08-19 12:34 [PATCH v2 2/5] Refactor pg_rewind for more clear decision making. Heikki Linnakangas <[email protected]>
2020-08-19 12:34 [PATCH 2/5] Refactor pg_rewind for more clear decision making. Heikki Linnakangas <[email protected]>
2020-08-19 12:34 [PATCH 2/5] Refactor pg_rewind for more clear decision making. Heikki Linnakangas <[email protected]>
2020-08-19 12:34 [PATCH 2/5] Refactor pg_rewind for more clear decision making. Heikki Linnakangas <[email protected]>
2020-08-19 12:34 [PATCH v2 2/5] Refactor pg_rewind for more clear decision making. Heikki Linnakangas <[email protected]>
2020-08-19 12:34 [PATCH v2 2/5] Refactor pg_rewind for more clear decision making. Heikki Linnakangas <[email protected]>
2020-08-19 12:34 [PATCH v2 2/5] Refactor pg_rewind for more clear decision making. Heikki Linnakangas <[email protected]>
2020-08-19 12:34 [PATCH v2 2/5] Refactor pg_rewind for more clear decision making. Heikki Linnakangas <[email protected]>
2020-08-19 12:34 [PATCH v2 2/5] Refactor pg_rewind for more clear decision making. Heikki Linnakangas <[email protected]>
2020-08-19 12:34 [PATCH v2 2/5] Refactor pg_rewind for more clear decision making. Heikki Linnakangas <[email protected]>
2020-08-19 12:34 [PATCH 2/5] Refactor pg_rewind for more clear decision making. Heikki Linnakangas <[email protected]>
2020-08-19 12:34 [PATCH 2/5] Refactor pg_rewind for more clear decision making. Heikki Linnakangas <[email protected]>
2020-08-19 12:34 [PATCH 2/5] Refactor pg_rewind for more clear decision making. Heikki Linnakangas <[email protected]>
2020-08-19 12:34 [PATCH 2/5] Refactor pg_rewind for more clear decision making. Heikki Linnakangas <[email protected]>
2020-08-19 12:34 [PATCH 2/5] Refactor pg_rewind for more clear decision making. Heikki Linnakangas <[email protected]>
2020-09-24 17:08 [PATCH v3 2/5] Refactor pg_rewind for more clear decision making. Heikki Linnakangas <[email protected]>
2024-06-16 14:58 Re: Using LibPq in TAP tests via FFI Andrew Dunstan <[email protected]>

This inbox is served by agora; see mirroring instructions
for how to clone and mirror all data and code used for this inbox