From 46ffa9002693ad468f3c569cf3cb2a891de308ae Mon Sep 17 00:00:00 2001
From: Thomas Munro <thomas.munro@gmail.com>
Date: Sun, 16 Oct 2022 12:07:33 +1300
Subject: [PATCH v2 06/10] Fix lstat() on broken junction points.

When using junction points to emulate symlinks on Windows, one edge case
was not handled correctly by commit c5cb8f3b: if a junction point is
broken (pointing to a non-existent path), we'd report ENOENT.  This
doesn't break any known use case, but was noticed while testing and is
fixed here for completeness.

Also add translation ERROR_CANT_RESOLVE_FILENAME -> ENOENT, as that is
one of the errors Windows can report depending on format of the broken
path.
---
 src/port/t/001_filesystem.c | 23 +++++++++++++++--------
 src/port/win32error.c       |  3 +++
 src/port/win32stat.c        | 27 ++++++++++++++++++++++-----
 3 files changed, 40 insertions(+), 13 deletions(-)

diff --git a/src/port/t/001_filesystem.c b/src/port/t/001_filesystem.c
index 137af164ed..bb1c681366 100644
--- a/src/port/t/001_filesystem.c
+++ b/src/port/t/001_filesystem.c
@@ -277,6 +277,13 @@ filesystem_metadata_tests(void)
 	PG_EXPECT(S_ISLNK(statbuf.st_mode));
 	PG_EXPECT_EQ(statbuf.st_size, strlen(path2), "got expected symlink size");
 
+	make_path(path, "broken-symlink");
+	make_path(path2, "does-not-exist");
+	PG_EXPECT_SYS(symlink(path2, path) == 0, "make a broken symlink");
+	PG_EXPECT_SYS(lstat(path, &statbuf) == 0, "lstat broken symlink");
+	PG_EXPECT(S_ISLNK(statbuf.st_mode));
+	PG_EXPECT_SYS(unlink(path) == 0);
+
 	/* Tests for link() and unlink(). */
 
 	make_path(path, "does-not-exist-1");
@@ -347,7 +354,7 @@ filesystem_metadata_tests(void)
 
 	fd = open(path, O_CREAT | O_EXCL | O_RDWR | PG_BINARY, 0777);
 	PG_EXPECT_SYS(fd >= 0, "touch name1.txt");
-	PG_EXPECT_SYS(close(fd) == 0);
+	PG_REQUIRE_SYS(close(fd) == 0);
 
 	handle = CreateFile(path, GENERIC_WRITE | GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
 	PG_REQUIRE(handle);
@@ -368,7 +375,7 @@ filesystem_metadata_tests(void)
 	make_path(path, "name1.txt");
 	fd = open(path, O_CREAT | O_EXCL | O_RDWR | PG_BINARY, 0777);
 	PG_EXPECT_SYS(fd >= 0, "touch name1.txt");
-	PG_EXPECT_SYS(close(fd) == 0);
+	PG_REQUIRE_SYS(fd < 0 || close(fd) == 0);
 
 	make_path(path2, "name2.txt");
 	PG_EXPECT_SYS(rename(path, path2) == 0, "rename name1.txt -> name2.txt");
@@ -378,14 +385,14 @@ filesystem_metadata_tests(void)
 
 	fd = open(path, O_CREAT | O_EXCL | O_RDWR | PG_BINARY, 0777);
 	PG_EXPECT_SYS(fd >= 0, "touch name1.txt");
-	PG_EXPECT_SYS(close(fd) == 0);
+	PG_REQUIRE_SYS(fd < 0 || close(fd) == 0);
 
 	make_path(path2, "name2.txt");
 	PG_EXPECT_SYS(rename(path, path2) == 0, "rename name1.txt -> name2.txt, replacing it");
 
 	fd = open(path, O_CREAT | O_EXCL | O_RDWR | PG_BINARY, 0777);
 	PG_EXPECT_SYS(fd >= 0, "touch name1.txt");
-	PG_EXPECT_SYS(close(fd) == 0);
+	PG_REQUIRE_SYS(fd < 0 || close(fd) == 0);
 
 	fd = open(path2, O_RDWR | PG_BINARY, 0777);
 	PG_EXPECT_SYS(fd >= 0, "open name2.txt");
@@ -414,12 +421,12 @@ filesystem_metadata_tests(void)
 	make_path(path2, "name2.txt");
 	PG_EXPECT_SYS(rename(path, path2) == 0,
 				  "can rename name1.txt -> name2.txt while name1.txt is open");
-	PG_EXPECT_SYS(close(fd) == 0);
+	PG_REQUIRE_SYS(fd < 0 || close(fd) == 0);
 
 	make_path(path, "name1.txt");
 	fd = open(path, O_CREAT | O_EXCL | O_RDWR | PG_BINARY, 0777);
 	PG_EXPECT_SYS(fd >= 0, "touch name1.txt");
-	PG_EXPECT_SYS(close(fd) == 0);
+	PG_REQUIRE_SYS(fd < 0 || close(fd) == 0);
 
 	fd = open(path2, O_RDWR | PG_BINARY, 0777);
 	PG_EXPECT_SYS(fd >= 0, "open name2.txt");
@@ -435,9 +442,9 @@ filesystem_metadata_tests(void)
 		PG_EXPECT_SYS(rename(path, path2) == -1,
 					  "Windows non-POSIX: cannot rename name1.txt -> name2.txt while unlinked file is still open");
 		PG_EXPECT_EQ(errno, EACCES);
-		PG_REQUIRE_SYS(unlink(path) == 0);
+		PG_EXPECT_SYS(unlink(path) == 0);
 	}
