From 18d82d03b51f1d34e8b076e89e54a4c8e0818343 Mon Sep 17 00:00:00 2001
From: Magnus Hagander <magnus@hagander.net>
Date: Wed, 21 Mar 2007 14:39:23 +0000
Subject: [PATCH] Native shared memory implementation for win32. Uses same
 underlying tech as before, but not the sysv emulation layer.

---
 configure                       |  10 +-
 configure.in                    |  12 +-
 src/backend/port/sysv_shmem.c   |  16 +-
 src/backend/port/win32/Makefile |   4 +-
 src/backend/port/win32/shmem.c  | 128 --------------
 src/backend/port/win32_shmem.c  | 287 ++++++++++++++++++++++++++++++++
 src/tools/msvc/Mkvcbuild.pm     |   4 +-
 7 files changed, 310 insertions(+), 151 deletions(-)
 delete mode 100644 src/backend/port/win32/shmem.c
 create mode 100644 src/backend/port/win32_shmem.c

diff --git a/configure b/configure
index b53acb350b2..71dbaee0aa4 100755
--- a/configure
+++ b/configure
@@ -22278,13 +22278,21 @@ fi
 
 
 # Select shared-memory implementation type.
+if test "$PORTNAME" != "win32"; then
 
 cat >>confdefs.h <<\_ACEOF
 #define USE_SYSV_SHARED_MEMORY 1
 _ACEOF
 
-SHMEM_IMPLEMENTATION="src/backend/port/sysv_shmem.c"
+  SHMEM_IMPLEMENTATION="src/backend/port/sysv_shmem.c"
+else
+
+cat >>confdefs.h <<\_ACEOF
+#define USE_WIN32_SHARED_MEMORY 1
+_ACEOF
 
+  SHMEM_IMPLEMENTATION="src/backend/port/win32_shmem.c"
+fi
 
 # If not set in template file, set bytes to use libc memset()
 if test x"$MEMSET_LOOP_LIMIT" = x"" ; then
diff --git a/configure.in b/configure.in
index e5d72eb1d30..273f79bf5f5 100644
--- a/configure.in
+++ b/configure.in
@@ -1,5 +1,5 @@
 dnl Process this file with autoconf to produce a configure script.
-dnl $PostgreSQL: pgsql/configure.in,v 1.502 2007/02/21 15:12:39 momjian Exp $
+dnl $PostgreSQL: pgsql/configure.in,v 1.503 2007/03/21 14:39:23 mha Exp $
 dnl
 dnl Developers, please strive to achieve this order:
 dnl
@@ -1367,9 +1367,13 @@ fi
 
 
 # Select shared-memory implementation type.
-AC_DEFINE(USE_SYSV_SHARED_MEMORY, 1, [Define to select SysV-style shared memory.])
-SHMEM_IMPLEMENTATION="src/backend/port/sysv_shmem.c"
-
+if test "$PORTNAME" != "win32"; then
+  AC_DEFINE(USE_SYSV_SHARED_MEMORY, 1, [Define to select SysV-style shared memory.])
+  SHMEM_IMPLEMENTATION="src/backend/port/sysv_shmem.c"
+else
+  AC_DEFINE(USE_WIN32_SHARED_MEMORY, 1, [Define to select Win32-style shared memory.])
+  SHMEM_IMPLEMENTATION="src/backend/port/win32_shmem.c"
+fi
 
 # If not set in template file, set bytes to use libc memset()
 if test x"$MEMSET_LOOP_LIMIT" = x"" ; then
