0006-Fix-lstat-on-broken-junction-points.patch

text/x-patch

Filename: 0006-Fix-lstat-on-broken-junction-points.patch
Type: text/x-patch
Part: 5
Message: Understanding, testing and improving our Windows filesystem code

Patch

Format: format-patch
Series: patch 0006
Subject: Fix lstat() on broken junction points.
File+
src/port/t/001_filesystem.c 7 0
src/port/win32error.c 3 0
src/port/win32stat.c 22 5
From b61a66f874d3b8151158e7956a3272e9259a8941 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 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 |  7 +++++++
 src/port/win32error.c       |  3 +++
 src/port/win32stat.c        | 27 ++++++++++++++++++++++-----
 3 files changed, 32 insertions(+), 5 deletions(-)

diff --git a/src/port/t/001_filesystem.c b/src/port/t/001_filesystem.c
index c2832b0a20..28ef069ee0 100644
--- a/src/port/t/001_filesystem.c
+++ b/src/port/t/001_filesystem.c
@@ -273,6 +273,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");
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.37.3