From: Thomas Munro <tmunro@postgresql.org>
Date: Sat, 6 Aug 2022 00:00:57 +0000 (+1200)
Subject: Provide lstat() for Windows.
X-Git-Tag: REL_13_17~17
X-Git-Url: http://git.postgresql.org/gitweb/?a=commitdiff_plain;h=ee219102d2e76d3e7277ac1a7ddda7757737b31b;p=postgresql.git

Provide lstat() for Windows.

Junction points will be reported with S_ISLNK(x.st_mode), simulating
POSIX lstat().  stat() will follow pseudo-symlinks, like in POSIX (but
only one level before giving up, unlike in POSIX).

This completes a TODO left by commit bed90759fcb.

Tested-by: Andrew Dunstan <andrew@dunslane.net> (earlier version)
Discussion: https://postgr.es/m/CA%2BhUKGLfOOeyZpm5ByVcAt7x5Pn-%3DxGRNCvgiUPVVzjFLtnY0w%40mail.gmail.com
(cherry picked from commit c5cb8f3b770c043509b61528664bcd805e1777e6)

Author: Thomas Munro <tmunro@postgresql.org>
Author: Alexandra Wang <alexandra.wang.oss@gmail.com>
---

diff --git a/src/include/port/win32_port.h b/src/include/port/win32_port.h
index f65f426cdbd..1ed65fefff6 100644
--- a/src/include/port/win32_port.h
+++ b/src/include/port/win32_port.h
@@ -270,10 +270,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
@@ -319,6 +320,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 6f7cd09087d..82bd2d0bfa6 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,110 @@ _pgstat64(const char *name, struct stat *buf)
 
 	ret = fileinfo_to_stat(hFile, buf);
 
+	/*
+	 * Junction points appear as directories to fileinfo_to_stat(), so we'll
+	 * need to do a bit more work to distinguish them.
+	 */
+	if (ret == 0 && S_ISDIR(buf->st_mode))
+	{
+		char		next[MAXPGPATH];
+		ssize_t		size;
+
+		/*
+		 * POSIX says we need to put the length of the target path into
+		 * st_size.  Use readlink() to get it, or learn that this is not a
+		 * junction point.
+		 */
+		size = readlink(name, next, sizeof(next));
+		if (size < 0)
+		{
+			if (errno == EACCES &&
+				pg_RtlGetLastNtStatus() == STATUS_DELETE_PENDING)
+			{
+				/* Unlinked underneath us. */
+				errno = ENOENT;
+				ret = -1;
+			}
+			else if (errno == EINVAL)
+			{
+				/* It's not a junction point, nothing to do. */
+			}
+			else
+			{
+				/* Some other failure. */
+				ret = -1;
+			}
+		}
+		else
+		{
+			/* It's a junction point, so report it as a symlink. */
+			buf->st_mode &= ~S_IFDIR;
+			buf->st_mode |= S_IFLNK;
+			buf->st_size = size;
+		}
+	}
+
 	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];
+		ssize_t		size;
+
+		/*
+		 * _pglstat64() already called readlink() once to be able to fill in
+		 * st_size, and now we need to do it again to get the path to follow.
+		 * That could be optimized, but stat() on symlinks is probably rare
+		 * and this way is simple.
+		 */
+		size = readlink(name, next, sizeof(next));
+		if (size < 0)
+		{
+			if (errno == EACCES &&
+				pg_RtlGetLastNtStatus() == STATUS_DELETE_PENDING)
+			{
+				/* Unlinked underneath us. */
+				errno = ENOENT;
+			}
+			return -1;
+		}
+		if (size >= sizeof(next))
+		{
+			errno = ENAMETOOLONG;
+			return -1;
+		}
+		next[size] = 0;
+
+		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().
  */