/* **********************************************************
 * Copyright (c) 2011-2022 Google, Inc.  All rights reserved.
 * Copyright (c) 2005-2010 VMware, Inc.  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.
 */

#include "share.h"
#include "drlibc.h"

#include <stdio.h>
#include <string.h>
#include <ctype.h>

#ifdef WINDOWS
#    include "config.h"
#    include "elm.h"
#    include "events.h"      /* for canary */
#    include "processes.h"   /* for canary */
#    include "../options.h"  /* for option checking */
#    include "ntdll_types.h" /* for NT_SUCCESS */

#    include <io.h>    /* for canary */
#    include <Fcntl.h> /* for canary */
#    include <aclapi.h>
#else
#    include <sys/stat.h>
#endif

#ifndef UNIT_TEST

#    ifdef WINDOWS

#        ifdef DEBUG

int debuglevel = DL_FATAL;
int abortlevel = DL_FATAL;

void
set_debuglevel(int level)
{
    debuglevel = level;
}

void
set_abortlevel(int level)
{
    abortlevel = level;
}

#            define CONFIG_MAX 8192

#            define HEADER_SNIPPET(defsfile)                  \
                "POLICY_VERSION=30000\n"                      \
                "BEGIN_BLOCK\n"                               \
                "GLOBAL\n"                                    \
                "DYNAMORIO_OPTIONS=\n"                        \
                "DYNAMORIO_RUNUNDER=1\n"                      \
                "DYNAMORIO_AUTOINJECT=\\lib\\dynamorio.dll\n" \
                "DYNAMORIO_HOT_PATCH_POLICIES=" defsfile "\n" \
                "DYNAMORIO_UNSUPPORTED=\n"                    \
                "END_BLOCK\n"

DWORD
load_test_config(const char *snippet, BOOL use_hotpatch_defs)
{
    char buf[CONFIG_MAX];
    _snprintf(buf, CONFIG_MAX, "%s%s",
              use_hotpatch_defs ? HEADER_SNIPPET("\\conf") : HEADER_SNIPPET(""), snippet);
    NULL_TERMINATE_BUFFER(buf);
    DO_ASSERT(strlen(buf) < CONFIG_MAX - 2);
    DO_DEBUG(DL_VERB, printf("importing %s\n", buf););
    CHECKED_OPERATION(policy_import(buf, FALSE, NULL, NULL));
    return ERROR_SUCCESS;
}

void
get_testdir(WCHAR *buf, UINT maxchars)
{
    WCHAR *filePart;
    WCHAR tmp[MAX_PATH];
    DWORD len;

    len = GetEnvironmentVariable(L"DYNAMORIO_WINDIR", tmp, maxchars);
    DO_ASSERT(len < maxchars);
    if (len == 0) {
        len = GetEnvironmentVariable(L_DYNAMORIO_VAR_HOME, tmp, maxchars);
        DO_ASSERT(len < maxchars);
        /* check for cygwin paths on windows */
        if (!file_exists(buf))
            len = 0;

        DO_DEBUG(DL_INFO, printf("ignoring invalid-looking DYNAMORIO_HOME=%S\n", tmp););
    }

    if (len == 0) {
        wcsncpy(tmp, L"..", MAX_PATH);
    }

    len = GetFullPathName(tmp, maxchars, buf, &filePart);

    DO_DEBUG(DL_INFO, printf("using drhome: %S\n", buf););

    DO_ASSERT(len != 0);

    return;
}

void
error_cb(unsigned int errcode, WCHAR *message)
{
    if (errcode || !errcode || message) {
        DO_ASSERT(0);
    }
}

extern BOOL do_once;

typedef struct evthelp__ {
    DWORD type;
    WCHAR *exename;
    ULONG pid;
    WCHAR *s3;
    WCHAR *s4;
    UINT maxchars;
    BOOL found;
} evthelp;

evthelp *cb_eh = NULL;
int last_record = -1;

void
check_event_cb(EVENTLOGRECORD *record)
{
    const WCHAR *strings;

    if (cb_eh->found)
        return;

    last_record = record->RecordNumber;

    if (record->EventID == cb_eh->type) {

        if (cb_eh->exename != NULL &&
            0 != wcscmp(get_event_exename(record), cb_eh->exename))
            return;

        if (cb_eh->pid != 0 && cb_eh->pid != get_event_pid(record))
            return;

        strings = get_message_strings(record);
        strings = next_message_string(strings);
        strings = next_message_string(strings);
        if (cb_eh->s3 != NULL) {
            cb_eh->s3[0] = L'\0';
            if (strings != NULL) {
                wcsncpy(cb_eh->s3, strings, cb_eh->maxchars);
                cb_eh->s3[cb_eh->maxchars - 1] = L'\0';
            }
        }
        if (cb_eh->s4 != NULL) {
            cb_eh->s4[0] = L'\0';
            strings = next_message_string(strings);
            if (strings != NULL) {
                wcsncpy(cb_eh->s4, strings, cb_eh->maxchars);
                cb_eh->s4[cb_eh->maxchars - 1] = L'\0';
            }
        }
        cb_eh->found = TRUE;
    }
}

void
reset_last_event()
{
    last_record = -1;
}

/* checks for events matching type, exename (if not null), and pid (if
 *  not 0). fills in s3 and s4 with 3rd and 4th message strings of
 *  match, if not null.
 * next search will start with event after matched event. */
BOOL
check_for_event(DWORD type, WCHAR *exename, ULONG pid, WCHAR *s3, WCHAR *s4,
                UINT maxchars)
{
    evthelp eh;

    eh.type = type;
    eh.exename = exename;
    eh.pid = pid;
    eh.s3 = s3;
    eh.s4 = s4;
    eh.maxchars = maxchars;
    eh.found = FALSE;

    cb_eh = &eh;

    /* backdoor */
    do_once = TRUE;

    CHECKED_OPERATION(
        start_eventlog_monitor(FALSE, NULL, check_event_cb, error_cb, last_record));
    DO_ASSERT(WAIT_OBJECT_0 ==
              WaitForSingleObject(get_eventlog_monitor_thread_handle(), 10000));
    stop_eventlog_monitor();

    return eh.found;
}

FILE *event_list_fp;

void
show_event_cb(unsigned int mID, unsigned int type, WCHAR *message, DWORD timestamp)
{
    /* fool the compiler */
    DO_ASSERT(type == 0 || type != 0 || timestamp == 0);
    fprintf(event_list_fp, " Event %d: %S\n", mID, message);
}

void
show_all_events(FILE *fp)
{
    DO_ASSERT(fp != NULL);

    event_list_fp = fp;
    /* backdoor */
    do_once = TRUE;
    CHECKED_OPERATION(
        start_eventlog_monitor(TRUE, show_event_cb, NULL, error_cb, (DWORD)-1));
    DO_ASSERT(WAIT_OBJECT_0 ==
              WaitForSingleObject(get_eventlog_monitor_thread_handle(), 10000));
    stop_eventlog_monitor();

    return;
}

#        endif /* _DEBUG */

void
wcstolower(WCHAR *str)
{
    UINT i;
    for (i = 0; i < wcslen(str); i++)
        str[i] = towlower(str[i]);
}

WCHAR *
get_exename_from_path(const WCHAR *path)
{
    WCHAR *name = wcsrchr(path, L'\\');
    if (name == NULL)
        name = (WCHAR *)path;
    else
        name += 1;
    return name;
}

DWORD
acquire_shutdown_privilege()
{
    HANDLE hToken = NULL;
    TOKEN_PRIVILEGES Priv;

    // get current thread token
    if (!OpenThreadToken(GetCurrentThread(), TOKEN_QUERY | TOKEN_ADJUST_PRIVILEGES, FALSE,
                         &hToken)) {
        // can't get thread token, try process token instead
        if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY | TOKEN_ADJUST_PRIVILEGES,
                              &hToken)) {
            return GetLastError();
        }
    }

    Priv.PrivilegeCount = 1;
    Priv.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
    LookupPrivilegeValue(NULL, SE_SHUTDOWN_NAME, &Priv.Privileges[0].Luid);
    // try to enable the privilege
    if (!AdjustTokenPrivileges(hToken, FALSE, &Priv, sizeof(Priv), NULL, 0))
        return GetLastError();

    return ERROR_SUCCESS;
}

/*
 * FIXME: shutdown reason. we should probably use this, BUT
 *  InitiateSystemShutdownEx is not included in VS6.0, so we'll have
 *  to dynamically link it in.
 *
 * from msdn:
 *  SHTDN_REASON_FLAG_PLANNED: The shutdown was planned. On Windows .NET
 *   Server, the system generates a state snapshot. For more information,
 *   see the help for Shutdown Event Tracker.
 *
 *  various combinations of major/minor flags are "recognized by the
 *   system"; the APPLICATION|RECONFIG is NOT one of these. However,
 *   "You can also define your own shutdown reasons and add them to the
 *    the registry." we should probably do this at installation, once
 *    we're happy we've got a good reason code.
 */
DWORD
reboot_system()
{
    DWORD res;

    res = acquire_shutdown_privilege();
    if (res != ERROR_SUCCESS)
        return res;

    /* do we need to harden this at all?
     * "If the the system is not ready to handle the request, the last
     *  error code is ERROR_NOT_READY. The application should wait a
     *  short while and retry the call."
     * also ERROR_MACHINE_LOCKED, ERROR_SHUTDOWN_IN_PROGRESS, etc. */

    res =
        InitiateSystemShutdown(NULL, L"A System Restart was requested.", 30, TRUE, TRUE);
    //                                SHTDN_REASON_MAJOR_APPLICATION |
    //                                SHTDN_REASON_MINOR_RECONFIG);

    return res;
}

#        define LAST_WCHAR(wstr) wstr[wcslen(wstr) - 1]

#    endif /* WINDOWS */

