Skip to content

Commit 71b3cc8

Browse files
committed
Restrict pg_rewind to whitelisted directories.
This is intended to be a minimum working version and in fact builds and passes tests. Note that tests for extra files have been changed to reflect new behavior and additional debugging informnation added in to output in case of failure. The patch iterates through a series of set directories to synchronize them only. This improves predictability of the complete state of the system after a rewind. One important outstanding question here is whether we need to ensure the possibility of backing up other files if they exist via an --include-path command line switch (this would not be a glob). In the thread discussing this patch, Michael Paquier has expressed concern about configuration files created by extensions or other components not being copied. I could add such a switch but the patch is long enough, and it is unclear enough to the extent this is needed at present, so I am leaving it at the reviewer's discretion whether I should add this here or submit a second patch later to add the ability to add additional paths to the filemap.
1 parent 846fcc8 commit 71b3cc8

File tree

7 files changed

+105
-55
lines changed

7 files changed

+105
-55
lines changed

doc/src/sgml/ref/pg_rewind.sgml

Lines changed: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -48,16 +48,26 @@ PostgreSQL documentation
4848
</para>
4949

5050
<para>
51-
The result is equivalent to replacing the target data directory with the
52-
source one. Only changed blocks from relation files are copied;
53-
all other files are copied in full, including configuration files. The
54-
advantage of <application>pg_rewind</application> over taking a new base backup, or
55-
tools like <application>rsync</application>, is that <application>pg_rewind</application> does
56-
not require reading through unchanged blocks in the cluster. This makes
51+
The result is equivalent to replacing the data-related files in the target
52+
data directory with the source one. Only changed blocks from relation files
53+
are copied; all other files relating to control or WAL information are copied
54+
in full. The advantage of <application>pg_rewind</> over taking a new base
55+
backup, or tools like <application>rsync</>, is that <application>pg_rewind</>
56+
does not require reading through unchanged blocks in the cluster. This makes
5757
it a lot faster when the database is large and only a small
5858
fraction of blocks differ between the clusters.
5959
</para>
6060

61+
<para>
62+
A second advantage is predictability. <application>pg_rewind</> is aware of
63+
what directories are relevant to restoring replication and which ones are not.
64+
The result is that you get something of a guaranteed state at the end. Log
65+
files on the source are unlikely to clobber those on the client. The
66+
<filename>postgresql.conf.auto</> is unlikely to be copied over. Replication
67+
slot information is removed (and must be added again), and so forth. Your
68+
system is as it had been before, but the data is synchronized from the master.
69+
</para>
70+
6171
<para>
6272
<application>pg_rewind</application> examines the timeline histories of the source
6373
and target clusters to determine the point where they diverged, and
@@ -84,7 +94,9 @@ PostgreSQL documentation
8494
<application>pg_rewind</application> session, it must be made available when the
8595
target server is started. This can be done by creating a
8696
<filename>recovery.conf</filename> file in the target data directory with a
87-
suitable <varname>restore_command</varname>.
97+
suitable <varname>restore_command</varbane>. Alternatively replication slots can
98+
be set just before promotion to ensure that the wal files have not been
99+
removed.
88100
</para>
89101

90102
<para>
@@ -206,7 +218,7 @@ PostgreSQL documentation
206218

207219
<para>
208220
The basic idea is to copy all file system-level changes from the source
209-
cluster to the target cluster:
221+
cluster to the target cluster if they implicate the data stored:
210222
</para>
211223

212224
<procedure>
@@ -229,9 +241,7 @@ PostgreSQL documentation
229241
</step>
230242
<step>
231243
<para>
232-
Copy all other files such as <filename>pg_xact</filename> and
233-
configuration files from the source cluster to the target cluster
234-
(everything except the relation files).
244+
Copy wal-related files such as those in <filename>pg_xact</filename>.
235245
</para>
236246
</step>
237247
<step>

src/bin/pg_rewind/copy_fetch.c

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,20 +26,45 @@
2626
static void recurse_dir(const char *datadir, const char *path,
2727
process_file_callback_t callback);
2828

