Mercurial > foo_out_sdl
diff foosdk/sdk/foobar2000/helpers/file_win32_wrapper.cpp @ 1:20d02a178406 default tip
*: check in everything else
yay
| author | Paper <paper@tflc.us> |
|---|---|
| date | Mon, 05 Jan 2026 02:15:46 -0500 |
| parents | |
| children |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/foosdk/sdk/foobar2000/helpers/file_win32_wrapper.cpp Mon Jan 05 02:15:46 2026 -0500 @@ -0,0 +1,312 @@ +#include "StdAfx.h" + +#ifdef _WIN32 + +#include "file_win32_wrapper.h" + +namespace file_win32_helpers { + t_filesize get_size(HANDLE p_handle) { + LARGE_INTEGER v = {}; + WIN32_IO_OP(GetFileSizeEx(p_handle, &v)); + return make_uint64(v); + } + void seek(HANDLE p_handle,t_sfilesize p_position,file::t_seek_mode p_mode) { + union { + t_int64 temp64; + struct { + DWORD temp_lo; + LONG temp_hi; + }; + }; + + temp64 = p_position; + SetLastError(ERROR_SUCCESS); + temp_lo = SetFilePointer(p_handle,temp_lo,&temp_hi,(DWORD)p_mode); + if (GetLastError() != ERROR_SUCCESS) exception_io_from_win32(GetLastError()); + } + + void fillOverlapped(OVERLAPPED & ol, HANDLE myEvent, t_filesize s) { + ol.hEvent = myEvent; + ol.Offset = (DWORD)( s & 0xFFFFFFFF ); + ol.OffsetHigh = (DWORD)(s >> 32); + } + + void writeOverlappedPass(HANDLE handle, HANDLE myEvent, t_filesize position, const void * in,DWORD inBytes, abort_callback & abort) { + abort.check(); + if (inBytes == 0) return; + OVERLAPPED ol = {}; + fillOverlapped(ol, myEvent, position); + ResetEvent(myEvent); + DWORD bytesWritten; + SetLastError(NO_ERROR); + if (WriteFile( handle, in, inBytes, &bytesWritten, &ol)) { + // succeeded already? + if (bytesWritten != inBytes) throw exception_io(); + return; + } + + { + const DWORD code = GetLastError(); + if (code != ERROR_IO_PENDING) exception_io_from_win32(code); + } + const HANDLE handles[] = {myEvent, abort.get_abort_event()}; + SetLastError(NO_ERROR); + DWORD state = WaitForMultipleObjects(_countof(handles), handles, FALSE, INFINITE); + if (state == WAIT_OBJECT_0) { + try { + WIN32_IO_OP( GetOverlappedResult(handle,&ol,&bytesWritten,TRUE) ); + } catch(...) { + CancelIo(handle); + throw; + } + if (bytesWritten != inBytes) throw exception_io(); + return; + } + CancelIo(handle); + throw exception_aborted(); + } + + void writeOverlapped(HANDLE handle, HANDLE myEvent, t_filesize & position, const void * in, size_t inBytes, abort_callback & abort) { + enum {writeMAX = 16*1024*1024}; + size_t done = 0; + while(done < inBytes) { + size_t delta = inBytes - done; + if (delta > writeMAX) delta = writeMAX; + writeOverlappedPass(handle, myEvent, position, (const BYTE*)in + done, (DWORD) delta, abort); + done += delta; + position += delta; + } + } + void writeStreamOverlapped(HANDLE handle, HANDLE myEvent, const void * in, size_t inBytes, abort_callback & abort) { + enum {writeMAX = 16*1024*1024}; + size_t done = 0; + while(done < inBytes) { + size_t delta = inBytes - done; + if (delta > writeMAX) delta = writeMAX; + writeOverlappedPass(handle, myEvent, 0, (const BYTE*)in + done, (DWORD) delta, abort); + done += delta; + } + } + + DWORD readOverlappedPass(HANDLE handle, HANDLE myEvent, t_filesize position, void * out, DWORD outBytes, abort_callback & abort) { + abort.check(); + if (outBytes == 0) return 0; + OVERLAPPED ol = {}; + fillOverlapped(ol, myEvent, position); + ResetEvent(myEvent); + DWORD bytesDone; + SetLastError(NO_ERROR); + if (ReadFile( handle, out, outBytes, &bytesDone, &ol)) { + // succeeded already? + return bytesDone; + } + + { + const DWORD code = GetLastError(); + switch(code) { + case ERROR_HANDLE_EOF: + case ERROR_BROKEN_PIPE: + return 0; + case ERROR_IO_PENDING: + break; // continue + default: + exception_io_from_win32(code); + }; + } + + const HANDLE handles[] = {myEvent, abort.get_abort_event()}; + SetLastError(NO_ERROR); + DWORD state = WaitForMultipleObjects(_countof(handles), handles, FALSE, INFINITE); + if (state == WAIT_OBJECT_0) { + SetLastError(NO_ERROR); + if (!GetOverlappedResult(handle,&ol,&bytesDone,TRUE)) { + const DWORD code = GetLastError(); + if (code == ERROR_HANDLE_EOF || code == ERROR_BROKEN_PIPE) bytesDone = 0; + else { + CancelIo(handle); + exception_io_from_win32(code); + } + } + return bytesDone; + } + CancelIo(handle); + throw exception_aborted(); + } + size_t readOverlapped(HANDLE handle, HANDLE myEvent, t_filesize & position, void * out, size_t outBytes, abort_callback & abort) { + enum {readMAX = 16*1024*1024}; + size_t done = 0; + while(done < outBytes) { + size_t delta = outBytes - done; + if (delta > readMAX) delta = readMAX; + delta = readOverlappedPass(handle, myEvent, position, (BYTE*) out + done, (DWORD) delta, abort); + if (delta == 0) break; + done += delta; + position += delta; + } + return done; + } + + size_t readStreamOverlapped(HANDLE handle, HANDLE myEvent, void * out, size_t outBytes, abort_callback & abort) { + enum {readMAX = 16*1024*1024}; + size_t done = 0; + while(done < outBytes) { + size_t delta = outBytes - done; + if (delta > readMAX) delta = readMAX; + delta = readOverlappedPass(handle, myEvent, 0, (BYTE*) out + done, (DWORD) delta, abort); + if (delta == 0) break; + done += delta; + } + return done; + } + + typedef BOOL (WINAPI * pCancelSynchronousIo_t)(HANDLE hThread); + + + struct createFileData_t { + LPCTSTR lpFileName; + DWORD dwDesiredAccess; + DWORD dwShareMode; + LPSECURITY_ATTRIBUTES lpSecurityAttributes; + DWORD dwCreationDisposition; + DWORD dwFlagsAndAttributes; + HANDLE hTemplateFile; + HANDLE hResult; + DWORD dwErrorCode; + }; + + HANDLE createFile(LPCTSTR lpFileName, DWORD dwDesiredAccess, DWORD dwShareMode, LPSECURITY_ATTRIBUTES lpSecurityAttributes, DWORD dwCreationDisposition, DWORD dwFlagsAndAttributes, HANDLE hTemplateFile, abort_callback & abort) { + abort.check(); + + return CreateFile(lpFileName, dwDesiredAccess, dwShareMode, lpSecurityAttributes, dwCreationDisposition, dwFlagsAndAttributes, hTemplateFile); + } + + size_t lowLevelIO(HANDLE hFile, const GUID & guid, size_t arg1, void * arg2, size_t arg2size, bool canWrite, abort_callback & abort) { + if ( guid == file_lowLevelIO::guid_flushFileBuffers ) { + if (!canWrite) { + PFC_ASSERT(!"File opened for reading, not writing"); + throw exception_io_denied(); + } + WIN32_IO_OP( ::FlushFileBuffers(hFile) ); + return 1; + } else if ( guid == file_lowLevelIO::guid_getFileTimes ) { + if ( arg2size == sizeof(file_lowLevelIO::filetimes_t) ) { + if (canWrite) WIN32_IO_OP(::FlushFileBuffers(hFile)); + auto ft = reinterpret_cast<file_lowLevelIO::filetimes_t *>(arg2); + static_assert(sizeof(t_filetimestamp) == sizeof(FILETIME), "struct sanity"); + WIN32_IO_OP( GetFileTime( hFile, (FILETIME*)&ft->creation, (FILETIME*)&ft->lastAccess, (FILETIME*)&ft->lastWrite) ); + return 1; + } + } else if ( guid == file_lowLevelIO::guid_setFileTimes ) { + if (arg2size == sizeof(file_lowLevelIO::filetimes_t)) { + if (!canWrite) { + PFC_ASSERT(!"File opened for reading, not writing"); + throw exception_io_denied(); + } + WIN32_IO_OP(::FlushFileBuffers(hFile)); + auto ft = reinterpret_cast<file_lowLevelIO::filetimes_t *>(arg2); + static_assert(sizeof(t_filetimestamp) == sizeof(FILETIME), "struct sanity"); + const FILETIME * pCreation = nullptr; + const FILETIME * pLastAccess = nullptr; + const FILETIME * pLastWrite = nullptr; + if ( ft->creation != filetimestamp_invalid ) pCreation = (const FILETIME*)&ft->creation; + if ( ft->lastAccess != filetimestamp_invalid ) pLastAccess = (const FILETIME*)&ft->lastAccess; + if ( ft->lastWrite != filetimestamp_invalid ) pLastWrite = (const FILETIME*)&ft->lastWrite; + WIN32_IO_OP( SetFileTime(hFile, pCreation, pLastAccess, pLastWrite) ); + return 1; + } + } + return 0; + } + + t_filestats2 stats2_from_handle(HANDLE h, const wchar_t * fallbackPath, uint32_t flags, abort_callback& a) { + a.check(); + // Sadly GetFileInformationByHandle() is UNRELIABLE with certain net shares + BY_HANDLE_FILE_INFORMATION info = {}; + if (GetFileInformationByHandle(h, &info)) { + return file_win32_helpers::translate_stats2(info); + } + + a.check(); + t_filestats2 ret; + + // ALWAYS get size, fail if bad handle + ret.m_size = get_size(h); + + if (flags & (stats2_timestamp | stats2_timestampCreate)) { + static_assert(sizeof(t_filetimestamp) == sizeof(FILETIME), "struct sanity"); + FILETIME ftCreate = {}, ftWrite = {}; + if (GetFileTime(h, &ftCreate, nullptr, &ftWrite)) { + ret.m_timestamp = make_uint64(ftWrite); ret.m_timestampCreate = make_uint64(ftCreate); + } + } + if (flags & stats2_flags) { + // No other way to get this from handle? + if (fallbackPath != nullptr && *fallbackPath != 0) { + DWORD attr = GetFileAttributes(fallbackPath); + if (attr != INVALID_FILE_ATTRIBUTES) { + attribs_from_win32(ret, attr); + } + } + } + return ret; + } + void attribs_from_win32(t_filestats2& out, DWORD in) { + out.set_readonly((in & FILE_ATTRIBUTE_READONLY) != 0); + out.set_folder((in & FILE_ATTRIBUTE_DIRECTORY) != 0); + out.set_hidden((in & FILE_ATTRIBUTE_HIDDEN) != 0); + out.set_system((in & FILE_ATTRIBUTE_SYSTEM) != 0); + out.set_remote(false); + } + + + // Seek penalty query, effectively: is this an SSD? + // Credit: + // https://devblogs.microsoft.com/oldnewthing/20201023-00/?p=104395 + static bool queryVolumeSeekPenalty(HANDLE hVolume, bool& out) { + STORAGE_PROPERTY_QUERY query = {}; + query.PropertyId = StorageDeviceSeekPenaltyProperty; + query.QueryType = PropertyStandardQuery; + DWORD count = 1; + DEVICE_SEEK_PENALTY_DESCRIPTOR result = {}; + if (!DeviceIoControl(hVolume, IOCTL_STORAGE_QUERY_PROPERTY, &query, sizeof(query), &result, sizeof(result), &count, nullptr)) { + return false; + } + out = result.IncursSeekPenalty; + return true; + } + + static HANDLE GetVolumeHandleForFile(PCWSTR filePath) { + wchar_t volumePath[MAX_PATH] = {}; + WIN32_OP_D(GetVolumePathName(filePath, volumePath, ARRAYSIZE(volumePath))); + + wchar_t volumeName[MAX_PATH] = {}; + WIN32_OP_D(GetVolumeNameForVolumeMountPoint(volumePath, volumeName, ARRAYSIZE(volumeName))); + + auto length = wcslen(volumeName); + if ( length == 0 ) { + PFC_ASSERT(!"???"); + return NULL; + } + if (length && volumeName[length - 1] == L'\\') { + volumeName[length - 1] = L'\0'; + } + + HANDLE ret; + WIN32_OP_D( ret = CreateFile(volumeName, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr) ); + return ret; + } + bool querySeekPenalty(const wchar_t* nativePath, bool& out) { + CHandle h; + h.Attach( GetVolumeHandleForFile( nativePath ) ); + if (!h) return false; + return queryVolumeSeekPenalty(h, out); + } + bool querySeekPenalty(const char* fb2k_path, bool& out) { + const char * path = fb2k_path; + if ( matchProtocol(path, "file")) path = afterProtocol(path); + return querySeekPenalty(pfc::wideFromUTF8(path), out); + } +} + +#endif // _WIN32 +