/* this sucks.
 * i can't believe this is best way to implement this in Win32...
 *  but i can't seem to find a better way.
 * msdn suggests using CreateFile() with CREATE_NEW or OPEN_EXISTING,
 *   and then checking error codes; but the problem there is that C:\\
 *   returns PATH_NOT_FOUND regardless. */
bool
file_exists(const TCHAR *fn)
{
#    ifdef WINDOWS
    WIN32_FIND_DATA fd;
    HANDLE search;

    DO_ASSERT(fn != NULL);

    search = FindFirstFile(fn, &fd);

    if (search == INVALID_HANDLE_VALUE) {

        /* special handling for e.g. C:\\ */
        if (LAST_WCHAR(fn) == L'\\' || LAST_WCHAR(fn) == L':') {
            WCHAR buf[MAX_PATH];
            _snwprintf(buf, MAX_PATH, L"%s%s*", fn,
                       LAST_WCHAR(fn) == L'\\' ? L"" : L"\\");
            NULL_TERMINATE_BUFFER(buf);
            search = FindFirstFile(buf, &fd);
            if (search != INVALID_HANDLE_VALUE) {
                FindClose(search);
                return TRUE;
            } else {
                DO_DEBUG(
                    DL_VERB,
                    printf("%S: even though we tried hard, %d\n", buf, GetLastError()););
            }
        }

        DO_DEBUG(DL_VERB,
                 printf("%S doesn't exist because of: %d\n", fn, GetLastError()););
        return FALSE;
    } else {
        FindClose(search);
        return TRUE;
    }
#    else
    struct stat64 st;
    /* Use the raw syscall to avoid glibc 2.33 deps (i#5474). */
    return dr_stat_syscall(fn, &st) == 0;
#    endif
}

#    ifdef WINDOWS
#        define MAX_COUNTER 999999

/* grokked from the core.
 * FIXME: shareme!
 * if NULL is passed for directory, then it is ignored and no directory
 *  check is done, and filename_base is assumed to be absolute.
 * TODO: make this a proactive check: make sure the file can be
 *  opened, eg, do a create/delete on the filename to be returned.
 */
BOOL
get_unique_filename(const WCHAR *directory, const WCHAR *filename_base,
                    const WCHAR *file_type, WCHAR *filename_buffer, UINT maxlen)
{
    UINT counter = 0;

    if (directory != NULL && !file_exists(directory))
        return FALSE;

    do {
        if (directory == NULL)
            _snwprintf(filename_buffer, maxlen, L"%s.%.8d%s", filename_base, counter,
                       file_type);
        else
            _snwprintf(filename_buffer, maxlen, L"%s\\%s.%.8d%s", directory,
                       filename_base, counter, file_type);
        filename_buffer[maxlen - 1] = L'\0';
    } while (file_exists(filename_buffer) && (++counter < MAX_COUNTER));

    return (counter < MAX_COUNTER);
}

DWORD
delete_file_on_boot(WCHAR *filename)
{
    DWORD res;
    BOOL success = MoveFileEx(filename, NULL, MOVEFILE_DELAY_UNTIL_REBOOT);

    /* reboot removal adds an entry to
     * HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session
     * Manager\PendingFileRenameOperations and smss.exe will delete the file on next boot
     */
    if (success)
        res = ERROR_SUCCESS;
    else
        res = GetLastError();
    return res;
}

DWORD
delete_file_rename_in_use(WCHAR *filename)
{
    DWORD res;
    BOOL success = DeleteFile(filename);

    if (success)
        return ERROR_SUCCESS;

    /* xref case 4512: if we leave a dll in a process after we're done
     *  using it, we won't be able to delete it; however, hopefully
     *  we can rename it so there won't be issues replacing it later. */
    res = GetLastError();
    if (res != ERROR_SUCCESS) {
        WCHAR tempname[MAX_PATH];
        if (get_unique_filename(NULL, filename, L".tmp", tempname, MAX_PATH)) {
            success = MoveFile(filename, tempname);
            if (success) {
                res = ERROR_SUCCESS;

                /* as best effort, we also schedule cleanup of the
                 * temporary file on next boot */
                delete_file_on_boot(tempname);
            } else
                res = GetLastError();
        }
    }

    return res;
}

#        ifndef PROTECTED_DACL_SECURITY_INFORMATION
#            define PROTECTED_DACL_SECURITY_INFORMATION (0x80000000L)
#        endif

/*
 * quick permissions xfer workaround for updating permissions
 *  on upgrade.
 */
DWORD
copy_file_permissions(WCHAR *filedst, WCHAR *filesrc)
{
    DWORD res = ERROR_SUCCESS;
    SECURITY_DESCRIPTOR *sd = NULL;
    ACL *dacl = NULL;
    res = GetNamedSecurityInfo(filesrc, SE_FILE_OBJECT, DACL_SECURITY_INFORMATION, NULL,
                               NULL, &dacl, NULL, &sd);

    if (res != ERROR_SUCCESS)
        return res;

    res = SetNamedSecurityInfo(filedst, SE_FILE_OBJECT,
                               DACL_SECURITY_INFORMATION |
                                   PROTECTED_DACL_SECURITY_INFORMATION,
                               NULL, NULL, dacl, NULL);

    LocalFree(sd);

    return res;
}

/* NOTE: for now we only consider the major/minor versions and
 *  platform id.
 *
 * the osinfo.szCSDVersion string contains service pack information,
 *  which could be used to distinguish e.g. XPSP2, 2K3SP1, if
 *  necessary.
 */

DWORD
get_platform(DWORD *platform)
{
    /* determine the OS version information */
    OSVERSIONINFOW osinfo;
    /* i#1418: GetVersionEx is just plain broken on win8.1+ so we use the Rtl version */
    typedef NTSTATUS(NTAPI * RtlGetVersion_t)(OSVERSIONINFOW * info);
    RtlGetVersion_t RtlGetVersion;
    NTSTATUS res = -1;
    HANDLE ntdll_handle = GetModuleHandle(_T("ntdll.dll"));
    /* i#1598: on any error or on unknown ver, best to assume it's a new ver
     * and will look most like the most recent known ver.  We'll still return error
     * return val below, but many callers don't check that (!).
     */
    *platform = PLATFORM_WIN_10;
    if (ntdll_handle == NULL)
        return GetLastError();
    RtlGetVersion =
        (RtlGetVersion_t)GetProcAddress((HMODULE)ntdll_handle, "RtlGetVersion");
    if (RtlGetVersion == NULL)
        return GetLastError();
    osinfo.dwOSVersionInfoSize = sizeof(osinfo);
    res = RtlGetVersion(&osinfo);
    if (NT_SUCCESS(res)) {

        DO_DEBUG(DL_VERB, WCHAR verbuf[MAX_PATH];
                 _snwprintf(verbuf, MAX_PATH, L"Major=%d, Minor=%d, Build=%d, SPinfo=%s",
                            osinfo.dwMajorVersion, osinfo.dwMinorVersion,
                            osinfo.dwBuildNumber, osinfo.szCSDVersion);
                 NULL_TERMINATE_BUFFER(verbuf); printf("%S\n", verbuf););

        if (osinfo.dwPlatformId != VER_PLATFORM_WIN32_NT)
            return ERROR_UNSUPPORTED_OS;

        if (osinfo.dwMajorVersion == 4) {
            if (osinfo.dwMinorVersion == 0) {
                *platform = PLATFORM_WIN_NT_4;
                return ERROR_SUCCESS;
            }
        } else if (osinfo.dwMajorVersion == 5) {
            if (osinfo.dwMinorVersion == 0) {
                *platform = PLATFORM_WIN_2000;
                return ERROR_SUCCESS;
            } else if (osinfo.dwMinorVersion == 1) {
                *platform = PLATFORM_WIN_XP;
                return ERROR_SUCCESS;
            } else if (osinfo.dwMinorVersion == 2) {
                *platform = PLATFORM_WIN_2003;
                return ERROR_SUCCESS;
            }
        } else if (osinfo.dwMajorVersion == 6) {
            if (osinfo.dwMinorVersion == 0) {
                *platform = PLATFORM_VISTA;
                return ERROR_SUCCESS;
            } else if (osinfo.dwMinorVersion == 1) {
                *platform = PLATFORM_WIN_7;
                return ERROR_SUCCESS;
            } else if (osinfo.dwMinorVersion == 2) {
                *platform = PLATFORM_WIN_8;
                return ERROR_SUCCESS;
            } else if (osinfo.dwMinorVersion == 3) {
                *platform = PLATFORM_WIN_8_1;
                return ERROR_SUCCESS;
            }
        } else if (osinfo.dwMajorVersion == 10) {
            if (osinfo.dwMinorVersion == 0) {
                if (GetProcAddress((HMODULE)ntdll_handle, "NtAllocateVirtualMemoryEx") !=
                    NULL)
                    *platform = PLATFORM_WIN_10_1803;
                else if (GetProcAddress((HMODULE)ntdll_handle, "NtCallEnclave") != NULL)
                    *platform = PLATFORM_WIN_10_1709;
                else if (GetProcAddress((HMODULE)ntdll_handle, "NtLoadHotPatch") != NULL)
                    *platform = PLATFORM_WIN_10_1703;
                else if (GetProcAddress((HMODULE)ntdll_handle,
                                        "NtCreateRegistryTransaction") != NULL)
                    *platform = PLATFORM_WIN_10_1607;
                else if (GetProcAddress((HMODULE)ntdll_handle, "NtCreateEnclave") != NULL)
                    *platform = PLATFORM_WIN_10_1511;
                else
                    *platform = PLATFORM_WIN_10;
                return ERROR_SUCCESS;
            }
        }
        return ERROR_UNSUPPORTED_OS;
    } else {
        return res;
    }
}

