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);