/*-------------------------------------------------------------------------
 *
 * win32_shmem.c
 *	  Implement shared memory using win32 facilities
 *
 * Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 * IDENTIFICATION
 *	  $PostgreSQL$
 *
 *-------------------------------------------------------------------------
 */
#include "postgres.h"

#include "miscadmin.h"
#include "storage/ipc.h"
#include "storage/pg_shmem.h"

typedef HANDLE IpcMemoryKey;		/* shared memory key passed to shmget(2) */

unsigned long UsedShmemSegID = 0;
void	   *UsedShmemSegAddr = NULL;


/*
 * 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.
 *
 * 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 *
GetShareMemName(void)
{
	char	   *retptr;
	DWORD		bufsize;

	bufsize = GetFullPathName(DataDir, 0, NULL, NULL);
	if (bufsize > 0)
	{
		retptr = malloc(bufsize+1+11); // 1 NULL and 11 for PostgreSQL
		if (retptr)
		{
			DWORD r;
			
			strcpy(retptr,"PostgreSQL:");
			r = GetFullPathName(DataDir, bufsize, retptr+11, NULL);
			if (r <= bufsize && r != 0)
			{
				char *cp;
				
				for (cp = retptr; *cp; cp++)
					if (*cp == '\\')
						*cp = '/';
				return retptr;
			}
		}
	}
	elog(FATAL, "could not generate full pathname for datadir %s: %lu",
		 DataDir, GetLastError());
	
	/* Silence compiler */
	return NULL;
}


/*
 * 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 = GetShareMemName();

	printf("Attempting duplicate check on '%s'\n", szShareMem);
	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 = GetShareMemName();

	/* Make sure PGSharedMemoryAttach doesn't fail without need */
	UsedShmemSegAddr = NULL;

	hmap = CreateFileMapping((HANDLE) 0xFFFFFFFF,	/* Use the swap file	*/
							 NULL,
							 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 (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.")));

	/*
	 * 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")));

	CloseHandle(hmap);

	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", size, szShareMem)));
	}

	free(szShareMem);

	
	/*
	 * 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)
	{
		UnmapViewOfFile(UsedShmemSegAddr);
		UsedShmemSegAddr = NULL;
	}
}