BOOL
is_wow64(HANDLE hProcess)
{
    /* IsWow64Pocess is only available on XP+ */
    typedef DWORD(WINAPI * IsWow64Process_Type)(HANDLE hProcess, PBOOL isWow64Process);
    static HANDLE kernel32_handle;
    static IsWow64Process_Type IsWow64Process;
    if (kernel32_handle == NULL)
        kernel32_handle = GetModuleHandle(L"kernel32.dll");
    if (IsWow64Process == NULL && kernel32_handle != NULL) {
        IsWow64Process =
            (IsWow64Process_Type)GetProcAddress(kernel32_handle, "IsWow64Process");
    }
    if (IsWow64Process == NULL) {
        /* should be NT or 2K */
        DO_DEBUG(DL_INFO, {
            DWORD platform = 0;
            get_platform(&platform);
            DO_ASSERT(platform == PLATFORM_WIN_NT_4 || platform == PLATFORM_WIN_2000);
        });
        return FALSE;
    } else {
        BOOL res;
        if (!IsWow64Process(hProcess, &res))
            return FALSE;
        return res;
    }
}

static const TCHAR *
get_dynamorio_home_helper(BOOL reset)
{
    static TCHAR dynamorio_home[MAXIMUM_PATH] = { 0 };
    int res;

    if (reset)
        dynamorio_home[0] = L'\0';

    if (dynamorio_home[0] != L'\0')
        return dynamorio_home;

    res = get_config_parameter(L_PRODUCT_NAME, FALSE, L_DYNAMORIO_VAR_HOME,
                               dynamorio_home, MAXIMUM_PATH);

    if (res == ERROR_SUCCESS && dynamorio_home[0] != L'\0')
        return dynamorio_home;
    else
        return NULL;
}

const TCHAR *
get_dynamorio_home()
{
    return get_dynamorio_home_helper(FALSE);
}

static const TCHAR *
get_dynamorio_logdir_helper(BOOL reset)
{
    static TCHAR dynamorio_logdir[MAXIMUM_PATH] = { 0 };
    DWORD res;

    if (reset)
        dynamorio_logdir[0] = L'\0';

    if (dynamorio_logdir[0] != L'\0')
        return dynamorio_logdir;

    res = get_config_parameter(L_PRODUCT_NAME, FALSE, L_DYNAMORIO_VAR_LOGDIR,
                               dynamorio_logdir, MAXIMUM_PATH);

    if (res == ERROR_SUCCESS && dynamorio_logdir[0] != L'\0')
        return dynamorio_logdir;
    else
        return NULL;
}

const TCHAR *
get_dynamorio_logdir()
{
    return get_dynamorio_logdir_helper(FALSE);
}

/* If a path is passed in, it is checked for 8.3 compatibility; else,
 * the default path is checked.  This routine does not check the
 * actual 8.3 reg key.
 */
BOOL
using_system32_for_preinject(const WCHAR *preinject)
{
    DWORD platform = 0;
    get_platform(&platform);
    if (platform == PLATFORM_WIN_NT_4) {
        return TRUE;
    } else {
        /* case 7586: we need to check if the system has disabled
         *  8.3 names; if so, we need to use the system32 for
         *  preinject (since spaces are not allowed in AppInitDLLs)
         */
        WCHAR short_path[MAX_PATH];
        WCHAR long_path[MAX_PATH];
        if (preinject == NULL) {
            /* note: with force_local_path == TRUE, we don't have
             *  to worry about get_preinject_path() calling this
             *  method back, and it will always return success.
             */
            get_preinject_path(short_path, MAX_PATH, TRUE, TRUE);
            wcsncat(short_path, L"\\" L_EXPAND_LEVEL(INJECT_DLL_8_3_NAME),
                    MAX_PATH - wcslen(short_path));
            NULL_TERMINATE_BUFFER(short_path);
            get_preinject_path(long_path, MAX_PATH, TRUE, FALSE);
            wcsncat(long_path, L"\\" L_EXPAND_LEVEL(INJECT_DLL_8_3_NAME),
                    MAX_PATH - wcslen(long_path));
            NULL_TERMINATE_BUFFER(long_path);
        } else {
            /* Check the passed-in file */
            GetShortPathName(preinject, short_path, BUFFER_SIZE_ELEMENTS(short_path));
            NULL_TERMINATE_BUFFER(short_path);
            wcsncpy(long_path, preinject, BUFFER_SIZE_ELEMENTS(long_path));
        }

        /* if 8.3 names are disabled, file_exists will return FALSE on
         * the GetShortPathName()'ed path.
         */
        return (file_exists(long_path) && !file_exists(short_path));
    }
}

/* if force_local_path, then this returns the in-installation
 *  path regardless of using_system32_for_preinject().
 * otherwise, this returns the path to the actuall DLL that
 *  will be injected, which depends on
 *  using_system32_for_preinject()
 * if short_path, calls GetShortPathName() on the path before returning it.
 *  for a canonical preinject path, this parameter should be TRUE.
 */
DWORD
get_preinject_path(WCHAR *buf, int nchars, BOOL force_local_path, BOOL short_path)
{
    if (!force_local_path && using_system32_for_preinject(NULL)) {
        UINT len;
        len = GetSystemDirectory(buf, MAX_PATH);

        if (len == 0)
            return GetLastError();
    } else {
        const WCHAR *home = get_dynamorio_home();
        /* using_system32_for_preinject() assumes we always succeed */
        _snwprintf(buf, nchars, L"%s\\lib", home == NULL ? L"" : home);
    }

    buf[nchars - 1] = L'\0';
    if (short_path)
        GetShortPathName(buf, buf, nchars);

    return ERROR_SUCCESS;
}

DWORD
get_preinject_name(WCHAR *buf, int nchars)
{
    DWORD res;

    if (using_system32_for_preinject(NULL)) {
        wcsncpy(buf, L_EXPAND_LEVEL(INJECT_DLL_NAME), nchars);
    } else {
        res = get_preinject_path(buf, nchars, FALSE, TRUE);
        if (res != ERROR_SUCCESS)
            return res;
        wcsncat(buf, L"\\" L_EXPAND_LEVEL(INJECT_DLL_8_3_NAME), nchars - wcslen(buf));
    }

    buf[nchars - 1] = L'\0';

    return ERROR_SUCCESS;
}

#    endif /* WINDOWS */

static dr_platform_t registry_view = DR_PLATFORM_DEFAULT;

void
set_dr_platform(dr_platform_t platform)
{
    registry_view = platform;
}

dr_platform_t
get_dr_platform()
{
    if (registry_view ==
        DR_PLATFORM_64BIT IF_X64(|| registry_view == DR_PLATFORM_DEFAULT))
        return DR_PLATFORM_64BIT;
    return DR_PLATFORM_32BIT;
}

#    ifdef WINDOWS

DWORD
platform_key_flags()
{
    /* PR 244206: have control over whether using WOW64 redirection or
     * raw 64-bit registry view.
     * These flags should be used for all Reg{Create,Open,Delete}KeyEx calls,
     * on XP+ (invalid on earlier platforms) on redirected keys
     * (most of HKLM\Software).
     * The flags don't matter on non-redirected trees like HKLM\System.
     * Since too many functions in libutil/ end up calling something
     * that reads/writes the registry, we don't pass the dr_platform_t
     * around and instead use a global variable.
     */
    DWORD platform = 0;
    get_platform(&platform);
    if (platform == PLATFORM_WIN_NT_4 || platform == PLATFORM_WIN_2000)
        return 0;
    else {
        switch (registry_view) {
        case DR_PLATFORM_DEFAULT: return 0;
        case DR_PLATFORM_32BIT: return KEY_WOW64_32KEY;
        case DR_PLATFORM_64BIT: return KEY_WOW64_64KEY;
        default: DO_ASSERT(false); return 0;
        }
    }
}

/* PR 244206: use this instead of RegDeleteKey for deleting redirected keys
 * (most of HKLM\Software)
 */
DWORD
delete_product_key(HKEY hkey, LPCWSTR subkey)
{
    /* RegDeleteKeyEx is only available on XP+.  We cannot delete
     * from 64-bit registry if we're WOW64 using RegDeleteKey, so we
     * dynamically look up RegDeleteKeyEx.
     * We could instead use NtDeleteKey and first open the subkey HKEY:
     * we could link with the core's ntdll.c, and also use is_wow64_process().
     */
    typedef DWORD(WINAPI * RegDeleteKeyExW_Type)(HKEY hKey, LPCWSTR lpSubKey,
                                                 REGSAM samDesired, DWORD Reserved);
    static HANDLE advapi32_handle;
    static RegDeleteKeyExW_Type RegDeleteKeyExW;
    if (advapi32_handle == NULL)
        advapi32_handle = GetModuleHandle(L"advapi32.dll");
    if (RegDeleteKeyExW == NULL && advapi32_handle != NULL) {
        RegDeleteKeyExW =
            (RegDeleteKeyExW_Type)GetProcAddress(advapi32_handle, "RegDeleteKeyExW");
    }
    if (RegDeleteKeyExW == NULL) {
        /* should be NT or 2K */
        DO_DEBUG(DL_INFO, {
            DWORD platform = 0;
            get_platform(&platform);
            DO_ASSERT(platform == PLATFORM_WIN_NT_4 || platform == PLATFORM_WIN_2000);
        });
        return RegDeleteKey(hkey, subkey);
    } else
        return RegDeleteKeyExW(hkey, subkey, platform_key_flags(), 0);
}

DWORD
create_root_key()
{
    int res;
    HKEY hkroot;

    res = RegCreateKeyEx(DYNAMORIO_REGISTRY_HIVE, L_DYNAMORIO_REGISTRY_KEY, 0, NULL,
                         REG_OPTION_NON_VOLATILE,
                         platform_key_flags() | KEY_WRITE | KEY_ENUMERATE_SUB_KEYS, NULL,
                         &hkroot, NULL);
    RegCloseKey(hkroot);

    return res;
}

/* Deletes the reg key created by create_root_key/setup_installation and the parent
 * company key if it's empty afterwards (might not be if PE or nodemgr has config subkeys
 * there. */
