diff --git a/doc/src/sgml/ref/pg_rewind.sgml b/doc/src/sgml/ref/pg_rewind.sgml
index 520d843f0e..0952f3903e 100644
--- a/doc/src/sgml/ref/pg_rewind.sgml
+++ b/doc/src/sgml/ref/pg_rewind.sgml
@@ -95,6 +95,25 @@ PostgreSQL documentation
are currently on by default.
must also be set to on, but is enabled by default.
+
+
+ Before rewriting any files on the target data directory,
+ pg_rewind checks if any files are writable,
+ failing immediately if that is not the case. It is possible to
+ execute again a rewind after fixing the permission issues. This can
+ happen for example for read-only SSL-related configuration files.
+ Note that if the file is copied from the source to the target, then
+ it may be necessary to rebuild the links properly as a rewind would
+ have caused those files to be created with newer permission sets.
+
+
+
+
+ If pg_rewind fails while processing, then
+ the data folder of the target is likely not in a state that can be
+ recovered. In such a case, taking a new fresh backup is recommended.
+
+
diff --git a/src/bin/pg_rewind/filemap.c b/src/bin/pg_rewind/filemap.c
index c3fc519895..c0b58c76ce 100644
--- a/src/bin/pg_rewind/filemap.c
+++ b/src/bin/pg_rewind/filemap.c
@@ -189,7 +189,56 @@ process_source_file(const char *path, file_type_t type, size_t newsize,
exists = false;
}
else
- exists = true;
+ {
+ int fd;
+
+ /*
+ * The file exists on the source and the target. Now check if
+ * the file is writable.
+ */
+ fd = open(localpath, O_WRONLY);
+
+ if (fd < 0)
+ {
+ if (errno == EISDIR)
+ {
+ /*
+ * No need to worry about directories, those are not
+ * valid targets for writes.
+ */
+ exists = true;
+ }
+ else if (errno == EACCES)
+ {
+ /*
+ * The file exists on the source and the target but it is
+ * not writable on target. In order to avoid failures
+ * mid-flight when processing the data directory, fail
+ * immediately and let the user know. This way, if anything
+ * like a read-only file is found, then pg_rewind complains
+ * and a successive run can be completed after doing some
+ * cleanup on this target data directory.
+ */
+ pg_fatal("file \"%s\" exists on both target and source server, but is not writable on target.\n"
+ "Please check the permissions on \"%s\" before executing again pg_rewind.\n",
+ path, localpath);
+ }
+ else
+ {
+ /*
+ * Complain about anything else, that should not happen,
+ * even ENOENT which has already been checked above.
+ */
+ pg_fatal("could not open file \"%s\": %s\n",
+ localpath, strerror(errno));
+ }
+ }
+ else
+ {
+ exists = true;
+ close(fd);
+ }
+ }
switch (type)
{
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..489e1f4593
--- /dev/null
+++ b/src/bin/pg_rewind/t/006_readonly.pl
@@ -0,0 +1,77 @@
+# 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');
+
+# 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);