1 /* gwin32file-sync-stream.c - a simple IStream implementation
2 *
3 * Copyright 2020 Руслан Ижбулатов
4 *
5 * SPDX-License-Identifier: LGPL-2.1-or-later
6 *
7 * This library is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU Lesser General Public
9 * License as published by the Free Software Foundation; either
10 * version 2.1 of the License, or (at your option) any later version.
11 *
12 * This library is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Lesser General Public License for more details.
16 *
17 * You should have received a copy of the GNU Lesser General Public License
18 * along with this library; if not, see <http://www.gnu.org/licenses/>.
19 */
20
21 /* A COM object that implements an IStream backed by a file HANDLE.
22 * Works just like `SHCreateStreamOnFileEx()`, but does not
23 * support locking, and doesn't force us to link to libshlwapi.
24 * Only supports synchronous access.
25 */
26 #include "config.h"
27 #define COBJMACROS
28 #define INITGUID
29 #include <windows.h>
30
31 #include "gwin32file-sync-stream.h"
32
33 static HRESULT STDMETHODCALLTYPE _file_sync_stream_query_interface (IStream *self_ptr,
34 REFIID ref_interface_guid,
35 LPVOID *output_object_ptr);
36 static ULONG STDMETHODCALLTYPE _file_sync_stream_release (IStream *self_ptr);
37 static ULONG STDMETHODCALLTYPE _file_sync_stream_add_ref (IStream *self_ptr);
38
39 static HRESULT STDMETHODCALLTYPE _file_sync_stream_read (IStream *self_ptr,
40 void *output_data,
41 ULONG bytes_to_read,
42 ULONG *output_bytes_read);
43
44 static HRESULT STDMETHODCALLTYPE _file_sync_stream_write (IStream *self_ptr,
45 const void *data,
46 ULONG bytes_to_write,
47 ULONG *output_bytes_written);
48
49
50 static HRESULT STDMETHODCALLTYPE _file_sync_stream_clone (IStream *self_ptr,
51 IStream **output_clone_ptr);
52 static HRESULT STDMETHODCALLTYPE _file_sync_stream_commit (IStream *self_ptr,
53 DWORD commit_flags);
54 static HRESULT STDMETHODCALLTYPE _file_sync_stream_copy_to (IStream *self_ptr,
55 IStream *output_stream,
56 ULARGE_INTEGER bytes_to_copy,
57 ULARGE_INTEGER *output_bytes_read,
58 ULARGE_INTEGER *output_bytes_written);
59 static HRESULT STDMETHODCALLTYPE _file_sync_stream_lock_region (IStream *self_ptr,
60 ULARGE_INTEGER lock_offset,
61 ULARGE_INTEGER lock_bytes,
62 DWORD lock_type);
63 static HRESULT STDMETHODCALLTYPE _file_sync_stream_revert (IStream *self_ptr);
64 static HRESULT STDMETHODCALLTYPE _file_sync_stream_seek (IStream *self_ptr,
65 LARGE_INTEGER move_distance,
66 DWORD origin,
67 ULARGE_INTEGER *output_new_position);
68 static HRESULT STDMETHODCALLTYPE _file_sync_stream_set_size (IStream *self_ptr,
69 ULARGE_INTEGER new_size);
70 static HRESULT STDMETHODCALLTYPE _file_sync_stream_stat (IStream *self_ptr,
71 STATSTG *output_stat,
72 DWORD flags);
73 static HRESULT STDMETHODCALLTYPE _file_sync_stream_unlock_region (IStream *self_ptr,
74 ULARGE_INTEGER lock_offset,
75 ULARGE_INTEGER lock_bytes,
76 DWORD lock_type);
77
78 static void _file_sync_stream_free (GWin32FileSyncStream *self);
79
80 static HRESULT STDMETHODCALLTYPE
81 _file_sync_stream_query_interface (IStream *self_ptr,
82 REFIID ref_interface_guid,
83 LPVOID *output_object_ptr)
84 {
85 *output_object_ptr = NULL;
86
87 if (IsEqualGUID (ref_interface_guid, &IID_IUnknown))
88 {
89 IUnknown_AddRef ((IUnknown *) self_ptr);
90 *output_object_ptr = self_ptr;
91 return S_OK;
92 }
93 else if (IsEqualGUID (ref_interface_guid, &IID_IStream))
94 {
95 IStream_AddRef (self_ptr);
96 *output_object_ptr = self_ptr;
97 return S_OK;
98 }
99
100 return E_NOINTERFACE;
101 }
102
103 static ULONG STDMETHODCALLTYPE
104 _file_sync_stream_add_ref (IStream *self_ptr)
105 {
106 GWin32FileSyncStream *self = (GWin32FileSyncStream *) self_ptr;
107
108 return ++self->ref_count;
109 }
110
111 static ULONG STDMETHODCALLTYPE
112 _file_sync_stream_release (IStream *self_ptr)
113 {
114 GWin32FileSyncStream *self = (GWin32FileSyncStream *) self_ptr;
115
116 int ref_count = --self->ref_count;
117
118 if (ref_count == 0)
119 _file_sync_stream_free (self);
120
121 return ref_count;
122 }
123
124 static HRESULT STDMETHODCALLTYPE
125 _file_sync_stream_read (IStream *self_ptr,
126 void *output_data,
127 ULONG bytes_to_read,
128 ULONG *output_bytes_read)
129 {
130 GWin32FileSyncStream *self = (GWin32FileSyncStream *) self_ptr;
131 DWORD bytes_read;
132
133 if (!ReadFile (self->file_handle, output_data, bytes_to_read, &bytes_read, NULL))
134 {
135 DWORD error = GetLastError ();
136 return __HRESULT_FROM_WIN32 (error);
137 }
138
139 if (output_bytes_read)
140 *output_bytes_read = bytes_read;
141
142 return S_OK;
143 }
144
145 static HRESULT STDMETHODCALLTYPE
146 _file_sync_stream_write (IStream *self_ptr,
147 const void *data,
148 ULONG bytes_to_write,
149 ULONG *output_bytes_written)
150 {
151 GWin32FileSyncStream *self = (GWin32FileSyncStream *) self_ptr;
152 DWORD bytes_written;
153
154 if (!WriteFile (self->file_handle, data, bytes_to_write, &bytes_written, NULL))
155 {
156 DWORD error = GetLastError ();
157 return __HRESULT_FROM_WIN32 (error);
158 }
159
160 if (output_bytes_written)
161 *output_bytes_written = bytes_written;
162
163 return S_OK;
164 }
165
166 static HRESULT STDMETHODCALLTYPE
167 _file_sync_stream_seek (IStream *self_ptr,
168 LARGE_INTEGER move_distance,
169 DWORD origin,
170 ULARGE_INTEGER *output_new_position)
171 {
172 GWin32FileSyncStream *self = (GWin32FileSyncStream *) self_ptr;
173 LARGE_INTEGER new_position;
174 DWORD move_method;
175
176 switch (origin)
177 {
178 case STREAM_SEEK_SET:
179 move_method = FILE_BEGIN;
180 break;
181 case STREAM_SEEK_CUR:
182 move_method = FILE_CURRENT;
183 break;
184 case STREAM_SEEK_END:
185 move_method = FILE_END;
186 break;
187 default:
188 return E_INVALIDARG;
189 }
190
191 if (!SetFilePointerEx (self->file_handle, move_distance, &new_position, move_method))
192 {
193 DWORD error = GetLastError ();
194 return __HRESULT_FROM_WIN32 (error);
195 }
196
197 (*output_new_position).QuadPart = new_position.QuadPart;
198
199 return S_OK;
200 }
201
202 static HRESULT STDMETHODCALLTYPE
203 _file_sync_stream_set_size (IStream *self_ptr,
204 ULARGE_INTEGER new_size)
205 {
206 GWin32FileSyncStream *self = (GWin32FileSyncStream *) self_ptr;
207 FILE_END_OF_FILE_INFO info;
208
209 info.EndOfFile.QuadPart = new_size.QuadPart;
210
211 if (SetFileInformationByHandle (self->file_handle, FileEndOfFileInfo, &info, sizeof (info)))
212 {
213 DWORD error = GetLastError ();
214 return __HRESULT_FROM_WIN32 (error);
215 }
216
217 return S_OK;
218 }
219
220 static HRESULT STDMETHODCALLTYPE
221 _file_sync_stream_copy_to (IStream *self_ptr,
222 IStream *output_stream,
223 ULARGE_INTEGER bytes_to_copy,
224 ULARGE_INTEGER *output_bytes_read,
225 ULARGE_INTEGER *output_bytes_written)
226 {
227 ULARGE_INTEGER counter;
228 ULARGE_INTEGER written_counter;
229 ULARGE_INTEGER read_counter;
230
231 counter.QuadPart = bytes_to_copy.QuadPart;
232 written_counter.QuadPart = 0;
233 read_counter.QuadPart = 0;
234
235 while (counter.QuadPart > 0)
236 {
237 HRESULT hr;
238 ULONG bytes_read;
239 ULONG bytes_written;
240 ULONG bytes_index;
241 #define _INTERNAL_BUFSIZE 1024
242 BYTE buffer[_INTERNAL_BUFSIZE];
243 #undef _INTERNAL_BUFSIZE
244 ULONG buffer_size = sizeof (buffer);
245 ULONG to_read = buffer_size;
246
247 if (counter.QuadPart < buffer_size)
248 to_read = (ULONG) counter.QuadPart;
249
250 /* Because MS SDK has a function IStream_Read() with 3 arguments */
251 hr = self_ptr->lpVtbl->Read (self_ptr, buffer, to_read, &bytes_read);
252 if (!SUCCEEDED (hr))
253 return hr;
254
255 read_counter.QuadPart += bytes_read;
256
257 if (bytes_read == 0)
258 break;
259
260 bytes_index = 0;
261
262 while (bytes_index < bytes_read)
263 {
264 /* Because MS SDK has a function IStream_Write() with 3 arguments */
265 hr = output_stream->lpVtbl->Write (output_stream, &buffer[bytes_index], bytes_read - bytes_index, &bytes_written);
266 if (!SUCCEEDED (hr))
267 return hr;
268
269 if (bytes_written == 0)
270 return __HRESULT_FROM_WIN32 (ERROR_WRITE_FAULT);
271
272 bytes_index += bytes_written;
273 written_counter.QuadPart += bytes_written;
274 }
275 }
276
277 if (output_bytes_read)
278 output_bytes_read->QuadPart = read_counter.QuadPart;
279 if (output_bytes_written)
280 output_bytes_written->QuadPart = written_counter.QuadPart;
281
282 return S_OK;
283 }
284
285 static HRESULT STDMETHODCALLTYPE
286 _file_sync_stream_commit (IStream *self_ptr,
287 DWORD commit_flags)
288 {
289 GWin32FileSyncStream *self = (GWin32FileSyncStream *) self_ptr;
290
291 if (!FlushFileBuffers (self->file_handle))
292 {
293 DWORD error = GetLastError ();
294 return __HRESULT_FROM_WIN32 (error);
295 }
296
297 return S_OK;
298 }
299
300 static HRESULT STDMETHODCALLTYPE
301 _file_sync_stream_revert (IStream *self_ptr)
302 {
303 return E_NOTIMPL;
304 }
305
306 static HRESULT STDMETHODCALLTYPE
307 _file_sync_stream_lock_region (IStream *self_ptr,
308 ULARGE_INTEGER lock_offset,
309 ULARGE_INTEGER lock_bytes,
310 DWORD lock_type)
311 {
312 return STG_E_INVALIDFUNCTION;
313 }
314
315 static HRESULT STDMETHODCALLTYPE
316 _file_sync_stream_unlock_region (IStream *self_ptr,
317 ULARGE_INTEGER lock_offset,
318 ULARGE_INTEGER lock_bytes,
319 DWORD lock_type)
320 {
321 return STG_E_INVALIDFUNCTION;
322 }
323
324 static HRESULT STDMETHODCALLTYPE
325 _file_sync_stream_stat (IStream *self_ptr,
326 STATSTG *output_stat,
327 DWORD flags)
328 {
329 GWin32FileSyncStream *self = (GWin32FileSyncStream *) self_ptr;
330 BOOL get_name = FALSE;
331 FILE_BASIC_INFO bi;
332 FILE_STANDARD_INFO si;
333
334 if (output_stat == NULL)
335 return STG_E_INVALIDPOINTER;
336
337 switch (flags)
338 {
339 case STATFLAG_DEFAULT:
340 get_name = TRUE;
341 break;
342 case STATFLAG_NONAME:
343 get_name = FALSE;
344 break;
345 default:
346 return STG_E_INVALIDFLAG;
347 }
348
349 if (!GetFileInformationByHandleEx (self->file_handle, FileBasicInfo, &bi, sizeof (bi)) ||
350 !GetFileInformationByHandleEx (self->file_handle, FileStandardInfo, &si, sizeof (si)))
351 {
352 DWORD error = GetLastError ();
353 return __HRESULT_FROM_WIN32 (error);
354 }
355
356 output_stat->type = STGTY_STREAM;
357 output_stat->mtime.dwLowDateTime = bi.LastWriteTime.LowPart;
358 output_stat->mtime.dwHighDateTime = bi.LastWriteTime.HighPart;
359 output_stat->ctime.dwLowDateTime = bi.CreationTime.LowPart;
360 output_stat->ctime.dwHighDateTime = bi.CreationTime.HighPart;
361 output_stat->atime.dwLowDateTime = bi.LastAccessTime.LowPart;
362 output_stat->atime.dwHighDateTime = bi.LastAccessTime.HighPart;
363 output_stat->grfLocksSupported = 0;
364 memset (&output_stat->clsid, 0, sizeof (CLSID));
365 output_stat->grfStateBits = 0;
366 output_stat->reserved = 0;
367 output_stat->cbSize.QuadPart = si.EndOfFile.QuadPart;
368 output_stat->grfMode = self->stgm_mode;
369
370 if (get_name)
371 {
372 DWORD tries;
373 wchar_t *buffer;
374
375 /* Nothing in the documentation guarantees that the name
376 * won't change between two invocations (one - to get the
377 * buffer size, the other - to fill the buffer).
378 * Re-try up to 5 times in case the required buffer size
379 * doesn't match.
380 */
381 for (tries = 5; tries > 0; tries--)
382 {
383 DWORD buffer_size;
384 DWORD buffer_size2;
385 DWORD error;
386
387 buffer_size = GetFinalPathNameByHandleW (self->file_handle, NULL, 0, 0);
388
389 if (buffer_size == 0)
390 {
391 DWORD my_error = GetLastError ();
392 return __HRESULT_FROM_WIN32 (my_error);
393 }
394
395 buffer = CoTaskMemAlloc (buffer_size);
396 buffer[buffer_size - 1] = 0;
397 buffer_size2 = GetFinalPathNameByHandleW (self->file_handle, buffer, buffer_size, 0);
398
399 if (buffer_size2 < buffer_size)
400 break;
401
402 error = GetLastError ();
403 CoTaskMemFree (buffer);
404 if (buffer_size2 == 0)
405 return __HRESULT_FROM_WIN32 (error);
406 }
407
408 if (tries == 0)
409 return __HRESULT_FROM_WIN32 (ERROR_BAD_LENGTH);
410
411 output_stat->pwcsName = buffer;
412 }
413 else
414 output_stat->pwcsName = NULL;
415
416 return S_OK;
417 }
418
419 static HRESULT STDMETHODCALLTYPE
420 _file_sync_stream_clone (IStream *self_ptr,
421 IStream **output_clone_ptr)
422 {
423 return E_NOTIMPL;
424 }
425
426 static IStreamVtbl _file_sync_stream_vtbl = {
427 _file_sync_stream_query_interface,
428 _file_sync_stream_add_ref,
429 _file_sync_stream_release,
430 _file_sync_stream_read,
431 _file_sync_stream_write,
432 _file_sync_stream_seek,
433 _file_sync_stream_set_size,
434 _file_sync_stream_copy_to,
435 _file_sync_stream_commit,
436 _file_sync_stream_revert,
437 _file_sync_stream_lock_region,
438 _file_sync_stream_unlock_region,
439 _file_sync_stream_stat,
440 _file_sync_stream_clone,
441 };
442
443 static void
444 _file_sync_stream_free (GWin32FileSyncStream *self)
445 {
446 if (self->owns_handle)
447 CloseHandle (self->file_handle);
448
449 g_free (self);
450 }
451
452 /**
453 * g_win32_file_sync_stream_new:
454 * @handle: a Win32 HANDLE for a file.
455 * @owns_handle: %TRUE if newly-created stream owns the handle
456 * (and closes it when destroyed)
457 * @stgm_mode: a combination of [STGM constants](https://docs.microsoft.com/en-us/windows/win32/stg/stgm-constants)
458 * that specify the mode with which the stream
459 * is opened.
460 * @output_hresult: (out) (optional): a HRESULT from the internal COM calls.
461 * Will be `S_OK` on success.
462 *
463 * Creates an IStream object backed by a HANDLE.
464 *
465 * @stgm_mode should match the mode of the @handle, otherwise the stream might
466 * attempt to perform operations that the @handle does not allow. The implementation
467 * itself ignores these flags completely, they are only used to report
468 * the mode of the stream to third parties.
469 *
470 * The stream only does synchronous access and will never return `E_PENDING` on I/O.
471 *
472 * The returned stream object should be treated just like any other
473 * COM object, and released via `IUnknown_Release()`.
474 * its elements have been unreffed with g_object_unref().
475 *
476 * Returns: (nullable) (transfer full): a new IStream object on success, %NULL on failure.
477 **/
478 IStream *
479 g_win32_file_sync_stream_new (HANDLE file_handle,
480 gboolean owns_handle,
481 DWORD stgm_mode,
482 HRESULT *output_hresult)
483 {
484 GWin32FileSyncStream *new_stream;
485 IStream *result;
486 HRESULT hr;
487
488 new_stream = g_new0 (GWin32FileSyncStream, 1);
489 new_stream->self.lpVtbl = &_file_sync_stream_vtbl;
490
491 hr = IUnknown_QueryInterface ((IUnknown *) new_stream, &IID_IStream, (void **) &result);
492 if (!SUCCEEDED (hr))
493 {
494 g_free (new_stream);
495
496 if (output_hresult)
497 *output_hresult = hr;
498
499 return NULL;
500 }
501
502 new_stream->stgm_mode = stgm_mode;
503 new_stream->file_handle = file_handle;
504 new_stream->owns_handle = owns_handle;
505
506 if (output_hresult)
507 *output_hresult = S_OK;
508
509 return result;
510 }