DWORD
destroy_root_key()
{
    DWORD res;
    /* This deletes just the product key. */
    res = recursive_delete_key(DYNAMORIO_REGISTRY_HIVE, L_DYNAMORIO_REGISTRY_KEY, NULL);
    /* Delete the company key (this will only work if it is empty, so no need to worry
     * about clobbering any config settings or doing too much damage if we screw up. */
    if (res == ERROR_SUCCESS) {
        WCHAR company_key[MAX_PATH];
        WCHAR *pop;
        wcsncpy(company_key, L_DYNAMORIO_REGISTRY_KEY, MAX_PATH);
        NULL_TERMINATE_BUFFER(company_key);
        pop = wcsstr(company_key, L_COMPANY_NAME);
        if (pop != NULL) {
            pop += wcslen(L_COMPANY_NAME);
            /* sanity check */
            if (pop == wcsrchr(company_key, L'\\')) {
                *pop = L'\0';
                delete_product_key(DYNAMORIO_REGISTRY_HIVE, company_key);
            } else
                res = ERROR_BAD_FORMAT;
        } else
            res = ERROR_BAD_FORMAT;
    }
    return res;
}

DWORD
setup_installation(const WCHAR *path, BOOL overwrite)
{
    WCHAR buf[MAX_PATH];

    /* if there's something there, leave it */
    if (!overwrite && get_dynamorio_home() != NULL)
        return ERROR_SUCCESS;

    DO_DEBUG(DL_INFO, printf("setting up installation at: %S\n", path););

    mkdir_with_parents(path);

    if (!file_exists(path))
        return ERROR_PATH_NOT_FOUND;

    _snwprintf(buf, MAX_PATH, L"%s\\%s", path, L"conf");
    NULL_TERMINATE_BUFFER(buf);

    DO_DEBUG(DL_INFO, printf("making config dir: %S\n", buf););

    mkdir_with_parents(buf);

    if (!file_exists(buf))
        return ERROR_PATH_NOT_FOUND;

    _snwprintf(buf, MAX_PATH, L"%s\\%s", path, L"logs");
    NULL_TERMINATE_BUFFER(buf);

    DO_DEBUG(DL_INFO, printf("making logdir: %S\n", buf););

    mkdir_with_parents(buf);

    if (!file_exists(buf))
        return ERROR_PATH_NOT_FOUND;

    CHECKED_OPERATION(create_root_key());

    CHECKED_OPERATION(
        set_config_parameter(L_PRODUCT_NAME, FALSE, L_DYNAMORIO_VAR_HOME, path));

    CHECKED_OPERATION(
        set_config_parameter(L_PRODUCT_NAME, FALSE, L_DYNAMORIO_VAR_LOGDIR, buf));

    /* reset the DR_HOME cache */
    get_dynamorio_home_helper(TRUE);

    return ERROR_SUCCESS;
}

/* modifies permissions for 4.3 cache/User-SID directories to be
 * created by users themselves
 */
DWORD
setup_cache_permissions(WCHAR *cacheRootDirectory)
{
    DWORD result = ERROR_UNSUPPORTED_OS;

#        define NUM_ACES 2
    /* in C const int isn't good enough */
    EXPLICIT_ACCESS ea[NUM_ACES];

    PSID pSIDEveryone = NULL;
    PSID pSIDCreatorOwner = NULL;
    PACL pACL = NULL;
    PACL pOldDACL = NULL;

    SID_IDENTIFIER_AUTHORITY SIDAuthWorld = SECURITY_WORLD_SID_AUTHORITY;
    SID_IDENTIFIER_AUTHORITY SIDAuthCreator = SECURITY_CREATOR_SID_AUTHORITY;
    DWORD dwRes;

    SECURITY_DESCRIPTOR *pSD = NULL;

    DWORD platform = 0; /* accomodating NT permissions */
    get_platform(&platform);

    /* Note that we prefer to not create ACLs from scratch, so that we
     * can accommodate Administrator groups unknown to us that would
     * have been inherited from \Program Files\.  We should always
     * start with a known ACL and just edit the new ACEs
     */

    dwRes = GetNamedSecurityInfo(cacheRootDirectory, SE_FILE_OBJECT,
                                 DACL_SECURITY_INFORMATION, NULL, NULL, &pOldDACL, NULL,
                                 &pSD);

    if (dwRes != ERROR_SUCCESS)
        return dwRes;

    /* Note: Although we are ADDING possibly existing ACE, it seems
     * like this is handled well and we don't grow the ACL.  For now
     * this doesn't matter to us, since we expect to have just copied
     * the flags from the lib\ directory so can't really accumulate.
     */

    // Create a SID for the Everyone group.
    if (!AllocateAndInitializeSid(&SIDAuthWorld, 1, SECURITY_WORLD_RID, 0, 0, 0, 0, 0, 0,
                                  0, &pSIDEveryone)) {
        DO_DEBUG(DL_VERB, printf("AllocateAndInitializeSid (Everyone).\n"););

        goto cleanup;
    }

    // Create a SID for the CREATOR OWNER group
    if (!AllocateAndInitializeSid(&SIDAuthCreator, 1, SECURITY_CREATOR_OWNER_RID, 0, 0, 0,
                                  0, 0, 0, 0, &pSIDCreatorOwner)) {
        DO_DEBUG(DL_VERB, printf("AllocateAndInitializeSid (CreatorOwner).\n"););

        goto cleanup;
    }

    ZeroMemory(&ea, NUM_ACES * sizeof(EXPLICIT_ACCESS));

    /* Grant create directory access to Everyone, which will be in
     * addition to existing Read/Execute permissions we are starting
     * with.
     */
    ea[0].grfAccessPermissions = FILE_ADD_SUBDIRECTORY;
    ea[0].grfAccessMode = GRANT_ACCESS;    /* not SET_ACCESS */
    ea[0].grfInheritance = NO_INHERITANCE; /* ONLY in cache\ folder! */
    ea[0].Trustee.TrusteeForm = TRUSTEE_IS_SID;
    ea[0].Trustee.TrusteeType = TRUSTEE_IS_WELL_KNOWN_GROUP;
    ea[0].Trustee.ptstrName = (LPTSTR)pSIDEveryone;

    /* Set full control for CREATOR OWNER on any subfolders */
    ea[1].grfAccessPermissions = GENERIC_ALL;
    ea[1].grfAccessMode = SET_ACCESS; /* we SET ALL */
    if (platform == PLATFORM_WIN_NT_4) {
        /* case 10502 INHERIT_ONLY_ACE seems to not work */
        /* we are mostly interested in any subdirectory, and cache/ is
         * already created (and also trusted), so adding it there
         * doesn't affect anything.
         */
        ea[1].grfInheritance = OBJECT_INHERIT_ACE | CONTAINER_INHERIT_ACE;
    } else {
        /* not using the same as NT, since Creator Owner may already
         * have this ACE (and normally does) so we'll clutter with a
         * new incomplete one */
        ea[1].grfInheritance =
            INHERIT_ONLY_ACE | OBJECT_INHERIT_ACE | CONTAINER_INHERIT_ACE;
    }
    ea[1].Trustee.TrusteeForm = TRUSTEE_IS_SID;
    ea[1].Trustee.TrusteeType = TRUSTEE_IS_WELL_KNOWN_GROUP;
    ea[1].Trustee.ptstrName = (LPTSTR)pSIDCreatorOwner;

    /* FIXME: we may want to disable the default group maybe should
     * set CREATOR GROUP to no access otherwise we get the default
     * Domain Users group (which usually is the Primary group) added,
     * e.g. KRAMMER\None:R(ead)
     */

    /* MSDN gave a false alarm that this doesn't exist on NT - It is
     * present at least on sp6.  FIXME: may want to use GetProcAddress
     * if we support earlier versions, but we'll know early enough.
     * We don't really need to support anything other than User SYSTEM
     * on NT for which we don't need this to work and can return
     * ERROR_UNSUPPORTED_OS
     */
    if (ERROR_SUCCESS !=
        SetEntriesInAcl(NUM_ACES, ea, pOldDACL, /* original DACL */
                        &pACL)) {
        DO_DEBUG(DL_VERB, printf("SetEntriesInAcl 0x%x\n", GetLastError()););
        goto cleanup;
    }

    // Try to modify the object's DACL.
    result = SetNamedSecurityInfo(
        cacheRootDirectory, // name of the object
        SE_FILE_OBJECT,     // type of object
        DACL_SECURITY_INFORMATION |
            PROTECTED_DACL_SECURITY_INFORMATION, // change only the object's DACL
        NULL, NULL,                              // do not change owner or group
        pACL,                                    // new DACL specified
        NULL);                                   // do not change SACL

    if (ERROR_SUCCESS == result) {
        DO_DEBUG(DL_VERB, printf("Successfully changed DACL\n"););
    }

cleanup:
    if (pSIDEveryone)
        FreeSid(pSIDEveryone);
    if (pSIDCreatorOwner)
        FreeSid(pSIDCreatorOwner);
    if (pACL)
        LocalFree(pACL);
    if (pSD)
        LocalFree(pSD);

    return result;
#        undef NUM_ACES
}

