[glib: 4/19] gio: add GWin32FileSyncStream




commit 895fc2eff2bc32f9936285c436003b587e9eda0b
Author: Руслан Ижбулатов <lrn1986 gmail com>
Date:   Thu Jun 4 15:02:02 2020 +0000

    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(+)
---
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 <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 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 Руслан Ижбулатов <lrn1986 gmail com>
+ *
+ * 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/>.
+ *
+ */
+#ifndef __G_WIN32_FILE_SYNC_STREAM_H__
+#define __G_WIN32_FILE_SYNC_STREAM_H__
+
+#include <gio/gio.h>
+
+#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',


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]