summaryrefslogtreecommitdiff
path: root/psutil/arch/windows/proc.c
diff options
context:
space:
mode:
Diffstat (limited to 'psutil/arch/windows/proc.c')
-rw-r--r--psutil/arch/windows/proc.c1238
1 files changed, 1238 insertions, 0 deletions
diff --git a/psutil/arch/windows/proc.c b/psutil/arch/windows/proc.c
new file mode 100644
index 00000000..fb5f7bb9
--- /dev/null
+++ b/psutil/arch/windows/proc.c
@@ -0,0 +1,1238 @@
+/*
+ * 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.
+ */
+
+// Fixes clash between winsock2.h and windows.h
+#define WIN32_LEAN_AND_MEAN
+
+#include <Python.h>
+#include <windows.h>
+#include <Psapi.h> // memory_info(), memory_maps()
+#include <signal.h>
+#include <tlhelp32.h> // threads(), PROCESSENTRY32
+
+// Link with Iphlpapi.lib
+#pragma comment(lib, "IPHLPAPI.lib")
+
+#include "../../_psutil_common.h"
+#include "proc.h"
+#include "process_info.h"
+#include "process_handles.h"
+#include "process_utils.h"
+
+
+// Raised by Process.wait().
+PyObject *TimeoutExpired;
+PyObject *TimeoutAbandoned;
+
+
+/*
+ * Return 1 if PID exists in the current process list, else 0.
+ */
+PyObject *
+psutil_pid_exists(PyObject *self, PyObject *args) {
+ DWORD pid;
+ int status;
+
+ if (! PyArg_ParseTuple(args, _Py_PARSE_PID, &pid))
+ return NULL;
+
+ status = psutil_pid_is_running(pid);
+ if (-1 == status)
+ return NULL; // exception raised in psutil_pid_is_running()
+ return PyBool_FromLong(status);
+}
+
+
+/*
+ * Return a Python list of all the PIDs running on the system.
+ */
+PyObject *
+psutil_pids(PyObject *self, PyObject *args) {
+ DWORD *proclist = NULL;
+ DWORD numberOfReturnedPIDs;
+ DWORD i;
+ PyObject *py_pid = NULL;
+ PyObject *py_retlist = PyList_New(0);
+
+ if (py_retlist == NULL)
+ return NULL;
+ proclist = psutil_get_pids(&numberOfReturnedPIDs);
+ if (proclist == NULL)
+ goto error;
+
+ for (i = 0; i < numberOfReturnedPIDs; i++) {
+ py_pid = PyLong_FromPid(proclist[i]);
+ if (!py_pid)
+ goto error;
+ if (PyList_Append(py_retlist, py_pid))
+ goto error;
+ Py_CLEAR(py_pid);
+ }
+
+ // free C array allocated for PIDs
+ free(proclist);
+ return py_retlist;
+
+error:
+ Py_XDECREF(py_pid);
+ Py_DECREF(py_retlist);
+ if (proclist != NULL)
+ free(proclist);
+ return NULL;
+}
+
+
+/*
+ * Kill a process given its PID.
+ */
+PyObject *
+psutil_proc_kill(PyObject *self, PyObject *args) {
+ HANDLE hProcess;
+ DWORD pid;
+
+ if (! PyArg_ParseTuple(args, _Py_PARSE_PID, &pid))
+ return NULL;
+ if (pid == 0)
+ return AccessDenied("automatically set for PID 0");
+
+ hProcess = OpenProcess(PROCESS_TERMINATE, FALSE, pid);
+ hProcess = psutil_check_phandle(hProcess, pid, 0);
+ if (hProcess == NULL) {
+ return NULL;
+ }
+
+ if (! TerminateProcess(hProcess, SIGTERM)) {
+ // ERROR_ACCESS_DENIED may happen if the process already died. See:
+ // https://github.com/giampaolo/psutil/issues/1099
+ // http://bugs.python.org/issue14252
+ if (GetLastError() != ERROR_ACCESS_DENIED) {
+ PyErr_SetFromOSErrnoWithSyscall("TerminateProcess");
+ return NULL;
+ }
+ }
+
+ CloseHandle(hProcess);
+ Py_RETURN_NONE;
+}
+
+
+/*
+ * Wait for process to terminate and return its exit code.
+ */
+PyObject *
+psutil_proc_wait(PyObject *self, PyObject *args) {
+ HANDLE hProcess;
+ DWORD ExitCode;
+ DWORD retVal;
+ DWORD pid;
+ long timeout;
+
+ if (! PyArg_ParseTuple(args, _Py_PARSE_PID "l", &pid, &timeout))
+ return NULL;
+ if (pid == 0)
+ return AccessDenied("automatically set for PID 0");
+
+ hProcess = OpenProcess(SYNCHRONIZE | PROCESS_QUERY_INFORMATION,
+ FALSE, pid);
+ if (hProcess == NULL) {
+ if (GetLastError() == ERROR_INVALID_PARAMETER) {
+ // no such process; we do not want to raise NSP but
+ // return None instead.
+ Py_RETURN_NONE;
+ }
+ else {
+ PyErr_SetFromOSErrnoWithSyscall("OpenProcess");
+ return NULL;
+ }
+ }
+
+ // wait until the process has terminated
+ Py_BEGIN_ALLOW_THREADS
+ retVal = WaitForSingleObject(hProcess, timeout);
+ Py_END_ALLOW_THREADS
+
+ // handle return code
+ if (retVal == WAIT_FAILED) {
+ PyErr_SetFromOSErrnoWithSyscall("WaitForSingleObject");
+ CloseHandle(hProcess);
+ return NULL;
+ }
+ if (retVal == WAIT_TIMEOUT) {
+ PyErr_SetString(TimeoutExpired,
+ "WaitForSingleObject() returned WAIT_TIMEOUT");
+ CloseHandle(hProcess);
+ return NULL;
+ }
+ if (retVal == WAIT_ABANDONED) {
+ psutil_debug("WaitForSingleObject() -> WAIT_ABANDONED");
+ PyErr_SetString(TimeoutAbandoned,
+ "WaitForSingleObject() returned WAIT_ABANDONED");
+ CloseHandle(hProcess);
+ return NULL;
+ }
+
+ // WaitForSingleObject() returned WAIT_OBJECT_0. It means the
+ // process is gone so we can get its process exit code. The PID
+ // may still stick around though but we'll handle that from Python.
+ if (GetExitCodeProcess(hProcess, &ExitCode) == 0) {
+ PyErr_SetFromOSErrnoWithSyscall("GetExitCodeProcess");
+ CloseHandle(hProcess);
+ return NULL;
+ }
+
+ CloseHandle(hProcess);
+
+#if PY_MAJOR_VERSION >= 3
+ return PyLong_FromLong((long) ExitCode);
+#else
+ return PyInt_FromLong((long) ExitCode);
+#endif
+}
+
+
+/*
+ * Return a Python tuple (user_time, kernel_time)
+ */
+PyObject *
+psutil_proc_times(PyObject *self, PyObject *args) {
+ DWORD pid;
+ HANDLE hProcess;
+ FILETIME ftCreate, ftExit, ftKernel, ftUser;
+
+ if (! PyArg_ParseTuple(args, _Py_PARSE_PID, &pid))
+ return NULL;
+
+ hProcess = psutil_handle_from_pid(pid, PROCESS_QUERY_LIMITED_INFORMATION);
+
+ if (hProcess == NULL)
+ return NULL;
+ if (! GetProcessTimes(hProcess, &ftCreate, &ftExit, &ftKernel, &ftUser)) {
+ if (GetLastError() == ERROR_ACCESS_DENIED) {
+ // usually means the process has died so we throw a NoSuchProcess
+ // here
+ NoSuchProcess("GetProcessTimes -> ERROR_ACCESS_DENIED");
+ }
+ else {
+ PyErr_SetFromWindowsErr(0);
+ }
+ CloseHandle(hProcess);
+ return NULL;
+ }
+
+ CloseHandle(hProcess);
+
+ /*
+ * User and kernel times are represented as a FILETIME structure
+ * which contains a 64-bit value representing the number of
+ * 100-nanosecond intervals since January 1, 1601 (UTC):
+ * http://msdn.microsoft.com/en-us/library/ms724284(VS.85).aspx
+ * To convert it into a float representing the seconds that the
+ * process has executed in user/kernel mode I borrowed the code
+ * below from Python's Modules/posixmodule.c
+ */
+ return Py_BuildValue(
+ "(ddd)",
+ (double)(ftUser.dwHighDateTime * HI_T + \
+ ftUser.dwLowDateTime * LO_T),
+ (double)(ftKernel.dwHighDateTime * HI_T + \
+ ftKernel.dwLowDateTime * LO_T),
+ psutil_FiletimeToUnixTime(ftCreate)
+ );
+}
+
+
+/*
+ * Return process executable path. Works for all processes regardless of
+ * privilege. NtQuerySystemInformation has some sort of internal cache,
+ * since it succeeds even when a process is gone (but not if a PID never
+ * existed).
+ */
+PyObject *
+psutil_proc_exe(PyObject *self, PyObject *args) {
+ DWORD pid;
+ NTSTATUS status;
+ PVOID buffer = NULL;
+ ULONG bufferSize = 0x104 * 2; // WIN_MAX_PATH * sizeof(wchar_t)
+ SYSTEM_PROCESS_ID_INFORMATION processIdInfo;
+ PyObject *py_exe;
+
+ if (! PyArg_ParseTuple(args, _Py_PARSE_PID, &pid))
+ return NULL;
+
+ if (pid == 0)
+ return AccessDenied("automatically set for PID 0");
+
+ buffer = MALLOC_ZERO(bufferSize);
+ if (! buffer) {
+ PyErr_NoMemory();
+ return NULL;
+ }
+
+ processIdInfo.ProcessId = (HANDLE)(ULONG_PTR)pid;
+ processIdInfo.ImageName.Length = 0;
+ processIdInfo.ImageName.MaximumLength = (USHORT)bufferSize;
+ processIdInfo.ImageName.Buffer = buffer;
+
+ status = NtQuerySystemInformation(
+ SystemProcessIdInformation,
+ &processIdInfo,
+ sizeof(SYSTEM_PROCESS_ID_INFORMATION),
+ NULL);
+
+ if ((status == STATUS_INFO_LENGTH_MISMATCH) &&
+ (processIdInfo.ImageName.MaximumLength <= bufferSize))
+ {
+ // Required length was NOT stored in MaximumLength (WOW64 issue).
+ ULONG maxBufferSize = 0x7FFF * 2; // NTFS_MAX_PATH * sizeof(wchar_t)
+ do {
+ // Iteratively double the size of the buffer up to maxBufferSize
+ bufferSize *= 2;
+ FREE(buffer);
+ buffer = MALLOC_ZERO(bufferSize);
+ if (! buffer) {
+ PyErr_NoMemory();
+ return NULL;
+ }
+
+ processIdInfo.ImageName.MaximumLength = (USHORT)bufferSize;
+ processIdInfo.ImageName.Buffer = buffer;
+
+ status = NtQuerySystemInformation(
+ SystemProcessIdInformation,
+ &processIdInfo,
+ sizeof(SYSTEM_PROCESS_ID_INFORMATION),
+ NULL);
+ } while ((status == STATUS_INFO_LENGTH_MISMATCH) &&
+ (bufferSize <= maxBufferSize));
+ }
+ else if (status == STATUS_INFO_LENGTH_MISMATCH) {
+ // Required length is stored in MaximumLength.
+ FREE(buffer);
+ buffer = MALLOC_ZERO(processIdInfo.ImageName.MaximumLength);
+ if (! buffer) {
+ PyErr_NoMemory();
+ return NULL;
+ }
+
+ processIdInfo.ImageName.Buffer = buffer;
+
+ status = NtQuerySystemInformation(
+ SystemProcessIdInformation,
+ &processIdInfo,
+ sizeof(SYSTEM_PROCESS_ID_INFORMATION),
+ NULL);
+ }
+
+ if (! NT_SUCCESS(status)) {
+ FREE(buffer);
+ if (psutil_pid_is_running(pid) == 0)
+ NoSuchProcess("psutil_pid_is_running -> 0");
+ else
+ psutil_SetFromNTStatusErr(status, "NtQuerySystemInformation");
+ return NULL;
+ }
+
+ if (processIdInfo.ImageName.Buffer == NULL) {
+ // Happens for PID 4.
+ py_exe = Py_BuildValue("s", "");
+ }
+ else {
+ py_exe = PyUnicode_FromWideChar(processIdInfo.ImageName.Buffer,
+ processIdInfo.ImageName.Length / 2);
+ }
+ FREE(buffer);
+ return py_exe;
+}
+
+
+/*
+ * Return process memory information as a Python tuple.
+ */
+PyObject *
+psutil_proc_memory_info(PyObject *self, PyObject *args) {
+ HANDLE hProcess;
+ DWORD pid;
+ PROCESS_MEMORY_COUNTERS_EX cnt;
+
+ if (! PyArg_ParseTuple(args, _Py_PARSE_PID, &pid))
+ return NULL;
+
+ hProcess = psutil_handle_from_pid(pid, PROCESS_QUERY_LIMITED_INFORMATION);
+ if (NULL == hProcess)
+ return NULL;
+
+ if (! GetProcessMemoryInfo(hProcess, (PPROCESS_MEMORY_COUNTERS)&cnt,
+ sizeof(cnt))) {
+ PyErr_SetFromWindowsErr(0);
+ CloseHandle(hProcess);
+ return NULL;
+ }
+ CloseHandle(hProcess);
+
+ // PROCESS_MEMORY_COUNTERS values are defined as SIZE_T which on 64bits
+ // is an (unsigned long long) and on 32bits is an (unsigned int).
+ // "_WIN64" is defined if we're running a 64bit Python interpreter not
+ // exclusively if the *system* is 64bit.
+#if defined(_WIN64)
+ return Py_BuildValue(
+ "(kKKKKKKKKK)",
+ cnt.PageFaultCount, // unsigned long
+ (unsigned long long)cnt.PeakWorkingSetSize,
+ (unsigned long long)cnt.WorkingSetSize,
+ (unsigned long long)cnt.QuotaPeakPagedPoolUsage,
+ (unsigned long long)cnt.QuotaPagedPoolUsage,
+ (unsigned long long)cnt.QuotaPeakNonPagedPoolUsage,
+ (unsigned long long)cnt.QuotaNonPagedPoolUsage,
+ (unsigned long long)cnt.PagefileUsage,
+ (unsigned long long)cnt.PeakPagefileUsage,
+ (unsigned long long)cnt.PrivateUsage);
+#else
+ return Py_BuildValue(
+ "(kIIIIIIIII)",
+ cnt.PageFaultCount, // unsigned long
+ (unsigned int)cnt.PeakWorkingSetSize,
+ (unsigned int)cnt.WorkingSetSize,
+ (unsigned int)cnt.QuotaPeakPagedPoolUsage,
+ (unsigned int)cnt.QuotaPagedPoolUsage,
+ (unsigned int)cnt.QuotaPeakNonPagedPoolUsage,
+ (unsigned int)cnt.QuotaNonPagedPoolUsage,
+ (unsigned int)cnt.PagefileUsage,
+ (unsigned int)cnt.PeakPagefileUsage,
+ (unsigned int)cnt.PrivateUsage);
+#endif
+}
+
+
+static int
+psutil_GetProcWsetInformation(
+ DWORD pid,
+ HANDLE hProcess,
+ PMEMORY_WORKING_SET_INFORMATION *wSetInfo)
+{
+ NTSTATUS status;
+ PVOID buffer;
+ SIZE_T bufferSize;
+
+ bufferSize = 0x8000;
+ buffer = MALLOC_ZERO(bufferSize);
+ if (! buffer) {
+ PyErr_NoMemory();
+ return 1;
+ }
+
+ while ((status = NtQueryVirtualMemory(
+ hProcess,
+ NULL,
+ MemoryWorkingSetInformation,
+ 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,
+ "NtQueryVirtualMemory bufsize is too large");
+ return 1;
+ }
+ buffer = MALLOC_ZERO(bufferSize);
+ if (! buffer) {
+ PyErr_NoMemory();
+ return 1;
+ }
+ }
+
+ if (!NT_SUCCESS(status)) {
+ if (status == STATUS_ACCESS_DENIED) {
+ AccessDenied("NtQueryVirtualMemory -> STATUS_ACCESS_DENIED");
+ }
+ else if (psutil_pid_is_running(pid) == 0) {
+ NoSuchProcess("psutil_pid_is_running -> 0");
+ }
+ else {
+ PyErr_Clear();
+ psutil_SetFromNTStatusErr(
+ status, "NtQueryVirtualMemory(MemoryWorkingSetInformation)");
+ }
+ HeapFree(GetProcessHeap(), 0, buffer);
+ return 1;
+ }
+
+ *wSetInfo = (PMEMORY_WORKING_SET_INFORMATION)buffer;
+ return 0;
+}
+
+
+/*
+ * Returns the USS of the process.
+ * Reference:
+ * https://dxr.mozilla.org/mozilla-central/source/xpcom/base/
+ * nsMemoryReporterManager.cpp
+ */
+PyObject *
+psutil_proc_memory_uss(PyObject *self, PyObject *args) {
+ DWORD pid;
+ HANDLE hProcess;
+ PSUTIL_PROCESS_WS_COUNTERS wsCounters;
+ PMEMORY_WORKING_SET_INFORMATION wsInfo;
+ ULONG_PTR i;
+
+ if (! PyArg_ParseTuple(args, _Py_PARSE_PID, &pid))
+ return NULL;
+ hProcess = psutil_handle_from_pid(pid, PROCESS_QUERY_INFORMATION);
+ if (hProcess == NULL)
+ return NULL;
+
+ if (psutil_GetProcWsetInformation(pid, hProcess, &wsInfo) != 0) {
+ CloseHandle(hProcess);
+ return NULL;
+ }
+ memset(&wsCounters, 0, sizeof(PSUTIL_PROCESS_WS_COUNTERS));
+
+ for (i = 0; i < wsInfo->NumberOfEntries; i++) {
+ // This is what ProcessHacker does.
+ /*
+ wsCounters.NumberOfPages++;
+ if (wsInfo->WorkingSetInfo[i].ShareCount > 1)
+ wsCounters.NumberOfSharedPages++;
+ if (wsInfo->WorkingSetInfo[i].ShareCount == 0)
+ wsCounters.NumberOfPrivatePages++;
+ if (wsInfo->WorkingSetInfo[i].Shared)
+ wsCounters.NumberOfShareablePages++;
+ */
+
+ // This is what we do: count shared pages that only one process
+ // is using as private (USS).
+ if (!wsInfo->WorkingSetInfo[i].Shared ||
+ wsInfo->WorkingSetInfo[i].ShareCount <= 1) {
+ wsCounters.NumberOfPrivatePages++;
+ }
+ }
+
+ HeapFree(GetProcessHeap(), 0, wsInfo);
+ CloseHandle(hProcess);
+
+ return Py_BuildValue("I", wsCounters.NumberOfPrivatePages);
+}
+
+
+/*
+ * Resume or suspends a process
+ */
+PyObject *
+psutil_proc_suspend_or_resume(PyObject *self, PyObject *args) {
+ DWORD pid;
+ NTSTATUS status;
+ HANDLE hProcess;
+ PyObject* suspend;
+
+ if (! PyArg_ParseTuple(args, _Py_PARSE_PID "O", &pid, &suspend))
+ return NULL;
+
+ hProcess = psutil_handle_from_pid(pid, PROCESS_SUSPEND_RESUME);
+ if (hProcess == NULL)
+ return NULL;
+
+ if (PyObject_IsTrue(suspend))
+ status = NtSuspendProcess(hProcess);
+ else
+ status = NtResumeProcess(hProcess);
+
+ if (! NT_SUCCESS(status)) {
+ CloseHandle(hProcess);
+ return psutil_SetFromNTStatusErr(status, "NtSuspend|ResumeProcess");
+ }
+
+ CloseHandle(hProcess);
+ Py_RETURN_NONE;
+}
+
+
+PyObject *
+psutil_proc_threads(PyObject *self, PyObject *args) {
+ HANDLE hThread = NULL;
+ THREADENTRY32 te32 = {0};
+ DWORD pid;
+ int pid_return;
+ int rc;
+ FILETIME ftDummy, ftKernel, ftUser;
+ HANDLE hThreadSnap = NULL;
+ PyObject *py_tuple = NULL;
+ PyObject *py_retlist = PyList_New(0);
+
+ if (py_retlist == NULL)
+ return NULL;
+ if (! PyArg_ParseTuple(args, _Py_PARSE_PID, &pid))
+ goto error;
+ if (pid == 0) {
+ // raise AD instead of returning 0 as procexp is able to
+ // retrieve useful information somehow
+ AccessDenied("forced for PID 0");
+ goto error;
+ }
+
+ pid_return = psutil_pid_is_running(pid);
+ if (pid_return == 0) {
+ NoSuchProcess("psutil_pid_is_running -> 0");
+ goto error;
+ }
+ if (pid_return == -1)
+ goto error;
+
+ hThreadSnap = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);
+ if (hThreadSnap == INVALID_HANDLE_VALUE) {
+ PyErr_SetFromOSErrnoWithSyscall("CreateToolhelp32Snapshot");
+ goto error;
+ }
+
+ // Fill in the size of the structure before using it
+ te32.dwSize = sizeof(THREADENTRY32);
+
+ if (! Thread32First(hThreadSnap, &te32)) {
+ PyErr_SetFromOSErrnoWithSyscall("Thread32First");
+ goto error;
+ }
+
+ // Walk the thread snapshot to find all threads of the process.
+ // If the thread belongs to the process, increase the counter.
+ do {
+ if (te32.th32OwnerProcessID == pid) {
+ py_tuple = NULL;
+ hThread = NULL;
+ hThread = OpenThread(THREAD_QUERY_INFORMATION,
+ FALSE, te32.th32ThreadID);
+ if (hThread == NULL) {
+ // thread has disappeared on us
+ continue;
+ }
+
+ rc = GetThreadTimes(hThread, &ftDummy, &ftDummy, &ftKernel,
+ &ftUser);
+ if (rc == 0) {
+ PyErr_SetFromOSErrnoWithSyscall("GetThreadTimes");
+ goto error;
+ }
+
+ /*
+ * User and kernel times are represented as a FILETIME structure
+ * which contains a 64-bit value representing the number of
+ * 100-nanosecond intervals since January 1, 1601 (UTC):
+ * http://msdn.microsoft.com/en-us/library/ms724284(VS.85).aspx
+ * To convert it into a float representing the seconds that the
+ * process has executed in user/kernel mode I borrowed the code
+ * below from Python's Modules/posixmodule.c
+ */
+ py_tuple = Py_BuildValue(
+ "kdd",
+ te32.th32ThreadID,
+ (double)(ftUser.dwHighDateTime * HI_T + \
+ ftUser.dwLowDateTime * LO_T),
+ (double)(ftKernel.dwHighDateTime * HI_T + \
+ ftKernel.dwLowDateTime * LO_T));
+ if (!py_tuple)
+ goto error;
+ if (PyList_Append(py_retlist, py_tuple))
+ goto error;
+ Py_CLEAR(py_tuple);
+
+ CloseHandle(hThread);
+ }
+ } while (Thread32Next(hThreadSnap, &te32));
+
+ CloseHandle(hThreadSnap);
+ return py_retlist;
+
+error:
+ Py_XDECREF(py_tuple);
+ Py_DECREF(py_retlist);
+ if (hThread != NULL)
+ CloseHandle(hThread);
+ if (hThreadSnap != NULL)
+ CloseHandle(hThreadSnap);
+ return NULL;
+}
+
+
+PyObject *
+psutil_proc_open_files(PyObject *self, PyObject *args) {
+ DWORD pid;
+ HANDLE processHandle;
+ DWORD access = PROCESS_DUP_HANDLE | PROCESS_QUERY_INFORMATION;
+ PyObject *py_retlist;
+
+ if (! PyArg_ParseTuple(args, _Py_PARSE_PID, &pid))
+ return NULL;
+
+ processHandle = psutil_handle_from_pid(pid, access);
+ if (processHandle == NULL)
+ return NULL;
+
+ py_retlist = psutil_get_open_files(pid, processHandle);
+ CloseHandle(processHandle);
+ return py_retlist;
+}
+
+
+static PTOKEN_USER
+_psutil_user_token_from_pid(DWORD pid) {
+ HANDLE hProcess = NULL;
+ HANDLE hToken = NULL;
+ PTOKEN_USER userToken = NULL;
+ ULONG bufferSize = 0x100;
+
+ hProcess = psutil_handle_from_pid(pid, PROCESS_QUERY_LIMITED_INFORMATION);
+ if (hProcess == NULL)
+ return NULL;
+
+ if (!OpenProcessToken(hProcess, TOKEN_QUERY, &hToken)) {
+ PyErr_SetFromOSErrnoWithSyscall("OpenProcessToken");
+ goto error;
+ }
+
+ // Get the user SID.
+ while (1) {
+ userToken = malloc(bufferSize);
+ if (userToken == NULL) {
+ PyErr_NoMemory();
+ goto error;
+ }
+ if (!GetTokenInformation(hToken, TokenUser, userToken, bufferSize,
+ &bufferSize))
+ {
+ if (GetLastError() == ERROR_INSUFFICIENT_BUFFER) {
+ free(userToken);
+ continue;
+ }
+ else {
+ PyErr_SetFromOSErrnoWithSyscall("GetTokenInformation");
+ goto error;
+ }
+ }
+ break;
+ }
+
+ CloseHandle(hProcess);
+ CloseHandle(hToken);
+ return userToken;
+
+error:
+ if (hProcess != NULL)
+ CloseHandle(hProcess);
+ if (hToken != NULL)
+ CloseHandle(hToken);
+ return NULL;
+}
+
+
+/*
+ * Return process username as a "DOMAIN//USERNAME" string.
+ */
+PyObject *
+psutil_proc_username(PyObject *self, PyObject *args) {
+ DWORD pid;
+ PTOKEN_USER userToken = NULL;
+ WCHAR *userName = NULL;
+ WCHAR *domainName = NULL;
+ ULONG nameSize = 0x100;
+ ULONG domainNameSize = 0x100;
+ SID_NAME_USE nameUse;
+ PyObject *py_username = NULL;
+ PyObject *py_domain = NULL;
+ PyObject *py_tuple = NULL;
+
+ if (! PyArg_ParseTuple(args, _Py_PARSE_PID, &pid))
+ return NULL;
+ userToken = _psutil_user_token_from_pid(pid);
+ if (userToken == NULL)
+ return NULL;
+
+ // resolve the SID to a name
+ while (1) {
+ userName = malloc(nameSize * sizeof(WCHAR));
+ if (userName == NULL) {
+ PyErr_NoMemory();
+ goto error;
+ }
+ domainName = malloc(domainNameSize * sizeof(WCHAR));
+ if (domainName == NULL) {
+ PyErr_NoMemory();
+ goto error;
+ }
+ if (!LookupAccountSidW(NULL, userToken->User.Sid, userName, &nameSize,
+ domainName, &domainNameSize, &nameUse))
+ {
+ if (GetLastError() == ERROR_INSUFFICIENT_BUFFER) {
+ free(userName);
+ free(domainName);
+ continue;
+ }
+ else if (GetLastError() == ERROR_NONE_MAPPED) {
+ // From MS doc:
+ // https://docs.microsoft.com/en-us/windows/win32/api/winbase/
+ // nf-winbase-lookupaccountsida
+ // If the function cannot find an account name for the SID,
+ // GetLastError returns ERROR_NONE_MAPPED. This can occur if
+ // a network time-out prevents the function from finding the
+ // name. It also occurs for SIDs that have no corresponding
+ // account name, such as a logon SID that identifies a logon
+ // session.
+ AccessDenied("LookupAccountSidW -> ERROR_NONE_MAPPED");
+ goto error;
+ }
+ else {
+ PyErr_SetFromOSErrnoWithSyscall("LookupAccountSidW");
+ goto error;
+ }
+ }
+ break;
+ }
+
+ py_domain = PyUnicode_FromWideChar(domainName, wcslen(domainName));
+ if (! py_domain)
+ goto error;
+ py_username = PyUnicode_FromWideChar(userName, wcslen(userName));
+ if (! py_username)
+ goto error;
+ py_tuple = Py_BuildValue("OO", py_domain, py_username);
+ if (! py_tuple)
+ goto error;
+ Py_DECREF(py_domain);
+ Py_DECREF(py_username);
+
+ free(userName);
+ free(domainName);
+ free(userToken);
+ return py_tuple;
+
+error:
+ if (userName != NULL)
+ free(userName);
+ if (domainName != NULL)
+ free(domainName);
+ if (userToken != NULL)
+ free(userToken);
+ Py_XDECREF(py_domain);
+ Py_XDECREF(py_username);
+ Py_XDECREF(py_tuple);
+ return NULL;
+}
+
+
+/*
+ * Get process priority as a Python integer.
+ */
+PyObject *
+psutil_proc_priority_get(PyObject *self, PyObject *args) {
+ DWORD pid;
+ DWORD priority;
+ HANDLE hProcess;
+
+ if (! PyArg_ParseTuple(args, _Py_PARSE_PID, &pid))
+ return NULL;
+
+ hProcess = psutil_handle_from_pid(pid, PROCESS_QUERY_LIMITED_INFORMATION);
+ if (hProcess == NULL)
+ return NULL;
+
+ priority = GetPriorityClass(hProcess);
+ if (priority == 0) {
+ PyErr_SetFromWindowsErr(0);
+ CloseHandle(hProcess);
+ return NULL;
+ }
+ CloseHandle(hProcess);
+ return Py_BuildValue("i", priority);
+}
+
+
+/*
+ * Set process priority.
+ */
+PyObject *
+psutil_proc_priority_set(PyObject *self, PyObject *args) {
+ DWORD pid;
+ int priority;
+ int retval;
+ HANDLE hProcess;
+ DWORD access = PROCESS_QUERY_INFORMATION | PROCESS_SET_INFORMATION;
+
+ if (! PyArg_ParseTuple(args, _Py_PARSE_PID "i", &pid, &priority))
+ return NULL;
+ hProcess = psutil_handle_from_pid(pid, access);
+ if (hProcess == NULL)
+ return NULL;
+
+ retval = SetPriorityClass(hProcess, priority);
+ if (retval == 0) {
+ PyErr_SetFromWindowsErr(0);
+ CloseHandle(hProcess);
+ return NULL;
+ }
+
+ CloseHandle(hProcess);
+ Py_RETURN_NONE;
+}
+
+
+/*
+ * Get process IO priority as a Python integer.
+ */
+PyObject *
+psutil_proc_io_priority_get(PyObject *self, PyObject *args) {
+ DWORD pid;
+ HANDLE hProcess;
+ DWORD IoPriority;
+ NTSTATUS status;
+
+ if (! PyArg_ParseTuple(args, _Py_PARSE_PID, &pid))
+ return NULL;
+
+ hProcess = psutil_handle_from_pid(pid, PROCESS_QUERY_LIMITED_INFORMATION);
+ if (hProcess == NULL)
+ return NULL;
+
+ status = NtQueryInformationProcess(
+ hProcess,
+ ProcessIoPriority,
+ &IoPriority,
+ sizeof(DWORD),
+ NULL
+ );
+
+ CloseHandle(hProcess);
+ if (! NT_SUCCESS(status))
+ return psutil_SetFromNTStatusErr(status, "NtQueryInformationProcess");
+ return Py_BuildValue("i", IoPriority);
+}
+
+
+/*
+ * Set process IO priority.
+ */
+PyObject *
+psutil_proc_io_priority_set(PyObject *self, PyObject *args) {
+ DWORD pid;
+ DWORD prio;
+ HANDLE hProcess;
+ NTSTATUS status;
+ DWORD access = PROCESS_QUERY_INFORMATION | PROCESS_SET_INFORMATION;
+
+ if (! PyArg_ParseTuple(args, _Py_PARSE_PID "i", &pid, &prio))
+ return NULL;
+
+ hProcess = psutil_handle_from_pid(pid, access);
+ if (hProcess == NULL)
+ return NULL;
+
+ status = NtSetInformationProcess(
+ hProcess,
+ ProcessIoPriority,
+ (PVOID)&prio,
+ sizeof(DWORD)
+ );
+
+ CloseHandle(hProcess);
+ if (! NT_SUCCESS(status))
+ return psutil_SetFromNTStatusErr(status, "NtSetInformationProcess");
+ Py_RETURN_NONE;
+}
+
+
+/*
+ * Return a Python tuple referencing process I/O counters.
+ */
+PyObject *
+psutil_proc_io_counters(PyObject *self, PyObject *args) {
+ DWORD pid;
+ HANDLE hProcess;
+ IO_COUNTERS IoCounters;
+
+ if (! PyArg_ParseTuple(args, _Py_PARSE_PID, &pid))
+ return NULL;
+ hProcess = psutil_handle_from_pid(pid, PROCESS_QUERY_LIMITED_INFORMATION);
+ if (NULL == hProcess)
+ return NULL;
+
+ if (! GetProcessIoCounters(hProcess, &IoCounters)) {
+ PyErr_SetFromWindowsErr(0);
+ CloseHandle(hProcess);
+ return NULL;
+ }
+
+ CloseHandle(hProcess);
+ return Py_BuildValue("(KKKKKK)",
+ IoCounters.ReadOperationCount,
+ IoCounters.WriteOperationCount,
+ IoCounters.ReadTransferCount,
+ IoCounters.WriteTransferCount,
+ IoCounters.OtherOperationCount,
+ IoCounters.OtherTransferCount);
+}
+
+
+/*
+ * Return process CPU affinity as a bitmask
+ */
+PyObject *
+psutil_proc_cpu_affinity_get(PyObject *self, PyObject *args) {
+ DWORD pid;
+ HANDLE hProcess;
+ DWORD_PTR proc_mask;
+ DWORD_PTR system_mask;
+
+ if (! PyArg_ParseTuple(args, _Py_PARSE_PID, &pid))
+ return NULL;
+ hProcess = psutil_handle_from_pid(pid, PROCESS_QUERY_LIMITED_INFORMATION);
+ if (hProcess == NULL) {
+ return NULL;
+ }
+ if (GetProcessAffinityMask(hProcess, &proc_mask, &system_mask) == 0) {
+ PyErr_SetFromWindowsErr(0);
+ CloseHandle(hProcess);
+ return NULL;
+ }
+
+ CloseHandle(hProcess);
+#ifdef _WIN64
+ return Py_BuildValue("K", (unsigned long long)proc_mask);
+#else
+ return Py_BuildValue("k", (unsigned long)proc_mask);
+#endif
+}
+
+
+/*
+ * Set process CPU affinity
+ */
+PyObject *
+psutil_proc_cpu_affinity_set(PyObject *self, PyObject *args) {
+ DWORD pid;
+ HANDLE hProcess;
+ DWORD access = PROCESS_QUERY_INFORMATION | PROCESS_SET_INFORMATION;
+ DWORD_PTR mask;
+
+#ifdef _WIN64
+ if (! PyArg_ParseTuple(args, _Py_PARSE_PID "K", &pid, &mask))
+#else
+ if (! PyArg_ParseTuple(args, _Py_PARSE_PID "k", &pid, &mask))
+#endif
+ {
+ return NULL;
+ }
+ hProcess = psutil_handle_from_pid(pid, access);
+ if (hProcess == NULL)
+ return NULL;
+
+ if (SetProcessAffinityMask(hProcess, mask) == 0) {
+ PyErr_SetFromWindowsErr(0);
+ CloseHandle(hProcess);
+ return NULL;
+ }
+
+ CloseHandle(hProcess);
+ Py_RETURN_NONE;
+}
+
+
+/*
+ * Return True if all process threads are in waiting/suspended state.
+ */
+PyObject *
+psutil_proc_is_suspended(PyObject *self, PyObject *args) {
+ DWORD pid;
+ ULONG i;
+ PSYSTEM_PROCESS_INFORMATION process;
+ PVOID buffer;
+
+ if (! PyArg_ParseTuple(args, _Py_PARSE_PID, &pid))
+ return NULL;
+ if (! psutil_get_proc_info(pid, &process, &buffer))
+ return NULL;
+ for (i = 0; i < process->NumberOfThreads; i++) {
+ if (process->Threads[i].ThreadState != Waiting ||
+ process->Threads[i].WaitReason != Suspended)
+ {
+ free(buffer);
+ Py_RETURN_FALSE;
+ }
+ }
+ free(buffer);
+ Py_RETURN_TRUE;
+}
+
+
+/*
+ * Return the number of handles opened by process.
+ */
+PyObject *
+psutil_proc_num_handles(PyObject *self, PyObject *args) {
+ DWORD pid;
+ HANDLE hProcess;
+ DWORD handleCount;
+
+ if (! PyArg_ParseTuple(args, _Py_PARSE_PID, &pid))
+ return NULL;
+ hProcess = psutil_handle_from_pid(pid, PROCESS_QUERY_LIMITED_INFORMATION);
+ if (NULL == hProcess)
+ return NULL;
+ if (! GetProcessHandleCount(hProcess, &handleCount)) {
+ PyErr_SetFromWindowsErr(0);
+ CloseHandle(hProcess);
+ return NULL;
+ }
+ CloseHandle(hProcess);
+ return Py_BuildValue("k", handleCount);
+}
+
+
+static char *get_region_protection_string(ULONG protection) {
+ switch (protection & 0xff) {
+ case PAGE_NOACCESS:
+ return "";
+ case PAGE_READONLY:
+ return "r";
+ case PAGE_READWRITE:
+ return "rw";
+ case PAGE_WRITECOPY:
+ return "wc";
+ case PAGE_EXECUTE:
+ return "x";
+ case PAGE_EXECUTE_READ:
+ return "xr";
+ case PAGE_EXECUTE_READWRITE:
+ return "xrw";
+ case PAGE_EXECUTE_WRITECOPY:
+ return "xwc";
+ default:
+ return "?";
+ }
+}
+
+
+/*
+ * Return a list of process's memory mappings.
+ */
+PyObject *
+psutil_proc_memory_maps(PyObject *self, PyObject *args) {
+ MEMORY_BASIC_INFORMATION basicInfo;
+ DWORD pid;
+ HANDLE hProcess = NULL;
+ PVOID baseAddress;
+ WCHAR mappedFileName[MAX_PATH];
+ LPVOID maxAddr;
+ // required by GetMappedFileNameW
+ DWORD access = PROCESS_QUERY_INFORMATION | PROCESS_VM_READ;
+ PyObject *py_retlist = PyList_New(0);
+ PyObject *py_tuple = NULL;
+ PyObject *py_str = NULL;
+
+ if (py_retlist == NULL)
+ return NULL;
+ if (! PyArg_ParseTuple(args, _Py_PARSE_PID, &pid))
+ goto error;
+ hProcess = psutil_handle_from_pid(pid, access);
+ if (NULL == hProcess)
+ goto error;
+
+ maxAddr = PSUTIL_SYSTEM_INFO.lpMaximumApplicationAddress;
+ baseAddress = NULL;
+
+ while (VirtualQueryEx(hProcess, baseAddress, &basicInfo,
+ sizeof(MEMORY_BASIC_INFORMATION)))
+ {
+ py_tuple = NULL;
+ if (baseAddress > maxAddr)
+ break;
+ if (GetMappedFileNameW(hProcess, baseAddress, mappedFileName,
+ sizeof(mappedFileName)))
+ {
+ py_str = PyUnicode_FromWideChar(mappedFileName,
+ wcslen(mappedFileName));
+ if (py_str == NULL)
+ goto error;
+#ifdef _WIN64
+ py_tuple = Py_BuildValue(
+ "(KsOI)",
+ (unsigned long long)baseAddress,
+#else
+ py_tuple = Py_BuildValue(
+ "(ksOI)",
+ (unsigned long)baseAddress,
+#endif
+ get_region_protection_string(basicInfo.Protect),
+ py_str,
+ basicInfo.RegionSize);
+
+ if (!py_tuple)
+ goto error;
+ if (PyList_Append(py_retlist, py_tuple))
+ goto error;
+ Py_CLEAR(py_tuple);
+ Py_CLEAR(py_str);
+ }
+ baseAddress = (PCHAR)baseAddress + basicInfo.RegionSize;
+ }
+
+ CloseHandle(hProcess);
+ return py_retlist;
+
+error:
+ Py_XDECREF(py_tuple);
+ Py_XDECREF(py_str);
+ Py_DECREF(py_retlist);
+ if (hProcess != NULL)
+ CloseHandle(hProcess);
+ return NULL;
+}
+
+
+/*
+ * Return a {pid:ppid, ...} dict for all running processes.
+ */
+PyObject *
+psutil_ppid_map(PyObject *self, PyObject *args) {
+ PyObject *py_pid = NULL;
+ PyObject *py_ppid = NULL;
+ PyObject *py_retdict = PyDict_New();
+ HANDLE handle = NULL;
+ PROCESSENTRY32 pe = {0};
+ pe.dwSize = sizeof(PROCESSENTRY32);
+
+ if (py_retdict == NULL)
+ return NULL;
+ handle = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
+ if (handle == INVALID_HANDLE_VALUE) {
+ PyErr_SetFromWindowsErr(0);
+ Py_DECREF(py_retdict);
+ return NULL;
+ }
+
+ if (Process32First(handle, &pe)) {
+ do {
+ py_pid = PyLong_FromPid(pe.th32ProcessID);
+ if (py_pid == NULL)
+ goto error;
+ py_ppid = PyLong_FromPid(pe.th32ParentProcessID);
+ if (py_ppid == NULL)
+ goto error;
+ if (PyDict_SetItem(py_retdict, py_pid, py_ppid))
+ goto error;
+ Py_CLEAR(py_pid);
+ Py_CLEAR(py_ppid);
+ } while (Process32Next(handle, &pe));
+ }
+
+ CloseHandle(handle);
+ return py_retdict;
+
+error:
+ Py_XDECREF(py_pid);
+ Py_XDECREF(py_ppid);
+ Py_DECREF(py_retdict);
+ CloseHandle(handle);
+ return NULL;
+}