/* cache_root should normally be get_dynamorio_home() */
DWORD
setup_cache_shared_directories(const WCHAR *cache_root)
{
    DWORD res;

    /* support for new-in-4.2 directories, update the permissions
     * on the cache/ to be the same as those on lib/, and the
     * cache/shared/ folder to be the same as those on logs/
     *
     * note that the relative paths of the cache and shared cache
     * directories here should match the values set in
     * setup_cache_shared_registry()
     */
    WCHAR libpath[MAX_PATH];
    WCHAR cachepath[MAX_PATH];
    WCHAR logspath[MAX_PATH];
    WCHAR sharedcachepath[MAX_PATH];

    _snwprintf(libpath, MAX_PATH, L"%s\\lib", get_dynamorio_home());
    NULL_TERMINATE_BUFFER(libpath);
    _snwprintf(cachepath, MAX_PATH, L"%s\\cache", cache_root);
    NULL_TERMINATE_BUFFER(cachepath);
    _snwprintf(logspath, MAX_PATH, L"%s\\logs", get_dynamorio_home());
    NULL_TERMINATE_BUFFER(logspath);
    _snwprintf(sharedcachepath, MAX_PATH, L"%s\\shared", cachepath);
    NULL_TERMINATE_BUFFER(sharedcachepath);

    mkdir_with_parents(sharedcachepath);
    /* FIXME: no error checking */

    res = copy_file_permissions(cachepath, libpath);
    if (res != ERROR_SUCCESS) {
        return res;
    }

    res = copy_file_permissions(sharedcachepath, logspath);
    if (res != ERROR_SUCCESS) {
        return res;
    }

    /* For in 4.3 ONLY if all users (most importantly services)
     * validate their per-user directory (or files) for ownership
     */
    res = setup_cache_permissions(cachepath);
    if (res != ERROR_SUCCESS) {
        return res;
    }

    return ERROR_SUCCESS;
}

/* cache_root should normally be get_dynamorio_home() */
DWORD
setup_cache_shared_registry(const WCHAR *cache_root, ConfigGroup *policy)
{
    /* note that nodemgr doesn't need to do call this routine,
     * since the registry keys are added to the node policies in
     * controller/servlets/PolicyUpdateResponseHandler.java in the controller. but
     * anyway we expect these to be forever the same, and in any
     * case not configurable from the controller.
     */
    WCHAR wpathbuf[MAX_PATH];

    /* set up cache\ shared\ registry keys */
    _snwprintf(wpathbuf, MAX_PATH, L"%s\\cache", cache_root);
    NULL_TERMINATE_BUFFER(wpathbuf);
    set_config_group_parameter(policy, L_IF_WIN(DYNAMORIO_VAR_CACHE_ROOT), wpathbuf);

    /* set up cache\ shared\ registry keys */
    _snwprintf(wpathbuf, MAX_PATH, L"%s\\cache\\shared", cache_root);
    NULL_TERMINATE_BUFFER(wpathbuf);
    set_config_group_parameter(policy, L_IF_WIN(DYNAMORIO_VAR_CACHE_SHARED), wpathbuf);
    return ERROR_SUCCESS;
}

/* note that this checks the opstring against the
 *  version of core that matches this build, NOT the version
 *  of the core that's actually installed! */
BOOL
check_opstring(const WCHAR *opstring)
{
    char *cbuf;
    options_t ops;
    int res;
    size_t cbuf_size = wcslen(opstring) + 1;
    cbuf = (char *)malloc(cbuf_size);
    /* FIXME: if malloc fails, do something */
    _snprintf(cbuf, cbuf_size, "%S", opstring);
    cbuf[cbuf_size - 1] = '\0';
    res = set_dynamo_options(&ops, cbuf);
    free(cbuf);
    return !res;
}

HANDLE hToken = NULL;
TOKEN_PRIVILEGES Priv, OldPriv;
DWORD PrivSize = sizeof(OldPriv);

DWORD
acquire_privileges()
{
    DWORD error;

    /* if the privileges are already acquired, don't bother.
       this almost certainly will cause failures if multiple
        threads are trying to acquire privileges.
     */
    // FIXME - this should have real synchronization!!!
    if (hToken != NULL)
        return ERROR_ALREADY_INITIALIZED;

    // get current thread token
    if (!OpenThreadToken(GetCurrentThread(), TOKEN_QUERY | TOKEN_ADJUST_PRIVILEGES, FALSE,
                         &hToken)) {
        // can't get thread token, try process token instead
        if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY | TOKEN_ADJUST_PRIVILEGES,
                              &hToken)) {
            return GetLastError();
        }
    }

    Priv.PrivilegeCount = 1;
    Priv.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
    LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &Priv.Privileges[0].Luid);
    // try to enable the privilege
    if (!AdjustTokenPrivileges(hToken, FALSE, &Priv, sizeof(Priv), &OldPriv, &PrivSize)) {
        return GetLastError();
    }
    error = GetLastError();
    if (error == ERROR_NOT_ALL_ASSIGNED) {
        /* acquiring SeDebugPrivilege requires being admin */
        return error;
    }

    return ERROR_SUCCESS;
}

DWORD
release_privileges()
{
    if (hToken == NULL)
        return ERROR_NO_SUCH_PRIVILEGE;

    AdjustTokenPrivileges(hToken, FALSE, &OldPriv, sizeof(OldPriv), NULL, NULL);
    CloseHandle(hToken);
    hToken = NULL;

    return ERROR_SUCCESS;
}

void
wstr_replace(WCHAR *str, WCHAR orig, WCHAR new)
{
    UINT i;
    for (i = 0; i < wcslen(str); i++)
        if (str[i] == orig)
            str[i] = new;
    return;
}

/* FIXME: should return error code if the directory wasn't created and
 * doesn't exist already
 */
void
mkdir_with_parents(const WCHAR *dirname)
{
    WCHAR buf[MAX_PATH], *temp_subdir;

    wcsncpy(buf, dirname, MAX_PATH);
    NULL_TERMINATE_BUFFER(buf);

    /* ensure proper slashes */
    wstr_replace(buf, L'/', L'\\');

    temp_subdir = buf;

    while (temp_subdir != NULL) {
        temp_subdir = wcschr(temp_subdir, L'\\');
        if (temp_subdir != NULL)
            *temp_subdir = L'\0';

        DO_DEBUG(DL_VERB, printf("trying to make: %S\n", buf););

        /* ok if this fails, eg the first time it will be C: */
        CreateDirectory(buf, NULL);

        if (temp_subdir != NULL) {
            *temp_subdir = L'\\';
            temp_subdir = temp_subdir + 1;
        }
    }
    return;
}

void
ensure_directory_exists_for_file(WCHAR *filename)
{
    WCHAR *slashptr, buf[MAX_PATH];
    wcsncpy(buf, filename, MAX_PATH);
    NULL_TERMINATE_BUFFER(buf);

    slashptr = wcsrchr(buf, L'\\');
    if (slashptr == NULL)
        return;

    *slashptr = L'\0';

    mkdir_with_parents(buf);
}

/* FIXME: apparently there's a bug in MSVCRT that converts
 *  \r\n to \r\r\n ? anyway that's what google and the evidence
 *  seem to indicate. (see policy.c for more)
 *
 * so we may want to convert this to using Win32 API instead of
 *  CRT. but then again we may not, just on principle. */
DWORD
write_file_contents(WCHAR *path, char *contents, BOOL overwrite)
{
    FILE *fp = NULL;
    DWORD res = ERROR_SUCCESS;

    ensure_directory_exists_for_file(path);

    fp = _wfopen(path, L"r");
    if (fp != NULL) {
        if (!overwrite)
            return ERROR_ALREADY_EXISTS;
        fclose(fp);
    }

    fp = _wfopen(path, L"w");
    if (fp == NULL) {
        res = delete_file_rename_in_use(path);
        if (res != ERROR_SUCCESS || (fp = _wfopen(path, L"w")) == NULL) {
            DO_DEBUG(DL_ERROR, printf("Unable to open file: %S (%d)\n", path, errno););
            return res;
        }
    }

    if (strlen(contents) != fwrite(contents, 1, strlen(contents), fp)) {
        DO_DEBUG(DL_ERROR, printf("Write failed to file: %S (errno=%d)\n", path, errno););
        res = ERROR_WRITE_FAULT;
    }

    DO_DEBUG(DL_INFO, printf("wrote file %S\n", path););

    fclose(fp);
    return res;
}

DWORD
write_file_contents_if_different(WCHAR *path, char *contents, BOOL *changed)
{
    char *existing;
    DWORD res;

    DO_ASSERT(path != NULL);
    DO_ASSERT(contents != NULL);
    DO_ASSERT(changed != NULL);

    existing = (char *)malloc(strlen(contents) + 1);
    res = read_file_contents(path, existing, strlen(contents) + 1, NULL);

    if (res == ERROR_SUCCESS && 0 == strcmp(contents, existing)) {
        *changed = FALSE;
        res = ERROR_SUCCESS;
    } else {
        *changed = TRUE;
        res = write_file_contents(path, contents, TRUE);
    }

    free(existing);

    return res;
}

#        define READ_BUF_SZ 1024

DWORD
read_file_contents(WCHAR *path, char *contents, SIZE_T maxchars, SIZE_T *needed)
{
    FILE *fp = NULL;
    DWORD res = ERROR_SUCCESS;
    SIZE_T n_read = 0;
    SIZE_T n_needed = 0;
    char buf[READ_BUF_SZ];

    DO_ASSERT(path != NULL);
    DO_ASSERT(contents != NULL || needed != NULL);
    DO_ASSERT(contents == NULL || maxchars > 0);

    fp = _wfopen(path, L"r");
    if (fp == NULL) {
        DO_DEBUG(DL_INFO, printf("Not found: %S\n", path););
        return ERROR_FILE_NOT_FOUND;
    }

    if (contents != NULL) {
        n_read = fread(contents, 1, maxchars, fp);

        /* NULL terminate string. */
        contents[n_read == maxchars ? n_read - 1 : n_read] = '\0';

        DO_DEBUG(DL_FINEST,
                 printf("*Read %zu bytes from %S (max=%zu)\n", n_read, path, maxchars););
    }

    n_needed = n_read;

    while (!feof(fp)) {
        res = ERROR_MORE_DATA;

        n_read = fread(buf, 1, READ_BUF_SZ, fp);

        DO_DEBUG(DL_FINEST, printf("  Read an additional %zu bytes\n", n_read););

        if (n_read == 0 && !feof(fp)) {
            res = ERROR_READ_FAULT;
            break;
        }

        n_needed += n_read;
    }

    /* + 1 for the NULL terminator */
    n_needed += 1;

    if (needed != NULL)
        *needed = n_needed;

    fclose(fp);

    if (res == ERROR_SUCCESS || res == ERROR_MORE_DATA) {
        DO_DEBUG(
            DL_VERB,
            printf("file %S contents: (%zu needed)\n\n%s\n", path, n_needed, contents););
    } else {
        DO_DEBUG(DL_ERROR, printf("read failed, error %d\n", res););
    }

    return res;
}

