(root)/
glib-2.79.0/
gio/
gwin32file-sync-stream.c
       1  /* gwin32file-sync-stream.c - a simple IStream implementation
       2   *
       3   * Copyright 2020 Руслан Ижбулатов
       4   *
       5   * SPDX-License-Identifier: LGPL-2.1-or-later
       6   *
       7   * This library is free software; you can redistribute it and/or
       8   * modify it under the terms of the GNU Lesser General Public
       9   * License as published by the Free Software Foundation; either
      10   * version 2.1 of the License, or (at your option) any later version.
      11   *
      12   * This library is distributed in the hope that it will be useful,
      13   * but WITHOUT ANY WARRANTY; without even the implied warranty of
      14   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
      15   * Lesser General Public License for more details.
      16   *
      17   * You should have received a copy of the GNU Lesser General Public License
      18   * along with this library; if not, see <http://www.gnu.org/licenses/>.
      19   */
      20  
      21  /* A COM object that implements an IStream backed by a file HANDLE.
      22   * Works just like `SHCreateStreamOnFileEx()`, but does not
      23   * support locking, and doesn't force us to link to libshlwapi.
      24   * Only supports synchronous access.
      25   */
      26  #include "config.h"
      27  #define COBJMACROS
      28  #define INITGUID
      29  #include <windows.h>
      30  
      31  #include "gwin32file-sync-stream.h"
      32  
      33  static HRESULT STDMETHODCALLTYPE _file_sync_stream_query_interface (IStream         *self_ptr,
      34                                                                      REFIID           ref_interface_guid,
      35                                                                      LPVOID          *output_object_ptr);
      36  static ULONG STDMETHODCALLTYPE   _file_sync_stream_release         (IStream         *self_ptr);
      37  static ULONG STDMETHODCALLTYPE   _file_sync_stream_add_ref         (IStream         *self_ptr);
      38  
      39  static HRESULT STDMETHODCALLTYPE _file_sync_stream_read            (IStream         *self_ptr,
      40                                                                      void            *output_data,
      41                                                                      ULONG            bytes_to_read,
      42                                                                      ULONG           *output_bytes_read);
      43  
      44  static HRESULT STDMETHODCALLTYPE _file_sync_stream_write           (IStream         *self_ptr,
      45                                                                      const void      *data,
      46                                                                      ULONG            bytes_to_write,
      47                                                                      ULONG           *output_bytes_written);
      48  
      49  
      50  static HRESULT STDMETHODCALLTYPE _file_sync_stream_clone           (IStream         *self_ptr,
      51                                                                      IStream        **output_clone_ptr);
      52  static HRESULT STDMETHODCALLTYPE _file_sync_stream_commit          (IStream         *self_ptr,
      53                                                                      DWORD            commit_flags);
      54  static HRESULT STDMETHODCALLTYPE _file_sync_stream_copy_to         (IStream         *self_ptr,
      55                                                                      IStream         *output_stream,
      56                                                                      ULARGE_INTEGER   bytes_to_copy,
      57                                                                      ULARGE_INTEGER  *output_bytes_read,
      58                                                                      ULARGE_INTEGER  *output_bytes_written);
      59  static HRESULT STDMETHODCALLTYPE _file_sync_stream_lock_region     (IStream         *self_ptr,
      60                                                                      ULARGE_INTEGER   lock_offset,
      61                                                                      ULARGE_INTEGER   lock_bytes,
      62                                                                      DWORD            lock_type);
      63  static HRESULT STDMETHODCALLTYPE _file_sync_stream_revert          (IStream         *self_ptr);
      64  static HRESULT STDMETHODCALLTYPE _file_sync_stream_seek            (IStream         *self_ptr,
      65                                                                      LARGE_INTEGER    move_distance,
      66                                                                      DWORD            origin,
      67                                                                      ULARGE_INTEGER  *output_new_position);
      68  static HRESULT STDMETHODCALLTYPE _file_sync_stream_set_size        (IStream         *self_ptr,
      69                                                                      ULARGE_INTEGER   new_size);
      70  static HRESULT STDMETHODCALLTYPE _file_sync_stream_stat            (IStream         *self_ptr,
      71                                                                      STATSTG         *output_stat,
      72                                                                      DWORD            flags);
      73  static HRESULT STDMETHODCALLTYPE _file_sync_stream_unlock_region   (IStream         *self_ptr,
      74                                                                      ULARGE_INTEGER   lock_offset,
      75                                                                      ULARGE_INTEGER   lock_bytes,
      76                                                                      DWORD            lock_type);
      77  
      78  static void _file_sync_stream_free (GWin32FileSyncStream *self);
      79  
      80  static HRESULT STDMETHODCALLTYPE
      81  _file_sync_stream_query_interface (IStream *self_ptr,
      82                                     REFIID   ref_interface_guid,
      83                                     LPVOID  *output_object_ptr)
      84  {
      85    *output_object_ptr = NULL;
      86  
      87    if (IsEqualGUID (ref_interface_guid, &IID_IUnknown))
      88      {
      89        IUnknown_AddRef ((IUnknown *) self_ptr);
      90        *output_object_ptr = self_ptr;
      91        return S_OK;
      92      }
      93    else if (IsEqualGUID (ref_interface_guid, &IID_IStream))
      94      {
      95        IStream_AddRef (self_ptr);
      96        *output_object_ptr = self_ptr;
      97        return S_OK;
      98      }
      99  
     100    return E_NOINTERFACE;
     101  }
     102  
     103  static ULONG STDMETHODCALLTYPE
     104  _file_sync_stream_add_ref (IStream *self_ptr)
     105  {
     106    GWin32FileSyncStream *self = (GWin32FileSyncStream *) self_ptr;
     107  
     108    return ++self->ref_count;
     109  }
     110  
     111  static ULONG STDMETHODCALLTYPE
     112  _file_sync_stream_release (IStream *self_ptr)
     113  {
     114    GWin32FileSyncStream *self = (GWin32FileSyncStream *) self_ptr;
     115  
     116    int ref_count = --self->ref_count;
     117  
     118    if (ref_count == 0)
     119      _file_sync_stream_free (self);
     120  
     121    return ref_count;
     122  }
     123  
     124  static HRESULT STDMETHODCALLTYPE
     125  _file_sync_stream_read (IStream *self_ptr,
     126                          void    *output_data,
     127                          ULONG    bytes_to_read,
     128                          ULONG   *output_bytes_read)
     129  {
     130    GWin32FileSyncStream *self = (GWin32FileSyncStream *) self_ptr;
     131    DWORD bytes_read;
     132  
     133    if (!ReadFile (self->file_handle, output_data, bytes_to_read, &bytes_read, NULL))
     134      {
     135        DWORD error = GetLastError ();
     136        return __HRESULT_FROM_WIN32 (error);
     137      }
     138  
     139    if (output_bytes_read)
     140      *output_bytes_read = bytes_read;
     141  
     142    return S_OK;
     143  }
     144  
     145  static HRESULT STDMETHODCALLTYPE
     146  _file_sync_stream_write (IStream    *self_ptr,
     147                           const void *data,
     148                           ULONG       bytes_to_write,
     149                           ULONG      *output_bytes_written)
     150  {
     151    GWin32FileSyncStream *self = (GWin32FileSyncStream *) self_ptr;
     152    DWORD bytes_written;
     153  
     154    if (!WriteFile (self->file_handle, data, bytes_to_write, &bytes_written, NULL))
     155      {
     156        DWORD error = GetLastError ();
     157        return __HRESULT_FROM_WIN32 (error);
     158      }
     159  
     160    if (output_bytes_written)
     161      *output_bytes_written = bytes_written;
     162  
     163    return S_OK;
     164  }
     165  
     166  static HRESULT STDMETHODCALLTYPE
     167  _file_sync_stream_seek (IStream        *self_ptr,
     168                          LARGE_INTEGER   move_distance,
     169                          DWORD           origin,
     170                          ULARGE_INTEGER *output_new_position)
     171  {
     172    GWin32FileSyncStream *self = (GWin32FileSyncStream *) self_ptr;
     173    LARGE_INTEGER new_position;
     174    DWORD move_method;
     175  
     176    switch (origin)
     177      {
     178      case STREAM_SEEK_SET:
     179        move_method = FILE_BEGIN;
     180        break;
     181      case STREAM_SEEK_CUR:
     182        move_method = FILE_CURRENT;
     183        break;
     184      case STREAM_SEEK_END:
     185        move_method = FILE_END;
     186        break;
     187      default:
     188        return E_INVALIDARG;
     189      }
     190  
     191    if (!SetFilePointerEx (self->file_handle, move_distance, &new_position, move_method))
     192      {
     193        DWORD error = GetLastError ();
     194        return __HRESULT_FROM_WIN32 (error);
     195      }
     196  
     197    (*output_new_position).QuadPart = new_position.QuadPart;
     198  
     199    return S_OK;
     200  }
     201  
     202  static HRESULT STDMETHODCALLTYPE
     203  _file_sync_stream_set_size (IStream        *self_ptr,
     204                              ULARGE_INTEGER  new_size)
     205  {
     206    GWin32FileSyncStream *self = (GWin32FileSyncStream *) self_ptr;
     207    FILE_END_OF_FILE_INFO info;
     208  
     209    info.EndOfFile.QuadPart = new_size.QuadPart;
     210  
     211    if (SetFileInformationByHandle (self->file_handle, FileEndOfFileInfo, &info, sizeof (info)))
     212      {
     213        DWORD error = GetLastError ();
     214        return __HRESULT_FROM_WIN32 (error);
     215      }
     216  
     217    return S_OK;
     218  }
     219  
     220  static HRESULT STDMETHODCALLTYPE
     221  _file_sync_stream_copy_to (IStream        *self_ptr,
     222                             IStream        *output_stream,
     223                             ULARGE_INTEGER  bytes_to_copy,
     224                             ULARGE_INTEGER *output_bytes_read,
     225                             ULARGE_INTEGER *output_bytes_written)
     226  {
     227    ULARGE_INTEGER counter;
     228    ULARGE_INTEGER written_counter;
     229    ULARGE_INTEGER read_counter;
     230  
     231    counter.QuadPart = bytes_to_copy.QuadPart;
     232    written_counter.QuadPart = 0;
     233    read_counter.QuadPart = 0;
     234  
     235    while (counter.QuadPart > 0)
     236      {
     237        HRESULT hr;
     238        ULONG bytes_read;
     239        ULONG bytes_written;
     240        ULONG bytes_index;
     241  #define _INTERNAL_BUFSIZE 1024
     242        BYTE buffer[_INTERNAL_BUFSIZE];
     243  #undef _INTERNAL_BUFSIZE
     244        ULONG buffer_size = sizeof (buffer);
     245        ULONG to_read = buffer_size;
     246  
     247        if (counter.QuadPart < buffer_size)
     248          to_read = (ULONG) counter.QuadPart;
     249  
     250        /* Because MS SDK has a function IStream_Read() with 3 arguments */
     251        hr = self_ptr->lpVtbl->Read (self_ptr, buffer, to_read, &bytes_read);
     252        if (!SUCCEEDED (hr))
     253          return hr;
     254  
     255        read_counter.QuadPart += bytes_read;
     256  
     257        if (bytes_read == 0)
     258          break;
     259  
     260        bytes_index = 0;
     261  
     262        while (bytes_index < bytes_read)
     263          {
     264            /* Because MS SDK has a function IStream_Write() with 3 arguments */
     265            hr = output_stream->lpVtbl->Write (output_stream, &buffer[bytes_index], bytes_read - bytes_index, &bytes_written);
     266            if (!SUCCEEDED (hr))
     267              return hr;
     268  
     269            if (bytes_written == 0)
     270              return __HRESULT_FROM_WIN32 (ERROR_WRITE_FAULT);
     271  
     272            bytes_index += bytes_written;
     273            written_counter.QuadPart += bytes_written;
     274          }
     275      }
     276  
     277    if (output_bytes_read)
     278      output_bytes_read->QuadPart = read_counter.QuadPart;
     279    if (output_bytes_written)
     280      output_bytes_written->QuadPart = written_counter.QuadPart;
     281  
     282    return S_OK;
     283  }
     284  
     285  static HRESULT STDMETHODCALLTYPE
     286  _file_sync_stream_commit (IStream *self_ptr,
     287                            DWORD    commit_flags)
     288  {
     289    GWin32FileSyncStream *self = (GWin32FileSyncStream *) self_ptr;
     290  
     291    if (!FlushFileBuffers (self->file_handle))
     292      {
     293        DWORD error = GetLastError ();
     294        return __HRESULT_FROM_WIN32 (error);
     295      }
     296  
     297    return S_OK;
     298  }
     299  
     300  static HRESULT STDMETHODCALLTYPE
     301  _file_sync_stream_revert (IStream *self_ptr)
     302  {
     303    return E_NOTIMPL;
     304  }
     305  
     306  static HRESULT STDMETHODCALLTYPE
     307  _file_sync_stream_lock_region (IStream        *self_ptr,
     308                                 ULARGE_INTEGER  lock_offset,
     309                                 ULARGE_INTEGER  lock_bytes,
     310                                 DWORD           lock_type)
     311  {
     312    return STG_E_INVALIDFUNCTION;
     313  }
     314  
     315  static HRESULT STDMETHODCALLTYPE
     316  _file_sync_stream_unlock_region (IStream        *self_ptr,
     317                                   ULARGE_INTEGER  lock_offset,
     318                                   ULARGE_INTEGER  lock_bytes,
     319                                   DWORD           lock_type)
     320  {
     321    return STG_E_INVALIDFUNCTION;
     322  }
     323  
     324  static HRESULT STDMETHODCALLTYPE
     325  _file_sync_stream_stat (IStream *self_ptr,
     326                          STATSTG *output_stat,
     327                          DWORD    flags)
     328  {
     329    GWin32FileSyncStream *self = (GWin32FileSyncStream *) self_ptr;
     330    BOOL get_name = FALSE;
     331    FILE_BASIC_INFO bi;
     332    FILE_STANDARD_INFO si;
     333  
     334    if (output_stat == NULL)
     335      return STG_E_INVALIDPOINTER;
     336  
     337    switch (flags)
     338      {
     339      case STATFLAG_DEFAULT:
     340        get_name = TRUE;
     341        break;
     342      case STATFLAG_NONAME:
     343        get_name = FALSE;
     344        break;
     345      default:
     346        return STG_E_INVALIDFLAG;
     347      }
     348  
     349    if (!GetFileInformationByHandleEx (self->file_handle, FileBasicInfo, &bi, sizeof (bi)) ||
     350        !GetFileInformationByHandleEx (self->file_handle, FileStandardInfo, &si, sizeof (si)))
     351      {
     352        DWORD error = GetLastError ();
     353        return __HRESULT_FROM_WIN32 (error);
     354      }
     355  
     356    output_stat->type = STGTY_STREAM;
     357    output_stat->mtime.dwLowDateTime = bi.LastWriteTime.LowPart;
     358    output_stat->mtime.dwHighDateTime = bi.LastWriteTime.HighPart;
     359    output_stat->ctime.dwLowDateTime = bi.CreationTime.LowPart;
     360    output_stat->ctime.dwHighDateTime = bi.CreationTime.HighPart;
     361    output_stat->atime.dwLowDateTime = bi.LastAccessTime.LowPart;
     362    output_stat->atime.dwHighDateTime = bi.LastAccessTime.HighPart;
     363    output_stat->grfLocksSupported = 0;
     364    memset (&output_stat->clsid, 0, sizeof (CLSID));
     365    output_stat->grfStateBits = 0;
     366    output_stat->reserved = 0;
     367    output_stat->cbSize.QuadPart = si.EndOfFile.QuadPart;
     368    output_stat->grfMode = self->stgm_mode;
     369  
     370    if (get_name)
     371      {
     372        DWORD tries;
     373        wchar_t *buffer;
     374  
     375        /* Nothing in the documentation guarantees that the name
     376         * won't change between two invocations (one - to get the
     377         * buffer size, the other - to fill the buffer).
     378         * Re-try up to 5 times in case the required buffer size
     379         * doesn't match.
     380         */
     381        for (tries = 5; tries > 0; tries--)
     382          {
     383            DWORD buffer_size;
     384            DWORD buffer_size2;
     385            DWORD error;
     386  
     387            buffer_size = GetFinalPathNameByHandleW (self->file_handle, NULL, 0, 0);
     388  
     389            if (buffer_size == 0)
     390              {
     391                DWORD my_error = GetLastError ();
     392                return __HRESULT_FROM_WIN32 (my_error);
     393              }
     394  
     395            buffer = CoTaskMemAlloc (buffer_size);
     396            buffer[buffer_size - 1] = 0;
     397            buffer_size2 = GetFinalPathNameByHandleW (self->file_handle, buffer, buffer_size, 0);
     398  
     399            if (buffer_size2 < buffer_size)
     400              break;
     401  
     402            error = GetLastError ();
     403            CoTaskMemFree (buffer);
     404            if (buffer_size2 == 0)
     405              return __HRESULT_FROM_WIN32 (error);
     406          }
     407  
     408        if (tries == 0)
     409          return __HRESULT_FROM_WIN32 (ERROR_BAD_LENGTH);
     410  
     411        output_stat->pwcsName = buffer;
     412      }
     413    else
     414      output_stat->pwcsName = NULL;
     415  
     416    return S_OK;
     417  }
     418  
     419  static HRESULT STDMETHODCALLTYPE
     420  _file_sync_stream_clone (IStream  *self_ptr,
     421                           IStream **output_clone_ptr)
     422  {
     423    return E_NOTIMPL;
     424  }
     425  
     426  static IStreamVtbl _file_sync_stream_vtbl = {
     427    _file_sync_stream_query_interface,
     428    _file_sync_stream_add_ref,
     429    _file_sync_stream_release,
     430    _file_sync_stream_read,
     431    _file_sync_stream_write,
     432    _file_sync_stream_seek,
     433    _file_sync_stream_set_size,
     434    _file_sync_stream_copy_to,
     435    _file_sync_stream_commit,
     436    _file_sync_stream_revert,
     437    _file_sync_stream_lock_region,
     438    _file_sync_stream_unlock_region,
     439    _file_sync_stream_stat,
     440    _file_sync_stream_clone,
     441  };
     442  
     443  static void
     444  _file_sync_stream_free (GWin32FileSyncStream *self)
     445  {
     446    if (self->owns_handle)
     447      CloseHandle (self->file_handle);
     448  
     449    g_free (self);
     450  }
     451  
     452  /**
     453   * g_win32_file_sync_stream_new:
     454   * @handle: a Win32 HANDLE for a file.
     455   * @owns_handle: %TRUE if newly-created stream owns the handle
     456   *               (and closes it when destroyed)
     457   * @stgm_mode: a combination of [STGM constants](https://docs.microsoft.com/en-us/windows/win32/stg/stgm-constants)
     458   *             that specify the mode with which the stream
     459   *             is opened.
     460   * @output_hresult: (out) (optional): a HRESULT from the internal COM calls.
     461   *                                    Will be `S_OK` on success.
     462   *
     463   * Creates an IStream object backed by a HANDLE.
     464   *
     465   * @stgm_mode should match the mode of the @handle, otherwise the stream might
     466   * attempt to perform operations that the @handle does not allow. The implementation
     467   * itself ignores these flags completely, they are only used to report
     468   * the mode of the stream to third parties.
     469   *
     470   * The stream only does synchronous access and will never return `E_PENDING` on I/O.
     471   *
     472   * The returned stream object should be treated just like any other
     473   * COM object, and released via `IUnknown_Release()`.
     474   * its elements have been unreffed with g_object_unref().
     475   *
     476   * Returns: (nullable) (transfer full): a new IStream object on success, %NULL on failure.
     477   **/
     478  IStream *
     479  g_win32_file_sync_stream_new (HANDLE    file_handle,
     480                                gboolean  owns_handle,
     481                                DWORD     stgm_mode,
     482                                HRESULT  *output_hresult)
     483  {
     484    GWin32FileSyncStream *new_stream;
     485    IStream *result;
     486    HRESULT hr;
     487  
     488    new_stream = g_new0 (GWin32FileSyncStream, 1);
     489    new_stream->self.lpVtbl = &_file_sync_stream_vtbl;
     490  
     491    hr = IUnknown_QueryInterface ((IUnknown *) new_stream, &IID_IStream, (void **) &result);
     492    if (!SUCCEEDED (hr))
     493      {
     494        g_free (new_stream);
     495  
     496        if (output_hresult)
     497          *output_hresult = hr;
     498  
     499        return NULL;
     500      }
     501  
     502    new_stream->stgm_mode = stgm_mode;
     503    new_stream->file_handle = file_handle;
     504    new_stream->owns_handle = owns_handle;
     505  
     506    if (output_hresult)
     507      *output_hresult = S_OK;
     508  
     509    return result;
     510  }