comparison 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
comparison
equal deleted inserted replaced
0:e9bb126753e7 1:20d02a178406
1 #include "StdAfx.h"
2
3 #ifdef _WIN32
4
5 #include "file_win32_wrapper.h"
6
7 namespace file_win32_helpers {
8 t_filesize get_size(HANDLE p_handle) {
9 LARGE_INTEGER v = {};
10 WIN32_IO_OP(GetFileSizeEx(p_handle, &v));
11 return make_uint64(v);
12 }
13 void seek(HANDLE p_handle,t_sfilesize p_position,file::t_seek_mode p_mode) {
14 union {
15 t_int64 temp64;
16 struct {
17 DWORD temp_lo;
18 LONG temp_hi;
19 };
20 };
21
22 temp64 = p_position;
23 SetLastError(ERROR_SUCCESS);
24 temp_lo = SetFilePointer(p_handle,temp_lo,&temp_hi,(DWORD)p_mode);
25 if (GetLastError() != ERROR_SUCCESS) exception_io_from_win32(GetLastError());
26 }
27
28 void fillOverlapped(OVERLAPPED & ol, HANDLE myEvent, t_filesize s) {
29 ol.hEvent = myEvent;
30 ol.Offset = (DWORD)( s & 0xFFFFFFFF );
31 ol.OffsetHigh = (DWORD)(s >> 32);
32 }
33
34 void writeOverlappedPass(HANDLE handle, HANDLE myEvent, t_filesize position, const void * in,DWORD inBytes, abort_callback & abort) {
35 abort.check();
36 if (inBytes == 0) return;
37 OVERLAPPED ol = {};
38 fillOverlapped(ol, myEvent, position);
39 ResetEvent(myEvent);
40 DWORD bytesWritten;
41 SetLastError(NO_ERROR);
42 if (WriteFile( handle, in, inBytes, &bytesWritten, &ol)) {
43 // succeeded already?
44 if (bytesWritten != inBytes) throw exception_io();
45 return;
46 }
47
48 {
49 const DWORD code = GetLastError();
50 if (code != ERROR_IO_PENDING) exception_io_from_win32(code);
51 }
52 const HANDLE handles[] = {myEvent, abort.get_abort_event()};
53 SetLastError(NO_ERROR);
54 DWORD state = WaitForMultipleObjects(_countof(handles), handles, FALSE, INFINITE);
55 if (state == WAIT_OBJECT_0) {
56 try {
57 WIN32_IO_OP( GetOverlappedResult(handle,&ol,&bytesWritten,TRUE) );
58 } catch(...) {
59 CancelIo(handle);
60 throw;
61 }
62 if (bytesWritten != inBytes) throw exception_io();
63 return;
64 }
65 CancelIo(handle);
66 throw exception_aborted();
67 }
68
69 void writeOverlapped(HANDLE handle, HANDLE myEvent, t_filesize & position, const void * in, size_t inBytes, abort_callback & abort) {
70 enum {writeMAX = 16*1024*1024};
71 size_t done = 0;
72 while(done < inBytes) {
73 size_t delta = inBytes - done;
74 if (delta > writeMAX) delta = writeMAX;
75 writeOverlappedPass(handle, myEvent, position, (const BYTE*)in + done, (DWORD) delta, abort);
76 done += delta;
77 position += delta;
78 }
79 }
80 void writeStreamOverlapped(HANDLE handle, HANDLE myEvent, const void * in, size_t inBytes, abort_callback & abort) {
81 enum {writeMAX = 16*1024*1024};
82 size_t done = 0;
83 while(done < inBytes) {
84 size_t delta = inBytes - done;
85 if (delta > writeMAX) delta = writeMAX;
86 writeOverlappedPass(handle, myEvent, 0, (const BYTE*)in + done, (DWORD) delta, abort);
87 done += delta;
88 }
89 }
90
91 DWORD readOverlappedPass(HANDLE handle, HANDLE myEvent, t_filesize position, void * out, DWORD outBytes, abort_callback & abort) {
92 abort.check();
93 if (outBytes == 0) return 0;
94 OVERLAPPED ol = {};
95 fillOverlapped(ol, myEvent, position);
96 ResetEvent(myEvent);
97 DWORD bytesDone;
98 SetLastError(NO_ERROR);
99 if (ReadFile( handle, out, outBytes, &bytesDone, &ol)) {
100 // succeeded already?
101 return bytesDone;
102 }
103
104 {
105 const DWORD code = GetLastError();
106 switch(code) {
107 case ERROR_HANDLE_EOF:
108 case ERROR_BROKEN_PIPE:
109 return 0;
110 case ERROR_IO_PENDING:
111 break; // continue
112 default:
113 exception_io_from_win32(code);
114 };
115 }
116
117 const HANDLE handles[] = {myEvent, abort.get_abort_event()};
118 SetLastError(NO_ERROR);
119 DWORD state = WaitForMultipleObjects(_countof(handles), handles, FALSE, INFINITE);
120 if (state == WAIT_OBJECT_0) {
121 SetLastError(NO_ERROR);
122 if (!GetOverlappedResult(handle,&ol,&bytesDone,TRUE)) {
123 const DWORD code = GetLastError();
124 if (code == ERROR_HANDLE_EOF || code == ERROR_BROKEN_PIPE) bytesDone = 0;
125 else {
126 CancelIo(handle);
127 exception_io_from_win32(code);
128 }
129 }
130 return bytesDone;
131 }
132 CancelIo(handle);
133 throw exception_aborted();
134 }
135 size_t readOverlapped(HANDLE handle, HANDLE myEvent, t_filesize & position, void * out, size_t outBytes, abort_callback & abort) {
136 enum {readMAX = 16*1024*1024};
137 size_t done = 0;
138 while(done < outBytes) {
139 size_t delta = outBytes - done;
140 if (delta > readMAX) delta = readMAX;
141 delta = readOverlappedPass(handle, myEvent, position, (BYTE*) out + done, (DWORD) delta, abort);
142 if (delta == 0) break;
143 done += delta;
144 position += delta;
145 }
146 return done;
147 }
148
149 size_t readStreamOverlapped(HANDLE handle, HANDLE myEvent, void * out, size_t outBytes, abort_callback & abort) {
150 enum {readMAX = 16*1024*1024};
151 size_t done = 0;
152 while(done < outBytes) {
153 size_t delta = outBytes - done;
154 if (delta > readMAX) delta = readMAX;
155 delta = readOverlappedPass(handle, myEvent, 0, (BYTE*) out + done, (DWORD) delta, abort);
156 if (delta == 0) break;
157 done += delta;
158 }
159 return done;
160 }
161
162 typedef BOOL (WINAPI * pCancelSynchronousIo_t)(HANDLE hThread);
163
164
165 struct createFileData_t {
166 LPCTSTR lpFileName;
167 DWORD dwDesiredAccess;
168 DWORD dwShareMode;
169 LPSECURITY_ATTRIBUTES lpSecurityAttributes;
170 DWORD dwCreationDisposition;
171 DWORD dwFlagsAndAttributes;
172 HANDLE hTemplateFile;
173 HANDLE hResult;
174 DWORD dwErrorCode;
175 };
176
177 HANDLE createFile(LPCTSTR lpFileName, DWORD dwDesiredAccess, DWORD dwShareMode, LPSECURITY_ATTRIBUTES lpSecurityAttributes, DWORD dwCreationDisposition, DWORD dwFlagsAndAttributes, HANDLE hTemplateFile, abort_callback & abort) {
178 abort.check();
179
180 return CreateFile(lpFileName, dwDesiredAccess, dwShareMode, lpSecurityAttributes, dwCreationDisposition, dwFlagsAndAttributes, hTemplateFile);
181 }
182
183 size_t lowLevelIO(HANDLE hFile, const GUID & guid, size_t arg1, void * arg2, size_t arg2size, bool canWrite, abort_callback & abort) {
184 if ( guid == file_lowLevelIO::guid_flushFileBuffers ) {
185 if (!canWrite) {
186 PFC_ASSERT(!"File opened for reading, not writing");
187 throw exception_io_denied();
188 }
189 WIN32_IO_OP( ::FlushFileBuffers(hFile) );
190 return 1;
191 } else if ( guid == file_lowLevelIO::guid_getFileTimes ) {
192 if ( arg2size == sizeof(file_lowLevelIO::filetimes_t) ) {
193 if (canWrite) WIN32_IO_OP(::FlushFileBuffers(hFile));
194 auto ft = reinterpret_cast<file_lowLevelIO::filetimes_t *>(arg2);
195 static_assert(sizeof(t_filetimestamp) == sizeof(FILETIME), "struct sanity");
196 WIN32_IO_OP( GetFileTime( hFile, (FILETIME*)&ft->creation, (FILETIME*)&ft->lastAccess, (FILETIME*)&ft->lastWrite) );
197 return 1;
198 }
199 } else if ( guid == file_lowLevelIO::guid_setFileTimes ) {
200 if (arg2size == sizeof(file_lowLevelIO::filetimes_t)) {
201 if (!canWrite) {
202 PFC_ASSERT(!"File opened for reading, not writing");
203 throw exception_io_denied();
204 }
205 WIN32_IO_OP(::FlushFileBuffers(hFile));
206 auto ft = reinterpret_cast<file_lowLevelIO::filetimes_t *>(arg2);
207 static_assert(sizeof(t_filetimestamp) == sizeof(FILETIME), "struct sanity");
208 const FILETIME * pCreation = nullptr;
209 const FILETIME * pLastAccess = nullptr;
210 const FILETIME * pLastWrite = nullptr;
211 if ( ft->creation != filetimestamp_invalid ) pCreation = (const FILETIME*)&ft->creation;
212 if ( ft->lastAccess != filetimestamp_invalid ) pLastAccess = (const FILETIME*)&ft->lastAccess;
213 if ( ft->lastWrite != filetimestamp_invalid ) pLastWrite = (const FILETIME*)&ft->lastWrite;
214 WIN32_IO_OP( SetFileTime(hFile, pCreation, pLastAccess, pLastWrite) );
215 return 1;
216 }
217 }
218 return 0;
219 }
220
221 t_filestats2 stats2_from_handle(HANDLE h, const wchar_t * fallbackPath, uint32_t flags, abort_callback& a) {
222 a.check();
223 // Sadly GetFileInformationByHandle() is UNRELIABLE with certain net shares
224 BY_HANDLE_FILE_INFORMATION info = {};
225 if (GetFileInformationByHandle(h, &info)) {
226 return file_win32_helpers::translate_stats2(info);
227 }
228
229 a.check();
230 t_filestats2 ret;
231
232 // ALWAYS get size, fail if bad handle
233 ret.m_size = get_size(h);
234
235 if (flags & (stats2_timestamp | stats2_timestampCreate)) {
236 static_assert(sizeof(t_filetimestamp) == sizeof(FILETIME), "struct sanity");
237 FILETIME ftCreate = {}, ftWrite = {};
238 if (GetFileTime(h, &ftCreate, nullptr, &ftWrite)) {
239 ret.m_timestamp = make_uint64(ftWrite); ret.m_timestampCreate = make_uint64(ftCreate);
240 }
241 }
242 if (flags & stats2_flags) {
243 // No other way to get this from handle?
244 if (fallbackPath != nullptr && *fallbackPath != 0) {
245 DWORD attr = GetFileAttributes(fallbackPath);
246 if (attr != INVALID_FILE_ATTRIBUTES) {
247 attribs_from_win32(ret, attr);
248 }
249 }
250 }
251 return ret;
252 }
253 void attribs_from_win32(t_filestats2& out, DWORD in) {
254 out.set_readonly((in & FILE_ATTRIBUTE_READONLY) != 0);
255 out.set_folder((in & FILE_ATTRIBUTE_DIRECTORY) != 0);
256 out.set_hidden((in & FILE_ATTRIBUTE_HIDDEN) != 0);
257 out.set_system((in & FILE_ATTRIBUTE_SYSTEM) != 0);
258 out.set_remote(false);
259 }
260
261
262 // Seek penalty query, effectively: is this an SSD?
263 // Credit:
264 // https://devblogs.microsoft.com/oldnewthing/20201023-00/?p=104395
265 static bool queryVolumeSeekPenalty(HANDLE hVolume, bool& out) {
266 STORAGE_PROPERTY_QUERY query = {};
267 query.PropertyId = StorageDeviceSeekPenaltyProperty;
268 query.QueryType = PropertyStandardQuery;
269 DWORD count = 1;
270 DEVICE_SEEK_PENALTY_DESCRIPTOR result = {};
271 if (!DeviceIoControl(hVolume, IOCTL_STORAGE_QUERY_PROPERTY, &query, sizeof(query), &result, sizeof(result), &count, nullptr)) {
272 return false;
273 }
274 out = result.IncursSeekPenalty;
275 return true;
276 }
277
278 static HANDLE GetVolumeHandleForFile(PCWSTR filePath) {
279 wchar_t volumePath[MAX_PATH] = {};
280 WIN32_OP_D(GetVolumePathName(filePath, volumePath, ARRAYSIZE(volumePath)));
281
282 wchar_t volumeName[MAX_PATH] = {};
283 WIN32_OP_D(GetVolumeNameForVolumeMountPoint(volumePath, volumeName, ARRAYSIZE(volumeName)));
284
285 auto length = wcslen(volumeName);
286 if ( length == 0 ) {
287 PFC_ASSERT(!"???");
288 return NULL;
289 }
290 if (length && volumeName[length - 1] == L'\\') {
291 volumeName[length - 1] = L'\0';
292 }
293
294 HANDLE ret;
295 WIN32_OP_D( ret = CreateFile(volumeName, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr) );
296 return ret;
297 }
298 bool querySeekPenalty(const wchar_t* nativePath, bool& out) {
299 CHandle h;
300 h.Attach( GetVolumeHandleForFile( nativePath ) );
301 if (!h) return false;
302 return queryVolumeSeekPenalty(h, out);
303 }
304 bool querySeekPenalty(const char* fb2k_path, bool& out) {
305 const char * path = fb2k_path;
306 if ( matchProtocol(path, "file")) path = afterProtocol(path);
307 return querySeekPenalty(pfc::wideFromUTF8(path), out);
308 }
309 }
310
311 #endif // _WIN32
312