diff --git a/src/backend/port/sysv_shmem.c b/src/backend/port/sysv_shmem.c
index 5488bc0510c..8677752e30d 100644
--- a/src/backend/port/sysv_shmem.c
+++ b/src/backend/port/sysv_shmem.c
@@ -10,7 +10,7 @@
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/port/sysv_shmem.c,v 1.49 2007/02/06 16:20:23 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/port/sysv_shmem.c,v 1.50 2007/03/21 14:39:23 mha Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -195,11 +195,8 @@ PGSharedMemoryIsInUse(unsigned long id1, unsigned long id2)
 {
 	IpcMemoryId shmId = (IpcMemoryId) id2;
 	struct shmid_ds shmStat;
-
-#ifndef WIN32
 	struct stat statbuf;
 	PGShmemHeader *hdr;
-#endif
 
 	/*
 	 * We detect whether a shared memory segment is in use by seeing whether
@@ -238,11 +235,8 @@ PGSharedMemoryIsInUse(unsigned long id1, unsigned long id2)
 	/*
 	 * Try to attach to the segment and see if it matches our data directory.
 	 * This avoids shmid-conflict problems on machines that are running
-	 * several postmasters under the same userid.  On Windows, which doesn't
-	 * have useful inode numbers, we can't do this so we punt and assume there
-	 * is a conflict.
+	 * several postmasters under the same userid. 
 	 */
-#ifndef WIN32
 	if (stat(DataDir, &statbuf) < 0)
 		return true;			/* if can't stat, be conservative */
 
@@ -265,7 +259,6 @@ PGSharedMemoryIsInUse(unsigned long id1, unsigned long id2)
 
 	/* Trouble --- looks a lot like there's still live backends */
 	shmdt((void *) hdr);
-#endif
 
 	return true;
 }
@@ -296,10 +289,7 @@ PGSharedMemoryCreate(Size size, bool makePrivate, int port)
 	void	   *memAddress;
 	PGShmemHeader *hdr;
 	IpcMemoryId shmid;
-
-#ifndef WIN32
 	struct stat statbuf;
-#endif
 
 	/* Room for a header? */
 	Assert(size > MAXALIGN(sizeof(PGShmemHeader)));
@@ -372,7 +362,6 @@ PGSharedMemoryCreate(Size size, bool makePrivate, int port)
 	hdr->creatorPID = getpid();
 	hdr->magic = PGShmemMagic;
 
-#ifndef WIN32
 	/* Fill in the data directory ID info, too */
 	if (stat(DataDir, &statbuf) < 0)
 		ereport(FATAL,
@@ -381,7 +370,6 @@ PGSharedMemoryCreate(Size size, bool makePrivate, int port)
 						DataDir)));
 	hdr->device = statbuf.st_dev;
 	hdr->inode = statbuf.st_ino;
