/*-
 * Copyright (c) 2014-2018 MongoDB, Inc.
 * Copyright (c) 2008-2014 WiredTiger, Inc.
 *	All rights reserved.
 *
 * See the file LICENSE for redistribution information.
 */

#include "wt_internal.h"

/*
 * __win_fs_exist --
 *	Return if the file exists.
 */
static int
__win_fs_exist(WT_FILE_SYSTEM *file_system,
    WT_SESSION *wt_session, const char *name, bool *existp)
{
	WT_DECL_ITEM(name_wide);
	WT_SESSION_IMPL *session;

	WT_UNUSED(file_system);

	session = (WT_SESSION_IMPL *)wt_session;
	*existp = false;

	WT_RET(__wt_to_utf16_string(session, name, &name_wide));

	if (GetFileAttributesW(name_wide->data) != INVALID_FILE_ATTRIBUTES)
		*existp = true;

	__wt_scr_free(session, &name_wide);
	return (0);
}

/*
 * __win_fs_remove --
 *	Remove a file.
 */
static int
__win_fs_remove(WT_FILE_SYSTEM *file_system,
    WT_SESSION *wt_session, const char *name, uint32_t flags)
{
	DWORD windows_error;
	WT_DECL_ITEM(name_wide);
	WT_DECL_RET;
	WT_SESSION_IMPL *session;

	WT_UNUSED(file_system);
	WT_UNUSED(flags);

	session = (WT_SESSION_IMPL *)wt_session;

	WT_RET(__wt_to_utf16_string(session, name, &name_wide));

	if (DeleteFileW(name_wide->data) == FALSE) {
		windows_error = __wt_getlasterror();
		ret = __wt_map_windows_error(windows_error);
		__wt_err(session, ret,
		    "%s: file-remove: DeleteFileW: %s",
		    name, __wt_formatmessage(session, windows_error));
		WT_ERR(ret);
	}

err:	__wt_scr_free(session, &name_wide);
	return (ret);
}

/*
 * __win_fs_rename --
 *	Rename a file.
 */
static int
__win_fs_rename(WT_FILE_SYSTEM *file_system,
    WT_SESSION *wt_session, const char *from, const char *to, uint32_t flags)
{
	DWORD windows_error;
	WT_DECL_ITEM(from_wide);
	WT_DECL_ITEM(to_wide);
	WT_DECL_RET;
	WT_SESSION_IMPL *session;

	WT_UNUSED(file_system);
	WT_UNUSED(flags);
	session = (WT_SESSION_IMPL *)wt_session;

	WT_ERR(__wt_to_utf16_string(session, from, &from_wide));
	WT_ERR(__wt_to_utf16_string(session, to, &to_wide));

	/*
	 * We want an atomic rename, but that's not guaranteed by MoveFileExW
	 * (or by any MSDN API). Don't set the MOVEFILE_COPY_ALLOWED flag to
	 * prevent the system from falling back to a copy and delete process.
	 * Do set the MOVEFILE_WRITE_THROUGH flag so the window is as small
	 * as possible, just in case. WiredTiger renames are done in a single
	 * directory and we expect that to be an atomic metadata update on any
	 * modern filesystem.
	 */
	if (MoveFileExW(from_wide->data, to_wide->data,
	    MOVEFILE_REPLACE_EXISTING | MOVEFILE_WRITE_THROUGH) == FALSE) {
		windows_error = __wt_getlasterror();
		ret = __wt_map_windows_error(windows_error);
		__wt_err(session, ret,
		    "%s to %s: file-rename: MoveFileExW: %s",
		    from, to, __wt_formatmessage(session, windows_error));
		WT_ERR(ret);
	}

err:	__wt_scr_free(session, &from_wide);
	__wt_scr_free(session, &to_wide);
	return (ret);
}

/*
 * __wt_win_fs_size --
 *	Get the size of a file in bytes, by file name.
 */