29+
/* List of directories to synchronize:
30+
* base data dirs (and ablespaces)
31+
* wal/transaction data
32+
* and that is it.
33+
*
34+
* This array is null-terminated to make
35+
* it easy to expand
36+
*/
37+
38+
const char *rewind_dirs[] = {
39+
"base", // Default tablespace
40+
"global", // global tablespace
41+
"pg_commit_ts", // In case we need to do PITR before up to sync
42+
"pg_logical", // WAL related and no good reason to exclude
43+
"pg_multixact", // WAL related and may need for vacuum-related reasons
44+
"pg_tblspc", // Pther tablespaces
45+
"pg_twophase", // mostly to *clear*
46+
"pg_wal", // WAL
47+
"pg_xact", // Commits of transactions
48+
NULL
49+
};
50+
2951
static void execute_pagemap(datapagemap_t *pagemap, const char *path);
3052

3153
/*
3254
* Traverse through all files in a data directory, calling 'callback'
3355
* for each file.
3456
*/
3557
void
36-
traverse_datadir(const char *datadir, process_file_callback_t callback)
58+
traverse_rewinddirs(const char *datadir, process_file_callback_t callback)
3759
{
38-
recurse_dir(datadir, NULL, callback);
60+
int i;
61+
for(i = 0; rewind_dirs[i] != NULL; i++){
62+
recurse_dir(datadir, rewind_dirs[i], callback);
63+
}
3964
}
4065

4166
/*
42-
* recursive part of traverse_datadir
67+
* recursive part of traverse_rewinddirs
4368
*
4469
* parentpath is the current subdirectory's path relative to datadir,
4570
* or NULL at the top level.

src/bin/pg_rewind/fetch.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ void
2828
fetchSourceFileList(void)
2929
{
3030
if (datadir_source)
31-
traverse_datadir(datadir_source, &process_source_file);
31+
traverse_rewinddirs(datadir_source, &process_source_file);
3232
else
3333
libpqProcessFileList();
3434
}

src/bin/pg_rewind/fetch.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,11 @@ extern void libpqConnect(const char *connstr);
3636
extern XLogRecPtr libpqGetCurrentXlogInsertLocation(void);
3737

3838
/* in copy_fetch.c */
39+
extern const char *rewind_dirs[];
3940
extern void copy_executeFileMap(filemap_t *map);
4041

4142
typedef void (*process_file_callback_t) (const char *path, file_type_t type, size_t size, const char *link_target);
4243
extern void traverse_datadir(const char *datadir, process_file_callback_t callback);
44+
extern void traverse_rewinddirs(const char *datadir, process_file_callback_t callback);
4345

4446
#endif /* FETCH_H */

src/bin/pg_rewind/libpq_fetch.c

