1
0
mirror of https://github.com/postgres/postgres.git synced 2025-05-28 05:21:27 +03:00

pg_combinebackup: Error if incremental file exists in full backup.

Suppose that you run a command like "pg_combinebackup b1 b2 -o output",
but both b1 and b2 contain an INCREMENTAL.$something file in a directory
that is expected to contain relation files. This is an error, but the
previous code would not detect the problem and instead write a garbage
full file named $something to the output directory. This commit adds
code to detect the error and a test case to verify the behavior.

It's difficult to imagine that this will ever happen unless someone
is intentionally trying to break incremental backup, but per discussion,
let's consider that the lack of adequate sanity checking in this area is
a bug and back-patch to v17, where incremental backup was introduced.

Patch by me, reviewed by Bertrand Drouvot and Amul Sul.

Discussion: http://postgr.es/m/CA+TgmoaD7dBYPqe7kMtO0dyto7rd0rUh7joh=JPUSaFszKY6Pg@mail.gmail.com
This commit is contained in:
Robert Haas 2024-11-04 10:11:05 -05:00
parent 0d635b615d
commit e367114429
3 changed files with 75 additions and 0 deletions

View File

@ -36,6 +36,7 @@ tests += {
't/006_db_file_copy.pl',
't/007_wal_level_minimal.pl',
't/008_promote.pl',
't/009_no_full_file.pl',
],
}
}

View File

@ -326,11 +326,19 @@ reconstruct_from_incremental_file(char *input_filename,
* result, then forget about performing reconstruction and just copy that
* file in its entirety.
*
* If we have only incremental files, and there's no full file at any
* point in the backup chain, something has gone wrong. Emit an error.
*
* Otherwise, reconstruct.
*/
if (copy_source != NULL)
copy_file(copy_source->filename, output_filename,
&checksum_ctx, copy_method, dry_run);
else if (sidx == 0 && source[0]->header_length != 0)
{
pg_fatal("full backup contains unexpected incremental file \"%s\"",
source[0]->filename);
}
else
{
write_reconstructed_file(input_filename, output_filename,

View File

@ -0,0 +1,66 @@
# Copyright (c) 2021-2024, PostgreSQL Global Development Group
use strict;
use warnings FATAL => 'all';
use File::Copy;
use PostgreSQL::Test::Cluster;
use PostgreSQL::Test::Utils;
use Test::More;
# Can be changed to test the other modes.
my $mode = $ENV{PG_TEST_PG_COMBINEBACKUP_MODE} || '--copy';
note "testing using mode $mode";
# Set up a new database instance.
my $primary = PostgreSQL::Test::Cluster->new('primary');
$primary->init(has_archiving => 1, allows_streaming => 1);
$primary->append_conf('postgresql.conf', 'summarize_wal = on');
$primary->start;
# Take a full backup.
my $backup1path = $primary->backup_dir . '/backup1';
$primary->command_ok(
[ 'pg_basebackup', '-D', $backup1path, '--no-sync', '-cfast' ],
"full backup");
# Take an incremental backup.
my $backup2path = $primary->backup_dir . '/backup2';
$primary->command_ok(
[
'pg_basebackup', '-D', $backup2path, '--no-sync', '-cfast',
'--incremental', $backup1path . '/backup_manifest'
],
"incremental backup");
# Find an incremental file in the incremental backup for which there is a full
# file in the full backup. When we find one, replace the full file with an
# incremental file.
my @filelist = grep { /^INCREMENTAL\./ } slurp_dir("$backup2path/base/1");
my $success = 0;
for my $iname (@filelist)
{
my $name = $iname;
$name =~ s/^INCREMENTAL.//;
if (-f "$backup1path/base/1/$name")
{
copy("$backup2path/base/1/$iname", "$backup1path/base/1/$iname")
|| die "copy $backup2path/base/1/$iname: $!";
unlink("$backup1path/base/1/$name")
|| die "unlink $backup1path/base/1/$name: $!";
$success = 1;
last;
}
}
# pg_combinebackup should fail.
my $outpath = $primary->backup_dir . '/out';
$primary->command_fails_like(
[
'pg_combinebackup', $backup1path, $backup2path, '-o', $outpath,
],
qr/full backup contains unexpected incremental file/,
"pg_combinebackup fails");
done_testing();