int
__wt_win_fs_size(WT_FILE_SYSTEM *file_system,
    WT_SESSION *wt_session, const char *name, wt_off_t *sizep)
{
	DWORD windows_error;
	WIN32_FILE_ATTRIBUTE_DATA data;
	WT_DECL_ITEM(name_wide);
	WT_DECL_RET;
	WT_SESSION_IMPL *session;

	WT_UNUSED(file_system);
	session = (WT_SESSION_IMPL *)wt_session;

	WT_RET(__wt_to_utf16_string(session, name, &name_wide));

	if (GetFileAttributesExW(
	    name_wide->data, GetFileExInfoStandard, &data) == 0) {
		windows_error = __wt_getlasterror();
		ret = __wt_map_windows_error(windows_error);
		__wt_err(session, ret,
		    "%s: file-size: GetFileAttributesEx: %s",
		    name, __wt_formatmessage(session, windows_error));
		WT_ERR(ret);
	}

	*sizep = ((int64_t)data.nFileSizeHigh << 32) | data.nFileSizeLow;

err:	__wt_scr_free(session, &name_wide);
	return (ret);
}

/*
 * __win_file_close --
 *	ANSI C close.
 */
static int
__win_file_close(WT_FILE_HANDLE *file_handle, WT_SESSION *wt_session)
{
	DWORD windows_error;
	WT_DECL_RET;
	WT_FILE_HANDLE_WIN *win_fh;
	WT_SESSION_IMPL *session;

	win_fh = (WT_FILE_HANDLE_WIN *)file_handle;
	session = (WT_SESSION_IMPL *)wt_session;

	/*
	 * Close the primary and secondary handles.
	 *
	 * We don't open Windows system handles when opening directories for
	 * flushing, as it's not necessary (or possible) to flush a directory
	 * on Windows. Confirm the file handle is open before closing it.
	 */
	if (win_fh->filehandle != INVALID_HANDLE_VALUE &&
	    CloseHandle(win_fh->filehandle) == 0) {
		windows_error = __wt_getlasterror();
		ret = __wt_map_windows_error(windows_error);
		__wt_err(session, ret,
		    "%s: handle-close: CloseHandle: %s",
		    file_handle->name,
		    __wt_formatmessage(session, windows_error));
	}

	if (win_fh->filehandle_secondary != INVALID_HANDLE_VALUE &&
	    CloseHandle(win_fh->filehandle_secondary) == 0) {
		windows_error = __wt_getlasterror();
		ret = __wt_map_windows_error(windows_error);
		__wt_err(session, ret,
		    "%s: handle-close: secondary: CloseHandle: %s",
		    file_handle->name,
		    __wt_formatmessage(session, windows_error));
	}

	__wt_free(session, file_handle->name);
	__wt_free(session, win_fh);
	return (ret);
}

/*
 * __win_file_lock --
 *	Lock/unlock a file.
 */
static int
__win_file_lock(
    WT_FILE_HANDLE *file_handle, WT_SESSION *wt_session, bool lock)
{
	DWORD windows_error;
	WT_DECL_RET;
	WT_FILE_HANDLE_WIN *win_fh;
	WT_SESSION_IMPL *session;

	win_fh = (WT_FILE_HANDLE_WIN *)file_handle;
	session = (WT_SESSION_IMPL *)wt_session;

	/*
	 * WiredTiger requires this function be able to acquire locks past
	 * the end of file.
	 *
	 * http://msdn.microsoft.com/
	 *    en-us/library/windows/desktop/aa365202%28v=vs.85%29.aspx
	 *
	 * You can lock bytes that are beyond the end of the current file.
	 * This is useful to coordinate adding records to the end of a file.
	 */
	if (lock) {
		if (LockFile(win_fh->filehandle, 0, 0, 1, 0) == FALSE) {
			windows_error = __wt_getlasterror();
			ret = __wt_map_windows_error(windows_error);
			__wt_err(session, ret,
			    "%s: handle-lock: LockFile: %s",
			    file_handle->name,
			    __wt_formatmessage(session, windows_error));
		}
	} else
		if (UnlockFile(win_fh->filehandle, 0, 0, 1, 0) == FALSE) {
			windows_error = __wt_getlasterror();
			ret = __wt_map_windows_error(windows_error);
			__wt_err(session, ret,
			    "%s: handle-lock: UnlockFile: %s",
			    file_handle->name,
			    __wt_formatmessage(session, windows_error));
		}
	return (ret);
}

