Skip to content

Commit

Permalink
Use GetFinalPathNameByHandle for Process.open_files
Browse files Browse the repository at this point in the history
Fixes #1967
  • Loading branch information
jschwartzentruber committed Jan 7, 2023
1 parent c6b7145 commit d68583a
Show file tree
Hide file tree
Showing 6 changed files with 43 additions and 177 deletions.
4 changes: 0 additions & 4 deletions psutil/_psutil_common.c
Original file line number Diff line number Diff line change
Expand Up @@ -270,10 +270,6 @@ psutil_loadlibs() {
"ntdll.dll", "NtSetInformationProcess");
if (! NtSetInformationProcess)
return 1;
NtQueryObject = psutil_GetProcAddressFromLib(
"ntdll.dll", "NtQueryObject");
if (! NtQueryObject)
return 1;
RtlIpv4AddressToStringA = psutil_GetProcAddressFromLib(
"ntdll.dll", "RtlIpv4AddressToStringA");
if (! RtlIpv4AddressToStringA)
Expand Down
1 change: 0 additions & 1 deletion psutil/_psutil_windows.c
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
* other modules:
* - NtQuerySystemInformation
* - NtQueryInformationProcess
* - NtQueryObject
* - NtSuspendProcess
* - NtResumeProcess
*/
Expand Down
9 changes: 0 additions & 9 deletions psutil/arch/windows/ntextapi.h
Original file line number Diff line number Diff line change
Expand Up @@ -660,15 +660,6 @@ ULONGLONG (CALLBACK *_GetTickCount64) (

#define GetTickCount64 _GetTickCount64

NTSTATUS (NTAPI *_NtQueryObject) (
HANDLE Handle,
OBJECT_INFORMATION_CLASS ObjectInformationClass,
PVOID ObjectInformation,
ULONG ObjectInformationLength,
PULONG ReturnLength);

#define NtQueryObject _NtQueryObject

NTSTATUS (WINAPI *_RtlGetVersion) (
PRTL_OSVERSIONINFOW lpVersionInformation
);
Expand Down
203 changes: 41 additions & 162 deletions psutil/arch/windows/process_handles.c
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,6 @@

/*
* 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.
Expand All @@ -23,12 +17,7 @@
#include <Python.h>

#include "../../_psutil_common.h"
#include "process_utils.h"


#define THREAD_TIMEOUT 100 // ms
// Global object shared between the 2 threads.
PUNICODE_STRING globalFileName = NULL;
#include "process_info.h"


static int
Expand Down Expand Up @@ -81,151 +70,32 @@ psutil_enum_handles(PSYSTEM_HANDLE_INFORMATION_EX *handles) {
}


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) {
static ULONG fileNameBufferSize = (
sizeof(WCHAR) * (_MAX_PATH + 1));
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);;
PyObject* py_retlist = PyList_New(0);
WCHAR* lpFileName = NULL;
ULONG status = 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;

lpFileName = MALLOC_ZERO(fileNameBufferSize);
if (!lpFileName) {
PyErr_NoMemory();
goto error;
}

for (i = 0; i < handlesList->NumberOfHandles; i++) {
hHandle = &handlesList->Handles[i];
if ((ULONG_PTR)hHandle->UniqueProcessId != dwPid)
Expand All @@ -243,25 +113,37 @@ psutil_get_open_files(DWORD dwPid, HANDLE hProcess) {
continue;
}

// This will set *globalFileName* global variable.
if (psutil_threaded_get_filename(hFile) != 0)
goto error;
if (GetFileType(hFile) != FILE_TYPE_DISK) {
goto cleanup_continue;
}

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
// Get the filename
if (! GetFinalPathNameByHandleW(
hFile,
lpFileName,
fileNameBufferSize,
VOLUME_NAME_NT))
{
status = GetLastError();
// Ignore some normal errors
if (status == ERROR_INVALID_FUNCTION) {
SetLastError(0);
goto cleanup_continue;
}
psutil_convert_winerr(status, "GetFinalPathNameByHandle");
goto error;
}

py_path = PyUnicode_FromWideChar(lpFileName,
wcslen(lpFileName));
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;
}
cleanup_continue:
CloseHandle(hFile);
hFile = NULL;
}
Expand All @@ -276,16 +158,13 @@ psutil_get_open_files(DWORD dwPid, HANDLE hProcess) {
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);
if (lpFileName != NULL)
FREE(lpFileName);

LeaveCriticalSection(&PSUTIL_CRITICAL_SECTION);
if (errorOccurred == TRUE)
return NULL;
return py_retlist;
Expand Down
2 changes: 1 addition & 1 deletion psutil/arch/windows/process_info.c
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ enum psutil_process_data_kind {
};


static void
void
psutil_convert_winerr(ULONG err, char* syscall) {
char fullmsg[8192];

Expand Down
1 change: 1 addition & 0 deletions psutil/arch/windows/process_info.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@

int psutil_get_proc_info(DWORD pid, PSYSTEM_PROCESS_INFORMATION *retProcess,
PVOID *retBuffer);
void psutil_convert_winerr(ULONG err, char* syscall);
PyObject* psutil_get_cmdline(DWORD pid, int use_peb);
PyObject* psutil_get_cwd(DWORD pid);
PyObject* psutil_get_environ(DWORD pid);
Expand Down

0 comments on commit d68583a

Please sign in to comment.