/* * 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 * * 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 #include #include #include #include #define COBJMACROS #define NONAMELESSUNION #include "windef.h" #include "winbase.h" #include "winnls.h" #include "winuser.h" #include "wine/unicode.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 = toupperW(*name1++) - toupperW(*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, ¤tEntry); 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, ¤tEntry); 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, ¤tEntry); current = previous; } else { currentEntry.leftChild = newEntryIndex; StorageBaseImpl_WriteDirEntry(This, current, ¤tEntry); found = TRUE; } } else if (diff > 0) { if (next != DIRENTRY_NULL) { StorageBaseImpl_ReadDirEntry(This, next, ¤tEntry); current = next; } else { currentEntry.rightChild = newEntryIndex; StorageBaseImpl_WriteDirEntry(This, current, ¤tEntry); 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, ¤tEntry); } 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); 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); return S_OK; } 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]; hr = StorageBaseImpl_ReadDirEntry(This->parentStorage, This->parentStorage->storageDirEntry, &entry); searchNode = entry.dirRootEntry; while (SUCCEEDED(hr) && searchNode != DIRENTRY_NULL) { 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)) { *ref = result; if (result != DIRENTRY_NULL) memcpy(This->name, result_name, sizeof(result_name)); } 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; if ( (rgelt==0) || ( (celt!=1) && (pceltFetched==0) ) ) return E_INVALIDARG; if (This->parentStorage->reverted) 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, ¤tSearchNode); if (FAILED(hr) || currentSearchNode == DIRENTRY_NULL) break; /* * Read the entry from the storage. */ StorageBaseImpl_ReadDirEntry(This->parentStorage, currentSearchNode, ¤tEntry); /* * Copy the information to the return buffer. */ StorageUtl_CopyDirEntryToSTATSTG(This->parentStorage, currentReturnStruct, ¤tEntry, STATFLAG_DEFAULT); /* * Step to the next item in the iteration */ (*pceltFetched)++; currentReturnStruct++; } if (SUCCEEDED(hr) && *pceltFetched != celt) hr = S_FALSE; 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; if (This->parentStorage->reverted) return STG_E_REVERTED; while ( (objectFetched < celt) ) { hr = IEnumSTATSTGImpl_GetNextRef(This, ¤tSearchNode); if (FAILED(hr) || currentSearchNode == DIRENTRY_NULL) break; objectFetched++; } if (SUCCEEDED(hr) && objectFetched != celt) return S_FALSE; return hr; } static HRESULT WINAPI IEnumSTATSTGImpl_Reset( IEnumSTATSTG* iface) { IEnumSTATSTGImpl* const This = impl_from_IEnumSTATSTG(iface); if (This->parentStorage->reverted) 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; if (This->parentStorage->reverted) 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); 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 return E_NOINTERFACE; IStorage_AddRef(iface); 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 ( lstrcmpW(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 ); return hr; } static BOOL StorageBaseImpl_IsStreamOpen(StorageBaseImpl * stg, DirRef streamEntry) { StgStreamImpl *strm; 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; 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, ¤tEntry); /* * 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) * * 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, ¤tEntry); 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, ¤tEntry); if (SUCCEEDED(res)) { StorageUtl_CopyDirEntryToSTATSTG( This, pstatstg, ¤tEntry, 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, ¤tEntry); 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, ¤tEntry); 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 */ strcpyW(currentEntry.name, pwcsNewName); /* Delete any sibling links */ currentEntry.leftChild = DIRENTRY_NULL; currentEntry.rightChild = DIRENTRY_NULL; StorageBaseImpl_WriteDirEntry(This, currentEntryRef, ¤tEntry); /* 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, ¤tEntry); 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; strcpyW(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; TRACE("(%p, %p)\n", iface, clsid); if (This->reverted) return STG_E_REVERTED; hRes = StorageBaseImpl_ReadDirEntry(This, This->storageDirEntry, ¤tEntry); if (SUCCEEDED(hRes)) { currentEntry.clsid = *clsid; hRes = StorageBaseImpl_WriteDirEntry(This, This->storageDirEntry, ¤tEntry); } 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, ¤tEntry); 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; } strcpyW(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 ); 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 ( lstrcmpW(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; /* 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) { return hr; } /* * Enumerate the elements */ hr = IStorage_EnumElements(childStorage, 0, 0, 0, &elements); if (FAILED(hr)) { IStorage_Release(childStorage); return hr; } do { /* * Obtain the next element */ hr = IEnumSTATSTG_Next(elements, 1, ¤tElement, 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); 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) { return(hr); } /* * Zap the stream */ hr = IStream_SetSize(pis, size); if(hr != S_OK) { return hr; } /* * Release the stream object. */ IStream_Release(pis); 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 ) { 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) 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); 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; iblockChainCache[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; iblockChainCache[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; iblockChainCache[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; iextBigBlockDepotCount; 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)); strcpyW(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, ¤tEntry); 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; iblockChainCache[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; jlocked_bytes)/sizeof(This->locked_bytes[0]); 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) && iblockChainCache[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; iblockChainCache[i]); for (i=0; ilocked_bytes)/sizeof(This->locked_bytes[0]); 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; ientries_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); } 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; ientries_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)) 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; } } 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)) 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)) return hr; hr = TransactedSnapshotImpl_MakeStreamDirty(This, index); if (FAILED(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); 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)) 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; 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)) return hr; hr = TransactedSnapshotImpl_EnsureReadEntry(This, dst); if (FAILED(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; } } 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)); strcpyW(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)); 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; } 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) < 0x100) { 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, sizeof(szwClipName)/sizeof(szwClipName[0]) ); 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]; BYTE pCompObjUnknown1[] = {0x01, 0x00, 0xFE, 0xFF, 0x03, 0x0A, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF}; 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; 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. * */ 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}; 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 }; 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. * * */ 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 * * */ 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 */ 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; }