From 10768af742ccf3c7dcdc8373a7b7f693edff6b81 Mon Sep 17 00:00:00 2001
From: Thomas Munro <thomas.munro@gmail.com>
Date: Sun, 5 Sep 2021 23:49:23 +1200
Subject: [PATCH] Handle STATUS_DELETE_PENDING on Windows.

1.  Update our open() wrapper to check for STATUS_DELETE_PENDING and
translate it to appropriate errors.

2.  Remove non-working code that intended to do the same thing in our
stat() wrapper, and build on top of open() instead.

XXX Work in progress, see TODO notes in the code

Discussion: https://postgr.es/m/CA%2BhUKGJz_pZTF9mckn6XgSv69%2BjGwdgLkxZ6b3NWGLBCVjqUZA%40mail.gmail.com
---
 src/include/port.h   |   1 +
 src/port/open.c      |  72 +++++++++++------
 src/port/win32stat.c | 180 ++++---------------------------------------
 3 files changed, 65 insertions(+), 188 deletions(-)

diff --git a/src/include/port.h b/src/include/port.h
index 82f63de325..88b2711121 100644
--- a/src/include/port.h
+++ b/src/include/port.h
@@ -290,6 +290,7 @@ extern bool rmtree(const char *path, bool rmtopdir);
  * passing of other special options.
  */
 #define		O_DIRECT	0x80000000
+#define		O_X_STAT	0x40000000
 extern int	pgwin32_open(const char *, int,...);
 extern FILE *pgwin32_fopen(const char *, const char *);
 #define		open(a,b,c) pgwin32_open(a,b,c)
diff --git a/src/port/open.c b/src/port/open.c
index 14c6debba9..de976fc7e3 100644
--- a/src/port/open.c
+++ b/src/port/open.c
@@ -23,6 +23,34 @@
 #include <assert.h>
 #include <sys/stat.h>
 
+#include <ntstatus.h>
+
+typedef NTSTATUS (__stdcall *RtlGetLastNtStatus_t)(void);
+
+static RtlGetLastNtStatus_t pg_RtlGetLastNtStatus;
+
+static int
+initialize_ntdll(void)
+{
+	HMODULE module;
+
+	if (pg_RtlGetLastNtStatus)
+		return 0;
+	module = LoadLibraryEx("ntdll.dll", NULL, 0);
+	if (!module)
+	{
+		_dosmaperr(GetLastError());
+		return -1;
+	}
+	pg_RtlGetLastNtStatus = (RtlGetLastNtStatus_t) (pg_funcptr_t)
+		GetProcAddress(module, "RtlGetLastNtStatus");
+	if (!pg_RtlGetLastNtStatus)
+	{
+		_dosmaperr(GetLastError());
+		return -1;
+	}
+	return 0;
+}
 
 static int
 openFlagsToCreateFileFlags(int openFlags)
@@ -66,13 +94,18 @@ pgwin32_open(const char *fileName, int fileFlags,...)
 	SECURITY_ATTRIBUTES sa;
 	int			loops = 0;
 
+	if (initialize_ntdll() < 0)
+		return -1;
+
 	/* Check that we can handle the request */
 	assert((fileFlags & ((O_RDONLY | O_WRONLY | O_RDWR) | O_APPEND |
 						 (O_RANDOM | O_SEQUENTIAL | O_TEMPORARY) |
 						 _O_SHORT_LIVED | O_DSYNC | O_DIRECT |
+						 O_X_STAT |
 						 (O_CREAT | O_TRUNC | O_EXCL) | (O_TEXT | O_BINARY))) == fileFlags);
 #ifndef FRONTEND
-	Assert(pgwin32_signal_event != NULL);	/* small chance of pg_usleep() */
+	/* XXX When called by stat very early on, this fails! */
+	//Assert(pgwin32_signal_event != NULL);	/* small chance of pg_usleep() */
 #endif
 
 #ifdef FRONTEND
@@ -102,6 +135,7 @@ pgwin32_open(const char *fileName, int fileFlags,...)
 						   &sa,
 						   openFlagsToCreateFileFlags(fileFlags),
 						   FILE_ATTRIBUTE_NORMAL |
+						   ((fileFlags & O_X_STAT) ? FILE_FLAG_BACKUP_SEMANTICS : 0) |
 						   ((fileFlags & O_RANDOM) ? FILE_FLAG_RANDOM_ACCESS : 0) |
 						   ((fileFlags & O_SEQUENTIAL) ? FILE_FLAG_SEQUENTIAL_SCAN : 0) |
 						   ((fileFlags & _O_SHORT_LIVED) ? FILE_ATTRIBUTE_TEMPORARY : 0) |
