[PATCH v7 0/1] MR3106: coml2: Move remaining storage functions from ole32
-- v7: coml2: Move storage functions from ole32 https://gitlab.winehq.org/wine/wine/-/merge_requests/3106
From: Fabian Maurer <dark.shadow4(a)web.de> --- dlls/coml2/Makefile.in | 6 +- dlls/coml2/coml2.spec | 21 +- dlls/{ole32 => coml2}/dictionary.c | 0 dlls/{ole32 => coml2}/dictionary.h | 0 dlls/{ole32 => coml2}/filelockbytes.c | 0 dlls/coml2/stg_prop.c | 3029 +++++++++ dlls/{ole32 => coml2}/stg_stream.c | 0 dlls/coml2/storage32.c | 8941 +++++++++++++++++++++++++ dlls/{ole32 => coml2}/storage32.h | 7 - dlls/ole32/Makefile.in | 3 - dlls/ole32/clipboard.c | 5 +- dlls/ole32/defaulthandler.c | 3 +- dlls/ole32/ole32.spec | 2 +- dlls/ole32/stg_prop.c | 3032 +-------- dlls/ole32/storage32.c | 8938 ------------------------ 15 files changed, 11999 insertions(+), 11988 deletions(-) rename dlls/{ole32 => coml2}/dictionary.c (100%) rename dlls/{ole32 => coml2}/dictionary.h (100%) rename dlls/{ole32 => coml2}/filelockbytes.c (100%) rename dlls/{ole32 => coml2}/stg_stream.c (100%) rename dlls/{ole32 => coml2}/storage32.h (98%) diff --git a/dlls/coml2/Makefile.in b/dlls/coml2/Makefile.in index 739f791d4c5..d96fd52dff4 100644 --- a/dlls/coml2/Makefile.in +++ b/dlls/coml2/Makefile.in @@ -1,9 +1,13 @@ EXTRADEFS = -DWINOLE32API= MODULE = coml2.dll IMPORTLIB = coml2 -IMPORTS = uuid +IMPORTS = uuid combase +DELAYIMPORTS = oleaut32 SOURCES = \ + dictionary.c \ + filelockbytes.c \ memlockbytes.c \ stg_prop.c \ + stg_stream.c \ storage32.c diff --git a/dlls/coml2/coml2.spec b/dlls/coml2/coml2.spec index f9747cb449d..306aa99a394 100644 --- a/dlls/coml2/coml2.spec +++ b/dlls/coml2/coml2.spec @@ -17,17 +17,20 @@ @ stdcall PropStgNameToFmtId(wstr ptr) @ stdcall ReadClassStg(ptr ptr) @ stdcall ReadClassStm(ptr ptr) -@ stub StgCreateDocfile -@ stub StgCreateDocfileOnILockBytes +@ stdcall StgCreateDocfile(wstr long long ptr) +@ stdcall StgCreateDocfileOnILockBytes(ptr long long ptr) @ stdcall StgCreatePropSetStg(ptr long ptr) -@ stub StgCreatePropStg -@ stub StgCreateStorageEx +@ stdcall StgCreatePropStg(ptr ptr ptr long long ptr) +@ stdcall StgCreateStorageEx(wstr long long long ptr ptr ptr ptr) @ stdcall StgIsStorageFile(wstr) @ stdcall StgIsStorageILockBytes(ptr) -@ stub StgOpenPropStg -@ stub StgOpenStorage -@ stub StgOpenStorageEx -@ stub StgOpenStorageOnILockBytes -@ stub StgSetTimes +@ stdcall StgOpenPropStg(ptr ptr long long ptr) +@ stdcall StgOpenStorage(wstr ptr long ptr long ptr) +@ stdcall StgOpenStorageEx(wstr long long long ptr ptr ptr ptr) +@ stdcall StgOpenStorageOnILockBytes(ptr ptr long ptr long ptr) +@ stdcall StgSetTimes(wstr ptr ptr ptr) @ stdcall WriteClassStg(ptr ptr) @ stdcall WriteClassStm(ptr ptr) + +# Wine internal exports +@ stdcall wine_PropertyStorage_ReadProperty(ptr ptr ptr long ptr ptr) PropertyStorage_ReadProperty diff --git a/dlls/ole32/dictionary.c b/dlls/coml2/dictionary.c similarity index 100% rename from dlls/ole32/dictionary.c rename to dlls/coml2/dictionary.c diff --git a/dlls/ole32/dictionary.h b/dlls/coml2/dictionary.h similarity index 100% rename from dlls/ole32/dictionary.h rename to dlls/coml2/dictionary.h diff --git a/dlls/ole32/filelockbytes.c b/dlls/coml2/filelockbytes.c similarity index 100% rename from dlls/ole32/filelockbytes.c rename to dlls/coml2/filelockbytes.c diff --git a/dlls/coml2/stg_prop.c b/dlls/coml2/stg_prop.c index 93d658c2a0a..f0c2c1f5ce5 100644 --- a/dlls/coml2/stg_prop.c +++ b/dlls/coml2/stg_prop.c @@ -52,9 +52,2948 @@ #include "wine/debug.h" #include "wine/heap.h" #include "oleauto.h" +#include "dictionary.h" +#include "storage32.h" WINE_DEFAULT_DEBUG_CHANNEL(storage); +static inline StorageImpl *impl_from_IPropertySetStorage( IPropertySetStorage *iface ) +{ + return CONTAINING_RECORD(iface, StorageImpl, base.IPropertySetStorage_iface); +} + +/* These are documented in MSDN, + * but they don't seem to be in any header file. + */ +#define PROPSETHDR_BYTEORDER_MAGIC 0xfffe +#define PROPSETHDR_OSVER_KIND_WIN16 0 +#define PROPSETHDR_OSVER_KIND_MAC 1 +#define PROPSETHDR_OSVER_KIND_WIN32 2 + +#define CP_UNICODE 1200 + +#define MAX_VERSION_0_PROP_NAME_LENGTH 256 + +#define CFTAG_WINDOWS (-1L) +#define CFTAG_MACINTOSH (-2L) +#define CFTAG_FMTID (-3L) +#define CFTAG_NODATA 0L + +#define ALIGNED_LENGTH(_Len, _Align) (((_Len)+(_Align))&~(_Align)) + +typedef struct tagPROPERTYSETHEADER +{ + WORD wByteOrder; /* always 0xfffe */ + WORD wFormat; /* can be zero or one */ + DWORD dwOSVer; /* OS version of originating system */ + CLSID clsid; /* application CLSID */ + DWORD reserved; /* always 1 */ +} PROPERTYSETHEADER; + +typedef struct tagFORMATIDOFFSET +{ + FMTID fmtid; + DWORD dwOffset; /* from beginning of stream */ +} FORMATIDOFFSET; + +typedef struct tagPROPERTYSECTIONHEADER +{ + DWORD cbSection; + DWORD cProperties; +} PROPERTYSECTIONHEADER; + +typedef struct tagPROPERTYIDOFFSET +{ + DWORD propid; + DWORD dwOffset; /* from beginning of section */ +} PROPERTYIDOFFSET; + +typedef struct tagPropertyStorage_impl PropertyStorage_impl; + +/* Initializes the property storage from the stream (and undoes any uncommitted + * changes in the process.) Returns an error if there is an error reading or + * if the stream format doesn't match what's expected. + */ +static HRESULT PropertyStorage_ReadFromStream(PropertyStorage_impl *); + +static HRESULT PropertyStorage_WriteToStream(PropertyStorage_impl *); + +/* Creates the dictionaries used by the property storage. If successful, all + * the dictionaries have been created. If failed, none has been. (This makes + * it a bit easier to deal with destroying them.) + */ +static HRESULT PropertyStorage_CreateDictionaries(PropertyStorage_impl *); + +static void PropertyStorage_DestroyDictionaries(PropertyStorage_impl *); + +/* Copies from propvar to prop. If propvar's type is VT_LPSTR, copies the + * string using PropertyStorage_StringCopy. + */ +static HRESULT PropertyStorage_PropVariantCopy(PROPVARIANT *prop, + const PROPVARIANT *propvar, UINT targetCP, UINT srcCP); + +/* Copies the string src, which is encoded using code page srcCP, and returns + * it in *dst, in the code page specified by targetCP. The returned string is + * allocated using CoTaskMemAlloc. + * If srcCP is CP_UNICODE, src is in fact an LPCWSTR. Similarly, if targetCP + * is CP_UNICODE, the returned string is in fact an LPWSTR. + * Returns S_OK on success, something else on failure. + */ +static HRESULT PropertyStorage_StringCopy(LPCSTR src, UINT srcCP, LPSTR *dst, + UINT targetCP); + +static const IPropertyStorageVtbl IPropertyStorage_Vtbl; + +/*********************************************************************** + * Implementation of IPropertyStorage + */ +struct tagPropertyStorage_impl +{ + IPropertyStorage IPropertyStorage_iface; + LONG ref; + CRITICAL_SECTION cs; + IStream *stm; + BOOL dirty; + FMTID fmtid; + CLSID clsid; + WORD format; + DWORD originatorOS; + DWORD grfFlags; + DWORD grfMode; + UINT codePage; + LCID locale; + PROPID highestProp; + struct dictionary *name_to_propid; + struct dictionary *propid_to_name; + struct dictionary *propid_to_prop; +}; + +static inline PropertyStorage_impl *impl_from_IPropertyStorage(IPropertyStorage *iface) +{ + return CONTAINING_RECORD(iface, PropertyStorage_impl, IPropertyStorage_iface); +} + +struct enum_stat_prop_stg +{ + IEnumSTATPROPSTG IEnumSTATPROPSTG_iface; + LONG refcount; + PropertyStorage_impl *storage; + STATPROPSTG *stats; + size_t current; + size_t count; +}; + +static struct enum_stat_prop_stg *impl_from_IEnumSTATPROPSTG(IEnumSTATPROPSTG *iface) +{ + return CONTAINING_RECORD(iface, struct enum_stat_prop_stg, IEnumSTATPROPSTG_iface); +} + +static HRESULT WINAPI enum_stat_prop_stg_QueryInterface(IEnumSTATPROPSTG *iface, REFIID riid, void **obj) +{ + TRACE("%p, %s, %p.\n", iface, debugstr_guid(riid), obj); + + if (IsEqualIID(riid, &IID_IEnumSTATPROPSTG) || + IsEqualIID(riid, &IID_IUnknown)) + { + *obj = iface; + IEnumSTATPROPSTG_AddRef(iface); + return S_OK; + } + + WARN("Unsupported interface %s.\n", debugstr_guid(riid)); + return E_NOINTERFACE; +} + +static ULONG WINAPI enum_stat_prop_stg_AddRef(IEnumSTATPROPSTG *iface) +{ + struct enum_stat_prop_stg *penum = impl_from_IEnumSTATPROPSTG(iface); + LONG refcount = InterlockedIncrement(&penum->refcount); + + TRACE("%p, refcount %lu.\n", iface, refcount); + + return refcount; +} + +static ULONG WINAPI enum_stat_prop_stg_Release(IEnumSTATPROPSTG *iface) +{ + struct enum_stat_prop_stg *penum = impl_from_IEnumSTATPROPSTG(iface); + LONG refcount = InterlockedDecrement(&penum->refcount); + + TRACE("%p, refcount %lu.\n", iface, refcount); + + if (!refcount) + { + IPropertyStorage_Release(&penum->storage->IPropertyStorage_iface); + heap_free(penum->stats); + heap_free(penum); + } + + return refcount; +} + +static HRESULT WINAPI enum_stat_prop_stg_Next(IEnumSTATPROPSTG *iface, ULONG celt, STATPROPSTG *ret, ULONG *fetched) +{ + struct enum_stat_prop_stg *penum = impl_from_IEnumSTATPROPSTG(iface); + ULONG count = 0; + WCHAR *name; + + TRACE("%p, %lu, %p, %p.\n", iface, celt, ret, fetched); + + if (penum->current == ~0u) + penum->current = 0; + + while (count < celt && penum->current < penum->count) + { + *ret = penum->stats[penum->current++]; + + if (dictionary_find(penum->storage->propid_to_name, UlongToPtr(ret->propid), (void **)&name)) + { + SIZE_T size = (lstrlenW(name) + 1) * sizeof(WCHAR); + ret->lpwstrName = CoTaskMemAlloc(size); + if (ret->lpwstrName) + memcpy(ret->lpwstrName, name, size); + } + ret++; + count++; + } + + if (fetched) + *fetched = count; + + return count < celt ? S_FALSE : S_OK; +} + +static HRESULT WINAPI enum_stat_prop_stg_Skip(IEnumSTATPROPSTG *iface, ULONG celt) +{ + FIXME("%p, %lu.\n", iface, celt); + + return S_OK; +} + +static HRESULT WINAPI enum_stat_prop_stg_Reset(IEnumSTATPROPSTG *iface) +{ + struct enum_stat_prop_stg *penum = impl_from_IEnumSTATPROPSTG(iface); + + TRACE("%p.\n", iface); + + penum->current = ~0u; + + return S_OK; +} + +static HRESULT WINAPI enum_stat_prop_stg_Clone(IEnumSTATPROPSTG *iface, IEnumSTATPROPSTG **ppenum) +{ + FIXME("%p, %p.\n", iface, ppenum); + + return E_NOTIMPL; +} + +static const IEnumSTATPROPSTGVtbl enum_stat_prop_stg_vtbl = +{ + enum_stat_prop_stg_QueryInterface, + enum_stat_prop_stg_AddRef, + enum_stat_prop_stg_Release, + enum_stat_prop_stg_Next, + enum_stat_prop_stg_Skip, + enum_stat_prop_stg_Reset, + enum_stat_prop_stg_Clone, +}; + +static BOOL prop_enum_stat(const void *k, const void *v, void *extra, void *arg) +{ + struct enum_stat_prop_stg *stg = arg; + PROPID propid = PtrToUlong(k); + const PROPVARIANT *prop = v; + STATPROPSTG *dest; + + dest = &stg->stats[stg->count]; + + dest->lpwstrName = NULL; + dest->propid = propid; + dest->vt = prop->vt; + stg->count++; + + return TRUE; +} + +static BOOL prop_enum_stat_count(const void *k, const void *v, void *extra, void *arg) +{ + DWORD *count = arg; + + *count += 1; + + return TRUE; +} + +static HRESULT create_enum_stat_prop_stg(PropertyStorage_impl *storage, IEnumSTATPROPSTG **ret) +{ + struct enum_stat_prop_stg *enum_obj; + DWORD count; + + enum_obj = heap_alloc_zero(sizeof(*enum_obj)); + if (!enum_obj) + return E_OUTOFMEMORY; + + enum_obj->IEnumSTATPROPSTG_iface.lpVtbl = &enum_stat_prop_stg_vtbl; + enum_obj->refcount = 1; + enum_obj->storage = storage; + IPropertyStorage_AddRef(&storage->IPropertyStorage_iface); + + count = 0; + dictionary_enumerate(storage->propid_to_prop, prop_enum_stat_count, &count); + + if (count) + { + if (!(enum_obj->stats = heap_alloc(sizeof(*enum_obj->stats) * count))) + { + IEnumSTATPROPSTG_Release(&enum_obj->IEnumSTATPROPSTG_iface); + return E_OUTOFMEMORY; + } + + dictionary_enumerate(storage->propid_to_prop, prop_enum_stat, enum_obj); + } + + *ret = &enum_obj->IEnumSTATPROPSTG_iface; + + return S_OK; +} + +/************************************************************************ + * IPropertyStorage_fnQueryInterface (IPropertyStorage) + */ +static HRESULT WINAPI IPropertyStorage_fnQueryInterface( + IPropertyStorage *iface, + REFIID riid, + void** ppvObject) +{ + PropertyStorage_impl *This = impl_from_IPropertyStorage(iface); + + TRACE("(%p, %s, %p)\n", This, debugstr_guid(riid), ppvObject); + + if (!ppvObject) + return E_INVALIDARG; + + *ppvObject = 0; + + if (IsEqualGUID(&IID_IUnknown, riid) || + IsEqualGUID(&IID_IPropertyStorage, riid)) + { + IPropertyStorage_AddRef(iface); + *ppvObject = iface; + return S_OK; + } + + return E_NOINTERFACE; +} + +/************************************************************************ + * IPropertyStorage_fnAddRef (IPropertyStorage) + */ +static ULONG WINAPI IPropertyStorage_fnAddRef( + IPropertyStorage *iface) +{ + PropertyStorage_impl *This = impl_from_IPropertyStorage(iface); + return InterlockedIncrement(&This->ref); +} + +/************************************************************************ + * IPropertyStorage_fnRelease (IPropertyStorage) + */ +static ULONG WINAPI IPropertyStorage_fnRelease( + IPropertyStorage *iface) +{ + PropertyStorage_impl *This = impl_from_IPropertyStorage(iface); + ULONG ref; + + ref = InterlockedDecrement(&This->ref); + if (ref == 0) + { + TRACE("Destroying %p\n", This); + if (This->dirty) + IPropertyStorage_Commit(iface, STGC_DEFAULT); + IStream_Release(This->stm); + This->cs.DebugInfo->Spare[0] = 0; + DeleteCriticalSection(&This->cs); + PropertyStorage_DestroyDictionaries(This); + HeapFree(GetProcessHeap(), 0, This); + } + return ref; +} + +static PROPVARIANT *PropertyStorage_FindProperty(PropertyStorage_impl *This, + DWORD propid) +{ + PROPVARIANT *ret = NULL; + + dictionary_find(This->propid_to_prop, UlongToPtr(propid), (void **)&ret); + TRACE("returning %p\n", ret); + return ret; +} + +/* Returns NULL if name is NULL. */ +static PROPVARIANT *PropertyStorage_FindPropertyByName( + PropertyStorage_impl *This, LPCWSTR name) +{ + PROPVARIANT *ret = NULL; + void *propid; + + if (!name) + return NULL; + if (This->codePage == CP_UNICODE) + { + if (dictionary_find(This->name_to_propid, name, &propid)) + ret = PropertyStorage_FindProperty(This, PtrToUlong(propid)); + } + else + { + LPSTR ansiName; + HRESULT hr = PropertyStorage_StringCopy((LPCSTR)name, CP_UNICODE, + &ansiName, This->codePage); + + if (SUCCEEDED(hr)) + { + if (dictionary_find(This->name_to_propid, ansiName, &propid)) + ret = PropertyStorage_FindProperty(This, PtrToUlong(propid)); + CoTaskMemFree(ansiName); + } + } + TRACE("returning %p\n", ret); + return ret; +} + +static LPWSTR PropertyStorage_FindPropertyNameById(PropertyStorage_impl *This, + DWORD propid) +{ + LPWSTR ret = NULL; + + dictionary_find(This->propid_to_name, UlongToPtr(propid), (void **)&ret); + TRACE("returning %p\n", ret); + return ret; +} + +/************************************************************************ + * IPropertyStorage_fnReadMultiple (IPropertyStorage) + */ +static HRESULT WINAPI IPropertyStorage_fnReadMultiple( + IPropertyStorage* iface, + ULONG cpspec, + const PROPSPEC rgpspec[], + PROPVARIANT rgpropvar[]) +{ + PropertyStorage_impl *This = impl_from_IPropertyStorage(iface); + HRESULT hr = S_OK; + ULONG i; + + TRACE("%p, %lu, %p, %p\n", iface, cpspec, rgpspec, rgpropvar); + + if (!cpspec) + return S_FALSE; + if (!rgpspec || !rgpropvar) + return E_INVALIDARG; + EnterCriticalSection(&This->cs); + for (i = 0; i < cpspec; i++) + { + PropVariantInit(&rgpropvar[i]); + if (rgpspec[i].ulKind == PRSPEC_LPWSTR) + { + PROPVARIANT *prop = PropertyStorage_FindPropertyByName(This, + rgpspec[i].lpwstr); + + if (prop) + PropertyStorage_PropVariantCopy(&rgpropvar[i], prop, GetACP(), + This->codePage); + } + else + { + switch (rgpspec[i].propid) + { + case PID_CODEPAGE: + rgpropvar[i].vt = VT_I2; + rgpropvar[i].iVal = This->codePage; + break; + case PID_LOCALE: + rgpropvar[i].vt = VT_I4; + rgpropvar[i].lVal = This->locale; + break; + default: + { + PROPVARIANT *prop = PropertyStorage_FindProperty(This, + rgpspec[i].propid); + + if (prop) + PropertyStorage_PropVariantCopy(&rgpropvar[i], prop, + GetACP(), This->codePage); + else + hr = S_FALSE; + } + } + } + } + LeaveCriticalSection(&This->cs); + return hr; +} + +static HRESULT PropertyStorage_StringCopy(LPCSTR src, UINT srcCP, LPSTR *dst, UINT dstCP) +{ + HRESULT hr = S_OK; + int len; + + TRACE("%s, %p, %d, %d\n", + srcCP == CP_UNICODE ? debugstr_w((LPCWSTR)src) : debugstr_a(src), dst, + dstCP, srcCP); + assert(src); + assert(dst); + *dst = NULL; + if (dstCP == srcCP) + { + size_t len; + + if (dstCP == CP_UNICODE) + len = (lstrlenW((LPCWSTR)src) + 1) * sizeof(WCHAR); + else + len = strlen(src) + 1; + *dst = CoTaskMemAlloc(len); + if (!*dst) + hr = STG_E_INSUFFICIENTMEMORY; + else + memcpy(*dst, src, len); + } + else + { + if (dstCP == CP_UNICODE) + { + len = MultiByteToWideChar(srcCP, 0, src, -1, NULL, 0); + *dst = CoTaskMemAlloc(len * sizeof(WCHAR)); + if (!*dst) + hr = STG_E_INSUFFICIENTMEMORY; + else + MultiByteToWideChar(srcCP, 0, src, -1, (LPWSTR)*dst, len); + } + else + { + LPCWSTR wideStr = NULL; + LPWSTR wideStr_tmp = NULL; + + if (srcCP == CP_UNICODE) + wideStr = (LPCWSTR)src; + else + { + len = MultiByteToWideChar(srcCP, 0, src, -1, NULL, 0); + wideStr_tmp = HeapAlloc(GetProcessHeap(), 0, len * sizeof(WCHAR)); + if (wideStr_tmp) + { + MultiByteToWideChar(srcCP, 0, src, -1, wideStr_tmp, len); + wideStr = wideStr_tmp; + } + else + hr = STG_E_INSUFFICIENTMEMORY; + } + if (SUCCEEDED(hr)) + { + len = WideCharToMultiByte(dstCP, 0, wideStr, -1, NULL, 0, + NULL, NULL); + *dst = CoTaskMemAlloc(len); + if (!*dst) + hr = STG_E_INSUFFICIENTMEMORY; + else + { + BOOL defCharUsed = FALSE; + + if (WideCharToMultiByte(dstCP, 0, wideStr, -1, *dst, len, + NULL, &defCharUsed) == 0 || defCharUsed) + { + CoTaskMemFree(*dst); + *dst = NULL; + hr = HRESULT_FROM_WIN32(ERROR_NO_UNICODE_TRANSLATION); + } + } + } + HeapFree(GetProcessHeap(), 0, wideStr_tmp); + } + } + TRACE("returning %#lx (%s)\n", hr, + dstCP == CP_UNICODE ? debugstr_w((LPCWSTR)*dst) : debugstr_a(*dst)); + return hr; +} + +static HRESULT PropertyStorage_PropVariantCopy(PROPVARIANT *prop, const PROPVARIANT *propvar, + UINT targetCP, UINT srcCP) +{ + HRESULT hr = S_OK; + + assert(prop); + assert(propvar); + + switch (propvar->vt) + { + case VT_LPSTR: + hr = PropertyStorage_StringCopy(propvar->pszVal, srcCP, &prop->pszVal, targetCP); + if (SUCCEEDED(hr)) + prop->vt = VT_LPSTR; + break; + case VT_BSTR: + if ((prop->bstrVal = SysAllocStringLen(propvar->bstrVal, SysStringLen(propvar->bstrVal)))) + prop->vt = VT_BSTR; + else + hr = E_OUTOFMEMORY; + break; + default: + hr = PropVariantCopy(prop, propvar); + } + + return hr; +} + +/* Stores the property with id propid and value propvar into this property + * storage. lcid is ignored if propvar's type is not VT_LPSTR. If propvar's + * type is VT_LPSTR, converts the string using lcid as the source code page + * and This->codePage as the target code page before storing. + * As a side effect, may change This->format to 1 if the type of propvar is + * a version 1-only property. + */ +static HRESULT PropertyStorage_StorePropWithId(PropertyStorage_impl *This, + PROPID propid, const PROPVARIANT *propvar, UINT cp) +{ + HRESULT hr = S_OK; + PROPVARIANT *prop = PropertyStorage_FindProperty(This, propid); + + assert(propvar); + if (propvar->vt & VT_BYREF || propvar->vt & VT_ARRAY) + This->format = 1; + switch (propvar->vt) + { + case VT_DECIMAL: + case VT_I1: + case VT_INT: + case VT_UINT: + case VT_VECTOR|VT_I1: + This->format = 1; + } + TRACE("Setting %#lx to type %d\n", propid, propvar->vt); + if (prop) + { + PropVariantClear(prop); + hr = PropertyStorage_PropVariantCopy(prop, propvar, This->codePage, cp); + } + else + { + prop = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, + sizeof(PROPVARIANT)); + if (prop) + { + hr = PropertyStorage_PropVariantCopy(prop, propvar, This->codePage, cp); + if (SUCCEEDED(hr)) + { + dictionary_insert(This->propid_to_prop, UlongToPtr(propid), prop); + if (propid > This->highestProp) + This->highestProp = propid; + } + else + HeapFree(GetProcessHeap(), 0, prop); + } + else + hr = STG_E_INSUFFICIENTMEMORY; + } + return hr; +} + +/* Adds the name srcName to the name dictionaries, mapped to property ID id. + * srcName is encoded in code page cp, and is converted to This->codePage. + * If cp is CP_UNICODE, srcName is actually a unicode string. + * As a side effect, may change This->format to 1 if srcName is too long for + * a version 0 property storage. + * Doesn't validate id. + */ +static HRESULT PropertyStorage_StoreNameWithId(PropertyStorage_impl *This, + LPCSTR srcName, UINT cp, PROPID id) +{ + LPSTR name; + HRESULT hr; + + assert(srcName); + + hr = PropertyStorage_StringCopy(srcName, cp, &name, This->codePage); + if (SUCCEEDED(hr)) + { + if (This->codePage == CP_UNICODE) + { + if (lstrlenW((LPWSTR)name) >= MAX_VERSION_0_PROP_NAME_LENGTH) + This->format = 1; + } + else + { + if (strlen(name) >= MAX_VERSION_0_PROP_NAME_LENGTH) + This->format = 1; + } + TRACE("Adding prop name %s, propid %ld\n", + This->codePage == CP_UNICODE ? debugstr_w((LPCWSTR)name) : + debugstr_a(name), id); + dictionary_insert(This->name_to_propid, name, UlongToPtr(id)); + dictionary_insert(This->propid_to_name, UlongToPtr(id), name); + } + return hr; +} + +/************************************************************************ + * IPropertyStorage_fnWriteMultiple (IPropertyStorage) + */ +static HRESULT WINAPI IPropertyStorage_fnWriteMultiple( + IPropertyStorage* iface, + ULONG cpspec, + const PROPSPEC rgpspec[], + const PROPVARIANT rgpropvar[], + PROPID propidNameFirst) +{ + PropertyStorage_impl *This = impl_from_IPropertyStorage(iface); + HRESULT hr = S_OK; + ULONG i; + + TRACE("%p, %lu, %p, %p.\n", iface, cpspec, rgpspec, rgpropvar); + + if (cpspec && (!rgpspec || !rgpropvar)) + return E_INVALIDARG; + if (!(This->grfMode & STGM_READWRITE)) + return STG_E_ACCESSDENIED; + EnterCriticalSection(&This->cs); + This->dirty = TRUE; + This->originatorOS = (DWORD)MAKELONG(LOWORD(GetVersion()), + PROPSETHDR_OSVER_KIND_WIN32) ; + for (i = 0; i < cpspec; i++) + { + if (rgpspec[i].ulKind == PRSPEC_LPWSTR) + { + PROPVARIANT *prop = PropertyStorage_FindPropertyByName(This, + rgpspec[i].lpwstr); + + if (prop) + PropVariantCopy(prop, &rgpropvar[i]); + else + { + /* Note that I don't do the special cases here that I do below, + * because naming the special PIDs isn't supported. + */ + if (propidNameFirst < PID_FIRST_USABLE || + propidNameFirst >= PID_MIN_READONLY) + hr = STG_E_INVALIDPARAMETER; + else + { + PROPID nextId = max(propidNameFirst, This->highestProp + 1); + + hr = PropertyStorage_StoreNameWithId(This, + (LPCSTR)rgpspec[i].lpwstr, CP_UNICODE, nextId); + if (SUCCEEDED(hr)) + hr = PropertyStorage_StorePropWithId(This, nextId, + &rgpropvar[i], GetACP()); + } + } + } + else + { + switch (rgpspec[i].propid) + { + case PID_DICTIONARY: + /* Can't set the dictionary */ + hr = STG_E_INVALIDPARAMETER; + break; + case PID_CODEPAGE: + /* Can only set the code page if nothing else has been set */ + if (dictionary_num_entries(This->propid_to_prop) == 0 && + rgpropvar[i].vt == VT_I2) + { + This->codePage = (USHORT)rgpropvar[i].iVal; + if (This->codePage == CP_UNICODE) + This->grfFlags &= ~PROPSETFLAG_ANSI; + else + This->grfFlags |= PROPSETFLAG_ANSI; + } + else + hr = STG_E_INVALIDPARAMETER; + break; + case PID_LOCALE: + /* Can only set the locale if nothing else has been set */ + if (dictionary_num_entries(This->propid_to_prop) == 0 && + rgpropvar[i].vt == VT_I4) + This->locale = rgpropvar[i].lVal; + else + hr = STG_E_INVALIDPARAMETER; + break; + case PID_ILLEGAL: + /* silently ignore like MSDN says */ + break; + default: + if (rgpspec[i].propid >= PID_MIN_READONLY) + hr = STG_E_INVALIDPARAMETER; + else + hr = PropertyStorage_StorePropWithId(This, + rgpspec[i].propid, &rgpropvar[i], GetACP()); + } + } + } + if (This->grfFlags & PROPSETFLAG_UNBUFFERED) + IPropertyStorage_Commit(iface, STGC_DEFAULT); + LeaveCriticalSection(&This->cs); + return hr; +} + +/************************************************************************ + * IPropertyStorage_fnDeleteMultiple (IPropertyStorage) + */ +static HRESULT WINAPI IPropertyStorage_fnDeleteMultiple( + IPropertyStorage* iface, + ULONG cpspec, + const PROPSPEC rgpspec[]) +{ + PropertyStorage_impl *This = impl_from_IPropertyStorage(iface); + ULONG i; + HRESULT hr; + + TRACE("%p, %ld, %p.\n", iface, cpspec, rgpspec); + + if (cpspec && !rgpspec) + return E_INVALIDARG; + if (!(This->grfMode & STGM_READWRITE)) + return STG_E_ACCESSDENIED; + hr = S_OK; + EnterCriticalSection(&This->cs); + This->dirty = TRUE; + for (i = 0; i < cpspec; i++) + { + if (rgpspec[i].ulKind == PRSPEC_LPWSTR) + { + void *propid; + + if (dictionary_find(This->name_to_propid, rgpspec[i].lpwstr, &propid)) + dictionary_remove(This->propid_to_prop, propid); + } + else + { + if (rgpspec[i].propid >= PID_FIRST_USABLE && + rgpspec[i].propid < PID_MIN_READONLY) + dictionary_remove(This->propid_to_prop, UlongToPtr(rgpspec[i].propid)); + else + hr = STG_E_INVALIDPARAMETER; + } + } + if (This->grfFlags & PROPSETFLAG_UNBUFFERED) + IPropertyStorage_Commit(iface, STGC_DEFAULT); + LeaveCriticalSection(&This->cs); + return hr; +} + +/************************************************************************ + * IPropertyStorage_fnReadPropertyNames (IPropertyStorage) + */ +static HRESULT WINAPI IPropertyStorage_fnReadPropertyNames( + IPropertyStorage* iface, + ULONG cpropid, + const PROPID rgpropid[], + LPOLESTR rglpwstrName[]) +{ + PropertyStorage_impl *This = impl_from_IPropertyStorage(iface); + ULONG i; + HRESULT hr = S_FALSE; + + TRACE("%p, %ld, %p, %p.\n", iface, cpropid, rgpropid, rglpwstrName); + + if (cpropid && (!rgpropid || !rglpwstrName)) + return E_INVALIDARG; + EnterCriticalSection(&This->cs); + for (i = 0; i < cpropid && SUCCEEDED(hr); i++) + { + LPWSTR name = PropertyStorage_FindPropertyNameById(This, rgpropid[i]); + + if (name) + { + size_t len = lstrlenW(name); + + hr = S_OK; + rglpwstrName[i] = CoTaskMemAlloc((len + 1) * sizeof(WCHAR)); + if (rglpwstrName[i]) + memcpy(rglpwstrName[i], name, (len + 1) * sizeof(WCHAR)); + else + hr = STG_E_INSUFFICIENTMEMORY; + } + else + rglpwstrName[i] = NULL; + } + LeaveCriticalSection(&This->cs); + return hr; +} + +/************************************************************************ + * IPropertyStorage_fnWritePropertyNames (IPropertyStorage) + */ +static HRESULT WINAPI IPropertyStorage_fnWritePropertyNames( + IPropertyStorage* iface, + ULONG cpropid, + const PROPID rgpropid[], + const LPOLESTR rglpwstrName[]) +{ + PropertyStorage_impl *This = impl_from_IPropertyStorage(iface); + ULONG i; + HRESULT hr; + + TRACE("%p, %lu, %p, %p.\n", iface, cpropid, rgpropid, rglpwstrName); + + if (cpropid && (!rgpropid || !rglpwstrName)) + return E_INVALIDARG; + if (!(This->grfMode & STGM_READWRITE)) + return STG_E_ACCESSDENIED; + hr = S_OK; + EnterCriticalSection(&This->cs); + This->dirty = TRUE; + for (i = 0; SUCCEEDED(hr) && i < cpropid; i++) + { + if (rgpropid[i] != PID_ILLEGAL) + hr = PropertyStorage_StoreNameWithId(This, (LPCSTR)rglpwstrName[i], + CP_UNICODE, rgpropid[i]); + } + if (This->grfFlags & PROPSETFLAG_UNBUFFERED) + IPropertyStorage_Commit(iface, STGC_DEFAULT); + LeaveCriticalSection(&This->cs); + return hr; +} + +/************************************************************************ + * IPropertyStorage_fnDeletePropertyNames (IPropertyStorage) + */ +static HRESULT WINAPI IPropertyStorage_fnDeletePropertyNames( + IPropertyStorage* iface, + ULONG cpropid, + const PROPID rgpropid[]) +{ + PropertyStorage_impl *This = impl_from_IPropertyStorage(iface); + ULONG i; + HRESULT hr; + + TRACE("%p, %ld, %p.\n", iface, cpropid, rgpropid); + + if (cpropid && !rgpropid) + return E_INVALIDARG; + if (!(This->grfMode & STGM_READWRITE)) + return STG_E_ACCESSDENIED; + hr = S_OK; + EnterCriticalSection(&This->cs); + This->dirty = TRUE; + for (i = 0; i < cpropid; i++) + { + LPWSTR name = NULL; + + if (dictionary_find(This->propid_to_name, UlongToPtr(rgpropid[i]), (void **)&name)) + { + dictionary_remove(This->propid_to_name, UlongToPtr(rgpropid[i])); + dictionary_remove(This->name_to_propid, name); + } + } + if (This->grfFlags & PROPSETFLAG_UNBUFFERED) + IPropertyStorage_Commit(iface, STGC_DEFAULT); + LeaveCriticalSection(&This->cs); + return hr; +} + +/************************************************************************ + * IPropertyStorage_fnCommit (IPropertyStorage) + */ +static HRESULT WINAPI IPropertyStorage_fnCommit( + IPropertyStorage* iface, + DWORD grfCommitFlags) +{ + PropertyStorage_impl *This = impl_from_IPropertyStorage(iface); + HRESULT hr; + + TRACE("%p, %#lx.\n", iface, grfCommitFlags); + + if (!(This->grfMode & STGM_READWRITE)) + return STG_E_ACCESSDENIED; + EnterCriticalSection(&This->cs); + if (This->dirty) + hr = PropertyStorage_WriteToStream(This); + else + hr = S_OK; + LeaveCriticalSection(&This->cs); + return hr; +} + +/************************************************************************ + * IPropertyStorage_fnRevert (IPropertyStorage) + */ +static HRESULT WINAPI IPropertyStorage_fnRevert( + IPropertyStorage* iface) +{ + HRESULT hr; + PropertyStorage_impl *This = impl_from_IPropertyStorage(iface); + + TRACE("%p\n", iface); + + EnterCriticalSection(&This->cs); + if (This->dirty) + { + PropertyStorage_DestroyDictionaries(This); + hr = PropertyStorage_CreateDictionaries(This); + if (SUCCEEDED(hr)) + hr = PropertyStorage_ReadFromStream(This); + } + else + hr = S_OK; + LeaveCriticalSection(&This->cs); + return hr; +} + +/************************************************************************ + * IPropertyStorage_fnEnum (IPropertyStorage) + */ +static HRESULT WINAPI IPropertyStorage_fnEnum(IPropertyStorage *iface, IEnumSTATPROPSTG **ppenum) +{ + PropertyStorage_impl *storage = impl_from_IPropertyStorage(iface); + + TRACE("%p, %p.\n", iface, ppenum); + + return create_enum_stat_prop_stg(storage, ppenum); +} + +/************************************************************************ + * IPropertyStorage_fnSetTimes (IPropertyStorage) + */ +static HRESULT WINAPI IPropertyStorage_fnSetTimes( + IPropertyStorage* iface, + const FILETIME* pctime, + const FILETIME* patime, + const FILETIME* pmtime) +{ + FIXME("\n"); + return E_NOTIMPL; +} + +/************************************************************************ + * IPropertyStorage_fnSetClass (IPropertyStorage) + */ +static HRESULT WINAPI IPropertyStorage_fnSetClass( + IPropertyStorage* iface, + REFCLSID clsid) +{ + PropertyStorage_impl *This = impl_from_IPropertyStorage(iface); + + TRACE("%p, %s\n", iface, debugstr_guid(clsid)); + + if (!clsid) + return E_INVALIDARG; + if (!(This->grfMode & STGM_READWRITE)) + return STG_E_ACCESSDENIED; + This->clsid = *clsid; + This->dirty = TRUE; + if (This->grfFlags & PROPSETFLAG_UNBUFFERED) + IPropertyStorage_Commit(iface, STGC_DEFAULT); + return S_OK; +} + +/************************************************************************ + * IPropertyStorage_fnStat (IPropertyStorage) + */ +static HRESULT WINAPI IPropertyStorage_fnStat( + IPropertyStorage* iface, + STATPROPSETSTG* statpsstg) +{ + PropertyStorage_impl *This = impl_from_IPropertyStorage(iface); + STATSTG stat; + HRESULT hr; + + TRACE("%p, %p\n", iface, statpsstg); + + if (!statpsstg) + return E_INVALIDARG; + + hr = IStream_Stat(This->stm, &stat, STATFLAG_NONAME); + if (SUCCEEDED(hr)) + { + statpsstg->fmtid = This->fmtid; + statpsstg->clsid = This->clsid; + statpsstg->grfFlags = This->grfFlags; + statpsstg->mtime = stat.mtime; + statpsstg->ctime = stat.ctime; + statpsstg->atime = stat.atime; + statpsstg->dwOSVersion = This->originatorOS; + } + return hr; +} + +static int PropertyStorage_PropNameCompare(const void *a, const void *b, + void *extra) +{ + PropertyStorage_impl *This = extra; + + if (This->codePage == CP_UNICODE) + { + TRACE("(%s, %s)\n", debugstr_w(a), debugstr_w(b)); + if (This->grfFlags & PROPSETFLAG_CASE_SENSITIVE) + return wcscmp(a, b); + else + return lstrcmpiW(a, b); + } + else + { + TRACE("(%s, %s)\n", debugstr_a(a), debugstr_a(b)); + if (This->grfFlags & PROPSETFLAG_CASE_SENSITIVE) + return lstrcmpA(a, b); + else + return lstrcmpiA(a, b); + } +} + +static void PropertyStorage_PropNameDestroy(void *k, void *d, void *extra) +{ + CoTaskMemFree(k); +} + +static int PropertyStorage_PropCompare(const void *a, const void *b, + void *extra) +{ + TRACE("%lu, %lu.\n", PtrToUlong(a), PtrToUlong(b)); + return PtrToUlong(a) - PtrToUlong(b); +} + +static void PropertyStorage_PropertyDestroy(void *k, void *d, void *extra) +{ + PropVariantClear(d); + HeapFree(GetProcessHeap(), 0, d); +} + +#ifdef WORDS_BIGENDIAN +/* Swaps each character in str to or from little endian; assumes the conversion + * is symmetric, that is, that lendian16toh is equivalent to htole16. + */ +static void PropertyStorage_ByteSwapString(LPWSTR str, size_t len) +{ + DWORD i; + + /* Swap characters to host order. + * FIXME: alignment? + */ + for (i = 0; i < len; i++) + str[i] = lendian16toh(str[i]); +} +#else +#define PropertyStorage_ByteSwapString(s, l) +#endif + +static void* WINAPI Allocate_CoTaskMemAlloc(void *this, ULONG size) +{ + return CoTaskMemAlloc(size); +} + +struct read_buffer +{ + BYTE *data; + size_t size; +}; + +static HRESULT buffer_test_offset(const struct read_buffer *buffer, size_t offset, size_t len) +{ + return len > buffer->size || offset > buffer->size - len ? STG_E_READFAULT : S_OK; +} + +static HRESULT buffer_read_uint64(const struct read_buffer *buffer, size_t offset, ULARGE_INTEGER *data) +{ + HRESULT hr; + + if (SUCCEEDED(hr = buffer_test_offset(buffer, offset, sizeof(*data)))) + StorageUtl_ReadULargeInteger(buffer->data, offset, data); + + return hr; +} + +static HRESULT buffer_read_dword(const struct read_buffer *buffer, size_t offset, DWORD *data) +{ + HRESULT hr; + + if (SUCCEEDED(hr = buffer_test_offset(buffer, offset, sizeof(*data)))) + StorageUtl_ReadDWord(buffer->data, offset, data); + + return hr; +} + +static HRESULT buffer_read_word(const struct read_buffer *buffer, size_t offset, WORD *data) +{ + HRESULT hr; + + if (SUCCEEDED(hr = buffer_test_offset(buffer, offset, sizeof(*data)))) + StorageUtl_ReadWord(buffer->data, offset, data); + + return hr; +} + +static HRESULT buffer_read_byte(const struct read_buffer *buffer, size_t offset, BYTE *data) +{ + HRESULT hr; + + if (SUCCEEDED(hr = buffer_test_offset(buffer, offset, sizeof(*data)))) + *data = *(buffer->data + offset); + + return hr; +} + +static HRESULT buffer_read_len(const struct read_buffer *buffer, size_t offset, void *dest, size_t len) +{ + HRESULT hr; + + if (SUCCEEDED(hr = buffer_test_offset(buffer, offset, len))) + memcpy(dest, buffer->data + offset, len); + + return hr; +} + +static HRESULT propertystorage_read_scalar(PROPVARIANT *prop, const struct read_buffer *buffer, size_t offset, + UINT codepage, void* (WINAPI *allocate)(void *this, ULONG size), void *allocate_data) +{ + HRESULT hr; + + assert(!(prop->vt & (VT_ARRAY | VT_VECTOR))); + + switch (prop->vt) + { + case VT_EMPTY: + case VT_NULL: + hr = S_OK; + break; + case VT_I1: + hr = buffer_read_byte(buffer, offset, (BYTE *)&prop->cVal); + TRACE("Read char 0x%x ('%c')\n", prop->cVal, prop->cVal); + break; + case VT_UI1: + hr = buffer_read_byte(buffer, offset, &prop->bVal); + TRACE("Read byte 0x%x\n", prop->bVal); + break; + case VT_BOOL: + hr = buffer_read_word(buffer, offset, (WORD *)&prop->boolVal); + TRACE("Read bool %d\n", prop->boolVal); + break; + case VT_I2: + hr = buffer_read_word(buffer, offset, (WORD *)&prop->iVal); + TRACE("Read short %d\n", prop->iVal); + break; + case VT_UI2: + hr = buffer_read_word(buffer, offset, &prop->uiVal); + TRACE("Read ushort %d\n", prop->uiVal); + break; + case VT_INT: + case VT_I4: + hr = buffer_read_dword(buffer, offset, (DWORD *)&prop->lVal); + TRACE("Read long %ld\n", prop->lVal); + break; + case VT_UINT: + case VT_UI4: + hr = buffer_read_dword(buffer, offset, &prop->ulVal); + TRACE("Read ulong %ld\n", prop->ulVal); + break; + case VT_I8: + hr = buffer_read_uint64(buffer, offset, (ULARGE_INTEGER *)&prop->hVal); + TRACE("Read long long %s\n", wine_dbgstr_longlong(prop->hVal.QuadPart)); + break; + case VT_UI8: + hr = buffer_read_uint64(buffer, offset, &prop->uhVal); + TRACE("Read ulong long %s\n", wine_dbgstr_longlong(prop->uhVal.QuadPart)); + break; + case VT_R8: + hr = buffer_read_len(buffer, offset, &prop->dblVal, sizeof(prop->dblVal)); + TRACE("Read double %f\n", prop->dblVal); + break; + case VT_LPSTR: + { + DWORD count; + + if (FAILED(hr = buffer_read_dword(buffer, offset, &count))) + break; + + offset += sizeof(DWORD); + + if (codepage == CP_UNICODE && count % sizeof(WCHAR)) + { + WARN("Unicode string has odd number of bytes\n"); + hr = STG_E_INVALIDHEADER; + } + else + { + prop->pszVal = allocate(allocate_data, count); + if (prop->pszVal) + { + if (FAILED(hr = buffer_read_len(buffer, offset, prop->pszVal, count))) + break; + + /* This is stored in the code page specified in codepage. + * Don't convert it, the caller will just store it as-is. + */ + if (codepage == CP_UNICODE) + { + /* Make sure it's NULL-terminated */ + prop->pszVal[count / sizeof(WCHAR) - 1] = '\0'; + TRACE("Read string value %s\n", + debugstr_w(prop->pwszVal)); + } + else + { + /* Make sure it's NULL-terminated */ + prop->pszVal[count - 1] = '\0'; + TRACE("Read string value %s\n", debugstr_a(prop->pszVal)); + } + } + else + hr = STG_E_INSUFFICIENTMEMORY; + } + break; + } + case VT_BSTR: + { + DWORD count, wcount; + + if (FAILED(hr = buffer_read_dword(buffer, offset, &count))) + break; + + offset += sizeof(DWORD); + + if (codepage == CP_UNICODE && count % sizeof(WCHAR)) + { + WARN("Unicode string has odd number of bytes\n"); + hr = STG_E_INVALIDHEADER; + } + else + { + if (codepage == CP_UNICODE) + wcount = count / sizeof(WCHAR); + else + { + if (FAILED(hr = buffer_test_offset(buffer, offset, count))) + break; + wcount = MultiByteToWideChar(codepage, 0, (LPCSTR)(buffer->data + offset), count, NULL, 0); + } + + prop->bstrVal = SysAllocStringLen(NULL, wcount); /* FIXME: use allocator? */ + + if (prop->bstrVal) + { + if (codepage == CP_UNICODE) + hr = buffer_read_len(buffer, offset, prop->bstrVal, count); + else + MultiByteToWideChar(codepage, 0, (LPCSTR)(buffer->data + offset), count, prop->bstrVal, wcount); + + prop->bstrVal[wcount - 1] = '\0'; + TRACE("Read string value %s\n", debugstr_w(prop->bstrVal)); + } + else + hr = STG_E_INSUFFICIENTMEMORY; + } + break; + } + case VT_BLOB: + { + DWORD count; + + if (FAILED(hr = buffer_read_dword(buffer, offset, &count))) + break; + + offset += sizeof(DWORD); + + prop->blob.cbSize = count; + prop->blob.pBlobData = allocate(allocate_data, count); + if (prop->blob.pBlobData) + { + hr = buffer_read_len(buffer, offset, prop->blob.pBlobData, count); + TRACE("Read blob value of size %ld\n", count); + } + else + hr = STG_E_INSUFFICIENTMEMORY; + break; + } + case VT_LPWSTR: + { + DWORD count; + + if (FAILED(hr = buffer_read_dword(buffer, offset, &count))) + break; + + offset += sizeof(DWORD); + + prop->pwszVal = allocate(allocate_data, count * sizeof(WCHAR)); + if (prop->pwszVal) + { + if (SUCCEEDED(hr = buffer_read_len(buffer, offset, prop->pwszVal, count * sizeof(WCHAR)))) + { + /* make sure string is NULL-terminated */ + prop->pwszVal[count - 1] = '\0'; + PropertyStorage_ByteSwapString(prop->pwszVal, count); + TRACE("Read string value %s\n", debugstr_w(prop->pwszVal)); + } + } + else + hr = STG_E_INSUFFICIENTMEMORY; + break; + } + case VT_FILETIME: + hr = buffer_read_uint64(buffer, offset, (ULARGE_INTEGER *)&prop->filetime); + break; + case VT_CF: + { + DWORD len = 0, tag = 0; + + if (SUCCEEDED(hr = buffer_read_dword(buffer, offset, &len))) + hr = buffer_read_dword(buffer, offset + sizeof(DWORD), &tag); + if (FAILED(hr)) + break; + + offset += 2 * sizeof(DWORD); + + if (len > 8) + { + len -= 8; + prop->pclipdata = allocate(allocate_data, sizeof (CLIPDATA)); + prop->pclipdata->cbSize = len; + prop->pclipdata->ulClipFmt = tag; + prop->pclipdata->pClipData = allocate(allocate_data, len - sizeof(prop->pclipdata->ulClipFmt)); + hr = buffer_read_len(buffer, offset, prop->pclipdata->pClipData, len - sizeof(prop->pclipdata->ulClipFmt)); + } + else + hr = STG_E_INVALIDPARAMETER; + } + break; + case VT_CLSID: + if (!(prop->puuid = allocate(allocate_data, sizeof (*prop->puuid)))) + return STG_E_INSUFFICIENTMEMORY; + + if (SUCCEEDED(hr = buffer_test_offset(buffer, offset, sizeof(*prop->puuid)))) + StorageUtl_ReadGUID(buffer->data, offset, prop->puuid); + + break; + default: + FIXME("unsupported type %d\n", prop->vt); + hr = STG_E_INVALIDPARAMETER; + } + + return hr; +} + +static size_t propertystorage_get_elemsize(const PROPVARIANT *prop) +{ + if (!(prop->vt & VT_VECTOR)) + return 0; + + switch (prop->vt & ~VT_VECTOR) + { + case VT_I1: return sizeof(*prop->cac.pElems); + case VT_UI1: return sizeof(*prop->caub.pElems); + case VT_I2: return sizeof(*prop->cai.pElems); + case VT_UI2: return sizeof(*prop->caui.pElems); + case VT_BOOL: return sizeof(*prop->cabool.pElems); + case VT_I4: return sizeof(*prop->cal.pElems); + case VT_UI4: return sizeof(*prop->caul.pElems); + case VT_R4: return sizeof(*prop->caflt.pElems); + case VT_ERROR: return sizeof(*prop->cascode.pElems); + case VT_I8: return sizeof(*prop->cah.pElems); + case VT_UI8: return sizeof(*prop->cauh.pElems); + case VT_R8: return sizeof(*prop->cadbl.pElems); + case VT_CY: return sizeof(*prop->cacy.pElems); + case VT_DATE: return sizeof(*prop->cadate.pElems); + case VT_FILETIME: return sizeof(*prop->cafiletime.pElems); + case VT_CLSID: return sizeof(*prop->cauuid.pElems); + case VT_VARIANT: return sizeof(*prop->capropvar.pElems); + default: + FIXME("Unhandled type %#x.\n", prop->vt); + return 0; + } +} + +HRESULT WINAPI PropertyStorage_ReadProperty(PROPVARIANT *prop, const struct read_buffer *buffer, + SIZE_T offset, UINT codepage, void* (WINAPI *allocate)(void *this, ULONG size), void *allocate_data) +{ + HRESULT hr; + DWORD vt; + + assert(prop); + assert(buffer->data); + + if (FAILED(hr = buffer_read_dword(buffer, offset, &vt))) + return hr; + + offset += sizeof(DWORD); + prop->vt = vt; + + if (prop->vt & VT_VECTOR) + { + DWORD count, i; + + switch (prop->vt & VT_VECTOR) + { + case VT_BSTR: + case VT_VARIANT: + case VT_LPSTR: + case VT_LPWSTR: + case VT_CF: + FIXME("Vector with variable length elements are not supported.\n"); + return STG_E_INVALIDPARAMETER; + default: + ; + } + + if (SUCCEEDED(hr = buffer_read_dword(buffer, offset, &count))) + { + size_t elemsize = propertystorage_get_elemsize(prop); + PROPVARIANT elem; + + offset += sizeof(DWORD); + + if ((prop->capropvar.pElems = allocate(allocate_data, elemsize * count))) + { + prop->capropvar.cElems = count; + elem.vt = prop->vt & ~VT_VECTOR; + + for (i = 0; i < count; ++i) + { + if (SUCCEEDED(hr = propertystorage_read_scalar(&elem, buffer, offset + i * elemsize, codepage, + allocate, allocate_data))) + { + memcpy(&prop->capropvar.pElems[i], &elem.lVal, elemsize); + } + } + } + else + hr = STG_E_INSUFFICIENTMEMORY; + } + } + else if (prop->vt & VT_ARRAY) + { + FIXME("VT_ARRAY properties are not supported.\n"); + hr = STG_E_INVALIDPARAMETER; + } + else + hr = propertystorage_read_scalar(prop, buffer, offset, codepage, allocate, allocate_data); + + return hr; +} + +static HRESULT PropertyStorage_ReadHeaderFromStream(IStream *stm, + PROPERTYSETHEADER *hdr) +{ + BYTE buf[sizeof(PROPERTYSETHEADER)]; + ULONG count = 0; + HRESULT hr; + + assert(stm); + assert(hdr); + hr = IStream_Read(stm, buf, sizeof(buf), &count); + if (SUCCEEDED(hr)) + { + if (count != sizeof(buf)) + { + WARN("read only %ld\n", count); + hr = STG_E_INVALIDHEADER; + } + else + { + StorageUtl_ReadWord(buf, offsetof(PROPERTYSETHEADER, wByteOrder), + &hdr->wByteOrder); + StorageUtl_ReadWord(buf, offsetof(PROPERTYSETHEADER, wFormat), + &hdr->wFormat); + StorageUtl_ReadDWord(buf, offsetof(PROPERTYSETHEADER, dwOSVer), + &hdr->dwOSVer); + StorageUtl_ReadGUID(buf, offsetof(PROPERTYSETHEADER, clsid), + &hdr->clsid); + StorageUtl_ReadDWord(buf, offsetof(PROPERTYSETHEADER, reserved), + &hdr->reserved); + } + } + TRACE("returning %#lx\n", hr); + return hr; +} + +static HRESULT PropertyStorage_ReadFmtIdOffsetFromStream(IStream *stm, + FORMATIDOFFSET *fmt) +{ + BYTE buf[sizeof(FORMATIDOFFSET)]; + ULONG count = 0; + HRESULT hr; + + assert(stm); + assert(fmt); + hr = IStream_Read(stm, buf, sizeof(buf), &count); + if (SUCCEEDED(hr)) + { + if (count != sizeof(buf)) + { + WARN("read only %ld\n", count); + hr = STG_E_INVALIDHEADER; + } + else + { + StorageUtl_ReadGUID(buf, offsetof(FORMATIDOFFSET, fmtid), + &fmt->fmtid); + StorageUtl_ReadDWord(buf, offsetof(FORMATIDOFFSET, dwOffset), + &fmt->dwOffset); + } + } + TRACE("returning %#lx\n", hr); + return hr; +} + +static HRESULT PropertyStorage_ReadSectionHeaderFromStream(IStream *stm, + PROPERTYSECTIONHEADER *hdr) +{ + BYTE buf[sizeof(PROPERTYSECTIONHEADER)]; + ULONG count = 0; + HRESULT hr; + + assert(stm); + assert(hdr); + hr = IStream_Read(stm, buf, sizeof(buf), &count); + if (SUCCEEDED(hr)) + { + if (count != sizeof(buf)) + { + WARN("read only %ld\n", count); + hr = STG_E_INVALIDHEADER; + } + else + { + StorageUtl_ReadDWord(buf, offsetof(PROPERTYSECTIONHEADER, + cbSection), &hdr->cbSection); + StorageUtl_ReadDWord(buf, offsetof(PROPERTYSECTIONHEADER, + cProperties), &hdr->cProperties); + } + } + TRACE("returning %#lx\n", hr); + return hr; +} + +/* Reads the dictionary from the memory buffer beginning at ptr. Interprets + * the entries according to the values of This->codePage and This->locale. + */ +static HRESULT PropertyStorage_ReadDictionary(PropertyStorage_impl *This, const struct read_buffer *buffer, + size_t offset) +{ + DWORD numEntries, i; + HRESULT hr; + + assert(This->name_to_propid); + assert(This->propid_to_name); + + if (FAILED(hr = buffer_read_dword(buffer, offset, &numEntries))) + return hr; + + TRACE("Reading %ld entries:\n", numEntries); + + offset += sizeof(DWORD); + + for (i = 0; SUCCEEDED(hr) && i < numEntries; i++) + { + PROPID propid; + DWORD cbEntry; + WCHAR ch = 0; + + if (SUCCEEDED(hr = buffer_read_dword(buffer, offset, &propid))) + hr = buffer_read_dword(buffer, offset + sizeof(PROPID), &cbEntry); + if (FAILED(hr)) + break; + + offset += sizeof(PROPID) + sizeof(DWORD); + + if (FAILED(hr = buffer_test_offset(buffer, offset, This->codePage == CP_UNICODE ? + ALIGNED_LENGTH(cbEntry * sizeof(WCHAR), 3) : cbEntry))) + { + WARN("Broken name length for entry %ld.\n", i); + return hr; + } + + /* Make sure the source string is NULL-terminated */ + if (This->codePage != CP_UNICODE) + buffer_read_byte(buffer, offset + cbEntry - 1, (BYTE *)&ch); + else + buffer_read_word(buffer, offset + (cbEntry - 1) * sizeof(WCHAR), &ch); + + if (ch) + { + WARN("Dictionary entry name %ld is not null-terminated.\n", i); + return E_FAIL; + } + + TRACE("Reading entry with ID %#lx, %ld chars, name %s.\n", propid, cbEntry, This->codePage == CP_UNICODE ? + debugstr_wn((WCHAR *)buffer->data, cbEntry) : debugstr_an((char *)buffer->data, cbEntry)); + + hr = PropertyStorage_StoreNameWithId(This, (char *)buffer->data + offset, This->codePage, propid); + /* Unicode entries are padded to DWORD boundaries */ + if (This->codePage == CP_UNICODE) + cbEntry = ALIGNED_LENGTH(cbEntry * sizeof(WCHAR), 3); + + offset += cbEntry; + } + + return hr; +} + +static HRESULT PropertyStorage_ReadFromStream(PropertyStorage_impl *This) +{ + struct read_buffer read_buffer; + PROPERTYSETHEADER hdr; + FORMATIDOFFSET fmtOffset; + PROPERTYSECTIONHEADER sectionHdr; + LARGE_INTEGER seek; + ULONG i; + STATSTG stat; + HRESULT hr; + BYTE *buf = NULL; + ULONG count = 0; + DWORD dictOffset = 0; + + This->dirty = FALSE; + This->highestProp = 0; + hr = IStream_Stat(This->stm, &stat, STATFLAG_NONAME); + if (FAILED(hr)) + goto end; + if (stat.cbSize.HighPart) + { + WARN("stream too big\n"); + /* maximum size varies, but it can't be this big */ + hr = STG_E_INVALIDHEADER; + goto end; + } + if (stat.cbSize.LowPart == 0) + { + /* empty stream is okay */ + hr = S_OK; + goto end; + } + else if (stat.cbSize.LowPart < sizeof(PROPERTYSETHEADER) + + sizeof(FORMATIDOFFSET)) + { + WARN("stream too small\n"); + hr = STG_E_INVALIDHEADER; + goto end; + } + seek.QuadPart = 0; + hr = IStream_Seek(This->stm, seek, STREAM_SEEK_SET, NULL); + if (FAILED(hr)) + goto end; + hr = PropertyStorage_ReadHeaderFromStream(This->stm, &hdr); + /* I've only seen reserved == 1, but the article says I shouldn't disallow + * higher values. + */ + if (hdr.wByteOrder != PROPSETHDR_BYTEORDER_MAGIC || hdr.reserved < 1) + { + WARN("bad magic in prop set header\n"); + hr = STG_E_INVALIDHEADER; + goto end; + } + if (hdr.wFormat != 0 && hdr.wFormat != 1) + { + WARN("bad format version %d\n", hdr.wFormat); + hr = STG_E_INVALIDHEADER; + goto end; + } + This->format = hdr.wFormat; + This->clsid = hdr.clsid; + This->originatorOS = hdr.dwOSVer; + if (PROPSETHDR_OSVER_KIND(hdr.dwOSVer) == PROPSETHDR_OSVER_KIND_MAC) + WARN("File comes from a Mac, strings will probably be screwed up\n"); + hr = PropertyStorage_ReadFmtIdOffsetFromStream(This->stm, &fmtOffset); + if (FAILED(hr)) + goto end; + if (fmtOffset.dwOffset > stat.cbSize.LowPart) + { + WARN("invalid offset %ld (stream length is %ld)\n", fmtOffset.dwOffset, stat.cbSize.LowPart); + hr = STG_E_INVALIDHEADER; + goto end; + } + /* wackiness alert: if the format ID is FMTID_DocSummaryInformation, there + * follows not one, but two sections. The first contains the standard properties + * for the document summary information, and the second consists of user-defined + * properties. This is the only case in which multiple sections are + * allowed. + * Reading the second stream isn't implemented yet. + */ + hr = PropertyStorage_ReadSectionHeaderFromStream(This->stm, §ionHdr); + if (FAILED(hr)) + goto end; + /* The section size includes the section header, so check it */ + if (sectionHdr.cbSection < sizeof(PROPERTYSECTIONHEADER)) + { + WARN("section header too small, got %ld\n", sectionHdr.cbSection); + hr = STG_E_INVALIDHEADER; + goto end; + } + buf = HeapAlloc(GetProcessHeap(), 0, sectionHdr.cbSection - + sizeof(PROPERTYSECTIONHEADER)); + if (!buf) + { + hr = STG_E_INSUFFICIENTMEMORY; + goto end; + } + read_buffer.data = buf; + read_buffer.size = sectionHdr.cbSection - sizeof(sectionHdr); + + hr = IStream_Read(This->stm, read_buffer.data, read_buffer.size, &count); + if (FAILED(hr)) + goto end; + TRACE("Reading %ld properties:\n", sectionHdr.cProperties); + for (i = 0; SUCCEEDED(hr) && i < sectionHdr.cProperties; i++) + { + PROPERTYIDOFFSET *idOffset = (PROPERTYIDOFFSET *)(read_buffer.data + + i * sizeof(PROPERTYIDOFFSET)); + + if (idOffset->dwOffset < sizeof(PROPERTYSECTIONHEADER) || + idOffset->dwOffset > sectionHdr.cbSection - sizeof(DWORD)) + hr = STG_E_INVALIDPOINTER; + else + { + if (idOffset->propid >= PID_FIRST_USABLE && + idOffset->propid < PID_MIN_READONLY && idOffset->propid > + This->highestProp) + This->highestProp = idOffset->propid; + if (idOffset->propid == PID_DICTIONARY) + { + /* Don't read the dictionary yet, its entries depend on the + * code page. Just store the offset so we know to read it + * later. + */ + dictOffset = idOffset->dwOffset; + TRACE("Dictionary offset is %ld\n", dictOffset); + } + else + { + PROPVARIANT prop; + + PropVariantInit(&prop); + if (SUCCEEDED(PropertyStorage_ReadProperty(&prop, &read_buffer, + idOffset->dwOffset - sizeof(PROPERTYSECTIONHEADER), This->codePage, + Allocate_CoTaskMemAlloc, NULL))) + { + TRACE("Read property with ID %#lx, type %d\n", idOffset->propid, prop.vt); + switch(idOffset->propid) + { + case PID_CODEPAGE: + if (prop.vt == VT_I2) + This->codePage = (USHORT)prop.iVal; + break; + case PID_LOCALE: + if (prop.vt == VT_I4) + This->locale = (LCID)prop.lVal; + break; + case PID_BEHAVIOR: + if (prop.vt == VT_I4 && prop.lVal) + This->grfFlags |= PROPSETFLAG_CASE_SENSITIVE; + /* The format should already be 1, but just in case */ + This->format = 1; + break; + default: + hr = PropertyStorage_StorePropWithId(This, + idOffset->propid, &prop, This->codePage); + } + } + PropVariantClear(&prop); + } + } + } + if (!This->codePage) + { + /* default to Unicode unless told not to, as specified on msdn */ + if (This->grfFlags & PROPSETFLAG_ANSI) + This->codePage = GetACP(); + else + This->codePage = CP_UNICODE; + } + if (!This->locale) + This->locale = LOCALE_SYSTEM_DEFAULT; + TRACE("Code page is %d, locale is %ld\n", This->codePage, This->locale); + if (dictOffset) + hr = PropertyStorage_ReadDictionary(This, &read_buffer, dictOffset - sizeof(PROPERTYSECTIONHEADER)); + +end: + HeapFree(GetProcessHeap(), 0, buf); + if (FAILED(hr)) + { + dictionary_destroy(This->name_to_propid); + This->name_to_propid = NULL; + dictionary_destroy(This->propid_to_name); + This->propid_to_name = NULL; + dictionary_destroy(This->propid_to_prop); + This->propid_to_prop = NULL; + } + return hr; +} + +static void PropertyStorage_MakeHeader(PropertyStorage_impl *This, + PROPERTYSETHEADER *hdr) +{ + assert(hdr); + StorageUtl_WriteWord(&hdr->wByteOrder, 0, PROPSETHDR_BYTEORDER_MAGIC); + StorageUtl_WriteWord(&hdr->wFormat, 0, This->format); + StorageUtl_WriteDWord(&hdr->dwOSVer, 0, This->originatorOS); + StorageUtl_WriteGUID(&hdr->clsid, 0, &This->clsid); + StorageUtl_WriteDWord(&hdr->reserved, 0, 1); +} + +static void PropertyStorage_MakeFmtIdOffset(PropertyStorage_impl *This, + FORMATIDOFFSET *fmtOffset) +{ + assert(fmtOffset); + StorageUtl_WriteGUID(fmtOffset, 0, &This->fmtid); + StorageUtl_WriteDWord(fmtOffset, offsetof(FORMATIDOFFSET, dwOffset), + sizeof(PROPERTYSETHEADER) + sizeof(FORMATIDOFFSET)); +} + +static void PropertyStorage_MakeSectionHdr(DWORD cbSection, DWORD numProps, + PROPERTYSECTIONHEADER *hdr) +{ + assert(hdr); + StorageUtl_WriteDWord(hdr, 0, cbSection); + StorageUtl_WriteDWord(hdr, offsetof(PROPERTYSECTIONHEADER, cProperties), numProps); +} + +static void PropertyStorage_MakePropertyIdOffset(DWORD propid, DWORD dwOffset, + PROPERTYIDOFFSET *propIdOffset) +{ + assert(propIdOffset); + StorageUtl_WriteDWord(propIdOffset, 0, propid); + StorageUtl_WriteDWord(propIdOffset, offsetof(PROPERTYIDOFFSET, dwOffset), dwOffset); +} + +static inline HRESULT PropertyStorage_WriteWStringToStream(IStream *stm, + LPCWSTR str, DWORD len, DWORD *written) +{ +#ifdef WORDS_BIGENDIAN + WCHAR *leStr = HeapAlloc(GetProcessHeap(), 0, len * sizeof(WCHAR)); + HRESULT hr; + + if (!leStr) + return E_OUTOFMEMORY; + memcpy(leStr, str, len * sizeof(WCHAR)); + PropertyStorage_ByteSwapString(leStr, len); + hr = IStream_Write(stm, leStr, len, written); + HeapFree(GetProcessHeap(), 0, leStr); + return hr; +#else + return IStream_Write(stm, str, len * sizeof(WCHAR), written); +#endif +} + +struct DictionaryClosure +{ + HRESULT hr; + DWORD bytesWritten; +}; + +static BOOL PropertyStorage_DictionaryWriter(const void *key, + const void *value, void *extra, void *closure) +{ + PropertyStorage_impl *This = extra; + struct DictionaryClosure *c = closure; + DWORD propid, keyLen; + ULONG count; + + assert(key); + assert(closure); + StorageUtl_WriteDWord(&propid, 0, PtrToUlong(value)); + c->hr = IStream_Write(This->stm, &propid, sizeof(propid), &count); + if (FAILED(c->hr)) + goto end; + c->bytesWritten += sizeof(DWORD); + if (This->codePage == CP_UNICODE) + { + DWORD pad = 0, pad_len; + + StorageUtl_WriteDWord(&keyLen, 0, lstrlenW((LPCWSTR)key) + 1); + c->hr = IStream_Write(This->stm, &keyLen, sizeof(keyLen), &count); + if (FAILED(c->hr)) + goto end; + c->bytesWritten += sizeof(DWORD); + c->hr = PropertyStorage_WriteWStringToStream(This->stm, key, keyLen, + &count); + if (FAILED(c->hr)) + goto end; + keyLen *= sizeof(WCHAR); + c->bytesWritten += keyLen; + + /* Align to 4 bytes. */ + pad_len = sizeof(DWORD) - keyLen % sizeof(DWORD); + if (pad_len) + { + c->hr = IStream_Write(This->stm, &pad, pad_len, &count); + if (FAILED(c->hr)) + goto end; + c->bytesWritten += pad_len; + } + } + else + { + StorageUtl_WriteDWord(&keyLen, 0, strlen((LPCSTR)key) + 1); + c->hr = IStream_Write(This->stm, &keyLen, sizeof(keyLen), &count); + if (FAILED(c->hr)) + goto end; + c->bytesWritten += sizeof(DWORD); + c->hr = IStream_Write(This->stm, key, keyLen, &count); + if (FAILED(c->hr)) + goto end; + c->bytesWritten += keyLen; + } +end: + return SUCCEEDED(c->hr); +} + +#define SECTIONHEADER_OFFSET sizeof(PROPERTYSETHEADER) + sizeof(FORMATIDOFFSET) + +/* Writes the dictionary to the stream. Assumes without checking that the + * dictionary isn't empty. + */ +static HRESULT PropertyStorage_WriteDictionaryToStream( + PropertyStorage_impl *This, DWORD *sectionOffset) +{ + HRESULT hr; + LARGE_INTEGER seek; + PROPERTYIDOFFSET propIdOffset; + ULONG count; + DWORD dwTemp; + struct DictionaryClosure closure; + + assert(sectionOffset); + + /* The dictionary's always the first property written, so seek to its + * spot. + */ + seek.QuadPart = SECTIONHEADER_OFFSET + sizeof(PROPERTYSECTIONHEADER); + hr = IStream_Seek(This->stm, seek, STREAM_SEEK_SET, NULL); + if (FAILED(hr)) + goto end; + PropertyStorage_MakePropertyIdOffset(PID_DICTIONARY, *sectionOffset, + &propIdOffset); + hr = IStream_Write(This->stm, &propIdOffset, sizeof(propIdOffset), &count); + if (FAILED(hr)) + goto end; + + seek.QuadPart = SECTIONHEADER_OFFSET + *sectionOffset; + hr = IStream_Seek(This->stm, seek, STREAM_SEEK_SET, NULL); + if (FAILED(hr)) + goto end; + StorageUtl_WriteDWord(&dwTemp, 0, dictionary_num_entries(This->name_to_propid)); + hr = IStream_Write(This->stm, &dwTemp, sizeof(dwTemp), &count); + if (FAILED(hr)) + goto end; + *sectionOffset += sizeof(dwTemp); + + closure.hr = S_OK; + closure.bytesWritten = 0; + dictionary_enumerate(This->name_to_propid, PropertyStorage_DictionaryWriter, + &closure); + hr = closure.hr; + if (FAILED(hr)) + goto end; + *sectionOffset += closure.bytesWritten; + if (closure.bytesWritten % sizeof(DWORD)) + { + DWORD padding = sizeof(DWORD) - closure.bytesWritten % sizeof(DWORD); + TRACE("adding %ld bytes of padding\n", padding); + *sectionOffset += padding; + } + +end: + return hr; +} + +static HRESULT PropertyStorage_WritePropertyToStream(PropertyStorage_impl *This, + DWORD propNum, DWORD propid, const PROPVARIANT *var, DWORD *sectionOffset) +{ + DWORD len, dwType, dwTemp, bytesWritten; + HRESULT hr; + LARGE_INTEGER seek; + PROPERTYIDOFFSET propIdOffset; + ULARGE_INTEGER ularge; + ULONG count; + + assert(var); + assert(sectionOffset); + + TRACE("%p, %ld, %#lx, %d, %ld.\n", This, propNum, propid, var->vt, + *sectionOffset); + + seek.QuadPart = SECTIONHEADER_OFFSET + sizeof(PROPERTYSECTIONHEADER) + + propNum * sizeof(PROPERTYIDOFFSET); + hr = IStream_Seek(This->stm, seek, STREAM_SEEK_SET, NULL); + if (FAILED(hr)) + goto end; + PropertyStorage_MakePropertyIdOffset(propid, *sectionOffset, &propIdOffset); + hr = IStream_Write(This->stm, &propIdOffset, sizeof(propIdOffset), &count); + if (FAILED(hr)) + goto end; + + seek.QuadPart = SECTIONHEADER_OFFSET + *sectionOffset; + hr = IStream_Seek(This->stm, seek, STREAM_SEEK_SET, NULL); + if (FAILED(hr)) + goto end; + StorageUtl_WriteDWord(&dwType, 0, var->vt); + hr = IStream_Write(This->stm, &dwType, sizeof(dwType), &count); + if (FAILED(hr)) + goto end; + *sectionOffset += sizeof(dwType); + + switch (var->vt) + { + case VT_EMPTY: + case VT_NULL: + bytesWritten = 0; + break; + case VT_I1: + case VT_UI1: + hr = IStream_Write(This->stm, &var->cVal, sizeof(var->cVal), + &count); + bytesWritten = count; + break; + case VT_I2: + case VT_UI2: + { + WORD wTemp; + + StorageUtl_WriteWord(&wTemp, 0, var->iVal); + hr = IStream_Write(This->stm, &wTemp, sizeof(wTemp), &count); + bytesWritten = count; + break; + } + case VT_I4: + case VT_UI4: + { + StorageUtl_WriteDWord(&dwTemp, 0, var->lVal); + hr = IStream_Write(This->stm, &dwTemp, sizeof(dwTemp), &count); + bytesWritten = count; + break; + } + case VT_I8: + case VT_UI8: + { + StorageUtl_WriteULargeInteger(&ularge, 0, &var->uhVal); + hr = IStream_Write(This->stm, &ularge, sizeof(ularge), &bytesWritten); + break; + } + case VT_LPSTR: + { + if (This->codePage == CP_UNICODE) + len = (lstrlenW(var->pwszVal) + 1) * sizeof(WCHAR); + else + len = lstrlenA(var->pszVal) + 1; + StorageUtl_WriteDWord(&dwTemp, 0, len); + hr = IStream_Write(This->stm, &dwTemp, sizeof(dwTemp), &count); + if (FAILED(hr)) + goto end; + hr = IStream_Write(This->stm, var->pszVal, len, &count); + bytesWritten = count + sizeof(DWORD); + break; + } + case VT_BSTR: + { + if (This->codePage == CP_UNICODE) + { + len = SysStringByteLen(var->bstrVal) + sizeof(WCHAR); + StorageUtl_WriteDWord(&dwTemp, 0, len); + hr = IStream_Write(This->stm, &dwTemp, sizeof(dwTemp), &count); + if (SUCCEEDED(hr)) + hr = IStream_Write(This->stm, var->bstrVal, len, &count); + } + else + { + char *str; + + len = WideCharToMultiByte(This->codePage, 0, var->bstrVal, SysStringLen(var->bstrVal) + 1, + NULL, 0, NULL, NULL); + + str = heap_alloc(len); + if (!str) + { + hr = E_OUTOFMEMORY; + goto end; + } + + WideCharToMultiByte(This->codePage, 0, var->bstrVal, SysStringLen(var->bstrVal), + str, len, NULL, NULL); + StorageUtl_WriteDWord(&dwTemp, 0, len); + hr = IStream_Write(This->stm, &dwTemp, sizeof(dwTemp), &count); + if (SUCCEEDED(hr)) + hr = IStream_Write(This->stm, str, len, &count); + heap_free(str); + } + + bytesWritten = count + sizeof(DWORD); + break; + } + case VT_LPWSTR: + { + len = lstrlenW(var->pwszVal) + 1; + + StorageUtl_WriteDWord(&dwTemp, 0, len); + hr = IStream_Write(This->stm, &dwTemp, sizeof(dwTemp), &count); + if (FAILED(hr)) + goto end; + hr = IStream_Write(This->stm, var->pwszVal, len * sizeof(WCHAR), + &count); + bytesWritten = count + sizeof(DWORD); + break; + } + case VT_FILETIME: + { + FILETIME temp; + + StorageUtl_WriteULargeInteger(&temp, 0, (const ULARGE_INTEGER *)&var->filetime); + hr = IStream_Write(This->stm, &temp, sizeof(temp), &count); + bytesWritten = count; + break; + } + case VT_CF: + { + DWORD cf_hdr[2]; + + len = var->pclipdata->cbSize; + StorageUtl_WriteDWord(&cf_hdr[0], 0, len + 8); + StorageUtl_WriteDWord(&cf_hdr[1], 0, var->pclipdata->ulClipFmt); + hr = IStream_Write(This->stm, cf_hdr, sizeof(cf_hdr), &count); + if (FAILED(hr)) + goto end; + hr = IStream_Write(This->stm, var->pclipdata->pClipData, + len - sizeof(var->pclipdata->ulClipFmt), &count); + if (FAILED(hr)) + goto end; + bytesWritten = count + sizeof cf_hdr; + break; + } + case VT_CLSID: + { + CLSID temp; + + StorageUtl_WriteGUID(&temp, 0, var->puuid); + hr = IStream_Write(This->stm, &temp, sizeof(temp), &count); + bytesWritten = count; + break; + } + case VT_BLOB: + { + StorageUtl_WriteDWord(&dwTemp, 0, var->blob.cbSize); + hr = IStream_Write(This->stm, &dwTemp, sizeof(dwTemp), &count); + if (FAILED(hr)) + goto end; + hr = IStream_Write(This->stm, var->blob.pBlobData, var->blob.cbSize, &count); + bytesWritten = count + sizeof(DWORD); + break; + } + default: + FIXME("unsupported type: %d\n", var->vt); + return STG_E_INVALIDPARAMETER; + } + + if (SUCCEEDED(hr)) + { + *sectionOffset += bytesWritten; + if (bytesWritten % sizeof(DWORD)) + { + DWORD padding = sizeof(DWORD) - bytesWritten % sizeof(DWORD); + TRACE("adding %ld bytes of padding\n", padding); + *sectionOffset += padding; + } + } + +end: + return hr; +} + +struct PropertyClosure +{ + HRESULT hr; + DWORD propNum; + DWORD *sectionOffset; +}; + +static BOOL PropertyStorage_PropertiesWriter(const void *key, const void *value, + void *extra, void *closure) +{ + PropertyStorage_impl *This = extra; + struct PropertyClosure *c = closure; + + assert(key); + assert(value); + assert(extra); + assert(closure); + c->hr = PropertyStorage_WritePropertyToStream(This, c->propNum++, + PtrToUlong(key), value, c->sectionOffset); + return SUCCEEDED(c->hr); +} + +static HRESULT PropertyStorage_WritePropertiesToStream( + PropertyStorage_impl *This, DWORD startingPropNum, DWORD *sectionOffset) +{ + struct PropertyClosure closure; + + assert(sectionOffset); + closure.hr = S_OK; + closure.propNum = startingPropNum; + closure.sectionOffset = sectionOffset; + dictionary_enumerate(This->propid_to_prop, PropertyStorage_PropertiesWriter, + &closure); + return closure.hr; +} + +static HRESULT PropertyStorage_WriteHeadersToStream(PropertyStorage_impl *This) +{ + HRESULT hr; + ULONG count = 0; + LARGE_INTEGER seek = { {0} }; + PROPERTYSETHEADER hdr; + FORMATIDOFFSET fmtOffset; + + hr = IStream_Seek(This->stm, seek, STREAM_SEEK_SET, NULL); + if (FAILED(hr)) + goto end; + PropertyStorage_MakeHeader(This, &hdr); + hr = IStream_Write(This->stm, &hdr, sizeof(hdr), &count); + if (FAILED(hr)) + goto end; + if (count != sizeof(hdr)) + { + hr = STG_E_WRITEFAULT; + goto end; + } + + PropertyStorage_MakeFmtIdOffset(This, &fmtOffset); + hr = IStream_Write(This->stm, &fmtOffset, sizeof(fmtOffset), &count); + if (FAILED(hr)) + goto end; + if (count != sizeof(fmtOffset)) + { + hr = STG_E_WRITEFAULT; + goto end; + } + hr = S_OK; + +end: + return hr; +} + +static HRESULT PropertyStorage_WriteToStream(PropertyStorage_impl *This) +{ + PROPERTYSECTIONHEADER sectionHdr; + HRESULT hr; + ULONG count; + LARGE_INTEGER seek; + DWORD numProps, prop, sectionOffset, dwTemp; + PROPVARIANT var; + + PropertyStorage_WriteHeadersToStream(This); + + /* Count properties. Always at least one property, the code page */ + numProps = 1; + if (dictionary_num_entries(This->name_to_propid)) + numProps++; + if (This->locale != LOCALE_SYSTEM_DEFAULT) + numProps++; + if (This->grfFlags & PROPSETFLAG_CASE_SENSITIVE) + numProps++; + numProps += dictionary_num_entries(This->propid_to_prop); + + /* Write section header with 0 bytes right now, I'll adjust it after + * writing properties. + */ + PropertyStorage_MakeSectionHdr(0, numProps, §ionHdr); + seek.QuadPart = SECTIONHEADER_OFFSET; + hr = IStream_Seek(This->stm, seek, STREAM_SEEK_SET, NULL); + if (FAILED(hr)) + goto end; + hr = IStream_Write(This->stm, §ionHdr, sizeof(sectionHdr), &count); + if (FAILED(hr)) + goto end; + + prop = 0; + sectionOffset = sizeof(PROPERTYSECTIONHEADER) + + numProps * sizeof(PROPERTYIDOFFSET); + + if (dictionary_num_entries(This->name_to_propid)) + { + prop++; + hr = PropertyStorage_WriteDictionaryToStream(This, §ionOffset); + if (FAILED(hr)) + goto end; + } + + PropVariantInit(&var); + + var.vt = VT_I2; + var.iVal = This->codePage; + hr = PropertyStorage_WritePropertyToStream(This, prop++, PID_CODEPAGE, + &var, §ionOffset); + if (FAILED(hr)) + goto end; + + if (This->locale != LOCALE_SYSTEM_DEFAULT) + { + var.vt = VT_I4; + var.lVal = This->locale; + hr = PropertyStorage_WritePropertyToStream(This, prop++, PID_LOCALE, + &var, §ionOffset); + if (FAILED(hr)) + goto end; + } + + if (This->grfFlags & PROPSETFLAG_CASE_SENSITIVE) + { + var.vt = VT_I4; + var.lVal = 1; + hr = PropertyStorage_WritePropertyToStream(This, prop++, PID_BEHAVIOR, + &var, §ionOffset); + if (FAILED(hr)) + goto end; + } + + hr = PropertyStorage_WritePropertiesToStream(This, prop, §ionOffset); + if (FAILED(hr)) + goto end; + + /* Now write the byte count of the section */ + seek.QuadPart = SECTIONHEADER_OFFSET; + hr = IStream_Seek(This->stm, seek, STREAM_SEEK_SET, NULL); + if (FAILED(hr)) + goto end; + StorageUtl_WriteDWord(&dwTemp, 0, sectionOffset); + hr = IStream_Write(This->stm, &dwTemp, sizeof(dwTemp), &count); + +end: + return hr; +} + +/*********************************************************************** + * PropertyStorage_Construct + */ +static void PropertyStorage_DestroyDictionaries(PropertyStorage_impl *This) +{ + dictionary_destroy(This->name_to_propid); + This->name_to_propid = NULL; + dictionary_destroy(This->propid_to_name); + This->propid_to_name = NULL; + dictionary_destroy(This->propid_to_prop); + This->propid_to_prop = NULL; +} + +static HRESULT PropertyStorage_CreateDictionaries(PropertyStorage_impl *This) +{ + HRESULT hr = S_OK; + + This->name_to_propid = dictionary_create( + PropertyStorage_PropNameCompare, PropertyStorage_PropNameDestroy, + This); + if (!This->name_to_propid) + { + hr = STG_E_INSUFFICIENTMEMORY; + goto end; + } + This->propid_to_name = dictionary_create(PropertyStorage_PropCompare, + NULL, This); + if (!This->propid_to_name) + { + hr = STG_E_INSUFFICIENTMEMORY; + goto end; + } + This->propid_to_prop = dictionary_create(PropertyStorage_PropCompare, + PropertyStorage_PropertyDestroy, This); + if (!This->propid_to_prop) + { + hr = STG_E_INSUFFICIENTMEMORY; + goto end; + } +end: + if (FAILED(hr)) + PropertyStorage_DestroyDictionaries(This); + return hr; +} + +static HRESULT PropertyStorage_BaseConstruct(IStream *stm, + REFFMTID rfmtid, DWORD grfMode, PropertyStorage_impl **pps) +{ + HRESULT hr = S_OK; + + assert(pps); + assert(rfmtid); + *pps = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof **pps); + if (!*pps) + return E_OUTOFMEMORY; + + (*pps)->IPropertyStorage_iface.lpVtbl = &IPropertyStorage_Vtbl; + (*pps)->ref = 1; + InitializeCriticalSection(&(*pps)->cs); + (*pps)->cs.DebugInfo->Spare[0] = (DWORD_PTR)(__FILE__ ": PropertyStorage_impl.cs"); + (*pps)->stm = stm; + (*pps)->fmtid = *rfmtid; + (*pps)->grfMode = grfMode; + + hr = PropertyStorage_CreateDictionaries(*pps); + if (FAILED(hr)) + { + (*pps)->cs.DebugInfo->Spare[0] = 0; + DeleteCriticalSection(&(*pps)->cs); + HeapFree(GetProcessHeap(), 0, *pps); + *pps = NULL; + } + else IStream_AddRef( stm ); + + return hr; +} + +static HRESULT PropertyStorage_ConstructFromStream(IStream *stm, + REFFMTID rfmtid, DWORD grfMode, IPropertyStorage** pps) +{ + PropertyStorage_impl *ps; + HRESULT hr; + + assert(pps); + hr = PropertyStorage_BaseConstruct(stm, rfmtid, grfMode, &ps); + if (SUCCEEDED(hr)) + { + hr = PropertyStorage_ReadFromStream(ps); + if (SUCCEEDED(hr)) + { + *pps = &ps->IPropertyStorage_iface; + TRACE("PropertyStorage %p constructed\n", ps); + hr = S_OK; + } + else IPropertyStorage_Release( &ps->IPropertyStorage_iface ); + } + return hr; +} + +static HRESULT PropertyStorage_ConstructEmpty(IStream *stm, + REFFMTID rfmtid, DWORD grfFlags, DWORD grfMode, IPropertyStorage** pps) +{ + PropertyStorage_impl *ps; + HRESULT hr; + + assert(pps); + hr = PropertyStorage_BaseConstruct(stm, rfmtid, grfMode, &ps); + if (SUCCEEDED(hr)) + { + ps->format = 0; + ps->grfFlags = grfFlags; + if (ps->grfFlags & PROPSETFLAG_CASE_SENSITIVE) + ps->format = 1; + /* default to Unicode unless told not to, as specified on msdn */ + if (ps->grfFlags & PROPSETFLAG_ANSI) + ps->codePage = GetACP(); + else + ps->codePage = CP_UNICODE; + ps->locale = LOCALE_SYSTEM_DEFAULT; + TRACE("Code page is %d, locale is %ld\n", ps->codePage, ps->locale); + *pps = &ps->IPropertyStorage_iface; + TRACE("PropertyStorage %p constructed\n", ps); + hr = S_OK; + } + return hr; +} + + +/*********************************************************************** + * Implementation of IPropertySetStorage + */ + +struct enum_stat_propset_stg +{ + IEnumSTATPROPSETSTG IEnumSTATPROPSETSTG_iface; + LONG refcount; + STATPROPSETSTG *stats; + size_t current; + size_t count; +}; + +static struct enum_stat_propset_stg *impl_from_IEnumSTATPROPSETSTG(IEnumSTATPROPSETSTG *iface) +{ + return CONTAINING_RECORD(iface, struct enum_stat_propset_stg, IEnumSTATPROPSETSTG_iface); +} + +static HRESULT WINAPI enum_stat_propset_stg_QueryInterface(IEnumSTATPROPSETSTG *iface, REFIID riid, void **obj) +{ + TRACE("%p, %s, %p.\n", iface, debugstr_guid(riid), obj); + + if (IsEqualIID(riid, &IID_IEnumSTATPROPSETSTG) || + IsEqualIID(riid, &IID_IUnknown)) + { + *obj = iface; + IEnumSTATPROPSETSTG_AddRef(iface); + return S_OK; + } + + WARN("Unsupported interface %s.\n", debugstr_guid(riid)); + return E_NOINTERFACE; +} + +static ULONG WINAPI enum_stat_propset_stg_AddRef(IEnumSTATPROPSETSTG *iface) +{ + struct enum_stat_propset_stg *psenum = impl_from_IEnumSTATPROPSETSTG(iface); + LONG refcount = InterlockedIncrement(&psenum->refcount); + + TRACE("%p, refcount %lu.\n", iface, refcount); + + return refcount; +} + +static ULONG WINAPI enum_stat_propset_stg_Release(IEnumSTATPROPSETSTG *iface) +{ + struct enum_stat_propset_stg *psenum = impl_from_IEnumSTATPROPSETSTG(iface); + LONG refcount = InterlockedDecrement(&psenum->refcount); + + TRACE("%p, refcount %lu.\n", iface, refcount); + + if (!refcount) + { + heap_free(psenum->stats); + heap_free(psenum); + } + + return refcount; +} + +static HRESULT WINAPI enum_stat_propset_stg_Next(IEnumSTATPROPSETSTG *iface, ULONG celt, + STATPROPSETSTG *ret, ULONG *fetched) +{ + struct enum_stat_propset_stg *psenum = impl_from_IEnumSTATPROPSETSTG(iface); + ULONG count = 0; + + TRACE("%p, %lu, %p, %p.\n", iface, celt, ret, fetched); + + if (psenum->current == ~0u) + psenum->current = 0; + + while (count < celt && psenum->current < psenum->count) + ret[count++] = psenum->stats[psenum->current++]; + + if (fetched) + *fetched = count; + + return count < celt ? S_FALSE : S_OK; +} + +static HRESULT WINAPI enum_stat_propset_stg_Skip(IEnumSTATPROPSETSTG *iface, ULONG celt) +{ + FIXME("%p, %lu.\n", iface, celt); + + return S_OK; +} + +static HRESULT WINAPI enum_stat_propset_stg_Reset(IEnumSTATPROPSETSTG *iface) +{ + struct enum_stat_propset_stg *psenum = impl_from_IEnumSTATPROPSETSTG(iface); + + TRACE("%p.\n", iface); + + psenum->current = ~0u; + + return S_OK; +} + +static HRESULT WINAPI enum_stat_propset_stg_Clone(IEnumSTATPROPSETSTG *iface, IEnumSTATPROPSETSTG **ppenum) +{ + FIXME("%p, %p.\n", iface, ppenum); + + return E_NOTIMPL; +} + +static const IEnumSTATPROPSETSTGVtbl enum_stat_propset_stg_vtbl = +{ + enum_stat_propset_stg_QueryInterface, + enum_stat_propset_stg_AddRef, + enum_stat_propset_stg_Release, + enum_stat_propset_stg_Next, + enum_stat_propset_stg_Skip, + enum_stat_propset_stg_Reset, + enum_stat_propset_stg_Clone, +}; + +static HRESULT create_enum_stat_propset_stg(StorageImpl *storage, IEnumSTATPROPSETSTG **ret) +{ + IStorage *stg = &storage->base.IStorage_iface; + IEnumSTATSTG *penum = NULL; + STATSTG stat; + ULONG count; + HRESULT hr; + + struct enum_stat_propset_stg *enum_obj; + + enum_obj = heap_alloc_zero(sizeof(*enum_obj)); + if (!enum_obj) + return E_OUTOFMEMORY; + + enum_obj->IEnumSTATPROPSETSTG_iface.lpVtbl = &enum_stat_propset_stg_vtbl; + enum_obj->refcount = 1; + + /* add all the property set elements into a list */ + hr = IStorage_EnumElements(stg, 0, NULL, 0, &penum); + if (FAILED(hr)) + goto done; + + /* Allocate stats array and fill it. */ + while ((hr = IEnumSTATSTG_Next(penum, 1, &stat, &count)) == S_OK) + { + enum_obj->count++; + CoTaskMemFree(stat.pwcsName); + } + + if (FAILED(hr)) + goto done; + + enum_obj->stats = heap_alloc(enum_obj->count * sizeof(*enum_obj->stats)); + if (!enum_obj->stats) + { + hr = E_OUTOFMEMORY; + goto done; + } + enum_obj->count = 0; + + if (FAILED(hr = IEnumSTATSTG_Reset(penum))) + goto done; + + while (IEnumSTATSTG_Next(penum, 1, &stat, &count) == S_OK) + { + if (!stat.pwcsName) + continue; + + if (stat.pwcsName[0] == 5 && stat.type == STGTY_STREAM) + { + STATPROPSETSTG *ptr = &enum_obj->stats[enum_obj->count++]; + + PropStgNameToFmtId(stat.pwcsName, &ptr->fmtid); + + TRACE("adding %s - %s.\n", debugstr_w(stat.pwcsName), debugstr_guid(&ptr->fmtid)); + + ptr->mtime = stat.mtime; + ptr->atime = stat.atime; + ptr->ctime = stat.ctime; + ptr->grfFlags = stat.grfMode; + ptr->clsid = stat.clsid; + } + CoTaskMemFree(stat.pwcsName); + } + +done: + + if (penum) + IEnumSTATSTG_Release(penum); + + if (SUCCEEDED(hr)) + { + *ret = &enum_obj->IEnumSTATPROPSETSTG_iface; + } + else + { + *ret = NULL; + IEnumSTATPROPSETSTG_Release(&enum_obj->IEnumSTATPROPSETSTG_iface); + } + + return hr; +} + +/************************************************************************ + * IPropertySetStorage_fnQueryInterface (IUnknown) + * + * This method forwards to the common QueryInterface implementation + */ +static HRESULT WINAPI IPropertySetStorage_fnQueryInterface( + IPropertySetStorage *ppstg, + REFIID riid, + void** ppvObject) +{ + StorageImpl *This = impl_from_IPropertySetStorage(ppstg); + return IStorage_QueryInterface( &This->base.IStorage_iface, riid, ppvObject ); +} + +/************************************************************************ + * IPropertySetStorage_fnAddRef (IUnknown) + * + * This method forwards to the common AddRef implementation + */ +static ULONG WINAPI IPropertySetStorage_fnAddRef( + IPropertySetStorage *ppstg) +{ + StorageImpl *This = impl_from_IPropertySetStorage(ppstg); + return IStorage_AddRef( &This->base.IStorage_iface ); +} + +/************************************************************************ + * IPropertySetStorage_fnRelease (IUnknown) + * + * This method forwards to the common Release implementation + */ +static ULONG WINAPI IPropertySetStorage_fnRelease( + IPropertySetStorage *ppstg) +{ + StorageImpl *This = impl_from_IPropertySetStorage(ppstg); + return IStorage_Release( &This->base.IStorage_iface ); +} + +/************************************************************************ + * IPropertySetStorage_fnCreate (IPropertySetStorage) + */ +static HRESULT WINAPI IPropertySetStorage_fnCreate( + IPropertySetStorage *ppstg, + REFFMTID rfmtid, + const CLSID* pclsid, + DWORD grfFlags, + DWORD grfMode, + IPropertyStorage** ppprstg) +{ + StorageImpl *This = impl_from_IPropertySetStorage(ppstg); + WCHAR name[CCH_MAX_PROPSTG_NAME + 1]; + IStream *stm = NULL; + HRESULT r; + + TRACE("%p, %s %#lx, %#lx, %p.\n", This, debugstr_guid(rfmtid), grfFlags, + grfMode, ppprstg); + + /* be picky */ + if (grfMode != (STGM_CREATE|STGM_READWRITE|STGM_SHARE_EXCLUSIVE)) + { + r = STG_E_INVALIDFLAG; + goto end; + } + + if (!rfmtid) + { + r = E_INVALIDARG; + goto end; + } + + /* FIXME: if (grfFlags & PROPSETFLAG_NONSIMPLE), we need to create a + * storage, not a stream. For now, disallow it. + */ + if (grfFlags & PROPSETFLAG_NONSIMPLE) + { + FIXME("PROPSETFLAG_NONSIMPLE not supported\n"); + r = STG_E_INVALIDFLAG; + goto end; + } + + r = FmtIdToPropStgName(rfmtid, name); + if (FAILED(r)) + goto end; + + r = IStorage_CreateStream( &This->base.IStorage_iface, name, grfMode, 0, 0, &stm ); + if (FAILED(r)) + goto end; + + r = PropertyStorage_ConstructEmpty(stm, rfmtid, grfFlags, grfMode, ppprstg); + + IStream_Release( stm ); + +end: + TRACE("returning %#lx\n", r); + return r; +} + +/************************************************************************ + * IPropertySetStorage_fnOpen (IPropertySetStorage) + */ +static HRESULT WINAPI IPropertySetStorage_fnOpen( + IPropertySetStorage *ppstg, + REFFMTID rfmtid, + DWORD grfMode, + IPropertyStorage** ppprstg) +{ + StorageImpl *This = impl_from_IPropertySetStorage(ppstg); + IStream *stm = NULL; + WCHAR name[CCH_MAX_PROPSTG_NAME + 1]; + HRESULT r; + + TRACE("%p, %s, %#lx, %p.\n", This, debugstr_guid(rfmtid), grfMode, ppprstg); + + /* be picky */ + if (grfMode != (STGM_READWRITE|STGM_SHARE_EXCLUSIVE) && + grfMode != (STGM_READ|STGM_SHARE_EXCLUSIVE)) + { + r = STG_E_INVALIDFLAG; + goto end; + } + + if (!rfmtid) + { + r = E_INVALIDARG; + goto end; + } + + r = FmtIdToPropStgName(rfmtid, name); + if (FAILED(r)) + goto end; + + r = IStorage_OpenStream( &This->base.IStorage_iface, name, 0, grfMode, 0, &stm ); + if (FAILED(r)) + goto end; + + r = PropertyStorage_ConstructFromStream(stm, rfmtid, grfMode, ppprstg); + + IStream_Release( stm ); + +end: + TRACE("returning %#lx\n", r); + return r; +} + +/************************************************************************ + * IPropertySetStorage_fnDelete (IPropertySetStorage) + */ +static HRESULT WINAPI IPropertySetStorage_fnDelete( + IPropertySetStorage *ppstg, + REFFMTID rfmtid) +{ + StorageImpl *This = impl_from_IPropertySetStorage(ppstg); + WCHAR name[CCH_MAX_PROPSTG_NAME + 1]; + HRESULT r; + + TRACE("%p %s\n", This, debugstr_guid(rfmtid)); + + if (!rfmtid) + return E_INVALIDARG; + + r = FmtIdToPropStgName(rfmtid, name); + if (FAILED(r)) + return r; + + return IStorage_DestroyElement(&This->base.IStorage_iface, name); +} + +static HRESULT WINAPI IPropertySetStorage_fnEnum(IPropertySetStorage *iface, IEnumSTATPROPSETSTG **enum_obj) +{ + StorageImpl *storage = impl_from_IPropertySetStorage(iface); + + TRACE("%p, %p.\n", iface, enum_obj); + + if (!enum_obj) + return E_INVALIDARG; + + return create_enum_stat_propset_stg(storage, enum_obj); +} + +/*********************************************************************** + * vtables + */ +const IPropertySetStorageVtbl IPropertySetStorage_Vtbl = +{ + IPropertySetStorage_fnQueryInterface, + IPropertySetStorage_fnAddRef, + IPropertySetStorage_fnRelease, + IPropertySetStorage_fnCreate, + IPropertySetStorage_fnOpen, + IPropertySetStorage_fnDelete, + IPropertySetStorage_fnEnum +}; + +static const IPropertyStorageVtbl IPropertyStorage_Vtbl = +{ + IPropertyStorage_fnQueryInterface, + IPropertyStorage_fnAddRef, + IPropertyStorage_fnRelease, + IPropertyStorage_fnReadMultiple, + IPropertyStorage_fnWriteMultiple, + IPropertyStorage_fnDeleteMultiple, + IPropertyStorage_fnReadPropertyNames, + IPropertyStorage_fnWritePropertyNames, + IPropertyStorage_fnDeletePropertyNames, + IPropertyStorage_fnCommit, + IPropertyStorage_fnRevert, + IPropertyStorage_fnEnum, + IPropertyStorage_fnSetTimes, + IPropertyStorage_fnSetClass, + IPropertyStorage_fnStat, +}; + /*********************************************************************** * Format ID <-> name conversion */ @@ -197,3 +3136,93 @@ HRESULT WINAPI PropStgNameToFmtId(const LPOLESTR str, FMTID *rfmtid) end: return hr; } + +HRESULT WINAPI StgCreatePropStg(IUnknown *unk, REFFMTID fmt, const CLSID *clsid, + DWORD flags, DWORD reserved, IPropertyStorage **prop_stg) +{ + IStorage *stg; + IStream *stm; + HRESULT r; + + TRACE("%p, %s, %s, %#lx, %ld, %p.\n", unk, debugstr_guid(fmt), debugstr_guid(clsid), flags, reserved, prop_stg); + + if (!fmt || reserved) + { + r = E_INVALIDARG; + goto end; + } + + if (flags & PROPSETFLAG_NONSIMPLE) + { + r = IUnknown_QueryInterface(unk, &IID_IStorage, (void **)&stg); + if (FAILED(r)) + goto end; + + /* FIXME: if (flags & PROPSETFLAG_NONSIMPLE), we need to create a + * storage, not a stream. For now, disallow it. + */ + FIXME("PROPSETFLAG_NONSIMPLE not supported\n"); + IStorage_Release(stg); + r = STG_E_INVALIDFLAG; + } + else + { + r = IUnknown_QueryInterface(unk, &IID_IStream, (void **)&stm); + if (FAILED(r)) + goto end; + + r = PropertyStorage_ConstructEmpty(stm, fmt, flags, + STGM_CREATE|STGM_READWRITE|STGM_SHARE_EXCLUSIVE, prop_stg); + + IStream_Release( stm ); + } + +end: + TRACE("returning %#lx\n", r); + return r; +} + +HRESULT WINAPI StgOpenPropStg(IUnknown *unk, REFFMTID fmt, DWORD flags, + DWORD reserved, IPropertyStorage **prop_stg) +{ + IStorage *stg; + IStream *stm; + HRESULT r; + + TRACE("%p, %s, %#lx, %ld, %p.\n", unk, debugstr_guid(fmt), flags, reserved, prop_stg); + + if (!fmt || reserved) + { + r = E_INVALIDARG; + goto end; + } + + if (flags & PROPSETFLAG_NONSIMPLE) + { + r = IUnknown_QueryInterface(unk, &IID_IStorage, (void **)&stg); + if (FAILED(r)) + goto end; + + /* FIXME: if (flags & PROPSETFLAG_NONSIMPLE), we need to open a + * storage, not a stream. For now, disallow it. + */ + FIXME("PROPSETFLAG_NONSIMPLE not supported\n"); + IStorage_Release(stg); + r = STG_E_INVALIDFLAG; + } + else + { + r = IUnknown_QueryInterface(unk, &IID_IStream, (void **)&stm); + if (FAILED(r)) + goto end; + + r = PropertyStorage_ConstructFromStream(stm, fmt, + STGM_READWRITE|STGM_SHARE_EXCLUSIVE, prop_stg); + + IStream_Release( stm ); + } + +end: + TRACE("returning %#lx\n", r); + return r; +} diff --git a/dlls/ole32/stg_stream.c b/dlls/coml2/stg_stream.c similarity index 100% rename from dlls/ole32/stg_stream.c rename to dlls/coml2/stg_stream.c diff --git a/dlls/coml2/storage32.c b/dlls/coml2/storage32.c index d6dab7d6b17..9b5c0e639bc 100644 --- a/dlls/coml2/storage32.c +++ b/dlls/coml2/storage32.c @@ -47,10 +47,8533 @@ #include "winreg.h" #include "wine/wingdi16.h" +#include "storage32.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; + +static HRESULT validateSTGM(DWORD stgm); +static DWORD GetShareModeFromSTGM(DWORD stgm); +static DWORD GetAccessModeFromSTGM(DWORD stgm); +static DWORD GetCreationModeFromSTGM(DWORD stgm); + +/**************************************************************************** + * 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); + + +/************************************************************************ + * 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, %ld: stub\n", This, timeout); + return E_NOTIMPL; +} + +static HRESULT WINAPI directwriterlock_ReleaseWriteAccess(IDirectWriterLock *iface) +{ + StorageBaseImpl *This = impl_from_IDirectWriterLock(iface); + FIXME("(%p): stub\n", This); + return E_NOTIMPL; +} + +static HRESULT WINAPI directwriterlock_HaveWriteAccess(IDirectWriterLock *iface) +{ + StorageBaseImpl *This = impl_from_IDirectWriterLock(iface); + FIXME("(%p): stub\n", This); + return E_NOTIMPL; +} + +static const IDirectWriterLockVtbl DirectWriterLockVtbl = +{ + directwriterlock_QueryInterface, + directwriterlock_AddRef, + directwriterlock_Release, + directwriterlock_WaitForWriteAccess, + directwriterlock_ReleaseWriteAccess, + directwriterlock_HaveWriteAccess +}; + + +/************************************************************************ + * StorageBaseImpl implementation : Tree helper functions + ***********************************************************************/ + +/**************************************************************************** + * + * Internal Method + * + * Case insensitive comparison of DirEntry.name by first considering + * their size. + * + * Returns <0 when name1 < name2 + * >0 when name1 > name2 + * 0 when name1 == name2 + */ +static LONG entryNameCmp( + const OLECHAR *name1, + const OLECHAR *name2) +{ + LONG diff = lstrlenW(name1) - lstrlenW(name2); + + while (diff == 0 && *name1 != 0) + { + /* + * We compare the string themselves only when they are of the same length + */ + diff = towupper(*name1++) - towupper(*name2++); + } + + return diff; +} + +/**************************************************************************** + * + * Internal Method + * + * Find and read the element of a storage with the given name. + */ +static DirRef findElement(StorageBaseImpl *storage, DirRef storageEntry, + const OLECHAR *name, DirEntry *data) +{ + DirRef currentEntry; + + /* Read the storage entry to find the root of the tree. */ + StorageBaseImpl_ReadDirEntry(storage, storageEntry, data); + + currentEntry = data->dirRootEntry; + + while (currentEntry != DIRENTRY_NULL) + { + LONG cmp; + + StorageBaseImpl_ReadDirEntry(storage, currentEntry, data); + + cmp = entryNameCmp(name, data->name); + + if (cmp == 0) + /* found it */ + break; + + else if (cmp < 0) + currentEntry = data->leftChild; + + else if (cmp > 0) + currentEntry = data->rightChild; + } + + return currentEntry; +} + +/**************************************************************************** + * + * Internal Method + * + * Find and read the binary tree parent of the element with the given name. + * + * If there is no such element, find a place where it could be inserted and + * return STG_E_FILENOTFOUND. + */ +static HRESULT findTreeParent(StorageBaseImpl *storage, DirRef storageEntry, + const OLECHAR *childName, DirEntry *parentData, DirRef *parentEntry, + ULONG *relation) +{ + DirRef childEntry; + DirEntry childData; + + /* Read the storage entry to find the root of the tree. */ + StorageBaseImpl_ReadDirEntry(storage, storageEntry, parentData); + + *parentEntry = storageEntry; + *relation = DIRENTRY_RELATION_DIR; + + childEntry = parentData->dirRootEntry; + + while (childEntry != DIRENTRY_NULL) + { + LONG cmp; + + StorageBaseImpl_ReadDirEntry(storage, childEntry, &childData); + + cmp = entryNameCmp(childName, childData.name); + + if (cmp == 0) + /* found it */ + break; + + else if (cmp < 0) + { + *parentData = childData; + *parentEntry = childEntry; + *relation = DIRENTRY_RELATION_PREVIOUS; + + childEntry = parentData->leftChild; + } + + else if (cmp > 0) + { + *parentData = childData; + *parentEntry = childEntry; + *relation = DIRENTRY_RELATION_NEXT; + + childEntry = parentData->rightChild; + } + } + + if (childEntry == DIRENTRY_NULL) + return STG_E_FILENOTFOUND; + else + return S_OK; +} + +static void setEntryLink(DirEntry *entry, ULONG relation, DirRef new_target) +{ + switch (relation) + { + case DIRENTRY_RELATION_PREVIOUS: + entry->leftChild = new_target; + break; + case DIRENTRY_RELATION_NEXT: + entry->rightChild = new_target; + break; + case DIRENTRY_RELATION_DIR: + entry->dirRootEntry = new_target; + break; + default: + assert(0); + } +} + +/**************************************************************************** + * + * Internal Method + * + * Add a directory entry to a storage + */ +static HRESULT insertIntoTree( + StorageBaseImpl *This, + DirRef parentStorageIndex, + DirRef newEntryIndex) +{ + DirEntry currentEntry; + DirEntry newEntry; + + /* + * Read the inserted entry + */ + StorageBaseImpl_ReadDirEntry(This, + newEntryIndex, + &newEntry); + + /* + * Read the storage entry + */ + StorageBaseImpl_ReadDirEntry(This, + parentStorageIndex, + ¤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("<-- %#lx\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, %lu, %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("<-- %#lx (asked %lu, got %lu)\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, %lu.\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("<-- %#lx\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, refcount %lu.\n", iface, 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, refcount %lu.\n", iface, ref); + + if (ref == 0) + { + /* + * Since we are using a system of base-classes, we want to call the + * destructor of the appropriate derived class. To do this, we are + * using virtual functions to implement the destructor. + */ + StorageBaseImpl_Destroy(This); + } + + return ref; +} + +static HRESULT StorageBaseImpl_CopyStorageEntryTo(StorageBaseImpl *This, + DirRef srcEntry, BOOL skip_storage, BOOL skip_stream, + SNB snbExclude, IStorage *pstgDest); + +static HRESULT StorageBaseImpl_CopyChildEntryTo(StorageBaseImpl *This, + DirRef srcEntry, BOOL skip_storage, BOOL skip_stream, + SNB snbExclude, IStorage *pstgDest) +{ + DirEntry data; + HRESULT hr; + BOOL skip = FALSE; + IStorage *pstgTmp; + IStream *pstrChild, *pstrTmp; + STATSTG strStat; + + if (srcEntry == DIRENTRY_NULL) + return S_OK; + + hr = StorageBaseImpl_ReadDirEntry( This, srcEntry, &data ); + + if (FAILED(hr)) + return hr; + + if ( snbExclude ) + { + WCHAR **snb = snbExclude; + + while ( *snb != NULL && !skip ) + { + if ( wcscmp(data.name, *snb) == 0 ) + skip = TRUE; + ++snb; + } + } + + if (!skip) + { + if (data.stgType == STGTY_STORAGE && !skip_storage) + { + /* + * create a new storage in destination storage + */ + hr = IStorage_CreateStorage( pstgDest, data.name, + STGM_FAILIFTHERE|STGM_WRITE|STGM_SHARE_EXCLUSIVE, + 0, 0, + &pstgTmp ); + + /* + * if it already exist, don't create a new one use this one + */ + if (hr == STG_E_FILEALREADYEXISTS) + { + hr = IStorage_OpenStorage( pstgDest, data.name, NULL, + STGM_WRITE|STGM_SHARE_EXCLUSIVE, + NULL, 0, &pstgTmp ); + } + + if (SUCCEEDED(hr)) + { + hr = StorageBaseImpl_CopyStorageEntryTo( This, srcEntry, skip_storage, + skip_stream, NULL, pstgTmp ); + + IStorage_Release(pstgTmp); + } + } + else if (data.stgType == STGTY_STREAM && !skip_stream) + { + /* + * create a new stream in destination storage. If the stream already + * exist, it will be deleted and a new one will be created. + */ + hr = IStorage_CreateStream( pstgDest, data.name, + STGM_CREATE|STGM_WRITE|STGM_SHARE_EXCLUSIVE, + 0, 0, &pstrTmp ); + + /* + * open child stream storage. This operation must succeed even if the + * stream is already open, so we use internal functions to do it. + */ + if (hr == S_OK) + { + StgStreamImpl *streamimpl = StgStreamImpl_Construct(This, STGM_READ|STGM_SHARE_EXCLUSIVE, srcEntry); + + if (streamimpl) + { + pstrChild = &streamimpl->IStream_iface; + if (pstrChild) + IStream_AddRef(pstrChild); + } + else + { + pstrChild = NULL; + hr = E_OUTOFMEMORY; + } + } + + if (hr == S_OK) + { + /* + * Get the size of the source stream + */ + IStream_Stat( pstrChild, &strStat, STATFLAG_NONAME ); + + /* + * Set the size of the destination stream. + */ + IStream_SetSize(pstrTmp, strStat.cbSize); + + /* + * do the copy + */ + hr = IStream_CopyTo( pstrChild, pstrTmp, strStat.cbSize, + NULL, NULL ); + + IStream_Release( pstrChild ); + } + + IStream_Release( pstrTmp ); + } + } + + /* copy siblings */ + if (SUCCEEDED(hr)) + hr = StorageBaseImpl_CopyChildEntryTo( This, data.leftChild, skip_storage, + skip_stream, snbExclude, pstgDest ); + + if (SUCCEEDED(hr)) + hr = StorageBaseImpl_CopyChildEntryTo( This, data.rightChild, skip_storage, + skip_stream, snbExclude, pstgDest ); + + TRACE("<-- %#lx\n", hr); + return hr; +} + +static BOOL StorageBaseImpl_IsStreamOpen(StorageBaseImpl * stg, DirRef streamEntry) +{ + StgStreamImpl *strm; + + TRACE("%p, %ld.\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, %ld.\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, %#lx, %ld, %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("<-- %#lx\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, %#lx, %p, %ld, %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("<-- %#lx\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, %ld, %p, %ld, %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, %#lx.\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: %ld, cbSize.Low/High: %ld/%ld, grfMode: %#lx, grfLocksSupported: %ld, grfStateBits: %#lx\n", debugstr_w(pstatstg->pwcsName), pstatstg->type, pstatstg->cbSize.LowPart, pstatstg->cbSize.HighPart, pstatstg->grfMode, pstatstg->grfLocksSupported, pstatstg->grfStateBits); + } + TRACE("<-- %#lx\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 */ + lstrcpyW(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, %#lx, %ld, %ld, %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; + + lstrcpyW(newStreamEntry.name, pwcsName); + + newStreamEntry.stgType = STGTY_STREAM; + newStreamEntry.startingBlock = BLOCK_END_OF_CHAIN; + newStreamEntry.size.LowPart = 0; + newStreamEntry.size.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, %#lx, %ld, %ld, %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: %#lx\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; + } + + lstrcpyW(newEntry.name, pwcsName); + + newEntry.stgType = STGTY_STORAGE; + newEntry.startingBlock = BLOCK_END_OF_CHAIN; + newEntry.size.LowPart = 0; + newEntry.size.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("<-- %#lx\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, %ld, %p, %p, %p.\n", iface, ciidExclude, rgiidExclude, snbExclude, pstgDest); + + if ( pstgDest == 0 ) + return STG_E_INVALIDPOINTER; + + for(i = 0; i < ciidExclude; ++i) + { + if(IsEqualGUID(&IID_IStorage, &rgiidExclude[i])) + skip_storage = TRUE; + else if(IsEqualGUID(&IID_IStream, &rgiidExclude[i])) + skip_stream = TRUE; + else + WARN("Unknown excluded GUID: %s\n", debugstr_guid(&rgiidExclude[i])); + } + + if (!skip_storage) + { + /* Give up early if it looks like this would be infinitely recursive. + * Oddly enough, this includes some cases that aren't really recursive, like + * copying to a transacted child. */ + IStorage *pstgDestAncestor = pstgDest; + IStorage *pstgDestAncestorChild = NULL; + + /* Go up the chain from the destination until we find the source storage. */ + while (pstgDestAncestor != iface) { + pstgDestAncestorChild = pstgDest; + + if (pstgDestAncestor->lpVtbl == &TransactedSnapshotImpl_Vtbl) + { + TransactedSnapshotImpl *snapshot = (TransactedSnapshotImpl*) pstgDestAncestor; + + pstgDestAncestor = &snapshot->transactedParent->IStorage_iface; + } + else if (pstgDestAncestor->lpVtbl == &StorageInternalImpl_Vtbl) + { + StorageInternalImpl *internal = (StorageInternalImpl*) pstgDestAncestor; + + pstgDestAncestor = &internal->parentStorage->IStorage_iface; + } + else + break; + } + + if (pstgDestAncestor == iface) + { + BOOL fail = TRUE; + + if (pstgDestAncestorChild && snbExclude) + { + StorageBaseImpl *ancestorChildBase = (StorageBaseImpl*)pstgDestAncestorChild; + DirEntry data; + WCHAR **snb = snbExclude; + + StorageBaseImpl_ReadDirEntry(ancestorChildBase, ancestorChildBase->storageDirEntry, &data); + + while ( *snb != NULL && fail ) + { + if ( wcscmp(data.name, *snb) == 0 ) + fail = FALSE; + ++snb; + } + } + + if (fail) + return STG_E_ACCESSDENIED; + } + } + + return StorageBaseImpl_CopyStorageEntryTo( This, This->storageDirEntry, + skip_storage, skip_stream, snbExclude, pstgDest ); +} + +/************************************************************************* + * MoveElementTo (IStorage) + */ +static HRESULT WINAPI StorageBaseImpl_MoveElementTo( + IStorage* iface, + const OLECHAR *pwcsName, /* [string][in] */ + IStorage *pstgDest, /* [unique][in] */ + const OLECHAR *pwcsNewName,/* [string][in] */ + DWORD grfFlags) /* [in] */ +{ + FIXME("%p, %s, %p, %s, %#lx: 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, %#lx.\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, %ld.\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("<-- %#lx\n", hr); + return hr; + } + + /* + * Enumerate the elements + */ + hr = IStorage_EnumElements(childStorage, 0, 0, 0, &elements); + if (FAILED(hr)) + { + IStorage_Release(childStorage); + TRACE("<-- %#lx\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("%#lx\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.HighPart = 0; + size.LowPart = 0; + + hr = IStorage_OpenStream(&parentStorage->IStorage_iface, + entryDataToDelete.name, NULL, STGM_WRITE | STGM_SHARE_EXCLUSIVE, 0, &pis); + + if (hr!=S_OK) + { + TRACE("<-- %#lx\n", hr); + return(hr); + } + + /* + * Zap the stream + */ + hr = IStream_SetSize(pis, size); + + if(hr != S_OK) + { + TRACE("<-- %#lx\n", hr); + return hr; + } + + /* + * Release the stream object. + */ + IStream_Release(pis); + TRACE("<-- %#lx\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("<-- %#lx\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("<-- %#lx\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.HighPart = 0; + offset.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=%#lx, smallblock=%#lx, sblimit=%#lx\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; + ULARGE_INTEGER offset; + DWORD bytes_written; + DWORD major_version, dirsectorcount; + + 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; + } + + memset(headerBigBlock, 0, HEADER_SIZE); + 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])); + } + + offset.QuadPart = 0; + 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.LowPart); + + StorageUtl_WriteDWord( + buffer, + OFFSET_PS_SIZE_HIGH, + newData->size.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.LowPart); + + if (This->bigBlockSize < 4096) + { + /* Version 3 files may have junk in the high part of size. */ + buffer->size.HighPart = 0; + } + else + { + StorageUtl_ReadDWord( + currentEntry, + OFFSET_PS_SIZE_HIGH, + &buffer->size.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.LowPart = 0; + offset.HighPart = 0; + cbTotalRead.QuadPart = 0; + + buffer = HeapAlloc(GetProcessHeap(),0,DEF_SMALL_BLOCK_SIZE); + do + { + resRead = SmallBlockChainStream_ReadAt(*ppsbChain, + offset, + min(This->smallBlockSize, size.LowPart - offset.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.LowPart += cbRead; + } + else + { + resRead = STG_E_READFAULT; + break; + } + } while (cbTotalRead.QuadPart < size.QuadPart); + HeapFree(GetProcessHeap(),0,buffer); + + size.HighPart = 0; + size.LowPart = 0; + + if (FAILED(resRead) || FAILED(resWrite)) + { + ERR("conversion failed: resRead = %#lx, resWrite = %#lx\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.HighPart = 0; + offset.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.LowPart - offset.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.LowPart += cbRead; + } + else + { + resRead = STG_E_READFAULT; + break; + } + } + HeapFree(GetProcessHeap(), 0, buffer); + + size.HighPart = 0; + size.LowPart = 0; + + if(FAILED(resRead) || FAILED(resWrite)) + { + ERR("conversion failed: resRead = %#lx, resWrite = %#lx\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 %ld, bigBlockDepotCount %ld\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 neededAddNumBlocks) +{ + 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 * neededAddNumBlocks; + + 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.HighPart = 0; + size.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) + { + DirEntry rootEntry; + /* + * Initialize the directory table + */ + memset(&rootEntry, 0, sizeof(rootEntry)); + lstrcpyW(rootEntry.name, L"Root Entry"); + rootEntry.sizeOfNameString = sizeof(L"Root Entry"); + rootEntry.stgType = STGTY_ROOT; + rootEntry.leftChild = DIRENTRY_NULL; + rootEntry.rightChild = DIRENTRY_NULL; + rootEntry.dirRootEntry = DIRENTRY_NULL; + rootEntry.startingBlock = BLOCK_END_OF_CHAIN; + rootEntry.size.HighPart = 0; + rootEntry.size.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.HighPart = 0; + offset.LowPart = OFFSET_TRANSACTIONSIG; + hr = StorageImpl_ReadAt(This, offset, data, 4, &bytes_read); + + if (SUCCEEDED(hr)) + { + StorageUtl_ReadDWord(data, 0, &This->transactionSig); + + if (oldTransactionSig != This->transactionSig) + { + /* Someone else wrote to this, so toss all cached information. */ + TRACE("signature changed\n"); + + hr = StorageImpl_Refresh(This, FALSE, FALSE); + } + + if (FAILED(hr)) + This->transactionSig = oldTransactionSig; + } + } + + *result = This->transactionSig; + + return hr; +} + +static HRESULT StorageImpl_SetTransactionSig(StorageBaseImpl *base, + ULONG value) +{ + StorageImpl *This = (StorageImpl*)base; + + This->transactionSig = value; + StorageImpl_SaveFileHeader(This); + + return S_OK; +} + +static HRESULT StorageImpl_LockRegion(StorageImpl *This, ULARGE_INTEGER offset, + ULARGE_INTEGER cb, DWORD dwLockType, BOOL *supported) +{ + if ((dwLockType & This->locks_supported) == 0) + { + if (supported) *supported = FALSE; + return S_OK; + } + + if (supported) *supported = TRUE; + return ILockBytes_LockRegion(This->lockBytes, offset, cb, dwLockType); +} + +static HRESULT StorageImpl_UnlockRegion(StorageImpl *This, ULARGE_INTEGER offset, + ULARGE_INTEGER cb, DWORD dwLockType) +{ + if ((dwLockType & This->locks_supported) == 0) + return S_OK; + + return ILockBytes_UnlockRegion(This->lockBytes, offset, cb, dwLockType); +} + +/* Internal function */ +static HRESULT StorageImpl_LockRegionSync(StorageImpl *This, ULARGE_INTEGER offset, + ULARGE_INTEGER cb, DWORD dwLockType, BOOL *supported) +{ + HRESULT hr; + int delay = 0; + DWORD start_time = GetTickCount(); + DWORD last_sanity_check = start_time; + ULARGE_INTEGER sanity_offset, sanity_cb; + + sanity_offset.QuadPart = RANGELOCK_UNK1_FIRST; + sanity_cb.QuadPart = RANGELOCK_UNK1_LAST - RANGELOCK_UNK1_FIRST + 1; + + do + { + hr = StorageImpl_LockRegion(This, offset, cb, dwLockType, supported); + + if (hr == STG_E_ACCESSDENIED || hr == STG_E_LOCKVIOLATION) + { + DWORD current_time = GetTickCount(); + if (current_time - start_time >= 20000) + { + /* timeout */ + break; + } + if (current_time - last_sanity_check >= 500) + { + /* Any storage implementation with the file open in a + * shared mode should not lock these bytes for writing. However, + * some programs (LibreOffice Writer) will keep ALL bytes locked + * when opening in exclusive mode. We can use a read lock to + * detect this case early, and not hang a full 20 seconds. + * + * This can collide with another attempt to open the file in + * exclusive mode, but it's unlikely, and someone would fail anyway. */ + hr = StorageImpl_LockRegion(This, sanity_offset, sanity_cb, WINE_LOCK_READ, NULL); + if (hr == STG_E_ACCESSDENIED || hr == STG_E_LOCKVIOLATION) + break; + if (SUCCEEDED(hr)) + { + StorageImpl_UnlockRegion(This, sanity_offset, sanity_cb, WINE_LOCK_READ); + hr = STG_E_ACCESSDENIED; + } + + last_sanity_check = current_time; + } + Sleep(delay); + if (delay < 150) delay++; + } + } while (hr == STG_E_ACCESSDENIED || hr == STG_E_LOCKVIOLATION); + + return hr; +} + +static HRESULT StorageImpl_LockTransaction(StorageBaseImpl *base, BOOL write) +{ + StorageImpl *This = (StorageImpl*)base; + HRESULT hr; + ULARGE_INTEGER offset, cb; + + if (write) + { + /* Synchronous grab of second priority range, the commit lock, and the + * lock-checking lock. */ + offset.QuadPart = RANGELOCK_TRANSACTION_FIRST; + cb.QuadPart = RANGELOCK_TRANSACTION_LAST - RANGELOCK_TRANSACTION_FIRST + 1; + } + else + { + offset.QuadPart = RANGELOCK_COMMIT; + cb.QuadPart = 1; + } + + hr = StorageImpl_LockRegionSync(This, offset, cb, LOCK_ONLYONCE, NULL); + + return hr; +} + +static HRESULT StorageImpl_UnlockTransaction(StorageBaseImpl *base, BOOL write) +{ + StorageImpl *This = (StorageImpl*)base; + HRESULT hr; + ULARGE_INTEGER offset, cb; + + if (write) + { + offset.QuadPart = RANGELOCK_TRANSACTION_FIRST; + cb.QuadPart = RANGELOCK_TRANSACTION_LAST - RANGELOCK_TRANSACTION_FIRST + 1; + } + else + { + offset.QuadPart = RANGELOCK_COMMIT; + cb.QuadPart = 1; + } + + hr = StorageImpl_UnlockRegion(This, offset, cb, LOCK_ONLYONCE); + + return hr; +} + +static HRESULT StorageImpl_GetFilename(StorageBaseImpl* iface, LPWSTR *result) +{ + StorageImpl *This = (StorageImpl*) iface; + STATSTG statstg; + HRESULT hr; + + hr = ILockBytes_Stat(This->lockBytes, &statstg, 0); + + *result = statstg.pwcsName; + + return hr; +} + +static HRESULT StorageImpl_CheckLockRange(StorageImpl *This, ULONG start, + ULONG end, HRESULT fail_hr) +{ + HRESULT hr; + ULARGE_INTEGER offset, cb; + + offset.QuadPart = start; + cb.QuadPart = 1 + end - start; + + hr = StorageImpl_LockRegion(This, offset, cb, LOCK_ONLYONCE, NULL); + if (SUCCEEDED(hr)) StorageImpl_UnlockRegion(This, offset, cb, LOCK_ONLYONCE); + + if (FAILED(hr)) + return fail_hr; + else + return S_OK; +} + +static HRESULT StorageImpl_LockOne(StorageImpl *This, ULONG start, ULONG end) +{ + HRESULT hr=S_OK; + int i, j; + ULARGE_INTEGER offset, cb; + + cb.QuadPart = 1; + + for (i=start; i<=end; i++) + { + offset.QuadPart = i; + hr = StorageImpl_LockRegion(This, offset, cb, LOCK_ONLYONCE, NULL); + if (hr != STG_E_ACCESSDENIED && hr != STG_E_LOCKVIOLATION) + break; + } + + if (SUCCEEDED(hr)) + { + for (j = 0; j < ARRAY_SIZE(This->locked_bytes); j++) + { + if (This->locked_bytes[j] == 0) + { + This->locked_bytes[j] = i; + break; + } + } + } + + return hr; +} + +static HRESULT StorageImpl_GrabLocks(StorageImpl *This, DWORD openFlags) +{ + HRESULT hr; + ULARGE_INTEGER offset; + ULARGE_INTEGER cb; + DWORD share_mode = STGM_SHARE_MODE(openFlags); + BOOL supported; + + if (openFlags & STGM_NOSNAPSHOT) + { + /* STGM_NOSNAPSHOT implies deny write */ + if (share_mode == STGM_SHARE_DENY_READ) share_mode = STGM_SHARE_EXCLUSIVE; + else if (share_mode != STGM_SHARE_EXCLUSIVE) share_mode = STGM_SHARE_DENY_WRITE; + } + + /* Wrap all other locking inside a single lock so we can check ranges safely */ + offset.QuadPart = RANGELOCK_CHECKLOCKS; + cb.QuadPart = 1; + hr = StorageImpl_LockRegionSync(This, offset, cb, LOCK_ONLYONCE, &supported); + + /* If the ILockBytes doesn't support locking that's ok. */ + if (!supported) return S_OK; + else if (FAILED(hr)) return hr; + + hr = S_OK; + + /* First check for any conflicting locks. */ + if ((openFlags & STGM_PRIORITY) == STGM_PRIORITY) + hr = StorageImpl_CheckLockRange(This, RANGELOCK_COMMIT, RANGELOCK_COMMIT, STG_E_LOCKVIOLATION); + + if (SUCCEEDED(hr) && (STGM_ACCESS_MODE(openFlags) != STGM_WRITE)) + hr = StorageImpl_CheckLockRange(This, RANGELOCK_DENY_READ_FIRST, RANGELOCK_DENY_READ_LAST, STG_E_SHAREVIOLATION); + + if (SUCCEEDED(hr) && (STGM_ACCESS_MODE(openFlags) != STGM_READ)) + hr = StorageImpl_CheckLockRange(This, RANGELOCK_DENY_WRITE_FIRST, RANGELOCK_DENY_WRITE_LAST, STG_E_SHAREVIOLATION); + + if (SUCCEEDED(hr) && (share_mode == STGM_SHARE_DENY_READ || share_mode == STGM_SHARE_EXCLUSIVE)) + hr = StorageImpl_CheckLockRange(This, RANGELOCK_READ_FIRST, RANGELOCK_READ_LAST, STG_E_LOCKVIOLATION); + + if (SUCCEEDED(hr) && (share_mode == STGM_SHARE_DENY_WRITE || share_mode == STGM_SHARE_EXCLUSIVE)) + hr = StorageImpl_CheckLockRange(This, RANGELOCK_WRITE_FIRST, RANGELOCK_WRITE_LAST, STG_E_LOCKVIOLATION); + + if (SUCCEEDED(hr) && STGM_ACCESS_MODE(openFlags) == STGM_READ && share_mode == STGM_SHARE_EXCLUSIVE) + { + hr = StorageImpl_CheckLockRange(This, 0, RANGELOCK_CHECKLOCKS-1, STG_E_LOCKVIOLATION); + + if (SUCCEEDED(hr)) + hr = StorageImpl_CheckLockRange(This, RANGELOCK_CHECKLOCKS+1, RANGELOCK_LAST, STG_E_LOCKVIOLATION); + } + + /* Then grab our locks. */ + if (SUCCEEDED(hr) && (openFlags & STGM_PRIORITY) == STGM_PRIORITY) + { + hr = StorageImpl_LockOne(This, RANGELOCK_PRIORITY1_FIRST, RANGELOCK_PRIORITY1_LAST); + if (SUCCEEDED(hr)) + hr = StorageImpl_LockOne(This, RANGELOCK_PRIORITY2_FIRST, RANGELOCK_PRIORITY2_LAST); + } + + if (SUCCEEDED(hr) && (STGM_ACCESS_MODE(openFlags) != STGM_WRITE)) + hr = StorageImpl_LockOne(This, RANGELOCK_READ_FIRST, RANGELOCK_READ_LAST); + + if (SUCCEEDED(hr) && (STGM_ACCESS_MODE(openFlags) != STGM_READ)) + hr = StorageImpl_LockOne(This, RANGELOCK_WRITE_FIRST, RANGELOCK_WRITE_LAST); + + if (SUCCEEDED(hr) && (share_mode == STGM_SHARE_DENY_READ || share_mode == STGM_SHARE_EXCLUSIVE)) + hr = StorageImpl_LockOne(This, RANGELOCK_DENY_READ_FIRST, RANGELOCK_DENY_READ_LAST); + + if (SUCCEEDED(hr) && (share_mode == STGM_SHARE_DENY_WRITE || share_mode == STGM_SHARE_EXCLUSIVE)) + hr = StorageImpl_LockOne(This, RANGELOCK_DENY_WRITE_FIRST, RANGELOCK_DENY_WRITE_LAST); + + if (SUCCEEDED(hr) && (openFlags & STGM_NOSNAPSHOT) == STGM_NOSNAPSHOT) + hr = StorageImpl_LockOne(This, RANGELOCK_NOSNAPSHOT_FIRST, RANGELOCK_NOSNAPSHOT_LAST); + + offset.QuadPart = RANGELOCK_CHECKLOCKS; + cb.QuadPart = 1; + StorageImpl_UnlockRegion(This, offset, cb, LOCK_ONLYONCE); + + return hr; +} + +static HRESULT StorageImpl_Flush(StorageBaseImpl *storage) +{ + StorageImpl *This = (StorageImpl*)storage; + int i; + HRESULT hr; + TRACE("(%p)\n", This); + + hr = BlockChainStream_Flush(This->smallBlockRootChain); + + if (SUCCEEDED(hr)) + hr = BlockChainStream_Flush(This->rootBlockChain); + + if (SUCCEEDED(hr)) + hr = BlockChainStream_Flush(This->smallBlockDepotChain); + + for (i=0; SUCCEEDED(hr) && i<BLOCKCHAIN_CACHE_SIZE; i++) + if (This->blockChainCache[i]) + hr = BlockChainStream_Flush(This->blockChainCache[i]); + + if (SUCCEEDED(hr)) + hr = ILockBytes_Flush(This->lockBytes); + + return hr; +} + +static void StorageImpl_Invalidate(StorageBaseImpl* iface) +{ + StorageImpl *This = (StorageImpl*) iface; + + StorageBaseImpl_DeleteAll(&This->base); + + This->base.reverted = TRUE; +} + +static void StorageImpl_Destroy(StorageBaseImpl* iface) +{ + StorageImpl *This = (StorageImpl*) iface; + int i; + TRACE("(%p)\n", This); + + StorageImpl_Flush(iface); + + StorageImpl_Invalidate(iface); + + HeapFree(GetProcessHeap(), 0, This->extBigBlockDepotLocations); + + BlockChainStream_Destroy(This->smallBlockRootChain); + BlockChainStream_Destroy(This->rootBlockChain); + BlockChainStream_Destroy(This->smallBlockDepotChain); + + for (i = 0; i < BLOCKCHAIN_CACHE_SIZE; i++) + BlockChainStream_Destroy(This->blockChainCache[i]); + + for (i = 0; i < ARRAY_SIZE(This->locked_bytes); i++) + { + ULARGE_INTEGER offset, cb; + cb.QuadPart = 1; + if (This->locked_bytes[i] != 0) + { + offset.QuadPart = This->locked_bytes[i]; + StorageImpl_UnlockRegion(This, offset, cb, LOCK_ONLYONCE); + } + } + + if (This->lockBytes) + ILockBytes_Release(This->lockBytes); + HeapFree(GetProcessHeap(), 0, This); +} + + +static const StorageBaseImplVtbl StorageImpl_BaseVtbl = +{ + StorageImpl_Destroy, + StorageImpl_Invalidate, + StorageImpl_Flush, + StorageImpl_GetFilename, + StorageImpl_CreateDirEntry, + StorageImpl_BaseWriteDirEntry, + StorageImpl_BaseReadDirEntry, + StorageImpl_DestroyDirEntry, + StorageImpl_StreamReadAt, + StorageImpl_StreamWriteAt, + StorageImpl_StreamSetSize, + StorageImpl_StreamLink, + StorageImpl_GetTransactionSig, + StorageImpl_SetTransactionSig, + StorageImpl_LockTransaction, + StorageImpl_UnlockTransaction +}; + + +/* + * Virtual function table for the IStorageBaseImpl class. + */ +static const IStorageVtbl StorageImpl_Vtbl = +{ + StorageBaseImpl_QueryInterface, + StorageBaseImpl_AddRef, + StorageBaseImpl_Release, + StorageBaseImpl_CreateStream, + StorageBaseImpl_OpenStream, + StorageBaseImpl_CreateStorage, + StorageBaseImpl_OpenStorage, + StorageBaseImpl_CopyTo, + StorageBaseImpl_MoveElementTo, + StorageBaseImpl_Commit, + StorageBaseImpl_Revert, + StorageBaseImpl_EnumElements, + StorageBaseImpl_DestroyElement, + StorageBaseImpl_RenameElement, + StorageBaseImpl_SetElementTimes, + StorageBaseImpl_SetClass, + StorageBaseImpl_SetStateBits, + StorageBaseImpl_Stat +}; + +static HRESULT StorageImpl_Construct( + HANDLE hFile, + LPCOLESTR pwcsName, + ILockBytes* pLkbyt, + DWORD openFlags, + BOOL fileBased, + BOOL create, + ULONG sector_size, + StorageImpl** result) +{ + StorageImpl* This; + HRESULT hr = S_OK; + STATSTG stat; + + if ( FAILED( validateSTGM(openFlags) )) + return STG_E_INVALIDFLAG; + + This = HeapAlloc(GetProcessHeap(), 0, sizeof(StorageImpl)); + if (!This) + return E_OUTOFMEMORY; + + memset(This, 0, sizeof(StorageImpl)); + + list_init(&This->base.strmHead); + + list_init(&This->base.storageHead); + + This->base.IStorage_iface.lpVtbl = &StorageImpl_Vtbl; + This->base.IPropertySetStorage_iface.lpVtbl = &IPropertySetStorage_Vtbl; + This->base.IDirectWriterLock_iface.lpVtbl = &DirectWriterLockVtbl; + This->base.baseVtbl = &StorageImpl_BaseVtbl; + This->base.openFlags = (openFlags & ~STGM_CREATE); + This->base.ref = 1; + This->base.create = create; + + if (openFlags == (STGM_DIRECT_SWMR|STGM_READWRITE|STGM_SHARE_DENY_WRITE)) + This->base.lockingrole = SWMR_Writer; + else if (openFlags == (STGM_DIRECT_SWMR|STGM_READ|STGM_SHARE_DENY_NONE)) + This->base.lockingrole = SWMR_Reader; + else + This->base.lockingrole = SWMR_None; + + This->base.reverted = FALSE; + + /* + * Initialize the big block cache. + */ + This->bigBlockSize = sector_size; + This->smallBlockSize = DEF_SMALL_BLOCK_SIZE; + if (hFile) + hr = FileLockBytesImpl_Construct(hFile, openFlags, pwcsName, &This->lockBytes); + else + { + This->lockBytes = pLkbyt; + ILockBytes_AddRef(pLkbyt); + } + + if (SUCCEEDED(hr)) + hr = ILockBytes_Stat(This->lockBytes, &stat, STATFLAG_NONAME); + + if (SUCCEEDED(hr)) + { + This->locks_supported = stat.grfLocksSupported; + if (!hFile) + /* Don't try to use wine-internal locking flag with custom ILockBytes */ + This->locks_supported &= ~WINE_LOCK_READ; + + hr = StorageImpl_GrabLocks(This, openFlags); + } + + if (SUCCEEDED(hr)) + hr = StorageImpl_Refresh(This, TRUE, create); + + if (FAILED(hr)) + { + IStorage_Release(&This->base.IStorage_iface); + *result = NULL; + } + else + { + StorageImpl_Flush(&This->base); + *result = This; + } + + return hr; +} + + +/************************************************************************ + * StorageInternalImpl implementation + ***********************************************************************/ + +static void StorageInternalImpl_Invalidate( StorageBaseImpl *base ) +{ + StorageInternalImpl* This = (StorageInternalImpl*) base; + + if (!This->base.reverted) + { + TRACE("Storage invalidated (stg=%p)\n", This); + + This->base.reverted = TRUE; + + This->parentStorage = NULL; + + StorageBaseImpl_DeleteAll(&This->base); + + list_remove(&This->ParentListEntry); + } +} + +static void StorageInternalImpl_Destroy( StorageBaseImpl *iface) +{ + StorageInternalImpl* This = (StorageInternalImpl*) iface; + + StorageInternalImpl_Invalidate(&This->base); + + HeapFree(GetProcessHeap(), 0, This); +} + +static HRESULT StorageInternalImpl_Flush(StorageBaseImpl* iface) +{ + StorageInternalImpl* This = (StorageInternalImpl*) iface; + + return StorageBaseImpl_Flush(This->parentStorage); +} + +static HRESULT StorageInternalImpl_GetFilename(StorageBaseImpl* iface, LPWSTR *result) +{ + StorageInternalImpl* This = (StorageInternalImpl*) iface; + + return StorageBaseImpl_GetFilename(This->parentStorage, result); +} + +static HRESULT StorageInternalImpl_CreateDirEntry(StorageBaseImpl *base, + const DirEntry *newData, DirRef *index) +{ + StorageInternalImpl* This = (StorageInternalImpl*) base; + + return StorageBaseImpl_CreateDirEntry(This->parentStorage, + newData, index); +} + +static HRESULT StorageInternalImpl_WriteDirEntry(StorageBaseImpl *base, + DirRef index, const DirEntry *data) +{ + StorageInternalImpl* This = (StorageInternalImpl*) base; + + return StorageBaseImpl_WriteDirEntry(This->parentStorage, + index, data); +} + +static HRESULT StorageInternalImpl_ReadDirEntry(StorageBaseImpl *base, + DirRef index, DirEntry *data) +{ + StorageInternalImpl* This = (StorageInternalImpl*) base; + + return StorageBaseImpl_ReadDirEntry(This->parentStorage, + index, data); +} + +static HRESULT StorageInternalImpl_DestroyDirEntry(StorageBaseImpl *base, + DirRef index) +{ + StorageInternalImpl* This = (StorageInternalImpl*) base; + + return StorageBaseImpl_DestroyDirEntry(This->parentStorage, + index); +} + +static HRESULT StorageInternalImpl_StreamReadAt(StorageBaseImpl *base, + DirRef index, ULARGE_INTEGER offset, ULONG size, void *buffer, ULONG *bytesRead) +{ + StorageInternalImpl* This = (StorageInternalImpl*) base; + + return StorageBaseImpl_StreamReadAt(This->parentStorage, + index, offset, size, buffer, bytesRead); +} + +static HRESULT StorageInternalImpl_StreamWriteAt(StorageBaseImpl *base, + DirRef index, ULARGE_INTEGER offset, ULONG size, const void *buffer, ULONG *bytesWritten) +{ + StorageInternalImpl* This = (StorageInternalImpl*) base; + + return StorageBaseImpl_StreamWriteAt(This->parentStorage, + index, offset, size, buffer, bytesWritten); +} + +static HRESULT StorageInternalImpl_StreamSetSize(StorageBaseImpl *base, + DirRef index, ULARGE_INTEGER newsize) +{ + StorageInternalImpl* This = (StorageInternalImpl*) base; + + return StorageBaseImpl_StreamSetSize(This->parentStorage, + index, newsize); +} + +static HRESULT StorageInternalImpl_StreamLink(StorageBaseImpl *base, + DirRef dst, DirRef src) +{ + StorageInternalImpl* This = (StorageInternalImpl*) base; + + return StorageBaseImpl_StreamLink(This->parentStorage, + dst, src); +} + +static HRESULT StorageInternalImpl_GetTransactionSig(StorageBaseImpl *base, + ULONG* result, BOOL refresh) +{ + return E_NOTIMPL; +} + +static HRESULT StorageInternalImpl_SetTransactionSig(StorageBaseImpl *base, + ULONG value) +{ + return E_NOTIMPL; +} + +static HRESULT StorageInternalImpl_LockTransaction(StorageBaseImpl *base, BOOL write) +{ + return E_NOTIMPL; +} + +static HRESULT StorageInternalImpl_UnlockTransaction(StorageBaseImpl *base, BOOL write) +{ + return E_NOTIMPL; +} + +/****************************************************************************** +** +** StorageInternalImpl_Commit +** +*/ +static HRESULT WINAPI StorageInternalImpl_Commit( + IStorage* iface, + DWORD grfCommitFlags) /* [in] */ +{ + StorageBaseImpl* This = impl_from_IStorage(iface); + TRACE("%p, %#lx.\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, %#lx.\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("<-- %#lx\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=%lx r=%lx d=%lx <-- %lx\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("%lx %s l=%lx r=%lx d=%lx\n", index, debugstr_w(data->name), data->leftChild, data->rightChild, data->dirRootEntry); + + hr = TransactedSnapshotImpl_EnsureReadEntry(This, index); + if (FAILED(hr)) + { + TRACE("<-- %#lx\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("<-- %#lx\n", hr); + return hr; + } + + memcpy(data, &This->entries[index].data, sizeof(DirEntry)); + + TRACE("%lx %s l=%lx r=%lx d=%lx\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("<-- %#lx\n", hr); + return hr; + } + + hr = TransactedSnapshotImpl_MakeStreamDirty(This, index); + if (FAILED(hr)) + { + TRACE("<-- %#lx\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("<-- %#lx\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("<-- %#lx\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("<-- %#lx\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("<-- %#lx\n", hr); + return hr; + } + + hr = TransactedSnapshotImpl_EnsureReadEntry(This, dst); + if (FAILED(hr)) + { + TRACE("<-- %#lx\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, %#lx\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("<-- %#lx\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 %lx\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(void *buffer, ULONG offset, WORD value) +{ + value = htole16(value); + memcpy((BYTE *)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(void *buffer, ULONG offset, DWORD value) +{ + value = htole32(value); + memcpy((BYTE *)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.HighPart); + value->u.HighPart = htole32(tmp.LowPart); +#else + memcpy(value, buffer + offset, sizeof(ULARGE_INTEGER)); +#endif +} + +void StorageUtl_WriteULargeInteger(void *buffer, ULONG offset, const ULARGE_INTEGER *value) +{ +#ifdef WORDS_BIGENDIAN + ULARGE_INTEGER tmp; + + tmp.LowPart = htole32(value->u.HighPart); + tmp.HighPart = htole32(value->u.LowPart); + memcpy((BYTE *)buffer + offset, &tmp, sizeof(ULARGE_INTEGER)); +#else + memcpy((BYTE *)buffer + offset, value, sizeof(ULARGE_INTEGER)); +#endif +} + +void StorageUtl_ReadGUID(const BYTE* buffer, ULONG offset, GUID* value) +{ + StorageUtl_ReadDWord(buffer, offset, (DWORD *)&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(void *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((BYTE *)buffer + offset + 8, value->Data4, sizeof(value->Data4)); +} + +void StorageUtl_CopyDirEntryToSTATSTG( + StorageBaseImpl* storage, + STATSTG* destination, + const DirEntry* source, + int statFlags) +{ + /* + * The copy of the string occurs only when the flag is not set + */ + if (!(statFlags & STATFLAG_NONAME) && source->stgType == STGTY_ROOT) + { + /* Use the filename for the root storage. */ + destination->pwcsName = 0; + StorageBaseImpl_GetFilename(storage, &destination->pwcsName); + } + else if( ((statFlags & STATFLAG_NONAME) != 0) || + (source->name[0] == 0) ) + { + destination->pwcsName = 0; + } + else + { + destination->pwcsName = + CoTaskMemAlloc((lstrlenW(source->name)+1)*sizeof(WCHAR)); + + lstrcpyW(destination->pwcsName, source->name); + } + + switch (source->stgType) + { + case STGTY_STORAGE: + case STGTY_ROOT: + destination->type = STGTY_STORAGE; + break; + case STGTY_STREAM: + destination->type = STGTY_STREAM; + break; + default: + destination->type = STGTY_STREAM; + break; + } + + destination->cbSize = source->size; +/* + currentReturnStruct->mtime = {0}; TODO + currentReturnStruct->ctime = {0}; + currentReturnStruct->atime = {0}; +*/ + destination->grfMode = 0; + destination->grfLocksSupported = 0; + destination->clsid = source->clsid; + destination->grfStateBits = 0; + destination->reserved = 0; +} + + +/************************************************************************ + * BlockChainStream implementation + ***********************************************************************/ + +/****************************************************************************** + * BlockChainStream_GetHeadOfChain + * + * Returns the head of this stream chain. + * Some special chains don't have directory entries, their heads are kept in + * This->headOfStreamPlaceHolder. + * + */ +static ULONG BlockChainStream_GetHeadOfChain(BlockChainStream* This) +{ + DirEntry chainEntry; + HRESULT hr; + + if (This->headOfStreamPlaceHolder != 0) + return *(This->headOfStreamPlaceHolder); + + if (This->ownerDirEntry != DIRENTRY_NULL) + { + hr = StorageImpl_ReadDirEntry( + This->parentStorage, + This->ownerDirEntry, + &chainEntry); + + if (SUCCEEDED(hr) && chainEntry.startingBlock < BLOCK_FIRST_SPECIAL) + return chainEntry.startingBlock; + } + + return BLOCK_END_OF_CHAIN; +} + +/* Read and save the index of all blocks in this stream. */ +static HRESULT BlockChainStream_UpdateIndexCache(BlockChainStream* This) +{ + ULONG next_sector, next_offset; + HRESULT hr; + struct BlockChainRun *last_run; + + if (This->indexCacheLen == 0) + { + last_run = NULL; + next_offset = 0; + next_sector = BlockChainStream_GetHeadOfChain(This); + } + else + { + last_run = &This->indexCache[This->indexCacheLen-1]; + next_offset = last_run->lastOffset+1; + hr = StorageImpl_GetNextBlockInChain(This->parentStorage, + last_run->firstSector + last_run->lastOffset - last_run->firstOffset, + &next_sector); + if (FAILED(hr)) return hr; + } + + while (next_sector != BLOCK_END_OF_CHAIN) + { + if (!last_run || next_sector != last_run->firstSector + next_offset - last_run->firstOffset) + { + /* Add the current block to the cache. */ + if (This->indexCacheSize == 0) + { + This->indexCache = HeapAlloc(GetProcessHeap(), 0, sizeof(struct BlockChainRun)*16); + if (!This->indexCache) return E_OUTOFMEMORY; + This->indexCacheSize = 16; + } + else if (This->indexCacheSize == This->indexCacheLen) + { + struct BlockChainRun *new_cache; + ULONG new_size; + + new_size = This->indexCacheSize * 2; + new_cache = HeapAlloc(GetProcessHeap(), 0, sizeof(struct BlockChainRun)*new_size); + if (!new_cache) return E_OUTOFMEMORY; + memcpy(new_cache, This->indexCache, sizeof(struct BlockChainRun)*This->indexCacheLen); + + HeapFree(GetProcessHeap(), 0, This->indexCache); + This->indexCache = new_cache; + This->indexCacheSize = new_size; + } + + This->indexCacheLen++; + last_run = &This->indexCache[This->indexCacheLen-1]; + last_run->firstSector = next_sector; + last_run->firstOffset = next_offset; + } + + last_run->lastOffset = next_offset; + + /* Find the next block. */ + next_offset++; + hr = StorageImpl_GetNextBlockInChain(This->parentStorage, next_sector, &next_sector); + if (FAILED(hr)) return hr; + } + + if (This->indexCacheLen) + { + This->tailIndex = last_run->firstSector + last_run->lastOffset - last_run->firstOffset; + This->numBlocks = last_run->lastOffset+1; + } + else + { + This->tailIndex = BLOCK_END_OF_CHAIN; + This->numBlocks = 0; + } + + return S_OK; +} + +/* Locate the nth block in this stream. */ +static ULONG BlockChainStream_GetSectorOfOffset(BlockChainStream *This, ULONG offset) +{ + ULONG min_offset = 0, max_offset = This->numBlocks-1; + ULONG min_run = 0, max_run = This->indexCacheLen-1; + + if (offset >= This->numBlocks) + return BLOCK_END_OF_CHAIN; + + while (min_run < max_run) + { + ULONG run_to_check = min_run + (offset - min_offset) * (max_run - min_run) / (max_offset - min_offset); + if (offset < This->indexCache[run_to_check].firstOffset) + { + max_offset = This->indexCache[run_to_check].firstOffset-1; + max_run = run_to_check-1; + } + else if (offset > This->indexCache[run_to_check].lastOffset) + { + min_offset = This->indexCache[run_to_check].lastOffset+1; + min_run = run_to_check+1; + } + else + /* Block is in this run. */ + min_run = max_run = run_to_check; + } + + return This->indexCache[min_run].firstSector + offset - This->indexCache[min_run].firstOffset; +} + +static HRESULT BlockChainStream_GetBlockAtOffset(BlockChainStream *This, + ULONG index, BlockChainBlock **block, ULONG *sector, BOOL create) +{ + BlockChainBlock *result=NULL; + int i; + + for (i=0; i<2; i++) + if (This->cachedBlocks[i].index == index) + { + *sector = This->cachedBlocks[i].sector; + *block = &This->cachedBlocks[i]; + return S_OK; + } + + *sector = BlockChainStream_GetSectorOfOffset(This, index); + if (*sector == BLOCK_END_OF_CHAIN) + return STG_E_DOCFILECORRUPT; + + if (create) + { + if (This->cachedBlocks[0].index == 0xffffffff) + result = &This->cachedBlocks[0]; + else if (This->cachedBlocks[1].index == 0xffffffff) + result = &This->cachedBlocks[1]; + else + { + result = &This->cachedBlocks[This->blockToEvict++]; + if (This->blockToEvict == 2) + This->blockToEvict = 0; + } + + if (result->dirty) + { + if (!StorageImpl_WriteBigBlock(This->parentStorage, result->sector, result->data)) + return STG_E_WRITEFAULT; + result->dirty = FALSE; + } + + result->read = FALSE; + result->index = index; + result->sector = *sector; + } + + *block = result; + return S_OK; +} + +BlockChainStream* BlockChainStream_Construct( + StorageImpl* parentStorage, + ULONG* headOfStreamPlaceHolder, + DirRef dirEntry) +{ + BlockChainStream* newStream; + + newStream = HeapAlloc(GetProcessHeap(), 0, sizeof(BlockChainStream)); + 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, 1); + 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, newNumBlocks - oldNumBlocks); + + 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, %li, %p, %lu, %p.\n",This, offset.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(&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.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.LowPart / This->parentStorage->smallBlockSize; + + ULONG offsetInBlock = offset.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.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.LowPart / This->parentStorage->smallBlockSize; + + ULONG offsetInBlock = offset.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.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.LowPart / This->parentStorage->smallBlockSize; + + if ((newSize.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.LowPart / This->parentStorage->smallBlockSize; + + if ((newSize.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.LowPart == size.LowPart) + return TRUE; + + if (newSize.LowPart < size.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.HighPart = 0; + + result.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]; + + memset(tempPath, 0, sizeof(tempPath)); + memset(tempFileName, 0, sizeof(tempFileName)); + + if ((GetTempPathW(MAX_PATH, tempPath)) == 0 ) + tempPath[0] = '.'; + + if (GetTempFileNameW(tempPath, L"STO", 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 = %#lx\n", *ppstgOpen, hr); + + return hr; +} + +/************************************************************************ + * 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 %#lx\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; +} /*********************************************************************** * WriteClassStg [coml2.@] @@ -238,3 +8761,421 @@ HRESULT WINAPI StgCreatePropSetStg(IStorage *pstg, DWORD reserved, IPropertySetS return IStorage_QueryInterface(pstg, &IID_IPropertySetStorage, (void**)propset); } + +/****************************************************************************** + * 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 object + * + * 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, %#lx, %ld, %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, %#lx, %#lx, %#lx, %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; +} + +/****************************************************************************** + * StgOpenStorageEx [OLE32.@] + */ +HRESULT WINAPI StgOpenStorageEx(const WCHAR* pwcsName, DWORD grfMode, DWORD stgfmt, DWORD grfAttrs, STGOPTIONS* pStgOptions, void* reserved, REFIID riid, void** ppObjectOpen) +{ + TRACE("%s, %#lx, %#lx, %#lx, %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, %#lx, %p, %ld, %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("<-- %#lx, 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; +} diff --git a/dlls/ole32/storage32.h b/dlls/coml2/storage32.h similarity index 98% rename from dlls/ole32/storage32.h rename to dlls/coml2/storage32.h index dd4b89aceec..b1b50420540 100644 --- a/dlls/ole32/storage32.h +++ b/dlls/coml2/storage32.h @@ -159,13 +159,6 @@ struct DirEntry HRESULT FileLockBytesImpl_Construct(HANDLE hFile, DWORD openFlags, LPCWSTR pwcsName, ILockBytes **pLockBytes); -/************************************************************************* - * Ole Convert support - */ - -HRESULT STORAGE_CreateOleStream(IStorage*, DWORD); -HRESULT OLECONVERT_CreateCompObjStream(LPSTORAGE pStorage, LPCSTR strOleTypeName); - enum swmr_mode { SWMR_None, diff --git a/dlls/ole32/Makefile.in b/dlls/ole32/Makefile.in index ee0fcc41f7d..565e7045862 100644 --- a/dlls/ole32/Makefile.in +++ b/dlls/ole32/Makefile.in @@ -15,11 +15,9 @@ SOURCES = \ datacache.c \ dcom.idl \ defaulthandler.c \ - dictionary.c \ drag_copy.svg \ drag_link.svg \ drag_move.svg \ - filelockbytes.c \ filemoniker.c \ ftmarshal.c \ git.c \ @@ -39,7 +37,6 @@ SOURCES = \ oleproxy.c \ pointermoniker.c \ stg_prop.c \ - stg_stream.c \ storage32.c \ usrmarshal.c diff --git a/dlls/ole32/clipboard.c b/dlls/ole32/clipboard.c index c54608985fb..302805d0c75 100644 --- a/dlls/ole32/clipboard.c +++ b/dlls/ole32/clipboard.c @@ -74,12 +74,13 @@ #include "wine/debug.h" #include "olestd.h" -#include "storage32.h" - #include "compobj_private.h" WINE_DEFAULT_DEBUG_CHANNEL(ole); +HRESULT STORAGE_CreateOleStream(IStorage*, DWORD); +HRESULT OLECONVERT_CreateCompObjStream(LPSTORAGE pStorage, LPCSTR strOleTypeName); + /* Structure of 'Ole Private Data' clipboard format */ typedef struct { diff --git a/dlls/ole32/defaulthandler.c b/dlls/ole32/defaulthandler.c index b69a054b572..3be31c39a81 100644 --- a/dlls/ole32/defaulthandler.c +++ b/dlls/ole32/defaulthandler.c @@ -58,7 +58,6 @@ #include "ole2.h" #include "compobj_private.h" -#include "storage32.h" #include "wine/debug.h" @@ -78,6 +77,8 @@ enum object_state object_state_deferred_close }; +HRESULT STORAGE_CreateOleStream(IStorage*, DWORD); + /**************************************************************************** * DefaultHandler * diff --git a/dlls/ole32/ole32.spec b/dlls/ole32/ole32.spec index e9982036eeb..e29c6f72dfb 100644 --- a/dlls/ole32/ole32.spec +++ b/dlls/ole32/ole32.spec @@ -281,7 +281,7 @@ @ stdcall StgOpenStorage(wstr ptr long ptr long ptr) @ stdcall StgOpenStorageEx(wstr long long long ptr ptr ptr ptr) @ stdcall StgOpenStorageOnILockBytes(ptr ptr long ptr long ptr) -@ stdcall StgSetTimes(wstr ptr ptr ptr ) +@ stdcall StgSetTimes(wstr ptr ptr ptr) @ stdcall StringFromCLSID(ptr ptr) combase.StringFromCLSID @ stdcall StringFromGUID2(ptr ptr long) combase.StringFromGUID2 @ stdcall StringFromIID(ptr ptr) combase.StringFromIID diff --git a/dlls/ole32/stg_prop.c b/dlls/ole32/stg_prop.c index 66b12f3ae9e..7939a9c484a 100644 --- a/dlls/ole32/stg_prop.c +++ b/dlls/ole32/stg_prop.c @@ -51,2948 +51,18 @@ #include "wine/asm.h" #include "wine/debug.h" #include "wine/heap.h" -#include "dictionary.h" -#include "storage32.h" #include "oleauto.h" WINE_DEFAULT_DEBUG_CHANNEL(storage); -static inline StorageImpl *impl_from_IPropertySetStorage( IPropertySetStorage *iface ) -{ - return CONTAINING_RECORD(iface, StorageImpl, base.IPropertySetStorage_iface); -} - -/* These are documented in MSDN, - * but they don't seem to be in any header file. - */ -#define PROPSETHDR_BYTEORDER_MAGIC 0xfffe -#define PROPSETHDR_OSVER_KIND_WIN16 0 -#define PROPSETHDR_OSVER_KIND_MAC 1 -#define PROPSETHDR_OSVER_KIND_WIN32 2 - -#define CP_UNICODE 1200 - -#define MAX_VERSION_0_PROP_NAME_LENGTH 256 - -#define CFTAG_WINDOWS (-1L) -#define CFTAG_MACINTOSH (-2L) -#define CFTAG_FMTID (-3L) -#define CFTAG_NODATA 0L - -#define ALIGNED_LENGTH(_Len, _Align) (((_Len)+(_Align))&~(_Align)) - -typedef struct tagPROPERTYSETHEADER -{ - WORD wByteOrder; /* always 0xfffe */ - WORD wFormat; /* can be zero or one */ - DWORD dwOSVer; /* OS version of originating system */ - CLSID clsid; /* application CLSID */ - DWORD reserved; /* always 1 */ -} PROPERTYSETHEADER; - -typedef struct tagFORMATIDOFFSET -{ - FMTID fmtid; - DWORD dwOffset; /* from beginning of stream */ -} FORMATIDOFFSET; - -typedef struct tagPROPERTYSECTIONHEADER -{ - DWORD cbSection; - DWORD cProperties; -} PROPERTYSECTIONHEADER; - -typedef struct tagPROPERTYIDOFFSET -{ - DWORD propid; - DWORD dwOffset; /* from beginning of section */ -} PROPERTYIDOFFSET; - -typedef struct tagPropertyStorage_impl PropertyStorage_impl; - -/* Initializes the property storage from the stream (and undoes any uncommitted - * changes in the process.) Returns an error if there is an error reading or - * if the stream format doesn't match what's expected. - */ -static HRESULT PropertyStorage_ReadFromStream(PropertyStorage_impl *); - -static HRESULT PropertyStorage_WriteToStream(PropertyStorage_impl *); - -/* Creates the dictionaries used by the property storage. If successful, all - * the dictionaries have been created. If failed, none has been. (This makes - * it a bit easier to deal with destroying them.) - */ -static HRESULT PropertyStorage_CreateDictionaries(PropertyStorage_impl *); - -static void PropertyStorage_DestroyDictionaries(PropertyStorage_impl *); - -/* Copies from propvar to prop. If propvar's type is VT_LPSTR, copies the - * string using PropertyStorage_StringCopy. - */ -static HRESULT PropertyStorage_PropVariantCopy(PROPVARIANT *prop, - const PROPVARIANT *propvar, UINT targetCP, UINT srcCP); - -/* Copies the string src, which is encoded using code page srcCP, and returns - * it in *dst, in the code page specified by targetCP. The returned string is - * allocated using CoTaskMemAlloc. - * If srcCP is CP_UNICODE, src is in fact an LPCWSTR. Similarly, if targetCP - * is CP_UNICODE, the returned string is in fact an LPWSTR. - * Returns S_OK on success, something else on failure. - */ -static HRESULT PropertyStorage_StringCopy(LPCSTR src, UINT srcCP, LPSTR *dst, - UINT targetCP); - -static const IPropertyStorageVtbl IPropertyStorage_Vtbl; - -/*********************************************************************** - * Implementation of IPropertyStorage - */ -struct tagPropertyStorage_impl -{ - IPropertyStorage IPropertyStorage_iface; - LONG ref; - CRITICAL_SECTION cs; - IStream *stm; - BOOL dirty; - FMTID fmtid; - CLSID clsid; - WORD format; - DWORD originatorOS; - DWORD grfFlags; - DWORD grfMode; - UINT codePage; - LCID locale; - PROPID highestProp; - struct dictionary *name_to_propid; - struct dictionary *propid_to_name; - struct dictionary *propid_to_prop; -}; - -static inline PropertyStorage_impl *impl_from_IPropertyStorage(IPropertyStorage *iface) -{ - return CONTAINING_RECORD(iface, PropertyStorage_impl, IPropertyStorage_iface); -} - -struct enum_stat_prop_stg -{ - IEnumSTATPROPSTG IEnumSTATPROPSTG_iface; - LONG refcount; - PropertyStorage_impl *storage; - STATPROPSTG *stats; - size_t current; - size_t count; -}; - -static struct enum_stat_prop_stg *impl_from_IEnumSTATPROPSTG(IEnumSTATPROPSTG *iface) -{ - return CONTAINING_RECORD(iface, struct enum_stat_prop_stg, IEnumSTATPROPSTG_iface); -} - -static HRESULT WINAPI enum_stat_prop_stg_QueryInterface(IEnumSTATPROPSTG *iface, REFIID riid, void **obj) -{ - TRACE("%p, %s, %p.\n", iface, debugstr_guid(riid), obj); - - if (IsEqualIID(riid, &IID_IEnumSTATPROPSTG) || - IsEqualIID(riid, &IID_IUnknown)) - { - *obj = iface; - IEnumSTATPROPSTG_AddRef(iface); - return S_OK; - } - - WARN("Unsupported interface %s.\n", debugstr_guid(riid)); - return E_NOINTERFACE; -} - -static ULONG WINAPI enum_stat_prop_stg_AddRef(IEnumSTATPROPSTG *iface) -{ - struct enum_stat_prop_stg *penum = impl_from_IEnumSTATPROPSTG(iface); - LONG refcount = InterlockedIncrement(&penum->refcount); - - TRACE("%p, refcount %lu.\n", iface, refcount); - - return refcount; -} - -static ULONG WINAPI enum_stat_prop_stg_Release(IEnumSTATPROPSTG *iface) -{ - struct enum_stat_prop_stg *penum = impl_from_IEnumSTATPROPSTG(iface); - LONG refcount = InterlockedDecrement(&penum->refcount); - - TRACE("%p, refcount %lu.\n", iface, refcount); - - if (!refcount) - { - IPropertyStorage_Release(&penum->storage->IPropertyStorage_iface); - heap_free(penum->stats); - heap_free(penum); - } - - return refcount; -} - -static HRESULT WINAPI enum_stat_prop_stg_Next(IEnumSTATPROPSTG *iface, ULONG celt, STATPROPSTG *ret, ULONG *fetched) -{ - struct enum_stat_prop_stg *penum = impl_from_IEnumSTATPROPSTG(iface); - ULONG count = 0; - WCHAR *name; - - TRACE("%p, %lu, %p, %p.\n", iface, celt, ret, fetched); - - if (penum->current == ~0u) - penum->current = 0; - - while (count < celt && penum->current < penum->count) - { - *ret = penum->stats[penum->current++]; - - if (dictionary_find(penum->storage->propid_to_name, UlongToPtr(ret->propid), (void **)&name)) - { - SIZE_T size = (lstrlenW(name) + 1) * sizeof(WCHAR); - ret->lpwstrName = CoTaskMemAlloc(size); - if (ret->lpwstrName) - memcpy(ret->lpwstrName, name, size); - } - ret++; - count++; - } - - if (fetched) - *fetched = count; - - return count < celt ? S_FALSE : S_OK; -} - -static HRESULT WINAPI enum_stat_prop_stg_Skip(IEnumSTATPROPSTG *iface, ULONG celt) -{ - FIXME("%p, %lu.\n", iface, celt); - - return S_OK; -} - -static HRESULT WINAPI enum_stat_prop_stg_Reset(IEnumSTATPROPSTG *iface) -{ - struct enum_stat_prop_stg *penum = impl_from_IEnumSTATPROPSTG(iface); - - TRACE("%p.\n", iface); - - penum->current = ~0u; - - return S_OK; -} - -static HRESULT WINAPI enum_stat_prop_stg_Clone(IEnumSTATPROPSTG *iface, IEnumSTATPROPSTG **ppenum) -{ - FIXME("%p, %p.\n", iface, ppenum); - - return E_NOTIMPL; -} - -static const IEnumSTATPROPSTGVtbl enum_stat_prop_stg_vtbl = -{ - enum_stat_prop_stg_QueryInterface, - enum_stat_prop_stg_AddRef, - enum_stat_prop_stg_Release, - enum_stat_prop_stg_Next, - enum_stat_prop_stg_Skip, - enum_stat_prop_stg_Reset, - enum_stat_prop_stg_Clone, -}; - -static BOOL prop_enum_stat(const void *k, const void *v, void *extra, void *arg) -{ - struct enum_stat_prop_stg *stg = arg; - PROPID propid = PtrToUlong(k); - const PROPVARIANT *prop = v; - STATPROPSTG *dest; - - dest = &stg->stats[stg->count]; - - dest->lpwstrName = NULL; - dest->propid = propid; - dest->vt = prop->vt; - stg->count++; - - return TRUE; -} - -static BOOL prop_enum_stat_count(const void *k, const void *v, void *extra, void *arg) -{ - DWORD *count = arg; - - *count += 1; - - return TRUE; -} - -static HRESULT create_enum_stat_prop_stg(PropertyStorage_impl *storage, IEnumSTATPROPSTG **ret) -{ - struct enum_stat_prop_stg *enum_obj; - DWORD count; - - enum_obj = heap_alloc_zero(sizeof(*enum_obj)); - if (!enum_obj) - return E_OUTOFMEMORY; - - enum_obj->IEnumSTATPROPSTG_iface.lpVtbl = &enum_stat_prop_stg_vtbl; - enum_obj->refcount = 1; - enum_obj->storage = storage; - IPropertyStorage_AddRef(&storage->IPropertyStorage_iface); - - count = 0; - dictionary_enumerate(storage->propid_to_prop, prop_enum_stat_count, &count); - - if (count) - { - if (!(enum_obj->stats = heap_alloc(sizeof(*enum_obj->stats) * count))) - { - IEnumSTATPROPSTG_Release(&enum_obj->IEnumSTATPROPSTG_iface); - return E_OUTOFMEMORY; - } - - dictionary_enumerate(storage->propid_to_prop, prop_enum_stat, enum_obj); - } - - *ret = &enum_obj->IEnumSTATPROPSTG_iface; - - return S_OK; -} - -/************************************************************************ - * IPropertyStorage_fnQueryInterface (IPropertyStorage) - */ -static HRESULT WINAPI IPropertyStorage_fnQueryInterface( - IPropertyStorage *iface, - REFIID riid, - void** ppvObject) -{ - PropertyStorage_impl *This = impl_from_IPropertyStorage(iface); - - TRACE("(%p, %s, %p)\n", This, debugstr_guid(riid), ppvObject); - - if (!ppvObject) - return E_INVALIDARG; - - *ppvObject = 0; - - if (IsEqualGUID(&IID_IUnknown, riid) || - IsEqualGUID(&IID_IPropertyStorage, riid)) - { - IPropertyStorage_AddRef(iface); - *ppvObject = iface; - return S_OK; - } - - return E_NOINTERFACE; -} - -/************************************************************************ - * IPropertyStorage_fnAddRef (IPropertyStorage) - */ -static ULONG WINAPI IPropertyStorage_fnAddRef( - IPropertyStorage *iface) -{ - PropertyStorage_impl *This = impl_from_IPropertyStorage(iface); - return InterlockedIncrement(&This->ref); -} - -/************************************************************************ - * IPropertyStorage_fnRelease (IPropertyStorage) - */ -static ULONG WINAPI IPropertyStorage_fnRelease( - IPropertyStorage *iface) -{ - PropertyStorage_impl *This = impl_from_IPropertyStorage(iface); - ULONG ref; - - ref = InterlockedDecrement(&This->ref); - if (ref == 0) - { - TRACE("Destroying %p\n", This); - if (This->dirty) - IPropertyStorage_Commit(iface, STGC_DEFAULT); - IStream_Release(This->stm); - This->cs.DebugInfo->Spare[0] = 0; - DeleteCriticalSection(&This->cs); - PropertyStorage_DestroyDictionaries(This); - HeapFree(GetProcessHeap(), 0, This); - } - return ref; -} - -static PROPVARIANT *PropertyStorage_FindProperty(PropertyStorage_impl *This, - DWORD propid) -{ - PROPVARIANT *ret = NULL; - - dictionary_find(This->propid_to_prop, UlongToPtr(propid), (void **)&ret); - TRACE("returning %p\n", ret); - return ret; -} - -/* Returns NULL if name is NULL. */ -static PROPVARIANT *PropertyStorage_FindPropertyByName( - PropertyStorage_impl *This, LPCWSTR name) -{ - PROPVARIANT *ret = NULL; - void *propid; - - if (!name) - return NULL; - if (This->codePage == CP_UNICODE) - { - if (dictionary_find(This->name_to_propid, name, &propid)) - ret = PropertyStorage_FindProperty(This, PtrToUlong(propid)); - } - else - { - LPSTR ansiName; - HRESULT hr = PropertyStorage_StringCopy((LPCSTR)name, CP_UNICODE, - &ansiName, This->codePage); - - if (SUCCEEDED(hr)) - { - if (dictionary_find(This->name_to_propid, ansiName, &propid)) - ret = PropertyStorage_FindProperty(This, PtrToUlong(propid)); - CoTaskMemFree(ansiName); - } - } - TRACE("returning %p\n", ret); - return ret; -} - -static LPWSTR PropertyStorage_FindPropertyNameById(PropertyStorage_impl *This, - DWORD propid) -{ - LPWSTR ret = NULL; - - dictionary_find(This->propid_to_name, UlongToPtr(propid), (void **)&ret); - TRACE("returning %p\n", ret); - return ret; -} - -/************************************************************************ - * IPropertyStorage_fnReadMultiple (IPropertyStorage) - */ -static HRESULT WINAPI IPropertyStorage_fnReadMultiple( - IPropertyStorage* iface, - ULONG cpspec, - const PROPSPEC rgpspec[], - PROPVARIANT rgpropvar[]) -{ - PropertyStorage_impl *This = impl_from_IPropertyStorage(iface); - HRESULT hr = S_OK; - ULONG i; - - TRACE("%p, %lu, %p, %p\n", iface, cpspec, rgpspec, rgpropvar); - - if (!cpspec) - return S_FALSE; - if (!rgpspec || !rgpropvar) - return E_INVALIDARG; - EnterCriticalSection(&This->cs); - for (i = 0; i < cpspec; i++) - { - PropVariantInit(&rgpropvar[i]); - if (rgpspec[i].ulKind == PRSPEC_LPWSTR) - { - PROPVARIANT *prop = PropertyStorage_FindPropertyByName(This, - rgpspec[i].lpwstr); - - if (prop) - PropertyStorage_PropVariantCopy(&rgpropvar[i], prop, GetACP(), - This->codePage); - } - else - { - switch (rgpspec[i].propid) - { - case PID_CODEPAGE: - rgpropvar[i].vt = VT_I2; - rgpropvar[i].iVal = This->codePage; - break; - case PID_LOCALE: - rgpropvar[i].vt = VT_I4; - rgpropvar[i].lVal = This->locale; - break; - default: - { - PROPVARIANT *prop = PropertyStorage_FindProperty(This, - rgpspec[i].propid); - - if (prop) - PropertyStorage_PropVariantCopy(&rgpropvar[i], prop, - GetACP(), This->codePage); - else - hr = S_FALSE; - } - } - } - } - LeaveCriticalSection(&This->cs); - return hr; -} - -static HRESULT PropertyStorage_StringCopy(LPCSTR src, UINT srcCP, LPSTR *dst, UINT dstCP) -{ - HRESULT hr = S_OK; - int len; - - TRACE("%s, %p, %d, %d\n", - srcCP == CP_UNICODE ? debugstr_w((LPCWSTR)src) : debugstr_a(src), dst, - dstCP, srcCP); - assert(src); - assert(dst); - *dst = NULL; - if (dstCP == srcCP) - { - size_t len; - - if (dstCP == CP_UNICODE) - len = (lstrlenW((LPCWSTR)src) + 1) * sizeof(WCHAR); - else - len = strlen(src) + 1; - *dst = CoTaskMemAlloc(len); - if (!*dst) - hr = STG_E_INSUFFICIENTMEMORY; - else - memcpy(*dst, src, len); - } - else - { - if (dstCP == CP_UNICODE) - { - len = MultiByteToWideChar(srcCP, 0, src, -1, NULL, 0); - *dst = CoTaskMemAlloc(len * sizeof(WCHAR)); - if (!*dst) - hr = STG_E_INSUFFICIENTMEMORY; - else - MultiByteToWideChar(srcCP, 0, src, -1, (LPWSTR)*dst, len); - } - else - { - LPCWSTR wideStr = NULL; - LPWSTR wideStr_tmp = NULL; - - if (srcCP == CP_UNICODE) - wideStr = (LPCWSTR)src; - else - { - len = MultiByteToWideChar(srcCP, 0, src, -1, NULL, 0); - wideStr_tmp = HeapAlloc(GetProcessHeap(), 0, len * sizeof(WCHAR)); - if (wideStr_tmp) - { - MultiByteToWideChar(srcCP, 0, src, -1, wideStr_tmp, len); - wideStr = wideStr_tmp; - } - else - hr = STG_E_INSUFFICIENTMEMORY; - } - if (SUCCEEDED(hr)) - { - len = WideCharToMultiByte(dstCP, 0, wideStr, -1, NULL, 0, - NULL, NULL); - *dst = CoTaskMemAlloc(len); - if (!*dst) - hr = STG_E_INSUFFICIENTMEMORY; - else - { - BOOL defCharUsed = FALSE; - - if (WideCharToMultiByte(dstCP, 0, wideStr, -1, *dst, len, - NULL, &defCharUsed) == 0 || defCharUsed) - { - CoTaskMemFree(*dst); - *dst = NULL; - hr = HRESULT_FROM_WIN32(ERROR_NO_UNICODE_TRANSLATION); - } - } - } - HeapFree(GetProcessHeap(), 0, wideStr_tmp); - } - } - TRACE("returning %#lx (%s)\n", hr, - dstCP == CP_UNICODE ? debugstr_w((LPCWSTR)*dst) : debugstr_a(*dst)); - return hr; -} - -static HRESULT PropertyStorage_PropVariantCopy(PROPVARIANT *prop, const PROPVARIANT *propvar, - UINT targetCP, UINT srcCP) -{ - HRESULT hr = S_OK; - - assert(prop); - assert(propvar); - - switch (propvar->vt) - { - case VT_LPSTR: - hr = PropertyStorage_StringCopy(propvar->pszVal, srcCP, &prop->pszVal, targetCP); - if (SUCCEEDED(hr)) - prop->vt = VT_LPSTR; - break; - case VT_BSTR: - if ((prop->bstrVal = SysAllocStringLen(propvar->bstrVal, SysStringLen(propvar->bstrVal)))) - prop->vt = VT_BSTR; - else - hr = E_OUTOFMEMORY; - break; - default: - hr = PropVariantCopy(prop, propvar); - } - - return hr; -} - -/* Stores the property with id propid and value propvar into this property - * storage. lcid is ignored if propvar's type is not VT_LPSTR. If propvar's - * type is VT_LPSTR, converts the string using lcid as the source code page - * and This->codePage as the target code page before storing. - * As a side effect, may change This->format to 1 if the type of propvar is - * a version 1-only property. - */ -static HRESULT PropertyStorage_StorePropWithId(PropertyStorage_impl *This, - PROPID propid, const PROPVARIANT *propvar, UINT cp) -{ - HRESULT hr = S_OK; - PROPVARIANT *prop = PropertyStorage_FindProperty(This, propid); - - assert(propvar); - if (propvar->vt & VT_BYREF || propvar->vt & VT_ARRAY) - This->format = 1; - switch (propvar->vt) - { - case VT_DECIMAL: - case VT_I1: - case VT_INT: - case VT_UINT: - case VT_VECTOR|VT_I1: - This->format = 1; - } - TRACE("Setting %#lx to type %d\n", propid, propvar->vt); - if (prop) - { - PropVariantClear(prop); - hr = PropertyStorage_PropVariantCopy(prop, propvar, This->codePage, cp); - } - else - { - prop = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, - sizeof(PROPVARIANT)); - if (prop) - { - hr = PropertyStorage_PropVariantCopy(prop, propvar, This->codePage, cp); - if (SUCCEEDED(hr)) - { - dictionary_insert(This->propid_to_prop, UlongToPtr(propid), prop); - if (propid > This->highestProp) - This->highestProp = propid; - } - else - HeapFree(GetProcessHeap(), 0, prop); - } - else - hr = STG_E_INSUFFICIENTMEMORY; - } - return hr; -} - -/* Adds the name srcName to the name dictionaries, mapped to property ID id. - * srcName is encoded in code page cp, and is converted to This->codePage. - * If cp is CP_UNICODE, srcName is actually a unicode string. - * As a side effect, may change This->format to 1 if srcName is too long for - * a version 0 property storage. - * Doesn't validate id. - */ -static HRESULT PropertyStorage_StoreNameWithId(PropertyStorage_impl *This, - LPCSTR srcName, UINT cp, PROPID id) -{ - LPSTR name; - HRESULT hr; - - assert(srcName); - - hr = PropertyStorage_StringCopy(srcName, cp, &name, This->codePage); - if (SUCCEEDED(hr)) - { - if (This->codePage == CP_UNICODE) - { - if (lstrlenW((LPWSTR)name) >= MAX_VERSION_0_PROP_NAME_LENGTH) - This->format = 1; - } - else - { - if (strlen(name) >= MAX_VERSION_0_PROP_NAME_LENGTH) - This->format = 1; - } - TRACE("Adding prop name %s, propid %ld\n", - This->codePage == CP_UNICODE ? debugstr_w((LPCWSTR)name) : - debugstr_a(name), id); - dictionary_insert(This->name_to_propid, name, UlongToPtr(id)); - dictionary_insert(This->propid_to_name, UlongToPtr(id), name); - } - return hr; -} - -/************************************************************************ - * IPropertyStorage_fnWriteMultiple (IPropertyStorage) - */ -static HRESULT WINAPI IPropertyStorage_fnWriteMultiple( - IPropertyStorage* iface, - ULONG cpspec, - const PROPSPEC rgpspec[], - const PROPVARIANT rgpropvar[], - PROPID propidNameFirst) -{ - PropertyStorage_impl *This = impl_from_IPropertyStorage(iface); - HRESULT hr = S_OK; - ULONG i; - - TRACE("%p, %lu, %p, %p.\n", iface, cpspec, rgpspec, rgpropvar); - - if (cpspec && (!rgpspec || !rgpropvar)) - return E_INVALIDARG; - if (!(This->grfMode & STGM_READWRITE)) - return STG_E_ACCESSDENIED; - EnterCriticalSection(&This->cs); - This->dirty = TRUE; - This->originatorOS = (DWORD)MAKELONG(LOWORD(GetVersion()), - PROPSETHDR_OSVER_KIND_WIN32) ; - for (i = 0; i < cpspec; i++) - { - if (rgpspec[i].ulKind == PRSPEC_LPWSTR) - { - PROPVARIANT *prop = PropertyStorage_FindPropertyByName(This, - rgpspec[i].lpwstr); - - if (prop) - PropVariantCopy(prop, &rgpropvar[i]); - else - { - /* Note that I don't do the special cases here that I do below, - * because naming the special PIDs isn't supported. - */ - if (propidNameFirst < PID_FIRST_USABLE || - propidNameFirst >= PID_MIN_READONLY) - hr = STG_E_INVALIDPARAMETER; - else - { - PROPID nextId = max(propidNameFirst, This->highestProp + 1); - - hr = PropertyStorage_StoreNameWithId(This, - (LPCSTR)rgpspec[i].lpwstr, CP_UNICODE, nextId); - if (SUCCEEDED(hr)) - hr = PropertyStorage_StorePropWithId(This, nextId, - &rgpropvar[i], GetACP()); - } - } - } - else - { - switch (rgpspec[i].propid) - { - case PID_DICTIONARY: - /* Can't set the dictionary */ - hr = STG_E_INVALIDPARAMETER; - break; - case PID_CODEPAGE: - /* Can only set the code page if nothing else has been set */ - if (dictionary_num_entries(This->propid_to_prop) == 0 && - rgpropvar[i].vt == VT_I2) - { - This->codePage = (USHORT)rgpropvar[i].iVal; - if (This->codePage == CP_UNICODE) - This->grfFlags &= ~PROPSETFLAG_ANSI; - else - This->grfFlags |= PROPSETFLAG_ANSI; - } - else - hr = STG_E_INVALIDPARAMETER; - break; - case PID_LOCALE: - /* Can only set the locale if nothing else has been set */ - if (dictionary_num_entries(This->propid_to_prop) == 0 && - rgpropvar[i].vt == VT_I4) - This->locale = rgpropvar[i].lVal; - else - hr = STG_E_INVALIDPARAMETER; - break; - case PID_ILLEGAL: - /* silently ignore like MSDN says */ - break; - default: - if (rgpspec[i].propid >= PID_MIN_READONLY) - hr = STG_E_INVALIDPARAMETER; - else - hr = PropertyStorage_StorePropWithId(This, - rgpspec[i].propid, &rgpropvar[i], GetACP()); - } - } - } - if (This->grfFlags & PROPSETFLAG_UNBUFFERED) - IPropertyStorage_Commit(iface, STGC_DEFAULT); - LeaveCriticalSection(&This->cs); - return hr; -} - -/************************************************************************ - * IPropertyStorage_fnDeleteMultiple (IPropertyStorage) - */ -static HRESULT WINAPI IPropertyStorage_fnDeleteMultiple( - IPropertyStorage* iface, - ULONG cpspec, - const PROPSPEC rgpspec[]) -{ - PropertyStorage_impl *This = impl_from_IPropertyStorage(iface); - ULONG i; - HRESULT hr; - - TRACE("%p, %ld, %p.\n", iface, cpspec, rgpspec); - - if (cpspec && !rgpspec) - return E_INVALIDARG; - if (!(This->grfMode & STGM_READWRITE)) - return STG_E_ACCESSDENIED; - hr = S_OK; - EnterCriticalSection(&This->cs); - This->dirty = TRUE; - for (i = 0; i < cpspec; i++) - { - if (rgpspec[i].ulKind == PRSPEC_LPWSTR) - { - void *propid; - - if (dictionary_find(This->name_to_propid, rgpspec[i].lpwstr, &propid)) - dictionary_remove(This->propid_to_prop, propid); - } - else - { - if (rgpspec[i].propid >= PID_FIRST_USABLE && - rgpspec[i].propid < PID_MIN_READONLY) - dictionary_remove(This->propid_to_prop, UlongToPtr(rgpspec[i].propid)); - else - hr = STG_E_INVALIDPARAMETER; - } - } - if (This->grfFlags & PROPSETFLAG_UNBUFFERED) - IPropertyStorage_Commit(iface, STGC_DEFAULT); - LeaveCriticalSection(&This->cs); - return hr; -} - -/************************************************************************ - * IPropertyStorage_fnReadPropertyNames (IPropertyStorage) - */ -static HRESULT WINAPI IPropertyStorage_fnReadPropertyNames( - IPropertyStorage* iface, - ULONG cpropid, - const PROPID rgpropid[], - LPOLESTR rglpwstrName[]) -{ - PropertyStorage_impl *This = impl_from_IPropertyStorage(iface); - ULONG i; - HRESULT hr = S_FALSE; - - TRACE("%p, %ld, %p, %p.\n", iface, cpropid, rgpropid, rglpwstrName); - - if (cpropid && (!rgpropid || !rglpwstrName)) - return E_INVALIDARG; - EnterCriticalSection(&This->cs); - for (i = 0; i < cpropid && SUCCEEDED(hr); i++) - { - LPWSTR name = PropertyStorage_FindPropertyNameById(This, rgpropid[i]); - - if (name) - { - size_t len = lstrlenW(name); - - hr = S_OK; - rglpwstrName[i] = CoTaskMemAlloc((len + 1) * sizeof(WCHAR)); - if (rglpwstrName[i]) - memcpy(rglpwstrName[i], name, (len + 1) * sizeof(WCHAR)); - else - hr = STG_E_INSUFFICIENTMEMORY; - } - else - rglpwstrName[i] = NULL; - } - LeaveCriticalSection(&This->cs); - return hr; -} - -/************************************************************************ - * IPropertyStorage_fnWritePropertyNames (IPropertyStorage) - */ -static HRESULT WINAPI IPropertyStorage_fnWritePropertyNames( - IPropertyStorage* iface, - ULONG cpropid, - const PROPID rgpropid[], - const LPOLESTR rglpwstrName[]) -{ - PropertyStorage_impl *This = impl_from_IPropertyStorage(iface); - ULONG i; - HRESULT hr; - - TRACE("%p, %lu, %p, %p.\n", iface, cpropid, rgpropid, rglpwstrName); - - if (cpropid && (!rgpropid || !rglpwstrName)) - return E_INVALIDARG; - if (!(This->grfMode & STGM_READWRITE)) - return STG_E_ACCESSDENIED; - hr = S_OK; - EnterCriticalSection(&This->cs); - This->dirty = TRUE; - for (i = 0; SUCCEEDED(hr) && i < cpropid; i++) - { - if (rgpropid[i] != PID_ILLEGAL) - hr = PropertyStorage_StoreNameWithId(This, (LPCSTR)rglpwstrName[i], - CP_UNICODE, rgpropid[i]); - } - if (This->grfFlags & PROPSETFLAG_UNBUFFERED) - IPropertyStorage_Commit(iface, STGC_DEFAULT); - LeaveCriticalSection(&This->cs); - return hr; -} - -/************************************************************************ - * IPropertyStorage_fnDeletePropertyNames (IPropertyStorage) - */ -static HRESULT WINAPI IPropertyStorage_fnDeletePropertyNames( - IPropertyStorage* iface, - ULONG cpropid, - const PROPID rgpropid[]) -{ - PropertyStorage_impl *This = impl_from_IPropertyStorage(iface); - ULONG i; - HRESULT hr; - - TRACE("%p, %ld, %p.\n", iface, cpropid, rgpropid); - - if (cpropid && !rgpropid) - return E_INVALIDARG; - if (!(This->grfMode & STGM_READWRITE)) - return STG_E_ACCESSDENIED; - hr = S_OK; - EnterCriticalSection(&This->cs); - This->dirty = TRUE; - for (i = 0; i < cpropid; i++) - { - LPWSTR name = NULL; - - if (dictionary_find(This->propid_to_name, UlongToPtr(rgpropid[i]), (void **)&name)) - { - dictionary_remove(This->propid_to_name, UlongToPtr(rgpropid[i])); - dictionary_remove(This->name_to_propid, name); - } - } - if (This->grfFlags & PROPSETFLAG_UNBUFFERED) - IPropertyStorage_Commit(iface, STGC_DEFAULT); - LeaveCriticalSection(&This->cs); - return hr; -} - -/************************************************************************ - * IPropertyStorage_fnCommit (IPropertyStorage) - */ -static HRESULT WINAPI IPropertyStorage_fnCommit( - IPropertyStorage* iface, - DWORD grfCommitFlags) -{ - PropertyStorage_impl *This = impl_from_IPropertyStorage(iface); - HRESULT hr; - - TRACE("%p, %#lx.\n", iface, grfCommitFlags); - - if (!(This->grfMode & STGM_READWRITE)) - return STG_E_ACCESSDENIED; - EnterCriticalSection(&This->cs); - if (This->dirty) - hr = PropertyStorage_WriteToStream(This); - else - hr = S_OK; - LeaveCriticalSection(&This->cs); - return hr; -} - -/************************************************************************ - * IPropertyStorage_fnRevert (IPropertyStorage) - */ -static HRESULT WINAPI IPropertyStorage_fnRevert( - IPropertyStorage* iface) -{ - HRESULT hr; - PropertyStorage_impl *This = impl_from_IPropertyStorage(iface); - - TRACE("%p\n", iface); - - EnterCriticalSection(&This->cs); - if (This->dirty) - { - PropertyStorage_DestroyDictionaries(This); - hr = PropertyStorage_CreateDictionaries(This); - if (SUCCEEDED(hr)) - hr = PropertyStorage_ReadFromStream(This); - } - else - hr = S_OK; - LeaveCriticalSection(&This->cs); - return hr; -} - -/************************************************************************ - * IPropertyStorage_fnEnum (IPropertyStorage) - */ -static HRESULT WINAPI IPropertyStorage_fnEnum(IPropertyStorage *iface, IEnumSTATPROPSTG **ppenum) -{ - PropertyStorage_impl *storage = impl_from_IPropertyStorage(iface); - - TRACE("%p, %p.\n", iface, ppenum); - - return create_enum_stat_prop_stg(storage, ppenum); -} - -/************************************************************************ - * IPropertyStorage_fnSetTimes (IPropertyStorage) - */ -static HRESULT WINAPI IPropertyStorage_fnSetTimes( - IPropertyStorage* iface, - const FILETIME* pctime, - const FILETIME* patime, - const FILETIME* pmtime) -{ - FIXME("\n"); - return E_NOTIMPL; -} - -/************************************************************************ - * IPropertyStorage_fnSetClass (IPropertyStorage) - */ -static HRESULT WINAPI IPropertyStorage_fnSetClass( - IPropertyStorage* iface, - REFCLSID clsid) -{ - PropertyStorage_impl *This = impl_from_IPropertyStorage(iface); - - TRACE("%p, %s\n", iface, debugstr_guid(clsid)); - - if (!clsid) - return E_INVALIDARG; - if (!(This->grfMode & STGM_READWRITE)) - return STG_E_ACCESSDENIED; - This->clsid = *clsid; - This->dirty = TRUE; - if (This->grfFlags & PROPSETFLAG_UNBUFFERED) - IPropertyStorage_Commit(iface, STGC_DEFAULT); - return S_OK; -} - -/************************************************************************ - * IPropertyStorage_fnStat (IPropertyStorage) - */ -static HRESULT WINAPI IPropertyStorage_fnStat( - IPropertyStorage* iface, - STATPROPSETSTG* statpsstg) -{ - PropertyStorage_impl *This = impl_from_IPropertyStorage(iface); - STATSTG stat; - HRESULT hr; - - TRACE("%p, %p\n", iface, statpsstg); - - if (!statpsstg) - return E_INVALIDARG; - - hr = IStream_Stat(This->stm, &stat, STATFLAG_NONAME); - if (SUCCEEDED(hr)) - { - statpsstg->fmtid = This->fmtid; - statpsstg->clsid = This->clsid; - statpsstg->grfFlags = This->grfFlags; - statpsstg->mtime = stat.mtime; - statpsstg->ctime = stat.ctime; - statpsstg->atime = stat.atime; - statpsstg->dwOSVersion = This->originatorOS; - } - return hr; -} - -static int PropertyStorage_PropNameCompare(const void *a, const void *b, - void *extra) -{ - PropertyStorage_impl *This = extra; - - if (This->codePage == CP_UNICODE) - { - TRACE("(%s, %s)\n", debugstr_w(a), debugstr_w(b)); - if (This->grfFlags & PROPSETFLAG_CASE_SENSITIVE) - return wcscmp(a, b); - else - return lstrcmpiW(a, b); - } - else - { - TRACE("(%s, %s)\n", debugstr_a(a), debugstr_a(b)); - if (This->grfFlags & PROPSETFLAG_CASE_SENSITIVE) - return lstrcmpA(a, b); - else - return lstrcmpiA(a, b); - } -} - -static void PropertyStorage_PropNameDestroy(void *k, void *d, void *extra) -{ - CoTaskMemFree(k); -} - -static int PropertyStorage_PropCompare(const void *a, const void *b, - void *extra) -{ - TRACE("%lu, %lu.\n", PtrToUlong(a), PtrToUlong(b)); - return PtrToUlong(a) - PtrToUlong(b); -} - -static void PropertyStorage_PropertyDestroy(void *k, void *d, void *extra) -{ - PropVariantClear(d); - HeapFree(GetProcessHeap(), 0, d); -} - -#ifdef WORDS_BIGENDIAN -/* Swaps each character in str to or from little endian; assumes the conversion - * is symmetric, that is, that lendian16toh is equivalent to htole16. - */ -static void PropertyStorage_ByteSwapString(LPWSTR str, size_t len) -{ - DWORD i; - - /* Swap characters to host order. - * FIXME: alignment? - */ - for (i = 0; i < len; i++) - str[i] = lendian16toh(str[i]); -} -#else -#define PropertyStorage_ByteSwapString(s, l) -#endif - -static void* WINAPI Allocate_CoTaskMemAlloc(void *this, ULONG size) -{ - return CoTaskMemAlloc(size); -} - -struct read_buffer -{ - BYTE *data; - size_t size; -}; - -static HRESULT buffer_test_offset(const struct read_buffer *buffer, size_t offset, size_t len) -{ - return len > buffer->size || offset > buffer->size - len ? STG_E_READFAULT : S_OK; -} - -static HRESULT buffer_read_uint64(const struct read_buffer *buffer, size_t offset, ULARGE_INTEGER *data) -{ - HRESULT hr; - - if (SUCCEEDED(hr = buffer_test_offset(buffer, offset, sizeof(*data)))) - StorageUtl_ReadULargeInteger(buffer->data, offset, data); - - return hr; -} - -static HRESULT buffer_read_dword(const struct read_buffer *buffer, size_t offset, DWORD *data) -{ - HRESULT hr; - - if (SUCCEEDED(hr = buffer_test_offset(buffer, offset, sizeof(*data)))) - StorageUtl_ReadDWord(buffer->data, offset, data); - - return hr; -} - -static HRESULT buffer_read_word(const struct read_buffer *buffer, size_t offset, WORD *data) -{ - HRESULT hr; - - if (SUCCEEDED(hr = buffer_test_offset(buffer, offset, sizeof(*data)))) - StorageUtl_ReadWord(buffer->data, offset, data); - - return hr; -} - -static HRESULT buffer_read_byte(const struct read_buffer *buffer, size_t offset, BYTE *data) -{ - HRESULT hr; - - if (SUCCEEDED(hr = buffer_test_offset(buffer, offset, sizeof(*data)))) - *data = *(buffer->data + offset); - - return hr; -} - -static HRESULT buffer_read_len(const struct read_buffer *buffer, size_t offset, void *dest, size_t len) -{ - HRESULT hr; - - if (SUCCEEDED(hr = buffer_test_offset(buffer, offset, len))) - memcpy(dest, buffer->data + offset, len); - - return hr; -} - -static HRESULT propertystorage_read_scalar(PROPVARIANT *prop, const struct read_buffer *buffer, size_t offset, - UINT codepage, void* (WINAPI *allocate)(void *this, ULONG size), void *allocate_data) -{ - HRESULT hr; - - assert(!(prop->vt & (VT_ARRAY | VT_VECTOR))); - - switch (prop->vt) - { - case VT_EMPTY: - case VT_NULL: - hr = S_OK; - break; - case VT_I1: - hr = buffer_read_byte(buffer, offset, (BYTE *)&prop->cVal); - TRACE("Read char 0x%x ('%c')\n", prop->cVal, prop->cVal); - break; - case VT_UI1: - hr = buffer_read_byte(buffer, offset, &prop->bVal); - TRACE("Read byte 0x%x\n", prop->bVal); - break; - case VT_BOOL: - hr = buffer_read_word(buffer, offset, (WORD *)&prop->boolVal); - TRACE("Read bool %d\n", prop->boolVal); - break; - case VT_I2: - hr = buffer_read_word(buffer, offset, (WORD *)&prop->iVal); - TRACE("Read short %d\n", prop->iVal); - break; - case VT_UI2: - hr = buffer_read_word(buffer, offset, &prop->uiVal); - TRACE("Read ushort %d\n", prop->uiVal); - break; - case VT_INT: - case VT_I4: - hr = buffer_read_dword(buffer, offset, (DWORD *)&prop->lVal); - TRACE("Read long %ld\n", prop->lVal); - break; - case VT_UINT: - case VT_UI4: - hr = buffer_read_dword(buffer, offset, &prop->ulVal); - TRACE("Read ulong %ld\n", prop->ulVal); - break; - case VT_I8: - hr = buffer_read_uint64(buffer, offset, (ULARGE_INTEGER *)&prop->hVal); - TRACE("Read long long %s\n", wine_dbgstr_longlong(prop->hVal.QuadPart)); - break; - case VT_UI8: - hr = buffer_read_uint64(buffer, offset, &prop->uhVal); - TRACE("Read ulong long %s\n", wine_dbgstr_longlong(prop->uhVal.QuadPart)); - break; - case VT_R8: - hr = buffer_read_len(buffer, offset, &prop->dblVal, sizeof(prop->dblVal)); - TRACE("Read double %f\n", prop->dblVal); - break; - case VT_LPSTR: - { - DWORD count; - - if (FAILED(hr = buffer_read_dword(buffer, offset, &count))) - break; - - offset += sizeof(DWORD); - - if (codepage == CP_UNICODE && count % sizeof(WCHAR)) - { - WARN("Unicode string has odd number of bytes\n"); - hr = STG_E_INVALIDHEADER; - } - else - { - prop->pszVal = allocate(allocate_data, count); - if (prop->pszVal) - { - if (FAILED(hr = buffer_read_len(buffer, offset, prop->pszVal, count))) - break; - - /* This is stored in the code page specified in codepage. - * Don't convert it, the caller will just store it as-is. - */ - if (codepage == CP_UNICODE) - { - /* Make sure it's NULL-terminated */ - prop->pszVal[count / sizeof(WCHAR) - 1] = '\0'; - TRACE("Read string value %s\n", - debugstr_w(prop->pwszVal)); - } - else - { - /* Make sure it's NULL-terminated */ - prop->pszVal[count - 1] = '\0'; - TRACE("Read string value %s\n", debugstr_a(prop->pszVal)); - } - } - else - hr = STG_E_INSUFFICIENTMEMORY; - } - break; - } - case VT_BSTR: - { - DWORD count, wcount; - - if (FAILED(hr = buffer_read_dword(buffer, offset, &count))) - break; - - offset += sizeof(DWORD); - - if (codepage == CP_UNICODE && count % sizeof(WCHAR)) - { - WARN("Unicode string has odd number of bytes\n"); - hr = STG_E_INVALIDHEADER; - } - else - { - if (codepage == CP_UNICODE) - wcount = count / sizeof(WCHAR); - else - { - if (FAILED(hr = buffer_test_offset(buffer, offset, count))) - break; - wcount = MultiByteToWideChar(codepage, 0, (LPCSTR)(buffer->data + offset), count, NULL, 0); - } - - prop->bstrVal = SysAllocStringLen(NULL, wcount); /* FIXME: use allocator? */ - - if (prop->bstrVal) - { - if (codepage == CP_UNICODE) - hr = buffer_read_len(buffer, offset, prop->bstrVal, count); - else - MultiByteToWideChar(codepage, 0, (LPCSTR)(buffer->data + offset), count, prop->bstrVal, wcount); - - prop->bstrVal[wcount - 1] = '\0'; - TRACE("Read string value %s\n", debugstr_w(prop->bstrVal)); - } - else - hr = STG_E_INSUFFICIENTMEMORY; - } - break; - } - case VT_BLOB: - { - DWORD count; - - if (FAILED(hr = buffer_read_dword(buffer, offset, &count))) - break; - - offset += sizeof(DWORD); - - prop->blob.cbSize = count; - prop->blob.pBlobData = allocate(allocate_data, count); - if (prop->blob.pBlobData) - { - hr = buffer_read_len(buffer, offset, prop->blob.pBlobData, count); - TRACE("Read blob value of size %ld\n", count); - } - else - hr = STG_E_INSUFFICIENTMEMORY; - break; - } - case VT_LPWSTR: - { - DWORD count; - - if (FAILED(hr = buffer_read_dword(buffer, offset, &count))) - break; - - offset += sizeof(DWORD); - - prop->pwszVal = allocate(allocate_data, count * sizeof(WCHAR)); - if (prop->pwszVal) - { - if (SUCCEEDED(hr = buffer_read_len(buffer, offset, prop->pwszVal, count * sizeof(WCHAR)))) - { - /* make sure string is NULL-terminated */ - prop->pwszVal[count - 1] = '\0'; - PropertyStorage_ByteSwapString(prop->pwszVal, count); - TRACE("Read string value %s\n", debugstr_w(prop->pwszVal)); - } - } - else - hr = STG_E_INSUFFICIENTMEMORY; - break; - } - case VT_FILETIME: - hr = buffer_read_uint64(buffer, offset, (ULARGE_INTEGER *)&prop->filetime); - break; - case VT_CF: - { - DWORD len = 0, tag = 0; - - if (SUCCEEDED(hr = buffer_read_dword(buffer, offset, &len))) - hr = buffer_read_dword(buffer, offset + sizeof(DWORD), &tag); - if (FAILED(hr)) - break; - - offset += 2 * sizeof(DWORD); - - if (len > 8) - { - len -= 8; - prop->pclipdata = allocate(allocate_data, sizeof (CLIPDATA)); - prop->pclipdata->cbSize = len; - prop->pclipdata->ulClipFmt = tag; - prop->pclipdata->pClipData = allocate(allocate_data, len - sizeof(prop->pclipdata->ulClipFmt)); - hr = buffer_read_len(buffer, offset, prop->pclipdata->pClipData, len - sizeof(prop->pclipdata->ulClipFmt)); - } - else - hr = STG_E_INVALIDPARAMETER; - } - break; - case VT_CLSID: - if (!(prop->puuid = allocate(allocate_data, sizeof (*prop->puuid)))) - return STG_E_INSUFFICIENTMEMORY; - - if (SUCCEEDED(hr = buffer_test_offset(buffer, offset, sizeof(*prop->puuid)))) - StorageUtl_ReadGUID(buffer->data, offset, prop->puuid); - - break; - default: - FIXME("unsupported type %d\n", prop->vt); - hr = STG_E_INVALIDPARAMETER; - } - - return hr; -} - -static size_t propertystorage_get_elemsize(const PROPVARIANT *prop) -{ - if (!(prop->vt & VT_VECTOR)) - return 0; - - switch (prop->vt & ~VT_VECTOR) - { - case VT_I1: return sizeof(*prop->cac.pElems); - case VT_UI1: return sizeof(*prop->caub.pElems); - case VT_I2: return sizeof(*prop->cai.pElems); - case VT_UI2: return sizeof(*prop->caui.pElems); - case VT_BOOL: return sizeof(*prop->cabool.pElems); - case VT_I4: return sizeof(*prop->cal.pElems); - case VT_UI4: return sizeof(*prop->caul.pElems); - case VT_R4: return sizeof(*prop->caflt.pElems); - case VT_ERROR: return sizeof(*prop->cascode.pElems); - case VT_I8: return sizeof(*prop->cah.pElems); - case VT_UI8: return sizeof(*prop->cauh.pElems); - case VT_R8: return sizeof(*prop->cadbl.pElems); - case VT_CY: return sizeof(*prop->cacy.pElems); - case VT_DATE: return sizeof(*prop->cadate.pElems); - case VT_FILETIME: return sizeof(*prop->cafiletime.pElems); - case VT_CLSID: return sizeof(*prop->cauuid.pElems); - case VT_VARIANT: return sizeof(*prop->capropvar.pElems); - default: - FIXME("Unhandled type %#x.\n", prop->vt); - return 0; - } -} - -static HRESULT PropertyStorage_ReadProperty(PROPVARIANT *prop, const struct read_buffer *buffer, - size_t offset, UINT codepage, void* (WINAPI *allocate)(void *this, ULONG size), void *allocate_data) -{ - HRESULT hr; - DWORD vt; - - assert(prop); - assert(buffer->data); - - if (FAILED(hr = buffer_read_dword(buffer, offset, &vt))) - return hr; - - offset += sizeof(DWORD); - prop->vt = vt; - - if (prop->vt & VT_VECTOR) - { - DWORD count, i; - - switch (prop->vt & VT_VECTOR) - { - case VT_BSTR: - case VT_VARIANT: - case VT_LPSTR: - case VT_LPWSTR: - case VT_CF: - FIXME("Vector with variable length elements are not supported.\n"); - return STG_E_INVALIDPARAMETER; - default: - ; - } - - if (SUCCEEDED(hr = buffer_read_dword(buffer, offset, &count))) - { - size_t elemsize = propertystorage_get_elemsize(prop); - PROPVARIANT elem; - - offset += sizeof(DWORD); - - if ((prop->capropvar.pElems = allocate(allocate_data, elemsize * count))) - { - prop->capropvar.cElems = count; - elem.vt = prop->vt & ~VT_VECTOR; - - for (i = 0; i < count; ++i) - { - if (SUCCEEDED(hr = propertystorage_read_scalar(&elem, buffer, offset + i * elemsize, codepage, - allocate, allocate_data))) - { - memcpy(&prop->capropvar.pElems[i], &elem.lVal, elemsize); - } - } - } - else - hr = STG_E_INSUFFICIENTMEMORY; - } - } - else if (prop->vt & VT_ARRAY) - { - FIXME("VT_ARRAY properties are not supported.\n"); - hr = STG_E_INVALIDPARAMETER; - } - else - hr = propertystorage_read_scalar(prop, buffer, offset, codepage, allocate, allocate_data); - - return hr; -} - -static HRESULT PropertyStorage_ReadHeaderFromStream(IStream *stm, - PROPERTYSETHEADER *hdr) -{ - BYTE buf[sizeof(PROPERTYSETHEADER)]; - ULONG count = 0; - HRESULT hr; - - assert(stm); - assert(hdr); - hr = IStream_Read(stm, buf, sizeof(buf), &count); - if (SUCCEEDED(hr)) - { - if (count != sizeof(buf)) - { - WARN("read only %ld\n", count); - hr = STG_E_INVALIDHEADER; - } - else - { - StorageUtl_ReadWord(buf, offsetof(PROPERTYSETHEADER, wByteOrder), - &hdr->wByteOrder); - StorageUtl_ReadWord(buf, offsetof(PROPERTYSETHEADER, wFormat), - &hdr->wFormat); - StorageUtl_ReadDWord(buf, offsetof(PROPERTYSETHEADER, dwOSVer), - &hdr->dwOSVer); - StorageUtl_ReadGUID(buf, offsetof(PROPERTYSETHEADER, clsid), - &hdr->clsid); - StorageUtl_ReadDWord(buf, offsetof(PROPERTYSETHEADER, reserved), - &hdr->reserved); - } - } - TRACE("returning %#lx\n", hr); - return hr; -} - -static HRESULT PropertyStorage_ReadFmtIdOffsetFromStream(IStream *stm, - FORMATIDOFFSET *fmt) -{ - BYTE buf[sizeof(FORMATIDOFFSET)]; - ULONG count = 0; - HRESULT hr; - - assert(stm); - assert(fmt); - hr = IStream_Read(stm, buf, sizeof(buf), &count); - if (SUCCEEDED(hr)) - { - if (count != sizeof(buf)) - { - WARN("read only %ld\n", count); - hr = STG_E_INVALIDHEADER; - } - else - { - StorageUtl_ReadGUID(buf, offsetof(FORMATIDOFFSET, fmtid), - &fmt->fmtid); - StorageUtl_ReadDWord(buf, offsetof(FORMATIDOFFSET, dwOffset), - &fmt->dwOffset); - } - } - TRACE("returning %#lx\n", hr); - return hr; -} - -static HRESULT PropertyStorage_ReadSectionHeaderFromStream(IStream *stm, - PROPERTYSECTIONHEADER *hdr) -{ - BYTE buf[sizeof(PROPERTYSECTIONHEADER)]; - ULONG count = 0; - HRESULT hr; - - assert(stm); - assert(hdr); - hr = IStream_Read(stm, buf, sizeof(buf), &count); - if (SUCCEEDED(hr)) - { - if (count != sizeof(buf)) - { - WARN("read only %ld\n", count); - hr = STG_E_INVALIDHEADER; - } - else - { - StorageUtl_ReadDWord(buf, offsetof(PROPERTYSECTIONHEADER, - cbSection), &hdr->cbSection); - StorageUtl_ReadDWord(buf, offsetof(PROPERTYSECTIONHEADER, - cProperties), &hdr->cProperties); - } - } - TRACE("returning %#lx\n", hr); - return hr; -} - -/* Reads the dictionary from the memory buffer beginning at ptr. Interprets - * the entries according to the values of This->codePage and This->locale. - */ -static HRESULT PropertyStorage_ReadDictionary(PropertyStorage_impl *This, const struct read_buffer *buffer, - size_t offset) -{ - DWORD numEntries, i; - HRESULT hr; - - assert(This->name_to_propid); - assert(This->propid_to_name); - - if (FAILED(hr = buffer_read_dword(buffer, offset, &numEntries))) - return hr; - - TRACE("Reading %ld entries:\n", numEntries); - - offset += sizeof(DWORD); - - for (i = 0; SUCCEEDED(hr) && i < numEntries; i++) - { - PROPID propid; - DWORD cbEntry; - WCHAR ch = 0; - - if (SUCCEEDED(hr = buffer_read_dword(buffer, offset, &propid))) - hr = buffer_read_dword(buffer, offset + sizeof(PROPID), &cbEntry); - if (FAILED(hr)) - break; - - offset += sizeof(PROPID) + sizeof(DWORD); - - if (FAILED(hr = buffer_test_offset(buffer, offset, This->codePage == CP_UNICODE ? - ALIGNED_LENGTH(cbEntry * sizeof(WCHAR), 3) : cbEntry))) - { - WARN("Broken name length for entry %ld.\n", i); - return hr; - } - - /* Make sure the source string is NULL-terminated */ - if (This->codePage != CP_UNICODE) - buffer_read_byte(buffer, offset + cbEntry - 1, (BYTE *)&ch); - else - buffer_read_word(buffer, offset + (cbEntry - 1) * sizeof(WCHAR), &ch); - - if (ch) - { - WARN("Dictionary entry name %ld is not null-terminated.\n", i); - return E_FAIL; - } - - TRACE("Reading entry with ID %#lx, %ld chars, name %s.\n", propid, cbEntry, This->codePage == CP_UNICODE ? - debugstr_wn((WCHAR *)buffer->data, cbEntry) : debugstr_an((char *)buffer->data, cbEntry)); - - hr = PropertyStorage_StoreNameWithId(This, (char *)buffer->data + offset, This->codePage, propid); - /* Unicode entries are padded to DWORD boundaries */ - if (This->codePage == CP_UNICODE) - cbEntry = ALIGNED_LENGTH(cbEntry * sizeof(WCHAR), 3); - - offset += cbEntry; - } - - return hr; -} - -static HRESULT PropertyStorage_ReadFromStream(PropertyStorage_impl *This) -{ - struct read_buffer read_buffer; - PROPERTYSETHEADER hdr; - FORMATIDOFFSET fmtOffset; - PROPERTYSECTIONHEADER sectionHdr; - LARGE_INTEGER seek; - ULONG i; - STATSTG stat; - HRESULT hr; - BYTE *buf = NULL; - ULONG count = 0; - DWORD dictOffset = 0; - - This->dirty = FALSE; - This->highestProp = 0; - hr = IStream_Stat(This->stm, &stat, STATFLAG_NONAME); - if (FAILED(hr)) - goto end; - if (stat.cbSize.HighPart) - { - WARN("stream too big\n"); - /* maximum size varies, but it can't be this big */ - hr = STG_E_INVALIDHEADER; - goto end; - } - if (stat.cbSize.LowPart == 0) - { - /* empty stream is okay */ - hr = S_OK; - goto end; - } - else if (stat.cbSize.LowPart < sizeof(PROPERTYSETHEADER) + - sizeof(FORMATIDOFFSET)) - { - WARN("stream too small\n"); - hr = STG_E_INVALIDHEADER; - goto end; - } - seek.QuadPart = 0; - hr = IStream_Seek(This->stm, seek, STREAM_SEEK_SET, NULL); - if (FAILED(hr)) - goto end; - hr = PropertyStorage_ReadHeaderFromStream(This->stm, &hdr); - /* I've only seen reserved == 1, but the article says I shouldn't disallow - * higher values. - */ - if (hdr.wByteOrder != PROPSETHDR_BYTEORDER_MAGIC || hdr.reserved < 1) - { - WARN("bad magic in prop set header\n"); - hr = STG_E_INVALIDHEADER; - goto end; - } - if (hdr.wFormat != 0 && hdr.wFormat != 1) - { - WARN("bad format version %d\n", hdr.wFormat); - hr = STG_E_INVALIDHEADER; - goto end; - } - This->format = hdr.wFormat; - This->clsid = hdr.clsid; - This->originatorOS = hdr.dwOSVer; - if (PROPSETHDR_OSVER_KIND(hdr.dwOSVer) == PROPSETHDR_OSVER_KIND_MAC) - WARN("File comes from a Mac, strings will probably be screwed up\n"); - hr = PropertyStorage_ReadFmtIdOffsetFromStream(This->stm, &fmtOffset); - if (FAILED(hr)) - goto end; - if (fmtOffset.dwOffset > stat.cbSize.LowPart) - { - WARN("invalid offset %ld (stream length is %ld)\n", fmtOffset.dwOffset, stat.cbSize.LowPart); - hr = STG_E_INVALIDHEADER; - goto end; - } - /* wackiness alert: if the format ID is FMTID_DocSummaryInformation, there - * follows not one, but two sections. The first contains the standard properties - * for the document summary information, and the second consists of user-defined - * properties. This is the only case in which multiple sections are - * allowed. - * Reading the second stream isn't implemented yet. - */ - hr = PropertyStorage_ReadSectionHeaderFromStream(This->stm, §ionHdr); - if (FAILED(hr)) - goto end; - /* The section size includes the section header, so check it */ - if (sectionHdr.cbSection < sizeof(PROPERTYSECTIONHEADER)) - { - WARN("section header too small, got %ld\n", sectionHdr.cbSection); - hr = STG_E_INVALIDHEADER; - goto end; - } - buf = HeapAlloc(GetProcessHeap(), 0, sectionHdr.cbSection - - sizeof(PROPERTYSECTIONHEADER)); - if (!buf) - { - hr = STG_E_INSUFFICIENTMEMORY; - goto end; - } - read_buffer.data = buf; - read_buffer.size = sectionHdr.cbSection - sizeof(sectionHdr); - - hr = IStream_Read(This->stm, read_buffer.data, read_buffer.size, &count); - if (FAILED(hr)) - goto end; - TRACE("Reading %ld properties:\n", sectionHdr.cProperties); - for (i = 0; SUCCEEDED(hr) && i < sectionHdr.cProperties; i++) - { - PROPERTYIDOFFSET *idOffset = (PROPERTYIDOFFSET *)(read_buffer.data + - i * sizeof(PROPERTYIDOFFSET)); - - if (idOffset->dwOffset < sizeof(PROPERTYSECTIONHEADER) || - idOffset->dwOffset > sectionHdr.cbSection - sizeof(DWORD)) - hr = STG_E_INVALIDPOINTER; - else - { - if (idOffset->propid >= PID_FIRST_USABLE && - idOffset->propid < PID_MIN_READONLY && idOffset->propid > - This->highestProp) - This->highestProp = idOffset->propid; - if (idOffset->propid == PID_DICTIONARY) - { - /* Don't read the dictionary yet, its entries depend on the - * code page. Just store the offset so we know to read it - * later. - */ - dictOffset = idOffset->dwOffset; - TRACE("Dictionary offset is %ld\n", dictOffset); - } - else - { - PROPVARIANT prop; - - PropVariantInit(&prop); - if (SUCCEEDED(PropertyStorage_ReadProperty(&prop, &read_buffer, - idOffset->dwOffset - sizeof(PROPERTYSECTIONHEADER), This->codePage, - Allocate_CoTaskMemAlloc, NULL))) - { - TRACE("Read property with ID %#lx, type %d\n", idOffset->propid, prop.vt); - switch(idOffset->propid) - { - case PID_CODEPAGE: - if (prop.vt == VT_I2) - This->codePage = (USHORT)prop.iVal; - break; - case PID_LOCALE: - if (prop.vt == VT_I4) - This->locale = (LCID)prop.lVal; - break; - case PID_BEHAVIOR: - if (prop.vt == VT_I4 && prop.lVal) - This->grfFlags |= PROPSETFLAG_CASE_SENSITIVE; - /* The format should already be 1, but just in case */ - This->format = 1; - break; - default: - hr = PropertyStorage_StorePropWithId(This, - idOffset->propid, &prop, This->codePage); - } - } - PropVariantClear(&prop); - } - } - } - if (!This->codePage) - { - /* default to Unicode unless told not to, as specified on msdn */ - if (This->grfFlags & PROPSETFLAG_ANSI) - This->codePage = GetACP(); - else - This->codePage = CP_UNICODE; - } - if (!This->locale) - This->locale = LOCALE_SYSTEM_DEFAULT; - TRACE("Code page is %d, locale is %ld\n", This->codePage, This->locale); - if (dictOffset) - hr = PropertyStorage_ReadDictionary(This, &read_buffer, dictOffset - sizeof(PROPERTYSECTIONHEADER)); - -end: - HeapFree(GetProcessHeap(), 0, buf); - if (FAILED(hr)) - { - dictionary_destroy(This->name_to_propid); - This->name_to_propid = NULL; - dictionary_destroy(This->propid_to_name); - This->propid_to_name = NULL; - dictionary_destroy(This->propid_to_prop); - This->propid_to_prop = NULL; - } - return hr; -} - -static void PropertyStorage_MakeHeader(PropertyStorage_impl *This, - PROPERTYSETHEADER *hdr) -{ - assert(hdr); - StorageUtl_WriteWord(&hdr->wByteOrder, 0, PROPSETHDR_BYTEORDER_MAGIC); - StorageUtl_WriteWord(&hdr->wFormat, 0, This->format); - StorageUtl_WriteDWord(&hdr->dwOSVer, 0, This->originatorOS); - StorageUtl_WriteGUID(&hdr->clsid, 0, &This->clsid); - StorageUtl_WriteDWord(&hdr->reserved, 0, 1); -} - -static void PropertyStorage_MakeFmtIdOffset(PropertyStorage_impl *This, - FORMATIDOFFSET *fmtOffset) -{ - assert(fmtOffset); - StorageUtl_WriteGUID(fmtOffset, 0, &This->fmtid); - StorageUtl_WriteDWord(fmtOffset, offsetof(FORMATIDOFFSET, dwOffset), - sizeof(PROPERTYSETHEADER) + sizeof(FORMATIDOFFSET)); -} - -static void PropertyStorage_MakeSectionHdr(DWORD cbSection, DWORD numProps, - PROPERTYSECTIONHEADER *hdr) -{ - assert(hdr); - StorageUtl_WriteDWord(hdr, 0, cbSection); - StorageUtl_WriteDWord(hdr, offsetof(PROPERTYSECTIONHEADER, cProperties), numProps); -} - -static void PropertyStorage_MakePropertyIdOffset(DWORD propid, DWORD dwOffset, - PROPERTYIDOFFSET *propIdOffset) -{ - assert(propIdOffset); - StorageUtl_WriteDWord(propIdOffset, 0, propid); - StorageUtl_WriteDWord(propIdOffset, offsetof(PROPERTYIDOFFSET, dwOffset), dwOffset); -} - -static inline HRESULT PropertyStorage_WriteWStringToStream(IStream *stm, - LPCWSTR str, DWORD len, DWORD *written) -{ -#ifdef WORDS_BIGENDIAN - WCHAR *leStr = HeapAlloc(GetProcessHeap(), 0, len * sizeof(WCHAR)); - HRESULT hr; - - if (!leStr) - return E_OUTOFMEMORY; - memcpy(leStr, str, len * sizeof(WCHAR)); - PropertyStorage_ByteSwapString(leStr, len); - hr = IStream_Write(stm, leStr, len, written); - HeapFree(GetProcessHeap(), 0, leStr); - return hr; -#else - return IStream_Write(stm, str, len * sizeof(WCHAR), written); -#endif -} - -struct DictionaryClosure -{ - HRESULT hr; - DWORD bytesWritten; -}; - -static BOOL PropertyStorage_DictionaryWriter(const void *key, - const void *value, void *extra, void *closure) -{ - PropertyStorage_impl *This = extra; - struct DictionaryClosure *c = closure; - DWORD propid, keyLen; - ULONG count; - - assert(key); - assert(closure); - StorageUtl_WriteDWord(&propid, 0, PtrToUlong(value)); - c->hr = IStream_Write(This->stm, &propid, sizeof(propid), &count); - if (FAILED(c->hr)) - goto end; - c->bytesWritten += sizeof(DWORD); - if (This->codePage == CP_UNICODE) - { - DWORD pad = 0, pad_len; - - StorageUtl_WriteDWord(&keyLen, 0, lstrlenW((LPCWSTR)key) + 1); - c->hr = IStream_Write(This->stm, &keyLen, sizeof(keyLen), &count); - if (FAILED(c->hr)) - goto end; - c->bytesWritten += sizeof(DWORD); - c->hr = PropertyStorage_WriteWStringToStream(This->stm, key, keyLen, - &count); - if (FAILED(c->hr)) - goto end; - keyLen *= sizeof(WCHAR); - c->bytesWritten += keyLen; - - /* Align to 4 bytes. */ - pad_len = sizeof(DWORD) - keyLen % sizeof(DWORD); - if (pad_len) - { - c->hr = IStream_Write(This->stm, &pad, pad_len, &count); - if (FAILED(c->hr)) - goto end; - c->bytesWritten += pad_len; - } - } - else - { - StorageUtl_WriteDWord(&keyLen, 0, strlen((LPCSTR)key) + 1); - c->hr = IStream_Write(This->stm, &keyLen, sizeof(keyLen), &count); - if (FAILED(c->hr)) - goto end; - c->bytesWritten += sizeof(DWORD); - c->hr = IStream_Write(This->stm, key, keyLen, &count); - if (FAILED(c->hr)) - goto end; - c->bytesWritten += keyLen; - } -end: - return SUCCEEDED(c->hr); -} - -#define SECTIONHEADER_OFFSET sizeof(PROPERTYSETHEADER) + sizeof(FORMATIDOFFSET) - -/* Writes the dictionary to the stream. Assumes without checking that the - * dictionary isn't empty. - */ -static HRESULT PropertyStorage_WriteDictionaryToStream( - PropertyStorage_impl *This, DWORD *sectionOffset) -{ - HRESULT hr; - LARGE_INTEGER seek; - PROPERTYIDOFFSET propIdOffset; - ULONG count; - DWORD dwTemp; - struct DictionaryClosure closure; - - assert(sectionOffset); - - /* The dictionary's always the first property written, so seek to its - * spot. - */ - seek.QuadPart = SECTIONHEADER_OFFSET + sizeof(PROPERTYSECTIONHEADER); - hr = IStream_Seek(This->stm, seek, STREAM_SEEK_SET, NULL); - if (FAILED(hr)) - goto end; - PropertyStorage_MakePropertyIdOffset(PID_DICTIONARY, *sectionOffset, - &propIdOffset); - hr = IStream_Write(This->stm, &propIdOffset, sizeof(propIdOffset), &count); - if (FAILED(hr)) - goto end; - - seek.QuadPart = SECTIONHEADER_OFFSET + *sectionOffset; - hr = IStream_Seek(This->stm, seek, STREAM_SEEK_SET, NULL); - if (FAILED(hr)) - goto end; - StorageUtl_WriteDWord(&dwTemp, 0, dictionary_num_entries(This->name_to_propid)); - hr = IStream_Write(This->stm, &dwTemp, sizeof(dwTemp), &count); - if (FAILED(hr)) - goto end; - *sectionOffset += sizeof(dwTemp); - - closure.hr = S_OK; - closure.bytesWritten = 0; - dictionary_enumerate(This->name_to_propid, PropertyStorage_DictionaryWriter, - &closure); - hr = closure.hr; - if (FAILED(hr)) - goto end; - *sectionOffset += closure.bytesWritten; - if (closure.bytesWritten % sizeof(DWORD)) - { - DWORD padding = sizeof(DWORD) - closure.bytesWritten % sizeof(DWORD); - TRACE("adding %ld bytes of padding\n", padding); - *sectionOffset += padding; - } - -end: - return hr; -} - -static HRESULT PropertyStorage_WritePropertyToStream(PropertyStorage_impl *This, - DWORD propNum, DWORD propid, const PROPVARIANT *var, DWORD *sectionOffset) -{ - DWORD len, dwType, dwTemp, bytesWritten; - HRESULT hr; - LARGE_INTEGER seek; - PROPERTYIDOFFSET propIdOffset; - ULARGE_INTEGER ularge; - ULONG count; - - assert(var); - assert(sectionOffset); - - TRACE("%p, %ld, %#lx, %d, %ld.\n", This, propNum, propid, var->vt, - *sectionOffset); - - seek.QuadPart = SECTIONHEADER_OFFSET + sizeof(PROPERTYSECTIONHEADER) + - propNum * sizeof(PROPERTYIDOFFSET); - hr = IStream_Seek(This->stm, seek, STREAM_SEEK_SET, NULL); - if (FAILED(hr)) - goto end; - PropertyStorage_MakePropertyIdOffset(propid, *sectionOffset, &propIdOffset); - hr = IStream_Write(This->stm, &propIdOffset, sizeof(propIdOffset), &count); - if (FAILED(hr)) - goto end; - - seek.QuadPart = SECTIONHEADER_OFFSET + *sectionOffset; - hr = IStream_Seek(This->stm, seek, STREAM_SEEK_SET, NULL); - if (FAILED(hr)) - goto end; - StorageUtl_WriteDWord(&dwType, 0, var->vt); - hr = IStream_Write(This->stm, &dwType, sizeof(dwType), &count); - if (FAILED(hr)) - goto end; - *sectionOffset += sizeof(dwType); - - switch (var->vt) - { - case VT_EMPTY: - case VT_NULL: - bytesWritten = 0; - break; - case VT_I1: - case VT_UI1: - hr = IStream_Write(This->stm, &var->cVal, sizeof(var->cVal), - &count); - bytesWritten = count; - break; - case VT_I2: - case VT_UI2: - { - WORD wTemp; - - StorageUtl_WriteWord(&wTemp, 0, var->iVal); - hr = IStream_Write(This->stm, &wTemp, sizeof(wTemp), &count); - bytesWritten = count; - break; - } - case VT_I4: - case VT_UI4: - { - StorageUtl_WriteDWord(&dwTemp, 0, var->lVal); - hr = IStream_Write(This->stm, &dwTemp, sizeof(dwTemp), &count); - bytesWritten = count; - break; - } - case VT_I8: - case VT_UI8: - { - StorageUtl_WriteULargeInteger(&ularge, 0, &var->uhVal); - hr = IStream_Write(This->stm, &ularge, sizeof(ularge), &bytesWritten); - break; - } - case VT_LPSTR: - { - if (This->codePage == CP_UNICODE) - len = (lstrlenW(var->pwszVal) + 1) * sizeof(WCHAR); - else - len = lstrlenA(var->pszVal) + 1; - StorageUtl_WriteDWord(&dwTemp, 0, len); - hr = IStream_Write(This->stm, &dwTemp, sizeof(dwTemp), &count); - if (FAILED(hr)) - goto end; - hr = IStream_Write(This->stm, var->pszVal, len, &count); - bytesWritten = count + sizeof(DWORD); - break; - } - case VT_BSTR: - { - if (This->codePage == CP_UNICODE) - { - len = SysStringByteLen(var->bstrVal) + sizeof(WCHAR); - StorageUtl_WriteDWord(&dwTemp, 0, len); - hr = IStream_Write(This->stm, &dwTemp, sizeof(dwTemp), &count); - if (SUCCEEDED(hr)) - hr = IStream_Write(This->stm, var->bstrVal, len, &count); - } - else - { - char *str; - - len = WideCharToMultiByte(This->codePage, 0, var->bstrVal, SysStringLen(var->bstrVal) + 1, - NULL, 0, NULL, NULL); - - str = heap_alloc(len); - if (!str) - { - hr = E_OUTOFMEMORY; - goto end; - } - - WideCharToMultiByte(This->codePage, 0, var->bstrVal, SysStringLen(var->bstrVal), - str, len, NULL, NULL); - StorageUtl_WriteDWord(&dwTemp, 0, len); - hr = IStream_Write(This->stm, &dwTemp, sizeof(dwTemp), &count); - if (SUCCEEDED(hr)) - hr = IStream_Write(This->stm, str, len, &count); - heap_free(str); - } - - bytesWritten = count + sizeof(DWORD); - break; - } - case VT_LPWSTR: - { - len = lstrlenW(var->pwszVal) + 1; - - StorageUtl_WriteDWord(&dwTemp, 0, len); - hr = IStream_Write(This->stm, &dwTemp, sizeof(dwTemp), &count); - if (FAILED(hr)) - goto end; - hr = IStream_Write(This->stm, var->pwszVal, len * sizeof(WCHAR), - &count); - bytesWritten = count + sizeof(DWORD); - break; - } - case VT_FILETIME: - { - FILETIME temp; - - StorageUtl_WriteULargeInteger(&temp, 0, (const ULARGE_INTEGER *)&var->filetime); - hr = IStream_Write(This->stm, &temp, sizeof(temp), &count); - bytesWritten = count; - break; - } - case VT_CF: - { - DWORD cf_hdr[2]; - - len = var->pclipdata->cbSize; - StorageUtl_WriteDWord(&cf_hdr[0], 0, len + 8); - StorageUtl_WriteDWord(&cf_hdr[1], 0, var->pclipdata->ulClipFmt); - hr = IStream_Write(This->stm, cf_hdr, sizeof(cf_hdr), &count); - if (FAILED(hr)) - goto end; - hr = IStream_Write(This->stm, var->pclipdata->pClipData, - len - sizeof(var->pclipdata->ulClipFmt), &count); - if (FAILED(hr)) - goto end; - bytesWritten = count + sizeof cf_hdr; - break; - } - case VT_CLSID: - { - CLSID temp; - - StorageUtl_WriteGUID(&temp, 0, var->puuid); - hr = IStream_Write(This->stm, &temp, sizeof(temp), &count); - bytesWritten = count; - break; - } - case VT_BLOB: - { - StorageUtl_WriteDWord(&dwTemp, 0, var->blob.cbSize); - hr = IStream_Write(This->stm, &dwTemp, sizeof(dwTemp), &count); - if (FAILED(hr)) - goto end; - hr = IStream_Write(This->stm, var->blob.pBlobData, var->blob.cbSize, &count); - bytesWritten = count + sizeof(DWORD); - break; - } - default: - FIXME("unsupported type: %d\n", var->vt); - return STG_E_INVALIDPARAMETER; - } - - if (SUCCEEDED(hr)) - { - *sectionOffset += bytesWritten; - if (bytesWritten % sizeof(DWORD)) - { - DWORD padding = sizeof(DWORD) - bytesWritten % sizeof(DWORD); - TRACE("adding %ld bytes of padding\n", padding); - *sectionOffset += padding; - } - } - -end: - return hr; -} - -struct PropertyClosure -{ - HRESULT hr; - DWORD propNum; - DWORD *sectionOffset; -}; - -static BOOL PropertyStorage_PropertiesWriter(const void *key, const void *value, - void *extra, void *closure) -{ - PropertyStorage_impl *This = extra; - struct PropertyClosure *c = closure; - - assert(key); - assert(value); - assert(extra); - assert(closure); - c->hr = PropertyStorage_WritePropertyToStream(This, c->propNum++, - PtrToUlong(key), value, c->sectionOffset); - return SUCCEEDED(c->hr); -} - -static HRESULT PropertyStorage_WritePropertiesToStream( - PropertyStorage_impl *This, DWORD startingPropNum, DWORD *sectionOffset) -{ - struct PropertyClosure closure; - - assert(sectionOffset); - closure.hr = S_OK; - closure.propNum = startingPropNum; - closure.sectionOffset = sectionOffset; - dictionary_enumerate(This->propid_to_prop, PropertyStorage_PropertiesWriter, - &closure); - return closure.hr; -} - -static HRESULT PropertyStorage_WriteHeadersToStream(PropertyStorage_impl *This) -{ - HRESULT hr; - ULONG count = 0; - LARGE_INTEGER seek = { {0} }; - PROPERTYSETHEADER hdr; - FORMATIDOFFSET fmtOffset; - - hr = IStream_Seek(This->stm, seek, STREAM_SEEK_SET, NULL); - if (FAILED(hr)) - goto end; - PropertyStorage_MakeHeader(This, &hdr); - hr = IStream_Write(This->stm, &hdr, sizeof(hdr), &count); - if (FAILED(hr)) - goto end; - if (count != sizeof(hdr)) - { - hr = STG_E_WRITEFAULT; - goto end; - } - - PropertyStorage_MakeFmtIdOffset(This, &fmtOffset); - hr = IStream_Write(This->stm, &fmtOffset, sizeof(fmtOffset), &count); - if (FAILED(hr)) - goto end; - if (count != sizeof(fmtOffset)) - { - hr = STG_E_WRITEFAULT; - goto end; - } - hr = S_OK; - -end: - return hr; -} - -static HRESULT PropertyStorage_WriteToStream(PropertyStorage_impl *This) -{ - PROPERTYSECTIONHEADER sectionHdr; - HRESULT hr; - ULONG count; - LARGE_INTEGER seek; - DWORD numProps, prop, sectionOffset, dwTemp; - PROPVARIANT var; - - PropertyStorage_WriteHeadersToStream(This); - - /* Count properties. Always at least one property, the code page */ - numProps = 1; - if (dictionary_num_entries(This->name_to_propid)) - numProps++; - if (This->locale != LOCALE_SYSTEM_DEFAULT) - numProps++; - if (This->grfFlags & PROPSETFLAG_CASE_SENSITIVE) - numProps++; - numProps += dictionary_num_entries(This->propid_to_prop); - - /* Write section header with 0 bytes right now, I'll adjust it after - * writing properties. - */ - PropertyStorage_MakeSectionHdr(0, numProps, §ionHdr); - seek.QuadPart = SECTIONHEADER_OFFSET; - hr = IStream_Seek(This->stm, seek, STREAM_SEEK_SET, NULL); - if (FAILED(hr)) - goto end; - hr = IStream_Write(This->stm, §ionHdr, sizeof(sectionHdr), &count); - if (FAILED(hr)) - goto end; - - prop = 0; - sectionOffset = sizeof(PROPERTYSECTIONHEADER) + - numProps * sizeof(PROPERTYIDOFFSET); - - if (dictionary_num_entries(This->name_to_propid)) - { - prop++; - hr = PropertyStorage_WriteDictionaryToStream(This, §ionOffset); - if (FAILED(hr)) - goto end; - } - - PropVariantInit(&var); - - var.vt = VT_I2; - var.iVal = This->codePage; - hr = PropertyStorage_WritePropertyToStream(This, prop++, PID_CODEPAGE, - &var, §ionOffset); - if (FAILED(hr)) - goto end; - - if (This->locale != LOCALE_SYSTEM_DEFAULT) - { - var.vt = VT_I4; - var.lVal = This->locale; - hr = PropertyStorage_WritePropertyToStream(This, prop++, PID_LOCALE, - &var, §ionOffset); - if (FAILED(hr)) - goto end; - } - - if (This->grfFlags & PROPSETFLAG_CASE_SENSITIVE) - { - var.vt = VT_I4; - var.lVal = 1; - hr = PropertyStorage_WritePropertyToStream(This, prop++, PID_BEHAVIOR, - &var, §ionOffset); - if (FAILED(hr)) - goto end; - } - - hr = PropertyStorage_WritePropertiesToStream(This, prop, §ionOffset); - if (FAILED(hr)) - goto end; - - /* Now write the byte count of the section */ - seek.QuadPart = SECTIONHEADER_OFFSET; - hr = IStream_Seek(This->stm, seek, STREAM_SEEK_SET, NULL); - if (FAILED(hr)) - goto end; - StorageUtl_WriteDWord(&dwTemp, 0, sectionOffset); - hr = IStream_Write(This->stm, &dwTemp, sizeof(dwTemp), &count); - -end: - return hr; -} - -/*********************************************************************** - * PropertyStorage_Construct - */ -static void PropertyStorage_DestroyDictionaries(PropertyStorage_impl *This) -{ - dictionary_destroy(This->name_to_propid); - This->name_to_propid = NULL; - dictionary_destroy(This->propid_to_name); - This->propid_to_name = NULL; - dictionary_destroy(This->propid_to_prop); - This->propid_to_prop = NULL; -} - -static HRESULT PropertyStorage_CreateDictionaries(PropertyStorage_impl *This) -{ - HRESULT hr = S_OK; - - This->name_to_propid = dictionary_create( - PropertyStorage_PropNameCompare, PropertyStorage_PropNameDestroy, - This); - if (!This->name_to_propid) - { - hr = STG_E_INSUFFICIENTMEMORY; - goto end; - } - This->propid_to_name = dictionary_create(PropertyStorage_PropCompare, - NULL, This); - if (!This->propid_to_name) - { - hr = STG_E_INSUFFICIENTMEMORY; - goto end; - } - This->propid_to_prop = dictionary_create(PropertyStorage_PropCompare, - PropertyStorage_PropertyDestroy, This); - if (!This->propid_to_prop) - { - hr = STG_E_INSUFFICIENTMEMORY; - goto end; - } -end: - if (FAILED(hr)) - PropertyStorage_DestroyDictionaries(This); - return hr; -} - -static HRESULT PropertyStorage_BaseConstruct(IStream *stm, - REFFMTID rfmtid, DWORD grfMode, PropertyStorage_impl **pps) -{ - HRESULT hr = S_OK; - - assert(pps); - assert(rfmtid); - *pps = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof **pps); - if (!*pps) - return E_OUTOFMEMORY; - - (*pps)->IPropertyStorage_iface.lpVtbl = &IPropertyStorage_Vtbl; - (*pps)->ref = 1; - InitializeCriticalSection(&(*pps)->cs); - (*pps)->cs.DebugInfo->Spare[0] = (DWORD_PTR)(__FILE__ ": PropertyStorage_impl.cs"); - (*pps)->stm = stm; - (*pps)->fmtid = *rfmtid; - (*pps)->grfMode = grfMode; - - hr = PropertyStorage_CreateDictionaries(*pps); - if (FAILED(hr)) - { - (*pps)->cs.DebugInfo->Spare[0] = 0; - DeleteCriticalSection(&(*pps)->cs); - HeapFree(GetProcessHeap(), 0, *pps); - *pps = NULL; - } - else IStream_AddRef( stm ); - - return hr; -} - -static HRESULT PropertyStorage_ConstructFromStream(IStream *stm, - REFFMTID rfmtid, DWORD grfMode, IPropertyStorage** pps) -{ - PropertyStorage_impl *ps; - HRESULT hr; - - assert(pps); - hr = PropertyStorage_BaseConstruct(stm, rfmtid, grfMode, &ps); - if (SUCCEEDED(hr)) - { - hr = PropertyStorage_ReadFromStream(ps); - if (SUCCEEDED(hr)) - { - *pps = &ps->IPropertyStorage_iface; - TRACE("PropertyStorage %p constructed\n", ps); - hr = S_OK; - } - else IPropertyStorage_Release( &ps->IPropertyStorage_iface ); - } - return hr; -} - -static HRESULT PropertyStorage_ConstructEmpty(IStream *stm, - REFFMTID rfmtid, DWORD grfFlags, DWORD grfMode, IPropertyStorage** pps) -{ - PropertyStorage_impl *ps; - HRESULT hr; - - assert(pps); - hr = PropertyStorage_BaseConstruct(stm, rfmtid, grfMode, &ps); - if (SUCCEEDED(hr)) - { - ps->format = 0; - ps->grfFlags = grfFlags; - if (ps->grfFlags & PROPSETFLAG_CASE_SENSITIVE) - ps->format = 1; - /* default to Unicode unless told not to, as specified on msdn */ - if (ps->grfFlags & PROPSETFLAG_ANSI) - ps->codePage = GetACP(); - else - ps->codePage = CP_UNICODE; - ps->locale = LOCALE_SYSTEM_DEFAULT; - TRACE("Code page is %d, locale is %ld\n", ps->codePage, ps->locale); - *pps = &ps->IPropertyStorage_iface; - TRACE("PropertyStorage %p constructed\n", ps); - hr = S_OK; - } - return hr; -} - - -/*********************************************************************** - * Implementation of IPropertySetStorage - */ - -struct enum_stat_propset_stg -{ - IEnumSTATPROPSETSTG IEnumSTATPROPSETSTG_iface; - LONG refcount; - STATPROPSETSTG *stats; - size_t current; - size_t count; -}; - -static struct enum_stat_propset_stg *impl_from_IEnumSTATPROPSETSTG(IEnumSTATPROPSETSTG *iface) -{ - return CONTAINING_RECORD(iface, struct enum_stat_propset_stg, IEnumSTATPROPSETSTG_iface); -} - -static HRESULT WINAPI enum_stat_propset_stg_QueryInterface(IEnumSTATPROPSETSTG *iface, REFIID riid, void **obj) -{ - TRACE("%p, %s, %p.\n", iface, debugstr_guid(riid), obj); - - if (IsEqualIID(riid, &IID_IEnumSTATPROPSETSTG) || - IsEqualIID(riid, &IID_IUnknown)) - { - *obj = iface; - IEnumSTATPROPSETSTG_AddRef(iface); - return S_OK; - } - - WARN("Unsupported interface %s.\n", debugstr_guid(riid)); - return E_NOINTERFACE; -} - -static ULONG WINAPI enum_stat_propset_stg_AddRef(IEnumSTATPROPSETSTG *iface) -{ - struct enum_stat_propset_stg *psenum = impl_from_IEnumSTATPROPSETSTG(iface); - LONG refcount = InterlockedIncrement(&psenum->refcount); - - TRACE("%p, refcount %lu.\n", iface, refcount); - - return refcount; -} - -static ULONG WINAPI enum_stat_propset_stg_Release(IEnumSTATPROPSETSTG *iface) -{ - struct enum_stat_propset_stg *psenum = impl_from_IEnumSTATPROPSETSTG(iface); - LONG refcount = InterlockedDecrement(&psenum->refcount); - - TRACE("%p, refcount %lu.\n", iface, refcount); - - if (!refcount) - { - heap_free(psenum->stats); - heap_free(psenum); - } - - return refcount; -} - -static HRESULT WINAPI enum_stat_propset_stg_Next(IEnumSTATPROPSETSTG *iface, ULONG celt, - STATPROPSETSTG *ret, ULONG *fetched) -{ - struct enum_stat_propset_stg *psenum = impl_from_IEnumSTATPROPSETSTG(iface); - ULONG count = 0; - - TRACE("%p, %lu, %p, %p.\n", iface, celt, ret, fetched); - - if (psenum->current == ~0u) - psenum->current = 0; - - while (count < celt && psenum->current < psenum->count) - ret[count++] = psenum->stats[psenum->current++]; - - if (fetched) - *fetched = count; - - return count < celt ? S_FALSE : S_OK; -} - -static HRESULT WINAPI enum_stat_propset_stg_Skip(IEnumSTATPROPSETSTG *iface, ULONG celt) -{ - FIXME("%p, %lu.\n", iface, celt); - - return S_OK; -} - -static HRESULT WINAPI enum_stat_propset_stg_Reset(IEnumSTATPROPSETSTG *iface) -{ - struct enum_stat_propset_stg *psenum = impl_from_IEnumSTATPROPSETSTG(iface); - - TRACE("%p.\n", iface); - - psenum->current = ~0u; - - return S_OK; -} - -static HRESULT WINAPI enum_stat_propset_stg_Clone(IEnumSTATPROPSETSTG *iface, IEnumSTATPROPSETSTG **ppenum) -{ - FIXME("%p, %p.\n", iface, ppenum); - - return E_NOTIMPL; -} - -static const IEnumSTATPROPSETSTGVtbl enum_stat_propset_stg_vtbl = -{ - enum_stat_propset_stg_QueryInterface, - enum_stat_propset_stg_AddRef, - enum_stat_propset_stg_Release, - enum_stat_propset_stg_Next, - enum_stat_propset_stg_Skip, - enum_stat_propset_stg_Reset, - enum_stat_propset_stg_Clone, -}; - -static HRESULT create_enum_stat_propset_stg(StorageImpl *storage, IEnumSTATPROPSETSTG **ret) -{ - IStorage *stg = &storage->base.IStorage_iface; - IEnumSTATSTG *penum = NULL; - STATSTG stat; - ULONG count; - HRESULT hr; - - struct enum_stat_propset_stg *enum_obj; - - enum_obj = heap_alloc_zero(sizeof(*enum_obj)); - if (!enum_obj) - return E_OUTOFMEMORY; - - enum_obj->IEnumSTATPROPSETSTG_iface.lpVtbl = &enum_stat_propset_stg_vtbl; - enum_obj->refcount = 1; - - /* add all the property set elements into a list */ - hr = IStorage_EnumElements(stg, 0, NULL, 0, &penum); - if (FAILED(hr)) - goto done; - - /* Allocate stats array and fill it. */ - while ((hr = IEnumSTATSTG_Next(penum, 1, &stat, &count)) == S_OK) - { - enum_obj->count++; - CoTaskMemFree(stat.pwcsName); - } - - if (FAILED(hr)) - goto done; - - enum_obj->stats = heap_alloc(enum_obj->count * sizeof(*enum_obj->stats)); - if (!enum_obj->stats) - { - hr = E_OUTOFMEMORY; - goto done; - } - enum_obj->count = 0; - - if (FAILED(hr = IEnumSTATSTG_Reset(penum))) - goto done; - - while (IEnumSTATSTG_Next(penum, 1, &stat, &count) == S_OK) - { - if (!stat.pwcsName) - continue; - - if (stat.pwcsName[0] == 5 && stat.type == STGTY_STREAM) - { - STATPROPSETSTG *ptr = &enum_obj->stats[enum_obj->count++]; - - PropStgNameToFmtId(stat.pwcsName, &ptr->fmtid); - - TRACE("adding %s - %s.\n", debugstr_w(stat.pwcsName), debugstr_guid(&ptr->fmtid)); - - ptr->mtime = stat.mtime; - ptr->atime = stat.atime; - ptr->ctime = stat.ctime; - ptr->grfFlags = stat.grfMode; - ptr->clsid = stat.clsid; - } - CoTaskMemFree(stat.pwcsName); - } - -done: - - if (penum) - IEnumSTATSTG_Release(penum); - - if (SUCCEEDED(hr)) - { - *ret = &enum_obj->IEnumSTATPROPSETSTG_iface; - } - else - { - *ret = NULL; - IEnumSTATPROPSETSTG_Release(&enum_obj->IEnumSTATPROPSETSTG_iface); - } - - return hr; -} - -/************************************************************************ - * IPropertySetStorage_fnQueryInterface (IUnknown) - * - * This method forwards to the common QueryInterface implementation - */ -static HRESULT WINAPI IPropertySetStorage_fnQueryInterface( - IPropertySetStorage *ppstg, - REFIID riid, - void** ppvObject) -{ - StorageImpl *This = impl_from_IPropertySetStorage(ppstg); - return IStorage_QueryInterface( &This->base.IStorage_iface, riid, ppvObject ); -} - -/************************************************************************ - * IPropertySetStorage_fnAddRef (IUnknown) - * - * This method forwards to the common AddRef implementation - */ -static ULONG WINAPI IPropertySetStorage_fnAddRef( - IPropertySetStorage *ppstg) -{ - StorageImpl *This = impl_from_IPropertySetStorage(ppstg); - return IStorage_AddRef( &This->base.IStorage_iface ); -} - -/************************************************************************ - * IPropertySetStorage_fnRelease (IUnknown) - * - * This method forwards to the common Release implementation - */ -static ULONG WINAPI IPropertySetStorage_fnRelease( - IPropertySetStorage *ppstg) -{ - StorageImpl *This = impl_from_IPropertySetStorage(ppstg); - return IStorage_Release( &This->base.IStorage_iface ); -} - -/************************************************************************ - * IPropertySetStorage_fnCreate (IPropertySetStorage) - */ -static HRESULT WINAPI IPropertySetStorage_fnCreate( - IPropertySetStorage *ppstg, - REFFMTID rfmtid, - const CLSID* pclsid, - DWORD grfFlags, - DWORD grfMode, - IPropertyStorage** ppprstg) -{ - StorageImpl *This = impl_from_IPropertySetStorage(ppstg); - WCHAR name[CCH_MAX_PROPSTG_NAME + 1]; - IStream *stm = NULL; - HRESULT r; - - TRACE("%p, %s %#lx, %#lx, %p.\n", This, debugstr_guid(rfmtid), grfFlags, - grfMode, ppprstg); - - /* be picky */ - if (grfMode != (STGM_CREATE|STGM_READWRITE|STGM_SHARE_EXCLUSIVE)) - { - r = STG_E_INVALIDFLAG; - goto end; - } - - if (!rfmtid) - { - r = E_INVALIDARG; - goto end; - } - - /* FIXME: if (grfFlags & PROPSETFLAG_NONSIMPLE), we need to create a - * storage, not a stream. For now, disallow it. - */ - if (grfFlags & PROPSETFLAG_NONSIMPLE) - { - FIXME("PROPSETFLAG_NONSIMPLE not supported\n"); - r = STG_E_INVALIDFLAG; - goto end; - } - - r = FmtIdToPropStgName(rfmtid, name); - if (FAILED(r)) - goto end; - - r = IStorage_CreateStream( &This->base.IStorage_iface, name, grfMode, 0, 0, &stm ); - if (FAILED(r)) - goto end; - - r = PropertyStorage_ConstructEmpty(stm, rfmtid, grfFlags, grfMode, ppprstg); - - IStream_Release( stm ); - -end: - TRACE("returning %#lx\n", r); - return r; -} - -/************************************************************************ - * IPropertySetStorage_fnOpen (IPropertySetStorage) - */ -static HRESULT WINAPI IPropertySetStorage_fnOpen( - IPropertySetStorage *ppstg, - REFFMTID rfmtid, - DWORD grfMode, - IPropertyStorage** ppprstg) -{ - StorageImpl *This = impl_from_IPropertySetStorage(ppstg); - IStream *stm = NULL; - WCHAR name[CCH_MAX_PROPSTG_NAME + 1]; - HRESULT r; - - TRACE("%p, %s, %#lx, %p.\n", This, debugstr_guid(rfmtid), grfMode, ppprstg); - - /* be picky */ - if (grfMode != (STGM_READWRITE|STGM_SHARE_EXCLUSIVE) && - grfMode != (STGM_READ|STGM_SHARE_EXCLUSIVE)) - { - r = STG_E_INVALIDFLAG; - goto end; - } - - if (!rfmtid) - { - r = E_INVALIDARG; - goto end; - } - - r = FmtIdToPropStgName(rfmtid, name); - if (FAILED(r)) - goto end; - - r = IStorage_OpenStream( &This->base.IStorage_iface, name, 0, grfMode, 0, &stm ); - if (FAILED(r)) - goto end; - - r = PropertyStorage_ConstructFromStream(stm, rfmtid, grfMode, ppprstg); - - IStream_Release( stm ); - -end: - TRACE("returning %#lx\n", r); - return r; -} - -/************************************************************************ - * IPropertySetStorage_fnDelete (IPropertySetStorage) - */ -static HRESULT WINAPI IPropertySetStorage_fnDelete( - IPropertySetStorage *ppstg, - REFFMTID rfmtid) -{ - StorageImpl *This = impl_from_IPropertySetStorage(ppstg); - WCHAR name[CCH_MAX_PROPSTG_NAME + 1]; - HRESULT r; - - TRACE("%p %s\n", This, debugstr_guid(rfmtid)); - - if (!rfmtid) - return E_INVALIDARG; - - r = FmtIdToPropStgName(rfmtid, name); - if (FAILED(r)) - return r; - - return IStorage_DestroyElement(&This->base.IStorage_iface, name); -} - -static HRESULT WINAPI IPropertySetStorage_fnEnum(IPropertySetStorage *iface, IEnumSTATPROPSETSTG **enum_obj) -{ - StorageImpl *storage = impl_from_IPropertySetStorage(iface); - - TRACE("%p, %p.\n", iface, enum_obj); - - if (!enum_obj) - return E_INVALIDARG; - - return create_enum_stat_propset_stg(storage, enum_obj); -} - -/*********************************************************************** - * vtables - */ -const IPropertySetStorageVtbl IPropertySetStorage_Vtbl = +struct read_buffer { - IPropertySetStorage_fnQueryInterface, - IPropertySetStorage_fnAddRef, - IPropertySetStorage_fnRelease, - IPropertySetStorage_fnCreate, - IPropertySetStorage_fnOpen, - IPropertySetStorage_fnDelete, - IPropertySetStorage_fnEnum + BYTE *data; + size_t size; }; -static const IPropertyStorageVtbl IPropertyStorage_Vtbl = -{ - IPropertyStorage_fnQueryInterface, - IPropertyStorage_fnAddRef, - IPropertyStorage_fnRelease, - IPropertyStorage_fnReadMultiple, - IPropertyStorage_fnWriteMultiple, - IPropertyStorage_fnDeleteMultiple, - IPropertyStorage_fnReadPropertyNames, - IPropertyStorage_fnWritePropertyNames, - IPropertyStorage_fnDeletePropertyNames, - IPropertyStorage_fnCommit, - IPropertyStorage_fnRevert, - IPropertyStorage_fnEnum, - IPropertyStorage_fnSetTimes, - IPropertyStorage_fnSetClass, - IPropertyStorage_fnStat, -}; +HRESULT WINAPI wine_PropertyStorage_ReadProperty(PROPVARIANT *prop, const struct read_buffer *buffer, + SIZE_T offset, UINT codepage, void* (WINAPI *allocate)(void *this, ULONG size), void *allocate_data); #ifdef __i386__ /* thiscall functions are i386-specific */ @@ -3025,7 +95,7 @@ BOOLEAN WINAPI StgConvertPropertyToVariant(const SERIALIZEDPROPERTYVALUE* prop, read_buffer.data = (BYTE *)prop; read_buffer.size = ~(size_t)0; - hr = PropertyStorage_ReadProperty(pvar, &read_buffer, 0, CodePage, Allocate_PMemoryAllocator, pma); + hr = wine_PropertyStorage_ReadProperty(pvar, &read_buffer, 0, CodePage, Allocate_PMemoryAllocator, pma); if (FAILED(hr)) { @@ -3044,93 +114,3 @@ SERIALIZEDPROPERTYVALUE* WINAPI StgConvertVariantToProperty(const PROPVARIANT *p return NULL; } - -HRESULT WINAPI StgCreatePropStg(IUnknown *unk, REFFMTID fmt, const CLSID *clsid, - DWORD flags, DWORD reserved, IPropertyStorage **prop_stg) -{ - IStorage *stg; - IStream *stm; - HRESULT r; - - TRACE("%p, %s, %s, %#lx, %ld, %p.\n", unk, debugstr_guid(fmt), debugstr_guid(clsid), flags, reserved, prop_stg); - - if (!fmt || reserved) - { - r = E_INVALIDARG; - goto end; - } - - if (flags & PROPSETFLAG_NONSIMPLE) - { - r = IUnknown_QueryInterface(unk, &IID_IStorage, (void **)&stg); - if (FAILED(r)) - goto end; - - /* FIXME: if (flags & PROPSETFLAG_NONSIMPLE), we need to create a - * storage, not a stream. For now, disallow it. - */ - FIXME("PROPSETFLAG_NONSIMPLE not supported\n"); - IStorage_Release(stg); - r = STG_E_INVALIDFLAG; - } - else - { - r = IUnknown_QueryInterface(unk, &IID_IStream, (void **)&stm); - if (FAILED(r)) - goto end; - - r = PropertyStorage_ConstructEmpty(stm, fmt, flags, - STGM_CREATE|STGM_READWRITE|STGM_SHARE_EXCLUSIVE, prop_stg); - - IStream_Release( stm ); - } - -end: - TRACE("returning %#lx\n", r); - return r; -} - -HRESULT WINAPI StgOpenPropStg(IUnknown *unk, REFFMTID fmt, DWORD flags, - DWORD reserved, IPropertyStorage **prop_stg) -{ - IStorage *stg; - IStream *stm; - HRESULT r; - - TRACE("%p, %s, %#lx, %ld, %p.\n", unk, debugstr_guid(fmt), flags, reserved, prop_stg); - - if (!fmt || reserved) - { - r = E_INVALIDARG; - goto end; - } - - if (flags & PROPSETFLAG_NONSIMPLE) - { - r = IUnknown_QueryInterface(unk, &IID_IStorage, (void **)&stg); - if (FAILED(r)) - goto end; - - /* FIXME: if (flags & PROPSETFLAG_NONSIMPLE), we need to open a - * storage, not a stream. For now, disallow it. - */ - FIXME("PROPSETFLAG_NONSIMPLE not supported\n"); - IStorage_Release(stg); - r = STG_E_INVALIDFLAG; - } - else - { - r = IUnknown_QueryInterface(unk, &IID_IStream, (void **)&stm); - if (FAILED(r)) - goto end; - - r = PropertyStorage_ConstructFromStream(stm, fmt, - STGM_READWRITE|STGM_SHARE_EXCLUSIVE, prop_stg); - - IStream_Release( stm ); - } - -end: - TRACE("returning %#lx\n", r); - return r; -} diff --git a/dlls/ole32/storage32.c b/dlls/ole32/storage32.c index 1bf08e45701..bf3ec26ff7b 100644 --- a/dlls/ole32/storage32.c +++ b/dlls/ole32/storage32.c @@ -43,7 +43,6 @@ #include "winuser.h" #include "wine/debug.h" -#include "storage32.h" #include "ole2.h" /* For Write/ReadClassStm */ #include "winreg.h" @@ -52,8944 +51,7 @@ 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 %#lx\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, %ld: stub\n", This, timeout); - return E_NOTIMPL; -} - -static HRESULT WINAPI directwriterlock_ReleaseWriteAccess(IDirectWriterLock *iface) -{ - StorageBaseImpl *This = impl_from_IDirectWriterLock(iface); - FIXME("(%p): stub\n", This); - return E_NOTIMPL; -} - -static HRESULT WINAPI directwriterlock_HaveWriteAccess(IDirectWriterLock *iface) -{ - StorageBaseImpl *This = impl_from_IDirectWriterLock(iface); - FIXME("(%p): stub\n", This); - return E_NOTIMPL; -} - -static const IDirectWriterLockVtbl DirectWriterLockVtbl = -{ - directwriterlock_QueryInterface, - directwriterlock_AddRef, - directwriterlock_Release, - directwriterlock_WaitForWriteAccess, - directwriterlock_ReleaseWriteAccess, - directwriterlock_HaveWriteAccess -}; - - -/************************************************************************ - * StorageBaseImpl implementation : Tree helper functions - ***********************************************************************/ - -/**************************************************************************** - * - * Internal Method - * - * Case insensitive comparison of DirEntry.name by first considering - * their size. - * - * Returns <0 when name1 < name2 - * >0 when name1 > name2 - * 0 when name1 == name2 - */ -static LONG entryNameCmp( - const OLECHAR *name1, - const OLECHAR *name2) -{ - LONG diff = lstrlenW(name1) - lstrlenW(name2); - - while (diff == 0 && *name1 != 0) - { - /* - * We compare the string themselves only when they are of the same length - */ - diff = towupper(*name1++) - towupper(*name2++); - } - - return diff; -} - -/**************************************************************************** - * - * Internal Method - * - * Find and read the element of a storage with the given name. - */ -static DirRef findElement(StorageBaseImpl *storage, DirRef storageEntry, - const OLECHAR *name, DirEntry *data) -{ - DirRef currentEntry; - - /* Read the storage entry to find the root of the tree. */ - StorageBaseImpl_ReadDirEntry(storage, storageEntry, data); - - currentEntry = data->dirRootEntry; - - while (currentEntry != DIRENTRY_NULL) - { - LONG cmp; - - StorageBaseImpl_ReadDirEntry(storage, currentEntry, data); - - cmp = entryNameCmp(name, data->name); - - if (cmp == 0) - /* found it */ - break; - - else if (cmp < 0) - currentEntry = data->leftChild; - - else if (cmp > 0) - currentEntry = data->rightChild; - } - - return currentEntry; -} - -/**************************************************************************** - * - * Internal Method - * - * Find and read the binary tree parent of the element with the given name. - * - * If there is no such element, find a place where it could be inserted and - * return STG_E_FILENOTFOUND. - */ -static HRESULT findTreeParent(StorageBaseImpl *storage, DirRef storageEntry, - const OLECHAR *childName, DirEntry *parentData, DirRef *parentEntry, - ULONG *relation) -{ - DirRef childEntry; - DirEntry childData; - - /* Read the storage entry to find the root of the tree. */ - StorageBaseImpl_ReadDirEntry(storage, storageEntry, parentData); - - *parentEntry = storageEntry; - *relation = DIRENTRY_RELATION_DIR; - - childEntry = parentData->dirRootEntry; - - while (childEntry != DIRENTRY_NULL) - { - LONG cmp; - - StorageBaseImpl_ReadDirEntry(storage, childEntry, &childData); - - cmp = entryNameCmp(childName, childData.name); - - if (cmp == 0) - /* found it */ - break; - - else if (cmp < 0) - { - *parentData = childData; - *parentEntry = childEntry; - *relation = DIRENTRY_RELATION_PREVIOUS; - - childEntry = parentData->leftChild; - } - - else if (cmp > 0) - { - *parentData = childData; - *parentEntry = childEntry; - *relation = DIRENTRY_RELATION_NEXT; - - childEntry = parentData->rightChild; - } - } - - if (childEntry == DIRENTRY_NULL) - return STG_E_FILENOTFOUND; - else - return S_OK; -} - -static void setEntryLink(DirEntry *entry, ULONG relation, DirRef new_target) -{ - switch (relation) - { - case DIRENTRY_RELATION_PREVIOUS: - entry->leftChild = new_target; - break; - case DIRENTRY_RELATION_NEXT: - entry->rightChild = new_target; - break; - case DIRENTRY_RELATION_DIR: - entry->dirRootEntry = new_target; - break; - default: - assert(0); - } -} - -/**************************************************************************** - * - * Internal Method - * - * Add a directory entry to a storage - */ -static HRESULT insertIntoTree( - StorageBaseImpl *This, - DirRef parentStorageIndex, - DirRef newEntryIndex) -{ - DirEntry currentEntry; - DirEntry newEntry; - - /* - * Read the inserted entry - */ - StorageBaseImpl_ReadDirEntry(This, - newEntryIndex, - &newEntry); - - /* - * Read the storage entry - */ - StorageBaseImpl_ReadDirEntry(This, - parentStorageIndex, - ¤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("<-- %#lx\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, %lu, %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("<-- %#lx (asked %lu, got %lu)\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, %lu.\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("<-- %#lx\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, refcount %lu.\n", iface, 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, refcount %lu.\n", iface, ref); - - if (ref == 0) - { - /* - * Since we are using a system of base-classes, we want to call the - * destructor of the appropriate derived class. To do this, we are - * using virtual functions to implement the destructor. - */ - StorageBaseImpl_Destroy(This); - } - - return ref; -} - -static HRESULT StorageBaseImpl_CopyStorageEntryTo(StorageBaseImpl *This, - DirRef srcEntry, BOOL skip_storage, BOOL skip_stream, - SNB snbExclude, IStorage *pstgDest); - -static HRESULT StorageBaseImpl_CopyChildEntryTo(StorageBaseImpl *This, - DirRef srcEntry, BOOL skip_storage, BOOL skip_stream, - SNB snbExclude, IStorage *pstgDest) -{ - DirEntry data; - HRESULT hr; - BOOL skip = FALSE; - IStorage *pstgTmp; - IStream *pstrChild, *pstrTmp; - STATSTG strStat; - - if (srcEntry == DIRENTRY_NULL) - return S_OK; - - hr = StorageBaseImpl_ReadDirEntry( This, srcEntry, &data ); - - if (FAILED(hr)) - return hr; - - if ( snbExclude ) - { - WCHAR **snb = snbExclude; - - while ( *snb != NULL && !skip ) - { - if ( wcscmp(data.name, *snb) == 0 ) - skip = TRUE; - ++snb; - } - } - - if (!skip) - { - if (data.stgType == STGTY_STORAGE && !skip_storage) - { - /* - * create a new storage in destination storage - */ - hr = IStorage_CreateStorage( pstgDest, data.name, - STGM_FAILIFTHERE|STGM_WRITE|STGM_SHARE_EXCLUSIVE, - 0, 0, - &pstgTmp ); - - /* - * if it already exist, don't create a new one use this one - */ - if (hr == STG_E_FILEALREADYEXISTS) - { - hr = IStorage_OpenStorage( pstgDest, data.name, NULL, - STGM_WRITE|STGM_SHARE_EXCLUSIVE, - NULL, 0, &pstgTmp ); - } - - if (SUCCEEDED(hr)) - { - hr = StorageBaseImpl_CopyStorageEntryTo( This, srcEntry, skip_storage, - skip_stream, NULL, pstgTmp ); - - IStorage_Release(pstgTmp); - } - } - else if (data.stgType == STGTY_STREAM && !skip_stream) - { - /* - * create a new stream in destination storage. If the stream already - * exist, it will be deleted and a new one will be created. - */ - hr = IStorage_CreateStream( pstgDest, data.name, - STGM_CREATE|STGM_WRITE|STGM_SHARE_EXCLUSIVE, - 0, 0, &pstrTmp ); - - /* - * open child stream storage. This operation must succeed even if the - * stream is already open, so we use internal functions to do it. - */ - if (hr == S_OK) - { - StgStreamImpl *streamimpl = StgStreamImpl_Construct(This, STGM_READ|STGM_SHARE_EXCLUSIVE, srcEntry); - - if (streamimpl) - { - pstrChild = &streamimpl->IStream_iface; - if (pstrChild) - IStream_AddRef(pstrChild); - } - else - { - pstrChild = NULL; - hr = E_OUTOFMEMORY; - } - } - - if (hr == S_OK) - { - /* - * Get the size of the source stream - */ - IStream_Stat( pstrChild, &strStat, STATFLAG_NONAME ); - - /* - * Set the size of the destination stream. - */ - IStream_SetSize(pstrTmp, strStat.cbSize); - - /* - * do the copy - */ - hr = IStream_CopyTo( pstrChild, pstrTmp, strStat.cbSize, - NULL, NULL ); - - IStream_Release( pstrChild ); - } - - IStream_Release( pstrTmp ); - } - } - - /* copy siblings */ - if (SUCCEEDED(hr)) - hr = StorageBaseImpl_CopyChildEntryTo( This, data.leftChild, skip_storage, - skip_stream, snbExclude, pstgDest ); - - if (SUCCEEDED(hr)) - hr = StorageBaseImpl_CopyChildEntryTo( This, data.rightChild, skip_storage, - skip_stream, snbExclude, pstgDest ); - - TRACE("<-- %#lx\n", hr); - return hr; -} - -static BOOL StorageBaseImpl_IsStreamOpen(StorageBaseImpl * stg, DirRef streamEntry) -{ - StgStreamImpl *strm; - - TRACE("%p, %ld.\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, %ld.\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, %#lx, %ld, %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("<-- %#lx\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, %#lx, %p, %ld, %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("<-- %#lx\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, %ld, %p, %ld, %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, %#lx.\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: %ld, cbSize.Low/High: %ld/%ld, grfMode: %#lx, grfLocksSupported: %ld, grfStateBits: %#lx\n", debugstr_w(pstatstg->pwcsName), pstatstg->type, pstatstg->cbSize.LowPart, pstatstg->cbSize.HighPart, pstatstg->grfMode, pstatstg->grfLocksSupported, pstatstg->grfStateBits); - } - TRACE("<-- %#lx\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 */ - lstrcpyW(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, %#lx, %ld, %ld, %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; - - lstrcpyW(newStreamEntry.name, pwcsName); - - newStreamEntry.stgType = STGTY_STREAM; - newStreamEntry.startingBlock = BLOCK_END_OF_CHAIN; - newStreamEntry.size.LowPart = 0; - newStreamEntry.size.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, %#lx, %ld, %ld, %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: %#lx\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; - } - - lstrcpyW(newEntry.name, pwcsName); - - newEntry.stgType = STGTY_STORAGE; - newEntry.startingBlock = BLOCK_END_OF_CHAIN; - newEntry.size.LowPart = 0; - newEntry.size.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("<-- %#lx\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, %ld, %p, %p, %p.\n", iface, ciidExclude, rgiidExclude, snbExclude, pstgDest); - - if ( pstgDest == 0 ) - return STG_E_INVALIDPOINTER; - - for(i = 0; i < ciidExclude; ++i) - { - if(IsEqualGUID(&IID_IStorage, &rgiidExclude[i])) - skip_storage = TRUE; - else if(IsEqualGUID(&IID_IStream, &rgiidExclude[i])) - skip_stream = TRUE; - else - WARN("Unknown excluded GUID: %s\n", debugstr_guid(&rgiidExclude[i])); - } - - if (!skip_storage) - { - /* Give up early if it looks like this would be infinitely recursive. - * Oddly enough, this includes some cases that aren't really recursive, like - * copying to a transacted child. */ - IStorage *pstgDestAncestor = pstgDest; - IStorage *pstgDestAncestorChild = NULL; - - /* Go up the chain from the destination until we find the source storage. */ - while (pstgDestAncestor != iface) { - pstgDestAncestorChild = pstgDest; - - if (pstgDestAncestor->lpVtbl == &TransactedSnapshotImpl_Vtbl) - { - TransactedSnapshotImpl *snapshot = (TransactedSnapshotImpl*) pstgDestAncestor; - - pstgDestAncestor = &snapshot->transactedParent->IStorage_iface; - } - else if (pstgDestAncestor->lpVtbl == &StorageInternalImpl_Vtbl) - { - StorageInternalImpl *internal = (StorageInternalImpl*) pstgDestAncestor; - - pstgDestAncestor = &internal->parentStorage->IStorage_iface; - } - else - break; - } - - if (pstgDestAncestor == iface) - { - BOOL fail = TRUE; - - if (pstgDestAncestorChild && snbExclude) - { - StorageBaseImpl *ancestorChildBase = (StorageBaseImpl*)pstgDestAncestorChild; - DirEntry data; - WCHAR **snb = snbExclude; - - StorageBaseImpl_ReadDirEntry(ancestorChildBase, ancestorChildBase->storageDirEntry, &data); - - while ( *snb != NULL && fail ) - { - if ( wcscmp(data.name, *snb) == 0 ) - fail = FALSE; - ++snb; - } - } - - if (fail) - return STG_E_ACCESSDENIED; - } - } - - return StorageBaseImpl_CopyStorageEntryTo( This, This->storageDirEntry, - skip_storage, skip_stream, snbExclude, pstgDest ); -} - -/************************************************************************* - * MoveElementTo (IStorage) - */ -static HRESULT WINAPI StorageBaseImpl_MoveElementTo( - IStorage* iface, - const OLECHAR *pwcsName, /* [string][in] */ - IStorage *pstgDest, /* [unique][in] */ - const OLECHAR *pwcsNewName,/* [string][in] */ - DWORD grfFlags) /* [in] */ -{ - FIXME("%p, %s, %p, %s, %#lx: 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, %#lx.\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, %ld.\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("<-- %#lx\n", hr); - return hr; - } - - /* - * Enumerate the elements - */ - hr = IStorage_EnumElements(childStorage, 0, 0, 0, &elements); - if (FAILED(hr)) - { - IStorage_Release(childStorage); - TRACE("<-- %#lx\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("%#lx\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.HighPart = 0; - size.LowPart = 0; - - hr = IStorage_OpenStream(&parentStorage->IStorage_iface, - entryDataToDelete.name, NULL, STGM_WRITE | STGM_SHARE_EXCLUSIVE, 0, &pis); - - if (hr!=S_OK) - { - TRACE("<-- %#lx\n", hr); - return(hr); - } - - /* - * Zap the stream - */ - hr = IStream_SetSize(pis, size); - - if(hr != S_OK) - { - TRACE("<-- %#lx\n", hr); - return hr; - } - - /* - * Release the stream object. - */ - IStream_Release(pis); - TRACE("<-- %#lx\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("<-- %#lx\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("<-- %#lx\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.HighPart = 0; - offset.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=%#lx, smallblock=%#lx, sblimit=%#lx\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; - ULARGE_INTEGER offset; - DWORD bytes_written; - DWORD major_version, dirsectorcount; - - 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; - } - - memset(headerBigBlock, 0, HEADER_SIZE); - 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])); - } - - offset.QuadPart = 0; - 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.LowPart); - - StorageUtl_WriteDWord( - buffer, - OFFSET_PS_SIZE_HIGH, - newData->size.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.LowPart); - - if (This->bigBlockSize < 4096) - { - /* Version 3 files may have junk in the high part of size. */ - buffer->size.HighPart = 0; - } - else - { - StorageUtl_ReadDWord( - currentEntry, - OFFSET_PS_SIZE_HIGH, - &buffer->size.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.LowPart = 0; - offset.HighPart = 0; - cbTotalRead.QuadPart = 0; - - buffer = HeapAlloc(GetProcessHeap(),0,DEF_SMALL_BLOCK_SIZE); - do - { - resRead = SmallBlockChainStream_ReadAt(*ppsbChain, - offset, - min(This->smallBlockSize, size.LowPart - offset.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.LowPart += cbRead; - } - else - { - resRead = STG_E_READFAULT; - break; - } - } while (cbTotalRead.QuadPart < size.QuadPart); - HeapFree(GetProcessHeap(),0,buffer); - - size.HighPart = 0; - size.LowPart = 0; - - if (FAILED(resRead) || FAILED(resWrite)) - { - ERR("conversion failed: resRead = %#lx, resWrite = %#lx\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.HighPart = 0; - offset.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.LowPart - offset.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.LowPart += cbRead; - } - else - { - resRead = STG_E_READFAULT; - break; - } - } - HeapFree(GetProcessHeap(), 0, buffer); - - size.HighPart = 0; - size.LowPart = 0; - - if(FAILED(resRead) || FAILED(resWrite)) - { - ERR("conversion failed: resRead = %#lx, resWrite = %#lx\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 %ld, bigBlockDepotCount %ld\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 neededAddNumBlocks) -{ - 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 * neededAddNumBlocks; - - 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.HighPart = 0; - size.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) - { - DirEntry rootEntry; - /* - * Initialize the directory table - */ - memset(&rootEntry, 0, sizeof(rootEntry)); - lstrcpyW(rootEntry.name, L"Root Entry"); - rootEntry.sizeOfNameString = sizeof(L"Root Entry"); - rootEntry.stgType = STGTY_ROOT; - rootEntry.leftChild = DIRENTRY_NULL; - rootEntry.rightChild = DIRENTRY_NULL; - rootEntry.dirRootEntry = DIRENTRY_NULL; - rootEntry.startingBlock = BLOCK_END_OF_CHAIN; - rootEntry.size.HighPart = 0; - rootEntry.size.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.HighPart = 0; - offset.LowPart = OFFSET_TRANSACTIONSIG; - hr = StorageImpl_ReadAt(This, offset, data, 4, &bytes_read); - - if (SUCCEEDED(hr)) - { - StorageUtl_ReadDWord(data, 0, &This->transactionSig); - - if (oldTransactionSig != This->transactionSig) - { - /* Someone else wrote to this, so toss all cached information. */ - TRACE("signature changed\n"); - - hr = StorageImpl_Refresh(This, FALSE, FALSE); - } - - if (FAILED(hr)) - This->transactionSig = oldTransactionSig; - } - } - - *result = This->transactionSig; - - return hr; -} - -static HRESULT StorageImpl_SetTransactionSig(StorageBaseImpl *base, - ULONG value) -{ - StorageImpl *This = (StorageImpl*)base; - - This->transactionSig = value; - StorageImpl_SaveFileHeader(This); - - return S_OK; -} - -static HRESULT StorageImpl_LockRegion(StorageImpl *This, ULARGE_INTEGER offset, - ULARGE_INTEGER cb, DWORD dwLockType, BOOL *supported) -{ - if ((dwLockType & This->locks_supported) == 0) - { - if (supported) *supported = FALSE; - return S_OK; - } - - if (supported) *supported = TRUE; - return ILockBytes_LockRegion(This->lockBytes, offset, cb, dwLockType); -} - -static HRESULT StorageImpl_UnlockRegion(StorageImpl *This, ULARGE_INTEGER offset, - ULARGE_INTEGER cb, DWORD dwLockType) -{ - if ((dwLockType & This->locks_supported) == 0) - return S_OK; - - return ILockBytes_UnlockRegion(This->lockBytes, offset, cb, dwLockType); -} - -/* Internal function */ -static HRESULT StorageImpl_LockRegionSync(StorageImpl *This, ULARGE_INTEGER offset, - ULARGE_INTEGER cb, DWORD dwLockType, BOOL *supported) -{ - HRESULT hr; - int delay = 0; - DWORD start_time = GetTickCount(); - DWORD last_sanity_check = start_time; - ULARGE_INTEGER sanity_offset, sanity_cb; - - sanity_offset.QuadPart = RANGELOCK_UNK1_FIRST; - sanity_cb.QuadPart = RANGELOCK_UNK1_LAST - RANGELOCK_UNK1_FIRST + 1; - - do - { - hr = StorageImpl_LockRegion(This, offset, cb, dwLockType, supported); - - if (hr == STG_E_ACCESSDENIED || hr == STG_E_LOCKVIOLATION) - { - DWORD current_time = GetTickCount(); - if (current_time - start_time >= 20000) - { - /* timeout */ - break; - } - if (current_time - last_sanity_check >= 500) - { - /* Any storage implementation with the file open in a - * shared mode should not lock these bytes for writing. However, - * some programs (LibreOffice Writer) will keep ALL bytes locked - * when opening in exclusive mode. We can use a read lock to - * detect this case early, and not hang a full 20 seconds. - * - * This can collide with another attempt to open the file in - * exclusive mode, but it's unlikely, and someone would fail anyway. */ - hr = StorageImpl_LockRegion(This, sanity_offset, sanity_cb, WINE_LOCK_READ, NULL); - if (hr == STG_E_ACCESSDENIED || hr == STG_E_LOCKVIOLATION) - break; - if (SUCCEEDED(hr)) - { - StorageImpl_UnlockRegion(This, sanity_offset, sanity_cb, WINE_LOCK_READ); - hr = STG_E_ACCESSDENIED; - } - - last_sanity_check = current_time; - } - Sleep(delay); - if (delay < 150) delay++; - } - } while (hr == STG_E_ACCESSDENIED || hr == STG_E_LOCKVIOLATION); - - return hr; -} - -static HRESULT StorageImpl_LockTransaction(StorageBaseImpl *base, BOOL write) -{ - StorageImpl *This = (StorageImpl*)base; - HRESULT hr; - ULARGE_INTEGER offset, cb; - - if (write) - { - /* Synchronous grab of second priority range, the commit lock, and the - * lock-checking lock. */ - offset.QuadPart = RANGELOCK_TRANSACTION_FIRST; - cb.QuadPart = RANGELOCK_TRANSACTION_LAST - RANGELOCK_TRANSACTION_FIRST + 1; - } - else - { - offset.QuadPart = RANGELOCK_COMMIT; - cb.QuadPart = 1; - } - - hr = StorageImpl_LockRegionSync(This, offset, cb, LOCK_ONLYONCE, NULL); - - return hr; -} - -static HRESULT StorageImpl_UnlockTransaction(StorageBaseImpl *base, BOOL write) -{ - StorageImpl *This = (StorageImpl*)base; - HRESULT hr; - ULARGE_INTEGER offset, cb; - - if (write) - { - offset.QuadPart = RANGELOCK_TRANSACTION_FIRST; - cb.QuadPart = RANGELOCK_TRANSACTION_LAST - RANGELOCK_TRANSACTION_FIRST + 1; - } - else - { - offset.QuadPart = RANGELOCK_COMMIT; - cb.QuadPart = 1; - } - - hr = StorageImpl_UnlockRegion(This, offset, cb, LOCK_ONLYONCE); - - return hr; -} - -static HRESULT StorageImpl_GetFilename(StorageBaseImpl* iface, LPWSTR *result) -{ - StorageImpl *This = (StorageImpl*) iface; - STATSTG statstg; - HRESULT hr; - - hr = ILockBytes_Stat(This->lockBytes, &statstg, 0); - - *result = statstg.pwcsName; - - return hr; -} - -static HRESULT StorageImpl_CheckLockRange(StorageImpl *This, ULONG start, - ULONG end, HRESULT fail_hr) -{ - HRESULT hr; - ULARGE_INTEGER offset, cb; - - offset.QuadPart = start; - cb.QuadPart = 1 + end - start; - - hr = StorageImpl_LockRegion(This, offset, cb, LOCK_ONLYONCE, NULL); - if (SUCCEEDED(hr)) StorageImpl_UnlockRegion(This, offset, cb, LOCK_ONLYONCE); - - if (FAILED(hr)) - return fail_hr; - else - return S_OK; -} - -static HRESULT StorageImpl_LockOne(StorageImpl *This, ULONG start, ULONG end) -{ - HRESULT hr=S_OK; - int i, j; - ULARGE_INTEGER offset, cb; - - cb.QuadPart = 1; - - for (i=start; i<=end; i++) - { - offset.QuadPart = i; - hr = StorageImpl_LockRegion(This, offset, cb, LOCK_ONLYONCE, NULL); - if (hr != STG_E_ACCESSDENIED && hr != STG_E_LOCKVIOLATION) - break; - } - - if (SUCCEEDED(hr)) - { - for (j = 0; j < ARRAY_SIZE(This->locked_bytes); j++) - { - if (This->locked_bytes[j] == 0) - { - This->locked_bytes[j] = i; - break; - } - } - } - - return hr; -} - -static HRESULT StorageImpl_GrabLocks(StorageImpl *This, DWORD openFlags) -{ - HRESULT hr; - ULARGE_INTEGER offset; - ULARGE_INTEGER cb; - DWORD share_mode = STGM_SHARE_MODE(openFlags); - BOOL supported; - - if (openFlags & STGM_NOSNAPSHOT) - { - /* STGM_NOSNAPSHOT implies deny write */ - if (share_mode == STGM_SHARE_DENY_READ) share_mode = STGM_SHARE_EXCLUSIVE; - else if (share_mode != STGM_SHARE_EXCLUSIVE) share_mode = STGM_SHARE_DENY_WRITE; - } - - /* Wrap all other locking inside a single lock so we can check ranges safely */ - offset.QuadPart = RANGELOCK_CHECKLOCKS; - cb.QuadPart = 1; - hr = StorageImpl_LockRegionSync(This, offset, cb, LOCK_ONLYONCE, &supported); - - /* If the ILockBytes doesn't support locking that's ok. */ - if (!supported) return S_OK; - else if (FAILED(hr)) return hr; - - hr = S_OK; - - /* First check for any conflicting locks. */ - if ((openFlags & STGM_PRIORITY) == STGM_PRIORITY) - hr = StorageImpl_CheckLockRange(This, RANGELOCK_COMMIT, RANGELOCK_COMMIT, STG_E_LOCKVIOLATION); - - if (SUCCEEDED(hr) && (STGM_ACCESS_MODE(openFlags) != STGM_WRITE)) - hr = StorageImpl_CheckLockRange(This, RANGELOCK_DENY_READ_FIRST, RANGELOCK_DENY_READ_LAST, STG_E_SHAREVIOLATION); - - if (SUCCEEDED(hr) && (STGM_ACCESS_MODE(openFlags) != STGM_READ)) - hr = StorageImpl_CheckLockRange(This, RANGELOCK_DENY_WRITE_FIRST, RANGELOCK_DENY_WRITE_LAST, STG_E_SHAREVIOLATION); - - if (SUCCEEDED(hr) && (share_mode == STGM_SHARE_DENY_READ || share_mode == STGM_SHARE_EXCLUSIVE)) - hr = StorageImpl_CheckLockRange(This, RANGELOCK_READ_FIRST, RANGELOCK_READ_LAST, STG_E_LOCKVIOLATION); - - if (SUCCEEDED(hr) && (share_mode == STGM_SHARE_DENY_WRITE || share_mode == STGM_SHARE_EXCLUSIVE)) - hr = StorageImpl_CheckLockRange(This, RANGELOCK_WRITE_FIRST, RANGELOCK_WRITE_LAST, STG_E_LOCKVIOLATION); - - if (SUCCEEDED(hr) && STGM_ACCESS_MODE(openFlags) == STGM_READ && share_mode == STGM_SHARE_EXCLUSIVE) - { - hr = StorageImpl_CheckLockRange(This, 0, RANGELOCK_CHECKLOCKS-1, STG_E_LOCKVIOLATION); - - if (SUCCEEDED(hr)) - hr = StorageImpl_CheckLockRange(This, RANGELOCK_CHECKLOCKS+1, RANGELOCK_LAST, STG_E_LOCKVIOLATION); - } - - /* Then grab our locks. */ - if (SUCCEEDED(hr) && (openFlags & STGM_PRIORITY) == STGM_PRIORITY) - { - hr = StorageImpl_LockOne(This, RANGELOCK_PRIORITY1_FIRST, RANGELOCK_PRIORITY1_LAST); - if (SUCCEEDED(hr)) - hr = StorageImpl_LockOne(This, RANGELOCK_PRIORITY2_FIRST, RANGELOCK_PRIORITY2_LAST); - } - - if (SUCCEEDED(hr) && (STGM_ACCESS_MODE(openFlags) != STGM_WRITE)) - hr = StorageImpl_LockOne(This, RANGELOCK_READ_FIRST, RANGELOCK_READ_LAST); - - if (SUCCEEDED(hr) && (STGM_ACCESS_MODE(openFlags) != STGM_READ)) - hr = StorageImpl_LockOne(This, RANGELOCK_WRITE_FIRST, RANGELOCK_WRITE_LAST); - - if (SUCCEEDED(hr) && (share_mode == STGM_SHARE_DENY_READ || share_mode == STGM_SHARE_EXCLUSIVE)) - hr = StorageImpl_LockOne(This, RANGELOCK_DENY_READ_FIRST, RANGELOCK_DENY_READ_LAST); - - if (SUCCEEDED(hr) && (share_mode == STGM_SHARE_DENY_WRITE || share_mode == STGM_SHARE_EXCLUSIVE)) - hr = StorageImpl_LockOne(This, RANGELOCK_DENY_WRITE_FIRST, RANGELOCK_DENY_WRITE_LAST); - - if (SUCCEEDED(hr) && (openFlags & STGM_NOSNAPSHOT) == STGM_NOSNAPSHOT) - hr = StorageImpl_LockOne(This, RANGELOCK_NOSNAPSHOT_FIRST, RANGELOCK_NOSNAPSHOT_LAST); - - offset.QuadPart = RANGELOCK_CHECKLOCKS; - cb.QuadPart = 1; - StorageImpl_UnlockRegion(This, offset, cb, LOCK_ONLYONCE); - - return hr; -} - -static HRESULT StorageImpl_Flush(StorageBaseImpl *storage) -{ - StorageImpl *This = (StorageImpl*)storage; - int i; - HRESULT hr; - TRACE("(%p)\n", This); - - hr = BlockChainStream_Flush(This->smallBlockRootChain); - - if (SUCCEEDED(hr)) - hr = BlockChainStream_Flush(This->rootBlockChain); - - if (SUCCEEDED(hr)) - hr = BlockChainStream_Flush(This->smallBlockDepotChain); - - for (i=0; SUCCEEDED(hr) && i<BLOCKCHAIN_CACHE_SIZE; i++) - if (This->blockChainCache[i]) - hr = BlockChainStream_Flush(This->blockChainCache[i]); - - if (SUCCEEDED(hr)) - hr = ILockBytes_Flush(This->lockBytes); - - return hr; -} - -static void StorageImpl_Invalidate(StorageBaseImpl* iface) -{ - StorageImpl *This = (StorageImpl*) iface; - - StorageBaseImpl_DeleteAll(&This->base); - - This->base.reverted = TRUE; -} - -static void StorageImpl_Destroy(StorageBaseImpl* iface) -{ - StorageImpl *This = (StorageImpl*) iface; - int i; - TRACE("(%p)\n", This); - - StorageImpl_Flush(iface); - - StorageImpl_Invalidate(iface); - - HeapFree(GetProcessHeap(), 0, This->extBigBlockDepotLocations); - - BlockChainStream_Destroy(This->smallBlockRootChain); - BlockChainStream_Destroy(This->rootBlockChain); - BlockChainStream_Destroy(This->smallBlockDepotChain); - - for (i = 0; i < BLOCKCHAIN_CACHE_SIZE; i++) - BlockChainStream_Destroy(This->blockChainCache[i]); - - for (i = 0; i < ARRAY_SIZE(This->locked_bytes); i++) - { - ULARGE_INTEGER offset, cb; - cb.QuadPart = 1; - if (This->locked_bytes[i] != 0) - { - offset.QuadPart = This->locked_bytes[i]; - StorageImpl_UnlockRegion(This, offset, cb, LOCK_ONLYONCE); - } - } - - if (This->lockBytes) - ILockBytes_Release(This->lockBytes); - HeapFree(GetProcessHeap(), 0, This); -} - - -static const StorageBaseImplVtbl StorageImpl_BaseVtbl = -{ - StorageImpl_Destroy, - StorageImpl_Invalidate, - StorageImpl_Flush, - StorageImpl_GetFilename, - StorageImpl_CreateDirEntry, - StorageImpl_BaseWriteDirEntry, - StorageImpl_BaseReadDirEntry, - StorageImpl_DestroyDirEntry, - StorageImpl_StreamReadAt, - StorageImpl_StreamWriteAt, - StorageImpl_StreamSetSize, - StorageImpl_StreamLink, - StorageImpl_GetTransactionSig, - StorageImpl_SetTransactionSig, - StorageImpl_LockTransaction, - StorageImpl_UnlockTransaction -}; - - -/* - * Virtual function table for the IStorageBaseImpl class. - */ -static const IStorageVtbl StorageImpl_Vtbl = -{ - StorageBaseImpl_QueryInterface, - StorageBaseImpl_AddRef, - StorageBaseImpl_Release, - StorageBaseImpl_CreateStream, - StorageBaseImpl_OpenStream, - StorageBaseImpl_CreateStorage, - StorageBaseImpl_OpenStorage, - StorageBaseImpl_CopyTo, - StorageBaseImpl_MoveElementTo, - StorageBaseImpl_Commit, - StorageBaseImpl_Revert, - StorageBaseImpl_EnumElements, - StorageBaseImpl_DestroyElement, - StorageBaseImpl_RenameElement, - StorageBaseImpl_SetElementTimes, - StorageBaseImpl_SetClass, - StorageBaseImpl_SetStateBits, - StorageBaseImpl_Stat -}; - -static HRESULT StorageImpl_Construct( - HANDLE hFile, - LPCOLESTR pwcsName, - ILockBytes* pLkbyt, - DWORD openFlags, - BOOL fileBased, - BOOL create, - ULONG sector_size, - StorageImpl** result) -{ - StorageImpl* This; - HRESULT hr = S_OK; - STATSTG stat; - - if ( FAILED( validateSTGM(openFlags) )) - return STG_E_INVALIDFLAG; - - This = HeapAlloc(GetProcessHeap(), 0, sizeof(StorageImpl)); - if (!This) - return E_OUTOFMEMORY; - - memset(This, 0, sizeof(StorageImpl)); - - list_init(&This->base.strmHead); - - list_init(&This->base.storageHead); - - This->base.IStorage_iface.lpVtbl = &StorageImpl_Vtbl; - This->base.IPropertySetStorage_iface.lpVtbl = &IPropertySetStorage_Vtbl; - This->base.IDirectWriterLock_iface.lpVtbl = &DirectWriterLockVtbl; - This->base.baseVtbl = &StorageImpl_BaseVtbl; - This->base.openFlags = (openFlags & ~STGM_CREATE); - This->base.ref = 1; - This->base.create = create; - - if (openFlags == (STGM_DIRECT_SWMR|STGM_READWRITE|STGM_SHARE_DENY_WRITE)) - This->base.lockingrole = SWMR_Writer; - else if (openFlags == (STGM_DIRECT_SWMR|STGM_READ|STGM_SHARE_DENY_NONE)) - This->base.lockingrole = SWMR_Reader; - else - This->base.lockingrole = SWMR_None; - - This->base.reverted = FALSE; - - /* - * Initialize the big block cache. - */ - This->bigBlockSize = sector_size; - This->smallBlockSize = DEF_SMALL_BLOCK_SIZE; - if (hFile) - hr = FileLockBytesImpl_Construct(hFile, openFlags, pwcsName, &This->lockBytes); - else - { - This->lockBytes = pLkbyt; - ILockBytes_AddRef(pLkbyt); - } - - if (SUCCEEDED(hr)) - hr = ILockBytes_Stat(This->lockBytes, &stat, STATFLAG_NONAME); - - if (SUCCEEDED(hr)) - { - This->locks_supported = stat.grfLocksSupported; - if (!hFile) - /* Don't try to use wine-internal locking flag with custom ILockBytes */ - This->locks_supported &= ~WINE_LOCK_READ; - - hr = StorageImpl_GrabLocks(This, openFlags); - } - - if (SUCCEEDED(hr)) - hr = StorageImpl_Refresh(This, TRUE, create); - - if (FAILED(hr)) - { - IStorage_Release(&This->base.IStorage_iface); - *result = NULL; - } - else - { - StorageImpl_Flush(&This->base); - *result = This; - } - - return hr; -} - - -/************************************************************************ - * StorageInternalImpl implementation - ***********************************************************************/ - -static void StorageInternalImpl_Invalidate( StorageBaseImpl *base ) -{ - StorageInternalImpl* This = (StorageInternalImpl*) base; - - if (!This->base.reverted) - { - TRACE("Storage invalidated (stg=%p)\n", This); - - This->base.reverted = TRUE; - - This->parentStorage = NULL; - - StorageBaseImpl_DeleteAll(&This->base); - - list_remove(&This->ParentListEntry); - } -} - -static void StorageInternalImpl_Destroy( StorageBaseImpl *iface) -{ - StorageInternalImpl* This = (StorageInternalImpl*) iface; - - StorageInternalImpl_Invalidate(&This->base); - - HeapFree(GetProcessHeap(), 0, This); -} - -static HRESULT StorageInternalImpl_Flush(StorageBaseImpl* iface) -{ - StorageInternalImpl* This = (StorageInternalImpl*) iface; - - return StorageBaseImpl_Flush(This->parentStorage); -} - -static HRESULT StorageInternalImpl_GetFilename(StorageBaseImpl* iface, LPWSTR *result) -{ - StorageInternalImpl* This = (StorageInternalImpl*) iface; - - return StorageBaseImpl_GetFilename(This->parentStorage, result); -} - -static HRESULT StorageInternalImpl_CreateDirEntry(StorageBaseImpl *base, - const DirEntry *newData, DirRef *index) -{ - StorageInternalImpl* This = (StorageInternalImpl*) base; - - return StorageBaseImpl_CreateDirEntry(This->parentStorage, - newData, index); -} - -static HRESULT StorageInternalImpl_WriteDirEntry(StorageBaseImpl *base, - DirRef index, const DirEntry *data) -{ - StorageInternalImpl* This = (StorageInternalImpl*) base; - - return StorageBaseImpl_WriteDirEntry(This->parentStorage, - index, data); -} - -static HRESULT StorageInternalImpl_ReadDirEntry(StorageBaseImpl *base, - DirRef index, DirEntry *data) -{ - StorageInternalImpl* This = (StorageInternalImpl*) base; - - return StorageBaseImpl_ReadDirEntry(This->parentStorage, - index, data); -} - -static HRESULT StorageInternalImpl_DestroyDirEntry(StorageBaseImpl *base, - DirRef index) -{ - StorageInternalImpl* This = (StorageInternalImpl*) base; - - return StorageBaseImpl_DestroyDirEntry(This->parentStorage, - index); -} - -static HRESULT StorageInternalImpl_StreamReadAt(StorageBaseImpl *base, - DirRef index, ULARGE_INTEGER offset, ULONG size, void *buffer, ULONG *bytesRead) -{ - StorageInternalImpl* This = (StorageInternalImpl*) base; - - return StorageBaseImpl_StreamReadAt(This->parentStorage, - index, offset, size, buffer, bytesRead); -} - -static HRESULT StorageInternalImpl_StreamWriteAt(StorageBaseImpl *base, - DirRef index, ULARGE_INTEGER offset, ULONG size, const void *buffer, ULONG *bytesWritten) -{ - StorageInternalImpl* This = (StorageInternalImpl*) base; - - return StorageBaseImpl_StreamWriteAt(This->parentStorage, - index, offset, size, buffer, bytesWritten); -} - -static HRESULT StorageInternalImpl_StreamSetSize(StorageBaseImpl *base, - DirRef index, ULARGE_INTEGER newsize) -{ - StorageInternalImpl* This = (StorageInternalImpl*) base; - - return StorageBaseImpl_StreamSetSize(This->parentStorage, - index, newsize); -} - -static HRESULT StorageInternalImpl_StreamLink(StorageBaseImpl *base, - DirRef dst, DirRef src) -{ - StorageInternalImpl* This = (StorageInternalImpl*) base; - - return StorageBaseImpl_StreamLink(This->parentStorage, - dst, src); -} - -static HRESULT StorageInternalImpl_GetTransactionSig(StorageBaseImpl *base, - ULONG* result, BOOL refresh) -{ - return E_NOTIMPL; -} - -static HRESULT StorageInternalImpl_SetTransactionSig(StorageBaseImpl *base, - ULONG value) -{ - return E_NOTIMPL; -} - -static HRESULT StorageInternalImpl_LockTransaction(StorageBaseImpl *base, BOOL write) -{ - return E_NOTIMPL; -} - -static HRESULT StorageInternalImpl_UnlockTransaction(StorageBaseImpl *base, BOOL write) -{ - return E_NOTIMPL; -} - -/****************************************************************************** -** -** StorageInternalImpl_Commit -** -*/ -static HRESULT WINAPI StorageInternalImpl_Commit( - IStorage* iface, - DWORD grfCommitFlags) /* [in] */ -{ - StorageBaseImpl* This = impl_from_IStorage(iface); - TRACE("%p, %#lx.\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, %#lx.\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("<-- %#lx\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=%lx r=%lx d=%lx <-- %lx\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("%lx %s l=%lx r=%lx d=%lx\n", index, debugstr_w(data->name), data->leftChild, data->rightChild, data->dirRootEntry); - - hr = TransactedSnapshotImpl_EnsureReadEntry(This, index); - if (FAILED(hr)) - { - TRACE("<-- %#lx\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("<-- %#lx\n", hr); - return hr; - } - - memcpy(data, &This->entries[index].data, sizeof(DirEntry)); - - TRACE("%lx %s l=%lx r=%lx d=%lx\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("<-- %#lx\n", hr); - return hr; - } - - hr = TransactedSnapshotImpl_MakeStreamDirty(This, index); - if (FAILED(hr)) - { - TRACE("<-- %#lx\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("<-- %#lx\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("<-- %#lx\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("<-- %#lx\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("<-- %#lx\n", hr); - return hr; - } - - hr = TransactedSnapshotImpl_EnsureReadEntry(This, dst); - if (FAILED(hr)) - { - TRACE("<-- %#lx\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, %#lx\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("<-- %#lx\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 %lx\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(void *buffer, ULONG offset, WORD value) -{ - value = htole16(value); - memcpy((BYTE *)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(void *buffer, ULONG offset, DWORD value) -{ - value = htole32(value); - memcpy((BYTE *)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.HighPart); - value->u.HighPart = htole32(tmp.LowPart); -#else - memcpy(value, buffer + offset, sizeof(ULARGE_INTEGER)); -#endif -} - -void StorageUtl_WriteULargeInteger(void *buffer, ULONG offset, const ULARGE_INTEGER *value) -{ -#ifdef WORDS_BIGENDIAN - ULARGE_INTEGER tmp; - - tmp.LowPart = htole32(value->u.HighPart); - tmp.HighPart = htole32(value->u.LowPart); - memcpy((BYTE *)buffer + offset, &tmp, sizeof(ULARGE_INTEGER)); -#else - memcpy((BYTE *)buffer + offset, value, sizeof(ULARGE_INTEGER)); -#endif -} - -void StorageUtl_ReadGUID(const BYTE* buffer, ULONG offset, GUID* value) -{ - StorageUtl_ReadDWord(buffer, offset, (DWORD *)&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(void *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((BYTE *)buffer + offset + 8, value->Data4, sizeof(value->Data4)); -} - -void StorageUtl_CopyDirEntryToSTATSTG( - StorageBaseImpl* storage, - STATSTG* destination, - const DirEntry* source, - int statFlags) -{ - /* - * The copy of the string occurs only when the flag is not set - */ - if (!(statFlags & STATFLAG_NONAME) && source->stgType == STGTY_ROOT) - { - /* Use the filename for the root storage. */ - destination->pwcsName = 0; - StorageBaseImpl_GetFilename(storage, &destination->pwcsName); - } - else if( ((statFlags & STATFLAG_NONAME) != 0) || - (source->name[0] == 0) ) - { - destination->pwcsName = 0; - } - else - { - destination->pwcsName = - CoTaskMemAlloc((lstrlenW(source->name)+1)*sizeof(WCHAR)); - - lstrcpyW(destination->pwcsName, source->name); - } - - switch (source->stgType) - { - case STGTY_STORAGE: - case STGTY_ROOT: - destination->type = STGTY_STORAGE; - break; - case STGTY_STREAM: - destination->type = STGTY_STREAM; - break; - default: - destination->type = STGTY_STREAM; - break; - } - - destination->cbSize = source->size; -/* - currentReturnStruct->mtime = {0}; TODO - currentReturnStruct->ctime = {0}; - currentReturnStruct->atime = {0}; -*/ - destination->grfMode = 0; - destination->grfLocksSupported = 0; - destination->clsid = source->clsid; - destination->grfStateBits = 0; - destination->reserved = 0; -} - - -/************************************************************************ - * BlockChainStream implementation - ***********************************************************************/ - -/****************************************************************************** - * BlockChainStream_GetHeadOfChain - * - * Returns the head of this stream chain. - * Some special chains don't have directory entries, their heads are kept in - * This->headOfStreamPlaceHolder. - * - */ -static ULONG BlockChainStream_GetHeadOfChain(BlockChainStream* This) -{ - DirEntry chainEntry; - HRESULT hr; - - if (This->headOfStreamPlaceHolder != 0) - return *(This->headOfStreamPlaceHolder); - - if (This->ownerDirEntry != DIRENTRY_NULL) - { - hr = StorageImpl_ReadDirEntry( - This->parentStorage, - This->ownerDirEntry, - &chainEntry); - - if (SUCCEEDED(hr) && chainEntry.startingBlock < BLOCK_FIRST_SPECIAL) - return chainEntry.startingBlock; - } - - return BLOCK_END_OF_CHAIN; -} - -/* Read and save the index of all blocks in this stream. */ -static HRESULT BlockChainStream_UpdateIndexCache(BlockChainStream* This) -{ - ULONG next_sector, next_offset; - HRESULT hr; - struct BlockChainRun *last_run; - - if (This->indexCacheLen == 0) - { - last_run = NULL; - next_offset = 0; - next_sector = BlockChainStream_GetHeadOfChain(This); - } - else - { - last_run = &This->indexCache[This->indexCacheLen-1]; - next_offset = last_run->lastOffset+1; - hr = StorageImpl_GetNextBlockInChain(This->parentStorage, - last_run->firstSector + last_run->lastOffset - last_run->firstOffset, - &next_sector); - if (FAILED(hr)) return hr; - } - - while (next_sector != BLOCK_END_OF_CHAIN) - { - if (!last_run || next_sector != last_run->firstSector + next_offset - last_run->firstOffset) - { - /* Add the current block to the cache. */ - if (This->indexCacheSize == 0) - { - This->indexCache = HeapAlloc(GetProcessHeap(), 0, sizeof(struct BlockChainRun)*16); - if (!This->indexCache) return E_OUTOFMEMORY; - This->indexCacheSize = 16; - } - else if (This->indexCacheSize == This->indexCacheLen) - { - struct BlockChainRun *new_cache; - ULONG new_size; - - new_size = This->indexCacheSize * 2; - new_cache = HeapAlloc(GetProcessHeap(), 0, sizeof(struct BlockChainRun)*new_size); - if (!new_cache) return E_OUTOFMEMORY; - memcpy(new_cache, This->indexCache, sizeof(struct BlockChainRun)*This->indexCacheLen); - - HeapFree(GetProcessHeap(), 0, This->indexCache); - This->indexCache = new_cache; - This->indexCacheSize = new_size; - } - - This->indexCacheLen++; - last_run = &This->indexCache[This->indexCacheLen-1]; - last_run->firstSector = next_sector; - last_run->firstOffset = next_offset; - } - - last_run->lastOffset = next_offset; - - /* Find the next block. */ - next_offset++; - hr = StorageImpl_GetNextBlockInChain(This->parentStorage, next_sector, &next_sector); - if (FAILED(hr)) return hr; - } - - if (This->indexCacheLen) - { - This->tailIndex = last_run->firstSector + last_run->lastOffset - last_run->firstOffset; - This->numBlocks = last_run->lastOffset+1; - } - else - { - This->tailIndex = BLOCK_END_OF_CHAIN; - This->numBlocks = 0; - } - - return S_OK; -} - -/* Locate the nth block in this stream. */ -static ULONG BlockChainStream_GetSectorOfOffset(BlockChainStream *This, ULONG offset) -{ - ULONG min_offset = 0, max_offset = This->numBlocks-1; - ULONG min_run = 0, max_run = This->indexCacheLen-1; - - if (offset >= This->numBlocks) - return BLOCK_END_OF_CHAIN; - - while (min_run < max_run) - { - ULONG run_to_check = min_run + (offset - min_offset) * (max_run - min_run) / (max_offset - min_offset); - if (offset < This->indexCache[run_to_check].firstOffset) - { - max_offset = This->indexCache[run_to_check].firstOffset-1; - max_run = run_to_check-1; - } - else if (offset > This->indexCache[run_to_check].lastOffset) - { - min_offset = This->indexCache[run_to_check].lastOffset+1; - min_run = run_to_check+1; - } - else - /* Block is in this run. */ - min_run = max_run = run_to_check; - } - - return This->indexCache[min_run].firstSector + offset - This->indexCache[min_run].firstOffset; -} - -static HRESULT BlockChainStream_GetBlockAtOffset(BlockChainStream *This, - ULONG index, BlockChainBlock **block, ULONG *sector, BOOL create) -{ - BlockChainBlock *result=NULL; - int i; - - for (i=0; i<2; i++) - if (This->cachedBlocks[i].index == index) - { - *sector = This->cachedBlocks[i].sector; - *block = &This->cachedBlocks[i]; - return S_OK; - } - - *sector = BlockChainStream_GetSectorOfOffset(This, index); - if (*sector == BLOCK_END_OF_CHAIN) - return STG_E_DOCFILECORRUPT; - - if (create) - { - if (This->cachedBlocks[0].index == 0xffffffff) - result = &This->cachedBlocks[0]; - else if (This->cachedBlocks[1].index == 0xffffffff) - result = &This->cachedBlocks[1]; - else - { - result = &This->cachedBlocks[This->blockToEvict++]; - if (This->blockToEvict == 2) - This->blockToEvict = 0; - } - - if (result->dirty) - { - if (!StorageImpl_WriteBigBlock(This->parentStorage, result->sector, result->data)) - return STG_E_WRITEFAULT; - result->dirty = FALSE; - } - - result->read = FALSE; - result->index = index; - result->sector = *sector; - } - - *block = result; - return S_OK; -} - -BlockChainStream* BlockChainStream_Construct( - StorageImpl* parentStorage, - ULONG* headOfStreamPlaceHolder, - DirRef dirEntry) -{ - BlockChainStream* newStream; - - newStream = HeapAlloc(GetProcessHeap(), 0, sizeof(BlockChainStream)); - 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, 1); - 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, newNumBlocks - oldNumBlocks); - - 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, %li, %p, %lu, %p.\n",This, offset.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(&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.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.LowPart / This->parentStorage->smallBlockSize; - - ULONG offsetInBlock = offset.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.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.LowPart / This->parentStorage->smallBlockSize; - - ULONG offsetInBlock = offset.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.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.LowPart / This->parentStorage->smallBlockSize; - - if ((newSize.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.LowPart / This->parentStorage->smallBlockSize; - - if ((newSize.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.LowPart == size.LowPart) - return TRUE; - - if (newSize.LowPart < size.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.HighPart = 0; - - result.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]; - - memset(tempPath, 0, sizeof(tempPath)); - memset(tempFileName, 0, sizeof(tempFileName)); - - if ((GetTempPathW(MAX_PATH, tempPath)) == 0 ) - tempPath[0] = '.'; - - if (GetTempFileNameW(tempPath, L"STO", 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 = %#lx\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 object - * - * 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, %#lx, %ld, %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, %#lx, %#lx, %#lx, %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; -} - -/****************************************************************************** - * StgOpenStorageEx [OLE32.@] - */ -HRESULT WINAPI StgOpenStorageEx(const WCHAR* pwcsName, DWORD grfMode, DWORD stgfmt, DWORD grfAttrs, STGOPTIONS* pStgOptions, void* reserved, REFIID riid, void** ppObjectOpen) -{ - TRACE("%s, %#lx, %#lx, %#lx, %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, %#lx, %p, %ld, %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("<-- %#lx, 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; -} /*********************************************************************** * OleLoadFromStream (OLE32.@) -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/3106
Hi, It looks like your patch introduced the new failures shown below. Please investigate and fix them before resubmitting your patch. If they are not new, fixing them anyway would help a lot. Otherwise please ask for the known failures list to be updated. The full results can be found at: https://testbot.winehq.org/JobDetails.pl?Key=140882 Your paranoid android. === debian11 (build log) === error: patch failed: dlls/coml2/Makefile.in:1 error: patch failed: dlls/coml2/coml2.spec:17 error: patch failed: dlls/coml2/storage32.c:47 error: patch failed: dlls/ole32/stg_prop.c:51 error: patch failed: dlls/ole32/storage32.c:52 Task: Patch failed to apply === debian11b (build log) === error: patch failed: dlls/coml2/Makefile.in:1 error: patch failed: dlls/coml2/coml2.spec:17 error: patch failed: dlls/coml2/storage32.c:47 error: patch failed: dlls/ole32/stg_prop.c:51 error: patch failed: dlls/ole32/storage32.c:52 Task: Patch failed to apply
On Tue Dec 5 17:48:16 2023 +0000, Fabian Maurer wrote:
Done, see https://gitlab.winehq.org/wine/wine/-/merge_requests/4606 Updated and renamed, the last and biggest bunch of ole32 -> coml2 code moving.
-- https://gitlab.winehq.org/wine/wine/-/merge_requests/3106#note_55307
Nikolay Sivov (@nsivov) commented about dlls/coml2/coml2.spec:
@ stdcall StgIsStorageILockBytes(ptr) -@ stub StgOpenPropStg -@ stub StgOpenStorage -@ stub StgOpenStorageEx -@ stub StgOpenStorageOnILockBytes -@ stub StgSetTimes +@ stdcall StgOpenPropStg(ptr ptr long long ptr) +@ stdcall StgOpenStorage(wstr ptr long ptr long ptr) +@ stdcall StgOpenStorageEx(wstr long long long ptr ptr ptr ptr) +@ stdcall StgOpenStorageOnILockBytes(ptr ptr long ptr long ptr) +@ stdcall StgSetTimes(wstr ptr ptr ptr) @ stdcall WriteClassStg(ptr ptr) @ stdcall WriteClassStm(ptr ptr) + +# Wine internal exports +@ stdcall wine_PropertyStorage_ReadProperty(ptr ptr ptr long ptr ptr) PropertyStorage_ReadProperty StgConvertPropertyToVariant, and probably StgConvertVariantToProperty too, are exported by ordinal from coml2. I don't think we should add private exports for those.
-- https://gitlab.winehq.org/wine/wine/-/merge_requests/3106#note_55319
participants (4)
-
Fabian Maurer -
Fabian Maurer (@DarkShadow44) -
Marvin -
Nikolay Sivov (@nsivov)