DWORD
delete_tree(const WCHAR *path)
{
    WIN32_FIND_DATA data;
    HANDLE hFind;
    WCHAR pathbuf[MAX_PATH], subdirbuf[MAX_PATH];

    if (path == NULL)
        return ERROR_INVALID_PARAMETER;

    _snwprintf(pathbuf, MAX_PATH, L"%s\\*.*", path);
    NULL_TERMINATE_BUFFER(pathbuf);

    hFind = FindFirstFile(pathbuf, &data);
    if (hFind == INVALID_HANDLE_VALUE)
        return GetLastError();

    DO_DEBUG(DL_VERB, printf("dt working on %S\n", path););
    do {
        if (wcscmp(data.cFileName, L".") == 0 || wcscmp(data.cFileName, L"..") == 0)
            continue;

        /* case 7407: FindNextFile on a FAT32 filesystem returns files in
         * the order they were written to disk, which could be different
         * from NTFS where the order is alphabetical (from MSDN).
         * Also on FAT32, FindNextFile sometimes puts us back in the loop
         * for the file we just renamed and we try to
         * delete_file_rename_in_use the file we just renamed for a very
         * long time (>3 hrs).
         *
         * FIXME: temporary hack:  if filename has .tmp in its name
         * (first ocurrance), assume we just renamed it and skip.
         *
         * note we may want to doublecheck that the file is indeed not
         * deletable although we now add it to the
         * PendingFileRenameOperations so such unused files can't stay
         * around for too long.
         */
        if (wcsstr(data.cFileName, L".tmp") != NULL)
            continue;

        DO_DEBUG(DL_VERB,
                 printf("dt still working on %S, %d\n", data.cFileName,
                        data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY););

        _snwprintf(subdirbuf, MAX_PATH, L"%s\\%s", path, data.cFileName);
        NULL_TERMINATE_BUFFER(subdirbuf);

        /* case 4512: use rename trick if file is in use, so that
         *  the uninstall/reinstall case will work */
        if (data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
            delete_tree(subdirbuf);
        else
            delete_file_rename_in_use(subdirbuf);
    } while (FindNextFile(hFind, &data));

    if (!FindClose(hFind))
        return GetLastError();

    if (!RemoveDirectory(path))
        return GetLastError();

    return ERROR_SUCCESS;
}

/*
 * helper function for registry permissions workaround. stopgap until
 *  we can make a decent permissions api.
 *
 * based on example code obtained from:
 *  http://www.codeproject.com/system/secntobj.asp
 */

PSID
getSID(WCHAR *user)
{
    DWORD dwSidLen = 0, dwDomainLen = 0;
    SID_NAME_USE SidNameUse;
    PSID pRet = NULL;
    PSID pSid = NULL;
    WCHAR *lpDomainName = NULL;

    /* The function on the first call retives the length that we need
     * to initialize the SID & domain name pointers */
    if (!LookupAccountName(NULL, user, NULL, &dwSidLen, NULL, &dwDomainLen,
                           &SidNameUse)) {
        if (ERROR_INSUFFICIENT_BUFFER == GetLastError()) {

            pSid = LocalAlloc(LMEM_ZEROINIT, dwSidLen);
            lpDomainName = LocalAlloc(LMEM_ZEROINIT, dwDomainLen * sizeof(WCHAR));

            if (pSid && lpDomainName &&
                LookupAccountName(NULL, user, pSid, &dwSidLen, lpDomainName, &dwDomainLen,
                                  &SidNameUse)) {
                pRet = pSid;
                pSid = NULL;
            }
        }
    }

    /* if successful, was set to NULL and left in pRet */
    LocalFree(pSid);
    LocalFree(lpDomainName);

    return pRet;
}

BOOL
make_acl(DWORD count, WCHAR **userArray, DWORD *maskArray, ACL **acl)
{
    DWORD dwLoop = 0;
    DWORD dwAclLen = 0;
    PACL pRetAcl = NULL;
    PSID *ppStoreSid = NULL;
    BOOL bRes = FALSE;

    if (acl == NULL)
        goto cleanup;

    ppStoreSid = LocalAlloc(LMEM_ZEROINIT, count * sizeof(void *));

    if (ppStoreSid == NULL)
        goto cleanup;

    for (dwLoop = 0; dwLoop < count; dwLoop++) {

        ppStoreSid[dwLoop] = getSID(userArray[dwLoop]);

        if (ppStoreSid[dwLoop] == NULL)
            goto cleanup;

        dwAclLen +=
            GetLengthSid(ppStoreSid[dwLoop]) + sizeof(ACCESS_ALLOWED_ACE) - sizeof(DWORD);
    }

    dwAclLen += sizeof(ACL);
    pRetAcl = LocalAlloc(LMEM_ZEROINIT, dwAclLen);

    if (pRetAcl == NULL || !InitializeAcl(pRetAcl, dwAclLen, ACL_REVISION))
        goto cleanup;

    for (dwLoop = 0; dwLoop < count; dwLoop++) {
        /* only adding access allowed ACE's */
        if (!AddAccessAllowedAce(pRetAcl, ACL_REVISION, maskArray[dwLoop],
                                 ppStoreSid[dwLoop]))
            goto cleanup;
    }

    *acl = pRetAcl;
    pRetAcl = NULL;
    bRes = TRUE;

cleanup:

    if (ppStoreSid != NULL) {
        for (dwLoop = 0; dwLoop < count; dwLoop++)
            LocalFree(ppStoreSid[dwLoop]);
        LocalFree(ppStoreSid);
    }

    /* if successful, was set to NULL and left in *acl */
    LocalFree(pRetAcl);

    return bRes;
}

#        define NUM_ACL_ENTRIES 4

DWORD
set_registry_permissions_for_user(WCHAR *hklm_keyname, WCHAR *user)
{
    SECURITY_DESCRIPTOR sd;
    SID *owner = NULL;
    ACL *acl1 = NULL;
    DWORD res;
    HKEY hkey = NULL;

    WCHAR *users[NUM_ACL_ENTRIES] = {
        L"Administrators",
        L"Everyone",
        L"SYSTEM",
        NULL,
    };

    DWORD masks[NUM_ACL_ENTRIES] = {
        KEY_ALL_ACCESS | DELETE | READ_CONTROL | WRITE_DAC | WRITE_OWNER,
        KEY_READ,
        KEY_ALL_ACCESS | DELETE | READ_CONTROL | WRITE_DAC | WRITE_OWNER,
        KEY_ALL_ACCESS,
    };

    users[NUM_ACL_ENTRIES - 1] = user;

    DO_DEBUG(DL_VERB, printf("Starting acl..\n"););

    res = RegOpenKeyEx(HKEY_LOCAL_MACHINE, hklm_keyname, 0,
                       platform_key_flags() | KEY_ALL_ACCESS, &hkey);

    if (res != ERROR_SUCCESS)
        goto error_out;

    DO_DEBUG(DL_VERB, printf("Got key handle.\n"););

    if (!InitializeSecurityDescriptor(&sd, SECURITY_DESCRIPTOR_REVISION)) {
        res = GetLastError();
        goto error_out;
    }

    owner = getSID(users[0]);

    if (NULL == owner) {
        res = ERROR_INVALID_DATA;
        goto error_out;
    }

    if (!SetSecurityDescriptorOwner(&sd, owner, FALSE)) {
        res = GetLastError();
        goto error_out;
    }

    DO_DEBUG(DL_VERB, printf("Set owner.\n"););

    if (!make_acl(NUM_ACL_ENTRIES, users, masks, &acl1)) {
        res = ERROR_ACCESS_DENIED;
        goto error_out;
    }

    DO_DEBUG(DL_VERB, printf("Made ACL.\n"););

    if (!SetSecurityDescriptorDacl(&sd, TRUE, acl1, FALSE)) {
        res = GetLastError();
        goto error_out;
    }

    if (!IsValidSecurityDescriptor(&sd)) {
        res = GetLastError();
        goto error_out;
    }

    res = RegSetKeySecurity(hkey, DACL_SECURITY_INFORMATION | OWNER_SECURITY_INFORMATION,
                            &sd);

    DO_DEBUG(DL_VERB, printf("Set sacl.\n"););

    goto cleanup;

error_out:

    /* make sure to return an error */
    if (res == ERROR_SUCCESS)
        res = ERROR_ACCESS_DENIED;

cleanup:

    if (hkey != NULL)
        RegCloseKey(hkey);

    LocalFree(owner);
    LocalFree(acl1);

    return res;
}

/* will limit to 1 MB */
#        define MAX_INSERT_SIZE (1024 * 1024)

static void
insert_file(FILE *file, wchar_t *file_src_name, BOOL delete)
{
    /* 3rd arg not needed but older headers do not declare as optional */
    int fd_src = _wopen(file_src_name, _O_RDONLY | _O_BINARY, 0);
    long length;
    int error;

    if (fd_src == -1) {
        fprintf(file, "Unable to open file \"%S\" for inserting\n", file_src_name);
        return;
    }
    length = _filelength(fd_src);
    if (length == -1L) {
        fprintf(file, "Unable to get file length for file \"%S\"\n", file_src_name);
        return;
    }
    if (length > MAX_INSERT_SIZE) {
        fprintf(file, "File size exceeds max insert length, truncating from %d to %d\n",
                length, MAX_INSERT_SIZE);
        length = MAX_INSERT_SIZE;
    }

    fprintf(file, "Inserting file: name=\"%S\" length=%d\n", file_src_name, length);
    /* hmm, there's prob. a better way to do this ... */
#        define COPY_BUF_SIZE 4096
    {
        char buf[COPY_BUF_SIZE] = { 0 };
        long i = 0;
        while (i + COPY_BUF_SIZE <= length) {
            _read(fd_src, buf, COPY_BUF_SIZE);
            fwrite(buf, 1, COPY_BUF_SIZE, file);
            i += COPY_BUF_SIZE;
        }
        if (i < length) {
            _read(fd_src, buf, length - i);
            fwrite(buf, 1, length - i, file);
        }
    }

    fprintf(file, "Finished inserting file\n");
    if (_read(fd_src, &error, sizeof(error)) != 0)
        fprintf(file, "ERROR : file continues beyond length\n");
    _close(fd_src);
    if (delete) {
        DeleteFile(file_src_name);
    }
    return;
}

/* see utils.h for description */
DWORD
get_violation_info(EVENTLOGRECORD *pevlr, /* INOUT */ VIOLATION_INFO *info)
{
    DO_ASSERT(pevlr != NULL && info != NULL && pevlr->EventID == MSG_SEC_FORENSICS);
    info->report = NULL;
    if (pevlr->EventID != MSG_SEC_FORENSICS)
        return ERROR_INVALID_PARAMETER;
    info->report = get_forensics_filename(pevlr);
    if (file_exists(info->report))
        return ERROR_SUCCESS;
    else
        return ERROR_FILE_NOT_FOUND;
}

wchar_t *canary_process_names[] = { L"canary.exe", L"services.exe", L"iexplore.exe" };
#        define num_canary_processes BUFFER_SIZE_ELEMENTS(canary_process_names)
/* how long to wait for an apparently hung canary process */
#        define CANARY_HANG_WAIT 20000
/* interval to wait for the canary process to do something */
#        define CANARY_SLEEP_WAIT 100

#        define OPTIONS_CANARY_NATIVE \
            L" -list_modules -check_for_hooked_mods_list ntdll.dll"
#        define OPTIONS_CANARY_THIN_CLIENT L""
#        define OPTIONS_CANARY_CLIENT L""
#        define OPTIONS_CANARY_MF L""
#        define OPTIONS_CANARY_INJECT L"-wait"

/* FIXME - could even get ldmps ... */
/* FIXME - xref case 10322 on -syslog_mask 0, eventually should remove and verify
 * expected eventlog output (and get PE to ignore them). */
#        define OPTIONS_THIN_CLIENT L"-thin_client -syslog_mask 0"
#        define OPTIONS_CLIENT L"-client -syslog_mask 0"
/* FIXME - temporary hack so virus scan correctly identified by canary. Weird case
 * since this is considered a survivable violation by default (and so ignores kill
 * proc).
 */
#        define OPTIONS_MF L"-apc_policy 0 -syslog_mask 0"

/* returns the appropriate canary fail code */
static int
run_individual_canary_test(FILE *file, WCHAR *logbase, WCHAR *dr_options, int exe_index,
                           ConfigGroup *policy, WCHAR *exe, WCHAR *exe_args,
                           BOOL inject_test, char *type, BOOL early_test)
{
    STARTUPINFO sinfo = { sizeof(sinfo), NULL, L"",  0,   0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
                          NULL,          NULL, NULL, NULL };
    PROCESS_INFORMATION pinfo;
    int canary_code = CANARY_SUCCESS;
    WCHAR logbuf[MAX_PATH] = { 0 };
    WCHAR outfile[MAX_PATH];
    WCHAR cmd_buf[5 * MAX_PATH];

    /* set up registry */
    get_unique_filename(logbase, L"canary_logs", L"", logbuf,
                        BUFFER_SIZE_ELEMENTS(logbuf));
    CreateDirectory(logbuf, NULL);
    set_config_group_parameter(get_child(canary_process_names[exe_index], policy),
                               L_DYNAMORIO_VAR_LOGDIR, logbuf);
    set_config_group_parameter(get_child(canary_process_names[exe_index], policy),
                               L_DYNAMORIO_VAR_OPTIONS, dr_options);
    write_config_group(policy);

    /* set up cmd_buf */
    _snwprintf(outfile, BUFFER_SIZE_ELEMENTS(outfile), L"%s\\out.rep", logbuf);
    NULL_TERMINATE_BUFFER(outfile);
    if (early_test) {
        /* we get the canary_process to re-launch itself to run test with early inject */
        _snwprintf(cmd_buf, BUFFER_SIZE_ELEMENTS(cmd_buf),
                   L"\"%s\" \"%s\" -launch_child %s%d \"\\\"%s\\\" %s\"", exe, outfile,
                   inject_test ? L"-verify_inject " : L"", CANARY_HANG_WAIT / 2, outfile,
                   exe_args);
    } else {
        _snwprintf(cmd_buf, BUFFER_SIZE_ELEMENTS(cmd_buf), L"\"%s\" \"%s\" %s", exe,
                   outfile, exe_args);
    }
    NULL_TERMINATE_BUFFER(cmd_buf);

    fprintf(file, "Starting Canary Process \"%S\" core_ops=\"%S\" type=%s%s\n", cmd_buf,
            dr_options, type, inject_test ? " inject" : "");
    if (CreateProcess(NULL, cmd_buf, NULL, NULL, TRUE, 0, NULL, NULL, &sinfo, &pinfo)) {
        if (inject_test && !early_test) {
            DWORD sleep_count = 0, under_dr_code, ws, build = 0;
            do {
                ws = WaitForSingleObject(pinfo.hProcess, CANARY_SLEEP_WAIT);
                sleep_count += CANARY_SLEEP_WAIT;
                under_dr_code = under_dynamorio_ex(pinfo.dwProcessId, &build);
            } while (ws == WAIT_TIMEOUT && sleep_count < CANARY_HANG_WAIT &&
                     (under_dr_code == DLL_UNKNOWN || under_dr_code == DLL_NONE));
            if (under_dr_code == DLL_UNKNOWN || under_dr_code == DLL_NONE) {
                canary_code = CANARY_FAIL_APP_INIT_INJECTION;
                fprintf(file, "Injection Failed - verify registry settings\n");
            } else {
                fprintf(file, "Verified Injection, build %d\n", build);
            }
            if (ws == WAIT_TIMEOUT)
                terminate_process(pinfo.dwProcessId);
        } else {
            DWORD ws = WaitForSingleObject(pinfo.hProcess, CANARY_HANG_WAIT);
            if (ws == WAIT_TIMEOUT) {
                if (early_test && inject_test) {
                    canary_code = CANARY_FAIL_EARLY_INJECTION;
                    fprintf(file, "Early Injection Failed\n");
                } else {
                    canary_code = CANARY_FAIL_HUNG;
                    fprintf(file, "Canary Hung\n");
                }
                terminate_process(pinfo.dwProcessId);
            } else {
                DWORD exit_code = 0;
                GetExitCodeProcess(pinfo.hProcess, &exit_code);
                /* FIXME - check return value, shouldn't ever fail though */
                if (exit_code != CANARY_PROCESS_EXP_EXIT_CODE) {
                    /* FIXME - the -1 is based on the core value for kill
                     * proc, should export that and use it, or really just check for
                     * violations since we'll want the forensics anyways. Doesn't
                     * disambiguate between dr error and violation. */
                    if (exit_code == (DWORD)-1) {
                        canary_code = CANARY_FAIL_VIOLATION;
                        fprintf(file, "Canary Violation or DR error\n");
                    } else {
                        canary_code = CANARY_FAIL_CRASH;
                        fprintf(file, "Canary Crashed 0x%08x\n", exit_code);
                    }
                } else if (early_test && inject_test) {
                    fprintf(file, "Verified Early Injection\n");
                }
            }
        }
        CloseHandle(pinfo.hProcess);
        CloseHandle(pinfo.hThread);
        {
            HANDLE hFind;
            WIN32_FIND_DATA data;
            WCHAR file_name[MAX_PATH], pattern[MAX_PATH];

            _snwprintf(pattern, BUFFER_SIZE_ELEMENTS(pattern), L"%s\\*.*", logbuf);
            NULL_TERMINATE_BUFFER(pattern);
            hFind = FindFirstFile(pattern, &data);
            if (hFind != INVALID_HANDLE_VALUE) {
                do {
                    if (wcscmp(data.cFileName, L".") == 0 ||
                        wcscmp(data.cFileName, L"..") == 0)
                        continue;
                    _snwprintf(file_name, BUFFER_SIZE_ELEMENTS(file_name), L"%s\\%s",
                               logbuf, data.cFileName);
                    NULL_TERMINATE_BUFFER(file_name);
                    insert_file(file, file_name, FALSE);
                } while (FindNextFile(hFind, &data));
                FindClose(hFind);
            }
        }
        fprintf(file, "Canary Finished\n");
    } else {
        fprintf(file, "Canary \"%S\" Failed to Launch\n", cmd_buf);
    }
    return canary_code;
}

#        pragma warning( \
            disable : 4127) // conditional expression is constant i.e while (FALSE)

