/*
 * Copyright (C) 2012 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/*
 * "find_lock.exe", for Windows only.
 *
 * References used:
 *
 * http://drdobbs.com/windows/184411099
 * article by Sven B. Schreiber, November 01, 1999
 *
 * http://www.codeguru.com/Cpp/W-P/system/processesmodules/article.php/c2827/
 * by Zoltan Csizmadia, November 14, 2000
 *
 * http://stackoverflow.com/questions/860656/
 * (same technique, but written in unsafe C#)
 *
 * Starting with Vista, we can also use the Restart Manager API as
 * explained here: (TODO for next version)
 * http://msdn.microsoft.com/en-us/magazine/cc163450.aspx
 */

#ifdef _WIN32

#include "utils.h"
#include <ctype.h>
#include <fcntl.h>
#include <io.h>
#include <process.h>


// NtDll structures from the the Dr Dobbs article, adjusted for our needs:

typedef void *POBJECT;
typedef LONG KPRIORITY;
typedef LARGE_INTEGER QWORD;

typedef struct {
    WORD  Length;
    WORD  MaximumLength;
    PWORD Buffer;
} UNICODE_STRING;

typedef struct {
    DWORD       dIdProcess;
    BYTE        bObjectType;    // OB_TYPE_*
    BYTE        bFlags;         // bits 0..2 HANDLE_FLAG_*
    WORD        wValue;         // multiple of 4
    POBJECT     pObject;
    ACCESS_MASK GrantedAccess;
} SYSTEM_HANDLE;

typedef struct {
    DWORD         dCount;
    SYSTEM_HANDLE ash[1];
} SYSTEM_HANDLE_INFORMATION;

typedef struct {
    DWORD PeakVirtualSize;
    DWORD VirtualSize;
    DWORD PageFaultCount;
    DWORD PeakWorkingSetSize;
    DWORD WorkingSetSize;
    DWORD QuotaPeakPagedPoolUsage;
    DWORD QuotaPagedPoolUsage;
    DWORD QuotaPeakNonPagedPoolUsage;
    DWORD QuotaNonPagedPoolUsage;
    DWORD PagefileUsage;
    DWORD PeakPagefileUsage;
} VM_COUNTERS;

typedef struct {
    HANDLE UniqueProcess;
    HANDLE UniqueThread;
} CLIENT_ID;

typedef enum {
    // Ignored. We don't actually use these values.
    Unused
} KWAIT_REASON;

typedef struct {
    QWORD        qKernelTime;       // 100 nsec units
    QWORD        qUserTime;         // 100 nsec units
    QWORD        qCreateTime;       // relative to 01-01-1601
    DWORD        d18;
    PVOID        pStartAddress;
    CLIENT_ID    Cid;               // process/thread ids
    DWORD        dPriority;
    DWORD        dBasePriority;
    DWORD        dContextSwitches;
    DWORD        dThreadState;      // 2=running, 5=waiting
    KWAIT_REASON WaitReason;
    DWORD        dReserved01;
} SYSTEM_THREAD;

typedef struct {
    DWORD          dNext;           // relative offset
    DWORD          dThreadCount;
    DWORD          dReserved01;
    DWORD          dReserved02;
    DWORD          dReserved03;
    DWORD          dReserved04;
    DWORD          dReserved05;
    DWORD          dReserved06;
    QWORD          qCreateTime;     // relative to 01-01-1601
    QWORD          qUserTime;       // 100 nsec units
    QWORD          qKernelTime;     // 100 nsec units
    UNICODE_STRING usName;
    KPRIORITY      BasePriority;
    DWORD          dUniqueProcessId;
    DWORD          dInheritedFromUniqueProcessId;
    DWORD          dHandleCount;
    DWORD          dReserved07;
    DWORD          dReserved08;
    VM_COUNTERS    VmCounters;
    DWORD          dCommitCharge;   // bytes
    SYSTEM_THREAD  ast[1];
} SYSTEM_PROCESS_INFORMATION;

// The sic opcode for NtQuerySystemInformation
typedef enum {
    SystemProcessInformation = 5,
    SystemHandleInformation = 16,
} SYSTEMINFOCLASS;


