+/*
+* Rufus: The Reliable USB Formatting Utility
+* Process search functionality
+*
+* Modified from Process Hacker:
+* https://github.com/processhacker2/processhacker2/
+* Copyright © 2017-2019 Pete Batard <pete@akeo.ie>
+* Copyright © 2017 dmex
+* Copyright © 2009-2016 wj32
+* Copyright (c) 2020, longpanda <admin@ventoy.net>
+*
+* This program is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published by
+* the Free Software Foundation, either version 3 of the License, or
+* (at your option) any later version.
+*
+* This program is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+
+#include <Windows.h>
+#include <winternl.h>
+#include <commctrl.h>
+#include <initguid.h>
+#include <vds.h>
+#include "resource.h"
+#include "Language.h"
+#include "Ventoy2Disk.h"
+#include "fat_filelib.h"
+#include "ff.h"
+#include "process.h"
+#include <Psapi.h>
+
+
+OPENED_LIBRARIES_VARS;
+
+STATIC WCHAR *_wHandleName = NULL;
+static PVOID PhHeapHandle = NULL;
+
+
+/*
+* Convert an NT Status to an error message
+*
+* \param Status An operattonal status.
+*
+* \return An error message string.
+*
+*/
+char* NtStatusError(NTSTATUS Status) {
+ static char unknown[32];
+
+ switch (Status) {
+ case STATUS_SUCCESS:
+ return "Operation Successful";
+ case STATUS_UNSUCCESSFUL:
+ return "Operation Failed";
+ case STATUS_BUFFER_OVERFLOW:
+ return "Buffer Overflow";
+ case STATUS_NOT_IMPLEMENTED:
+ return "Not Implemented";
+ case STATUS_INFO_LENGTH_MISMATCH:
+ return "Info Length Mismatch";
+ case STATUS_INVALID_HANDLE:
+ return "Invalid Handle.";
+ case STATUS_INVALID_PARAMETER:
+ return "Invalid Parameter";
+ case STATUS_NO_MEMORY:
+ return "Not Enough Quota";
+ case STATUS_ACCESS_DENIED:
+ return "Access Denied";
+ case STATUS_BUFFER_TOO_SMALL:
+ return "Buffer Too Small";
+ case STATUS_OBJECT_TYPE_MISMATCH:
+ return "Wrong Type";
+ case STATUS_OBJECT_NAME_INVALID:
+ return "Object Name Invalid";
+ case STATUS_OBJECT_NAME_NOT_FOUND:
+ return "Object Name not found";
+ case STATUS_OBJECT_PATH_INVALID:
+ return "Object Path Invalid";
+ case STATUS_SHARING_VIOLATION:
+ return "Sharing Violation";
+ case STATUS_INSUFFICIENT_RESOURCES:
+ return "Insufficient resources";
+ case STATUS_NOT_SUPPORTED:
+ return "Operation is not supported";
+ default:
+ safe_sprintf(unknown, "Unknown error 0x%08lx", Status);
+ return unknown;
+ }
+}
+
+
+static NTSTATUS PhCreateHeap(VOID)
+{
+ NTSTATUS status = STATUS_SUCCESS;
+
+ if (PhHeapHandle != NULL)
+ return STATUS_ALREADY_COMPLETE;
+
+ PF_INIT_OR_SET_STATUS(RtlCreateHeap, Ntdll);
+
+ if (NT_SUCCESS(status)) {
+ PhHeapHandle = pfRtlCreateHeap(HEAP_NO_SERIALIZE | HEAP_GROWABLE, NULL, 2 * MB, 1 * MB, NULL, NULL);
+ if (PhHeapHandle == NULL)
+ status = STATUS_UNSUCCESSFUL;
+ }
+
+ return status;
+}
+
+static NTSTATUS PhDestroyHeap(VOID)
+{
+ NTSTATUS status = STATUS_SUCCESS;
+
+ if (PhHeapHandle == NULL)
+ return STATUS_ALREADY_COMPLETE;
+
+ PF_INIT_OR_SET_STATUS(RtlDestroyHeap, Ntdll);
+
+ if (NT_SUCCESS(status)) {
+ if (pfRtlDestroyHeap(PhHeapHandle) == NULL) {
+ PhHeapHandle = NULL;
+ }
+ else {
+ status = STATUS_UNSUCCESSFUL;
+ }
+ }
+
+ return status;
+}
+
+/**
+* Allocates a block of memory.
+*
+* \param Size The number of bytes to allocate.
+*
+* \return A pointer to the allocated block of memory.
+*
+*/
+static PVOID PhAllocate(SIZE_T Size)
+{
+ PF_INIT(RtlAllocateHeap, Ntdll);
+ if (pfRtlAllocateHeap == NULL)
+ return NULL;
+
+ return pfRtlAllocateHeap(PhHeapHandle, 0, Size);
+}
+
+/**
+* Frees a block of memory allocated with PhAllocate().
+*
+* \param Memory A pointer to a block of memory.
+*
+*/
+static VOID PhFree(PVOID Memory)
+{
+ PF_INIT(RtlFreeHeap, Ntdll);
+
+ if (pfRtlFreeHeap != NULL)
+ pfRtlFreeHeap(PhHeapHandle, 0, Memory);
+}
+
+/**
+* Enumerates all open handles.
+*
+* \param Handles A variable which receives a pointer to a structure containing information about
+* all opened handles. You must free the structure using PhFree() when you no longer need it.
+*
+* \return An NTStatus indicating success or the error code.
+*/
+NTSTATUS PhEnumHandlesEx(PSYSTEM_HANDLE_INFORMATION_EX *Handles)
+{
+ static ULONG initialBufferSize = 0x10000;
+ NTSTATUS status = STATUS_SUCCESS;
+ PVOID buffer;
+ ULONG bufferSize;
+
+ PF_INIT_OR_SET_STATUS(NtQuerySystemInformation, Ntdll);
+ if (!NT_SUCCESS(status))
+ return status;
+
+ bufferSize = initialBufferSize;
+ buffer = PhAllocate(bufferSize);
+ if (buffer == NULL)
+ return STATUS_NO_MEMORY;
+
+ while ((status = pfNtQuerySystemInformation(SystemExtendedHandleInformation,
+ buffer, bufferSize, NULL)) == STATUS_INFO_LENGTH_MISMATCH) {
+ PhFree(buffer);
+ bufferSize *= 2;
+
+ // Fail if we're resizing the buffer to something very large.
+ if (bufferSize > PH_LARGE_BUFFER_SIZE)
+ return STATUS_INSUFFICIENT_RESOURCES;
+
+ buffer = PhAllocate(bufferSize);
+ if (buffer == NULL)
+ return STATUS_NO_MEMORY;
+ }
+
+ if (!NT_SUCCESS(status)) {
+ PhFree(buffer);
+ return status;
+ }
+
+ if (bufferSize <= 0x200000)
+ initialBufferSize = bufferSize;
+ *Handles = (PSYSTEM_HANDLE_INFORMATION_EX)buffer;
+
+ return status;
+}
+
+/**
+* Opens a process.
+*
+* \param ProcessHandle A variable which receives a handle to the process.
+* \param DesiredAccess The desired access to the process.
+* \param ProcessId The ID of the process.
+*
+* \return An NTStatus indicating success or the error code.
+*/
+NTSTATUS PhOpenProcess(PHANDLE ProcessHandle, ACCESS_MASK DesiredAccess, HANDLE ProcessId)
+{
+ NTSTATUS status = STATUS_SUCCESS;
+ OBJECT_ATTRIBUTES objectAttributes;
+ CLIENT_ID clientId;
+
+ if ((LONG_PTR)ProcessId == (LONG_PTR)GetCurrentProcessId()) {
+ *ProcessHandle = NtCurrentProcess();
+ return 0;
+ }
+
+ PF_INIT_OR_SET_STATUS(NtOpenProcess, Ntdll);
+ if (!NT_SUCCESS(status))
+ return status;
+
+ clientId.UniqueProcess = ProcessId;
+ clientId.UniqueThread = NULL;
+
+ InitializeObjectAttributes(&objectAttributes, NULL, 0, NULL, NULL);
+ status = pfNtOpenProcess(ProcessHandle, DesiredAccess, &objectAttributes, &clientId);
+
+ return status;
+}
+
+/**
+* Query processes with open handles to a file, volume or disk.
+*
+* \param VolumeOrFileHandle The handle to the target.
+* \param Information The returned list of processes.
+*
+* \return An NTStatus indicating success or the error code.
+*/
+NTSTATUS PhQueryProcessesUsingVolumeOrFile(HANDLE VolumeOrFileHandle,
+ PFILE_PROCESS_IDS_USING_FILE_INFORMATION *Information)
+{
+ static ULONG initialBufferSize = 16 * KB;
+ NTSTATUS status = STATUS_SUCCESS;
+ PVOID buffer;
+ ULONG bufferSize;
+ IO_STATUS_BLOCK isb;
+
+ PF_INIT_OR_SET_STATUS(NtQueryInformationFile, NtDll);
+ if (!NT_SUCCESS(status))
+ return status;
+
+ bufferSize = initialBufferSize;
+ buffer = PhAllocate(bufferSize);
+ if (buffer == NULL)
+ return STATUS_INSUFFICIENT_RESOURCES;
+
+ while ((status = pfNtQueryInformationFile(VolumeOrFileHandle, &isb, buffer, bufferSize,
+ FileProcessIdsUsingFileInformation)) == STATUS_INFO_LENGTH_MISMATCH) {
+ PhFree(buffer);
+ bufferSize *= 2;
+ // Fail if we're resizing the buffer to something very large.
+ if (bufferSize > 64 * MB)
+ return STATUS_INSUFFICIENT_RESOURCES;
+ buffer = PhAllocate(bufferSize);
+ }
+
+ if (!NT_SUCCESS(status)) {
+ PhFree(buffer);
+ return status;
+ }
+
+ if (bufferSize <= 64 * MB)
+ initialBufferSize = bufferSize;
+ *Information = (PFILE_PROCESS_IDS_USING_FILE_INFORMATION)buffer;
+
+ return status;
+}
+
+/**
+* Query the full commandline that was used to create a process.
+* This can be helpful to differentiate between service instances (svchost.exe).
+* Taken from: https://stackoverflow.com/a/14012919/1069307
+*
+* \param hProcess A handle to a process.
+*
+* \return A Unicode commandline string, or NULL on error.
+* The returned string must be freed by the caller.
+*/
+static PWSTR GetProcessCommandLine(HANDLE hProcess)
+{
+ PWSTR wcmdline = NULL;
+ BOOL wow;
+ DWORD pp_offset, cmd_offset;
+ NTSTATUS status = STATUS_SUCCESS;
+ SYSTEM_INFO si;
+ PBYTE peb = NULL, pp = NULL;
+
+ // Determine if 64 or 32-bit processor
+ GetNativeSystemInfo(&si);
+ if ((si.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_AMD64) || (si.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_ARM64)) {
+ pp_offset = 0x20;
+ cmd_offset = 0x70;
+ }
+ else {
+ pp_offset = 0x10;
+ cmd_offset = 0x40;
+ }
+
+ // PEB and Process Parameters (we only need the beginning of these structs)
+ peb = (PBYTE)calloc(pp_offset + 8, 1);
+ if (peb == NULL)
+ goto out;
+ pp = (PBYTE)calloc(cmd_offset + 16, 1);
+ if (pp == NULL)
+ goto out;
+
+ IsWow64Process(GetCurrentProcess(), &wow);
+ if (wow) {
+ // 32-bit process running on a 64-bit OS
+ PROCESS_BASIC_INFORMATION_WOW64 pbi = { 0 };
+ ULONGLONG params;
+ UNICODE_STRING_WOW64* ucmdline;
+
+ PF_INIT_OR_OUT(NtWow64QueryInformationProcess64, NtDll);
+ PF_INIT_OR_OUT(NtWow64ReadVirtualMemory64, NtDll);
+
+ status = pfNtWow64QueryInformationProcess64(hProcess, 0, &pbi, sizeof(pbi), NULL);
+ if (!NT_SUCCESS(status))
+ goto out;
+
+ status = pfNtWow64ReadVirtualMemory64(hProcess, pbi.PebBaseAddress, peb, pp_offset + 8, NULL);
+ if (!NT_SUCCESS(status))
+ goto out;
+
+ // Read Process Parameters from the 64-bit address space
+ params = (ULONGLONG)*((ULONGLONG*)(peb + pp_offset));
+ status = pfNtWow64ReadVirtualMemory64(hProcess, params, pp, cmd_offset + 16, NULL);
+ if (!NT_SUCCESS(status))
+ goto out;
+
+ ucmdline = (UNICODE_STRING_WOW64*)(pp + cmd_offset);
+ wcmdline = (PWSTR)calloc(ucmdline->Length + 1, sizeof(WCHAR));
+ if (wcmdline == NULL)
+ goto out;
+ status = pfNtWow64ReadVirtualMemory64(hProcess, ucmdline->Buffer, wcmdline, ucmdline->Length, NULL);
+ if (!NT_SUCCESS(status)) {
+ safe_free(wcmdline);
+ goto out;
+ }
+ }
+ else {
+ // 32-bit process on a 32-bit OS, or 64-bit process on a 64-bit OS
+ PROCESS_BASIC_INFORMATION pbi = { 0 };
+ PBYTE* params;
+ UNICODE_STRING* ucmdline;
+
+ PF_INIT_OR_OUT(NtQueryInformationProcess, NtDll);
+
+ status = pfNtQueryInformationProcess(hProcess, 0, &pbi, sizeof(pbi), NULL);
+ if (!NT_SUCCESS(status))
+ goto out;
+
+ // Read PEB
+ if (!ReadProcessMemory(hProcess, pbi.PebBaseAddress, peb, pp_offset + 8, NULL))
+ goto out;
+
+ // Read Process Parameters
+ params = (PBYTE*)*(LPVOID*)(peb + pp_offset);
+ if (!ReadProcessMemory(hProcess, params, pp, cmd_offset + 16, NULL))
+ goto out;
+
+ ucmdline = (UNICODE_STRING*)(pp + cmd_offset);
+ // In the absolute, someone could craft a process with dodgy attributes to try to cause an overflow
+ ucmdline->Length = min(ucmdline->Length, 512);
+ wcmdline = (PWSTR)calloc(ucmdline->Length + 1, sizeof(WCHAR));
+ if (!ReadProcessMemory(hProcess, ucmdline->Buffer, wcmdline, ucmdline->Length, NULL)) {
+ safe_free(wcmdline);
+ goto out;
+ }
+ }
+
+out:
+ free(peb);
+ free(pp);
+ return wcmdline;
+}
+
+
+static int GetDevicePathName(PHY_DRIVE_INFO *pPhyDrive, WCHAR *wDevPath)
+{
+ int i;
+ CHAR PhyDrive[128];
+ CHAR DevPath[MAX_PATH] = { 0 };
+
+ safe_sprintf(PhyDrive, "\\\\.\\PhysicalDrive%d", pPhyDrive->PhyDrive);
+
+ if (0 == QueryDosDeviceA(PhyDrive + 4, DevPath, sizeof(DevPath)))
+ {
+ Log("QueryDosDeviceA failed error:%u", GetLastError());
+ strcpy_s(DevPath, sizeof(DevPath), "???");
+ }
+ else
+ {
+ Log("QueryDosDeviceA success %s", DevPath);
+ }
+
+ for (i = 0; DevPath[i] && i < MAX_PATH; i++)
+ {
+ wDevPath[i] = DevPath[i];
+ }
+
+ return 0;
+}
+
+
+static __inline DWORD GetModuleFileNameExU(HANDLE hProcess, HMODULE hModule, char* lpFilename, DWORD nSize)
+{
+ DWORD ret = 0, err = ERROR_INVALID_DATA;
+ // coverity[returned_null]
+ walloc(lpFilename, nSize);
+ ret = GetModuleFileNameExW(hProcess, hModule, wlpFilename, nSize);
+ err = GetLastError();
+ if ((ret != 0)
+ && ((ret = wchar_to_utf8_no_alloc(wlpFilename, lpFilename, nSize)) == 0)) {
+ err = GetLastError();
+ }
+ wfree(lpFilename);
+ SetLastError(err);
+ return ret;
+}
+
+int FindProcessOccupyDisk(HANDLE hDrive, PHY_DRIVE_INFO *pPhyDrive)
+{
+ WCHAR wDevPath[MAX_PATH] = { 0 };
+ const char *access_rights_str[8] = { "n", "r", "w", "rw", "x", "rx", "wx", "rwx" };
+ NTSTATUS status = STATUS_SUCCESS;
+ PSYSTEM_HANDLE_INFORMATION_EX handles = NULL;
+ POBJECT_NAME_INFORMATION buffer = NULL;
+ ULONG_PTR i;
+ ULONG_PTR pid[2];
+ ULONG_PTR last_access_denied_pid = 0;
+ ULONG bufferSize;
+ USHORT wHandleNameLen;
+ HANDLE dupHandle = NULL;
+ HANDLE processHandle = NULL;
+ BOOLEAN bFound = FALSE, bGotCmdLine, verbose = TRUE;
+ ULONG access_rights = 0;
+ DWORD size;
+ char cmdline[MAX_PATH] = { 0 };
+ wchar_t wexe_path[MAX_PATH], *wcmdline;
+ int cur_pid;
+
+
+ Log("FindProcessOccupyDisk for PhyDrive %d", pPhyDrive->PhyDrive);
+
+ GetDevicePathName(pPhyDrive, wDevPath);
+ _wHandleName = wDevPath;
+
+
+ PF_INIT_OR_SET_STATUS(NtQueryObject, Ntdll);
+ PF_INIT_OR_SET_STATUS(NtDuplicateObject, NtDll);
+ PF_INIT_OR_SET_STATUS(NtClose, NtDll);
+
+ if (NT_SUCCESS(status))
+ status = PhCreateHeap();
+
+ if (NT_SUCCESS(status))
+ status = PhEnumHandlesEx(&handles);
+
+ if (!NT_SUCCESS(status)) {
+ Log("Warning: Could not enumerate process handles: %s", NtStatusError(status));
+ goto out;
+ }
+
+ pid[0] = (ULONG_PTR)0;
+ cur_pid = 1;
+
+ wHandleNameLen = (USHORT)wcslen(_wHandleName);
+
+ bufferSize = 0x200;
+ buffer = PhAllocate(bufferSize);
+ if (buffer == NULL)
+ goto out;
+
+ for (i = 0;; i++) {
+ ULONG attempts = 8;
+ PSYSTEM_HANDLE_TABLE_ENTRY_INFO_EX handleInfo =
+ (i < handles->NumberOfHandles) ? &handles->Handles[i] : NULL;
+
+ if ((dupHandle != NULL) && (processHandle != NtCurrentProcess())) {
+ pfNtClose(dupHandle);
+ dupHandle = NULL;
+ }
+
+ // Update the current handle's process PID and compare against last
+ // Note: Be careful about not trying to overflow our list!
+ pid[cur_pid] = (handleInfo != NULL) ? handleInfo->UniqueProcessId : -1;
+
+ if (pid[0] != pid[1]) {
+ cur_pid = (cur_pid + 1) % 2;
+
+ // If we're switching process and found a match, print it
+ if (bFound) {
+ Log("* [%06u] %s (%s)", (UINT32)pid[cur_pid], cmdline, access_rights_str[access_rights & 0x7]);
+ bFound = FALSE;
+ access_rights = 0;
+ }
+
+ // Close the previous handle
+ if (processHandle != NULL) {
+ if (processHandle != NtCurrentProcess())
+ pfNtClose(processHandle);
+ processHandle = NULL;
+ }
+ }
+
+ // Exit loop condition
+ if (i >= handles->NumberOfHandles)
+ break;
+
+ // Don't bother with processes we can't access
+ if (handleInfo->UniqueProcessId == last_access_denied_pid)
+ continue;
+
+ // Filter out handles that aren't opened with Read (bit 0), Write (bit 1) or Execute (bit 5) access
+ if ((handleInfo->GrantedAccess & 0x23) == 0)
+ continue;
+
+ // Open the process to which the handle we are after belongs, if not already opened
+ if (pid[0] != pid[1]) {
+ status = PhOpenProcess(&processHandle, PROCESS_DUP_HANDLE | PROCESS_QUERY_INFORMATION | PROCESS_VM_READ,
+ (HANDLE)handleInfo->UniqueProcessId);
+ // There exists some processes we can't access
+ if (!NT_SUCCESS(status)) {
+ //Log("SearchProcess: Could not open process %ld: %s",
+ // handleInfo->UniqueProcessId, NtStatusError(status));
+ processHandle = NULL;
+ if (status == STATUS_ACCESS_DENIED) {
+ last_access_denied_pid = handleInfo->UniqueProcessId;
+ }
+ continue;
+ }
+ }
+
+ // Now duplicate this handle onto our own process, so that we can access its properties
+ if (processHandle == NtCurrentProcess()) {
+ continue;
+ }
+ else {
+ status = pfNtDuplicateObject(processHandle, (HANDLE)handleInfo->HandleValue,
+ NtCurrentProcess(), &dupHandle, 0, 0, 0);
+ if (!NT_SUCCESS(status))
+ continue;
+ }
+
+ // Filter non-storage handles. We're not interested in them and they make NtQueryObject() freeze
+ if (GetFileType(dupHandle) != FILE_TYPE_DISK)
+ continue;
+
+ // A loop is needed because the I/O subsystem likes to give us the wrong return lengths...
+ do {
+ ULONG returnSize;
+ // TODO: We might potentially still need a timeout on ObjectName queries, as PH does...
+ status = pfNtQueryObject(dupHandle, ObjectNameInformation, buffer, bufferSize, &returnSize);
+ if (status == STATUS_BUFFER_OVERFLOW || status == STATUS_INFO_LENGTH_MISMATCH ||
+ status == STATUS_BUFFER_TOO_SMALL) {
+ Log("SearchProcess: Realloc from %d to %d", bufferSize, returnSize);
+ bufferSize = returnSize;
+ PhFree(buffer);
+ buffer = PhAllocate(bufferSize);
+ }
+ else {
+ break;
+ }
+ } while (--attempts);
+ if (!NT_SUCCESS(status)) {
+ Log("SearchProcess: NtQueryObject failed for handle %X of process %ld: %s",
+ handleInfo->HandleValue, handleInfo->UniqueProcessId, NtStatusError(status));
+ continue;
+ }
+
+ // we are looking for a partial match and the current length is smaller
+ if (wHandleNameLen > buffer->Name.Length)
+ continue;
+
+ // Match against our target string
+ if (wcsncmp(_wHandleName, buffer->Name.Buffer, wHandleNameLen) != 0)
+ continue;
+
+ // If we are here, we have a process accessing our target!
+ bFound = TRUE;
+
+ // Keep a mask of all the access rights being used
+ access_rights |= handleInfo->GrantedAccess;
+ // The Executable bit is in a place we don't like => reposition it
+ if (access_rights & 0x20)
+ access_rights = (access_rights & 0x03) | 0x04;
+
+ // If this is the very first process we find, print a header
+ if (cmdline[0] == 0)
+ Log("WARNING: The following process(es) or service(s) are accessing %S:", _wHandleName);
+
+ // Where possible, try to get the full command line
+ bGotCmdLine = FALSE;
+ size = MAX_PATH;
+ wcmdline = GetProcessCommandLine(processHandle);
+ if (wcmdline != NULL) {
+ bGotCmdLine = TRUE;
+ wchar_to_utf8_no_alloc(wcmdline, cmdline, sizeof(cmdline));
+ free(wcmdline);
+ }
+
+ // If we couldn't get the full commandline, try to get the executable path
+ if (!bGotCmdLine)
+ bGotCmdLine = (GetModuleFileNameExU(processHandle, 0, cmdline, MAX_PATH - 1) != 0);
+
+ // The above may not work on Windows 7, so try QueryFullProcessImageName (Vista or later)
+ if (!bGotCmdLine) {
+ bGotCmdLine = QueryFullProcessImageNameW(processHandle, 0, wexe_path, &size);
+ if (bGotCmdLine)
+ wchar_to_utf8_no_alloc(wexe_path, cmdline, sizeof(cmdline));
+ }
+
+ // Still nothing? Try GetProcessImageFileName. Note that GetProcessImageFileName uses
+ // '\Device\Harddisk#\Partition#\' instead drive letters
+ if (!bGotCmdLine) {
+ bGotCmdLine = (GetProcessImageFileNameW(processHandle, wexe_path, MAX_PATH) != 0);
+ if (bGotCmdLine)
+ wchar_to_utf8_no_alloc(wexe_path, cmdline, sizeof(cmdline));
+ }
+
+ // Complete failure => Just craft a default process name that includes the PID
+ if (!bGotCmdLine) {
+ safe_sprintf(cmdline, "Unknown_Process_0x%llx", (ULONGLONG)handleInfo->UniqueProcessId);
+ }
+ }
+
+out:
+ if (cmdline[0] != 0)
+ Log("You should close these applications before attempting to reformat the drive.");
+ else
+ Log("NOTE: Could not identify the process(es) or service(s) accessing %S", _wHandleName);
+
+ PhFree(buffer);
+ PhFree(handles);
+ PhDestroyHeap();
+
+ return 0;
+}
+