blob: bc3b6069444b30c15cf682bfa5b99d957466ed6c [file] [log] [blame]
/* 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;
}