#define STATUS_SUCCESS               0x00000000
#define STATUS_UNSUCCESSFUL          0xC0000001
#define STATUS_NOT_IMPLEMENTED       0xC0000002
#define STATUS_INVALID_INFO_CLASS    0xC0000003
#define STATUS_INFO_LENGTH_MISMATCH  0xC0000004
#define STATUS_INVALID_PARAMETER     0xC000000D

typedef DWORD (WINAPI *NtQuerySystemInformationFuncPtr)(
                                      DWORD sic, VOID* pData, DWORD sSize, ULONG* pdSize);
typedef DWORD (WINAPI *NtQueryInformationFileFuncPtr)(HANDLE, PVOID, PVOID, DWORD, DWORD);
typedef DWORD (WINAPI *NtQueryObjectFuncPtr)(HANDLE, DWORD, VOID*, DWORD, VOID*);

static NtQuerySystemInformationFuncPtr sNtQuerySystemInformationFunc;
static NtQueryInformationFileFuncPtr   sNtQueryInformationFileFunc;
static NtQueryObjectFuncPtr            sNtQueryObjectFunc;

//------------

// Get the NT DLL functions we need to use.
static bool init() {

    sNtQuerySystemInformationFunc =
        (NtQuerySystemInformationFuncPtr) GetProcAddress(
            GetModuleHandleA("ntdll.dll"), "NtQuerySystemInformation");

    sNtQueryInformationFileFunc =
        (NtQueryInformationFileFuncPtr) GetProcAddress(
            GetModuleHandleA("ntdll.dll"), "NtQueryInformationFile");

    sNtQueryObjectFunc =
        (NtQueryObjectFuncPtr) GetProcAddress(
            GetModuleHandleA("ntdll.dll"), "NtQueryObject");

    return sNtQuerySystemInformationFunc != NULL &&
           sNtQueryInformationFileFunc   != NULL &&
           sNtQueryObjectFunc            != NULL;
}

static void terminate() {
    sNtQuerySystemInformationFunc = NULL;
    sNtQueryInformationFileFunc = NULL;
    sNtQueryObjectFunc = NULL;
}

static bool adjustPrivileges() {
    char *error = NULL;
    HANDLE tokenH;

    // Open a process token that lets us adjust privileges
    BOOL ok = OpenProcessToken(GetCurrentProcess(),   // ProcessHandle
                               TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, // DesiredAccess
                               &tokenH);              // TokenHandle
    if (!ok) {
        error = "OpenProcessToken failed: ";
        goto bail_out;
    }

    // Lookup the privilege by name and get its local LUID token.
    // What we request:
    // SE_DEBUG_NAME, aka "SeDebugPrivilege"
    // MSDN: Required to debug and adjust the memory of a process owned by another account.
    //       User Right: Debug programs.
    TOKEN_PRIVILEGES priv;
    priv.PrivilegeCount = 1;
    priv.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
    ok = LookupPrivilegeValueA(NULL,                        // lpSystemName
                               SE_DEBUG_NAME,               // lpName
                               &(priv.Privileges[0].Luid)); // lpLuid
    if (!ok) {
        error = "LookupPrivilegeValue failed: ";
        goto bail_out;
    }

    ok = AdjustTokenPrivileges(tokenH,  // TokenHandle
                               FALSE,   // DisableAllPrivileges
                               &priv,   // NewState
                               0,       // BufferLength
                               NULL,    // PreviousState
                               0);      // ReturnLength
    if (!ok) {
        error = "AdjustTokenPrivileges failed: ";
        goto bail_out;
    }

bail_out:
    if (error != NULL && gIsDebug) {
        CString err;
        err.setLastWin32Error(error);
        fprintf(stderr, "%s", err.cstr());
    }

    if (tokenH != NULL) {
        CloseHandle(tokenH);
    }

    return !!ok;
}

static bool getHandleType(HANDLE h, CString *type) {
    bool result = false;
    ULONG size = 0;
    // Get the size of the type string
    int status = sNtQueryObjectFunc(h, 2, NULL, 0, &size);
    if (status == STATUS_INFO_LENGTH_MISMATCH && size > 0) {
        // Get the type string itself
        char *buf = new char[size];
        status = sNtQueryObjectFunc(h, 2, buf, size, NULL);
        if (status == 0 && size > 96) {
            // The type string we want is a wide unicode (UTF16)
            // zero-terminated string located at offset 96 in the
            // buffer. In our case we want the string to be
            // "Directory" or "File" so we know the max useful length
            // is 9.
            // Since we can only deal with ansi strings in this program,
            // we'll make a crude copy of every other byte and just check
            // that the other bytes are zero.
            const char *c = buf + 96;
            const char *e = buf + 96 + size;
            // we'll write at the beginning of our buffer
            char *dest = buf;
            char *dend = dest + 9;
            for (; c < e && dest < dend && c[0] != '\0' && c[1] == '\0'; c += 2, dest++) {
                *dest = *c;
            }
            *(dest++) = '\0';
            type->set(buf, dest - buf);
            result = true;
        }

        free(buf);
    }
    return result;
}

