From 895fc2eff2bc32f9936285c436003b587e9eda0b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=A0=D1=83=D1=81=D0=BB=D0=B0=D0=BD=20=D0=98=D0=B6=D0=B1?= =?UTF-8?q?=D1=83=D0=BB=D0=B0=D1=82=D0=BE=D0=B2?= Date: Thu, 4 Jun 2020 15:02:02 +0000 Subject: [PATCH] gio: add GWin32FileSyncStream This is a COM object that implements IStream by using a HANDLE and WinAPI file functions to access the file (only a file; pipes are not supported). Only supports synchronous access (this is a feature - the APIs that read from this stream internally will never return the COM equivalent of EWOULDBLOCK, which greatly simplifies their use). --- gio/gwin32file-sync-stream.c | 508 +++++++++++++++++++++++++++++++++++ gio/gwin32file-sync-stream.h | 44 +++ gio/meson.build | 1 + 3 files changed, 553 insertions(+) create mode 100755 gio/gwin32file-sync-stream.c create mode 100755 gio/gwin32file-sync-stream.h diff --git a/gio/gwin32file-sync-stream.c b/gio/gwin32file-sync-stream.c new file mode 100755 index 000000000..bc3b60694 --- /dev/null +++ b/gio/gwin32file-sync-stream.c @@ -0,0 +1,508 @@ +/* gwin32file-sync-stream.c - a simple IStream implementation + * + * Copyright 2020 Руслан Ижбулатов + * + * 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 error = GetLastError (); + return __HRESULT_FROM_WIN32 (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; +} diff --git a/gio/gwin32file-sync-stream.h b/gio/gwin32file-sync-stream.h new file mode 100755 index 000000000..8e7f5fd59 --- /dev/null +++ b/gio/gwin32file-sync-stream.h @@ -0,0 +1,44 @@ +/* GIO - GLib Input, Output and Streaming Library + * + * Copyright (C) 2020 Руслан Ижбулатов + * + * 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 . + * + */ +#ifndef __G_WIN32_FILE_SYNC_STREAM_H__ +#define __G_WIN32_FILE_SYNC_STREAM_H__ + +#include + +#ifdef G_PLATFORM_WIN32 + +typedef struct _GWin32FileSyncStream GWin32FileSyncStream; + +struct _GWin32FileSyncStream +{ + IStream self; + ULONG ref_count; + HANDLE file_handle; + gboolean owns_handle; + DWORD stgm_mode; +}; + +IStream *g_win32_file_sync_stream_new (HANDLE file_handle, + gboolean owns_handle, + DWORD stgm_mode, + HRESULT *output_hresult); + +#endif /* G_PLATFORM_WIN32 */ + +#endif /* __G_WIN32_FILE_SYNC_STREAM_H__ */ \ No newline at end of file diff --git a/gio/meson.build b/gio/meson.build index f79d22053..b452f071b 100644 --- a/gio/meson.build +++ b/gio/meson.build @@ -436,6 +436,7 @@ else 'gwin32volumemonitor.c', 'gwin32inputstream.c', 'gwin32outputstream.c', + 'gwin32file-sync-stream.c', 'gwin32networkmonitor.c', 'gwin32networkmonitor.h', 'gwin32notificationbackend.c',