From 4b6d369f731f111216f9ed6613ad1b41872fb0ea Mon Sep 17 00:00:00 2001
From: Bernd Helmle <mailings@oopsware.de>
Date: Thu, 20 Feb 2020 17:18:10 +0100
Subject: [PATCH] Make scanning pg_tblspc more robust.

Currently we dive blindly into the subdirectories referenced by
the symlinks (or under Windows via file junctions) located in pg_tblspc,
which has various problems. We need to filter out any other possible tablespace
location found in the referenced subdirectory, which could likely happen
if you have e.g. an upgraded instance via pg_upgrade or other installations using
the same tablespace location.

Instead of filtering out any of these possible foreign directories, try to
resolve the locations in pg_tblspc directly. This is done with the new
scan_tablespaces() helper function, which indirectly calls scan_directory()
after resolving the links in pg_tblspc. It explicitely follows directories found
there too, since it might happen that users copy over tablespace locations directly
into pg_tblspc.
---
 src/bin/pg_checksums/pg_checksums.c | 99 ++++++++++++++++++++++++++++-
 1 file changed, 97 insertions(+), 2 deletions(-)

diff --git a/src/bin/pg_checksums/pg_checksums.c b/src/bin/pg_checksums/pg_checksums.c
index 46ee1f1dc3..4e2e9cb3e2 100644
--- a/src/bin/pg_checksums/pg_checksums.c
+++ b/src/bin/pg_checksums/pg_checksums.c
@@ -108,6 +108,15 @@ static const char *const skip[] = {
 	NULL,
 };
 
+/*
+ * Forwarded declarations.
+ */
+static int64
+scan_directory(const char *basedir, const char *subdir, bool sizeonly);
+
+static int64
+scan_tablespaces(const char *basedir, bool sizeonly);
+
 /*
  * Report current progress status.  Parts borrowed from
  * src/bin/pg_basebackup/pg_basebackup.c.
@@ -267,6 +276,91 @@ scan_file(const char *fn, BlockNumber segmentno)
 	close(f);
 }
 
+/*
+ * Scans pg_tblspc of the given base directory for either links
+ * or directories and examines wether an existing tablespace version
+ * directory exists there. If true, scan_directory() will do the checksum
+ * legwork. sizeonly specifies wether we want to compute the directory size
+ * only, required for progress reporting.
+ */
+static int64
+scan_tablespaces(const char *basedir, bool sizeonly)
+{
+	int64          dirsize = 0;
+	char           path[MAXPGPATH];
+	DIR           *dir;
+	struct dirent *de;
+
+	snprintf(path, sizeof(path), "%s/%s", basedir, "pg_tblspc");
+	dir = opendir(path);
+
+	if (!dir)
+	{
+		pg_log_error("could not open directory \"%s\": %m", path);
+		exit(1);
+	}
+
+	/*
+	 * Scan through the pg_tblspc contents. We expect either a symlink
+	 * (or Windows junction) or a directory. The latter might exist due
+	 * systems where administrators copy over the tablespace directory
+	 * directly into PGDATA, e.g. on recovery systems.
+	 */
+	while((de = readdir(dir)) != NULL)
+	{
+		char tblspc_path[MAXPGPATH];
+		char fn[MAXPGPATH];
+		struct stat st;
+
+		if (strcmp(de->d_name, ".") == 0 ||
+			strcmp(de->d_name, "..") == 0)
+			continue;
+
+		snprintf(fn, sizeof(fn), "%s/%s", path, de->d_name);
+		if (lstat(fn, &st) < 0)
+		{
+			pg_log_error("could not stat file \"%s\": %m", fn);
+			exit(1);
+		}
+
+		if (S_ISREG(st.st_mode))
+		{
+			pg_log_debug("ignoring file %s in pg_tblspc", de->d_name);
+			continue;
+		}
+
+#ifndef WIN32
+		if (S_ISDIR(st.st_mode) || S_ISLNK(st.st_mode))
+#else
+		if (S_ISDIR(st.st_mode) || pgwin32_is_junction(fn))
+#endif
+		{
+			/*
+			 * Every link or tablespace directory found here
+			 * must have a TABLESPACE_VERSION_DIRECTORY. We build
+			 * the direct path to it and check via lstat wether it exists.
+			 */
+			snprintf(tblspc_path, sizeof(tblspc_path), "%s/%s",
+					 fn, TABLESPACE_VERSION_DIRECTORY);
+
+			if (lstat(tblspc_path, &st) < 0)
+			{
+				pg_log_warning("directory or link \"%s\" doesn't contain a valid tablespace" , fn);
+				continue;
+			}
+
+			/* Seems okay, dive into and do the ordinary checksum checks. */
+			dirsize += scan_directory(fn, TABLESPACE_VERSION_DIRECTORY, sizeonly);
+		}
+		else
+		{
+			pg_log_debug("ignoring file %s in pg_tblspc", de->d_name);
+		}
+	}
+
+	return dirsize;
+}
+
 /*
  * Scan the given directory for items which can be checksummed and
  * operate on each one of them.  If "sizeonly" is true, the size of
@@ -438,6 +532,7 @@ main(int argc, char *argv[])
 				break;
 			case 'v':
 				verbose = true;
+				pg_logging_set_level(PG_LOG_DEBUG);
 				break;
 			case 'D':
 				DataDir = optarg;
@@ -553,12 +648,12 @@ main(int argc, char *argv[])
 		{
 			total_size = scan_directory(DataDir, "global", true);
 			total_size += scan_directory(DataDir, "base", true);
-			total_size += scan_directory(DataDir, "pg_tblspc", true);
+			total_size += scan_tablespaces(DataDir, true);
 		}
 
 		(void) scan_directory(DataDir, "global", false);
 		(void) scan_directory(DataDir, "base", false);
-		(void) scan_directory(DataDir, "pg_tblspc", false);
+		(void) scan_tablespaces(DataDir, false);
 
 		if (showprogress)
 		{
-- 
2.24.1