// These is the wide unicode representations of the type we want to find.
static const char kFileW[] = "File";

static char isFileHandleType(HANDLE handle) {
    char type = 0;
    ULONG size = 0;
    // Get the size of the type string
    int status = sNtQueryObjectFunc(handle, 2, NULL, 0, &size);
    if (status == STATUS_INFO_LENGTH_MISMATCH && size > 0) {
        // Get the type string itself
        char *buf = new char[size];
        status = sNtQueryObjectFunc(handle, 2, buf, size, NULL);
        if (status == 0 && size > 96) {
            // The type string we want is a wide unicode (UTF16-LE)
            // zero-terminated string located at offset 96 in the
            // buffer. In our case we want the string to be "File".
            //
            // Since we're reading wide unicode, we want each character
            // to be the one from our string followed by a zero byte.
            // e.g. c should point to F \0 i \0 l \0 e \0 \0 \0.
            const char *c = buf + 96;
            type = c[0];

            int len = sizeof(kFileW);
            const char *d = kFileW;

            for (; type != 0 && len > 0; c+=2, d++, len--) {
                if (c[0] != *d || c[1] != 0) {
                    type = 0;
                    break;
                }
            }
        }

        free(buf);
    }
    return type;
}

typedef struct {
    HANDLE handle;
    CString *outStr;
    bool result;
} SFileNameInfo;

static unsigned __stdcall FileNameThreadFunc(void *param) {
    SFileNameInfo *info = (SFileNameInfo *)param;
    if (info == NULL) {
        return 1;
    }

    char buf[MAX_PATH*2 + 4];
    DWORD iob[2] = { 0, 0 };

    DWORD status = sNtQueryInformationFileFunc(info->handle, iob, buf, sizeof(buf), 9);
    if (status == STATUS_SUCCESS) {
        // The result is a buffer with:
        // - DWORD (4 bytes) for the *byte* length (so twice the character length)
        // - Actual string in Unicode
        // Not sure of the actual type, but it does look like a UNICODE_STRING struct.

        DWORD len = ((DWORD *)buf)[0];
        if (len <= MAX_PATH * 2) {
            // We can't handle wide Unicode. What we do is convert it into
            // straight ansi by just retaining the first of each couple bytes.
            // Bytes that cannot be mapped (e.g. 2nd byte is != 0) will be
            // simply converted to 0xFF.

            unsigned char *dest = (unsigned char *)buf + 4;
            unsigned char *src  = (unsigned char *)buf + 4;
            for (DWORD i = 0; i < len; dest++, src += 2, i += 2) {
                if (src[1] == 0) {
                    *dest = *src;
                } else {
                    *dest = 0xFF;
                }
            }
            *dest = '\0';
            info->outStr->set(buf + 4, len);
            info->result = true;
            return 0;
        }
    }
    return 1;
}

static bool getFileName(HANDLE handle, CString *outStr) {
    SFileNameInfo info;
    info.handle = handle;
    info.outStr = outStr;
    info.result = false;

    // sNtQueryInformationFileFunc might hang on some handles.
    // A trick is to do it in a thread and if it takes too loog then
    // just shutdown the thread, since it's deadlocked anyway.
    unsigned threadId;
    HANDLE th = (HANDLE)_beginthreadex(NULL,                    // security
                                       0,                       // stack_size
                                       &FileNameThreadFunc,     // address
                                       &info,                   // arglist
                                       0,                       // initflag
                                       &threadId);              // thrdaddr

    if (th == NULL) {
        // Failed to create thread. Shouldn't really happen.
        outStr->set("<failed to create thread>");
        return false;
    }

    bool result = false;

    // Wait for thread or kill it if it takes too long.
    if (WaitForSingleObject(th /*handle*/, 200 /*ms*/) == WAIT_TIMEOUT) {
        TerminateThread(th /*handle*/, 0 /*retCode*/);
        outStr->set("<timeout>");
    } else {
        result = info.result;
    }

    CloseHandle(th);
    return result;
}

