diff --git a/src/drive.c b/src/drive.c index e015a287..b432a5ef 100644 --- a/src/drive.c +++ b/src/drive.c @@ -172,8 +172,7 @@ static HANDLE GetHandle(char* Path, BOOL bLockDrive, BOOL bWriteAccess, BOOL bWr uprintf("Warning: Could not obtain exclusive rights. Retrying with write sharing enabled..."); bWriteShare = TRUE; // Try to report the process that is locking the drive - // We also use bit 6 as a flag to indicate that SearchProcess was called. - access_mask = SearchProcess(DevPath, SEARCH_PROCESS_TIMEOUT, TRUE, TRUE, FALSE) | 0x40; + access_mask = GetProcessSearch(SEARCH_PROCESS_TIMEOUT, 0x07, FALSE); } Sleep(DRIVE_ACCESS_TIMEOUT / DRIVE_ACCESS_RETRIES); } @@ -203,9 +202,7 @@ static HANDLE GetHandle(char* Path, BOOL bLockDrive, BOOL bWriteAccess, BOOL bWr uprintf("Could not lock access to %s: %s", Path, WindowsErrorString()); // See if we can report the processes are accessing the drive if (!IS_ERROR(FormatStatus) && (access_mask == 0)) - // Double the search process timeout here, as Windows is so bloated with processes - // that 10 seconds has become way too small to get much of any results these days... - access_mask = SearchProcess(DevPath, 2 * SEARCH_PROCESS_TIMEOUT, TRUE, TRUE, FALSE); + access_mask = GetProcessSearch(SEARCH_PROCESS_TIMEOUT, 0x07, FALSE); // Try to continue if the only access rights we saw were for read-only if ((access_mask & 0x07) != 0x01) safe_closehandle(hDrive); diff --git a/src/license.h b/src/license.h index f01d407b..378e59a6 100644 --- a/src/license.h +++ b/src/license.h @@ -1,7 +1,7 @@ /* * Rufus: The Reliable USB Formatting Utility * Licensing Data - * Copyright © 2011-2015 Pete Batard + * Copyright © 2011-2023 Pete Batard * * 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 @@ -89,8 +89,8 @@ const char* additional_copyrights = "https://www.codeguru.com/forum/showthread.php?p=1951973\\line\n" "Public Domain\\line\n" "\\line\n" -"Handle search & process enumeration from Process Hacker by wj32 & dmex:\\line\n" -"https://processhacker.sourceforge.io/\\line\n" +"Handle search & process enumeration from System Informer by wj32 & dmex:\\line\n" +"https://systeminformer.sourceforge.io/\\line\n" "GNU General Public License (GPL) v3 or later\\line\n" "\\line\n" "Decompression support from BusyBox/Bled:\\line\n" diff --git a/src/process.c b/src/process.c index 4ffe9478..fe9575dd 100644 --- a/src/process.c +++ b/src/process.c @@ -2,8 +2,8 @@ * Rufus: The Reliable USB Formatting Utility * Process search functionality * - * Modified from Process Hacker: - * https://github.com/processhacker2/processhacker2/ + * Modified from System Informer (a.k.a. Process Hacker): + * https://github.com/winsiderss/systeminformer * Copyright © 2017-2023 Pete Batard * Copyright © 2017 dmex * Copyright © 2009-2016 wj32 @@ -31,6 +31,7 @@ #include #include "rufus.h" +#include "drive.h" #include "process.h" #include "missing.h" #include "msapi_utf8.h" @@ -53,10 +54,10 @@ PF_TYPE_DECL(NTAPI, NTSTATUS, NtAdjustPrivilegesToken, (HANDLE, BOOLEAN, PTOKEN_ PF_TYPE_DECL(NTAPI, NTSTATUS, NtClose, (HANDLE)); static PVOID PhHeapHandle = NULL; -static wchar_t* _wHandleName; -static BOOL _bPartialMatch, _bIgnoreSelf, _bQuiet; -static BYTE access_mask; -extern StrArray BlockingProcess; +static HANDLE hSearchProcessThread = NULL; +static BlockingProcess blocking_process = { 0 }; + +extern StrArray BlockingProcessList; /* * Convert an NT Status to an error message @@ -110,7 +111,6 @@ char* NtStatusError(NTSTATUS Status) { } } - static NTSTATUS PhCreateHeap(VOID) { NTSTATUS status = STATUS_SUCCESS; @@ -155,7 +155,6 @@ static NTSTATUS PhDestroyHeap(VOID) * \param Size The number of bytes to allocate. * * \return A pointer to the allocated block of memory. - * */ static PVOID PhAllocate(SIZE_T Size) { @@ -173,7 +172,6 @@ static PVOID PhAllocate(SIZE_T Size) * Frees a block of memory allocated with PhAllocate(). * * \param Memory A pointer to a block of memory. - * */ static VOID PhFree(PVOID Memory) { @@ -423,10 +421,18 @@ out: return wcmdline; } + +/** + * The search process thread. + * Note: Avoid using uprintf statements here, as it may lock the thread. + * + * \param param The thread parameters. + * + * \return A thread exit code. + */ static DWORD WINAPI SearchProcessThread(LPVOID param) { - const char *access_rights_str[8] = { "n", "r", "w", "rw", "x", "rx", "wx", "rwx" }; - char tmp[MAX_PATH]; + BOOL bInitSuccess = FALSE; NTSTATUS status = STATUS_SUCCESS; PSYSTEM_HANDLE_INFORMATION_EX handles = NULL; POBJECT_NAME_INFORMATION buffer = NULL; @@ -434,276 +440,533 @@ static DWORD WINAPI SearchProcessThread(LPVOID param) ULONG_PTR pid[2]; ULONG_PTR last_access_denied_pid = 0; ULONG bufferSize; - USHORT wHandleNameLen; + wchar_t** wHandleName = NULL; + USHORT* wHandleNameLen = NULL; HANDLE dupHandle = NULL; HANDLE processHandle = NULL; - BOOLEAN bFound = FALSE, bGotCmdLine, verbose = !_bQuiet; + HANDLE hLock = NULL; + BOOLEAN bFound = FALSE, bGotCmdLine; ULONG access_rights = 0; DWORD size; - char cmdline[MAX_PATH] = { 0 }; wchar_t wexe_path[MAX_PATH], *wcmdline; - int cur_pid; + uint64_t start_time; + char cmdline[MAX_PATH] = { 0 }, tmp[64]; + int cur_pid, j, nHandles = 0; - PF_INIT_OR_SET_STATUS(NtQueryObject, Ntdll); - PF_INIT_OR_SET_STATUS(NtDuplicateObject, NtDll); - PF_INIT_OR_SET_STATUS(NtClose, NtDll); + PF_INIT_OR_OUT(NtQueryObject, Ntdll); + PF_INIT_OR_OUT(NtDuplicateObject, NtDll); + PF_INIT_OR_OUT(NtClose, NtDll); - StrArrayClear(&BlockingProcess); + // Initialize the blocking process struct + memset(&blocking_process, 0, sizeof(blocking_process)); + hLock = CreateMutexA(NULL, TRUE, NULL); + if (hLock == NULL) + goto out; + blocking_process.hStart = CreateEventA(NULL, TRUE, FALSE, NULL); + if (blocking_process.hStart == NULL) + goto out; + if (!ReleaseMutex(hLock)) + goto out; + // Only assign the mutex handle once our init is complete + blocking_process.hLock = hLock; - if (NT_SUCCESS(status)) - status = PhCreateHeap(); + if (!NT_SUCCESS(PhCreateHeap())) + goto out; - if (NT_SUCCESS(status)) - status = PhEnumHandlesEx(&handles); - - if (!NT_SUCCESS(status)) { - uprintf("Warning: Could not enumerate process handles: %s", NtStatusError(status)); + // Wait until we are signaled active one way or another + if (!blocking_process.bActive && + (WaitForSingleObject(blocking_process.hStart, INFINITE) != WAIT_OBJECT_0)) { 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 = NULL; - - // We are seeing reports of application crashes due to access - // violation exceptions here, so, since this is not critical code, - // we add an exception handler to ignore them. - TRY_AND_HANDLE( - EXCEPTION_ACCESS_VIOLATION, - { handleInfo = (i < handles->NumberOfHandles) ? &handles->Handles[i] : NULL; }, - { continue; } - ); - - if ((dupHandle != NULL) && (processHandle != NtCurrentProcess())) { - pfNtClose(dupHandle); - dupHandle = NULL; + bInitSuccess = TRUE; + while (blocking_process.bActive) { + // Get a lock to our data + if (WaitForSingleObject(hLock, SEARCH_PROCESS_LOCK_TIMEOUT) != WAIT_OBJECT_0) + goto out; + // No handles to check => just sleep for a while + if (blocking_process.nHandles == 0) { + ReleaseMutex(hLock); + Sleep(500); + continue; } - - // Update the current handle's process PID and compare against last - // Note: Be careful about not trying to overflow our list! - TRY_AND_HANDLE( - EXCEPTION_ACCESS_VIOLATION, - { pid[cur_pid] = (handleInfo != NULL) ? handleInfo->UniqueProcessId : -1; }, - { continue; } - ); - - if (pid[0] != pid[1]) { - cur_pid = (cur_pid + 1) % 2; - - // If we're switching process and found a match, print it - if (bFound) { - static_sprintf (tmp, "● [%06u] %s (%s)", (uint32_t)pid[cur_pid], cmdline, access_rights_str[access_rights & 0x7]); - // tmp may contain a '%' so don't feed it as a naked format string - vuprintf("%s", tmp); - StrArrayAdd(&BlockingProcess, tmp, TRUE); - bFound = FALSE; - access_rights = 0; + // Work on our own copy of the handle names so we don't have to hold the + // mutex for string comparison. Update only if the version has changed. + if (blocking_process.nVersion[0] != blocking_process.nVersion[1]) { + assert(blocking_process.wHandleName != NULL && blocking_process.nHandles != 0); + if (blocking_process.wHandleName == NULL || blocking_process.nHandles == 0) { + ReleaseMutex(hLock); + goto out; } - - // Close the previous handle - if (processHandle != NULL) { - if (processHandle != NtCurrentProcess()) - pfNtClose(processHandle); - processHandle = NULL; + if (wHandleName != NULL) { + for (i = 0; i < nHandles; i++) + free(wHandleName[i]); + free(wHandleName); } - } - - CHECK_FOR_USER_CANCEL; - - // Exit loop condition - if (i >= handles->NumberOfHandles) - break; - - if (handleInfo == NULL) - continue; - - // 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)) { - uuprintf("SearchProcess: Could not open process %ld: %s", - handleInfo->UniqueProcessId, NtStatusError(status)); - processHandle = NULL; - if (status == STATUS_ACCESS_DENIED) { - last_access_denied_pid = handleInfo->UniqueProcessId; + safe_free(wHandleNameLen); + nHandles = blocking_process.nHandles; + wHandleName = calloc(nHandles, sizeof(wchar_t*)); + if (wHandleName == NULL) { + ReleaseMutex(hLock); + goto out; + } + wHandleNameLen = calloc(nHandles, sizeof(USHORT)); + if (wHandleNameLen == NULL) { + ReleaseMutex(hLock); + goto out; + } + for (i = 0; i < nHandles; i++) { + wHandleName[i] = wcsdup(blocking_process.wHandleName[i]); + wHandleNameLen[i] = (USHORT)wcslen(blocking_process.wHandleName[i]); + if (wHandleName[i] == NULL) { + ReleaseMutex(hLock); + goto out; } - continue; } + blocking_process.nVersion[1] = blocking_process.nVersion[0]; + blocking_process.nPass = 0; + } + ReleaseMutex(hLock); + + start_time = GetTickCount64(); + // Get a list of all opened handles + if (!NT_SUCCESS(PhEnumHandlesEx(&handles))) { + Sleep(1000); + continue; } - // Now duplicate this handle onto our own process, so that we can access its properties - if (processHandle == NtCurrentProcess()) { - if (_bIgnoreSelf) + pid[0] = (ULONG_PTR)0; + cur_pid = 1; + bufferSize = 0x200; + buffer = PhAllocate(bufferSize); + if (buffer == NULL) + goto out; + + for (i = 0; blocking_process.bActive; i++) { + ULONG attempts = 8; + PSYSTEM_HANDLE_TABLE_ENTRY_INFO_EX handleInfo = NULL; + + // We are seeing reports of application crashes due to access + // violation exceptions here, so, since this is not critical code, + // we add an exception handler to ignore them. + TRY_AND_HANDLE( + EXCEPTION_ACCESS_VIOLATION, + { handleInfo = (i < handles->NumberOfHandles) ? &handles->Handles[i] : NULL; }, + { continue; } + ); + + 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! + TRY_AND_HANDLE( + EXCEPTION_ACCESS_VIOLATION, + { pid[cur_pid] = (handleInfo != NULL) ? handleInfo->UniqueProcessId : -1; }, + { continue; } + ); + + if (pid[0] != pid[1]) { + cur_pid = (cur_pid + 1) % 2; + + // If we're switching process and found a match, store it + if (bFound) { + if (WaitForSingleObject(hLock, SEARCH_PROCESS_LOCK_TIMEOUT) == WAIT_OBJECT_0) { + ProcessEntry* pe = blocking_process.Process; + // Prune entries that have not been detected for a few passes + for (j = 0; j < MAX_BLOCKING_PROCESSES; j++) + if (pe[j].pid != 0 && pe[j].seen_on_pass < blocking_process.nPass - 1) + pe[j].pid = 0; + // Try to reuse an existing entry for the current pid + for (j = 0; (j < MAX_BLOCKING_PROCESSES) && (pe[j].pid != pid[cur_pid]); j++); + if (j == MAX_BLOCKING_PROCESSES) + for (j = 0; (j < MAX_BLOCKING_PROCESSES) && (pe[j].pid != 0); j++); + if (j != MAX_BLOCKING_PROCESSES) { + pe[j].pid = pid[cur_pid]; + pe[j].access_rights = access_rights & 0x7; + pe[j].seen_on_pass = blocking_process.nPass; + static_strcpy(pe[j].cmdline, cmdline); + } else if (usb_debug) { + OutputDebugStringA("SearchProcessThread: No empty slot!\n"); + } + ReleaseMutex(hLock); + } + bFound = FALSE; + access_rights = 0; + } + + // Close the previous handle + if (processHandle != NULL) { + if (processHandle != NtCurrentProcess()) + pfNtClose(processHandle); + processHandle = NULL; + } + } + + // Exit thread condition + if (!blocking_process.bActive) + goto out; + + // Exit loop condition + if (i >= handles->NumberOfHandles) + break; + + if (handleInfo == NULL) + continue; + + // 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)) { + 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; - dupHandle = (HANDLE)handleInfo->HandleValue; - } 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; + // 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) { - uuprintf("SearchProcess: Realloc from %d to %d", bufferSize, returnSize); - bufferSize = returnSize; - PhFree(buffer); - buffer = PhAllocate(bufferSize); - } else { - break; + // 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) { + bufferSize = returnSize; + PhFree(buffer); + buffer = PhAllocate(bufferSize); + } else { + break; + } + } while (--attempts); + if (!NT_SUCCESS(status)) + continue; + + for (j = 0; j < nHandles; j++) { + // Don't bother comparing if length of our handle string is larger than the current data + if (wHandleNameLen[j] > buffer->Name.Length) + continue; + // Match against our target string(s) + if (wcsncmp(wHandleName[j], buffer->Name.Buffer, wHandleNameLen[j]) == 0) + break; + } + if (j == nHandles) + continue; + 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; + access_rights &= 0x07; + + // 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 all Windows version, so fall back to QueryFullProcessImageName + if (!bGotCmdLine) { + bGotCmdLine = (QueryFullProcessImageNameW(processHandle, 0, wexe_path, &size) != FALSE); + 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) { + static_sprintf(cmdline, "Unknown_Process_%" PRIu64, + (ULONGLONG)handleInfo->UniqueProcessId); } - } while (--attempts); - if (!NT_SUCCESS(status)) { - uuprintf("SearchProcess: NtQueryObject failed for handle %X of process %ld: %s", - handleInfo->HandleValue, handleInfo->UniqueProcessId, NtStatusError(status)); - continue; - } - - // Don't bother comparing if we are looking for full match and the length is different - if ((!_bPartialMatch) && (wHandleNameLen != buffer->Name.Length)) - continue; - - // Likewise, if we are looking for a partial match and the current length is smaller - if ((_bPartialMatch) && (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; - access_mask |= (BYTE) (access_rights & 0x7) + 0x80; // Bit 7 is always set if a process was found - - // If this is the very first process we find, print a header - if (cmdline[0] == 0) - vuprintf("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 all Windows version, so fall back to QueryFullProcessImageName - if (!bGotCmdLine) { - bGotCmdLine = (QueryFullProcessImageNameW(processHandle, 0, wexe_path, &size) != FALSE); - 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) { - static_sprintf(cmdline, "Unknown_Process_%" PRIu64, - (ULONGLONG)handleInfo->UniqueProcessId); } + PhFree(buffer); + PhFree(handles); + // We are the only ones updating the counter so no need for lock + blocking_process.nPass++; + // In extended debug mode, notify how much time our search took to the debug facility + static_sprintf(tmp, "Process search run #%d completed in %llu ms\n", + blocking_process.nPass, GetTickCount64() - start_time); + if (usb_debug) + OutputDebugStringA(tmp); + Sleep(1000); } out: - if (cmdline[0] != 0) - vuprintf("You should close these applications before attempting to reformat the drive."); - else - vuprintf("NOTE: Could not identify the process(es) or service(s) accessing %S", _wHandleName); + if (!bInitSuccess) + uprintf("Warning: Could not start process handle enumerator!"); + + if (wHandleName != NULL) { + for (i = 0; i < nHandles; i++) + free(wHandleName[i]); + free(wHandleName); + } + safe_free(wHandleNameLen); - PhFree(buffer); - PhFree(handles); PhDestroyHeap(); + if ((hLock != NULL) && (hLock != INVALID_HANDLE_VALUE) && + (WaitForSingleObject(hLock, 1000) == WAIT_OBJECT_0)) { + blocking_process.hLock = NULL; + blocking_process.bActive = FALSE; + for (i = 0; i < blocking_process.nHandles; i++) + free(blocking_process.wHandleName[i]); + safe_free(blocking_process.wHandleName); + safe_closehandle(blocking_process.hStart); + ReleaseMutex(hLock); + } + safe_closehandle(hLock); + ExitThread(0); } /** - * Search all the processes and list the ones that have a specific handle open. + * Start the process search thread. * - * \param HandleName The name of the handle to look for. - * \param dwTimeOut The maximum amounf of time (ms) that may be spent searching - * \param bPartialMatch Whether partial matches should be allowed. - * \param bIgnoreSelf Whether the current process should be listed. - * \param bQuiet Prints minimal output. + * \return TRUE on success, FALSE otherwise. * - * \return a byte containing the cumulated access rights (f----xwr) from all the handles found - * with bit 7 ('f') also set if at least one process was found. */ -BYTE SearchProcess(char* HandleName, DWORD dwTimeOut, BOOL bPartialMatch, BOOL bIgnoreSelf, BOOL bQuiet) +BOOL StartProcessSearch(void) { - HANDLE handle; - DWORD res = 0; + int i; - _wHandleName = utf8_to_wchar(HandleName); - _bPartialMatch = bPartialMatch; - _bIgnoreSelf = bIgnoreSelf; - _bQuiet = bQuiet; - access_mask = 0x00; + if (hSearchProcessThread != NULL) + return TRUE; - assert(_wHandleName != NULL); - - handle = CreateThread(NULL, 0, SearchProcessThread, NULL, 0, NULL); - if (handle == NULL) { - uprintf("Warning: Unable to create conflicting process search thread"); - goto out; + hSearchProcessThread = CreateThread(NULL, 0, SearchProcessThread, NULL, 0, NULL); + if (hSearchProcessThread == NULL) { + uprintf("Failed to start process search thread: %s", WindowsErrorString()); + return FALSE; } - res = WaitForSingleObjectWithMessages(handle, dwTimeOut); - if (res == WAIT_TIMEOUT) { - // Timeout - kill the thread - TerminateThread(handle, 0); - uprintf("Search for conflicting processes was interrupted due to timeout"); - } else if (res != WAIT_OBJECT_0) { - TerminateThread(handle, 0); - uprintf("Warning: Failed to wait for conflicting process search thread %s", WindowsErrorString()); + SetThreadPriority(SearchProcessThread, THREAD_PRIORITY_LOWEST); + + // Wait until we have hLock + for (i = 0; (i < 50) && (blocking_process.hLock == NULL); i++) + Sleep(100); + if (i >= 50) { + uprintf("Failed to start process search thread: hLock init failure!"); + TerminateThread(hSearchProcessThread, 0); + CloseHandle(hSearchProcessThread); + hSearchProcessThread = NULL; + return FALSE; } + + return TRUE; +} + +/** + * Stop the process search thread.. + * + */ +void StopProcessSearch(void) +{ + if (hSearchProcessThread == NULL) + return; + + // No need for a lock on this one + blocking_process.bActive = FALSE; + if (WaitForSingleObject(hSearchProcessThread, SEARCH_PROCESS_LOCK_TIMEOUT) != WAIT_OBJECT_0) { + uprintf("Process search thread did not exit within timeout - forcefully terminating it!"); + TerminateThread(hSearchProcessThread, 0); + CloseHandle(hSearchProcessThread); + } + hSearchProcessThread = NULL; +} + +/** + * Set up the handles that the process search will run against. + * + * \param DeviceNum The device number for the currently selected drive. + * + * \return TRUE on success, FALSE otherwise. + * + */ +BOOL SetProcessSearch(DWORD DeviceNum) +{ + char* PhysicalPath = NULL, DevPath[MAX_PATH]; + char drive_letter[27], drive_name[] = "?:"; + uint32_t i, nHandles = 0; + wchar_t** wHandleName = NULL; + + if (hSearchProcessThread == NULL) { + uprintf("Process search thread is not started!"); + return FALSE; + } + + assert(blocking_process.hLock != NULL); + + // Populate the handle names + wHandleName = calloc(MAX_NUM_HANDLES, sizeof(wchar_t*)); + if (wHandleName == NULL) + return FALSE; + // Physical drive handle name + PhysicalPath = GetPhysicalName(DeviceNum); + if (QueryDosDeviceA(&PhysicalPath[4], DevPath, sizeof(DevPath)) != 0) + wHandleName[nHandles++] = utf8_to_wchar(DevPath); + free(PhysicalPath); + // Logical drive(s) handle name(s) + GetDriveLetters(DeviceNum, drive_letter); + for (i = 0; nHandles < MAX_NUM_HANDLES && drive_letter[i]; i++) { + drive_name[0] = drive_letter[i]; + if (QueryDosDeviceA(drive_name, DevPath, sizeof(DevPath)) != 0) + wHandleName[nHandles++] = utf8_to_wchar(DevPath); + } + if (WaitForSingleObject(blocking_process.hLock, SEARCH_PROCESS_LOCK_TIMEOUT) != WAIT_OBJECT_0) { + uprintf("Could not obtain process search lock"); + free(wHandleName); + nHandles = 0; + return FALSE; + } + + if (blocking_process.wHandleName != NULL) + for (i = 0; i < blocking_process.nHandles; i++) + free(blocking_process.wHandleName[i]); + free(blocking_process.wHandleName); + blocking_process.wHandleName = wHandleName; + blocking_process.nHandles = nHandles; + blocking_process.nVersion[0]++; + blocking_process.bActive = TRUE; + if (!SetEvent(blocking_process.hStart)) + uprintf("Could not send start event to process search: %s", WindowsErrorString); + return ReleaseMutex(blocking_process.hLock); +} + +/** + * Check whether the corresponding PID is that of a running process. + * + * \param pid The PID of the process to check. + * + * \return TRUE if the process is detected as currently running, FALSE otherwise. + * + */ +static BOOL IsProcessRunning(uint64_t pid) +{ + HANDLE hProcess = NULL; + DWORD dwExitCode; + BOOL ret = FALSE; + NTSTATUS status; + + PF_INIT_OR_OUT(NtClose, NtDll); + + status = PhOpenProcess(&hProcess, PROCESS_QUERY_LIMITED_INFORMATION, (HANDLE)pid); + if (!NT_SUCCESS(status) || (hProcess == NULL)) + return FALSE; + if (GetExitCodeProcess(hProcess, &dwExitCode)) + ret = (dwExitCode == STILL_ACTIVE); + pfNtClose(hProcess); out: - free(_wHandleName); - return access_mask; + return ret; +} + +/** + * Report the result of the process search. + * + * \param timeout Maximum time that should be spend in this function before aborting (in ms). + * \param access_mask Desired access mask (x = 0x4, w = 0x2, r = 0x1). + * \param bIgnoreStaleProcesses Whether to ignore processes that are no longer active. + * + * \return The combined access mask of all the matching processes found. + * The BlockingProcessList string array is also updated with the results. + * + */ +BYTE GetProcessSearch(uint32_t timeout, uint8_t access_mask, BOOL bIgnoreStaleProcesses) +{ + const char* access_rights_str[8] = { "n", "r", "w", "rw", "x", "rx", "wx", "rwx" }; + char tmp[MAX_PATH]; + int i, j; + uint32_t elapsed = 0; + BYTE returned_mask = 0; + + StrArrayClear(&BlockingProcessList); + if (hSearchProcessThread == NULL) { + uprintf("Process search thread is not started!"); + return 0; + } + + assert(blocking_process.hLock != NULL); + if (blocking_process.hLock == NULL) + return 0; + +retry: + if (WaitForSingleObject(blocking_process.hLock, SEARCH_PROCESS_LOCK_TIMEOUT) != WAIT_OBJECT_0) + return 0; + + // Make sure we have at least one pass with the current handles in order to report them. + // If we have a timeout, wait until timeout has elapsed to give up. + if ((blocking_process.nVersion[0] != blocking_process.nVersion[1]) || + (blocking_process.nPass < 1)) { + ReleaseMutex(blocking_process.hLock); + if (elapsed < timeout) { + Sleep(100); + elapsed += 100; + goto retry; + } + if (timeout != 0) + uprintf("Timeout while retrieving conflicting process list"); + return 0; + } + + for (i = 0, j = 0; i < MAX_BLOCKING_PROCESSES; i++) { + if (blocking_process.Process[i].pid == 0) + continue; + if ((blocking_process.Process[i].access_rights & access_mask) == 0) + continue; + if (bIgnoreStaleProcesses && !IsProcessRunning(blocking_process.Process[i].pid)) + continue; + returned_mask |= blocking_process.Process[i].access_rights; + static_sprintf(tmp, "● [%llu] %s (%s)", blocking_process.Process[i].pid, blocking_process.Process[i].cmdline, + access_rights_str[blocking_process.Process[i].access_rights & 0x7]); + StrArrayAdd(&BlockingProcessList, tmp, TRUE); + if (j++ == 0) + uprintf("WARNING: The following application(s) or service(s) are accessing the drive:"); + // tmp may contain a '%' so don't feed it as a naked format string + uprintf("%s", tmp); + } + if (j != 0) + uprintf("You should close these applications before retrying the operation."); + ReleaseMutex(blocking_process.hLock); + + return returned_mask & access_mask; } /** diff --git a/src/process.h b/src/process.h index 5ff0210d..fcaa61cb 100644 --- a/src/process.h +++ b/src/process.h @@ -2,9 +2,9 @@ * Rufus: The Reliable USB Formatting Utility * Process search functionality * - * Modified from Process Hacker: - * https://github.com/processhacker2/processhacker2/ - * Copyright © 2017-2019 Pete Batard + * Modified from System Informer (a.k.a. Process Hacker): + * https://github.com/winsiderss/systeminformer + * Copyright © 2017-2023 Pete Batard * Copyright © 2017 dmex * Copyright © 2009-2016 wj32 * @@ -25,6 +25,7 @@ #include #include #include +#include #pragma once @@ -294,3 +295,25 @@ typedef struct _RTL_HEAP_PARAMETERS #define SE_TIME_ZONE_PRIVILEGE (34L) #define SE_CREATE_SYMBOLIC_LINK_PRIVILEGE (35L) #define SE_MAX_WELL_KNOWN_PRIVILEGE SE_CREATE_SYMBOLIC_LINK_PRIVILEGE + +#define MAX_NUM_HANDLES 16 +#define MAX_BLOCKING_PROCESSES 16 +#define SEARCH_PROCESS_LOCK_TIMEOUT 2000 + +typedef struct { + uint64_t pid; // PID of the process + uint8_t access_rights; // rwx access rights + uint32_t seen_on_pass; // nPass value of when this process was last detected + char cmdline[MAX_PATH]; // Command line for the process +} ProcessEntry; + +typedef struct { + BOOL bActive; // Indicates whether the search for processes is currently active + uint32_t nVersion[2]; // Version indicator for the handle name list. + uint32_t nHandles; // Number of handle names in the list below + wchar_t** wHandleName; // Handle names we search against + HANDLE hLock; // Global lock to request for modifying this structure + HANDLE hStart; // Event indicating that the search for processes can be started + ProcessEntry Process[MAX_BLOCKING_PROCESSES]; // Fixed size process list + uint32_t nPass; // Incremental counter of how many passes we ran +} BlockingProcess; diff --git a/src/rufus.c b/src/rufus.c index f3eda853..5c877ac7 100755 --- a/src/rufus.c +++ b/src/rufus.c @@ -141,7 +141,7 @@ char embedded_sl_version_ext[2][32]; char ClusterSizeLabel[MAX_CLUSTER_SIZES][64]; char msgbox[1024], msgbox_title[32], *ini_file = NULL, *image_path = NULL, *short_image_path; char *archive_path = NULL, image_option_txt[128], *fido_url = NULL, *save_image_type = NULL; -StrArray BlockingProcess, ImageList; +StrArray BlockingProcessList, ImageList; // Number of steps for each FS for FCC_STRUCTURE_PROGRESS const int nb_steps[FS_MAX] = { 5, 5, 12, 1, 10, 1, 1, 1, 1 }; const char* flash_type[BADLOCKS_PATTERN_TYPES] = { "SLC", "MLC", "TLC" }; @@ -2105,7 +2105,7 @@ static void InitDialog(HWND hDlg) IGNORE_RETVAL(ComboBox_SetCurSel(hDiskID, 0)); // Create the string arrays - StrArrayCreate(&BlockingProcess, 16); + StrArrayCreate(&BlockingProcessList, 16); StrArrayCreate(&ImageList, 16); // Set various checkboxes CheckDlgButton(hDlg, IDC_QUICK_FORMAT, BST_CHECKED); @@ -2169,85 +2169,6 @@ static void PrintStatusTimeout(const char* str, BOOL val) PrintStatus(STATUS_MSG_TIMEOUT, (val)?MSG_250:MSG_251, str); } -// Check for conflicting processes accessing the drive. -// If bPrompt is true, ask the user whether they want to proceed. -// dwTimeOut is the maximum amount of time we allow for this call to execute (in ms) -// If bPrompt is false, the return value is the amount of time remaining before -// dwTimeOut would expire (or zero if we spent more than dwTimeout in this procedure). -// If bPrompt is true, the return value is 0 on error, dwTimeOut on success. -DWORD CheckDriveAccess(DWORD dwTimeOut, BOOL bPrompt) -{ - uint32_t i, j; - DWORD ret = 0, proceed = TRUE; - BYTE access_mask; - char *PhysicalPath = NULL, DevPath[MAX_PATH]; - char drive_letter[27], drive_name[] = "?:"; - char title[128]; - uint64_t start_time = GetTickCount64(), cur_time, end_time = start_time + dwTimeOut; - - // Get the current selected device - DWORD DeviceNum = (DWORD)ComboBox_GetCurItemData(hDeviceList); - if ((DeviceNum < 0x80) || (DeviceNum == (DWORD)-1)) - return FALSE; - - // "Checking for conflicting processes..." - if (bPrompt) - PrintInfo(0, MSG_278); - - // Search for any blocking processes against the physical drive - PhysicalPath = GetPhysicalName(DeviceNum); - if (QueryDosDeviceA(&PhysicalPath[4], DevPath, sizeof(DevPath)) != 0) { - access_mask = SearchProcess(DevPath, dwTimeOut, TRUE, TRUE, TRUE); - CHECK_FOR_USER_CANCEL; - if (access_mask != 0) { - proceed = FALSE; - uprintf("Found potentially blocking process(es) against %s:", &PhysicalPath[4]); - for (j = 0; j < BlockingProcess.Index; j++) - // BlockingProcess.String[j] may contain a '%' so don't feed it as a naked format string - uprintf("%s", BlockingProcess.String[j]); - } - } - - // Search for any blocking processes against the logical volume(s) - GetDriveLetters(DeviceNum, drive_letter); - for (i = 0; drive_letter[i]; i++) { - drive_name[0] = drive_letter[i]; - if (QueryDosDeviceA(drive_name, DevPath, sizeof(DevPath)) != 0) { - StrArrayClear(&BlockingProcess); - cur_time = GetTickCount64(); - if (cur_time >= end_time) - break; - access_mask = SearchProcess(DevPath, (DWORD)(end_time - cur_time), TRUE, TRUE, TRUE); - CHECK_FOR_USER_CANCEL; - // Ignore if all we have is read-only - if ((access_mask & 0x06) || (access_mask == 0x80)) { - proceed = FALSE; - uprintf("Found potentially blocking process(es) against %s", drive_name); - for (j = 0; j < BlockingProcess.Index; j++) - // BlockingProcess.String[j] may contain a '%' so don't feed it as a naked format string - uprintf("%s", BlockingProcess.String[j]); - } - } - } - - // Prompt the user if we detected blocking processes - if (bPrompt && !proceed) { - ComboBox_GetTextU(hDeviceList, title, sizeof(title)); - proceed = Notification(MSG_WARNING_QUESTION, NULL, NULL, title, lmprintf(MSG_132)); - } - if (bPrompt) { - ret = proceed ? dwTimeOut : 0; - } else { - ret = (DWORD)(GetTickCount64() - start_time); - ret = (dwTimeOut > ret) ? (dwTimeOut - ret) : 0; - } - -out: - PrintInfo(0, MSG_210); - free(PhysicalPath); - return ret; -} - /* * Main dialog callback */ @@ -2358,7 +2279,8 @@ static INT_PTR CALLBACK MainCallback(HWND hDlg, UINT message, WPARAM wParam, LPA SHChangeNotifyDeregister(ulRegister); PostQuitMessage(0); ClearDrives(); - StrArrayDestroy(&BlockingProcess); + StopProcessSearch(); + StrArrayDestroy(&BlockingProcessList); StrArrayDestroy(&ImageList); DestroyAllTooltips(); DestroyWindow(hLogDialog); @@ -2447,12 +2369,19 @@ static INT_PTR CALLBACK MainCallback(HWND hDlg, UINT message, WPARAM wParam, LPA if (HIWORD(wParam) != CBN_SELCHANGE) break; nb_devices = ComboBox_GetCount(hDeviceList); - PrintStatusDebug(0, (nb_devices==1)?MSG_208:MSG_209, nb_devices); + PrintStatusDebug(0, (nb_devices == 1) ? MSG_208 : MSG_209, nb_devices); PopulateProperties(); nDeviceIndex = ComboBox_GetCurSel(hDeviceList); DeviceNum = (nDeviceIndex == CB_ERR) ? 0 : (DWORD)ComboBox_GetItemData(hDeviceList, nDeviceIndex); SendMessage(hMainDialog, WM_COMMAND, (CBN_SELCHANGE_INTERNAL << 16) | IDC_FILE_SYSTEM, ComboBox_GetCurSel(hFileSystem)); + if (nb_devices == 0) { + // No need to run the process search if no device is selected + StopProcessSearch(); + } else if (!StartProcessSearch() || !SetProcessSearch(DeviceNum)) { + uprintf("Failed to start conflicting process search"); + StopProcessSearch(); + } break; case IDC_IMAGE_OPTION: if (HIWORD(wParam) != CBN_SELCHANGE) @@ -2654,7 +2583,7 @@ static INT_PTR CALLBACK MainCallback(HWND hDlg, UINT message, WPARAM wParam, LPA EnableControls(FALSE, FALSE); FormatStatus = 0; LastWriteError = 0; - StrArrayClear(&BlockingProcess); + StrArrayClear(&BlockingProcessList); no_confirmation_on_cancel = FALSE; SendMessage(hMainDialog, UM_PROGRESS_INIT, 0, 0); selection_default = (int)ComboBox_GetCurItemData(hBootType); @@ -3049,8 +2978,15 @@ static INT_PTR CALLBACK MainCallback(HWND hDlg, UINT message, WPARAM wParam, LPA } } - if (!CheckDriveAccess(SEARCH_PROCESS_TIMEOUT, TRUE)) - goto aborted_start; + // Detect processes that have write (0x2) or exec (0x4) permissions against our drive. + // Ideally, exec should be no big deal, but Windows complains on USB ejection if a + // process such as cmd.exe holds exec rights, so we follow suit. + if (GetProcessSearch(SEARCH_PROCESS_TIMEOUT, 0x06, TRUE)) { + char title[128]; + ComboBox_GetTextU(hDeviceList, title, sizeof(title)); + if (!Notification(MSG_WARNING_QUESTION, NULL, NULL, title, lmprintf(MSG_132))) + goto aborted_start; + } GetWindowTextU(hDeviceList, tmp, ARRAYSIZE(tmp)); if (MessageBoxExU(hMainDialog, lmprintf(MSG_003, tmp), @@ -3132,8 +3068,9 @@ static INT_PTR CALLBACK MainCallback(HWND hDlg, UINT message, WPARAM wParam, LPA PrintInfo(0, MSG_212); MessageBeep(MB_ICONERROR); FlashTaskbar(dialog_handle); - if (BlockingProcess.Index > 0) { - ListDialog(lmprintf(MSG_042), lmprintf(MSG_055), BlockingProcess.String, BlockingProcess.Index); + GetProcessSearch(0, 0x07, FALSE); + if (BlockingProcessList.Index > 0) { + ListDialog(lmprintf(MSG_042), lmprintf(MSG_055), BlockingProcessList.String, BlockingProcessList.Index); } else { if (WindowsVersion.Version >= WINDOWS_10) { // Try to detect if 'Controlled Folder Access' is enabled on Windows 10 or later. See also: @@ -3831,18 +3768,7 @@ extern int TestHashes(void); // Ctrl-T => Alternate Test mode that doesn't require a full rebuild if ((ctrl_without_focus || ((GetKeyState(VK_CONTROL) & 0x8000) && (msg.message == WM_KEYDOWN))) && (msg.wParam == 'T')) { -// TestHashes(); - dll_resolver_t resolver = { 0 }; - resolver.path = "C:\\Windows\\System32\\Dism\\FfuProvider.dll"; - resolver.count = 3; - resolver.name = calloc(resolver.count, sizeof(char*)); - resolver.address = calloc(resolver.count, sizeof(uint32_t)); - resolver.name[0] = "FfuCaptureImage"; - resolver.name[1] = "FfuApplyImage"; - resolver.name[2] = "FfuMountImage"; - uprintf("Got %d resolved addresses", ResolveDllAddress(&resolver)); - free(resolver.name); - free(resolver.address); + TestHashes(); continue; } #endif diff --git a/src/rufus.h b/src/rufus.h index e1fc1cf8..803fb432 100644 --- a/src/rufus.h +++ b/src/rufus.h @@ -87,8 +87,8 @@ #define STATUS_MSG_TIMEOUT 3500 // How long should cheat mode messages appear for on the status bar #define WRITE_RETRIES 4 #define WRITE_TIMEOUT 5000 // How long we should wait between write retries (in ms) -#define SEARCH_PROCESS_TIMEOUT 10000 // How long we should search for conflicting processes before giving up (in ms) -#define NET_SESSION_TIMEOUT 3500 // How long we should wait to connect, send or receive internet data +#define SEARCH_PROCESS_TIMEOUT 5000 // How long we should wait to get the conflicting process data (in ms) +#define NET_SESSION_TIMEOUT 3500 // How long we should wait to connect, send or receive internet data (in ms) #define FS_DEFAULT FS_FAT32 #define SINGLE_CLUSTERSIZE_DEFAULT 0x00000100 #define BADLOCKS_PATTERN_TYPES 5 @@ -729,8 +729,10 @@ extern char* ToLocaleName(DWORD lang_id); extern void SetAlertPromptMessages(void); extern BOOL SetAlertPromptHook(void); extern void ClrAlertPromptHook(void); -extern DWORD CheckDriveAccess(DWORD dwTimeOut, BOOL bPrompt); -extern BYTE SearchProcess(char* HandleName, DWORD dwTimeout, BOOL bPartialMatch, BOOL bIgnoreSelf, BOOL bQuiet); +extern BOOL StartProcessSearch(void); +extern void StopProcessSearch(void); +extern BOOL SetProcessSearch(DWORD DeviceNum); +extern BYTE GetProcessSearch(uint32_t timeout, uint8_t access_mask, BOOL bIgnoreStaleProcesses); extern BOOL EnablePrivileges(void); extern void FlashTaskbar(HANDLE handle); extern DWORD WaitForSingleObjectWithMessages(HANDLE hHandle, DWORD dwMilliseconds); diff --git a/src/rufus.rc b/src/rufus.rc index 4b757140..c6afc22d 100644 --- a/src/rufus.rc +++ b/src/rufus.rc @@ -33,7 +33,7 @@ LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL IDD_DIALOG DIALOGEX 12, 12, 232, 326 STYLE DS_SETFONT | DS_MODALFRAME | DS_CENTER | WS_MINIMIZEBOX | WS_POPUP | WS_CAPTION | WS_SYSMENU EXSTYLE WS_EX_ACCEPTFILES -CAPTION "Rufus 4.3.2084" +CAPTION "Rufus 4.3.2085" FONT 9, "Segoe UI Symbol", 400, 0, 0x0 BEGIN LTEXT "Drive Properties",IDS_DRIVE_PROPERTIES_TXT,8,6,53,12,NOT WS_GROUP @@ -392,8 +392,8 @@ END // VS_VERSION_INFO VERSIONINFO - FILEVERSION 4,3,2084,0 - PRODUCTVERSION 4,3,2084,0 + FILEVERSION 4,3,2085,0 + PRODUCTVERSION 4,3,2085,0 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -411,13 +411,13 @@ BEGIN VALUE "Comments", "https://rufus.ie" VALUE "CompanyName", "Akeo Consulting" VALUE "FileDescription", "Rufus" - VALUE "FileVersion", "4.3.2084" + VALUE "FileVersion", "4.3.2085" VALUE "InternalName", "Rufus" VALUE "LegalCopyright", " 2011-2023 Pete Batard (GPL v3)" VALUE "LegalTrademarks", "https://www.gnu.org/licenses/gpl-3.0.html" VALUE "OriginalFilename", "rufus-4.3.exe" VALUE "ProductName", "Rufus" - VALUE "ProductVersion", "4.3.2084" + VALUE "ProductVersion", "4.3.2085" END END BLOCK "VarFileInfo" diff --git a/src/stdio.c b/src/stdio.c index 95ba5634..8f0b6470 100644 --- a/src/stdio.c +++ b/src/stdio.c @@ -562,8 +562,8 @@ BOOL WriteFileWithRetry(HANDLE hFile, LPCVOID lpBuffer, DWORD nNumberOfBytesToWr break; if (nTry < nNumRetries) { uprintf("Retrying in %d seconds...", WRITE_TIMEOUT / 1000); - // Don't sit idly but use the downtime to check for conflicting processes... - Sleep(CheckDriveAccess(WRITE_TIMEOUT, FALSE)); + // TODO: Call GetProcessSearch() here? + Sleep(WRITE_TIMEOUT); } } if (SCODE_CODE(GetLastError()) == ERROR_SUCCESS)