/*
 * __win_file_read --
 *	Read a chunk.
 */
static int
__win_file_read(WT_FILE_HANDLE *file_handle,
    WT_SESSION *wt_session, wt_off_t offset, size_t len, void *buf)
{
	DWORD chunk, nr, windows_error;
	OVERLAPPED overlapped = { 0 };
	WT_DECL_RET;
	WT_FILE_HANDLE_WIN *win_fh;
	WT_SESSION_IMPL *session;
	uint8_t *addr;

	win_fh = (WT_FILE_HANDLE_WIN *)file_handle;
	session = (WT_SESSION_IMPL *)wt_session;

	nr = 0;

	/* Assert direct I/O is aligned and a multiple of the alignment. */
	WT_ASSERT(session,
	    !win_fh->direct_io ||
	    S2C(session)->buffer_alignment == 0 ||
	    (!((uintptr_t)buf &
	    (uintptr_t)(S2C(session)->buffer_alignment - 1)) &&
	    len >= S2C(session)->buffer_alignment &&
	    len % S2C(session)->buffer_alignment == 0));

	/* Break reads larger than 1GB into 1GB chunks. */
	for (addr = buf; len > 0; addr += nr, len -= (size_t)nr, offset += nr) {
		chunk = (DWORD)WT_MIN(len, WT_GIGABYTE);
		overlapped.Offset = UINT32_MAX & offset;
		overlapped.OffsetHigh = UINT32_MAX & (offset >> 32);

		if (!ReadFile(
		    win_fh->filehandle, addr, chunk, &nr, &overlapped)) {
			windows_error = __wt_getlasterror();
			ret = __wt_map_windows_error(windows_error);
			__wt_err(session, ret,
			    "%s: handle-read: ReadFile: failed to read %lu "
			    "bytes at offset %" PRIuMAX ": %s",
			    file_handle->name, chunk, (uintmax_t)offset,
			    __wt_formatmessage(session, windows_error));
			return (ret);
		}
	}
	return (0);
}

/*
 * __win_file_size --
 *	Get the size of a file in bytes, by file handle.
 */
static int
__win_file_size(
    WT_FILE_HANDLE *file_handle, WT_SESSION *wt_session, wt_off_t *sizep)
{
	DWORD windows_error;
	LARGE_INTEGER size;
	WT_DECL_RET;
	WT_FILE_HANDLE_WIN *win_fh;
	WT_SESSION_IMPL *session;

	win_fh = (WT_FILE_HANDLE_WIN *)file_handle;
	session = (WT_SESSION_IMPL *)wt_session;

	if (GetFileSizeEx(win_fh->filehandle, &size) != 0) {
		*sizep = size.QuadPart;
		return (0);
	}

	windows_error = __wt_getlasterror();
	ret = __wt_map_windows_error(windows_error);
	__wt_err(session, ret,
	    "%s: handle-size: GetFileSizeEx: %s",
	    file_handle->name, __wt_formatmessage(session, windows_error));
	return (ret);
}

/*
 * __win_file_sync --
 *	MSVC fsync.
 */
static int
__win_file_sync(WT_FILE_HANDLE *file_handle, WT_SESSION *wt_session)
{
	DWORD windows_error;
	WT_DECL_RET;
	WT_FILE_HANDLE_WIN *win_fh;
	WT_SESSION_IMPL *session;

	win_fh = (WT_FILE_HANDLE_WIN *)file_handle;
	session = (WT_SESSION_IMPL *)wt_session;

	/*
	 * We don't open Windows system handles when opening directories
	 * for flushing, as it is not necessary (or possible) to flush
	 * a directory on Windows. Confirm the file handle is set before
	 * attempting to sync it.
	 */
	if (win_fh->filehandle == INVALID_HANDLE_VALUE)
		return (0);

	if (FlushFileBuffers(win_fh->filehandle) == FALSE) {
		windows_error = __wt_getlasterror();
		ret = __wt_map_windows_error(windows_error);
		__wt_err(session, ret,
		    "%s handle-sync: FlushFileBuffers: %s",
		    file_handle->name,
		    __wt_formatmessage(session, windows_error));
		return (ret);
	}
	return (0);
}