// Find the name of the process (e.g. "java.exe") given its id.
// processesPtr must be the list returned by getAllProcesses().
// Special handling for javaw.exe: this isn't quite useful so
// we also try to find and append the parent process name.
static bool getProcessName(SYSTEM_PROCESS_INFORMATION *processesPtr,
                           DWORD remoteProcessId,
                           CString *outStr) {
    SYSTEM_PROCESS_INFORMATION *ptr = processesPtr;
    while (ptr != NULL) {
        if (ptr->dUniqueProcessId == remoteProcessId) {
            // This is the process we want.

            UNICODE_STRING *uniStr = &(ptr->usName);
            WORD len = uniStr->Length;

            char buf[MAX_PATH];
            if (len <= MAX_PATH * 2) {
                // We can't handle wide Unicode. What we do is convert it into
                // straight ansi by just retaining the first of each couple bytes.
                // Bytes that cannot be mapped (e.g. 2nd byte is != 0) will be
                // simply converted to 0xFF.

                unsigned char *dest = (unsigned char *)buf;
                unsigned char *src  = (unsigned char *)uniStr->Buffer;
                for (WORD i = 0; i < len; dest++, src += 2, i += 2) {
                    if (src[1] == 0) {
                        *dest = *src;
                    } else {
                        *dest = 0xFF;
                    }
                }
                *dest = '\0';
                outStr->set(buf, len);

                if (strcmp(buf, "javaw.exe") == 0) {
                    // Heuristic: eclipse often shows up as javaw.exe
                    // but what is useful is to report eclipse to the user
                    // instead.
                    // So in this case, look at the parent and report it too.
                    DWORD parentId = ptr->dInheritedFromUniqueProcessId;
                    if (parentId > 0) {
                        CString name2;
                        bool ok2 = getProcessName(processesPtr,
                                                  parentId,
                                                  &name2);
                        if (ok2) {
                            outStr->add(" (");
                            outStr->add(name2.cstr());
                            outStr->add(")");
                        }
                    }
                }

                return true;
            }
        }

        // Look at the next process, if any.
        if (ptr->dNext == NULL) {
            break;
        } else {
            ptr = (SYSTEM_PROCESS_INFORMATION *)((char *)ptr + ptr->dNext);
        }
    }

    outStr->setf("<process id %08x name not found>", remoteProcessId);
    return false;
}

// Query system for all processes information.
// Returns an error string in case of error.
// Returns the virtual_alloc-allocated buffer on success or NULL on error.
// It's up to the caller to do a VirtualFree on the returned buffer.
static SYSTEM_PROCESS_INFORMATION *queryAllProcess(const char **error) {
    // Allocate a buffer for the process information. We don't know the
    // exact size. A normal system might typically have between 100-200 processes.
    // We'll resize the buffer if not big enough.
    DWORD infoSize = 4096;
    SYSTEM_PROCESS_INFORMATION *infoPtr =
        (SYSTEM_PROCESS_INFORMATION *) VirtualAlloc(NULL, infoSize, MEM_COMMIT, PAGE_READWRITE);

    if (infoPtr != NULL) {
        // Query the actual size needed (or the data if it fits in the buffer)
        DWORD needed = 0;
        if (sNtQuerySystemInformationFunc(
                SystemProcessInformation, infoPtr, infoSize, &needed) != 0) {
            if (needed == 0) {
                // Shouldn't happen.
                *error = "No processes found";
                goto bail_out;
            }

            // Realloc
            VirtualFree(infoPtr, 0, MEM_RELEASE);
            infoSize += needed;
            infoPtr = (SYSTEM_PROCESS_INFORMATION *) VirtualAlloc(
                            NULL, infoSize, MEM_COMMIT, PAGE_READWRITE);

            // Query all the processes objects again
            if (sNtQuerySystemInformationFunc(
                    SystemProcessInformation, infoPtr, infoSize, NULL) != 0) {
                *error = "Failed to query system processes";
                goto bail_out;
            }
        }
    }

    if (infoPtr == NULL) {
        *error = "Failed to allocate system processes info buffer";
        goto bail_out;
    }

bail_out:
    if (*error != NULL) {
        VirtualFree(infoPtr, 0, MEM_RELEASE);
        infoPtr = NULL;
    }
    return infoPtr;
}