/* see utils.h for description */
BOOL
run_canary_test_ex(FILE *file, /* INOUT */ CANARY_INFO *info, const WCHAR *scratch_folder,
                   const WCHAR *canary_process)
{
    ConfigGroup *policy, *save_policy;
    WCHAR exe_buf[num_canary_processes][MAX_PATH];
    WCHAR log_folder[MAX_PATH];
    DWORD i;
    BOOL autoinject_set = is_autoinjection_set();

    info->canary_code = ERROR_SUCCESS;
    info->url = L"CFail";
    info->msg = L"Canary Failed";

    read_config_group(&save_policy, L_PRODUCT_NAME, TRUE);
    save_policy->should_clear = TRUE;
    read_config_group(&policy, L_PRODUCT_NAME, TRUE);
    policy->should_clear = TRUE;
    remove_children(policy);

    _snwprintf(log_folder, BUFFER_SIZE_ELEMENTS(log_folder), L"%s\\canary_logs",
               scratch_folder);
    NULL_TERMINATE_BUFFER(log_folder);
    CreateDirectory(log_folder, NULL);

    for (i = 0; i < num_canary_processes; i++) {
        _snwprintf(exe_buf[i], BUFFER_SIZE_ELEMENTS(exe_buf[i]), L"%s\\%s",
                   scratch_folder, canary_process_names[i]);
        NULL_TERMINATE_BUFFER(exe_buf[i]);
        if (CopyFile(canary_process, exe_buf[i], FALSE) == 0) {
            fprintf(file, "Failed to copy canary file %S to %S\n", canary_process,
                    exe_buf[i]);
            /* FIXME- continue if file exists from a previous run that didn't clean up */
            info->canary_code = CANARY_UNABLE_TO_TEST;
            goto canary_exit;
        }
        add_config_group(policy, new_config_group(canary_process_names[i]));
        set_config_group_parameter(get_child(canary_process_names[i], policy),
                                   L_DYNAMORIO_VAR_RUNUNDER, L"1");
    }
    write_config_group(policy);

    /* FIXME - monitor eventlog though we should still detect via forensics and/or
     * exit code (crash/violation).  Xref 10322, for now we suppress eventlogs. */
    /* FIXME - the verify injection tests need work, should just talk to canary proc. */
    /* FIXME - verify canary output - necessary? not clear what action would be */
    /* Files are copied, begin runs */

#        define DO_RUN(run_flag, core_ops, canary_options, inject, run_name, test_type) \
            do {                                                                        \
                if (TEST(run_flag, info->run_flags)) {                                  \
                    WCHAR *canary_ops = TEST(run_flag, info->fault_run)                 \
                        ? info->canary_fault_args                                       \
                        : canary_options;                                               \
                    for (i = 0; i < num_canary_processes; i++) {                        \
                        int code = run_individual_canary_test(                          \
                            file, log_folder, core_ops, i, policy, exe_buf[i],          \
                            canary_ops, inject, run_name, FALSE /* not early */);       \
                        if (code >= 0 && test_type != CANARY_TEST_TYPE_NATIVE) {        \
                            code = run_individual_canary_test(                          \
                                file, log_folder, core_ops, i, policy, exe_buf[i],      \
                                canary_ops, inject, run_name, TRUE /* early inject*/);  \
                        }                                                               \
                        if (code < 0) {                                                 \
                            if (CANARY_RUN_REQUIRES_PASS(run_flag, info->run_flags)) {  \
                                info->canary_code = GET_CANARY_CODE(test_type, code);   \
                                goto canary_exit;                                       \
                            }                                                           \
                            break; /* skip remaining tests in run once first failure    \
                                      found */                                          \
                        }                                                               \
                    }                                                                   \
                }                                                                       \
            } while (FALSE)

    /* First the native runs. */
    unset_autoinjection();

    /* native info gathering run. */
    DO_RUN(CANARY_RUN_NATIVE, L"", OPTIONS_CANARY_NATIVE, FALSE, "native",
           CANARY_TEST_TYPE_NATIVE);

    set_autoinjection(); /* Going to do the non-native runs now */

    /* Now the -thin_client inject run */
    DO_RUN(CANARY_RUN_THIN_CLIENT_INJECT, OPTIONS_THIN_CLIENT, OPTIONS_CANARY_INJECT,
           TRUE, "-thin_client", CANARY_TEST_TYPE_THIN_CLIENT);

    /* now the full -thin_client run */
    DO_RUN(CANARY_RUN_THIN_CLIENT, OPTIONS_THIN_CLIENT, OPTIONS_CANARY_THIN_CLIENT, FALSE,
           "-thin_client", CANARY_TEST_TYPE_THIN_CLIENT);

    /* Now the -client run */
    DO_RUN(CANARY_RUN_CLIENT, OPTIONS_CLIENT, OPTIONS_CANARY_CLIENT, FALSE, "-client",
           CANARY_TEST_TYPE_CLIENT);

    /* Now the MF run */
    DO_RUN(CANARY_RUN_MF, OPTIONS_MF, OPTIONS_CANARY_MF, FALSE, "MF",
           CANARY_TEST_TYPE_MF);

#        undef DO_RUN

canary_exit:
    if (autoinject_set)
        set_autoinjection();
    else
        unset_autoinjection();
    free_config_group(policy);
    write_config_group(save_policy);
    free_config_group(save_policy);
    fprintf(file, "Canary code 0x%08x\n", info->canary_code);
    if (info->canary_code >= 0) {
        info->url = L"ctest";
        info->msg = L"Canary success";
    }
    return (info->canary_code >= 0);
}

