diff --git a/.vs/rufus.vcxproj b/.vs/rufus.vcxproj index 988855c5..bd4ec373 100644 --- a/.vs/rufus.vcxproj +++ b/.vs/rufus.vcxproj @@ -388,6 +388,7 @@ + diff --git a/.vs/rufus.vcxproj.filters b/.vs/rufus.vcxproj.filters index 1e5e232d..c0b18862 100644 --- a/.vs/rufus.vcxproj.filters +++ b/.vs/rufus.vcxproj.filters @@ -167,6 +167,9 @@ Header Files + + Header Files + diff --git a/src/checksum.c b/src/checksum.c index 0b24640e..b6c62829 100644 --- a/src/checksum.c +++ b/src/checksum.c @@ -5,7 +5,7 @@ * Copyright © 2004-2019 Tom St Denis * Copyright © 2004 g10 Code GmbH * Copyright © 2002-2015 Wei Dai & Igor Pavlov - * Copyright © 2015-2020 Pete Batard + * Copyright © 2015-2021 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 @@ -62,6 +62,7 @@ #include "db.h" #include "rufus.h" +#include "winio.h" #include "missing.h" #include "resource.h" #include "msapi_utf8.h" @@ -86,13 +87,17 @@ #define SHA512_HASHSIZE 64 #define MAX_HASHSIZE SHA512_HASHSIZE +/* Number of buffers we work with */ +#define NUM_BUFFERS 3 // 2 + 1 as a mere double buffered async I/O + // would modify the buffer being processed. + /* Globals */ char sum_str[CHECKSUM_MAX][150]; -uint32_t bufnum, sum_count[CHECKSUM_MAX] = { MD5_HASHSIZE, SHA1_HASHSIZE, SHA256_HASHSIZE, SHA512_HASHSIZE }; +uint32_t proc_bufnum, sum_count[CHECKSUM_MAX] = { MD5_HASHSIZE, SHA1_HASHSIZE, SHA256_HASHSIZE, SHA512_HASHSIZE }; HANDLE data_ready[CHECKSUM_MAX] = { 0 }, thread_ready[CHECKSUM_MAX] = { 0 }; -DWORD read_size[2]; +DWORD read_size[NUM_BUFFERS]; BOOL enable_extra_hashes = FALSE; -uint8_t ALIGNED(64) buffer[2][BUFFER_SIZE]; +uint8_t ALIGNED(64) buffer[NUM_BUFFERS][BUFFER_SIZE]; extern int default_thread_priority; /* @@ -1095,8 +1100,8 @@ DWORD WINAPI IndividualSumThread(void* param) uprintf("Failed to wait for event for checksum thread #%d: %s", i, WindowsErrorString()); return 1; } - if (read_size[bufnum] != 0) { - sum_write[i](&sum_ctx, buffer[bufnum], (size_t)read_size[bufnum]); + if (read_size[proc_bufnum] != 0) { + sum_write[i](&sum_ctx, buffer[proc_bufnum], (size_t)read_size[proc_bufnum]); if (!SetEvent(thread_ready[i])) goto error; } else { @@ -1121,9 +1126,10 @@ DWORD WINAPI SumThread(void* param) { DWORD_PTR* thread_affinity = (DWORD_PTR*)param; HANDLE sum_thread[CHECKSUM_MAX] = { NULL, NULL, NULL, NULL }; - HANDLE h = INVALID_HANDLE_VALUE; - uint64_t rb; - int i, _bufnum, r = -1; + DWORD wr; + VOID* fd = NULL; + uint64_t processed_bytes; + int i, read_bufnum, r = -1; int num_checksums = CHECKSUM_MAX - (enable_extra_hashes ? 0 : 1); if ((image_path == NULL) || (thread_affinity == NULL)) @@ -1158,53 +1164,60 @@ DWORD WINAPI SumThread(void* param) SetThreadAffinityMask(sum_thread[i], thread_affinity[i+1]); } - h = CreateFileU(image_path, GENERIC_READ, FILE_SHARE_READ, NULL, - OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN, NULL); - if (h == INVALID_HANDLE_VALUE) { + fd = CreateFileAsync(image_path, GENERIC_READ, FILE_SHARE_READ, OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN); + if (fd == NULL) { uprintf("Could not open file: %s", WindowsErrorString()); FormatStatus = ERROR_SEVERITY_ERROR | FAC(FACILITY_STORAGE) | ERROR_OPEN_FAILED; goto out; } - bufnum = 0; - _bufnum = 0; - read_size[0] = 1; // Don't trigger the first loop break + read_bufnum = 0; + proc_bufnum = 1; + read_size[proc_bufnum] = 1; // To avoid early loop exit UpdateProgressWithInfoInit(hMainDialog, FALSE); - for (rb = 0; ;rb += read_size[_bufnum]) { - // Update the progress and check for cancel - UpdateProgressWithInfo(OP_NOOP_WITH_TASKBAR, MSG_271, rb, img_report.image_size); + + // Start the initial read + ReadFileAsync(fd, buffer[read_bufnum], BUFFER_SIZE); + + for (processed_bytes = 0; read_size[proc_bufnum] != 0; processed_bytes += read_size[proc_bufnum]) { + // 0. Update the progress and check for cancel + UpdateProgressWithInfo(OP_NOOP_WITH_TASKBAR, MSG_271, processed_bytes, img_report.image_size); CHECK_FOR_USER_CANCEL; - // Signal the threads that we have data to process - if (rb != 0) { - bufnum = _bufnum; - // Toggle the read buffer - _bufnum = (bufnum + 1) % 2; - // Signal the waiting threads - for (i = 0; i < num_checksums; i++) { - if (!SetEvent(data_ready[i])) { - uprintf("Could not signal checksum thread %d: %s", i, WindowsErrorString()); - goto out; - } - } - } - - // Break the loop when data has been exhausted - if (read_size[bufnum] == 0) - break; - - // Read data (double buffered) - if (!ReadFile(h, buffer[_bufnum], BUFFER_SIZE, &read_size[_bufnum], NULL)) { - FormatStatus = ERROR_SEVERITY_ERROR | FAC(FACILITY_STORAGE) | ERROR_READ_FAULT; + // 1. Wait for the current read operation to complete (and update the read size) + if ((!WaitFileAsync(fd, DRIVE_ACCESS_TIMEOUT)) || + (!GetSizeAsync(fd, &read_size[read_bufnum]))) { uprintf("Read error: %s", WindowsErrorString()); + FormatStatus = ERROR_SEVERITY_ERROR | FAC(FACILITY_STORAGE) | ERROR_READ_FAULT; goto out; } - // Wait for the thread to signal they are ready to process data - if (WaitForMultipleObjects(num_checksums, thread_ready, TRUE, WAIT_TIME) != WAIT_OBJECT_0) { + // 2. Switch to the next reading buffer + read_bufnum = (read_bufnum + 1) % NUM_BUFFERS; + + // 3. Launch the next asynchronous read operation + ReadFileAsync(fd, buffer[read_bufnum], BUFFER_SIZE); + + // 4. Wait for all the sum threads to indicate that they are ready to process data + wr = WaitForMultipleObjects(num_checksums, thread_ready, TRUE, WAIT_TIME); + if (wr != WAIT_OBJECT_0) { + if (wr == STATUS_TIMEOUT) + SetLastError(ERROR_TIMEOUT); uprintf("Checksum threads failed to signal: %s", WindowsErrorString()); goto out; } + + // 5. Set the target buffer we want to process to the buffer we just read data into + // Note that this variable should only be updated AFTER all the threads have signalled. + proc_bufnum = (read_bufnum + NUM_BUFFERS - 1) % NUM_BUFFERS; + + // 6. Signal the waiting threads that there is data available + for (i = 0; i < num_checksums; i++) { + if (!SetEvent(data_ready[i])) { + uprintf("Could not signal checksum thread %d: %s", i, WindowsErrorString()); + goto out; + } + } } // Our last event with read_size=0 signaled the threads to exit - wait for that to happen @@ -1232,7 +1245,7 @@ out: safe_closehandle(data_ready[i]); safe_closehandle(thread_ready[i]); } - safe_closehandle(h); + CloseFileAsync(fd); PostMessage(hMainDialog, UM_FORMAT_COMPLETED, (WPARAM)FALSE, 0); if (r == 0) MyDialogBox(hMainInstance, IDD_CHECKSUM, hMainDialog, ChecksumCallback); diff --git a/src/rufus.rc b/src/rufus.rc index 3e01bb8e..7f7b50d4 100644 --- a/src/rufus.rc +++ b/src/rufus.rc @@ -35,7 +35,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 3.14.1735" +CAPTION "Rufus 3.14.1736" FONT 9, "Segoe UI Symbol", 400, 0, 0x0 BEGIN LTEXT "Drive Properties",IDS_DRIVE_PROPERTIES_TXT,8,6,53,12,NOT WS_GROUP @@ -397,8 +397,8 @@ END // VS_VERSION_INFO VERSIONINFO - FILEVERSION 3,14,1735,0 - PRODUCTVERSION 3,14,1735,0 + FILEVERSION 3,14,1736,0 + PRODUCTVERSION 3,14,1736,0 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -416,13 +416,13 @@ BEGIN VALUE "Comments", "https://rufus.ie" VALUE "CompanyName", "Akeo Consulting" VALUE "FileDescription", "Rufus" - VALUE "FileVersion", "3.14.1735" + VALUE "FileVersion", "3.14.1736" VALUE "InternalName", "Rufus" VALUE "LegalCopyright", "© 2011-2021 Pete Batard (GPL v3)" VALUE "LegalTrademarks", "https://www.gnu.org/licenses/gpl-3.0.html" VALUE "OriginalFilename", "rufus-3.14.exe" VALUE "ProductName", "Rufus" - VALUE "ProductVersion", "3.14.1735" + VALUE "ProductVersion", "3.14.1736" END END BLOCK "VarFileInfo" diff --git a/src/winio.h b/src/winio.h new file mode 100644 index 00000000..be10e8cf --- /dev/null +++ b/src/winio.h @@ -0,0 +1,155 @@ +/* +* Rufus: The Reliable USB Formatting Utility +* Windows I/O redefinitions, that would be totally unnecessary had +* Microsoft done a proper job with their asynchronous APIs. +* Copyright © 2021 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 +* 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 . +*/ + +#include +#include "msapi_utf8.h" + +#pragma once + +// https://docs.microsoft.com/en-us/windows/win32/api/minwinbase/ns-minwinbase-overlapped +// See Microsoft? It's not THAT hard to define an OVERLAPPED struct in a manner that +// doesn't qualify as an example of "Crimes against humanity" for the Geneva convention. +typedef struct { + ULONG_PTR Internal[2]; + ULONG64 Offset; + HANDLE hEvent; + BOOL bOffsetUpdated; +} NOW_THATS_WHAT_I_CALL_AN_OVERLAPPED; + +// File Descriptor for asynchronous accesses. +// The status field is a threestate value reflecting the result +// of the current asynchronous read operation: +// 1: Read was successful and completed synchronously +// -1: Read is pending asynchronously +// 0: Read Error +typedef struct { + HANDLE hFile; + INT iStatus; + NOW_THATS_WHAT_I_CALL_AN_OVERLAPPED Overlapped; +} ASYNC_FD; + +/// +/// Open a file for asynchronous access. The values for the flags are the same as the ones +/// for the native CreateFile() call. Note that FILE_FLAG_OVERLAPPED will always be added +/// to dwFlagsAndAttributes before the file is instantiated, and that an internal +/// OVERLAPPED structure with its associated wait event is also created. +/// +/// The name of the file or device to be created or opened +/// The requested access to the file or device +/// The requested sharing mode of the file or device +/// Action to take on a file or device that exists or does not exist +/// The file or device attributes and flags +/// Non NULL on success +static __inline VOID* CreateFileAsync(LPCSTR lpFileName, DWORD dwDesiredAccess, + DWORD dwShareMode, DWORD dwCreationDisposition, DWORD dwFlagsAndAttributes) +{ + ASYNC_FD* fd = calloc(sizeof(ASYNC_FD), 1); + if (fd == NULL) { + SetLastError(ERROR_NOT_ENOUGH_MEMORY); + return NULL; + } + fd->Overlapped.hEvent = CreateEventA(NULL, TRUE, FALSE, NULL); + fd->hFile = CreateFileU(lpFileName, dwDesiredAccess, dwShareMode, NULL, + dwCreationDisposition, FILE_FLAG_OVERLAPPED | dwFlagsAndAttributes, NULL); + if (fd->hFile == INVALID_HANDLE_VALUE) { + CloseHandle(fd->Overlapped.hEvent); + free(fd); + return NULL; + } + return fd; +} + +/// +/// Close a previously opened asynchronous file +/// +/// The file descriptor +static __inline VOID CloseFileAsync(VOID* fd) +{ + ASYNC_FD* _fd = (ASYNC_FD*)fd; + if (_fd == NULL) + return; + CloseHandle(_fd->hFile); + CloseHandle(_fd->Overlapped.hEvent); + free(_fd); +} + +/// +/// Initiate a read operation for asynchronous I/O. +/// +/// The file descriptor +/// The buffer that receives the data +/// Number of bytes requested +/// TRUE on success, FALSE on error +static __inline BOOL ReadFileAsync(VOID* fd, LPVOID lpBuffer, DWORD nNumberOfBytesToRead) +{ + ASYNC_FD* _fd = (ASYNC_FD*)fd; + _fd->Overlapped.bOffsetUpdated = FALSE; + if (!ReadFile(_fd->hFile, lpBuffer, nNumberOfBytesToRead, NULL, + (OVERLAPPED*)&_fd->Overlapped)) + // TODO: Is it possible to get ERROR_HANDLE_EOF here? + _fd->iStatus = (GetLastError() == ERROR_IO_PENDING) ? -1 : 0; + else + _fd->iStatus = 1; + return (_fd->iStatus != 0); +} + +/// +/// Wait for an asynchronous operation to complete, with timeout. +/// This function also succeeds if the I/O already completed synchronously. +/// +/// The file descriptor +/// A timeout value, in ms +/// TRUE on success, FALSE on error +static __inline BOOL WaitFileAsync(VOID* fd, DWORD dwTimeout) +{ + ASYNC_FD* _fd = (ASYNC_FD*)fd; + if (_fd->iStatus > 0) // Read completed synchronously + return TRUE; + return (WaitForSingleObject(_fd->Overlapped.hEvent, dwTimeout) == WAIT_OBJECT_0); +} + +/// +/// Return the number of bytes read and keep track/update the current offset +/// for an asynchronous read operation. +/// +/// The file descriptor +/// A pointer that receives the number of bytes read. +/// TRUE on success, FALSE on error +static __inline BOOL GetSizeAsync(VOID* fd, LPDWORD lpNumberOfBytesRead) +{ + ASYNC_FD* _fd = (ASYNC_FD*)fd; + // Previous call to ReadFileAsync() failed + if (_fd->iStatus == 0) { + *lpNumberOfBytesRead = 0; + return FALSE; + } + // Detect if we already read the size and updated the offset + if (_fd->Overlapped.bOffsetUpdated) { + SetLastError(ERROR_NO_MORE_ITEMS); + return FALSE; + } + // TODO: Use a timeout and call GetOverlappedResultEx() on Windows 8 and later + if (!GetOverlappedResult(_fd->hFile, (OVERLAPPED*)&_fd->Overlapped, + lpNumberOfBytesRead, (_fd->iStatus < 0))) + return (GetLastError() == ERROR_HANDLE_EOF); + _fd->Overlapped.Offset += *lpNumberOfBytesRead; + _fd->Overlapped.bOffsetUpdated = TRUE; + return TRUE; +}