From b9cb66b5e74e5b78ff51e53ec49c9f6c5a87193d Mon Sep 17 00:00:00 2001
From: Robert Haas <rhaas@postgresql.org>
Date: Wed, 17 Apr 2024 08:48:20 -0400
Subject: [PATCH v1] Restrict where INCREMENTAL.${NAME} files are recognized.

Previously, they were recognized anywhere in an incremental backup
directory; now, we restrict this to places where they are expected to
appear. That means this code will need updating if we ever do
incremental backups of files in other places (e.g. SLRU files), but
it lets you create a file called INCREMENTAL.config (or something like
that) at the top level of the data directory and still have things
work.

Patch by me, per complaint from David Steele

Discussion: http://postgr.es/m/ec8af07d-a86b-4cf0-8bbd-b97063d4b312@pgmasters.net
---
 src/bin/pg_combinebackup/pg_combinebackup.c | 46 +++++++++++++++------
 src/bin/pg_combinebackup/t/005_integrity.pl |  9 ++++
 2 files changed, 42 insertions(+), 13 deletions(-)

diff --git a/src/bin/pg_combinebackup/pg_combinebackup.c b/src/bin/pg_combinebackup/pg_combinebackup.c
index 2788c78fdd..4b23c47c95 100644
--- a/src/bin/pg_combinebackup/pg_combinebackup.c
+++ b/src/bin/pg_combinebackup/pg_combinebackup.c
@@ -799,25 +799,44 @@ process_directory_recursively(Oid tsoid,
 	char		manifest_prefix[MAXPGPATH];
 	DIR		   *dir;
 	struct dirent *de;
-	bool		is_pg_tblspc;
-	bool		is_pg_wal;
+	bool		is_pg_tblspc = false;
+	bool		is_pg_wal = false;
+	bool		is_incremental_dir = false;
 	manifest_data *latest_manifest = manifests[n_prior_backups];
 	pg_checksum_type checksum_type;
 
 	/*
-	 * pg_tblspc and pg_wal are special cases, so detect those here.
+	 * Classify this directory.
 	 *
-	 * pg_tblspc is only special at the top level, but subdirectories of
-	 * pg_wal are just as special as the top level directory.
+	 * We set is_pg_tblspc only for the toplevel pg_tblspc directory, because
+	 * the symlinks in that specific directory require special handling.
 	 *
-	 * Since incremental backup does not exist in pre-v10 versions, we don't
-	 * have to worry about the old pg_xlog naming.
+	 * We set is_pg_wal for the toplevel WAL directory and all of its
+	 * subdirectories, because those files are not included in the backup
+	 * manifest and hence need special treatement. (Since incremental backup
+	 * does not exist in pre-v10 versions, we don't have to worry about the
+	 * old pg_xlog naming.)
+	 *
+	 * We set is_incremental_dir for directories that can contain incremental
+	 * files requiring reconstruction. If such files occur outside these
+	 * directories, we want to just copy them straight to the output directory.
+	 * This is to protect against a user creating a file with a strange name
+	 * like INCREMENTAL.config and then compaining that incremental backups
+	 * don't work properly. The test here is a bit tricky: incremental files
+	 * occur in subdirectories of base, in pg_global itself, and in
+	 * subdirectories of pg_tblspc only if in-place tablespaces are used.
 	 */
-	is_pg_tblspc = !OidIsValid(tsoid) && relative_path != NULL &&
-		strcmp(relative_path, "pg_tblspc") == 0;
-	is_pg_wal = !OidIsValid(tsoid) && relative_path != NULL &&
-		(strcmp(relative_path, "pg_wal") == 0 ||
-		 strncmp(relative_path, "pg_wal/", 7) == 0);
+	if (OidIsValid(tsoid))
+		is_incremental_dir = true;
+	else if (relative_path != NULL)
+	{
+		is_pg_tblspc = strcmp(relative_path, "pg_tblspc") == 0;
+		is_pg_wal = (strcmp(relative_path, "pg_wal") == 0 ||
+			strncmp(relative_path, "pg_wal/", 7) == 0);
+		is_incremental_dir = strncmp(relative_path, "base/", 5) == 0 ||
+			strcmp(relative_path, "global") == 0 ||
+			strncmp(relative_path, "pg_tblspc/", 10) == 0;
+	}
 
 	/*
 	 * If we're under pg_wal, then we don't need checksums, because these
@@ -955,7 +974,8 @@ process_directory_recursively(Oid tsoid,
 		 * If it's an incremental file, hand it off to the reconstruction
 		 * code, which will figure out what to do.
 		 */
-		if (strncmp(de->d_name, INCREMENTAL_PREFIX,
+		if (is_incremental_dir &&
+			strncmp(de->d_name, INCREMENTAL_PREFIX,
 					INCREMENTAL_PREFIX_LENGTH) == 0)
 		{
 			/* Output path should not include "INCREMENTAL." prefix. */
diff --git a/src/bin/pg_combinebackup/t/005_integrity.pl b/src/bin/pg_combinebackup/t/005_integrity.pl
index c3f1a2a6f4..08b6d7da1d 100644
--- a/src/bin/pg_combinebackup/t/005_integrity.pl
+++ b/src/bin/pg_combinebackup/t/005_integrity.pl
@@ -19,6 +19,15 @@ $node1->init(has_archiving => 1, allows_streaming => 1);
 $node1->append_conf('postgresql.conf', 'summarize_wal = on');
 $node1->start;
 
+# Create a file called INCREMENTAL.config in the root directory of the
+# first database instance. We only recognize INCREMENTAL.${original_name}
+# files under base and global and in tablespace directories, so this shouldn't
+# cause anything to fail.
+my $strangely_named_config_file = $node1->data_dir . '/INCREMENTAL.config';
+open(my $icfg, '>', $strangely_named_config_file)
+	|| die "$strangely_named_config_file: $!";
+close($icfg);
+
 # Set up another new database instance.  force_initdb is used because
 # we want it to be a separate cluster with a different system ID.
 my $node2 = PostgreSQL::Test::Cluster->new('node2');
-- 
2.39.3 (Apple Git-145)