/*
 * __win_file_set_end --
 *	Truncate or extend a file.
 */
static int
__win_file_set_end(
    WT_FILE_HANDLE *file_handle, WT_SESSION *wt_session, wt_off_t len)
{
	DWORD windows_error;
	LARGE_INTEGER largeint;
	WT_DECL_RET;
	WT_FILE_HANDLE_WIN *win_fh;
	WT_SESSION_IMPL *session;

	win_fh = (WT_FILE_HANDLE_WIN *)file_handle;
	session = (WT_SESSION_IMPL *)wt_session;

	largeint.QuadPart = len;

	if (win_fh->filehandle_secondary == INVALID_HANDLE_VALUE)
		WT_RET_MSG(session, EINVAL,
		    "%s: handle-set-end: no secondary handle",
		    file_handle->name);

	if (SetFilePointerEx(win_fh->filehandle_secondary,
	    largeint, NULL, FILE_BEGIN) == FALSE) {
		windows_error = __wt_getlasterror();
		ret = __wt_map_windows_error(windows_error);
		__wt_err(session, ret,
		    "%s: handle-set-end: SetFilePointerEx: %s",
		    file_handle->name,
		    __wt_formatmessage(session, windows_error));
		return (ret);
	}

	if (SetEndOfFile(win_fh->filehandle_secondary) == FALSE) {
		if (GetLastError() == ERROR_USER_MAPPED_FILE)
			return (EBUSY);
		windows_error = __wt_getlasterror();
		ret = __wt_map_windows_error(windows_error);
		__wt_err(session, ret,
		    "%s: handle-set-end: SetEndOfFile: %s",
		    file_handle->name,
		    __wt_formatmessage(session, windows_error));
		return (ret);
	}
	return (0);
}

/*
 * __win_file_write --
 *	Write a chunk.
 */
static int
__win_file_write(WT_FILE_HANDLE *file_handle,
    WT_SESSION *wt_session, wt_off_t offset, size_t len, const void *buf)
{
	DWORD chunk, nw, windows_error;
	OVERLAPPED overlapped = { 0 };
	WT_DECL_RET;
	WT_FILE_HANDLE_WIN *win_fh;
	WT_SESSION_IMPL *session;
	const uint8_t *addr;

	win_fh = (WT_FILE_HANDLE_WIN *)file_handle;
	session = (WT_SESSION_IMPL *)wt_session;

	nw = 0;

	/* Assert direct I/O is aligned and a multiple of the alignment. */
	WT_ASSERT(session,
	    !win_fh->direct_io ||
	    S2C(session)->buffer_alignment == 0 ||
	    (!((uintptr_t)buf &
	    (uintptr_t)(S2C(session)->buffer_alignment - 1)) &&
	    len >= S2C(session)->buffer_alignment &&
	    len % S2C(session)->buffer_alignment == 0));

	/* Break writes larger than 1GB into 1GB chunks. */
	for (addr = buf; len > 0; addr += nw, len -= (size_t)nw, offset += nw) {
		chunk = (DWORD)WT_MIN(len, WT_GIGABYTE);
		overlapped.Offset = UINT32_MAX & offset;
		overlapped.OffsetHigh = UINT32_MAX & (offset >> 32);

		if (!WriteFile(
		    win_fh->filehandle, addr, chunk, &nw, &overlapped)) {
			windows_error = __wt_getlasterror();
			ret = __wt_map_windows_error(windows_error);
			__wt_err(session, ret,
			    "%s: handle-write: WriteFile: failed to write %lu "
			    "bytes at offset %" PRIuMAX ": %s",
			    file_handle->name, chunk, (uintmax_t)offset,
			    __wt_formatmessage(session, windows_error));
			return (ret);
		}
	}
	return (0);
}