// Query system for all handle information.
// Returns an error string in case of error.
// Returns the virtual_alloc-allocated buffer on success or NULL on error.
// It's up to the caller to do a VirtualFree on the returned buffer.
static SYSTEM_HANDLE_INFORMATION *queryAllHandles(const char **error) {
    // Allocate a buffer. It won't be large enough to get the handles
    // (e.g. there might be 10k or 40k handles around). We'll resize
    // it once we know the actual size.
    DWORD infoSize = 4096;
    SYSTEM_HANDLE_INFORMATION *infoPtr =
        (SYSTEM_HANDLE_INFORMATION *) VirtualAlloc(NULL, infoSize, MEM_COMMIT, PAGE_READWRITE);

    if (infoPtr != NULL) {
        // Query the actual size needed
        DWORD needed = 0;
        if (sNtQuerySystemInformationFunc(
                SystemHandleInformation, infoPtr, infoSize, &needed) != 0) {
            if (needed == 0) {
                // Shouldn't happen.
                *error = "No handles found";
                goto bail_out;
            }

            // Realloc
            VirtualFree(infoPtr, 0, MEM_RELEASE);
            infoSize += needed;
            infoPtr = (SYSTEM_HANDLE_INFORMATION *) VirtualAlloc(
                            NULL, infoSize, MEM_COMMIT, PAGE_READWRITE);
        }
    }

    if (infoPtr == NULL) {
        *error = "Failed to allocate system handle info buffer";
        goto bail_out;
    }

    // Query all the handle objects
    if (sNtQuerySystemInformationFunc(SystemHandleInformation, infoPtr, infoSize, NULL) != 0) {
        *error = "Failed to query system handles";
        goto bail_out;
    }

bail_out:
    if (*error != NULL) {
        VirtualFree(infoPtr, 0, MEM_RELEASE);
        infoPtr = NULL;
    }
    return infoPtr;
}

