diff --git a/dlls/ole32/storage32.c b/dlls/ole32/storage32.c index a2d00b13c7d..7f1affbfaca 100644 --- a/dlls/ole32/storage32.c +++ b/dlls/ole32/storage32.c @@ -110,6 +110,7 @@ static BOOL StorageImpl_WriteBigBlock(StorageImpl* This, ULONG blockIndex, const static void StorageImpl_SetNextBlockInChain(StorageImpl* This, ULONG blockIndex, ULONG nextBlock); static HRESULT StorageImpl_LoadFileHeader(StorageImpl* This); static void StorageImpl_SaveFileHeader(StorageImpl* This); +static HRESULT StorageImpl_LockRegionSync(StorageImpl *This, ULARGE_INTEGER offset, ULARGE_INTEGER cb, DWORD dwLockType); static void Storage32Impl_AddBlockDepot(StorageImpl* This, ULONG blockIndex, ULONG depotIndex); static ULONG Storage32Impl_AddExtBlockDepot(StorageImpl* This); @@ -191,6 +192,9 @@ typedef struct TransactedSnapshotImpl * Changes are committed to the transacted parent. */ StorageBaseImpl *transactedParent; + + /* The transaction signature from when we last committed */ + ULONG lastTransactionSig; } TransactedSnapshotImpl; /* Generic function to create a transacted wrapper for a direct storage object. */ @@ -2640,6 +2644,81 @@ static HRESULT StorageImpl_StreamLink(StorageBaseImpl *base, DirRef dst, return hr; } +static HRESULT StorageImpl_GetTransactionSig(StorageBaseImpl *base, + ULONG* result, BOOL refresh) +{ + StorageImpl *This = (StorageImpl*)base; + HRESULT hr=S_OK; + + 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)) + { + /* FIXME: Throw out everything and reload the file if this changed. */ + StorageUtl_ReadDWord(data, 0, &This->transactionSig); + } + } + + *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_LockTransaction(StorageBaseImpl *base) +{ + StorageImpl *This = (StorageImpl*)base; + HRESULT hr; + ULARGE_INTEGER offset, cb; + + /* Synchronous grab of both priority ranges, the commit lock, and the + * lock-checking lock. */ + offset.QuadPart = RANGELOCK_TRANSACTION_FIRST; + cb.QuadPart = RANGELOCK_TRANSACTION_LAST - RANGELOCK_TRANSACTION_FIRST + 1; + + hr = StorageImpl_LockRegionSync(This, offset, cb, LOCK_ONLYONCE); + + if (hr == STG_E_INVALIDFUNCTION) + hr = S_OK; + + return hr; +} + +static HRESULT StorageImpl_UnlockTransaction(StorageBaseImpl *base) +{ + StorageImpl *This = (StorageImpl*)base; + HRESULT hr; + ULARGE_INTEGER offset, cb; + + offset.QuadPart = RANGELOCK_TRANSACTION_FIRST; + cb.QuadPart = RANGELOCK_TRANSACTION_LAST - RANGELOCK_TRANSACTION_FIRST + 1; + + hr = ILockBytes_UnlockRegion(This->lockBytes, offset, cb, LOCK_ONLYONCE); + + if (hr == STG_E_INVALIDFUNCTION) + hr = S_OK; + + return hr; +} + static HRESULT StorageImpl_GetFilename(StorageBaseImpl* iface, LPWSTR *result) { StorageImpl *This = (StorageImpl*) iface; @@ -2740,7 +2819,11 @@ static const StorageBaseImplVtbl StorageImpl_BaseVtbl = StorageImpl_StreamReadAt, StorageImpl_StreamWriteAt, StorageImpl_StreamSetSize, - StorageImpl_StreamLink + StorageImpl_StreamLink, + StorageImpl_GetTransactionSig, + StorageImpl_SetTransactionSig, + StorageImpl_LockTransaction, + StorageImpl_UnlockTransaction }; static HRESULT StorageImpl_LockRegionSync(StorageImpl *This, ULARGE_INTEGER offset, @@ -3774,6 +3857,11 @@ static HRESULT StorageImpl_LoadFileHeader( OFFSET_ROOTSTARTBLOCK, &This->rootStartBlock); + StorageUtl_ReadDWord( + headerBigBlock, + OFFSET_TRANSACTIONSIG, + &This->transactionSig); + StorageUtl_ReadDWord( headerBigBlock, OFFSET_SMALLBLOCKLIMIT, @@ -3932,6 +4020,11 @@ static void StorageImpl_SaveFileHeader( OFFSET_ROOTSTARTBLOCK, This->rootStartBlock); + StorageUtl_WriteDWord( + headerBigBlock, + OFFSET_TRANSACTIONSIG, + This->transactionSig); + StorageUtl_WriteDWord( headerBigBlock, OFFSET_SMALLBLOCKLIMIT, @@ -4880,6 +4973,7 @@ static HRESULT WINAPI TransactedSnapshotImpl_Commit( DirEntry data; ULARGE_INTEGER zero; HRESULT hr; + ULONG transactionSig; zero.QuadPart = 0; @@ -4889,89 +4983,113 @@ static HRESULT WINAPI TransactedSnapshotImpl_Commit( if ( STGM_ACCESS_MODE( This->base.openFlags ) == STGM_READ ) return STG_E_ACCESSDENIED; - /* 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) - return S_OK; - - hr = TransactedSnapshotImpl_CopyTree(This); - if (FAILED(hr)) return hr; - - 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); - + hr = StorageBaseImpl_LockTransaction(This->transactedParent); + if (hr == E_NOTIMPL) hr = S_OK; if (SUCCEEDED(hr)) { - data.dirRootEntry = dir_root_ref; - data.clsid = root_entry->data.clsid; - data.ctime = root_entry->data.ctime; - data.mtime = root_entry->data.mtime; - - hr = StorageBaseImpl_WriteDirEntry(This->transactedParent, - root_entry->transactedParentEntry, &data); - } - - /* Try to flush after updating the root storage, but if the flush fails, keep - * going, on the theory that it'll either succeed later or the subsequent - * writes will fail. */ - StorageBaseImpl_Flush(This->transactedParent); - - if (SUCCEEDED(hr)) - { - /* Destroy the old now-orphaned data. */ - for (i=0; ientries_size; i++) + hr = StorageBaseImpl_GetTransactionSig(This->transactedParent, &transactionSig, TRUE); + if (SUCCEEDED(hr)) { - TransactedDirEntry *entry = &This->entries[i]; - if (entry->inuse) + if ((grfCommitFlags & STGC_ONLYIFCURRENT) && transactionSig != This->lastTransactionSig) + hr = STG_E_NOTCURRENT; + + if (SUCCEEDED(hr)) { - if (entry->deleted) + This->lastTransactionSig = transactionSig+1; + hr = StorageBaseImpl_SetTransactionSig(This->transactedParent, This->lastTransactionSig); + } + } + else if (hr == E_NOTIMPL) + hr = S_OK; + + if (FAILED(hr)) goto end; + + /* To prevent data loss, we create the new structure in the file before we + * delete the old one, so that in case of errors the old data is intact. We + * shouldn't do this if STGC_OVERWRITE is set, but that flag should only be + * needed in the rare situation where we have just enough free disk space to + * overwrite the existing data. */ + + root_entry = &This->entries[This->base.storageDirEntry]; + + if (!root_entry->read) + goto end; + + hr = TransactedSnapshotImpl_CopyTree(This); + if (FAILED(hr)) goto end; + + if (root_entry->data.dirRootEntry == DIRENTRY_NULL) + dir_root_ref = DIRENTRY_NULL; + else + dir_root_ref = This->entries[root_entry->data.dirRootEntry].newTransactedParentEntry; + + hr = StorageBaseImpl_Flush(This->transactedParent); + + /* Update the storage to use the new data in one step. */ + if (SUCCEEDED(hr)) + hr = StorageBaseImpl_ReadDirEntry(This->transactedParent, + root_entry->transactedParentEntry, &data); + + if (SUCCEEDED(hr)) + { + data.dirRootEntry = dir_root_ref; + data.clsid = root_entry->data.clsid; + data.ctime = root_entry->data.ctime; + data.mtime = root_entry->data.mtime; + + hr = StorageBaseImpl_WriteDirEntry(This->transactedParent, + root_entry->transactedParentEntry, &data); + } + + /* Try to flush after updating the root storage, but if the flush fails, keep + * going, on the theory that it'll either succeed later or the subsequent + * writes will fail. */ + StorageBaseImpl_Flush(This->transactedParent); + + if (SUCCEEDED(hr)) + { + /* Destroy the old now-orphaned data. */ + for (i=0; ientries_size; i++) + { + TransactedDirEntry *entry = &This->entries[i]; + if (entry->inuse) { - 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) + if (entry->deleted) + { + StorageBaseImpl_StreamSetSize(This->transactedParent, + entry->transactedParentEntry, zero); 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; + 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; } - entry->dirty = FALSE; - entry->transactedParentEntry = entry->newTransactedParentEntry; } } } - } - else - { - TransactedSnapshotImpl_DestroyTemporaryCopy(This, DIRENTRY_NULL); - } + else + { + TransactedSnapshotImpl_DestroyTemporaryCopy(This, DIRENTRY_NULL); + } - if (SUCCEEDED(hr)) - hr = StorageBaseImpl_Flush(This->transactedParent); + if (SUCCEEDED(hr)) + hr = StorageBaseImpl_Flush(This->transactedParent); +end: + StorageBaseImpl_UnlockTransaction(This->transactedParent); + } return hr; } @@ -5266,6 +5384,28 @@ static HRESULT TransactedSnapshotImpl_StreamLink(StorageBaseImpl *base, 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) +{ + return E_NOTIMPL; +} + +static HRESULT TransactedSnapshotImpl_UnlockTransaction(StorageBaseImpl *base) +{ + return E_NOTIMPL; +} + static const IStorageVtbl TransactedSnapshotImpl_Vtbl = { StorageBaseImpl_QueryInterface, @@ -5301,7 +5441,11 @@ static const StorageBaseImplVtbl TransactedSnapshotImpl_BaseVtbl = TransactedSnapshotImpl_StreamReadAt, TransactedSnapshotImpl_StreamWriteAt, TransactedSnapshotImpl_StreamSetSize, - TransactedSnapshotImpl_StreamLink + TransactedSnapshotImpl_StreamLink, + TransactedSnapshotImpl_GetTransactionSig, + TransactedSnapshotImpl_SetTransactionSig, + TransactedSnapshotImpl_LockTransaction, + TransactedSnapshotImpl_UnlockTransaction }; static HRESULT TransactedSnapshotImpl_Construct(StorageBaseImpl *parentStorage, @@ -5328,6 +5472,9 @@ static HRESULT TransactedSnapshotImpl_Construct(StorageBaseImpl *parentStorage, (*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); @@ -5525,6 +5672,28 @@ static HRESULT StorageInternalImpl_StreamLink(StorageBaseImpl *base, 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) +{ + return E_NOTIMPL; +} + +static HRESULT StorageInternalImpl_UnlockTransaction(StorageBaseImpl *base) +{ + return E_NOTIMPL; +} + /****************************************************************************** ** ** Storage32InternalImpl_Commit @@ -5875,7 +6044,11 @@ static const StorageBaseImplVtbl StorageInternalImpl_BaseVtbl = StorageInternalImpl_StreamReadAt, StorageInternalImpl_StreamWriteAt, StorageInternalImpl_StreamSetSize, - StorageInternalImpl_StreamLink + StorageInternalImpl_StreamLink, + StorageInternalImpl_GetTransactionSig, + StorageInternalImpl_SetTransactionSig, + StorageInternalImpl_LockTransaction, + StorageInternalImpl_UnlockTransaction }; /****************************************************************************** @@ -7810,13 +7983,6 @@ HRESULT WINAPI StgOpenStorage( return STG_E_INVALIDFLAG; grfMode &= ~0xf0; /* remove the existing sharing mode */ grfMode |= STGM_SHARE_DENY_NONE; - - /* STGM_PRIORITY stops other IStorage objects on the same file from - * committing until the STGM_PRIORITY IStorage is closed. it also - * stops non-transacted mode StgOpenStorage calls with write access from - * succeeding. obviously, both of these cannot be achieved through just - * file share flags */ - FIXME("STGM_PRIORITY mode not implemented correctly\n"); } /* diff --git a/dlls/ole32/storage32.h b/dlls/ole32/storage32.h index 83fdd4c8078..d5041d1f4c4 100644 --- a/dlls/ole32/storage32.h +++ b/dlls/ole32/storage32.h @@ -51,6 +51,7 @@ static const ULONG OFFSET_SMALLBLOCKSIZEBITS = 0x00000020; static const ULONG OFFSET_DIRSECTORCOUNT = 0x00000028; static const ULONG OFFSET_BBDEPOTCOUNT = 0x0000002C; static const ULONG OFFSET_ROOTSTARTBLOCK = 0x00000030; +static const ULONG OFFSET_TRANSACTIONSIG = 0x00000034; static const ULONG OFFSET_SMALLBLOCKLIMIT = 0x00000038; static const ULONG OFFSET_SBDEPOTSTART = 0x0000003C; static const ULONG OFFSET_SBDEPOTCOUNT = 0x00000040; @@ -246,6 +247,10 @@ struct StorageBaseImplVtbl { HRESULT (*StreamWriteAt)(StorageBaseImpl*,DirRef,ULARGE_INTEGER,ULONG,const void*,ULONG*); HRESULT (*StreamSetSize)(StorageBaseImpl*,DirRef,ULARGE_INTEGER); HRESULT (*StreamLink)(StorageBaseImpl*,DirRef,DirRef); + HRESULT (*GetTransactionSig)(StorageBaseImpl*,ULONG*,BOOL); + HRESULT (*SetTransactionSig)(StorageBaseImpl*,ULONG); + HRESULT (*LockTransaction)(StorageBaseImpl*); + HRESULT (*UnlockTransaction)(StorageBaseImpl*); }; static inline void StorageBaseImpl_Destroy(StorageBaseImpl *This) @@ -323,6 +328,28 @@ static inline HRESULT StorageBaseImpl_StreamLink(StorageBaseImpl *This, return This->baseVtbl->StreamLink(This, dst, src); } +static inline HRESULT StorageBaseImpl_GetTransactionSig(StorageBaseImpl *This, + ULONG* result, BOOL refresh) +{ + return This->baseVtbl->GetTransactionSig(This, result, refresh); +} + +static inline HRESULT StorageBaseImpl_SetTransactionSig(StorageBaseImpl *This, + ULONG value) +{ + return This->baseVtbl->SetTransactionSig(This, value); +} + +static inline HRESULT StorageBaseImpl_LockTransaction(StorageBaseImpl *This) +{ + return This->baseVtbl->LockTransaction(This); +} + +static inline HRESULT StorageBaseImpl_UnlockTransaction(StorageBaseImpl *This) +{ + return This->baseVtbl->UnlockTransaction(This); +} + /**************************************************************************** * StorageBaseImpl stream list handlers */ @@ -359,6 +386,7 @@ struct StorageImpl ULONG extBigBlockDepotLocationsSize; ULONG extBigBlockDepotCount; ULONG bigBlockDepotStart[COUNT_BBDEPOTINHEADER]; + ULONG transactionSig; ULONG extBlockDepotCached[MAX_BIG_BLOCK_SIZE / 4]; ULONG indexExtBlockDepotCached; diff --git a/dlls/ole32/tests/storage32.c b/dlls/ole32/tests/storage32.c index b5c93aefb2f..5db912006f3 100644 --- a/dlls/ole32/tests/storage32.c +++ b/dlls/ole32/tests/storage32.c @@ -3491,7 +3491,7 @@ static void test_transacted_shared(void) /* commit fails because we're out of date */ r = IStorage_Commit(stgrw, STGC_ONLYIFCURRENT); - todo_wine ok(r==STG_E_NOTCURRENT, "IStorage->Commit failed %x\n", r); + ok(r==STG_E_NOTCURRENT, "IStorage->Commit failed %x\n", r); /* unless we force it */ r = IStorage_Commit(stgrw, STGC_DEFAULT); @@ -3515,7 +3515,7 @@ static void test_transacted_shared(void) /* and committing fails forever */ r = IStorage_Commit(stg, STGC_ONLYIFCURRENT); - todo_wine ok(r==STG_E_NOTCURRENT, "IStorage->Commit failed %x\n", r); + ok(r==STG_E_NOTCURRENT, "IStorage->Commit failed %x\n", r); IStream_Release(stm);