summaryrefslogtreecommitdiff
path: root/psutil/arch/windows/proc_handles.c
diff options
context:
space:
mode:
Diffstat (limited to 'psutil/arch/windows/proc_handles.c')
-rw-r--r--psutil/arch/windows/proc_handles.c292
1 files changed, 292 insertions, 0 deletions
diff --git a/psutil/arch/windows/proc_handles.c b/psutil/arch/windows/proc_handles.c
new file mode 100644
index 00000000..30e7cd2d
--- /dev/null
+++ b/psutil/arch/windows/proc_handles.c
@@ -0,0 +1,292 @@
+/*
+ * Copyright (c) 2009, Giampaolo Rodola'. All rights reserved.
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+/*
+ * This module retrieves handles opened by a process.
+ * We use NtQuerySystemInformation to enumerate them and NtQueryObject
+ * to obtain the corresponding file name.
+ * Since NtQueryObject hangs for certain handle types we call it in a
+ * separate thread which gets killed if it doesn't complete within 100ms.
+ * This is a limitation of the Windows API and ProcessHacker uses the
+ * same trick: https://github.com/giampaolo/psutil/pull/597
+ *
+ * CREDITS: original implementation was written by Jeff Tang.
+ * It was then rewritten by Giampaolo Rodola many years later.
+ * Utility functions for getting the file handles and names were re-adapted
+ * from the excellent ProcessHacker.
+ */
+
+#include <windows.h>
+#include <Python.h>
+
+#include "../../_psutil_common.h"
+#include "proc_utils.h"
+
+
+#define THREAD_TIMEOUT 100 // ms
+// Global object shared between the 2 threads.
+PUNICODE_STRING globalFileName = NULL;
+
+
+static int
+psutil_enum_handles(PSYSTEM_HANDLE_INFORMATION_EX *handles) {
+ static ULONG initialBufferSize = 0x10000;
+ NTSTATUS status;
+ PVOID buffer;
+ ULONG bufferSize;
+
+ bufferSize = initialBufferSize;
+ buffer = MALLOC_ZERO(bufferSize);
+ if (buffer == NULL) {
+ PyErr_NoMemory();
+ return 1;
+ }
+
+ while ((status = NtQuerySystemInformation(
+ SystemExtendedHandleInformation,
+ buffer,
+ bufferSize,
+ NULL
+ )) == STATUS_INFO_LENGTH_MISMATCH)
+ {
+ FREE(buffer);
+ bufferSize *= 2;
+
+ // Fail if we're resizing the buffer to something very large.
+ if (bufferSize > 256 * 1024 * 1024) {
+ PyErr_SetString(
+ PyExc_RuntimeError,
+ "SystemExtendedHandleInformation buffer too big");
+ return 1;
+ }
+
+ buffer = MALLOC_ZERO(bufferSize);
+ if (buffer == NULL) {
+ PyErr_NoMemory();
+ return 1;
+ }
+ }
+
+ if (! NT_SUCCESS(status)) {
+ psutil_SetFromNTStatusErr(status, "NtQuerySystemInformation");
+ FREE(buffer);
+ return 1;
+ }
+
+ *handles = (PSYSTEM_HANDLE_INFORMATION_EX)buffer;
+ return 0;
+}
+
+
+static int
+psutil_get_filename(LPVOID lpvParam) {
+ HANDLE hFile = *((HANDLE*)lpvParam);
+ NTSTATUS status;
+ ULONG bufferSize;
+ ULONG attempts = 8;
+
+ bufferSize = 0x200;
+ globalFileName = MALLOC_ZERO(bufferSize);
+ if (globalFileName == NULL) {
+ PyErr_NoMemory();
+ goto error;
+ }
+
+
+ // Note: also this is supposed to hang, hence why we do it in here.
+ if (GetFileType(hFile) != FILE_TYPE_DISK) {
+ SetLastError(0);
+ globalFileName->Length = 0;
+ return 0;
+ }
+
+ // A loop is needed because the I/O subsystem likes to give us the
+ // wrong return lengths...
+ do {
+ status = NtQueryObject(
+ hFile,
+ ObjectNameInformation,
+ globalFileName,
+ bufferSize,
+ &bufferSize
+ );
+ if (status == STATUS_BUFFER_OVERFLOW ||
+ status == STATUS_INFO_LENGTH_MISMATCH ||
+ status == STATUS_BUFFER_TOO_SMALL)
+ {
+ FREE(globalFileName);
+ globalFileName = MALLOC_ZERO(bufferSize);
+ if (globalFileName == NULL) {
+ PyErr_NoMemory();
+ goto error;
+ }
+ }
+ else {
+ break;
+ }
+ } while (--attempts);
+
+ if (! NT_SUCCESS(status)) {
+ psutil_SetFromNTStatusErr(status, "NtQuerySystemInformation");
+ FREE(globalFileName);
+ globalFileName = NULL;
+ return 1;
+ }
+
+ return 0;
+
+error:
+ if (globalFileName != NULL) {
+ FREE(globalFileName);
+ globalFileName = NULL;
+ }
+ return 1;
+}
+
+
+static DWORD
+psutil_threaded_get_filename(HANDLE hFile) {
+ DWORD dwWait;
+ HANDLE hThread;
+ DWORD threadRetValue;
+
+ hThread = CreateThread(
+ NULL, 0, (LPTHREAD_START_ROUTINE)psutil_get_filename, &hFile, 0, NULL);
+ if (hThread == NULL) {
+ PyErr_SetFromOSErrnoWithSyscall("CreateThread");
+ return 1;
+ }
+
+ // Wait for the worker thread to finish.
+ dwWait = WaitForSingleObject(hThread, THREAD_TIMEOUT);
+
+ // If the thread hangs, kill it and cleanup.
+ if (dwWait == WAIT_TIMEOUT) {
+ psutil_debug(
+ "get handle name thread timed out after %i ms", THREAD_TIMEOUT);
+ if (TerminateThread(hThread, 0) == 0) {
+ PyErr_SetFromOSErrnoWithSyscall("TerminateThread");
+ CloseHandle(hThread);
+ return 1;
+ }
+ CloseHandle(hThread);
+ return 0;
+ }
+
+ if (dwWait == WAIT_FAILED) {
+ psutil_debug("WaitForSingleObject -> WAIT_FAILED");
+ if (TerminateThread(hThread, 0) == 0) {
+ PyErr_SetFromOSErrnoWithSyscall(
+ "WaitForSingleObject -> WAIT_FAILED -> TerminateThread");
+ CloseHandle(hThread);
+ return 1;
+ }
+ PyErr_SetFromOSErrnoWithSyscall("WaitForSingleObject");
+ CloseHandle(hThread);
+ return 1;
+ }
+
+ if (GetExitCodeThread(hThread, &threadRetValue) == 0) {
+ if (TerminateThread(hThread, 0) == 0) {
+ PyErr_SetFromOSErrnoWithSyscall(
+ "GetExitCodeThread (failed) -> TerminateThread");
+ CloseHandle(hThread);
+ return 1;
+ }
+
+ CloseHandle(hThread);
+ PyErr_SetFromOSErrnoWithSyscall("GetExitCodeThread");
+ return 1;
+ }
+ CloseHandle(hThread);
+ return threadRetValue;
+}
+
+
+PyObject *
+psutil_get_open_files(DWORD dwPid, HANDLE hProcess) {
+ PSYSTEM_HANDLE_INFORMATION_EX handlesList = NULL;
+ PSYSTEM_HANDLE_TABLE_ENTRY_INFO_EX hHandle = NULL;
+ HANDLE hFile = NULL;
+ ULONG i = 0;
+ BOOLEAN errorOccurred = FALSE;
+ PyObject* py_path = NULL;
+ PyObject* py_retlist = PyList_New(0);;
+
+ if (!py_retlist)
+ return NULL;
+
+ // Due to the use of global variables, ensure only 1 call
+ // to psutil_get_open_files() is running.
+ EnterCriticalSection(&PSUTIL_CRITICAL_SECTION);
+
+ if (psutil_enum_handles(&handlesList) != 0)
+ goto error;
+
+ for (i = 0; i < handlesList->NumberOfHandles; i++) {
+ hHandle = &handlesList->Handles[i];
+ if ((ULONG_PTR)hHandle->UniqueProcessId != dwPid)
+ continue;
+ if (! DuplicateHandle(
+ hProcess,
+ hHandle->HandleValue,
+ GetCurrentProcess(),
+ &hFile,
+ 0,
+ TRUE,
+ DUPLICATE_SAME_ACCESS))
+ {
+ // Will fail if not a regular file; just skip it.
+ continue;
+ }
+
+ // This will set *globalFileName* global variable.
+ if (psutil_threaded_get_filename(hFile) != 0)
+ goto error;
+
+ if ((globalFileName != NULL) && (globalFileName->Length > 0)) {
+ py_path = PyUnicode_FromWideChar(globalFileName->Buffer,
+ wcslen(globalFileName->Buffer));
+ if (! py_path)
+ goto error;
+ if (PyList_Append(py_retlist, py_path))
+ goto error;
+ Py_CLEAR(py_path); // also sets to NULL
+ }
+
+ // Loop cleanup section.
+ if (globalFileName != NULL) {
+ FREE(globalFileName);
+ globalFileName = NULL;
+ }
+ CloseHandle(hFile);
+ hFile = NULL;
+ }
+
+ goto exit;
+
+error:
+ Py_XDECREF(py_retlist);
+ errorOccurred = TRUE;
+ goto exit;
+
+exit:
+ if (hFile != NULL)
+ CloseHandle(hFile);
+ if (globalFileName != NULL) {
+ FREE(globalFileName);
+ globalFileName = NULL;
+ }
+ if (py_path != NULL)
+ Py_DECREF(py_path);
+ if (handlesList != NULL)
+ FREE(handlesList);
+
+ LeaveCriticalSection(&PSUTIL_CRITICAL_SECTION);
+ if (errorOccurred == TRUE)
+ return NULL;
+ return py_retlist;
+}