/*
 * __win_open_file --
 *	Open a file handle.
 */
static int
__win_open_file(WT_FILE_SYSTEM *file_system, WT_SESSION *wt_session,
    const char *name, WT_FS_OPEN_FILE_TYPE file_type, uint32_t flags,
    WT_FILE_HANDLE **file_handlep)
{
	DWORD dwCreationDisposition, windows_error;
	WT_CONNECTION_IMPL *conn;
	WT_DECL_ITEM(name_wide);
	WT_DECL_RET;
	WT_FILE_HANDLE *file_handle;
	WT_FILE_HANDLE_WIN *win_fh;
	WT_SESSION_IMPL *session;
	int desired_access, f;

	WT_UNUSED(file_system);
	session = (WT_SESSION_IMPL *)wt_session;
	conn = S2C(session);
	*file_handlep = NULL;

	WT_RET(__wt_calloc_one(session, &win_fh));
	win_fh->direct_io = false;

	/* Set up error handling. */
	win_fh->filehandle =
	    win_fh->filehandle_secondary = INVALID_HANDLE_VALUE;

	WT_ERR(__wt_to_utf16_string(session, name, &name_wide));

	/*
	 * Opening a file handle on a directory is only to support filesystems
	 * that require a directory sync for durability, and Windows doesn't
	 * require that functionality: create an empty WT_FH structure with
	 * invalid handles.
	 */
	if (file_type == WT_FS_OPEN_FILE_TYPE_DIRECTORY)
		goto directory_open;

	desired_access = GENERIC_READ;
	if (!LF_ISSET(WT_FS_OPEN_READONLY))
		desired_access |= GENERIC_WRITE;

	/*
	 * Security:
	 * The application may spawn a new process, and we don't want another
	 * process to have access to our file handles.
	 *
	 * TODO: Set tighter file permissions but set bInheritHandle to false
	 * to prevent inheritance
	 */
	f = FILE_ATTRIBUTE_NORMAL;

	dwCreationDisposition = 0;
	if (LF_ISSET(WT_FS_OPEN_CREATE)) {
		dwCreationDisposition = CREATE_NEW;
		if (LF_ISSET(WT_FS_OPEN_EXCLUSIVE))
			dwCreationDisposition = CREATE_ALWAYS;
	} else
		dwCreationDisposition = OPEN_EXISTING;

	/* Direct I/O. */
	if (LF_ISSET(WT_FS_OPEN_DIRECTIO)) {
		f |= FILE_FLAG_NO_BUFFERING;
		win_fh->direct_io = true;
	}

	/* FILE_FLAG_WRITE_THROUGH does not require aligned buffers */
	if (FLD_ISSET(conn->write_through, file_type))
		f |= FILE_FLAG_WRITE_THROUGH;

	if (file_type == WT_FS_OPEN_FILE_TYPE_LOG &&
	    FLD_ISSET(conn->txn_logsync, WT_LOG_DSYNC))
		f |= FILE_FLAG_WRITE_THROUGH;

	/* If the user indicated a random workload, disable read-ahead. */
	if (file_type == WT_FS_OPEN_FILE_TYPE_DATA &&
	    LF_ISSET(WT_FS_OPEN_ACCESS_RAND))
		f |= FILE_FLAG_RANDOM_ACCESS;

	/* If the user indicated a sequential workload, set that. */
	if (file_type == WT_FS_OPEN_FILE_TYPE_DATA &&
	    LF_ISSET(WT_FS_OPEN_ACCESS_SEQ))
		f |= FILE_FLAG_SEQUENTIAL_SCAN;

	win_fh->filehandle = CreateFileW(name_wide->data, desired_access,
	    FILE_SHARE_READ | FILE_SHARE_WRITE,
	    NULL, dwCreationDisposition, f, NULL);
	if (win_fh->filehandle == INVALID_HANDLE_VALUE) {
		if (LF_ISSET(WT_FS_OPEN_CREATE) &&
		    GetLastError() == ERROR_FILE_EXISTS)
			win_fh->filehandle = CreateFileW(name_wide->data,
			    desired_access, FILE_SHARE_READ | FILE_SHARE_WRITE,
			    NULL, OPEN_EXISTING, f, NULL);
		if (win_fh->filehandle == INVALID_HANDLE_VALUE) {
			windows_error = __wt_getlasterror();
			ret = __wt_map_windows_error(windows_error);
			__wt_err(session, ret,
			    win_fh->direct_io ?
			    "%s: handle-open: CreateFileW: failed with direct "
			    "I/O configured, some filesystem types do not "
			    "support direct I/O: %s" :
			    "%s: handle-open: CreateFileW: %s",
			    name, __wt_formatmessage(session, windows_error));
			WT_ERR(ret);
		}
	}

	/*
	 * Open a second handle to file to support file extension/truncation
	 * concurrently with reads on the file. Writes would also move the
	 * file pointer.
	 */
	if (!LF_ISSET(WT_FS_OPEN_READONLY)) {
		win_fh->filehandle_secondary = CreateFileW(name_wide->data,
		    desired_access, FILE_SHARE_READ | FILE_SHARE_WRITE,
		    NULL, OPEN_EXISTING, f, NULL);
		if (win_fh->filehandle_secondary == INVALID_HANDLE_VALUE) {
			windows_error = __wt_getlasterror();
			ret = __wt_map_windows_error(windows_error);
			__wt_err(session, ret,
			    "%s: handle-open: Creatively: secondary: %s",
			    name, __wt_formatmessage(session, windows_error));
			WT_ERR(ret);
		}
	}

directory_open:
	/* Initialize public information. */
	file_handle = (WT_FILE_HANDLE *)win_fh;
	WT_ERR(__wt_strdup(session, name, &file_handle->name));

	file_handle->close = __win_file_close;
	file_handle->fh_lock = __win_file_lock;
#ifdef WORDS_BIGENDIAN
	/*
	 * The underlying objects are little-endian, mapping objects isn't
	 * currently supported on big-endian systems.
	 */
#else
	file_handle->fh_map = __wt_win_map;
	file_handle->fh_unmap = __wt_win_unmap;
#endif
	file_handle->fh_read = __win_file_read;
	file_handle->fh_size = __win_file_size;
	file_handle->fh_sync = __win_file_sync;

	/* Extend and truncate share the same implementation. */
	file_handle->fh_extend = __win_file_set_end;
	file_handle->fh_truncate = __win_file_set_end;

	file_handle->fh_write = __win_file_write;

	*file_handlep = file_handle;

	__wt_scr_free(session, &name_wide);
	return (0);

err:	__wt_scr_free(session, &name_wide);
	WT_TRET(__win_file_close((WT_FILE_HANDLE *)win_fh, wt_session));
	return (ret);
}

