Home | History | Annotate | Download | only in find_lock
      1 /*
      2  * Copyright (C) 2012 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 /*
     18  * "find_lock.exe", for Windows only.
     19  *
     20  * References used:
     21  *
     22  * http://drdobbs.com/windows/184411099
     23  * article by Sven B. Schreiber, November 01, 1999
     24  *
     25  * http://www.codeguru.com/Cpp/W-P/system/processesmodules/article.php/c2827/
     26  * by Zoltan Csizmadia, November 14, 2000
     27  *
     28  * http://stackoverflow.com/questions/860656/
     29  * (same technique, but written in unsafe C#)
     30  *
     31  * Starting with Vista, we can also use the Restart Manager API as
     32  * explained here: (TODO for next version)
     33  * http://msdn.microsoft.com/en-us/magazine/cc163450.aspx
     34  */
     35 
     36 #ifdef _WIN32
     37 
     38 #include "utils.h"
     39 #include <ctype.h>
     40 #include <fcntl.h>
     41 #include <io.h>
     42 #include <process.h>
     43 
     44 
     45 // NtDll structures from the the Dr Dobbs article, adjusted for our needs:
     46 
     47 typedef void *POBJECT;
     48 typedef LONG KPRIORITY;
     49 typedef LARGE_INTEGER QWORD;
     50 
     51 typedef struct {
     52     WORD  Length;
     53     WORD  MaximumLength;
     54     PWORD Buffer;
     55 } UNICODE_STRING;
     56 
     57 typedef struct {
     58     DWORD       dIdProcess;
     59     BYTE        bObjectType;    // OB_TYPE_*
     60     BYTE        bFlags;         // bits 0..2 HANDLE_FLAG_*
     61     WORD        wValue;         // multiple of 4
     62     POBJECT     pObject;
     63     ACCESS_MASK GrantedAccess;
     64 } SYSTEM_HANDLE;
     65 
     66 typedef struct {
     67     DWORD         dCount;
     68     SYSTEM_HANDLE ash[1];
     69 } SYSTEM_HANDLE_INFORMATION;
     70 
     71 typedef struct {
     72     DWORD PeakVirtualSize;
     73     DWORD VirtualSize;
     74     DWORD PageFaultCount;
     75     DWORD PeakWorkingSetSize;
     76     DWORD WorkingSetSize;
     77     DWORD QuotaPeakPagedPoolUsage;
     78     DWORD QuotaPagedPoolUsage;
     79     DWORD QuotaPeakNonPagedPoolUsage;
     80     DWORD QuotaNonPagedPoolUsage;
     81     DWORD PagefileUsage;
     82     DWORD PeakPagefileUsage;
     83 } VM_COUNTERS;
     84 
     85 typedef struct {
     86     HANDLE UniqueProcess;
     87     HANDLE UniqueThread;
     88 } CLIENT_ID;
     89 
     90 typedef enum {
     91     // Ignored. We don't actually use these values.
     92     Unused
     93 } KWAIT_REASON;
     94 
     95 typedef struct {
     96     QWORD        qKernelTime;       // 100 nsec units
     97     QWORD        qUserTime;         // 100 nsec units
     98     QWORD        qCreateTime;       // relative to 01-01-1601
     99     DWORD        d18;
    100     PVOID        pStartAddress;
    101     CLIENT_ID    Cid;               // process/thread ids
    102     DWORD        dPriority;
    103     DWORD        dBasePriority;
    104     DWORD        dContextSwitches;
    105     DWORD        dThreadState;      // 2=running, 5=waiting
    106     KWAIT_REASON WaitReason;
    107     DWORD        dReserved01;
    108 } SYSTEM_THREAD;
    109 
    110 typedef struct {
    111     DWORD          dNext;           // relative offset
    112     DWORD          dThreadCount;
    113     DWORD          dReserved01;
    114     DWORD          dReserved02;
    115     DWORD          dReserved03;
    116     DWORD          dReserved04;
    117     DWORD          dReserved05;
    118     DWORD          dReserved06;
    119     QWORD          qCreateTime;     // relative to 01-01-1601
    120     QWORD          qUserTime;       // 100 nsec units
    121     QWORD          qKernelTime;     // 100 nsec units
    122     UNICODE_STRING usName;
    123     KPRIORITY      BasePriority;
    124     DWORD          dUniqueProcessId;
    125     DWORD          dInheritedFromUniqueProcessId;
    126     DWORD          dHandleCount;
    127     DWORD          dReserved07;
    128     DWORD          dReserved08;
    129     VM_COUNTERS    VmCounters;
    130     DWORD          dCommitCharge;   // bytes
    131     SYSTEM_THREAD  ast[1];
    132 } SYSTEM_PROCESS_INFORMATION;
    133 
    134 // The sic opcode for NtQuerySystemInformation
    135 typedef enum {
    136     SystemProcessInformation = 5,
    137     SystemHandleInformation = 16,
    138 } SYSTEMINFOCLASS;
    139 
    140 
    141 #define STATUS_SUCCESS               0x00000000
    142 #define STATUS_UNSUCCESSFUL          0xC0000001
    143 #define STATUS_NOT_IMPLEMENTED       0xC0000002
    144 #define STATUS_INVALID_INFO_CLASS    0xC0000003
    145 #define STATUS_INFO_LENGTH_MISMATCH  0xC0000004
    146 #define STATUS_INVALID_PARAMETER     0xC000000D
    147 
    148 typedef DWORD (WINAPI *NtQuerySystemInformationFuncPtr)(
    149                                       DWORD sic, VOID* pData, DWORD sSize, ULONG* pdSize);
    150 typedef DWORD (WINAPI *NtQueryInformationFileFuncPtr)(HANDLE, PVOID, PVOID, DWORD, DWORD);
    151 typedef DWORD (WINAPI *NtQueryObjectFuncPtr)(HANDLE, DWORD, VOID*, DWORD, VOID*);
    152 
    153 static NtQuerySystemInformationFuncPtr sNtQuerySystemInformationFunc;
    154 static NtQueryInformationFileFuncPtr   sNtQueryInformationFileFunc;
    155 static NtQueryObjectFuncPtr            sNtQueryObjectFunc;
    156 
    157 //------------
    158 
    159 // Get the NT DLL functions we need to use.
    160 static bool init() {
    161 
    162     sNtQuerySystemInformationFunc =
    163         (NtQuerySystemInformationFuncPtr) GetProcAddress(
    164             GetModuleHandleA("ntdll.dll"), "NtQuerySystemInformation");
    165 
    166     sNtQueryInformationFileFunc =
    167         (NtQueryInformationFileFuncPtr) GetProcAddress(
    168             GetModuleHandleA("ntdll.dll"), "NtQueryInformationFile");
    169 
    170     sNtQueryObjectFunc =
    171         (NtQueryObjectFuncPtr) GetProcAddress(
    172             GetModuleHandleA("ntdll.dll"), "NtQueryObject");
    173 
    174     return sNtQuerySystemInformationFunc != NULL &&
    175            sNtQueryInformationFileFunc   != NULL &&
    176            sNtQueryObjectFunc            != NULL;
    177 }
    178 
    179 static void terminate() {
    180     sNtQuerySystemInformationFunc = NULL;
    181     sNtQueryInformationFileFunc = NULL;
    182     sNtQueryObjectFunc = NULL;
    183 }
    184 
    185 static bool adjustPrivileges() {
    186     char *error = NULL;
    187     HANDLE tokenH;
    188 
    189     // Open a process token that lets us adjust privileges
    190     BOOL ok = OpenProcessToken(GetCurrentProcess(),   // ProcessHandle
    191                                TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, // DesiredAccess
    192                                &tokenH);              // TokenHandle
    193     if (!ok) {
    194         error = "OpenProcessToken failed: ";
    195         goto bail_out;
    196     }
    197 
    198     // Lookup the privilege by name and get its local LUID token.
    199     // What we request:
    200     // SE_DEBUG_NAME, aka "SeDebugPrivilege"
    201     // MSDN: Required to debug and adjust the memory of a process owned by another account.
    202     //       User Right: Debug programs.
    203     TOKEN_PRIVILEGES priv;
    204     priv.PrivilegeCount = 1;
    205     priv.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
    206     ok = LookupPrivilegeValueA(NULL,                        // lpSystemName
    207                                SE_DEBUG_NAME,               // lpName
    208                                &(priv.Privileges[0].Luid)); // lpLuid
    209     if (!ok) {
    210         error = "LookupPrivilegeValue failed: ";
    211         goto bail_out;
    212     }
    213 
    214     ok = AdjustTokenPrivileges(tokenH,  // TokenHandle
    215                                FALSE,   // DisableAllPrivileges
    216                                &priv,   // NewState
    217                                0,       // BufferLength
    218                                NULL,    // PreviousState
    219                                0);      // ReturnLength
    220     if (!ok) {
    221         error = "AdjustTokenPrivileges failed: ";
    222         goto bail_out;
    223     }
    224 
    225 bail_out:
    226     if (error != NULL && gIsDebug) {
    227         CString err;
    228         err.setLastWin32Error(error);
    229         fprintf(stderr, "%s", err.cstr());
    230     }
    231 
    232     if (tokenH != NULL) {
    233         CloseHandle(tokenH);
    234     }
    235 
    236     return !!ok;
    237 }
    238 
    239 static bool getHandleType(HANDLE h, CString *type) {
    240     bool result = false;
    241     ULONG size = 0;
    242     // Get the size of the type string
    243     int status = sNtQueryObjectFunc(h, 2, NULL, 0, &size);
    244     if (status == STATUS_INFO_LENGTH_MISMATCH && size > 0) {
    245         // Get the type string itself
    246         char *buf = new char[size];
    247         status = sNtQueryObjectFunc(h, 2, buf, size, NULL);
    248         if (status == 0 && size > 96) {
    249             // The type string we want is a wide unicode (UTF16)
    250             // zero-terminated string located at offset 96 in the
    251             // buffer. In our case we want the string to be
    252             // "Directory" or "File" so we know the max useful length
    253             // is 9.
    254             // Since we can only deal with ansi strings in this program,
    255             // we'll make a crude copy of every other byte and just check
    256             // that the other bytes are zero.
    257             const char *c = buf + 96;
    258             const char *e = buf + 96 + size;
    259             // we'll write at the beginning of our buffer
    260             char *dest = buf;
    261             char *dend = dest + 9;
    262             for (; c < e && dest < dend && c[0] != '\0' && c[1] == '\0'; c += 2, dest++) {
    263                 *dest = *c;
    264             }
    265             *(dest++) = '\0';
    266             type->set(buf, dest - buf);
    267             result = true;
    268         }
    269 
    270         free(buf);
    271     }
    272     return result;
    273 }
    274 
    275 // These is the wide unicode representations of the type we want to find.
    276 static const char kFileW[] = "File";
    277 
    278 static char isFileHandleType(HANDLE handle) {
    279     char type = 0;
    280     ULONG size = 0;
    281     // Get the size of the type string
    282     int status = sNtQueryObjectFunc(handle, 2, NULL, 0, &size);
    283     if (status == STATUS_INFO_LENGTH_MISMATCH && size > 0) {
    284         // Get the type string itself
    285         char *buf = new char[size];
    286         status = sNtQueryObjectFunc(handle, 2, buf, size, NULL);
    287         if (status == 0 && size > 96) {
    288             // The type string we want is a wide unicode (UTF16-LE)
    289             // zero-terminated string located at offset 96 in the
    290             // buffer. In our case we want the string to be "File".
    291             //
    292             // Since we're reading wide unicode, we want each character
    293             // to be the one from our string followed by a zero byte.
    294             // e.g. c should point to F \0 i \0 l \0 e \0 \0 \0.
    295             const char *c = buf + 96;
    296             type = c[0];
    297 
    298             int len = sizeof(kFileW);
    299             const char *d = kFileW;
    300 
    301             for (; type != 0 && len > 0; c+=2, d++, len--) {
    302                 if (c[0] != *d || c[1] != 0) {
    303                     type = 0;
    304                     break;
    305                 }
    306             }
    307         }
    308 
    309         free(buf);
    310     }
    311     return type;
    312 }
    313 
    314 typedef struct {
    315     HANDLE handle;
    316     CString *outStr;
    317     bool result;
    318 } SFileNameInfo;
    319 
    320 static unsigned __stdcall FileNameThreadFunc(void *param) {
    321     SFileNameInfo *info = (SFileNameInfo *)param;
    322     if (info == NULL) {
    323         return 1;
    324     }
    325 
    326     char buf[MAX_PATH*2 + 4];
    327     DWORD iob[2] = { 0, 0 };
    328 
    329     DWORD status = sNtQueryInformationFileFunc(info->handle, iob, buf, sizeof(buf), 9);
    330     if (status == STATUS_SUCCESS) {
    331         // The result is a buffer with:
    332         // - DWORD (4 bytes) for the *byte* length (so twice the character length)
    333         // - Actual string in Unicode
    334         // Not sure of the actual type, but it does look like a UNICODE_STRING struct.
    335 
    336         DWORD len = ((DWORD *)buf)[0];
    337         if (len <= MAX_PATH * 2) {
    338             // We can't handle wide Unicode. What we do is convert it into
    339             // straight ansi by just retaining the first of each couple bytes.
    340             // Bytes that cannot be mapped (e.g. 2nd byte is != 0) will be
    341             // simply converted to 0xFF.
    342 
    343             unsigned char *dest = (unsigned char *)buf + 4;
    344             unsigned char *src  = (unsigned char *)buf + 4;
    345             for (DWORD i = 0; i < len; dest++, src += 2, i += 2) {
    346                 if (src[1] == 0) {
    347                     *dest = *src;
    348                 } else {
    349                     *dest = 0xFF;
    350                 }
    351             }
    352             *dest = '\0';
    353             info->outStr->set(buf + 4, len);
    354             info->result = true;
    355             return 0;
    356         }
    357     }
    358     return 1;
    359 }
    360 
    361 static bool getFileName(HANDLE handle, CString *outStr) {
    362     SFileNameInfo info;
    363     info.handle = handle;
    364     info.outStr = outStr;
    365     info.result = false;
    366 
    367     // sNtQueryInformationFileFunc might hang on some handles.
    368     // A trick is to do it in a thread and if it takes too loog then
    369     // just shutdown the thread, since it's deadlocked anyway.
    370     unsigned threadId;
    371     HANDLE th = (HANDLE)_beginthreadex(NULL,                    // security
    372                                        0,                       // stack_size
    373                                        &FileNameThreadFunc,     // address
    374                                        &info,                   // arglist
    375                                        0,                       // initflag
    376                                        &threadId);              // thrdaddr
    377 
    378     if (th == NULL) {
    379         // Failed to create thread. Shouldn't really happen.
    380         outStr->set("<failed to create thread>");
    381         return false;
    382     }
    383 
    384     bool result = false;
    385 
    386     // Wait for thread or kill it if it takes too long.
    387     if (WaitForSingleObject(th /*handle*/, 200 /*ms*/) == WAIT_TIMEOUT) {
    388         TerminateThread(th /*handle*/, 0 /*retCode*/);
    389         outStr->set("<timeout>");
    390     } else {
    391         result = info.result;
    392     }
    393 
    394     CloseHandle(th);
    395     return result;
    396 }
    397 
    398 // Find the name of the process (e.g. "java.exe") given its id.
    399 // processesPtr must be the list returned by getAllProcesses().
    400 // Special handling for javaw.exe: this isn't quite useful so
    401 // we also try to find and append the parent process name.
    402 static bool getProcessName(SYSTEM_PROCESS_INFORMATION *processesPtr,
    403                            DWORD remoteProcessId,
    404                            CString *outStr) {
    405     SYSTEM_PROCESS_INFORMATION *ptr = processesPtr;
    406     while (ptr != NULL) {
    407         if (ptr->dUniqueProcessId == remoteProcessId) {
    408             // This is the process we want.
    409 
    410             UNICODE_STRING *uniStr = &(ptr->usName);
    411             WORD len = uniStr->Length;
    412 
    413             char buf[MAX_PATH];
    414             if (len <= MAX_PATH * 2) {
    415                 // We can't handle wide Unicode. What we do is convert it into
    416                 // straight ansi by just retaining the first of each couple bytes.
    417                 // Bytes that cannot be mapped (e.g. 2nd byte is != 0) will be
    418                 // simply converted to 0xFF.
    419 
    420                 unsigned char *dest = (unsigned char *)buf;
    421                 unsigned char *src  = (unsigned char *)uniStr->Buffer;
    422                 for (WORD i = 0; i < len; dest++, src += 2, i += 2) {
    423                     if (src[1] == 0) {
    424                         *dest = *src;
    425                     } else {
    426                         *dest = 0xFF;
    427                     }
    428                 }
    429                 *dest = '\0';
    430                 outStr->set(buf, len);
    431 
    432                 if (strcmp(buf, "javaw.exe") == 0) {
    433                     // Heuristic: eclipse often shows up as javaw.exe
    434                     // but what is useful is to report eclipse to the user
    435                     // instead.
    436                     // So in this case, look at the parent and report it too.
    437                     DWORD parentId = ptr->dInheritedFromUniqueProcessId;
    438                     if (parentId > 0) {
    439                         CString name2;
    440                         bool ok2 = getProcessName(processesPtr,
    441                                                   parentId,
    442                                                   &name2);
    443                         if (ok2) {
    444                             outStr->add(" (");
    445                             outStr->add(name2.cstr());
    446                             outStr->add(")");
    447                         }
    448                     }
    449                 }
    450 
    451                 return true;
    452             }
    453         }
    454 
    455         // Look at the next process, if any.
    456         if (ptr->dNext == NULL) {
    457             break;
    458         } else {
    459             ptr = (SYSTEM_PROCESS_INFORMATION *)((char *)ptr + ptr->dNext);
    460         }
    461     }
    462 
    463     outStr->setf("<process id %08x name not found>", remoteProcessId);
    464     return false;
    465 }
    466 
    467 // Query system for all processes information.
    468 // Returns an error string in case of error.
    469 // Returns the virtual_alloc-allocated buffer on success or NULL on error.
    470 // It's up to the caller to do a VirtualFree on the returned buffer.
    471 static SYSTEM_PROCESS_INFORMATION *queryAllProcess(const char **error) {
    472     // Allocate a buffer for the process information. We don't know the
    473     // exact size. A normal system might typically have between 100-200 processes.
    474     // We'll resize the buffer if not big enough.
    475     DWORD infoSize = 4096;
    476     SYSTEM_PROCESS_INFORMATION *infoPtr =
    477         (SYSTEM_PROCESS_INFORMATION *) VirtualAlloc(NULL, infoSize, MEM_COMMIT, PAGE_READWRITE);
    478 
    479     if (infoPtr != NULL) {
    480         // Query the actual size needed (or the data if it fits in the buffer)
    481         DWORD needed = 0;
    482         if (sNtQuerySystemInformationFunc(
    483                 SystemProcessInformation, infoPtr, infoSize, &needed) != 0) {
    484             if (needed == 0) {
    485                 // Shouldn't happen.
    486                 *error = "No processes found";
    487                 goto bail_out;
    488             }
    489 
    490             // Realloc
    491             VirtualFree(infoPtr, 0, MEM_RELEASE);
    492             infoSize += needed;
    493             infoPtr = (SYSTEM_PROCESS_INFORMATION *) VirtualAlloc(
    494                             NULL, infoSize, MEM_COMMIT, PAGE_READWRITE);
    495 
    496             // Query all the processes objects again
    497             if (sNtQuerySystemInformationFunc(
    498                     SystemProcessInformation, infoPtr, infoSize, NULL) != 0) {
    499                 *error = "Failed to query system processes";
    500                 goto bail_out;
    501             }
    502         }
    503     }
    504 
    505     if (infoPtr == NULL) {
    506         *error = "Failed to allocate system processes info buffer";
    507         goto bail_out;
    508     }
    509 
    510 bail_out:
    511     if (*error != NULL) {
    512         VirtualFree(infoPtr, 0, MEM_RELEASE);
    513         infoPtr = NULL;
    514     }
    515     return infoPtr;
    516 }
    517 
    518 // Query system for all handle information.
    519 // Returns an error string in case of error.
    520 // Returns the virtual_alloc-allocated buffer on success or NULL on error.
    521 // It's up to the caller to do a VirtualFree on the returned buffer.
    522 static SYSTEM_HANDLE_INFORMATION *queryAllHandles(const char **error) {
    523     // Allocate a buffer. It won't be large enough to get the handles
    524     // (e.g. there might be 10k or 40k handles around). We'll resize
    525     // it once we know the actual size.
    526     DWORD infoSize = 4096;
    527     SYSTEM_HANDLE_INFORMATION *infoPtr =
    528         (SYSTEM_HANDLE_INFORMATION *) VirtualAlloc(NULL, infoSize, MEM_COMMIT, PAGE_READWRITE);
    529 
    530     if (infoPtr != NULL) {
    531         // Query the actual size needed
    532         DWORD needed = 0;
    533         if (sNtQuerySystemInformationFunc(
    534                 SystemHandleInformation, infoPtr, infoSize, &needed) != 0) {
    535             if (needed == 0) {
    536                 // Shouldn't happen.
    537                 *error = "No handles found";
    538                 goto bail_out;
    539             }
    540 
    541             // Realloc
    542             VirtualFree(infoPtr, 0, MEM_RELEASE);
    543             infoSize += needed;
    544             infoPtr = (SYSTEM_HANDLE_INFORMATION *) VirtualAlloc(
    545                             NULL, infoSize, MEM_COMMIT, PAGE_READWRITE);
    546         }
    547     }
    548 
    549     if (infoPtr == NULL) {
    550         *error = "Failed to allocate system handle info buffer";
    551         goto bail_out;
    552     }
    553 
    554     // Query all the handle objects
    555     if (sNtQuerySystemInformationFunc(SystemHandleInformation, infoPtr, infoSize, NULL) != 0) {
    556         *error = "Failed to query system handles";
    557         goto bail_out;
    558     }
    559 
    560 bail_out:
    561     if (*error != NULL) {
    562         VirtualFree(infoPtr, 0, MEM_RELEASE);
    563         infoPtr = NULL;
    564     }
    565     return infoPtr;
    566 }
    567 
    568 bool findLock(CPath &path, CString *outModule) {
    569     bool result = false;
    570     const char *error = NULL;
    571 
    572     SYSTEM_PROCESS_INFORMATION *processesPtr = NULL;
    573     SYSTEM_HANDLE_INFORMATION  *handlesPtr   = NULL;
    574 
    575     const HANDLE currProcessH = GetCurrentProcess();
    576     const DWORD currProcessId = GetCurrentProcessId();
    577     HANDLE remoteProcessH = NULL;
    578     DWORD remoteProcessId = 0;
    579     DWORD matchProcessId = 0;
    580 
    581     int numHandleFound = 0;
    582     int numHandleChecked = 0;
    583     int numHandleDirs = 0;
    584     int numHandleFiles = 0;
    585     int numProcessMatch = 0;
    586 
    587     BYTE ob_type_file = 0;
    588 
    589     // Get the path to search, without the drive letter.
    590     const char *searchPath = path.cstr();
    591     if (isalpha(searchPath[0]) && searchPath[1] == ':') {
    592         searchPath += 2;
    593     }
    594     size_t searchPathLen = strlen(searchPath);
    595 
    596     if (gIsDebug) fprintf(stderr, "Search path: '%s'\n", searchPath);
    597 
    598     if (!init()) {
    599         error = "Failed to bind to ntdll.dll";
    600         goto bail_out;
    601     }
    602 
    603     if (!adjustPrivileges()) {
    604         // We can still continue even if the privilege escalation failed.
    605         // The apparent effect is that we'll fail to query the name of
    606         // some processes, yet it will work for some of them.
    607         if (gIsDebug) fprintf(stderr, "Warning: adusting privileges failed. Continuing anyway.\n");
    608     } else {
    609         if (gIsDebug) fprintf(stderr, "Privileges adjusted.\n"); // DEBUG remove lter
    610     }
    611 
    612     processesPtr = queryAllProcess(&error);
    613     if (processesPtr == NULL) goto bail_out;
    614 
    615     handlesPtr = queryAllHandles(&error);
    616     if (handlesPtr == NULL) goto bail_out;
    617 
    618     numHandleFound = handlesPtr->dCount;
    619 
    620     // Check all the handles
    621     for (int n = handlesPtr->dCount, i = 0; i < n; i++) {
    622         SYSTEM_HANDLE sysh = handlesPtr->ash[i];
    623 
    624         if (ob_type_file != 0 && sysh.bObjectType != ob_type_file) {
    625             continue;
    626         }
    627 
    628         HANDLE handle = (HANDLE) sysh.wValue;
    629         DWORD remoteId = sysh.dIdProcess;
    630         HANDLE remoteH = NULL;
    631 
    632         if (remoteId == matchProcessId) {
    633             // We already matched that process, we can skip its other entries.
    634             continue;
    635         }
    636 
    637         if (remoteId == currProcessId) {
    638             // We don't match ourselves
    639             continue;
    640         }
    641 
    642         // Open a remote process.
    643         // Most entries of a given process seem to be consecutive, so we
    644         // only open the remote process handle if it's a different id.
    645         if (remoteProcessH == NULL && remoteId == remoteProcessId) {
    646             // We already tried to open this process and it failed.
    647             // It's not going to be any better the next time so skip it.
    648             continue;
    649         }
    650         if (remoteProcessH == NULL || remoteId != remoteProcessId) {
    651             if (remoteProcessH != NULL) {
    652                 CloseHandle(remoteProcessH);
    653             }
    654 
    655             remoteProcessId = remoteId;
    656             remoteProcessH = OpenProcess(PROCESS_DUP_HANDLE,
    657                                          FALSE /*inheritHandle*/,
    658                                          remoteProcessId);
    659             if (remoteProcessH == NULL) {
    660                 continue;
    661             }
    662         }
    663 
    664         if (remoteProcessH != NULL) {
    665             // Duplicate the remote handle
    666             if (DuplicateHandle(remoteProcessH,     // hSourceProcessHandle
    667                                 handle,             // hSourceHandle
    668                                 currProcessH,       // hTargetProcessHandle
    669                                 &remoteH,           // lpTargetHandle
    670                                 0,                  // dwDesiredAccess (ignored by same access)
    671                                 FALSE,              // bInheritHandle
    672                                 DUPLICATE_SAME_ACCESS) == 0) {
    673                 continue;
    674             }
    675         }
    676 
    677         numHandleChecked++;
    678 
    679         char type = isFileHandleType(remoteH);
    680 
    681         if (type != 0) {
    682             if (type == 'D') numHandleDirs++;
    683             else if (type == 'F') numHandleFiles++;
    684 
    685             // TODO simplify by not keeping directory handles
    686             if (ob_type_file == 0 && type == 'F') {
    687                 // We found the first file handle. Remember it's system_handle object type
    688                 // and then use it to filter the following system_handle.
    689                 // For some reason OB_TYPE_FILE should be 0x1A but empirically I find it
    690                 // to be 0x1C, so we just make this test more dynamic.
    691                 ob_type_file = sysh.bObjectType;
    692             }
    693 
    694             // Try to get a filename out of that file or directory handle.
    695             CString name("<unknown>");
    696             bool ok = getFileName(remoteH, &name);
    697 
    698             if (gIsDebug) {
    699                 fprintf(stderr, "P:%08x | t:%02x | f:%02x | v:%08x | %c | %s %s\n",
    700                     sysh.dIdProcess, sysh.bObjectType, sysh.bFlags, sysh.wValue,
    701                     type,
    702                     ok ? "OK" : "FAIL",
    703                     name.cstr()
    704                     );
    705             }
    706 
    707             if (ok) {
    708                 // We got a file path. Let's check if it matches our target path.
    709                 if (_strnicmp(searchPath, name.cstr(), searchPathLen) == 0) {
    710                     // Remember this process id so that we can ignore all its following entries.
    711                     matchProcessId = remoteId;
    712 
    713                     // Find out its process name
    714                     CString procName("<unknown>");
    715                     ok = getProcessName(processesPtr, remoteProcessId, &procName);
    716                     if (ok) {
    717                         numProcessMatch++;
    718 
    719                         if (!outModule->isEmpty()) {
    720                             outModule->add(";");
    721                         }
    722                         outModule->add(procName.cstr());
    723                         result = true;
    724                     }
    725 
    726                     if (gIsDebug) {
    727                         fprintf(stderr, "==> MATCH FOUND: %s  %s\n",
    728                             ok ? "OK" : "FAIL",
    729                             procName.cstr()
    730                             );
    731                     }
    732                 }
    733             }
    734 
    735         }
    736 
    737         if (remoteH != NULL) {
    738             CloseHandle(remoteH);
    739             remoteH = NULL;
    740         }
    741     }
    742 
    743 bail_out:
    744 
    745     if (gIsDebug) {
    746         fprintf(stderr, "Processes matched: %d\n", numProcessMatch);
    747         fprintf(stderr, "Handles: %d found, %d checked, %d dirs, %d files\n",
    748                numHandleFound,
    749                numHandleChecked,
    750                numHandleDirs,
    751                numHandleFiles);
    752     }
    753 
    754     if (error != NULL) {
    755         CString msg;
    756         msg.setLastWin32Error(NULL);
    757         if (gIsDebug) fprintf(stderr, "[ERROR] %s: %s", error, msg.cstr());
    758     }
    759 
    760     if (remoteProcessH != NULL) {
    761         CloseHandle(remoteProcessH);
    762     }
    763 
    764     if (currProcessH != NULL) {
    765         CloseHandle(currProcessH);
    766     }
    767 
    768     if (handlesPtr != NULL) {
    769         VirtualFree(handlesPtr, 0, MEM_RELEASE);
    770         handlesPtr = NULL;
    771     }
    772 
    773     terminate();
    774 
    775     return result;
    776 }
    777 
    778 #endif /* _WIN32 */
    779