| /* gwin32file-sync-stream.c - a simple IStream implementation |
| * |
| * Copyright 2020 Руслан Ижбулатов |
| * |
| * SPDX-License-Identifier: LGPL-2.1-or-later |
| * |
| * This library is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU Lesser General Public |
| * License as published by the Free Software Foundation; either |
| * version 2.1 of the License, or (at your option) any later version. |
| * |
| * This library is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| * Lesser General Public License for more details. |
| * |
| * You should have received a copy of the GNU Lesser General Public License |
| * along with this library; if not, see <http://www.gnu.org/licenses/>. |
| */ |
| |
| /* A COM object that implements an IStream backed by a file HANDLE. |
| * Works just like `SHCreateStreamOnFileEx()`, but does not |
| * support locking, and doesn't force us to link to libshlwapi. |
| * Only supports synchronous access. |
| */ |
| #include "config.h" |
| #define COBJMACROS |
| #define INITGUID |
| #include <windows.h> |
| |
| #include "gwin32file-sync-stream.h" |
| |
| static HRESULT STDMETHODCALLTYPE _file_sync_stream_query_interface (IStream *self_ptr, |
| REFIID ref_interface_guid, |
| LPVOID *output_object_ptr); |
| static ULONG STDMETHODCALLTYPE _file_sync_stream_release (IStream *self_ptr); |
| static ULONG STDMETHODCALLTYPE _file_sync_stream_add_ref (IStream *self_ptr); |
| |
| static HRESULT STDMETHODCALLTYPE _file_sync_stream_read (IStream *self_ptr, |
| void *output_data, |
| ULONG bytes_to_read, |
| ULONG *output_bytes_read); |
| |
| static HRESULT STDMETHODCALLTYPE _file_sync_stream_write (IStream *self_ptr, |
| const void *data, |
| ULONG bytes_to_write, |
| ULONG *output_bytes_written); |
| |
| |
| static HRESULT STDMETHODCALLTYPE _file_sync_stream_clone (IStream *self_ptr, |
| IStream **output_clone_ptr); |
| static HRESULT STDMETHODCALLTYPE _file_sync_stream_commit (IStream *self_ptr, |
| DWORD commit_flags); |
| 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); |
| static HRESULT STDMETHODCALLTYPE _file_sync_stream_lock_region (IStream *self_ptr, |
| ULARGE_INTEGER lock_offset, |
| ULARGE_INTEGER lock_bytes, |
| DWORD lock_type); |
| static HRESULT STDMETHODCALLTYPE _file_sync_stream_revert (IStream *self_ptr); |
| static HRESULT STDMETHODCALLTYPE _file_sync_stream_seek (IStream *self_ptr, |
| LARGE_INTEGER move_distance, |
| DWORD origin, |
| ULARGE_INTEGER *output_new_position); |
| static HRESULT STDMETHODCALLTYPE _file_sync_stream_set_size (IStream *self_ptr, |
| ULARGE_INTEGER new_size); |
| static HRESULT STDMETHODCALLTYPE _file_sync_stream_stat (IStream *self_ptr, |
| STATSTG *output_stat, |
| DWORD flags); |
| static HRESULT STDMETHODCALLTYPE _file_sync_stream_unlock_region (IStream *self_ptr, |
| ULARGE_INTEGER lock_offset, |
| ULARGE_INTEGER lock_bytes, |
| DWORD lock_type); |
| |
| static void _file_sync_stream_free (GWin32FileSyncStream *self); |
| |
| static HRESULT STDMETHODCALLTYPE |
| _file_sync_stream_query_interface (IStream *self_ptr, |
| REFIID ref_interface_guid, |
| LPVOID *output_object_ptr) |
| { |
| *output_object_ptr = NULL; |
| |
| if (IsEqualGUID (ref_interface_guid, &IID_IUnknown)) |
| { |
| IUnknown_AddRef ((IUnknown *) self_ptr); |
| *output_object_ptr = self_ptr; |
| return S_OK; |
| } |
| else if (IsEqualGUID (ref_interface_guid, &IID_IStream)) |
| { |
| IStream_AddRef (self_ptr); |
| *output_object_ptr = self_ptr; |
| return S_OK; |
| } |
| |
| return E_NOINTERFACE; |
| } |
| |
| static ULONG STDMETHODCALLTYPE |
| _file_sync_stream_add_ref (IStream *self_ptr) |
| { |
| GWin32FileSyncStream *self = (GWin32FileSyncStream *) self_ptr; |
| |
| return ++self->ref_count; |
| } |
| |
| static ULONG STDMETHODCALLTYPE |
| _file_sync_stream_release (IStream *self_ptr) |
| { |
| GWin32FileSyncStream *self = (GWin32FileSyncStream *) self_ptr; |
| |
| int ref_count = --self->ref_count; |
| |
| if (ref_count == 0) |
| _file_sync_stream_free (self); |
| |
| return ref_count; |
| } |
| |
| static HRESULT STDMETHODCALLTYPE |
| _file_sync_stream_read (IStream *self_ptr, |
| void *output_data, |
| ULONG bytes_to_read, |
| ULONG *output_bytes_read) |
| { |
| GWin32FileSyncStream *self = (GWin32FileSyncStream *) self_ptr; |
| DWORD bytes_read; |
| |
| if (!ReadFile (self->file_handle, output_data, bytes_to_read, &bytes_read, NULL)) |
| { |
| DWORD error = GetLastError (); |
| return __HRESULT_FROM_WIN32 (error); |
| } |
| |
| if (output_bytes_read) |
| *output_bytes_read = bytes_read; |
| |
| return S_OK; |
| } |
| |
| static HRESULT STDMETHODCALLTYPE |
| _file_sync_stream_write (IStream *self_ptr, |
| const void *data, |
| ULONG bytes_to_write, |
| ULONG *output_bytes_written) |
| { |
| GWin32FileSyncStream *self = (GWin32FileSyncStream *) self_ptr; |
| DWORD bytes_written; |
| |
| if (!WriteFile (self->file_handle, data, bytes_to_write, &bytes_written, NULL)) |
| { |
| DWORD error = GetLastError (); |
| return __HRESULT_FROM_WIN32 (error); |
| } |
| |
| if (output_bytes_written) |
| *output_bytes_written = bytes_written; |
| |
| return S_OK; |
| } |
| |
| static HRESULT STDMETHODCALLTYPE |
| _file_sync_stream_seek (IStream *self_ptr, |
| LARGE_INTEGER move_distance, |
| DWORD origin, |
| ULARGE_INTEGER *output_new_position) |
| { |
| GWin32FileSyncStream *self = (GWin32FileSyncStream *) self_ptr; |
| LARGE_INTEGER new_position; |
| DWORD move_method; |
| |
| switch (origin) |
| { |
| case STREAM_SEEK_SET: |
| move_method = FILE_BEGIN; |
| break; |
| case STREAM_SEEK_CUR: |
| move_method = FILE_CURRENT; |
| break; |
| case STREAM_SEEK_END: |
| move_method = FILE_END; |
| break; |
| default: |
| return E_INVALIDARG; |
| } |
| |
| if (!SetFilePointerEx (self->file_handle, move_distance, &new_position, move_method)) |
| { |
| DWORD error = GetLastError (); |
| return __HRESULT_FROM_WIN32 (error); |
| } |
| |
| (*output_new_position).QuadPart = new_position.QuadPart; |
| |
| return S_OK; |
| } |
| |
| static HRESULT STDMETHODCALLTYPE |
| _file_sync_stream_set_size (IStream *self_ptr, |
| ULARGE_INTEGER new_size) |
| { |
| GWin32FileSyncStream *self = (GWin32FileSyncStream *) self_ptr; |
| FILE_END_OF_FILE_INFO info; |
| |
| info.EndOfFile.QuadPart = new_size.QuadPart; |
| |
| if (SetFileInformationByHandle (self->file_handle, FileEndOfFileInfo, &info, sizeof (info))) |
| { |
| DWORD error = GetLastError (); |
| return __HRESULT_FROM_WIN32 (error); |
| } |
| |
| return S_OK; |
| } |
| |
| 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) |
| { |
| ULARGE_INTEGER counter; |
| ULARGE_INTEGER written_counter; |
| ULARGE_INTEGER read_counter; |
| |
| counter.QuadPart = bytes_to_copy.QuadPart; |
| written_counter.QuadPart = 0; |
| read_counter.QuadPart = 0; |
| |
| while (counter.QuadPart > 0) |
| { |
| HRESULT hr; |
| ULONG bytes_read; |
| ULONG bytes_written; |
| ULONG bytes_index; |
| #define _INTERNAL_BUFSIZE 1024 |
| BYTE buffer[_INTERNAL_BUFSIZE]; |
| #undef _INTERNAL_BUFSIZE |
| ULONG buffer_size = sizeof (buffer); |
| ULONG to_read = buffer_size; |
| |
| if (counter.QuadPart < buffer_size) |
| to_read = (ULONG) counter.QuadPart; |
| |
| /* Because MS SDK has a function IStream_Read() with 3 arguments */ |
| hr = self_ptr->lpVtbl->Read (self_ptr, buffer, to_read, &bytes_read); |
| if (!SUCCEEDED (hr)) |
| return hr; |
| |
| read_counter.QuadPart += bytes_read; |
| |
| if (bytes_read == 0) |
| break; |
| |
| bytes_index = 0; |
| |
| while (bytes_index < bytes_read) |
| { |
| /* Because MS SDK has a function IStream_Write() with 3 arguments */ |
| hr = output_stream->lpVtbl->Write (output_stream, &buffer[bytes_index], bytes_read - bytes_index, &bytes_written); |
| if (!SUCCEEDED (hr)) |
| return hr; |
| |
| if (bytes_written == 0) |
| return __HRESULT_FROM_WIN32 (ERROR_WRITE_FAULT); |
| |
| bytes_index += bytes_written; |
| written_counter.QuadPart += bytes_written; |
| } |
| } |
| |
| if (output_bytes_read) |
| output_bytes_read->QuadPart = read_counter.QuadPart; |
| if (output_bytes_written) |
| output_bytes_written->QuadPart = written_counter.QuadPart; |
| |
| return S_OK; |
| } |
| |
| static HRESULT STDMETHODCALLTYPE |
| _file_sync_stream_commit (IStream *self_ptr, |
| DWORD commit_flags) |
| { |
| GWin32FileSyncStream *self = (GWin32FileSyncStream *) self_ptr; |
| |
| if (!FlushFileBuffers (self->file_handle)) |
| { |
| DWORD error = GetLastError (); |
| return __HRESULT_FROM_WIN32 (error); |
| } |
| |
| return S_OK; |
| } |
| |
| static HRESULT STDMETHODCALLTYPE |
| _file_sync_stream_revert (IStream *self_ptr) |
| { |
| return E_NOTIMPL; |
| } |
| |
| static HRESULT STDMETHODCALLTYPE |
| _file_sync_stream_lock_region (IStream *self_ptr, |
| ULARGE_INTEGER lock_offset, |
| ULARGE_INTEGER lock_bytes, |
| DWORD lock_type) |
| { |
| return STG_E_INVALIDFUNCTION; |
| } |
| |
| static HRESULT STDMETHODCALLTYPE |
| _file_sync_stream_unlock_region (IStream *self_ptr, |
| ULARGE_INTEGER lock_offset, |
| ULARGE_INTEGER lock_bytes, |
| DWORD lock_type) |
| { |
| return STG_E_INVALIDFUNCTION; |
| } |
| |
| static HRESULT STDMETHODCALLTYPE |
| _file_sync_stream_stat (IStream *self_ptr, |
| STATSTG *output_stat, |
| DWORD flags) |
| { |
| GWin32FileSyncStream *self = (GWin32FileSyncStream *) self_ptr; |
| BOOL get_name = FALSE; |
| FILE_BASIC_INFO bi; |
| FILE_STANDARD_INFO si; |
| |
| if (output_stat == NULL) |
| return STG_E_INVALIDPOINTER; |
| |
| switch (flags) |
| { |
| case STATFLAG_DEFAULT: |
| get_name = TRUE; |
| break; |
| case STATFLAG_NONAME: |
| get_name = FALSE; |
| break; |
| default: |
| return STG_E_INVALIDFLAG; |
| } |
| |
| if (!GetFileInformationByHandleEx (self->file_handle, FileBasicInfo, &bi, sizeof (bi)) || |
| !GetFileInformationByHandleEx (self->file_handle, FileStandardInfo, &si, sizeof (si))) |
| { |
| DWORD error = GetLastError (); |
| return __HRESULT_FROM_WIN32 (error); |
| } |
| |
| output_stat->type = STGTY_STREAM; |
| output_stat->mtime.dwLowDateTime = bi.LastWriteTime.LowPart; |
| output_stat->mtime.dwHighDateTime = bi.LastWriteTime.HighPart; |
| output_stat->ctime.dwLowDateTime = bi.CreationTime.LowPart; |
| output_stat->ctime.dwHighDateTime = bi.CreationTime.HighPart; |
| output_stat->atime.dwLowDateTime = bi.LastAccessTime.LowPart; |
| output_stat->atime.dwHighDateTime = bi.LastAccessTime.HighPart; |
| output_stat->grfLocksSupported = 0; |
| memset (&output_stat->clsid, 0, sizeof (CLSID)); |
| output_stat->grfStateBits = 0; |
| output_stat->reserved = 0; |
| output_stat->cbSize.QuadPart = si.EndOfFile.QuadPart; |
| output_stat->grfMode = self->stgm_mode; |
| |
| if (get_name) |
| { |
| DWORD tries; |
| wchar_t *buffer; |
| |
| /* Nothing in the documentation guarantees that the name |
| * won't change between two invocations (one - to get the |
| * buffer size, the other - to fill the buffer). |
| * Re-try up to 5 times in case the required buffer size |
| * doesn't match. |
| */ |
| for (tries = 5; tries > 0; tries--) |
| { |
| DWORD buffer_size; |
| DWORD buffer_size2; |
| DWORD error; |
| |
| buffer_size = GetFinalPathNameByHandleW (self->file_handle, NULL, 0, 0); |
| |
| if (buffer_size == 0) |
| { |
| DWORD my_error = GetLastError (); |
| return __HRESULT_FROM_WIN32 (my_error); |
| } |
| |
| buffer = CoTaskMemAlloc (buffer_size); |
| buffer[buffer_size - 1] = 0; |
| buffer_size2 = GetFinalPathNameByHandleW (self->file_handle, buffer, buffer_size, 0); |
| |
| if (buffer_size2 < buffer_size) |
| break; |
| |
| error = GetLastError (); |
| CoTaskMemFree (buffer); |
| if (buffer_size2 == 0) |
| return __HRESULT_FROM_WIN32 (error); |
| } |
| |
| if (tries == 0) |
| return __HRESULT_FROM_WIN32 (ERROR_BAD_LENGTH); |
| |
| output_stat->pwcsName = buffer; |
| } |
| else |
| output_stat->pwcsName = NULL; |
| |
| return S_OK; |
| } |
| |
| static HRESULT STDMETHODCALLTYPE |
| _file_sync_stream_clone (IStream *self_ptr, |
| IStream **output_clone_ptr) |
| { |
| return E_NOTIMPL; |
| } |
| |
| static IStreamVtbl _file_sync_stream_vtbl = { |
| _file_sync_stream_query_interface, |
| _file_sync_stream_add_ref, |
| _file_sync_stream_release, |
| _file_sync_stream_read, |
| _file_sync_stream_write, |
| _file_sync_stream_seek, |
| _file_sync_stream_set_size, |
| _file_sync_stream_copy_to, |
| _file_sync_stream_commit, |
| _file_sync_stream_revert, |
| _file_sync_stream_lock_region, |
| _file_sync_stream_unlock_region, |
| _file_sync_stream_stat, |
| _file_sync_stream_clone, |
| }; |
| |
| static void |
| _file_sync_stream_free (GWin32FileSyncStream *self) |
| { |
| if (self->owns_handle) |
| CloseHandle (self->file_handle); |
| |
| g_free (self); |
| } |
| |
| /** |
| * g_win32_file_sync_stream_new: |
| * @handle: a Win32 HANDLE for a file. |
| * @owns_handle: %TRUE if newly-created stream owns the handle |
| * (and closes it when destroyed) |
| * @stgm_mode: a combination of [STGM constants](https://docs.microsoft.com/en-us/windows/win32/stg/stgm-constants) |
| * that specify the mode with which the stream |
| * is opened. |
| * @output_hresult: (out) (optional): a HRESULT from the internal COM calls. |
| * Will be `S_OK` on success. |
| * |
| * Creates an IStream object backed by a HANDLE. |
| * |
| * @stgm_mode should match the mode of the @handle, otherwise the stream might |
| * attempt to perform operations that the @handle does not allow. The implementation |
| * itself ignores these flags completely, they are only used to report |
| * the mode of the stream to third parties. |
| * |
| * The stream only does synchronous access and will never return `E_PENDING` on I/O. |
| * |
| * The returned stream object should be treated just like any other |
| * COM object, and released via `IUnknown_Release()`. |
| * its elements have been unreffed with g_object_unref(). |
| * |
| * Returns: (nullable) (transfer full): a new IStream object on success, %NULL on failure. |
| **/ |
| IStream * |
| g_win32_file_sync_stream_new (HANDLE file_handle, |
| gboolean owns_handle, |
| DWORD stgm_mode, |
| HRESULT *output_hresult) |
| { |
| GWin32FileSyncStream *new_stream; |
| IStream *result; |
| HRESULT hr; |
| |
| new_stream = g_new0 (GWin32FileSyncStream, 1); |
| new_stream->self.lpVtbl = &_file_sync_stream_vtbl; |
| |
| hr = IUnknown_QueryInterface ((IUnknown *) new_stream, &IID_IStream, (void **) &result); |
| if (!SUCCEEDED (hr)) |
| { |
| g_free (new_stream); |
| |
| if (output_hresult) |
| *output_hresult = hr; |
| |
| return NULL; |
| } |
| |
| new_stream->stgm_mode = stgm_mode; |
| new_stream->file_handle = file_handle; |
| new_stream->owns_handle = owns_handle; |
| |
| if (output_hresult) |
| *output_hresult = S_OK; |
| |
| return result; |
| } |