@@ -140,32 +174,20 @@ pgwin32_open(const char *fileName, int fileFlags,...)
 		/*
 		 * ERROR_ACCESS_DENIED is returned if the file is deleted but not yet
 		 * gone (Windows NT status code is STATUS_DELETE_PENDING).  In that
-		 * case we want to wait a bit and try again, giving up after 1 second
-		 * (since this condition should never persist very long).  However,
-		 * there are other commonly-hit cases that return ERROR_ACCESS_DENIED,
-		 * so care is needed.  In particular that happens if we try to open a
-		 * directory, or of course if there's an actual file-permissions
-		 * problem.  To distinguish these cases, try a stat().  In the
-		 * delete-pending case, it will either also get STATUS_DELETE_PENDING,
-		 * or it will see the file as gone and fail with ENOENT.  In other
-		 * cases it will usually succeed.  The only somewhat-likely case where
-		 * this coding will uselessly wait is if there's a permissions problem
-		 * with a containing directory, which we hope will never happen in any
-		 * performance-critical code paths.
+		 * case, we'd better ask for the NT status too so we can translate it
+		 * to a more Unix-like error.
+		 *
+		 * If there's no O_CREAT flag, then we'll pretend the file is
+		 * invisible.  With O_CREAT, we have no choice but to report that
+		 * there's a file in the way (which wouldn't happen on Unix).
 		 */
-		if (err == ERROR_ACCESS_DENIED)
+		if (err == ERROR_ACCESS_DENIED &&
+			pg_RtlGetLastNtStatus() == STATUS_DELETE_PENDING)
 		{
-			if (loops < 10)
-			{
-				struct stat st;
-
-				if (stat(fileName, &st) != 0)
-				{
-					pg_usleep(100000);
-					loops++;
-					continue;
-				}
-			}
+			if (fileFlags & O_CREAT)
+				err = ERROR_FILE_EXISTS;
+			else
+				err = ERROR_FILE_NOT_FOUND;
 		}
 
 		_dosmaperr(err);
diff --git a/src/port/win32stat.c b/src/port/win32stat.c
index 2ad8ee1359..efe5c3221c 100644
--- a/src/port/win32stat.c
+++ b/src/port/win32stat.c
@@ -18,53 +18,6 @@
 #include "c.h"
 #include <windows.h>
 
-/*
- * In order to support MinGW and MSVC2013 we use NtQueryInformationFile as an
- * alternative for GetFileInformationByHandleEx. It is loaded from the ntdll
- * library.
- */
-#if _WIN32_WINNT < 0x0600
-#include <winternl.h>
-
-#if !defined(__MINGW32__) && !defined(__MINGW64__)
-/* MinGW includes this in <winternl.h>, but it is missing in MSVC */
-typedef struct _FILE_STANDARD_INFORMATION
-{
-	LARGE_INTEGER AllocationSize;
-	LARGE_INTEGER EndOfFile;
-	ULONG		NumberOfLinks;
-	BOOLEAN		DeletePending;
-	BOOLEAN		Directory;
-} FILE_STANDARD_INFORMATION;
-#define FileStandardInformation 5
-#endif							/* !defined(__MINGW32__) &&
-								 * !defined(__MINGW64__) */
-
-typedef NTSTATUS (NTAPI * PFN_NTQUERYINFORMATIONFILE)
-			(IN HANDLE FileHandle,
-			 OUT PIO_STATUS_BLOCK IoStatusBlock,
-			 OUT PVOID FileInformation,
-			 IN ULONG Length,
-			 IN FILE_INFORMATION_CLASS FileInformationClass);
-
-static PFN_NTQUERYINFORMATIONFILE _NtQueryInformationFile = NULL;
-
-static HMODULE ntdll = NULL;
-
-/*
- * Load DLL file just once regardless of how many functions we load/call in it.
- */
-static void
-LoadNtdll(void)
-{
-	if (ntdll != NULL)
-		return;
-	ntdll = LoadLibraryEx("ntdll.dll", NULL, 0);
-}
-
-#endif							/* _WIN32_WINNT < 0x0600 */
-
-
 /*
  * Convert a FILETIME struct into a 64 bit time_t.
  */