bool findLock(CPath &path, CString *outModule) {
    bool result = false;
    const char *error = NULL;

    SYSTEM_PROCESS_INFORMATION *processesPtr = NULL;
    SYSTEM_HANDLE_INFORMATION  *handlesPtr   = NULL;

    const HANDLE currProcessH = GetCurrentProcess();
    const DWORD currProcessId = GetCurrentProcessId();
    HANDLE remoteProcessH = NULL;
    DWORD remoteProcessId = 0;
    DWORD matchProcessId = 0;

    int numHandleFound = 0;
    int numHandleChecked = 0;
    int numHandleDirs = 0;
    int numHandleFiles = 0;
    int numProcessMatch = 0;

    BYTE ob_type_file = 0;

    // Get the path to search, without the drive letter.
    const char *searchPath = path.cstr();
    if (isalpha(searchPath[0]) && searchPath[1] == ':') {
        searchPath += 2;
    }
    size_t searchPathLen = strlen(searchPath);

    if (gIsDebug) fprintf(stderr, "Search path: '%s'\n", searchPath);

    if (!init()) {
        error = "Failed to bind to ntdll.dll";
        goto bail_out;
    }

    if (!adjustPrivileges()) {
        // We can still continue even if the privilege escalation failed.
        // The apparent effect is that we'll fail to query the name of
        // some processes, yet it will work for some of them.
        if (gIsDebug) fprintf(stderr, "Warning: adusting privileges failed. Continuing anyway.\n");
    } else {
        if (gIsDebug) fprintf(stderr, "Privileges adjusted.\n"); // DEBUG remove lter
    }

    processesPtr = queryAllProcess(&error);
    if (processesPtr == NULL) goto bail_out;

    handlesPtr = queryAllHandles(&error);
    if (handlesPtr == NULL) goto bail_out;

    numHandleFound = handlesPtr->dCount;

    // Check all the handles
    for (int n = handlesPtr->dCount, i = 0; i < n; i++) {
        SYSTEM_HANDLE sysh = handlesPtr->ash[i];

        if (ob_type_file != 0 && sysh.bObjectType != ob_type_file) {
            continue;
        }

        HANDLE handle = (HANDLE) sysh.wValue;
        DWORD remoteId = sysh.dIdProcess;
        HANDLE remoteH = NULL;

        if (remoteId == matchProcessId) {
            // We already matched that process, we can skip its other entries.
            continue;
        }

        if (remoteId == currProcessId) {
            // We don't match ourselves
            continue;
        }

        // Open a remote process.
        // Most entries of a given process seem to be consecutive, so we
        // only open the remote process handle if it's a different id.
        if (remoteProcessH == NULL && remoteId == remoteProcessId) {
            // We already tried to open this process and it failed.
            // It's not going to be any better the next time so skip it.
            continue;
        }
        if (remoteProcessH == NULL || remoteId != remoteProcessId) {
            if (remoteProcessH != NULL) {
                CloseHandle(remoteProcessH);
            }

            remoteProcessId = remoteId;
            remoteProcessH = OpenProcess(PROCESS_DUP_HANDLE,
                                         FALSE /*inheritHandle*/,
                                         remoteProcessId);
            if (remoteProcessH == NULL) {
                continue;
            }
        }

        if (remoteProcessH != NULL) {
            // Duplicate the remote handle
            if (DuplicateHandle(remoteProcessH,     // hSourceProcessHandle
                                handle,             // hSourceHandle
                                currProcessH,       // hTargetProcessHandle
                                &remoteH,           // lpTargetHandle
                                0,                  // dwDesiredAccess (ignored by same access)
                                FALSE,              // bInheritHandle
                                DUPLICATE_SAME_ACCESS) == 0) {
                continue;
            }
        }

        numHandleChecked++;

        char type = isFileHandleType(remoteH);

        if (type != 0) {
            if (type == 'D') numHandleDirs++;
            else if (type == 'F') numHandleFiles++;

            // TODO simplify by not keeping directory handles
            if (ob_type_file == 0 && type == 'F') {
                // We found the first file handle. Remember it's system_handle object type
                // and then use it to filter the following system_handle.
                // For some reason OB_TYPE_FILE should be 0x1A but empirically I find it
                // to be 0x1C, so we just make this test more dynamic.
                ob_type_file = sysh.bObjectType;
            }

            // Try to get a filename out of that file or directory handle.
            CString name("<unknown>");
            bool ok = getFileName(remoteH, &name);

            if (gIsDebug) {
                fprintf(stderr, "P:%08x | t:%02x | f:%02x | v:%08x | %c | %s %s\n",
                    sysh.dIdProcess, sysh.bObjectType, sysh.bFlags, sysh.wValue,
                    type,
                    ok ? "OK" : "FAIL",
                    name.cstr()
                    );
            }

            if (ok) {
                // We got a file path. Let's check if it matches our target path.
                if (_strnicmp(searchPath, name.cstr(), searchPathLen) == 0) {
                    // Remember this process id so that we can ignore all its following entries.
                    matchProcessId = remoteId;

                    // Find out its process name
                    CString procName("<unknown>");
                    ok = getProcessName(processesPtr, remoteProcessId, &procName);
                    if (ok) {
                        numProcessMatch++;

                        if (!outModule->isEmpty()) {
                            outModule->add(";");
                        }
                        outModule->add(procName.cstr());
                        result = true;
                    }

                    if (gIsDebug) {
                        fprintf(stderr, "==> MATCH FOUND: %s  %s\n",
                            ok ? "OK" : "FAIL",
                            procName.cstr()
                            );
                    }
                }
            }

        }

        if (remoteH != NULL) {
            CloseHandle(remoteH);
            remoteH = NULL;
        }
    }

bail_out:

    if (gIsDebug) {
        fprintf(stderr, "Processes matched: %d\n", numProcessMatch);
        fprintf(stderr, "Handles: %d found, %d checked, %d dirs, %d files\n",
               numHandleFound,
               numHandleChecked,
               numHandleDirs,
               numHandleFiles);
    }

    if (error != NULL) {
        CString msg;
        msg.setLastWin32Error(NULL);
        if (gIsDebug) fprintf(stderr, "[ERROR] %s: %s", error, msg.cstr());
    }

    if (remoteProcessH != NULL) {
        CloseHandle(remoteProcessH);
    }

    if (currProcessH != NULL) {
        CloseHandle(currProcessH);
    }

    if (handlesPtr != NULL) {
        VirtualFree(handlesPtr, 0, MEM_RELEASE);
        handlesPtr = NULL;
    }

    terminate();

    return result;
}

#endif /* _WIN32 */