/* see utils.h for description */
BOOL
run_canary_test(/* INOUT */ CANARY_INFO *info, WCHAR *version_msg)
{
    BOOL result;
    DWORD res;
    FILE *report_file;
    WCHAR scratch_folder[MAX_PATH], canary_process[MAX_PATH];
    const WCHAR *dynamorio_home = get_dynamorio_home();
    const WCHAR *dynamorio_logdir = get_dynamorio_logdir();
    _snwprintf(canary_process, BUFFER_SIZE_ELEMENTS(canary_process),
               L"%s\\bin\\canary.exe", dynamorio_home);
    NULL_TERMINATE_BUFFER(canary_process);
    _snwprintf(scratch_folder, BUFFER_SIZE_ELEMENTS(scratch_folder), L"%s\\canary_test",
               dynamorio_logdir);
    NULL_TERMINATE_BUFFER(scratch_folder);
    /* xref case 10157, let's try to make sure this stays clean */
    delete_tree(scratch_folder);
    CreateDirectory(scratch_folder, NULL);
    /* FIXME - verify directory created */
    /* Using get unique file name since we plan to run this more then once,
     * though only an issue if the caller doesn't cleanup the report file and
     * leaves it locked. */
    get_unique_filename(dynamorio_logdir, L"canary_report", L".crep", info->buf_report,
                        BUFFER_SIZE_ELEMENTS(info->buf_report));
    info->report = info->buf_report;
    report_file = _wfopen(info->report, L"wb");
    /* FIXME - verify file creation */
    fprintf(report_file, "%S\n", version_msg == NULL ? L"unknown version" : version_msg);
    result = run_canary_test_ex(report_file, info, scratch_folder, canary_process);
    res = delete_tree(scratch_folder);
    fprintf(report_file, "Deleted scratch folder \"%S\", code %d\n", scratch_folder, res);
    fclose(report_file);
    return result;
}

#    endif /* WINDOWS */

#else // ifdef UNIT_TEST

int
main()
{
    set_debuglevel(DL_INFO);
    set_abortlevel(DL_WARN);

    /* read/write file */
    {
        char *test1, *test2;
        char buffy[1024];
        WCHAR *fn = L"utils.tst";
        SIZE_T needed;
        BOOL changed;

        test1 = "This is a stupid file.\r\n\r\nDon't you think?\r\n";
        test2 = "foo\r\n";

        CHECKED_OPERATION(write_file_contents(fn, test1, TRUE));

        DO_ASSERT(ERROR_MORE_DATA == read_file_contents(fn, NULL, 0, &needed));
        DO_ASSERT(strlen(test1) + 1 == needed);

        CHECKED_OPERATION(read_file_contents(fn, buffy, needed, NULL));
        DO_ASSERT(0 == strcmp(test1, buffy));

        CHECKED_OPERATION(write_file_contents_if_different(fn, test1, &changed));
        DO_ASSERT(!changed);

        CHECKED_OPERATION(write_file_contents_if_different(fn, test2, &changed));
        DO_ASSERT(changed);

        CHECKED_OPERATION(read_file_contents(fn, buffy, 1024, NULL));
        DO_ASSERT(0 == strcmp(test2, buffy));
    }

    /* file existence */
    {
        WCHAR *fn = L"tester-file";

        DeleteFile(fn);
        DO_ASSERT(!file_exists(fn));
        DO_ASSERT(!file_exists(fn));

        CHECKED_OPERATION(write_file_contents(fn, "testing", TRUE));
        DO_ASSERT(file_exists(fn));
        DeleteFile(fn);

        DO_ASSERT(file_exists(L"C:\\"));
        DO_ASSERT(!file_exists(L"%%RY:\\\\zZsduf"));
    }

    /* mkdir_with_parents / delete_tree */
    {
        delete_tree(L"__foo_test");
        mkdir_with_parents(L"__foo_test");
        DO_ASSERT(file_exists(L"__foo_test"));
        mkdir_with_parents(L"__foo_test\\foo\\bar\\goo");
        DO_ASSERT(file_exists(L"__foo_test\\foo\\bar\\goo"));
        mkdir_with_parents(L"__foo_test/lib/bar/goo/dood");
        DO_ASSERT(file_exists(L"__foo_test\\lib\\bar\\goo\\dood"));
        CHECKED_OPERATION(delete_tree(L"__foo_test"));
        DO_ASSERT(!file_exists(L"__foo_test"));
        DO_ASSERT(!file_exists(L"__foo_test\\foo\\bar\\goo"));
        DO_ASSERT(!file_exists(L"__foo_test\\lib\\bar\\goo\\dood"));
    }

    /* setup_installation */
    {
        CHECKED_OPERATION(setup_installation(L"C:\\", TRUE));
        CHECKED_OPERATION(setup_installation(L"C:\\foobarra", FALSE));
        DO_ASSERT_WSTR_EQ(L"C:\\", get_dynamorio_home());
        CHECKED_OPERATION(setup_installation(L"C:\\foobarra", TRUE));
        DO_ASSERT_WSTR_EQ(L"C:\\foobarra", get_dynamorio_home());
    }

    {
        WCHAR piname[MAX_PATH];
        BOOL bres = using_system32_for_preinject(NULL);
        printf("Using SYSTEM32 for preinject: %s\n", bres ? "TRUE" : "FALSE");
        CHECKED_OPERATION(get_preinject_name(piname, MAX_PATH));
        printf("Preinject name: %S\n", piname);
    }

    printf("All Test Passed\n");

    return 0;
}

#endif