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