/* **********************************************************
 * Copyright (c) 2011-2014 Google, Inc.   All rights reserved.
 * Copyright (c) 2009-2010 Derek Bruening   All rights reserved.
 * **********************************************************/

/*
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * * Redistributions of source code must retain the above copyright notice,
 *   this list of conditions and the following disclaimer.
 *
 * * Redistributions in binary form must reproduce the above copyright notice,
 *   this list of conditions and the following disclaimer in the documentation
 *   and/or other materials provided with the distribution.
 *
 * * Neither the name of VMware, Inc. nor the names of its contributors may be
 *   used to endorse or promote products derived from this software without
 *   specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL VMWARE, INC. OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
 * DAMAGE.
 */

/* kernel32 and kernelbase redirection routines */

#include "kernel32_redir.h" /* must be included first */
#include "../../globals.h"
#include "drwinapi.h"
#include "drwinapi_private.h"

#ifndef WINDOWS
#    error Windows-only
#endif

/* We use a hashtale for faster lookups than a linear walk */
static strhash_table_t *kernel32_table;

static const redirect_import_t redirect_kernel32[] = {
    /* Process and thread-related routines */
    { "GetCurrentProcess", (app_pc)redirect_GetCurrentProcess },
    { "GetCurrentProcessId", (app_pc)redirect_GetCurrentProcessId },
    { "GetCurrentThread", (app_pc)redirect_GetCurrentThread },
    { "GetCurrentThreadId", (app_pc)redirect_GetCurrentThreadId },
    { "ExitProcess", (app_pc)redirect_ExitProcess },
    /* To avoid the FlsCallback being interpreted, and to avoid hangs
     * and other issues with Fls locks (i#875).
     */
    { "FlsAlloc", (app_pc)redirect_FlsAlloc },
    { "FlsFree", (app_pc)redirect_FlsFree },
    { "FlsGetValue", (app_pc)redirect_FlsGetValue },
    { "FlsSetValue", (app_pc)redirect_FlsSetValue },

    /* Library routines */
    /* As an initial interception of loader queries, but simpler than
     * intercepting Ldr*: plus, needed to intercept FlsAlloc called by msvcrt
     * init routine.
     * XXX i#235: redirect GetModuleHandle{ExA,ExW} as well
     */
    { "GetModuleHandleA", (app_pc)redirect_GetModuleHandleA },
    { "GetModuleHandleW", (app_pc)redirect_GetModuleHandleW },
    { "GetProcAddress", (app_pc)redirect_GetProcAddress },
    { "LoadLibraryA", (app_pc)redirect_LoadLibraryA },
    { "LoadLibraryW", (app_pc)redirect_LoadLibraryW },
    { "LoadLibraryExA", (app_pc)redirect_LoadLibraryExA },
    { "LoadLibraryExW", (app_pc)redirect_LoadLibraryExW },
    { "FreeLibrary", (app_pc)redirect_FreeLibrary },
    { "GetModuleFileNameA", (app_pc)redirect_GetModuleFileNameA },
    { "GetModuleFileNameW", (app_pc)redirect_GetModuleFileNameW },

    /* Memory-related routines */
    { "DecodePointer", (app_pc)redirect_DecodePointer },
    { "EncodePointer", (app_pc)redirect_EncodePointer },
    { "GetProcessHeap", (app_pc)redirect_GetProcessHeap },
    { "HeapAlloc", (app_pc)redirect_HeapAlloc },
    { "HeapCompact", (app_pc)redirect_HeapCompact },
    { "HeapCreate", (app_pc)redirect_HeapCreate },
    { "HeapDestroy", (app_pc)redirect_HeapDestroy },
    { "HeapFree", (app_pc)redirect_HeapFree },
    { "HeapReAlloc", (app_pc)redirect_HeapReAlloc },
    { "HeapSetInformation ", (app_pc)redirect_HeapSetInformation },
    { "HeapSize", (app_pc)redirect_HeapSize },
    { "HeapValidate", (app_pc)redirect_HeapValidate },
    { "HeapWalk", (app_pc)redirect_HeapWalk },
    { "IsBadReadPtr", (app_pc)redirect_IsBadReadPtr },
    { "LocalAlloc", (app_pc)redirect_LocalAlloc },
    { "LocalFree", (app_pc)redirect_LocalFree },
    { "LocalReAlloc", (app_pc)redirect_LocalReAlloc },
    { "LocalLock", (app_pc)redirect_LocalLock },
    { "LocalHandle", (app_pc)redirect_LocalHandle },
    { "LocalUnlock", (app_pc)redirect_LocalUnlock },
    { "LocalSize", (app_pc)redirect_LocalSize },
    { "LocalFlags", (app_pc)redirect_LocalFlags },
    { "ReadProcessMemory", (app_pc)redirect_ReadProcessMemory },
    { "VirtualAlloc", (app_pc)redirect_VirtualAlloc },
    { "VirtualFree", (app_pc)redirect_VirtualFree },
    { "VirtualProtect", (app_pc)redirect_VirtualProtect },
    { "VirtualQuery", (app_pc)redirect_VirtualQuery },
    { "VirtualQueryEx", (app_pc)redirect_VirtualQueryEx },

    /* File-related routines */
    { "CreateDirectoryA", (app_pc)redirect_CreateDirectoryA },
    { "CreateDirectoryW", (app_pc)redirect_CreateDirectoryW },
    { "RemoveDirectoryA", (app_pc)redirect_RemoveDirectoryA },
    { "RemoveDirectoryW", (app_pc)redirect_RemoveDirectoryW },
    { "GetCurrentDirectoryA", (app_pc)redirect_GetCurrentDirectoryA },
    { "GetCurrentDirectoryW", (app_pc)redirect_GetCurrentDirectoryW },
    { "SetCurrentDirectoryA", (app_pc)redirect_SetCurrentDirectoryA },
    { "SetCurrentDirectoryW", (app_pc)redirect_SetCurrentDirectoryW },
    { "CreateFileA", (app_pc)redirect_CreateFileA },
    { "CreateFileW", (app_pc)redirect_CreateFileW },
    { "DeleteFileA", (app_pc)redirect_DeleteFileA },
    { "DeleteFileW", (app_pc)redirect_DeleteFileW },
    { "ReadFile", (app_pc)redirect_ReadFile },
#if 0 /* FIXME i#1089: disabling until we have console support */
    {"WriteFile",                      (app_pc)redirect_WriteFile},
#endif
    { "CreateFileMappingA", (app_pc)redirect_CreateFileMappingA },
    { "CreateFileMappingW", (app_pc)redirect_CreateFileMappingW },
    { "MapViewOfFile", (app_pc)redirect_MapViewOfFile },
    { "MapViewOfFileEx", (app_pc)redirect_MapViewOfFileEx },
    { "UnmapViewOfFile", (app_pc)redirect_UnmapViewOfFile },
    { "FlushViewOfFile", (app_pc)redirect_FlushViewOfFile },
    { "CreatePipe", (app_pc)redirect_CreatePipe },
    { "DeviceIoControl", (app_pc)redirect_DeviceIoControl },
    { "CloseHandle", (app_pc)redirect_CloseHandle },
    { "DuplicateHandle", (app_pc)redirect_DuplicateHandle },
    { "FileTimeToLocalFileTime", (app_pc)redirect_FileTimeToLocalFileTime },
    { "LocalFileTimeToFileTime", (app_pc)redirect_LocalFileTimeToFileTime },
    { "FileTimeToSystemTime", (app_pc)redirect_FileTimeToSystemTime },
    { "SystemTimeToFileTime", (app_pc)redirect_SystemTimeToFileTime },
    { "GetSystemTimeAsFileTime", (app_pc)redirect_GetSystemTimeAsFileTime },
    { "GetFileTime", (app_pc)redirect_GetFileTime },
    { "SetFileTime", (app_pc)redirect_SetFileTime },
    { "FindClose", (app_pc)redirect_FindClose },
    { "FindFirstFileA", (app_pc)redirect_FindFirstFileA },
    { "FindFirstFileW", (app_pc)redirect_FindFirstFileW },
    { "FindNextFileA", (app_pc)redirect_FindNextFileA },
    { "FindNextFileW", (app_pc)redirect_FindNextFileW },
    { "FlushFileBuffers", (app_pc)redirect_FlushFileBuffers },
    { "GetDiskFreeSpaceA", (app_pc)redirect_GetDiskFreeSpaceA },
    { "GetDiskFreeSpaceW", (app_pc)redirect_GetDiskFreeSpaceW },
    { "GetDriveTypeA", (app_pc)redirect_GetDriveTypeA },
    { "GetDriveTypeW", (app_pc)redirect_GetDriveTypeW },
    { "GetFileAttributesA", (app_pc)redirect_GetFileAttributesA },
    { "GetFileAttributesW", (app_pc)redirect_GetFileAttributesW },
    { "GetFileInformationByHandle", (app_pc)redirect_GetFileInformationByHandle },
    { "GetFileSize", (app_pc)redirect_GetFileSize },
    { "GetFileType", (app_pc)redirect_GetFileType },
    /* skipped a few in alpha order, to focus on those invoked by dbghelp */
    { "GetStdHandle", (app_pc)redirect_GetStdHandle },

    /* Synchronization routines */
    { "InitializeCriticalSectionAndSpinCount",
      (app_pc)redirect_InitializeCriticalSectionAndSpinCount },
    { "InitializeCriticalSectionEx", (app_pc)redirect_InitializeCriticalSectionEx },
    { "DeleteCriticalSection", (app_pc)redirect_DeleteCriticalSection },
    { "EnterCriticalSection", (app_pc)redirect_EnterCriticalSection },
    { "LeaveCriticalSection", (app_pc)redirect_LeaveCriticalSection },
    { "InterlockedCompareExchange ", (app_pc)redirect_InterlockedCompareExchange },
    { "InterlockedDecrement", (app_pc)redirect_InterlockedDecrement },
    { "InterlockedExchange", (app_pc)redirect_InterlockedExchange },
    { "InterlockedIncrement", (app_pc)redirect_InterlockedIncrement },
    { "WaitForSingleObject", (app_pc)redirect_WaitForSingleObject },

    /* Miscellaneous routines */
    { "GetLastError", (app_pc)redirect_GetLastError },
    { "SetLastError", (app_pc)redirect_SetLastError },
};
#define REDIRECT_KERNEL32_NUM (sizeof(redirect_kernel32) / sizeof(redirect_kernel32[0]))

