diff --git a/src/bin/pg_rewind/libpq_fetch.c b/src/bin/pg_rewind/libpq_fetch.c
index 5914b15017..821e4b0c81 100644
--- a/src/bin/pg_rewind/libpq_fetch.c
+++ b/src/bin/pg_rewind/libpq_fetch.c
@@ -337,7 +337,18 @@ receiveFileChunks(const char *sql)
pg_log(PG_DEBUG, "received chunk for file \"%s\", offset %s, size %d\n",
filename, chunkoff_str, chunksize);
- open_target_file(filename, false);
+ /*
+ * Truncate new files which are fully copied. We are sure to not
+ * truncate a file whose data has been partially fetched for two
+ * reasons:
+ * 1) The set of file ranges is ordered so as the chunks of each
+ * file is processed sequentially.
+ * 2) Only files fully copied register a chunk with an offset of
+ * 0, which means that the beginning of a file is received, so it
+ * should be truncated first. Note that the truncation is lossy,
+ * and may be tried on a file which does not exist locally.
+ */
+ open_target_file(filename, chunkoff == 0);
write_target_range(chunk, chunkoff, chunksize);
@@ -464,8 +475,6 @@ libpq_executeFileMap(filemap_t *map)
break;
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);
break;
@@ -501,12 +510,17 @@ libpq_executeFileMap(filemap_t *map)
/*
* We've now copied the list of file ranges that we need to fetch to the
- * temporary table. Now, actually fetch all of those ranges.
+ * temporary table. Now, actually fetch all of those ranges. The elements
+ * fetched are ordered so as the truncation of files fully copied from the
+ * source server is done after receiving their first chunk. This also
+ * gives a better sequential performance to the operations, especially
+ * when working on large files.
*/
sql =
"SELECT path, begin,\n"
" pg_read_binary_file(path, begin, len, true) AS chunk\n"
- "FROM fetchchunks\n";
+ "FROM fetchchunks\n"
+ "ORDER BY path, begin\n";
receiveFileChunks(sql);
}
diff --git a/src/bin/pg_rewind/t/006_readonly.pl b/src/bin/pg_rewind/t/006_readonly.pl
new file mode 100644
index 0000000000..51db93a9a0
--- /dev/null
+++ b/src/bin/pg_rewind/t/006_readonly.pl
@@ -0,0 +1,85 @@
+# Test how pg_rewind reacts to read-only files in the data dirs.
+# All such files should be ignored in the process.
+
+use strict;
+use warnings;
+use TestLib;
+use Test::More tests => 2;
+
+use File::Copy;
+use File::Find;
+
+use RewindTest;
+
+my $test_mode = "remote";
+
+RewindTest::setup_cluster($test_mode);
+RewindTest::start_master();
+RewindTest::create_standby($test_mode);
+
+# Create the same read-only file in standby and master
+my $test_master_datadir = $node_master->data_dir;
+my $test_standby_datadir = $node_standby->data_dir;
+my $readonly_master = "$test_master_datadir/readonly_file";
+my $readonly_standby = "$test_standby_datadir/readonly_file";
+
+append_to_file($readonly_master, "in master");
+append_to_file($readonly_standby, "in standby");
+chmod 0400, $readonly_master, $readonly_standby;
+
+RewindTest::promote_standby();
+
+# Stop the master and run pg_rewind.
+$node_master->stop;
+
+my $master_pgdata = $node_master->data_dir;
+my $standby_pgdata = $node_standby->data_dir;
+my $standby_connstr = $node_standby->connstr('postgres');
+my $tmp_folder = TestLib::tempdir;
+
+# Keep a temporary postgresql.conf for master node or it would be
+# overwritten during the rewind.
+copy(
+ "$master_pgdata/postgresql.conf",
+ "$tmp_folder/master-postgresql.conf.tmp");
+
+# Including read-only data in the source and the target will
+# cause pg_rewind to fail.
+my $rewind_command = [ 'pg_rewind', "--debug",
+ "--source-server", $standby_connstr,
+ "--target-pgdata=$master_pgdata" ];
+
+command_fails($rewind_command, 'pg_rewind fails with read-only');
+
+# Need to put the server back into a clean state for next rewind.
+# Move back postgresql.conf with old settings to allow the node to
+# start and stop, allowing the follow-up rewind to work properly.
+move("$tmp_folder/master-postgresql.conf.tmp",
+ "$master_pgdata/postgresql.conf");
+$node_master->start;
+$node_master->stop;
+
+# Now remove the read-only data on both sides, the data folder
+# from the previous attempt should still be able to work.
+unlink($readonly_master);
+unlink($readonly_standby);
+command_ok($rewind_command, 'pg_rewind passes without read-only');
+
+# Now move back postgresql.conf with old settings
+move("$tmp_folder/master-postgresql.conf.tmp",
+ "$master_pgdata/postgresql.conf");
+
+# Plug-in rewound node to the now-promoted standby node
+my $port_standby = $node_standby->port;
+$node_master->append_conf('recovery.conf', qq(
+primary_conninfo='port=$port_standby'
+standby_mode=on
+recovery_target_timeline='latest'
+));
+
+# Restart the master to check that rewind went correctly.
+$node_master->start;
+
+RewindTest::clean_rewind_test();
+
+exit(0);