-	PG_REQUIRE_SYS(close(fd) == 0);
+	PG_REQUIRE_SYS(fd < 0 || close(fd) == 0);
 
 #ifdef WIN32
 
diff --git a/src/port/win32error.c b/src/port/win32error.c
index a78d323827..67ce805d77 100644
--- a/src/port/win32error.c
+++ b/src/port/win32error.c
@@ -167,6 +167,9 @@ static const struct
 	},
 	{
 		ERROR_INVALID_NAME, ENOENT
+	},
+	{
+		ERROR_CANT_RESOLVE_FILENAME, ENOENT
 	}
 };
 
diff --git a/src/port/win32stat.c b/src/port/win32stat.c
index 5f3d0d22ff..ce8d87093d 100644
--- a/src/port/win32stat.c
+++ b/src/port/win32stat.c
@@ -125,15 +125,30 @@ _pglstat64(const char *name, struct stat *buf)
 
 	hFile = pgwin32_open_handle(name, O_RDONLY, true);
 	if (hFile == INVALID_HANDLE_VALUE)
-		return -1;
-
-	ret = fileinfo_to_stat(hFile, buf);
+	{
+		if (errno == ENOENT)
+		{
+			/*
+			 * If it's a junction point pointing to a non-existent path, we'll
+			 * have ENOENT here (because pgwin32_open_handle does not use
+			 * FILE_FLAG_OPEN_REPARSE_POINT).  In that case, we'll try again
+			 * with readlink() below, which will distinguish true ENOENT from
+			 * pseudo-symlink.
+			 */
+			memset(buf, 0, sizeof(*buf));
+			ret = 0;
+		}
+		else
+			return -1;
+	}
+	else
+		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))
+	if ((ret == 0 && S_ISDIR(buf->st_mode)) || hFile == INVALID_HANDLE_VALUE)
 	{
 		char		next[MAXPGPATH];
 		ssize_t		size;
@@ -169,10 +184,12 @@ _pglstat64(const char *name, struct stat *buf)
 			buf->st_mode &= ~S_IFDIR;
 			buf->st_mode |= S_IFLNK;
 			buf->st_size = size;
+			ret = 0;
 		}
 	}
 
-	CloseHandle(hFile);
+	if (hFile != INVALID_HANDLE_VALUE)
+		CloseHandle(hFile);
 	return ret;
 }
 
-- 
2.35.1