void
kernel32_redir_init(void)
{
    uint i;
    kernel32_table = strhash_hash_create(
        GLOBAL_DCONTEXT, hashtable_num_bits(REDIRECT_KERNEL32_NUM * 2),
        80 /* load factor: not perf-critical, plus static */,
        HASHTABLE_SHARED | HASHTABLE_PERSISTENT,
        NULL _IF_DEBUG("kernel32 redirection table"));
    TABLE_RWLOCK(kernel32_table, write, lock);
    for (i = 0; i < REDIRECT_KERNEL32_NUM; i++) {
        strhash_hash_add(GLOBAL_DCONTEXT, kernel32_table, redirect_kernel32[i].name,
                         (void *)redirect_kernel32[i].func);
    }
    TABLE_RWLOCK(kernel32_table, write, unlock);

    kernel32_redir_init_proc();
    kernel32_redir_init_mem();
    kernel32_redir_init_file();
}

void
kernel32_redir_exit(void)
{
    kernel32_redir_exit_file();
    kernel32_redir_exit_mem();
    kernel32_redir_exit_proc();

    strhash_hash_destroy(GLOBAL_DCONTEXT, kernel32_table);
}

void
kernel32_redir_onload(privmod_t *mod)
{
    if (!dynamo_initialized)
        SELF_UNPROTECT_DATASEC(DATASEC_RARELY_PROT);

    /* Rather than statically linking to real kernel32 we want to invoke
     * routines in the private kernel32 so we look them up here.
     */

    /* We give sub-modules a chance to remove entries from the table (i#1385) */
    TABLE_RWLOCK(kernel32_table, write, lock);
    kernel32_redir_onload_proc(mod, kernel32_table);
    kernel32_redir_onload_lib(mod);
    kernel32_redir_onload_file(mod);
    TABLE_RWLOCK(kernel32_table, write, unlock);

    if (!dynamo_initialized)
        SELF_PROTECT_DATASEC(DATASEC_RARELY_PROT);
}

/* We assume the caller has already ruled out kernel32 calling into kernelbase,
 * which we do not want to redirect.
 */
app_pc
kernel32_redir_lookup(const char *name)
{
    app_pc res;
    TABLE_RWLOCK(kernel32_table, read, lock);
    res = strhash_hash_lookup(GLOBAL_DCONTEXT, kernel32_table, name);
    TABLE_RWLOCK(kernel32_table, read, unlock);
    return res;
}