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