/*-
 * See the file LICENSE for redistribution information.
 *
 * Copyright (c) 1996-2003
 *	Sleepycat Software.  All rights reserved.
 */

#include "db_config.h"

#ifndef lint
static const char revid[] = "$Id: os_map.c,v 11.43 2003/02/17 16:05:45 bostic Exp $";
#endif /* not lint */

#include "db_int.h"

static int __os_map
  __P((DB_ENV *, char *, REGINFO *, DB_FH *, size_t, int, int, int, void **));
static int __os_unique_name __P((char *, HANDLE, char *, size_t));

/*
 * __os_r_sysattach --
 *	Create/join a shared memory region.
 */
int
__os_r_sysattach(dbenv, infop, rp)
	DB_ENV *dbenv;
	REGINFO *infop;
	REGION *rp;
{
	DB_FH *fhp;
	int is_system, ret;

	/*
	 * Try to open/create the file.  We DO NOT need to ensure that multiple
	 * threads/processes attempting to simultaneously create the region are
	 * properly ordered, our caller has already taken care of that.
	 */
	if ((ret = __os_open(dbenv, infop->name,
	    DB_OSO_DIRECT |
	    F_ISSET(infop, REGION_CREATE_OK) ? DB_OSO_CREATE: 0,
	    infop->mode, &fhp)) != 0) {
		__db_err(dbenv, "%s: %s", infop->name, db_strerror(ret));
		return (ret);
	}

	/*
	 * On Windows/9X, files that are opened by multiple processes do not
	 * share data correctly.  For this reason, the DB_SYSTEM_MEM flag is
	 * implied for any application that does not specify the DB_PRIVATE
	 * flag.
	 */
	is_system = F_ISSET(dbenv, DB_ENV_SYSTEM_MEM) ||
	    (!F_ISSET(dbenv, DB_ENV_PRIVATE) && __os_is_winnt() == 0);

	/*
	 * Map the file in.  If we're creating an in-system-memory region,
	 * specify a segment ID (which is never used again) so that the
	 * calling code writes out the REGENV_REF structure to the primary
	 * environment file.
	 */
	ret = __os_map(dbenv, infop->name, infop, fhp, rp->size,
	   1, is_system, 0, &infop->addr);
	if (ret == 0 && is_system == 1)
		rp->segid = 1;

	(void)__os_closehandle(dbenv, fhp);

	return (ret);
}

/*
 * __os_r_sysdetach --
 *	Detach from a shared memory region.
 */
int
__os_r_sysdetach(dbenv, infop, destroy)
	DB_ENV *dbenv;
	REGINFO *infop;
	int destroy;
{
	int ret, t_ret;

	if (infop->wnt_handle != NULL) {
		(void)CloseHandle(infop->wnt_handle);
		infop->wnt_handle = NULL;
	}

	ret = !UnmapViewOfFile(infop->addr) ? __os_win32_errno() : 0;
	if (ret != 0)
		__db_err(dbenv, "UnmapViewOfFile: %s", strerror(ret));

	if (!F_ISSET(dbenv, DB_ENV_SYSTEM_MEM) && destroy) {
		if (F_ISSET(dbenv, DB_ENV_OVERWRITE))
			(void)__db_overwrite(dbenv, infop->name);
		if ((t_ret = __os_unlink(dbenv, infop->name)) != 0 && ret == 0)
			ret = t_ret;
	}

	return (ret);
}

/*
 * __os_mapfile --
 *	Map in a shared memory file.
 */
int
__os_mapfile(dbenv, path, fhp, len, is_rdonly, addr)
	DB_ENV *dbenv;
	char *path;
	DB_FH *fhp;
	int is_rdonly;
	size_t len;
	void **addr;
{
	/* If the user replaced the map call, call through their interface. */
	if (DB_GLOBAL(j_map) != NULL)
		return (DB_GLOBAL(j_map)(path, len, 0, is_rdonly, addr));

	return (__os_map(dbenv, path, NULL, fhp, len, 0, 0, is_rdonly, addr));
}

/*
 * __os_unmapfile --
 *	Unmap the shared memory file.
 */
int
__os_unmapfile(dbenv, addr, len)
	DB_ENV *dbenv;
	void *addr;
	size_t len;
{
	/* If the user replaced the map call, call through their interface. */
	if (DB_GLOBAL(j_unmap) != NULL)
		return (DB_GLOBAL(j_unmap)(addr, len));

	return (!UnmapViewOfFile(addr) ? __os_win32_errno() : 0);
}

/*
 * __os_unique_name --
 *	Create a unique identifying name from a pathname (may be absolute or
 *	relative) and/or a file descriptor.
 *
 *	The name returned must be unique (different files map to different
 *	names), and repeatable (same files, map to same names).  It's not
 *	so easy to do by name.  Should handle not only:
 *
 *		foo.bar  ==  ./foo.bar  ==  c:/whatever_path/foo.bar
 *
 *	but also understand that:
 *
 *		foo.bar  ==  Foo.Bar	(FAT file system)
 *		foo.bar  !=  Foo.Bar	(NTFS)
 *
 *	The best solution is to use the file index, found in the file
 *	information structure (similar to UNIX inode #).
 *
 *	When a file is deleted, its file index may be reused,
 *	but if the unique name has not gone from its namespace,
 *	we may get a conflict.  So to ensure some tie in to the
 *	original pathname, we also use the creation time and the
 *	file basename.  This is not a perfect system, but it
 *	should work for all but anamolous test cases.
 *
 */
