From db4d22d0eff091e96d92c82566c9e9d297040b93 Mon Sep 17 00:00:00 2001
From: Magnus Hagander <magnus@hagander.net>
Date: Sun, 9 Jan 2011 15:06:55 +0100
Subject: [PATCH] Add pgreadlink() on Windows to read junction points

Add support for reading back information about the symbolic
links we've created with pgsymlink(), which are actually
Junction Points. Just like pgsymlink() can only create directory
symlinks, pgreadlink() can only read directory symlinks.
---
 src/include/port.h |   3 ++
 src/port/dirmod.c  | 118 +++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 121 insertions(+)

diff --git a/src/include/port.h b/src/include/port.h
index 2f5abdd3348..7ad464c07dc 100644
--- a/src/include/port.h
+++ b/src/include/port.h
@@ -284,8 +284,11 @@ extern int	pgunlink(const char *path);
  */
 #if defined(WIN32) && !defined(__CYGWIN__)
 extern int	pgsymlink(const char *oldpath, const char *newpath);
+extern int	pgreadlink(const char *path, char *buf, size_t size);
+extern bool pgwin32_is_junction(char *path);
 
 #define symlink(oldpath, newpath)	pgsymlink(oldpath, newpath)
+#define readlink(path, buf, size)	pgreadlink(path, buf, size)
 #endif
 
 extern bool rmtree(const char *path, bool rmtopdir);
diff --git a/src/port/dirmod.c b/src/port/dirmod.c
index 0d373a428bc..b6a5564c2c4 100644
--- a/src/port/dirmod.c
+++ b/src/port/dirmod.c
@@ -297,6 +297,124 @@ pgsymlink(const char *oldpath, const char *newpath)
 
 	return 0;
 }
+
+/*
+ *	pgreadlink - uses Win32 junction points
+ */
+int
+pgreadlink(const char *path, char *buf, size_t size)
+{
+	DWORD		attr;
+	HANDLE		h;
+	char		buffer[MAX_PATH * sizeof(WCHAR) + sizeof(REPARSE_JUNCTION_DATA_BUFFER)];
+	REPARSE_JUNCTION_DATA_BUFFER *reparseBuf = (REPARSE_JUNCTION_DATA_BUFFER *) buffer;
+	DWORD		len;
+	int			r;
+
+	attr = GetFileAttributes(path);
+	if (attr == INVALID_FILE_ATTRIBUTES)
+	{
+		_dosmaperr(GetLastError());
+		return -1;
+	}
+	if ((attr & FILE_ATTRIBUTE_REPARSE_POINT) == 0)
+	{
+		errno = EINVAL;
+		return -1;
+	}
+
+	h = CreateFile(path,
+				   GENERIC_READ,
+				   FILE_SHARE_READ | FILE_SHARE_WRITE,
+				   NULL,
+				   OPEN_EXISTING,
+				   FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS,
+				   0);
+	if (h == INVALID_HANDLE_VALUE)
+	{
+		_dosmaperr(GetLastError());
+		return -1;
+	}
+
+	if (!DeviceIoControl(h,
+						 FSCTL_GET_REPARSE_POINT,
+						 NULL,
+						 0,
+						 (LPVOID) reparseBuf,
+						 sizeof(buffer),
+						 &len,
+						 NULL))
+	{
+		LPSTR		msg;
+
+		errno = 0;
+		FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
+					  NULL, GetLastError(),
+					  MAKELANGID(LANG_ENGLISH, SUBLANG_DEFAULT),
+					  (LPSTR) &msg, 0, NULL);
+#ifndef FRONTEND
+		ereport(ERROR,
+				(errcode_for_file_access(),
+				 errmsg("could not get junction for \"%s\": %s",
+						path, msg)));
+#else
+		fprintf(stderr, _("could not get junction for \"%s\": %s\n"),
+				path, msg);
+#endif
+		LocalFree(msg);
+		CloseHandle(h);
+		errno = EINVAL;
+		return -1;
+	}
+	CloseHandle(h);
+
+	/* Got it, let's get some results from this */
+	if (reparseBuf->ReparseTag != IO_REPARSE_TAG_MOUNT_POINT)
+	{
+		errno = EINVAL;
+		return -1;
+	}
+
+	r = WideCharToMultiByte(CP_ACP, 0,
+							reparseBuf->PathBuffer, -1,
+							buf,
+							size,
+							NULL, NULL);
+
+	if (r <= 0)
+	{
+		errno = EINVAL;
+		return -1;
+	}
+
+	/*
+	 * If the path starts with "\??\", which it will do in most (all?) cases,
+	 * strip those out.
+	 */
+	if (r > 4 && strncmp(buf, "\\??\\", 4) == 0)
+	{
+		memmove(buf, buf + 4, strlen(buf + 4) + 1);
+		r -= 4;
+	}
+	return r;
+}
+
+/*
+ * Assumes the file exists, so will return false if it doesn't
+ * (since a nonexistant file is not a junction)
+ */
+bool
+pgwin32_is_junction(char *path)
+{
+	DWORD		attr = GetFileAttributes(path);
+
+	if (attr == INVALID_FILE_ATTRIBUTES)
+	{
+		_dosmaperr(GetLastError());
+		return false;
+	}
+	return ((attr & FILE_ATTRIBUTE_REPARSE_POINT) == FILE_ATTRIBUTE_REPARSE_POINT);
+}
 #endif   /* defined(WIN32) && !defined(__CYGWIN__) */
 
 
-- 
GitLab