-#endif
 
 	/*
 	 * Initialize space allocation status for segment.
diff --git a/src/backend/port/win32/Makefile b/src/backend/port/win32/Makefile
index daa98f65519..620d921eddb 100644
--- a/src/backend/port/win32/Makefile
+++ b/src/backend/port/win32/Makefile
@@ -4,7 +4,7 @@
 #    Makefile for backend/port/win32
 #
 # IDENTIFICATION
-#    $PostgreSQL: pgsql/src/backend/port/win32/Makefile,v 1.9 2007/01/20 17:16:12 petere Exp $
+#    $PostgreSQL: pgsql/src/backend/port/win32/Makefile,v 1.10 2007/03/21 14:39:23 mha Exp $
 #
 #-------------------------------------------------------------------------
 
@@ -12,7 +12,7 @@ subdir = src/backend/port/win32
 top_builddir = ../../../..
 include $(top_builddir)/src/Makefile.global
 
-OBJS = shmem.o timer.o socket.o signal.o security.o
+OBJS = timer.o socket.o signal.o security.o
 
 all: SUBSYS.o
 
diff --git a/src/backend/port/win32/shmem.c b/src/backend/port/win32/shmem.c
deleted file mode 100644
index 18dac2f553d..00000000000
--- a/src/backend/port/win32/shmem.c
+++ /dev/null
@@ -1,128 +0,0 @@
-/*-------------------------------------------------------------------------
- *
- * shmem.c
- *	  Microsoft Windows Win32 Shared Memory Emulation
- *
- * Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group
- *
- * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/port/win32/shmem.c,v 1.14 2007/01/05 22:19:35 momjian Exp $
- *
- *-------------------------------------------------------------------------
- */
-
-#include "postgres.h"
-
-static DWORD s_segsize = 0;
-
-/* Detach from a shared mem area based on its address */
-int
-shmdt(const void *shmaddr)
-{
-	if (UnmapViewOfFile((LPCVOID *) shmaddr))
-		return 0;
-	else
-		return -1;
-}
-
-/* Attach to an existing area */
-void *
-shmat(int memId, void *shmaddr, int flag)
-{
-	/* TODO -- shmat needs to count # attached to shared mem */
-	void	   *lpmem = MapViewOfFileEx((HANDLE) memId,
-										FILE_MAP_WRITE | FILE_MAP_READ,
-			0, 0, /* (DWORD)pshmdsc->segsize */ 0 /* s_segsize */ , shmaddr);
-
-	if (lpmem == NULL)
-	{
-		lpmem = (void *) -1;
-		_dosmaperr(GetLastError());
-	}
-
-	return lpmem;
-}
-
-/* Control a shared mem area */
-int
-shmctl(int shmid, int flag, struct shmid_ds * dummy)
-{
-	if (flag == IPC_RMID)
-	{
-		/* Delete the area */
-		CloseHandle((HANDLE) shmid);
-		return 0;
-	}
-	if (flag == IPC_STAT)
-	{
-		/* Can only test for if exists */
-		int			hmap = shmget(shmid, 0, 0);
-
-		if (hmap < 0)
-		{
-			/* Shared memory does not exist */
-			errno = EINVAL;
-			return -1;
-		}
-		else
-		{
-			/* Shared memory does exist and must be in use */
-			shmctl(hmap, IPC_RMID, NULL);		/* Release our hold on it */
-			errno = 0;
-			return 0;
-		}
-	}
-
-	errno = EINVAL;
-	return -1;
-}
-
-/* Get an area based on the IPC key */
-int
-shmget(int memKey, int size, int flag)
-{
-	HANDLE		hmap;
-	char		szShareMem[32];
-	DWORD		dwRet;
-
-	s_segsize = size;
-	sprintf(szShareMem, "PostgreSQL.%d", memKey);
-
-	if (flag & IPC_CREAT)
-	{
-		hmap = CreateFileMapping((HANDLE) 0xFFFFFFFF,	/* Use the swap file	*/
-								 NULL,
-								 PAGE_READWRITE,		/* Memory is Read/Write */
-								 0L,	/* Size Upper 32 Bits	*/
-								 (DWORD) s_segsize,		/* Size Lower 32 bits */
-								 szShareMem);
-	}
-	else
-	{
-		hmap = OpenFileMapping(FILE_MAP_ALL_ACCESS,
-							   FALSE,
-							   szShareMem);
-		if (!hmap)
-		{
-			errno = ENOENT;
-			return -1;
-		}
-	}
-
-	dwRet = GetLastError();
-	if (dwRet == ERROR_ALREADY_EXISTS && hmap && (flag & (IPC_CREAT | IPC_EXCL)))
-	{
-		/* Caller wanted to create the segment -- error if already exists */
-		CloseHandle(hmap);
-		errno = EEXIST;
-		return -1;
-	}
-	else if (!hmap)
-	{
-		/* Unable to get shared memory */
-		_dosmaperr(GetLastError());
-		return -1;
-	}
-
-	return (int) hmap;
-}
diff --git a/src/backend/port/win32_shmem.c b/src/backend/port/win32_shmem.c
new file mode 100644
index 00000000000..b912be54ecf
--- /dev/null
+++ b/src/backend/port/win32_shmem.c
@@ -0,0 +1,287 @@
+/*-------------------------------------------------------------------------
+ *
+ * win32_shmem.c
+ *	  Implement shared memory using win32 facilities
+ *
+ * Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ *	  $PostgreSQL: pgsql/src/backend/port/win32_shmem.c,v 1.1 2007/03/21 14:39:23 mha Exp $
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "miscadmin.h"
+#include "storage/ipc.h"
+#include "storage/pg_shmem.h"
+
+unsigned long UsedShmemSegID = 0;
+void	   *UsedShmemSegAddr = NULL;
+
+static void pgwin32_SharedMemoryDelete(int status, Datum shmId);
+
+/*
+ * Generate shared memory segment name. Expand the data directory, to generate
+ * an identifier unique for this data directory. Then replace all backslashes
+ * with forward slashes, since backslashes aren't permitted in global object names.
+ *
+ * Store the shared memory segment in the Global\ namespace (requires NT2 TSE or
+ * 2000, but that's all we support for other reasons as well), to make sure you can't
+ * open two postmasters in different sessions against the same data directory.
+ *
+ * XXX: What happens with junctions? It's only someone breaking things on purpose,
+ *		and this is still better than before, but we might want to do something about
+ *		that sometime in the future.
+ */
+static char *
+GetSharedMemName(void)
+{
+	char	   *retptr;
+	DWORD		bufsize;
+	DWORD		r;
+	char	   *cp;
+
+	bufsize = GetFullPathName(DataDir, 0, NULL, NULL);
+	if (bufsize == 0)
+		elog(FATAL, "could not get size for full pathname of datadir %s: %lu",
+			 DataDir, GetLastError());
+
+	retptr = malloc(bufsize + 1 + 18);	/* 1 NULL and 18 for
+										 * Global\PostgreSQL: */
+	if (retptr == NULL)
+		elog(FATAL, "could not allocate memory for shared memory name");
+
+	strcpy(retptr, "Global\\PostgreSQL:");
+	r = GetFullPathName(DataDir, bufsize, retptr + 11, NULL);
+	if (r == 0 || r > bufsize)
+		elog(FATAL, "could not generate full pathname for datadir %s: %lu",
+			 DataDir, GetLastError());
+
+	for (cp = retptr; *cp; cp++)
+		if (*cp == '\\')
+			*cp = '/';
+
+	return retptr;
+}
+
+
+/*
+ * PGSharedMemoryIsInUse
+ *
+ * Is a previously-existing shmem segment still existing and in use?
+ *
+ * The point of this exercise is to detect the case where a prior postmaster
+ * crashed, but it left child backends that are still running.	Therefore
+ * we only care about shmem segments that are associated with the intended
+ * DataDir.  This is an important consideration since accidental matches of
+ * shmem segment IDs are reasonably common.
+ *
+ */
+bool
+PGSharedMemoryIsInUse(unsigned long id1, unsigned long id2)
+{
+	char	   *szShareMem;
+	HANDLE		hmap;
+
+	szShareMem = GetSharedMemName();
+
+	hmap = OpenFileMapping(FILE_MAP_READ, FALSE, szShareMem);
+
+	free(szShareMem);
+
+	if (hmap == NULL)
+		return false;
+
+	CloseHandle(hmap);
+	return true;
+}
+
+
+/*
+ * PGSharedMemoryCreate
+ *
+ * Create a shared memory segment of the given size and initialize its
+ * standard header.
+ *
+ * makePrivate means to always create a new segment, rather than attach to
+ * or recycle any existing segment. On win32, we always create a new segment,
+ * since there is no need for recycling (segments go away automatically
+ * when the last backend exits)
+ *
+ */
+PGShmemHeader *
+PGSharedMemoryCreate(Size size, bool makePrivate, int port)
+{
+	void	   *memAddress;
+	PGShmemHeader *hdr;
+	HANDLE		hmap,
+				hmap2;
+	char	   *szShareMem;
+
+	/* Room for a header? */
+	Assert(size > MAXALIGN(sizeof(PGShmemHeader)));
+
+	szShareMem = GetSharedMemName();
+
+	UsedShmemSegAddr = NULL;
+
+	hmap = CreateFileMapping((HANDLE) 0xFFFFFFFF,		/* Use the pagefile */
+							 NULL,		/* Default security attrs */
+							 PAGE_READWRITE,	/* Memory is Read/Write */
+							 0L,	/* Size Upper 32 Bits	*/
+							 (DWORD) size,		/* Size Lower 32 bits */
+							 szShareMem);
+
+	if (!hmap)
+		ereport(FATAL,
+				(errmsg("could not create shared memory segment: %lu", GetLastError()),
+				 errdetail("Failed system call was CreateFileMapping(size=%lu, name=%s)", size, szShareMem)));
+
+	/*
+	 * If the segment already existed, CreateFileMapping() will return a
+	 * handle to the existing one.
+	 */
+	if (GetLastError() == ERROR_ALREADY_EXISTS)
+	{
+		/*
+		 * When recycling a shared memory segment, it may take a short while
+		 * before it gets dropped from the global namespace. So re-try after
+		 * sleeping for a second.
+		 */
+		CloseHandle(hmap);		/* Close the old handle, since we got a valid
+								 * one to the previous segment. */
+
+		Sleep(1000);
+
+		hmap = CreateFileMapping((HANDLE) 0xFFFFFFFF, NULL, PAGE_READWRITE, 0L, (DWORD) size, szShareMem);
+		if (!hmap)
+			ereport(FATAL,
+					(errmsg("could not create shared memory segment: %lu", GetLastError()),
+					 errdetail("Failed system call was CreateFileMapping(size=%lu, name=%s)", size, szShareMem)));
+
+		if (GetLastError() == ERROR_ALREADY_EXISTS)
+			ereport(FATAL,
+				 (errmsg("pre-existing shared memory block is still in use"),
+				  errhint("Check if there are any old server processes still running, and terminate them.")));
+	}
+
+	free(szShareMem);
+
+	/*
+	 * Make the handle inheritable
+	 */
+	if (!DuplicateHandle(GetCurrentProcess(), hmap, GetCurrentProcess(), &hmap2, 0, TRUE, DUPLICATE_SAME_ACCESS))
+		ereport(FATAL,
+				(errmsg("could not create shared memory segment: %lu", GetLastError()),
+				 errdetail("Failed system call was DuplicateHandle")));
+
+	/*
+	 * Close the old, non-inheritable handle. If this fails we don't really
+	 * care.
+	 */
+	if (!CloseHandle(hmap))
+		elog(LOG, "could not close handle to shared memory: %lu", GetLastError());
+
+
+	/* Register on-exit routine to delete the new segment */
+	on_shmem_exit(pgwin32_SharedMemoryDelete, Int32GetDatum((unsigned long) hmap2));
+
+	/*
+	 * Get a pointer to the new shared memory segment. Map the whole segment
+	 * at once, and let the system decide on the initial address.
+	 */
+	memAddress = MapViewOfFileEx(hmap2, FILE_MAP_WRITE | FILE_MAP_READ, 0, 0, 0, NULL);
+	if (!memAddress)
+		ereport(FATAL,
+				(errmsg("could not create shared memory segment: %lu", GetLastError()),
+				 errdetail("Failed system call was MapViewOfFileEx")));
+
+
+
+	/*
+	 * OK, we created a new segment.  Mark it as created by this process. The
+	 * order of assignments here is critical so that another Postgres process
+	 * can't see the header as valid but belonging to an invalid PID!
+	 */
+	hdr = (PGShmemHeader *) memAddress;
+	hdr->creatorPID = getpid();
+	hdr->magic = PGShmemMagic;
+
+	/*
+	 * Initialize space allocation status for segment.
+	 */
+	hdr->totalsize = size;
+	hdr->freeoffset = MAXALIGN(sizeof(PGShmemHeader));
+
+	/* Save info for possible future use */
+	UsedShmemSegAddr = memAddress;
+	UsedShmemSegID = (unsigned long) hmap2;
+
+	return hdr;
+}
+
+/*
+ * PGSharedMemoryReAttach
+ *
+ * Re-attach to an already existing shared memory segment. Use the
+ * handle inherited from the postmaster.
+ *
+ * UsedShmemSegID and UsedShmemSegAddr are implicit parameters to this
+ * routine.  The caller must have already restored them to the postmaster's
+ * values.
+ */
+void
+PGSharedMemoryReAttach(void)
+{
+	PGShmemHeader *hdr;
+	void	   *origUsedShmemSegAddr = UsedShmemSegAddr;
+
+	Assert(UsedShmemSegAddr != NULL);
+	Assert(IsUnderPostmaster);
+
+	hdr = (PGShmemHeader *) MapViewOfFileEx((HANDLE) UsedShmemSegID, FILE_MAP_READ | FILE_MAP_WRITE, 0, 0, 0, UsedShmemSegAddr);
+	if (!hdr)
+		elog(FATAL, "could not reattach to shared memory (key=%d, addr=%p): %lu",
+			 (int) UsedShmemSegID, UsedShmemSegAddr, GetLastError());
+	if (hdr != origUsedShmemSegAddr)
+		elog(FATAL, "reattaching to shared memory returned unexpected address (got %p, expected %p)",
+			 hdr, origUsedShmemSegAddr);
+	if (hdr->magic != PGShmemMagic)
+		elog(FATAL, "reattaching to shared memory returned non-PostgreSQL memory");
+
+	UsedShmemSegAddr = hdr;		/* probably redundant */
+}
+
+/*
+ * PGSharedMemoryDetach
+ *
+ * Detach from the shared memory segment, if still attached.  This is not
+ * intended for use by the process that originally created the segment. Rather,
+ * this is for subprocesses that have inherited an attachment and want to
+ * get rid of it.
+ */
+void
+PGSharedMemoryDetach(void)
+{
+	if (UsedShmemSegAddr != NULL)
+	{
+		if (!UnmapViewOfFile(UsedShmemSegAddr))
+			elog(LOG, "could not unmap view of shared memory: %lu", GetLastError());
+
+		UsedShmemSegAddr = NULL;
+	}
+}
+
+
+/*
+ *	pgwin32_SharedMemoryDelete(status, shmId)		deletes a shared memory segment
+ *	(called as an on_shmem_exit callback, hence funny argument list)
+ */
+static void
+pgwin32_SharedMemoryDelete(int status, Datum shmId)
+{
+	PGSharedMemoryDetach();
+	if (!CloseHandle((HANDLE) DatumGetInt32(shmId)))
+		elog(LOG, "could not close handle to shared memory: %lu", GetLastError());
+}
diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm
index 9837cc77139..87f3595ce8f 100644
--- a/src/tools/msvc/Mkvcbuild.pm
+++ b/src/tools/msvc/Mkvcbuild.pm
@@ -2,7 +2,7 @@ package Mkvcbuild;
 #
 # Package that generates build files for msvc build
 #
-# $PostgreSQL: pgsql/src/tools/msvc/Mkvcbuild.pm,v 1.3 2007/03/19 09:34:09 mha Exp $
+# $PostgreSQL: pgsql/src/tools/msvc/Mkvcbuild.pm,v 1.4 2007/03/21 14:39:23 mha Exp $
 #
 use Carp;
 use Win32;
@@ -58,7 +58,7 @@ sub mkvcbuild
     $postgres->AddFile('src\backend\utils\fmgrtab.c');
     $postgres->ReplaceFile('src\backend\port\dynloader.c','src\backend\port\dynloader\win32.c');
     $postgres->ReplaceFile('src\backend\port\pg_sema.c','src\backend\port\win32_sema.c');
-    $postgres->ReplaceFile('src\backend\port\pg_shmem.c','src\backend\port\sysv_shmem.c');
+    $postgres->ReplaceFile('src\backend\port\pg_shmem.c','src\backend\port\win32_shmem.c');
     $postgres->AddFiles('src\port',@pgportfiles);
     $postgres->AddDir('src\timezone');
     $postgres->AddFiles('src\backend\parser','scan.l','gram.y');
-- 
GitLab