From 5e64700facc43fd937349bc8d843b15abb8da5cc Mon Sep 17 00:00:00 2001
From: Thomas Munro <thomas.munro@gmail.com>
Date: Thu, 28 Jul 2022 15:19:38 +1200
Subject: [PATCH v3 2/4] Provide lstat() for Windows.

Junction points will be reported with S_ISLNK(x.st_mode), like in POSIX.
stat() will follow symlinks, like in POSIX (but only one level before it
gives up, unlike in POSIX).

Discussion: https://postgr.es/m/CA%2BhUKGLfOOeyZpm5ByVcAt7x5Pn-%3DxGRNCvgiUPVVzjFLtnY0w%40mail.gmail.com
---
 src/include/port/win32_port.h | 18 +++++++-
 src/port/win32stat.c          | 79 +++++++++++++++++++++++++++++++++--
 2 files changed, 92 insertions(+), 5 deletions(-)

diff --git a/src/include/port/win32_port.h b/src/include/port/win32_port.h
index 4de5bf3bf6..b8cf2e1480 100644
--- a/src/include/port/win32_port.h
+++ b/src/include/port/win32_port.h
@@ -286,10 +286,11 @@ struct stat						/* This should match struct __stat64 */
 
 extern int	_pgfstat64(int fileno, struct stat *buf);
 extern int	_pgstat64(const char *name, struct stat *buf);
+extern int	_pglstat64(const char *name, struct stat *buf);
 
 #define fstat(fileno, sb)	_pgfstat64(fileno, sb)
 #define stat(path, sb)		_pgstat64(path, sb)
-#define lstat(path, sb)		_pgstat64(path, sb)
+#define lstat(path, sb)		_pglstat64(path, sb)
 
 /* These macros are not provided by older MinGW, nor by MSVC */
 #ifndef S_IRUSR
@@ -335,6 +336,21 @@ extern int	_pgstat64(const char *name, struct stat *buf);
 #define S_ISREG(m) (((m) & S_IFMT) == S_IFREG)
 #endif
 
+/*
+ * In order for lstat() to be able to report junction points as symlinks, we
+ * need to hijack a bit in st_mode, since neither MSVC nor MinGW provides
+ * S_ISLNK and there aren't any spare bits.  We'll steal the one for character
+ * devices, because we don't otherwise make use of those.
+ */
+#ifdef S_ISLNK
+#error "S_ISLNK is already defined"
+#endif
+#ifdef S_IFLNK
+#error "S_IFLNK is already defined"
+#endif
+#define S_IFLNK S_IFCHR
+#define S_ISLNK(m) (((m) & S_IFLNK) == S_IFLNK)
+
 /*
  * Supplement to <fcntl.h>.
  * This is the same value as _O_NOINHERIT in the MS header file. This is
diff --git a/src/port/win32stat.c b/src/port/win32stat.c
index e03ed5f35c..686c510fed 100644
--- a/src/port/win32stat.c
+++ b/src/port/win32stat.c
@@ -15,7 +15,11 @@
 
 #ifdef WIN32
 
+#define UMDF_USING_NTSTATUS
+
 #include "c.h"
+#include "port/win32ntdll.h"
+
 #include <windows.h>
 
 /*
@@ -107,12 +111,10 @@ fileinfo_to_stat(HANDLE hFile, struct stat *buf)
 }
 
 /*
- * Windows implementation of stat().
- *
- * This currently also implements lstat(), though perhaps that should change.
+ * Windows implementation of lstat().
  */
 int
-_pgstat64(const char *name, struct stat *buf)
+_pglstat64(const char *name, struct stat *buf)
 {
 	/*
 	 * Our open wrapper will report STATUS_DELETE_PENDING as ENOENT.  We
@@ -129,10 +131,79 @@ _pgstat64(const char *name, struct stat *buf)
 
 	ret = fileinfo_to_stat(hFile, buf);
 
+	/*
+	 * Unfortunately it's not possible for fileinfo_to_stat() to see a
+	 * FILE_ATTRIBUTE_REPARSE_POINT flag with just a handle, so at this point
+	 * junction points appear as directories.  Ask for the full attributes
+	 * while we still have the handle open.  Someone else can unlink the file
+	 * while we have it open, but they can't create another file with the same
+	 * name.
+	 */
+	if (ret == 0 && S_ISDIR(buf->st_mode))
+	{
+		DWORD		attr;
+
+		attr = GetFileAttributes(name);
+		if (attr == INVALID_FILE_ATTRIBUTES)
+		{
+			DWORD		err;
+
+			/* If it's been unlinked since we opened it, report as ENOENT. */
+			err = GetLastError();
+			if (err == ERROR_ACCESS_DENIED &&
+				pg_RtlGetLastNtStatus() == STATUS_DELETE_PENDING)
+				errno = ENOENT;
+			else
+				_dosmaperr(err);
+
+			ret = -1;
+		}
+		else if (attr & FILE_ATTRIBUTE_REPARSE_POINT)
+		{
+			buf->st_mode &= ~S_IFDIR;
+			buf->st_mode |= S_IFLNK;
+		}
+	}
+
 	CloseHandle(hFile);
 	return ret;
 }
 
+/*
+ * Windows implementation of stat().
+ */
+int
+_pgstat64(const char *name, struct stat *buf)
+{
+	int			ret;
+
+	ret = _pglstat64(name, buf);
+
+	/* Do we need to follow a symlink (junction point)? */
+	if (ret == 0 && S_ISLNK(buf->st_mode))
+	{
+		char		next[MAXPGPATH];
+
+		if (readlink(name, next, sizeof(next)) < 0)
+			return -1;
+
+		ret = _pglstat64(next, buf);
+		if (ret == 0 && S_ISLNK(buf->st_mode))
+		{
+			/*
+			 * We're only prepared to go one hop, because we only expect to
+			 * deal with the simple cases that we create.  The error for too
+			 * many symlinks is supposed to be ELOOP, but Windows hasn't got
+			 * it.
+			 */
+			errno = EIO;
+			return -1;
+		}
+	}
+
+	return ret;
+}
+
 /*
  * Windows implementation of fstat().
  */
-- 
2.37.1

