1use strict;
2use warnings;
3use Cwd;
4use Config;
5use PostgresNode;
6use TestLib;
7use Test::More tests => 72;
8
9program_help_ok('pg_basebackup');
10program_version_ok('pg_basebackup');
11program_options_handling_ok('pg_basebackup');
12
13my $tempdir = TestLib::tempdir;
14
15my $node = get_new_node('main');
16
17# Initialize node without replication settings
18$node->init;
19$node->start;
20my $pgdata = $node->data_dir;
21
22$node->command_fails(['pg_basebackup'],
23	'pg_basebackup needs target directory specified');
24
25# Some Windows ANSI code pages may reject this filename, in which case we
26# quietly proceed without this bit of test coverage.
27if (open my $badchars, '>>', "$tempdir/pgdata/FOO\xe0\xe0\xe0BAR")
28{
29	print $badchars "test backup of file with non-UTF8 name\n";
30	close $badchars;
31}
32
33$node->set_replication_conf();
34system_or_bail 'pg_ctl', '-D', $pgdata, 'reload';
35
36$node->command_fails(
37	[ 'pg_basebackup', '-D', "$tempdir/backup" ],
38	'pg_basebackup fails because of WAL configuration');
39
40ok(!-d "$tempdir/backup", 'backup directory was cleaned up');
41
42$node->command_fails([ 'pg_basebackup', '-D', "$tempdir/backup", '-n' ],
43	'failing run with no-clean option');
44
45ok(-d "$tempdir/backup", 'backup directory was created and left behind');
46
47open my $conf, '>>', "$pgdata/postgresql.conf";
48print $conf "max_replication_slots = 10\n";
49print $conf "max_wal_senders = 10\n";
50print $conf "wal_level = replica\n";
51close $conf;
52$node->restart;
53
54# Write some files to test that they are not copied.
55foreach my $filename (
56	qw(backup_label tablespace_map postgresql.auto.conf.tmp current_logfiles.tmp)
57  )
58{
59	open my $file, '>>', "$pgdata/$filename";
60	print $file "DONOTCOPY";
61	close $file;
62}
63
64$node->command_ok([ 'pg_basebackup', '-D', "$tempdir/backup", '-X', 'none' ],
65	'pg_basebackup runs');
66ok(-f "$tempdir/backup/PG_VERSION", 'backup was created');
67
68# Only archive_status directory should be copied in pg_wal/.
69is_deeply(
70	[ sort(slurp_dir("$tempdir/backup/pg_wal/")) ],
71	[ sort qw(. .. archive_status) ],
72	'no WAL files copied');
73
74# Contents of these directories should not be copied.
75foreach my $dirname (
76	qw(pg_dynshmem pg_notify pg_replslot pg_serial pg_snapshots pg_stat_tmp pg_subtrans)
77  )
78{
79	is_deeply(
80		[ sort(slurp_dir("$tempdir/backup/$dirname/")) ],
81		[ sort qw(. ..) ],
82		"contents of $dirname/ not copied");
83}
84
85# These files should not be copied.
86foreach my $filename (
87	qw(postgresql.auto.conf.tmp postmaster.opts postmaster.pid tablespace_map current_logfiles.tmp)
88  )
89{
90	ok(!-f "$tempdir/backup/$filename", "$filename not copied");
91}
92
93# Make sure existing backup_label was ignored.
94isnt(slurp_file("$tempdir/backup/backup_label"),
95	'DONOTCOPY', 'existing backup_label not copied');
96
97$node->command_ok(
98	[   'pg_basebackup', '-D', "$tempdir/backup2", '--waldir',
99		"$tempdir/xlog2" ],
100	'separate xlog directory');
101ok(-f "$tempdir/backup2/PG_VERSION", 'backup was created');
102ok(-d "$tempdir/xlog2/",             'xlog directory was created');
103
104$node->command_ok([ 'pg_basebackup', '-D', "$tempdir/tarbackup", '-Ft' ],
105	'tar format');
106ok(-f "$tempdir/tarbackup/base.tar", 'backup tar was created');
107
108$node->command_fails(
109	[ 'pg_basebackup', '-D', "$tempdir/backup_foo", '-Fp', "-T=/foo" ],
110	'-T with empty old directory fails');
111$node->command_fails(
112	[ 'pg_basebackup', '-D', "$tempdir/backup_foo", '-Fp', "-T/foo=" ],
113	'-T with empty new directory fails');
114$node->command_fails(
115	[   'pg_basebackup', '-D', "$tempdir/backup_foo", '-Fp',
116		"-T/foo=/bar=/baz" ],
117	'-T with multiple = fails');
118$node->command_fails(
119	[ 'pg_basebackup', '-D', "$tempdir/backup_foo", '-Fp', "-Tfoo=/bar" ],
120	'-T with old directory not absolute fails');
121$node->command_fails(
122	[ 'pg_basebackup', '-D', "$tempdir/backup_foo", '-Fp', "-T/foo=bar" ],
123	'-T with new directory not absolute fails');
124$node->command_fails(
125	[ 'pg_basebackup', '-D', "$tempdir/backup_foo", '-Fp', "-Tfoo" ],
126	'-T with invalid format fails');
127
128# Tar format doesn't support filenames longer than 100 bytes.
129my $superlongname = "superlongname_" . ("x" x 100);
130my $superlongpath = "$pgdata/$superlongname";
131
132open my $file, '>', "$superlongpath"
133  or die "unable to create file $superlongpath";
134close $file;
135$node->command_fails(
136	[ 'pg_basebackup', '-D', "$tempdir/tarbackup_l1", '-Ft' ],
137	'pg_basebackup tar with long name fails');
138unlink "$pgdata/$superlongname";
139
140# The following tests test symlinks. Windows doesn't have symlinks, so
141# skip on Windows.
142SKIP:
143{
144	skip "symlinks not supported on Windows", 11 if ($windows_os);
145
146	# Move pg_replslot out of $pgdata and create a symlink to it.
147	$node->stop;
148
149	rename("$pgdata/pg_replslot", "$tempdir/pg_replslot")
150	  or BAIL_OUT "could not move $pgdata/pg_replslot";
151	symlink("$tempdir/pg_replslot", "$pgdata/pg_replslot")
152	  or BAIL_OUT "could not symlink to $pgdata/pg_replslot";
153
154	$node->start;
155
156	# Create a temporary directory in the system location and symlink it
157	# to our physical temp location.  That way we can use shorter names
158	# for the tablespace directories, which hopefully won't run afoul of
159	# the 99 character length limit.
160	my $shorter_tempdir = TestLib::tempdir_short . "/tempdir";
161	symlink "$tempdir", $shorter_tempdir;
162
163	mkdir "$tempdir/tblspc1";
164	$node->safe_psql('postgres',
165		"CREATE TABLESPACE tblspc1 LOCATION '$shorter_tempdir/tblspc1';");
166	$node->safe_psql('postgres',
167		"CREATE TABLE test1 (a int) TABLESPACE tblspc1;");
168	$node->command_ok([ 'pg_basebackup', '-D', "$tempdir/tarbackup2", '-Ft' ],
169		'tar format with tablespaces');
170	ok(-f "$tempdir/tarbackup2/base.tar", 'backup tar was created');
171	my @tblspc_tars = glob "$tempdir/tarbackup2/[0-9]*.tar";
172	is(scalar(@tblspc_tars), 1, 'one tablespace tar was created');
173
174	$node->command_fails(
175		[ 'pg_basebackup', '-D', "$tempdir/backup1", '-Fp' ],
176		'plain format with tablespaces fails without tablespace mapping');
177
178	$node->command_ok(
179		[   'pg_basebackup', '-D', "$tempdir/backup1", '-Fp',
180			"-T$shorter_tempdir/tblspc1=$tempdir/tbackup/tblspc1" ],
181		'plain format with tablespaces succeeds with tablespace mapping');
182	ok(-d "$tempdir/tbackup/tblspc1", 'tablespace was relocated');
183	opendir(my $dh, "$pgdata/pg_tblspc") or die;
184	ok( (   grep {
185				-l "$tempdir/backup1/pg_tblspc/$_"
186				  and readlink "$tempdir/backup1/pg_tblspc/$_" eq
187				  "$tempdir/tbackup/tblspc1"
188			  } readdir($dh)),
189		"tablespace symlink was updated");
190	closedir $dh;
191
192	ok( -d "$tempdir/backup1/pg_replslot",
193		'pg_replslot symlink copied as directory');
194
195	mkdir "$tempdir/tbl=spc2";
196	$node->safe_psql('postgres', "DROP TABLE test1;");
197	$node->safe_psql('postgres', "DROP TABLESPACE tblspc1;");
198	$node->safe_psql('postgres',
199		"CREATE TABLESPACE tblspc2 LOCATION '$shorter_tempdir/tbl=spc2';");
200	$node->command_ok(
201		[   'pg_basebackup', '-D', "$tempdir/backup3", '-Fp',
202			"-T$shorter_tempdir/tbl\\=spc2=$tempdir/tbackup/tbl\\=spc2" ],
203		'mapping tablespace with = sign in path');
204	ok(-d "$tempdir/tbackup/tbl=spc2",
205		'tablespace with = sign was relocated');
206	$node->safe_psql('postgres', "DROP TABLESPACE tblspc2;");
207
208	mkdir "$tempdir/$superlongname";
209	$node->safe_psql('postgres',
210		"CREATE TABLESPACE tblspc3 LOCATION '$tempdir/$superlongname';");
211	$node->command_ok(
212		[ 'pg_basebackup', '-D', "$tempdir/tarbackup_l3", '-Ft' ],
213		'pg_basebackup tar with long symlink target');
214	$node->safe_psql('postgres', "DROP TABLESPACE tblspc3;");
215}
216
217$node->command_ok([ 'pg_basebackup', '-D', "$tempdir/backupR", '-R' ],
218	'pg_basebackup -R runs');
219ok(-f "$tempdir/backupR/recovery.conf", 'recovery.conf was created');
220my $recovery_conf = slurp_file "$tempdir/backupR/recovery.conf";
221
222my $port = $node->port;
223like(
224	$recovery_conf,
225	qr/^standby_mode = 'on'\n/m,
226	'recovery.conf sets standby_mode');
227like(
228	$recovery_conf,
229	qr/^primary_conninfo = '.*port=$port.*'\n/m,
230	'recovery.conf sets primary_conninfo');
231
232$node->command_ok(
233	[ 'pg_basebackup', '-D', "$tempdir/backupxd" ],
234	'pg_basebackup runs in default xlog mode');
235ok(grep(/^[0-9A-F]{24}$/, slurp_dir("$tempdir/backupxd/pg_wal")),
236	'WAL files copied');
237
238$node->command_ok(
239	[ 'pg_basebackup', '-D', "$tempdir/backupxf", '-X', 'fetch' ],
240	'pg_basebackup -X fetch runs');
241ok(grep(/^[0-9A-F]{24}$/, slurp_dir("$tempdir/backupxf/pg_wal")),
242	'WAL files copied');
243$node->command_ok(
244	[ 'pg_basebackup', '-D', "$tempdir/backupxs", '-X', 'stream' ],
245	'pg_basebackup -X stream runs');
246ok(grep(/^[0-9A-F]{24}$/, slurp_dir("$tempdir/backupxf/pg_wal")),
247	'WAL files copied');
248$node->command_ok(
249	[ 'pg_basebackup', '-D', "$tempdir/backupxst", '-X', 'stream', '-Ft' ],
250	'pg_basebackup -X stream runs in tar mode');
251ok(-f "$tempdir/backupxst/pg_wal.tar", "tar file was created");
252$node->command_ok(
253	[   'pg_basebackup',         '-D',
254		"$tempdir/backupnoslot", '-X',
255		'stream',                '--no-slot' ],
256	'pg_basebackup -X stream runs with --no-slot');
257
258$node->command_fails(
259	[   'pg_basebackup',             '-D',
260		"$tempdir/backupxs_sl_fail", '-X',
261		'stream',                    '-S',
262		'slot1' ],
263	'pg_basebackup fails with nonexistent replication slot');
264
265$node->safe_psql('postgres',
266	q{SELECT * FROM pg_create_physical_replication_slot('slot1')});
267my $lsn = $node->safe_psql('postgres',
268	q{SELECT restart_lsn FROM pg_replication_slots WHERE slot_name = 'slot1'}
269);
270is($lsn, '', 'restart LSN of new slot is null');
271$node->command_fails(
272	[ 'pg_basebackup', '-D', "$tempdir/fail", '-S', 'slot1', '-X', 'none' ],
273	'pg_basebackup with replication slot fails without WAL streaming');
274$node->command_ok(
275	[   'pg_basebackup', '-D', "$tempdir/backupxs_sl", '-X',
276		'stream',        '-S', 'slot1' ],
277	'pg_basebackup -X stream with replication slot runs');
278$lsn = $node->safe_psql('postgres',
279	q{SELECT restart_lsn FROM pg_replication_slots WHERE slot_name = 'slot1'}
280);
281like($lsn, qr!^0/[0-9A-Z]{7,8}$!, 'restart LSN of slot has advanced');
282
283$node->command_ok(
284	[   'pg_basebackup', '-D', "$tempdir/backupxs_sl_R", '-X',
285		'stream',        '-S', 'slot1',                  '-R' ],
286	'pg_basebackup with replication slot and -R runs');
287like(
288	slurp_file("$tempdir/backupxs_sl_R/recovery.conf"),
289	qr/^primary_slot_name = 'slot1'\n/m,
290	'recovery.conf sets primary_slot_name');
291