/*
 * __win_terminate --
 *	Discard a Windows configuration.
 */
static int
__win_terminate(WT_FILE_SYSTEM *file_system, WT_SESSION *wt_session)
{
	WT_SESSION_IMPL *session;

	session = (WT_SESSION_IMPL *)wt_session;

	__wt_free(session, file_system);
	return (0);
}

/*
 * __wt_os_win --
 *	Initialize a MSVC configuration.
 */
int
__wt_os_win(WT_SESSION_IMPL *session)
{
	WT_CONNECTION_IMPL *conn;
	WT_FILE_SYSTEM *file_system;

	conn = S2C(session);

	WT_RET(__wt_calloc_one(session, &file_system));

	/* Initialize the Windows jump table. */
	file_system->fs_directory_list = __wt_win_directory_list;
	file_system->fs_directory_list_single = __wt_win_directory_list_single;
	file_system->fs_directory_list_free = __wt_win_directory_list_free;
	file_system->fs_exist = __win_fs_exist;
	file_system->fs_open_file = __win_open_file;
	file_system->fs_remove = __win_fs_remove;
	file_system->fs_rename = __win_fs_rename;
	file_system->fs_size = __wt_win_fs_size;
	file_system->terminate = __win_terminate;

	/* Switch it into place. */
	conn->file_system = file_system;

	return (0);
}
