From 85eb22f6d290765c8b7b931f4e968b4808ff33a3 Mon Sep 17 00:00:00 2001
From: Andres Freund <andres@anarazel.de>
Date: Mon, 21 Feb 2022 16:47:26 -0800
Subject: [PATCH v1 4/4] WIP: Add linux-only AssertNoDeletedFilesOpenPid().

Author:
Reviewed-By:
Discussion: https://postgr.es/m/
Backpatch:
---
 src/include/storage/fd.h            |  3 ++
 src/backend/commands/dbcommands.c   |  6 ++++
 src/backend/storage/file/fd.c       | 52 +++++++++++++++++++++++++++++
 src/backend/storage/ipc/procarray.c | 22 ++++++++++++
 4 files changed, 83 insertions(+)

diff --git a/src/include/storage/fd.h b/src/include/storage/fd.h
index 3bb8f669b64..d12482be90d 100644
--- a/src/include/storage/fd.h
+++ b/src/include/storage/fd.h
@@ -192,6 +192,9 @@ extern void SyncDataDirectory(void);
 extern int	data_sync_elevel(int elevel);
 
 extern void AssertFileNotDeleted(int fd);
+extern void AssertNoDeletedFilesOpen(void);
+extern void AssertNoDeletedFilesOpenPid(int pid);
+
 /* Filename components */
 #define PG_TEMP_FILES_DIR "pgsql_tmp"
 #define PG_TEMP_FILE_PREFIX "pgsql_tmp"
diff --git a/src/backend/commands/dbcommands.c b/src/backend/commands/dbcommands.c
index c37e3c9a9a4..2b9ccddbde7 100644
--- a/src/backend/commands/dbcommands.c
+++ b/src/backend/commands/dbcommands.c
@@ -1069,6 +1069,8 @@ dropdb(const char *dbname, bool missing_ok, bool force)
 	WaitForProcSignalBarrier(EmitProcSignalBarrier(PROCSIGNAL_BARRIER_SMGRRELEASE));
 #endif
 
+	AssertNoDeletedFilesOpen();
+
 	/*
 	 * Remove all tablespace subdirs belonging to the database.
 	 */
@@ -1322,6 +1324,8 @@ movedb(const char *dbname, const char *tblspcname)
 	WaitForProcSignalBarrier(EmitProcSignalBarrier(PROCSIGNAL_BARRIER_SMGRRELEASE));
 #endif
 
+	AssertNoDeletedFilesOpen();
+
 	/*
 	 * Now drop all buffers holding data of the target database; they should
 	 * no longer be dirty so DropDatabaseBuffers is safe.
@@ -2453,6 +2457,8 @@ dbase_redo(XLogReaderState *record)
 		WaitForProcSignalBarrier(EmitProcSignalBarrier(PROCSIGNAL_BARRIER_SMGRRELEASE));
 #endif
 
+		AssertNoDeletedFilesOpen();
+
 		for (i = 0; i < xlrec->ntablespaces; i++)
 		{
 			dst_path = GetDatabasePath(xlrec->db_id, xlrec->tablespace_ids[i]);
diff --git a/src/backend/storage/file/fd.c b/src/backend/storage/file/fd.c
index 123815e4a80..782412b7d32 100644
--- a/src/backend/storage/file/fd.c
+++ b/src/backend/storage/file/fd.c
@@ -3846,6 +3846,58 @@ data_sync_elevel(int elevel)
 	return data_sync_retry ? elevel : PANIC;
 }
 
+void
+AssertNoDeletedFilesOpenPid(int pid)
+{
+#if defined(__linux__)
+	const char *const deleted_suffix = " (deleted)";
+	char		proc_dir[MAXPGPATH];
+	struct dirent *de;
+	DIR		   *dirdesc;
+
+	sprintf(proc_dir, "/proc/%d/fd", pid);
+
+	elog(LOG, "checking %s", proc_dir);
+
+	dirdesc = AllocateDir(proc_dir);
+	if (dirdesc == NULL)
+	{
+		elog(WARNING, "could not open %s", proc_dir);
+		return;
+	}
+
+	while ((de = ReadDir(dirdesc, proc_dir)) != NULL)
+	{
+		char        path[MAXPGPATH];
+		char        target[MAXPGPATH];
+		int			ret;
+
+		if (strcmp(de->d_name, ".") == 0 ||
+			strcmp(de->d_name, "..") == 0)
+			continue;
+
+		sprintf(path, "/proc/%d/fd/%s", pid, de->d_name);
+
+		ret = readlink(path, target, sizeof(path) - 1);
+
+		// FIXME: Tolerate most errors here
+		if (ret == -1)
+			elog(PANIC, "readlink failed: %m");
+
+		/* readlink doesn't null terminate */
+		target[ret] = 0;
+
+		if (pg_str_endswith(target, deleted_suffix))
+		{
+			elog(PANIC, "pid %d has deleted file %s open in fd %s",
+				 pid, target, de->d_name);
+		}
+	}
+
+	FreeDir(dirdesc);
+#endif
+}
+
 void
 AssertFileNotDeleted(int fd)
 {
diff --git a/src/backend/storage/ipc/procarray.c b/src/backend/storage/ipc/procarray.c
index 13d192ec2b4..e33d16f7588 100644
--- a/src/backend/storage/ipc/procarray.c
+++ b/src/backend/storage/ipc/procarray.c
@@ -58,6 +58,7 @@
 #include "commands/dbcommands.h"
 #include "miscadmin.h"
 #include "pgstat.h"
+#include "storage/fd.h"
 #include "storage/proc.h"
 #include "storage/procarray.h"
 #include "storage/spin.h"
@@ -3772,6 +3773,27 @@ CountOtherDBBackends(Oid databaseId, int *nbackends, int *nprepared)
 	return true;				/* timed out, still conflicts */
 }
 
+void
+AssertNoDeletedFilesOpen(void)
+{
+#if defined(__linux__)
+	ProcArrayStruct *arrayP = procArray;
+
+	LWLockAcquire(ProcArrayLock, LW_SHARED);
+
+	for (int index = 0; index < arrayP->numProcs; index++)
+	{
+		int			pgprocno = arrayP->pgprocnos[index];
+		PGPROC	   *proc = &allProcs[pgprocno];
+
+		AssertNoDeletedFilesOpenPid(proc->pid);
+	}
+
+	LWLockRelease(ProcArrayLock);
+#endif
+}
+
+
 /*
  * Terminate existing connections to the specified database. This routine
  * is used by the DROP DATABASE command when user has asked to forcefully
-- 
2.34.0