static int
__os_unique_name(orig_path, hfile, result_path, result_path_len)
	char *orig_path, *result_path;
	HANDLE hfile;
	size_t result_path_len;
{
	BY_HANDLE_FILE_INFORMATION fileinfo;
	char *basename, *p;

	/*
	 * In Windows, pathname components are delimited by '/' or '\', and
	 * if neither is present, we need to strip off leading drive letter
	 * (e.g. c:foo.txt).
	 */
	basename = strrchr(orig_path, '/');
	p = strrchr(orig_path, '\\');
	if (basename == NULL || (p != NULL && p > basename))
		basename = p;
	if (basename == NULL)
		basename = strrchr(orig_path, ':');

	if (basename == NULL)
		basename = orig_path;
	else
		basename++;

	if (!GetFileInformationByHandle(hfile, &fileinfo))
		return (__os_win32_errno());

	(void)snprintf(result_path, result_path_len,
	    "__db_shmem.%8.8lx.%8.8lx.%8.8lx.%8.8lx.%8.8lx.%s",
	    fileinfo.dwVolumeSerialNumber,
	    fileinfo.nFileIndexHigh,
	    fileinfo.nFileIndexLow,
	    fileinfo.ftCreationTime.dwHighDateTime,
	    fileinfo.ftCreationTime.dwHighDateTime,
	    basename);

	return (0);
}

/*
 * __os_map --
 *	The mmap(2) function for Windows.
 */
static int
__os_map(dbenv, path, infop, fhp, len, is_region, is_system, is_rdonly, addr)
	DB_ENV *dbenv;
	REGINFO *infop;
	char *path;
	DB_FH *fhp;
	int is_region, is_system, is_rdonly;
	size_t len;
	void **addr;
{
	HANDLE hMemory;
	int ret, use_pagefile;
	char shmem_name[MAXPATHLEN];
	void *pMemory;

	ret = 0;
	if (infop != NULL)
		infop->wnt_handle = NULL;

	use_pagefile = is_region && is_system;

	/*
	 * If creating a region in system space, get a matching name in the
	 * paging file namespace.
	 */
	if (use_pagefile && (ret = __os_unique_name(
	    path, fhp->handle, shmem_name, sizeof(shmem_name))) != 0)
		return (ret);

	/*
	 * XXX
	 * DB: We have not implemented copy-on-write here.
	 *
	 * If this is an region in system memory, we try to open it using the
	 * OpenFileMapping() first, and only call CreateFileMapping() if we're
	 * really creating the section.  There are two reasons:
	 *
	 * 1) We only create the mapping if we have newly created the region.
	 *    This avoids a long-running problem caused by Windows reference
	 *    counting, where regions that are closed by all processes are
	 *    deleted.  It turns out that just checking for a zeroed region
	 *    is not good enough. See [#4882] and [#7127] for the details.
	 *
	 * 2) CreateFileMapping seems to mess up making the commit charge to
	 *    the process. It thinks, incorrectly, that when we want to join a
	 *    previously existing section, that it should make a commit charge
	 *    for the whole section.  In fact, there is no new committed memory
	 *    whatever.  The call can fail if there is insufficient memory free
	 *    to handle the erroneous commit charge.  So, we find that the
	 *    bogus commit is not made if we call OpenFileMapping.
	 */
	hMemory = NULL;
	if (use_pagefile) {
		hMemory = OpenFileMapping(
		    is_rdonly ? FILE_MAP_READ : FILE_MAP_ALL_ACCESS,
		    0, shmem_name);

		if (hMemory == NULL && F_ISSET(infop, REGION_CREATE_OK))
			hMemory = CreateFileMapping((HANDLE)-1, 0,
			    is_rdonly ? PAGE_READONLY : PAGE_READWRITE,
			    0, (DWORD)len, shmem_name);
	} else
		hMemory = CreateFileMapping(fhp->handle, 0,
		    is_rdonly ? PAGE_READONLY : PAGE_READWRITE,
		    0, (DWORD)len, NULL);

	if (hMemory == NULL) {
		ret = __os_win32_errno();
		__db_err(dbenv, "OpenFileMapping: %s", strerror(ret));
		return (__db_panic(dbenv, ret));
	}

	pMemory = MapViewOfFile(hMemory,
	    (is_rdonly ? FILE_MAP_READ : FILE_MAP_ALL_ACCESS), 0, 0, len);
	if (pMemory == NULL) {
		ret = __os_win32_errno();
		__db_err(dbenv, "MapViewOfFile: %s", strerror(ret));
		return (__db_panic(dbenv, ret));
	}

	/*
	 * XXX
	 * It turns out that the kernel object underlying the named section
	 * is reference counted, but that the call to MapViewOfFile() above
	 * does NOT increment the reference count! So, if we close the handle
	 * here, the kernel deletes the object from the kernel namespace.
	 * When a second process comes along to join the region, the kernel
	 * happily creates a new object with the same name, but completely
	 * different identity. The two processes then have distinct isolated
	 * mapped sections, not at all what was wanted. Not closing the handle
	 * here fixes this problem.  We carry the handle around in the region
	 * structure so we can close it when unmap is called.
	 */
	if (use_pagefile && infop != NULL)
		infop->wnt_handle = hMemory;
	else
		CloseHandle(hMemory);

	*addr = pMemory;
	return (ret);
}
