| /* | |
| * 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 <assert.h> | |
| #include <stdarg.h> | |
| #include <stdio.h> | |
| #include <stdlib.h> | |
| #include <string.h> | |
| #define COBJMACROS | |
| #define NONAMELESSUNION | |
| #include "windef.h" | |
| #include "winbase.h" | |
| #include "winnls.h" | |
| #include "winuser.h" | |
| #include "wine/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); | |
| TRACE("%p,%s,%p\n", iface, debugstr_guid(riid), ppvObject); | |
| if (ppvObject==0) | |
| return E_INVALIDARG; | |
| *ppvObject = 0; | |
| if (IsEqualGUID(&IID_IUnknown, riid) || | |
| IsEqualGUID(&IID_IEnumSTATSTG, riid)) | |
| { | |
| *ppvObject = &This->IEnumSTATSTG_iface; | |
| IEnumSTATSTG_AddRef(&This->IEnumSTATSTG_iface); | |
| TRACE("<-- %p\n", *ppvObject); | |
| return S_OK; | |
| } | |
| TRACE("<-- E_NOINTERFACE\n"); | |
| return E_NOINTERFACE; | |
| } | |
| static ULONG WINAPI IEnumSTATSTGImpl_AddRef( | |
| IEnumSTATSTG* iface) | |
| { | |
| IEnumSTATSTGImpl* const This = impl_from_IEnumSTATSTG(iface); | |
| return InterlockedIncrement(&This->ref); | |
| } | |
| static ULONG WINAPI IEnumSTATSTGImpl_Release( | |
| IEnumSTATSTG* iface) | |
| { | |
| IEnumSTATSTGImpl* const This = impl_from_IEnumSTATSTG(iface); | |
| ULONG newRef; | |
| newRef = InterlockedDecrement(&This->ref); | |
| if (newRef==0) | |
| { | |
| IEnumSTATSTGImpl_Destroy(This); | |
| } | |
| return newRef; | |
| } | |
| static HRESULT IEnumSTATSTGImpl_GetNextRef( | |
| IEnumSTATSTGImpl* This, | |
| DirRef *ref) | |
| { | |
| DirRef result = DIRENTRY_NULL; | |
| DirRef searchNode; | |
| DirEntry entry; | |
| HRESULT hr; | |
| WCHAR result_name[DIRENTRY_NAME_MAX_LEN]; | |
| TRACE("%p,%p\n", This, ref); | |
| 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)); | |
| } | |
| TRACE("<-- %08x\n", hr); | |
| return hr; | |
| } | |
| static HRESULT WINAPI IEnumSTATSTGImpl_Next( | |
| IEnumSTATSTG* iface, | |
| ULONG celt, | |
| STATSTG* rgelt, | |
| ULONG* pceltFetched) | |
| { | |
| IEnumSTATSTGImpl* const This = impl_from_IEnumSTATSTG(iface); | |
| DirEntry currentEntry; | |
| STATSTG* currentReturnStruct = rgelt; | |
| ULONG objectFetched = 0; | |
| DirRef currentSearchNode; | |
| HRESULT hr=S_OK; | |
| TRACE("%p,%u,%p,%p\n", iface, celt, rgelt, pceltFetched); | |
| if ( (rgelt==0) || ( (celt!=1) && (pceltFetched==0) ) ) | |
| return E_INVALIDARG; | |
| if (This->parentStorage->reverted) | |
| { | |
| TRACE("<-- STG_E_REVERTED\n"); | |
| return STG_E_REVERTED; | |
| } | |
| /* | |
| * To avoid the special case, get another pointer to a ULONG value if | |
| * the caller didn't supply one. | |
| */ | |
| if (pceltFetched==0) | |
| pceltFetched = &objectFetched; | |
| /* | |
| * Start the iteration, we will iterate until we hit the end of the | |
| * linked list or until we hit the number of items to iterate through | |
| */ | |
| *pceltFetched = 0; | |
| while ( *pceltFetched < celt ) | |
| { | |
| hr = IEnumSTATSTGImpl_GetNextRef(This, ¤tSearchNode); | |
| if (FAILED(hr) || currentSearchNode == DIRENTRY_NULL) | |
| { | |
| memset(currentReturnStruct, 0, sizeof(*currentReturnStruct)); | |
| break; | |
| } | |
| /* | |
| * Read the entry from the storage. | |
| */ | |
| hr = StorageBaseImpl_ReadDirEntry(This->parentStorage, | |
| currentSearchNode, | |
| ¤tEntry); | |
| if (FAILED(hr)) break; | |
| /* | |
| * 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; | |
| TRACE("<-- %08x (asked %u, got %u)\n", hr, celt, *pceltFetched); | |
| return hr; | |
| } | |
| static HRESULT WINAPI IEnumSTATSTGImpl_Skip( | |
| IEnumSTATSTG* iface, | |
| ULONG celt) | |
| { | |
| IEnumSTATSTGImpl* const This = impl_from_IEnumSTATSTG(iface); | |
| ULONG objectFetched = 0; | |
| DirRef currentSearchNode; | |
| HRESULT hr=S_OK; | |
| TRACE("%p,%u\n", iface, celt); | |
| if (This->parentStorage->reverted) | |
| { | |
| TRACE("<-- STG_E_REVERTED\n"); | |
| return STG_E_REVERTED; | |
| } | |
| while ( (objectFetched < celt) ) | |
| { | |
| hr = IEnumSTATSTGImpl_GetNextRef(This, ¤tSearchNode); | |
| if (FAILED(hr) || currentSearchNode == DIRENTRY_NULL) | |
| break; | |
| objectFetched++; | |
| } | |
| if (SUCCEEDED(hr) && objectFetched != celt) | |
| return S_FALSE; | |
| TRACE("<-- %08x\n", hr); | |
| return hr; | |
| } | |
| static HRESULT WINAPI IEnumSTATSTGImpl_Reset( | |
| IEnumSTATSTG* iface) | |
| { | |
| IEnumSTATSTGImpl* const This = impl_from_IEnumSTATSTG(iface); | |
| TRACE("%p\n", iface); | |
| if (This->parentStorage->reverted) | |
| { | |
| TRACE("<-- STG_E_REVERTED\n"); | |
| return STG_E_REVERTED; | |
| } | |
| This->name[0] = 0; | |
| return S_OK; | |
| } | |
| static IEnumSTATSTGImpl* IEnumSTATSTGImpl_Construct(StorageBaseImpl*,DirRef); | |
| static HRESULT WINAPI IEnumSTATSTGImpl_Clone( | |
| IEnumSTATSTG* iface, | |
| IEnumSTATSTG** ppenum) | |
| { | |
| IEnumSTATSTGImpl* const This = impl_from_IEnumSTATSTG(iface); | |
| IEnumSTATSTGImpl* newClone; | |
| TRACE("%p,%p\n", iface, ppenum); | |
| if (This->parentStorage->reverted) | |
| { | |
| TRACE("<-- STG_E_REVERTED\n"); | |
| return STG_E_REVERTED; | |
| } | |
| if (ppenum==0) | |
| return E_INVALIDARG; | |
| newClone = IEnumSTATSTGImpl_Construct(This->parentStorage, | |
| This->storageDirEntry); | |
| if (!newClone) | |
| { | |
| *ppenum = NULL; | |
| return E_OUTOFMEMORY; | |
| } | |
| /* | |
| * The new clone enumeration must point to the same current node as | |
| * the old one. | |
| */ | |
| memcpy(newClone->name, This->name, sizeof(newClone->name)); | |
| *ppenum = &newClone->IEnumSTATSTG_iface; | |
| return S_OK; | |
| } | |
| /* | |
| * Virtual function table for the IEnumSTATSTGImpl class. | |
| */ | |
| static const IEnumSTATSTGVtbl IEnumSTATSTGImpl_Vtbl = | |
| { | |
| IEnumSTATSTGImpl_QueryInterface, | |
| IEnumSTATSTGImpl_AddRef, | |
| IEnumSTATSTGImpl_Release, | |
| IEnumSTATSTGImpl_Next, | |
| IEnumSTATSTGImpl_Skip, | |
| IEnumSTATSTGImpl_Reset, | |
| IEnumSTATSTGImpl_Clone | |
| }; | |
| static IEnumSTATSTGImpl* IEnumSTATSTGImpl_Construct( | |
| StorageBaseImpl* parentStorage, | |
| DirRef storageDirEntry) | |
| { | |
| IEnumSTATSTGImpl* newEnumeration; | |
| newEnumeration = HeapAlloc(GetProcessHeap(), 0, sizeof(IEnumSTATSTGImpl)); | |
| if (newEnumeration) | |
| { | |
| newEnumeration->IEnumSTATSTG_iface.lpVtbl = &IEnumSTATSTGImpl_Vtbl; | |
| newEnumeration->ref = 1; | |
| newEnumeration->name[0] = 0; | |
| /* | |
| * We want to nail-down the reference to the storage in case the | |
| * enumeration out-lives the storage in the client application. | |
| */ | |
| newEnumeration->parentStorage = parentStorage; | |
| IStorage_AddRef(&newEnumeration->parentStorage->IStorage_iface); | |
| newEnumeration->storageDirEntry = storageDirEntry; | |
| } | |
| return newEnumeration; | |
| } | |
| /************************************************************************ | |
| * StorageBaseImpl implementation | |
| ***********************************************************************/ | |
| static inline StorageBaseImpl *impl_from_IStorage( IStorage *iface ) | |
| { | |
| return CONTAINING_RECORD(iface, StorageBaseImpl, IStorage_iface); | |
| } | |
| /************************************************************************ | |
| * StorageBaseImpl_QueryInterface (IUnknown) | |
| * | |
| * This method implements the common QueryInterface for all IStorage | |
| * implementations contained in this file. | |
| * | |
| * See Windows documentation for more details on IUnknown methods. | |
| */ | |
| static HRESULT WINAPI StorageBaseImpl_QueryInterface( | |
| IStorage* iface, | |
| REFIID riid, | |
| void** ppvObject) | |
| { | |
| StorageBaseImpl *This = impl_from_IStorage(iface); | |
| TRACE("%p,%s,%p\n", iface, debugstr_guid(riid), ppvObject); | |
| if (!ppvObject) | |
| return E_INVALIDARG; | |
| *ppvObject = 0; | |
| if (IsEqualGUID(&IID_IUnknown, riid) || | |
| IsEqualGUID(&IID_IStorage, riid)) | |
| { | |
| *ppvObject = &This->IStorage_iface; | |
| } | |
| else if (IsEqualGUID(&IID_IPropertySetStorage, riid)) | |
| { | |
| *ppvObject = &This->IPropertySetStorage_iface; | |
| } | |
| /* locking interface is reported for writer only */ | |
| else if (IsEqualGUID(&IID_IDirectWriterLock, riid) && This->lockingrole == SWMR_Writer) | |
| { | |
| *ppvObject = &This->IDirectWriterLock_iface; | |
| } | |
| else | |
| { | |
| TRACE("<-- E_NOINTERFACE\n"); | |
| return E_NOINTERFACE; | |
| } | |
| IStorage_AddRef(iface); | |
| TRACE("<-- %p\n", *ppvObject); | |
| return S_OK; | |
| } | |
| /************************************************************************ | |
| * StorageBaseImpl_AddRef (IUnknown) | |
| * | |
| * This method implements the common AddRef for all IStorage | |
| * implementations contained in this file. | |
| * | |
| * See Windows documentation for more details on IUnknown methods. | |
| */ | |
| static ULONG WINAPI StorageBaseImpl_AddRef( | |
| IStorage* iface) | |
| { | |
| StorageBaseImpl *This = impl_from_IStorage(iface); | |
| ULONG ref = InterlockedIncrement(&This->ref); | |
| TRACE("(%p) AddRef to %d\n", This, ref); | |
| return ref; | |
| } | |
| /************************************************************************ | |
| * StorageBaseImpl_Release (IUnknown) | |
| * | |
| * This method implements the common Release for all IStorage | |
| * implementations contained in this file. | |
| * | |
| * See Windows documentation for more details on IUnknown methods. | |
| */ | |
| static ULONG WINAPI StorageBaseImpl_Release( | |
| IStorage* iface) | |
| { | |
| StorageBaseImpl *This = impl_from_IStorage(iface); | |
| ULONG ref = InterlockedDecrement(&This->ref); | |
| TRACE("(%p) ReleaseRef to %d\n", This, ref); | |
| if (ref == 0) | |
| { | |
| /* | |
| * Since we are using a system of base-classes, we want to call the | |
| * destructor of the appropriate derived class. To do this, we are | |
| * using virtual functions to implement the destructor. | |
| */ | |
| StorageBaseImpl_Destroy(This); | |
| } | |
| return ref; | |
| } | |
| static HRESULT StorageBaseImpl_CopyStorageEntryTo(StorageBaseImpl *This, | |
| DirRef srcEntry, BOOL skip_storage, BOOL skip_stream, | |
| SNB snbExclude, IStorage *pstgDest); | |
| static HRESULT StorageBaseImpl_CopyChildEntryTo(StorageBaseImpl *This, | |
| DirRef srcEntry, BOOL skip_storage, BOOL skip_stream, | |
| SNB snbExclude, IStorage *pstgDest) | |
| { | |
| DirEntry data; | |
| HRESULT hr; | |
| BOOL skip = FALSE; | |
| IStorage *pstgTmp; | |
| IStream *pstrChild, *pstrTmp; | |
| STATSTG strStat; | |
| if (srcEntry == DIRENTRY_NULL) | |
| return S_OK; | |
| hr = StorageBaseImpl_ReadDirEntry( This, srcEntry, &data ); | |
| if (FAILED(hr)) | |
| return hr; | |
| if ( snbExclude ) | |
| { | |
| WCHAR **snb = snbExclude; | |
| while ( *snb != NULL && !skip ) | |
| { | |
| if ( 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 ); | |
| TRACE("<-- %08x\n", hr); | |
| return hr; | |
| } | |
| static BOOL StorageBaseImpl_IsStreamOpen(StorageBaseImpl * stg, DirRef streamEntry) | |
| { | |
| StgStreamImpl *strm; | |
| TRACE("%p,%d\n", stg, streamEntry); | |
| LIST_FOR_EACH_ENTRY(strm, &stg->strmHead, StgStreamImpl, StrmListEntry) | |
| { | |
| if (strm->dirEntry == streamEntry) | |
| { | |
| return TRUE; | |
| } | |
| } | |
| return FALSE; | |
| } | |
| static BOOL StorageBaseImpl_IsStorageOpen(StorageBaseImpl * stg, DirRef storageEntry) | |
| { | |
| StorageInternalImpl *childstg; | |
| TRACE("%p,%d\n", stg, storageEntry); | |
| LIST_FOR_EACH_ENTRY(childstg, &stg->storageHead, StorageInternalImpl, ParentListEntry) | |
| { | |
| if (childstg->base.storageDirEntry == storageEntry) | |
| { | |
| return TRUE; | |
| } | |
| } | |
| return FALSE; | |
| } | |
| /************************************************************************ | |
| * StorageBaseImpl_OpenStream (IStorage) | |
| * | |
| * This method will open the specified stream object from the current storage. | |
| * | |
| * See Windows documentation for more details on IStorage methods. | |
| */ | |
| static HRESULT WINAPI StorageBaseImpl_OpenStream( | |
| IStorage* iface, | |
| const OLECHAR* pwcsName, /* [string][in] */ | |
| void* reserved1, /* [unique][in] */ | |
| DWORD grfMode, /* [in] */ | |
| DWORD reserved2, /* [in] */ | |
| IStream** ppstm) /* [out] */ | |
| { | |
| StorageBaseImpl *This = impl_from_IStorage(iface); | |
| StgStreamImpl* newStream; | |
| DirEntry currentEntry; | |
| DirRef streamEntryRef; | |
| HRESULT res = STG_E_UNKNOWN; | |
| TRACE("(%p, %s, %p, %x, %d, %p)\n", | |
| iface, debugstr_w(pwcsName), reserved1, grfMode, reserved2, ppstm); | |
| if ( (pwcsName==NULL) || (ppstm==0) ) | |
| { | |
| res = E_INVALIDARG; | |
| goto end; | |
| } | |
| *ppstm = NULL; | |
| if ( FAILED( validateSTGM(grfMode) ) || | |
| STGM_SHARE_MODE(grfMode) != STGM_SHARE_EXCLUSIVE) | |
| { | |
| res = STG_E_INVALIDFLAG; | |
| goto end; | |
| } | |
| /* | |
| * As documented. | |
| */ | |
| if ( (grfMode & STGM_DELETEONRELEASE) || (grfMode & STGM_TRANSACTED) ) | |
| { | |
| res = STG_E_INVALIDFUNCTION; | |
| goto end; | |
| } | |
| if (This->reverted) | |
| { | |
| res = STG_E_REVERTED; | |
| goto end; | |
| } | |
| /* | |
| * Check that we're compatible with the parent's storage mode, but | |
| * only if we are not in transacted mode | |
| */ | |
| if(!(This->openFlags & STGM_TRANSACTED)) { | |
| if ( STGM_ACCESS_MODE( grfMode ) > STGM_ACCESS_MODE( This->openFlags ) ) | |
| { | |
| res = STG_E_INVALIDFLAG; | |
| goto end; | |
| } | |
| } | |
| /* | |
| * Search for the element with the given name | |
| */ | |
| streamEntryRef = findElement( | |
| This, | |
| This->storageDirEntry, | |
| pwcsName, | |
| ¤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, %s)\n", iface, wine_dbgstr_guid(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 ); | |
| TRACE("<-- %08x\n", hr); | |
| return hr; | |
| } | |
| /************************************************************************* | |
| * CopyTo (IStorage) | |
| */ | |
| static HRESULT WINAPI StorageBaseImpl_CopyTo( | |
| IStorage* iface, | |
| DWORD ciidExclude, /* [in] */ | |
| const IID* rgiidExclude, /* [size_is][unique][in] */ | |
| SNB snbExclude, /* [unique][in] */ | |
| IStorage* pstgDest) /* [unique][in] */ | |
| { | |
| StorageBaseImpl *This = impl_from_IStorage(iface); | |
| BOOL skip_storage = FALSE, skip_stream = FALSE; | |
| DWORD i; | |
| TRACE("(%p, %d, %p, %p, %p)\n", | |
| iface, ciidExclude, rgiidExclude, | |
| snbExclude, pstgDest); | |
| if ( pstgDest == 0 ) | |
| return STG_E_INVALIDPOINTER; | |
| for(i = 0; i < ciidExclude; ++i) | |
| { | |
| if(IsEqualGUID(&IID_IStorage, &rgiidExclude[i])) | |
| skip_storage = TRUE; | |
| else if(IsEqualGUID(&IID_IStream, &rgiidExclude[i])) | |
| skip_stream = TRUE; | |
| else | |
| WARN("Unknown excluded GUID: %s\n", debugstr_guid(&rgiidExclude[i])); | |
| } | |
| if (!skip_storage) | |
| { | |
| /* Give up early if it looks like this would be infinitely recursive. | |
| * Oddly enough, this includes some cases that aren't really recursive, like | |
| * copying to a transacted child. */ | |
| IStorage *pstgDestAncestor = pstgDest; | |
| IStorage *pstgDestAncestorChild = NULL; | |
| /* Go up the chain from the destination until we find the source storage. */ | |
| while (pstgDestAncestor != iface) { | |
| pstgDestAncestorChild = pstgDest; | |
| if (pstgDestAncestor->lpVtbl == &TransactedSnapshotImpl_Vtbl) | |
| { | |
| TransactedSnapshotImpl *snapshot = (TransactedSnapshotImpl*) pstgDestAncestor; | |
| pstgDestAncestor = &snapshot->transactedParent->IStorage_iface; | |
| } | |
| else if (pstgDestAncestor->lpVtbl == &StorageInternalImpl_Vtbl) | |
| { | |
| StorageInternalImpl *internal = (StorageInternalImpl*) pstgDestAncestor; | |
| pstgDestAncestor = &internal->parentStorage->IStorage_iface; | |
| } | |
| else | |
| break; | |
| } | |
| if (pstgDestAncestor == iface) | |
| { | |
| BOOL fail = TRUE; | |
| if (pstgDestAncestorChild && snbExclude) | |
| { | |
| StorageBaseImpl *ancestorChildBase = (StorageBaseImpl*)pstgDestAncestorChild; | |
| DirEntry data; | |
| WCHAR **snb = snbExclude; | |
| StorageBaseImpl_ReadDirEntry(ancestorChildBase, ancestorChildBase->storageDirEntry, &data); | |
| while ( *snb != NULL && fail ) | |
| { | |
| if ( 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; | |
| TRACE("%p,%d\n", parentStorage, indexToDelete); | |
| /* Invalidate any open storage objects. */ | |
| LIST_FOR_EACH_ENTRY_SAFE(stg, stg2, &parentStorage->storageHead, StorageInternalImpl, ParentListEntry) | |
| { | |
| if (stg->base.storageDirEntry == indexToDelete) | |
| { | |
| StorageBaseImpl_Invalidate(&stg->base); | |
| } | |
| } | |
| /* | |
| * Open the storage and enumerate it | |
| */ | |
| hr = IStorage_OpenStorage( | |
| &parentStorage->IStorage_iface, | |
| entryDataToDelete.name, | |
| 0, | |
| STGM_WRITE | STGM_SHARE_EXCLUSIVE, | |
| 0, | |
| 0, | |
| &childStorage); | |
| if (hr != S_OK) | |
| { | |
| TRACE("<-- %08x\n", hr); | |
| return hr; | |
| } | |
| /* | |
| * Enumerate the elements | |
| */ | |
| hr = IStorage_EnumElements(childStorage, 0, 0, 0, &elements); | |
| if (FAILED(hr)) | |
| { | |
| IStorage_Release(childStorage); | |
| TRACE("<-- %08x\n", hr); | |
| return hr; | |
| } | |
| do | |
| { | |
| /* | |
| * Obtain the next element | |
| */ | |
| hr = IEnumSTATSTG_Next(elements, 1, ¤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); | |
| TRACE("%08x\n", hr); | |
| return destroyHr; | |
| } | |
| /********************************************************************* | |
| * | |
| * Internal helper function for StorageBaseImpl_DestroyElement() | |
| * | |
| * Perform the deletion of a stream's data | |
| * | |
| */ | |
| static HRESULT deleteStreamContents( | |
| StorageBaseImpl *parentStorage, | |
| DirRef indexToDelete, | |
| DirEntry entryDataToDelete) | |
| { | |
| IStream *pis; | |
| HRESULT hr; | |
| ULARGE_INTEGER size; | |
| StgStreamImpl *strm, *strm2; | |
| /* Invalidate any open stream objects. */ | |
| LIST_FOR_EACH_ENTRY_SAFE(strm, strm2, &parentStorage->strmHead, StgStreamImpl, StrmListEntry) | |
| { | |
| if (strm->dirEntry == indexToDelete) | |
| { | |
| TRACE("Stream deleted %p\n", strm); | |
| strm->parentStorage = NULL; | |
| list_remove(&strm->StrmListEntry); | |
| } | |
| } | |
| size.u.HighPart = 0; | |
| size.u.LowPart = 0; | |
| hr = IStorage_OpenStream(&parentStorage->IStorage_iface, | |
| entryDataToDelete.name, NULL, STGM_WRITE | STGM_SHARE_EXCLUSIVE, 0, &pis); | |
| if (hr!=S_OK) | |
| { | |
| TRACE("<-- %08x\n", hr); | |
| return(hr); | |
| } | |
| /* | |
| * Zap the stream | |
| */ | |
| hr = IStream_SetSize(pis, size); | |
| if(hr != S_OK) | |
| { | |
| TRACE("<-- %08x\n", hr); | |
| return hr; | |
| } | |
| /* | |
| * Release the stream object. | |
| */ | |
| IStream_Release(pis); | |
| TRACE("<-- %08x\n", hr); | |
| return S_OK; | |
| } | |
| /************************************************************************* | |
| * DestroyElement (IStorage) | |
| * | |
| * Strategy: This implementation is built this way for simplicity not for speed. | |
| * I always delete the topmost element of the enumeration and adjust | |
| * the deleted element pointer all the time. This takes longer to | |
| * do but allows reinvoking DestroyElement whenever we encounter a | |
| * storage object. The optimisation resides in the usage of another | |
| * enumeration strategy that would give all the leaves of a storage | |
| * first. (postfix order) | |
| */ | |
| static HRESULT WINAPI StorageBaseImpl_DestroyElement( | |
| IStorage* iface, | |
| const OLECHAR *pwcsName)/* [string][in] */ | |
| { | |
| StorageBaseImpl *This = impl_from_IStorage(iface); | |
| HRESULT hr = S_OK; | |
| DirEntry entryToDelete; | |
| DirRef entryToDeleteRef; | |
| TRACE("(%p, %s)\n", | |
| iface, debugstr_w(pwcsName)); | |
| if (pwcsName==NULL) | |
| return STG_E_INVALIDPOINTER; | |
| if (This->reverted) | |
| return STG_E_REVERTED; | |
| if ( !(This->openFlags & STGM_TRANSACTED) && | |
| STGM_ACCESS_MODE( This->openFlags ) == STGM_READ ) | |
| return STG_E_ACCESSDENIED; | |
| entryToDeleteRef = findElement( | |
| This, | |
| This->storageDirEntry, | |
| pwcsName, | |
| &entryToDelete); | |
| if ( entryToDeleteRef == DIRENTRY_NULL ) | |
| { | |
| TRACE("<-- STG_E_FILENOTFOUND\n"); | |
| return STG_E_FILENOTFOUND; | |
| } | |
| if ( entryToDelete.stgType == STGTY_STORAGE ) | |
| { | |
| hr = deleteStorageContents( | |
| This, | |
| entryToDeleteRef, | |
| entryToDelete); | |
| } | |
| else if ( entryToDelete.stgType == STGTY_STREAM ) | |
| { | |
| hr = deleteStreamContents( | |
| This, | |
| entryToDeleteRef, | |
| entryToDelete); | |
| } | |
| if (hr!=S_OK) | |
| { | |
| TRACE("<-- %08x\n", hr); | |
| return hr; | |
| } | |
| /* | |
| * Remove the entry from its parent storage | |
| */ | |
| hr = removeFromTree( | |
| This, | |
| This->storageDirEntry, | |
| entryToDeleteRef); | |
| /* | |
| * Invalidate the entry | |
| */ | |
| if (SUCCEEDED(hr)) | |
| StorageBaseImpl_DestroyDirEntry(This, entryToDeleteRef); | |
| if (SUCCEEDED(hr)) | |
| hr = StorageBaseImpl_Flush(This); | |
| TRACE("<-- %08x\n", hr); | |
| return hr; | |
| } | |
| static void StorageBaseImpl_DeleteAll(StorageBaseImpl * stg) | |
| { | |
| struct list *cur, *cur2; | |
| StgStreamImpl *strm=NULL; | |
| StorageInternalImpl *childstg=NULL; | |
| LIST_FOR_EACH_SAFE(cur, cur2, &stg->strmHead) { | |
| strm = LIST_ENTRY(cur,StgStreamImpl,StrmListEntry); | |
| TRACE("Streams invalidated (stg=%p strm=%p next=%p prev=%p)\n", stg,strm,cur->next,cur->prev); | |
| strm->parentStorage = NULL; | |
| list_remove(cur); | |
| } | |
| LIST_FOR_EACH_SAFE(cur, cur2, &stg->storageHead) { | |
| childstg = LIST_ENTRY(cur,StorageInternalImpl,ParentListEntry); | |
| StorageBaseImpl_Invalidate( &childstg->base ); | |
| } | |
| if (stg->transactedChild) | |
| { | |
| StorageBaseImpl_Invalidate(stg->transactedChild); | |
| stg->transactedChild = NULL; | |
| } | |
| } | |
| /****************************************************************************** | |
| * SetElementTimes (IStorage) | |
| */ | |
| static HRESULT WINAPI StorageBaseImpl_SetElementTimes( | |
| IStorage* iface, | |
| const OLECHAR *pwcsName,/* [string][in] */ | |
| const FILETIME *pctime, /* [in] */ | |
| const FILETIME *patime, /* [in] */ | |
| const FILETIME *pmtime) /* [in] */ | |
| { | |
| FIXME("(%s,...), stub!\n",debugstr_w(pwcsName)); | |
| return S_OK; | |
| } | |
| /****************************************************************************** | |
| * SetStateBits (IStorage) | |
| */ | |
| static HRESULT WINAPI StorageBaseImpl_SetStateBits( | |
| IStorage* iface, | |
| DWORD grfStateBits,/* [in] */ | |
| DWORD grfMask) /* [in] */ | |
| { | |
| StorageBaseImpl *This = impl_from_IStorage(iface); | |
| if (This->reverted) | |
| return STG_E_REVERTED; | |
| This->stateBits = (This->stateBits & ~grfMask) | (grfStateBits & grfMask); | |
| return S_OK; | |
| } | |
| /****************************************************************************** | |
| * Internal stream list handlers | |
| */ | |
| void StorageBaseImpl_AddStream(StorageBaseImpl * stg, StgStreamImpl * strm) | |
| { | |
| TRACE("Stream added (stg=%p strm=%p)\n", stg, strm); | |
| list_add_tail(&stg->strmHead,&strm->StrmListEntry); | |
| } | |
| void StorageBaseImpl_RemoveStream(StorageBaseImpl * stg, StgStreamImpl * strm) | |
| { | |
| TRACE("Stream removed (stg=%p strm=%p)\n", stg,strm); | |
| list_remove(&(strm->StrmListEntry)); | |
| } | |
| static HRESULT StorageBaseImpl_CopyStream( | |
| StorageBaseImpl *dst, DirRef dst_entry, | |
| StorageBaseImpl *src, DirRef src_entry) | |
| { | |
| HRESULT hr; | |
| BYTE data[4096]; | |
| DirEntry srcdata; | |
| ULARGE_INTEGER bytes_copied; | |
| ULONG bytestocopy, bytesread, byteswritten; | |
| hr = StorageBaseImpl_ReadDirEntry(src, src_entry, &srcdata); | |
| if (SUCCEEDED(hr)) | |
| { | |
| hr = StorageBaseImpl_StreamSetSize(dst, dst_entry, srcdata.size); | |
| bytes_copied.QuadPart = 0; | |
| while (bytes_copied.QuadPart < srcdata.size.QuadPart && SUCCEEDED(hr)) | |
| { | |
| bytestocopy = min(4096, srcdata.size.QuadPart - bytes_copied.QuadPart); | |
| hr = StorageBaseImpl_StreamReadAt(src, src_entry, bytes_copied, bytestocopy, | |
| data, &bytesread); | |
| if (SUCCEEDED(hr) && bytesread != bytestocopy) hr = STG_E_READFAULT; | |
| if (SUCCEEDED(hr)) | |
| hr = StorageBaseImpl_StreamWriteAt(dst, dst_entry, bytes_copied, bytestocopy, | |
| data, &byteswritten); | |
| if (SUCCEEDED(hr)) | |
| { | |
| if (byteswritten != bytestocopy) hr = STG_E_WRITEFAULT; | |
| bytes_copied.QuadPart += byteswritten; | |
| } | |
| } | |
| } | |
| return hr; | |
| } | |
| static HRESULT StorageBaseImpl_DupStorageTree( | |
| StorageBaseImpl *dst, DirRef *dst_entry, | |
| StorageBaseImpl *src, DirRef src_entry) | |
| { | |
| HRESULT hr; | |
| DirEntry data; | |
| BOOL has_stream=FALSE; | |
| if (src_entry == DIRENTRY_NULL) | |
| { | |
| *dst_entry = DIRENTRY_NULL; | |
| return S_OK; | |
| } | |
| hr = StorageBaseImpl_ReadDirEntry(src, src_entry, &data); | |
| if (SUCCEEDED(hr)) | |
| { | |
| has_stream = (data.stgType == STGTY_STREAM && data.size.QuadPart != 0); | |
| data.startingBlock = BLOCK_END_OF_CHAIN; | |
| data.size.QuadPart = 0; | |
| hr = StorageBaseImpl_DupStorageTree(dst, &data.leftChild, src, data.leftChild); | |
| } | |
| if (SUCCEEDED(hr)) | |
| hr = StorageBaseImpl_DupStorageTree(dst, &data.rightChild, src, data.rightChild); | |
| if (SUCCEEDED(hr)) | |
| hr = StorageBaseImpl_DupStorageTree(dst, &data.dirRootEntry, src, data.dirRootEntry); | |
| if (SUCCEEDED(hr)) | |
| hr = StorageBaseImpl_CreateDirEntry(dst, &data, dst_entry); | |
| if (SUCCEEDED(hr) && has_stream) | |
| hr = StorageBaseImpl_CopyStream(dst, *dst_entry, src, src_entry); | |
| return hr; | |
| } | |
| static HRESULT StorageBaseImpl_CopyStorageTree( | |
| StorageBaseImpl *dst, DirRef dst_entry, | |
| StorageBaseImpl *src, DirRef src_entry) | |
| { | |
| HRESULT hr; | |
| DirEntry src_data, dst_data; | |
| DirRef new_root_entry; | |
| hr = StorageBaseImpl_ReadDirEntry(src, src_entry, &src_data); | |
| if (SUCCEEDED(hr)) | |
| { | |
| hr = StorageBaseImpl_DupStorageTree(dst, &new_root_entry, src, src_data.dirRootEntry); | |
| } | |
| if (SUCCEEDED(hr)) | |
| { | |
| hr = StorageBaseImpl_ReadDirEntry(dst, dst_entry, &dst_data); | |
| dst_data.clsid = src_data.clsid; | |
| dst_data.ctime = src_data.ctime; | |
| dst_data.mtime = src_data.mtime; | |
| dst_data.dirRootEntry = new_root_entry; | |
| } | |
| if (SUCCEEDED(hr)) | |
| hr = StorageBaseImpl_WriteDirEntry(dst, dst_entry, &dst_data); | |
| return hr; | |
| } | |
| static HRESULT StorageBaseImpl_DeleteStorageTree(StorageBaseImpl *This, DirRef entry, BOOL include_siblings) | |
| { | |
| HRESULT hr; | |
| DirEntry data; | |
| ULARGE_INTEGER zero; | |
| if (entry == DIRENTRY_NULL) | |
| return S_OK; | |
| zero.QuadPart = 0; | |
| hr = StorageBaseImpl_ReadDirEntry(This, entry, &data); | |
| if (SUCCEEDED(hr) && include_siblings) | |
| hr = StorageBaseImpl_DeleteStorageTree(This, data.leftChild, TRUE); | |
| if (SUCCEEDED(hr) && include_siblings) | |
| hr = StorageBaseImpl_DeleteStorageTree(This, data.rightChild, TRUE); | |
| if (SUCCEEDED(hr)) | |
| hr = StorageBaseImpl_DeleteStorageTree(This, data.dirRootEntry, TRUE); | |
| if (SUCCEEDED(hr) && data.stgType == STGTY_STREAM) | |
| hr = StorageBaseImpl_StreamSetSize(This, entry, zero); | |
| if (SUCCEEDED(hr)) | |
| hr = StorageBaseImpl_DestroyDirEntry(This, entry); | |
| return hr; | |
| } | |
| /************************************************************************ | |
| * StorageImpl implementation | |
| ***********************************************************************/ | |
| static HRESULT StorageImpl_ReadAt(StorageImpl* This, | |
| ULARGE_INTEGER offset, | |
| void* buffer, | |
| ULONG size, | |
| ULONG* bytesRead) | |
| { | |
| return ILockBytes_ReadAt(This->lockBytes,offset,buffer,size,bytesRead); | |
| } | |
| static HRESULT StorageImpl_WriteAt(StorageImpl* This, | |
| ULARGE_INTEGER offset, | |
| const void* buffer, | |
| const ULONG size, | |
| ULONG* bytesWritten) | |
| { | |
| return ILockBytes_WriteAt(This->lockBytes,offset,buffer,size,bytesWritten); | |
| } | |
| /****************************************************************************** | |
| * StorageImpl_LoadFileHeader | |
| * | |
| * This method will read in the file header | |
| */ | |
| static HRESULT StorageImpl_LoadFileHeader( | |
| StorageImpl* This) | |
| { | |
| HRESULT hr; | |
| BYTE headerBigBlock[HEADER_SIZE]; | |
| int index; | |
| ULARGE_INTEGER offset; | |
| DWORD bytes_read; | |
| TRACE("\n"); | |
| /* | |
| * Get a pointer to the big block of data containing the header. | |
| */ | |
| offset.u.HighPart = 0; | |
| offset.u.LowPart = 0; | |
| hr = StorageImpl_ReadAt(This, offset, headerBigBlock, HEADER_SIZE, &bytes_read); | |
| if (SUCCEEDED(hr) && bytes_read != HEADER_SIZE) | |
| hr = STG_E_FILENOTFOUND; | |
| /* | |
| * Extract the information from the header. | |
| */ | |
| if (SUCCEEDED(hr)) | |
| { | |
| /* | |
| * Check for the "magic number" signature and return an error if it is not | |
| * found. | |
| */ | |
| if (memcmp(headerBigBlock, STORAGE_oldmagic, sizeof(STORAGE_oldmagic))==0) | |
| { | |
| return STG_E_OLDFORMAT; | |
| } | |
| if (memcmp(headerBigBlock, STORAGE_magic, sizeof(STORAGE_magic))!=0) | |
| { | |
| return STG_E_INVALIDHEADER; | |
| } | |
| StorageUtl_ReadWord( | |
| headerBigBlock, | |
| OFFSET_BIGBLOCKSIZEBITS, | |
| &This->bigBlockSizeBits); | |
| StorageUtl_ReadWord( | |
| headerBigBlock, | |
| OFFSET_SMALLBLOCKSIZEBITS, | |
| &This->smallBlockSizeBits); | |
| StorageUtl_ReadDWord( | |
| headerBigBlock, | |
| OFFSET_BBDEPOTCOUNT, | |
| &This->bigBlockDepotCount); | |
| StorageUtl_ReadDWord( | |
| headerBigBlock, | |
| OFFSET_ROOTSTARTBLOCK, | |
| &This->rootStartBlock); | |
| StorageUtl_ReadDWord( | |
| headerBigBlock, | |
| OFFSET_TRANSACTIONSIG, | |
| &This->transactionSig); | |
| StorageUtl_ReadDWord( | |
| headerBigBlock, | |
| OFFSET_SMALLBLOCKLIMIT, | |
| &This->smallBlockLimit); | |
| StorageUtl_ReadDWord( | |
| headerBigBlock, | |
| OFFSET_SBDEPOTSTART, | |
| &This->smallBlockDepotStart); | |
| StorageUtl_ReadDWord( | |
| headerBigBlock, | |
| OFFSET_EXTBBDEPOTSTART, | |
| &This->extBigBlockDepotStart); | |
| StorageUtl_ReadDWord( | |
| headerBigBlock, | |
| OFFSET_EXTBBDEPOTCOUNT, | |
| &This->extBigBlockDepotCount); | |
| for (index = 0; index < COUNT_BBDEPOTINHEADER; index ++) | |
| { | |
| StorageUtl_ReadDWord( | |
| headerBigBlock, | |
| OFFSET_BBDEPOTSTART + (sizeof(ULONG)*index), | |
| &(This->bigBlockDepotStart[index])); | |
| } | |
| /* | |
| * Make the bitwise arithmetic to get the size of the blocks in bytes. | |
| */ | |
| This->bigBlockSize = 0x000000001 << (DWORD)This->bigBlockSizeBits; | |
| This->smallBlockSize = 0x000000001 << (DWORD)This->smallBlockSizeBits; | |
| /* | |
| * Right now, the code is making some assumptions about the size of the | |
| * blocks, just make sure they are what we're expecting. | |
| */ | |
| if ((This->bigBlockSize != MIN_BIG_BLOCK_SIZE && This->bigBlockSize != MAX_BIG_BLOCK_SIZE) || | |
| This->smallBlockSize != DEF_SMALL_BLOCK_SIZE || | |
| This->smallBlockLimit != LIMIT_TO_USE_SMALL_BLOCK) | |
| { | |
| FIXME("Broken OLE storage file? bigblock=0x%x, smallblock=0x%x, sblimit=0x%x\n", | |
| This->bigBlockSize, This->smallBlockSize, This->smallBlockLimit); | |
| hr = STG_E_INVALIDHEADER; | |
| } | |
| else | |
| hr = S_OK; | |
| } | |
| return hr; | |
| } | |
| /****************************************************************************** | |
| * StorageImpl_SaveFileHeader | |
| * | |
| * This method will save to the file the header | |
| */ | |
| static void StorageImpl_SaveFileHeader( | |
| StorageImpl* This) | |
| { | |
| BYTE headerBigBlock[HEADER_SIZE]; | |
| int index; | |
| HRESULT hr; | |
| ULARGE_INTEGER offset; | |
| DWORD bytes_read, bytes_written; | |
| DWORD major_version, dirsectorcount; | |
| /* | |
| * Get a pointer to the big block of data containing the header. | |
| */ | |
| offset.u.HighPart = 0; | |
| offset.u.LowPart = 0; | |
| hr = StorageImpl_ReadAt(This, offset, headerBigBlock, HEADER_SIZE, &bytes_read); | |
| if (SUCCEEDED(hr) && bytes_read != HEADER_SIZE) | |
| hr = STG_E_FILENOTFOUND; | |
| if (This->bigBlockSizeBits == 0x9) | |
| major_version = 3; | |
| else if (This->bigBlockSizeBits == 0xc) | |
| major_version = 4; | |
| else | |
| { | |
| ERR("invalid big block shift 0x%x\n", This->bigBlockSizeBits); | |
| major_version = 4; | |
| } | |
| /* | |
| * If the block read failed, the file is probably new. | |
| */ | |
| if (FAILED(hr)) | |
| { | |
| /* | |
| * Initialize for all unknown fields. | |
| */ | |
| memset(headerBigBlock, 0, HEADER_SIZE); | |
| /* | |
| * Initialize the magic number. | |
| */ | |
| memcpy(headerBigBlock, STORAGE_magic, sizeof(STORAGE_magic)); | |
| } | |
| /* | |
| * Write the information to the header. | |
| */ | |
| StorageUtl_WriteWord( | |
| headerBigBlock, | |
| OFFSET_MINORVERSION, | |
| 0x3e); | |
| StorageUtl_WriteWord( | |
| headerBigBlock, | |
| OFFSET_MAJORVERSION, | |
| major_version); | |
| StorageUtl_WriteWord( | |
| headerBigBlock, | |
| OFFSET_BYTEORDERMARKER, | |
| (WORD)-2); | |
| StorageUtl_WriteWord( | |
| headerBigBlock, | |
| OFFSET_BIGBLOCKSIZEBITS, | |
| This->bigBlockSizeBits); | |
| StorageUtl_WriteWord( | |
| headerBigBlock, | |
| OFFSET_SMALLBLOCKSIZEBITS, | |
| This->smallBlockSizeBits); | |
| if (major_version >= 4) | |
| { | |
| if (This->rootBlockChain) | |
| dirsectorcount = BlockChainStream_GetCount(This->rootBlockChain); | |
| else | |
| /* This file is being created, and it will start out with one block. */ | |
| dirsectorcount = 1; | |
| } | |
| else | |
| /* This field must be 0 in versions older than 4 */ | |
| dirsectorcount = 0; | |
| StorageUtl_WriteDWord( | |
| headerBigBlock, | |
| OFFSET_DIRSECTORCOUNT, | |
| dirsectorcount); | |
| StorageUtl_WriteDWord( | |
| headerBigBlock, | |
| OFFSET_BBDEPOTCOUNT, | |
| This->bigBlockDepotCount); | |
| StorageUtl_WriteDWord( | |
| headerBigBlock, | |
| OFFSET_ROOTSTARTBLOCK, | |
| This->rootStartBlock); | |
| StorageUtl_WriteDWord( | |
| headerBigBlock, | |
| OFFSET_TRANSACTIONSIG, | |
| This->transactionSig); | |
| StorageUtl_WriteDWord( | |
| headerBigBlock, | |
| OFFSET_SMALLBLOCKLIMIT, | |
| This->smallBlockLimit); | |
| StorageUtl_WriteDWord( | |
| headerBigBlock, | |
| OFFSET_SBDEPOTSTART, | |
| This->smallBlockDepotStart); | |
| StorageUtl_WriteDWord( | |
| headerBigBlock, | |
| OFFSET_SBDEPOTCOUNT, | |
| This->smallBlockDepotChain ? | |
| BlockChainStream_GetCount(This->smallBlockDepotChain) : 0); | |
| StorageUtl_WriteDWord( | |
| headerBigBlock, | |
| OFFSET_EXTBBDEPOTSTART, | |
| This->extBigBlockDepotStart); | |
| StorageUtl_WriteDWord( | |
| headerBigBlock, | |
| OFFSET_EXTBBDEPOTCOUNT, | |
| This->extBigBlockDepotCount); | |
| for (index = 0; index < COUNT_BBDEPOTINHEADER; index ++) | |
| { | |
| StorageUtl_WriteDWord( | |
| headerBigBlock, | |
| OFFSET_BBDEPOTSTART + (sizeof(ULONG)*index), | |
| (This->bigBlockDepotStart[index])); | |
| } | |
| /* | |
| * Write the big block back to the file. | |
| */ | |
| StorageImpl_WriteAt(This, offset, headerBigBlock, HEADER_SIZE, &bytes_written); | |
| } | |
| /************************************************************************ | |
| * StorageImpl implementation : DirEntry methods | |
| ***********************************************************************/ | |
| /****************************************************************************** | |
| * StorageImpl_ReadRawDirEntry | |
| * | |
| * This method will read the raw data from a directory entry in the file. | |
| * | |
| * buffer must be RAW_DIRENTRY_SIZE bytes long. | |
| */ | |
| static HRESULT StorageImpl_ReadRawDirEntry(StorageImpl *This, ULONG index, BYTE *buffer) | |
| { | |
| ULARGE_INTEGER offset; | |
| HRESULT hr; | |
| ULONG bytesRead; | |
| offset.QuadPart = (ULONGLONG)index * RAW_DIRENTRY_SIZE; | |
| hr = BlockChainStream_ReadAt( | |
| This->rootBlockChain, | |
| offset, | |
| RAW_DIRENTRY_SIZE, | |
| buffer, | |
| &bytesRead); | |
| if (bytesRead != RAW_DIRENTRY_SIZE) | |
| return STG_E_READFAULT; | |
| return hr; | |
| } | |
| /****************************************************************************** | |
| * StorageImpl_WriteRawDirEntry | |
| * | |
| * This method will write the raw data from a directory entry in the file. | |
| * | |
| * buffer must be RAW_DIRENTRY_SIZE bytes long. | |
| */ | |
| static HRESULT StorageImpl_WriteRawDirEntry(StorageImpl *This, ULONG index, const BYTE *buffer) | |
| { | |
| ULARGE_INTEGER offset; | |
| ULONG bytesRead; | |
| offset.QuadPart = (ULONGLONG)index * RAW_DIRENTRY_SIZE; | |
| return BlockChainStream_WriteAt( | |
| This->rootBlockChain, | |
| offset, | |
| RAW_DIRENTRY_SIZE, | |
| buffer, | |
| &bytesRead); | |
| } | |
| /*************************************************************************** | |
| * | |
| * Internal Method | |
| * | |
| * Mark a directory entry in the file as free. | |
| */ | |
| static HRESULT StorageImpl_DestroyDirEntry( | |
| StorageBaseImpl *base, | |
| DirRef index) | |
| { | |
| BYTE emptyData[RAW_DIRENTRY_SIZE]; | |
| StorageImpl *storage = (StorageImpl*)base; | |
| memset(emptyData, 0, RAW_DIRENTRY_SIZE); | |
| return StorageImpl_WriteRawDirEntry(storage, index, emptyData); | |
| } | |
| /****************************************************************************** | |
| * UpdateRawDirEntry | |
| * | |
| * Update raw directory entry data from the fields in newData. | |
| * | |
| * buffer must be RAW_DIRENTRY_SIZE bytes long. | |
| */ | |
| static void UpdateRawDirEntry(BYTE *buffer, const DirEntry *newData) | |
| { | |
| memset(buffer, 0, RAW_DIRENTRY_SIZE); | |
| memcpy( | |
| buffer + OFFSET_PS_NAME, | |
| newData->name, | |
| DIRENTRY_NAME_BUFFER_LEN ); | |
| memcpy(buffer + OFFSET_PS_STGTYPE, &newData->stgType, 1); | |
| StorageUtl_WriteWord( | |
| buffer, | |
| OFFSET_PS_NAMELENGTH, | |
| newData->sizeOfNameString); | |
| StorageUtl_WriteDWord( | |
| buffer, | |
| OFFSET_PS_LEFTCHILD, | |
| newData->leftChild); | |
| StorageUtl_WriteDWord( | |
| buffer, | |
| OFFSET_PS_RIGHTCHILD, | |
| newData->rightChild); | |
| StorageUtl_WriteDWord( | |
| buffer, | |
| OFFSET_PS_DIRROOT, | |
| newData->dirRootEntry); | |
| StorageUtl_WriteGUID( | |
| buffer, | |
| OFFSET_PS_GUID, | |
| &newData->clsid); | |
| StorageUtl_WriteDWord( | |
| buffer, | |
| OFFSET_PS_CTIMELOW, | |
| newData->ctime.dwLowDateTime); | |
| StorageUtl_WriteDWord( | |
| buffer, | |
| OFFSET_PS_CTIMEHIGH, | |
| newData->ctime.dwHighDateTime); | |
| StorageUtl_WriteDWord( | |
| buffer, | |
| OFFSET_PS_MTIMELOW, | |
| newData->mtime.dwLowDateTime); | |
| StorageUtl_WriteDWord( | |
| buffer, | |
| OFFSET_PS_MTIMEHIGH, | |
| newData->ctime.dwHighDateTime); | |
| StorageUtl_WriteDWord( | |
| buffer, | |
| OFFSET_PS_STARTBLOCK, | |
| newData->startingBlock); | |
| StorageUtl_WriteDWord( | |
| buffer, | |
| OFFSET_PS_SIZE, | |
| newData->size.u.LowPart); | |
| StorageUtl_WriteDWord( | |
| buffer, | |
| OFFSET_PS_SIZE_HIGH, | |
| newData->size.u.HighPart); | |
| } | |
| /*************************************************************************** | |
| * | |
| * Internal Method | |
| * | |
| * Reserve a directory entry in the file and initialize it. | |
| */ | |
| static HRESULT StorageImpl_CreateDirEntry( | |
| StorageBaseImpl *base, | |
| const DirEntry *newData, | |
| DirRef *index) | |
| { | |
| StorageImpl *storage = (StorageImpl*)base; | |
| ULONG currentEntryIndex = 0; | |
| ULONG newEntryIndex = DIRENTRY_NULL; | |
| HRESULT hr = S_OK; | |
| BYTE currentData[RAW_DIRENTRY_SIZE]; | |
| WORD sizeOfNameString; | |
| do | |
| { | |
| hr = StorageImpl_ReadRawDirEntry(storage, | |
| currentEntryIndex, | |
| currentData); | |
| if (SUCCEEDED(hr)) | |
| { | |
| StorageUtl_ReadWord( | |
| currentData, | |
| OFFSET_PS_NAMELENGTH, | |
| &sizeOfNameString); | |
| if (sizeOfNameString == 0) | |
| { | |
| /* | |
| * The entry exists and is available, we found it. | |
| */ | |
| newEntryIndex = currentEntryIndex; | |
| } | |
| } | |
| else | |
| { | |
| /* | |
| * We exhausted the directory entries, we will create more space below | |
| */ | |
| newEntryIndex = currentEntryIndex; | |
| } | |
| currentEntryIndex++; | |
| } while (newEntryIndex == DIRENTRY_NULL); | |
| /* | |
| * grow the directory stream | |
| */ | |
| if (FAILED(hr)) | |
| { | |
| BYTE emptyData[RAW_DIRENTRY_SIZE]; | |
| ULARGE_INTEGER newSize; | |
| ULONG entryIndex; | |
| ULONG lastEntry = 0; | |
| ULONG blockCount = 0; | |
| /* | |
| * obtain the new count of blocks in the directory stream | |
| */ | |
| blockCount = BlockChainStream_GetCount( | |
| storage->rootBlockChain)+1; | |
| /* | |
| * initialize the size used by the directory stream | |
| */ | |
| newSize.QuadPart = (ULONGLONG)storage->bigBlockSize * blockCount; | |
| /* | |
| * add a block to the directory stream | |
| */ | |
| BlockChainStream_SetSize(storage->rootBlockChain, newSize); | |
| /* | |
| * memset the empty entry in order to initialize the unused newly | |
| * created entries | |
| */ | |
| memset(emptyData, 0, RAW_DIRENTRY_SIZE); | |
| /* | |
| * initialize them | |
| */ | |
| lastEntry = storage->bigBlockSize / RAW_DIRENTRY_SIZE * blockCount; | |
| for( | |
| entryIndex = newEntryIndex + 1; | |
| entryIndex < lastEntry; | |
| entryIndex++) | |
| { | |
| StorageImpl_WriteRawDirEntry( | |
| storage, | |
| entryIndex, | |
| emptyData); | |
| } | |
| StorageImpl_SaveFileHeader(storage); | |
| } | |
| UpdateRawDirEntry(currentData, newData); | |
| hr = StorageImpl_WriteRawDirEntry(storage, newEntryIndex, currentData); | |
| if (SUCCEEDED(hr)) | |
| *index = newEntryIndex; | |
| return hr; | |
| } | |
| /****************************************************************************** | |
| * StorageImpl_ReadDirEntry | |
| * | |
| * This method will read the specified directory entry. | |
| */ | |
| static HRESULT StorageImpl_ReadDirEntry( | |
| StorageImpl* This, | |
| DirRef index, | |
| DirEntry* buffer) | |
| { | |
| BYTE currentEntry[RAW_DIRENTRY_SIZE]; | |
| HRESULT readRes; | |
| readRes = StorageImpl_ReadRawDirEntry(This, index, currentEntry); | |
| if (SUCCEEDED(readRes)) | |
| { | |
| memset(buffer->name, 0, sizeof(buffer->name)); | |
| memcpy( | |
| buffer->name, | |
| (WCHAR *)currentEntry+OFFSET_PS_NAME, | |
| DIRENTRY_NAME_BUFFER_LEN ); | |
| TRACE("storage name: %s\n", debugstr_w(buffer->name)); | |
| memcpy(&buffer->stgType, currentEntry + OFFSET_PS_STGTYPE, 1); | |
| StorageUtl_ReadWord( | |
| currentEntry, | |
| OFFSET_PS_NAMELENGTH, | |
| &buffer->sizeOfNameString); | |
| StorageUtl_ReadDWord( | |
| currentEntry, | |
| OFFSET_PS_LEFTCHILD, | |
| &buffer->leftChild); | |
| StorageUtl_ReadDWord( | |
| currentEntry, | |
| OFFSET_PS_RIGHTCHILD, | |
| &buffer->rightChild); | |
| StorageUtl_ReadDWord( | |
| currentEntry, | |
| OFFSET_PS_DIRROOT, | |
| &buffer->dirRootEntry); | |
| StorageUtl_ReadGUID( | |
| currentEntry, | |
| OFFSET_PS_GUID, | |
| &buffer->clsid); | |
| StorageUtl_ReadDWord( | |
| currentEntry, | |
| OFFSET_PS_CTIMELOW, | |
| &buffer->ctime.dwLowDateTime); | |
| StorageUtl_ReadDWord( | |
| currentEntry, | |
| OFFSET_PS_CTIMEHIGH, | |
| &buffer->ctime.dwHighDateTime); | |
| StorageUtl_ReadDWord( | |
| currentEntry, | |
| OFFSET_PS_MTIMELOW, | |
| &buffer->mtime.dwLowDateTime); | |
| StorageUtl_ReadDWord( | |
| currentEntry, | |
| OFFSET_PS_MTIMEHIGH, | |
| &buffer->mtime.dwHighDateTime); | |
| StorageUtl_ReadDWord( | |
| currentEntry, | |
| OFFSET_PS_STARTBLOCK, | |
| &buffer->startingBlock); | |
| StorageUtl_ReadDWord( | |
| currentEntry, | |
| OFFSET_PS_SIZE, | |
| &buffer->size.u.LowPart); | |
| if (This->bigBlockSize < 4096) | |
| { | |
| /* Version 3 files may have junk in the high part of size. */ | |
| buffer->size.u.HighPart = 0; | |
| } | |
| else | |
| { | |
| StorageUtl_ReadDWord( | |
| currentEntry, | |
| OFFSET_PS_SIZE_HIGH, | |
| &buffer->size.u.HighPart); | |
| } | |
| } | |
| return readRes; | |
| } | |
| /********************************************************************* | |
| * Write the specified directory entry to the file | |
| */ | |
| static HRESULT StorageImpl_WriteDirEntry( | |
| StorageImpl* This, | |
| DirRef index, | |
| const DirEntry* buffer) | |
| { | |
| BYTE currentEntry[RAW_DIRENTRY_SIZE]; | |
| UpdateRawDirEntry(currentEntry, buffer); | |
| return StorageImpl_WriteRawDirEntry(This, index, currentEntry); | |
| } | |
| /************************************************************************ | |
| * StorageImpl implementation : Block methods | |
| ***********************************************************************/ | |
| static ULONGLONG StorageImpl_GetBigBlockOffset(StorageImpl* This, ULONG index) | |
| { | |
| return (ULONGLONG)(index+1) * This->bigBlockSize; | |
| } | |
| static HRESULT StorageImpl_ReadBigBlock( | |
| StorageImpl* This, | |
| ULONG blockIndex, | |
| void* buffer, | |
| ULONG* out_read) | |
| { | |
| ULARGE_INTEGER ulOffset; | |
| DWORD read=0; | |
| HRESULT hr; | |
| ulOffset.QuadPart = StorageImpl_GetBigBlockOffset(This, blockIndex); | |
| hr = StorageImpl_ReadAt(This, ulOffset, buffer, This->bigBlockSize, &read); | |
| if (SUCCEEDED(hr) && read < This->bigBlockSize) | |
| { | |
| /* File ends during this block; fill the rest with 0's. */ | |
| memset((LPBYTE)buffer+read, 0, This->bigBlockSize-read); | |
| } | |
| if (out_read) *out_read = read; | |
| return hr; | |
| } | |
| static BOOL StorageImpl_ReadDWordFromBigBlock( | |
| StorageImpl* This, | |
| ULONG blockIndex, | |
| ULONG offset, | |
| DWORD* value) | |
| { | |
| ULARGE_INTEGER ulOffset; | |
| DWORD read; | |
| DWORD tmp; | |
| ulOffset.QuadPart = StorageImpl_GetBigBlockOffset(This, blockIndex); | |
| ulOffset.QuadPart += offset; | |
| StorageImpl_ReadAt(This, ulOffset, &tmp, sizeof(DWORD), &read); | |
| *value = lendian32toh(tmp); | |
| return (read == sizeof(DWORD)); | |
| } | |
| static BOOL StorageImpl_WriteBigBlock( | |
| StorageImpl* This, | |
| ULONG blockIndex, | |
| const void* buffer) | |
| { | |
| ULARGE_INTEGER ulOffset; | |
| DWORD wrote; | |
| ulOffset.QuadPart = StorageImpl_GetBigBlockOffset(This, blockIndex); | |
| StorageImpl_WriteAt(This, ulOffset, buffer, This->bigBlockSize, &wrote); | |
| return (wrote == This->bigBlockSize); | |
| } | |
| static BOOL StorageImpl_WriteDWordToBigBlock( | |
| StorageImpl* This, | |
| ULONG blockIndex, | |
| ULONG offset, | |
| DWORD value) | |
| { | |
| ULARGE_INTEGER ulOffset; | |
| DWORD wrote; | |
| ulOffset.QuadPart = StorageImpl_GetBigBlockOffset(This, blockIndex); | |
| ulOffset.QuadPart += offset; | |
| value = htole32(value); | |
| StorageImpl_WriteAt(This, ulOffset, &value, sizeof(DWORD), &wrote); | |
| return (wrote == sizeof(DWORD)); | |
| } | |
| /****************************************************************************** | |
| * Storage32Impl_SmallBlocksToBigBlocks | |
| * | |
| * This method will convert a small block chain to a big block chain. | |
| * The small block chain will be destroyed. | |
| */ | |
| static BlockChainStream* Storage32Impl_SmallBlocksToBigBlocks( | |
| StorageImpl* This, | |
| SmallBlockChainStream** ppsbChain) | |
| { | |
| ULONG bbHeadOfChain = BLOCK_END_OF_CHAIN; | |
| ULARGE_INTEGER size, offset; | |
| ULONG cbRead, cbWritten; | |
| ULARGE_INTEGER cbTotalRead; | |
| DirRef streamEntryRef; | |
| HRESULT resWrite = S_OK; | |
| HRESULT resRead; | |
| DirEntry streamEntry; | |
| BYTE *buffer; | |
| BlockChainStream *bbTempChain = NULL; | |
| BlockChainStream *bigBlockChain = NULL; | |
| /* | |
| * Create a temporary big block chain that doesn't have | |
| * an associated directory entry. This temporary chain will be | |
| * used to copy data from small blocks to big blocks. | |
| */ | |
| bbTempChain = BlockChainStream_Construct(This, | |
| &bbHeadOfChain, | |
| DIRENTRY_NULL); | |
| if(!bbTempChain) return NULL; | |
| /* | |
| * Grow the big block chain. | |
| */ | |
| size = SmallBlockChainStream_GetSize(*ppsbChain); | |
| BlockChainStream_SetSize(bbTempChain, size); | |
| /* | |
| * Copy the contents of the small block chain to the big block chain | |
| * by small block size increments. | |
| */ | |
| offset.u.LowPart = 0; | |
| offset.u.HighPart = 0; | |
| cbTotalRead.QuadPart = 0; | |
| buffer = HeapAlloc(GetProcessHeap(),0,DEF_SMALL_BLOCK_SIZE); | |
| do | |
| { | |
| resRead = SmallBlockChainStream_ReadAt(*ppsbChain, | |
| offset, | |
| min(This->smallBlockSize, size.u.LowPart - offset.u.LowPart), | |
| buffer, | |
| &cbRead); | |
| if (FAILED(resRead)) | |
| break; | |
| if (cbRead > 0) | |
| { | |
| cbTotalRead.QuadPart += cbRead; | |
| resWrite = BlockChainStream_WriteAt(bbTempChain, | |
| offset, | |
| cbRead, | |
| buffer, | |
| &cbWritten); | |
| if (FAILED(resWrite)) | |
| break; | |
| offset.u.LowPart += cbRead; | |
| } | |
| else | |
| { | |
| resRead = STG_E_READFAULT; | |
| break; | |
| } | |
| } while (cbTotalRead.QuadPart < size.QuadPart); | |
| HeapFree(GetProcessHeap(),0,buffer); | |
| size.u.HighPart = 0; | |
| size.u.LowPart = 0; | |
| if (FAILED(resRead) || FAILED(resWrite)) | |
| { | |
| ERR("conversion failed: resRead = 0x%08x, resWrite = 0x%08x\n", resRead, resWrite); | |
| BlockChainStream_SetSize(bbTempChain, size); | |
| BlockChainStream_Destroy(bbTempChain); | |
| return NULL; | |
| } | |
| /* | |
| * Destroy the small block chain. | |
| */ | |
| streamEntryRef = (*ppsbChain)->ownerDirEntry; | |
| SmallBlockChainStream_SetSize(*ppsbChain, size); | |
| SmallBlockChainStream_Destroy(*ppsbChain); | |
| *ppsbChain = 0; | |
| /* | |
| * Change the directory entry. This chain is now a big block chain | |
| * and it doesn't reside in the small blocks chain anymore. | |
| */ | |
| StorageImpl_ReadDirEntry(This, streamEntryRef, &streamEntry); | |
| streamEntry.startingBlock = bbHeadOfChain; | |
| StorageImpl_WriteDirEntry(This, streamEntryRef, &streamEntry); | |
| /* | |
| * Destroy the temporary entryless big block chain. | |
| * Create a new big block chain associated with this entry. | |
| */ | |
| BlockChainStream_Destroy(bbTempChain); | |
| bigBlockChain = BlockChainStream_Construct(This, | |
| NULL, | |
| streamEntryRef); | |
| return bigBlockChain; | |
| } | |
| /****************************************************************************** | |
| * Storage32Impl_BigBlocksToSmallBlocks | |
| * | |
| * This method will convert a big block chain to a small block chain. | |
| * The big block chain will be destroyed on success. | |
| */ | |
| static SmallBlockChainStream* Storage32Impl_BigBlocksToSmallBlocks( | |
| StorageImpl* This, | |
| BlockChainStream** ppbbChain, | |
| ULARGE_INTEGER newSize) | |
| { | |
| ULARGE_INTEGER size, offset, cbTotalRead; | |
| ULONG cbRead, cbWritten, sbHeadOfChain = BLOCK_END_OF_CHAIN; | |
| DirRef streamEntryRef; | |
| HRESULT resWrite = S_OK, resRead = S_OK; | |
| DirEntry streamEntry; | |
| BYTE* buffer; | |
| SmallBlockChainStream* sbTempChain; | |
| TRACE("%p %p\n", This, ppbbChain); | |
| sbTempChain = SmallBlockChainStream_Construct(This, &sbHeadOfChain, | |
| DIRENTRY_NULL); | |
| if(!sbTempChain) | |
| return NULL; | |
| SmallBlockChainStream_SetSize(sbTempChain, newSize); | |
| size = BlockChainStream_GetSize(*ppbbChain); | |
| size.QuadPart = min(size.QuadPart, newSize.QuadPart); | |
| offset.u.HighPart = 0; | |
| offset.u.LowPart = 0; | |
| cbTotalRead.QuadPart = 0; | |
| buffer = HeapAlloc(GetProcessHeap(), 0, This->bigBlockSize); | |
| while(cbTotalRead.QuadPart < size.QuadPart) | |
| { | |
| resRead = BlockChainStream_ReadAt(*ppbbChain, offset, | |
| min(This->bigBlockSize, size.u.LowPart - offset.u.LowPart), | |
| buffer, &cbRead); | |
| if(FAILED(resRead)) | |
| break; | |
| if(cbRead > 0) | |
| { | |
| cbTotalRead.QuadPart += cbRead; | |
| resWrite = SmallBlockChainStream_WriteAt(sbTempChain, offset, | |
| cbRead, buffer, &cbWritten); | |
| if(FAILED(resWrite)) | |
| break; | |
| offset.u.LowPart += cbRead; | |
| } | |
| else | |
| { | |
| resRead = STG_E_READFAULT; | |
| break; | |
| } | |
| } | |
| HeapFree(GetProcessHeap(), 0, buffer); | |
| size.u.HighPart = 0; | |
| size.u.LowPart = 0; | |
| if(FAILED(resRead) || FAILED(resWrite)) | |
| { | |
| ERR("conversion failed: resRead = 0x%08x, resWrite = 0x%08x\n", resRead, resWrite); | |
| SmallBlockChainStream_SetSize(sbTempChain, size); | |
| SmallBlockChainStream_Destroy(sbTempChain); | |
| return NULL; | |
| } | |
| /* destroy the original big block chain */ | |
| streamEntryRef = (*ppbbChain)->ownerDirEntry; | |
| BlockChainStream_SetSize(*ppbbChain, size); | |
| BlockChainStream_Destroy(*ppbbChain); | |
| *ppbbChain = NULL; | |
| StorageImpl_ReadDirEntry(This, streamEntryRef, &streamEntry); | |
| streamEntry.startingBlock = sbHeadOfChain; | |
| StorageImpl_WriteDirEntry(This, streamEntryRef, &streamEntry); | |
| SmallBlockChainStream_Destroy(sbTempChain); | |
| return SmallBlockChainStream_Construct(This, NULL, streamEntryRef); | |
| } | |
| /****************************************************************************** | |
| * Storage32Impl_AddBlockDepot | |
| * | |
| * This will create a depot block, essentially it is a block initialized | |
| * to BLOCK_UNUSEDs. | |
| */ | |
| static void Storage32Impl_AddBlockDepot(StorageImpl* This, ULONG blockIndex, ULONG depotIndex) | |
| { | |
| BYTE blockBuffer[MAX_BIG_BLOCK_SIZE]; | |
| ULONG rangeLockIndex = RANGELOCK_FIRST / This->bigBlockSize - 1; | |
| ULONG blocksPerDepot = This->bigBlockSize / sizeof(ULONG); | |
| ULONG rangeLockDepot = rangeLockIndex / blocksPerDepot; | |
| /* | |
| * Initialize blocks as free | |
| */ | |
| memset(blockBuffer, BLOCK_UNUSED, This->bigBlockSize); | |
| /* Reserve the range lock sector */ | |
| if (depotIndex == rangeLockDepot) | |
| { | |
| ((ULONG*)blockBuffer)[rangeLockIndex % blocksPerDepot] = BLOCK_END_OF_CHAIN; | |
| } | |
| StorageImpl_WriteBigBlock(This, blockIndex, blockBuffer); | |
| } | |
| /****************************************************************************** | |
| * Storage32Impl_GetExtDepotBlock | |
| * | |
| * Returns the index of the block that corresponds to the specified depot | |
| * index. This method is only for depot indexes equal or greater than | |
| * COUNT_BBDEPOTINHEADER. | |
| */ | |
| static ULONG Storage32Impl_GetExtDepotBlock(StorageImpl* This, ULONG depotIndex) | |
| { | |
| ULONG depotBlocksPerExtBlock = (This->bigBlockSize / sizeof(ULONG)) - 1; | |
| ULONG numExtBlocks = depotIndex - COUNT_BBDEPOTINHEADER; | |
| ULONG extBlockCount = numExtBlocks / depotBlocksPerExtBlock; | |
| ULONG extBlockOffset = numExtBlocks % depotBlocksPerExtBlock; | |
| ULONG blockIndex = BLOCK_UNUSED; | |
| ULONG extBlockIndex; | |
| BYTE depotBuffer[MAX_BIG_BLOCK_SIZE]; | |
| int index, num_blocks; | |
| assert(depotIndex >= COUNT_BBDEPOTINHEADER); | |
| if (extBlockCount >= This->extBigBlockDepotCount) | |
| return BLOCK_UNUSED; | |
| if (This->indexExtBlockDepotCached != extBlockCount) | |
| { | |
| extBlockIndex = This->extBigBlockDepotLocations[extBlockCount]; | |
| StorageImpl_ReadBigBlock(This, extBlockIndex, depotBuffer, NULL); | |
| num_blocks = This->bigBlockSize / 4; | |
| for (index = 0; index < num_blocks; index++) | |
| { | |
| StorageUtl_ReadDWord(depotBuffer, index*sizeof(ULONG), &blockIndex); | |
| This->extBlockDepotCached[index] = blockIndex; | |
| } | |
| This->indexExtBlockDepotCached = extBlockCount; | |
| } | |
| blockIndex = This->extBlockDepotCached[extBlockOffset]; | |
| return blockIndex; | |
| } | |
| /****************************************************************************** | |
| * Storage32Impl_SetExtDepotBlock | |
| * | |
| * Associates the specified block index to the specified depot index. | |
| * This method is only for depot indexes equal or greater than | |
| * COUNT_BBDEPOTINHEADER. | |
| */ | |
| static void Storage32Impl_SetExtDepotBlock(StorageImpl* This, ULONG depotIndex, ULONG blockIndex) | |
| { | |
| ULONG depotBlocksPerExtBlock = (This->bigBlockSize / sizeof(ULONG)) - 1; | |
| ULONG numExtBlocks = depotIndex - COUNT_BBDEPOTINHEADER; | |
| ULONG extBlockCount = numExtBlocks / depotBlocksPerExtBlock; | |
| ULONG extBlockOffset = numExtBlocks % depotBlocksPerExtBlock; | |
| ULONG extBlockIndex; | |
| assert(depotIndex >= COUNT_BBDEPOTINHEADER); | |
| assert(extBlockCount < This->extBigBlockDepotCount); | |
| extBlockIndex = This->extBigBlockDepotLocations[extBlockCount]; | |
| if (extBlockIndex != BLOCK_UNUSED) | |
| { | |
| StorageImpl_WriteDWordToBigBlock(This, extBlockIndex, | |
| extBlockOffset * sizeof(ULONG), | |
| blockIndex); | |
| } | |
| if (This->indexExtBlockDepotCached == extBlockCount) | |
| { | |
| This->extBlockDepotCached[extBlockOffset] = blockIndex; | |
| } | |
| } | |
| /****************************************************************************** | |
| * Storage32Impl_AddExtBlockDepot | |
| * | |
| * Creates an extended depot block. | |
| */ | |
| static ULONG Storage32Impl_AddExtBlockDepot(StorageImpl* This) | |
| { | |
| ULONG numExtBlocks = This->extBigBlockDepotCount; | |
| ULONG nextExtBlock = This->extBigBlockDepotStart; | |
| BYTE depotBuffer[MAX_BIG_BLOCK_SIZE]; | |
| ULONG index = BLOCK_UNUSED; | |
| ULONG nextBlockOffset = This->bigBlockSize - sizeof(ULONG); | |
| ULONG blocksPerDepotBlock = This->bigBlockSize / sizeof(ULONG); | |
| ULONG depotBlocksPerExtBlock = blocksPerDepotBlock - 1; | |
| index = (COUNT_BBDEPOTINHEADER + (numExtBlocks * depotBlocksPerExtBlock)) * | |
| blocksPerDepotBlock; | |
| if ((numExtBlocks == 0) && (nextExtBlock == BLOCK_END_OF_CHAIN)) | |
| { | |
| /* | |
| * The first extended block. | |
| */ | |
| This->extBigBlockDepotStart = index; | |
| } | |
| else | |
| { | |
| /* | |
| * Find the last existing extended block. | |
| */ | |
| nextExtBlock = This->extBigBlockDepotLocations[This->extBigBlockDepotCount-1]; | |
| /* | |
| * Add the new extended block to the chain. | |
| */ | |
| StorageImpl_WriteDWordToBigBlock(This, nextExtBlock, nextBlockOffset, | |
| index); | |
| } | |
| /* | |
| * Initialize this block. | |
| */ | |
| memset(depotBuffer, BLOCK_UNUSED, This->bigBlockSize); | |
| StorageImpl_WriteBigBlock(This, index, depotBuffer); | |
| /* Add the block to our cache. */ | |
| if (This->extBigBlockDepotLocationsSize == numExtBlocks) | |
| { | |
| ULONG new_cache_size = (This->extBigBlockDepotLocationsSize+1)*2; | |
| ULONG *new_cache = HeapAlloc(GetProcessHeap(), 0, sizeof(ULONG) * new_cache_size); | |
| memcpy(new_cache, This->extBigBlockDepotLocations, sizeof(ULONG) * This->extBigBlockDepotLocationsSize); | |
| HeapFree(GetProcessHeap(), 0, This->extBigBlockDepotLocations); | |
| This->extBigBlockDepotLocations = new_cache; | |
| This->extBigBlockDepotLocationsSize = new_cache_size; | |
| } | |
| This->extBigBlockDepotLocations[numExtBlocks] = index; | |
| return index; | |
| } | |
| /************************************************************************ | |
| * StorageImpl_GetNextBlockInChain | |
| * | |
| * This method will retrieve the block index of the next big block in | |
| * in the chain. | |
| * | |
| * Params: This - Pointer to the Storage object. | |
| * blockIndex - Index of the block to retrieve the chain | |
| * for. | |
| * nextBlockIndex - receives the return value. | |
| * | |
| * Returns: This method returns the index of the next block in the chain. | |
| * It will return the constants: | |
| * BLOCK_SPECIAL - If the block given was not part of a | |
| * chain. | |
| * BLOCK_END_OF_CHAIN - If the block given was the last in | |
| * a chain. | |
| * BLOCK_UNUSED - If the block given was not past of a chain | |
| * and is available. | |
| * BLOCK_EXTBBDEPOT - This block is part of the extended | |
| * big block depot. | |
| * | |
| * See Windows documentation for more details on IStorage methods. | |
| */ | |
| static HRESULT StorageImpl_GetNextBlockInChain( | |
| StorageImpl* This, | |
| ULONG blockIndex, | |
| ULONG* nextBlockIndex) | |
| { | |
| ULONG offsetInDepot = blockIndex * sizeof (ULONG); | |
| ULONG depotBlockCount = offsetInDepot / This->bigBlockSize; | |
| ULONG depotBlockOffset = offsetInDepot % This->bigBlockSize; | |
| BYTE depotBuffer[MAX_BIG_BLOCK_SIZE]; | |
| ULONG read; | |
| ULONG depotBlockIndexPos; | |
| int index, num_blocks; | |
| *nextBlockIndex = BLOCK_SPECIAL; | |
| if(depotBlockCount >= This->bigBlockDepotCount) | |
| { | |
| WARN("depotBlockCount %d, bigBlockDepotCount %d\n", depotBlockCount, | |
| This->bigBlockDepotCount); | |
| return STG_E_READFAULT; | |
| } | |
| /* | |
| * Cache the currently accessed depot block. | |
| */ | |
| if (depotBlockCount != This->indexBlockDepotCached) | |
| { | |
| This->indexBlockDepotCached = depotBlockCount; | |
| if (depotBlockCount < COUNT_BBDEPOTINHEADER) | |
| { | |
| depotBlockIndexPos = This->bigBlockDepotStart[depotBlockCount]; | |
| } | |
| else | |
| { | |
| /* | |
| * We have to look in the extended depot. | |
| */ | |
| depotBlockIndexPos = Storage32Impl_GetExtDepotBlock(This, depotBlockCount); | |
| } | |
| StorageImpl_ReadBigBlock(This, depotBlockIndexPos, depotBuffer, &read); | |
| if (!read) | |
| return STG_E_READFAULT; | |
| num_blocks = This->bigBlockSize / 4; | |
| for (index = 0; index < num_blocks; index++) | |
| { | |
| StorageUtl_ReadDWord(depotBuffer, index*sizeof(ULONG), nextBlockIndex); | |
| This->blockDepotCached[index] = *nextBlockIndex; | |
| } | |
| } | |
| *nextBlockIndex = This->blockDepotCached[depotBlockOffset/sizeof(ULONG)]; | |
| return S_OK; | |
| } | |
| /****************************************************************************** | |
| * Storage32Impl_GetNextExtendedBlock | |
| * | |
| * Given an extended block this method will return the next extended block. | |
| * | |
| * NOTES: | |
| * The last ULONG of an extended block is the block index of the next | |
| * extended block. Extended blocks are marked as BLOCK_EXTBBDEPOT in the | |
| * depot. | |
| * | |
| * Return values: | |
| * - The index of the next extended block | |
| * - BLOCK_UNUSED: there is no next extended block. | |
| * - Any other return values denotes failure. | |
| */ | |
| static ULONG Storage32Impl_GetNextExtendedBlock(StorageImpl* This, ULONG blockIndex) | |
| { | |
| ULONG nextBlockIndex = BLOCK_SPECIAL; | |
| ULONG depotBlockOffset = This->bigBlockSize - sizeof(ULONG); | |
| StorageImpl_ReadDWordFromBigBlock(This, blockIndex, depotBlockOffset, | |
| &nextBlockIndex); | |
| return nextBlockIndex; | |
| } | |
| /****************************************************************************** | |
| * StorageImpl_SetNextBlockInChain | |
| * | |
| * This method will write the index of the specified block's next block | |
| * in the big block depot. | |
| * | |
| * For example: to create the chain 3 -> 1 -> 7 -> End of Chain | |
| * do the following | |
| * | |
| * StorageImpl_SetNextBlockInChain(This, 3, 1); | |
| * StorageImpl_SetNextBlockInChain(This, 1, 7); | |
| * StorageImpl_SetNextBlockInChain(This, 7, BLOCK_END_OF_CHAIN); | |
| * | |
| */ | |
| static void StorageImpl_SetNextBlockInChain( | |
| StorageImpl* This, | |
| ULONG blockIndex, | |
| ULONG nextBlock) | |
| { | |
| ULONG offsetInDepot = blockIndex * sizeof (ULONG); | |
| ULONG depotBlockCount = offsetInDepot / This->bigBlockSize; | |
| ULONG depotBlockOffset = offsetInDepot % This->bigBlockSize; | |
| ULONG depotBlockIndexPos; | |
| assert(depotBlockCount < This->bigBlockDepotCount); | |
| assert(blockIndex != nextBlock); | |
| if (blockIndex == (RANGELOCK_FIRST / This->bigBlockSize) - 1) | |
| /* This should never happen (storage file format spec forbids it), but | |
| * older versions of Wine may have generated broken files. We don't want to | |
| * assert and potentially lose data, but we do want to know if this ever | |
| * happens in a newly-created file. */ | |
| ERR("Using range lock page\n"); | |
| if (depotBlockCount < COUNT_BBDEPOTINHEADER) | |
| { | |
| depotBlockIndexPos = This->bigBlockDepotStart[depotBlockCount]; | |
| } | |
| else | |
| { | |
| /* | |
| * We have to look in the extended depot. | |
| */ | |
| depotBlockIndexPos = Storage32Impl_GetExtDepotBlock(This, depotBlockCount); | |
| } | |
| StorageImpl_WriteDWordToBigBlock(This, depotBlockIndexPos, depotBlockOffset, | |
| nextBlock); | |
| /* | |
| * Update the cached block depot, if necessary. | |
| */ | |
| if (depotBlockCount == This->indexBlockDepotCached) | |
| { | |
| This->blockDepotCached[depotBlockOffset/sizeof(ULONG)] = nextBlock; | |
| } | |
| } | |
| /****************************************************************************** | |
| * StorageImpl_GetNextFreeBigBlock | |
| * | |
| * Returns the index of the next free big block. | |
| * If the big block depot is filled, this method will enlarge it. | |
| * | |
| */ | |
| static ULONG StorageImpl_GetNextFreeBigBlock( | |
| StorageImpl* This) | |
| { | |
| ULONG depotBlockIndexPos; | |
| BYTE depotBuffer[MAX_BIG_BLOCK_SIZE]; | |
| ULONG depotBlockOffset; | |
| ULONG blocksPerDepot = This->bigBlockSize / sizeof(ULONG); | |
| ULONG nextBlockIndex = BLOCK_SPECIAL; | |
| int depotIndex = 0; | |
| ULONG freeBlock = BLOCK_UNUSED; | |
| ULONG read; | |
| ULARGE_INTEGER neededSize; | |
| STATSTG statstg; | |
| depotIndex = This->prevFreeBlock / blocksPerDepot; | |
| depotBlockOffset = (This->prevFreeBlock % blocksPerDepot) * sizeof(ULONG); | |
| /* | |
| * Scan the entire big block depot until we find a block marked free | |
| */ | |
| while (nextBlockIndex != BLOCK_UNUSED) | |
| { | |
| if (depotIndex < COUNT_BBDEPOTINHEADER) | |
| { | |
| depotBlockIndexPos = This->bigBlockDepotStart[depotIndex]; | |
| /* | |
| * Grow the primary depot. | |
| */ | |
| if (depotBlockIndexPos == BLOCK_UNUSED) | |
| { | |
| depotBlockIndexPos = depotIndex*blocksPerDepot; | |
| /* | |
| * Add a block depot. | |
| */ | |
| Storage32Impl_AddBlockDepot(This, depotBlockIndexPos, depotIndex); | |
| This->bigBlockDepotCount++; | |
| This->bigBlockDepotStart[depotIndex] = depotBlockIndexPos; | |
| /* | |
| * Flag it as a block depot. | |
| */ | |
| StorageImpl_SetNextBlockInChain(This, | |
| depotBlockIndexPos, | |
| BLOCK_SPECIAL); | |
| /* Save new header information. | |
| */ | |
| StorageImpl_SaveFileHeader(This); | |
| } | |
| } | |
| else | |
| { | |
| depotBlockIndexPos = Storage32Impl_GetExtDepotBlock(This, depotIndex); | |
| if (depotBlockIndexPos == BLOCK_UNUSED) | |
| { | |
| /* | |
| * Grow the extended depot. | |
| */ | |
| ULONG extIndex = BLOCK_UNUSED; | |
| ULONG numExtBlocks = depotIndex - COUNT_BBDEPOTINHEADER; | |
| ULONG extBlockOffset = numExtBlocks % (blocksPerDepot - 1); | |
| if (extBlockOffset == 0) | |
| { | |
| /* We need an extended block. | |
| */ | |
| extIndex = Storage32Impl_AddExtBlockDepot(This); | |
| This->extBigBlockDepotCount++; | |
| depotBlockIndexPos = extIndex + 1; | |
| } | |
| else | |
| depotBlockIndexPos = depotIndex * blocksPerDepot; | |
| /* | |
| * Add a block depot and mark it in the extended block. | |
| */ | |
| Storage32Impl_AddBlockDepot(This, depotBlockIndexPos, depotIndex); | |
| This->bigBlockDepotCount++; | |
| Storage32Impl_SetExtDepotBlock(This, depotIndex, depotBlockIndexPos); | |
| /* Flag the block depot. | |
| */ | |
| StorageImpl_SetNextBlockInChain(This, | |
| depotBlockIndexPos, | |
| BLOCK_SPECIAL); | |
| /* If necessary, flag the extended depot block. | |
| */ | |
| if (extIndex != BLOCK_UNUSED) | |
| StorageImpl_SetNextBlockInChain(This, extIndex, BLOCK_EXTBBDEPOT); | |
| /* Save header information. | |
| */ | |
| StorageImpl_SaveFileHeader(This); | |
| } | |
| } | |
| StorageImpl_ReadBigBlock(This, depotBlockIndexPos, depotBuffer, &read); | |
| if (read) | |
| { | |
| while ( ( (depotBlockOffset/sizeof(ULONG) ) < blocksPerDepot) && | |
| ( nextBlockIndex != BLOCK_UNUSED)) | |
| { | |
| StorageUtl_ReadDWord(depotBuffer, depotBlockOffset, &nextBlockIndex); | |
| if (nextBlockIndex == BLOCK_UNUSED) | |
| { | |
| freeBlock = (depotIndex * blocksPerDepot) + | |
| (depotBlockOffset/sizeof(ULONG)); | |
| } | |
| depotBlockOffset += sizeof(ULONG); | |
| } | |
| } | |
| depotIndex++; | |
| depotBlockOffset = 0; | |
| } | |
| /* | |
| * make sure that the block physically exists before using it | |
| */ | |
| neededSize.QuadPart = StorageImpl_GetBigBlockOffset(This, freeBlock)+This->bigBlockSize; | |
| ILockBytes_Stat(This->lockBytes, &statstg, STATFLAG_NONAME); | |
| if (neededSize.QuadPart > statstg.cbSize.QuadPart) | |
| ILockBytes_SetSize(This->lockBytes, neededSize); | |
| This->prevFreeBlock = freeBlock; | |
| return freeBlock; | |
| } | |
| /****************************************************************************** | |
| * StorageImpl_FreeBigBlock | |
| * | |
| * This method will flag the specified block as free in the big block depot. | |
| */ | |
| static void StorageImpl_FreeBigBlock( | |
| StorageImpl* This, | |
| ULONG blockIndex) | |
| { | |
| StorageImpl_SetNextBlockInChain(This, blockIndex, BLOCK_UNUSED); | |
| if (blockIndex < This->prevFreeBlock) | |
| This->prevFreeBlock = blockIndex; | |
| } | |
| static HRESULT StorageImpl_BaseWriteDirEntry(StorageBaseImpl *base, | |
| DirRef index, const DirEntry *data) | |
| { | |
| StorageImpl *This = (StorageImpl*)base; | |
| return StorageImpl_WriteDirEntry(This, index, data); | |
| } | |
| static HRESULT StorageImpl_BaseReadDirEntry(StorageBaseImpl *base, | |
| DirRef index, DirEntry *data) | |
| { | |
| StorageImpl *This = (StorageImpl*)base; | |
| return StorageImpl_ReadDirEntry(This, index, data); | |
| } | |
| static BlockChainStream **StorageImpl_GetFreeBlockChainCacheEntry(StorageImpl* This) | |
| { | |
| int i; | |
| for (i=0; i<BLOCKCHAIN_CACHE_SIZE; i++) | |
| { | |
| if (!This->blockChainCache[i]) | |
| { | |
| return &This->blockChainCache[i]; | |
| } | |
| } | |
| i = This->blockChainToEvict; | |
| BlockChainStream_Destroy(This->blockChainCache[i]); | |
| This->blockChainCache[i] = NULL; | |
| This->blockChainToEvict++; | |
| if (This->blockChainToEvict == BLOCKCHAIN_CACHE_SIZE) | |
| This->blockChainToEvict = 0; | |
| return &This->blockChainCache[i]; | |
| } | |
| static BlockChainStream **StorageImpl_GetCachedBlockChainStream(StorageImpl *This, | |
| DirRef index) | |
| { | |
| int i, free_index=-1; | |
| for (i=0; i<BLOCKCHAIN_CACHE_SIZE; i++) | |
| { | |
| if (!This->blockChainCache[i]) | |
| { | |
| if (free_index == -1) free_index = i; | |
| } | |
| else if (This->blockChainCache[i]->ownerDirEntry == index) | |
| { | |
| return &This->blockChainCache[i]; | |
| } | |
| } | |
| if (free_index == -1) | |
| { | |
| free_index = This->blockChainToEvict; | |
| BlockChainStream_Destroy(This->blockChainCache[free_index]); | |
| This->blockChainCache[free_index] = NULL; | |
| This->blockChainToEvict++; | |
| if (This->blockChainToEvict == BLOCKCHAIN_CACHE_SIZE) | |
| This->blockChainToEvict = 0; | |
| } | |
| This->blockChainCache[free_index] = BlockChainStream_Construct(This, NULL, index); | |
| return &This->blockChainCache[free_index]; | |
| } | |
| static void StorageImpl_DeleteCachedBlockChainStream(StorageImpl *This, DirRef index) | |
| { | |
| int i; | |
| for (i=0; i<BLOCKCHAIN_CACHE_SIZE; i++) | |
| { | |
| if (This->blockChainCache[i] && This->blockChainCache[i]->ownerDirEntry == index) | |
| { | |
| BlockChainStream_Destroy(This->blockChainCache[i]); | |
| This->blockChainCache[i] = NULL; | |
| return; | |
| } | |
| } | |
| } | |
| static HRESULT StorageImpl_StreamReadAt(StorageBaseImpl *base, DirRef index, | |
| ULARGE_INTEGER offset, ULONG size, void *buffer, ULONG *bytesRead) | |
| { | |
| StorageImpl *This = (StorageImpl*)base; | |
| DirEntry data; | |
| HRESULT hr; | |
| ULONG bytesToRead; | |
| hr = StorageImpl_ReadDirEntry(This, index, &data); | |
| if (FAILED(hr)) return hr; | |
| if (data.size.QuadPart == 0) | |
| { | |
| *bytesRead = 0; | |
| return S_OK; | |
| } | |
| if (offset.QuadPart + size > data.size.QuadPart) | |
| { | |
| bytesToRead = data.size.QuadPart - offset.QuadPart; | |
| } | |
| else | |
| { | |
| bytesToRead = size; | |
| } | |
| if (data.size.QuadPart < LIMIT_TO_USE_SMALL_BLOCK) | |
| { | |
| SmallBlockChainStream *stream; | |
| stream = SmallBlockChainStream_Construct(This, NULL, index); | |
| if (!stream) return E_OUTOFMEMORY; | |
| hr = SmallBlockChainStream_ReadAt(stream, offset, bytesToRead, buffer, bytesRead); | |
| SmallBlockChainStream_Destroy(stream); | |
| return hr; | |
| } | |
| else | |
| { | |
| BlockChainStream *stream = NULL; | |
| stream = *StorageImpl_GetCachedBlockChainStream(This, index); | |
| if (!stream) return E_OUTOFMEMORY; | |
| hr = BlockChainStream_ReadAt(stream, offset, bytesToRead, buffer, bytesRead); | |
| return hr; | |
| } | |
| } | |
| static HRESULT StorageImpl_StreamSetSize(StorageBaseImpl *base, DirRef index, | |
| ULARGE_INTEGER newsize) | |
| { | |
| StorageImpl *This = (StorageImpl*)base; | |
| DirEntry data; | |
| HRESULT hr; | |
| SmallBlockChainStream *smallblock=NULL; | |
| BlockChainStream **pbigblock=NULL, *bigblock=NULL; | |
| hr = StorageImpl_ReadDirEntry(This, index, &data); | |
| if (FAILED(hr)) return hr; | |
| /* In simple mode keep the stream size above the small block limit */ | |
| if (This->base.openFlags & STGM_SIMPLE) | |
| newsize.QuadPart = max(newsize.QuadPart, LIMIT_TO_USE_SMALL_BLOCK); | |
| if (data.size.QuadPart == newsize.QuadPart) | |
| return S_OK; | |
| /* Create a block chain object of the appropriate type */ | |
| if (data.size.QuadPart == 0) | |
| { | |
| if (newsize.QuadPart < LIMIT_TO_USE_SMALL_BLOCK) | |
| { | |
| smallblock = SmallBlockChainStream_Construct(This, NULL, index); | |
| if (!smallblock) return E_OUTOFMEMORY; | |
| } | |
| else | |
| { | |
| pbigblock = StorageImpl_GetCachedBlockChainStream(This, index); | |
| bigblock = *pbigblock; | |
| if (!bigblock) return E_OUTOFMEMORY; | |
| } | |
| } | |
| else if (data.size.QuadPart < LIMIT_TO_USE_SMALL_BLOCK) | |
| { | |
| smallblock = SmallBlockChainStream_Construct(This, NULL, index); | |
| if (!smallblock) return E_OUTOFMEMORY; | |
| } | |
| else | |
| { | |
| pbigblock = StorageImpl_GetCachedBlockChainStream(This, index); | |
| bigblock = *pbigblock; | |
| if (!bigblock) return E_OUTOFMEMORY; | |
| } | |
| /* Change the block chain type if necessary. */ | |
| if (smallblock && newsize.QuadPart >= LIMIT_TO_USE_SMALL_BLOCK) | |
| { | |
| bigblock = Storage32Impl_SmallBlocksToBigBlocks(This, &smallblock); | |
| if (!bigblock) | |
| { | |
| SmallBlockChainStream_Destroy(smallblock); | |
| return E_FAIL; | |
| } | |
| pbigblock = StorageImpl_GetFreeBlockChainCacheEntry(This); | |
| *pbigblock = bigblock; | |
| } | |
| else if (bigblock && newsize.QuadPart < LIMIT_TO_USE_SMALL_BLOCK) | |
| { | |
| smallblock = Storage32Impl_BigBlocksToSmallBlocks(This, pbigblock, newsize); | |
| if (!smallblock) | |
| return E_FAIL; | |
| } | |
| /* Set the size of the block chain. */ | |
| if (smallblock) | |
| { | |
| SmallBlockChainStream_SetSize(smallblock, newsize); | |
| SmallBlockChainStream_Destroy(smallblock); | |
| } | |
| else | |
| { | |
| BlockChainStream_SetSize(bigblock, newsize); | |
| } | |
| /* Set the size in the directory entry. */ | |
| hr = StorageImpl_ReadDirEntry(This, index, &data); | |
| if (SUCCEEDED(hr)) | |
| { | |
| data.size = newsize; | |
| hr = StorageImpl_WriteDirEntry(This, index, &data); | |
| } | |
| return hr; | |
| } | |
| static HRESULT StorageImpl_StreamWriteAt(StorageBaseImpl *base, DirRef index, | |
| ULARGE_INTEGER offset, ULONG size, const void *buffer, ULONG *bytesWritten) | |
| { | |
| StorageImpl *This = (StorageImpl*)base; | |
| DirEntry data; | |
| HRESULT hr; | |
| ULARGE_INTEGER newSize; | |
| hr = StorageImpl_ReadDirEntry(This, index, &data); | |
| if (FAILED(hr)) return hr; | |
| /* Grow the stream if necessary */ | |
| newSize.QuadPart = offset.QuadPart + size; | |
| if (newSize.QuadPart > data.size.QuadPart) | |
| { | |
| hr = StorageImpl_StreamSetSize(base, index, newSize); | |
| if (FAILED(hr)) | |
| return hr; | |
| hr = StorageImpl_ReadDirEntry(This, index, &data); | |
| if (FAILED(hr)) return hr; | |
| } | |
| if (data.size.QuadPart < LIMIT_TO_USE_SMALL_BLOCK) | |
| { | |
| SmallBlockChainStream *stream; | |
| stream = SmallBlockChainStream_Construct(This, NULL, index); | |
| if (!stream) return E_OUTOFMEMORY; | |
| hr = SmallBlockChainStream_WriteAt(stream, offset, size, buffer, bytesWritten); | |
| SmallBlockChainStream_Destroy(stream); | |
| return hr; | |
| } | |
| else | |
| { | |
| BlockChainStream *stream; | |
| stream = *StorageImpl_GetCachedBlockChainStream(This, index); | |
| if (!stream) return E_OUTOFMEMORY; | |
| return BlockChainStream_WriteAt(stream, offset, size, buffer, bytesWritten); | |
| } | |
| } | |
| static HRESULT StorageImpl_StreamLink(StorageBaseImpl *base, DirRef dst, | |
| DirRef src) | |
| { | |
| StorageImpl *This = (StorageImpl*)base; | |
| DirEntry dst_data, src_data; | |
| HRESULT hr; | |
| hr = StorageImpl_ReadDirEntry(This, dst, &dst_data); | |
| if (SUCCEEDED(hr)) | |
| hr = StorageImpl_ReadDirEntry(This, src, &src_data); | |
| if (SUCCEEDED(hr)) | |
| { | |
| StorageImpl_DeleteCachedBlockChainStream(This, src); | |
| dst_data.startingBlock = src_data.startingBlock; | |
| dst_data.size = src_data.size; | |
| hr = StorageImpl_WriteDirEntry(This, dst, &dst_data); | |
| } | |
| return hr; | |
| } | |
| static HRESULT StorageImpl_Refresh(StorageImpl *This, BOOL new_object, BOOL create) | |
| { | |
| HRESULT hr=S_OK; | |
| DirEntry currentEntry; | |
| DirRef currentEntryRef; | |
| BlockChainStream *blockChainStream; | |
| if (create) | |
| { | |
| ULARGE_INTEGER size; | |
| BYTE bigBlockBuffer[MAX_BIG_BLOCK_SIZE]; | |
| /* Discard any existing data. */ | |
| size.QuadPart = 0; | |
| ILockBytes_SetSize(This->lockBytes, size); | |
| /* | |
| * Initialize all header variables: | |
| * - The big block depot consists of one block and it is at block 0 | |
| * - The directory table starts at block 1 | |
| * - There is no small block depot | |
| */ | |
| memset( This->bigBlockDepotStart, | |
| BLOCK_UNUSED, | |
| sizeof(This->bigBlockDepotStart)); | |
| This->bigBlockDepotCount = 1; | |
| This->bigBlockDepotStart[0] = 0; | |
| This->rootStartBlock = 1; | |
| This->smallBlockLimit = LIMIT_TO_USE_SMALL_BLOCK; | |
| This->smallBlockDepotStart = BLOCK_END_OF_CHAIN; | |
| if (This->bigBlockSize == 4096) | |
| This->bigBlockSizeBits = MAX_BIG_BLOCK_SIZE_BITS; | |
| else | |
| This->bigBlockSizeBits = MIN_BIG_BLOCK_SIZE_BITS; | |
| This->smallBlockSizeBits = DEF_SMALL_BLOCK_SIZE_BITS; | |
| This->extBigBlockDepotStart = BLOCK_END_OF_CHAIN; | |
| This->extBigBlockDepotCount = 0; | |
| StorageImpl_SaveFileHeader(This); | |
| /* | |
| * Add one block for the big block depot and one block for the directory table | |
| */ | |
| size.u.HighPart = 0; | |
| size.u.LowPart = This->bigBlockSize * 3; | |
| ILockBytes_SetSize(This->lockBytes, size); | |
| /* | |
| * Initialize the big block depot | |
| */ | |
| memset(bigBlockBuffer, BLOCK_UNUSED, This->bigBlockSize); | |
| StorageUtl_WriteDWord(bigBlockBuffer, 0, BLOCK_SPECIAL); | |
| StorageUtl_WriteDWord(bigBlockBuffer, sizeof(ULONG), BLOCK_END_OF_CHAIN); | |
| StorageImpl_WriteBigBlock(This, 0, bigBlockBuffer); | |
| } | |
| else | |
| { | |
| /* | |
| * Load the header for the file. | |
| */ | |
| hr = StorageImpl_LoadFileHeader(This); | |
| if (FAILED(hr)) | |
| { | |
| return hr; | |
| } | |
| } | |
| /* | |
| * There is no block depot cached yet. | |
| */ | |
| This->indexBlockDepotCached = 0xFFFFFFFF; | |
| This->indexExtBlockDepotCached = 0xFFFFFFFF; | |
| /* | |
| * Start searching for free blocks with block 0. | |
| */ | |
| This->prevFreeBlock = 0; | |
| This->firstFreeSmallBlock = 0; | |
| /* Read the extended big block depot locations. */ | |
| if (This->extBigBlockDepotCount != 0) | |
| { | |
| ULONG current_block = This->extBigBlockDepotStart; | |
| ULONG cache_size = This->extBigBlockDepotCount * 2; | |
| ULONG i; | |
| This->extBigBlockDepotLocations = HeapAlloc(GetProcessHeap(), 0, sizeof(ULONG) * cache_size); | |
| if (!This->extBigBlockDepotLocations) | |
| { | |
| return E_OUTOFMEMORY; | |
| } | |
| This->extBigBlockDepotLocationsSize = cache_size; | |
| for (i=0; i<This->extBigBlockDepotCount; i++) | |
| { | |
| if (current_block == BLOCK_END_OF_CHAIN) | |
| { | |
| WARN("File has too few extended big block depot blocks.\n"); | |
| return STG_E_DOCFILECORRUPT; | |
| } | |
| This->extBigBlockDepotLocations[i] = current_block; | |
| current_block = Storage32Impl_GetNextExtendedBlock(This, current_block); | |
| } | |
| } | |
| else | |
| { | |
| This->extBigBlockDepotLocations = NULL; | |
| This->extBigBlockDepotLocationsSize = 0; | |
| } | |
| /* | |
| * Create the block chain abstractions. | |
| */ | |
| if(!(blockChainStream = | |
| BlockChainStream_Construct(This, &This->rootStartBlock, DIRENTRY_NULL))) | |
| { | |
| return STG_E_READFAULT; | |
| } | |
| if (!new_object) | |
| BlockChainStream_Destroy(This->rootBlockChain); | |
| This->rootBlockChain = blockChainStream; | |
| if(!(blockChainStream = | |
| BlockChainStream_Construct(This, &This->smallBlockDepotStart, | |
| DIRENTRY_NULL))) | |
| { | |
| return STG_E_READFAULT; | |
| } | |
| if (!new_object) | |
| BlockChainStream_Destroy(This->smallBlockDepotChain); | |
| This->smallBlockDepotChain = blockChainStream; | |
| /* | |
| * Write the root storage entry (memory only) | |
| */ | |
| if (create) | |
| { | |
| static const WCHAR rootentryW[] = {'R','o','o','t',' ','E','n','t','r','y',0}; | |
| DirEntry rootEntry; | |
| /* | |
| * Initialize the directory table | |
| */ | |
| memset(&rootEntry, 0, sizeof(rootEntry)); | |
| 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; i<BLOCKCHAIN_CACHE_SIZE; i++) | |
| { | |
| BlockChainStream_Destroy(This->blockChainCache[i]); | |
| This->blockChainCache[i] = NULL; | |
| } | |
| } | |
| return hr; | |
| } | |
| static HRESULT StorageImpl_GetTransactionSig(StorageBaseImpl *base, | |
| ULONG* result, BOOL refresh) | |
| { | |
| StorageImpl *This = (StorageImpl*)base; | |
| HRESULT hr=S_OK; | |
| DWORD oldTransactionSig = This->transactionSig; | |
| if (refresh) | |
| { | |
| ULARGE_INTEGER offset; | |
| ULONG bytes_read; | |
| BYTE data[4]; | |
| offset.u.HighPart = 0; | |
| offset.u.LowPart = OFFSET_TRANSACTIONSIG; | |
| hr = StorageImpl_ReadAt(This, offset, data, 4, &bytes_read); | |
| if (SUCCEEDED(hr)) | |
| { | |
| StorageUtl_ReadDWord(data, 0, &This->transactionSig); | |
| if (oldTransactionSig != This->transactionSig) | |
| { | |
| /* Someone else wrote to this, so toss all cached information. */ | |
| TRACE("signature changed\n"); | |
| hr = StorageImpl_Refresh(This, FALSE, FALSE); | |
| } | |
| if (FAILED(hr)) | |
| This->transactionSig = oldTransactionSig; | |
| } | |
| } | |
| *result = This->transactionSig; | |
| return hr; | |
| } | |
| static HRESULT StorageImpl_SetTransactionSig(StorageBaseImpl *base, | |
| ULONG value) | |
| { | |
| StorageImpl *This = (StorageImpl*)base; | |
| This->transactionSig = value; | |
| StorageImpl_SaveFileHeader(This); | |
| return S_OK; | |
| } | |
| static HRESULT StorageImpl_LockRegion(StorageImpl *This, ULARGE_INTEGER offset, | |
| ULARGE_INTEGER cb, DWORD dwLockType, BOOL *supported) | |
| { | |
| if ((dwLockType & This->locks_supported) == 0) | |
| { | |
| if (supported) *supported = FALSE; | |
| return S_OK; | |
| } | |
| if (supported) *supported = TRUE; | |
| return ILockBytes_LockRegion(This->lockBytes, offset, cb, dwLockType); | |
| } | |
| static HRESULT StorageImpl_UnlockRegion(StorageImpl *This, ULARGE_INTEGER offset, | |
| ULARGE_INTEGER cb, DWORD dwLockType) | |
| { | |
| if ((dwLockType & This->locks_supported) == 0) | |
| return S_OK; | |
| return ILockBytes_UnlockRegion(This->lockBytes, offset, cb, dwLockType); | |
| } | |
| /* Internal function */ | |
| static HRESULT StorageImpl_LockRegionSync(StorageImpl *This, ULARGE_INTEGER offset, | |
| ULARGE_INTEGER cb, DWORD dwLockType, BOOL *supported) | |
| { | |
| HRESULT hr; | |
| int delay = 0; | |
| DWORD start_time = GetTickCount(); | |
| DWORD last_sanity_check = start_time; | |
| ULARGE_INTEGER sanity_offset, sanity_cb; | |
| sanity_offset.QuadPart = RANGELOCK_UNK1_FIRST; | |
| sanity_cb.QuadPart = RANGELOCK_UNK1_LAST - RANGELOCK_UNK1_FIRST + 1; | |
| do | |
| { | |
| hr = StorageImpl_LockRegion(This, offset, cb, dwLockType, supported); | |
| if (hr == STG_E_ACCESSDENIED || hr == STG_E_LOCKVIOLATION) | |
| { | |
| DWORD current_time = GetTickCount(); | |
| if (current_time - start_time >= 20000) | |
| { | |
| /* timeout */ | |
| break; | |
| } | |
| if (current_time - last_sanity_check >= 500) | |
| { | |
| /* Any storage implementation with the file open in a | |
| * shared mode should not lock these bytes for writing. However, | |
| * some programs (LibreOffice Writer) will keep ALL bytes locked | |
| * when opening in exclusive mode. We can use a read lock to | |
| * detect this case early, and not hang a full 20 seconds. | |
| * | |
| * This can collide with another attempt to open the file in | |
| * exclusive mode, but it's unlikely, and someone would fail anyway. */ | |
| hr = StorageImpl_LockRegion(This, sanity_offset, sanity_cb, WINE_LOCK_READ, NULL); | |
| if (hr == STG_E_ACCESSDENIED || hr == STG_E_LOCKVIOLATION) | |
| break; | |
| if (SUCCEEDED(hr)) | |
| { | |
| StorageImpl_UnlockRegion(This, sanity_offset, sanity_cb, WINE_LOCK_READ); | |
| hr = STG_E_ACCESSDENIED; | |
| } | |
| last_sanity_check = current_time; | |
| } | |
| Sleep(delay); | |
| if (delay < 150) delay++; | |
| } | |
| } while (hr == STG_E_ACCESSDENIED || hr == STG_E_LOCKVIOLATION); | |
| return hr; | |
| } | |
| static HRESULT StorageImpl_LockTransaction(StorageBaseImpl *base, BOOL write) | |
| { | |
| StorageImpl *This = (StorageImpl*)base; | |
| HRESULT hr; | |
| ULARGE_INTEGER offset, cb; | |
| if (write) | |
| { | |
| /* Synchronous grab of second priority range, the commit lock, and the | |
| * lock-checking lock. */ | |
| offset.QuadPart = RANGELOCK_TRANSACTION_FIRST; | |
| cb.QuadPart = RANGELOCK_TRANSACTION_LAST - RANGELOCK_TRANSACTION_FIRST + 1; | |
| } | |
| else | |
| { | |
| offset.QuadPart = RANGELOCK_COMMIT; | |
| cb.QuadPart = 1; | |
| } | |
| hr = StorageImpl_LockRegionSync(This, offset, cb, LOCK_ONLYONCE, NULL); | |
| return hr; | |
| } | |
| static HRESULT StorageImpl_UnlockTransaction(StorageBaseImpl *base, BOOL write) | |
| { | |
| StorageImpl *This = (StorageImpl*)base; | |
| HRESULT hr; | |
| ULARGE_INTEGER offset, cb; | |
| if (write) | |
| { | |
| offset.QuadPart = RANGELOCK_TRANSACTION_FIRST; | |
| cb.QuadPart = RANGELOCK_TRANSACTION_LAST - RANGELOCK_TRANSACTION_FIRST + 1; | |
| } | |
| else | |
| { | |
| offset.QuadPart = RANGELOCK_COMMIT; | |
| cb.QuadPart = 1; | |
| } | |
| hr = StorageImpl_UnlockRegion(This, offset, cb, LOCK_ONLYONCE); | |
| return hr; | |
| } | |
| static HRESULT StorageImpl_GetFilename(StorageBaseImpl* iface, LPWSTR *result) | |
| { | |
| StorageImpl *This = (StorageImpl*) iface; | |
| STATSTG statstg; | |
| HRESULT hr; | |
| hr = ILockBytes_Stat(This->lockBytes, &statstg, 0); | |
| *result = statstg.pwcsName; | |
| return hr; | |
| } | |
| static HRESULT StorageImpl_CheckLockRange(StorageImpl *This, ULONG start, | |
| ULONG end, HRESULT fail_hr) | |
| { | |
| HRESULT hr; | |
| ULARGE_INTEGER offset, cb; | |
| offset.QuadPart = start; | |
| cb.QuadPart = 1 + end - start; | |
| hr = StorageImpl_LockRegion(This, offset, cb, LOCK_ONLYONCE, NULL); | |
| if (SUCCEEDED(hr)) StorageImpl_UnlockRegion(This, offset, cb, LOCK_ONLYONCE); | |
| if (FAILED(hr)) | |
| return fail_hr; | |
| else | |
| return S_OK; | |
| } | |
| static HRESULT StorageImpl_LockOne(StorageImpl *This, ULONG start, ULONG end) | |
| { | |
| HRESULT hr=S_OK; | |
| int i, j; | |
| ULARGE_INTEGER offset, cb; | |
| cb.QuadPart = 1; | |
| for (i=start; i<=end; i++) | |
| { | |
| offset.QuadPart = i; | |
| hr = StorageImpl_LockRegion(This, offset, cb, LOCK_ONLYONCE, NULL); | |
| if (hr != STG_E_ACCESSDENIED && hr != STG_E_LOCKVIOLATION) | |
| break; | |
| } | |
| if (SUCCEEDED(hr)) | |
| { | |
| for (j=0; j<sizeof(This->locked_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) && i<BLOCKCHAIN_CACHE_SIZE; i++) | |
| if (This->blockChainCache[i]) | |
| hr = BlockChainStream_Flush(This->blockChainCache[i]); | |
| if (SUCCEEDED(hr)) | |
| hr = ILockBytes_Flush(This->lockBytes); | |
| return hr; | |
| } | |
| static void StorageImpl_Invalidate(StorageBaseImpl* iface) | |
| { | |
| StorageImpl *This = (StorageImpl*) iface; | |
| StorageBaseImpl_DeleteAll(&This->base); | |
| This->base.reverted = TRUE; | |
| } | |
| static void StorageImpl_Destroy(StorageBaseImpl* iface) | |
| { | |
| StorageImpl *This = (StorageImpl*) iface; | |
| int i; | |
| TRACE("(%p)\n", This); | |
| StorageImpl_Flush(iface); | |
| StorageImpl_Invalidate(iface); | |
| HeapFree(GetProcessHeap(), 0, This->extBigBlockDepotLocations); | |
| BlockChainStream_Destroy(This->smallBlockRootChain); | |
| BlockChainStream_Destroy(This->rootBlockChain); | |
| BlockChainStream_Destroy(This->smallBlockDepotChain); | |
| for (i=0; i<BLOCKCHAIN_CACHE_SIZE; i++) | |
| BlockChainStream_Destroy(This->blockChainCache[i]); | |
| for (i=0; i<sizeof(This->locked_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; i<This->entries_size; i++) | |
| { | |
| TransactedDirEntry *entry = &This->entries[i]; | |
| if (entry->inuse) | |
| { | |
| if (entry->deleted) | |
| { | |
| StorageBaseImpl_StreamSetSize(This->transactedParent, | |
| entry->transactedParentEntry, zero); | |
| StorageBaseImpl_DestroyDirEntry(This->transactedParent, | |
| entry->transactedParentEntry); | |
| memset(entry, 0, sizeof(TransactedDirEntry)); | |
| This->firstFreeEntry = min(i, This->firstFreeEntry); | |
| } | |
| else if (entry->read && entry->transactedParentEntry != entry->newTransactedParentEntry) | |
| { | |
| if (entry->transactedParentEntry != DIRENTRY_NULL) | |
| StorageBaseImpl_DestroyDirEntry(This->transactedParent, | |
| entry->transactedParentEntry); | |
| if (entry->stream_dirty) | |
| { | |
| StorageBaseImpl_StreamSetSize(This->scratch, entry->stream_entry, zero); | |
| StorageBaseImpl_DestroyDirEntry(This->scratch, entry->stream_entry); | |
| entry->stream_dirty = FALSE; | |
| } | |
| entry->dirty = FALSE; | |
| entry->transactedParentEntry = entry->newTransactedParentEntry; | |
| } | |
| } | |
| } | |
| } | |
| else | |
| { | |
| TransactedSnapshotImpl_DestroyTemporaryCopy(This, DIRENTRY_NULL); | |
| } | |
| if (SUCCEEDED(hr)) | |
| hr = StorageBaseImpl_Flush(This->transactedParent); | |
| end: | |
| StorageBaseImpl_UnlockTransaction(This->transactedParent, TRUE); | |
| } | |
| TRACE("<-- %08x\n", hr); | |
| return hr; | |
| } | |
| static HRESULT WINAPI TransactedSnapshotImpl_Revert( | |
| IStorage* iface) | |
| { | |
| TransactedSnapshotImpl* This = (TransactedSnapshotImpl*)impl_from_IStorage(iface); | |
| ULARGE_INTEGER zero; | |
| ULONG i; | |
| TRACE("(%p)\n", iface); | |
| /* Destroy the open objects. */ | |
| StorageBaseImpl_DeleteAll(&This->base); | |
| /* Clear out the scratch file. */ | |
| zero.QuadPart = 0; | |
| for (i=0; i<This->entries_size; i++) | |
| { | |
| if (This->entries[i].stream_dirty) | |
| { | |
| StorageBaseImpl_StreamSetSize(This->scratch, This->entries[i].stream_entry, | |
| zero); | |
| StorageBaseImpl_DestroyDirEntry(This->scratch, This->entries[i].stream_entry); | |
| } | |
| } | |
| memset(This->entries, 0, sizeof(TransactedDirEntry) * This->entries_size); | |
| This->firstFreeEntry = 0; | |
| This->base.storageDirEntry = TransactedSnapshotImpl_CreateStubEntry(This, This->transactedParent->storageDirEntry); | |
| return S_OK; | |
| } | |
| static void TransactedSnapshotImpl_Invalidate(StorageBaseImpl* This) | |
| { | |
| if (!This->reverted) | |
| { | |
| TRACE("Storage invalidated (stg=%p)\n", This); | |
| This->reverted = TRUE; | |
| StorageBaseImpl_DeleteAll(This); | |
| } | |
| } | |
| static void TransactedSnapshotImpl_Destroy( StorageBaseImpl *iface) | |
| { | |
| TransactedSnapshotImpl* This = (TransactedSnapshotImpl*) iface; | |
| IStorage_Revert(&This->base.IStorage_iface); | |
| IStorage_Release(&This->transactedParent->IStorage_iface); | |
| IStorage_Release(&This->scratch->IStorage_iface); | |
| HeapFree(GetProcessHeap(), 0, This->entries); | |
| HeapFree(GetProcessHeap(), 0, This); | |
| } | |
| static HRESULT TransactedSnapshotImpl_Flush(StorageBaseImpl* iface) | |
| { | |
| /* We only need to flush when committing. */ | |
| return S_OK; | |
| } | |
| static HRESULT TransactedSnapshotImpl_GetFilename(StorageBaseImpl* iface, LPWSTR *result) | |
| { | |
| TransactedSnapshotImpl* This = (TransactedSnapshotImpl*) iface; | |
| return StorageBaseImpl_GetFilename(This->transactedParent, result); | |
| } | |
| static HRESULT TransactedSnapshotImpl_CreateDirEntry(StorageBaseImpl *base, | |
| const DirEntry *newData, DirRef *index) | |
| { | |
| TransactedSnapshotImpl* This = (TransactedSnapshotImpl*) base; | |
| DirRef new_ref; | |
| TransactedDirEntry *new_entry; | |
| new_ref = TransactedSnapshotImpl_FindFreeEntry(This); | |
| if (new_ref == DIRENTRY_NULL) | |
| return E_OUTOFMEMORY; | |
| new_entry = &This->entries[new_ref]; | |
| new_entry->newTransactedParentEntry = new_entry->transactedParentEntry = DIRENTRY_NULL; | |
| new_entry->read = TRUE; | |
| new_entry->dirty = TRUE; | |
| memcpy(&new_entry->data, newData, sizeof(DirEntry)); | |
| *index = new_ref; | |
| TRACE("%s l=%x r=%x d=%x <-- %x\n", debugstr_w(newData->name), newData->leftChild, newData->rightChild, newData->dirRootEntry, *index); | |
| return S_OK; | |
| } | |
| static HRESULT TransactedSnapshotImpl_WriteDirEntry(StorageBaseImpl *base, | |
| DirRef index, const DirEntry *data) | |
| { | |
| TransactedSnapshotImpl* This = (TransactedSnapshotImpl*) base; | |
| HRESULT hr; | |
| TRACE("%x %s l=%x r=%x d=%x\n", index, debugstr_w(data->name), data->leftChild, data->rightChild, data->dirRootEntry); | |
| hr = TransactedSnapshotImpl_EnsureReadEntry(This, index); | |
| if (FAILED(hr)) | |
| { | |
| TRACE("<-- %08x\n", hr); | |
| return hr; | |
| } | |
| memcpy(&This->entries[index].data, data, sizeof(DirEntry)); | |
| if (index != This->base.storageDirEntry) | |
| { | |
| This->entries[index].dirty = TRUE; | |
| if (data->size.QuadPart == 0 && | |
| This->entries[index].transactedParentEntry != DIRENTRY_NULL) | |
| { | |
| /* Since this entry is modified, and we aren't using its stream data, we | |
| * no longer care about the original entry. */ | |
| DirRef delete_ref; | |
| delete_ref = TransactedSnapshotImpl_CreateStubEntry(This, This->entries[index].transactedParentEntry); | |
| if (delete_ref != DIRENTRY_NULL) | |
| This->entries[delete_ref].deleted = TRUE; | |
| This->entries[index].transactedParentEntry = This->entries[index].newTransactedParentEntry = DIRENTRY_NULL; | |
| } | |
| } | |
| TRACE("<-- S_OK\n"); | |
| return S_OK; | |
| } | |
| static HRESULT TransactedSnapshotImpl_ReadDirEntry(StorageBaseImpl *base, | |
| DirRef index, DirEntry *data) | |
| { | |
| TransactedSnapshotImpl* This = (TransactedSnapshotImpl*) base; | |
| HRESULT hr; | |
| hr = TransactedSnapshotImpl_EnsureReadEntry(This, index); | |
| if (FAILED(hr)) | |
| { | |
| TRACE("<-- %08x\n", hr); | |
| return hr; | |
| } | |
| memcpy(data, &This->entries[index].data, sizeof(DirEntry)); | |
| TRACE("%x %s l=%x r=%x d=%x\n", index, debugstr_w(data->name), data->leftChild, data->rightChild, data->dirRootEntry); | |
| return S_OK; | |
| } | |
| static HRESULT TransactedSnapshotImpl_DestroyDirEntry(StorageBaseImpl *base, | |
| DirRef index) | |
| { | |
| TransactedSnapshotImpl* This = (TransactedSnapshotImpl*) base; | |
| if (This->entries[index].transactedParentEntry == DIRENTRY_NULL || | |
| This->entries[index].data.size.QuadPart != 0) | |
| { | |
| /* If we deleted this entry while it has stream data. We must have left the | |
| * data because some other entry is using it, and we need to leave the | |
| * original entry alone. */ | |
| memset(&This->entries[index], 0, sizeof(TransactedDirEntry)); | |
| This->firstFreeEntry = min(index, This->firstFreeEntry); | |
| } | |
| else | |
| { | |
| This->entries[index].deleted = TRUE; | |
| } | |
| return S_OK; | |
| } | |
| static HRESULT TransactedSnapshotImpl_StreamReadAt(StorageBaseImpl *base, | |
| DirRef index, ULARGE_INTEGER offset, ULONG size, void *buffer, ULONG *bytesRead) | |
| { | |
| TransactedSnapshotImpl* This = (TransactedSnapshotImpl*) base; | |
| if (This->entries[index].stream_dirty) | |
| { | |
| return StorageBaseImpl_StreamReadAt(This->scratch, | |
| This->entries[index].stream_entry, offset, size, buffer, bytesRead); | |
| } | |
| else if (This->entries[index].transactedParentEntry == DIRENTRY_NULL) | |
| { | |
| /* This stream doesn't live in the parent, and we haven't allocated storage | |
| * for it yet */ | |
| *bytesRead = 0; | |
| return S_OK; | |
| } | |
| else | |
| { | |
| return StorageBaseImpl_StreamReadAt(This->transactedParent, | |
| This->entries[index].transactedParentEntry, offset, size, buffer, bytesRead); | |
| } | |
| } | |
| static HRESULT TransactedSnapshotImpl_StreamWriteAt(StorageBaseImpl *base, | |
| DirRef index, ULARGE_INTEGER offset, ULONG size, const void *buffer, ULONG *bytesWritten) | |
| { | |
| TransactedSnapshotImpl* This = (TransactedSnapshotImpl*) base; | |
| HRESULT hr; | |
| hr = TransactedSnapshotImpl_EnsureReadEntry(This, index); | |
| if (FAILED(hr)) | |
| { | |
| TRACE("<-- %08x\n", hr); | |
| return hr; | |
| } | |
| hr = TransactedSnapshotImpl_MakeStreamDirty(This, index); | |
| if (FAILED(hr)) | |
| { | |
| TRACE("<-- %08x\n", hr); | |
| return hr; | |
| } | |
| hr = StorageBaseImpl_StreamWriteAt(This->scratch, | |
| This->entries[index].stream_entry, offset, size, buffer, bytesWritten); | |
| if (SUCCEEDED(hr) && size != 0) | |
| This->entries[index].data.size.QuadPart = max( | |
| This->entries[index].data.size.QuadPart, | |
| offset.QuadPart + size); | |
| TRACE("<-- %08x\n", hr); | |
| return hr; | |
| } | |
| static HRESULT TransactedSnapshotImpl_StreamSetSize(StorageBaseImpl *base, | |
| DirRef index, ULARGE_INTEGER newsize) | |
| { | |
| TransactedSnapshotImpl* This = (TransactedSnapshotImpl*) base; | |
| HRESULT hr; | |
| hr = TransactedSnapshotImpl_EnsureReadEntry(This, index); | |
| if (FAILED(hr)) | |
| { | |
| TRACE("<-- %08x\n", hr); | |
| return hr; | |
| } | |
| if (This->entries[index].data.size.QuadPart == newsize.QuadPart) | |
| return S_OK; | |
| if (newsize.QuadPart == 0) | |
| { | |
| /* Destroy any parent references or entries in the scratch file. */ | |
| if (This->entries[index].stream_dirty) | |
| { | |
| ULARGE_INTEGER zero; | |
| zero.QuadPart = 0; | |
| StorageBaseImpl_StreamSetSize(This->scratch, | |
| This->entries[index].stream_entry, zero); | |
| StorageBaseImpl_DestroyDirEntry(This->scratch, | |
| This->entries[index].stream_entry); | |
| This->entries[index].stream_dirty = FALSE; | |
| } | |
| else if (This->entries[index].transactedParentEntry != DIRENTRY_NULL) | |
| { | |
| DirRef delete_ref; | |
| delete_ref = TransactedSnapshotImpl_CreateStubEntry(This, This->entries[index].transactedParentEntry); | |
| if (delete_ref != DIRENTRY_NULL) | |
| This->entries[delete_ref].deleted = TRUE; | |
| This->entries[index].transactedParentEntry = This->entries[index].newTransactedParentEntry = DIRENTRY_NULL; | |
| } | |
| } | |
| else | |
| { | |
| hr = TransactedSnapshotImpl_MakeStreamDirty(This, index); | |
| if (FAILED(hr)) return hr; | |
| hr = StorageBaseImpl_StreamSetSize(This->scratch, | |
| This->entries[index].stream_entry, newsize); | |
| } | |
| if (SUCCEEDED(hr)) | |
| This->entries[index].data.size = newsize; | |
| TRACE("<-- %08x\n", hr); | |
| return hr; | |
| } | |
| static HRESULT TransactedSnapshotImpl_StreamLink(StorageBaseImpl *base, | |
| DirRef dst, DirRef src) | |
| { | |
| TransactedSnapshotImpl* This = (TransactedSnapshotImpl*) base; | |
| HRESULT hr; | |
| TransactedDirEntry *dst_entry, *src_entry; | |
| hr = TransactedSnapshotImpl_EnsureReadEntry(This, src); | |
| if (FAILED(hr)) | |
| { | |
| TRACE("<-- %08x\n", hr); | |
| return hr; | |
| } | |
| hr = TransactedSnapshotImpl_EnsureReadEntry(This, dst); | |
| if (FAILED(hr)) | |
| { | |
| TRACE("<-- %08x\n", hr); | |
| return hr; | |
| } | |
| dst_entry = &This->entries[dst]; | |
| src_entry = &This->entries[src]; | |
| dst_entry->stream_dirty = src_entry->stream_dirty; | |
| dst_entry->stream_entry = src_entry->stream_entry; | |
| dst_entry->transactedParentEntry = src_entry->transactedParentEntry; | |
| dst_entry->newTransactedParentEntry = src_entry->newTransactedParentEntry; | |
| dst_entry->data.size = src_entry->data.size; | |
| return S_OK; | |
| } | |
| static HRESULT TransactedSnapshotImpl_GetTransactionSig(StorageBaseImpl *base, | |
| ULONG* result, BOOL refresh) | |
| { | |
| return E_NOTIMPL; | |
| } | |
| static HRESULT TransactedSnapshotImpl_SetTransactionSig(StorageBaseImpl *base, | |
| ULONG value) | |
| { | |
| return E_NOTIMPL; | |
| } | |
| static HRESULT TransactedSnapshotImpl_LockTransaction(StorageBaseImpl *base, BOOL write) | |
| { | |
| return E_NOTIMPL; | |
| } | |
| static HRESULT TransactedSnapshotImpl_UnlockTransaction(StorageBaseImpl *base, BOOL write) | |
| { | |
| return E_NOTIMPL; | |
| } | |
| static const IStorageVtbl TransactedSnapshotImpl_Vtbl = | |
| { | |
| StorageBaseImpl_QueryInterface, | |
| StorageBaseImpl_AddRef, | |
| StorageBaseImpl_Release, | |
| StorageBaseImpl_CreateStream, | |
| StorageBaseImpl_OpenStream, | |
| StorageBaseImpl_CreateStorage, | |
| StorageBaseImpl_OpenStorage, | |
| StorageBaseImpl_CopyTo, | |
| StorageBaseImpl_MoveElementTo, | |
| TransactedSnapshotImpl_Commit, | |
| TransactedSnapshotImpl_Revert, | |
| StorageBaseImpl_EnumElements, | |
| StorageBaseImpl_DestroyElement, | |
| StorageBaseImpl_RenameElement, | |
| StorageBaseImpl_SetElementTimes, | |
| StorageBaseImpl_SetClass, | |
| StorageBaseImpl_SetStateBits, | |
| StorageBaseImpl_Stat | |
| }; | |
| static const StorageBaseImplVtbl TransactedSnapshotImpl_BaseVtbl = | |
| { | |
| TransactedSnapshotImpl_Destroy, | |
| TransactedSnapshotImpl_Invalidate, | |
| TransactedSnapshotImpl_Flush, | |
| TransactedSnapshotImpl_GetFilename, | |
| TransactedSnapshotImpl_CreateDirEntry, | |
| TransactedSnapshotImpl_WriteDirEntry, | |
| TransactedSnapshotImpl_ReadDirEntry, | |
| TransactedSnapshotImpl_DestroyDirEntry, | |
| TransactedSnapshotImpl_StreamReadAt, | |
| TransactedSnapshotImpl_StreamWriteAt, | |
| TransactedSnapshotImpl_StreamSetSize, | |
| TransactedSnapshotImpl_StreamLink, | |
| TransactedSnapshotImpl_GetTransactionSig, | |
| TransactedSnapshotImpl_SetTransactionSig, | |
| TransactedSnapshotImpl_LockTransaction, | |
| TransactedSnapshotImpl_UnlockTransaction | |
| }; | |
| static HRESULT TransactedSnapshotImpl_Construct(StorageBaseImpl *parentStorage, | |
| TransactedSnapshotImpl** result) | |
| { | |
| HRESULT hr; | |
| *result = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(TransactedSnapshotImpl)); | |
| if (*result) | |
| { | |
| IStorage *scratch; | |
| (*result)->base.IStorage_iface.lpVtbl = &TransactedSnapshotImpl_Vtbl; | |
| /* This is OK because the property set storage functions use the IStorage functions. */ | |
| (*result)->base.IPropertySetStorage_iface.lpVtbl = parentStorage->IPropertySetStorage_iface.lpVtbl; | |
| (*result)->base.baseVtbl = &TransactedSnapshotImpl_BaseVtbl; | |
| list_init(&(*result)->base.strmHead); | |
| list_init(&(*result)->base.storageHead); | |
| (*result)->base.ref = 1; | |
| (*result)->base.openFlags = parentStorage->openFlags; | |
| /* This cannot fail, except with E_NOTIMPL in which case we don't care */ | |
| StorageBaseImpl_GetTransactionSig(parentStorage, &(*result)->lastTransactionSig, FALSE); | |
| /* Create a new temporary storage to act as the scratch file. */ | |
| hr = StgCreateDocfile(NULL, STGM_READWRITE|STGM_SHARE_EXCLUSIVE|STGM_CREATE|STGM_DELETEONRELEASE, | |
| 0, &scratch); | |
| (*result)->scratch = impl_from_IStorage(scratch); | |
| if (SUCCEEDED(hr)) | |
| { | |
| ULONG num_entries = 20; | |
| (*result)->entries = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(TransactedDirEntry) * num_entries); | |
| (*result)->entries_size = num_entries; | |
| (*result)->firstFreeEntry = 0; | |
| if ((*result)->entries) | |
| { | |
| /* parentStorage already has 1 reference, which we take over here. */ | |
| (*result)->transactedParent = parentStorage; | |
| parentStorage->transactedChild = &(*result)->base; | |
| (*result)->base.storageDirEntry = TransactedSnapshotImpl_CreateStubEntry(*result, parentStorage->storageDirEntry); | |
| } | |
| else | |
| { | |
| IStorage_Release(scratch); | |
| hr = E_OUTOFMEMORY; | |
| } | |
| } | |
| if (FAILED(hr)) HeapFree(GetProcessHeap(), 0, *result); | |
| return hr; | |
| } | |
| else | |
| return E_OUTOFMEMORY; | |
| } | |
| /************************************************************************ | |
| * TransactedSharedImpl implementation | |
| ***********************************************************************/ | |
| static void TransactedSharedImpl_Invalidate(StorageBaseImpl* This) | |
| { | |
| if (!This->reverted) | |
| { | |
| TRACE("Storage invalidated (stg=%p)\n", This); | |
| This->reverted = TRUE; | |
| StorageBaseImpl_DeleteAll(This); | |
| } | |
| } | |
| static void TransactedSharedImpl_Destroy( StorageBaseImpl *iface) | |
| { | |
| TransactedSharedImpl* This = (TransactedSharedImpl*) iface; | |
| TransactedSharedImpl_Invalidate(&This->base); | |
| IStorage_Release(&This->transactedParent->IStorage_iface); | |
| IStorage_Release(&This->scratch->base.IStorage_iface); | |
| HeapFree(GetProcessHeap(), 0, This); | |
| } | |
| static HRESULT TransactedSharedImpl_Flush(StorageBaseImpl* iface) | |
| { | |
| /* We only need to flush when committing. */ | |
| return S_OK; | |
| } | |
| static HRESULT TransactedSharedImpl_GetFilename(StorageBaseImpl* iface, LPWSTR *result) | |
| { | |
| TransactedSharedImpl* This = (TransactedSharedImpl*) iface; | |
| return StorageBaseImpl_GetFilename(This->transactedParent, result); | |
| } | |
| static HRESULT TransactedSharedImpl_CreateDirEntry(StorageBaseImpl *base, | |
| const DirEntry *newData, DirRef *index) | |
| { | |
| TransactedSharedImpl* This = (TransactedSharedImpl*) base; | |
| return StorageBaseImpl_CreateDirEntry(&This->scratch->base, | |
| newData, index); | |
| } | |
| static HRESULT TransactedSharedImpl_WriteDirEntry(StorageBaseImpl *base, | |
| DirRef index, const DirEntry *data) | |
| { | |
| TransactedSharedImpl* This = (TransactedSharedImpl*) base; | |
| return StorageBaseImpl_WriteDirEntry(&This->scratch->base, | |
| index, data); | |
| } | |
| static HRESULT TransactedSharedImpl_ReadDirEntry(StorageBaseImpl *base, | |
| DirRef index, DirEntry *data) | |
| { | |
| TransactedSharedImpl* This = (TransactedSharedImpl*) base; | |
| return StorageBaseImpl_ReadDirEntry(&This->scratch->base, | |
| index, data); | |
| } | |
| static HRESULT TransactedSharedImpl_DestroyDirEntry(StorageBaseImpl *base, | |
| DirRef index) | |
| { | |
| TransactedSharedImpl* This = (TransactedSharedImpl*) base; | |
| return StorageBaseImpl_DestroyDirEntry(&This->scratch->base, | |
| index); | |
| } | |
| static HRESULT TransactedSharedImpl_StreamReadAt(StorageBaseImpl *base, | |
| DirRef index, ULARGE_INTEGER offset, ULONG size, void *buffer, ULONG *bytesRead) | |
| { | |
| TransactedSharedImpl* This = (TransactedSharedImpl*) base; | |
| return StorageBaseImpl_StreamReadAt(&This->scratch->base, | |
| index, offset, size, buffer, bytesRead); | |
| } | |
| static HRESULT TransactedSharedImpl_StreamWriteAt(StorageBaseImpl *base, | |
| DirRef index, ULARGE_INTEGER offset, ULONG size, const void *buffer, ULONG *bytesWritten) | |
| { | |
| TransactedSharedImpl* This = (TransactedSharedImpl*) base; | |
| return StorageBaseImpl_StreamWriteAt(&This->scratch->base, | |
| index, offset, size, buffer, bytesWritten); | |
| } | |
| static HRESULT TransactedSharedImpl_StreamSetSize(StorageBaseImpl *base, | |
| DirRef index, ULARGE_INTEGER newsize) | |
| { | |
| TransactedSharedImpl* This = (TransactedSharedImpl*) base; | |
| return StorageBaseImpl_StreamSetSize(&This->scratch->base, | |
| index, newsize); | |
| } | |
| static HRESULT TransactedSharedImpl_StreamLink(StorageBaseImpl *base, | |
| DirRef dst, DirRef src) | |
| { | |
| TransactedSharedImpl* This = (TransactedSharedImpl*) base; | |
| return StorageBaseImpl_StreamLink(&This->scratch->base, | |
| dst, src); | |
| } | |
| static HRESULT TransactedSharedImpl_GetTransactionSig(StorageBaseImpl *base, | |
| ULONG* result, BOOL refresh) | |
| { | |
| return E_NOTIMPL; | |
| } | |
| static HRESULT TransactedSharedImpl_SetTransactionSig(StorageBaseImpl *base, | |
| ULONG value) | |
| { | |
| return E_NOTIMPL; | |
| } | |
| static HRESULT TransactedSharedImpl_LockTransaction(StorageBaseImpl *base, BOOL write) | |
| { | |
| return E_NOTIMPL; | |
| } | |
| static HRESULT TransactedSharedImpl_UnlockTransaction(StorageBaseImpl *base, BOOL write) | |
| { | |
| return E_NOTIMPL; | |
| } | |
| static HRESULT WINAPI TransactedSharedImpl_Commit( | |
| IStorage* iface, | |
| DWORD grfCommitFlags) /* [in] */ | |
| { | |
| TransactedSharedImpl* This = (TransactedSharedImpl*)impl_from_IStorage(iface); | |
| DirRef new_storage_ref, prev_storage_ref; | |
| DirEntry src_data, dst_data; | |
| HRESULT hr; | |
| ULONG transactionSig; | |
| TRACE("(%p,%x)\n", iface, grfCommitFlags); | |
| /* Cannot commit a read-only transacted storage */ | |
| if ( STGM_ACCESS_MODE( This->base.openFlags ) == STGM_READ ) | |
| return STG_E_ACCESSDENIED; | |
| hr = StorageBaseImpl_LockTransaction(This->transactedParent, TRUE); | |
| if (hr == E_NOTIMPL) hr = S_OK; | |
| if (SUCCEEDED(hr)) | |
| { | |
| hr = StorageBaseImpl_GetTransactionSig(This->transactedParent, &transactionSig, TRUE); | |
| if (SUCCEEDED(hr)) | |
| { | |
| if ((grfCommitFlags & STGC_ONLYIFCURRENT) && transactionSig != This->lastTransactionSig) | |
| hr = STG_E_NOTCURRENT; | |
| if (SUCCEEDED(hr)) | |
| hr = StorageBaseImpl_SetTransactionSig(This->transactedParent, transactionSig+1); | |
| } | |
| else if (hr == E_NOTIMPL) | |
| hr = S_OK; | |
| if (SUCCEEDED(hr)) | |
| hr = StorageBaseImpl_ReadDirEntry(&This->scratch->base, This->scratch->base.storageDirEntry, &src_data); | |
| /* FIXME: If we're current, we should be able to copy only the changes in scratch. */ | |
| if (SUCCEEDED(hr)) | |
| hr = StorageBaseImpl_DupStorageTree(This->transactedParent, &new_storage_ref, &This->scratch->base, src_data.dirRootEntry); | |
| if (SUCCEEDED(hr)) | |
| hr = StorageBaseImpl_Flush(This->transactedParent); | |
| if (SUCCEEDED(hr)) | |
| hr = StorageBaseImpl_ReadDirEntry(This->transactedParent, This->transactedParent->storageDirEntry, &dst_data); | |
| if (SUCCEEDED(hr)) | |
| { | |
| prev_storage_ref = dst_data.dirRootEntry; | |
| dst_data.dirRootEntry = new_storage_ref; | |
| dst_data.clsid = src_data.clsid; | |
| dst_data.ctime = src_data.ctime; | |
| dst_data.mtime = src_data.mtime; | |
| hr = StorageBaseImpl_WriteDirEntry(This->transactedParent, This->transactedParent->storageDirEntry, &dst_data); | |
| } | |
| if (SUCCEEDED(hr)) | |
| { | |
| /* Try to flush after updating the root storage, but if the flush fails, keep | |
| * going, on the theory that it'll either succeed later or the subsequent | |
| * writes will fail. */ | |
| StorageBaseImpl_Flush(This->transactedParent); | |
| hr = StorageBaseImpl_DeleteStorageTree(This->transactedParent, prev_storage_ref, TRUE); | |
| } | |
| if (SUCCEEDED(hr)) | |
| hr = StorageBaseImpl_Flush(This->transactedParent); | |
| StorageBaseImpl_UnlockTransaction(This->transactedParent, TRUE); | |
| if (SUCCEEDED(hr)) | |
| hr = IStorage_Commit(&This->scratch->base.IStorage_iface, STGC_DEFAULT); | |
| if (SUCCEEDED(hr)) | |
| { | |
| This->lastTransactionSig = transactionSig+1; | |
| } | |
| } | |
| TRACE("<-- %08x\n", hr); | |
| return hr; | |
| } | |
| static HRESULT WINAPI TransactedSharedImpl_Revert( | |
| IStorage* iface) | |
| { | |
| TransactedSharedImpl* This = (TransactedSharedImpl*)impl_from_IStorage(iface); | |
| TRACE("(%p)\n", iface); | |
| /* Destroy the open objects. */ | |
| StorageBaseImpl_DeleteAll(&This->base); | |
| return IStorage_Revert(&This->scratch->base.IStorage_iface); | |
| } | |
| static const IStorageVtbl TransactedSharedImpl_Vtbl = | |
| { | |
| StorageBaseImpl_QueryInterface, | |
| StorageBaseImpl_AddRef, | |
| StorageBaseImpl_Release, | |
| StorageBaseImpl_CreateStream, | |
| StorageBaseImpl_OpenStream, | |
| StorageBaseImpl_CreateStorage, | |
| StorageBaseImpl_OpenStorage, | |
| StorageBaseImpl_CopyTo, | |
| StorageBaseImpl_MoveElementTo, | |
| TransactedSharedImpl_Commit, | |
| TransactedSharedImpl_Revert, | |
| StorageBaseImpl_EnumElements, | |
| StorageBaseImpl_DestroyElement, | |
| StorageBaseImpl_RenameElement, | |
| StorageBaseImpl_SetElementTimes, | |
| StorageBaseImpl_SetClass, | |
| StorageBaseImpl_SetStateBits, | |
| StorageBaseImpl_Stat | |
| }; | |
| static const StorageBaseImplVtbl TransactedSharedImpl_BaseVtbl = | |
| { | |
| TransactedSharedImpl_Destroy, | |
| TransactedSharedImpl_Invalidate, | |
| TransactedSharedImpl_Flush, | |
| TransactedSharedImpl_GetFilename, | |
| TransactedSharedImpl_CreateDirEntry, | |
| TransactedSharedImpl_WriteDirEntry, | |
| TransactedSharedImpl_ReadDirEntry, | |
| TransactedSharedImpl_DestroyDirEntry, | |
| TransactedSharedImpl_StreamReadAt, | |
| TransactedSharedImpl_StreamWriteAt, | |
| TransactedSharedImpl_StreamSetSize, | |
| TransactedSharedImpl_StreamLink, | |
| TransactedSharedImpl_GetTransactionSig, | |
| TransactedSharedImpl_SetTransactionSig, | |
| TransactedSharedImpl_LockTransaction, | |
| TransactedSharedImpl_UnlockTransaction | |
| }; | |
| static HRESULT TransactedSharedImpl_Construct(StorageBaseImpl *parentStorage, | |
| TransactedSharedImpl** result) | |
| { | |
| HRESULT hr; | |
| *result = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(TransactedSharedImpl)); | |
| if (*result) | |
| { | |
| IStorage *scratch; | |
| (*result)->base.IStorage_iface.lpVtbl = &TransactedSharedImpl_Vtbl; | |
| /* This is OK because the property set storage functions use the IStorage functions. */ | |
| (*result)->base.IPropertySetStorage_iface.lpVtbl = parentStorage->IPropertySetStorage_iface.lpVtbl; | |
| (*result)->base.baseVtbl = &TransactedSharedImpl_BaseVtbl; | |
| list_init(&(*result)->base.strmHead); | |
| list_init(&(*result)->base.storageHead); | |
| (*result)->base.ref = 1; | |
| (*result)->base.openFlags = parentStorage->openFlags; | |
| hr = StorageBaseImpl_LockTransaction(parentStorage, FALSE); | |
| if (SUCCEEDED(hr)) | |
| { | |
| STGOPTIONS stgo; | |
| /* This cannot fail, except with E_NOTIMPL in which case we don't care */ | |
| StorageBaseImpl_GetTransactionSig(parentStorage, &(*result)->lastTransactionSig, FALSE); | |
| stgo.usVersion = 1; | |
| stgo.reserved = 0; | |
| stgo.ulSectorSize = 4096; | |
| stgo.pwcsTemplateFile = NULL; | |
| /* Create a new temporary storage to act as the scratch file. */ | |
| hr = StgCreateStorageEx(NULL, STGM_READWRITE|STGM_SHARE_EXCLUSIVE|STGM_CREATE|STGM_DELETEONRELEASE|STGM_TRANSACTED, | |
| STGFMT_DOCFILE, 0, &stgo, NULL, &IID_IStorage, (void**)&scratch); | |
| (*result)->scratch = (TransactedSnapshotImpl*)impl_from_IStorage(scratch); | |
| if (SUCCEEDED(hr)) | |
| { | |
| hr = StorageBaseImpl_CopyStorageTree(&(*result)->scratch->base, (*result)->scratch->base.storageDirEntry, | |
| parentStorage, parentStorage->storageDirEntry); | |
| if (SUCCEEDED(hr)) | |
| { | |
| hr = IStorage_Commit(scratch, STGC_DEFAULT); | |
| (*result)->base.storageDirEntry = (*result)->scratch->base.storageDirEntry; | |
| (*result)->transactedParent = parentStorage; | |
| } | |
| if (FAILED(hr)) | |
| IStorage_Release(scratch); | |
| } | |
| StorageBaseImpl_UnlockTransaction(parentStorage, FALSE); | |
| } | |
| if (FAILED(hr)) HeapFree(GetProcessHeap(), 0, *result); | |
| return hr; | |
| } | |
| else | |
| return E_OUTOFMEMORY; | |
| } | |
| static HRESULT Storage_ConstructTransacted(StorageBaseImpl *parentStorage, | |
| BOOL toplevel, StorageBaseImpl** result) | |
| { | |
| static int fixme_flags=STGM_NOSCRATCH|STGM_NOSNAPSHOT; | |
| if (parentStorage->openFlags & fixme_flags) | |
| { | |
| fixme_flags &= ~parentStorage->openFlags; | |
| FIXME("Unimplemented flags %x\n", parentStorage->openFlags); | |
| } | |
| if (toplevel && !(parentStorage->openFlags & STGM_NOSNAPSHOT) && | |
| STGM_SHARE_MODE(parentStorage->openFlags) != STGM_SHARE_DENY_WRITE && | |
| STGM_SHARE_MODE(parentStorage->openFlags) != STGM_SHARE_EXCLUSIVE) | |
| { | |
| /* Need to create a temp file for the snapshot */ | |
| return TransactedSharedImpl_Construct(parentStorage, (TransactedSharedImpl**)result); | |
| } | |
| return TransactedSnapshotImpl_Construct(parentStorage, | |
| (TransactedSnapshotImpl**)result); | |
| } | |
| static HRESULT Storage_Construct( | |
| HANDLE hFile, | |
| LPCOLESTR pwcsName, | |
| ILockBytes* pLkbyt, | |
| DWORD openFlags, | |
| BOOL fileBased, | |
| BOOL create, | |
| ULONG sector_size, | |
| StorageBaseImpl** result) | |
| { | |
| StorageImpl *newStorage; | |
| StorageBaseImpl *newTransactedStorage; | |
| HRESULT hr; | |
| hr = StorageImpl_Construct(hFile, pwcsName, pLkbyt, openFlags, fileBased, create, sector_size, &newStorage); | |
| if (FAILED(hr)) goto end; | |
| if (openFlags & STGM_TRANSACTED) | |
| { | |
| hr = Storage_ConstructTransacted(&newStorage->base, TRUE, &newTransactedStorage); | |
| if (FAILED(hr)) | |
| IStorage_Release(&newStorage->base.IStorage_iface); | |
| else | |
| *result = newTransactedStorage; | |
| } | |
| else | |
| *result = &newStorage->base; | |
| end: | |
| return hr; | |
| } | |
| /************************************************************************ | |
| * StorageUtl helper functions | |
| ***********************************************************************/ | |
| void StorageUtl_ReadWord(const BYTE* buffer, ULONG offset, WORD* value) | |
| { | |
| WORD tmp; | |
| memcpy(&tmp, buffer+offset, sizeof(WORD)); | |
| *value = lendian16toh(tmp); | |
| } | |
| void StorageUtl_WriteWord(BYTE* buffer, ULONG offset, WORD value) | |
| { | |
| value = htole16(value); | |
| memcpy(buffer+offset, &value, sizeof(WORD)); | |
| } | |
| void StorageUtl_ReadDWord(const BYTE* buffer, ULONG offset, DWORD* value) | |
| { | |
| DWORD tmp; | |
| memcpy(&tmp, buffer+offset, sizeof(DWORD)); | |
| *value = lendian32toh(tmp); | |
| } | |
| void StorageUtl_WriteDWord(BYTE* buffer, ULONG offset, DWORD value) | |
| { | |
| value = htole32(value); | |
| memcpy(buffer+offset, &value, sizeof(DWORD)); | |
| } | |
| void StorageUtl_ReadULargeInteger(const BYTE* buffer, ULONG offset, | |
| ULARGE_INTEGER* value) | |
| { | |
| #ifdef WORDS_BIGENDIAN | |
| ULARGE_INTEGER tmp; | |
| memcpy(&tmp, buffer + offset, sizeof(ULARGE_INTEGER)); | |
| value->u.LowPart = htole32(tmp.u.HighPart); | |
| value->u.HighPart = htole32(tmp.u.LowPart); | |
| #else | |
| memcpy(value, buffer + offset, sizeof(ULARGE_INTEGER)); | |
| #endif | |
| } | |
| void StorageUtl_WriteULargeInteger(BYTE* buffer, ULONG offset, | |
| const ULARGE_INTEGER *value) | |
| { | |
| #ifdef WORDS_BIGENDIAN | |
| ULARGE_INTEGER tmp; | |
| tmp.u.LowPart = htole32(value->u.HighPart); | |
| tmp.u.HighPart = htole32(value->u.LowPart); | |
| memcpy(buffer + offset, &tmp, sizeof(ULARGE_INTEGER)); | |
| #else | |
| memcpy(buffer + offset, value, sizeof(ULARGE_INTEGER)); | |
| #endif | |
| } | |
| void StorageUtl_ReadGUID(const BYTE* buffer, ULONG offset, GUID* value) | |
| { | |
| StorageUtl_ReadDWord(buffer, offset, &(value->Data1)); | |
| StorageUtl_ReadWord(buffer, offset+4, &(value->Data2)); | |
| StorageUtl_ReadWord(buffer, offset+6, &(value->Data3)); | |
| memcpy(value->Data4, buffer+offset+8, sizeof(value->Data4)); | |
| } | |
| void StorageUtl_WriteGUID(BYTE* buffer, ULONG offset, const GUID* value) | |
| { | |
| StorageUtl_WriteDWord(buffer, offset, value->Data1); | |
| StorageUtl_WriteWord(buffer, offset+4, value->Data2); | |
| StorageUtl_WriteWord(buffer, offset+6, value->Data3); | |
| memcpy(buffer+offset+8, value->Data4, sizeof(value->Data4)); | |
| } | |
| void StorageUtl_CopyDirEntryToSTATSTG( | |
| StorageBaseImpl* storage, | |
| STATSTG* destination, | |
| const DirEntry* source, | |
| int statFlags) | |
| { | |
| /* | |
| * The copy of the string occurs only when the flag is not set | |
| */ | |
| if (!(statFlags & STATFLAG_NONAME) && source->stgType == STGTY_ROOT) | |
| { | |
| /* Use the filename for the root storage. */ | |
| destination->pwcsName = 0; | |
| StorageBaseImpl_GetFilename(storage, &destination->pwcsName); | |
| } | |
| else if( ((statFlags & STATFLAG_NONAME) != 0) || | |
| (source->name[0] == 0) ) | |
| { | |
| destination->pwcsName = 0; | |
| } | |
| else | |
| { | |
| destination->pwcsName = | |
| CoTaskMemAlloc((lstrlenW(source->name)+1)*sizeof(WCHAR)); | |
| 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)); | |
| if(!newStream) | |
| return NULL; | |
| newStream->parentStorage = parentStorage; | |
| newStream->headOfStreamPlaceHolder = headOfStreamPlaceHolder; | |
| newStream->ownerDirEntry = dirEntry; | |
| newStream->indexCache = NULL; | |
| newStream->indexCacheLen = 0; | |
| newStream->indexCacheSize = 0; | |
| newStream->cachedBlocks[0].index = 0xffffffff; | |
| newStream->cachedBlocks[0].dirty = FALSE; | |
| newStream->cachedBlocks[1].index = 0xffffffff; | |
| newStream->cachedBlocks[1].dirty = FALSE; | |
| newStream->blockToEvict = 0; | |
| if (FAILED(BlockChainStream_UpdateIndexCache(newStream))) | |
| { | |
| HeapFree(GetProcessHeap(), 0, newStream->indexCache); | |
| HeapFree(GetProcessHeap(), 0, newStream); | |
| return NULL; | |
| } | |
| return newStream; | |
| } | |
| HRESULT BlockChainStream_Flush(BlockChainStream* This) | |
| { | |
| int i; | |
| if (!This) return S_OK; | |
| for (i=0; i<2; i++) | |
| { | |
| if (This->cachedBlocks[i].dirty) | |
| { | |
| if (StorageImpl_WriteBigBlock(This->parentStorage, This->cachedBlocks[i].sector, This->cachedBlocks[i].data)) | |
| This->cachedBlocks[i].dirty = FALSE; | |
| else | |
| return STG_E_WRITEFAULT; | |
| } | |
| } | |
| return S_OK; | |
| } | |
| void BlockChainStream_Destroy(BlockChainStream* This) | |
| { | |
| if (This) | |
| { | |
| BlockChainStream_Flush(This); | |
| HeapFree(GetProcessHeap(), 0, This->indexCache); | |
| } | |
| HeapFree(GetProcessHeap(), 0, This); | |
| } | |
| /****************************************************************************** | |
| * BlockChainStream_Shrink | |
| * | |
| * Shrinks this chain in the big block depot. | |
| */ | |
| static BOOL BlockChainStream_Shrink(BlockChainStream* This, | |
| ULARGE_INTEGER newSize) | |
| { | |
| ULONG blockIndex; | |
| ULONG numBlocks; | |
| int i; | |
| /* | |
| * Figure out how many blocks are needed to contain the new size | |
| */ | |
| numBlocks = newSize.QuadPart / This->parentStorage->bigBlockSize; | |
| if ((newSize.QuadPart % This->parentStorage->bigBlockSize) != 0) | |
| numBlocks++; | |
| if (numBlocks) | |
| { | |
| /* | |
| * Go to the new end of chain | |
| */ | |
| blockIndex = BlockChainStream_GetSectorOfOffset(This, numBlocks-1); | |
| /* Mark the new end of chain */ | |
| StorageImpl_SetNextBlockInChain( | |
| This->parentStorage, | |
| blockIndex, | |
| BLOCK_END_OF_CHAIN); | |
| This->tailIndex = blockIndex; | |
| } | |
| else | |
| { | |
| if (This->headOfStreamPlaceHolder != 0) | |
| { | |
| *This->headOfStreamPlaceHolder = BLOCK_END_OF_CHAIN; | |
| } | |
| else | |
| { | |
| DirEntry chainEntry; | |
| assert(This->ownerDirEntry != DIRENTRY_NULL); | |
| StorageImpl_ReadDirEntry( | |
| This->parentStorage, | |
| This->ownerDirEntry, | |
| &chainEntry); | |
| chainEntry.startingBlock = BLOCK_END_OF_CHAIN; | |
| StorageImpl_WriteDirEntry( | |
| This->parentStorage, | |
| This->ownerDirEntry, | |
| &chainEntry); | |
| } | |
| This->tailIndex = BLOCK_END_OF_CHAIN; | |
| } | |
| This->numBlocks = numBlocks; | |
| /* | |
| * Mark the extra blocks as free | |
| */ | |
| while (This->indexCacheLen && This->indexCache[This->indexCacheLen-1].lastOffset >= numBlocks) | |
| { | |
| struct BlockChainRun *last_run = &This->indexCache[This->indexCacheLen-1]; | |
| StorageImpl_FreeBigBlock(This->parentStorage, | |
| last_run->firstSector + last_run->lastOffset - last_run->firstOffset); | |
| if (last_run->lastOffset == last_run->firstOffset) | |
| This->indexCacheLen--; | |
| else | |
| last_run->lastOffset--; | |
| } | |
| /* | |
| * Reset the last accessed block cache. | |
| */ | |
| for (i=0; i<2; i++) | |
| { | |
| if (This->cachedBlocks[i].index >= numBlocks) | |
| { | |
| This->cachedBlocks[i].index = 0xffffffff; | |
| This->cachedBlocks[i].dirty = FALSE; | |
| } | |
| } | |
| return TRUE; | |
| } | |
| /****************************************************************************** | |
| * BlockChainStream_Enlarge | |
| * | |
| * Grows this chain in the big block depot. | |
| */ | |
| static BOOL BlockChainStream_Enlarge(BlockChainStream* This, | |
| ULARGE_INTEGER newSize) | |
| { | |
| ULONG blockIndex, currentBlock; | |
| ULONG newNumBlocks; | |
| ULONG oldNumBlocks = 0; | |
| blockIndex = BlockChainStream_GetHeadOfChain(This); | |
| /* | |
| * Empty chain. Create the head. | |
| */ | |
| if (blockIndex == BLOCK_END_OF_CHAIN) | |
| { | |
| blockIndex = StorageImpl_GetNextFreeBigBlock(This->parentStorage); | |
| StorageImpl_SetNextBlockInChain(This->parentStorage, | |
| blockIndex, | |
| BLOCK_END_OF_CHAIN); | |
| if (This->headOfStreamPlaceHolder != 0) | |
| { | |
| *(This->headOfStreamPlaceHolder) = blockIndex; | |
| } | |
| else | |
| { | |
| DirEntry chainEntry; | |
| assert(This->ownerDirEntry != DIRENTRY_NULL); | |
| StorageImpl_ReadDirEntry( | |
| This->parentStorage, | |
| This->ownerDirEntry, | |
| &chainEntry); | |
| chainEntry.startingBlock = blockIndex; | |
| StorageImpl_WriteDirEntry( | |
| This->parentStorage, | |
| This->ownerDirEntry, | |
| &chainEntry); | |
| } | |
| This->tailIndex = blockIndex; | |
| This->numBlocks = 1; | |
| } | |
| /* | |
| * Figure out how many blocks are needed to contain this stream | |
| */ | |
| newNumBlocks = newSize.QuadPart / This->parentStorage->bigBlockSize; | |
| if ((newSize.QuadPart % This->parentStorage->bigBlockSize) != 0) | |
| newNumBlocks++; | |
| /* | |
| * Go to the current end of chain | |
| */ | |
| if (This->tailIndex == BLOCK_END_OF_CHAIN) | |
| { | |
| currentBlock = blockIndex; | |
| while (blockIndex != BLOCK_END_OF_CHAIN) | |
| { | |
| This->numBlocks++; | |
| currentBlock = blockIndex; | |
| if(FAILED(StorageImpl_GetNextBlockInChain(This->parentStorage, currentBlock, | |
| &blockIndex))) | |
| return FALSE; | |
| } | |
| This->tailIndex = currentBlock; | |
| } | |
| currentBlock = This->tailIndex; | |
| oldNumBlocks = This->numBlocks; | |
| /* | |
| * Add new blocks to the chain | |
| */ | |
| if (oldNumBlocks < newNumBlocks) | |
| { | |
| while (oldNumBlocks < newNumBlocks) | |
| { | |
| blockIndex = StorageImpl_GetNextFreeBigBlock(This->parentStorage); | |
| StorageImpl_SetNextBlockInChain( | |
| This->parentStorage, | |
| currentBlock, | |
| blockIndex); | |
| StorageImpl_SetNextBlockInChain( | |
| This->parentStorage, | |
| blockIndex, | |
| BLOCK_END_OF_CHAIN); | |
| currentBlock = blockIndex; | |
| oldNumBlocks++; | |
| } | |
| This->tailIndex = blockIndex; | |
| This->numBlocks = newNumBlocks; | |
| } | |
| if (FAILED(BlockChainStream_UpdateIndexCache(This))) | |
| return FALSE; | |
| return TRUE; | |
| } | |
| /****************************************************************************** | |
| * BlockChainStream_GetSize | |
| * | |
| * Returns the size of this chain. | |
| * Will return the block count if this chain doesn't have a directory entry. | |
| */ | |
| static ULARGE_INTEGER BlockChainStream_GetSize(BlockChainStream* This) | |
| { | |
| DirEntry chainEntry; | |
| if(This->headOfStreamPlaceHolder == NULL) | |
| { | |
| /* | |
| * This chain has a directory entry so use the size value from there. | |
| */ | |
| StorageImpl_ReadDirEntry( | |
| This->parentStorage, | |
| This->ownerDirEntry, | |
| &chainEntry); | |
| return chainEntry.size; | |
| } | |
| else | |
| { | |
| /* | |
| * this chain is a chain that does not have a directory entry, figure out the | |
| * size by making the product number of used blocks times the | |
| * size of them | |
| */ | |
| ULARGE_INTEGER result; | |
| result.QuadPart = | |
| (ULONGLONG)BlockChainStream_GetCount(This) * | |
| This->parentStorage->bigBlockSize; | |
| return result; | |
| } | |
| } | |
| /****************************************************************************** | |
| * BlockChainStream_SetSize | |
| * | |
| * Sets the size of this stream. The big block depot will be updated. | |
| * The file will grow if we grow the chain. | |
| * | |
| * TODO: Free the actual blocks in the file when we shrink the chain. | |
| * Currently, the blocks are still in the file. So the file size | |
| * doesn't shrink even if we shrink streams. | |
| */ | |
| BOOL BlockChainStream_SetSize( | |
| BlockChainStream* This, | |
| ULARGE_INTEGER newSize) | |
| { | |
| ULARGE_INTEGER size = BlockChainStream_GetSize(This); | |
| if (newSize.QuadPart == size.QuadPart) | |
| return TRUE; | |
| if (newSize.QuadPart < size.QuadPart) | |
| { | |
| BlockChainStream_Shrink(This, newSize); | |
| } | |
| else | |
| { | |
| BlockChainStream_Enlarge(This, newSize); | |
| } | |
| return TRUE; | |
| } | |
| /****************************************************************************** | |
| * BlockChainStream_ReadAt | |
| * | |
| * Reads a specified number of bytes from this chain at the specified offset. | |
| * bytesRead may be NULL. | |
| * Failure will be returned if the specified number of bytes has not been read. | |
| */ | |
| HRESULT BlockChainStream_ReadAt(BlockChainStream* This, | |
| ULARGE_INTEGER offset, | |
| ULONG size, | |
| void* buffer, | |
| ULONG* bytesRead) | |
| { | |
| ULONG blockNoInSequence = offset.QuadPart / This->parentStorage->bigBlockSize; | |
| ULONG offsetInBlock = offset.QuadPart % This->parentStorage->bigBlockSize; | |
| ULONG bytesToReadInBuffer; | |
| ULONG blockIndex; | |
| BYTE* bufferWalker; | |
| ULARGE_INTEGER stream_size; | |
| HRESULT hr; | |
| BlockChainBlock *cachedBlock; | |
| TRACE("(%p)-> %i %p %i %p\n",This, offset.u.LowPart, buffer, size, bytesRead); | |
| /* | |
| * Find the first block in the stream that contains part of the buffer. | |
| */ | |
| blockIndex = BlockChainStream_GetSectorOfOffset(This, blockNoInSequence); | |
| *bytesRead = 0; | |
| stream_size = BlockChainStream_GetSize(This); | |
| if (stream_size.QuadPart > offset.QuadPart) | |
| size = min(stream_size.QuadPart - offset.QuadPart, size); | |
| else | |
| return S_OK; | |
| /* | |
| * Start reading the buffer. | |
| */ | |
| bufferWalker = buffer; | |
| while (size > 0) | |
| { | |
| ULARGE_INTEGER ulOffset; | |
| DWORD bytesReadAt; | |
| /* | |
| * Calculate how many bytes we can copy from this big block. | |
| */ | |
| bytesToReadInBuffer = | |
| min(This->parentStorage->bigBlockSize - offsetInBlock, size); | |
| hr = BlockChainStream_GetBlockAtOffset(This, blockNoInSequence, &cachedBlock, &blockIndex, size == bytesToReadInBuffer); | |
| if (FAILED(hr)) | |
| return hr; | |
| if (!cachedBlock) | |
| { | |
| /* Not in cache, and we're going to read past the end of the block. */ | |
| ulOffset.QuadPart = StorageImpl_GetBigBlockOffset(This->parentStorage, blockIndex) + | |
| offsetInBlock; | |
| StorageImpl_ReadAt(This->parentStorage, | |
| ulOffset, | |
| bufferWalker, | |
| bytesToReadInBuffer, | |
| &bytesReadAt); | |
| } | |
| else | |
| { | |
| if (!cachedBlock->read) | |
| { | |
| ULONG read; | |
| if (FAILED(StorageImpl_ReadBigBlock(This->parentStorage, cachedBlock->sector, cachedBlock->data, &read)) && !read) | |
| return STG_E_READFAULT; | |
| cachedBlock->read = TRUE; | |
| } | |
| memcpy(bufferWalker, cachedBlock->data+offsetInBlock, bytesToReadInBuffer); | |
| bytesReadAt = bytesToReadInBuffer; | |
| } | |
| blockNoInSequence++; | |
| bufferWalker += bytesReadAt; | |
| size -= bytesReadAt; | |
| *bytesRead += bytesReadAt; | |
| offsetInBlock = 0; /* There is no offset on the next block */ | |
| if (bytesToReadInBuffer != bytesReadAt) | |
| break; | |
| } | |
| return S_OK; | |
| } | |
| /****************************************************************************** | |
| * BlockChainStream_WriteAt | |
| * | |
| * Writes the specified number of bytes to this chain at the specified offset. | |
| * Will fail if not all specified number of bytes have been written. | |
| */ | |
| HRESULT BlockChainStream_WriteAt(BlockChainStream* This, | |
| ULARGE_INTEGER offset, | |
| ULONG size, | |
| const void* buffer, | |
| ULONG* bytesWritten) | |
| { | |
| ULONG blockNoInSequence = offset.QuadPart / This->parentStorage->bigBlockSize; | |
| ULONG offsetInBlock = offset.QuadPart % This->parentStorage->bigBlockSize; | |
| ULONG bytesToWrite; | |
| ULONG blockIndex; | |
| const BYTE* bufferWalker; | |
| HRESULT hr; | |
| BlockChainBlock *cachedBlock; | |
| *bytesWritten = 0; | |
| bufferWalker = buffer; | |
| while (size > 0) | |
| { | |
| ULARGE_INTEGER ulOffset; | |
| DWORD bytesWrittenAt; | |
| /* | |
| * Calculate how many bytes we can copy to this big block. | |
| */ | |
| bytesToWrite = | |
| min(This->parentStorage->bigBlockSize - offsetInBlock, size); | |
| hr = BlockChainStream_GetBlockAtOffset(This, blockNoInSequence, &cachedBlock, &blockIndex, size == bytesToWrite); | |
| /* BlockChainStream_SetSize should have already been called to ensure we have | |
| * enough blocks in the chain to write into */ | |
| if (FAILED(hr)) | |
| { | |
| ERR("not enough blocks in chain to write data\n"); | |
| return hr; | |
| } | |
| if (!cachedBlock) | |
| { | |
| /* Not in cache, and we're going to write past the end of the block. */ | |
| ulOffset.QuadPart = StorageImpl_GetBigBlockOffset(This->parentStorage, blockIndex) + | |
| offsetInBlock; | |
| StorageImpl_WriteAt(This->parentStorage, | |
| ulOffset, | |
| bufferWalker, | |
| bytesToWrite, | |
| &bytesWrittenAt); | |
| } | |
| else | |
| { | |
| if (!cachedBlock->read && bytesToWrite != This->parentStorage->bigBlockSize) | |
| { | |
| ULONG read; | |
| if (FAILED(StorageImpl_ReadBigBlock(This->parentStorage, cachedBlock->sector, cachedBlock->data, &read)) && !read) | |
| return STG_E_READFAULT; | |
| } | |
| memcpy(cachedBlock->data+offsetInBlock, bufferWalker, bytesToWrite); | |
| bytesWrittenAt = bytesToWrite; | |
| cachedBlock->read = TRUE; | |
| cachedBlock->dirty = TRUE; | |
| } | |
| blockNoInSequence++; | |
| bufferWalker += bytesWrittenAt; | |
| size -= bytesWrittenAt; | |
| *bytesWritten += bytesWrittenAt; | |
| offsetInBlock = 0; /* There is no offset on the next block */ | |
| if (bytesWrittenAt != bytesToWrite) | |
| break; | |
| } | |
| return (size == 0) ? S_OK : STG_E_WRITEFAULT; | |
| } | |
| /************************************************************************ | |
| * SmallBlockChainStream implementation | |
| ***********************************************************************/ | |
| SmallBlockChainStream* SmallBlockChainStream_Construct( | |
| StorageImpl* parentStorage, | |
| ULONG* headOfStreamPlaceHolder, | |
| DirRef dirEntry) | |
| { | |
| SmallBlockChainStream* newStream; | |
| newStream = HeapAlloc(GetProcessHeap(), 0, sizeof(SmallBlockChainStream)); | |
| newStream->parentStorage = parentStorage; | |
| newStream->headOfStreamPlaceHolder = headOfStreamPlaceHolder; | |
| newStream->ownerDirEntry = dirEntry; | |
| return newStream; | |
| } | |
| void SmallBlockChainStream_Destroy( | |
| SmallBlockChainStream* This) | |
| { | |
| HeapFree(GetProcessHeap(), 0, This); | |
| } | |
| /****************************************************************************** | |
| * SmallBlockChainStream_GetHeadOfChain | |
| * | |
| * Returns the head of this chain of small blocks. | |
| */ | |
| static ULONG SmallBlockChainStream_GetHeadOfChain( | |
| SmallBlockChainStream* This) | |
| { | |
| DirEntry chainEntry; | |
| HRESULT hr; | |
| if (This->headOfStreamPlaceHolder != NULL) | |
| return *(This->headOfStreamPlaceHolder); | |
| if (This->ownerDirEntry) | |
| { | |
| hr = StorageImpl_ReadDirEntry( | |
| This->parentStorage, | |
| This->ownerDirEntry, | |
| &chainEntry); | |
| if (SUCCEEDED(hr) && chainEntry.startingBlock < BLOCK_FIRST_SPECIAL) | |
| return chainEntry.startingBlock; | |
| } | |
| return BLOCK_END_OF_CHAIN; | |
| } | |
| /****************************************************************************** | |
| * SmallBlockChainStream_GetNextBlockInChain | |
| * | |
| * Returns the index of the next small block in this chain. | |
| * | |
| * Return Values: | |
| * - BLOCK_END_OF_CHAIN: end of this chain | |
| * - BLOCK_UNUSED: small block 'blockIndex' is free | |
| */ | |
| static HRESULT SmallBlockChainStream_GetNextBlockInChain( | |
| SmallBlockChainStream* This, | |
| ULONG blockIndex, | |
| ULONG* nextBlockInChain) | |
| { | |
| ULARGE_INTEGER offsetOfBlockInDepot; | |
| DWORD buffer; | |
| ULONG bytesRead; | |
| HRESULT res; | |
| *nextBlockInChain = BLOCK_END_OF_CHAIN; | |
| offsetOfBlockInDepot.QuadPart = (ULONGLONG)blockIndex * sizeof(ULONG); | |
| /* | |
| * Read those bytes in the buffer from the small block file. | |
| */ | |
| res = BlockChainStream_ReadAt( | |
| This->parentStorage->smallBlockDepotChain, | |
| offsetOfBlockInDepot, | |
| sizeof(DWORD), | |
| &buffer, | |
| &bytesRead); | |
| if (SUCCEEDED(res) && bytesRead != sizeof(DWORD)) | |
| res = STG_E_READFAULT; | |
| if (SUCCEEDED(res)) | |
| { | |
| StorageUtl_ReadDWord((BYTE *)&buffer, 0, nextBlockInChain); | |
| return S_OK; | |
| } | |
| return res; | |
| } | |
| /****************************************************************************** | |
| * SmallBlockChainStream_SetNextBlockInChain | |
| * | |
| * Writes the index of the next block of the specified block in the small | |
| * block depot. | |
| * To set the end of chain use BLOCK_END_OF_CHAIN as nextBlock. | |
| * To flag a block as free use BLOCK_UNUSED as nextBlock. | |
| */ | |
| static void SmallBlockChainStream_SetNextBlockInChain( | |
| SmallBlockChainStream* This, | |
| ULONG blockIndex, | |
| ULONG nextBlock) | |
| { | |
| ULARGE_INTEGER offsetOfBlockInDepot; | |
| DWORD buffer; | |
| ULONG bytesWritten; | |
| offsetOfBlockInDepot.QuadPart = (ULONGLONG)blockIndex * sizeof(ULONG); | |
| StorageUtl_WriteDWord((BYTE *)&buffer, 0, nextBlock); | |
| /* | |
| * Read those bytes in the buffer from the small block file. | |
| */ | |
| BlockChainStream_WriteAt( | |
| This->parentStorage->smallBlockDepotChain, | |
| offsetOfBlockInDepot, | |
| sizeof(DWORD), | |
| &buffer, | |
| &bytesWritten); | |
| } | |
| /****************************************************************************** | |
| * SmallBlockChainStream_FreeBlock | |
| * | |
| * Flag small block 'blockIndex' as free in the small block depot. | |
| */ | |
| static void SmallBlockChainStream_FreeBlock( | |
| SmallBlockChainStream* This, | |
| ULONG blockIndex) | |
| { | |
| SmallBlockChainStream_SetNextBlockInChain(This, blockIndex, BLOCK_UNUSED); | |
| } | |
| /****************************************************************************** | |
| * SmallBlockChainStream_GetNextFreeBlock | |
| * | |
| * Returns the index of a free small block. The small block depot will be | |
| * enlarged if necessary. The small block chain will also be enlarged if | |
| * necessary. | |
| */ | |
| static ULONG SmallBlockChainStream_GetNextFreeBlock( | |
| SmallBlockChainStream* This) | |
| { | |
| ULARGE_INTEGER offsetOfBlockInDepot; | |
| DWORD buffer; | |
| ULONG bytesRead; | |
| ULONG blockIndex = This->parentStorage->firstFreeSmallBlock; | |
| ULONG nextBlockIndex = BLOCK_END_OF_CHAIN; | |
| HRESULT res = S_OK; | |
| ULONG smallBlocksPerBigBlock; | |
| DirEntry rootEntry; | |
| ULONG blocksRequired; | |
| ULARGE_INTEGER old_size, size_required; | |
| offsetOfBlockInDepot.u.HighPart = 0; | |
| /* | |
| * Scan the small block depot for a free block | |
| */ | |
| while (nextBlockIndex != BLOCK_UNUSED) | |
| { | |
| offsetOfBlockInDepot.QuadPart = (ULONGLONG)blockIndex * sizeof(ULONG); | |
| res = BlockChainStream_ReadAt( | |
| This->parentStorage->smallBlockDepotChain, | |
| offsetOfBlockInDepot, | |
| sizeof(DWORD), | |
| &buffer, | |
| &bytesRead); | |
| /* | |
| * If we run out of space for the small block depot, enlarge it | |
| */ | |
| if (SUCCEEDED(res) && bytesRead == sizeof(DWORD)) | |
| { | |
| StorageUtl_ReadDWord((BYTE *)&buffer, 0, &nextBlockIndex); | |
| if (nextBlockIndex != BLOCK_UNUSED) | |
| blockIndex++; | |
| } | |
| else | |
| { | |
| ULONG count = | |
| BlockChainStream_GetCount(This->parentStorage->smallBlockDepotChain); | |
| BYTE smallBlockDepot[MAX_BIG_BLOCK_SIZE]; | |
| ULARGE_INTEGER newSize, offset; | |
| ULONG bytesWritten; | |
| newSize.QuadPart = (ULONGLONG)(count + 1) * This->parentStorage->bigBlockSize; | |
| BlockChainStream_Enlarge(This->parentStorage->smallBlockDepotChain, newSize); | |
| /* | |
| * Initialize all the small blocks to free | |
| */ | |
| memset(smallBlockDepot, BLOCK_UNUSED, This->parentStorage->bigBlockSize); | |
| offset.QuadPart = (ULONGLONG)count * This->parentStorage->bigBlockSize; | |
| BlockChainStream_WriteAt(This->parentStorage->smallBlockDepotChain, | |
| offset, This->parentStorage->bigBlockSize, smallBlockDepot, &bytesWritten); | |
| StorageImpl_SaveFileHeader(This->parentStorage); | |
| } | |
| } | |
| This->parentStorage->firstFreeSmallBlock = blockIndex+1; | |
| smallBlocksPerBigBlock = | |
| This->parentStorage->bigBlockSize / This->parentStorage->smallBlockSize; | |
| /* | |
| * Verify if we have to allocate big blocks to contain small blocks | |
| */ | |
| blocksRequired = (blockIndex / smallBlocksPerBigBlock) + 1; | |
| size_required.QuadPart = (ULONGLONG)blocksRequired * This->parentStorage->bigBlockSize; | |
| old_size = BlockChainStream_GetSize(This->parentStorage->smallBlockRootChain); | |
| if (size_required.QuadPart > old_size.QuadPart) | |
| { | |
| BlockChainStream_SetSize( | |
| This->parentStorage->smallBlockRootChain, | |
| size_required); | |
| StorageImpl_ReadDirEntry( | |
| This->parentStorage, | |
| This->parentStorage->base.storageDirEntry, | |
| &rootEntry); | |
| rootEntry.size = size_required; | |
| StorageImpl_WriteDirEntry( | |
| This->parentStorage, | |
| This->parentStorage->base.storageDirEntry, | |
| &rootEntry); | |
| } | |
| return blockIndex; | |
| } | |
| /****************************************************************************** | |
| * SmallBlockChainStream_ReadAt | |
| * | |
| * Reads a specified number of bytes from this chain at the specified offset. | |
| * bytesRead may be NULL. | |
| * Failure will be returned if the specified number of bytes has not been read. | |
| */ | |
| HRESULT SmallBlockChainStream_ReadAt( | |
| SmallBlockChainStream* This, | |
| ULARGE_INTEGER offset, | |
| ULONG size, | |
| void* buffer, | |
| ULONG* bytesRead) | |
| { | |
| HRESULT rc = S_OK; | |
| ULARGE_INTEGER offsetInBigBlockFile; | |
| ULONG blockNoInSequence = | |
| offset.u.LowPart / This->parentStorage->smallBlockSize; | |
| ULONG offsetInBlock = offset.u.LowPart % This->parentStorage->smallBlockSize; | |
| ULONG bytesToReadInBuffer; | |
| ULONG blockIndex; | |
| ULONG bytesReadFromBigBlockFile; | |
| BYTE* bufferWalker; | |
| ULARGE_INTEGER stream_size; | |
| /* | |
| * This should never happen on a small block file. | |
| */ | |
| assert(offset.u.HighPart==0); | |
| *bytesRead = 0; | |
| stream_size = SmallBlockChainStream_GetSize(This); | |
| if (stream_size.QuadPart > offset.QuadPart) | |
| size = min(stream_size.QuadPart - offset.QuadPart, size); | |
| else | |
| return S_OK; | |
| /* | |
| * Find the first block in the stream that contains part of the buffer. | |
| */ | |
| blockIndex = SmallBlockChainStream_GetHeadOfChain(This); | |
| while ( (blockNoInSequence > 0) && (blockIndex != BLOCK_END_OF_CHAIN)) | |
| { | |
| rc = SmallBlockChainStream_GetNextBlockInChain(This, blockIndex, &blockIndex); | |
| if(FAILED(rc)) | |
| return rc; | |
| blockNoInSequence--; | |
| } | |
| /* | |
| * Start reading the buffer. | |
| */ | |
| bufferWalker = buffer; | |
| while ( (size > 0) && (blockIndex != BLOCK_END_OF_CHAIN) ) | |
| { | |
| /* | |
| * Calculate how many bytes we can copy from this small block. | |
| */ | |
| bytesToReadInBuffer = | |
| min(This->parentStorage->smallBlockSize - offsetInBlock, size); | |
| /* | |
| * Calculate the offset of the small block in the small block file. | |
| */ | |
| offsetInBigBlockFile.QuadPart = | |
| (ULONGLONG)blockIndex * This->parentStorage->smallBlockSize; | |
| offsetInBigBlockFile.QuadPart += offsetInBlock; | |
| /* | |
| * Read those bytes in the buffer from the small block file. | |
| * The small block has already been identified so it shouldn't fail | |
| * unless the file is corrupt. | |
| */ | |
| rc = BlockChainStream_ReadAt(This->parentStorage->smallBlockRootChain, | |
| offsetInBigBlockFile, | |
| bytesToReadInBuffer, | |
| bufferWalker, | |
| &bytesReadFromBigBlockFile); | |
| if (FAILED(rc)) | |
| return rc; | |
| if (!bytesReadFromBigBlockFile) | |
| return STG_E_DOCFILECORRUPT; | |
| /* | |
| * Step to the next big block. | |
| */ | |
| rc = SmallBlockChainStream_GetNextBlockInChain(This, blockIndex, &blockIndex); | |
| if(FAILED(rc)) | |
| return STG_E_DOCFILECORRUPT; | |
| bufferWalker += bytesReadFromBigBlockFile; | |
| size -= bytesReadFromBigBlockFile; | |
| *bytesRead += bytesReadFromBigBlockFile; | |
| offsetInBlock = (offsetInBlock + bytesReadFromBigBlockFile) % This->parentStorage->smallBlockSize; | |
| } | |
| return S_OK; | |
| } | |
| /****************************************************************************** | |
| * SmallBlockChainStream_WriteAt | |
| * | |
| * Writes the specified number of bytes to this chain at the specified offset. | |
| * Will fail if not all specified number of bytes have been written. | |
| */ | |
| HRESULT SmallBlockChainStream_WriteAt( | |
| SmallBlockChainStream* This, | |
| ULARGE_INTEGER offset, | |
| ULONG size, | |
| const void* buffer, | |
| ULONG* bytesWritten) | |
| { | |
| ULARGE_INTEGER offsetInBigBlockFile; | |
| ULONG blockNoInSequence = | |
| offset.u.LowPart / This->parentStorage->smallBlockSize; | |
| ULONG offsetInBlock = offset.u.LowPart % This->parentStorage->smallBlockSize; | |
| ULONG bytesToWriteInBuffer; | |
| ULONG blockIndex; | |
| ULONG bytesWrittenToBigBlockFile; | |
| const BYTE* bufferWalker; | |
| HRESULT res; | |
| /* | |
| * This should never happen on a small block file. | |
| */ | |
| assert(offset.u.HighPart==0); | |
| /* | |
| * Find the first block in the stream that contains part of the buffer. | |
| */ | |
| blockIndex = SmallBlockChainStream_GetHeadOfChain(This); | |
| while ( (blockNoInSequence > 0) && (blockIndex != BLOCK_END_OF_CHAIN)) | |
| { | |
| if(FAILED(SmallBlockChainStream_GetNextBlockInChain(This, blockIndex, &blockIndex))) | |
| return STG_E_DOCFILECORRUPT; | |
| blockNoInSequence--; | |
| } | |
| /* | |
| * Start writing the buffer. | |
| */ | |
| *bytesWritten = 0; | |
| bufferWalker = buffer; | |
| while ( (size > 0) && (blockIndex != BLOCK_END_OF_CHAIN) ) | |
| { | |
| /* | |
| * Calculate how many bytes we can copy to this small block. | |
| */ | |
| bytesToWriteInBuffer = | |
| min(This->parentStorage->smallBlockSize - offsetInBlock, size); | |
| /* | |
| * Calculate the offset of the small block in the small block file. | |
| */ | |
| offsetInBigBlockFile.QuadPart = | |
| (ULONGLONG)blockIndex * This->parentStorage->smallBlockSize; | |
| offsetInBigBlockFile.QuadPart += offsetInBlock; | |
| /* | |
| * Write those bytes in the buffer to the small block file. | |
| */ | |
| res = BlockChainStream_WriteAt( | |
| This->parentStorage->smallBlockRootChain, | |
| offsetInBigBlockFile, | |
| bytesToWriteInBuffer, | |
| bufferWalker, | |
| &bytesWrittenToBigBlockFile); | |
| if (FAILED(res)) | |
| return res; | |
| /* | |
| * Step to the next big block. | |
| */ | |
| res = SmallBlockChainStream_GetNextBlockInChain(This, blockIndex, &blockIndex); | |
| if (FAILED(res)) | |
| return res; | |
| bufferWalker += bytesWrittenToBigBlockFile; | |
| size -= bytesWrittenToBigBlockFile; | |
| *bytesWritten += bytesWrittenToBigBlockFile; | |
| offsetInBlock = (offsetInBlock + bytesWrittenToBigBlockFile) % This->parentStorage->smallBlockSize; | |
| } | |
| return (size == 0) ? S_OK : STG_E_WRITEFAULT; | |
| } | |
| /****************************************************************************** | |
| * SmallBlockChainStream_Shrink | |
| * | |
| * Shrinks this chain in the small block depot. | |
| */ | |
| static BOOL SmallBlockChainStream_Shrink( | |
| SmallBlockChainStream* This, | |
| ULARGE_INTEGER newSize) | |
| { | |
| ULONG blockIndex, extraBlock; | |
| ULONG numBlocks; | |
| ULONG count = 0; | |
| numBlocks = newSize.u.LowPart / This->parentStorage->smallBlockSize; | |
| if ((newSize.u.LowPart % This->parentStorage->smallBlockSize) != 0) | |
| numBlocks++; | |
| blockIndex = SmallBlockChainStream_GetHeadOfChain(This); | |
| /* | |
| * Go to the new end of chain | |
| */ | |
| while (count < numBlocks) | |
| { | |
| if(FAILED(SmallBlockChainStream_GetNextBlockInChain(This, blockIndex, | |
| &blockIndex))) | |
| return FALSE; | |
| count++; | |
| } | |
| /* | |
| * If the count is 0, we have a special case, the head of the chain was | |
| * just freed. | |
| */ | |
| if (count == 0) | |
| { | |
| DirEntry chainEntry; | |
| StorageImpl_ReadDirEntry(This->parentStorage, | |
| This->ownerDirEntry, | |
| &chainEntry); | |
| chainEntry.startingBlock = BLOCK_END_OF_CHAIN; | |
| StorageImpl_WriteDirEntry(This->parentStorage, | |
| This->ownerDirEntry, | |
| &chainEntry); | |
| /* | |
| * We start freeing the chain at the head block. | |
| */ | |
| extraBlock = blockIndex; | |
| } | |
| else | |
| { | |
| /* Get the next block before marking the new end */ | |
| if(FAILED(SmallBlockChainStream_GetNextBlockInChain(This, blockIndex, | |
| &extraBlock))) | |
| return FALSE; | |
| /* Mark the new end of chain */ | |
| SmallBlockChainStream_SetNextBlockInChain( | |
| This, | |
| blockIndex, | |
| BLOCK_END_OF_CHAIN); | |
| } | |
| /* | |
| * Mark the extra blocks as free | |
| */ | |
| while (extraBlock != BLOCK_END_OF_CHAIN) | |
| { | |
| if(FAILED(SmallBlockChainStream_GetNextBlockInChain(This, extraBlock, | |
| &blockIndex))) | |
| return FALSE; | |
| SmallBlockChainStream_FreeBlock(This, extraBlock); | |
| This->parentStorage->firstFreeSmallBlock = min(This->parentStorage->firstFreeSmallBlock, extraBlock); | |
| extraBlock = blockIndex; | |
| } | |
| return TRUE; | |
| } | |
| /****************************************************************************** | |
| * SmallBlockChainStream_Enlarge | |
| * | |
| * Grows this chain in the small block depot. | |
| */ | |
| static BOOL SmallBlockChainStream_Enlarge( | |
| SmallBlockChainStream* This, | |
| ULARGE_INTEGER newSize) | |
| { | |
| ULONG blockIndex, currentBlock; | |
| ULONG newNumBlocks; | |
| ULONG oldNumBlocks = 0; | |
| blockIndex = SmallBlockChainStream_GetHeadOfChain(This); | |
| /* | |
| * Empty chain. Create the head. | |
| */ | |
| if (blockIndex == BLOCK_END_OF_CHAIN) | |
| { | |
| blockIndex = SmallBlockChainStream_GetNextFreeBlock(This); | |
| SmallBlockChainStream_SetNextBlockInChain( | |
| This, | |
| blockIndex, | |
| BLOCK_END_OF_CHAIN); | |
| if (This->headOfStreamPlaceHolder != NULL) | |
| { | |
| *(This->headOfStreamPlaceHolder) = blockIndex; | |
| } | |
| else | |
| { | |
| DirEntry chainEntry; | |
| StorageImpl_ReadDirEntry(This->parentStorage, This->ownerDirEntry, | |
| &chainEntry); | |
| chainEntry.startingBlock = blockIndex; | |
| StorageImpl_WriteDirEntry(This->parentStorage, This->ownerDirEntry, | |
| &chainEntry); | |
| } | |
| } | |
| currentBlock = blockIndex; | |
| /* | |
| * Figure out how many blocks are needed to contain this stream | |
| */ | |
| newNumBlocks = newSize.u.LowPart / This->parentStorage->smallBlockSize; | |
| if ((newSize.u.LowPart % This->parentStorage->smallBlockSize) != 0) | |
| newNumBlocks++; | |
| /* | |
| * Go to the current end of chain | |
| */ | |
| while (blockIndex != BLOCK_END_OF_CHAIN) | |
| { | |
| oldNumBlocks++; | |
| currentBlock = blockIndex; | |
| if(FAILED(SmallBlockChainStream_GetNextBlockInChain(This, currentBlock, &blockIndex))) | |
| return FALSE; | |
| } | |
| /* | |
| * Add new blocks to the chain | |
| */ | |
| while (oldNumBlocks < newNumBlocks) | |
| { | |
| blockIndex = SmallBlockChainStream_GetNextFreeBlock(This); | |
| SmallBlockChainStream_SetNextBlockInChain(This, currentBlock, blockIndex); | |
| SmallBlockChainStream_SetNextBlockInChain( | |
| This, | |
| blockIndex, | |
| BLOCK_END_OF_CHAIN); | |
| currentBlock = blockIndex; | |
| oldNumBlocks++; | |
| } | |
| return TRUE; | |
| } | |
| /****************************************************************************** | |
| * SmallBlockChainStream_SetSize | |
| * | |
| * Sets the size of this stream. | |
| * The file will grow if we grow the chain. | |
| * | |
| * TODO: Free the actual blocks in the file when we shrink the chain. | |
| * Currently, the blocks are still in the file. So the file size | |
| * doesn't shrink even if we shrink streams. | |
| */ | |
| BOOL SmallBlockChainStream_SetSize( | |
| SmallBlockChainStream* This, | |
| ULARGE_INTEGER newSize) | |
| { | |
| ULARGE_INTEGER size = SmallBlockChainStream_GetSize(This); | |
| if (newSize.u.LowPart == size.u.LowPart) | |
| return TRUE; | |
| if (newSize.u.LowPart < size.u.LowPart) | |
| { | |
| SmallBlockChainStream_Shrink(This, newSize); | |
| } | |
| else | |
| { | |
| SmallBlockChainStream_Enlarge(This, newSize); | |
| } | |
| return TRUE; | |
| } | |
| /****************************************************************************** | |
| * SmallBlockChainStream_GetCount | |
| * | |
| * Returns the number of small blocks that comprises this chain. | |
| * This is not the size of the stream as the last block may not be full! | |
| * | |
| */ | |
| static ULONG SmallBlockChainStream_GetCount(SmallBlockChainStream* This) | |
| { | |
| ULONG blockIndex; | |
| ULONG count = 0; | |
| blockIndex = SmallBlockChainStream_GetHeadOfChain(This); | |
| while(blockIndex != BLOCK_END_OF_CHAIN) | |
| { | |
| count++; | |
| if(FAILED(SmallBlockChainStream_GetNextBlockInChain(This, | |
| blockIndex, &blockIndex))) | |
| return 0; | |
| } | |
| return count; | |
| } | |
| /****************************************************************************** | |
| * SmallBlockChainStream_GetSize | |
| * | |
| * Returns the size of this chain. | |
| */ | |
| static ULARGE_INTEGER SmallBlockChainStream_GetSize(SmallBlockChainStream* This) | |
| { | |
| DirEntry chainEntry; | |
| if(This->headOfStreamPlaceHolder != NULL) | |
| { | |
| ULARGE_INTEGER result; | |
| result.u.HighPart = 0; | |
| result.u.LowPart = SmallBlockChainStream_GetCount(This) * | |
| This->parentStorage->smallBlockSize; | |
| return result; | |
| } | |
| StorageImpl_ReadDirEntry( | |
| This->parentStorage, | |
| This->ownerDirEntry, | |
| &chainEntry); | |
| return chainEntry.size; | |
| } | |
| /************************************************************************ | |
| * Miscellaneous storage functions | |
| ***********************************************************************/ | |
| static HRESULT create_storagefile( | |
| LPCOLESTR pwcsName, | |
| DWORD grfMode, | |
| DWORD grfAttrs, | |
| STGOPTIONS* pStgOptions, | |
| REFIID riid, | |
| void** ppstgOpen) | |
| { | |
| StorageBaseImpl* newStorage = 0; | |
| HANDLE hFile = INVALID_HANDLE_VALUE; | |
| HRESULT hr = STG_E_INVALIDFLAG; | |
| DWORD shareMode; | |
| DWORD accessMode; | |
| DWORD creationMode; | |
| DWORD fileAttributes; | |
| WCHAR tempFileName[MAX_PATH]; | |
| if (ppstgOpen == 0) | |
| return STG_E_INVALIDPOINTER; | |
| if (pStgOptions->ulSectorSize != MIN_BIG_BLOCK_SIZE && pStgOptions->ulSectorSize != MAX_BIG_BLOCK_SIZE) | |
| return STG_E_INVALIDPARAMETER; | |
| /* if no share mode given then DENY_NONE is the default */ | |
| if (STGM_SHARE_MODE(grfMode) == 0) | |
| grfMode |= STGM_SHARE_DENY_NONE; | |
| if ( FAILED( validateSTGM(grfMode) )) | |
| goto end; | |
| /* StgCreateDocFile seems to refuse readonly access, despite MSDN */ | |
| switch(STGM_ACCESS_MODE(grfMode)) | |
| { | |
| case STGM_WRITE: | |
| case STGM_READWRITE: | |
| break; | |
| default: | |
| goto end; | |
| } | |
| /* in direct mode, can only use SHARE_EXCLUSIVE */ | |
| if (!(grfMode & STGM_TRANSACTED) && (STGM_SHARE_MODE(grfMode) != STGM_SHARE_EXCLUSIVE)) | |
| goto end; | |
| /* but in transacted mode, any share mode is valid */ | |
| /* | |
| * Generate a unique name. | |
| */ | |
| if (pwcsName == 0) | |
| { | |
| WCHAR tempPath[MAX_PATH]; | |
| static const WCHAR prefix[] = { 'S', 'T', 'O', 0 }; | |
| memset(tempPath, 0, sizeof(tempPath)); | |
| memset(tempFileName, 0, sizeof(tempFileName)); | |
| if ((GetTempPathW(MAX_PATH, tempPath)) == 0 ) | |
| tempPath[0] = '.'; | |
| if (GetTempFileNameW(tempPath, prefix, 0, tempFileName) != 0) | |
| pwcsName = tempFileName; | |
| else | |
| { | |
| hr = STG_E_INSUFFICIENTMEMORY; | |
| goto end; | |
| } | |
| creationMode = TRUNCATE_EXISTING; | |
| } | |
| else | |
| { | |
| creationMode = GetCreationModeFromSTGM(grfMode); | |
| } | |
| /* | |
| * Interpret the STGM value grfMode | |
| */ | |
| shareMode = GetShareModeFromSTGM(grfMode); | |
| accessMode = GetAccessModeFromSTGM(grfMode); | |
| if (grfMode & STGM_DELETEONRELEASE) | |
| fileAttributes = FILE_FLAG_RANDOM_ACCESS | FILE_FLAG_DELETE_ON_CLOSE; | |
| else | |
| fileAttributes = FILE_ATTRIBUTE_NORMAL | FILE_FLAG_RANDOM_ACCESS; | |
| *ppstgOpen = 0; | |
| hFile = CreateFileW(pwcsName, | |
| accessMode, | |
| shareMode, | |
| NULL, | |
| creationMode, | |
| fileAttributes, | |
| 0); | |
| if (hFile == INVALID_HANDLE_VALUE) | |
| { | |
| if(GetLastError() == ERROR_FILE_EXISTS) | |
| hr = STG_E_FILEALREADYEXISTS; | |
| else | |
| hr = E_FAIL; | |
| goto end; | |
| } | |
| /* | |
| * Allocate and initialize the new IStorage object. | |
| */ | |
| hr = Storage_Construct( | |
| hFile, | |
| pwcsName, | |
| NULL, | |
| grfMode, | |
| TRUE, | |
| TRUE, | |
| pStgOptions->ulSectorSize, | |
| &newStorage); | |
| if (FAILED(hr)) | |
| { | |
| goto end; | |
| } | |
| hr = IStorage_QueryInterface(&newStorage->IStorage_iface, riid, ppstgOpen); | |
| IStorage_Release(&newStorage->IStorage_iface); | |
| end: | |
| TRACE("<-- %p r = %08x\n", *ppstgOpen, hr); | |
| return hr; | |
| } | |
| /****************************************************************************** | |
| * StgCreateDocfile [OLE32.@] | |
| * Creates a new compound file storage object | |
| * | |
| * PARAMS | |
| * pwcsName [ I] Unicode string with filename (can be relative or NULL) | |
| * grfMode [ I] Access mode for opening the new storage object (see STGM_ constants) | |
| * reserved [ ?] unused?, usually 0 | |
| * ppstgOpen [IO] A pointer to IStorage pointer to the new onject | |
| * | |
| * RETURNS | |
| * S_OK if the file was successfully created | |
| * some STG_E_ value if error | |
| * NOTES | |
| * if pwcsName is NULL, create file with new unique name | |
| * the function can returns | |
| * STG_S_CONVERTED if the specified file was successfully converted to storage format | |
| * (unrealized now) | |
| */ | |
| HRESULT WINAPI StgCreateDocfile( | |
| LPCOLESTR pwcsName, | |
| DWORD grfMode, | |
| DWORD reserved, | |
| IStorage **ppstgOpen) | |
| { | |
| STGOPTIONS stgoptions = {1, 0, 512}; | |
| TRACE("(%s, %x, %d, %p)\n", | |
| debugstr_w(pwcsName), grfMode, | |
| reserved, ppstgOpen); | |
| if (ppstgOpen == 0) | |
| return STG_E_INVALIDPOINTER; | |
| if (reserved != 0) | |
| return STG_E_INVALIDPARAMETER; | |
| return create_storagefile(pwcsName, grfMode, 0, &stgoptions, &IID_IStorage, (void**)ppstgOpen); | |
| } | |
| /****************************************************************************** | |
| * StgCreateStorageEx [OLE32.@] | |
| */ | |
| HRESULT WINAPI StgCreateStorageEx(const WCHAR* pwcsName, DWORD grfMode, DWORD stgfmt, DWORD grfAttrs, STGOPTIONS* pStgOptions, void* reserved, REFIID riid, void** ppObjectOpen) | |
| { | |
| TRACE("(%s, %x, %x, %x, %p, %p, %p, %p)\n", debugstr_w(pwcsName), | |
| grfMode, stgfmt, grfAttrs, pStgOptions, reserved, riid, ppObjectOpen); | |
| if (stgfmt != STGFMT_FILE && grfAttrs != 0) | |
| { | |
| ERR("grfAttrs must be 0 if stgfmt != STGFMT_FILE\n"); | |
| return STG_E_INVALIDPARAMETER; | |
| } | |
| if (stgfmt == STGFMT_FILE && grfAttrs != 0 && grfAttrs != FILE_FLAG_NO_BUFFERING) | |
| { | |
| ERR("grfAttrs must be 0 or FILE_FLAG_NO_BUFFERING if stgfmt == STGFMT_FILE\n"); | |
| return STG_E_INVALIDPARAMETER; | |
| } | |
| if (stgfmt == STGFMT_FILE) | |
| { | |
| ERR("Cannot use STGFMT_FILE - this is NTFS only\n"); | |
| return STG_E_INVALIDPARAMETER; | |
| } | |
| if (stgfmt == STGFMT_STORAGE || stgfmt == STGFMT_DOCFILE) | |
| { | |
| STGOPTIONS defaultOptions = {1, 0, 512}; | |
| if (!pStgOptions) pStgOptions = &defaultOptions; | |
| return create_storagefile(pwcsName, grfMode, grfAttrs, pStgOptions, riid, ppObjectOpen); | |
| } | |
| ERR("Invalid stgfmt argument\n"); | |
| return STG_E_INVALIDPARAMETER; | |
| } | |
| /****************************************************************************** | |
| * StgCreatePropSetStg [OLE32.@] | |
| */ | |
| HRESULT WINAPI StgCreatePropSetStg(IStorage *pstg, DWORD reserved, | |
| IPropertySetStorage **propset) | |
| { | |
| TRACE("(%p, 0x%x, %p)\n", pstg, reserved, propset); | |
| if (reserved) | |
| return STG_E_INVALIDPARAMETER; | |
| return IStorage_QueryInterface(pstg, &IID_IPropertySetStorage, (void**)propset); | |
| } | |
| /****************************************************************************** | |
| * StgOpenStorageEx [OLE32.@] | |
| */ | |
| HRESULT WINAPI StgOpenStorageEx(const WCHAR* pwcsName, DWORD grfMode, DWORD stgfmt, DWORD grfAttrs, STGOPTIONS* pStgOptions, void* reserved, REFIID riid, void** ppObjectOpen) | |
| { | |
| TRACE("(%s, %x, %x, %x, %p, %p, %p, %p)\n", debugstr_w(pwcsName), | |
| grfMode, stgfmt, grfAttrs, pStgOptions, reserved, riid, ppObjectOpen); | |
| if (stgfmt != STGFMT_DOCFILE && grfAttrs != 0) | |
| { | |
| ERR("grfAttrs must be 0 if stgfmt != STGFMT_DOCFILE\n"); | |
| return STG_E_INVALIDPARAMETER; | |
| } | |
| switch (stgfmt) | |
| { | |
| case STGFMT_FILE: | |
| ERR("Cannot use STGFMT_FILE - this is NTFS only\n"); | |
| return STG_E_INVALIDPARAMETER; | |
| case STGFMT_STORAGE: | |
| break; | |
| case STGFMT_DOCFILE: | |
| if (grfAttrs && grfAttrs != FILE_FLAG_NO_BUFFERING) | |
| { | |
| ERR("grfAttrs must be 0 or FILE_FLAG_NO_BUFFERING if stgfmt == STGFMT_DOCFILE\n"); | |
| return STG_E_INVALIDPARAMETER; | |
| } | |
| FIXME("Stub: calling StgOpenStorage, but ignoring pStgOptions and grfAttrs\n"); | |
| break; | |
| case STGFMT_ANY: | |
| WARN("STGFMT_ANY assuming storage\n"); | |
| break; | |
| default: | |
| return STG_E_INVALIDPARAMETER; | |
| } | |
| return StgOpenStorage(pwcsName, NULL, grfMode, NULL, 0, (IStorage **)ppObjectOpen); | |
| } | |
| /****************************************************************************** | |
| * StgOpenStorage [OLE32.@] | |
| */ | |
| HRESULT WINAPI StgOpenStorage( | |
| const OLECHAR *pwcsName, | |
| IStorage *pstgPriority, | |
| DWORD grfMode, | |
| SNB snbExclude, | |
| DWORD reserved, | |
| IStorage **ppstgOpen) | |
| { | |
| StorageBaseImpl* newStorage = 0; | |
| HRESULT hr = S_OK; | |
| HANDLE hFile = 0; | |
| DWORD shareMode; | |
| DWORD accessMode; | |
| LPWSTR temp_name = NULL; | |
| TRACE("(%s, %p, %x, %p, %d, %p)\n", | |
| debugstr_w(pwcsName), pstgPriority, grfMode, | |
| snbExclude, reserved, ppstgOpen); | |
| if (pstgPriority) | |
| { | |
| /* FIXME: Copy ILockBytes instead? But currently for STGM_PRIORITY it'll be read-only. */ | |
| hr = StorageBaseImpl_GetFilename((StorageBaseImpl*)pstgPriority, &temp_name); | |
| if (FAILED(hr)) goto end; | |
| pwcsName = temp_name; | |
| TRACE("using filename %s\n", debugstr_w(temp_name)); | |
| } | |
| if (pwcsName == 0) | |
| { | |
| hr = STG_E_INVALIDNAME; | |
| goto end; | |
| } | |
| if (ppstgOpen == 0) | |
| { | |
| hr = STG_E_INVALIDPOINTER; | |
| goto end; | |
| } | |
| if (reserved) | |
| { | |
| hr = STG_E_INVALIDPARAMETER; | |
| goto end; | |
| } | |
| if (grfMode & STGM_PRIORITY) | |
| { | |
| if (grfMode & (STGM_TRANSACTED|STGM_SIMPLE|STGM_NOSCRATCH|STGM_NOSNAPSHOT)) | |
| return STG_E_INVALIDFLAG; | |
| if (grfMode & STGM_DELETEONRELEASE) | |
| return STG_E_INVALIDFUNCTION; | |
| if(STGM_ACCESS_MODE(grfMode) != STGM_READ) | |
| return STG_E_INVALIDFLAG; | |
| grfMode &= ~0xf0; /* remove the existing sharing mode */ | |
| grfMode |= STGM_SHARE_DENY_NONE; | |
| } | |
| /* | |
| * Validate the sharing mode | |
| */ | |
| if (grfMode & STGM_DIRECT_SWMR) | |
| { | |
| if ((STGM_SHARE_MODE(grfMode) != STGM_SHARE_DENY_WRITE) && | |
| (STGM_SHARE_MODE(grfMode) != STGM_SHARE_DENY_NONE)) | |
| { | |
| hr = STG_E_INVALIDFLAG; | |
| goto end; | |
| } | |
| } | |
| else if (!(grfMode & (STGM_TRANSACTED|STGM_PRIORITY))) | |
| switch(STGM_SHARE_MODE(grfMode)) | |
| { | |
| case STGM_SHARE_EXCLUSIVE: | |
| case STGM_SHARE_DENY_WRITE: | |
| break; | |
| default: | |
| hr = STG_E_INVALIDFLAG; | |
| goto end; | |
| } | |
| if ( FAILED( validateSTGM(grfMode) ) || | |
| (grfMode&STGM_CREATE)) | |
| { | |
| hr = STG_E_INVALIDFLAG; | |
| goto end; | |
| } | |
| /* shared reading requires transacted or single writer mode */ | |
| if( STGM_SHARE_MODE(grfMode) == STGM_SHARE_DENY_WRITE && | |
| STGM_ACCESS_MODE(grfMode) == STGM_READWRITE && | |
| !(grfMode & STGM_TRANSACTED) && !(grfMode & STGM_DIRECT_SWMR)) | |
| { | |
| hr = STG_E_INVALIDFLAG; | |
| goto end; | |
| } | |
| /* | |
| * Interpret the STGM value grfMode | |
| */ | |
| shareMode = GetShareModeFromSTGM(grfMode); | |
| accessMode = GetAccessModeFromSTGM(grfMode); | |
| *ppstgOpen = 0; | |
| hFile = CreateFileW( pwcsName, | |
| accessMode, | |
| shareMode, | |
| NULL, | |
| OPEN_EXISTING, | |
| FILE_ATTRIBUTE_NORMAL | FILE_FLAG_RANDOM_ACCESS, | |
| 0); | |
| if (hFile==INVALID_HANDLE_VALUE) | |
| { | |
| DWORD last_error = GetLastError(); | |
| hr = E_FAIL; | |
| switch (last_error) | |
| { | |
| case ERROR_FILE_NOT_FOUND: | |
| hr = STG_E_FILENOTFOUND; | |
| break; | |
| case ERROR_PATH_NOT_FOUND: | |
| hr = STG_E_PATHNOTFOUND; | |
| break; | |
| case ERROR_ACCESS_DENIED: | |
| case ERROR_WRITE_PROTECT: | |
| hr = STG_E_ACCESSDENIED; | |
| break; | |
| case ERROR_SHARING_VIOLATION: | |
| hr = STG_E_SHAREVIOLATION; | |
| break; | |
| default: | |
| hr = E_FAIL; | |
| } | |
| goto end; | |
| } | |
| /* | |
| * Refuse to open the file if it's too small to be a structured storage file | |
| * FIXME: verify the file when reading instead of here | |
| */ | |
| if (GetFileSize(hFile, NULL) < HEADER_SIZE) | |
| { | |
| CloseHandle(hFile); | |
| hr = STG_E_FILEALREADYEXISTS; | |
| goto end; | |
| } | |
| /* | |
| * Allocate and initialize the new IStorage object. | |
| */ | |
| hr = Storage_Construct( | |
| hFile, | |
| pwcsName, | |
| NULL, | |
| grfMode, | |
| TRUE, | |
| FALSE, | |
| 512, | |
| &newStorage); | |
| if (FAILED(hr)) | |
| { | |
| /* | |
| * According to the docs if the file is not a storage, return STG_E_FILEALREADYEXISTS | |
| */ | |
| if(hr == STG_E_INVALIDHEADER) | |
| hr = STG_E_FILEALREADYEXISTS; | |
| goto end; | |
| } | |
| *ppstgOpen = &newStorage->IStorage_iface; | |
| end: | |
| CoTaskMemFree(temp_name); | |
| if (pstgPriority) IStorage_Release(pstgPriority); | |
| TRACE("<-- %08x, IStorage %p\n", hr, ppstgOpen ? *ppstgOpen : NULL); | |
| return hr; | |
| } | |
| /****************************************************************************** | |
| * StgCreateDocfileOnILockBytes [OLE32.@] | |
| */ | |
| HRESULT WINAPI StgCreateDocfileOnILockBytes( | |
| ILockBytes *plkbyt, | |
| DWORD grfMode, | |
| DWORD reserved, | |
| IStorage** ppstgOpen) | |
| { | |
| StorageBaseImpl* newStorage = 0; | |
| HRESULT hr = S_OK; | |
| if ((ppstgOpen == 0) || (plkbyt == 0)) | |
| return STG_E_INVALIDPOINTER; | |
| /* | |
| * Allocate and initialize the new IStorage object. | |
| */ | |
| hr = Storage_Construct( | |
| 0, | |
| 0, | |
| plkbyt, | |
| grfMode, | |
| FALSE, | |
| TRUE, | |
| 512, | |
| &newStorage); | |
| if (FAILED(hr)) | |
| { | |
| return hr; | |
| } | |
| *ppstgOpen = &newStorage->IStorage_iface; | |
| return hr; | |
| } | |
| /****************************************************************************** | |
| * StgOpenStorageOnILockBytes [OLE32.@] | |
| */ | |
| HRESULT WINAPI StgOpenStorageOnILockBytes( | |
| ILockBytes *plkbyt, | |
| IStorage *pstgPriority, | |
| DWORD grfMode, | |
| SNB snbExclude, | |
| DWORD reserved, | |
| IStorage **ppstgOpen) | |
| { | |
| StorageBaseImpl* newStorage = 0; | |
| HRESULT hr = S_OK; | |
| if ((plkbyt == 0) || (ppstgOpen == 0)) | |
| return STG_E_INVALIDPOINTER; | |
| if ( FAILED( validateSTGM(grfMode) )) | |
| return STG_E_INVALIDFLAG; | |
| *ppstgOpen = 0; | |
| /* | |
| * Allocate and initialize the new IStorage object. | |
| */ | |
| hr = Storage_Construct( | |
| 0, | |
| 0, | |
| plkbyt, | |
| grfMode, | |
| FALSE, | |
| FALSE, | |
| 512, | |
| &newStorage); | |
| if (FAILED(hr)) | |
| { | |
| return hr; | |
| } | |
| *ppstgOpen = &newStorage->IStorage_iface; | |
| return hr; | |
| } | |
| /****************************************************************************** | |
| * StgSetTimes [ole32.@] | |
| * StgSetTimes [OLE32.@] | |
| * | |
| * | |
| */ | |
| HRESULT WINAPI StgSetTimes(OLECHAR const *str, FILETIME const *pctime, | |
| FILETIME const *patime, FILETIME const *pmtime) | |
| { | |
| IStorage *stg = NULL; | |
| HRESULT r; | |
| TRACE("%s %p %p %p\n", debugstr_w(str), pctime, patime, pmtime); | |
| r = StgOpenStorage(str, NULL, STGM_READWRITE | STGM_SHARE_DENY_WRITE, | |
| 0, 0, &stg); | |
| if( SUCCEEDED(r) ) | |
| { | |
| r = IStorage_SetElementTimes(stg, NULL, pctime, patime, pmtime); | |
| IStorage_Release(stg); | |
| } | |
| return r; | |
| } | |
| /****************************************************************************** | |
| * StgIsStorageILockBytes [OLE32.@] | |
| * | |
| * Determines if the ILockBytes contains a storage object. | |
| */ | |
| HRESULT WINAPI StgIsStorageILockBytes(ILockBytes *plkbyt) | |
| { | |
| BYTE sig[sizeof(STORAGE_magic)]; | |
| ULARGE_INTEGER offset; | |
| ULONG read = 0; | |
| offset.u.HighPart = 0; | |
| offset.u.LowPart = 0; | |
| ILockBytes_ReadAt(plkbyt, offset, sig, sizeof(sig), &read); | |
| if (read == sizeof(sig) && memcmp(sig, STORAGE_magic, sizeof(sig)) == 0) | |
| return S_OK; | |
| return S_FALSE; | |
| } | |
| /****************************************************************************** | |
| * WriteClassStg [OLE32.@] | |
| * | |
| * This method will store the specified CLSID in the specified storage object | |
| */ | |
| HRESULT WINAPI WriteClassStg(IStorage* pStg, REFCLSID rclsid) | |
| { | |
| if(!pStg) | |
| return E_INVALIDARG; | |
| if(!rclsid) | |
| return STG_E_INVALIDPOINTER; | |
| return IStorage_SetClass(pStg, rclsid); | |
| } | |
| /*********************************************************************** | |
| * ReadClassStg (OLE32.@) | |
| * | |
| * This method reads the CLSID previously written to a storage object with | |
| * the WriteClassStg. | |
| * | |
| * PARAMS | |
| * pstg [I] IStorage pointer | |
| * pclsid [O] Pointer to where the CLSID is written | |
| * | |
| * RETURNS | |
| * Success: S_OK. | |
| * Failure: HRESULT code. | |
| */ | |
| HRESULT WINAPI ReadClassStg(IStorage *pstg,CLSID *pclsid){ | |
| STATSTG pstatstg; | |
| HRESULT hRes; | |
| TRACE("(%p, %p)\n", pstg, pclsid); | |
| if(!pstg || !pclsid) | |
| return E_INVALIDARG; | |
| /* | |
| * read a STATSTG structure (contains the clsid) from the storage | |
| */ | |
| hRes=IStorage_Stat(pstg,&pstatstg,STATFLAG_NONAME); | |
| if(SUCCEEDED(hRes)) | |
| *pclsid=pstatstg.clsid; | |
| return hRes; | |
| } | |
| /*********************************************************************** | |
| * OleLoadFromStream (OLE32.@) | |
| * | |
| * This function loads an object from stream | |
| */ | |
| HRESULT WINAPI OleLoadFromStream(IStream *pStm,REFIID iidInterface,void** ppvObj) | |
| { | |
| CLSID clsid; | |
| HRESULT res; | |
| LPPERSISTSTREAM xstm; | |
| TRACE("(%p,%s,%p)\n",pStm,debugstr_guid(iidInterface),ppvObj); | |
| res=ReadClassStm(pStm,&clsid); | |
| if (FAILED(res)) | |
| return res; | |
| res=CoCreateInstance(&clsid,NULL,CLSCTX_INPROC_SERVER,iidInterface,ppvObj); | |
| if (FAILED(res)) | |
| return res; | |
| res=IUnknown_QueryInterface((IUnknown*)*ppvObj,&IID_IPersistStream,(LPVOID*)&xstm); | |
| if (FAILED(res)) { | |
| IUnknown_Release((IUnknown*)*ppvObj); | |
| return res; | |
| } | |
| res=IPersistStream_Load(xstm,pStm); | |
| IPersistStream_Release(xstm); | |
| /* FIXME: all refcounts ok at this point? I think they should be: | |
| * pStm : unchanged | |
| * ppvObj : 1 | |
| * xstm : 0 (released) | |
| */ | |
| return res; | |
| } | |
| /*********************************************************************** | |
| * OleSaveToStream (OLE32.@) | |
| * | |
| * This function saves an object with the IPersistStream interface on it | |
| * to the specified stream. | |
| */ | |
| HRESULT WINAPI OleSaveToStream(IPersistStream *pPStm,IStream *pStm) | |
| { | |
| CLSID clsid; | |
| HRESULT res; | |
| TRACE("(%p,%p)\n",pPStm,pStm); | |
| res=IPersistStream_GetClassID(pPStm,&clsid); | |
| if (SUCCEEDED(res)){ | |
| res=WriteClassStm(pStm,&clsid); | |
| if (SUCCEEDED(res)) | |
| res=IPersistStream_Save(pPStm,pStm,TRUE); | |
| } | |
| TRACE("Finished Save\n"); | |
| return res; | |
| } | |
| /************************************************************************* | |
| * STORAGE_CreateOleStream [Internal] | |
| * | |
| * Creates the "\001OLE" stream in the IStorage if necessary. | |
| * | |
| * PARAMS | |
| * storage [I] Dest storage to create the stream in | |
| * flags [I] flags to be set for newly created stream | |
| * | |
| * RETURNS | |
| * HRESULT return value | |
| * | |
| * NOTES | |
| * | |
| * This stream is still unknown, MS Word seems to have extra data | |
| * but since the data is stored in the OLESTREAM there should be | |
| * no need to recreate the stream. If the stream is manually | |
| * deleted it will create it with this default data. | |
| * | |
| */ | |
| HRESULT STORAGE_CreateOleStream(IStorage *storage, DWORD flags) | |
| { | |
| static const WCHAR stream_1oleW[] = {1,'O','l','e',0}; | |
| static const DWORD version_magic = 0x02000001; | |
| IStream *stream; | |
| HRESULT hr; | |
| hr = IStorage_CreateStream(storage, stream_1oleW, STGM_WRITE | STGM_SHARE_EXCLUSIVE, 0, 0, &stream); | |
| if (hr == S_OK) | |
| { | |
| struct empty_1ole_stream { | |
| DWORD version_magic; | |
| DWORD flags; | |
| DWORD update_options; | |
| DWORD reserved; | |
| DWORD mon_stream_size; | |
| }; | |
| struct empty_1ole_stream stream_data; | |
| stream_data.version_magic = version_magic; | |
| stream_data.flags = flags; | |
| stream_data.update_options = 0; | |
| stream_data.reserved = 0; | |
| stream_data.mon_stream_size = 0; | |
| hr = IStream_Write(stream, &stream_data, sizeof(stream_data), NULL); | |
| IStream_Release(stream); | |
| } | |
| return hr; | |
| } | |
| /* write a string to a stream, preceded by its length */ | |
| static HRESULT STREAM_WriteString( IStream *stm, LPCWSTR string ) | |
| { | |
| HRESULT r; | |
| LPSTR str; | |
| DWORD len = 0; | |
| if( string ) | |
| len = WideCharToMultiByte( CP_ACP, 0, string, -1, NULL, 0, NULL, NULL); | |
| r = IStream_Write( stm, &len, sizeof(len), NULL); | |
| if( FAILED( r ) ) | |
| return r; | |
| if(len == 0) | |
| return r; | |
| str = CoTaskMemAlloc( len ); | |
| WideCharToMultiByte( CP_ACP, 0, string, -1, str, len, NULL, NULL); | |
| r = IStream_Write( stm, str, len, NULL); | |
| CoTaskMemFree( str ); | |
| return r; | |
| } | |
| /* read a string preceded by its length from a stream */ | |
| static HRESULT STREAM_ReadString( IStream *stm, LPWSTR *string ) | |
| { | |
| HRESULT r; | |
| DWORD len, count = 0; | |
| LPSTR str; | |
| LPWSTR wstr; | |
| r = IStream_Read( stm, &len, sizeof(len), &count ); | |
| if( FAILED( r ) ) | |
| return r; | |
| if( count != sizeof(len) ) | |
| return E_OUTOFMEMORY; | |
| TRACE("%d bytes\n",len); | |
| str = CoTaskMemAlloc( len ); | |
| if( !str ) | |
| return E_OUTOFMEMORY; | |
| count = 0; | |
| r = IStream_Read( stm, str, len, &count ); | |
| if( FAILED( r ) ) | |
| { | |
| CoTaskMemFree( str ); | |
| return r; | |
| } | |
| if( count != len ) | |
| { | |
| CoTaskMemFree( str ); | |
| return E_OUTOFMEMORY; | |
| } | |
| TRACE("Read string %s\n",debugstr_an(str,len)); | |
| len = MultiByteToWideChar( CP_ACP, 0, str, count, NULL, 0 ); | |
| wstr = CoTaskMemAlloc( (len + 1)*sizeof (WCHAR) ); | |
| if( wstr ) | |
| { | |
| MultiByteToWideChar( CP_ACP, 0, str, count, wstr, len ); | |
| wstr[len] = 0; | |
| } | |
| CoTaskMemFree( str ); | |
| *string = wstr; | |
| return r; | |
| } | |
| static HRESULT STORAGE_WriteCompObj( LPSTORAGE pstg, CLSID *clsid, | |
| LPCWSTR lpszUserType, LPCWSTR szClipName, LPCWSTR szProgIDName ) | |
| { | |
| IStream *pstm; | |
| HRESULT r = S_OK; | |
| static const WCHAR szwStreamName[] = {1, 'C', 'o', 'm', 'p', 'O', 'b', 'j', 0}; | |
| static const BYTE unknown1[12] = | |
| { 0x01, 0x00, 0xFE, 0xFF, 0x03, 0x0A, 0x00, 0x00, | |
| 0xFF, 0xFF, 0xFF, 0xFF}; | |
| static const BYTE unknown2[16] = | |
| { 0xF4, 0x39, 0xB2, 0x71, 0x00, 0x00, 0x00, 0x00, | |
| 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; | |
| TRACE("%p %s %s %s %s\n", pstg, debugstr_guid(clsid), | |
| debugstr_w(lpszUserType), debugstr_w(szClipName), | |
| debugstr_w(szProgIDName)); | |
| /* Create a CompObj stream */ | |
| r = IStorage_CreateStream(pstg, szwStreamName, | |
| STGM_CREATE | STGM_WRITE | STGM_SHARE_EXCLUSIVE, 0, 0, &pstm ); | |
| if( FAILED (r) ) | |
| return r; | |
| /* Write CompObj Structure to stream */ | |
| r = IStream_Write(pstm, unknown1, sizeof(unknown1), NULL); | |
| if( SUCCEEDED( r ) ) | |
| r = WriteClassStm( pstm, clsid ); | |
| if( SUCCEEDED( r ) ) | |
| r = STREAM_WriteString( pstm, lpszUserType ); | |
| if( SUCCEEDED( r ) ) | |
| r = STREAM_WriteString( pstm, szClipName ); | |
| if( SUCCEEDED( r ) ) | |
| r = STREAM_WriteString( pstm, szProgIDName ); | |
| if( SUCCEEDED( r ) ) | |
| r = IStream_Write(pstm, unknown2, sizeof(unknown2), NULL); | |
| IStream_Release( pstm ); | |
| return r; | |
| } | |
| /*********************************************************************** | |
| * WriteFmtUserTypeStg (OLE32.@) | |
| */ | |
| HRESULT WINAPI WriteFmtUserTypeStg( | |
| LPSTORAGE pstg, CLIPFORMAT cf, LPOLESTR lpszUserType) | |
| { | |
| STATSTG stat; | |
| HRESULT r; | |
| WCHAR szwClipName[0x40]; | |
| CLSID clsid; | |
| LPWSTR wstrProgID = NULL; | |
| DWORD n; | |
| TRACE("(%p,%x,%s)\n",pstg,cf,debugstr_w(lpszUserType)); | |
| /* get the clipboard format name */ | |
| if( cf ) | |
| { | |
| n = GetClipboardFormatNameW( cf, szwClipName, | |
| 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; | |
| } |