Lines changed: 46 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,7 @@ libpqProcessFileList(void)
147147
{
148148
PGresult *res;
149149
const char *sql;
150-
int i;
150+
int i, p;
151151

152152
/*
153153
* Create a recursive directory listing of the whole data directory.
@@ -163,7 +163,8 @@ libpqProcessFileList(void)
163163
"WITH RECURSIVE files (path, filename, size, isdir) AS (\n"
164164
" SELECT '' AS path, filename, size, isdir FROM\n"
165165
" (SELECT pg_ls_dir('.', true, false) AS filename) AS fn,\n"
166-
" pg_stat_file(fn.filename, true) AS this\n"
166+
" LATERAL pg_stat_file(fn.filename, true) AS this\n"
167+
" WHERE filename = $1\n"
167168
" UNION ALL\n"
168169
" SELECT parent.path || parent.filename || '/' AS path,\n"
169170
" fn, this.size, this.isdir\n"
@@ -177,44 +178,56 @@ libpqProcessFileList(void)
177178
"FROM files\n"
178179
"LEFT OUTER JOIN pg_tablespace ON files.path = 'pg_tblspc/'\n"
179180
" AND oid::text = files.filename\n";
180-
res = PQexec(conn, sql);
181181

182-
if (PQresultStatus(res) != PGRES_TUPLES_OK)
183-
pg_fatal("could not fetch file list: %s",
184-
PQresultErrorMessage(res));
182+
/* Going through the directories in a loop. Doing it this way
183+
* makes it easier to add more inclusions later.
184+
*
185+
* Note that the query filters out on top-level directories before
186+
* recursion so this will not give us problems in terms of listing
187+
* lots of files many times.
188+
*/
189+
for (p = 0; rewind_dirs[p] != NULL; ++p)
190+
{
191+
const char *paths[1];
192+
paths[0] = rewind_dirs[p];
193+
res = PQexecParams(conn, sql, 1, NULL, paths, NULL, NULL, 0);
185194

186-
/* sanity check the result set */
187-
if (PQnfields(res) != 4)
188-
pg_fatal("unexpected result set while fetching file list\n");
195+
if (PQresultStatus(res) != PGRES_TUPLES_OK)
196+
pg_fatal("could not fetch file list: %s",
197+
PQresultErrorMessage(res));
189198

190-
/* Read result to local variables */
191-
for (i = 0; i < PQntuples(res); i++)
192-
{
193-
char *path = PQgetvalue(res, i, 0);
194-
int64 filesize = atol(PQgetvalue(res, i, 1));
195-
bool isdir = (strcmp(PQgetvalue(res, i, 2), "t") == 0);
196-
char *link_target = PQgetvalue(res, i, 3);
197-
file_type_t type;
199+
/* sanity check the result set */
200+
if (PQnfields(res) != 4)
201+
pg_fatal("unexpected result set while fetching file list\n");
198202

199-
if (PQgetisnull(res, 0, 1))
203+
/* Read result to local variables */
204+
for (i = 0; i < PQntuples(res); i++)
200205
{
201-
/*
202-
* The file was removed from the server while the query was
203-
* running. Ignore it.
204-
*/
205-
continue;
206+
char *path = PQgetvalue(res, i, 0);
207+
int64 filesize = atol(PQgetvalue(res, i, 1));
208+
bool isdir = (strcmp(PQgetvalue(res, i, 2), "t") == 0);
209+
char *link_target = PQgetvalue(res, i, 3);
210+
file_type_t type;
211+
212+
if (PQgetisnull(res, 0, 1))
213+
{
214+
/*
215+
* The file was removed from the server while the query was
216+
* running. Ignore it.
217+
*/
218+
continue;
219+
}
220+
221+
if (link_target[0])
222+
type = FILE_TYPE_SYMLINK;
223+
else if (isdir)
224+
type = FILE_TYPE_DIRECTORY;
225+
else
226+
type = FILE_TYPE_REGULAR;
227+
process_source_file(path, type, filesize, link_target);
206228
}
207-
208-
if (link_target[0])
209-
type = FILE_TYPE_SYMLINK;
210-
else if (isdir)
211-
type = FILE_TYPE_DIRECTORY;
212-
else
213-
type = FILE_TYPE_REGULAR;
214-
215-
process_source_file(path, type, filesize, link_target);
229+
PQclear(res);
216230
}
217-
PQclear(res);
218231
}
219232

220233
/*----

src/bin/pg_rewind/pg_rewind.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -287,7 +287,7 @@ main(int argc, char **argv)
287287
pg_log(PG_PROGRESS, "reading source file list\n");
288288
fetchSourceFileList();
289289
pg_log(PG_PROGRESS, "reading target file list\n");
290-
traverse_datadir(datadir_target, &process_target_file);
290+
traverse_rewinddirs(datadir_target, &process_target_file);
291291

292292
/*
293293
* Read the target WAL from last checkpoint before the point of fork, to

src/bin/pg_rewind/t/003_extrafiles.pl

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -71,12 +71,12 @@ sub run_test
7171
"$test_master_datadir/tst_both_dir/both_file2",
7272
"$test_master_datadir/tst_both_dir/both_subdir",
7373
"$test_master_datadir/tst_both_dir/both_subdir/both_file3",
74-
"$test_master_datadir/tst_standby_dir",
75-
"$test_master_datadir/tst_standby_dir/standby_file1",
76-
"$test_master_datadir/tst_standby_dir/standby_file2",
77-
"$test_master_datadir/tst_standby_dir/standby_subdir",
78-
"$test_master_datadir/tst_standby_dir/standby_subdir/standby_file3" ],
79-
"file lists match");
74+
"$test_master_datadir/tst_master_dir",
75+
"$test_master_datadir/tst_master_dir/master_file1",
76+
"$test_master_datadir/tst_master_dir/master_file2",
77+
"$test_master_datadir/tst_master_dir/master_subdir",
78+
"$test_master_datadir/tst_master_dir/master_subdir/master_file3", ],
79+
"file lists match") or (diag("Files found:"), diag(explain(\@paths)));
8080

8181
RewindTest::clean_rewind_test();
8282
}

0 commit comments

Comments
 (0)