/* 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 . */ /* 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 #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; }