| /* | |
| * Compound Storage (32 bit version) | |
| * Stream implementation | |
| * | |
| * This file contains the implementation of the stream interface | |
| * for streams contained in a compound storage. | |
| * | |
| * Copyright 1999 Francis Beaudet | |
| * Copyright 1999 Thuy Nguyen | |
| * | |
| * 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, write to the Free Software | |
| * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA | |
| */ | |
| #include <assert.h> | |
| #include <stdlib.h> | |
| #include <stdarg.h> | |
| #include <stdio.h> | |
| #include <string.h> | |
| #define COBJMACROS | |
| #define NONAMELESSUNION | |
| #include "windef.h" | |
| #include "winbase.h" | |
| #include "winerror.h" | |
| #include "winternl.h" | |
| #include "wine/debug.h" | |
| #include "storage32.h" | |
| WINE_DEFAULT_DEBUG_CHANNEL(storage); | |
| /*** | |
| * This implements the IUnknown method QueryInterface for this | |
| * class | |
| */ | |
| static HRESULT WINAPI StgStreamImpl_QueryInterface( | |
| IStream* iface, | |
| REFIID riid, /* [in] */ | |
| void** ppvObject) /* [iid_is][out] */ | |
| { | |
| StgStreamImpl* This = impl_from_IStream(iface); | |
| if (ppvObject==0) | |
| return E_INVALIDARG; | |
| *ppvObject = 0; | |
| if (IsEqualIID(&IID_IUnknown, riid) || | |
| IsEqualIID(&IID_ISequentialStream, riid) || | |
| IsEqualIID(&IID_IStream, riid)) | |
| { | |
| *ppvObject = &This->IStream_iface; | |
| } | |
| else | |
| return E_NOINTERFACE; | |
| IStream_AddRef(iface); | |
| return S_OK; | |
| } | |
| /*** | |
| * This implements the IUnknown method AddRef for this | |
| * class | |
| */ | |
| static ULONG WINAPI StgStreamImpl_AddRef( | |
| IStream* iface) | |
| { | |
| StgStreamImpl* This = impl_from_IStream(iface); | |
| return InterlockedIncrement(&This->ref); | |
| } | |
| /*** | |
| * This implements the IUnknown method Release for this | |
| * class | |
| */ | |
| static ULONG WINAPI StgStreamImpl_Release( | |
| IStream* iface) | |
| { | |
| StgStreamImpl* This = impl_from_IStream(iface); | |
| ULONG ref = InterlockedDecrement(&This->ref); | |
| if (!ref) | |
| { | |
| TRACE("(%p)\n", This); | |
| /* | |
| * Release the reference we are holding on the parent storage. | |
| * IStorage_Release(&This->parentStorage->IStorage_iface); | |
| * | |
| * No, don't do this. Some apps call IStorage_Release without | |
| * calling IStream_Release first. If we grab a reference the | |
| * file is not closed, and the app fails when it tries to | |
| * reopen the file (Easy-PC, for example). Just inform the | |
| * storage that we have closed the stream | |
| */ | |
| if (This->parentStorage) | |
| StorageBaseImpl_RemoveStream(This->parentStorage, This); | |
| This->parentStorage = 0; | |
| HeapFree(GetProcessHeap(), 0, This); | |
| } | |
| return ref; | |
| } | |
| /*** | |
| * This method is part of the ISequentialStream interface. | |
| * | |
| * It reads a block of information from the stream at the current | |
| * position. It then moves the current position at the end of the | |
| * read block | |
| * | |
| * See the documentation of ISequentialStream for more info. | |
| */ | |
| static HRESULT WINAPI StgStreamImpl_Read( | |
| IStream* iface, | |
| void* pv, /* [length_is][size_is][out] */ | |
| ULONG cb, /* [in] */ | |
| ULONG* pcbRead) /* [out] */ | |
| { | |
| StgStreamImpl* This = impl_from_IStream(iface); | |
| ULONG bytesReadBuffer; | |
| HRESULT res; | |
| TRACE("(%p, %p, %d, %p)\n", | |
| iface, pv, cb, pcbRead); | |
| if (!This->parentStorage) | |
| { | |
| WARN("storage reverted\n"); | |
| return STG_E_REVERTED; | |
| } | |
| /* | |
| * If the caller is not interested in the number of bytes read, | |
| * we use another buffer to avoid "if" statements in the code. | |
| */ | |
| if (pcbRead==0) | |
| pcbRead = &bytesReadBuffer; | |
| res = StorageBaseImpl_StreamReadAt(This->parentStorage, | |
| This->dirEntry, | |
| This->currentPosition, | |
| cb, | |
| pv, | |
| pcbRead); | |
| if (SUCCEEDED(res)) | |
| { | |
| /* | |
| * Advance the pointer for the number of positions read. | |
| */ | |
| This->currentPosition.QuadPart += *pcbRead; | |
| } | |
| TRACE("<-- %08x\n", res); | |
| return res; | |
| } | |
| /*** | |
| * This method is part of the ISequentialStream interface. | |
| * | |
| * It writes a block of information to the stream at the current | |
| * position. It then moves the current position at the end of the | |
| * written block. If the stream is too small to fit the block, | |
| * the stream is grown to fit. | |
| * | |
| * See the documentation of ISequentialStream for more info. | |
| */ | |
| static HRESULT WINAPI StgStreamImpl_Write( | |
| IStream* iface, | |
| const void* pv, /* [size_is][in] */ | |
| ULONG cb, /* [in] */ | |
| ULONG* pcbWritten) /* [out] */ | |
| { | |
| StgStreamImpl* This = impl_from_IStream(iface); | |
| ULONG bytesWritten = 0; | |
| HRESULT res; | |
| TRACE("(%p, %p, %d, %p)\n", | |
| iface, pv, cb, pcbWritten); | |
| /* | |
| * Do we have permission to write to this stream? | |
| */ | |
| switch(STGM_ACCESS_MODE(This->grfMode)) | |
| { | |
| case STGM_WRITE: | |
| case STGM_READWRITE: | |
| break; | |
| default: | |
| WARN("access denied by flags: 0x%x\n", STGM_ACCESS_MODE(This->grfMode)); | |
| return STG_E_ACCESSDENIED; | |
| } | |
| if (!pv) | |
| return STG_E_INVALIDPOINTER; | |
| if (!This->parentStorage) | |
| { | |
| WARN("storage reverted\n"); | |
| return STG_E_REVERTED; | |
| } | |
| /* | |
| * If the caller is not interested in the number of bytes written, | |
| * we use another buffer to avoid "if" statements in the code. | |
| */ | |
| if (pcbWritten == 0) | |
| pcbWritten = &bytesWritten; | |
| /* | |
| * Initialize the out parameter | |
| */ | |
| *pcbWritten = 0; | |
| if (cb == 0) | |
| { | |
| TRACE("<-- S_OK, written 0\n"); | |
| return S_OK; | |
| } | |
| res = StorageBaseImpl_StreamWriteAt(This->parentStorage, | |
| This->dirEntry, | |
| This->currentPosition, | |
| cb, | |
| pv, | |
| pcbWritten); | |
| /* | |
| * Advance the position pointer for the number of positions written. | |
| */ | |
| This->currentPosition.QuadPart += *pcbWritten; | |
| if (SUCCEEDED(res)) | |
| res = StorageBaseImpl_Flush(This->parentStorage); | |
| TRACE("<-- %08x, written %u\n", res, *pcbWritten); | |
| return res; | |
| } | |
| /*** | |
| * This method is part of the IStream interface. | |
| * | |
| * It will move the current stream pointer according to the parameters | |
| * given. | |
| * | |
| * See the documentation of IStream for more info. | |
| */ | |
| static HRESULT WINAPI StgStreamImpl_Seek( | |
| IStream* iface, | |
| LARGE_INTEGER dlibMove, /* [in] */ | |
| DWORD dwOrigin, /* [in] */ | |
| ULARGE_INTEGER* plibNewPosition) /* [out] */ | |
| { | |
| StgStreamImpl* This = impl_from_IStream(iface); | |
| ULARGE_INTEGER newPosition; | |
| DirEntry currentEntry; | |
| HRESULT hr; | |
| TRACE("(%p, %d, %d, %p)\n", | |
| iface, dlibMove.u.LowPart, dwOrigin, plibNewPosition); | |
| /* | |
| * fail if the stream has no parent (as does windows) | |
| */ | |
| if (!This->parentStorage) | |
| { | |
| WARN("storage reverted\n"); | |
| return STG_E_REVERTED; | |
| } | |
| /* | |
| * The caller is allowed to pass in NULL as the new position return value. | |
| * If it happens, we assign it to a dynamic variable to avoid special cases | |
| * in the code below. | |
| */ | |
| if (plibNewPosition == 0) | |
| { | |
| plibNewPosition = &newPosition; | |
| } | |
| /* | |
| * The file pointer is moved depending on the given "function" | |
| * parameter. | |
| */ | |
| switch (dwOrigin) | |
| { | |
| case STREAM_SEEK_SET: | |
| plibNewPosition->u.HighPart = 0; | |
| plibNewPosition->u.LowPart = 0; | |
| break; | |
| case STREAM_SEEK_CUR: | |
| *plibNewPosition = This->currentPosition; | |
| break; | |
| case STREAM_SEEK_END: | |
| hr = StorageBaseImpl_ReadDirEntry(This->parentStorage, This->dirEntry, ¤tEntry); | |
| if (FAILED(hr)) return hr; | |
| *plibNewPosition = currentEntry.size; | |
| break; | |
| default: | |
| WARN("invalid dwOrigin %d\n", dwOrigin); | |
| return STG_E_INVALIDFUNCTION; | |
| } | |
| plibNewPosition->QuadPart += dlibMove.QuadPart; | |
| /* | |
| * tell the caller what we calculated | |
| */ | |
| This->currentPosition = *plibNewPosition; | |
| return S_OK; | |
| } | |
| /*** | |
| * This method is part of the IStream interface. | |
| * | |
| * It will change the size of a stream. | |
| * | |
| * See the documentation of IStream for more info. | |
| */ | |
| static HRESULT WINAPI StgStreamImpl_SetSize( | |
| IStream* iface, | |
| ULARGE_INTEGER libNewSize) /* [in] */ | |
| { | |
| StgStreamImpl* This = impl_from_IStream(iface); | |
| HRESULT hr; | |
| TRACE("(%p, %d)\n", iface, libNewSize.u.LowPart); | |
| if(!This->parentStorage) | |
| { | |
| WARN("storage reverted\n"); | |
| return STG_E_REVERTED; | |
| } | |
| /* | |
| * As documented. | |
| */ | |
| if (libNewSize.u.HighPart != 0) | |
| { | |
| WARN("invalid value for libNewSize.u.HighPart %d\n", libNewSize.u.HighPart); | |
| return STG_E_INVALIDFUNCTION; | |
| } | |
| /* | |
| * Do we have permission? | |
| */ | |
| if (!(This->grfMode & (STGM_WRITE | STGM_READWRITE))) | |
| { | |
| WARN("access denied\n"); | |
| return STG_E_ACCESSDENIED; | |
| } | |
| hr = StorageBaseImpl_StreamSetSize(This->parentStorage, This->dirEntry, libNewSize); | |
| if (SUCCEEDED(hr)) | |
| hr = StorageBaseImpl_Flush(This->parentStorage); | |
| return hr; | |
| } | |
| /*** | |
| * This method is part of the IStream interface. | |
| * | |
| * It will copy the 'cb' Bytes to 'pstm' IStream. | |
| * | |
| * See the documentation of IStream for more info. | |
| */ | |
| static HRESULT WINAPI StgStreamImpl_CopyTo( | |
| IStream* iface, | |
| IStream* pstm, /* [unique][in] */ | |
| ULARGE_INTEGER cb, /* [in] */ | |
| ULARGE_INTEGER* pcbRead, /* [out] */ | |
| ULARGE_INTEGER* pcbWritten) /* [out] */ | |
| { | |
| StgStreamImpl* This = impl_from_IStream(iface); | |
| HRESULT hr = S_OK; | |
| BYTE tmpBuffer[128]; | |
| ULONG bytesRead, bytesWritten, copySize; | |
| ULARGE_INTEGER totalBytesRead; | |
| ULARGE_INTEGER totalBytesWritten; | |
| TRACE("(%p, %p, %d, %p, %p)\n", | |
| iface, pstm, cb.u.LowPart, pcbRead, pcbWritten); | |
| /* | |
| * Sanity check | |
| */ | |
| if (!This->parentStorage) | |
| { | |
| WARN("storage reverted\n"); | |
| return STG_E_REVERTED; | |
| } | |
| if ( pstm == 0 ) | |
| return STG_E_INVALIDPOINTER; | |
| totalBytesRead.QuadPart = 0; | |
| totalBytesWritten.QuadPart = 0; | |
| while ( cb.QuadPart > 0 ) | |
| { | |
| if ( cb.QuadPart >= sizeof(tmpBuffer) ) | |
| copySize = sizeof(tmpBuffer); | |
| else | |
| copySize = cb.u.LowPart; | |
| IStream_Read(iface, tmpBuffer, copySize, &bytesRead); | |
| totalBytesRead.QuadPart += bytesRead; | |
| IStream_Write(pstm, tmpBuffer, bytesRead, &bytesWritten); | |
| totalBytesWritten.QuadPart += bytesWritten; | |
| /* | |
| * Check that read & write operations were successful | |
| */ | |
| if (bytesRead != bytesWritten) | |
| { | |
| hr = STG_E_MEDIUMFULL; | |
| WARN("medium full\n"); | |
| break; | |
| } | |
| if (bytesRead!=copySize) | |
| cb.QuadPart = 0; | |
| else | |
| cb.QuadPart -= bytesRead; | |
| } | |
| if (pcbRead) pcbRead->QuadPart = totalBytesRead.QuadPart; | |
| if (pcbWritten) pcbWritten->QuadPart = totalBytesWritten.QuadPart; | |
| return hr; | |
| } | |
| /*** | |
| * This method is part of the IStream interface. | |
| * | |
| * For streams contained in structured storages, this method | |
| * does nothing. This is what the documentation tells us. | |
| * | |
| * See the documentation of IStream for more info. | |
| */ | |
| static HRESULT WINAPI StgStreamImpl_Commit( | |
| IStream* iface, | |
| DWORD grfCommitFlags) /* [in] */ | |
| { | |
| StgStreamImpl* This = impl_from_IStream(iface); | |
| if (!This->parentStorage) | |
| { | |
| WARN("storage reverted\n"); | |
| return STG_E_REVERTED; | |
| } | |
| return StorageBaseImpl_Flush(This->parentStorage); | |
| } | |
| /*** | |
| * This method is part of the IStream interface. | |
| * | |
| * For streams contained in structured storages, this method | |
| * does nothing. This is what the documentation tells us. | |
| * | |
| * See the documentation of IStream for more info. | |
| */ | |
| static HRESULT WINAPI StgStreamImpl_Revert( | |
| IStream* iface) | |
| { | |
| return S_OK; | |
| } | |
| static HRESULT WINAPI StgStreamImpl_LockRegion( | |
| IStream* iface, | |
| ULARGE_INTEGER libOffset, /* [in] */ | |
| ULARGE_INTEGER cb, /* [in] */ | |
| DWORD dwLockType) /* [in] */ | |
| { | |
| StgStreamImpl* This = impl_from_IStream(iface); | |
| if (!This->parentStorage) | |
| { | |
| WARN("storage reverted\n"); | |
| return STG_E_REVERTED; | |
| } | |
| FIXME("not implemented!\n"); | |
| return E_NOTIMPL; | |
| } | |
| static HRESULT WINAPI StgStreamImpl_UnlockRegion( | |
| IStream* iface, | |
| ULARGE_INTEGER libOffset, /* [in] */ | |
| ULARGE_INTEGER cb, /* [in] */ | |
| DWORD dwLockType) /* [in] */ | |
| { | |
| StgStreamImpl* This = impl_from_IStream(iface); | |
| if (!This->parentStorage) | |
| { | |
| WARN("storage reverted\n"); | |
| return STG_E_REVERTED; | |
| } | |
| FIXME("not implemented!\n"); | |
| return E_NOTIMPL; | |
| } | |
| /*** | |
| * This method is part of the IStream interface. | |
| * | |
| * This method returns information about the current | |
| * stream. | |
| * | |
| * See the documentation of IStream for more info. | |
| */ | |
| static HRESULT WINAPI StgStreamImpl_Stat( | |
| IStream* iface, | |
| STATSTG* pstatstg, /* [out] */ | |
| DWORD grfStatFlag) /* [in] */ | |
| { | |
| StgStreamImpl* This = impl_from_IStream(iface); | |
| DirEntry currentEntry; | |
| HRESULT hr; | |
| TRACE("%p %p %d\n", This, pstatstg, grfStatFlag); | |
| /* | |
| * if stream has no parent, return STG_E_REVERTED | |
| */ | |
| if (!This->parentStorage) | |
| { | |
| WARN("storage reverted\n"); | |
| return STG_E_REVERTED; | |
| } | |
| /* | |
| * Read the information from the directory entry. | |
| */ | |
| hr = StorageBaseImpl_ReadDirEntry(This->parentStorage, | |
| This->dirEntry, | |
| ¤tEntry); | |
| if (SUCCEEDED(hr)) | |
| { | |
| StorageUtl_CopyDirEntryToSTATSTG(This->parentStorage, | |
| pstatstg, | |
| ¤tEntry, | |
| grfStatFlag); | |
| pstatstg->grfMode = This->grfMode; | |
| /* In simple create mode cbSize is the current pos */ | |
| if((This->parentStorage->openFlags & STGM_SIMPLE) && This->parentStorage->create) | |
| pstatstg->cbSize = This->currentPosition; | |
| return S_OK; | |
| } | |
| WARN("failed to read entry\n"); | |
| return hr; | |
| } | |
| /*** | |
| * This method is part of the IStream interface. | |
| * | |
| * This method returns a clone of the interface that allows for | |
| * another seek pointer | |
| * | |
| * See the documentation of IStream for more info. | |
| * | |
| * I am not totally sure what I am doing here but I presume that this | |
| * should be basically as simple as creating a new stream with the same | |
| * parent etc and positioning its seek cursor. | |
| */ | |
| static HRESULT WINAPI StgStreamImpl_Clone( | |
| IStream* iface, | |
| IStream** ppstm) /* [out] */ | |
| { | |
| StgStreamImpl* This = impl_from_IStream(iface); | |
| HRESULT hres; | |
| StgStreamImpl* new_stream; | |
| LARGE_INTEGER seek_pos; | |
| TRACE("%p %p\n", This, ppstm); | |
| /* | |
| * Sanity check | |
| */ | |
| if (!This->parentStorage) | |
| return STG_E_REVERTED; | |
| if ( ppstm == 0 ) | |
| return STG_E_INVALIDPOINTER; | |
| new_stream = StgStreamImpl_Construct (This->parentStorage, This->grfMode, This->dirEntry); | |
| if (!new_stream) | |
| return STG_E_INSUFFICIENTMEMORY; /* Currently the only reason for new_stream=0 */ | |
| *ppstm = &new_stream->IStream_iface; | |
| IStream_AddRef(*ppstm); | |
| seek_pos.QuadPart = This->currentPosition.QuadPart; | |
| hres = IStream_Seek(*ppstm, seek_pos, STREAM_SEEK_SET, NULL); | |
| assert (SUCCEEDED(hres)); | |
| return S_OK; | |
| } | |
| /* | |
| * Virtual function table for the StgStreamImpl class. | |
| */ | |
| static const IStreamVtbl StgStreamVtbl = | |
| { | |
| StgStreamImpl_QueryInterface, | |
| StgStreamImpl_AddRef, | |
| StgStreamImpl_Release, | |
| StgStreamImpl_Read, | |
| StgStreamImpl_Write, | |
| StgStreamImpl_Seek, | |
| StgStreamImpl_SetSize, | |
| StgStreamImpl_CopyTo, | |
| StgStreamImpl_Commit, | |
| StgStreamImpl_Revert, | |
| StgStreamImpl_LockRegion, | |
| StgStreamImpl_UnlockRegion, | |
| StgStreamImpl_Stat, | |
| StgStreamImpl_Clone | |
| }; | |
| /****************************************************************************** | |
| ** StgStreamImpl implementation | |
| */ | |
| /*** | |
| * This is the constructor for the StgStreamImpl class. | |
| * | |
| * Params: | |
| * parentStorage - Pointer to the storage that contains the stream to open | |
| * dirEntry - Index of the directory entry that points to this stream. | |
| */ | |
| StgStreamImpl* StgStreamImpl_Construct( | |
| StorageBaseImpl* parentStorage, | |
| DWORD grfMode, | |
| DirRef dirEntry) | |
| { | |
| StgStreamImpl* newStream; | |
| newStream = HeapAlloc(GetProcessHeap(), 0, sizeof(StgStreamImpl)); | |
| if (newStream) | |
| { | |
| /* | |
| * Set-up the virtual function table and reference count. | |
| */ | |
| newStream->IStream_iface.lpVtbl = &StgStreamVtbl; | |
| newStream->ref = 0; | |
| newStream->parentStorage = parentStorage; | |
| /* | |
| * We want to nail-down the reference to the storage in case the | |
| * stream out-lives the storage in the client application. | |
| * | |
| * -- IStorage_AddRef(&newStream->parentStorage->IStorage_iface); | |
| * | |
| * No, don't do this. Some apps call IStorage_Release without | |
| * calling IStream_Release first. If we grab a reference the | |
| * file is not closed, and the app fails when it tries to | |
| * reopen the file (Easy-PC, for example) | |
| */ | |
| newStream->grfMode = grfMode; | |
| newStream->dirEntry = dirEntry; | |
| /* | |
| * Start the stream at the beginning. | |
| */ | |
| newStream->currentPosition.u.HighPart = 0; | |
| newStream->currentPosition.u.LowPart = 0; | |
| /* add us to the storage's list of active streams */ | |
| StorageBaseImpl_AddStream(parentStorage, newStream); | |
| } | |
| return newStream; | |
| } |