reactos/dll/win32/ole32/storage32.c

10698 lines
288 KiB
C
Raw Normal View History

/*
* Compound Storage (32 bit version)
* Storage implementation
*
* This file contains the compound file implementation
* of the storage interface.
*
* Copyright 1999 Francis Beaudet
* Copyright 1999 Sylvain St-Germain
* Copyright 1999 Thuy Nguyen
* Copyright 2005 Mike McCormack
*
* 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
Sync to Wine-20050725: Robert Shearman <rob@codeweavers.com> - Marshal return value from IRemUnknown_RemQueryInterface. - We should be starting with 1 reference. - IRpcStubBuffer_Disconnect can be called more than once. - Silence now noisy error messages caused by changes in the way we call ipid_to_stubmanager. Move the error message to the one place it is needed. - By-pass the RPC runtime if possible when calling an STA by posting a message directly to the apartment window for it to process. Fixes a deadlock in InstallShield caused by having to create a thread when freeing an object that comes from an STA apartment. Added tests that fail without this fix. - Hack around broken state management so InstallShield works. - Delete the stub manager outside of the apartment critical section because the deletion may require the object to re-enter the apartment. - Always query for the correct stub interface, otherwise we will be pointing to the completely wrong object when a proxy does a queryinterface. - Remove assumption that the stub buffer will handle the lifetime of the object. Alex Villacis Lasso <a_villacis@palosanto.com> - Initialize RegisteredClass properly in CoRegisterClassObject to prevent crash in CoRevokeClassObject when accessing (uninitialized) pMarshalledData. Mike McCormack <mike@codeweavers.com> - Fix gcc 4.0 -Wpointer-sign warnings. Vitaly Lipatov <lav@etersoft.ru> - Added some documentation. Stefan Huehner <stefan@huehner.org> - Fix some missing-declarations warnings. Marcus Meissner <meissner@suse.de> - 16bit interfaces are cdecl, so drop the WINAPI. - 16bit COM interfaces are cdecl, not WINAPI. - OleInitializeWOW gets 2 arguments. - Added OleSetMenuDescriptor16 stub. Marcus Meissner <marcus@jet.franken.de> - Implemented IsValidInterface16, CoMemAlloc. Added debug to HGLOBALLockBytes16_QueryInterface. svn path=/trunk/; revision=17332
2005-08-12 17:19:46 +00:00
*
* NOTES
* The compound file implementation of IStorage used for create
* and manage substorages and streams within a storage object
* residing in a compound file object.
*/
#include <assert.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define COBJMACROS
#define NONAMELESSUNION
#include "windef.h"
#include "winbase.h"
#include "winnls.h"
#include "winuser.h"
#include "wine/debug.h"
#include "storage32.h"
#include "ole2.h" /* For Write/ReadClassStm */
#include "winreg.h"
#include "wine/wingdi16.h"
#include "compobj_private.h"
WINE_DEFAULT_DEBUG_CHANNEL(storage);
/*
* These are signatures to detect the type of Document file.
*/
static const BYTE STORAGE_magic[8] ={0xd0,0xcf,0x11,0xe0,0xa1,0xb1,0x1a,0xe1};
static const BYTE STORAGE_oldmagic[8] ={0xd0,0xcf,0x11,0xe0,0x0e,0x11,0xfc,0x0d};
extern const IPropertySetStorageVtbl IPropertySetStorage_Vtbl;
/****************************************************************************
* StorageInternalImpl definitions.
*
* Definition of the implementation structure for the IStorage interface.
* This one implements the IStorage interface for storage that are
* inside another storage.
*/
typedef struct StorageInternalImpl
{
struct StorageBaseImpl base;
/*
* Entry in the parent's stream tracking list
*/
struct list ParentListEntry;
StorageBaseImpl *parentStorage;
} StorageInternalImpl;
static const IStorageVtbl StorageInternalImpl_Vtbl;
static StorageInternalImpl* StorageInternalImpl_Construct(StorageBaseImpl*,DWORD,DirRef);
typedef struct TransactedDirEntry
{
/* If applicable, a reference to the original DirEntry in the transacted
* parent. If this is a newly-created entry, DIRENTRY_NULL. */
DirRef transactedParentEntry;
/* True if this entry is being used. */
BOOL inuse;
/* True if data is up to date. */
BOOL read;
/* True if this entry has been modified. */
BOOL dirty;
/* True if this entry's stream has been modified. */
BOOL stream_dirty;
/* True if this entry has been deleted in the transacted storage, but the
* delete has not yet been committed. */
BOOL deleted;
/* If this entry's stream has been modified, a reference to where the stream
* is stored in the snapshot file. */
DirRef stream_entry;
/* This directory entry's data, including any changes that have been made. */
DirEntry data;
/* A reference to the parent of this node. This is only valid while we are
* committing changes. */
DirRef parent;
/* A reference to a newly-created entry in the transacted parent. This is
* always equal to transactedParentEntry except when committing changes. */
DirRef newTransactedParentEntry;
} TransactedDirEntry;
/****************************************************************************
* Transacted storage object.
*/
typedef struct TransactedSnapshotImpl
{
struct StorageBaseImpl base;
/*
* Modified streams are temporarily saved to the scratch file.
*/
StorageBaseImpl *scratch;
/* The directory structure is kept here, so that we can track how these
* entries relate to those in the parent storage. */
TransactedDirEntry *entries;
ULONG entries_size;
ULONG firstFreeEntry;
/*
* Changes are committed to the transacted parent.
*/
StorageBaseImpl *transactedParent;
/* The transaction signature from when we last committed */
ULONG lastTransactionSig;
} TransactedSnapshotImpl;
static const IStorageVtbl TransactedSnapshotImpl_Vtbl;
static HRESULT Storage_ConstructTransacted(StorageBaseImpl*,BOOL,StorageBaseImpl**);
typedef struct TransactedSharedImpl
{
struct StorageBaseImpl base;
/*
* Snapshot and uncommitted changes go here.
*/
TransactedSnapshotImpl *scratch;
/*
* Changes are committed to the transacted parent.
*/
StorageBaseImpl *transactedParent;
/* The transaction signature from when we last committed */
ULONG lastTransactionSig;
} TransactedSharedImpl;
/****************************************************************************
* BlockChainStream definitions.
*
* The BlockChainStream class is a utility class that is used to create an
* abstraction of the big block chains in the storage file.
*/
struct BlockChainRun
{
/* This represents a range of blocks that happen reside in consecutive sectors. */
ULONG firstSector;
ULONG firstOffset;
ULONG lastOffset;
};
typedef struct BlockChainBlock
{
ULONG index;
ULONG sector;
BOOL read;
BOOL dirty;
BYTE data[MAX_BIG_BLOCK_SIZE];
} BlockChainBlock;
struct BlockChainStream
{
StorageImpl* parentStorage;
ULONG* headOfStreamPlaceHolder;
DirRef ownerDirEntry;
struct BlockChainRun* indexCache;
ULONG indexCacheLen;
ULONG indexCacheSize;
BlockChainBlock cachedBlocks[2];
ULONG blockToEvict;
ULONG tailIndex;
ULONG numBlocks;
};
/* Returns the number of blocks that comprises this chain.
* This is not the size of the stream as the last block may not be full!
*/
static inline ULONG BlockChainStream_GetCount(BlockChainStream* This)
{
return This->numBlocks;
}
static BlockChainStream* BlockChainStream_Construct(StorageImpl*,ULONG*,DirRef);
static void BlockChainStream_Destroy(BlockChainStream*);
static HRESULT BlockChainStream_ReadAt(BlockChainStream*,ULARGE_INTEGER,ULONG,void*,ULONG*);
static HRESULT BlockChainStream_WriteAt(BlockChainStream*,ULARGE_INTEGER,ULONG,const void*,ULONG*);
static HRESULT BlockChainStream_Flush(BlockChainStream*);
static ULARGE_INTEGER BlockChainStream_GetSize(BlockChainStream*);
static BOOL BlockChainStream_SetSize(BlockChainStream*,ULARGE_INTEGER);
/****************************************************************************
* SmallBlockChainStream definitions.
*
* The SmallBlockChainStream class is a utility class that is used to create an
* abstraction of the small block chains in the storage file.
*/
struct SmallBlockChainStream
{
StorageImpl* parentStorage;
DirRef ownerDirEntry;
ULONG* headOfStreamPlaceHolder;
};
static SmallBlockChainStream* SmallBlockChainStream_Construct(StorageImpl*,ULONG*,DirRef);
static void SmallBlockChainStream_Destroy(SmallBlockChainStream*);
static HRESULT SmallBlockChainStream_ReadAt(SmallBlockChainStream*,ULARGE_INTEGER,ULONG,void*,ULONG*);
static HRESULT SmallBlockChainStream_WriteAt(SmallBlockChainStream*,ULARGE_INTEGER,ULONG,const void*,ULONG*);
static ULARGE_INTEGER SmallBlockChainStream_GetSize(SmallBlockChainStream*);
static BOOL SmallBlockChainStream_SetSize(SmallBlockChainStream*,ULARGE_INTEGER);
/************************************************************************
* STGM Functions
***********************************************************************/
/************************************************************************
* This method validates an STGM parameter that can contain the values below
*
* The stgm modes in 0x0000ffff are not bit masks, but distinct 4 bit values.
* The stgm values contained in 0xffff0000 are bitmasks.
*
* STGM_DIRECT 0x00000000
* STGM_TRANSACTED 0x00010000
* STGM_SIMPLE 0x08000000
*
* STGM_READ 0x00000000
* STGM_WRITE 0x00000001
* STGM_READWRITE 0x00000002
*
* STGM_SHARE_DENY_NONE 0x00000040
* STGM_SHARE_DENY_READ 0x00000030
* STGM_SHARE_DENY_WRITE 0x00000020
* STGM_SHARE_EXCLUSIVE 0x00000010
*
* STGM_PRIORITY 0x00040000
* STGM_DELETEONRELEASE 0x04000000
*
* STGM_CREATE 0x00001000
* STGM_CONVERT 0x00020000
* STGM_FAILIFTHERE 0x00000000
*
* STGM_NOSCRATCH 0x00100000
* STGM_NOSNAPSHOT 0x00200000
*/
static HRESULT validateSTGM(DWORD stgm)
{
DWORD access = STGM_ACCESS_MODE(stgm);
DWORD share = STGM_SHARE_MODE(stgm);
DWORD create = STGM_CREATE_MODE(stgm);
if (stgm&~STGM_KNOWN_FLAGS)
{
ERR("unknown flags %08x\n", stgm);
return E_FAIL;
}
switch (access)
{
case STGM_READ:
case STGM_WRITE:
case STGM_READWRITE:
break;
default:
return E_FAIL;
}
switch (share)
{
case STGM_SHARE_DENY_NONE:
case STGM_SHARE_DENY_READ:
case STGM_SHARE_DENY_WRITE:
case STGM_SHARE_EXCLUSIVE:
break;
case 0:
if (!(stgm & STGM_TRANSACTED))
return E_FAIL;
break;
default:
return E_FAIL;
}
switch (create)
{
case STGM_CREATE:
case STGM_FAILIFTHERE:
break;
default:
return E_FAIL;
}
/*
* STGM_DIRECT | STGM_TRANSACTED | STGM_SIMPLE
*/
if ( (stgm & STGM_TRANSACTED) && (stgm & STGM_SIMPLE) )
return E_FAIL;
/*
* STGM_CREATE | STGM_CONVERT
* if both are false, STGM_FAILIFTHERE is set to TRUE
*/
if ( create == STGM_CREATE && (stgm & STGM_CONVERT) )
return E_FAIL;
/*
* STGM_NOSCRATCH requires STGM_TRANSACTED
*/
if ( (stgm & STGM_NOSCRATCH) && !(stgm & STGM_TRANSACTED) )
return E_FAIL;
/*
* STGM_NOSNAPSHOT requires STGM_TRANSACTED and
* not STGM_SHARE_EXCLUSIVE or STGM_SHARE_DENY_WRITE`
*/
if ( (stgm & STGM_NOSNAPSHOT) &&
(!(stgm & STGM_TRANSACTED) ||
share == STGM_SHARE_EXCLUSIVE ||
share == STGM_SHARE_DENY_WRITE) )
return E_FAIL;
return S_OK;
}
/************************************************************************
* GetShareModeFromSTGM
*
* This method will return a share mode flag from a STGM value.
* The STGM value is assumed valid.
*/
static DWORD GetShareModeFromSTGM(DWORD stgm)
{
switch (STGM_SHARE_MODE(stgm))
{
case 0:
assert(stgm & STGM_TRANSACTED);
/* fall-through */
case STGM_SHARE_DENY_NONE:
return FILE_SHARE_READ | FILE_SHARE_WRITE;
case STGM_SHARE_DENY_READ:
return FILE_SHARE_WRITE;
case STGM_SHARE_DENY_WRITE:
case STGM_SHARE_EXCLUSIVE:
return FILE_SHARE_READ;
}
ERR("Invalid share mode!\n");
assert(0);
return 0;
}
/************************************************************************
* GetAccessModeFromSTGM
*
* This method will return an access mode flag from a STGM value.
* The STGM value is assumed valid.
*/
static DWORD GetAccessModeFromSTGM(DWORD stgm)
{
switch (STGM_ACCESS_MODE(stgm))
{
case STGM_READ:
return GENERIC_READ;
case STGM_WRITE:
case STGM_READWRITE:
return GENERIC_READ | GENERIC_WRITE;
}
ERR("Invalid access mode!\n");
assert(0);
return 0;
}
/************************************************************************
* GetCreationModeFromSTGM
*
* This method will return a creation mode flag from a STGM value.
* The STGM value is assumed valid.
*/
static DWORD GetCreationModeFromSTGM(DWORD stgm)
{
switch(STGM_CREATE_MODE(stgm))
{
case STGM_CREATE:
return CREATE_ALWAYS;
case STGM_CONVERT:
FIXME("STGM_CONVERT not implemented!\n");
return CREATE_NEW;
case STGM_FAILIFTHERE:
return CREATE_NEW;
}
ERR("Invalid create mode!\n");
assert(0);
return 0;
}
/************************************************************************
* IDirectWriterLock implementation
***********************************************************************/
static inline StorageBaseImpl *impl_from_IDirectWriterLock( IDirectWriterLock *iface )
{
return CONTAINING_RECORD(iface, StorageBaseImpl, IDirectWriterLock_iface);
}
static HRESULT WINAPI directwriterlock_QueryInterface(IDirectWriterLock *iface, REFIID riid, void **obj)
{
StorageBaseImpl *This = impl_from_IDirectWriterLock(iface);
return IStorage_QueryInterface(&This->IStorage_iface, riid, obj);
}
static ULONG WINAPI directwriterlock_AddRef(IDirectWriterLock *iface)
{
StorageBaseImpl *This = impl_from_IDirectWriterLock(iface);
return IStorage_AddRef(&This->IStorage_iface);
}
static ULONG WINAPI directwriterlock_Release(IDirectWriterLock *iface)
{
StorageBaseImpl *This = impl_from_IDirectWriterLock(iface);
return IStorage_Release(&This->IStorage_iface);
}
static HRESULT WINAPI directwriterlock_WaitForWriteAccess(IDirectWriterLock *iface, DWORD timeout)
{
StorageBaseImpl *This = impl_from_IDirectWriterLock(iface);
FIXME("(%p)->(%d): stub\n", This, timeout);
return E_NOTIMPL;
}
static HRESULT WINAPI directwriterlock_ReleaseWriteAccess(IDirectWriterLock *iface)
{
StorageBaseImpl *This = impl_from_IDirectWriterLock(iface);
FIXME("(%p): stub\n", This);
return E_NOTIMPL;
}
static HRESULT WINAPI directwriterlock_HaveWriteAccess(IDirectWriterLock *iface)
{
StorageBaseImpl *This = impl_from_IDirectWriterLock(iface);
FIXME("(%p): stub\n", This);
return E_NOTIMPL;
}
static const IDirectWriterLockVtbl DirectWriterLockVtbl =
{
directwriterlock_QueryInterface,
directwriterlock_AddRef,
directwriterlock_Release,
directwriterlock_WaitForWriteAccess,
directwriterlock_ReleaseWriteAccess,
directwriterlock_HaveWriteAccess
};
/************************************************************************
* StorageBaseImpl implementation : Tree helper functions
***********************************************************************/
/****************************************************************************
*
* Internal Method
*
* Case insensitive comparison of DirEntry.name by first considering
* their size.
*
* Returns <0 when name1 < name2
* >0 when name1 > name2
* 0 when name1 == name2
*/
static LONG entryNameCmp(
const OLECHAR *name1,
const OLECHAR *name2)
{
LONG diff = lstrlenW(name1) - lstrlenW(name2);
while (diff == 0 && *name1 != 0)
{
/*
* We compare the string themselves only when they are of the same length
*/
diff = towupper(*name1++) - towupper(*name2++);
}
return diff;
}
/****************************************************************************
*
* Internal Method
*
* Find and read the element of a storage with the given name.
*/
static DirRef findElement(StorageBaseImpl *storage, DirRef storageEntry,
const OLECHAR *name, DirEntry *data)
{
DirRef currentEntry;
/* Read the storage entry to find the root of the tree. */
StorageBaseImpl_ReadDirEntry(storage, storageEntry, data);
currentEntry = data->dirRootEntry;
while (currentEntry != DIRENTRY_NULL)
{
LONG cmp;
StorageBaseImpl_ReadDirEntry(storage, currentEntry, data);
cmp = entryNameCmp(name, data->name);
if (cmp == 0)
/* found it */
break;
else if (cmp < 0)
currentEntry = data->leftChild;
else if (cmp > 0)
currentEntry = data->rightChild;
}
return currentEntry;
}
/****************************************************************************
*
* Internal Method
*
* Find and read the binary tree parent of the element with the given name.
*
* If there is no such element, find a place where it could be inserted and
* return STG_E_FILENOTFOUND.
*/
static HRESULT findTreeParent(StorageBaseImpl *storage, DirRef storageEntry,
const OLECHAR *childName, DirEntry *parentData, DirRef *parentEntry,
ULONG *relation)
{
DirRef childEntry;
DirEntry childData;
/* Read the storage entry to find the root of the tree. */
StorageBaseImpl_ReadDirEntry(storage, storageEntry, parentData);
*parentEntry = storageEntry;
*relation = DIRENTRY_RELATION_DIR;
childEntry = parentData->dirRootEntry;
while (childEntry != DIRENTRY_NULL)
{
LONG cmp;
StorageBaseImpl_ReadDirEntry(storage, childEntry, &childData);
cmp = entryNameCmp(childName, childData.name);
if (cmp == 0)
/* found it */
break;
else if (cmp < 0)
{
*parentData = childData;
*parentEntry = childEntry;
*relation = DIRENTRY_RELATION_PREVIOUS;
childEntry = parentData->leftChild;
}
else if (cmp > 0)
{
*parentData = childData;
*parentEntry = childEntry;
*relation = DIRENTRY_RELATION_NEXT;
childEntry = parentData->rightChild;
}
}
if (childEntry == DIRENTRY_NULL)
return STG_E_FILENOTFOUND;
else
return S_OK;
}
static void setEntryLink(DirEntry *entry, ULONG relation, DirRef new_target)
{
switch (relation)
{
case DIRENTRY_RELATION_PREVIOUS:
entry->leftChild = new_target;
break;
case DIRENTRY_RELATION_NEXT:
entry->rightChild = new_target;
break;
case DIRENTRY_RELATION_DIR:
entry->dirRootEntry = new_target;
break;
default:
assert(0);
}
}
/****************************************************************************
*
* Internal Method
*
* Add a directory entry to a storage
*/
static HRESULT insertIntoTree(
StorageBaseImpl *This,
DirRef parentStorageIndex,
DirRef newEntryIndex)
{
DirEntry currentEntry;
DirEntry newEntry;
/*
* Read the inserted entry
*/
StorageBaseImpl_ReadDirEntry(This,
newEntryIndex,
&newEntry);
/*
* Read the storage entry
*/
StorageBaseImpl_ReadDirEntry(This,
parentStorageIndex,
&currentEntry);
if (currentEntry.dirRootEntry != DIRENTRY_NULL)
{
/*
* The root storage contains some element, therefore, start the research
* for the appropriate location.
*/
BOOL found = FALSE;
DirRef current, next, previous, currentEntryId;
/*
* Keep a reference to the root of the storage's element tree
*/
currentEntryId = currentEntry.dirRootEntry;
/*
* Read
*/
StorageBaseImpl_ReadDirEntry(This,
currentEntry.dirRootEntry,
&currentEntry);
previous = currentEntry.leftChild;
next = currentEntry.rightChild;
current = currentEntryId;
while (!found)
{
LONG diff = entryNameCmp( newEntry.name, currentEntry.name);
if (diff < 0)
{
if (previous != DIRENTRY_NULL)
{
StorageBaseImpl_ReadDirEntry(This,
previous,
&currentEntry);
current = previous;
}
else
{
currentEntry.leftChild = newEntryIndex;
StorageBaseImpl_WriteDirEntry(This,
current,
&currentEntry);
found = TRUE;
}
}
else if (diff > 0)
{
if (next != DIRENTRY_NULL)
{
StorageBaseImpl_ReadDirEntry(This,
next,
&currentEntry);
current = next;
}
else
{
currentEntry.rightChild = newEntryIndex;
StorageBaseImpl_WriteDirEntry(This,
current,
&currentEntry);
found = TRUE;
}
}
else
{
/*
* Trying to insert an item with the same name in the
* subtree structure.
*/
return STG_E_FILEALREADYEXISTS;
}
previous = currentEntry.leftChild;
next = currentEntry.rightChild;
}
}
else
{
/*
* The storage is empty, make the new entry the root of its element tree
*/
currentEntry.dirRootEntry = newEntryIndex;
StorageBaseImpl_WriteDirEntry(This,
parentStorageIndex,
&currentEntry);
}
return S_OK;
}
/*************************************************************************
*
* Internal Method
*
* This method removes a directory entry from its parent storage tree without
* freeing any resources attached to it.
*/
static HRESULT removeFromTree(
StorageBaseImpl *This,
DirRef parentStorageIndex,
DirRef deletedIndex)
{
DirEntry entryToDelete;
DirEntry parentEntry;
DirRef parentEntryRef;
ULONG typeOfRelation;
HRESULT hr;
hr = StorageBaseImpl_ReadDirEntry(This, deletedIndex, &entryToDelete);
if (hr != S_OK)
return hr;
/*
* Find the element that links to the one we want to delete.
*/
hr = findTreeParent(This, parentStorageIndex, entryToDelete.name,
&parentEntry, &parentEntryRef, &typeOfRelation);
if (hr != S_OK)
return hr;
if (entryToDelete.leftChild != DIRENTRY_NULL)
{
/*
* Replace the deleted entry with its left child
*/
setEntryLink(&parentEntry, typeOfRelation, entryToDelete.leftChild);
hr = StorageBaseImpl_WriteDirEntry(
This,
parentEntryRef,
&parentEntry);
if(FAILED(hr))
{
return hr;
}
if (entryToDelete.rightChild != DIRENTRY_NULL)
{
/*
* We need to reinsert the right child somewhere. We already know it and
* its children are greater than everything in the left tree, so we
* insert it at the rightmost point in the left tree.
*/
DirRef newRightChildParent = entryToDelete.leftChild;
DirEntry newRightChildParentEntry;
do
{
hr = StorageBaseImpl_ReadDirEntry(
This,
newRightChildParent,
&newRightChildParentEntry);
if (FAILED(hr))
{
return hr;
}
if (newRightChildParentEntry.rightChild != DIRENTRY_NULL)
newRightChildParent = newRightChildParentEntry.rightChild;
} while (newRightChildParentEntry.rightChild != DIRENTRY_NULL);
newRightChildParentEntry.rightChild = entryToDelete.rightChild;
hr = StorageBaseImpl_WriteDirEntry(
This,
newRightChildParent,
&newRightChildParentEntry);
if (FAILED(hr))
{
return hr;
}
}
}
else
{
/*
* Replace the deleted entry with its right child
*/
setEntryLink(&parentEntry, typeOfRelation, entryToDelete.rightChild);
hr = StorageBaseImpl_WriteDirEntry(
This,
parentEntryRef,
&parentEntry);
if(FAILED(hr))
{
return hr;
}
}
return hr;
}
/************************************************************************
* IEnumSTATSTGImpl implementation for StorageBaseImpl_EnumElements
***********************************************************************/
/*
* IEnumSTATSTGImpl definitions.
*
* Definition of the implementation structure for the IEnumSTATSTGImpl interface.
* This class allows iterating through the content of a storage and finding
* specific items inside it.
*/
struct IEnumSTATSTGImpl
{
IEnumSTATSTG IEnumSTATSTG_iface;
LONG ref; /* Reference count */
StorageBaseImpl* parentStorage; /* Reference to the parent storage */
DirRef storageDirEntry; /* Directory entry of the storage to enumerate */
WCHAR name[DIRENTRY_NAME_MAX_LEN]; /* The most recent name visited */
};
static inline IEnumSTATSTGImpl *impl_from_IEnumSTATSTG(IEnumSTATSTG *iface)
{
return CONTAINING_RECORD(iface, IEnumSTATSTGImpl, IEnumSTATSTG_iface);
}
static void IEnumSTATSTGImpl_Destroy(IEnumSTATSTGImpl* This)
{
IStorage_Release(&This->parentStorage->IStorage_iface);
HeapFree(GetProcessHeap(), 0, This);
}
static HRESULT WINAPI IEnumSTATSTGImpl_QueryInterface(
IEnumSTATSTG* iface,
REFIID riid,
void** ppvObject)
{
IEnumSTATSTGImpl* const This = impl_from_IEnumSTATSTG(iface);
TRACE("%p,%s,%p\n", iface, debugstr_guid(riid), ppvObject);
if (ppvObject==0)
return E_INVALIDARG;
*ppvObject = 0;
if (IsEqualGUID(&IID_IUnknown, riid) ||
IsEqualGUID(&IID_IEnumSTATSTG, riid))
{
*ppvObject = &This->IEnumSTATSTG_iface;
IEnumSTATSTG_AddRef(&This->IEnumSTATSTG_iface);
TRACE("<-- %p\n", *ppvObject);
return S_OK;
}
TRACE("<-- E_NOINTERFACE\n");
return E_NOINTERFACE;
}
static ULONG WINAPI IEnumSTATSTGImpl_AddRef(
IEnumSTATSTG* iface)
{
IEnumSTATSTGImpl* const This = impl_from_IEnumSTATSTG(iface);
return InterlockedIncrement(&This->ref);
}
static ULONG WINAPI IEnumSTATSTGImpl_Release(
IEnumSTATSTG* iface)
{
IEnumSTATSTGImpl* const This = impl_from_IEnumSTATSTG(iface);
ULONG newRef;
newRef = InterlockedDecrement(&This->ref);
if (newRef==0)
{
IEnumSTATSTGImpl_Destroy(This);
}
return newRef;
}
static HRESULT IEnumSTATSTGImpl_GetNextRef(
IEnumSTATSTGImpl* This,
DirRef *ref)
{
DirRef result = DIRENTRY_NULL;
DirRef searchNode;
DirEntry entry;
HRESULT hr;
WCHAR result_name[DIRENTRY_NAME_MAX_LEN];
Sync from Wine-20050830 to Wine-0_9_1: Francois Gouget <fgouget@free.fr> - Assorted spelling fixes. - Fix .spec declarations for functinos with 64bit arguments (reported by winapi_check). Mike Hearn <mh@codeweavers.com> - Robert Shearman <rob@codeweavers.com> Change stub manager to track the number of normal marshals instead of using the state machine so that multiple marshals of the same object and interface work correctly. Robert Shearman <rob@codeweavers.com> - Pass WM_QUIT to outer modal loop. - Fix the return codes during unmarshaling so that it returns failure codes instead of S_FALSE returned from IStream_Read. - Don't fail in CoRegisterClassObject if the class already exists and the REGCLS_MULTIPLEUSE flag is specified. - Fix ref-counting rules to match native DCOM Dlls. - Add exception handling for stubs. - Implement OleRegEnumVerbs. - The [string] attribute is only valid on byte, char and wchar_t types, so replace "unsigned short" by "WCHAR". - Implement OleIsRunning. - Add a stubbed out implementation of IAdviseSink and advise the delegate object to use it. - Initialize out pointer to NULL before access check in IStorage_CreateStorage. - WriteClassStg should return E_INVALIDARG if pstg is NULL instead of asserting. - ReadClassStg should return E_INVALIDARG if pstg is NULL instead of crashing and a NULL pclsid should cause it to return the same value. - Make the interfaces that should be supported by the data cache explicit so their is no confusion in this file as to what it should be implementing and what this object should implement. - Delegate some IOleObject methods to the server IOleObject if it is running. - Implement some IRunningObject functions that actually start the server and initialize it. - Remove redunant braces. - Compact multi-line comments that fit into one line. - Remove comments that state the obvious. - Remove extra brackets that are redundant because the -> operator binds tighter than &. - Change "this" to "This" to make code more like other interface implementations. - Remove redundant comparisons with NULL for pointers. - Re-arrange some functions and vtables so we don't have declarations for all of the functions in the file. - Fix a trace to refer to the object ID rather than the legacy MID. - Fix the error case of CoMarshalInterThreadInterface to release the stream. - Move all 16-bit functions to an appropriate 16-bit file. - Implement OLE object notifications, making sure to cope with the case of the advise holder not being created because no notifications are needed. - Implement a Stop function and use this to properly implement IOleObject_Close, IAdviseSink_OnClose and the destructor. - We shouldn't pass the application name into CreateProcess because the value stored in the registry could include arguments. - Extend COM_OpenKeyForCLSID to open a subkey and return an HRESULT. - Fix up the callers and reorganize CoGetClassObject to split out the inproc code into another function. Alex Villacís Lasso <a_villacis@palosanto.com> - Add NULL check for sinkInterface in DataCache_GetAdvise. - Add missing ! to fix a reversed condition check in OleCreateDefaultHandler, in order to match intent in comment. Vincent Béron <vberon@mecano.gme.usherb.ca> - Use the proper calling convention for 2 16-bit functions. Alexandre Julliard <julliard@winehq.org> - We are no longer generating .spec.c files. - Use a more portable scheme for storing the name of a critical section. - Fixed some traces to use the right printf format and avoid typecasts. - Removed unused debug channels. - We are no longer generating .dbg.c files. Richard Cohen <richard@daijobu.co.uk> - Relax the share mode validation for transacted storage, with a test. Eric Pouech <eric.pouech@wanadoo.fr> - Fixes for function prototypes without arguments. Michael Jung <mjung@iss.tu-darmstadt.de> - Added tracing to CoCreateInstance. Markus Amsler <markus.amsler@oribi.org> - Improve c2man Documented-Total count. svn path=/trunk/; revision=19370
2005-11-20 15:01:10 +00:00
TRACE("%p,%p\n", This, ref);
hr = StorageBaseImpl_ReadDirEntry(This->parentStorage,
This->parentStorage->storageDirEntry, &entry);
searchNode = entry.dirRootEntry;
while (SUCCEEDED(hr) && searchNode != DIRENTRY_NULL)
Sync from Wine-20050830 to Wine-0_9_1: Francois Gouget <fgouget@free.fr> - Assorted spelling fixes. - Fix .spec declarations for functinos with 64bit arguments (reported by winapi_check). Mike Hearn <mh@codeweavers.com> - Robert Shearman <rob@codeweavers.com> Change stub manager to track the number of normal marshals instead of using the state machine so that multiple marshals of the same object and interface work correctly. Robert Shearman <rob@codeweavers.com> - Pass WM_QUIT to outer modal loop. - Fix the return codes during unmarshaling so that it returns failure codes instead of S_FALSE returned from IStream_Read. - Don't fail in CoRegisterClassObject if the class already exists and the REGCLS_MULTIPLEUSE flag is specified. - Fix ref-counting rules to match native DCOM Dlls. - Add exception handling for stubs. - Implement OleRegEnumVerbs. - The [string] attribute is only valid on byte, char and wchar_t types, so replace "unsigned short" by "WCHAR". - Implement OleIsRunning. - Add a stubbed out implementation of IAdviseSink and advise the delegate object to use it. - Initialize out pointer to NULL before access check in IStorage_CreateStorage. - WriteClassStg should return E_INVALIDARG if pstg is NULL instead of asserting. - ReadClassStg should return E_INVALIDARG if pstg is NULL instead of crashing and a NULL pclsid should cause it to return the same value. - Make the interfaces that should be supported by the data cache explicit so their is no confusion in this file as to what it should be implementing and what this object should implement. - Delegate some IOleObject methods to the server IOleObject if it is running. - Implement some IRunningObject functions that actually start the server and initialize it. - Remove redunant braces. - Compact multi-line comments that fit into one line. - Remove comments that state the obvious. - Remove extra brackets that are redundant because the -> operator binds tighter than &. - Change "this" to "This" to make code more like other interface implementations. - Remove redundant comparisons with NULL for pointers. - Re-arrange some functions and vtables so we don't have declarations for all of the functions in the file. - Fix a trace to refer to the object ID rather than the legacy MID. - Fix the error case of CoMarshalInterThreadInterface to release the stream. - Move all 16-bit functions to an appropriate 16-bit file. - Implement OLE object notifications, making sure to cope with the case of the advise holder not being created because no notifications are needed. - Implement a Stop function and use this to properly implement IOleObject_Close, IAdviseSink_OnClose and the destructor. - We shouldn't pass the application name into CreateProcess because the value stored in the registry could include arguments. - Extend COM_OpenKeyForCLSID to open a subkey and return an HRESULT. - Fix up the callers and reorganize CoGetClassObject to split out the inproc code into another function. Alex Villacís Lasso <a_villacis@palosanto.com> - Add NULL check for sinkInterface in DataCache_GetAdvise. - Add missing ! to fix a reversed condition check in OleCreateDefaultHandler, in order to match intent in comment. Vincent Béron <vberon@mecano.gme.usherb.ca> - Use the proper calling convention for 2 16-bit functions. Alexandre Julliard <julliard@winehq.org> - We are no longer generating .spec.c files. - Use a more portable scheme for storing the name of a critical section. - Fixed some traces to use the right printf format and avoid typecasts. - Removed unused debug channels. - We are no longer generating .dbg.c files. Richard Cohen <richard@daijobu.co.uk> - Relax the share mode validation for transacted storage, with a test. Eric Pouech <eric.pouech@wanadoo.fr> - Fixes for function prototypes without arguments. Michael Jung <mjung@iss.tu-darmstadt.de> - Added tracing to CoCreateInstance. Markus Amsler <markus.amsler@oribi.org> - Improve c2man Documented-Total count. svn path=/trunk/; revision=19370
2005-11-20 15:01:10 +00:00
{
hr = StorageBaseImpl_ReadDirEntry(This->parentStorage, searchNode, &entry);
if (SUCCEEDED(hr))
{
LONG diff = entryNameCmp( entry.name, This->name);
if (diff <= 0)
{
searchNode = entry.rightChild;
}
else
{
result = searchNode;
memcpy(result_name, entry.name, sizeof(result_name));
searchNode = entry.leftChild;
}
}
}
if (SUCCEEDED(hr))
Sync from Wine-20050830 to Wine-0_9_1: Francois Gouget <fgouget@free.fr> - Assorted spelling fixes. - Fix .spec declarations for functinos with 64bit arguments (reported by winapi_check). Mike Hearn <mh@codeweavers.com> - Robert Shearman <rob@codeweavers.com> Change stub manager to track the number of normal marshals instead of using the state machine so that multiple marshals of the same object and interface work correctly. Robert Shearman <rob@codeweavers.com> - Pass WM_QUIT to outer modal loop. - Fix the return codes during unmarshaling so that it returns failure codes instead of S_FALSE returned from IStream_Read. - Don't fail in CoRegisterClassObject if the class already exists and the REGCLS_MULTIPLEUSE flag is specified. - Fix ref-counting rules to match native DCOM Dlls. - Add exception handling for stubs. - Implement OleRegEnumVerbs. - The [string] attribute is only valid on byte, char and wchar_t types, so replace "unsigned short" by "WCHAR". - Implement OleIsRunning. - Add a stubbed out implementation of IAdviseSink and advise the delegate object to use it. - Initialize out pointer to NULL before access check in IStorage_CreateStorage. - WriteClassStg should return E_INVALIDARG if pstg is NULL instead of asserting. - ReadClassStg should return E_INVALIDARG if pstg is NULL instead of crashing and a NULL pclsid should cause it to return the same value. - Make the interfaces that should be supported by the data cache explicit so their is no confusion in this file as to what it should be implementing and what this object should implement. - Delegate some IOleObject methods to the server IOleObject if it is running. - Implement some IRunningObject functions that actually start the server and initialize it. - Remove redunant braces. - Compact multi-line comments that fit into one line. - Remove comments that state the obvious. - Remove extra brackets that are redundant because the -> operator binds tighter than &. - Change "this" to "This" to make code more like other interface implementations. - Remove redundant comparisons with NULL for pointers. - Re-arrange some functions and vtables so we don't have declarations for all of the functions in the file. - Fix a trace to refer to the object ID rather than the legacy MID. - Fix the error case of CoMarshalInterThreadInterface to release the stream. - Move all 16-bit functions to an appropriate 16-bit file. - Implement OLE object notifications, making sure to cope with the case of the advise holder not being created because no notifications are needed. - Implement a Stop function and use this to properly implement IOleObject_Close, IAdviseSink_OnClose and the destructor. - We shouldn't pass the application name into CreateProcess because the value stored in the registry could include arguments. - Extend COM_OpenKeyForCLSID to open a subkey and return an HRESULT. - Fix up the callers and reorganize CoGetClassObject to split out the inproc code into another function. Alex Villacís Lasso <a_villacis@palosanto.com> - Add NULL check for sinkInterface in DataCache_GetAdvise. - Add missing ! to fix a reversed condition check in OleCreateDefaultHandler, in order to match intent in comment. Vincent Béron <vberon@mecano.gme.usherb.ca> - Use the proper calling convention for 2 16-bit functions. Alexandre Julliard <julliard@winehq.org> - We are no longer generating .spec.c files. - Use a more portable scheme for storing the name of a critical section. - Fixed some traces to use the right printf format and avoid typecasts. - Removed unused debug channels. - We are no longer generating .dbg.c files. Richard Cohen <richard@daijobu.co.uk> - Relax the share mode validation for transacted storage, with a test. Eric Pouech <eric.pouech@wanadoo.fr> - Fixes for function prototypes without arguments. Michael Jung <mjung@iss.tu-darmstadt.de> - Added tracing to CoCreateInstance. Markus Amsler <markus.amsler@oribi.org> - Improve c2man Documented-Total count. svn path=/trunk/; revision=19370
2005-11-20 15:01:10 +00:00
{
*ref = result;
if (result != DIRENTRY_NULL)
memcpy(This->name, result_name, sizeof(result_name));
Sync from Wine-20050830 to Wine-0_9_1: Francois Gouget <fgouget@free.fr> - Assorted spelling fixes. - Fix .spec declarations for functinos with 64bit arguments (reported by winapi_check). Mike Hearn <mh@codeweavers.com> - Robert Shearman <rob@codeweavers.com> Change stub manager to track the number of normal marshals instead of using the state machine so that multiple marshals of the same object and interface work correctly. Robert Shearman <rob@codeweavers.com> - Pass WM_QUIT to outer modal loop. - Fix the return codes during unmarshaling so that it returns failure codes instead of S_FALSE returned from IStream_Read. - Don't fail in CoRegisterClassObject if the class already exists and the REGCLS_MULTIPLEUSE flag is specified. - Fix ref-counting rules to match native DCOM Dlls. - Add exception handling for stubs. - Implement OleRegEnumVerbs. - The [string] attribute is only valid on byte, char and wchar_t types, so replace "unsigned short" by "WCHAR". - Implement OleIsRunning. - Add a stubbed out implementation of IAdviseSink and advise the delegate object to use it. - Initialize out pointer to NULL before access check in IStorage_CreateStorage. - WriteClassStg should return E_INVALIDARG if pstg is NULL instead of asserting. - ReadClassStg should return E_INVALIDARG if pstg is NULL instead of crashing and a NULL pclsid should cause it to return the same value. - Make the interfaces that should be supported by the data cache explicit so their is no confusion in this file as to what it should be implementing and what this object should implement. - Delegate some IOleObject methods to the server IOleObject if it is running. - Implement some IRunningObject functions that actually start the server and initialize it. - Remove redunant braces. - Compact multi-line comments that fit into one line. - Remove comments that state the obvious. - Remove extra brackets that are redundant because the -> operator binds tighter than &. - Change "this" to "This" to make code more like other interface implementations. - Remove redundant comparisons with NULL for pointers. - Re-arrange some functions and vtables so we don't have declarations for all of the functions in the file. - Fix a trace to refer to the object ID rather than the legacy MID. - Fix the error case of CoMarshalInterThreadInterface to release the stream. - Move all 16-bit functions to an appropriate 16-bit file. - Implement OLE object notifications, making sure to cope with the case of the advise holder not being created because no notifications are needed. - Implement a Stop function and use this to properly implement IOleObject_Close, IAdviseSink_OnClose and the destructor. - We shouldn't pass the application name into CreateProcess because the value stored in the registry could include arguments. - Extend COM_OpenKeyForCLSID to open a subkey and return an HRESULT. - Fix up the callers and reorganize CoGetClassObject to split out the inproc code into another function. Alex Villacís Lasso <a_villacis@palosanto.com> - Add NULL check for sinkInterface in DataCache_GetAdvise. - Add missing ! to fix a reversed condition check in OleCreateDefaultHandler, in order to match intent in comment. Vincent Béron <vberon@mecano.gme.usherb.ca> - Use the proper calling convention for 2 16-bit functions. Alexandre Julliard <julliard@winehq.org> - We are no longer generating .spec.c files. - Use a more portable scheme for storing the name of a critical section. - Fixed some traces to use the right printf format and avoid typecasts. - Removed unused debug channels. - We are no longer generating .dbg.c files. Richard Cohen <richard@daijobu.co.uk> - Relax the share mode validation for transacted storage, with a test. Eric Pouech <eric.pouech@wanadoo.fr> - Fixes for function prototypes without arguments. Michael Jung <mjung@iss.tu-darmstadt.de> - Added tracing to CoCreateInstance. Markus Amsler <markus.amsler@oribi.org> - Improve c2man Documented-Total count. svn path=/trunk/; revision=19370
2005-11-20 15:01:10 +00:00
}
TRACE("<-- %08x\n", hr);
return hr;
}
static HRESULT WINAPI IEnumSTATSTGImpl_Next(
IEnumSTATSTG* iface,
ULONG celt,
STATSTG* rgelt,
ULONG* pceltFetched)
{
IEnumSTATSTGImpl* const This = impl_from_IEnumSTATSTG(iface);
DirEntry currentEntry;
STATSTG* currentReturnStruct = rgelt;
ULONG objectFetched = 0;
DirRef currentSearchNode;
HRESULT hr=S_OK;
TRACE("%p,%u,%p,%p\n", iface, celt, rgelt, pceltFetched);
if ( (rgelt==0) || ( (celt!=1) && (pceltFetched==0) ) )
return E_INVALIDARG;
if (This->parentStorage->reverted)
{
TRACE("<-- STG_E_REVERTED\n");
return STG_E_REVERTED;
}
/*
* To avoid the special case, get another pointer to a ULONG value if
* the caller didn't supply one.
*/
if (pceltFetched==0)
pceltFetched = &objectFetched;
/*
* Start the iteration, we will iterate until we hit the end of the
* linked list or until we hit the number of items to iterate through
*/
*pceltFetched = 0;
while ( *pceltFetched < celt )
{
hr = IEnumSTATSTGImpl_GetNextRef(This, &currentSearchNode);
if (FAILED(hr) || currentSearchNode == DIRENTRY_NULL)
{
memset(currentReturnStruct, 0, sizeof(*currentReturnStruct));
break;
}
/*
* Read the entry from the storage.
*/
hr = StorageBaseImpl_ReadDirEntry(This->parentStorage,
currentSearchNode,
&currentEntry);
if (FAILED(hr)) break;
/*
* Copy the information to the return buffer.
*/
StorageUtl_CopyDirEntryToSTATSTG(This->parentStorage,
currentReturnStruct,
&currentEntry,
STATFLAG_DEFAULT);
/*
* Step to the next item in the iteration
*/
(*pceltFetched)++;
currentReturnStruct++;
}
if (SUCCEEDED(hr) && *pceltFetched != celt)
hr = S_FALSE;
TRACE("<-- %08x (asked %u, got %u)\n", hr, celt, *pceltFetched);
return hr;
}
static HRESULT WINAPI IEnumSTATSTGImpl_Skip(
IEnumSTATSTG* iface,
ULONG celt)
{
IEnumSTATSTGImpl* const This = impl_from_IEnumSTATSTG(iface);
ULONG objectFetched = 0;
DirRef currentSearchNode;
HRESULT hr=S_OK;
TRACE("%p,%u\n", iface, celt);
if (This->parentStorage->reverted)
{
TRACE("<-- STG_E_REVERTED\n");
return STG_E_REVERTED;
}
while ( (objectFetched < celt) )
{
hr = IEnumSTATSTGImpl_GetNextRef(This, &currentSearchNode);
if (FAILED(hr) || currentSearchNode == DIRENTRY_NULL)
break;
objectFetched++;
}
if (SUCCEEDED(hr) && objectFetched != celt)
return S_FALSE;
TRACE("<-- %08x\n", hr);
return hr;
}
static HRESULT WINAPI IEnumSTATSTGImpl_Reset(
IEnumSTATSTG* iface)
{
IEnumSTATSTGImpl* const This = impl_from_IEnumSTATSTG(iface);
TRACE("%p\n", iface);
if (This->parentStorage->reverted)
{
TRACE("<-- STG_E_REVERTED\n");
return STG_E_REVERTED;
}
This->name[0] = 0;
return S_OK;
}
static IEnumSTATSTGImpl* IEnumSTATSTGImpl_Construct(StorageBaseImpl*,DirRef);
static HRESULT WINAPI IEnumSTATSTGImpl_Clone(
IEnumSTATSTG* iface,
IEnumSTATSTG** ppenum)
{
IEnumSTATSTGImpl* const This = impl_from_IEnumSTATSTG(iface);
IEnumSTATSTGImpl* newClone;
TRACE("%p,%p\n", iface, ppenum);
if (This->parentStorage->reverted)
{
TRACE("<-- STG_E_REVERTED\n");
return STG_E_REVERTED;
}
if (ppenum==0)
return E_INVALIDARG;
newClone = IEnumSTATSTGImpl_Construct(This->parentStorage,
This->storageDirEntry);
if (!newClone)
{
*ppenum = NULL;
return E_OUTOFMEMORY;
}
/*
* The new clone enumeration must point to the same current node as
* the old one.
*/
memcpy(newClone->name, This->name, sizeof(newClone->name));
*ppenum = &newClone->IEnumSTATSTG_iface;
return S_OK;
}
/*
* Virtual function table for the IEnumSTATSTGImpl class.
*/
static const IEnumSTATSTGVtbl IEnumSTATSTGImpl_Vtbl =
{
IEnumSTATSTGImpl_QueryInterface,
IEnumSTATSTGImpl_AddRef,
IEnumSTATSTGImpl_Release,
IEnumSTATSTGImpl_Next,
IEnumSTATSTGImpl_Skip,
IEnumSTATSTGImpl_Reset,
IEnumSTATSTGImpl_Clone
};
static IEnumSTATSTGImpl* IEnumSTATSTGImpl_Construct(
StorageBaseImpl* parentStorage,
DirRef storageDirEntry)
{
IEnumSTATSTGImpl* newEnumeration;
newEnumeration = HeapAlloc(GetProcessHeap(), 0, sizeof(IEnumSTATSTGImpl));
if (newEnumeration)
{
newEnumeration->IEnumSTATSTG_iface.lpVtbl = &IEnumSTATSTGImpl_Vtbl;
newEnumeration->ref = 1;
newEnumeration->name[0] = 0;
/*
* We want to nail-down the reference to the storage in case the
* enumeration out-lives the storage in the client application.
*/
newEnumeration->parentStorage = parentStorage;
IStorage_AddRef(&newEnumeration->parentStorage->IStorage_iface);
newEnumeration->storageDirEntry = storageDirEntry;
}
return newEnumeration;
}
/************************************************************************
* StorageBaseImpl implementation
***********************************************************************/
static inline StorageBaseImpl *impl_from_IStorage( IStorage *iface )
{
return CONTAINING_RECORD(iface, StorageBaseImpl, IStorage_iface);
}
/************************************************************************
* StorageBaseImpl_QueryInterface (IUnknown)
*
* This method implements the common QueryInterface for all IStorage
* implementations contained in this file.
*
* See Windows documentation for more details on IUnknown methods.
*/
static HRESULT WINAPI StorageBaseImpl_QueryInterface(
IStorage* iface,
REFIID riid,
void** ppvObject)
{
StorageBaseImpl *This = impl_from_IStorage(iface);
TRACE("%p,%s,%p\n", iface, debugstr_guid(riid), ppvObject);
if (!ppvObject)
return E_INVALIDARG;
*ppvObject = 0;
if (IsEqualGUID(&IID_IUnknown, riid) ||
IsEqualGUID(&IID_IStorage, riid))
{
*ppvObject = &This->IStorage_iface;
}
else if (IsEqualGUID(&IID_IPropertySetStorage, riid))
{
*ppvObject = &This->IPropertySetStorage_iface;
}
/* locking interface is reported for writer only */
else if (IsEqualGUID(&IID_IDirectWriterLock, riid) && This->lockingrole == SWMR_Writer)
{
*ppvObject = &This->IDirectWriterLock_iface;
}
else
{
TRACE("<-- E_NOINTERFACE\n");
return E_NOINTERFACE;
}
IStorage_AddRef(iface);
TRACE("<-- %p\n", *ppvObject);
return S_OK;
}
/************************************************************************
* StorageBaseImpl_AddRef (IUnknown)
*
* This method implements the common AddRef for all IStorage
* implementations contained in this file.
*
* See Windows documentation for more details on IUnknown methods.
*/
static ULONG WINAPI StorageBaseImpl_AddRef(
IStorage* iface)
{
StorageBaseImpl *This = impl_from_IStorage(iface);
ULONG ref = InterlockedIncrement(&This->ref);
TRACE("(%p) AddRef to %d\n", This, ref);
return ref;
}
/************************************************************************
* StorageBaseImpl_Release (IUnknown)
*
* This method implements the common Release for all IStorage
* implementations contained in this file.
*
* See Windows documentation for more details on IUnknown methods.
*/
static ULONG WINAPI StorageBaseImpl_Release(
IStorage* iface)
{
StorageBaseImpl *This = impl_from_IStorage(iface);
ULONG ref = InterlockedDecrement(&This->ref);
TRACE("(%p) ReleaseRef to %d\n", This, ref);
if (ref == 0)
{
/*
* Since we are using a system of base-classes, we want to call the
* destructor of the appropriate derived class. To do this, we are
* using virtual functions to implement the destructor.
*/
StorageBaseImpl_Destroy(This);
}
return ref;
}
static HRESULT StorageBaseImpl_CopyStorageEntryTo(StorageBaseImpl *This,
DirRef srcEntry, BOOL skip_storage, BOOL skip_stream,
SNB snbExclude, IStorage *pstgDest);
static HRESULT StorageBaseImpl_CopyChildEntryTo(StorageBaseImpl *This,
DirRef srcEntry, BOOL skip_storage, BOOL skip_stream,
SNB snbExclude, IStorage *pstgDest)
{
DirEntry data;
HRESULT hr;
BOOL skip = FALSE;
IStorage *pstgTmp;
IStream *pstrChild, *pstrTmp;
STATSTG strStat;
if (srcEntry == DIRENTRY_NULL)
return S_OK;
hr = StorageBaseImpl_ReadDirEntry( This, srcEntry, &data );
if (FAILED(hr))
return hr;
if ( snbExclude )
{
WCHAR **snb = snbExclude;
while ( *snb != NULL && !skip )
{
if ( wcscmp(data.name, *snb) == 0 )
skip = TRUE;
++snb;
}
}
if (!skip)
{
if (data.stgType == STGTY_STORAGE && !skip_storage)
{
/*
* create a new storage in destination storage
*/
hr = IStorage_CreateStorage( pstgDest, data.name,
STGM_FAILIFTHERE|STGM_WRITE|STGM_SHARE_EXCLUSIVE,
0, 0,
&pstgTmp );
/*
* if it already exist, don't create a new one use this one
*/
if (hr == STG_E_FILEALREADYEXISTS)
{
hr = IStorage_OpenStorage( pstgDest, data.name, NULL,
STGM_WRITE|STGM_SHARE_EXCLUSIVE,
NULL, 0, &pstgTmp );
}
if (SUCCEEDED(hr))
{
hr = StorageBaseImpl_CopyStorageEntryTo( This, srcEntry, skip_storage,
skip_stream, NULL, pstgTmp );
IStorage_Release(pstgTmp);
}
}
else if (data.stgType == STGTY_STREAM && !skip_stream)
{
/*
* create a new stream in destination storage. If the stream already
* exist, it will be deleted and a new one will be created.
*/
hr = IStorage_CreateStream( pstgDest, data.name,
STGM_CREATE|STGM_WRITE|STGM_SHARE_EXCLUSIVE,
0, 0, &pstrTmp );
/*
* open child stream storage. This operation must succeed even if the
* stream is already open, so we use internal functions to do it.
*/
if (hr == S_OK)
{
StgStreamImpl *streamimpl = StgStreamImpl_Construct(This, STGM_READ|STGM_SHARE_EXCLUSIVE, srcEntry);
if (streamimpl)
{
pstrChild = &streamimpl->IStream_iface;
if (pstrChild)
IStream_AddRef(pstrChild);
}
else
{
pstrChild = NULL;
hr = E_OUTOFMEMORY;
}
}
if (hr == S_OK)
{
/*
* Get the size of the source stream
*/
IStream_Stat( pstrChild, &strStat, STATFLAG_NONAME );
/*
* Set the size of the destination stream.
*/
IStream_SetSize(pstrTmp, strStat.cbSize);
/*
* do the copy
*/
hr = IStream_CopyTo( pstrChild, pstrTmp, strStat.cbSize,
NULL, NULL );
IStream_Release( pstrChild );
}
IStream_Release( pstrTmp );
}
}
/* copy siblings */
if (SUCCEEDED(hr))
hr = StorageBaseImpl_CopyChildEntryTo( This, data.leftChild, skip_storage,
skip_stream, snbExclude, pstgDest );
if (SUCCEEDED(hr))
hr = StorageBaseImpl_CopyChildEntryTo( This, data.rightChild, skip_storage,
skip_stream, snbExclude, pstgDest );
TRACE("<-- %08x\n", hr);
return hr;
}
static BOOL StorageBaseImpl_IsStreamOpen(StorageBaseImpl * stg, DirRef streamEntry)
{
StgStreamImpl *strm;
TRACE("%p,%d\n", stg, streamEntry);
LIST_FOR_EACH_ENTRY(strm, &stg->strmHead, StgStreamImpl, StrmListEntry)
{
if (strm->dirEntry == streamEntry)
{
return TRUE;
}
}
return FALSE;
}
static BOOL StorageBaseImpl_IsStorageOpen(StorageBaseImpl * stg, DirRef storageEntry)
{
StorageInternalImpl *childstg;
TRACE("%p,%d\n", stg, storageEntry);
LIST_FOR_EACH_ENTRY(childstg, &stg->storageHead, StorageInternalImpl, ParentListEntry)
{
if (childstg->base.storageDirEntry == storageEntry)
{
return TRUE;
}
}
return FALSE;
}
/************************************************************************
* StorageBaseImpl_OpenStream (IStorage)
*
* This method will open the specified stream object from the current storage.
*
* See Windows documentation for more details on IStorage methods.
*/
static HRESULT WINAPI StorageBaseImpl_OpenStream(
IStorage* iface,
const OLECHAR* pwcsName, /* [string][in] */
void* reserved1, /* [unique][in] */
DWORD grfMode, /* [in] */
DWORD reserved2, /* [in] */
IStream** ppstm) /* [out] */
{
StorageBaseImpl *This = impl_from_IStorage(iface);
StgStreamImpl* newStream;
DirEntry currentEntry;
DirRef streamEntryRef;
HRESULT res = STG_E_UNKNOWN;
TRACE("(%p, %s, %p, %x, %d, %p)\n",
iface, debugstr_w(pwcsName), reserved1, grfMode, reserved2, ppstm);
if ( (pwcsName==NULL) || (ppstm==0) )
{
res = E_INVALIDARG;
goto end;
}
*ppstm = NULL;
if ( FAILED( validateSTGM(grfMode) ) ||
STGM_SHARE_MODE(grfMode) != STGM_SHARE_EXCLUSIVE)
{
res = STG_E_INVALIDFLAG;
goto end;
}
/*
* As documented.
*/
if ( (grfMode & STGM_DELETEONRELEASE) || (grfMode & STGM_TRANSACTED) )
{
res = STG_E_INVALIDFUNCTION;
goto end;
}
if (This->reverted)
{
res = STG_E_REVERTED;
goto end;
}
/*
* Check that we're compatible with the parent's storage mode, but
* only if we are not in transacted mode
*/
if(!(This->openFlags & STGM_TRANSACTED)) {
if ( STGM_ACCESS_MODE( grfMode ) > STGM_ACCESS_MODE( This->openFlags ) )
{
res = STG_E_INVALIDFLAG;
goto end;
}
}
/*
* Search for the element with the given name
*/
streamEntryRef = findElement(
This,
This->storageDirEntry,
pwcsName,
&currentEntry);
/*
* If it was found, construct the stream object and return a pointer to it.
*/
if ( (streamEntryRef!=DIRENTRY_NULL) &&
(currentEntry.stgType==STGTY_STREAM) )
{
if (StorageBaseImpl_IsStreamOpen(This, streamEntryRef))
{
/* A single stream cannot be opened a second time. */
res = STG_E_ACCESSDENIED;
goto end;
}
newStream = StgStreamImpl_Construct(This, grfMode, streamEntryRef);
if (newStream)
{
newStream->grfMode = grfMode;
*ppstm = &newStream->IStream_iface;
IStream_AddRef(*ppstm);
res = S_OK;
goto end;
}
res = E_OUTOFMEMORY;
goto end;
}
res = STG_E_FILENOTFOUND;
end:
if (res == S_OK)
TRACE("<-- IStream %p\n", *ppstm);
TRACE("<-- %08x\n", res);
return res;
}
/************************************************************************
* StorageBaseImpl_OpenStorage (IStorage)
Sync to Wine-20050725: Robert Shearman <rob@codeweavers.com> - Marshal return value from IRemUnknown_RemQueryInterface. - We should be starting with 1 reference. - IRpcStubBuffer_Disconnect can be called more than once. - Silence now noisy error messages caused by changes in the way we call ipid_to_stubmanager. Move the error message to the one place it is needed. - By-pass the RPC runtime if possible when calling an STA by posting a message directly to the apartment window for it to process. Fixes a deadlock in InstallShield caused by having to create a thread when freeing an object that comes from an STA apartment. Added tests that fail without this fix. - Hack around broken state management so InstallShield works. - Delete the stub manager outside of the apartment critical section because the deletion may require the object to re-enter the apartment. - Always query for the correct stub interface, otherwise we will be pointing to the completely wrong object when a proxy does a queryinterface. - Remove assumption that the stub buffer will handle the lifetime of the object. Alex Villacis Lasso <a_villacis@palosanto.com> - Initialize RegisteredClass properly in CoRegisterClassObject to prevent crash in CoRevokeClassObject when accessing (uninitialized) pMarshalledData. Mike McCormack <mike@codeweavers.com> - Fix gcc 4.0 -Wpointer-sign warnings. Vitaly Lipatov <lav@etersoft.ru> - Added some documentation. Stefan Huehner <stefan@huehner.org> - Fix some missing-declarations warnings. Marcus Meissner <meissner@suse.de> - 16bit interfaces are cdecl, so drop the WINAPI. - 16bit COM interfaces are cdecl, not WINAPI. - OleInitializeWOW gets 2 arguments. - Added OleSetMenuDescriptor16 stub. Marcus Meissner <marcus@jet.franken.de> - Implemented IsValidInterface16, CoMemAlloc. Added debug to HGLOBALLockBytes16_QueryInterface. svn path=/trunk/; revision=17332
2005-08-12 17:19:46 +00:00
*
* This method will open a new storage object from the current storage.
*
* See Windows documentation for more details on IStorage methods.
*/
static HRESULT WINAPI StorageBaseImpl_OpenStorage(
IStorage* iface,
const OLECHAR* pwcsName, /* [string][unique][in] */
IStorage* pstgPriority, /* [unique][in] */
DWORD grfMode, /* [in] */
SNB snbExclude, /* [unique][in] */
DWORD reserved, /* [in] */
IStorage** ppstg) /* [out] */
{
StorageBaseImpl *This = impl_from_IStorage(iface);
StorageInternalImpl* newStorage;
StorageBaseImpl* newTransactedStorage;
DirEntry currentEntry;
DirRef storageEntryRef;
HRESULT res = STG_E_UNKNOWN;
TRACE("(%p, %s, %p, %x, %p, %d, %p)\n",
iface, debugstr_w(pwcsName), pstgPriority,
grfMode, snbExclude, reserved, ppstg);
if ((pwcsName==NULL) || (ppstg==0) )
{
res = E_INVALIDARG;
goto end;
}
if (This->openFlags & STGM_SIMPLE)
{
res = STG_E_INVALIDFUNCTION;
goto end;
}
/* as documented */
if (snbExclude != NULL)
{
res = STG_E_INVALIDPARAMETER;
goto end;
}
if ( FAILED( validateSTGM(grfMode) ))
{
res = STG_E_INVALIDFLAG;
goto end;
}
/*
* As documented.
*/
if ( STGM_SHARE_MODE(grfMode) != STGM_SHARE_EXCLUSIVE ||
(grfMode & STGM_DELETEONRELEASE) ||
(grfMode & STGM_PRIORITY) )
{
res = STG_E_INVALIDFUNCTION;
goto end;
}
if (This->reverted)
return STG_E_REVERTED;
/*
* Check that we're compatible with the parent's storage mode,
* but only if we are not transacted
*/
if(!(This->openFlags & STGM_TRANSACTED)) {
if ( STGM_ACCESS_MODE( grfMode ) > STGM_ACCESS_MODE( This->openFlags ) )
{
res = STG_E_ACCESSDENIED;
goto end;
}
}
*ppstg = NULL;
storageEntryRef = findElement(
This,
This->storageDirEntry,
pwcsName,
&currentEntry);
if ( (storageEntryRef!=DIRENTRY_NULL) &&
(currentEntry.stgType==STGTY_STORAGE) )
{
if (StorageBaseImpl_IsStorageOpen(This, storageEntryRef))
{
/* A single storage cannot be opened a second time. */
res = STG_E_ACCESSDENIED;
goto end;
}
newStorage = StorageInternalImpl_Construct(
This,
grfMode,
storageEntryRef);
if (newStorage != 0)
{
if (grfMode & STGM_TRANSACTED)
{
res = Storage_ConstructTransacted(&newStorage->base, FALSE, &newTransactedStorage);
if (FAILED(res))
{
HeapFree(GetProcessHeap(), 0, newStorage);
goto end;
}
*ppstg = &newTransactedStorage->IStorage_iface;
}
else
{
*ppstg = &newStorage->base.IStorage_iface;
}
list_add_tail(&This->storageHead, &newStorage->ParentListEntry);
res = S_OK;
goto end;
}
res = STG_E_INSUFFICIENTMEMORY;
goto end;
}
res = STG_E_FILENOTFOUND;
end:
TRACE("<-- %08x\n", res);
return res;
}
/************************************************************************
* StorageBaseImpl_EnumElements (IStorage)
*
* This method will create an enumerator object that can be used to
* retrieve information about all the elements in the storage object.
*
* See Windows documentation for more details on IStorage methods.
*/
static HRESULT WINAPI StorageBaseImpl_EnumElements(
IStorage* iface,
DWORD reserved1, /* [in] */
void* reserved2, /* [size_is][unique][in] */
DWORD reserved3, /* [in] */
IEnumSTATSTG** ppenum) /* [out] */
{
StorageBaseImpl *This = impl_from_IStorage(iface);
IEnumSTATSTGImpl* newEnum;
TRACE("(%p, %d, %p, %d, %p)\n",
iface, reserved1, reserved2, reserved3, ppenum);
if (!ppenum)
return E_INVALIDARG;
if (This->reverted)
return STG_E_REVERTED;
newEnum = IEnumSTATSTGImpl_Construct(
This,
This->storageDirEntry);
if (newEnum)
{
*ppenum = &newEnum->IEnumSTATSTG_iface;
return S_OK;
}
return E_OUTOFMEMORY;
}
/************************************************************************
* StorageBaseImpl_Stat (IStorage)
*
* This method will retrieve information about this storage object.
*
* See Windows documentation for more details on IStorage methods.
*/
static HRESULT WINAPI StorageBaseImpl_Stat(
IStorage* iface,
STATSTG* pstatstg, /* [out] */
DWORD grfStatFlag) /* [in] */
{
StorageBaseImpl *This = impl_from_IStorage(iface);
DirEntry currentEntry;
HRESULT res = STG_E_UNKNOWN;
TRACE("(%p, %p, %x)\n",
iface, pstatstg, grfStatFlag);
if (!pstatstg)
{
res = E_INVALIDARG;
goto end;
}
if (This->reverted)
{
res = STG_E_REVERTED;
goto end;
}
res = StorageBaseImpl_ReadDirEntry(
This,
This->storageDirEntry,
&currentEntry);
if (SUCCEEDED(res))
{
StorageUtl_CopyDirEntryToSTATSTG(
This,
pstatstg,
&currentEntry,
grfStatFlag);
pstatstg->grfMode = This->openFlags;
pstatstg->grfStateBits = This->stateBits;
}
end:
if (res == S_OK)
{
TRACE("<-- STATSTG: pwcsName: %s, type: %d, cbSize.Low/High: %d/%d, grfMode: %08x, grfLocksSupported: %d, grfStateBits: %08x\n", debugstr_w(pstatstg->pwcsName), pstatstg->type, pstatstg->cbSize.u.LowPart, pstatstg->cbSize.u.HighPart, pstatstg->grfMode, pstatstg->grfLocksSupported, pstatstg->grfStateBits);
}
TRACE("<-- %08x\n", res);
return res;
}
/************************************************************************
* StorageBaseImpl_RenameElement (IStorage)
*
* This method will rename the specified element.
*
* See Windows documentation for more details on IStorage methods.
*/
static HRESULT WINAPI StorageBaseImpl_RenameElement(
IStorage* iface,
const OLECHAR* pwcsOldName, /* [in] */
const OLECHAR* pwcsNewName) /* [in] */
{
StorageBaseImpl *This = impl_from_IStorage(iface);
DirEntry currentEntry;
DirRef currentEntryRef;
TRACE("(%p, %s, %s)\n",
iface, debugstr_w(pwcsOldName), debugstr_w(pwcsNewName));
if (This->reverted)
return STG_E_REVERTED;
currentEntryRef = findElement(This,
This->storageDirEntry,
pwcsNewName,
&currentEntry);
if (currentEntryRef != DIRENTRY_NULL)
{
/*
* There is already an element with the new name
*/
return STG_E_FILEALREADYEXISTS;
}
/*
* Search for the old element name
*/
currentEntryRef = findElement(This,
This->storageDirEntry,
pwcsOldName,
&currentEntry);
if (currentEntryRef != DIRENTRY_NULL)
{
if (StorageBaseImpl_IsStreamOpen(This, currentEntryRef) ||
StorageBaseImpl_IsStorageOpen(This, currentEntryRef))
{
WARN("Element is already open; cannot rename.\n");
return STG_E_ACCESSDENIED;
}
/* Remove the element from its current position in the tree */
removeFromTree(This, This->storageDirEntry,
currentEntryRef);
/* Change the name of the element */
lstrcpyW(currentEntry.name, pwcsNewName);
/* Delete any sibling links */
currentEntry.leftChild = DIRENTRY_NULL;
currentEntry.rightChild = DIRENTRY_NULL;
StorageBaseImpl_WriteDirEntry(This, currentEntryRef,
&currentEntry);
/* Insert the element in a new position in the tree */
insertIntoTree(This, This->storageDirEntry,
currentEntryRef);
}
else
{
/*
* There is no element with the old name
*/
return STG_E_FILENOTFOUND;
}
return StorageBaseImpl_Flush(This);
}
/************************************************************************
* StorageBaseImpl_CreateStream (IStorage)
*
* This method will create a stream object within this storage
*
* See Windows documentation for more details on IStorage methods.
*/
static HRESULT WINAPI StorageBaseImpl_CreateStream(
IStorage* iface,
const OLECHAR* pwcsName, /* [string][in] */
DWORD grfMode, /* [in] */
DWORD reserved1, /* [in] */
DWORD reserved2, /* [in] */
IStream** ppstm) /* [out] */
{
StorageBaseImpl *This = impl_from_IStorage(iface);
StgStreamImpl* newStream;
DirEntry currentEntry, newStreamEntry;
DirRef currentEntryRef, newStreamEntryRef;
HRESULT hr;
TRACE("(%p, %s, %x, %d, %d, %p)\n",
iface, debugstr_w(pwcsName), grfMode,
reserved1, reserved2, ppstm);
if (ppstm == 0)
return STG_E_INVALIDPOINTER;
if (pwcsName == 0)
return STG_E_INVALIDNAME;
if (reserved1 || reserved2)
return STG_E_INVALIDPARAMETER;
if ( FAILED( validateSTGM(grfMode) ))
return STG_E_INVALIDFLAG;
if (STGM_SHARE_MODE(grfMode) != STGM_SHARE_EXCLUSIVE)
return STG_E_INVALIDFLAG;
if (This->reverted)
return STG_E_REVERTED;
/*
* As documented.
*/
if ((grfMode & STGM_DELETEONRELEASE) ||
(grfMode & STGM_TRANSACTED))
return STG_E_INVALIDFUNCTION;
/*
* Don't worry about permissions in transacted mode, as we can always write
* changes; we just can't always commit them.
*/
if(!(This->openFlags & STGM_TRANSACTED)) {
/* Can't create a stream on read-only storage */
if ( STGM_ACCESS_MODE( This->openFlags ) == STGM_READ )
return STG_E_ACCESSDENIED;
/* Can't create a stream with greater access than the parent. */
if ( STGM_ACCESS_MODE( grfMode ) > STGM_ACCESS_MODE( This->openFlags ) )
return STG_E_ACCESSDENIED;
}
if(This->openFlags & STGM_SIMPLE)
if(grfMode & STGM_CREATE) return STG_E_INVALIDFLAG;
*ppstm = 0;
currentEntryRef = findElement(This,
This->storageDirEntry,
pwcsName,
&currentEntry);
if (currentEntryRef != DIRENTRY_NULL)
{
/*
* An element with this name already exists
*/
if (STGM_CREATE_MODE(grfMode) == STGM_CREATE)
{
IStorage_DestroyElement(iface, pwcsName);
}
else
return STG_E_FILEALREADYEXISTS;
}
/*
* memset the empty entry
*/
memset(&newStreamEntry, 0, sizeof(DirEntry));
newStreamEntry.sizeOfNameString =
( lstrlenW(pwcsName)+1 ) * sizeof(WCHAR);
if (newStreamEntry.sizeOfNameString > DIRENTRY_NAME_BUFFER_LEN)
return STG_E_INVALIDNAME;
lstrcpyW(newStreamEntry.name, pwcsName);
newStreamEntry.stgType = STGTY_STREAM;
newStreamEntry.startingBlock = BLOCK_END_OF_CHAIN;
newStreamEntry.size.u.LowPart = 0;
newStreamEntry.size.u.HighPart = 0;
newStreamEntry.leftChild = DIRENTRY_NULL;
newStreamEntry.rightChild = DIRENTRY_NULL;
newStreamEntry.dirRootEntry = DIRENTRY_NULL;
/* call CoFileTime to get the current time
newStreamEntry.ctime
newStreamEntry.mtime
*/
/* newStreamEntry.clsid */
/*
* Create an entry with the new data
*/
hr = StorageBaseImpl_CreateDirEntry(This, &newStreamEntry, &newStreamEntryRef);
if (FAILED(hr))
return hr;
/*
* Insert the new entry in the parent storage's tree.
*/
hr = insertIntoTree(
This,
This->storageDirEntry,
newStreamEntryRef);
if (FAILED(hr))
{
StorageBaseImpl_DestroyDirEntry(This, newStreamEntryRef);
return hr;
}
/*
* Open the stream to return it.
*/
newStream = StgStreamImpl_Construct(This, grfMode, newStreamEntryRef);
if (newStream)
{
*ppstm = &newStream->IStream_iface;
IStream_AddRef(*ppstm);
}
else
{
return STG_E_INSUFFICIENTMEMORY;
}
return StorageBaseImpl_Flush(This);
}
/************************************************************************
* StorageBaseImpl_SetClass (IStorage)
*
* This method will write the specified CLSID in the directory entry of this
* storage.
*
* See Windows documentation for more details on IStorage methods.
*/
static HRESULT WINAPI StorageBaseImpl_SetClass(
IStorage* iface,
REFCLSID clsid) /* [in] */
{
StorageBaseImpl *This = impl_from_IStorage(iface);
HRESULT hRes;
DirEntry currentEntry;
2018-01-20 11:57:25 +00:00
TRACE("(%p, %s)\n", iface, wine_dbgstr_guid(clsid));
if (This->reverted)
return STG_E_REVERTED;
hRes = StorageBaseImpl_ReadDirEntry(This,
This->storageDirEntry,
&currentEntry);
if (SUCCEEDED(hRes))
{
currentEntry.clsid = *clsid;
hRes = StorageBaseImpl_WriteDirEntry(This,
This->storageDirEntry,
&currentEntry);
}
if (SUCCEEDED(hRes))
hRes = StorageBaseImpl_Flush(This);
return hRes;
}
/************************************************************************
* StorageBaseImpl_CreateStorage (IStorage)
*
* This method will create the storage object within the provided storage.
*
* See Windows documentation for more details on IStorage methods.
*/
static HRESULT WINAPI StorageBaseImpl_CreateStorage(
IStorage* iface,
const OLECHAR *pwcsName, /* [string][in] */
DWORD grfMode, /* [in] */
DWORD reserved1, /* [in] */
DWORD reserved2, /* [in] */
IStorage **ppstg) /* [out] */
{
StorageBaseImpl* This = impl_from_IStorage(iface);
DirEntry currentEntry;
DirEntry newEntry;
DirRef currentEntryRef;
DirRef newEntryRef;
HRESULT hr;
TRACE("(%p, %s, %x, %d, %d, %p)\n",
iface, debugstr_w(pwcsName), grfMode,
reserved1, reserved2, ppstg);
if (ppstg == 0)
return STG_E_INVALIDPOINTER;
if (This->openFlags & STGM_SIMPLE)
{
return STG_E_INVALIDFUNCTION;
}
if (pwcsName == 0)
return STG_E_INVALIDNAME;
*ppstg = NULL;
if ( FAILED( validateSTGM(grfMode) ) ||
(grfMode & STGM_DELETEONRELEASE) )
{
WARN("bad grfMode: 0x%x\n", grfMode);
return STG_E_INVALIDFLAG;
}
if (This->reverted)
return STG_E_REVERTED;
/*
* Check that we're compatible with the parent's storage mode
*/
if ( !(This->openFlags & STGM_TRANSACTED) &&
STGM_ACCESS_MODE( grfMode ) > STGM_ACCESS_MODE( This->openFlags ) )
{
WARN("access denied\n");
return STG_E_ACCESSDENIED;
}
currentEntryRef = findElement(This,
This->storageDirEntry,
pwcsName,
&currentEntry);
if (currentEntryRef != DIRENTRY_NULL)
{
/*
* An element with this name already exists
*/
if (STGM_CREATE_MODE(grfMode) == STGM_CREATE &&
((This->openFlags & STGM_TRANSACTED) ||
STGM_ACCESS_MODE(This->openFlags) != STGM_READ))
{
hr = IStorage_DestroyElement(iface, pwcsName);
if (FAILED(hr))
return hr;
}
else
{
WARN("file already exists\n");
return STG_E_FILEALREADYEXISTS;
}
}
else if (!(This->openFlags & STGM_TRANSACTED) &&
STGM_ACCESS_MODE(This->openFlags) == STGM_READ)
{
WARN("read-only storage\n");
return STG_E_ACCESSDENIED;
}
memset(&newEntry, 0, sizeof(DirEntry));
newEntry.sizeOfNameString = (lstrlenW(pwcsName)+1)*sizeof(WCHAR);
if (newEntry.sizeOfNameString > DIRENTRY_NAME_BUFFER_LEN)
{
FIXME("name too long\n");
return STG_E_INVALIDNAME;
}
lstrcpyW(newEntry.name, pwcsName);
newEntry.stgType = STGTY_STORAGE;
newEntry.startingBlock = BLOCK_END_OF_CHAIN;
newEntry.size.u.LowPart = 0;
newEntry.size.u.HighPart = 0;
newEntry.leftChild = DIRENTRY_NULL;
newEntry.rightChild = DIRENTRY_NULL;
newEntry.dirRootEntry = DIRENTRY_NULL;
/* call CoFileTime to get the current time
newEntry.ctime
newEntry.mtime
*/
/* newEntry.clsid */
/*
* Create a new directory entry for the storage
*/
hr = StorageBaseImpl_CreateDirEntry(This, &newEntry, &newEntryRef);
if (FAILED(hr))
return hr;
/*
* Insert the new directory entry into the parent storage's tree
*/
hr = insertIntoTree(
This,
This->storageDirEntry,
newEntryRef);
if (FAILED(hr))
{
StorageBaseImpl_DestroyDirEntry(This, newEntryRef);
return hr;
}
/*
* Open it to get a pointer to return.
*/
hr = IStorage_OpenStorage(iface, pwcsName, 0, grfMode, 0, 0, ppstg);
if( (hr != S_OK) || (*ppstg == NULL))
{
return hr;
}
if (SUCCEEDED(hr))
hr = StorageBaseImpl_Flush(This);
return S_OK;
}
static HRESULT StorageBaseImpl_CopyStorageEntryTo(StorageBaseImpl *This,
DirRef srcEntry, BOOL skip_storage, BOOL skip_stream,
SNB snbExclude, IStorage *pstgDest)
{
DirEntry data;
HRESULT hr;
hr = StorageBaseImpl_ReadDirEntry( This, srcEntry, &data );
if (SUCCEEDED(hr))
hr = IStorage_SetClass( pstgDest, &data.clsid );
if (SUCCEEDED(hr))
hr = StorageBaseImpl_CopyChildEntryTo( This, data.dirRootEntry, skip_storage,
skip_stream, snbExclude, pstgDest );
TRACE("<-- %08x\n", hr);
return hr;
}
/*************************************************************************
* CopyTo (IStorage)
*/
static HRESULT WINAPI StorageBaseImpl_CopyTo(
IStorage* iface,
DWORD ciidExclude, /* [in] */
const IID* rgiidExclude, /* [size_is][unique][in] */
SNB snbExclude, /* [unique][in] */
IStorage* pstgDest) /* [unique][in] */
{
StorageBaseImpl *This = impl_from_IStorage(iface);
BOOL skip_storage = FALSE, skip_stream = FALSE;
DWORD i;
TRACE("(%p, %d, %p, %p, %p)\n",
iface, ciidExclude, rgiidExclude,
snbExclude, pstgDest);
if ( pstgDest == 0 )
return STG_E_INVALIDPOINTER;
for(i = 0; i < ciidExclude; ++i)
{
if(IsEqualGUID(&IID_IStorage, &rgiidExclude[i]))
skip_storage = TRUE;
else if(IsEqualGUID(&IID_IStream, &rgiidExclude[i]))
skip_stream = TRUE;
else
WARN("Unknown excluded GUID: %s\n", debugstr_guid(&rgiidExclude[i]));
}
if (!skip_storage)
{
/* Give up early if it looks like this would be infinitely recursive.
* Oddly enough, this includes some cases that aren't really recursive, like
* copying to a transacted child. */
IStorage *pstgDestAncestor = pstgDest;
IStorage *pstgDestAncestorChild = NULL;
/* Go up the chain from the destination until we find the source storage. */
while (pstgDestAncestor != iface) {
pstgDestAncestorChild = pstgDest;
if (pstgDestAncestor->lpVtbl == &TransactedSnapshotImpl_Vtbl)
{
TransactedSnapshotImpl *snapshot = (TransactedSnapshotImpl*) pstgDestAncestor;
pstgDestAncestor = &snapshot->transactedParent->IStorage_iface;
}
else if (pstgDestAncestor->lpVtbl == &StorageInternalImpl_Vtbl)
{
StorageInternalImpl *internal = (StorageInternalImpl*) pstgDestAncestor;
pstgDestAncestor = &internal->parentStorage->IStorage_iface;
}
else
break;
}
if (pstgDestAncestor == iface)
{
BOOL fail = TRUE;
if (pstgDestAncestorChild && snbExclude)
{
StorageBaseImpl *ancestorChildBase = (StorageBaseImpl*)pstgDestAncestorChild;
DirEntry data;
WCHAR **snb = snbExclude;
StorageBaseImpl_ReadDirEntry(ancestorChildBase, ancestorChildBase->storageDirEntry, &data);
while ( *snb != NULL && fail )
{
if ( wcscmp(data.name, *snb) == 0 )
fail = FALSE;
++snb;
}
}
if (fail)
return STG_E_ACCESSDENIED;
}
}
return StorageBaseImpl_CopyStorageEntryTo( This, This->storageDirEntry,
skip_storage, skip_stream, snbExclude, pstgDest );
}
/*************************************************************************
* MoveElementTo (IStorage)
*/
static HRESULT WINAPI StorageBaseImpl_MoveElementTo(
IStorage* iface,
const OLECHAR *pwcsName, /* [string][in] */
IStorage *pstgDest, /* [unique][in] */
const OLECHAR *pwcsNewName,/* [string][in] */
DWORD grfFlags) /* [in] */
{
FIXME("(%p %s %p %s %u): stub\n", iface,
debugstr_w(pwcsName), pstgDest,
debugstr_w(pwcsNewName), grfFlags);
return E_NOTIMPL;
}
/*************************************************************************
* Commit (IStorage)
*
* Ensures that any changes made to a storage object open in transacted mode
* are reflected in the parent storage
*
* In a non-transacted mode, this ensures all cached writes are completed.
*/
static HRESULT WINAPI StorageBaseImpl_Commit(
IStorage* iface,
DWORD grfCommitFlags)/* [in] */
{
StorageBaseImpl* This = impl_from_IStorage(iface);
TRACE("(%p %d)\n", iface, grfCommitFlags);
return StorageBaseImpl_Flush(This);
}
/*************************************************************************
* Revert (IStorage)
*
* Discard all changes that have been made since the last commit operation
*/
static HRESULT WINAPI StorageBaseImpl_Revert(
IStorage* iface)
{
TRACE("(%p)\n", iface);
return S_OK;
}
/*********************************************************************
*
* Internal helper function for StorageBaseImpl_DestroyElement()
*
* Delete the contents of a storage entry.
*
*/
static HRESULT deleteStorageContents(
StorageBaseImpl *parentStorage,
DirRef indexToDelete,
DirEntry entryDataToDelete)
{
IEnumSTATSTG *elements = 0;
IStorage *childStorage = 0;
STATSTG currentElement;
HRESULT hr;
HRESULT destroyHr = S_OK;
StorageInternalImpl *stg, *stg2;
TRACE("%p,%d\n", parentStorage, indexToDelete);
/* Invalidate any open storage objects. */
LIST_FOR_EACH_ENTRY_SAFE(stg, stg2, &parentStorage->storageHead, StorageInternalImpl, ParentListEntry)
{
if (stg->base.storageDirEntry == indexToDelete)
{
StorageBaseImpl_Invalidate(&stg->base);
}
}
/*
* Open the storage and enumerate it
*/
hr = IStorage_OpenStorage(
&parentStorage->IStorage_iface,
entryDataToDelete.name,
0,
STGM_WRITE | STGM_SHARE_EXCLUSIVE,
0,
0,
&childStorage);
if (hr != S_OK)
{
TRACE("<-- %08x\n", hr);
return hr;
}
/*
* Enumerate the elements
*/
hr = IStorage_EnumElements(childStorage, 0, 0, 0, &elements);
if (FAILED(hr))
{
IStorage_Release(childStorage);
TRACE("<-- %08x\n", hr);
return hr;
}
do
{
/*
* Obtain the next element
*/
hr = IEnumSTATSTG_Next(elements, 1, &currentElement, NULL);
if (hr==S_OK)
{
destroyHr = IStorage_DestroyElement(childStorage, currentElement.pwcsName);
CoTaskMemFree(currentElement.pwcsName);
}
/*
* We need to Reset the enumeration every time because we delete elements
* and the enumeration could be invalid
*/
IEnumSTATSTG_Reset(elements);
} while ((hr == S_OK) && (destroyHr == S_OK));
IStorage_Release(childStorage);
IEnumSTATSTG_Release(elements);
TRACE("%08x\n", hr);
return destroyHr;
}
/*********************************************************************
*
* Internal helper function for StorageBaseImpl_DestroyElement()
*
* Perform the deletion of a stream's data
*
*/
static HRESULT deleteStreamContents(
StorageBaseImpl *parentStorage,
DirRef indexToDelete,
DirEntry entryDataToDelete)
{
IStream *pis;
HRESULT hr;
ULARGE_INTEGER size;
StgStreamImpl *strm, *strm2;
/* Invalidate any open stream objects. */
LIST_FOR_EACH_ENTRY_SAFE(strm, strm2, &parentStorage->strmHead, StgStreamImpl, StrmListEntry)
{
if (strm->dirEntry == indexToDelete)
{
TRACE("Stream deleted %p\n", strm);
strm->parentStorage = NULL;
list_remove(&strm->StrmListEntry);
}
}
size.u.HighPart = 0;
size.u.LowPart = 0;
hr = IStorage_OpenStream(&parentStorage->IStorage_iface,
entryDataToDelete.name, NULL, STGM_WRITE | STGM_SHARE_EXCLUSIVE, 0, &pis);
if (hr!=S_OK)
{
TRACE("<-- %08x\n", hr);
return(hr);
}
/*
* Zap the stream
*/
hr = IStream_SetSize(pis, size);
if(hr != S_OK)
{
TRACE("<-- %08x\n", hr);
return hr;
}
/*
* Release the stream object.
*/
IStream_Release(pis);
TRACE("<-- %08x\n", hr);
return S_OK;
}
/*************************************************************************
* DestroyElement (IStorage)
*
* Strategy: This implementation is built this way for simplicity not for speed.
* I always delete the topmost element of the enumeration and adjust
* the deleted element pointer all the time. This takes longer to
* do but allows reinvoking DestroyElement whenever we encounter a
* storage object. The optimisation resides in the usage of another
* enumeration strategy that would give all the leaves of a storage
* first. (postfix order)
*/
static HRESULT WINAPI StorageBaseImpl_DestroyElement(
IStorage* iface,
const OLECHAR *pwcsName)/* [string][in] */
{
StorageBaseImpl *This = impl_from_IStorage(iface);
HRESULT hr = S_OK;
DirEntry entryToDelete;
DirRef entryToDeleteRef;
TRACE("(%p, %s)\n",
iface, debugstr_w(pwcsName));
if (pwcsName==NULL)
return STG_E_INVALIDPOINTER;
if (This->reverted)
return STG_E_REVERTED;
if ( !(This->openFlags & STGM_TRANSACTED) &&
STGM_ACCESS_MODE( This->openFlags ) == STGM_READ )
return STG_E_ACCESSDENIED;
entryToDeleteRef = findElement(
This,
This->storageDirEntry,
pwcsName,
&entryToDelete);
if ( entryToDeleteRef == DIRENTRY_NULL )
{
TRACE("<-- STG_E_FILENOTFOUND\n");
return STG_E_FILENOTFOUND;
}
if ( entryToDelete.stgType == STGTY_STORAGE )
{
hr = deleteStorageContents(
This,
entryToDeleteRef,
entryToDelete);
}
else if ( entryToDelete.stgType == STGTY_STREAM )
{
hr = deleteStreamContents(
This,
entryToDeleteRef,
entryToDelete);
}
if (hr!=S_OK)
{
TRACE("<-- %08x\n", hr);
return hr;
}
/*
* Remove the entry from its parent storage
*/
hr = removeFromTree(
This,
This->storageDirEntry,
entryToDeleteRef);
/*
* Invalidate the entry
*/
if (SUCCEEDED(hr))
StorageBaseImpl_DestroyDirEntry(This, entryToDeleteRef);
if (SUCCEEDED(hr))
hr = StorageBaseImpl_Flush(This);
TRACE("<-- %08x\n", hr);
return hr;
}
static void StorageBaseImpl_DeleteAll(StorageBaseImpl * stg)
{
struct list *cur, *cur2;
StgStreamImpl *strm=NULL;
StorageInternalImpl *childstg=NULL;
LIST_FOR_EACH_SAFE(cur, cur2, &stg->strmHead) {
strm = LIST_ENTRY(cur,StgStreamImpl,StrmListEntry);
TRACE("Streams invalidated (stg=%p strm=%p next=%p prev=%p)\n", stg,strm,cur->next,cur->prev);
strm->parentStorage = NULL;
list_remove(cur);
}
LIST_FOR_EACH_SAFE(cur, cur2, &stg->storageHead) {
childstg = LIST_ENTRY(cur,StorageInternalImpl,ParentListEntry);
StorageBaseImpl_Invalidate( &childstg->base );
}
if (stg->transactedChild)
{
StorageBaseImpl_Invalidate(stg->transactedChild);
stg->transactedChild = NULL;
}
}
/******************************************************************************
* SetElementTimes (IStorage)
*/
static HRESULT WINAPI StorageBaseImpl_SetElementTimes(
IStorage* iface,
const OLECHAR *pwcsName,/* [string][in] */
const FILETIME *pctime, /* [in] */
const FILETIME *patime, /* [in] */
const FILETIME *pmtime) /* [in] */
{
FIXME("(%s,...), stub!\n",debugstr_w(pwcsName));
return S_OK;
}
/******************************************************************************
* SetStateBits (IStorage)
*/
static HRESULT WINAPI StorageBaseImpl_SetStateBits(
IStorage* iface,
DWORD grfStateBits,/* [in] */
DWORD grfMask) /* [in] */
{
StorageBaseImpl *This = impl_from_IStorage(iface);
if (This->reverted)
return STG_E_REVERTED;
This->stateBits = (This->stateBits & ~grfMask) | (grfStateBits & grfMask);
return S_OK;
}
/******************************************************************************
* Internal stream list handlers
*/
void StorageBaseImpl_AddStream(StorageBaseImpl * stg, StgStreamImpl * strm)
{
TRACE("Stream added (stg=%p strm=%p)\n", stg, strm);
list_add_tail(&stg->strmHead,&strm->StrmListEntry);
}
void StorageBaseImpl_RemoveStream(StorageBaseImpl * stg, StgStreamImpl * strm)
{
TRACE("Stream removed (stg=%p strm=%p)\n", stg,strm);
list_remove(&(strm->StrmListEntry));
}
static HRESULT StorageBaseImpl_CopyStream(
StorageBaseImpl *dst, DirRef dst_entry,
StorageBaseImpl *src, DirRef src_entry)
{
HRESULT hr;
BYTE data[4096];
DirEntry srcdata;
ULARGE_INTEGER bytes_copied;
ULONG bytestocopy, bytesread, byteswritten;
hr = StorageBaseImpl_ReadDirEntry(src, src_entry, &srcdata);
if (SUCCEEDED(hr))
{
hr = StorageBaseImpl_StreamSetSize(dst, dst_entry, srcdata.size);
bytes_copied.QuadPart = 0;
while (bytes_copied.QuadPart < srcdata.size.QuadPart && SUCCEEDED(hr))
{
bytestocopy = min(4096, srcdata.size.QuadPart - bytes_copied.QuadPart);
hr = StorageBaseImpl_StreamReadAt(src, src_entry, bytes_copied, bytestocopy,
data, &bytesread);
if (SUCCEEDED(hr) && bytesread != bytestocopy) hr = STG_E_READFAULT;
if (SUCCEEDED(hr))
hr = StorageBaseImpl_StreamWriteAt(dst, dst_entry, bytes_copied, bytestocopy,
data, &byteswritten);
if (SUCCEEDED(hr))
{
if (byteswritten != bytestocopy) hr = STG_E_WRITEFAULT;
bytes_copied.QuadPart += byteswritten;
}
}
}
return hr;
}
static HRESULT StorageBaseImpl_DupStorageTree(
StorageBaseImpl *dst, DirRef *dst_entry,
StorageBaseImpl *src, DirRef src_entry)
{
HRESULT hr;
DirEntry data;
BOOL has_stream=FALSE;
if (src_entry == DIRENTRY_NULL)
{
*dst_entry = DIRENTRY_NULL;
return S_OK;
}
hr = StorageBaseImpl_ReadDirEntry(src, src_entry, &data);
if (SUCCEEDED(hr))
{
has_stream = (data.stgType == STGTY_STREAM && data.size.QuadPart != 0);
data.startingBlock = BLOCK_END_OF_CHAIN;
data.size.QuadPart = 0;
hr = StorageBaseImpl_DupStorageTree(dst, &data.leftChild, src, data.leftChild);
}
if (SUCCEEDED(hr))
hr = StorageBaseImpl_DupStorageTree(dst, &data.rightChild, src, data.rightChild);
if (SUCCEEDED(hr))
hr = StorageBaseImpl_DupStorageTree(dst, &data.dirRootEntry, src, data.dirRootEntry);
if (SUCCEEDED(hr))
hr = StorageBaseImpl_CreateDirEntry(dst, &data, dst_entry);
if (SUCCEEDED(hr) && has_stream)
hr = StorageBaseImpl_CopyStream(dst, *dst_entry, src, src_entry);
return hr;
}
static HRESULT StorageBaseImpl_CopyStorageTree(
StorageBaseImpl *dst, DirRef dst_entry,
StorageBaseImpl *src, DirRef src_entry)
{
HRESULT hr;
DirEntry src_data, dst_data;
DirRef new_root_entry;
hr = StorageBaseImpl_ReadDirEntry(src, src_entry, &src_data);
if (SUCCEEDED(hr))
{
hr = StorageBaseImpl_DupStorageTree(dst, &new_root_entry, src, src_data.dirRootEntry);
}
if (SUCCEEDED(hr))
{
hr = StorageBaseImpl_ReadDirEntry(dst, dst_entry, &dst_data);
dst_data.clsid = src_data.clsid;
dst_data.ctime = src_data.ctime;
dst_data.mtime = src_data.mtime;
dst_data.dirRootEntry = new_root_entry;
}
if (SUCCEEDED(hr))
hr = StorageBaseImpl_WriteDirEntry(dst, dst_entry, &dst_data);
return hr;
}
static HRESULT StorageBaseImpl_DeleteStorageTree(StorageBaseImpl *This, DirRef entry, BOOL include_siblings)
{
HRESULT hr;
DirEntry data;
ULARGE_INTEGER zero;
if (entry == DIRENTRY_NULL)
return S_OK;
zero.QuadPart = 0;
hr = StorageBaseImpl_ReadDirEntry(This, entry, &data);
if (SUCCEEDED(hr) && include_siblings)
hr = StorageBaseImpl_DeleteStorageTree(This, data.leftChild, TRUE);
if (SUCCEEDED(hr) && include_siblings)
hr = StorageBaseImpl_DeleteStorageTree(This, data.rightChild, TRUE);
if (SUCCEEDED(hr))
hr = StorageBaseImpl_DeleteStorageTree(This, data.dirRootEntry, TRUE);
if (SUCCEEDED(hr) && data.stgType == STGTY_STREAM)
hr = StorageBaseImpl_StreamSetSize(This, entry, zero);
if (SUCCEEDED(hr))
hr = StorageBaseImpl_DestroyDirEntry(This, entry);
return hr;
}
/************************************************************************
* StorageImpl implementation
***********************************************************************/
static HRESULT StorageImpl_ReadAt(StorageImpl* This,
ULARGE_INTEGER offset,
void* buffer,
ULONG size,
ULONG* bytesRead)
{
return ILockBytes_ReadAt(This->lockBytes,offset,buffer,size,bytesRead);
}
static HRESULT StorageImpl_WriteAt(StorageImpl* This,
ULARGE_INTEGER offset,
const void* buffer,
const ULONG size,
ULONG* bytesWritten)
{
return ILockBytes_WriteAt(This->lockBytes,offset,buffer,size,bytesWritten);
}
/******************************************************************************
* StorageImpl_LoadFileHeader
*
* This method will read in the file header
*/
static HRESULT StorageImpl_LoadFileHeader(
StorageImpl* This)
{
HRESULT hr;
BYTE headerBigBlock[HEADER_SIZE];
int index;
ULARGE_INTEGER offset;
DWORD bytes_read;
TRACE("\n");
/*
* Get a pointer to the big block of data containing the header.
*/
offset.u.HighPart = 0;
offset.u.LowPart = 0;
hr = StorageImpl_ReadAt(This, offset, headerBigBlock, HEADER_SIZE, &bytes_read);
if (SUCCEEDED(hr) && bytes_read != HEADER_SIZE)
hr = STG_E_FILENOTFOUND;
/*
* Extract the information from the header.
*/
if (SUCCEEDED(hr))
{
/*
* Check for the "magic number" signature and return an error if it is not
* found.
*/
if (memcmp(headerBigBlock, STORAGE_oldmagic, sizeof(STORAGE_oldmagic))==0)
{
return STG_E_OLDFORMAT;
}
if (memcmp(headerBigBlock, STORAGE_magic, sizeof(STORAGE_magic))!=0)
{
return STG_E_INVALIDHEADER;
}
StorageUtl_ReadWord(
headerBigBlock,
OFFSET_BIGBLOCKSIZEBITS,
&This->bigBlockSizeBits);
StorageUtl_ReadWord(
headerBigBlock,
OFFSET_SMALLBLOCKSIZEBITS,
&This->smallBlockSizeBits);
StorageUtl_ReadDWord(
headerBigBlock,
OFFSET_BBDEPOTCOUNT,
&This->bigBlockDepotCount);
StorageUtl_ReadDWord(
headerBigBlock,
OFFSET_ROOTSTARTBLOCK,
&This->rootStartBlock);
StorageUtl_ReadDWord(
headerBigBlock,
OFFSET_TRANSACTIONSIG,
&This->transactionSig);
StorageUtl_ReadDWord(
headerBigBlock,
OFFSET_SMALLBLOCKLIMIT,
&This->smallBlockLimit);
StorageUtl_ReadDWord(
headerBigBlock,
OFFSET_SBDEPOTSTART,
&This->smallBlockDepotStart);
StorageUtl_ReadDWord(
headerBigBlock,
OFFSET_EXTBBDEPOTSTART,
&This->extBigBlockDepotStart);
StorageUtl_ReadDWord(
headerBigBlock,
OFFSET_EXTBBDEPOTCOUNT,
&This->extBigBlockDepotCount);
for (index = 0; index < COUNT_BBDEPOTINHEADER; index ++)
{
StorageUtl_ReadDWord(
headerBigBlock,
OFFSET_BBDEPOTSTART + (sizeof(ULONG)*index),
&(This->bigBlockDepotStart[index]));
}
/*
* Make the bitwise arithmetic to get the size of the blocks in bytes.
*/
This->bigBlockSize = 0x000000001 << (DWORD)This->bigBlockSizeBits;
This->smallBlockSize = 0x000000001 << (DWORD)This->smallBlockSizeBits;
/*
* Right now, the code is making some assumptions about the size of the
* blocks, just make sure they are what we're expecting.
*/
if ((This->bigBlockSize != MIN_BIG_BLOCK_SIZE && This->bigBlockSize != MAX_BIG_BLOCK_SIZE) ||
This->smallBlockSize != DEF_SMALL_BLOCK_SIZE ||
This->smallBlockLimit != LIMIT_TO_USE_SMALL_BLOCK)
{
FIXME("Broken OLE storage file? bigblock=0x%x, smallblock=0x%x, sblimit=0x%x\n",
This->bigBlockSize, This->smallBlockSize, This->smallBlockLimit);
hr = STG_E_INVALIDHEADER;
}
else
hr = S_OK;
}
return hr;
}
/******************************************************************************
* StorageImpl_SaveFileHeader
*
* This method will save to the file the header
*/
static void StorageImpl_SaveFileHeader(
StorageImpl* This)
{
BYTE headerBigBlock[HEADER_SIZE];
int index;
HRESULT hr;
ULARGE_INTEGER offset;
DWORD bytes_read, bytes_written;
DWORD major_version, dirsectorcount;
/*
* Get a pointer to the big block of data containing the header.
*/
offset.u.HighPart = 0;
offset.u.LowPart = 0;
hr = StorageImpl_ReadAt(This, offset, headerBigBlock, HEADER_SIZE, &bytes_read);
if (SUCCEEDED(hr) && bytes_read != HEADER_SIZE)
hr = STG_E_FILENOTFOUND;
if (This->bigBlockSizeBits == 0x9)
major_version = 3;
else if (This->bigBlockSizeBits == 0xc)
major_version = 4;
else
{
ERR("invalid big block shift 0x%x\n", This->bigBlockSizeBits);
major_version = 4;
}
/*
* If the block read failed, the file is probably new.
*/
if (FAILED(hr))
{
/*
* Initialize for all unknown fields.
*/
memset(headerBigBlock, 0, HEADER_SIZE);
/*
* Initialize the magic number.
*/
memcpy(headerBigBlock, STORAGE_magic, sizeof(STORAGE_magic));
}
/*
* Write the information to the header.
*/
StorageUtl_WriteWord(
headerBigBlock,
OFFSET_MINORVERSION,
0x3e);
StorageUtl_WriteWord(
headerBigBlock,
OFFSET_MAJORVERSION,
major_version);
StorageUtl_WriteWord(
headerBigBlock,
OFFSET_BYTEORDERMARKER,
(WORD)-2);
StorageUtl_WriteWord(
headerBigBlock,
OFFSET_BIGBLOCKSIZEBITS,
This->bigBlockSizeBits);
StorageUtl_WriteWord(
headerBigBlock,
OFFSET_SMALLBLOCKSIZEBITS,
This->smallBlockSizeBits);
if (major_version >= 4)
{
if (This->rootBlockChain)
dirsectorcount = BlockChainStream_GetCount(This->rootBlockChain);
else
/* This file is being created, and it will start out with one block. */
dirsectorcount = 1;
}
else
/* This field must be 0 in versions older than 4 */
dirsectorcount = 0;
StorageUtl_WriteDWord(
headerBigBlock,
OFFSET_DIRSECTORCOUNT,
dirsectorcount);
StorageUtl_WriteDWord(
headerBigBlock,
OFFSET_BBDEPOTCOUNT,
This->bigBlockDepotCount);
StorageUtl_WriteDWord(
headerBigBlock,
OFFSET_ROOTSTARTBLOCK,
This->rootStartBlock);
StorageUtl_WriteDWord(
headerBigBlock,
OFFSET_TRANSACTIONSIG,
This->transactionSig);
StorageUtl_WriteDWord(
headerBigBlock,
OFFSET_SMALLBLOCKLIMIT,
This->smallBlockLimit);
StorageUtl_WriteDWord(
headerBigBlock,
OFFSET_SBDEPOTSTART,
This->smallBlockDepotStart);
StorageUtl_WriteDWord(
headerBigBlock,
OFFSET_SBDEPOTCOUNT,
This->smallBlockDepotChain ?
BlockChainStream_GetCount(This->smallBlockDepotChain) : 0);
StorageUtl_WriteDWord(
headerBigBlock,
OFFSET_EXTBBDEPOTSTART,
This->extBigBlockDepotStart);
StorageUtl_WriteDWord(
headerBigBlock,
OFFSET_EXTBBDEPOTCOUNT,
This->extBigBlockDepotCount);
for (index = 0; index < COUNT_BBDEPOTINHEADER; index ++)
{
StorageUtl_WriteDWord(
headerBigBlock,
OFFSET_BBDEPOTSTART + (sizeof(ULONG)*index),
(This->bigBlockDepotStart[index]));
}
/*
* Write the big block back to the file.
*/
StorageImpl_WriteAt(This, offset, headerBigBlock, HEADER_SIZE, &bytes_written);
}
/************************************************************************
* StorageImpl implementation : DirEntry methods
***********************************************************************/
/******************************************************************************
* StorageImpl_ReadRawDirEntry
*
* This method will read the raw data from a directory entry in the file.
*
* buffer must be RAW_DIRENTRY_SIZE bytes long.
*/
static HRESULT StorageImpl_ReadRawDirEntry(StorageImpl *This, ULONG index, BYTE *buffer)
{
ULARGE_INTEGER offset;
HRESULT hr;
ULONG bytesRead;
offset.QuadPart = (ULONGLONG)index * RAW_DIRENTRY_SIZE;
hr = BlockChainStream_ReadAt(
This->rootBlockChain,
offset,
RAW_DIRENTRY_SIZE,
buffer,
&bytesRead);
if (bytesRead != RAW_DIRENTRY_SIZE)
return STG_E_READFAULT;
return hr;
}
/******************************************************************************
* StorageImpl_WriteRawDirEntry
*
* This method will write the raw data from a directory entry in the file.
*
* buffer must be RAW_DIRENTRY_SIZE bytes long.
*/
static HRESULT StorageImpl_WriteRawDirEntry(StorageImpl *This, ULONG index, const BYTE *buffer)
{
ULARGE_INTEGER offset;
ULONG bytesRead;
offset.QuadPart = (ULONGLONG)index * RAW_DIRENTRY_SIZE;
return BlockChainStream_WriteAt(
This->rootBlockChain,
offset,
RAW_DIRENTRY_SIZE,
buffer,
&bytesRead);
}
/***************************************************************************
*
* Internal Method
*
* Mark a directory entry in the file as free.
*/
static HRESULT StorageImpl_DestroyDirEntry(
StorageBaseImpl *base,
DirRef index)
{
BYTE emptyData[RAW_DIRENTRY_SIZE];
StorageImpl *storage = (StorageImpl*)base;
memset(emptyData, 0, RAW_DIRENTRY_SIZE);
return StorageImpl_WriteRawDirEntry(storage, index, emptyData);
}
/******************************************************************************
* UpdateRawDirEntry
*
* Update raw directory entry data from the fields in newData.
*
* buffer must be RAW_DIRENTRY_SIZE bytes long.
*/
static void UpdateRawDirEntry(BYTE *buffer, const DirEntry *newData)
{
memset(buffer, 0, RAW_DIRENTRY_SIZE);
memcpy(
buffer + OFFSET_PS_NAME,
newData->name,
DIRENTRY_NAME_BUFFER_LEN );
memcpy(buffer + OFFSET_PS_STGTYPE, &newData->stgType, 1);
StorageUtl_WriteWord(
buffer,
OFFSET_PS_NAMELENGTH,
newData->sizeOfNameString);
StorageUtl_WriteDWord(
buffer,
OFFSET_PS_LEFTCHILD,
newData->leftChild);
StorageUtl_WriteDWord(
buffer,
OFFSET_PS_RIGHTCHILD,
newData->rightChild);
StorageUtl_WriteDWord(
buffer,
OFFSET_PS_DIRROOT,
newData->dirRootEntry);
StorageUtl_WriteGUID(
buffer,
OFFSET_PS_GUID,
&newData->clsid);
StorageUtl_WriteDWord(
buffer,
OFFSET_PS_CTIMELOW,
newData->ctime.dwLowDateTime);
StorageUtl_WriteDWord(
buffer,
OFFSET_PS_CTIMEHIGH,
newData->ctime.dwHighDateTime);
StorageUtl_WriteDWord(
buffer,
OFFSET_PS_MTIMELOW,
newData->mtime.dwLowDateTime);
StorageUtl_WriteDWord(
buffer,
OFFSET_PS_MTIMEHIGH,
newData->ctime.dwHighDateTime);
StorageUtl_WriteDWord(
buffer,
OFFSET_PS_STARTBLOCK,
newData->startingBlock);
StorageUtl_WriteDWord(
buffer,
OFFSET_PS_SIZE,
newData->size.u.LowPart);
StorageUtl_WriteDWord(
buffer,
OFFSET_PS_SIZE_HIGH,
newData->size.u.HighPart);
}
/***************************************************************************
*
* Internal Method
*
* Reserve a directory entry in the file and initialize it.
*/
static HRESULT StorageImpl_CreateDirEntry(
StorageBaseImpl *base,
const DirEntry *newData,
DirRef *index)
{
StorageImpl *storage = (StorageImpl*)base;
ULONG currentEntryIndex = 0;
ULONG newEntryIndex = DIRENTRY_NULL;
HRESULT hr = S_OK;
BYTE currentData[RAW_DIRENTRY_SIZE];
WORD sizeOfNameString;
do
{
hr = StorageImpl_ReadRawDirEntry(storage,
currentEntryIndex,
currentData);
if (SUCCEEDED(hr))
{
StorageUtl_ReadWord(
currentData,
OFFSET_PS_NAMELENGTH,
&sizeOfNameString);
if (sizeOfNameString == 0)
{
/*
* The entry exists and is available, we found it.
*/
newEntryIndex = currentEntryIndex;
}
}
else
{
/*
* We exhausted the directory entries, we will create more space below
*/
newEntryIndex = currentEntryIndex;
}
currentEntryIndex++;
} while (newEntryIndex == DIRENTRY_NULL);
/*
* grow the directory stream
*/
if (FAILED(hr))
{
BYTE emptyData[RAW_DIRENTRY_SIZE];
ULARGE_INTEGER newSize;
ULONG entryIndex;
ULONG lastEntry = 0;
ULONG blockCount = 0;
/*
* obtain the new count of blocks in the directory stream
*/
blockCount = BlockChainStream_GetCount(
storage->rootBlockChain)+1;
/*
* initialize the size used by the directory stream
*/
newSize.QuadPart = (ULONGLONG)storage->bigBlockSize * blockCount;
/*
* add a block to the directory stream
*/
BlockChainStream_SetSize(storage->rootBlockChain, newSize);
/*
* memset the empty entry in order to initialize the unused newly
* created entries
*/
memset(emptyData, 0, RAW_DIRENTRY_SIZE);
/*
* initialize them
*/
lastEntry = storage->bigBlockSize / RAW_DIRENTRY_SIZE * blockCount;
for(
entryIndex = newEntryIndex + 1;
entryIndex < lastEntry;
entryIndex++)
{
StorageImpl_WriteRawDirEntry(
storage,
entryIndex,
emptyData);
}
StorageImpl_SaveFileHeader(storage);
}
UpdateRawDirEntry(currentData, newData);
hr = StorageImpl_WriteRawDirEntry(storage, newEntryIndex, currentData);
if (SUCCEEDED(hr))
*index = newEntryIndex;
return hr;
}
/******************************************************************************
* StorageImpl_ReadDirEntry
*
* This method will read the specified directory entry.
*/
static HRESULT StorageImpl_ReadDirEntry(
StorageImpl* This,
DirRef index,
DirEntry* buffer)
{
BYTE currentEntry[RAW_DIRENTRY_SIZE];
HRESULT readRes;
readRes = StorageImpl_ReadRawDirEntry(This, index, currentEntry);
if (SUCCEEDED(readRes))
{
memset(buffer->name, 0, sizeof(buffer->name));
memcpy(
buffer->name,
(WCHAR *)currentEntry+OFFSET_PS_NAME,
DIRENTRY_NAME_BUFFER_LEN );
TRACE("storage name: %s\n", debugstr_w(buffer->name));
memcpy(&buffer->stgType, currentEntry + OFFSET_PS_STGTYPE, 1);
StorageUtl_ReadWord(
currentEntry,
OFFSET_PS_NAMELENGTH,
&buffer->sizeOfNameString);
StorageUtl_ReadDWord(
currentEntry,
OFFSET_PS_LEFTCHILD,
&buffer->leftChild);
StorageUtl_ReadDWord(
currentEntry,
OFFSET_PS_RIGHTCHILD,
&buffer->rightChild);
StorageUtl_ReadDWord(
currentEntry,
OFFSET_PS_DIRROOT,
&buffer->dirRootEntry);
StorageUtl_ReadGUID(
currentEntry,
OFFSET_PS_GUID,
&buffer->clsid);
StorageUtl_ReadDWord(
currentEntry,
OFFSET_PS_CTIMELOW,
&buffer->ctime.dwLowDateTime);
StorageUtl_ReadDWord(
currentEntry,
OFFSET_PS_CTIMEHIGH,
&buffer->ctime.dwHighDateTime);
StorageUtl_ReadDWord(
currentEntry,
OFFSET_PS_MTIMELOW,
&buffer->mtime.dwLowDateTime);
StorageUtl_ReadDWord(
currentEntry,
OFFSET_PS_MTIMEHIGH,
&buffer->mtime.dwHighDateTime);
StorageUtl_ReadDWord(
currentEntry,
OFFSET_PS_STARTBLOCK,
&buffer->startingBlock);
StorageUtl_ReadDWord(
currentEntry,
OFFSET_PS_SIZE,
&buffer->size.u.LowPart);
if (This->bigBlockSize < 4096)
{
/* Version 3 files may have junk in the high part of size. */
buffer->size.u.HighPart = 0;
}
else
{
StorageUtl_ReadDWord(
currentEntry,
OFFSET_PS_SIZE_HIGH,
&buffer->size.u.HighPart);
}
}
return readRes;
}
/*********************************************************************
* Write the specified directory entry to the file
*/
static HRESULT StorageImpl_WriteDirEntry(
StorageImpl* This,
DirRef index,
const DirEntry* buffer)
{
BYTE currentEntry[RAW_DIRENTRY_SIZE];
UpdateRawDirEntry(currentEntry, buffer);
return StorageImpl_WriteRawDirEntry(This, index, currentEntry);
}
/************************************************************************
* StorageImpl implementation : Block methods
***********************************************************************/
static ULONGLONG StorageImpl_GetBigBlockOffset(StorageImpl* This, ULONG index)
{
return (ULONGLONG)(index+1) * This->bigBlockSize;
}
static HRESULT StorageImpl_ReadBigBlock(
StorageImpl* This,
ULONG blockIndex,
void* buffer,
ULONG* out_read)
{
ULARGE_INTEGER ulOffset;
DWORD read=0;
HRESULT hr;
ulOffset.QuadPart = StorageImpl_GetBigBlockOffset(This, blockIndex);
hr = StorageImpl_ReadAt(This, ulOffset, buffer, This->bigBlockSize, &read);
if (SUCCEEDED(hr) && read < This->bigBlockSize)
{
/* File ends during this block; fill the rest with 0's. */
memset((LPBYTE)buffer+read, 0, This->bigBlockSize-read);
}
if (out_read) *out_read = read;
return hr;
}
static BOOL StorageImpl_ReadDWordFromBigBlock(
StorageImpl* This,
ULONG blockIndex,
ULONG offset,
DWORD* value)
{
ULARGE_INTEGER ulOffset;
DWORD read;
DWORD tmp;
ulOffset.QuadPart = StorageImpl_GetBigBlockOffset(This, blockIndex);
ulOffset.QuadPart += offset;
StorageImpl_ReadAt(This, ulOffset, &tmp, sizeof(DWORD), &read);
*value = lendian32toh(tmp);
return (read == sizeof(DWORD));
}
static BOOL StorageImpl_WriteBigBlock(
StorageImpl* This,
ULONG blockIndex,
const void* buffer)
{
ULARGE_INTEGER ulOffset;
DWORD wrote;
ulOffset.QuadPart = StorageImpl_GetBigBlockOffset(This, blockIndex);
StorageImpl_WriteAt(This, ulOffset, buffer, This->bigBlockSize, &wrote);
return (wrote == This->bigBlockSize);
}
static BOOL StorageImpl_WriteDWordToBigBlock(
StorageImpl* This,
ULONG blockIndex,
ULONG offset,
DWORD value)
{
ULARGE_INTEGER ulOffset;
DWORD wrote;
ulOffset.QuadPart = StorageImpl_GetBigBlockOffset(This, blockIndex);
ulOffset.QuadPart += offset;
value = htole32(value);
StorageImpl_WriteAt(This, ulOffset, &value, sizeof(DWORD), &wrote);
return (wrote == sizeof(DWORD));
}
/******************************************************************************
* Storage32Impl_SmallBlocksToBigBlocks
*
* This method will convert a small block chain to a big block chain.
* The small block chain will be destroyed.
*/
static BlockChainStream* Storage32Impl_SmallBlocksToBigBlocks(
StorageImpl* This,
SmallBlockChainStream** ppsbChain)
{
ULONG bbHeadOfChain = BLOCK_END_OF_CHAIN;
ULARGE_INTEGER size, offset;
ULONG cbRead, cbWritten;
ULARGE_INTEGER cbTotalRead;
DirRef streamEntryRef;
HRESULT resWrite = S_OK;
HRESULT resRead;
DirEntry streamEntry;
BYTE *buffer;
BlockChainStream *bbTempChain = NULL;
BlockChainStream *bigBlockChain = NULL;
/*
* Create a temporary big block chain that doesn't have
* an associated directory entry. This temporary chain will be
* used to copy data from small blocks to big blocks.
*/
bbTempChain = BlockChainStream_Construct(This,
&bbHeadOfChain,
DIRENTRY_NULL);
if(!bbTempChain) return NULL;
/*
* Grow the big block chain.
*/
size = SmallBlockChainStream_GetSize(*ppsbChain);
BlockChainStream_SetSize(bbTempChain, size);
/*
* Copy the contents of the small block chain to the big block chain
* by small block size increments.
*/
offset.u.LowPart = 0;
offset.u.HighPart = 0;
cbTotalRead.QuadPart = 0;
buffer = HeapAlloc(GetProcessHeap(),0,DEF_SMALL_BLOCK_SIZE);
do
{
resRead = SmallBlockChainStream_ReadAt(*ppsbChain,
offset,
min(This->smallBlockSize, size.u.LowPart - offset.u.LowPart),
buffer,
&cbRead);
if (FAILED(resRead))
break;
if (cbRead > 0)
{
cbTotalRead.QuadPart += cbRead;
resWrite = BlockChainStream_WriteAt(bbTempChain,
offset,
cbRead,
buffer,
&cbWritten);
if (FAILED(resWrite))
break;
offset.u.LowPart += cbRead;
}
else
{
resRead = STG_E_READFAULT;
break;
}
} while (cbTotalRead.QuadPart < size.QuadPart);
HeapFree(GetProcessHeap(),0,buffer);
size.u.HighPart = 0;
size.u.LowPart = 0;
if (FAILED(resRead) || FAILED(resWrite))
{
ERR("conversion failed: resRead = 0x%08x, resWrite = 0x%08x\n", resRead, resWrite);
BlockChainStream_SetSize(bbTempChain, size);
BlockChainStream_Destroy(bbTempChain);
return NULL;
}
/*
* Destroy the small block chain.
*/
streamEntryRef = (*ppsbChain)->ownerDirEntry;
SmallBlockChainStream_SetSize(*ppsbChain, size);
SmallBlockChainStream_Destroy(*ppsbChain);
*ppsbChain = 0;
/*
* Change the directory entry. This chain is now a big block chain
* and it doesn't reside in the small blocks chain anymore.
*/
StorageImpl_ReadDirEntry(This, streamEntryRef, &streamEntry);
streamEntry.startingBlock = bbHeadOfChain;
StorageImpl_WriteDirEntry(This, streamEntryRef, &streamEntry);
/*
* Destroy the temporary entryless big block chain.
* Create a new big block chain associated with this entry.
*/
BlockChainStream_Destroy(bbTempChain);
bigBlockChain = BlockChainStream_Construct(This,
NULL,
streamEntryRef);
return bigBlockChain;
}
/******************************************************************************
* Storage32Impl_BigBlocksToSmallBlocks
*
* This method will convert a big block chain to a small block chain.
* The big block chain will be destroyed on success.
*/
static SmallBlockChainStream* Storage32Impl_BigBlocksToSmallBlocks(
StorageImpl* This,
BlockChainStream** ppbbChain,
ULARGE_INTEGER newSize)
{
ULARGE_INTEGER size, offset, cbTotalRead;
ULONG cbRead, cbWritten, sbHeadOfChain = BLOCK_END_OF_CHAIN;
DirRef streamEntryRef;
HRESULT resWrite = S_OK, resRead = S_OK;
DirEntry streamEntry;
BYTE* buffer;
SmallBlockChainStream* sbTempChain;
TRACE("%p %p\n", This, ppbbChain);
sbTempChain = SmallBlockChainStream_Construct(This, &sbHeadOfChain,
DIRENTRY_NULL);
if(!sbTempChain)
return NULL;
SmallBlockChainStream_SetSize(sbTempChain, newSize);
size = BlockChainStream_GetSize(*ppbbChain);
size.QuadPart = min(size.QuadPart, newSize.QuadPart);
offset.u.HighPart = 0;
offset.u.LowPart = 0;
cbTotalRead.QuadPart = 0;
buffer = HeapAlloc(GetProcessHeap(), 0, This->bigBlockSize);
while(cbTotalRead.QuadPart < size.QuadPart)
{
resRead = BlockChainStream_ReadAt(*ppbbChain, offset,
min(This->bigBlockSize, size.u.LowPart - offset.u.LowPart),
buffer, &cbRead);
if(FAILED(resRead))
break;
if(cbRead > 0)
{
cbTotalRead.QuadPart += cbRead;
resWrite = SmallBlockChainStream_WriteAt(sbTempChain, offset,
cbRead, buffer, &cbWritten);
if(FAILED(resWrite))
break;
offset.u.LowPart += cbRead;
}
else
{
resRead = STG_E_READFAULT;
break;
}
}
HeapFree(GetProcessHeap(), 0, buffer);
size.u.HighPart = 0;
size.u.LowPart = 0;
if(FAILED(resRead) || FAILED(resWrite))
{
ERR("conversion failed: resRead = 0x%08x, resWrite = 0x%08x\n", resRead, resWrite);
SmallBlockChainStream_SetSize(sbTempChain, size);
SmallBlockChainStream_Destroy(sbTempChain);
return NULL;
}
/* destroy the original big block chain */
streamEntryRef = (*ppbbChain)->ownerDirEntry;
BlockChainStream_SetSize(*ppbbChain, size);
BlockChainStream_Destroy(*ppbbChain);
*ppbbChain = NULL;
StorageImpl_ReadDirEntry(This, streamEntryRef, &streamEntry);
streamEntry.startingBlock = sbHeadOfChain;
StorageImpl_WriteDirEntry(This, streamEntryRef, &streamEntry);
SmallBlockChainStream_Destroy(sbTempChain);
return SmallBlockChainStream_Construct(This, NULL, streamEntryRef);
}
/******************************************************************************
* Storage32Impl_AddBlockDepot
*
* This will create a depot block, essentially it is a block initialized
* to BLOCK_UNUSEDs.
*/
static void Storage32Impl_AddBlockDepot(StorageImpl* This, ULONG blockIndex, ULONG depotIndex)
{
BYTE blockBuffer[MAX_BIG_BLOCK_SIZE];
ULONG rangeLockIndex = RANGELOCK_FIRST / This->bigBlockSize - 1;
ULONG blocksPerDepot = This->bigBlockSize / sizeof(ULONG);
ULONG rangeLockDepot = rangeLockIndex / blocksPerDepot;
/*
* Initialize blocks as free
*/
memset(blockBuffer, BLOCK_UNUSED, This->bigBlockSize);
/* Reserve the range lock sector */
if (depotIndex == rangeLockDepot)
{
((ULONG*)blockBuffer)[rangeLockIndex % blocksPerDepot] = BLOCK_END_OF_CHAIN;
}
StorageImpl_WriteBigBlock(This, blockIndex, blockBuffer);
}
/******************************************************************************
* Storage32Impl_GetExtDepotBlock
*
* Returns the index of the block that corresponds to the specified depot
* index. This method is only for depot indexes equal or greater than
* COUNT_BBDEPOTINHEADER.
*/
static ULONG Storage32Impl_GetExtDepotBlock(StorageImpl* This, ULONG depotIndex)
{
ULONG depotBlocksPerExtBlock = (This->bigBlockSize / sizeof(ULONG)) - 1;
ULONG numExtBlocks = depotIndex - COUNT_BBDEPOTINHEADER;
ULONG extBlockCount = numExtBlocks / depotBlocksPerExtBlock;
ULONG extBlockOffset = numExtBlocks % depotBlocksPerExtBlock;
ULONG blockIndex = BLOCK_UNUSED;
ULONG extBlockIndex;
BYTE depotBuffer[MAX_BIG_BLOCK_SIZE];
int index, num_blocks;
assert(depotIndex >= COUNT_BBDEPOTINHEADER);
if (extBlockCount >= This->extBigBlockDepotCount)
return BLOCK_UNUSED;
if (This->indexExtBlockDepotCached != extBlockCount)
{
extBlockIndex = This->extBigBlockDepotLocations[extBlockCount];
StorageImpl_ReadBigBlock(This, extBlockIndex, depotBuffer, NULL);
num_blocks = This->bigBlockSize / 4;
for (index = 0; index < num_blocks; index++)
{
StorageUtl_ReadDWord(depotBuffer, index*sizeof(ULONG), &blockIndex);
This->extBlockDepotCached[index] = blockIndex;
}
This->indexExtBlockDepotCached = extBlockCount;
}
blockIndex = This->extBlockDepotCached[extBlockOffset];
return blockIndex;
}
/******************************************************************************
* Storage32Impl_SetExtDepotBlock
*
* Associates the specified block index to the specified depot index.
* This method is only for depot indexes equal or greater than
* COUNT_BBDEPOTINHEADER.
*/
static void Storage32Impl_SetExtDepotBlock(StorageImpl* This, ULONG depotIndex, ULONG blockIndex)
{
ULONG depotBlocksPerExtBlock = (This->bigBlockSize / sizeof(ULONG)) - 1;
ULONG numExtBlocks = depotIndex - COUNT_BBDEPOTINHEADER;
ULONG extBlockCount = numExtBlocks / depotBlocksPerExtBlock;
ULONG extBlockOffset = numExtBlocks % depotBlocksPerExtBlock;
ULONG extBlockIndex;
assert(depotIndex >= COUNT_BBDEPOTINHEADER);
assert(extBlockCount < This->extBigBlockDepotCount);
extBlockIndex = This->extBigBlockDepotLocations[extBlockCount];
if (extBlockIndex != BLOCK_UNUSED)
{
StorageImpl_WriteDWordToBigBlock(This, extBlockIndex,
extBlockOffset * sizeof(ULONG),
blockIndex);
}
if (This->indexExtBlockDepotCached == extBlockCount)
{
This->extBlockDepotCached[extBlockOffset] = blockIndex;
}
}
/******************************************************************************
* Storage32Impl_AddExtBlockDepot
*
* Creates an extended depot block.
*/
static ULONG Storage32Impl_AddExtBlockDepot(StorageImpl* This)
{
ULONG numExtBlocks = This->extBigBlockDepotCount;
ULONG nextExtBlock = This->extBigBlockDepotStart;
BYTE depotBuffer[MAX_BIG_BLOCK_SIZE];
ULONG index = BLOCK_UNUSED;
ULONG nextBlockOffset = This->bigBlockSize - sizeof(ULONG);
ULONG blocksPerDepotBlock = This->bigBlockSize / sizeof(ULONG);
ULONG depotBlocksPerExtBlock = blocksPerDepotBlock - 1;
index = (COUNT_BBDEPOTINHEADER + (numExtBlocks * depotBlocksPerExtBlock)) *
blocksPerDepotBlock;
if ((numExtBlocks == 0) && (nextExtBlock == BLOCK_END_OF_CHAIN))
{
/*
* The first extended block.
*/
This->extBigBlockDepotStart = index;
}
else
{
/*
* Find the last existing extended block.
*/
nextExtBlock = This->extBigBlockDepotLocations[This->extBigBlockDepotCount-1];
/*
* Add the new extended block to the chain.
*/
StorageImpl_WriteDWordToBigBlock(This, nextExtBlock, nextBlockOffset,
index);
}
/*
* Initialize this block.
*/
memset(depotBuffer, BLOCK_UNUSED, This->bigBlockSize);
StorageImpl_WriteBigBlock(This, index, depotBuffer);
/* Add the block to our cache. */
if (This->extBigBlockDepotLocationsSize == numExtBlocks)
{
ULONG new_cache_size = (This->extBigBlockDepotLocationsSize+1)*2;
ULONG *new_cache = HeapAlloc(GetProcessHeap(), 0, sizeof(ULONG) * new_cache_size);
memcpy(new_cache, This->extBigBlockDepotLocations, sizeof(ULONG) * This->extBigBlockDepotLocationsSize);
HeapFree(GetProcessHeap(), 0, This->extBigBlockDepotLocations);
This->extBigBlockDepotLocations = new_cache;
This->extBigBlockDepotLocationsSize = new_cache_size;
}
This->extBigBlockDepotLocations[numExtBlocks] = index;
return index;
}
/************************************************************************
* StorageImpl_GetNextBlockInChain
*
* This method will retrieve the block index of the next big block in
* in the chain.
*
* Params: This - Pointer to the Storage object.
* blockIndex - Index of the block to retrieve the chain
* for.
* nextBlockIndex - receives the return value.
*
* Returns: This method returns the index of the next block in the chain.
* It will return the constants:
* BLOCK_SPECIAL - If the block given was not part of a
* chain.
* BLOCK_END_OF_CHAIN - If the block given was the last in
* a chain.
* BLOCK_UNUSED - If the block given was not past of a chain
* and is available.
* BLOCK_EXTBBDEPOT - This block is part of the extended
* big block depot.
*
* See Windows documentation for more details on IStorage methods.
*/
static HRESULT StorageImpl_GetNextBlockInChain(
StorageImpl* This,
ULONG blockIndex,
ULONG* nextBlockIndex)
{
ULONG offsetInDepot = blockIndex * sizeof (ULONG);
ULONG depotBlockCount = offsetInDepot / This->bigBlockSize;
ULONG depotBlockOffset = offsetInDepot % This->bigBlockSize;
BYTE depotBuffer[MAX_BIG_BLOCK_SIZE];
ULONG read;
ULONG depotBlockIndexPos;
int index, num_blocks;
*nextBlockIndex = BLOCK_SPECIAL;
if(depotBlockCount >= This->bigBlockDepotCount)
{
WARN("depotBlockCount %d, bigBlockDepotCount %d\n", depotBlockCount,
This->bigBlockDepotCount);
return STG_E_READFAULT;
}
/*
* Cache the currently accessed depot block.
*/
if (depotBlockCount != This->indexBlockDepotCached)
{
This->indexBlockDepotCached = depotBlockCount;
if (depotBlockCount < COUNT_BBDEPOTINHEADER)
{
depotBlockIndexPos = This->bigBlockDepotStart[depotBlockCount];
}
else
{
/*
* We have to look in the extended depot.
*/
depotBlockIndexPos = Storage32Impl_GetExtDepotBlock(This, depotBlockCount);
}
StorageImpl_ReadBigBlock(This, depotBlockIndexPos, depotBuffer, &read);
if (!read)
return STG_E_READFAULT;
num_blocks = This->bigBlockSize / 4;
for (index = 0; index < num_blocks; index++)
{
StorageUtl_ReadDWord(depotBuffer, index*sizeof(ULONG), nextBlockIndex);
This->blockDepotCached[index] = *nextBlockIndex;
}
}
*nextBlockIndex = This->blockDepotCached[depotBlockOffset/sizeof(ULONG)];
return S_OK;
}
/******************************************************************************
* Storage32Impl_GetNextExtendedBlock
*
* Given an extended block this method will return the next extended block.
*
* NOTES:
* The last ULONG of an extended block is the block index of the next
* extended block. Extended blocks are marked as BLOCK_EXTBBDEPOT in the
* depot.
*
* Return values:
* - The index of the next extended block
* - BLOCK_UNUSED: there is no next extended block.
* - Any other return values denotes failure.
*/
static ULONG Storage32Impl_GetNextExtendedBlock(StorageImpl* This, ULONG blockIndex)
{
ULONG nextBlockIndex = BLOCK_SPECIAL;
ULONG depotBlockOffset = This->bigBlockSize - sizeof(ULONG);
StorageImpl_ReadDWordFromBigBlock(This, blockIndex, depotBlockOffset,
&nextBlockIndex);
return nextBlockIndex;
}
/******************************************************************************
* StorageImpl_SetNextBlockInChain
*
* This method will write the index of the specified block's next block
* in the big block depot.
*
* For example: to create the chain 3 -> 1 -> 7 -> End of Chain
* do the following
*
* StorageImpl_SetNextBlockInChain(This, 3, 1);
* StorageImpl_SetNextBlockInChain(This, 1, 7);
* StorageImpl_SetNextBlockInChain(This, 7, BLOCK_END_OF_CHAIN);
*
*/
static void StorageImpl_SetNextBlockInChain(
StorageImpl* This,
ULONG blockIndex,
ULONG nextBlock)
{
ULONG offsetInDepot = blockIndex * sizeof (ULONG);
ULONG depotBlockCount = offsetInDepot / This->bigBlockSize;
ULONG depotBlockOffset = offsetInDepot % This->bigBlockSize;
ULONG depotBlockIndexPos;
assert(depotBlockCount < This->bigBlockDepotCount);
assert(blockIndex != nextBlock);
if (blockIndex == (RANGELOCK_FIRST / This->bigBlockSize) - 1)
/* This should never happen (storage file format spec forbids it), but
* older versions of Wine may have generated broken files. We don't want to
* assert and potentially lose data, but we do want to know if this ever
* happens in a newly-created file. */
ERR("Using range lock page\n");
if (depotBlockCount < COUNT_BBDEPOTINHEADER)
{
depotBlockIndexPos = This->bigBlockDepotStart[depotBlockCount];
}
else
{
/*
* We have to look in the extended depot.
*/
depotBlockIndexPos = Storage32Impl_GetExtDepotBlock(This, depotBlockCount);
}
StorageImpl_WriteDWordToBigBlock(This, depotBlockIndexPos, depotBlockOffset,
nextBlock);
/*
* Update the cached block depot, if necessary.
*/
if (depotBlockCount == This->indexBlockDepotCached)
{
This->blockDepotCached[depotBlockOffset/sizeof(ULONG)] = nextBlock;
}
}
/******************************************************************************
* StorageImpl_GetNextFreeBigBlock
*
* Returns the index of the next free big block.
* If the big block depot is filled, this method will enlarge it.
*
*/
static ULONG StorageImpl_GetNextFreeBigBlock(
StorageImpl* This)
{
ULONG depotBlockIndexPos;
BYTE depotBuffer[MAX_BIG_BLOCK_SIZE];
ULONG depotBlockOffset;
ULONG blocksPerDepot = This->bigBlockSize / sizeof(ULONG);
ULONG nextBlockIndex = BLOCK_SPECIAL;
int depotIndex = 0;
ULONG freeBlock = BLOCK_UNUSED;
ULONG read;
ULARGE_INTEGER neededSize;
STATSTG statstg;
depotIndex = This->prevFreeBlock / blocksPerDepot;
depotBlockOffset = (This->prevFreeBlock % blocksPerDepot) * sizeof(ULONG);
/*
* Scan the entire big block depot until we find a block marked free
*/
while (nextBlockIndex != BLOCK_UNUSED)
{
if (depotIndex < COUNT_BBDEPOTINHEADER)
{
depotBlockIndexPos = This->bigBlockDepotStart[depotIndex];
/*
* Grow the primary depot.
*/
if (depotBlockIndexPos == BLOCK_UNUSED)
{
depotBlockIndexPos = depotIndex*blocksPerDepot;
/*
* Add a block depot.
*/
Storage32Impl_AddBlockDepot(This, depotBlockIndexPos, depotIndex);
This->bigBlockDepotCount++;
This->bigBlockDepotStart[depotIndex] = depotBlockIndexPos;
/*
* Flag it as a block depot.
*/
StorageImpl_SetNextBlockInChain(This,
depotBlockIndexPos,
BLOCK_SPECIAL);
/* Save new header information.
*/
StorageImpl_SaveFileHeader(This);
}
}
else
{
depotBlockIndexPos = Storage32Impl_GetExtDepotBlock(This, depotIndex);
if (depotBlockIndexPos == BLOCK_UNUSED)
{
/*
* Grow the extended depot.
*/
ULONG extIndex = BLOCK_UNUSED;
ULONG numExtBlocks = depotIndex - COUNT_BBDEPOTINHEADER;
ULONG extBlockOffset = numExtBlocks % (blocksPerDepot - 1);
if (extBlockOffset == 0)
{
/* We need an extended block.
*/
extIndex = Storage32Impl_AddExtBlockDepot(This);
This->extBigBlockDepotCount++;
depotBlockIndexPos = extIndex + 1;
}
else
depotBlockIndexPos = depotIndex * blocksPerDepot;
/*
* Add a block depot and mark it in the extended block.
*/
Storage32Impl_AddBlockDepot(This, depotBlockIndexPos, depotIndex);
This->bigBlockDepotCount++;
Storage32Impl_SetExtDepotBlock(This, depotIndex, depotBlockIndexPos);
/* Flag the block depot.
*/
StorageImpl_SetNextBlockInChain(This,
depotBlockIndexPos,
BLOCK_SPECIAL);
/* If necessary, flag the extended depot block.
*/
if (extIndex != BLOCK_UNUSED)
StorageImpl_SetNextBlockInChain(This, extIndex, BLOCK_EXTBBDEPOT);
/* Save header information.
*/
StorageImpl_SaveFileHeader(This);
}
}
StorageImpl_ReadBigBlock(This, depotBlockIndexPos, depotBuffer, &read);
if (read)
{
while ( ( (depotBlockOffset/sizeof(ULONG) ) < blocksPerDepot) &&
( nextBlockIndex != BLOCK_UNUSED))
{
StorageUtl_ReadDWord(depotBuffer, depotBlockOffset, &nextBlockIndex);
if (nextBlockIndex == BLOCK_UNUSED)
{
freeBlock = (depotIndex * blocksPerDepot) +
(depotBlockOffset/sizeof(ULONG));
}
depotBlockOffset += sizeof(ULONG);
}
}
depotIndex++;
depotBlockOffset = 0;
}
/*
* make sure that the block physically exists before using it
*/
neededSize.QuadPart = StorageImpl_GetBigBlockOffset(This, freeBlock)+This->bigBlockSize;
ILockBytes_Stat(This->lockBytes, &statstg, STATFLAG_NONAME);
if (neededSize.QuadPart > statstg.cbSize.QuadPart)
ILockBytes_SetSize(This->lockBytes, neededSize);
This->prevFreeBlock = freeBlock;
return freeBlock;
}
/******************************************************************************
* StorageImpl_FreeBigBlock
*
* This method will flag the specified block as free in the big block depot.
*/
static void StorageImpl_FreeBigBlock(
StorageImpl* This,
ULONG blockIndex)
{
StorageImpl_SetNextBlockInChain(This, blockIndex, BLOCK_UNUSED);
if (blockIndex < This->prevFreeBlock)
This->prevFreeBlock = blockIndex;
}
static HRESULT StorageImpl_BaseWriteDirEntry(StorageBaseImpl *base,
DirRef index, const DirEntry *data)
{
StorageImpl *This = (StorageImpl*)base;
return StorageImpl_WriteDirEntry(This, index, data);
}
static HRESULT StorageImpl_BaseReadDirEntry(StorageBaseImpl *base,
DirRef index, DirEntry *data)
{
StorageImpl *This = (StorageImpl*)base;
return StorageImpl_ReadDirEntry(This, index, data);
}
static BlockChainStream **StorageImpl_GetFreeBlockChainCacheEntry(StorageImpl* This)
{
int i;
for (i=0; i<BLOCKCHAIN_CACHE_SIZE; i++)
{
if (!This->blockChainCache[i])
{
return &This->blockChainCache[i];
}
}
i = This->blockChainToEvict;
BlockChainStream_Destroy(This->blockChainCache[i]);
This->blockChainCache[i] = NULL;
This->blockChainToEvict++;
if (This->blockChainToEvict == BLOCKCHAIN_CACHE_SIZE)
This->blockChainToEvict = 0;
return &This->blockChainCache[i];
}
static BlockChainStream **StorageImpl_GetCachedBlockChainStream(StorageImpl *This,
DirRef index)
{
int i, free_index=-1;
for (i=0; i<BLOCKCHAIN_CACHE_SIZE; i++)
{
if (!This->blockChainCache[i])
{
if (free_index == -1) free_index = i;
}
else if (This->blockChainCache[i]->ownerDirEntry == index)
{
return &This->blockChainCache[i];
}
}
if (free_index == -1)
{
free_index = This->blockChainToEvict;
BlockChainStream_Destroy(This->blockChainCache[free_index]);
This->blockChainCache[free_index] = NULL;
This->blockChainToEvict++;
if (This->blockChainToEvict == BLOCKCHAIN_CACHE_SIZE)
This->blockChainToEvict = 0;
}
This->blockChainCache[free_index] = BlockChainStream_Construct(This, NULL, index);
return &This->blockChainCache[free_index];
}
static void StorageImpl_DeleteCachedBlockChainStream(StorageImpl *This, DirRef index)
{
int i;
for (i=0; i<BLOCKCHAIN_CACHE_SIZE; i++)
{
if (This->blockChainCache[i] && This->blockChainCache[i]->ownerDirEntry == index)
{
BlockChainStream_Destroy(This->blockChainCache[i]);
This->blockChainCache[i] = NULL;
return;
}
}
}
static HRESULT StorageImpl_StreamReadAt(StorageBaseImpl *base, DirRef index,
ULARGE_INTEGER offset, ULONG size, void *buffer, ULONG *bytesRead)
{
StorageImpl *This = (StorageImpl*)base;
DirEntry data;
HRESULT hr;
ULONG bytesToRead;
hr = StorageImpl_ReadDirEntry(This, index, &data);
if (FAILED(hr)) return hr;
if (data.size.QuadPart == 0)
{
*bytesRead = 0;
return S_OK;
}
if (offset.QuadPart + size > data.size.QuadPart)
{
bytesToRead = data.size.QuadPart - offset.QuadPart;
}
else
{
bytesToRead = size;
}
if (data.size.QuadPart < LIMIT_TO_USE_SMALL_BLOCK)
{
SmallBlockChainStream *stream;
stream = SmallBlockChainStream_Construct(This, NULL, index);
if (!stream) return E_OUTOFMEMORY;
hr = SmallBlockChainStream_ReadAt(stream, offset, bytesToRead, buffer, bytesRead);
SmallBlockChainStream_Destroy(stream);
return hr;
}
else
{
BlockChainStream *stream = NULL;
stream = *StorageImpl_GetCachedBlockChainStream(This, index);
if (!stream) return E_OUTOFMEMORY;
hr = BlockChainStream_ReadAt(stream, offset, bytesToRead, buffer, bytesRead);
return hr;
}
}
static HRESULT StorageImpl_StreamSetSize(StorageBaseImpl *base, DirRef index,
ULARGE_INTEGER newsize)
{
StorageImpl *This = (StorageImpl*)base;
DirEntry data;
HRESULT hr;
SmallBlockChainStream *smallblock=NULL;
BlockChainStream **pbigblock=NULL, *bigblock=NULL;
hr = StorageImpl_ReadDirEntry(This, index, &data);
if (FAILED(hr)) return hr;
/* In simple mode keep the stream size above the small block limit */
if (This->base.openFlags & STGM_SIMPLE)
newsize.QuadPart = max(newsize.QuadPart, LIMIT_TO_USE_SMALL_BLOCK);
if (data.size.QuadPart == newsize.QuadPart)
return S_OK;
/* Create a block chain object of the appropriate type */
if (data.size.QuadPart == 0)
{
if (newsize.QuadPart < LIMIT_TO_USE_SMALL_BLOCK)
{
smallblock = SmallBlockChainStream_Construct(This, NULL, index);
if (!smallblock) return E_OUTOFMEMORY;
}
else
{
pbigblock = StorageImpl_GetCachedBlockChainStream(This, index);
bigblock = *pbigblock;
if (!bigblock) return E_OUTOFMEMORY;
}
}
else if (data.size.QuadPart < LIMIT_TO_USE_SMALL_BLOCK)
{
smallblock = SmallBlockChainStream_Construct(This, NULL, index);
if (!smallblock) return E_OUTOFMEMORY;
}
else
{
pbigblock = StorageImpl_GetCachedBlockChainStream(This, index);
bigblock = *pbigblock;
if (!bigblock) return E_OUTOFMEMORY;
}
/* Change the block chain type if necessary. */
if (smallblock && newsize.QuadPart >= LIMIT_TO_USE_SMALL_BLOCK)
{
bigblock = Storage32Impl_SmallBlocksToBigBlocks(This, &smallblock);
if (!bigblock)
{
SmallBlockChainStream_Destroy(smallblock);
return E_FAIL;
}
pbigblock = StorageImpl_GetFreeBlockChainCacheEntry(This);
*pbigblock = bigblock;
}
else if (bigblock && newsize.QuadPart < LIMIT_TO_USE_SMALL_BLOCK)
{
smallblock = Storage32Impl_BigBlocksToSmallBlocks(This, pbigblock, newsize);
if (!smallblock)
return E_FAIL;
}
/* Set the size of the block chain. */
if (smallblock)
{
SmallBlockChainStream_SetSize(smallblock, newsize);
SmallBlockChainStream_Destroy(smallblock);
}
else
{
BlockChainStream_SetSize(bigblock, newsize);
}
/* Set the size in the directory entry. */
hr = StorageImpl_ReadDirEntry(This, index, &data);
if (SUCCEEDED(hr))
{
data.size = newsize;
hr = StorageImpl_WriteDirEntry(This, index, &data);
}
return hr;
}
static HRESULT StorageImpl_StreamWriteAt(StorageBaseImpl *base, DirRef index,
ULARGE_INTEGER offset, ULONG size, const void *buffer, ULONG *bytesWritten)
{
StorageImpl *This = (StorageImpl*)base;
DirEntry data;
HRESULT hr;
ULARGE_INTEGER newSize;
hr = StorageImpl_ReadDirEntry(This, index, &data);
if (FAILED(hr)) return hr;
/* Grow the stream if necessary */
newSize.QuadPart = offset.QuadPart + size;
if (newSize.QuadPart > data.size.QuadPart)
{
hr = StorageImpl_StreamSetSize(base, index, newSize);
if (FAILED(hr))
return hr;
hr = StorageImpl_ReadDirEntry(This, index, &data);
if (FAILED(hr)) return hr;
}
if (data.size.QuadPart < LIMIT_TO_USE_SMALL_BLOCK)
{
SmallBlockChainStream *stream;
stream = SmallBlockChainStream_Construct(This, NULL, index);
if (!stream) return E_OUTOFMEMORY;
hr = SmallBlockChainStream_WriteAt(stream, offset, size, buffer, bytesWritten);
SmallBlockChainStream_Destroy(stream);
return hr;
}
else
{
BlockChainStream *stream;
stream = *StorageImpl_GetCachedBlockChainStream(This, index);
if (!stream) return E_OUTOFMEMORY;
return BlockChainStream_WriteAt(stream, offset, size, buffer, bytesWritten);
}
}
static HRESULT StorageImpl_StreamLink(StorageBaseImpl *base, DirRef dst,
DirRef src)
{
StorageImpl *This = (StorageImpl*)base;
DirEntry dst_data, src_data;
HRESULT hr;
hr = StorageImpl_ReadDirEntry(This, dst, &dst_data);
if (SUCCEEDED(hr))
hr = StorageImpl_ReadDirEntry(This, src, &src_data);
if (SUCCEEDED(hr))
{
StorageImpl_DeleteCachedBlockChainStream(This, src);
dst_data.startingBlock = src_data.startingBlock;
dst_data.size = src_data.size;
hr = StorageImpl_WriteDirEntry(This, dst, &dst_data);
}
return hr;
}
static HRESULT StorageImpl_Refresh(StorageImpl *This, BOOL new_object, BOOL create)
{
HRESULT hr=S_OK;
DirEntry currentEntry;
DirRef currentEntryRef;
BlockChainStream *blockChainStream;
if (create)
{
ULARGE_INTEGER size;
BYTE bigBlockBuffer[MAX_BIG_BLOCK_SIZE];
/* Discard any existing data. */
size.QuadPart = 0;
ILockBytes_SetSize(This->lockBytes, size);
/*
* Initialize all header variables:
* - The big block depot consists of one block and it is at block 0
* - The directory table starts at block 1
* - There is no small block depot
*/
memset( This->bigBlockDepotStart,
BLOCK_UNUSED,
sizeof(This->bigBlockDepotStart));
This->bigBlockDepotCount = 1;
This->bigBlockDepotStart[0] = 0;
This->rootStartBlock = 1;
This->smallBlockLimit = LIMIT_TO_USE_SMALL_BLOCK;
This->smallBlockDepotStart = BLOCK_END_OF_CHAIN;
if (This->bigBlockSize == 4096)
This->bigBlockSizeBits = MAX_BIG_BLOCK_SIZE_BITS;
else
This->bigBlockSizeBits = MIN_BIG_BLOCK_SIZE_BITS;
This->smallBlockSizeBits = DEF_SMALL_BLOCK_SIZE_BITS;
This->extBigBlockDepotStart = BLOCK_END_OF_CHAIN;
This->extBigBlockDepotCount = 0;
StorageImpl_SaveFileHeader(This);
/*
* Add one block for the big block depot and one block for the directory table
*/
size.u.HighPart = 0;
size.u.LowPart = This->bigBlockSize * 3;
ILockBytes_SetSize(This->lockBytes, size);
/*
* Initialize the big block depot
*/
memset(bigBlockBuffer, BLOCK_UNUSED, This->bigBlockSize);
StorageUtl_WriteDWord(bigBlockBuffer, 0, BLOCK_SPECIAL);
StorageUtl_WriteDWord(bigBlockBuffer, sizeof(ULONG), BLOCK_END_OF_CHAIN);
StorageImpl_WriteBigBlock(This, 0, bigBlockBuffer);
}
else
{
/*
* Load the header for the file.
*/
hr = StorageImpl_LoadFileHeader(This);
if (FAILED(hr))
{
return hr;
}
}
/*
* There is no block depot cached yet.
*/
This->indexBlockDepotCached = 0xFFFFFFFF;
This->indexExtBlockDepotCached = 0xFFFFFFFF;
/*
* Start searching for free blocks with block 0.
*/
This->prevFreeBlock = 0;
This->firstFreeSmallBlock = 0;
/* Read the extended big block depot locations. */
if (This->extBigBlockDepotCount != 0)
{
ULONG current_block = This->extBigBlockDepotStart;
ULONG cache_size = This->extBigBlockDepotCount * 2;
ULONG i;
This->extBigBlockDepotLocations = HeapAlloc(GetProcessHeap(), 0, sizeof(ULONG) * cache_size);
if (!This->extBigBlockDepotLocations)
{
return E_OUTOFMEMORY;
}
This->extBigBlockDepotLocationsSize = cache_size;
for (i=0; i<This->extBigBlockDepotCount; i++)
{
if (current_block == BLOCK_END_OF_CHAIN)
{
WARN("File has too few extended big block depot blocks.\n");
return STG_E_DOCFILECORRUPT;
}
This->extBigBlockDepotLocations[i] = current_block;
current_block = Storage32Impl_GetNextExtendedBlock(This, current_block);
}
}
else
{
This->extBigBlockDepotLocations = NULL;
This->extBigBlockDepotLocationsSize = 0;
}
/*
* Create the block chain abstractions.
*/
if(!(blockChainStream =
BlockChainStream_Construct(This, &This->rootStartBlock, DIRENTRY_NULL)))
{
return STG_E_READFAULT;
}
if (!new_object)
BlockChainStream_Destroy(This->rootBlockChain);
This->rootBlockChain = blockChainStream;
if(!(blockChainStream =
BlockChainStream_Construct(This, &This->smallBlockDepotStart,
DIRENTRY_NULL)))
{
return STG_E_READFAULT;
}
if (!new_object)
BlockChainStream_Destroy(This->smallBlockDepotChain);
This->smallBlockDepotChain = blockChainStream;
/*
* Write the root storage entry (memory only)
*/
if (create)
{
static const WCHAR rootentryW[] = {'R','o','o','t',' ','E','n','t','r','y',0};
DirEntry rootEntry;
/*
* Initialize the directory table
*/
memset(&rootEntry, 0, sizeof(rootEntry));
lstrcpyW(rootEntry.name, rootentryW);
rootEntry.sizeOfNameString = sizeof(rootentryW);
rootEntry.stgType = STGTY_ROOT;
rootEntry.leftChild = DIRENTRY_NULL;
rootEntry.rightChild = DIRENTRY_NULL;
rootEntry.dirRootEntry = DIRENTRY_NULL;
rootEntry.startingBlock = BLOCK_END_OF_CHAIN;
rootEntry.size.u.HighPart = 0;
rootEntry.size.u.LowPart = 0;
StorageImpl_WriteDirEntry(This, 0, &rootEntry);
}
/*
* Find the ID of the root storage.
*/
currentEntryRef = 0;
do
{
hr = StorageImpl_ReadDirEntry(
This,
currentEntryRef,
&currentEntry);
if (SUCCEEDED(hr))
{
if ( (currentEntry.sizeOfNameString != 0 ) &&
(currentEntry.stgType == STGTY_ROOT) )
{
This->base.storageDirEntry = currentEntryRef;
}
}
currentEntryRef++;
} while (SUCCEEDED(hr) && (This->base.storageDirEntry == DIRENTRY_NULL) );
if (FAILED(hr))
{
return STG_E_READFAULT;
}
/*
* Create the block chain abstraction for the small block root chain.
*/
if(!(blockChainStream =
BlockChainStream_Construct(This, NULL, This->base.storageDirEntry)))
{
return STG_E_READFAULT;
}
if (!new_object)
BlockChainStream_Destroy(This->smallBlockRootChain);
This->smallBlockRootChain = blockChainStream;
if (!new_object)
{
int i;
for (i=0; i<BLOCKCHAIN_CACHE_SIZE; i++)
{
BlockChainStream_Destroy(This->blockChainCache[i]);
This->blockChainCache[i] = NULL;
}
}
return hr;
}
static HRESULT StorageImpl_GetTransactionSig(StorageBaseImpl *base,
ULONG* result, BOOL refresh)
{
StorageImpl *This = (StorageImpl*)base;
HRESULT hr=S_OK;
DWORD oldTransactionSig = This->transactionSig;
if (refresh)
{
ULARGE_INTEGER offset;
ULONG bytes_read;
BYTE data[4];
offset.u.HighPart = 0;
offset.u.LowPart = OFFSET_TRANSACTIONSIG;
hr = StorageImpl_ReadAt(This, offset, data, 4, &bytes_read);
if (SUCCEEDED(hr))
{
StorageUtl_ReadDWord(data, 0, &This->transactionSig);
if (oldTransactionSig != This->transactionSig)
{
/* Someone else wrote to this, so toss all cached information. */
TRACE("signature changed\n");
hr = StorageImpl_Refresh(This, FALSE, FALSE);
}
if (FAILED(hr))
This->transactionSig = oldTransactionSig;
}
}
*result = This->transactionSig;
return hr;
}
static HRESULT StorageImpl_SetTransactionSig(StorageBaseImpl *base,
ULONG value)
{
StorageImpl *This = (StorageImpl*)base;
This->transactionSig = value;
StorageImpl_SaveFileHeader(This);
return S_OK;
}
static HRESULT StorageImpl_LockRegion(StorageImpl *This, ULARGE_INTEGER offset,
ULARGE_INTEGER cb, DWORD dwLockType, BOOL *supported)
{
if ((dwLockType & This->locks_supported) == 0)
{
if (supported) *supported = FALSE;
return S_OK;
}
if (supported) *supported = TRUE;
return ILockBytes_LockRegion(This->lockBytes, offset, cb, dwLockType);
}
static HRESULT StorageImpl_UnlockRegion(StorageImpl *This, ULARGE_INTEGER offset,
ULARGE_INTEGER cb, DWORD dwLockType)
{
if ((dwLockType & This->locks_supported) == 0)
return S_OK;
return ILockBytes_UnlockRegion(This->lockBytes, offset, cb, dwLockType);
}
/* Internal function */
static HRESULT StorageImpl_LockRegionSync(StorageImpl *This, ULARGE_INTEGER offset,
ULARGE_INTEGER cb, DWORD dwLockType, BOOL *supported)
{
HRESULT hr;
int delay = 0;
DWORD start_time = GetTickCount();
DWORD last_sanity_check = start_time;
ULARGE_INTEGER sanity_offset, sanity_cb;
sanity_offset.QuadPart = RANGELOCK_UNK1_FIRST;
sanity_cb.QuadPart = RANGELOCK_UNK1_LAST - RANGELOCK_UNK1_FIRST + 1;
do
{
hr = StorageImpl_LockRegion(This, offset, cb, dwLockType, supported);
if (hr == STG_E_ACCESSDENIED || hr == STG_E_LOCKVIOLATION)
{
DWORD current_time = GetTickCount();
if (current_time - start_time >= 20000)
{
/* timeout */
break;
}
if (current_time - last_sanity_check >= 500)
{
/* Any storage implementation with the file open in a
* shared mode should not lock these bytes for writing. However,
* some programs (LibreOffice Writer) will keep ALL bytes locked
* when opening in exclusive mode. We can use a read lock to
* detect this case early, and not hang a full 20 seconds.
*
* This can collide with another attempt to open the file in
* exclusive mode, but it's unlikely, and someone would fail anyway. */
hr = StorageImpl_LockRegion(This, sanity_offset, sanity_cb, WINE_LOCK_READ, NULL);
if (hr == STG_E_ACCESSDENIED || hr == STG_E_LOCKVIOLATION)
break;
if (SUCCEEDED(hr))
{
StorageImpl_UnlockRegion(This, sanity_offset, sanity_cb, WINE_LOCK_READ);
hr = STG_E_ACCESSDENIED;
}
last_sanity_check = current_time;
}
Sleep(delay);
if (delay < 150) delay++;
}
} while (hr == STG_E_ACCESSDENIED || hr == STG_E_LOCKVIOLATION);
return hr;
}
static HRESULT StorageImpl_LockTransaction(StorageBaseImpl *base, BOOL write)
{
StorageImpl *This = (StorageImpl*)base;
HRESULT hr;
ULARGE_INTEGER offset, cb;
if (write)
{
/* Synchronous grab of second priority range, the commit lock, and the
* lock-checking lock. */
offset.QuadPart = RANGELOCK_TRANSACTION_FIRST;
cb.QuadPart = RANGELOCK_TRANSACTION_LAST - RANGELOCK_TRANSACTION_FIRST + 1;
}
else
{
offset.QuadPart = RANGELOCK_COMMIT;
cb.QuadPart = 1;
}
hr = StorageImpl_LockRegionSync(This, offset, cb, LOCK_ONLYONCE, NULL);
return hr;
}
static HRESULT StorageImpl_UnlockTransaction(StorageBaseImpl *base, BOOL write)
{
StorageImpl *This = (StorageImpl*)base;
HRESULT hr;
ULARGE_INTEGER offset, cb;
if (write)
{
offset.QuadPart = RANGELOCK_TRANSACTION_FIRST;
cb.QuadPart = RANGELOCK_TRANSACTION_LAST - RANGELOCK_TRANSACTION_FIRST + 1;
}
else
{
offset.QuadPart = RANGELOCK_COMMIT;
cb.QuadPart = 1;
}
hr = StorageImpl_UnlockRegion(This, offset, cb, LOCK_ONLYONCE);
return hr;
}
static HRESULT StorageImpl_GetFilename(StorageBaseImpl* iface, LPWSTR *result)
{
StorageImpl *This = (StorageImpl*) iface;
STATSTG statstg;
HRESULT hr;
hr = ILockBytes_Stat(This->lockBytes, &statstg, 0);
*result = statstg.pwcsName;
return hr;
}
static HRESULT StorageImpl_CheckLockRange(StorageImpl *This, ULONG start,
ULONG end, HRESULT fail_hr)
{
HRESULT hr;
ULARGE_INTEGER offset, cb;
offset.QuadPart = start;
cb.QuadPart = 1 + end - start;
hr = StorageImpl_LockRegion(This, offset, cb, LOCK_ONLYONCE, NULL);
if (SUCCEEDED(hr)) StorageImpl_UnlockRegion(This, offset, cb, LOCK_ONLYONCE);
if (FAILED(hr))
return fail_hr;
else
return S_OK;
}
static HRESULT StorageImpl_LockOne(StorageImpl *This, ULONG start, ULONG end)
{
HRESULT hr=S_OK;
int i, j;
ULARGE_INTEGER offset, cb;
cb.QuadPart = 1;
for (i=start; i<=end; i++)
{
offset.QuadPart = i;
hr = StorageImpl_LockRegion(This, offset, cb, LOCK_ONLYONCE, NULL);
if (hr != STG_E_ACCESSDENIED && hr != STG_E_LOCKVIOLATION)
break;
}
if (SUCCEEDED(hr))
{
for (j = 0; j < ARRAY_SIZE(This->locked_bytes); j++)
{
if (This->locked_bytes[j] == 0)
{
This->locked_bytes[j] = i;
break;
}
}
}
return hr;
}
static HRESULT StorageImpl_GrabLocks(StorageImpl *This, DWORD openFlags)
{
HRESULT hr;
ULARGE_INTEGER offset;
ULARGE_INTEGER cb;
DWORD share_mode = STGM_SHARE_MODE(openFlags);
BOOL supported;
if (openFlags & STGM_NOSNAPSHOT)
{
/* STGM_NOSNAPSHOT implies deny write */
if (share_mode == STGM_SHARE_DENY_READ) share_mode = STGM_SHARE_EXCLUSIVE;
else if (share_mode != STGM_SHARE_EXCLUSIVE) share_mode = STGM_SHARE_DENY_WRITE;
}
/* Wrap all other locking inside a single lock so we can check ranges safely */
offset.QuadPart = RANGELOCK_CHECKLOCKS;
cb.QuadPart = 1;
hr = StorageImpl_LockRegionSync(This, offset, cb, LOCK_ONLYONCE, &supported);
/* If the ILockBytes doesn't support locking that's ok. */
if (!supported) return S_OK;
else if (FAILED(hr)) return hr;
hr = S_OK;
/* First check for any conflicting locks. */
if ((openFlags & STGM_PRIORITY) == STGM_PRIORITY)
hr = StorageImpl_CheckLockRange(This, RANGELOCK_COMMIT, RANGELOCK_COMMIT, STG_E_LOCKVIOLATION);
if (SUCCEEDED(hr) && (STGM_ACCESS_MODE(openFlags) != STGM_WRITE))
hr = StorageImpl_CheckLockRange(This, RANGELOCK_DENY_READ_FIRST, RANGELOCK_DENY_READ_LAST, STG_E_SHAREVIOLATION);
if (SUCCEEDED(hr) && (STGM_ACCESS_MODE(openFlags) != STGM_READ))
hr = StorageImpl_CheckLockRange(This, RANGELOCK_DENY_WRITE_FIRST, RANGELOCK_DENY_WRITE_LAST, STG_E_SHAREVIOLATION);
if (SUCCEEDED(hr) && (share_mode == STGM_SHARE_DENY_READ || share_mode == STGM_SHARE_EXCLUSIVE))
hr = StorageImpl_CheckLockRange(This, RANGELOCK_READ_FIRST, RANGELOCK_READ_LAST, STG_E_LOCKVIOLATION);
if (SUCCEEDED(hr) && (share_mode == STGM_SHARE_DENY_WRITE || share_mode == STGM_SHARE_EXCLUSIVE))
hr = StorageImpl_CheckLockRange(This, RANGELOCK_WRITE_FIRST, RANGELOCK_WRITE_LAST, STG_E_LOCKVIOLATION);
if (SUCCEEDED(hr) && STGM_ACCESS_MODE(openFlags) == STGM_READ && share_mode == STGM_SHARE_EXCLUSIVE)
{
hr = StorageImpl_CheckLockRange(This, 0, RANGELOCK_CHECKLOCKS-1, STG_E_LOCKVIOLATION);
if (SUCCEEDED(hr))
hr = StorageImpl_CheckLockRange(This, RANGELOCK_CHECKLOCKS+1, RANGELOCK_LAST, STG_E_LOCKVIOLATION);
}
/* Then grab our locks. */
if (SUCCEEDED(hr) && (openFlags & STGM_PRIORITY) == STGM_PRIORITY)
{
hr = StorageImpl_LockOne(This, RANGELOCK_PRIORITY1_FIRST, RANGELOCK_PRIORITY1_LAST);
if (SUCCEEDED(hr))
hr = StorageImpl_LockOne(This, RANGELOCK_PRIORITY2_FIRST, RANGELOCK_PRIORITY2_LAST);
}
if (SUCCEEDED(hr) && (STGM_ACCESS_MODE(openFlags) != STGM_WRITE))
hr = StorageImpl_LockOne(This, RANGELOCK_READ_FIRST, RANGELOCK_READ_LAST);
if (SUCCEEDED(hr) && (STGM_ACCESS_MODE(openFlags) != STGM_READ))
hr = StorageImpl_LockOne(This, RANGELOCK_WRITE_FIRST, RANGELOCK_WRITE_LAST);
if (SUCCEEDED(hr) && (share_mode == STGM_SHARE_DENY_READ || share_mode == STGM_SHARE_EXCLUSIVE))
hr = StorageImpl_LockOne(This, RANGELOCK_DENY_READ_FIRST, RANGELOCK_DENY_READ_LAST);
if (SUCCEEDED(hr) && (share_mode == STGM_SHARE_DENY_WRITE || share_mode == STGM_SHARE_EXCLUSIVE))
hr = StorageImpl_LockOne(This, RANGELOCK_DENY_WRITE_FIRST, RANGELOCK_DENY_WRITE_LAST);
if (SUCCEEDED(hr) && (openFlags & STGM_NOSNAPSHOT) == STGM_NOSNAPSHOT)
hr = StorageImpl_LockOne(This, RANGELOCK_NOSNAPSHOT_FIRST, RANGELOCK_NOSNAPSHOT_LAST);
offset.QuadPart = RANGELOCK_CHECKLOCKS;
cb.QuadPart = 1;
StorageImpl_UnlockRegion(This, offset, cb, LOCK_ONLYONCE);
return hr;
}
static HRESULT StorageImpl_Flush(StorageBaseImpl *storage)
{
StorageImpl *This = (StorageImpl*)storage;
int i;
HRESULT hr;
TRACE("(%p)\n", This);
hr = BlockChainStream_Flush(This->smallBlockRootChain);
if (SUCCEEDED(hr))
hr = BlockChainStream_Flush(This->rootBlockChain);
if (SUCCEEDED(hr))
hr = BlockChainStream_Flush(This->smallBlockDepotChain);
for (i=0; SUCCEEDED(hr) && i<BLOCKCHAIN_CACHE_SIZE; i++)
if (This->blockChainCache[i])
hr = BlockChainStream_Flush(This->blockChainCache[i]);
if (SUCCEEDED(hr))
hr = ILockBytes_Flush(This->lockBytes);
return hr;
}
static void StorageImpl_Invalidate(StorageBaseImpl* iface)
{
StorageImpl *This = (StorageImpl*) iface;
StorageBaseImpl_DeleteAll(&This->base);
This->base.reverted = TRUE;
}
static void StorageImpl_Destroy(StorageBaseImpl* iface)
{
StorageImpl *This = (StorageImpl*) iface;
int i;
TRACE("(%p)\n", This);
StorageImpl_Flush(iface);
StorageImpl_Invalidate(iface);
HeapFree(GetProcessHeap(), 0, This->extBigBlockDepotLocations);
BlockChainStream_Destroy(This->smallBlockRootChain);
BlockChainStream_Destroy(This->rootBlockChain);
BlockChainStream_Destroy(This->smallBlockDepotChain);
for (i = 0; i < BLOCKCHAIN_CACHE_SIZE; i++)
BlockChainStream_Destroy(This->blockChainCache[i]);
for (i = 0; i < ARRAY_SIZE(This->locked_bytes); i++)
{
ULARGE_INTEGER offset, cb;
cb.QuadPart = 1;
if (This->locked_bytes[i] != 0)
{
offset.QuadPart = This->locked_bytes[i];
StorageImpl_UnlockRegion(This, offset, cb, LOCK_ONLYONCE);
}
}
if (This->lockBytes)
ILockBytes_Release(This->lockBytes);
HeapFree(GetProcessHeap(), 0, This);
}
static const StorageBaseImplVtbl StorageImpl_BaseVtbl =
{
StorageImpl_Destroy,
StorageImpl_Invalidate,
StorageImpl_Flush,
StorageImpl_GetFilename,
StorageImpl_CreateDirEntry,
StorageImpl_BaseWriteDirEntry,
StorageImpl_BaseReadDirEntry,
StorageImpl_DestroyDirEntry,
StorageImpl_StreamReadAt,
StorageImpl_StreamWriteAt,
StorageImpl_StreamSetSize,
StorageImpl_StreamLink,
StorageImpl_GetTransactionSig,
StorageImpl_SetTransactionSig,
StorageImpl_LockTransaction,
StorageImpl_UnlockTransaction
};
/*
* Virtual function table for the IStorageBaseImpl class.
*/
static const IStorageVtbl StorageImpl_Vtbl =
{
StorageBaseImpl_QueryInterface,
StorageBaseImpl_AddRef,
StorageBaseImpl_Release,
StorageBaseImpl_CreateStream,
StorageBaseImpl_OpenStream,
StorageBaseImpl_CreateStorage,
StorageBaseImpl_OpenStorage,
StorageBaseImpl_CopyTo,
StorageBaseImpl_MoveElementTo,
StorageBaseImpl_Commit,
StorageBaseImpl_Revert,
StorageBaseImpl_EnumElements,
StorageBaseImpl_DestroyElement,
StorageBaseImpl_RenameElement,
StorageBaseImpl_SetElementTimes,
StorageBaseImpl_SetClass,
StorageBaseImpl_SetStateBits,
StorageBaseImpl_Stat
};
static HRESULT StorageImpl_Construct(
HANDLE hFile,
LPCOLESTR pwcsName,
ILockBytes* pLkbyt,
DWORD openFlags,
BOOL fileBased,
BOOL create,
ULONG sector_size,
StorageImpl** result)
{
StorageImpl* This;
HRESULT hr = S_OK;
STATSTG stat;
if ( FAILED( validateSTGM(openFlags) ))
return STG_E_INVALIDFLAG;
This = HeapAlloc(GetProcessHeap(), 0, sizeof(StorageImpl));
if (!This)
return E_OUTOFMEMORY;
memset(This, 0, sizeof(StorageImpl));
list_init(&This->base.strmHead);
list_init(&This->base.storageHead);
This->base.IStorage_iface.lpVtbl = &StorageImpl_Vtbl;
This->base.IPropertySetStorage_iface.lpVtbl = &IPropertySetStorage_Vtbl;
This->base.IDirectWriterLock_iface.lpVtbl = &DirectWriterLockVtbl;
This->base.baseVtbl = &StorageImpl_BaseVtbl;
This->base.openFlags = (openFlags & ~STGM_CREATE);
This->base.ref = 1;
This->base.create = create;
if (openFlags == (STGM_DIRECT_SWMR|STGM_READWRITE|STGM_SHARE_DENY_WRITE))
This->base.lockingrole = SWMR_Writer;
else if (openFlags == (STGM_DIRECT_SWMR|STGM_READ|STGM_SHARE_DENY_NONE))
This->base.lockingrole = SWMR_Reader;
else
This->base.lockingrole = SWMR_None;
This->base.reverted = FALSE;
/*
* Initialize the big block cache.
*/
This->bigBlockSize = sector_size;
This->smallBlockSize = DEF_SMALL_BLOCK_SIZE;
if (hFile)
hr = FileLockBytesImpl_Construct(hFile, openFlags, pwcsName, &This->lockBytes);
else
{
This->lockBytes = pLkbyt;
ILockBytes_AddRef(pLkbyt);
}
if (SUCCEEDED(hr))
hr = ILockBytes_Stat(This->lockBytes, &stat, STATFLAG_NONAME);
if (SUCCEEDED(hr))
{
This->locks_supported = stat.grfLocksSupported;
if (!hFile)
/* Don't try to use wine-internal locking flag with custom ILockBytes */
This->locks_supported &= ~WINE_LOCK_READ;
hr = StorageImpl_GrabLocks(This, openFlags);
}
if (SUCCEEDED(hr))
hr = StorageImpl_Refresh(This, TRUE, create);
if (FAILED(hr))
{
IStorage_Release(&This->base.IStorage_iface);
*result = NULL;
}
else
{
StorageImpl_Flush(&This->base);
*result = This;
}
return hr;
}
/************************************************************************
* StorageInternalImpl implementation
***********************************************************************/
static void StorageInternalImpl_Invalidate( StorageBaseImpl *base )
{
StorageInternalImpl* This = (StorageInternalImpl*) base;
if (!This->base.reverted)
{
TRACE("Storage invalidated (stg=%p)\n", This);
This->base.reverted = TRUE;
This->parentStorage = NULL;
StorageBaseImpl_DeleteAll(&This->base);
list_remove(&This->ParentListEntry);
}
}
static void StorageInternalImpl_Destroy( StorageBaseImpl *iface)
{
StorageInternalImpl* This = (StorageInternalImpl*) iface;
StorageInternalImpl_Invalidate(&This->base);
HeapFree(GetProcessHeap(), 0, This);
}
static HRESULT StorageInternalImpl_Flush(StorageBaseImpl* iface)
{
StorageInternalImpl* This = (StorageInternalImpl*) iface;
return StorageBaseImpl_Flush(This->parentStorage);
}
static HRESULT StorageInternalImpl_GetFilename(StorageBaseImpl* iface, LPWSTR *result)
{
StorageInternalImpl* This = (StorageInternalImpl*) iface;
return StorageBaseImpl_GetFilename(This->parentStorage, result);
}
static HRESULT StorageInternalImpl_CreateDirEntry(StorageBaseImpl *base,
const DirEntry *newData, DirRef *index)
{
StorageInternalImpl* This = (StorageInternalImpl*) base;
return StorageBaseImpl_CreateDirEntry(This->parentStorage,
newData, index);
}
static HRESULT StorageInternalImpl_WriteDirEntry(StorageBaseImpl *base,
DirRef index, const DirEntry *data)
{
StorageInternalImpl* This = (StorageInternalImpl*) base;
return StorageBaseImpl_WriteDirEntry(This->parentStorage,
index, data);
}
static HRESULT StorageInternalImpl_ReadDirEntry(StorageBaseImpl *base,
DirRef index, DirEntry *data)
{
StorageInternalImpl* This = (StorageInternalImpl*) base;
return StorageBaseImpl_ReadDirEntry(This->parentStorage,
index, data);
}
static HRESULT StorageInternalImpl_DestroyDirEntry(StorageBaseImpl *base,
DirRef index)
{
StorageInternalImpl* This = (StorageInternalImpl*) base;
return StorageBaseImpl_DestroyDirEntry(This->parentStorage,
index);
}
static HRESULT StorageInternalImpl_StreamReadAt(StorageBaseImpl *base,
DirRef index, ULARGE_INTEGER offset, ULONG size, void *buffer, ULONG *bytesRead)
{
StorageInternalImpl* This = (StorageInternalImpl*) base;
return StorageBaseImpl_StreamReadAt(This->parentStorage,
index, offset, size, buffer, bytesRead);
}
static HRESULT StorageInternalImpl_StreamWriteAt(StorageBaseImpl *base,
DirRef index, ULARGE_INTEGER offset, ULONG size, const void *buffer, ULONG *bytesWritten)
{
StorageInternalImpl* This = (StorageInternalImpl*) base;
return StorageBaseImpl_StreamWriteAt(This->parentStorage,
index, offset, size, buffer, bytesWritten);
}
static HRESULT StorageInternalImpl_StreamSetSize(StorageBaseImpl *base,
DirRef index, ULARGE_INTEGER newsize)
{
StorageInternalImpl* This = (StorageInternalImpl*) base;
return StorageBaseImpl_StreamSetSize(This->parentStorage,
index, newsize);
}
static HRESULT StorageInternalImpl_StreamLink(StorageBaseImpl *base,
DirRef dst, DirRef src)
{
StorageInternalImpl* This = (StorageInternalImpl*) base;
return StorageBaseImpl_StreamLink(This->parentStorage,
dst, src);
}
static HRESULT StorageInternalImpl_GetTransactionSig(StorageBaseImpl *base,
ULONG* result, BOOL refresh)
{
return E_NOTIMPL;
}
static HRESULT StorageInternalImpl_SetTransactionSig(StorageBaseImpl *base,
ULONG value)
{
return E_NOTIMPL;
}
static HRESULT StorageInternalImpl_LockTransaction(StorageBaseImpl *base, BOOL write)
{
return E_NOTIMPL;
}
static HRESULT StorageInternalImpl_UnlockTransaction(StorageBaseImpl *base, BOOL write)
{
return E_NOTIMPL;
}
/******************************************************************************
**
** StorageInternalImpl_Commit
**
*/
static HRESULT WINAPI StorageInternalImpl_Commit(
IStorage* iface,
DWORD grfCommitFlags) /* [in] */
{
StorageBaseImpl* This = impl_from_IStorage(iface);
TRACE("(%p,%x)\n", iface, grfCommitFlags);
return StorageBaseImpl_Flush(This);
}
/******************************************************************************
**
** StorageInternalImpl_Revert
**
*/
static HRESULT WINAPI StorageInternalImpl_Revert(
IStorage* iface)
{
FIXME("(%p): stub\n", iface);
return S_OK;
}
/*
* Virtual function table for the StorageInternalImpl class.
*/
static const IStorageVtbl StorageInternalImpl_Vtbl =
{
StorageBaseImpl_QueryInterface,
StorageBaseImpl_AddRef,
StorageBaseImpl_Release,
StorageBaseImpl_CreateStream,
StorageBaseImpl_OpenStream,
StorageBaseImpl_CreateStorage,
StorageBaseImpl_OpenStorage,
StorageBaseImpl_CopyTo,
StorageBaseImpl_MoveElementTo,
StorageInternalImpl_Commit,
StorageInternalImpl_Revert,
StorageBaseImpl_EnumElements,
StorageBaseImpl_DestroyElement,
StorageBaseImpl_RenameElement,
StorageBaseImpl_SetElementTimes,
StorageBaseImpl_SetClass,
StorageBaseImpl_SetStateBits,
StorageBaseImpl_Stat
};
static const StorageBaseImplVtbl StorageInternalImpl_BaseVtbl =
{
StorageInternalImpl_Destroy,
StorageInternalImpl_Invalidate,
StorageInternalImpl_Flush,
StorageInternalImpl_GetFilename,
StorageInternalImpl_CreateDirEntry,
StorageInternalImpl_WriteDirEntry,
StorageInternalImpl_ReadDirEntry,
StorageInternalImpl_DestroyDirEntry,
StorageInternalImpl_StreamReadAt,
StorageInternalImpl_StreamWriteAt,
StorageInternalImpl_StreamSetSize,
StorageInternalImpl_StreamLink,
StorageInternalImpl_GetTransactionSig,
StorageInternalImpl_SetTransactionSig,
StorageInternalImpl_LockTransaction,
StorageInternalImpl_UnlockTransaction
};
static StorageInternalImpl* StorageInternalImpl_Construct(
StorageBaseImpl* parentStorage,
DWORD openFlags,
DirRef storageDirEntry)
{
StorageInternalImpl* newStorage;
newStorage = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(StorageInternalImpl));
if (newStorage!=0)
{
list_init(&newStorage->base.strmHead);
list_init(&newStorage->base.storageHead);
/*
* Initialize the virtual function table.
*/
newStorage->base.IStorage_iface.lpVtbl = &StorageInternalImpl_Vtbl;
newStorage->base.IPropertySetStorage_iface.lpVtbl = &IPropertySetStorage_Vtbl;
newStorage->base.baseVtbl = &StorageInternalImpl_BaseVtbl;
newStorage->base.openFlags = (openFlags & ~STGM_CREATE);
newStorage->base.reverted = FALSE;
newStorage->base.ref = 1;
newStorage->parentStorage = parentStorage;
/*
* Keep a reference to the directory entry of this storage
*/
newStorage->base.storageDirEntry = storageDirEntry;
newStorage->base.create = FALSE;
return newStorage;
}
return 0;
}
/************************************************************************
* TransactedSnapshotImpl implementation
***********************************************************************/
static DirRef TransactedSnapshotImpl_FindFreeEntry(TransactedSnapshotImpl *This)
{
DirRef result=This->firstFreeEntry;
while (result < This->entries_size && This->entries[result].inuse)
result++;
if (result == This->entries_size)
{
ULONG new_size = This->entries_size * 2;
TransactedDirEntry *new_entries;
new_entries = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(TransactedDirEntry) * new_size);
if (!new_entries) return DIRENTRY_NULL;
memcpy(new_entries, This->entries, sizeof(TransactedDirEntry) * This->entries_size);
HeapFree(GetProcessHeap(), 0, This->entries);
This->entries = new_entries;
This->entries_size = new_size;
}
This->entries[result].inuse = TRUE;
This->firstFreeEntry = result+1;
return result;
}
static DirRef TransactedSnapshotImpl_CreateStubEntry(
TransactedSnapshotImpl *This, DirRef parentEntryRef)
{
DirRef stubEntryRef;
TransactedDirEntry *entry;
stubEntryRef = TransactedSnapshotImpl_FindFreeEntry(This);
if (stubEntryRef != DIRENTRY_NULL)
{
entry = &This->entries[stubEntryRef];
entry->newTransactedParentEntry = entry->transactedParentEntry = parentEntryRef;
entry->read = FALSE;
}
return stubEntryRef;
}
static HRESULT TransactedSnapshotImpl_EnsureReadEntry(
TransactedSnapshotImpl *This, DirRef entry)
{
HRESULT hr=S_OK;
DirEntry data;
if (!This->entries[entry].read)
{
hr = StorageBaseImpl_ReadDirEntry(This->transactedParent,
This->entries[entry].transactedParentEntry,
&data);
if (SUCCEEDED(hr) && data.leftChild != DIRENTRY_NULL)
{
data.leftChild = TransactedSnapshotImpl_CreateStubEntry(This, data.leftChild);
if (data.leftChild == DIRENTRY_NULL)
hr = E_OUTOFMEMORY;
}
if (SUCCEEDED(hr) && data.rightChild != DIRENTRY_NULL)
{
data.rightChild = TransactedSnapshotImpl_CreateStubEntry(This, data.rightChild);
if (data.rightChild == DIRENTRY_NULL)
hr = E_OUTOFMEMORY;
}
if (SUCCEEDED(hr) && data.dirRootEntry != DIRENTRY_NULL)
{
data.dirRootEntry = TransactedSnapshotImpl_CreateStubEntry(This, data.dirRootEntry);
if (data.dirRootEntry == DIRENTRY_NULL)
hr = E_OUTOFMEMORY;
}
if (SUCCEEDED(hr))
{
memcpy(&This->entries[entry].data, &data, sizeof(DirEntry));
This->entries[entry].read = TRUE;
}
}
return hr;
}
static HRESULT TransactedSnapshotImpl_MakeStreamDirty(
TransactedSnapshotImpl *This, DirRef entry)
{
HRESULT hr = S_OK;
if (!This->entries[entry].stream_dirty)
{
DirEntry new_entrydata;
memset(&new_entrydata, 0, sizeof(DirEntry));
new_entrydata.name[0] = 'S';
new_entrydata.sizeOfNameString = 1;
new_entrydata.stgType = STGTY_STREAM;
new_entrydata.startingBlock = BLOCK_END_OF_CHAIN;
new_entrydata.leftChild = DIRENTRY_NULL;
new_entrydata.rightChild = DIRENTRY_NULL;
new_entrydata.dirRootEntry = DIRENTRY_NULL;
hr = StorageBaseImpl_CreateDirEntry(This->scratch, &new_entrydata,
&This->entries[entry].stream_entry);
if (SUCCEEDED(hr) && This->entries[entry].transactedParentEntry != DIRENTRY_NULL)
{
hr = StorageBaseImpl_CopyStream(
This->scratch, This->entries[entry].stream_entry,
This->transactedParent, This->entries[entry].transactedParentEntry);
if (FAILED(hr))
StorageBaseImpl_DestroyDirEntry(This->scratch, This->entries[entry].stream_entry);
}
if (SUCCEEDED(hr))
This->entries[entry].stream_dirty = TRUE;
if (This->entries[entry].transactedParentEntry != DIRENTRY_NULL)
{
/* Since this entry is modified, and we aren't using its stream data, we
* no longer care about the original entry. */
DirRef delete_ref;
delete_ref = TransactedSnapshotImpl_CreateStubEntry(This, This->entries[entry].transactedParentEntry);
if (delete_ref != DIRENTRY_NULL)
This->entries[delete_ref].deleted = TRUE;
This->entries[entry].transactedParentEntry = This->entries[entry].newTransactedParentEntry = DIRENTRY_NULL;
}
}
return hr;
}
/* Find the first entry in a depth-first traversal. */
static DirRef TransactedSnapshotImpl_FindFirstChild(
TransactedSnapshotImpl* This, DirRef parent)
{
DirRef cursor, prev;
TransactedDirEntry *entry;
cursor = parent;
entry = &This->entries[cursor];
while (entry->read)
{
if (entry->data.leftChild != DIRENTRY_NULL)
{
prev = cursor;
cursor = entry->data.leftChild;
entry = &This->entries[cursor];
entry->parent = prev;
}
else if (entry->data.rightChild != DIRENTRY_NULL)
{
prev = cursor;
cursor = entry->data.rightChild;
entry = &This->entries[cursor];
entry->parent = prev;
}
else if (entry->data.dirRootEntry != DIRENTRY_NULL)
{
prev = cursor;
cursor = entry->data.dirRootEntry;
entry = &This->entries[cursor];
entry->parent = prev;
}
else
break;
}
return cursor;
}
/* Find the next entry in a depth-first traversal. */
static DirRef TransactedSnapshotImpl_FindNextChild(
TransactedSnapshotImpl* This, DirRef current)
{
DirRef parent;
TransactedDirEntry *parent_entry;
parent = This->entries[current].parent;
parent_entry = &This->entries[parent];
if (parent != DIRENTRY_NULL && parent_entry->data.dirRootEntry != current)
{
if (parent_entry->data.rightChild != current && parent_entry->data.rightChild != DIRENTRY_NULL)
{
This->entries[parent_entry->data.rightChild].parent = parent;
return TransactedSnapshotImpl_FindFirstChild(This, parent_entry->data.rightChild);
}
if (parent_entry->data.dirRootEntry != DIRENTRY_NULL)
{
This->entries[parent_entry->data.dirRootEntry].parent = parent;
return TransactedSnapshotImpl_FindFirstChild(This, parent_entry->data.dirRootEntry);
}
}
return parent;
}
/* Return TRUE if we've made a copy of this entry for committing to the parent. */
static inline BOOL TransactedSnapshotImpl_MadeCopy(
TransactedSnapshotImpl* This, DirRef entry)
{
return entry != DIRENTRY_NULL &&
This->entries[entry].newTransactedParentEntry != This->entries[entry].transactedParentEntry;
}
/* Destroy the entries created by CopyTree. */
static void TransactedSnapshotImpl_DestroyTemporaryCopy(
TransactedSnapshotImpl* This, DirRef stop)
{
DirRef cursor;
TransactedDirEntry *entry;
ULARGE_INTEGER zero;
zero.QuadPart = 0;
if (!This->entries[This->base.storageDirEntry].read)
return;
cursor = This->entries[This->base.storageDirEntry].data.dirRootEntry;
if (cursor == DIRENTRY_NULL)
return;
cursor = TransactedSnapshotImpl_FindFirstChild(This, cursor);
while (cursor != DIRENTRY_NULL && cursor != stop)
{
if (TransactedSnapshotImpl_MadeCopy(This, cursor))
{
entry = &This->entries[cursor];
if (entry->stream_dirty)
StorageBaseImpl_StreamSetSize(This->transactedParent,
entry->newTransactedParentEntry, zero);
StorageBaseImpl_DestroyDirEntry(This->transactedParent,
entry->newTransactedParentEntry);
entry->newTransactedParentEntry = entry->transactedParentEntry;
}
cursor = TransactedSnapshotImpl_FindNextChild(This, cursor);
}
}
/* Make a copy of our edited tree that we can use in the parent. */
static HRESULT TransactedSnapshotImpl_CopyTree(TransactedSnapshotImpl* This)
{
DirRef cursor;
TransactedDirEntry *entry;
HRESULT hr = S_OK;
cursor = This->base.storageDirEntry;
entry = &This->entries[cursor];
entry->parent = DIRENTRY_NULL;
entry->newTransactedParentEntry = entry->transactedParentEntry;
if (entry->data.dirRootEntry == DIRENTRY_NULL)
return S_OK;
This->entries[entry->data.dirRootEntry].parent = DIRENTRY_NULL;
cursor = TransactedSnapshotImpl_FindFirstChild(This, entry->data.dirRootEntry);
entry = &This->entries[cursor];
while (cursor != DIRENTRY_NULL)
{
/* Make a copy of this entry in the transacted parent. */
if (!entry->read ||
(!entry->dirty && !entry->stream_dirty &&
!TransactedSnapshotImpl_MadeCopy(This, entry->data.leftChild) &&
!TransactedSnapshotImpl_MadeCopy(This, entry->data.rightChild) &&
!TransactedSnapshotImpl_MadeCopy(This, entry->data.dirRootEntry)))
entry->newTransactedParentEntry = entry->transactedParentEntry;
else
{
DirEntry newData;
memcpy(&newData, &entry->data, sizeof(DirEntry));
newData.size.QuadPart = 0;
newData.startingBlock = BLOCK_END_OF_CHAIN;
if (newData.leftChild != DIRENTRY_NULL)
newData.leftChild = This->entries[newData.leftChild].newTransactedParentEntry;
if (newData.rightChild != DIRENTRY_NULL)
newData.rightChild = This->entries[newData.rightChild].newTransactedParentEntry;
if (newData.dirRootEntry != DIRENTRY_NULL)
newData.dirRootEntry = This->entries[newData.dirRootEntry].newTransactedParentEntry;
hr = StorageBaseImpl_CreateDirEntry(This->transactedParent, &newData,
&entry->newTransactedParentEntry);
if (FAILED(hr))
{
TransactedSnapshotImpl_DestroyTemporaryCopy(This, cursor);
return hr;
}
if (entry->stream_dirty)
{
hr = StorageBaseImpl_CopyStream(
This->transactedParent, entry->newTransactedParentEntry,
This->scratch, entry->stream_entry);
}
else if (entry->data.size.QuadPart)
{
hr = StorageBaseImpl_StreamLink(
This->transactedParent, entry->newTransactedParentEntry,
entry->transactedParentEntry);
}
if (FAILED(hr))
{
cursor = TransactedSnapshotImpl_FindNextChild(This, cursor);
TransactedSnapshotImpl_DestroyTemporaryCopy(This, cursor);
return hr;
}
}
cursor = TransactedSnapshotImpl_FindNextChild(This, cursor);
entry = &This->entries[cursor];
}
return hr;
}
static HRESULT WINAPI TransactedSnapshotImpl_Commit(
IStorage* iface,
DWORD grfCommitFlags) /* [in] */
{
TransactedSnapshotImpl* This = (TransactedSnapshotImpl*)impl_from_IStorage(iface);
TransactedDirEntry *root_entry;
DirRef i, dir_root_ref;
DirEntry data;
ULARGE_INTEGER zero;
HRESULT hr;
ULONG transactionSig;
zero.QuadPart = 0;
TRACE("(%p,%x)\n", iface, grfCommitFlags);
/* Cannot commit a read-only transacted storage */
if ( STGM_ACCESS_MODE( This->base.openFlags ) == STGM_READ )
return STG_E_ACCESSDENIED;
hr = StorageBaseImpl_LockTransaction(This->transactedParent, TRUE);
if (hr == E_NOTIMPL) hr = S_OK;
if (SUCCEEDED(hr))
{
hr = StorageBaseImpl_GetTransactionSig(This->transactedParent, &transactionSig, TRUE);
if (SUCCEEDED(hr))
{
if (transactionSig != This->lastTransactionSig)
{
ERR("file was externally modified\n");
hr = STG_E_NOTCURRENT;
}
if (SUCCEEDED(hr))
{
This->lastTransactionSig = transactionSig+1;
hr = StorageBaseImpl_SetTransactionSig(This->transactedParent, This->lastTransactionSig);
}
}
else if (hr == E_NOTIMPL)
hr = S_OK;
if (FAILED(hr)) goto end;
/* To prevent data loss, we create the new structure in the file before we
* delete the old one, so that in case of errors the old data is intact. We
* shouldn't do this if STGC_OVERWRITE is set, but that flag should only be
* needed in the rare situation where we have just enough free disk space to
* overwrite the existing data. */
root_entry = &This->entries[This->base.storageDirEntry];
if (!root_entry->read)
goto end;
hr = TransactedSnapshotImpl_CopyTree(This);
if (FAILED(hr)) goto end;
if (root_entry->data.dirRootEntry == DIRENTRY_NULL)
dir_root_ref = DIRENTRY_NULL;
else
dir_root_ref = This->entries[root_entry->data.dirRootEntry].newTransactedParentEntry;
hr = StorageBaseImpl_Flush(This->transactedParent);
/* Update the storage to use the new data in one step. */
if (SUCCEEDED(hr))
hr = StorageBaseImpl_ReadDirEntry(This->transactedParent,
root_entry->transactedParentEntry, &data);
if (SUCCEEDED(hr))
{
data.dirRootEntry = dir_root_ref;
data.clsid = root_entry->data.clsid;
data.ctime = root_entry->data.ctime;
data.mtime = root_entry->data.mtime;
hr = StorageBaseImpl_WriteDirEntry(This->transactedParent,
root_entry->transactedParentEntry, &data);
}
/* Try to flush after updating the root storage, but if the flush fails, keep
* going, on the theory that it'll either succeed later or the subsequent
* writes will fail. */
StorageBaseImpl_Flush(This->transactedParent);
if (SUCCEEDED(hr))
{
/* Destroy the old now-orphaned data. */
for (i=0; i<This->entries_size; i++)
{
TransactedDirEntry *entry = &This->entries[i];
if (entry->inuse)
{
if (entry->deleted)
{
StorageBaseImpl_StreamSetSize(This->transactedParent,
entry->transactedParentEntry, zero);
StorageBaseImpl_DestroyDirEntry(This->transactedParent,
entry->transactedParentEntry);
memset(entry, 0, sizeof(TransactedDirEntry));
This->firstFreeEntry = min(i, This->firstFreeEntry);
}
else if (entry->read && entry->transactedParentEntry != entry->newTransactedParentEntry)
{
if (entry->transactedParentEntry != DIRENTRY_NULL)
StorageBaseImpl_DestroyDirEntry(This->transactedParent,
entry->transactedParentEntry);
if (entry->stream_dirty)
{
StorageBaseImpl_StreamSetSize(This->scratch, entry->stream_entry, zero);
StorageBaseImpl_DestroyDirEntry(This->scratch, entry->stream_entry);
entry->stream_dirty = FALSE;
}
entry->dirty = FALSE;
entry->transactedParentEntry = entry->newTransactedParentEntry;
}
}
}
}
else
{
TransactedSnapshotImpl_DestroyTemporaryCopy(This, DIRENTRY_NULL);
}
if (SUCCEEDED(hr))
hr = StorageBaseImpl_Flush(This->transactedParent);
end:
StorageBaseImpl_UnlockTransaction(This->transactedParent, TRUE);
}
TRACE("<-- %08x\n", hr);
return hr;
}
static HRESULT WINAPI TransactedSnapshotImpl_Revert(
IStorage* iface)
{
TransactedSnapshotImpl* This = (TransactedSnapshotImpl*)impl_from_IStorage(iface);
ULARGE_INTEGER zero;
ULONG i;
TRACE("(%p)\n", iface);
/* Destroy the open objects. */
StorageBaseImpl_DeleteAll(&This->base);
/* Clear out the scratch file. */
zero.QuadPart = 0;
for (i=0; i<This->entries_size; i++)
{
if (This->entries[i].stream_dirty)
{
StorageBaseImpl_StreamSetSize(This->scratch, This->entries[i].stream_entry,
zero);
StorageBaseImpl_DestroyDirEntry(This->scratch, This->entries[i].stream_entry);
}
}
memset(This->entries, 0, sizeof(TransactedDirEntry) * This->entries_size);
This->firstFreeEntry = 0;
This->base.storageDirEntry = TransactedSnapshotImpl_CreateStubEntry(This, This->transactedParent->storageDirEntry);
return S_OK;
}
static void TransactedSnapshotImpl_Invalidate(StorageBaseImpl* This)
{
if (!This->reverted)
{
TRACE("Storage invalidated (stg=%p)\n", This);
This->reverted = TRUE;
StorageBaseImpl_DeleteAll(This);
}
}
static void TransactedSnapshotImpl_Destroy( StorageBaseImpl *iface)
{
TransactedSnapshotImpl* This = (TransactedSnapshotImpl*) iface;
IStorage_Revert(&This->base.IStorage_iface);
IStorage_Release(&This->transactedParent->IStorage_iface);
IStorage_Release(&This->scratch->IStorage_iface);
HeapFree(GetProcessHeap(), 0, This->entries);
HeapFree(GetProcessHeap(), 0, This);
}
static HRESULT TransactedSnapshotImpl_Flush(StorageBaseImpl* iface)
{
/* We only need to flush when committing. */
return S_OK;
}
static HRESULT TransactedSnapshotImpl_GetFilename(StorageBaseImpl* iface, LPWSTR *result)
{
TransactedSnapshotImpl* This = (TransactedSnapshotImpl*) iface;
return StorageBaseImpl_GetFilename(This->transactedParent, result);
}
static HRESULT TransactedSnapshotImpl_CreateDirEntry(StorageBaseImpl *base,
const DirEntry *newData, DirRef *index)
{
TransactedSnapshotImpl* This = (TransactedSnapshotImpl*) base;
DirRef new_ref;
TransactedDirEntry *new_entry;
new_ref = TransactedSnapshotImpl_FindFreeEntry(This);
if (new_ref == DIRENTRY_NULL)
return E_OUTOFMEMORY;
new_entry = &This->entries[new_ref];
new_entry->newTransactedParentEntry = new_entry->transactedParentEntry = DIRENTRY_NULL;
new_entry->read = TRUE;
new_entry->dirty = TRUE;
memcpy(&new_entry->data, newData, sizeof(DirEntry));
*index = new_ref;
TRACE("%s l=%x r=%x d=%x <-- %x\n", debugstr_w(newData->name), newData->leftChild, newData->rightChild, newData->dirRootEntry, *index);
return S_OK;
}
static HRESULT TransactedSnapshotImpl_WriteDirEntry(StorageBaseImpl *base,
DirRef index, const DirEntry *data)
{
TransactedSnapshotImpl* This = (TransactedSnapshotImpl*) base;
HRESULT hr;
TRACE("%x %s l=%x r=%x d=%x\n", index, debugstr_w(data->name), data->leftChild, data->rightChild, data->dirRootEntry);
hr = TransactedSnapshotImpl_EnsureReadEntry(This, index);
if (FAILED(hr))
{
TRACE("<-- %08x\n", hr);
return hr;
}
memcpy(&This->entries[index].data, data, sizeof(DirEntry));
if (index != This->base.storageDirEntry)
{
This->entries[index].dirty = TRUE;
if (data->size.QuadPart == 0 &&
This->entries[index].transactedParentEntry != DIRENTRY_NULL)
{
/* Since this entry is modified, and we aren't using its stream data, we
* no longer care about the original entry. */
DirRef delete_ref;
delete_ref = TransactedSnapshotImpl_CreateStubEntry(This, This->entries[index].transactedParentEntry);
if (delete_ref != DIRENTRY_NULL)
This->entries[delete_ref].deleted = TRUE;
This->entries[index].transactedParentEntry = This->entries[index].newTransactedParentEntry = DIRENTRY_NULL;
}
}
TRACE("<-- S_OK\n");
return S_OK;
}
static HRESULT TransactedSnapshotImpl_ReadDirEntry(StorageBaseImpl *base,
DirRef index, DirEntry *data)
{
TransactedSnapshotImpl* This = (TransactedSnapshotImpl*) base;
HRESULT hr;
hr = TransactedSnapshotImpl_EnsureReadEntry(This, index);
if (FAILED(hr))
{
TRACE("<-- %08x\n", hr);
return hr;
}
memcpy(data, &This->entries[index].data, sizeof(DirEntry));
TRACE("%x %s l=%x r=%x d=%x\n", index, debugstr_w(data->name), data->leftChild, data->rightChild, data->dirRootEntry);
return S_OK;
}
static HRESULT TransactedSnapshotImpl_DestroyDirEntry(StorageBaseImpl *base,
DirRef index)
{
TransactedSnapshotImpl* This = (TransactedSnapshotImpl*) base;
if (This->entries[index].transactedParentEntry == DIRENTRY_NULL ||
This->entries[index].data.size.QuadPart != 0)
{
/* If we deleted this entry while it has stream data. We must have left the
* data because some other entry is using it, and we need to leave the
* original entry alone. */
memset(&This->entries[index], 0, sizeof(TransactedDirEntry));
This->firstFreeEntry = min(index, This->firstFreeEntry);
}
else
{
This->entries[index].deleted = TRUE;
}
return S_OK;
}
static HRESULT TransactedSnapshotImpl_StreamReadAt(StorageBaseImpl *base,
DirRef index, ULARGE_INTEGER offset, ULONG size, void *buffer, ULONG *bytesRead)
{
TransactedSnapshotImpl* This = (TransactedSnapshotImpl*) base;
if (This->entries[index].stream_dirty)
{
return StorageBaseImpl_StreamReadAt(This->scratch,
This->entries[index].stream_entry, offset, size, buffer, bytesRead);
}
else if (This->entries[index].transactedParentEntry == DIRENTRY_NULL)
{
/* This stream doesn't live in the parent, and we haven't allocated storage
* for it yet */
*bytesRead = 0;
return S_OK;
}
else
{
return StorageBaseImpl_StreamReadAt(This->transactedParent,
This->entries[index].transactedParentEntry, offset, size, buffer, bytesRead);
}
}
static HRESULT TransactedSnapshotImpl_StreamWriteAt(StorageBaseImpl *base,
DirRef index, ULARGE_INTEGER offset, ULONG size, const void *buffer, ULONG *bytesWritten)
{
TransactedSnapshotImpl* This = (TransactedSnapshotImpl*) base;
HRESULT hr;
hr = TransactedSnapshotImpl_EnsureReadEntry(This, index);
if (FAILED(hr))
{
TRACE("<-- %08x\n", hr);
return hr;
}
hr = TransactedSnapshotImpl_MakeStreamDirty(This, index);
if (FAILED(hr))
{
TRACE("<-- %08x\n", hr);
return hr;
}
hr = StorageBaseImpl_StreamWriteAt(This->scratch,
This->entries[index].stream_entry, offset, size, buffer, bytesWritten);
if (SUCCEEDED(hr) && size != 0)
This->entries[index].data.size.QuadPart = max(
This->entries[index].data.size.QuadPart,
offset.QuadPart + size);
TRACE("<-- %08x\n", hr);
return hr;
}
static HRESULT TransactedSnapshotImpl_StreamSetSize(StorageBaseImpl *base,
DirRef index, ULARGE_INTEGER newsize)
{
TransactedSnapshotImpl* This = (TransactedSnapshotImpl*) base;
HRESULT hr;
hr = TransactedSnapshotImpl_EnsureReadEntry(This, index);
if (FAILED(hr))
{
TRACE("<-- %08x\n", hr);
return hr;
}
if (This->entries[index].data.size.QuadPart == newsize.QuadPart)
return S_OK;
if (newsize.QuadPart == 0)
{
/* Destroy any parent references or entries in the scratch file. */
if (This->entries[index].stream_dirty)
{
ULARGE_INTEGER zero;
zero.QuadPart = 0;
StorageBaseImpl_StreamSetSize(This->scratch,
This->entries[index].stream_entry, zero);
StorageBaseImpl_DestroyDirEntry(This->scratch,
This->entries[index].stream_entry);
This->entries[index].stream_dirty = FALSE;
}
else if (This->entries[index].transactedParentEntry != DIRENTRY_NULL)
{
DirRef delete_ref;
delete_ref = TransactedSnapshotImpl_CreateStubEntry(This, This->entries[index].transactedParentEntry);
if (delete_ref != DIRENTRY_NULL)
This->entries[delete_ref].deleted = TRUE;
This->entries[index].transactedParentEntry = This->entries[index].newTransactedParentEntry = DIRENTRY_NULL;
}
}
else
{
hr = TransactedSnapshotImpl_MakeStreamDirty(This, index);
if (FAILED(hr)) return hr;
hr = StorageBaseImpl_StreamSetSize(This->scratch,
This->entries[index].stream_entry, newsize);
}
if (SUCCEEDED(hr))
This->entries[index].data.size = newsize;
TRACE("<-- %08x\n", hr);
return hr;
}
static HRESULT TransactedSnapshotImpl_StreamLink(StorageBaseImpl *base,
DirRef dst, DirRef src)
{
TransactedSnapshotImpl* This = (TransactedSnapshotImpl*) base;
HRESULT hr;
TransactedDirEntry *dst_entry, *src_entry;
hr = TransactedSnapshotImpl_EnsureReadEntry(This, src);
if (FAILED(hr))
{
TRACE("<-- %08x\n", hr);
return hr;
}
hr = TransactedSnapshotImpl_EnsureReadEntry(This, dst);
if (FAILED(hr))
{
TRACE("<-- %08x\n", hr);
return hr;
}
dst_entry = &This->entries[dst];
src_entry = &This->entries[src];
dst_entry->stream_dirty = src_entry->stream_dirty;
dst_entry->stream_entry = src_entry->stream_entry;
dst_entry->transactedParentEntry = src_entry->transactedParentEntry;
dst_entry->newTransactedParentEntry = src_entry->newTransactedParentEntry;
dst_entry->data.size = src_entry->data.size;
return S_OK;
}
static HRESULT TransactedSnapshotImpl_GetTransactionSig(StorageBaseImpl *base,
ULONG* result, BOOL refresh)
{
return E_NOTIMPL;
}
static HRESULT TransactedSnapshotImpl_SetTransactionSig(StorageBaseImpl *base,
ULONG value)
{
return E_NOTIMPL;
}
static HRESULT TransactedSnapshotImpl_LockTransaction(StorageBaseImpl *base, BOOL write)
{
return E_NOTIMPL;
}
static HRESULT TransactedSnapshotImpl_UnlockTransaction(StorageBaseImpl *base, BOOL write)
{
return E_NOTIMPL;
}
static const IStorageVtbl TransactedSnapshotImpl_Vtbl =
{
StorageBaseImpl_QueryInterface,
StorageBaseImpl_AddRef,
StorageBaseImpl_Release,
StorageBaseImpl_CreateStream,
StorageBaseImpl_OpenStream,
StorageBaseImpl_CreateStorage,
StorageBaseImpl_OpenStorage,
StorageBaseImpl_CopyTo,
StorageBaseImpl_MoveElementTo,
TransactedSnapshotImpl_Commit,
TransactedSnapshotImpl_Revert,
StorageBaseImpl_EnumElements,
StorageBaseImpl_DestroyElement,
StorageBaseImpl_RenameElement,
StorageBaseImpl_SetElementTimes,
StorageBaseImpl_SetClass,
StorageBaseImpl_SetStateBits,
StorageBaseImpl_Stat
};
static const StorageBaseImplVtbl TransactedSnapshotImpl_BaseVtbl =
{
TransactedSnapshotImpl_Destroy,
TransactedSnapshotImpl_Invalidate,
TransactedSnapshotImpl_Flush,
TransactedSnapshotImpl_GetFilename,
TransactedSnapshotImpl_CreateDirEntry,
TransactedSnapshotImpl_WriteDirEntry,
TransactedSnapshotImpl_ReadDirEntry,
TransactedSnapshotImpl_DestroyDirEntry,
TransactedSnapshotImpl_StreamReadAt,
TransactedSnapshotImpl_StreamWriteAt,
TransactedSnapshotImpl_StreamSetSize,
TransactedSnapshotImpl_StreamLink,
TransactedSnapshotImpl_GetTransactionSig,
TransactedSnapshotImpl_SetTransactionSig,
TransactedSnapshotImpl_LockTransaction,
TransactedSnapshotImpl_UnlockTransaction
};
static HRESULT TransactedSnapshotImpl_Construct(StorageBaseImpl *parentStorage,
TransactedSnapshotImpl** result)
{
HRESULT hr;
*result = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(TransactedSnapshotImpl));
if (*result)
{
IStorage *scratch;
(*result)->base.IStorage_iface.lpVtbl = &TransactedSnapshotImpl_Vtbl;
/* This is OK because the property set storage functions use the IStorage functions. */
(*result)->base.IPropertySetStorage_iface.lpVtbl = parentStorage->IPropertySetStorage_iface.lpVtbl;
(*result)->base.baseVtbl = &TransactedSnapshotImpl_BaseVtbl;
list_init(&(*result)->base.strmHead);
list_init(&(*result)->base.storageHead);
(*result)->base.ref = 1;
(*result)->base.openFlags = parentStorage->openFlags;
/* This cannot fail, except with E_NOTIMPL in which case we don't care */
StorageBaseImpl_GetTransactionSig(parentStorage, &(*result)->lastTransactionSig, FALSE);
/* Create a new temporary storage to act as the scratch file. */
hr = StgCreateDocfile(NULL, STGM_READWRITE|STGM_SHARE_EXCLUSIVE|STGM_CREATE|STGM_DELETEONRELEASE,
0, &scratch);
(*result)->scratch = impl_from_IStorage(scratch);
if (SUCCEEDED(hr))
{
ULONG num_entries = 20;
(*result)->entries = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(TransactedDirEntry) * num_entries);
(*result)->entries_size = num_entries;
(*result)->firstFreeEntry = 0;
if ((*result)->entries)
{
/* parentStorage already has 1 reference, which we take over here. */
(*result)->transactedParent = parentStorage;
parentStorage->transactedChild = &(*result)->base;
(*result)->base.storageDirEntry = TransactedSnapshotImpl_CreateStubEntry(*result, parentStorage->storageDirEntry);
}
else
{
IStorage_Release(scratch);
hr = E_OUTOFMEMORY;
}
}
if (FAILED(hr)) HeapFree(GetProcessHeap(), 0, *result);
return hr;
}
else
return E_OUTOFMEMORY;
}
/************************************************************************
* TransactedSharedImpl implementation
***********************************************************************/
static void TransactedSharedImpl_Invalidate(StorageBaseImpl* This)
{
if (!This->reverted)
{
TRACE("Storage invalidated (stg=%p)\n", This);
This->reverted = TRUE;
StorageBaseImpl_DeleteAll(This);
}
}
static void TransactedSharedImpl_Destroy( StorageBaseImpl *iface)
{
TransactedSharedImpl* This = (TransactedSharedImpl*) iface;
TransactedSharedImpl_Invalidate(&This->base);
IStorage_Release(&This->transactedParent->IStorage_iface);
IStorage_Release(&This->scratch->base.IStorage_iface);
HeapFree(GetProcessHeap(), 0, This);
}
static HRESULT TransactedSharedImpl_Flush(StorageBaseImpl* iface)
{
/* We only need to flush when committing. */
return S_OK;
}
static HRESULT TransactedSharedImpl_GetFilename(StorageBaseImpl* iface, LPWSTR *result)
{
TransactedSharedImpl* This = (TransactedSharedImpl*) iface;
return StorageBaseImpl_GetFilename(This->transactedParent, result);
}
static HRESULT TransactedSharedImpl_CreateDirEntry(StorageBaseImpl *base,
const DirEntry *newData, DirRef *index)
{
TransactedSharedImpl* This = (TransactedSharedImpl*) base;
return StorageBaseImpl_CreateDirEntry(&This->scratch->base,
newData, index);
}
static HRESULT TransactedSharedImpl_WriteDirEntry(StorageBaseImpl *base,
DirRef index, const DirEntry *data)
{
TransactedSharedImpl* This = (TransactedSharedImpl*) base;
return StorageBaseImpl_WriteDirEntry(&This->scratch->base,
index, data);
}
static HRESULT TransactedSharedImpl_ReadDirEntry(StorageBaseImpl *base,
DirRef index, DirEntry *data)
{
TransactedSharedImpl* This = (TransactedSharedImpl*) base;
return StorageBaseImpl_ReadDirEntry(&This->scratch->base,
index, data);
}
static HRESULT TransactedSharedImpl_DestroyDirEntry(StorageBaseImpl *base,
DirRef index)
{
TransactedSharedImpl* This = (TransactedSharedImpl*) base;
return StorageBaseImpl_DestroyDirEntry(&This->scratch->base,
index);
}
static HRESULT TransactedSharedImpl_StreamReadAt(StorageBaseImpl *base,
DirRef index, ULARGE_INTEGER offset, ULONG size, void *buffer, ULONG *bytesRead)
{
TransactedSharedImpl* This = (TransactedSharedImpl*) base;
return StorageBaseImpl_StreamReadAt(&This->scratch->base,
index, offset, size, buffer, bytesRead);
}
static HRESULT TransactedSharedImpl_StreamWriteAt(StorageBaseImpl *base,
DirRef index, ULARGE_INTEGER offset, ULONG size, const void *buffer, ULONG *bytesWritten)
{
TransactedSharedImpl* This = (TransactedSharedImpl*) base;
return StorageBaseImpl_StreamWriteAt(&This->scratch->base,
index, offset, size, buffer, bytesWritten);
}
static HRESULT TransactedSharedImpl_StreamSetSize(StorageBaseImpl *base,
DirRef index, ULARGE_INTEGER newsize)
{
TransactedSharedImpl* This = (TransactedSharedImpl*) base;
return StorageBaseImpl_StreamSetSize(&This->scratch->base,
index, newsize);
}
static HRESULT TransactedSharedImpl_StreamLink(StorageBaseImpl *base,
DirRef dst, DirRef src)
{
TransactedSharedImpl* This = (TransactedSharedImpl*) base;
return StorageBaseImpl_StreamLink(&This->scratch->base,
dst, src);
}
static HRESULT TransactedSharedImpl_GetTransactionSig(StorageBaseImpl *base,
ULONG* result, BOOL refresh)
{
return E_NOTIMPL;
}
static HRESULT TransactedSharedImpl_SetTransactionSig(StorageBaseImpl *base,
ULONG value)
{
return E_NOTIMPL;
}
static HRESULT TransactedSharedImpl_LockTransaction(StorageBaseImpl *base, BOOL write)
{
return E_NOTIMPL;
}
static HRESULT TransactedSharedImpl_UnlockTransaction(StorageBaseImpl *base, BOOL write)
{
return E_NOTIMPL;
}
static HRESULT WINAPI TransactedSharedImpl_Commit(
IStorage* iface,
DWORD grfCommitFlags) /* [in] */
{
TransactedSharedImpl* This = (TransactedSharedImpl*)impl_from_IStorage(iface);
DirRef new_storage_ref, prev_storage_ref;
DirEntry src_data, dst_data;
HRESULT hr;
ULONG transactionSig;
TRACE("(%p,%x)\n", iface, grfCommitFlags);
/* Cannot commit a read-only transacted storage */
if ( STGM_ACCESS_MODE( This->base.openFlags ) == STGM_READ )
return STG_E_ACCESSDENIED;
hr = StorageBaseImpl_LockTransaction(This->transactedParent, TRUE);
if (hr == E_NOTIMPL) hr = S_OK;
if (SUCCEEDED(hr))
{
hr = StorageBaseImpl_GetTransactionSig(This->transactedParent, &transactionSig, TRUE);
if (SUCCEEDED(hr))
{
if ((grfCommitFlags & STGC_ONLYIFCURRENT) && transactionSig != This->lastTransactionSig)
hr = STG_E_NOTCURRENT;
if (SUCCEEDED(hr))
hr = StorageBaseImpl_SetTransactionSig(This->transactedParent, transactionSig+1);
}
else if (hr == E_NOTIMPL)
hr = S_OK;
if (SUCCEEDED(hr))
hr = StorageBaseImpl_ReadDirEntry(&This->scratch->base, This->scratch->base.storageDirEntry, &src_data);
/* FIXME: If we're current, we should be able to copy only the changes in scratch. */
if (SUCCEEDED(hr))
hr = StorageBaseImpl_DupStorageTree(This->transactedParent, &new_storage_ref, &This->scratch->base, src_data.dirRootEntry);
if (SUCCEEDED(hr))
hr = StorageBaseImpl_Flush(This->transactedParent);
if (SUCCEEDED(hr))
hr = StorageBaseImpl_ReadDirEntry(This->transactedParent, This->transactedParent->storageDirEntry, &dst_data);
if (SUCCEEDED(hr))
{
prev_storage_ref = dst_data.dirRootEntry;
dst_data.dirRootEntry = new_storage_ref;
dst_data.clsid = src_data.clsid;
dst_data.ctime = src_data.ctime;
dst_data.mtime = src_data.mtime;
hr = StorageBaseImpl_WriteDirEntry(This->transactedParent, This->transactedParent->storageDirEntry, &dst_data);
}
if (SUCCEEDED(hr))
{
/* Try to flush after updating the root storage, but if the flush fails, keep
* going, on the theory that it'll either succeed later or the subsequent
* writes will fail. */
StorageBaseImpl_Flush(This->transactedParent);
hr = StorageBaseImpl_DeleteStorageTree(This->transactedParent, prev_storage_ref, TRUE);
}
if (SUCCEEDED(hr))
hr = StorageBaseImpl_Flush(This->transactedParent);
StorageBaseImpl_UnlockTransaction(This->transactedParent, TRUE);
if (SUCCEEDED(hr))
hr = IStorage_Commit(&This->scratch->base.IStorage_iface, STGC_DEFAULT);
if (SUCCEEDED(hr))
{
This->lastTransactionSig = transactionSig+1;
}
}
TRACE("<-- %08x\n", hr);
return hr;
}
static HRESULT WINAPI TransactedSharedImpl_Revert(
IStorage* iface)
{
TransactedSharedImpl* This = (TransactedSharedImpl*)impl_from_IStorage(iface);
TRACE("(%p)\n", iface);
/* Destroy the open objects. */
StorageBaseImpl_DeleteAll(&This->base);
return IStorage_Revert(&This->scratch->base.IStorage_iface);
}
static const IStorageVtbl TransactedSharedImpl_Vtbl =
{
StorageBaseImpl_QueryInterface,
StorageBaseImpl_AddRef,
StorageBaseImpl_Release,
StorageBaseImpl_CreateStream,
StorageBaseImpl_OpenStream,
StorageBaseImpl_CreateStorage,
StorageBaseImpl_OpenStorage,
StorageBaseImpl_CopyTo,
StorageBaseImpl_MoveElementTo,
TransactedSharedImpl_Commit,
TransactedSharedImpl_Revert,
StorageBaseImpl_EnumElements,
StorageBaseImpl_DestroyElement,
StorageBaseImpl_RenameElement,
StorageBaseImpl_SetElementTimes,
StorageBaseImpl_SetClass,
StorageBaseImpl_SetStateBits,
StorageBaseImpl_Stat
};
static const StorageBaseImplVtbl TransactedSharedImpl_BaseVtbl =
{
TransactedSharedImpl_Destroy,
TransactedSharedImpl_Invalidate,
TransactedSharedImpl_Flush,
TransactedSharedImpl_GetFilename,
TransactedSharedImpl_CreateDirEntry,
TransactedSharedImpl_WriteDirEntry,
TransactedSharedImpl_ReadDirEntry,
TransactedSharedImpl_DestroyDirEntry,
TransactedSharedImpl_StreamReadAt,
TransactedSharedImpl_StreamWriteAt,
TransactedSharedImpl_StreamSetSize,
TransactedSharedImpl_StreamLink,
TransactedSharedImpl_GetTransactionSig,
TransactedSharedImpl_SetTransactionSig,
TransactedSharedImpl_LockTransaction,
TransactedSharedImpl_UnlockTransaction
};
static HRESULT TransactedSharedImpl_Construct(StorageBaseImpl *parentStorage,
TransactedSharedImpl** result)
{
HRESULT hr;
*result = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(TransactedSharedImpl));
if (*result)
{
IStorage *scratch;
(*result)->base.IStorage_iface.lpVtbl = &TransactedSharedImpl_Vtbl;
/* This is OK because the property set storage functions use the IStorage functions. */
(*result)->base.IPropertySetStorage_iface.lpVtbl = parentStorage->IPropertySetStorage_iface.lpVtbl;
(*result)->base.baseVtbl = &TransactedSharedImpl_BaseVtbl;
list_init(&(*result)->base.strmHead);
list_init(&(*result)->base.storageHead);
(*result)->base.ref = 1;
(*result)->base.openFlags = parentStorage->openFlags;
hr = StorageBaseImpl_LockTransaction(parentStorage, FALSE);
if (SUCCEEDED(hr))
{
STGOPTIONS stgo;
/* This cannot fail, except with E_NOTIMPL in which case we don't care */
StorageBaseImpl_GetTransactionSig(parentStorage, &(*result)->lastTransactionSig, FALSE);
stgo.usVersion = 1;
stgo.reserved = 0;
stgo.ulSectorSize = 4096;
stgo.pwcsTemplateFile = NULL;
/* Create a new temporary storage to act as the scratch file. */
hr = StgCreateStorageEx(NULL, STGM_READWRITE|STGM_SHARE_EXCLUSIVE|STGM_CREATE|STGM_DELETEONRELEASE|STGM_TRANSACTED,
STGFMT_DOCFILE, 0, &stgo, NULL, &IID_IStorage, (void**)&scratch);
(*result)->scratch = (TransactedSnapshotImpl*)impl_from_IStorage(scratch);
if (SUCCEEDED(hr))
{
hr = StorageBaseImpl_CopyStorageTree(&(*result)->scratch->base, (*result)->scratch->base.storageDirEntry,
parentStorage, parentStorage->storageDirEntry);
if (SUCCEEDED(hr))
{
hr = IStorage_Commit(scratch, STGC_DEFAULT);
(*result)->base.storageDirEntry = (*result)->scratch->base.storageDirEntry;
(*result)->transactedParent = parentStorage;
}
if (FAILED(hr))
IStorage_Release(scratch);
}
StorageBaseImpl_UnlockTransaction(parentStorage, FALSE);
}
if (FAILED(hr)) HeapFree(GetProcessHeap(), 0, *result);
return hr;
}
else
return E_OUTOFMEMORY;
}
static HRESULT Storage_ConstructTransacted(StorageBaseImpl *parentStorage,
BOOL toplevel, StorageBaseImpl** result)
{
static int fixme_flags=STGM_NOSCRATCH|STGM_NOSNAPSHOT;
if (parentStorage->openFlags & fixme_flags)
{
fixme_flags &= ~parentStorage->openFlags;
FIXME("Unimplemented flags %x\n", parentStorage->openFlags);
}
if (toplevel && !(parentStorage->openFlags & STGM_NOSNAPSHOT) &&
STGM_SHARE_MODE(parentStorage->openFlags) != STGM_SHARE_DENY_WRITE &&
STGM_SHARE_MODE(parentStorage->openFlags) != STGM_SHARE_EXCLUSIVE)
{
/* Need to create a temp file for the snapshot */
return TransactedSharedImpl_Construct(parentStorage, (TransactedSharedImpl**)result);
}
return TransactedSnapshotImpl_Construct(parentStorage,
(TransactedSnapshotImpl**)result);
}
static HRESULT Storage_Construct(
HANDLE hFile,
LPCOLESTR pwcsName,
ILockBytes* pLkbyt,
DWORD openFlags,
BOOL fileBased,
BOOL create,
ULONG sector_size,
StorageBaseImpl** result)
{
StorageImpl *newStorage;
StorageBaseImpl *newTransactedStorage;
HRESULT hr;
hr = StorageImpl_Construct(hFile, pwcsName, pLkbyt, openFlags, fileBased, create, sector_size, &newStorage);
if (FAILED(hr)) goto end;
if (openFlags & STGM_TRANSACTED)
{
hr = Storage_ConstructTransacted(&newStorage->base, TRUE, &newTransactedStorage);
if (FAILED(hr))
IStorage_Release(&newStorage->base.IStorage_iface);
else
*result = newTransactedStorage;
}
else
*result = &newStorage->base;
end:
return hr;
}
/************************************************************************
* StorageUtl helper functions
***********************************************************************/
void StorageUtl_ReadWord(const BYTE* buffer, ULONG offset, WORD* value)
{
WORD tmp;
memcpy(&tmp, buffer+offset, sizeof(WORD));
*value = lendian16toh(tmp);
}
void StorageUtl_WriteWord(BYTE* buffer, ULONG offset, WORD value)
{
value = htole16(value);
memcpy(buffer+offset, &value, sizeof(WORD));
}
void StorageUtl_ReadDWord(const BYTE* buffer, ULONG offset, DWORD* value)
{
DWORD tmp;
memcpy(&tmp, buffer+offset, sizeof(DWORD));
*value = lendian32toh(tmp);
}
void StorageUtl_WriteDWord(BYTE* buffer, ULONG offset, DWORD value)
{
value = htole32(value);
memcpy(buffer+offset, &value, sizeof(DWORD));
}
void StorageUtl_ReadULargeInteger(const BYTE* buffer, ULONG offset,
ULARGE_INTEGER* value)
{
#ifdef WORDS_BIGENDIAN
ULARGE_INTEGER tmp;
memcpy(&tmp, buffer + offset, sizeof(ULARGE_INTEGER));
value->u.LowPart = htole32(tmp.u.HighPart);
value->u.HighPart = htole32(tmp.u.LowPart);
#else
memcpy(value, buffer + offset, sizeof(ULARGE_INTEGER));
#endif
}
void StorageUtl_WriteULargeInteger(BYTE* buffer, ULONG offset,
const ULARGE_INTEGER *value)
{
#ifdef WORDS_BIGENDIAN
ULARGE_INTEGER tmp;
tmp.u.LowPart = htole32(value->u.HighPart);
tmp.u.HighPart = htole32(value->u.LowPart);
memcpy(buffer + offset, &tmp, sizeof(ULARGE_INTEGER));
#else
memcpy(buffer + offset, value, sizeof(ULARGE_INTEGER));
#endif
}
void StorageUtl_ReadGUID(const BYTE* buffer, ULONG offset, GUID* value)
{
StorageUtl_ReadDWord(buffer, offset, &(value->Data1));
StorageUtl_ReadWord(buffer, offset+4, &(value->Data2));
StorageUtl_ReadWord(buffer, offset+6, &(value->Data3));
memcpy(value->Data4, buffer+offset+8, sizeof(value->Data4));
}
void StorageUtl_WriteGUID(BYTE* buffer, ULONG offset, const GUID* value)
{
StorageUtl_WriteDWord(buffer, offset, value->Data1);
StorageUtl_WriteWord(buffer, offset+4, value->Data2);
StorageUtl_WriteWord(buffer, offset+6, value->Data3);
memcpy(buffer+offset+8, value->Data4, sizeof(value->Data4));
}
void StorageUtl_CopyDirEntryToSTATSTG(
StorageBaseImpl* storage,
STATSTG* destination,
const DirEntry* source,
int statFlags)
{
/*
* The copy of the string occurs only when the flag is not set
*/
if (!(statFlags & STATFLAG_NONAME) && source->stgType == STGTY_ROOT)
{
/* Use the filename for the root storage. */
destination->pwcsName = 0;
StorageBaseImpl_GetFilename(storage, &destination->pwcsName);
}
else if( ((statFlags & STATFLAG_NONAME) != 0) ||
(source->name[0] == 0) )
{
destination->pwcsName = 0;
}
else
{
destination->pwcsName =
CoTaskMemAlloc((lstrlenW(source->name)+1)*sizeof(WCHAR));
lstrcpyW(destination->pwcsName, source->name);
}
switch (source->stgType)
{
case STGTY_STORAGE:
case STGTY_ROOT:
destination->type = STGTY_STORAGE;
break;
case STGTY_STREAM:
destination->type = STGTY_STREAM;
break;
default:
destination->type = STGTY_STREAM;
break;
}
destination->cbSize = source->size;
/*
currentReturnStruct->mtime = {0}; TODO
currentReturnStruct->ctime = {0};
currentReturnStruct->atime = {0};
*/
destination->grfMode = 0;
destination->grfLocksSupported = 0;
destination->clsid = source->clsid;
destination->grfStateBits = 0;
destination->reserved = 0;
}
/************************************************************************
* BlockChainStream implementation
***********************************************************************/
/******************************************************************************
* BlockChainStream_GetHeadOfChain
*
* Returns the head of this stream chain.
* Some special chains don't have directory entries, their heads are kept in
* This->headOfStreamPlaceHolder.
*
*/
static ULONG BlockChainStream_GetHeadOfChain(BlockChainStream* This)
{
DirEntry chainEntry;
HRESULT hr;
if (This->headOfStreamPlaceHolder != 0)
return *(This->headOfStreamPlaceHolder);
if (This->ownerDirEntry != DIRENTRY_NULL)
{
hr = StorageImpl_ReadDirEntry(
This->parentStorage,
This->ownerDirEntry,
&chainEntry);
if (SUCCEEDED(hr) && chainEntry.startingBlock < BLOCK_FIRST_SPECIAL)
return chainEntry.startingBlock;
}
return BLOCK_END_OF_CHAIN;
}
/* Read and save the index of all blocks in this stream. */
static HRESULT BlockChainStream_UpdateIndexCache(BlockChainStream* This)
{
ULONG next_sector, next_offset;
HRESULT hr;
struct BlockChainRun *last_run;
if (This->indexCacheLen == 0)
{
last_run = NULL;
next_offset = 0;
next_sector = BlockChainStream_GetHeadOfChain(This);
}
else
{
last_run = &This->indexCache[This->indexCacheLen-1];
next_offset = last_run->lastOffset+1;
hr = StorageImpl_GetNextBlockInChain(This->parentStorage,
last_run->firstSector + last_run->lastOffset - last_run->firstOffset,
&next_sector);
if (FAILED(hr)) return hr;
}
while (next_sector != BLOCK_END_OF_CHAIN)
{
if (!last_run || next_sector != last_run->firstSector + next_offset - last_run->firstOffset)
{
/* Add the current block to the cache. */
if (This->indexCacheSize == 0)
{
This->indexCache = HeapAlloc(GetProcessHeap(), 0, sizeof(struct BlockChainRun)*16);
if (!This->indexCache) return E_OUTOFMEMORY;
This->indexCacheSize = 16;
}
else if (This->indexCacheSize == This->indexCacheLen)
{
struct BlockChainRun *new_cache;
ULONG new_size;
new_size = This->indexCacheSize * 2;
new_cache = HeapAlloc(GetProcessHeap(), 0, sizeof(struct BlockChainRun)*new_size);
if (!new_cache) return E_OUTOFMEMORY;
memcpy(new_cache, This->indexCache, sizeof(struct BlockChainRun)*This->indexCacheLen);
HeapFree(GetProcessHeap(), 0, This->indexCache);
This->indexCache = new_cache;
This->indexCacheSize = new_size;
}
This->indexCacheLen++;
last_run = &This->indexCache[This->indexCacheLen-1];
last_run->firstSector = next_sector;
last_run->firstOffset = next_offset;
}
last_run->lastOffset = next_offset;
/* Find the next block. */
next_offset++;
hr = StorageImpl_GetNextBlockInChain(This->parentStorage, next_sector, &next_sector);
if (FAILED(hr)) return hr;
}
if (This->indexCacheLen)
{
This->tailIndex = last_run->firstSector + last_run->lastOffset - last_run->firstOffset;
This->numBlocks = last_run->lastOffset+1;
}
else
{
This->tailIndex = BLOCK_END_OF_CHAIN;
This->numBlocks = 0;
}
return S_OK;
}
/* Locate the nth block in this stream. */
static ULONG BlockChainStream_GetSectorOfOffset(BlockChainStream *This, ULONG offset)
{
ULONG min_offset = 0, max_offset = This->numBlocks-1;
ULONG min_run = 0, max_run = This->indexCacheLen-1;
if (offset >= This->numBlocks)
return BLOCK_END_OF_CHAIN;
while (min_run < max_run)
{
ULONG run_to_check = min_run + (offset - min_offset) * (max_run - min_run) / (max_offset - min_offset);
if (offset < This->indexCache[run_to_check].firstOffset)
{
max_offset = This->indexCache[run_to_check].firstOffset-1;
max_run = run_to_check-1;
}
else if (offset > This->indexCache[run_to_check].lastOffset)
{
min_offset = This->indexCache[run_to_check].lastOffset+1;
min_run = run_to_check+1;
}
else
/* Block is in this run. */
min_run = max_run = run_to_check;
}
return This->indexCache[min_run].firstSector + offset - This->indexCache[min_run].firstOffset;
}
static HRESULT BlockChainStream_GetBlockAtOffset(BlockChainStream *This,
ULONG index, BlockChainBlock **block, ULONG *sector, BOOL create)
{
BlockChainBlock *result=NULL;
int i;
for (i=0; i<2; i++)
if (This->cachedBlocks[i].index == index)
{
*sector = This->cachedBlocks[i].sector;
*block = &This->cachedBlocks[i];
return S_OK;
}
*sector = BlockChainStream_GetSectorOfOffset(This, index);
if (*sector == BLOCK_END_OF_CHAIN)
return STG_E_DOCFILECORRUPT;
if (create)
{
if (This->cachedBlocks[0].index == 0xffffffff)
result = &This->cachedBlocks[0];
else if (This->cachedBlocks[1].index == 0xffffffff)
result = &This->cachedBlocks[1];
else
{
result = &This->cachedBlocks[This->blockToEvict++];
if (This->blockToEvict == 2)
This->blockToEvict = 0;
}
if (result->dirty)
{
if (!StorageImpl_WriteBigBlock(This->parentStorage, result->sector, result->data))
return STG_E_WRITEFAULT;
result->dirty = FALSE;
}
result->read = FALSE;
result->index = index;
result->sector = *sector;
}
*block = result;
return S_OK;
}
BlockChainStream* BlockChainStream_Construct(
StorageImpl* parentStorage,
ULONG* headOfStreamPlaceHolder,
DirRef dirEntry)
{
BlockChainStream* newStream;
newStream = HeapAlloc(GetProcessHeap(), 0, sizeof(BlockChainStream));
2018-01-20 11:57:25 +00:00
if(!newStream)
return NULL;
newStream->parentStorage = parentStorage;
newStream->headOfStreamPlaceHolder = headOfStreamPlaceHolder;
newStream->ownerDirEntry = dirEntry;
newStream->indexCache = NULL;
newStream->indexCacheLen = 0;
newStream->indexCacheSize = 0;
newStream->cachedBlocks[0].index = 0xffffffff;
newStream->cachedBlocks[0].dirty = FALSE;
newStream->cachedBlocks[1].index = 0xffffffff;
newStream->cachedBlocks[1].dirty = FALSE;
newStream->blockToEvict = 0;
if (FAILED(BlockChainStream_UpdateIndexCache(newStream)))
{
HeapFree(GetProcessHeap(), 0, newStream->indexCache);
HeapFree(GetProcessHeap(), 0, newStream);
return NULL;
}
return newStream;
}
HRESULT BlockChainStream_Flush(BlockChainStream* This)
{
int i;
if (!This) return S_OK;
for (i=0; i<2; i++)
{
if (This->cachedBlocks[i].dirty)
{
if (StorageImpl_WriteBigBlock(This->parentStorage, This->cachedBlocks[i].sector, This->cachedBlocks[i].data))
This->cachedBlocks[i].dirty = FALSE;
else
return STG_E_WRITEFAULT;
}
}
return S_OK;
}
void BlockChainStream_Destroy(BlockChainStream* This)
{
if (This)
{
BlockChainStream_Flush(This);
HeapFree(GetProcessHeap(), 0, This->indexCache);
}
HeapFree(GetProcessHeap(), 0, This);
}
/******************************************************************************
* BlockChainStream_Shrink
*
* Shrinks this chain in the big block depot.
*/
static BOOL BlockChainStream_Shrink(BlockChainStream* This,
ULARGE_INTEGER newSize)
{
ULONG blockIndex;
ULONG numBlocks;
int i;
/*
* Figure out how many blocks are needed to contain the new size
*/
numBlocks = newSize.QuadPart / This->parentStorage->bigBlockSize;
if ((newSize.QuadPart % This->parentStorage->bigBlockSize) != 0)
numBlocks++;
if (numBlocks)
{
/*
* Go to the new end of chain
*/
blockIndex = BlockChainStream_GetSectorOfOffset(This, numBlocks-1);
/* Mark the new end of chain */
StorageImpl_SetNextBlockInChain(
This->parentStorage,
blockIndex,
BLOCK_END_OF_CHAIN);
This->tailIndex = blockIndex;
}
else
{
if (This->headOfStreamPlaceHolder != 0)
{
*This->headOfStreamPlaceHolder = BLOCK_END_OF_CHAIN;
}
else
{
DirEntry chainEntry;
assert(This->ownerDirEntry != DIRENTRY_NULL);
StorageImpl_ReadDirEntry(
This->parentStorage,
This->ownerDirEntry,
&chainEntry);
chainEntry.startingBlock = BLOCK_END_OF_CHAIN;
StorageImpl_WriteDirEntry(
This->parentStorage,
This->ownerDirEntry,
&chainEntry);
}
This->tailIndex = BLOCK_END_OF_CHAIN;
}
This->numBlocks = numBlocks;
/*
* Mark the extra blocks as free
*/
while (This->indexCacheLen && This->indexCache[This->indexCacheLen-1].lastOffset >= numBlocks)
{
struct BlockChainRun *last_run = &This->indexCache[This->indexCacheLen-1];
StorageImpl_FreeBigBlock(This->parentStorage,
last_run->firstSector + last_run->lastOffset - last_run->firstOffset);
if (last_run->lastOffset == last_run->firstOffset)
This->indexCacheLen--;
else
last_run->lastOffset--;
}
/*
* Reset the last accessed block cache.
*/
for (i=0; i<2; i++)
{
if (This->cachedBlocks[i].index >= numBlocks)
{
This->cachedBlocks[i].index = 0xffffffff;
This->cachedBlocks[i].dirty = FALSE;
}
}
return TRUE;
}
/******************************************************************************
* BlockChainStream_Enlarge
*
* Grows this chain in the big block depot.
*/
static BOOL BlockChainStream_Enlarge(BlockChainStream* This,
ULARGE_INTEGER newSize)
{
ULONG blockIndex, currentBlock;
ULONG newNumBlocks;
ULONG oldNumBlocks = 0;
blockIndex = BlockChainStream_GetHeadOfChain(This);
/*
* Empty chain. Create the head.
*/
if (blockIndex == BLOCK_END_OF_CHAIN)
{
blockIndex = StorageImpl_GetNextFreeBigBlock(This->parentStorage);
StorageImpl_SetNextBlockInChain(This->parentStorage,
blockIndex,
BLOCK_END_OF_CHAIN);
if (This->headOfStreamPlaceHolder != 0)
{
*(This->headOfStreamPlaceHolder) = blockIndex;
}
else
{
DirEntry chainEntry;
assert(This->ownerDirEntry != DIRENTRY_NULL);
StorageImpl_ReadDirEntry(
This->parentStorage,
This->ownerDirEntry,
&chainEntry);
chainEntry.startingBlock = blockIndex;
StorageImpl_WriteDirEntry(
This->parentStorage,
This->ownerDirEntry,
&chainEntry);
}
This->tailIndex = blockIndex;
This->numBlocks = 1;
}
/*
* Figure out how many blocks are needed to contain this stream
*/
newNumBlocks = newSize.QuadPart / This->parentStorage->bigBlockSize;
if ((newSize.QuadPart % This->parentStorage->bigBlockSize) != 0)
newNumBlocks++;
/*
* Go to the current end of chain
*/
if (This->tailIndex == BLOCK_END_OF_CHAIN)
{
currentBlock = blockIndex;
while (blockIndex != BLOCK_END_OF_CHAIN)
{
This->numBlocks++;
currentBlock = blockIndex;
if(FAILED(StorageImpl_GetNextBlockInChain(This->parentStorage, currentBlock,
&blockIndex)))
return FALSE;
}
This->tailIndex = currentBlock;
}
currentBlock = This->tailIndex;
oldNumBlocks = This->numBlocks;
/*
* Add new blocks to the chain
*/
if (oldNumBlocks < newNumBlocks)
{
while (oldNumBlocks < newNumBlocks)
{
blockIndex = StorageImpl_GetNextFreeBigBlock(This->parentStorage);
StorageImpl_SetNextBlockInChain(
This->parentStorage,
currentBlock,
blockIndex);
StorageImpl_SetNextBlockInChain(
This->parentStorage,
blockIndex,
BLOCK_END_OF_CHAIN);
currentBlock = blockIndex;
oldNumBlocks++;
}
This->tailIndex = blockIndex;
This->numBlocks = newNumBlocks;
}
if (FAILED(BlockChainStream_UpdateIndexCache(This)))
return FALSE;
return TRUE;
}
/******************************************************************************
* BlockChainStream_GetSize
*
* Returns the size of this chain.
* Will return the block count if this chain doesn't have a directory entry.
*/
static ULARGE_INTEGER BlockChainStream_GetSize(BlockChainStream* This)
{
DirEntry chainEntry;
if(This->headOfStreamPlaceHolder == NULL)
{
/*
* This chain has a directory entry so use the size value from there.
*/
StorageImpl_ReadDirEntry(
This->parentStorage,
This->ownerDirEntry,
&chainEntry);
return chainEntry.size;
}
else
{
/*
* this chain is a chain that does not have a directory entry, figure out the
* size by making the product number of used blocks times the
* size of them
*/
ULARGE_INTEGER result;
result.QuadPart =
(ULONGLONG)BlockChainStream_GetCount(This) *
This->parentStorage->bigBlockSize;
return result;
}
}
/******************************************************************************
* BlockChainStream_SetSize
*
* Sets the size of this stream. The big block depot will be updated.
* The file will grow if we grow the chain.
*
* TODO: Free the actual blocks in the file when we shrink the chain.
* Currently, the blocks are still in the file. So the file size
* doesn't shrink even if we shrink streams.
*/
BOOL BlockChainStream_SetSize(
BlockChainStream* This,
ULARGE_INTEGER newSize)
{
ULARGE_INTEGER size = BlockChainStream_GetSize(This);
if (newSize.QuadPart == size.QuadPart)
return TRUE;
if (newSize.QuadPart < size.QuadPart)
{
BlockChainStream_Shrink(This, newSize);
}
else
{
BlockChainStream_Enlarge(This, newSize);
}
return TRUE;
}
/******************************************************************************
* BlockChainStream_ReadAt
*
* Reads a specified number of bytes from this chain at the specified offset.
* bytesRead may be NULL.
* Failure will be returned if the specified number of bytes has not been read.
*/
HRESULT BlockChainStream_ReadAt(BlockChainStream* This,
ULARGE_INTEGER offset,
ULONG size,
void* buffer,
ULONG* bytesRead)
{
ULONG blockNoInSequence = offset.QuadPart / This->parentStorage->bigBlockSize;
ULONG offsetInBlock = offset.QuadPart % This->parentStorage->bigBlockSize;
ULONG bytesToReadInBuffer;
ULONG blockIndex;
BYTE* bufferWalker;
ULARGE_INTEGER stream_size;
HRESULT hr;
BlockChainBlock *cachedBlock;
TRACE("(%p)-> %i %p %i %p\n",This, offset.u.LowPart, buffer, size, bytesRead);
/*
* Find the first block in the stream that contains part of the buffer.
*/
blockIndex = BlockChainStream_GetSectorOfOffset(This, blockNoInSequence);
*bytesRead = 0;
stream_size = BlockChainStream_GetSize(This);
if (stream_size.QuadPart > offset.QuadPart)
size = min(stream_size.QuadPart - offset.QuadPart, size);
else
return S_OK;
/*
* Start reading the buffer.
*/
bufferWalker = buffer;
while (size > 0)
{
ULARGE_INTEGER ulOffset;
DWORD bytesReadAt;
/*
* Calculate how many bytes we can copy from this big block.
*/
bytesToReadInBuffer =
min(This->parentStorage->bigBlockSize - offsetInBlock, size);
hr = BlockChainStream_GetBlockAtOffset(This, blockNoInSequence, &cachedBlock, &blockIndex, size == bytesToReadInBuffer);
if (FAILED(hr))
return hr;
if (!cachedBlock)
{
/* Not in cache, and we're going to read past the end of the block. */
ulOffset.QuadPart = StorageImpl_GetBigBlockOffset(This->parentStorage, blockIndex) +
offsetInBlock;
StorageImpl_ReadAt(This->parentStorage,
ulOffset,
bufferWalker,
bytesToReadInBuffer,
&bytesReadAt);
}
else
{
if (!cachedBlock->read)
{
ULONG read;
if (FAILED(StorageImpl_ReadBigBlock(This->parentStorage, cachedBlock->sector, cachedBlock->data, &read)) && !read)
return STG_E_READFAULT;
cachedBlock->read = TRUE;
}
memcpy(bufferWalker, cachedBlock->data+offsetInBlock, bytesToReadInBuffer);
bytesReadAt = bytesToReadInBuffer;
}
blockNoInSequence++;
bufferWalker += bytesReadAt;
size -= bytesReadAt;
*bytesRead += bytesReadAt;
offsetInBlock = 0; /* There is no offset on the next block */
if (bytesToReadInBuffer != bytesReadAt)
break;
}
return S_OK;
}
/******************************************************************************
* BlockChainStream_WriteAt
*
* Writes the specified number of bytes to this chain at the specified offset.
* Will fail if not all specified number of bytes have been written.
*/
HRESULT BlockChainStream_WriteAt(BlockChainStream* This,
ULARGE_INTEGER offset,
ULONG size,
const void* buffer,
ULONG* bytesWritten)
{
ULONG blockNoInSequence = offset.QuadPart / This->parentStorage->bigBlockSize;
ULONG offsetInBlock = offset.QuadPart % This->parentStorage->bigBlockSize;
ULONG bytesToWrite;
ULONG blockIndex;
const BYTE* bufferWalker;
HRESULT hr;
BlockChainBlock *cachedBlock;
*bytesWritten = 0;
bufferWalker = buffer;
while (size > 0)
{
ULARGE_INTEGER ulOffset;
DWORD bytesWrittenAt;
/*
* Calculate how many bytes we can copy to this big block.
*/
bytesToWrite =
min(This->parentStorage->bigBlockSize - offsetInBlock, size);
hr = BlockChainStream_GetBlockAtOffset(This, blockNoInSequence, &cachedBlock, &blockIndex, size == bytesToWrite);
/* BlockChainStream_SetSize should have already been called to ensure we have
* enough blocks in the chain to write into */
if (FAILED(hr))
{
ERR("not enough blocks in chain to write data\n");
return hr;
}
Sync from Wine-20050830 to Wine-0_9_1: Francois Gouget <fgouget@free.fr> - Assorted spelling fixes. - Fix .spec declarations for functinos with 64bit arguments (reported by winapi_check). Mike Hearn <mh@codeweavers.com> - Robert Shearman <rob@codeweavers.com> Change stub manager to track the number of normal marshals instead of using the state machine so that multiple marshals of the same object and interface work correctly. Robert Shearman <rob@codeweavers.com> - Pass WM_QUIT to outer modal loop. - Fix the return codes during unmarshaling so that it returns failure codes instead of S_FALSE returned from IStream_Read. - Don't fail in CoRegisterClassObject if the class already exists and the REGCLS_MULTIPLEUSE flag is specified. - Fix ref-counting rules to match native DCOM Dlls. - Add exception handling for stubs. - Implement OleRegEnumVerbs. - The [string] attribute is only valid on byte, char and wchar_t types, so replace "unsigned short" by "WCHAR". - Implement OleIsRunning. - Add a stubbed out implementation of IAdviseSink and advise the delegate object to use it. - Initialize out pointer to NULL before access check in IStorage_CreateStorage. - WriteClassStg should return E_INVALIDARG if pstg is NULL instead of asserting. - ReadClassStg should return E_INVALIDARG if pstg is NULL instead of crashing and a NULL pclsid should cause it to return the same value. - Make the interfaces that should be supported by the data cache explicit so their is no confusion in this file as to what it should be implementing and what this object should implement. - Delegate some IOleObject methods to the server IOleObject if it is running. - Implement some IRunningObject functions that actually start the server and initialize it. - Remove redunant braces. - Compact multi-line comments that fit into one line. - Remove comments that state the obvious. - Remove extra brackets that are redundant because the -> operator binds tighter than &. - Change "this" to "This" to make code more like other interface implementations. - Remove redundant comparisons with NULL for pointers. - Re-arrange some functions and vtables so we don't have declarations for all of the functions in the file. - Fix a trace to refer to the object ID rather than the legacy MID. - Fix the error case of CoMarshalInterThreadInterface to release the stream. - Move all 16-bit functions to an appropriate 16-bit file. - Implement OLE object notifications, making sure to cope with the case of the advise holder not being created because no notifications are needed. - Implement a Stop function and use this to properly implement IOleObject_Close, IAdviseSink_OnClose and the destructor. - We shouldn't pass the application name into CreateProcess because the value stored in the registry could include arguments. - Extend COM_OpenKeyForCLSID to open a subkey and return an HRESULT. - Fix up the callers and reorganize CoGetClassObject to split out the inproc code into another function. Alex Villacís Lasso <a_villacis@palosanto.com> - Add NULL check for sinkInterface in DataCache_GetAdvise. - Add missing ! to fix a reversed condition check in OleCreateDefaultHandler, in order to match intent in comment. Vincent Béron <vberon@mecano.gme.usherb.ca> - Use the proper calling convention for 2 16-bit functions. Alexandre Julliard <julliard@winehq.org> - We are no longer generating .spec.c files. - Use a more portable scheme for storing the name of a critical section. - Fixed some traces to use the right printf format and avoid typecasts. - Removed unused debug channels. - We are no longer generating .dbg.c files. Richard Cohen <richard@daijobu.co.uk> - Relax the share mode validation for transacted storage, with a test. Eric Pouech <eric.pouech@wanadoo.fr> - Fixes for function prototypes without arguments. Michael Jung <mjung@iss.tu-darmstadt.de> - Added tracing to CoCreateInstance. Markus Amsler <markus.amsler@oribi.org> - Improve c2man Documented-Total count. svn path=/trunk/; revision=19370
2005-11-20 15:01:10 +00:00
if (!cachedBlock)
{
/* Not in cache, and we're going to write past the end of the block. */
ulOffset.QuadPart = StorageImpl_GetBigBlockOffset(This->parentStorage, blockIndex) +
offsetInBlock;
StorageImpl_WriteAt(This->parentStorage,
ulOffset,
bufferWalker,
bytesToWrite,
&bytesWrittenAt);
}
else
{
if (!cachedBlock->read && bytesToWrite != This->parentStorage->bigBlockSize)
{
ULONG read;
if (FAILED(StorageImpl_ReadBigBlock(This->parentStorage, cachedBlock->sector, cachedBlock->data, &read)) && !read)
return STG_E_READFAULT;
}
memcpy(cachedBlock->data+offsetInBlock, bufferWalker, bytesToWrite);
bytesWrittenAt = bytesToWrite;
cachedBlock->read = TRUE;
cachedBlock->dirty = TRUE;
}
blockNoInSequence++;
bufferWalker += bytesWrittenAt;
size -= bytesWrittenAt;
*bytesWritten += bytesWrittenAt;
offsetInBlock = 0; /* There is no offset on the next block */
if (bytesWrittenAt != bytesToWrite)
break;
}
return (size == 0) ? S_OK : STG_E_WRITEFAULT;
}
/************************************************************************
* SmallBlockChainStream implementation
***********************************************************************/
SmallBlockChainStream* SmallBlockChainStream_Construct(
StorageImpl* parentStorage,
ULONG* headOfStreamPlaceHolder,
DirRef dirEntry)
{
SmallBlockChainStream* newStream;
newStream = HeapAlloc(GetProcessHeap(), 0, sizeof(SmallBlockChainStream));
newStream->parentStorage = parentStorage;
newStream->headOfStreamPlaceHolder = headOfStreamPlaceHolder;
newStream->ownerDirEntry = dirEntry;
return newStream;
}
void SmallBlockChainStream_Destroy(
SmallBlockChainStream* This)
{
HeapFree(GetProcessHeap(), 0, This);
}
/******************************************************************************
* SmallBlockChainStream_GetHeadOfChain
*
* Returns the head of this chain of small blocks.
*/
static ULONG SmallBlockChainStream_GetHeadOfChain(
SmallBlockChainStream* This)
{
DirEntry chainEntry;
HRESULT hr;
if (This->headOfStreamPlaceHolder != NULL)
return *(This->headOfStreamPlaceHolder);
if (This->ownerDirEntry)
{
hr = StorageImpl_ReadDirEntry(
This->parentStorage,
This->ownerDirEntry,
&chainEntry);
if (SUCCEEDED(hr) && chainEntry.startingBlock < BLOCK_FIRST_SPECIAL)
return chainEntry.startingBlock;
}
return BLOCK_END_OF_CHAIN;
}
/******************************************************************************
* SmallBlockChainStream_GetNextBlockInChain
*
* Returns the index of the next small block in this chain.
*
* Return Values:
* - BLOCK_END_OF_CHAIN: end of this chain
* - BLOCK_UNUSED: small block 'blockIndex' is free
*/
static HRESULT SmallBlockChainStream_GetNextBlockInChain(
SmallBlockChainStream* This,
ULONG blockIndex,
ULONG* nextBlockInChain)
{
ULARGE_INTEGER offsetOfBlockInDepot;
DWORD buffer;
ULONG bytesRead;
HRESULT res;
*nextBlockInChain = BLOCK_END_OF_CHAIN;
offsetOfBlockInDepot.QuadPart = (ULONGLONG)blockIndex * sizeof(ULONG);
/*
* Read those bytes in the buffer from the small block file.
*/
res = BlockChainStream_ReadAt(
This->parentStorage->smallBlockDepotChain,
offsetOfBlockInDepot,
sizeof(DWORD),
&buffer,
&bytesRead);
if (SUCCEEDED(res) && bytesRead != sizeof(DWORD))
res = STG_E_READFAULT;
if (SUCCEEDED(res))
{
StorageUtl_ReadDWord((BYTE *)&buffer, 0, nextBlockInChain);
return S_OK;
}
return res;
}
/******************************************************************************
* SmallBlockChainStream_SetNextBlockInChain
*
* Writes the index of the next block of the specified block in the small
* block depot.
* To set the end of chain use BLOCK_END_OF_CHAIN as nextBlock.
* To flag a block as free use BLOCK_UNUSED as nextBlock.
*/
static void SmallBlockChainStream_SetNextBlockInChain(
SmallBlockChainStream* This,
ULONG blockIndex,
ULONG nextBlock)
{
ULARGE_INTEGER offsetOfBlockInDepot;
DWORD buffer;
ULONG bytesWritten;
offsetOfBlockInDepot.QuadPart = (ULONGLONG)blockIndex * sizeof(ULONG);
StorageUtl_WriteDWord((BYTE *)&buffer, 0, nextBlock);
/*
* Read those bytes in the buffer from the small block file.
*/
BlockChainStream_WriteAt(
This->parentStorage->smallBlockDepotChain,
offsetOfBlockInDepot,
sizeof(DWORD),
&buffer,
&bytesWritten);
}
/******************************************************************************
* SmallBlockChainStream_FreeBlock
*
* Flag small block 'blockIndex' as free in the small block depot.
*/
static void SmallBlockChainStream_FreeBlock(
SmallBlockChainStream* This,
ULONG blockIndex)
{
SmallBlockChainStream_SetNextBlockInChain(This, blockIndex, BLOCK_UNUSED);
}
/******************************************************************************
* SmallBlockChainStream_GetNextFreeBlock
*
* Returns the index of a free small block. The small block depot will be
* enlarged if necessary. The small block chain will also be enlarged if
* necessary.
*/
static ULONG SmallBlockChainStream_GetNextFreeBlock(
SmallBlockChainStream* This)
{
ULARGE_INTEGER offsetOfBlockInDepot;
DWORD buffer;
ULONG bytesRead;
ULONG blockIndex = This->parentStorage->firstFreeSmallBlock;
ULONG nextBlockIndex = BLOCK_END_OF_CHAIN;
HRESULT res = S_OK;
ULONG smallBlocksPerBigBlock;
DirEntry rootEntry;
ULONG blocksRequired;
ULARGE_INTEGER old_size, size_required;
offsetOfBlockInDepot.u.HighPart = 0;
/*
* Scan the small block depot for a free block
*/
while (nextBlockIndex != BLOCK_UNUSED)
{
offsetOfBlockInDepot.QuadPart = (ULONGLONG)blockIndex * sizeof(ULONG);
res = BlockChainStream_ReadAt(
This->parentStorage->smallBlockDepotChain,
offsetOfBlockInDepot,
sizeof(DWORD),
&buffer,
&bytesRead);
/*
* If we run out of space for the small block depot, enlarge it
*/
if (SUCCEEDED(res) && bytesRead == sizeof(DWORD))
{
StorageUtl_ReadDWord((BYTE *)&buffer, 0, &nextBlockIndex);
if (nextBlockIndex != BLOCK_UNUSED)
blockIndex++;
}
else
{
ULONG count =
BlockChainStream_GetCount(This->parentStorage->smallBlockDepotChain);
BYTE smallBlockDepot[MAX_BIG_BLOCK_SIZE];
ULARGE_INTEGER newSize, offset;
ULONG bytesWritten;
newSize.QuadPart = (ULONGLONG)(count + 1) * This->parentStorage->bigBlockSize;
BlockChainStream_Enlarge(This->parentStorage->smallBlockDepotChain, newSize);
/*
* Initialize all the small blocks to free
*/
memset(smallBlockDepot, BLOCK_UNUSED, This->parentStorage->bigBlockSize);
offset.QuadPart = (ULONGLONG)count * This->parentStorage->bigBlockSize;
BlockChainStream_WriteAt(This->parentStorage->smallBlockDepotChain,
offset, This->parentStorage->bigBlockSize, smallBlockDepot, &bytesWritten);
StorageImpl_SaveFileHeader(This->parentStorage);
}
}
This->parentStorage->firstFreeSmallBlock = blockIndex+1;
smallBlocksPerBigBlock =
This->parentStorage->bigBlockSize / This->parentStorage->smallBlockSize;
/*
* Verify if we have to allocate big blocks to contain small blocks
*/
blocksRequired = (blockIndex / smallBlocksPerBigBlock) + 1;
size_required.QuadPart = (ULONGLONG)blocksRequired * This->parentStorage->bigBlockSize;
old_size = BlockChainStream_GetSize(This->parentStorage->smallBlockRootChain);
if (size_required.QuadPart > old_size.QuadPart)
{
BlockChainStream_SetSize(
This->parentStorage->smallBlockRootChain,
size_required);
StorageImpl_ReadDirEntry(
This->parentStorage,
This->parentStorage->base.storageDirEntry,
&rootEntry);
rootEntry.size = size_required;
StorageImpl_WriteDirEntry(
This->parentStorage,
This->parentStorage->base.storageDirEntry,
&rootEntry);
}
return blockIndex;
}
/******************************************************************************
* SmallBlockChainStream_ReadAt
*
* Reads a specified number of bytes from this chain at the specified offset.
* bytesRead may be NULL.
* Failure will be returned if the specified number of bytes has not been read.
*/
HRESULT SmallBlockChainStream_ReadAt(
SmallBlockChainStream* This,
ULARGE_INTEGER offset,
ULONG size,
void* buffer,
ULONG* bytesRead)
{
HRESULT rc = S_OK;
ULARGE_INTEGER offsetInBigBlockFile;
ULONG blockNoInSequence =
offset.u.LowPart / This->parentStorage->smallBlockSize;
ULONG offsetInBlock = offset.u.LowPart % This->parentStorage->smallBlockSize;
ULONG bytesToReadInBuffer;
ULONG blockIndex;
ULONG bytesReadFromBigBlockFile;
BYTE* bufferWalker;
ULARGE_INTEGER stream_size;
/*
* This should never happen on a small block file.
*/
assert(offset.u.HighPart==0);
*bytesRead = 0;
stream_size = SmallBlockChainStream_GetSize(This);
if (stream_size.QuadPart > offset.QuadPart)
size = min(stream_size.QuadPart - offset.QuadPart, size);
else
return S_OK;
/*
* Find the first block in the stream that contains part of the buffer.
*/
blockIndex = SmallBlockChainStream_GetHeadOfChain(This);
while ( (blockNoInSequence > 0) && (blockIndex != BLOCK_END_OF_CHAIN))
{
rc = SmallBlockChainStream_GetNextBlockInChain(This, blockIndex, &blockIndex);
if(FAILED(rc))
return rc;
blockNoInSequence--;
}
/*
* Start reading the buffer.
*/
bufferWalker = buffer;
while ( (size > 0) && (blockIndex != BLOCK_END_OF_CHAIN) )
{
/*
* Calculate how many bytes we can copy from this small block.
*/
bytesToReadInBuffer =
min(This->parentStorage->smallBlockSize - offsetInBlock, size);
/*
* Calculate the offset of the small block in the small block file.
*/
offsetInBigBlockFile.QuadPart =
(ULONGLONG)blockIndex * This->parentStorage->smallBlockSize;
offsetInBigBlockFile.QuadPart += offsetInBlock;
/*
* Read those bytes in the buffer from the small block file.
* The small block has already been identified so it shouldn't fail
* unless the file is corrupt.
*/
rc = BlockChainStream_ReadAt(This->parentStorage->smallBlockRootChain,
offsetInBigBlockFile,
bytesToReadInBuffer,
bufferWalker,
&bytesReadFromBigBlockFile);
if (FAILED(rc))
return rc;
if (!bytesReadFromBigBlockFile)
return STG_E_DOCFILECORRUPT;
/*
* Step to the next big block.
*/
rc = SmallBlockChainStream_GetNextBlockInChain(This, blockIndex, &blockIndex);
if(FAILED(rc))
return STG_E_DOCFILECORRUPT;
bufferWalker += bytesReadFromBigBlockFile;
size -= bytesReadFromBigBlockFile;
*bytesRead += bytesReadFromBigBlockFile;
offsetInBlock = (offsetInBlock + bytesReadFromBigBlockFile) % This->parentStorage->smallBlockSize;
}
return S_OK;
}
/******************************************************************************
* SmallBlockChainStream_WriteAt
*
* Writes the specified number of bytes to this chain at the specified offset.
* Will fail if not all specified number of bytes have been written.
*/
HRESULT SmallBlockChainStream_WriteAt(
SmallBlockChainStream* This,
ULARGE_INTEGER offset,
ULONG size,
const void* buffer,
ULONG* bytesWritten)
{
ULARGE_INTEGER offsetInBigBlockFile;
ULONG blockNoInSequence =
offset.u.LowPart / This->parentStorage->smallBlockSize;
ULONG offsetInBlock = offset.u.LowPart % This->parentStorage->smallBlockSize;
ULONG bytesToWriteInBuffer;
ULONG blockIndex;
ULONG bytesWrittenToBigBlockFile;
const BYTE* bufferWalker;
HRESULT res;
/*
* This should never happen on a small block file.
*/
assert(offset.u.HighPart==0);
/*
* Find the first block in the stream that contains part of the buffer.
*/
blockIndex = SmallBlockChainStream_GetHeadOfChain(This);
while ( (blockNoInSequence > 0) && (blockIndex != BLOCK_END_OF_CHAIN))
{
if(FAILED(SmallBlockChainStream_GetNextBlockInChain(This, blockIndex, &blockIndex)))
return STG_E_DOCFILECORRUPT;
blockNoInSequence--;
}
/*
* Start writing the buffer.
*/
*bytesWritten = 0;
bufferWalker = buffer;
while ( (size > 0) && (blockIndex != BLOCK_END_OF_CHAIN) )
{
/*
* Calculate how many bytes we can copy to this small block.
*/
bytesToWriteInBuffer =
min(This->parentStorage->smallBlockSize - offsetInBlock, size);
/*
* Calculate the offset of the small block in the small block file.
*/
offsetInBigBlockFile.QuadPart =
(ULONGLONG)blockIndex * This->parentStorage->smallBlockSize;
offsetInBigBlockFile.QuadPart += offsetInBlock;
/*
* Write those bytes in the buffer to the small block file.
*/
res = BlockChainStream_WriteAt(
This->parentStorage->smallBlockRootChain,
offsetInBigBlockFile,
bytesToWriteInBuffer,
bufferWalker,
&bytesWrittenToBigBlockFile);
if (FAILED(res))
return res;
/*
* Step to the next big block.
*/
res = SmallBlockChainStream_GetNextBlockInChain(This, blockIndex, &blockIndex);
if (FAILED(res))
return res;
bufferWalker += bytesWrittenToBigBlockFile;
size -= bytesWrittenToBigBlockFile;
*bytesWritten += bytesWrittenToBigBlockFile;
offsetInBlock = (offsetInBlock + bytesWrittenToBigBlockFile) % This->parentStorage->smallBlockSize;
}
return (size == 0) ? S_OK : STG_E_WRITEFAULT;
}
/******************************************************************************
* SmallBlockChainStream_Shrink
*
* Shrinks this chain in the small block depot.
*/
static BOOL SmallBlockChainStream_Shrink(
SmallBlockChainStream* This,
ULARGE_INTEGER newSize)
{
ULONG blockIndex, extraBlock;
ULONG numBlocks;
ULONG count = 0;
numBlocks = newSize.u.LowPart / This->parentStorage->smallBlockSize;
if ((newSize.u.LowPart % This->parentStorage->smallBlockSize) != 0)
numBlocks++;
blockIndex = SmallBlockChainStream_GetHeadOfChain(This);
/*
* Go to the new end of chain
*/
while (count < numBlocks)
{
if(FAILED(SmallBlockChainStream_GetNextBlockInChain(This, blockIndex,
&blockIndex)))
return FALSE;
count++;
}
/*
* If the count is 0, we have a special case, the head of the chain was
* just freed.
*/
if (count == 0)
{
DirEntry chainEntry;
StorageImpl_ReadDirEntry(This->parentStorage,
This->ownerDirEntry,
&chainEntry);
chainEntry.startingBlock = BLOCK_END_OF_CHAIN;
StorageImpl_WriteDirEntry(This->parentStorage,
This->ownerDirEntry,
&chainEntry);
/*
* We start freeing the chain at the head block.
*/
extraBlock = blockIndex;
}
else
{
/* Get the next block before marking the new end */
if(FAILED(SmallBlockChainStream_GetNextBlockInChain(This, blockIndex,
&extraBlock)))
return FALSE;
/* Mark the new end of chain */
SmallBlockChainStream_SetNextBlockInChain(
This,
blockIndex,
BLOCK_END_OF_CHAIN);
}
/*
* Mark the extra blocks as free
*/
while (extraBlock != BLOCK_END_OF_CHAIN)
{
if(FAILED(SmallBlockChainStream_GetNextBlockInChain(This, extraBlock,
&blockIndex)))
return FALSE;
SmallBlockChainStream_FreeBlock(This, extraBlock);
This->parentStorage->firstFreeSmallBlock = min(This->parentStorage->firstFreeSmallBlock, extraBlock);
extraBlock = blockIndex;
}
return TRUE;
}
/******************************************************************************
* SmallBlockChainStream_Enlarge
*
* Grows this chain in the small block depot.
*/
static BOOL SmallBlockChainStream_Enlarge(
SmallBlockChainStream* This,
ULARGE_INTEGER newSize)
{
ULONG blockIndex, currentBlock;
ULONG newNumBlocks;
ULONG oldNumBlocks = 0;
blockIndex = SmallBlockChainStream_GetHeadOfChain(This);
/*
* Empty chain. Create the head.
*/
if (blockIndex == BLOCK_END_OF_CHAIN)
{
blockIndex = SmallBlockChainStream_GetNextFreeBlock(This);
SmallBlockChainStream_SetNextBlockInChain(
This,
blockIndex,
BLOCK_END_OF_CHAIN);
if (This->headOfStreamPlaceHolder != NULL)
{
*(This->headOfStreamPlaceHolder) = blockIndex;
}
else
{
DirEntry chainEntry;
StorageImpl_ReadDirEntry(This->parentStorage, This->ownerDirEntry,
&chainEntry);
chainEntry.startingBlock = blockIndex;
StorageImpl_WriteDirEntry(This->parentStorage, This->ownerDirEntry,
&chainEntry);
}
}
currentBlock = blockIndex;
/*
* Figure out how many blocks are needed to contain this stream
*/
newNumBlocks = newSize.u.LowPart / This->parentStorage->smallBlockSize;
if ((newSize.u.LowPart % This->parentStorage->smallBlockSize) != 0)
newNumBlocks++;
/*
* Go to the current end of chain
*/
while (blockIndex != BLOCK_END_OF_CHAIN)
{
oldNumBlocks++;
currentBlock = blockIndex;
if(FAILED(SmallBlockChainStream_GetNextBlockInChain(This, currentBlock, &blockIndex)))
return FALSE;
}
/*
* Add new blocks to the chain
*/
while (oldNumBlocks < newNumBlocks)
{
blockIndex = SmallBlockChainStream_GetNextFreeBlock(This);
SmallBlockChainStream_SetNextBlockInChain(This, currentBlock, blockIndex);
SmallBlockChainStream_SetNextBlockInChain(
This,
blockIndex,
BLOCK_END_OF_CHAIN);
currentBlock = blockIndex;
oldNumBlocks++;
}
return TRUE;
}
/******************************************************************************
* SmallBlockChainStream_SetSize
*
* Sets the size of this stream.
* The file will grow if we grow the chain.
*
* TODO: Free the actual blocks in the file when we shrink the chain.
* Currently, the blocks are still in the file. So the file size
* doesn't shrink even if we shrink streams.
*/
BOOL SmallBlockChainStream_SetSize(
SmallBlockChainStream* This,
ULARGE_INTEGER newSize)
{
ULARGE_INTEGER size = SmallBlockChainStream_GetSize(This);
if (newSize.u.LowPart == size.u.LowPart)
return TRUE;
if (newSize.u.LowPart < size.u.LowPart)
{
SmallBlockChainStream_Shrink(This, newSize);
}
else
{
SmallBlockChainStream_Enlarge(This, newSize);
}
return TRUE;
}
/******************************************************************************
* SmallBlockChainStream_GetCount
*
* Returns the number of small blocks that comprises this chain.
* This is not the size of the stream as the last block may not be full!
*
*/
static ULONG SmallBlockChainStream_GetCount(SmallBlockChainStream* This)
{
ULONG blockIndex;
ULONG count = 0;
blockIndex = SmallBlockChainStream_GetHeadOfChain(This);
while(blockIndex != BLOCK_END_OF_CHAIN)
{
count++;
if(FAILED(SmallBlockChainStream_GetNextBlockInChain(This,
blockIndex, &blockIndex)))
return 0;
}
return count;
}
/******************************************************************************
* SmallBlockChainStream_GetSize
*
* Returns the size of this chain.
*/
static ULARGE_INTEGER SmallBlockChainStream_GetSize(SmallBlockChainStream* This)
{
DirEntry chainEntry;
if(This->headOfStreamPlaceHolder != NULL)
{
ULARGE_INTEGER result;
result.u.HighPart = 0;
result.u.LowPart = SmallBlockChainStream_GetCount(This) *
This->parentStorage->smallBlockSize;
return result;
}
StorageImpl_ReadDirEntry(
This->parentStorage,
This->ownerDirEntry,
&chainEntry);
return chainEntry.size;
}
/************************************************************************
* Miscellaneous storage functions
***********************************************************************/
static HRESULT create_storagefile(
LPCOLESTR pwcsName,
DWORD grfMode,
DWORD grfAttrs,
STGOPTIONS* pStgOptions,
REFIID riid,
void** ppstgOpen)
{
StorageBaseImpl* newStorage = 0;
HANDLE hFile = INVALID_HANDLE_VALUE;
HRESULT hr = STG_E_INVALIDFLAG;
DWORD shareMode;
DWORD accessMode;
DWORD creationMode;
DWORD fileAttributes;
WCHAR tempFileName[MAX_PATH];
if (ppstgOpen == 0)
return STG_E_INVALIDPOINTER;
if (pStgOptions->ulSectorSize != MIN_BIG_BLOCK_SIZE && pStgOptions->ulSectorSize != MAX_BIG_BLOCK_SIZE)
return STG_E_INVALIDPARAMETER;
/* if no share mode given then DENY_NONE is the default */
if (STGM_SHARE_MODE(grfMode) == 0)
grfMode |= STGM_SHARE_DENY_NONE;
if ( FAILED( validateSTGM(grfMode) ))
goto end;
/* StgCreateDocFile seems to refuse readonly access, despite MSDN */
switch(STGM_ACCESS_MODE(grfMode))
{
case STGM_WRITE:
case STGM_READWRITE:
break;
default:
goto end;
}
/* in direct mode, can only use SHARE_EXCLUSIVE */
if (!(grfMode & STGM_TRANSACTED) && (STGM_SHARE_MODE(grfMode) != STGM_SHARE_EXCLUSIVE))
goto end;
/* but in transacted mode, any share mode is valid */
/*
* Generate a unique name.
*/
if (pwcsName == 0)
{
WCHAR tempPath[MAX_PATH];
static const WCHAR prefix[] = { 'S', 'T', 'O', 0 };
memset(tempPath, 0, sizeof(tempPath));
memset(tempFileName, 0, sizeof(tempFileName));
if ((GetTempPathW(MAX_PATH, tempPath)) == 0 )
tempPath[0] = '.';
if (GetTempFileNameW(tempPath, prefix, 0, tempFileName) != 0)
pwcsName = tempFileName;
else
{
hr = STG_E_INSUFFICIENTMEMORY;
goto end;
}
creationMode = TRUNCATE_EXISTING;
}
else
{
creationMode = GetCreationModeFromSTGM(grfMode);
}
/*
* Interpret the STGM value grfMode
*/
shareMode = GetShareModeFromSTGM(grfMode);
accessMode = GetAccessModeFromSTGM(grfMode);
if (grfMode & STGM_DELETEONRELEASE)
fileAttributes = FILE_FLAG_RANDOM_ACCESS | FILE_FLAG_DELETE_ON_CLOSE;
else
fileAttributes = FILE_ATTRIBUTE_NORMAL | FILE_FLAG_RANDOM_ACCESS;
*ppstgOpen = 0;
hFile = CreateFileW(pwcsName,
accessMode,
shareMode,
NULL,
creationMode,
fileAttributes,
0);
if (hFile == INVALID_HANDLE_VALUE)
{
if(GetLastError() == ERROR_FILE_EXISTS)
hr = STG_E_FILEALREADYEXISTS;
else
hr = E_FAIL;
goto end;
}
/*
* Allocate and initialize the new IStorage object.
*/
hr = Storage_Construct(
hFile,
pwcsName,
NULL,
grfMode,
TRUE,
TRUE,
pStgOptions->ulSectorSize,
&newStorage);
if (FAILED(hr))
{
goto end;
}
hr = IStorage_QueryInterface(&newStorage->IStorage_iface, riid, ppstgOpen);
IStorage_Release(&newStorage->IStorage_iface);
end:
TRACE("<-- %p r = %08x\n", *ppstgOpen, hr);
return hr;
}
/******************************************************************************
* StgCreateDocfile [OLE32.@]
* Creates a new compound file storage object
*
* PARAMS
* pwcsName [ I] Unicode string with filename (can be relative or NULL)
* grfMode [ I] Access mode for opening the new storage object (see STGM_ constants)
* reserved [ ?] unused?, usually 0
* ppstgOpen [IO] A pointer to IStorage pointer to the new onject
*
* RETURNS
* S_OK if the file was successfully created
* some STG_E_ value if error
* NOTES
* if pwcsName is NULL, create file with new unique name
* the function can returns
* STG_S_CONVERTED if the specified file was successfully converted to storage format
* (unrealized now)
*/
HRESULT WINAPI StgCreateDocfile(
LPCOLESTR pwcsName,
DWORD grfMode,
DWORD reserved,
IStorage **ppstgOpen)
{
STGOPTIONS stgoptions = {1, 0, 512};
TRACE("(%s, %x, %d, %p)\n",
debugstr_w(pwcsName), grfMode,
reserved, ppstgOpen);
if (ppstgOpen == 0)
return STG_E_INVALIDPOINTER;
if (reserved != 0)
return STG_E_INVALIDPARAMETER;
return create_storagefile(pwcsName, grfMode, 0, &stgoptions, &IID_IStorage, (void**)ppstgOpen);
}
/******************************************************************************
* StgCreateStorageEx [OLE32.@]
*/
HRESULT WINAPI StgCreateStorageEx(const WCHAR* pwcsName, DWORD grfMode, DWORD stgfmt, DWORD grfAttrs, STGOPTIONS* pStgOptions, void* reserved, REFIID riid, void** ppObjectOpen)
{
TRACE("(%s, %x, %x, %x, %p, %p, %p, %p)\n", debugstr_w(pwcsName),
grfMode, stgfmt, grfAttrs, pStgOptions, reserved, riid, ppObjectOpen);
if (stgfmt != STGFMT_FILE && grfAttrs != 0)
{
ERR("grfAttrs must be 0 if stgfmt != STGFMT_FILE\n");
return STG_E_INVALIDPARAMETER;
}
if (stgfmt == STGFMT_FILE && grfAttrs != 0 && grfAttrs != FILE_FLAG_NO_BUFFERING)
{
ERR("grfAttrs must be 0 or FILE_FLAG_NO_BUFFERING if stgfmt == STGFMT_FILE\n");
return STG_E_INVALIDPARAMETER;
}
if (stgfmt == STGFMT_FILE)
{
ERR("Cannot use STGFMT_FILE - this is NTFS only\n");
return STG_E_INVALIDPARAMETER;
}
if (stgfmt == STGFMT_STORAGE || stgfmt == STGFMT_DOCFILE)
{
STGOPTIONS defaultOptions = {1, 0, 512};
if (!pStgOptions) pStgOptions = &defaultOptions;
return create_storagefile(pwcsName, grfMode, grfAttrs, pStgOptions, riid, ppObjectOpen);
}
ERR("Invalid stgfmt argument\n");
return STG_E_INVALIDPARAMETER;
}
/******************************************************************************
* StgCreatePropSetStg [OLE32.@]
*/
HRESULT WINAPI StgCreatePropSetStg(IStorage *pstg, DWORD reserved,
IPropertySetStorage **propset)
{
TRACE("(%p, 0x%x, %p)\n", pstg, reserved, propset);
if (reserved)
return STG_E_INVALIDPARAMETER;
return IStorage_QueryInterface(pstg, &IID_IPropertySetStorage, (void**)propset);
}
/******************************************************************************
* StgOpenStorageEx [OLE32.@]
*/
HRESULT WINAPI StgOpenStorageEx(const WCHAR* pwcsName, DWORD grfMode, DWORD stgfmt, DWORD grfAttrs, STGOPTIONS* pStgOptions, void* reserved, REFIID riid, void** ppObjectOpen)
{
TRACE("(%s, %x, %x, %x, %p, %p, %p, %p)\n", debugstr_w(pwcsName),
grfMode, stgfmt, grfAttrs, pStgOptions, reserved, riid, ppObjectOpen);
if (stgfmt != STGFMT_DOCFILE && grfAttrs != 0)
{
ERR("grfAttrs must be 0 if stgfmt != STGFMT_DOCFILE\n");
return STG_E_INVALIDPARAMETER;
}
switch (stgfmt)
{
case STGFMT_FILE:
ERR("Cannot use STGFMT_FILE - this is NTFS only\n");
return STG_E_INVALIDPARAMETER;
case STGFMT_STORAGE:
break;
case STGFMT_DOCFILE:
if (grfAttrs && grfAttrs != FILE_FLAG_NO_BUFFERING)
{
ERR("grfAttrs must be 0 or FILE_FLAG_NO_BUFFERING if stgfmt == STGFMT_DOCFILE\n");
return STG_E_INVALIDPARAMETER;
}
FIXME("Stub: calling StgOpenStorage, but ignoring pStgOptions and grfAttrs\n");
break;
case STGFMT_ANY:
WARN("STGFMT_ANY assuming storage\n");
break;
default:
return STG_E_INVALIDPARAMETER;
}
return StgOpenStorage(pwcsName, NULL, grfMode, NULL, 0, (IStorage **)ppObjectOpen);
}
/******************************************************************************
* StgOpenStorage [OLE32.@]
*/
HRESULT WINAPI StgOpenStorage(
const OLECHAR *pwcsName,
IStorage *pstgPriority,
DWORD grfMode,
SNB snbExclude,
DWORD reserved,
IStorage **ppstgOpen)
{
StorageBaseImpl* newStorage = 0;
HRESULT hr = S_OK;
HANDLE hFile = 0;
DWORD shareMode;
DWORD accessMode;
LPWSTR temp_name = NULL;
TRACE("(%s, %p, %x, %p, %d, %p)\n",
debugstr_w(pwcsName), pstgPriority, grfMode,
snbExclude, reserved, ppstgOpen);
if (pstgPriority)
{
/* FIXME: Copy ILockBytes instead? But currently for STGM_PRIORITY it'll be read-only. */
hr = StorageBaseImpl_GetFilename((StorageBaseImpl*)pstgPriority, &temp_name);
if (FAILED(hr)) goto end;
pwcsName = temp_name;
TRACE("using filename %s\n", debugstr_w(temp_name));
}
if (pwcsName == 0)
{
hr = STG_E_INVALIDNAME;
goto end;
}
if (ppstgOpen == 0)
{
hr = STG_E_INVALIDPOINTER;
goto end;
}
if (reserved)
{
hr = STG_E_INVALIDPARAMETER;
goto end;
}
if (grfMode & STGM_PRIORITY)
{
if (grfMode & (STGM_TRANSACTED|STGM_SIMPLE|STGM_NOSCRATCH|STGM_NOSNAPSHOT))
return STG_E_INVALIDFLAG;
if (grfMode & STGM_DELETEONRELEASE)
return STG_E_INVALIDFUNCTION;
if(STGM_ACCESS_MODE(grfMode) != STGM_READ)
return STG_E_INVALIDFLAG;
grfMode &= ~0xf0; /* remove the existing sharing mode */
grfMode |= STGM_SHARE_DENY_NONE;
}
/*
* Validate the sharing mode
*/
if (grfMode & STGM_DIRECT_SWMR)
{
if ((STGM_SHARE_MODE(grfMode) != STGM_SHARE_DENY_WRITE) &&
(STGM_SHARE_MODE(grfMode) != STGM_SHARE_DENY_NONE))
{
hr = STG_E_INVALIDFLAG;
goto end;
}
}
else if (!(grfMode & (STGM_TRANSACTED|STGM_PRIORITY)))
switch(STGM_SHARE_MODE(grfMode))
{
case STGM_SHARE_EXCLUSIVE:
case STGM_SHARE_DENY_WRITE:
break;
default:
hr = STG_E_INVALIDFLAG;
goto end;
}
if ( FAILED( validateSTGM(grfMode) ) ||
(grfMode&STGM_CREATE))
{
hr = STG_E_INVALIDFLAG;
goto end;
}
/* shared reading requires transacted or single writer mode */
if( STGM_SHARE_MODE(grfMode) == STGM_SHARE_DENY_WRITE &&
STGM_ACCESS_MODE(grfMode) == STGM_READWRITE &&
!(grfMode & STGM_TRANSACTED) && !(grfMode & STGM_DIRECT_SWMR))
{
hr = STG_E_INVALIDFLAG;
goto end;
}
/*
* Interpret the STGM value grfMode
*/
shareMode = GetShareModeFromSTGM(grfMode);
accessMode = GetAccessModeFromSTGM(grfMode);
*ppstgOpen = 0;
hFile = CreateFileW( pwcsName,
accessMode,
shareMode,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL | FILE_FLAG_RANDOM_ACCESS,
0);
if (hFile==INVALID_HANDLE_VALUE)
{
DWORD last_error = GetLastError();
hr = E_FAIL;
switch (last_error)
{
case ERROR_FILE_NOT_FOUND:
hr = STG_E_FILENOTFOUND;
break;
case ERROR_PATH_NOT_FOUND:
hr = STG_E_PATHNOTFOUND;
break;
case ERROR_ACCESS_DENIED:
case ERROR_WRITE_PROTECT:
hr = STG_E_ACCESSDENIED;
break;
case ERROR_SHARING_VIOLATION:
hr = STG_E_SHAREVIOLATION;
break;
default:
hr = E_FAIL;
}
goto end;
}
/*
* Refuse to open the file if it's too small to be a structured storage file
* FIXME: verify the file when reading instead of here
*/
if (GetFileSize(hFile, NULL) < HEADER_SIZE)
{
CloseHandle(hFile);
hr = STG_E_FILEALREADYEXISTS;
goto end;
}
/*
* Allocate and initialize the new IStorage object.
*/
hr = Storage_Construct(
hFile,
pwcsName,
NULL,
grfMode,
TRUE,
FALSE,
512,
&newStorage);
if (FAILED(hr))
{
/*
* According to the docs if the file is not a storage, return STG_E_FILEALREADYEXISTS
*/
if(hr == STG_E_INVALIDHEADER)
hr = STG_E_FILEALREADYEXISTS;
goto end;
}
*ppstgOpen = &newStorage->IStorage_iface;
end:
CoTaskMemFree(temp_name);
if (pstgPriority) IStorage_Release(pstgPriority);
TRACE("<-- %08x, IStorage %p\n", hr, ppstgOpen ? *ppstgOpen : NULL);
return hr;
}
/******************************************************************************
* StgCreateDocfileOnILockBytes [OLE32.@]
*/
HRESULT WINAPI StgCreateDocfileOnILockBytes(
ILockBytes *plkbyt,
DWORD grfMode,
DWORD reserved,
IStorage** ppstgOpen)
{
StorageBaseImpl* newStorage = 0;
HRESULT hr = S_OK;
if ((ppstgOpen == 0) || (plkbyt == 0))
return STG_E_INVALIDPOINTER;
/*
* Allocate and initialize the new IStorage object.
*/
hr = Storage_Construct(
0,
0,
plkbyt,
grfMode,
FALSE,
TRUE,
512,
&newStorage);
if (FAILED(hr))
{
return hr;
}
*ppstgOpen = &newStorage->IStorage_iface;
return hr;
}
/******************************************************************************
* StgOpenStorageOnILockBytes [OLE32.@]
*/
HRESULT WINAPI StgOpenStorageOnILockBytes(
ILockBytes *plkbyt,
IStorage *pstgPriority,
DWORD grfMode,
SNB snbExclude,
DWORD reserved,
IStorage **ppstgOpen)
{
StorageBaseImpl* newStorage = 0;
HRESULT hr = S_OK;
if ((plkbyt == 0) || (ppstgOpen == 0))
return STG_E_INVALIDPOINTER;
if ( FAILED( validateSTGM(grfMode) ))
return STG_E_INVALIDFLAG;
*ppstgOpen = 0;
/*
* Allocate and initialize the new IStorage object.
*/
hr = Storage_Construct(
0,
0,
plkbyt,
grfMode,
FALSE,
FALSE,
512,
&newStorage);
if (FAILED(hr))
{
return hr;
}
*ppstgOpen = &newStorage->IStorage_iface;
return hr;
}
/******************************************************************************
* StgSetTimes [ole32.@]
* StgSetTimes [OLE32.@]
*
*
*/
HRESULT WINAPI StgSetTimes(OLECHAR const *str, FILETIME const *pctime,
FILETIME const *patime, FILETIME const *pmtime)
{
IStorage *stg = NULL;
HRESULT r;
TRACE("%s %p %p %p\n", debugstr_w(str), pctime, patime, pmtime);
r = StgOpenStorage(str, NULL, STGM_READWRITE | STGM_SHARE_DENY_WRITE,
0, 0, &stg);
if( SUCCEEDED(r) )
{
r = IStorage_SetElementTimes(stg, NULL, pctime, patime, pmtime);
IStorage_Release(stg);
}
return r;
}
/******************************************************************************
* StgIsStorageILockBytes [OLE32.@]
*
* Determines if the ILockBytes contains a storage object.
*/
HRESULT WINAPI StgIsStorageILockBytes(ILockBytes *plkbyt)
{
BYTE sig[sizeof(STORAGE_magic)];
ULARGE_INTEGER offset;
ULONG read = 0;
offset.u.HighPart = 0;
offset.u.LowPart = 0;
ILockBytes_ReadAt(plkbyt, offset, sig, sizeof(sig), &read);
if (read == sizeof(sig) && memcmp(sig, STORAGE_magic, sizeof(sig)) == 0)
return S_OK;
return S_FALSE;
}
/******************************************************************************
* WriteClassStg [OLE32.@]
*
* This method will store the specified CLSID in the specified storage object
*/
HRESULT WINAPI WriteClassStg(IStorage* pStg, REFCLSID rclsid)
{
if(!pStg)
return E_INVALIDARG;
if(!rclsid)
return STG_E_INVALIDPOINTER;
return IStorage_SetClass(pStg, rclsid);
}
/***********************************************************************
* ReadClassStg (OLE32.@)
*
* This method reads the CLSID previously written to a storage object with
* the WriteClassStg.
*
* PARAMS
* pstg [I] IStorage pointer
* pclsid [O] Pointer to where the CLSID is written
*
* RETURNS
* Success: S_OK.
* Failure: HRESULT code.
*/
HRESULT WINAPI ReadClassStg(IStorage *pstg,CLSID *pclsid){
STATSTG pstatstg;
HRESULT hRes;
TRACE("(%p, %p)\n", pstg, pclsid);
if(!pstg || !pclsid)
return E_INVALIDARG;
/*
* read a STATSTG structure (contains the clsid) from the storage
*/
hRes=IStorage_Stat(pstg,&pstatstg,STATFLAG_NONAME);
if(SUCCEEDED(hRes))
*pclsid=pstatstg.clsid;
return hRes;
}
/***********************************************************************
* OleLoadFromStream (OLE32.@)
*
* This function loads an object from stream
*/
HRESULT WINAPI OleLoadFromStream(IStream *pStm,REFIID iidInterface,void** ppvObj)
{
CLSID clsid;
HRESULT res;
LPPERSISTSTREAM xstm;
TRACE("(%p,%s,%p)\n",pStm,debugstr_guid(iidInterface),ppvObj);
res=ReadClassStm(pStm,&clsid);
if (FAILED(res))
return res;
res=CoCreateInstance(&clsid,NULL,CLSCTX_INPROC_SERVER,iidInterface,ppvObj);
if (FAILED(res))
return res;
res=IUnknown_QueryInterface((IUnknown*)*ppvObj,&IID_IPersistStream,(LPVOID*)&xstm);
if (FAILED(res)) {
IUnknown_Release((IUnknown*)*ppvObj);
return res;
}
res=IPersistStream_Load(xstm,pStm);
IPersistStream_Release(xstm);
/* FIXME: all refcounts ok at this point? I think they should be:
* pStm : unchanged
* ppvObj : 1
* xstm : 0 (released)
*/
return res;
}
/***********************************************************************
* OleSaveToStream (OLE32.@)
*
* This function saves an object with the IPersistStream interface on it
* to the specified stream.
*/
HRESULT WINAPI OleSaveToStream(IPersistStream *pPStm,IStream *pStm)
{
CLSID clsid;
HRESULT res;
TRACE("(%p,%p)\n",pPStm,pStm);
res=IPersistStream_GetClassID(pPStm,&clsid);
if (SUCCEEDED(res)){
res=WriteClassStm(pStm,&clsid);
if (SUCCEEDED(res))
res=IPersistStream_Save(pPStm,pStm,TRUE);
}
TRACE("Finished Save\n");
return res;
}
/*************************************************************************
* STORAGE_CreateOleStream [Internal]
*
* Creates the "\001OLE" stream in the IStorage if necessary.
*
* PARAMS
* storage [I] Dest storage to create the stream in
* flags [I] flags to be set for newly created stream
*
* RETURNS
* HRESULT return value
*
* NOTES
*
* This stream is still unknown, MS Word seems to have extra data
* but since the data is stored in the OLESTREAM there should be
* no need to recreate the stream. If the stream is manually
* deleted it will create it with this default data.
*
*/
HRESULT STORAGE_CreateOleStream(IStorage *storage, DWORD flags)
{
static const WCHAR stream_1oleW[] = {1,'O','l','e',0};
static const DWORD version_magic = 0x02000001;
IStream *stream;
HRESULT hr;
hr = IStorage_CreateStream(storage, stream_1oleW, STGM_WRITE | STGM_SHARE_EXCLUSIVE, 0, 0, &stream);
if (hr == S_OK)
{
struct empty_1ole_stream {
DWORD version_magic;
DWORD flags;
DWORD update_options;
DWORD reserved;
DWORD mon_stream_size;
};
struct empty_1ole_stream stream_data;
stream_data.version_magic = version_magic;
stream_data.flags = flags;
stream_data.update_options = 0;
stream_data.reserved = 0;
stream_data.mon_stream_size = 0;
hr = IStream_Write(stream, &stream_data, sizeof(stream_data), NULL);
IStream_Release(stream);
}
return hr;
}
/* write a string to a stream, preceded by its length */
static HRESULT STREAM_WriteString( IStream *stm, LPCWSTR string )
{
HRESULT r;
LPSTR str;
DWORD len = 0;
if( string )
len = WideCharToMultiByte( CP_ACP, 0, string, -1, NULL, 0, NULL, NULL);
r = IStream_Write( stm, &len, sizeof(len), NULL);
if( FAILED( r ) )
return r;
if(len == 0)
return r;
str = CoTaskMemAlloc( len );
WideCharToMultiByte( CP_ACP, 0, string, -1, str, len, NULL, NULL);
r = IStream_Write( stm, str, len, NULL);
CoTaskMemFree( str );
return r;
}
/* read a string preceded by its length from a stream */
static HRESULT STREAM_ReadString( IStream *stm, LPWSTR *string )
{
HRESULT r;
DWORD len, count = 0;
LPSTR str;
LPWSTR wstr;
r = IStream_Read( stm, &len, sizeof(len), &count );
if( FAILED( r ) )
return r;
if( count != sizeof(len) )
return E_OUTOFMEMORY;
TRACE("%d bytes\n",len);
str = CoTaskMemAlloc( len );
if( !str )
return E_OUTOFMEMORY;
count = 0;
r = IStream_Read( stm, str, len, &count );
if( FAILED( r ) )
{
CoTaskMemFree( str );
return r;
}
if( count != len )
{
CoTaskMemFree( str );
return E_OUTOFMEMORY;
}
TRACE("Read string %s\n",debugstr_an(str,len));
len = MultiByteToWideChar( CP_ACP, 0, str, count, NULL, 0 );
wstr = CoTaskMemAlloc( (len + 1)*sizeof (WCHAR) );
if( wstr )
{
MultiByteToWideChar( CP_ACP, 0, str, count, wstr, len );
wstr[len] = 0;
}
CoTaskMemFree( str );
*string = wstr;
return r;
}
static HRESULT STORAGE_WriteCompObj( LPSTORAGE pstg, CLSID *clsid,
LPCWSTR lpszUserType, LPCWSTR szClipName, LPCWSTR szProgIDName )
{
IStream *pstm;
HRESULT r = S_OK;
static const WCHAR szwStreamName[] = {1, 'C', 'o', 'm', 'p', 'O', 'b', 'j', 0};
static const BYTE unknown1[12] =
{ 0x01, 0x00, 0xFE, 0xFF, 0x03, 0x0A, 0x00, 0x00,
0xFF, 0xFF, 0xFF, 0xFF};
static const BYTE unknown2[16] =
{ 0xF4, 0x39, 0xB2, 0x71, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
TRACE("%p %s %s %s %s\n", pstg, debugstr_guid(clsid),
debugstr_w(lpszUserType), debugstr_w(szClipName),
debugstr_w(szProgIDName));
/* Create a CompObj stream */
r = IStorage_CreateStream(pstg, szwStreamName,
STGM_CREATE | STGM_WRITE | STGM_SHARE_EXCLUSIVE, 0, 0, &pstm );
if( FAILED (r) )
return r;
/* Write CompObj Structure to stream */
r = IStream_Write(pstm, unknown1, sizeof(unknown1), NULL);
if( SUCCEEDED( r ) )
r = WriteClassStm( pstm, clsid );
if( SUCCEEDED( r ) )
r = STREAM_WriteString( pstm, lpszUserType );
if( SUCCEEDED( r ) )
r = STREAM_WriteString( pstm, szClipName );
if( SUCCEEDED( r ) )
r = STREAM_WriteString( pstm, szProgIDName );
if( SUCCEEDED( r ) )
r = IStream_Write(pstm, unknown2, sizeof(unknown2), NULL);
IStream_Release( pstm );
return r;
}
/***********************************************************************
* WriteFmtUserTypeStg (OLE32.@)
*/
HRESULT WINAPI WriteFmtUserTypeStg(
LPSTORAGE pstg, CLIPFORMAT cf, LPOLESTR lpszUserType)
{
STATSTG stat;
HRESULT r;
WCHAR szwClipName[0x40];
CLSID clsid;
LPWSTR wstrProgID = NULL;
DWORD n;
TRACE("(%p,%x,%s)\n",pstg,cf,debugstr_w(lpszUserType));
/* get the clipboard format name */
if( cf )
{
n = GetClipboardFormatNameW(cf, szwClipName, ARRAY_SIZE(szwClipName));
szwClipName[n]=0;
}
TRACE("Clipboard name is %s\n", debugstr_w(szwClipName));
r = IStorage_Stat(pstg, &stat, STATFLAG_NONAME);
if(SUCCEEDED(r))
clsid = stat.clsid;
else
clsid = CLSID_NULL;
ProgIDFromCLSID(&clsid, &wstrProgID);
TRACE("progid is %s\n",debugstr_w(wstrProgID));
r = STORAGE_WriteCompObj( pstg, &clsid, lpszUserType,
cf ? szwClipName : NULL, wstrProgID );
CoTaskMemFree(wstrProgID);
return r;
}
/******************************************************************************
* ReadFmtUserTypeStg [OLE32.@]
*/
HRESULT WINAPI ReadFmtUserTypeStg (LPSTORAGE pstg, CLIPFORMAT* pcf, LPOLESTR* lplpszUserType)
{
HRESULT r;
IStream *stm = 0;
static const WCHAR szCompObj[] = { 1, 'C','o','m','p','O','b','j', 0 };
unsigned char unknown1[12];
unsigned char unknown2[16];
DWORD count;
LPWSTR szProgIDName = NULL, szCLSIDName = NULL, szOleTypeName = NULL;
CLSID clsid;
TRACE("(%p,%p,%p)\n", pstg, pcf, lplpszUserType);
r = IStorage_OpenStream( pstg, szCompObj, NULL,
STGM_READ | STGM_SHARE_EXCLUSIVE, 0, &stm );
if( FAILED ( r ) )
{
WARN("Failed to open stream r = %08x\n", r);
return r;
}
/* read the various parts of the structure */
r = IStream_Read( stm, unknown1, sizeof(unknown1), &count );
if( FAILED( r ) || ( count != sizeof(unknown1) ) )
goto end;
r = ReadClassStm( stm, &clsid );
if( FAILED( r ) )
goto end;
r = STREAM_ReadString( stm, &szCLSIDName );
if( FAILED( r ) )
goto end;
r = STREAM_ReadString( stm, &szOleTypeName );
if( FAILED( r ) )
goto end;
r = STREAM_ReadString( stm, &szProgIDName );
if( FAILED( r ) )
goto end;
r = IStream_Read( stm, unknown2, sizeof(unknown2), &count );
if( FAILED( r ) || ( count != sizeof(unknown2) ) )
goto end;
/* ok, success... now we just need to store what we found */
if( pcf )
*pcf = RegisterClipboardFormatW( szOleTypeName );
if( lplpszUserType )
{
*lplpszUserType = szCLSIDName;
szCLSIDName = NULL;
}
end:
CoTaskMemFree( szCLSIDName );
CoTaskMemFree( szOleTypeName );
CoTaskMemFree( szProgIDName );
IStream_Release( stm );
return r;
}
/******************************************************************************
* StgIsStorageFile [OLE32.@]
* Verify if the file contains a storage object
*
* PARAMS
* fn [ I] Filename
*
* RETURNS
* S_OK if file has magic bytes as a storage object
* S_FALSE if file is not storage
*/
HRESULT WINAPI
StgIsStorageFile(LPCOLESTR fn)
{
HANDLE hf;
BYTE magic[8];
DWORD bytes_read;
TRACE("%s\n", debugstr_w(fn));
hf = CreateFileW(fn, GENERIC_READ,
FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE,
NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
if (hf == INVALID_HANDLE_VALUE)
return STG_E_FILENOTFOUND;
if (!ReadFile(hf, magic, 8, &bytes_read, NULL))
{
WARN(" unable to read file\n");
CloseHandle(hf);
return S_FALSE;
}
CloseHandle(hf);
if (bytes_read != 8) {
TRACE(" too short\n");
return S_FALSE;
}
if (!memcmp(magic,STORAGE_magic,8)) {
TRACE(" -> YES\n");
return S_OK;
}
TRACE(" -> Invalid header.\n");
return S_FALSE;
}
/***********************************************************************
* WriteClassStm (OLE32.@)
*
* Writes a CLSID to a stream.
*
* PARAMS
* pStm [I] Stream to write to.
* rclsid [I] CLSID to write.
*
* RETURNS
* Success: S_OK.
* Failure: HRESULT code.
*/
HRESULT WINAPI WriteClassStm(IStream *pStm,REFCLSID rclsid)
{
TRACE("(%p,%p)\n",pStm,rclsid);
if (!pStm || !rclsid)
return E_INVALIDARG;
return IStream_Write(pStm,rclsid,sizeof(CLSID),NULL);
}
/***********************************************************************
* ReadClassStm (OLE32.@)
*
* Reads a CLSID from a stream.
*
* PARAMS
* pStm [I] Stream to read from.
* rclsid [O] CLSID to read.
*
* RETURNS
* Success: S_OK.
* Failure: HRESULT code.
*/
HRESULT WINAPI ReadClassStm(IStream *pStm,CLSID *pclsid)
{
ULONG nbByte;
HRESULT res;
TRACE("(%p,%p)\n",pStm,pclsid);
if (!pStm || !pclsid)
return E_INVALIDARG;
/* clear the output args */
*pclsid = CLSID_NULL;
res = IStream_Read(pStm, pclsid, sizeof(CLSID), &nbByte);
if (FAILED(res))
return res;
if (nbByte != sizeof(CLSID))
return STG_E_READFAULT;
else
return S_OK;
}
/************************************************************************
* OleConvert Functions
***********************************************************************/
#define OLESTREAM_ID 0x501
#define OLESTREAM_MAX_STR_LEN 255
/* OLESTREAM memory structure to use for Get and Put Routines */
typedef struct
{
DWORD dwOleID;
DWORD dwTypeID;
DWORD dwOleTypeNameLength;
CHAR strOleTypeName[OLESTREAM_MAX_STR_LEN];
CHAR *pstrOleObjFileName;
DWORD dwOleObjFileNameLength;
DWORD dwMetaFileWidth;
DWORD dwMetaFileHeight;
CHAR strUnknown[8]; /* don't know what this 8 byte information in OLE stream is. */
DWORD dwDataLength;
BYTE *pData;
} OLECONVERT_OLESTREAM_DATA;
/* CompObj Stream structure */
typedef struct
{
BYTE byUnknown1[12];
CLSID clsid;
DWORD dwCLSIDNameLength;
CHAR strCLSIDName[OLESTREAM_MAX_STR_LEN];
DWORD dwOleTypeNameLength;
CHAR strOleTypeName[OLESTREAM_MAX_STR_LEN];
DWORD dwProgIDNameLength;
CHAR strProgIDName[OLESTREAM_MAX_STR_LEN];
BYTE byUnknown2[16];
} OLECONVERT_ISTORAGE_COMPOBJ;
/* Ole Presentation Stream structure */
typedef struct
{
BYTE byUnknown1[28];
DWORD dwExtentX;
DWORD dwExtentY;
DWORD dwSize;
BYTE *pData;
} OLECONVERT_ISTORAGE_OLEPRES;
/*************************************************************************
* OLECONVERT_LoadOLE10 [Internal]
*
* Loads the OLE10 STREAM to memory
*
* PARAMS
* pOleStream [I] The OLESTREAM
* pData [I] Data Structure for the OLESTREAM Data
*
* RETURNS
* Success: S_OK
* Failure: CONVERT10_E_OLESTREAM_GET for invalid Get
* CONVERT10_E_OLESTREAM_FMT if the OLEID is invalid
*
* NOTES
* This function is used by OleConvertOLESTREAMToIStorage only.
*
* Memory allocated for pData must be freed by the caller
*/
static HRESULT OLECONVERT_LoadOLE10(LPOLESTREAM pOleStream, OLECONVERT_OLESTREAM_DATA *pData, BOOL bStrem1)
{
DWORD dwSize;
HRESULT hRes = S_OK;
int nTryCnt=0;
int max_try = 6;
pData->pData = NULL;
pData->pstrOleObjFileName = NULL;
for( nTryCnt=0;nTryCnt < max_try; nTryCnt++)
{
/* Get the OleID */
dwSize = pOleStream->lpstbl->Get(pOleStream, (void *)&(pData->dwOleID), sizeof(pData->dwOleID));
if(dwSize != sizeof(pData->dwOleID))
{
hRes = CONVERT10_E_OLESTREAM_GET;
}
else if(pData->dwOleID != OLESTREAM_ID)
{
hRes = CONVERT10_E_OLESTREAM_FMT;
}
else
{
hRes = S_OK;
break;
}
}
if(hRes == S_OK)
{
/* Get the TypeID... more info needed for this field */
dwSize = pOleStream->lpstbl->Get(pOleStream, (void *)&(pData->dwTypeID), sizeof(pData->dwTypeID));
if(dwSize != sizeof(pData->dwTypeID))
{
hRes = CONVERT10_E_OLESTREAM_GET;
}
}
if(hRes == S_OK)
{
if(pData->dwTypeID != 0)
{
/* Get the length of the OleTypeName */
dwSize = pOleStream->lpstbl->Get(pOleStream, (void *) &(pData->dwOleTypeNameLength), sizeof(pData->dwOleTypeNameLength));
if(dwSize != sizeof(pData->dwOleTypeNameLength))
{
hRes = CONVERT10_E_OLESTREAM_GET;
}
if(hRes == S_OK)
{
if(pData->dwOleTypeNameLength > 0)
{
/* Get the OleTypeName */
dwSize = pOleStream->lpstbl->Get(pOleStream, pData->strOleTypeName, pData->dwOleTypeNameLength);
if(dwSize != pData->dwOleTypeNameLength)
{
hRes = CONVERT10_E_OLESTREAM_GET;
}
}
}
if(bStrem1)
{
dwSize = pOleStream->lpstbl->Get(pOleStream, (void *)&(pData->dwOleObjFileNameLength), sizeof(pData->dwOleObjFileNameLength));
if(dwSize != sizeof(pData->dwOleObjFileNameLength))
{
hRes = CONVERT10_E_OLESTREAM_GET;
}
if(hRes == S_OK)
{
if(pData->dwOleObjFileNameLength < 1) /* there is no file name exist */
pData->dwOleObjFileNameLength = sizeof(pData->dwOleObjFileNameLength);
pData->pstrOleObjFileName = HeapAlloc(GetProcessHeap(), 0, pData->dwOleObjFileNameLength);
if(pData->pstrOleObjFileName)
{
dwSize = pOleStream->lpstbl->Get(pOleStream, pData->pstrOleObjFileName, pData->dwOleObjFileNameLength);
if(dwSize != pData->dwOleObjFileNameLength)
{
hRes = CONVERT10_E_OLESTREAM_GET;
}
}
else
hRes = CONVERT10_E_OLESTREAM_GET;
}
}
else
{
/* Get the Width of the Metafile */
dwSize = pOleStream->lpstbl->Get(pOleStream, (void *)&(pData->dwMetaFileWidth), sizeof(pData->dwMetaFileWidth));
if(dwSize != sizeof(pData->dwMetaFileWidth))
{
hRes = CONVERT10_E_OLESTREAM_GET;
}
if(hRes == S_OK)
{
/* Get the Height of the Metafile */
dwSize = pOleStream->lpstbl->Get(pOleStream, (void *)&(pData->dwMetaFileHeight), sizeof(pData->dwMetaFileHeight));
if(dwSize != sizeof(pData->dwMetaFileHeight))
{
hRes = CONVERT10_E_OLESTREAM_GET;
}
}
}
if(hRes == S_OK)
{
/* Get the Length of the Data */
dwSize = pOleStream->lpstbl->Get(pOleStream, (void *)&(pData->dwDataLength), sizeof(pData->dwDataLength));
if(dwSize != sizeof(pData->dwDataLength))
{
hRes = CONVERT10_E_OLESTREAM_GET;
}
}
if(hRes == S_OK) /* I don't know what this 8 byte information is. We have to figure out */
{
if(!bStrem1) /* if it is a second OLE stream data */
{
pData->dwDataLength -= 8;
dwSize = pOleStream->lpstbl->Get(pOleStream, pData->strUnknown, sizeof(pData->strUnknown));
if(dwSize != sizeof(pData->strUnknown))
{
hRes = CONVERT10_E_OLESTREAM_GET;
}
}
}
if(hRes == S_OK)
{
if(pData->dwDataLength > 0)
{
pData->pData = HeapAlloc(GetProcessHeap(),0,pData->dwDataLength);
/* Get Data (ex. IStorage, Metafile, or BMP) */
if(pData->pData)
{
dwSize = pOleStream->lpstbl->Get(pOleStream, (void *)pData->pData, pData->dwDataLength);
if(dwSize != pData->dwDataLength)
{
hRes = CONVERT10_E_OLESTREAM_GET;
}
}
else
{
hRes = CONVERT10_E_OLESTREAM_GET;
}
}
}
}
}
return hRes;
}
/*************************************************************************
* OLECONVERT_SaveOLE10 [Internal]
*
* Saves the OLE10 STREAM From memory
*
* PARAMS
* pData [I] Data Structure for the OLESTREAM Data
* pOleStream [I] The OLESTREAM to save
*
* RETURNS
* Success: S_OK
* Failure: CONVERT10_E_OLESTREAM_PUT for invalid Put
*
* NOTES
* This function is used by OleConvertIStorageToOLESTREAM only.
*
*/
static HRESULT OLECONVERT_SaveOLE10(OLECONVERT_OLESTREAM_DATA *pData, LPOLESTREAM pOleStream)
{
DWORD dwSize;
HRESULT hRes = S_OK;
/* Set the OleID */
dwSize = pOleStream->lpstbl->Put(pOleStream, (void *)&(pData->dwOleID), sizeof(pData->dwOleID));
if(dwSize != sizeof(pData->dwOleID))
{
hRes = CONVERT10_E_OLESTREAM_PUT;
}
if(hRes == S_OK)
{
/* Set the TypeID */
dwSize = pOleStream->lpstbl->Put(pOleStream, (void *)&(pData->dwTypeID), sizeof(pData->dwTypeID));
if(dwSize != sizeof(pData->dwTypeID))
{
hRes = CONVERT10_E_OLESTREAM_PUT;
}
}
if(pData->dwOleID == OLESTREAM_ID && pData->dwTypeID != 0 && hRes == S_OK)
{
/* Set the Length of the OleTypeName */
dwSize = pOleStream->lpstbl->Put(pOleStream, (void *)&(pData->dwOleTypeNameLength), sizeof(pData->dwOleTypeNameLength));
if(dwSize != sizeof(pData->dwOleTypeNameLength))
{
hRes = CONVERT10_E_OLESTREAM_PUT;
}
if(hRes == S_OK)
{
if(pData->dwOleTypeNameLength > 0)
{
/* Set the OleTypeName */
dwSize = pOleStream->lpstbl->Put(pOleStream, pData->strOleTypeName, pData->dwOleTypeNameLength);
if(dwSize != pData->dwOleTypeNameLength)
{
hRes = CONVERT10_E_OLESTREAM_PUT;
}
}
}
if(hRes == S_OK)
{
/* Set the width of the Metafile */
dwSize = pOleStream->lpstbl->Put(pOleStream, (void *)&(pData->dwMetaFileWidth), sizeof(pData->dwMetaFileWidth));
if(dwSize != sizeof(pData->dwMetaFileWidth))
{
hRes = CONVERT10_E_OLESTREAM_PUT;
}
}
if(hRes == S_OK)
{
/* Set the height of the Metafile */
dwSize = pOleStream->lpstbl->Put(pOleStream, (void *)&(pData->dwMetaFileHeight), sizeof(pData->dwMetaFileHeight));
if(dwSize != sizeof(pData->dwMetaFileHeight))
{
hRes = CONVERT10_E_OLESTREAM_PUT;
}
}
if(hRes == S_OK)
{
/* Set the length of the Data */
dwSize = pOleStream->lpstbl->Put(pOleStream, (void *)&(pData->dwDataLength), sizeof(pData->dwDataLength));
if(dwSize != sizeof(pData->dwDataLength))
{
hRes = CONVERT10_E_OLESTREAM_PUT;
}
}
if(hRes == S_OK)
{
if(pData->dwDataLength > 0)
{
/* Set the Data (eg. IStorage, Metafile, Bitmap) */
dwSize = pOleStream->lpstbl->Put(pOleStream, (void *) pData->pData, pData->dwDataLength);
if(dwSize != pData->dwDataLength)
{
hRes = CONVERT10_E_OLESTREAM_PUT;
}
}
}
}
return hRes;
}
/*************************************************************************
* OLECONVERT_GetOLE20FromOLE10[Internal]
*
* This function copies OLE10 Data (the IStorage in the OLESTREAM) to disk,
* opens it, and copies the content to the dest IStorage for
* OleConvertOLESTREAMToIStorage
*
*
* PARAMS
* pDestStorage [I] The IStorage to copy the data to
* pBuffer [I] Buffer that contains the IStorage from the OLESTREAM
* nBufferLength [I] The size of the buffer
*
* RETURNS
* Nothing
*
* NOTES
*
*
*/
static void OLECONVERT_GetOLE20FromOLE10(LPSTORAGE pDestStorage, const BYTE *pBuffer, DWORD nBufferLength)
{
HRESULT hRes;
HANDLE hFile;
IStorage *pTempStorage;
DWORD dwNumOfBytesWritten;
WCHAR wstrTempDir[MAX_PATH], wstrTempFile[MAX_PATH];
static const WCHAR wstrPrefix[] = {'s', 'i', 's', 0};
/* Create a temp File */
GetTempPathW(MAX_PATH, wstrTempDir);
GetTempFileNameW(wstrTempDir, wstrPrefix, 0, wstrTempFile);
hFile = CreateFileW(wstrTempFile, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0);
if(hFile != INVALID_HANDLE_VALUE)
{
/* Write IStorage Data to File */
WriteFile(hFile, pBuffer, nBufferLength, &dwNumOfBytesWritten, NULL);
CloseHandle(hFile);
/* Open and copy temp storage to the Dest Storage */
hRes = StgOpenStorage(wstrTempFile, NULL, STGM_READ, NULL, 0, &pTempStorage);
if(hRes == S_OK)
{
hRes = IStorage_CopyTo(pTempStorage, 0, NULL, NULL, pDestStorage);
IStorage_Release(pTempStorage);
}
DeleteFileW(wstrTempFile);
}
}
/*************************************************************************
* OLECONVERT_WriteOLE20ToBuffer [Internal]
*
* Saves the OLE10 STREAM From memory
*
* PARAMS
* pStorage [I] The Src IStorage to copy
* pData [I] The Dest Memory to write to.
*
* RETURNS
* The size in bytes allocated for pData
*
* NOTES
* Memory allocated for pData must be freed by the caller
*
* Used by OleConvertIStorageToOLESTREAM only.
*
*/
static DWORD OLECONVERT_WriteOLE20ToBuffer(LPSTORAGE pStorage, BYTE **pData)
{
HANDLE hFile;
HRESULT hRes;
DWORD nDataLength = 0;
IStorage *pTempStorage;
WCHAR wstrTempDir[MAX_PATH], wstrTempFile[MAX_PATH];
static const WCHAR wstrPrefix[] = {'s', 'i', 's', 0};
*pData = NULL;
/* Create temp Storage */
GetTempPathW(MAX_PATH, wstrTempDir);
GetTempFileNameW(wstrTempDir, wstrPrefix, 0, wstrTempFile);
hRes = StgCreateDocfile(wstrTempFile, STGM_CREATE | STGM_READWRITE | STGM_SHARE_EXCLUSIVE, 0, &pTempStorage);
if(hRes == S_OK)
{
/* Copy Src Storage to the Temp Storage */
IStorage_CopyTo(pStorage, 0, NULL, NULL, pTempStorage);
IStorage_Release(pTempStorage);
/* Open Temp Storage as a file and copy to memory */
hFile = CreateFileW(wstrTempFile, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
if(hFile != INVALID_HANDLE_VALUE)
{
nDataLength = GetFileSize(hFile, NULL);
*pData = HeapAlloc(GetProcessHeap(),0,nDataLength);
ReadFile(hFile, *pData, nDataLength, &nDataLength, 0);
CloseHandle(hFile);
}
DeleteFileW(wstrTempFile);
}
return nDataLength;
}
/*************************************************************************
* OLECONVERT_CreateCompObjStream [Internal]
*
* Creates a "\001CompObj" is the destination IStorage if necessary.
*
* PARAMS
* pStorage [I] The dest IStorage to create the CompObj Stream
* if necessary.
* strOleTypeName [I] The ProgID
*
* RETURNS
* Success: S_OK
* Failure: REGDB_E_CLASSNOTREG if cannot reconstruct the stream
*
* NOTES
* This function is used by OleConvertOLESTREAMToIStorage only.
*
* The stream data is stored in the OLESTREAM and there should be
* no need to recreate the stream. If the stream is manually
* deleted it will attempt to create it by querying the registry.
*
*
*/
HRESULT OLECONVERT_CreateCompObjStream(LPSTORAGE pStorage, LPCSTR strOleTypeName)
{
IStream *pStream;
HRESULT hStorageRes, hRes = S_OK;
OLECONVERT_ISTORAGE_COMPOBJ IStorageCompObj;
static const WCHAR wstrStreamName[] = {1,'C', 'o', 'm', 'p', 'O', 'b', 'j', 0};
WCHAR bufferW[OLESTREAM_MAX_STR_LEN];
static const BYTE pCompObjUnknown1[] = {0x01, 0x00, 0xFE, 0xFF, 0x03, 0x0A, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF};
static const BYTE pCompObjUnknown2[] = {0xF4, 0x39, 0xB2, 0x71};
/* Initialize the CompObj structure */
memset(&IStorageCompObj, 0, sizeof(IStorageCompObj));
memcpy(IStorageCompObj.byUnknown1, pCompObjUnknown1, sizeof(pCompObjUnknown1));
memcpy(IStorageCompObj.byUnknown2, pCompObjUnknown2, sizeof(pCompObjUnknown2));
/* Create a CompObj stream if it doesn't exist */
hStorageRes = IStorage_CreateStream(pStorage, wstrStreamName,
STGM_WRITE | STGM_SHARE_EXCLUSIVE, 0, 0, &pStream );
if(hStorageRes == S_OK)
{
/* copy the OleTypeName to the compobj struct */
IStorageCompObj.dwOleTypeNameLength = strlen(strOleTypeName)+1;
strcpy(IStorageCompObj.strOleTypeName, strOleTypeName);
/* copy the OleTypeName to the compobj struct */
/* Note: in the test made, these were Identical */
IStorageCompObj.dwProgIDNameLength = strlen(strOleTypeName)+1;
strcpy(IStorageCompObj.strProgIDName, strOleTypeName);
/* Get the CLSID */
MultiByteToWideChar( CP_ACP, 0, IStorageCompObj.strProgIDName, -1,
bufferW, OLESTREAM_MAX_STR_LEN );
hRes = CLSIDFromProgID(bufferW, &(IStorageCompObj.clsid));
if(hRes == S_OK)
{
HKEY hKey;
LONG hErr;
/* Get the CLSID Default Name from the Registry */
hErr = open_classes_key(HKEY_CLASSES_ROOT, bufferW, MAXIMUM_ALLOWED, &hKey);
if(hErr == ERROR_SUCCESS)
{
char strTemp[OLESTREAM_MAX_STR_LEN];
IStorageCompObj.dwCLSIDNameLength = OLESTREAM_MAX_STR_LEN;
Sync to Wine-20050830: Francois Gouget <fgouget@free.fr> - Use LPSTORAGE to better match the PSDK. Document the real SEGPTR type using the standard documentation mechanisms. Fixes winapi_check warnings. Robert Shearman <rob@codeweavers.com> - Remove unused thread handle entry from the apartment structure. - Implement IMarshal on proxies so that we don't end up with proxies to proxies, causing potential deadlock issues and performance problems. - Add a test for this situation and remove the todo_wine from another test that now succeeds. - Add stub for CoAllowSetForegroundWindow. Vincent Beron <vberon@mecano.gme.usherb.ca> - Remove local declarations already in included public headers. - Correct mismatches between spec files and comments about export number. Alexandre Julliard <julliard@winehq.org> - Uncomment the typedef in the DECLARE_INTERFACE macro, and get rid of duplicate typedefs. - Use the proper WOW functions everywhere instead of the K32WOW variant. - Don't prefix the functions DllCanUnloadNow, DllGetClassObject and Dll(Un)RegisterServer with the dll name so that the compiler can check the prototypes. - Remove duplicate definition of FILE_BEGIN. - Replace the _ICOM_THIS_From macros by inline functions the way it's already done in shelllink.c. Mike McCormack <mike@codeweavers.com> - Warning fixes for gcc 4.0. - Fix some gcc 4.0 warnings. - return a precomputed result for a NULL string - pass strlen an LPSTR to eliminate a sign warning Marcus Meissner <marcus@jet.franken.de> - Implemented ILockBytes16 (memorystream) support for the 16bit compound storage implementation. - Added ReadClassStg, OleDoAutoConvert, GetConvertStg implementations/stubs. Marcus Meissner <meissner@suse.de> - Added CoCreateInstance16, CoGetClassObject16, OleLoad16 stubs. svn path=/trunk/; revision=17678
2005-09-05 21:56:14 +00:00
hErr = RegQueryValueA(hKey, NULL, strTemp, (LONG*) &(IStorageCompObj.dwCLSIDNameLength));
if(hErr == ERROR_SUCCESS)
{
strcpy(IStorageCompObj.strCLSIDName, strTemp);
}
RegCloseKey(hKey);
}
}
/* Write CompObj Structure to stream */
hRes = IStream_Write(pStream, IStorageCompObj.byUnknown1, sizeof(IStorageCompObj.byUnknown1), NULL);
WriteClassStm(pStream,&(IStorageCompObj.clsid));
hRes = IStream_Write(pStream, &(IStorageCompObj.dwCLSIDNameLength), sizeof(IStorageCompObj.dwCLSIDNameLength), NULL);
if(IStorageCompObj.dwCLSIDNameLength > 0)
{
hRes = IStream_Write(pStream, IStorageCompObj.strCLSIDName, IStorageCompObj.dwCLSIDNameLength, NULL);
}
hRes = IStream_Write(pStream, &(IStorageCompObj.dwOleTypeNameLength) , sizeof(IStorageCompObj.dwOleTypeNameLength), NULL);
if(IStorageCompObj.dwOleTypeNameLength > 0)
{
hRes = IStream_Write(pStream, IStorageCompObj.strOleTypeName , IStorageCompObj.dwOleTypeNameLength, NULL);
}
hRes = IStream_Write(pStream, &(IStorageCompObj.dwProgIDNameLength) , sizeof(IStorageCompObj.dwProgIDNameLength), NULL);
if(IStorageCompObj.dwProgIDNameLength > 0)
{
hRes = IStream_Write(pStream, IStorageCompObj.strProgIDName , IStorageCompObj.dwProgIDNameLength, NULL);
}
hRes = IStream_Write(pStream, IStorageCompObj.byUnknown2 , sizeof(IStorageCompObj.byUnknown2), NULL);
IStream_Release(pStream);
}
return hRes;
}
/*************************************************************************
* OLECONVERT_CreateOlePresStream[Internal]
*
* Creates the "\002OlePres000" Stream with the Metafile data
*
* PARAMS
* pStorage [I] The dest IStorage to create \002OLEPres000 stream in.
* dwExtentX [I] Width of the Metafile
* dwExtentY [I] Height of the Metafile
* pData [I] Metafile data
* dwDataLength [I] Size of the Metafile data
*
* RETURNS
* Success: S_OK
* Failure: CONVERT10_E_OLESTREAM_PUT for invalid Put
*
* NOTES
* This function is used by OleConvertOLESTREAMToIStorage only.
*
*/
Sync to Wine-20050725: Robert Shearman <rob@codeweavers.com> - Marshal return value from IRemUnknown_RemQueryInterface. - We should be starting with 1 reference. - IRpcStubBuffer_Disconnect can be called more than once. - Silence now noisy error messages caused by changes in the way we call ipid_to_stubmanager. Move the error message to the one place it is needed. - By-pass the RPC runtime if possible when calling an STA by posting a message directly to the apartment window for it to process. Fixes a deadlock in InstallShield caused by having to create a thread when freeing an object that comes from an STA apartment. Added tests that fail without this fix. - Hack around broken state management so InstallShield works. - Delete the stub manager outside of the apartment critical section because the deletion may require the object to re-enter the apartment. - Always query for the correct stub interface, otherwise we will be pointing to the completely wrong object when a proxy does a queryinterface. - Remove assumption that the stub buffer will handle the lifetime of the object. Alex Villacis Lasso <a_villacis@palosanto.com> - Initialize RegisteredClass properly in CoRegisterClassObject to prevent crash in CoRevokeClassObject when accessing (uninitialized) pMarshalledData. Mike McCormack <mike@codeweavers.com> - Fix gcc 4.0 -Wpointer-sign warnings. Vitaly Lipatov <lav@etersoft.ru> - Added some documentation. Stefan Huehner <stefan@huehner.org> - Fix some missing-declarations warnings. Marcus Meissner <meissner@suse.de> - 16bit interfaces are cdecl, so drop the WINAPI. - 16bit COM interfaces are cdecl, not WINAPI. - OleInitializeWOW gets 2 arguments. - Added OleSetMenuDescriptor16 stub. Marcus Meissner <marcus@jet.franken.de> - Implemented IsValidInterface16, CoMemAlloc. Added debug to HGLOBALLockBytes16_QueryInterface. svn path=/trunk/; revision=17332
2005-08-12 17:19:46 +00:00
static void OLECONVERT_CreateOlePresStream(LPSTORAGE pStorage, DWORD dwExtentX, DWORD dwExtentY , BYTE *pData, DWORD dwDataLength)
{
HRESULT hRes;
IStream *pStream;
static const WCHAR wstrStreamName[] = {2, 'O', 'l', 'e', 'P', 'r', 'e', 's', '0', '0', '0', 0};
static const BYTE pOlePresStreamHeader[] =
{
0xFF, 0xFF, 0xFF, 0xFF, 0x03, 0x00, 0x00, 0x00,
0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00
};
static const BYTE pOlePresStreamHeaderEmpty[] =
{
0x00, 0x00, 0x00, 0x00,
0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00
};
/* Create the OlePres000 Stream */
hRes = IStorage_CreateStream(pStorage, wstrStreamName,
STGM_CREATE | STGM_WRITE | STGM_SHARE_EXCLUSIVE, 0, 0, &pStream );
if(hRes == S_OK)
{
DWORD nHeaderSize;
OLECONVERT_ISTORAGE_OLEPRES OlePres;
memset(&OlePres, 0, sizeof(OlePres));
/* Do we have any metafile data to save */
if(dwDataLength > 0)
{
memcpy(OlePres.byUnknown1, pOlePresStreamHeader, sizeof(pOlePresStreamHeader));
nHeaderSize = sizeof(pOlePresStreamHeader);
}
else
{
memcpy(OlePres.byUnknown1, pOlePresStreamHeaderEmpty, sizeof(pOlePresStreamHeaderEmpty));
nHeaderSize = sizeof(pOlePresStreamHeaderEmpty);
}
/* Set width and height of the metafile */
OlePres.dwExtentX = dwExtentX;
OlePres.dwExtentY = -dwExtentY;
/* Set Data and Length */
if(dwDataLength > sizeof(METAFILEPICT16))
{
OlePres.dwSize = dwDataLength - sizeof(METAFILEPICT16);
OlePres.pData = &(pData[8]);
}
/* Save OlePres000 Data to Stream */
hRes = IStream_Write(pStream, OlePres.byUnknown1, nHeaderSize, NULL);
hRes = IStream_Write(pStream, &(OlePres.dwExtentX), sizeof(OlePres.dwExtentX), NULL);
hRes = IStream_Write(pStream, &(OlePres.dwExtentY), sizeof(OlePres.dwExtentY), NULL);
hRes = IStream_Write(pStream, &(OlePres.dwSize), sizeof(OlePres.dwSize), NULL);
if(OlePres.dwSize > 0)
{
hRes = IStream_Write(pStream, OlePres.pData, OlePres.dwSize, NULL);
}
IStream_Release(pStream);
}
}
/*************************************************************************
* OLECONVERT_CreateOle10NativeStream [Internal]
*
* Creates the "\001Ole10Native" Stream (should contain a BMP)
*
* PARAMS
* pStorage [I] Dest storage to create the stream in
* pData [I] Ole10 Native Data (ex. bmp)
* dwDataLength [I] Size of the Ole10 Native Data
*
* RETURNS
* Nothing
*
* NOTES
* This function is used by OleConvertOLESTREAMToIStorage only.
*
* Might need to verify the data and return appropriate error message
*
*/
static void OLECONVERT_CreateOle10NativeStream(LPSTORAGE pStorage, const BYTE *pData, DWORD dwDataLength)
{
HRESULT hRes;
IStream *pStream;
static const WCHAR wstrStreamName[] = {1, 'O', 'l', 'e', '1', '0', 'N', 'a', 't', 'i', 'v', 'e', 0};
/* Create the Ole10Native Stream */
hRes = IStorage_CreateStream(pStorage, wstrStreamName,
STGM_CREATE | STGM_WRITE | STGM_SHARE_EXCLUSIVE, 0, 0, &pStream );
if(hRes == S_OK)
{
/* Write info to stream */
hRes = IStream_Write(pStream, &dwDataLength, sizeof(dwDataLength), NULL);
hRes = IStream_Write(pStream, pData, dwDataLength, NULL);
IStream_Release(pStream);
}
}
/*************************************************************************
* OLECONVERT_GetOLE10ProgID [Internal]
*
* Finds the ProgID (or OleTypeID) from the IStorage
*
* PARAMS
* pStorage [I] The Src IStorage to get the ProgID
* strProgID [I] the ProgID string to get
* dwSize [I] the size of the string
*
* RETURNS
* Success: S_OK
* Failure: REGDB_E_CLASSNOTREG if cannot reconstruct the stream
*
* NOTES
* This function is used by OleConvertIStorageToOLESTREAM only.
*
*
*/
Sync to Wine-20050725: Robert Shearman <rob@codeweavers.com> - Marshal return value from IRemUnknown_RemQueryInterface. - We should be starting with 1 reference. - IRpcStubBuffer_Disconnect can be called more than once. - Silence now noisy error messages caused by changes in the way we call ipid_to_stubmanager. Move the error message to the one place it is needed. - By-pass the RPC runtime if possible when calling an STA by posting a message directly to the apartment window for it to process. Fixes a deadlock in InstallShield caused by having to create a thread when freeing an object that comes from an STA apartment. Added tests that fail without this fix. - Hack around broken state management so InstallShield works. - Delete the stub manager outside of the apartment critical section because the deletion may require the object to re-enter the apartment. - Always query for the correct stub interface, otherwise we will be pointing to the completely wrong object when a proxy does a queryinterface. - Remove assumption that the stub buffer will handle the lifetime of the object. Alex Villacis Lasso <a_villacis@palosanto.com> - Initialize RegisteredClass properly in CoRegisterClassObject to prevent crash in CoRevokeClassObject when accessing (uninitialized) pMarshalledData. Mike McCormack <mike@codeweavers.com> - Fix gcc 4.0 -Wpointer-sign warnings. Vitaly Lipatov <lav@etersoft.ru> - Added some documentation. Stefan Huehner <stefan@huehner.org> - Fix some missing-declarations warnings. Marcus Meissner <meissner@suse.de> - 16bit interfaces are cdecl, so drop the WINAPI. - 16bit COM interfaces are cdecl, not WINAPI. - OleInitializeWOW gets 2 arguments. - Added OleSetMenuDescriptor16 stub. Marcus Meissner <marcus@jet.franken.de> - Implemented IsValidInterface16, CoMemAlloc. Added debug to HGLOBALLockBytes16_QueryInterface. svn path=/trunk/; revision=17332
2005-08-12 17:19:46 +00:00
static HRESULT OLECONVERT_GetOLE10ProgID(LPSTORAGE pStorage, char *strProgID, DWORD *dwSize)
{
HRESULT hRes;
IStream *pStream;
LARGE_INTEGER iSeekPos;
OLECONVERT_ISTORAGE_COMPOBJ CompObj;
static const WCHAR wstrStreamName[] = {1,'C', 'o', 'm', 'p', 'O', 'b', 'j', 0};
/* Open the CompObj Stream */
hRes = IStorage_OpenStream(pStorage, wstrStreamName, NULL,
STGM_READ | STGM_SHARE_EXCLUSIVE, 0, &pStream );
if(hRes == S_OK)
{
/*Get the OleType from the CompObj Stream */
iSeekPos.u.LowPart = sizeof(CompObj.byUnknown1) + sizeof(CompObj.clsid);
iSeekPos.u.HighPart = 0;
IStream_Seek(pStream, iSeekPos, STREAM_SEEK_SET, NULL);
IStream_Read(pStream, &CompObj.dwCLSIDNameLength, sizeof(CompObj.dwCLSIDNameLength), NULL);
iSeekPos.u.LowPart = CompObj.dwCLSIDNameLength;
IStream_Seek(pStream, iSeekPos, STREAM_SEEK_CUR , NULL);
IStream_Read(pStream, &CompObj.dwOleTypeNameLength, sizeof(CompObj.dwOleTypeNameLength), NULL);
iSeekPos.u.LowPart = CompObj.dwOleTypeNameLength;
IStream_Seek(pStream, iSeekPos, STREAM_SEEK_CUR , NULL);
IStream_Read(pStream, dwSize, sizeof(*dwSize), NULL);
if(*dwSize > 0)
{
IStream_Read(pStream, strProgID, *dwSize, NULL);
}
IStream_Release(pStream);
}
else
{
STATSTG stat;
LPOLESTR wstrProgID;
/* Get the OleType from the registry */
REFCLSID clsid = &(stat.clsid);
IStorage_Stat(pStorage, &stat, STATFLAG_NONAME);
hRes = ProgIDFromCLSID(clsid, &wstrProgID);
if(hRes == S_OK)
{
*dwSize = WideCharToMultiByte(CP_ACP, 0, wstrProgID, -1, strProgID, *dwSize, NULL, FALSE);
CoTaskMemFree(wstrProgID);
}
}
return hRes;
}
/*************************************************************************
* OLECONVERT_GetOle10PresData [Internal]
*
* Converts IStorage "/001Ole10Native" stream to a OLE10 Stream
*
* PARAMS
* pStorage [I] Src IStroage
* pOleStream [I] Dest OleStream Mem Struct
*
* RETURNS
* Nothing
*
* NOTES
* This function is used by OleConvertIStorageToOLESTREAM only.
*
* Memory allocated for pData must be freed by the caller
*
*
*/
Sync to Wine-20050725: Robert Shearman <rob@codeweavers.com> - Marshal return value from IRemUnknown_RemQueryInterface. - We should be starting with 1 reference. - IRpcStubBuffer_Disconnect can be called more than once. - Silence now noisy error messages caused by changes in the way we call ipid_to_stubmanager. Move the error message to the one place it is needed. - By-pass the RPC runtime if possible when calling an STA by posting a message directly to the apartment window for it to process. Fixes a deadlock in InstallShield caused by having to create a thread when freeing an object that comes from an STA apartment. Added tests that fail without this fix. - Hack around broken state management so InstallShield works. - Delete the stub manager outside of the apartment critical section because the deletion may require the object to re-enter the apartment. - Always query for the correct stub interface, otherwise we will be pointing to the completely wrong object when a proxy does a queryinterface. - Remove assumption that the stub buffer will handle the lifetime of the object. Alex Villacis Lasso <a_villacis@palosanto.com> - Initialize RegisteredClass properly in CoRegisterClassObject to prevent crash in CoRevokeClassObject when accessing (uninitialized) pMarshalledData. Mike McCormack <mike@codeweavers.com> - Fix gcc 4.0 -Wpointer-sign warnings. Vitaly Lipatov <lav@etersoft.ru> - Added some documentation. Stefan Huehner <stefan@huehner.org> - Fix some missing-declarations warnings. Marcus Meissner <meissner@suse.de> - 16bit interfaces are cdecl, so drop the WINAPI. - 16bit COM interfaces are cdecl, not WINAPI. - OleInitializeWOW gets 2 arguments. - Added OleSetMenuDescriptor16 stub. Marcus Meissner <marcus@jet.franken.de> - Implemented IsValidInterface16, CoMemAlloc. Added debug to HGLOBALLockBytes16_QueryInterface. svn path=/trunk/; revision=17332
2005-08-12 17:19:46 +00:00
static void OLECONVERT_GetOle10PresData(LPSTORAGE pStorage, OLECONVERT_OLESTREAM_DATA *pOleStreamData)
{
HRESULT hRes;
IStream *pStream;
static const WCHAR wstrStreamName[] = {1, 'O', 'l', 'e', '1', '0', 'N', 'a', 't', 'i', 'v', 'e', 0};
/* Initialize Default data for OLESTREAM */
pOleStreamData[0].dwOleID = OLESTREAM_ID;
pOleStreamData[0].dwTypeID = 2;
pOleStreamData[1].dwOleID = OLESTREAM_ID;
pOleStreamData[1].dwTypeID = 0;
pOleStreamData[0].dwMetaFileWidth = 0;
pOleStreamData[0].dwMetaFileHeight = 0;
pOleStreamData[0].pData = NULL;
pOleStreamData[1].pData = NULL;
/* Open Ole10Native Stream */
hRes = IStorage_OpenStream(pStorage, wstrStreamName, NULL,
STGM_READ | STGM_SHARE_EXCLUSIVE, 0, &pStream );
if(hRes == S_OK)
{
/* Read Size and Data */
IStream_Read(pStream, &(pOleStreamData->dwDataLength), sizeof(pOleStreamData->dwDataLength), NULL);
if(pOleStreamData->dwDataLength > 0)
{
pOleStreamData->pData = HeapAlloc(GetProcessHeap(),0,pOleStreamData->dwDataLength);
IStream_Read(pStream, pOleStreamData->pData, pOleStreamData->dwDataLength, NULL);
}
IStream_Release(pStream);
}
}
/*************************************************************************
* OLECONVERT_GetOle20PresData[Internal]
*
* Converts IStorage "/002OlePres000" stream to a OLE10 Stream
*
* PARAMS
* pStorage [I] Src IStroage
* pOleStreamData [I] Dest OleStream Mem Struct
*
* RETURNS
* Nothing
*
* NOTES
* This function is used by OleConvertIStorageToOLESTREAM only.
*
* Memory allocated for pData must be freed by the caller
*/
Sync to Wine-20050725: Robert Shearman <rob@codeweavers.com> - Marshal return value from IRemUnknown_RemQueryInterface. - We should be starting with 1 reference. - IRpcStubBuffer_Disconnect can be called more than once. - Silence now noisy error messages caused by changes in the way we call ipid_to_stubmanager. Move the error message to the one place it is needed. - By-pass the RPC runtime if possible when calling an STA by posting a message directly to the apartment window for it to process. Fixes a deadlock in InstallShield caused by having to create a thread when freeing an object that comes from an STA apartment. Added tests that fail without this fix. - Hack around broken state management so InstallShield works. - Delete the stub manager outside of the apartment critical section because the deletion may require the object to re-enter the apartment. - Always query for the correct stub interface, otherwise we will be pointing to the completely wrong object when a proxy does a queryinterface. - Remove assumption that the stub buffer will handle the lifetime of the object. Alex Villacis Lasso <a_villacis@palosanto.com> - Initialize RegisteredClass properly in CoRegisterClassObject to prevent crash in CoRevokeClassObject when accessing (uninitialized) pMarshalledData. Mike McCormack <mike@codeweavers.com> - Fix gcc 4.0 -Wpointer-sign warnings. Vitaly Lipatov <lav@etersoft.ru> - Added some documentation. Stefan Huehner <stefan@huehner.org> - Fix some missing-declarations warnings. Marcus Meissner <meissner@suse.de> - 16bit interfaces are cdecl, so drop the WINAPI. - 16bit COM interfaces are cdecl, not WINAPI. - OleInitializeWOW gets 2 arguments. - Added OleSetMenuDescriptor16 stub. Marcus Meissner <marcus@jet.franken.de> - Implemented IsValidInterface16, CoMemAlloc. Added debug to HGLOBALLockBytes16_QueryInterface. svn path=/trunk/; revision=17332
2005-08-12 17:19:46 +00:00
static void OLECONVERT_GetOle20PresData(LPSTORAGE pStorage, OLECONVERT_OLESTREAM_DATA *pOleStreamData)
{
HRESULT hRes;
IStream *pStream;
OLECONVERT_ISTORAGE_OLEPRES olePress;
static const WCHAR wstrStreamName[] = {2, 'O', 'l', 'e', 'P', 'r', 'e', 's', '0', '0', '0', 0};
/* Initialize Default data for OLESTREAM */
pOleStreamData[0].dwOleID = OLESTREAM_ID;
pOleStreamData[0].dwTypeID = 2;
pOleStreamData[0].dwMetaFileWidth = 0;
pOleStreamData[0].dwMetaFileHeight = 0;
pOleStreamData[0].dwDataLength = OLECONVERT_WriteOLE20ToBuffer(pStorage, &(pOleStreamData[0].pData));
pOleStreamData[1].dwOleID = OLESTREAM_ID;
pOleStreamData[1].dwTypeID = 0;
pOleStreamData[1].dwOleTypeNameLength = 0;
pOleStreamData[1].strOleTypeName[0] = 0;
pOleStreamData[1].dwMetaFileWidth = 0;
pOleStreamData[1].dwMetaFileHeight = 0;
pOleStreamData[1].pData = NULL;
pOleStreamData[1].dwDataLength = 0;
/* Open OlePress000 stream */
hRes = IStorage_OpenStream(pStorage, wstrStreamName, NULL,
STGM_READ | STGM_SHARE_EXCLUSIVE, 0, &pStream );
if(hRes == S_OK)
{
LARGE_INTEGER iSeekPos;
METAFILEPICT16 MetaFilePict;
static const char strMetafilePictName[] = "METAFILEPICT";
/* Set the TypeID for a Metafile */
pOleStreamData[1].dwTypeID = 5;
/* Set the OleTypeName to Metafile */
pOleStreamData[1].dwOleTypeNameLength = strlen(strMetafilePictName) +1;
strcpy(pOleStreamData[1].strOleTypeName, strMetafilePictName);
iSeekPos.u.HighPart = 0;
iSeekPos.u.LowPart = sizeof(olePress.byUnknown1);
/* Get Presentation Data */
IStream_Seek(pStream, iSeekPos, STREAM_SEEK_SET, NULL);
IStream_Read(pStream, &(olePress.dwExtentX), sizeof(olePress.dwExtentX), NULL);
IStream_Read(pStream, &(olePress.dwExtentY), sizeof(olePress.dwExtentY), NULL);
IStream_Read(pStream, &(olePress.dwSize), sizeof(olePress.dwSize), NULL);
/*Set width and Height */
pOleStreamData[1].dwMetaFileWidth = olePress.dwExtentX;
pOleStreamData[1].dwMetaFileHeight = -olePress.dwExtentY;
if(olePress.dwSize > 0)
{
/* Set Length */
pOleStreamData[1].dwDataLength = olePress.dwSize + sizeof(METAFILEPICT16);
/* Set MetaFilePict struct */
MetaFilePict.mm = 8;
MetaFilePict.xExt = olePress.dwExtentX;
MetaFilePict.yExt = olePress.dwExtentY;
MetaFilePict.hMF = 0;
/* Get Metafile Data */
pOleStreamData[1].pData = HeapAlloc(GetProcessHeap(),0,pOleStreamData[1].dwDataLength);
memcpy(pOleStreamData[1].pData, &MetaFilePict, sizeof(MetaFilePict));
IStream_Read(pStream, &(pOleStreamData[1].pData[sizeof(MetaFilePict)]), pOleStreamData[1].dwDataLength-sizeof(METAFILEPICT16), NULL);
}
IStream_Release(pStream);
}
}
/*************************************************************************
* OleConvertOLESTREAMToIStorage [OLE32.@]
*
* Read info on MSDN
*
* TODO
* DVTARGETDEVICE parameter is not handled
* Still unsure of some mem fields for OLE 10 Stream
* Still some unknowns for the IStorage: "\002OlePres000", "\001CompObj",
* and "\001OLE" streams
*
*/
HRESULT WINAPI OleConvertOLESTREAMToIStorage (
LPOLESTREAM pOleStream,
LPSTORAGE pstg,
const DVTARGETDEVICE* ptd)
{
int i;
HRESULT hRes=S_OK;
OLECONVERT_OLESTREAM_DATA pOleStreamData[2];
TRACE("%p %p %p\n", pOleStream, pstg, ptd);
memset(pOleStreamData, 0, sizeof(pOleStreamData));
if(ptd != NULL)
{
FIXME("DVTARGETDEVICE is not NULL, unhandled parameter\n");
}
if(pstg == NULL || pOleStream == NULL)
{
hRes = E_INVALIDARG;
}
if(hRes == S_OK)
{
/* Load the OLESTREAM to Memory */
hRes = OLECONVERT_LoadOLE10(pOleStream, &pOleStreamData[0], TRUE);
}
if(hRes == S_OK)
{
/* Load the OLESTREAM to Memory (part 2)*/
hRes = OLECONVERT_LoadOLE10(pOleStream, &pOleStreamData[1], FALSE);
}
if(hRes == S_OK)
{
if(pOleStreamData[0].dwDataLength > sizeof(STORAGE_magic))
{
/* Do we have the IStorage Data in the OLESTREAM */
if(memcmp(pOleStreamData[0].pData, STORAGE_magic, sizeof(STORAGE_magic)) ==0)
{
OLECONVERT_GetOLE20FromOLE10(pstg, pOleStreamData[0].pData, pOleStreamData[0].dwDataLength);
OLECONVERT_CreateOlePresStream(pstg, pOleStreamData[1].dwMetaFileWidth, pOleStreamData[1].dwMetaFileHeight, pOleStreamData[1].pData, pOleStreamData[1].dwDataLength);
}
else
{
/* It must be an original OLE 1.0 source */
OLECONVERT_CreateOle10NativeStream(pstg, pOleStreamData[0].pData, pOleStreamData[0].dwDataLength);
}
}
else
{
/* It must be an original OLE 1.0 source */
OLECONVERT_CreateOle10NativeStream(pstg, pOleStreamData[0].pData, pOleStreamData[0].dwDataLength);
}
/* Create CompObj Stream if necessary */
hRes = OLECONVERT_CreateCompObjStream(pstg, pOleStreamData[0].strOleTypeName);
if(hRes == S_OK)
{
/*Create the Ole Stream if necessary */
STORAGE_CreateOleStream(pstg, 0);
}
}
/* Free allocated memory */
for(i=0; i < 2; i++)
{
HeapFree(GetProcessHeap(),0,pOleStreamData[i].pData);
HeapFree(GetProcessHeap(),0,pOleStreamData[i].pstrOleObjFileName);
pOleStreamData[i].pstrOleObjFileName = NULL;
}
return hRes;
}
/*************************************************************************
* OleConvertIStorageToOLESTREAM [OLE32.@]
*
* Read info on MSDN
*
* Read info on MSDN
*
* TODO
* Still unsure of some mem fields for OLE 10 Stream
* Still some unknowns for the IStorage: "\002OlePres000", "\001CompObj",
* and "\001OLE" streams.
*
*/
HRESULT WINAPI OleConvertIStorageToOLESTREAM (
LPSTORAGE pstg,
LPOLESTREAM pOleStream)
{
int i;
HRESULT hRes = S_OK;
IStream *pStream;
OLECONVERT_OLESTREAM_DATA pOleStreamData[2];
static const WCHAR wstrStreamName[] = {1, 'O', 'l', 'e', '1', '0', 'N', 'a', 't', 'i', 'v', 'e', 0};
TRACE("%p %p\n", pstg, pOleStream);
memset(pOleStreamData, 0, sizeof(pOleStreamData));
if(pstg == NULL || pOleStream == NULL)
{
hRes = E_INVALIDARG;
}
if(hRes == S_OK)
{
/* Get the ProgID */
pOleStreamData[0].dwOleTypeNameLength = OLESTREAM_MAX_STR_LEN;
hRes = OLECONVERT_GetOLE10ProgID(pstg, pOleStreamData[0].strOleTypeName, &(pOleStreamData[0].dwOleTypeNameLength));
}
if(hRes == S_OK)
{
/* Was it originally Ole10 */
hRes = IStorage_OpenStream(pstg, wstrStreamName, 0, STGM_READ | STGM_SHARE_EXCLUSIVE, 0, &pStream);
if(hRes == S_OK)
{
IStream_Release(pStream);
/* Get Presentation Data for Ole10Native */
OLECONVERT_GetOle10PresData(pstg, pOleStreamData);
}
else
{
/* Get Presentation Data (OLE20) */
OLECONVERT_GetOle20PresData(pstg, pOleStreamData);
}
/* Save OLESTREAM */
hRes = OLECONVERT_SaveOLE10(&(pOleStreamData[0]), pOleStream);
if(hRes == S_OK)
{
hRes = OLECONVERT_SaveOLE10(&(pOleStreamData[1]), pOleStream);
}
}
/* Free allocated memory */
for(i=0; i < 2; i++)
{
HeapFree(GetProcessHeap(),0,pOleStreamData[i].pData);
}
return hRes;
}
enum stream_1ole_flags {
OleStream_LinkedObject = 0x00000001,
OleStream_Convert = 0x00000004
};
/***********************************************************************
* GetConvertStg (OLE32.@)
*/
HRESULT WINAPI GetConvertStg(IStorage *stg)
{
static const WCHAR stream_1oleW[] = {1,'O','l','e',0};
static const DWORD version_magic = 0x02000001;
DWORD header[2];
IStream *stream;
HRESULT hr;
TRACE("%p\n", stg);
if (!stg) return E_INVALIDARG;
hr = IStorage_OpenStream(stg, stream_1oleW, NULL, STGM_READ | STGM_SHARE_EXCLUSIVE, 0, &stream);
if (FAILED(hr)) return hr;
hr = IStream_Read(stream, header, sizeof(header), NULL);
IStream_Release(stream);
if (FAILED(hr)) return hr;
if (header[0] != version_magic)
{
ERR("got wrong version magic for 1Ole stream, 0x%08x\n", header[0]);
return E_FAIL;
}
return header[1] & OleStream_Convert ? S_OK : S_FALSE;
}
/***********************************************************************
* SetConvertStg (OLE32.@)
*/
HRESULT WINAPI SetConvertStg(IStorage *storage, BOOL convert)
{
static const WCHAR stream_1oleW[] = {1,'O','l','e',0};
DWORD flags = convert ? OleStream_Convert : 0;
IStream *stream;
DWORD header[2];
HRESULT hr;
TRACE("(%p, %d)\n", storage, convert);
hr = IStorage_OpenStream(storage, stream_1oleW, NULL, STGM_READWRITE | STGM_SHARE_EXCLUSIVE, 0, &stream);
if (FAILED(hr))
{
if (hr != STG_E_FILENOTFOUND)
return hr;
return STORAGE_CreateOleStream(storage, flags);
}
hr = IStream_Read(stream, header, sizeof(header), NULL);
if (FAILED(hr))
{
IStream_Release(stream);
return hr;
}
/* update flag if differs */
if ((header[1] ^ flags) & OleStream_Convert)
{
LARGE_INTEGER pos = {{0}};
if (header[1] & OleStream_Convert)
flags = header[1] & ~OleStream_Convert;
else
flags = header[1] | OleStream_Convert;
pos.QuadPart = sizeof(DWORD);
hr = IStream_Seek(stream, pos, STREAM_SEEK_SET, NULL);
if (FAILED(hr))
{
IStream_Release(stream);
return hr;
}
hr = IStream_Write(stream, &flags, sizeof(flags), NULL);
}
IStream_Release(stream);
return hr;
}