@@ -161,125 +114,31 @@ fileinfo_to_stat(HANDLE hFile, struct stat *buf)
 int
 _pgstat64(const char *name, struct stat *buf)
 {
+	int			fd;
+	int			rc;
+	int			save_errno;
+
 	/*
-	 * We must use a handle so lstat() returns the information of the target
-	 * file.  To have a reliable test for ERROR_DELETE_PENDING, we use
-	 * NtQueryInformationFile from Windows 2000 or
-	 * GetFileInformationByHandleEx from Server 2008 / Vista.
+	 * Our open wrapper will report STATUS_DELETE_PENDING as ENOENT.  We pass
+	 * in a special private flag to say that it's _pgstat64() calling, to
+	 * activate a mode that allows directories to be opened for limited
+	 * purposes.
+	 *
+	 * XXX Think about fd pressure, since we're opening an fd?
 	 */
-	SECURITY_ATTRIBUTES sa;
-	HANDLE		hFile;
-	int			ret;
-#if _WIN32_WINNT < 0x0600
-	IO_STATUS_BLOCK ioStatus;
-	FILE_STANDARD_INFORMATION standardInfo;
-#else
-	FILE_STANDARD_INFO standardInfo;
-#endif
-
-	if (name == NULL || buf == NULL)
-	{
-		errno = EINVAL;
-		return -1;
-	}
+	fd = pgwin32_open(name, O_RDONLY | O_X_STAT, 0);
 
-	/* fast not-exists check */
-	if (GetFileAttributes(name) == INVALID_FILE_ATTRIBUTES)
-	{
-		_dosmaperr(GetLastError());
+	if (fd < 0)
 		return -1;
-	}
 
-	/* get a file handle as lightweight as we can */
-	sa.nLength = sizeof(SECURITY_ATTRIBUTES);
-	sa.bInheritHandle = TRUE;
-	sa.lpSecurityDescriptor = NULL;
-	hFile = CreateFile(name,
-					   GENERIC_READ,
-					   (FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE),
-					   &sa,
-					   OPEN_EXISTING,
-					   (FILE_FLAG_NO_BUFFERING | FILE_FLAG_BACKUP_SEMANTICS |
-						FILE_FLAG_OVERLAPPED),
-					   NULL);
-	if (hFile == INVALID_HANDLE_VALUE)
-	{
-		DWORD		err = GetLastError();
+	rc = _pgfstat64(fd, buf);
+	save_errno = errno;
 
-		CloseHandle(hFile);
-		_dosmaperr(err);
-		return -1;
-	}
-
-	memset(&standardInfo, 0, sizeof(standardInfo));
-
-#if _WIN32_WINNT < 0x0600
-	if (_NtQueryInformationFile == NULL)
-	{
-		/* First time through: load ntdll.dll and find NtQueryInformationFile */
-		LoadNtdll();
-		if (ntdll == NULL)
-		{
-			DWORD		err = GetLastError();
-
-			CloseHandle(hFile);
-			_dosmaperr(err);
-			return -1;
-		}
-
-		_NtQueryInformationFile = (PFN_NTQUERYINFORMATIONFILE) (pg_funcptr_t)
-			GetProcAddress(ntdll, "NtQueryInformationFile");
-		if (_NtQueryInformationFile == NULL)
-		{
-			DWORD		err = GetLastError();
-
-			CloseHandle(hFile);
-			_dosmaperr(err);
-			return -1;
-		}
-	}
+	close(fd);
 
-	if (!NT_SUCCESS(_NtQueryInformationFile(hFile, &ioStatus, &standardInfo,
-											sizeof(standardInfo),
-											FileStandardInformation)))
-	{
-		DWORD		err = GetLastError();
+	errno = save_errno;
 
-		CloseHandle(hFile);
-		_dosmaperr(err);
-		return -1;
-	}
-#else
-	if (!GetFileInformationByHandleEx(hFile, FileStandardInfo, &standardInfo,
-									  sizeof(standardInfo)))
-	{
-		DWORD		err = GetLastError();
-
-		CloseHandle(hFile);
-		_dosmaperr(err);
-		return -1;
-	}
-#endif							/* _WIN32_WINNT < 0x0600 */
-
-	if (standardInfo.DeletePending)
-	{
-		/*
-		 * File has been deleted, but is not gone from the filesystem yet.
-		 * This can happen when some process with FILE_SHARE_DELETE has it
-		 * open, and it will be fully removed once that handle is closed.
-		 * Meanwhile, we can't open it, so indicate that the file just doesn't
-		 * exist.
-		 */
-		CloseHandle(hFile);
-		errno = ENOENT;
-		return -1;
-	}
-
-	/* At last we can invoke fileinfo_to_stat */
-	ret = fileinfo_to_stat(hFile, buf);
-
-	CloseHandle(hFile);
-	return ret;
+	return rc;
 }
 
 /*
@@ -296,11 +155,6 @@ _pgfstat64(int fileno, struct stat *buf)
 		return -1;
 	}
 
-	/*
-	 * Since we already have a file handle there is no need to check for
-	 * ERROR_DELETE_PENDING.
-	 */
-
 	return fileinfo_to_stat(hFile, buf);
 }
 
-- 
2.30.2

