From: Fabian Maurer dark.shadow4@web.de
--- dlls/coml2/dictionary.c | 191 + dlls/coml2/dictionary.h | 93 + dlls/coml2/filelockbytes.c | 401 ++ dlls/coml2/stg_prop.c | 3297 +++++++++++ dlls/coml2/stg_stream.c | 705 +++ dlls/coml2/storage32.c | 10649 +++++++++++++++++++++++++++++++++++ dlls/coml2/storage32.h | 571 ++ 7 files changed, 15907 insertions(+) create mode 100644 dlls/coml2/dictionary.c create mode 100644 dlls/coml2/dictionary.h create mode 100644 dlls/coml2/filelockbytes.c create mode 100644 dlls/coml2/stg_prop.c create mode 100644 dlls/coml2/stg_stream.c create mode 100644 dlls/coml2/storage32.c create mode 100644 dlls/coml2/storage32.h
diff --git a/dlls/coml2/dictionary.c b/dlls/coml2/dictionary.c new file mode 100644 index 00000000000..593a4ff1588 --- /dev/null +++ b/dlls/coml2/dictionary.c @@ -0,0 +1,191 @@ +/* Simple dictionary implementation using a linked list. + * FIXME: a skip list would be faster. + * + * Copyright 2005 Juan Lang + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ +#include <assert.h> +#include <stdarg.h> +#include "windef.h" +#include "winbase.h" +#include "dictionary.h" +#include "wine/debug.h" + +WINE_DEFAULT_DEBUG_CHANNEL(storage); + +struct dictionary_entry +{ + void *key; + void *value; + struct dictionary_entry *next; +}; + +struct dictionary +{ + comparefunc comp; + destroyfunc destroy; + void *extra; + struct dictionary_entry *head; + UINT num_entries; +}; + +struct dictionary *dictionary_create(comparefunc c, destroyfunc d, void *extra) +{ + struct dictionary *ret; + + TRACE("(%p, %p, %p)\n", c, d, extra); + if (!c) + return NULL; + ret = HeapAlloc(GetProcessHeap(), 0, sizeof(struct dictionary)); + if (ret) + { + ret->comp = c; + ret->destroy = d; + ret->extra = extra; + ret->head = NULL; + ret->num_entries = 0; + } + TRACE("returning %p\n", ret); + return ret; +} + +void dictionary_destroy(struct dictionary *d) +{ + TRACE("(%p)\n", d); + if (d) + { + struct dictionary_entry *p; + + for (p = d->head; p; ) + { + struct dictionary_entry *next = p->next; + + if (d->destroy) + d->destroy(p->key, p->value, d->extra); + HeapFree(GetProcessHeap(), 0, p); + p = next; + } + HeapFree(GetProcessHeap(), 0, d); + } +} + +UINT dictionary_num_entries(struct dictionary *d) +{ + return d ? d->num_entries : 0; +} + +/* Returns the address of the pointer to the node containing k. (It returns + * the address of either h->head or the address of the next member of the + * prior node. It's useful when you want to delete.) + * Assumes h and prev are not NULL. + */ +static struct dictionary_entry **dictionary_find_internal(struct dictionary *d, + const void *k) +{ + struct dictionary_entry **ret = NULL; + struct dictionary_entry *p; + + assert(d); + /* special case for head containing the desired element */ + if (d->head && d->comp(k, d->head->key, d->extra) == 0) + ret = &d->head; + for (p = d->head; !ret && p && p->next; p = p->next) + { + if (d->comp(k, p->next->key, d->extra) == 0) + ret = &p->next; + } + return ret; +} + +void dictionary_insert(struct dictionary *d, const void *k, const void *v) +{ + struct dictionary_entry **prior; + + TRACE("(%p, %p, %p)\n", d, k, v); + if (!d) + return; + if ((prior = dictionary_find_internal(d, k))) + { + if (d->destroy) + d->destroy((*prior)->key, (*prior)->value, d->extra); + (*prior)->key = (void *)k; + (*prior)->value = (void *)v; + } + else + { + struct dictionary_entry *elem = HeapAlloc(GetProcessHeap(), 0, + sizeof(struct dictionary_entry)); + + if (!elem) + return; + elem->key = (void *)k; + elem->value = (void *)v; + elem->next = d->head; + d->head = elem; + d->num_entries++; + } +} + +BOOL dictionary_find(struct dictionary *d, const void *k, void **value) +{ + struct dictionary_entry **prior; + BOOL ret = FALSE; + + TRACE("(%p, %p, %p)\n", d, k, value); + if (!d) + return FALSE; + if (!value) + return FALSE; + if ((prior = dictionary_find_internal(d, k))) + { + *value = (*prior)->value; + ret = TRUE; + } + TRACE("returning %d (%p)\n", ret, *value); + return ret; +} + +void dictionary_remove(struct dictionary *d, const void *k) +{ + struct dictionary_entry **prior, *temp; + + TRACE("(%p, %p)\n", d, k); + if (!d) + return; + if ((prior = dictionary_find_internal(d, k))) + { + temp = *prior; + if (d->destroy) + d->destroy((*prior)->key, (*prior)->value, d->extra); + *prior = (*prior)->next; + HeapFree(GetProcessHeap(), 0, temp); + d->num_entries--; + } +} + +void dictionary_enumerate(struct dictionary *d, enumeratefunc e, void *closure) +{ + struct dictionary_entry *p; + + TRACE("(%p, %p, %p)\n", d, e, closure); + if (!d) + return; + if (!e) + return; + for (p = d->head; p; p = p->next) + if (!e(p->key, p->value, d->extra, closure)) + break; +} diff --git a/dlls/coml2/dictionary.h b/dlls/coml2/dictionary.h new file mode 100644 index 00000000000..231a6474723 --- /dev/null +++ b/dlls/coml2/dictionary.h @@ -0,0 +1,93 @@ +/* Simple dictionary + * + * Copyright 2005 Juan Lang + * + * This is a pretty basic dictionary, or map if you prefer. It's not + * thread-safe. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ +#ifndef __DICTIONARY_H__ +#define __DICTIONARY_H__ + +#include <stdarg.h> +#include "windef.h" +#include "winbase.h" + +struct dictionary; + +/* Returns whether key a is less than, equal to, or greater than key b, in + * the same way (a - b) would for integers or strcmp(a, b) would for ANSI + * strings. + */ +typedef int (*comparefunc)(const void *a, const void *b, void *extra); + +/* Called for every element removed from the dictionary. See + * dictionary_destroy, dictionary_insert, and dictionary_remove. + */ +typedef void (*destroyfunc)(void *k, void *v, void *extra); + +/* Called for each element in the dictionary. Return FALSE if you don't want + * to enumerate any more. + */ +typedef BOOL (*enumeratefunc)(const void *k, const void *v, void *extra, + void *closure); + +/* Constructs a dictionary, using c as a comparison function for keys. + * If d is not NULL, it will be called whenever an item is about to be removed + * from the table, for example when dictionary_remove is called for a key, or + * when dictionary_destroy is called. + * extra is passed to c (and d, if it's provided). + * Assumes c is not NULL. + */ +struct dictionary *dictionary_create(comparefunc c, destroyfunc d, void *extra); + +/* Assumes d is not NULL. */ +void dictionary_destroy(struct dictionary *d); + +/* Returns how many entries have been stored in the dictionary. If two values + * with the same key are inserted, only one is counted. + */ +UINT dictionary_num_entries(struct dictionary *d); + +/* Sets an element with key k and value v to the dictionary. If a value + * already exists with key k, its value is replaced, and the destroyfunc (if + * set) is called for the previous item. + * Assumes k and v can be bitwise-copied. + * Both k and v are allowed to be NULL, in case you want to use integer + * values for either the key or the value. + * Assumes d is not NULL. + */ +void dictionary_insert(struct dictionary *d, const void *k, const void *v); + +/* If a value with key k has been inserted into the dictionary, *v is set + * to its associated value. Returns FALSE if the key is not found, and TRUE + * if it is. *v is undefined if it returns FALSE. (It is not set to NULL, + * because this dictionary doesn't prevent you from using NULL as a value + * value; see dictionary_insert.) + * Assumes d and v are not NULL. + */ +BOOL dictionary_find(struct dictionary *d, const void *k, void **v); + +/* Removes the element with key k from the dictionary. Calls the destroyfunc + * for the dictionary with the element if found (so you may destroy it if it's + * dynamically allocated.) + * Assumes d is not NULL. + */ +void dictionary_remove(struct dictionary *d, const void *k); + +void dictionary_enumerate(struct dictionary *d, enumeratefunc e, void *closure); + +#endif /* ndef __DICTIONARY_H__ */ diff --git a/dlls/coml2/filelockbytes.c b/dlls/coml2/filelockbytes.c new file mode 100644 index 00000000000..d806916f1d5 --- /dev/null +++ b/dlls/coml2/filelockbytes.c @@ -0,0 +1,401 @@ +/****************************************************************************** + * + * File-based ILockBytes implementation + * + * Copyright 1999 Thuy Nguyen + * Copyright 2010 Vincent Povirk for CodeWeavers + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#include <assert.h> +#include <stdlib.h> +#include <stdarg.h> +#include <stdio.h> +#include <string.h> +#include <limits.h> + +#define COBJMACROS +#include "windef.h" +#include "winbase.h" +#include "winuser.h" +#include "winerror.h" +#include "objbase.h" +#include "ole2.h" + +#include "storage32.h" + +#include "wine/debug.h" + +WINE_DEFAULT_DEBUG_CHANNEL(storage); + +typedef struct FileLockBytesImpl +{ + ILockBytes ILockBytes_iface; + LONG ref; + HANDLE hfile; + DWORD flProtect; + LPWSTR pwcsName; +} FileLockBytesImpl; + +static const ILockBytesVtbl FileLockBytesImpl_Vtbl; + +static inline FileLockBytesImpl *impl_from_ILockBytes(ILockBytes *iface) +{ + return CONTAINING_RECORD(iface, FileLockBytesImpl, ILockBytes_iface); +} + +/*********************************************************** + * Prototypes for private methods + */ + +/**************************************************************************** + * GetProtectMode + * + * This function will return a protection mode flag for a file-mapping object + * from the open flags of a file. + */ +static DWORD GetProtectMode(DWORD openFlags) +{ + switch(STGM_ACCESS_MODE(openFlags)) + { + case STGM_WRITE: + case STGM_READWRITE: + return PAGE_READWRITE; + } + return PAGE_READONLY; +} + +/****************************************************************************** + * FileLockBytesImpl_Construct + * + * Initialize a big block object supported by a file. + */ +HRESULT FileLockBytesImpl_Construct(HANDLE hFile, DWORD openFlags, LPCWSTR pwcsName, ILockBytes **pLockBytes) +{ + FileLockBytesImpl *This; + WCHAR fullpath[MAX_PATH]; + + if (hFile == INVALID_HANDLE_VALUE) + return E_FAIL; + + This = HeapAlloc(GetProcessHeap(), 0, sizeof(FileLockBytesImpl)); + + if (!This) + return E_OUTOFMEMORY; + + This->ILockBytes_iface.lpVtbl = &FileLockBytesImpl_Vtbl; + This->ref = 1; + This->hfile = hFile; + This->flProtect = GetProtectMode(openFlags); + + if(pwcsName) { + if (!GetFullPathNameW(pwcsName, MAX_PATH, fullpath, NULL)) + { + lstrcpynW(fullpath, pwcsName, MAX_PATH); + } + This->pwcsName = HeapAlloc(GetProcessHeap(), 0, + (lstrlenW(fullpath)+1)*sizeof(WCHAR)); + if (!This->pwcsName) + { + HeapFree(GetProcessHeap(), 0, This); + return E_OUTOFMEMORY; + } + lstrcpyW(This->pwcsName, fullpath); + } + else + This->pwcsName = NULL; + + *pLockBytes = &This->ILockBytes_iface; + + return S_OK; +} + +/* ILockByte Interfaces */ + +static HRESULT WINAPI FileLockBytesImpl_QueryInterface(ILockBytes *iface, REFIID riid, + void **ppvObject) +{ + if (IsEqualIID(riid, &IID_IUnknown) || IsEqualIID(riid, &IID_ILockBytes)) + *ppvObject = iface; + else + { + *ppvObject = NULL; + return E_NOINTERFACE; + } + + IUnknown_AddRef((IUnknown*)*ppvObject); + + return S_OK; +} + +static ULONG WINAPI FileLockBytesImpl_AddRef(ILockBytes *iface) +{ + FileLockBytesImpl* This = impl_from_ILockBytes(iface); + return InterlockedIncrement(&This->ref); +} + +static ULONG WINAPI FileLockBytesImpl_Release(ILockBytes *iface) +{ + FileLockBytesImpl* This = impl_from_ILockBytes(iface); + ULONG ref; + + ref = InterlockedDecrement(&This->ref); + + if (ref == 0) + { + CloseHandle(This->hfile); + HeapFree(GetProcessHeap(), 0, This->pwcsName); + HeapFree(GetProcessHeap(), 0, This); + } + + return ref; +} + +/****************************************************************************** + * This method is part of the ILockBytes interface. + * + * It reads a block of information from the byte array at the specified + * offset. + * + * See the documentation of ILockBytes for more info. + */ +static HRESULT WINAPI FileLockBytesImpl_ReadAt( + ILockBytes* iface, + ULARGE_INTEGER ulOffset, /* [in] */ + void* pv, /* [length_is][size_is][out] */ + ULONG cb, /* [in] */ + ULONG* pcbRead) /* [out] */ +{ + FileLockBytesImpl* This = impl_from_ILockBytes(iface); + ULONG bytes_left = cb; + LPBYTE readPtr = pv; + BOOL ret; + LARGE_INTEGER offset; + ULONG cbRead; + + TRACE("%p, %ld, %p, %lu, %p.\n", iface, ulOffset.LowPart, pv, cb, pcbRead); + + /* verify a sane environment */ + if (!This) return E_FAIL; + + if (pcbRead) + *pcbRead = 0; + + offset.QuadPart = ulOffset.QuadPart; + + ret = SetFilePointerEx(This->hfile, offset, NULL, FILE_BEGIN); + + if (!ret) + return STG_E_READFAULT; + + while (bytes_left) + { + ret = ReadFile(This->hfile, readPtr, bytes_left, &cbRead, NULL); + + if (!ret || cbRead == 0) + return STG_E_READFAULT; + + if (pcbRead) + *pcbRead += cbRead; + + bytes_left -= cbRead; + readPtr += cbRead; + } + + TRACE("finished\n"); + return S_OK; +} + +/****************************************************************************** + * This method is part of the ILockBytes interface. + * + * It writes the specified bytes at the specified offset. + * position. If the file is too small, it will be resized. + * + * See the documentation of ILockBytes for more info. + */ +static HRESULT WINAPI FileLockBytesImpl_WriteAt( + ILockBytes* iface, + ULARGE_INTEGER ulOffset, /* [in] */ + const void* pv, /* [size_is][in] */ + ULONG cb, /* [in] */ + ULONG* pcbWritten) /* [out] */ +{ + FileLockBytesImpl* This = impl_from_ILockBytes(iface); + ULONG bytes_left = cb; + const BYTE *writePtr = pv; + BOOL ret; + LARGE_INTEGER offset; + ULONG cbWritten; + + TRACE("%p, %ld, %p, %lu, %p.\n", iface, ulOffset.LowPart, pv, cb, pcbWritten); + + /* verify a sane environment */ + if (!This) return E_FAIL; + + if (This->flProtect != PAGE_READWRITE) + return STG_E_ACCESSDENIED; + + if (pcbWritten) + *pcbWritten = 0; + + offset.QuadPart = ulOffset.QuadPart; + + ret = SetFilePointerEx(This->hfile, offset, NULL, FILE_BEGIN); + + if (!ret) + return STG_E_WRITEFAULT; + + while (bytes_left) + { + ret = WriteFile(This->hfile, writePtr, bytes_left, &cbWritten, NULL); + + if (!ret) + return STG_E_WRITEFAULT; + + if (pcbWritten) + *pcbWritten += cbWritten; + + bytes_left -= cbWritten; + writePtr += cbWritten; + } + + TRACE("finished\n"); + return S_OK; +} + +static HRESULT WINAPI FileLockBytesImpl_Flush(ILockBytes* iface) +{ + return S_OK; +} + +/****************************************************************************** + * ILockBytes_SetSize + * + * Sets the size of the file. + * + */ +static HRESULT WINAPI FileLockBytesImpl_SetSize(ILockBytes* iface, ULARGE_INTEGER newSize) +{ + FileLockBytesImpl* This = impl_from_ILockBytes(iface); + HRESULT hr = S_OK; + LARGE_INTEGER newpos; + + TRACE("new size %lu\n", newSize.LowPart); + + newpos.QuadPart = newSize.QuadPart; + if (SetFilePointerEx(This->hfile, newpos, NULL, FILE_BEGIN)) + { + SetEndOfFile(This->hfile); + } + + return hr; +} + +static HRESULT get_lock_error(void) +{ + switch (GetLastError()) + { + case ERROR_LOCK_VIOLATION: return STG_E_LOCKVIOLATION; break; + case ERROR_ACCESS_DENIED: return STG_E_ACCESSDENIED; break; + case ERROR_NOT_SUPPORTED: return STG_E_INVALIDFUNCTION; break; + default: + FIXME("no mapping for error %ld\n", GetLastError()); + return STG_E_INVALIDFUNCTION; + } +} + +static HRESULT WINAPI FileLockBytesImpl_LockRegion(ILockBytes* iface, + ULARGE_INTEGER libOffset, ULARGE_INTEGER cb, DWORD dwLockType) +{ + FileLockBytesImpl* This = impl_from_ILockBytes(iface); + OVERLAPPED ol; + DWORD lock_flags = LOCKFILE_FAIL_IMMEDIATELY; + + TRACE("ofs %lu count %lu flags %lx\n", libOffset.LowPart, cb.LowPart, dwLockType); + + if (dwLockType & LOCK_WRITE) + return STG_E_INVALIDFUNCTION; + + if (dwLockType & (LOCK_EXCLUSIVE|LOCK_ONLYONCE)) + lock_flags |= LOCKFILE_EXCLUSIVE_LOCK; + + ol.hEvent = 0; + ol.Offset = libOffset.LowPart; + ol.OffsetHigh = libOffset.HighPart; + + if (LockFileEx(This->hfile, lock_flags, 0, cb.LowPart, cb.HighPart, &ol)) + return S_OK; + return get_lock_error(); +} + +static HRESULT WINAPI FileLockBytesImpl_UnlockRegion(ILockBytes* iface, + ULARGE_INTEGER libOffset, ULARGE_INTEGER cb, DWORD dwLockType) +{ + FileLockBytesImpl* This = impl_from_ILockBytes(iface); + OVERLAPPED ol; + + TRACE("ofs %lu count %lu flags %lx\n", libOffset.LowPart, cb.LowPart, dwLockType); + + if (dwLockType & LOCK_WRITE) + return STG_E_INVALIDFUNCTION; + + ol.hEvent = 0; + ol.Offset = libOffset.LowPart; + ol.OffsetHigh = libOffset.HighPart; + + if (UnlockFileEx(This->hfile, 0, cb.LowPart, cb.HighPart, &ol)) + return S_OK; + return get_lock_error(); +} + +static HRESULT WINAPI FileLockBytesImpl_Stat(ILockBytes* iface, + STATSTG *pstatstg, DWORD grfStatFlag) +{ + FileLockBytesImpl* This = impl_from_ILockBytes(iface); + + if (!(STATFLAG_NONAME & grfStatFlag) && This->pwcsName) + { + pstatstg->pwcsName = + CoTaskMemAlloc((lstrlenW(This->pwcsName)+1)*sizeof(WCHAR)); + + lstrcpyW(pstatstg->pwcsName, This->pwcsName); + } + else + pstatstg->pwcsName = NULL; + + pstatstg->type = STGTY_LOCKBYTES; + + pstatstg->cbSize.LowPart = GetFileSize(This->hfile, &pstatstg->cbSize.HighPart); + /* FIXME: If the implementation is exported, we'll need to set other fields. */ + + pstatstg->grfLocksSupported = LOCK_EXCLUSIVE|LOCK_ONLYONCE|WINE_LOCK_READ; + + return S_OK; +} + +static const ILockBytesVtbl FileLockBytesImpl_Vtbl = { + FileLockBytesImpl_QueryInterface, + FileLockBytesImpl_AddRef, + FileLockBytesImpl_Release, + FileLockBytesImpl_ReadAt, + FileLockBytesImpl_WriteAt, + FileLockBytesImpl_Flush, + FileLockBytesImpl_SetSize, + FileLockBytesImpl_LockRegion, + FileLockBytesImpl_UnlockRegion, + FileLockBytesImpl_Stat +}; diff --git a/dlls/coml2/stg_prop.c b/dlls/coml2/stg_prop.c new file mode 100644 index 00000000000..8bc159c7dd5 --- /dev/null +++ b/dlls/coml2/stg_prop.c @@ -0,0 +1,3297 @@ +/* + * Compound Storage (32 bit version) + * Storage implementation + * + * This file contains the compound file implementation + * of the storage interface. + * + * Copyright 1999 Francis Beaudet + * Copyright 1999 Sylvain St-Germain + * Copyright 1999 Thuy Nguyen + * Copyright 2005 Mike McCormack + * Copyright 2005 Juan Lang + * Copyright 2006 Mike McCormack + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + * + * TODO: + * - I don't honor the maximum property set size. + * - Certain bogus files could result in reading past the end of a buffer. + * - Mac-generated files won't be read correctly, even if they're little + * endian, because I disregard whether the generator was a Mac. This means + * strings will probably be munged (as I don't understand Mac scripts.) + * - Not all PROPVARIANT types are supported. + * - User defined properties are not supported, see comment in + * PropertyStorage_ReadFromStream + */ + +#include <assert.h> +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#define COBJMACROS +#include "windef.h" +#include "winbase.h" +#include "winnls.h" +#include "winuser.h" +#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 = +{ + 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 + */ +static const WCHAR szSummaryInfo[] = L"\5SummaryInformation"; +static const WCHAR szDocSummaryInfo[] = L"\5DocumentSummaryInformation"; + +#define BITS_PER_BYTE 8 +#define CHARMASK 0x1f +#define BITS_IN_CHARMASK 5 +#define NUM_ALPHA_CHARS 26 + +/*********************************************************************** + * FmtIdToPropStgName [ole32.@] + * Returns the storage name of the format ID rfmtid. + * PARAMS + * rfmtid [I] Format ID for which to return a storage name + * str [O] Storage name associated with rfmtid. + * + * RETURNS + * E_INVALIDARG if rfmtid or str i NULL, S_OK otherwise. + * + * NOTES + * str must be at least CCH_MAX_PROPSTG_NAME characters in length. + */ +HRESULT WINAPI FmtIdToPropStgName(const FMTID *rfmtid, LPOLESTR str) +{ + static const char fmtMap[] = "abcdefghijklmnopqrstuvwxyz012345"; + + TRACE("%s, %p\n", debugstr_guid(rfmtid), str); + + if (!rfmtid) return E_INVALIDARG; + if (!str) return E_INVALIDARG; + + if (IsEqualGUID(&FMTID_SummaryInformation, rfmtid)) + lstrcpyW(str, szSummaryInfo); + else if (IsEqualGUID(&FMTID_DocSummaryInformation, rfmtid)) + lstrcpyW(str, szDocSummaryInfo); + else if (IsEqualGUID(&FMTID_UserDefinedProperties, rfmtid)) + lstrcpyW(str, szDocSummaryInfo); + else + { + const BYTE *fmtptr; + WCHAR *pstr = str; + ULONG bitsRemaining = BITS_PER_BYTE; + + *pstr++ = 5; + for (fmtptr = (const BYTE *)rfmtid; fmtptr < (const BYTE *)rfmtid + sizeof(FMTID); ) + { + ULONG i = *fmtptr >> (BITS_PER_BYTE - bitsRemaining); + + if (bitsRemaining >= BITS_IN_CHARMASK) + { + *pstr = (WCHAR)(fmtMap[i & CHARMASK]); + if (bitsRemaining == BITS_PER_BYTE && *pstr >= 'a' && + *pstr <= 'z') + *pstr += 'A' - 'a'; + pstr++; + bitsRemaining -= BITS_IN_CHARMASK; + if (bitsRemaining == 0) + { + fmtptr++; + bitsRemaining = BITS_PER_BYTE; + } + } + else + { + if (++fmtptr < (const BYTE *)rfmtid + sizeof(FMTID)) + i |= *fmtptr << bitsRemaining; + *pstr++ = (WCHAR)(fmtMap[i & CHARMASK]); + bitsRemaining += BITS_PER_BYTE - BITS_IN_CHARMASK; + } + } + *pstr = 0; + } + TRACE("returning %s\n", debugstr_w(str)); + return S_OK; +} + +/*********************************************************************** + * PropStgNameToFmtId [ole32.@] + * Returns the format ID corresponding to the given name. + * PARAMS + * str [I] Storage name to convert to a format ID. + * rfmtid [O] Format ID corresponding to str. + * + * RETURNS + * E_INVALIDARG if rfmtid or str is NULL or if str can't be converted to + * a format ID, S_OK otherwise. + */ +HRESULT WINAPI PropStgNameToFmtId(const LPOLESTR str, FMTID *rfmtid) +{ + HRESULT hr = STG_E_INVALIDNAME; + + TRACE("%s, %p\n", debugstr_w(str), rfmtid); + + if (!rfmtid) return E_INVALIDARG; + if (!str) return STG_E_INVALIDNAME; + + if (!lstrcmpiW(str, szDocSummaryInfo)) + { + *rfmtid = FMTID_DocSummaryInformation; + hr = S_OK; + } + else if (!lstrcmpiW(str, szSummaryInfo)) + { + *rfmtid = FMTID_SummaryInformation; + hr = S_OK; + } + else + { + ULONG bits; + BYTE *fmtptr = (BYTE *)rfmtid - 1; + const WCHAR *pstr = str; + + memset(rfmtid, 0, sizeof(*rfmtid)); + for (bits = 0; bits < sizeof(FMTID) * BITS_PER_BYTE; + bits += BITS_IN_CHARMASK) + { + ULONG bitsUsed = bits % BITS_PER_BYTE, bitsStored; + WCHAR wc; + + if (bitsUsed == 0) + fmtptr++; + wc = *++pstr - 'A'; + if (wc > NUM_ALPHA_CHARS) + { + wc += 'A' - 'a'; + if (wc > NUM_ALPHA_CHARS) + { + wc += 'a' - '0' + NUM_ALPHA_CHARS; + if (wc > CHARMASK) + { + WARN("invalid character (%d)\n", *pstr); + goto end; + } + } + } + *fmtptr |= wc << bitsUsed; + bitsStored = min(BITS_PER_BYTE - bitsUsed, BITS_IN_CHARMASK); + if (bitsStored < BITS_IN_CHARMASK) + { + wc >>= BITS_PER_BYTE - bitsUsed; + if (bits + bitsStored == sizeof(FMTID) * BITS_PER_BYTE) + { + if (wc != 0) + { + WARN("extra bits\n"); + goto end; + } + break; + } + fmtptr++; + *fmtptr |= (BYTE)wc; + } + } + hr = S_OK; + } +end: + return hr; +} + +#ifdef __i386__ /* thiscall functions are i386-specific */ + +#define DEFINE_STDCALL_WRAPPER(num,func,args) \ + __ASM_STDCALL_FUNC(func, args, \ + "popl %eax\n\t" \ + "popl %ecx\n\t" \ + "pushl %eax\n\t" \ + "movl (%ecx), %eax\n\t" \ + "jmp *(4*(" #num "))(%eax)" ) + +DEFINE_STDCALL_WRAPPER(0,Allocate_PMemoryAllocator,8) +extern void* WINAPI Allocate_PMemoryAllocator(void *this, ULONG cbSize); + +#else + +static void* WINAPI Allocate_PMemoryAllocator(void *this, ULONG cbSize) +{ + void* (WINAPI *fn)(void*,ULONG) = **(void***)this; + return fn(this, cbSize); +} + +#endif + +BOOLEAN WINAPI StgConvertPropertyToVariant(const SERIALIZEDPROPERTYVALUE* prop, + USHORT CodePage, PROPVARIANT* pvar, void* pma) +{ + struct read_buffer read_buffer; + HRESULT hr; + + read_buffer.data = (BYTE *)prop; + read_buffer.size = ~(size_t)0; + hr = PropertyStorage_ReadProperty(pvar, &read_buffer, 0, CodePage, Allocate_PMemoryAllocator, pma); + + if (FAILED(hr)) + { + FIXME("should raise C++ exception on failure\n"); + PropVariantInit(pvar); + } + + return FALSE; +} + +SERIALIZEDPROPERTYVALUE* WINAPI StgConvertVariantToProperty(const PROPVARIANT *pvar, + USHORT CodePage, SERIALIZEDPROPERTYVALUE *pprop, ULONG *pcb, PROPID pid, + BOOLEAN fReserved, ULONG *pcIndirect) +{ + FIXME("%p, %d, %p, %p, %ld, %d, %p.\n", pvar, CodePage, pprop, pcb, pid, fReserved, pcIndirect); + + 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/coml2/stg_stream.c b/dlls/coml2/stg_stream.c new file mode 100644 index 00000000000..25ead2c7c49 --- /dev/null +++ b/dlls/coml2/stg_stream.c @@ -0,0 +1,705 @@ +/* + * Compound Storage (32 bit version) + * Stream implementation + * + * This file contains the implementation of the stream interface + * for streams contained in a compound storage. + * + * Copyright 1999 Francis Beaudet + * Copyright 1999 Thuy Nguyen + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#include <stdlib.h> +#include <stdarg.h> +#include <stdio.h> +#include <string.h> + +#define COBJMACROS +#include "windef.h" +#include "winbase.h" +#include "winerror.h" +#include "winternl.h" +#include "wine/debug.h" + +#include "storage32.h" + +WINE_DEFAULT_DEBUG_CHANNEL(storage); + +/*** + * This implements the IUnknown method QueryInterface for this + * class + */ +static HRESULT WINAPI StgStreamImpl_QueryInterface( + IStream* iface, + REFIID riid, /* [in] */ + void** ppvObject) /* [iid_is][out] */ +{ + StgStreamImpl* This = impl_from_IStream(iface); + + if (ppvObject==0) + return E_INVALIDARG; + + *ppvObject = 0; + + if (IsEqualIID(&IID_IUnknown, riid) || + IsEqualIID(&IID_ISequentialStream, riid) || + IsEqualIID(&IID_IStream, riid)) + { + *ppvObject = &This->IStream_iface; + } + else + return E_NOINTERFACE; + + IStream_AddRef(iface); + + return S_OK; +} + +/*** + * This implements the IUnknown method AddRef for this + * class + */ +static ULONG WINAPI StgStreamImpl_AddRef( + IStream* iface) +{ + StgStreamImpl* This = impl_from_IStream(iface); + return InterlockedIncrement(&This->ref); +} + +/*** + * This implements the IUnknown method Release for this + * class + */ +static ULONG WINAPI StgStreamImpl_Release( + IStream* iface) +{ + StgStreamImpl* This = impl_from_IStream(iface); + ULONG ref = InterlockedDecrement(&This->ref); + + if (!ref) + { + TRACE("(%p)\n", This); + + /* + * Release the reference we are holding on the parent storage. + * IStorage_Release(&This->parentStorage->IStorage_iface); + * + * No, don't do this. Some apps call IStorage_Release without + * calling IStream_Release first. If we grab a reference the + * file is not closed, and the app fails when it tries to + * reopen the file (Easy-PC, for example). Just inform the + * storage that we have closed the stream + */ + + if (This->parentStorage) + StorageBaseImpl_RemoveStream(This->parentStorage, This); + This->parentStorage = 0; + HeapFree(GetProcessHeap(), 0, This); + } + + return ref; +} + +/*** + * This method is part of the ISequentialStream interface. + * + * It reads a block of information from the stream at the current + * position. It then moves the current position at the end of the + * read block + * + * See the documentation of ISequentialStream for more info. + */ +static HRESULT WINAPI StgStreamImpl_Read( + IStream* iface, + void* pv, /* [length_is][size_is][out] */ + ULONG cb, /* [in] */ + ULONG* pcbRead) /* [out] */ +{ + StgStreamImpl* This = impl_from_IStream(iface); + + ULONG bytesReadBuffer; + HRESULT res; + + TRACE("%p, %p, %lu, %p.\n", iface, pv, cb, pcbRead); + + if (!This->parentStorage) + { + WARN("storage reverted\n"); + return STG_E_REVERTED; + } + + /* + * If the caller is not interested in the number of bytes read, + * we use another buffer to avoid "if" statements in the code. + */ + if (pcbRead==0) + pcbRead = &bytesReadBuffer; + + res = StorageBaseImpl_StreamReadAt(This->parentStorage, + This->dirEntry, + This->currentPosition, + cb, + pv, + pcbRead); + + if (SUCCEEDED(res)) + { + /* + * Advance the pointer for the number of positions read. + */ + This->currentPosition.QuadPart += *pcbRead; + } + + TRACE("<-- %#lx\n", res); + return res; +} + +/*** + * This method is part of the ISequentialStream interface. + * + * It writes a block of information to the stream at the current + * position. It then moves the current position at the end of the + * written block. If the stream is too small to fit the block, + * the stream is grown to fit. + * + * See the documentation of ISequentialStream for more info. + */ +static HRESULT WINAPI StgStreamImpl_Write( + IStream* iface, + const void* pv, /* [size_is][in] */ + ULONG cb, /* [in] */ + ULONG* pcbWritten) /* [out] */ +{ + StgStreamImpl* This = impl_from_IStream(iface); + + ULONG bytesWritten = 0; + HRESULT res; + + TRACE("%p, %p, %lu, %p.\n", iface, pv, cb, pcbWritten); + + /* + * Do we have permission to write to this stream? + */ + switch(STGM_ACCESS_MODE(This->grfMode)) + { + case STGM_WRITE: + case STGM_READWRITE: + break; + default: + WARN("access denied by flags: %#lx\n", STGM_ACCESS_MODE(This->grfMode)); + return STG_E_ACCESSDENIED; + } + + if (!pv) + return STG_E_INVALIDPOINTER; + + if (!This->parentStorage) + { + WARN("storage reverted\n"); + return STG_E_REVERTED; + } + + /* + * If the caller is not interested in the number of bytes written, + * we use another buffer to avoid "if" statements in the code. + */ + if (pcbWritten == 0) + pcbWritten = &bytesWritten; + + /* + * Initialize the out parameter + */ + *pcbWritten = 0; + + if (cb == 0) + { + TRACE("<-- S_OK, written 0\n"); + return S_OK; + } + + res = StorageBaseImpl_StreamWriteAt(This->parentStorage, + This->dirEntry, + This->currentPosition, + cb, + pv, + pcbWritten); + + /* + * Advance the position pointer for the number of positions written. + */ + This->currentPosition.QuadPart += *pcbWritten; + + if (SUCCEEDED(res)) + res = StorageBaseImpl_Flush(This->parentStorage); + + TRACE("<-- %#lx, written %lu\n", res, *pcbWritten); + return res; +} + +/*** + * This method is part of the IStream interface. + * + * It will move the current stream pointer according to the parameters + * given. + * + * See the documentation of IStream for more info. + */ +static HRESULT WINAPI StgStreamImpl_Seek( + IStream* iface, + LARGE_INTEGER dlibMove, /* [in] */ + DWORD dwOrigin, /* [in] */ + ULARGE_INTEGER* plibNewPosition) /* [out] */ +{ + StgStreamImpl* This = impl_from_IStream(iface); + + ULARGE_INTEGER newPosition; + DirEntry currentEntry; + HRESULT hr; + + TRACE("%p, %ld, %ld, %p.\n", iface, dlibMove.LowPart, dwOrigin, plibNewPosition); + + /* + * fail if the stream has no parent (as does windows) + */ + + if (!This->parentStorage) + { + WARN("storage reverted\n"); + return STG_E_REVERTED; + } + + /* + * The caller is allowed to pass in NULL as the new position return value. + * If it happens, we assign it to a dynamic variable to avoid special cases + * in the code below. + */ + if (plibNewPosition == 0) + { + plibNewPosition = &newPosition; + } + + /* + * The file pointer is moved depending on the given "function" + * parameter. + */ + switch (dwOrigin) + { + case STREAM_SEEK_SET: + plibNewPosition->u.HighPart = 0; + plibNewPosition->u.LowPart = 0; + break; + case STREAM_SEEK_CUR: + *plibNewPosition = This->currentPosition; + break; + case STREAM_SEEK_END: + hr = StorageBaseImpl_ReadDirEntry(This->parentStorage, This->dirEntry, ¤tEntry); + if (FAILED(hr)) return hr; + *plibNewPosition = currentEntry.size; + break; + default: + WARN("invalid dwOrigin %ld\n", dwOrigin); + return STG_E_INVALIDFUNCTION; + } + + plibNewPosition->QuadPart += dlibMove.QuadPart; + + /* + * tell the caller what we calculated + */ + This->currentPosition = *plibNewPosition; + + return S_OK; +} + +/*** + * This method is part of the IStream interface. + * + * It will change the size of a stream. + * + * See the documentation of IStream for more info. + */ +static HRESULT WINAPI StgStreamImpl_SetSize( + IStream* iface, + ULARGE_INTEGER libNewSize) /* [in] */ +{ + StgStreamImpl* This = impl_from_IStream(iface); + + HRESULT hr; + + TRACE("%p, %ld.\n", iface, libNewSize.LowPart); + + if(!This->parentStorage) + { + WARN("storage reverted\n"); + return STG_E_REVERTED; + } + + /* + * As documented. + */ + if (libNewSize.HighPart != 0) + { + WARN("invalid value for libNewSize.HighPart %ld\n", libNewSize.HighPart); + return STG_E_INVALIDFUNCTION; + } + + /* + * Do we have permission? + */ + if (!(This->grfMode & (STGM_WRITE | STGM_READWRITE))) + { + WARN("access denied\n"); + return STG_E_ACCESSDENIED; + } + + hr = StorageBaseImpl_StreamSetSize(This->parentStorage, This->dirEntry, libNewSize); + + if (SUCCEEDED(hr)) + hr = StorageBaseImpl_Flush(This->parentStorage); + + return hr; +} + +/*** + * This method is part of the IStream interface. + * + * It will copy the 'cb' Bytes to 'pstm' IStream. + * + * See the documentation of IStream for more info. + */ +static HRESULT WINAPI StgStreamImpl_CopyTo( + IStream* iface, + IStream* pstm, /* [unique][in] */ + ULARGE_INTEGER cb, /* [in] */ + ULARGE_INTEGER* pcbRead, /* [out] */ + ULARGE_INTEGER* pcbWritten) /* [out] */ +{ + StgStreamImpl* This = impl_from_IStream(iface); + HRESULT hr = S_OK; + BYTE tmpBuffer[128]; + ULONG bytesRead, bytesWritten, copySize; + ULARGE_INTEGER totalBytesRead; + ULARGE_INTEGER totalBytesWritten; + + TRACE("%p, %p, %ld, %p, %p.\n", iface, pstm, cb.LowPart, pcbRead, pcbWritten); + + /* + * Sanity check + */ + + if (!This->parentStorage) + { + WARN("storage reverted\n"); + return STG_E_REVERTED; + } + + if ( pstm == 0 ) + return STG_E_INVALIDPOINTER; + + totalBytesRead.QuadPart = 0; + totalBytesWritten.QuadPart = 0; + + while ( cb.QuadPart > 0 ) + { + if ( cb.QuadPart >= sizeof(tmpBuffer) ) + copySize = sizeof(tmpBuffer); + else + copySize = cb.LowPart; + + IStream_Read(iface, tmpBuffer, copySize, &bytesRead); + + totalBytesRead.QuadPart += bytesRead; + + IStream_Write(pstm, tmpBuffer, bytesRead, &bytesWritten); + + totalBytesWritten.QuadPart += bytesWritten; + + /* + * Check that read & write operations were successful + */ + if (bytesRead != bytesWritten) + { + hr = STG_E_MEDIUMFULL; + WARN("medium full\n"); + break; + } + + if (bytesRead!=copySize) + cb.QuadPart = 0; + else + cb.QuadPart -= bytesRead; + } + + if (pcbRead) pcbRead->QuadPart = totalBytesRead.QuadPart; + if (pcbWritten) pcbWritten->QuadPart = totalBytesWritten.QuadPart; + + return hr; +} + +/*** + * This method is part of the IStream interface. + * + * For streams contained in structured storages, this method + * does nothing. This is what the documentation tells us. + * + * See the documentation of IStream for more info. + */ +static HRESULT WINAPI StgStreamImpl_Commit( + IStream* iface, + DWORD grfCommitFlags) /* [in] */ +{ + StgStreamImpl* This = impl_from_IStream(iface); + + if (!This->parentStorage) + { + WARN("storage reverted\n"); + return STG_E_REVERTED; + } + + return StorageBaseImpl_Flush(This->parentStorage); +} + +/*** + * This method is part of the IStream interface. + * + * For streams contained in structured storages, this method + * does nothing. This is what the documentation tells us. + * + * See the documentation of IStream for more info. + */ +static HRESULT WINAPI StgStreamImpl_Revert( + IStream* iface) +{ + return S_OK; +} + +static HRESULT WINAPI StgStreamImpl_LockRegion( + IStream* iface, + ULARGE_INTEGER libOffset, /* [in] */ + ULARGE_INTEGER cb, /* [in] */ + DWORD dwLockType) /* [in] */ +{ + StgStreamImpl* This = impl_from_IStream(iface); + + if (!This->parentStorage) + { + WARN("storage reverted\n"); + return STG_E_REVERTED; + } + + FIXME("not implemented!\n"); + return E_NOTIMPL; +} + +static HRESULT WINAPI StgStreamImpl_UnlockRegion( + IStream* iface, + ULARGE_INTEGER libOffset, /* [in] */ + ULARGE_INTEGER cb, /* [in] */ + DWORD dwLockType) /* [in] */ +{ + StgStreamImpl* This = impl_from_IStream(iface); + + if (!This->parentStorage) + { + WARN("storage reverted\n"); + return STG_E_REVERTED; + } + + FIXME("not implemented!\n"); + return E_NOTIMPL; +} + +/*** + * This method is part of the IStream interface. + * + * This method returns information about the current + * stream. + * + * See the documentation of IStream for more info. + */ +static HRESULT WINAPI StgStreamImpl_Stat( + IStream* iface, + STATSTG* pstatstg, /* [out] */ + DWORD grfStatFlag) /* [in] */ +{ + StgStreamImpl* This = impl_from_IStream(iface); + + DirEntry currentEntry; + HRESULT hr; + + TRACE("%p, %p, %#lx.\n", This, pstatstg, grfStatFlag); + + /* + * if stream has no parent, return STG_E_REVERTED + */ + + if (!This->parentStorage) + { + WARN("storage reverted\n"); + return STG_E_REVERTED; + } + + /* + * Read the information from the directory entry. + */ + hr = StorageBaseImpl_ReadDirEntry(This->parentStorage, + This->dirEntry, + ¤tEntry); + + if (SUCCEEDED(hr)) + { + StorageUtl_CopyDirEntryToSTATSTG(This->parentStorage, + pstatstg, + ¤tEntry, + grfStatFlag); + + pstatstg->grfMode = This->grfMode; + + /* In simple create mode cbSize is the current pos */ + if((This->parentStorage->openFlags & STGM_SIMPLE) && This->parentStorage->create) + pstatstg->cbSize = This->currentPosition; + + return S_OK; + } + + WARN("failed to read entry\n"); + return hr; +} + +/*** + * This method is part of the IStream interface. + * + * This method returns a clone of the interface that allows for + * another seek pointer + * + * See the documentation of IStream for more info. + * + * I am not totally sure what I am doing here but I presume that this + * should be basically as simple as creating a new stream with the same + * parent etc and positioning its seek cursor. + */ +static HRESULT WINAPI StgStreamImpl_Clone( + IStream* iface, + IStream** ppstm) /* [out] */ +{ + StgStreamImpl* This = impl_from_IStream(iface); + StgStreamImpl* new_stream; + LARGE_INTEGER seek_pos; + + TRACE("%p %p\n", This, ppstm); + + /* + * Sanity check + */ + + if (!This->parentStorage) + return STG_E_REVERTED; + + if ( ppstm == 0 ) + return STG_E_INVALIDPOINTER; + + new_stream = StgStreamImpl_Construct (This->parentStorage, This->grfMode, This->dirEntry); + + if (!new_stream) + return STG_E_INSUFFICIENTMEMORY; /* Currently the only reason for new_stream=0 */ + + *ppstm = &new_stream->IStream_iface; + IStream_AddRef(*ppstm); + + seek_pos.QuadPart = This->currentPosition.QuadPart; + + return IStream_Seek(*ppstm, seek_pos, STREAM_SEEK_SET, NULL); +} + +/* + * Virtual function table for the StgStreamImpl class. + */ +static const IStreamVtbl StgStreamVtbl = +{ + StgStreamImpl_QueryInterface, + StgStreamImpl_AddRef, + StgStreamImpl_Release, + StgStreamImpl_Read, + StgStreamImpl_Write, + StgStreamImpl_Seek, + StgStreamImpl_SetSize, + StgStreamImpl_CopyTo, + StgStreamImpl_Commit, + StgStreamImpl_Revert, + StgStreamImpl_LockRegion, + StgStreamImpl_UnlockRegion, + StgStreamImpl_Stat, + StgStreamImpl_Clone +}; + +/****************************************************************************** +** StgStreamImpl implementation +*/ + +/*** + * This is the constructor for the StgStreamImpl class. + * + * Params: + * parentStorage - Pointer to the storage that contains the stream to open + * dirEntry - Index of the directory entry that points to this stream. + */ +StgStreamImpl* StgStreamImpl_Construct( + StorageBaseImpl* parentStorage, + DWORD grfMode, + DirRef dirEntry) +{ + StgStreamImpl* newStream; + + newStream = HeapAlloc(GetProcessHeap(), 0, sizeof(StgStreamImpl)); + + if (newStream) + { + /* + * Set-up the virtual function table and reference count. + */ + newStream->IStream_iface.lpVtbl = &StgStreamVtbl; + newStream->ref = 0; + + newStream->parentStorage = parentStorage; + + /* + * We want to nail-down the reference to the storage in case the + * stream out-lives the storage in the client application. + * + * -- IStorage_AddRef(&newStream->parentStorage->IStorage_iface); + * + * No, don't do this. Some apps call IStorage_Release without + * calling IStream_Release first. If we grab a reference the + * file is not closed, and the app fails when it tries to + * reopen the file (Easy-PC, for example) + */ + + newStream->grfMode = grfMode; + newStream->dirEntry = dirEntry; + + /* + * Start the stream at the beginning. + */ + newStream->currentPosition.HighPart = 0; + newStream->currentPosition.LowPart = 0; + + /* add us to the storage's list of active streams */ + StorageBaseImpl_AddStream(parentStorage, newStream); + } + + return newStream; +} diff --git a/dlls/coml2/storage32.c b/dlls/coml2/storage32.c new file mode 100644 index 00000000000..0d3f9922d78 --- /dev/null +++ b/dlls/coml2/storage32.c @@ -0,0 +1,10649 @@ +/* + * Compound Storage (32 bit version) + * Storage implementation + * + * This file contains the compound file implementation + * of the storage interface. + * + * Copyright 1999 Francis Beaudet + * Copyright 1999 Sylvain St-Germain + * Copyright 1999 Thuy Nguyen + * Copyright 2005 Mike McCormack + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + * + * NOTES + * The compound file implementation of IStorage used for create + * and manage substorages and streams within a storage object + * residing in a compound file object. + */ + +#include <assert.h> +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#define COBJMACROS +#include "windef.h" +#include "winbase.h" +#include "winnls.h" +#include "winuser.h" +#include "wine/debug.h" + +#include "storage32.h" +#include "ole2.h" /* For Write/ReadClassStm */ + +#include "winreg.h" +#include "wine/wingdi16.h" +#include "compobj_private.h" + +WINE_DEFAULT_DEBUG_CHANNEL(storage); + + +/* + * These are signatures to detect the type of Document file. + */ +static const BYTE STORAGE_magic[8] ={0xd0,0xcf,0x11,0xe0,0xa1,0xb1,0x1a,0xe1}; +static const BYTE STORAGE_oldmagic[8] ={0xd0,0xcf,0x11,0xe0,0x0e,0x11,0xfc,0x0d}; + +extern const IPropertySetStorageVtbl IPropertySetStorage_Vtbl; + + +/**************************************************************************** + * StorageInternalImpl definitions. + * + * Definition of the implementation structure for the IStorage interface. + * This one implements the IStorage interface for storage that are + * inside another storage. + */ +typedef struct StorageInternalImpl +{ + struct StorageBaseImpl base; + + /* + * Entry in the parent's stream tracking list + */ + struct list ParentListEntry; + + StorageBaseImpl *parentStorage; +} StorageInternalImpl; + +static const IStorageVtbl StorageInternalImpl_Vtbl; +static StorageInternalImpl* StorageInternalImpl_Construct(StorageBaseImpl*,DWORD,DirRef); + +typedef struct TransactedDirEntry +{ + /* If applicable, a reference to the original DirEntry in the transacted + * parent. If this is a newly-created entry, DIRENTRY_NULL. */ + DirRef transactedParentEntry; + + /* True if this entry is being used. */ + BOOL inuse; + + /* True if data is up to date. */ + BOOL read; + + /* True if this entry has been modified. */ + BOOL dirty; + + /* True if this entry's stream has been modified. */ + BOOL stream_dirty; + + /* True if this entry has been deleted in the transacted storage, but the + * delete has not yet been committed. */ + BOOL deleted; + + /* If this entry's stream has been modified, a reference to where the stream + * is stored in the snapshot file. */ + DirRef stream_entry; + + /* This directory entry's data, including any changes that have been made. */ + DirEntry data; + + /* A reference to the parent of this node. This is only valid while we are + * committing changes. */ + DirRef parent; + + /* A reference to a newly-created entry in the transacted parent. This is + * always equal to transactedParentEntry except when committing changes. */ + DirRef newTransactedParentEntry; +} TransactedDirEntry; + + +/**************************************************************************** + * Transacted storage object. + */ +typedef struct TransactedSnapshotImpl +{ + struct StorageBaseImpl base; + + /* + * Modified streams are temporarily saved to the scratch file. + */ + StorageBaseImpl *scratch; + + /* The directory structure is kept here, so that we can track how these + * entries relate to those in the parent storage. */ + TransactedDirEntry *entries; + ULONG entries_size; + ULONG firstFreeEntry; + + /* + * Changes are committed to the transacted parent. + */ + StorageBaseImpl *transactedParent; + + /* The transaction signature from when we last committed */ + ULONG lastTransactionSig; +} TransactedSnapshotImpl; + +static const IStorageVtbl TransactedSnapshotImpl_Vtbl; +static HRESULT Storage_ConstructTransacted(StorageBaseImpl*,BOOL,StorageBaseImpl**); + +typedef struct TransactedSharedImpl +{ + struct StorageBaseImpl base; + + /* + * Snapshot and uncommitted changes go here. + */ + TransactedSnapshotImpl *scratch; + + /* + * Changes are committed to the transacted parent. + */ + StorageBaseImpl *transactedParent; + + /* The transaction signature from when we last committed */ + ULONG lastTransactionSig; +} TransactedSharedImpl; + + +/**************************************************************************** + * BlockChainStream definitions. + * + * The BlockChainStream class is a utility class that is used to create an + * abstraction of the big block chains in the storage file. + */ + +struct BlockChainRun +{ + /* This represents a range of blocks that happen reside in consecutive sectors. */ + ULONG firstSector; + ULONG firstOffset; + ULONG lastOffset; +}; + +typedef struct BlockChainBlock +{ + ULONG index; + ULONG sector; + BOOL read; + BOOL dirty; + BYTE data[MAX_BIG_BLOCK_SIZE]; +} BlockChainBlock; + +struct BlockChainStream +{ + StorageImpl* parentStorage; + ULONG* headOfStreamPlaceHolder; + DirRef ownerDirEntry; + struct BlockChainRun* indexCache; + ULONG indexCacheLen; + ULONG indexCacheSize; + BlockChainBlock cachedBlocks[2]; + ULONG blockToEvict; + ULONG tailIndex; + ULONG numBlocks; +}; + +/* Returns the number of blocks that comprises this chain. + * This is not the size of the stream as the last block may not be full! + */ +static inline ULONG BlockChainStream_GetCount(BlockChainStream* This) +{ + return This->numBlocks; +} + +static BlockChainStream* BlockChainStream_Construct(StorageImpl*,ULONG*,DirRef); +static void BlockChainStream_Destroy(BlockChainStream*); +static HRESULT BlockChainStream_ReadAt(BlockChainStream*,ULARGE_INTEGER,ULONG,void*,ULONG*); +static HRESULT BlockChainStream_WriteAt(BlockChainStream*,ULARGE_INTEGER,ULONG,const void*,ULONG*); +static HRESULT BlockChainStream_Flush(BlockChainStream*); +static ULARGE_INTEGER BlockChainStream_GetSize(BlockChainStream*); +static BOOL BlockChainStream_SetSize(BlockChainStream*,ULARGE_INTEGER); + + +/**************************************************************************** + * SmallBlockChainStream definitions. + * + * The SmallBlockChainStream class is a utility class that is used to create an + * abstraction of the small block chains in the storage file. + */ + +struct SmallBlockChainStream +{ + StorageImpl* parentStorage; + DirRef ownerDirEntry; + ULONG* headOfStreamPlaceHolder; +}; + +static SmallBlockChainStream* SmallBlockChainStream_Construct(StorageImpl*,ULONG*,DirRef); +static void SmallBlockChainStream_Destroy(SmallBlockChainStream*); +static HRESULT SmallBlockChainStream_ReadAt(SmallBlockChainStream*,ULARGE_INTEGER,ULONG,void*,ULONG*); +static HRESULT SmallBlockChainStream_WriteAt(SmallBlockChainStream*,ULARGE_INTEGER,ULONG,const void*,ULONG*); +static ULARGE_INTEGER SmallBlockChainStream_GetSize(SmallBlockChainStream*); +static BOOL SmallBlockChainStream_SetSize(SmallBlockChainStream*,ULARGE_INTEGER); + + +/************************************************************************ + * STGM Functions + ***********************************************************************/ + +/************************************************************************ + * This method validates an STGM parameter that can contain the values below + * + * The stgm modes in 0x0000ffff are not bit masks, but distinct 4 bit values. + * The stgm values contained in 0xffff0000 are bitmasks. + * + * STGM_DIRECT 0x00000000 + * STGM_TRANSACTED 0x00010000 + * STGM_SIMPLE 0x08000000 + * + * STGM_READ 0x00000000 + * STGM_WRITE 0x00000001 + * STGM_READWRITE 0x00000002 + * + * STGM_SHARE_DENY_NONE 0x00000040 + * STGM_SHARE_DENY_READ 0x00000030 + * STGM_SHARE_DENY_WRITE 0x00000020 + * STGM_SHARE_EXCLUSIVE 0x00000010 + * + * STGM_PRIORITY 0x00040000 + * STGM_DELETEONRELEASE 0x04000000 + * + * STGM_CREATE 0x00001000 + * STGM_CONVERT 0x00020000 + * STGM_FAILIFTHERE 0x00000000 + * + * STGM_NOSCRATCH 0x00100000 + * STGM_NOSNAPSHOT 0x00200000 + */ +static HRESULT validateSTGM(DWORD stgm) +{ + DWORD access = STGM_ACCESS_MODE(stgm); + DWORD share = STGM_SHARE_MODE(stgm); + DWORD create = STGM_CREATE_MODE(stgm); + + if (stgm&~STGM_KNOWN_FLAGS) + { + ERR("unknown flags %#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; +} + +/****************************************************************************** + * StgCreatePropSetStg [OLE32.@] + */ +HRESULT WINAPI StgCreatePropSetStg(IStorage *pstg, DWORD reserved, + IPropertySetStorage **propset) +{ + TRACE("%p, %#lx, %p.\n", pstg, reserved, propset); + if (reserved) + return STG_E_INVALIDPARAMETER; + + return IStorage_QueryInterface(pstg, &IID_IPropertySetStorage, (void**)propset); +} + +/****************************************************************************** + * StgOpenStorageEx [OLE32.@] + */ +HRESULT WINAPI StgOpenStorageEx(const WCHAR* pwcsName, DWORD grfMode, DWORD stgfmt, DWORD grfAttrs, STGOPTIONS* pStgOptions, void* reserved, REFIID riid, void** ppObjectOpen) +{ + TRACE("%s, %#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; +} + +/****************************************************************************** + * StgIsStorageILockBytes [OLE32.@] + * + * Determines if the ILockBytes contains a storage object. + */ +HRESULT WINAPI StgIsStorageILockBytes(ILockBytes *plkbyt) +{ + BYTE sig[sizeof(STORAGE_magic)]; + ULARGE_INTEGER offset; + ULONG read = 0; + + offset.HighPart = 0; + offset.LowPart = 0; + + ILockBytes_ReadAt(plkbyt, offset, sig, sizeof(sig), &read); + + if (read == sizeof(sig) && memcmp(sig, STORAGE_magic, sizeof(sig)) == 0) + return S_OK; + + return S_FALSE; +} + +/****************************************************************************** + * WriteClassStg [OLE32.@] + * + * This method will store the specified CLSID in the specified storage object + */ +HRESULT WINAPI WriteClassStg(IStorage* pStg, REFCLSID rclsid) +{ + if(!pStg) + return E_INVALIDARG; + + if(!rclsid) + return STG_E_INVALIDPOINTER; + + return IStorage_SetClass(pStg, rclsid); +} + +/*********************************************************************** + * ReadClassStg (OLE32.@) + * + * This method reads the CLSID previously written to a storage object with + * the WriteClassStg. + * + * PARAMS + * pstg [I] IStorage pointer + * pclsid [O] Pointer to where the CLSID is written + * + * RETURNS + * Success: S_OK. + * Failure: HRESULT code. + */ +HRESULT WINAPI ReadClassStg(IStorage *pstg,CLSID *pclsid){ + + STATSTG pstatstg; + HRESULT hRes; + + TRACE("(%p, %p)\n", pstg, pclsid); + + if(!pstg || !pclsid) + return E_INVALIDARG; + + /* + * read a STATSTG structure (contains the clsid) from the storage + */ + hRes=IStorage_Stat(pstg,&pstatstg,STATFLAG_NONAME); + + if(SUCCEEDED(hRes)) + *pclsid=pstatstg.clsid; + + return hRes; +} + +/*********************************************************************** + * OleLoadFromStream (OLE32.@) + * + * This function loads an object from stream + */ +HRESULT WINAPI OleLoadFromStream(IStream *pStm,REFIID iidInterface,void** ppvObj) +{ + CLSID clsid; + HRESULT res; + LPPERSISTSTREAM xstm; + + TRACE("(%p,%s,%p)\n",pStm,debugstr_guid(iidInterface),ppvObj); + + res=ReadClassStm(pStm,&clsid); + if (FAILED(res)) + return res; + res=CoCreateInstance(&clsid,NULL,CLSCTX_INPROC_SERVER,iidInterface,ppvObj); + if (FAILED(res)) + return res; + res=IUnknown_QueryInterface((IUnknown*)*ppvObj,&IID_IPersistStream,(LPVOID*)&xstm); + if (FAILED(res)) { + IUnknown_Release((IUnknown*)*ppvObj); + return res; + } + res=IPersistStream_Load(xstm,pStm); + IPersistStream_Release(xstm); + /* FIXME: all refcounts ok at this point? I think they should be: + * pStm : unchanged + * ppvObj : 1 + * xstm : 0 (released) + */ + return res; +} + +/*********************************************************************** + * OleSaveToStream (OLE32.@) + * + * This function saves an object with the IPersistStream interface on it + * to the specified stream. + */ +HRESULT WINAPI OleSaveToStream(IPersistStream *pPStm,IStream *pStm) +{ + + CLSID clsid; + HRESULT res; + + TRACE("(%p,%p)\n",pPStm,pStm); + + res=IPersistStream_GetClassID(pPStm,&clsid); + + if (SUCCEEDED(res)){ + + res=WriteClassStm(pStm,&clsid); + + if (SUCCEEDED(res)) + + res=IPersistStream_Save(pPStm,pStm,TRUE); + } + + TRACE("Finished Save\n"); + return res; +} + +/************************************************************************* + * STORAGE_CreateOleStream [Internal] + * + * Creates the "\001OLE" stream in the IStorage if necessary. + * + * PARAMS + * storage [I] Dest storage to create the stream in + * flags [I] flags to be set for newly created stream + * + * RETURNS + * HRESULT return value + * + * NOTES + * + * This stream is still unknown, MS Word seems to have extra data + * but since the data is stored in the OLESTREAM there should be + * no need to recreate the stream. If the stream is manually + * deleted it will create it with this default data. + * + */ +HRESULT STORAGE_CreateOleStream(IStorage *storage, DWORD flags) +{ + static const DWORD version_magic = 0x02000001; + IStream *stream; + HRESULT hr; + + hr = IStorage_CreateStream(storage, L"\1Ole", STGM_WRITE | STGM_SHARE_EXCLUSIVE, 0, 0, &stream); + if (hr == S_OK) + { + struct empty_1ole_stream { + DWORD version_magic; + DWORD flags; + DWORD update_options; + DWORD reserved; + DWORD mon_stream_size; + }; + struct empty_1ole_stream stream_data; + + stream_data.version_magic = version_magic; + stream_data.flags = flags; + stream_data.update_options = 0; + stream_data.reserved = 0; + stream_data.mon_stream_size = 0; + + hr = IStream_Write(stream, &stream_data, sizeof(stream_data), NULL); + IStream_Release(stream); + } + + return hr; +} + +/* write a string to a stream, preceded by its length */ +static HRESULT STREAM_WriteString( IStream *stm, LPCWSTR string ) +{ + HRESULT r; + LPSTR str; + DWORD len = 0; + + if( string ) + len = WideCharToMultiByte( CP_ACP, 0, string, -1, NULL, 0, NULL, NULL); + r = IStream_Write( stm, &len, sizeof(len), NULL); + if( FAILED( r ) ) + return r; + if(len == 0) + return r; + str = CoTaskMemAlloc( len ); + WideCharToMultiByte( CP_ACP, 0, string, -1, str, len, NULL, NULL); + r = IStream_Write( stm, str, len, NULL); + CoTaskMemFree( str ); + return r; +} + +/* read a string preceded by its length from a stream */ +static HRESULT STREAM_ReadString( IStream *stm, LPWSTR *string ) +{ + HRESULT r; + DWORD len, count = 0; + LPSTR str; + LPWSTR wstr; + + r = IStream_Read( stm, &len, sizeof(len), &count ); + if( FAILED( r ) ) + return r; + if( count != sizeof(len) ) + return E_OUTOFMEMORY; + + TRACE("%ld bytes\n",len); + + str = CoTaskMemAlloc( len ); + if( !str ) + return E_OUTOFMEMORY; + count = 0; + r = IStream_Read( stm, str, len, &count ); + if( FAILED( r ) ) + { + CoTaskMemFree( str ); + return r; + } + if( count != len ) + { + CoTaskMemFree( str ); + return E_OUTOFMEMORY; + } + + TRACE("Read string %s\n",debugstr_an(str,len)); + + len = MultiByteToWideChar( CP_ACP, 0, str, count, NULL, 0 ); + wstr = CoTaskMemAlloc( (len + 1)*sizeof (WCHAR) ); + if( wstr ) + { + MultiByteToWideChar( CP_ACP, 0, str, count, wstr, len ); + wstr[len] = 0; + } + CoTaskMemFree( str ); + + *string = wstr; + + return r; +} + + +static HRESULT STORAGE_WriteCompObj( LPSTORAGE pstg, CLSID *clsid, + LPCWSTR lpszUserType, LPCWSTR szClipName, LPCWSTR szProgIDName ) +{ + IStream *pstm; + HRESULT r = S_OK; + + static const BYTE unknown1[12] = + { 0x01, 0x00, 0xFE, 0xFF, 0x03, 0x0A, 0x00, 0x00, + 0xFF, 0xFF, 0xFF, 0xFF}; + static const BYTE unknown2[16] = + { 0xF4, 0x39, 0xB2, 0x71, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; + + TRACE("%p %s %s %s %s\n", pstg, debugstr_guid(clsid), + debugstr_w(lpszUserType), debugstr_w(szClipName), + debugstr_w(szProgIDName)); + + /* Create a CompObj stream */ + r = IStorage_CreateStream(pstg, L"\1CompObj", + STGM_CREATE | STGM_WRITE | STGM_SHARE_EXCLUSIVE, 0, 0, &pstm ); + if( FAILED (r) ) + return r; + + /* Write CompObj Structure to stream */ + r = IStream_Write(pstm, unknown1, sizeof(unknown1), NULL); + + if( SUCCEEDED( r ) ) + r = WriteClassStm( pstm, clsid ); + + if( SUCCEEDED( r ) ) + r = STREAM_WriteString( pstm, lpszUserType ); + if( SUCCEEDED( r ) ) + r = STREAM_WriteString( pstm, szClipName ); + if( SUCCEEDED( r ) ) + r = STREAM_WriteString( pstm, szProgIDName ); + if( SUCCEEDED( r ) ) + r = IStream_Write(pstm, unknown2, sizeof(unknown2), NULL); + + IStream_Release( pstm ); + + return r; +} + +/*********************************************************************** + * WriteFmtUserTypeStg (OLE32.@) + */ +HRESULT WINAPI WriteFmtUserTypeStg( + LPSTORAGE pstg, CLIPFORMAT cf, LPOLESTR lpszUserType) +{ + STATSTG stat; + HRESULT r; + WCHAR szwClipName[0x40]; + CLSID clsid; + LPWSTR wstrProgID = NULL; + DWORD n; + + TRACE("(%p,%x,%s)\n",pstg,cf,debugstr_w(lpszUserType)); + + /* get the clipboard format name */ + if( cf ) + { + n = GetClipboardFormatNameW(cf, szwClipName, ARRAY_SIZE(szwClipName)); + szwClipName[n]=0; + } + + TRACE("Clipboard name is %s\n", debugstr_w(szwClipName)); + + r = IStorage_Stat(pstg, &stat, STATFLAG_NONAME); + if(SUCCEEDED(r)) + clsid = stat.clsid; + else + clsid = CLSID_NULL; + + ProgIDFromCLSID(&clsid, &wstrProgID); + + TRACE("progid is %s\n",debugstr_w(wstrProgID)); + + r = STORAGE_WriteCompObj( pstg, &clsid, lpszUserType, + cf ? szwClipName : NULL, wstrProgID ); + + CoTaskMemFree(wstrProgID); + + return r; +} + + +/****************************************************************************** + * ReadFmtUserTypeStg [OLE32.@] + */ +HRESULT WINAPI ReadFmtUserTypeStg (LPSTORAGE pstg, CLIPFORMAT* pcf, LPOLESTR* lplpszUserType) +{ + HRESULT r; + IStream *stm = 0; + unsigned char unknown1[12]; + unsigned char unknown2[16]; + DWORD count; + LPWSTR szProgIDName = NULL, szCLSIDName = NULL, szOleTypeName = NULL; + CLSID clsid; + + TRACE("(%p,%p,%p)\n", pstg, pcf, lplpszUserType); + + r = IStorage_OpenStream( pstg, L"\1CompObj", NULL, STGM_READ | STGM_SHARE_EXCLUSIVE, 0, &stm ); + if( FAILED ( r ) ) + { + WARN("Failed to open stream r = %#lx\n", r); + return r; + } + + /* read the various parts of the structure */ + r = IStream_Read( stm, unknown1, sizeof(unknown1), &count ); + if( FAILED( r ) || ( count != sizeof(unknown1) ) ) + goto end; + r = ReadClassStm( stm, &clsid ); + if( FAILED( r ) ) + goto end; + + r = STREAM_ReadString( stm, &szCLSIDName ); + if( FAILED( r ) ) + goto end; + + r = STREAM_ReadString( stm, &szOleTypeName ); + if( FAILED( r ) ) + goto end; + + r = STREAM_ReadString( stm, &szProgIDName ); + if( FAILED( r ) ) + goto end; + + r = IStream_Read( stm, unknown2, sizeof(unknown2), &count ); + if( FAILED( r ) || ( count != sizeof(unknown2) ) ) + goto end; + + /* ok, success... now we just need to store what we found */ + if( pcf ) + *pcf = RegisterClipboardFormatW( szOleTypeName ); + + if( lplpszUserType ) + { + *lplpszUserType = szCLSIDName; + szCLSIDName = NULL; + } + +end: + CoTaskMemFree( szCLSIDName ); + CoTaskMemFree( szOleTypeName ); + CoTaskMemFree( szProgIDName ); + IStream_Release( stm ); + + return r; +} + +/****************************************************************************** + * StgIsStorageFile [OLE32.@] + * Verify if the file contains a storage object + * + * PARAMS + * fn [ I] Filename + * + * RETURNS + * S_OK if file has magic bytes as a storage object + * S_FALSE if file is not storage + */ +HRESULT WINAPI +StgIsStorageFile(LPCOLESTR fn) +{ + HANDLE hf; + BYTE magic[8]; + DWORD bytes_read; + + TRACE("%s\n", debugstr_w(fn)); + hf = CreateFileW(fn, GENERIC_READ, + FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE, + NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0); + + if (hf == INVALID_HANDLE_VALUE) + return STG_E_FILENOTFOUND; + + if (!ReadFile(hf, magic, 8, &bytes_read, NULL)) + { + WARN(" unable to read file\n"); + CloseHandle(hf); + return S_FALSE; + } + + CloseHandle(hf); + + if (bytes_read != 8) { + TRACE(" too short\n"); + return S_FALSE; + } + + if (!memcmp(magic,STORAGE_magic,8)) { + TRACE(" -> YES\n"); + return S_OK; + } + + TRACE(" -> Invalid header.\n"); + return S_FALSE; +} + +/*********************************************************************** + * WriteClassStm (OLE32.@) + * + * Writes a CLSID to a stream. + * + * PARAMS + * pStm [I] Stream to write to. + * rclsid [I] CLSID to write. + * + * RETURNS + * Success: S_OK. + * Failure: HRESULT code. + */ +HRESULT WINAPI WriteClassStm(IStream *pStm,REFCLSID rclsid) +{ + TRACE("(%p,%p)\n",pStm,rclsid); + + if (!pStm || !rclsid) + return E_INVALIDARG; + + return IStream_Write(pStm,rclsid,sizeof(CLSID),NULL); +} + +/*********************************************************************** + * ReadClassStm (OLE32.@) + * + * Reads a CLSID from a stream. + * + * PARAMS + * pStm [I] Stream to read from. + * rclsid [O] CLSID to read. + * + * RETURNS + * Success: S_OK. + * Failure: HRESULT code. + */ +HRESULT WINAPI ReadClassStm(IStream *pStm,CLSID *pclsid) +{ + ULONG nbByte; + HRESULT res; + + TRACE("(%p,%p)\n",pStm,pclsid); + + if (!pStm || !pclsid) + return E_INVALIDARG; + + /* clear the output args */ + *pclsid = CLSID_NULL; + + res = IStream_Read(pStm, pclsid, sizeof(CLSID), &nbByte); + + if (FAILED(res)) + return res; + + if (nbByte != sizeof(CLSID)) + return STG_E_READFAULT; + else + return S_OK; +} + + +/************************************************************************ + * OleConvert Functions + ***********************************************************************/ + +#define OLESTREAM_ID 0x501 +#define OLESTREAM_MAX_STR_LEN 255 + +/* OLESTREAM memory structure to use for Get and Put Routines */ +typedef struct +{ + DWORD dwOleID; + DWORD dwTypeID; + DWORD dwOleTypeNameLength; + CHAR strOleTypeName[OLESTREAM_MAX_STR_LEN]; + CHAR *pstrOleObjFileName; + DWORD dwOleObjFileNameLength; + DWORD dwMetaFileWidth; + DWORD dwMetaFileHeight; + CHAR strUnknown[8]; /* don't know what this 8 byte information in OLE stream is. */ + DWORD dwDataLength; + BYTE *pData; +} OLECONVERT_OLESTREAM_DATA; + +/* CompObj Stream structure */ +typedef struct +{ + BYTE byUnknown1[12]; + CLSID clsid; + DWORD dwCLSIDNameLength; + CHAR strCLSIDName[OLESTREAM_MAX_STR_LEN]; + DWORD dwOleTypeNameLength; + CHAR strOleTypeName[OLESTREAM_MAX_STR_LEN]; + DWORD dwProgIDNameLength; + CHAR strProgIDName[OLESTREAM_MAX_STR_LEN]; + BYTE byUnknown2[16]; +} OLECONVERT_ISTORAGE_COMPOBJ; + +/* Ole Presentation Stream structure */ +typedef struct +{ + BYTE byUnknown1[28]; + DWORD dwExtentX; + DWORD dwExtentY; + DWORD dwSize; + BYTE *pData; +} OLECONVERT_ISTORAGE_OLEPRES; + + +/************************************************************************* + * OLECONVERT_LoadOLE10 [Internal] + * + * Loads the OLE10 STREAM to memory + * + * PARAMS + * pOleStream [I] The OLESTREAM + * pData [I] Data Structure for the OLESTREAM Data + * + * RETURNS + * Success: S_OK + * Failure: CONVERT10_E_OLESTREAM_GET for invalid Get + * CONVERT10_E_OLESTREAM_FMT if the OLEID is invalid + * + * NOTES + * This function is used by OleConvertOLESTREAMToIStorage only. + * + * Memory allocated for pData must be freed by the caller + */ +static HRESULT OLECONVERT_LoadOLE10(LPOLESTREAM pOleStream, OLECONVERT_OLESTREAM_DATA *pData, BOOL bStream1) +{ + DWORD dwSize; + HRESULT hRes = S_OK; + int nTryCnt=0; + int max_try = 6; + + pData->pData = NULL; + pData->pstrOleObjFileName = NULL; + + for( nTryCnt=0;nTryCnt < max_try; nTryCnt++) + { + /* Get the OleID */ + dwSize = pOleStream->lpstbl->Get(pOleStream, (void *)&(pData->dwOleID), sizeof(pData->dwOleID)); + if(dwSize != sizeof(pData->dwOleID)) + { + hRes = CONVERT10_E_OLESTREAM_GET; + } + else if(pData->dwOleID != OLESTREAM_ID) + { + hRes = CONVERT10_E_OLESTREAM_FMT; + } + else + { + hRes = S_OK; + break; + } + } + + if(hRes == S_OK) + { + /* Get the TypeID... more info needed for this field */ + dwSize = pOleStream->lpstbl->Get(pOleStream, (void *)&(pData->dwTypeID), sizeof(pData->dwTypeID)); + if(dwSize != sizeof(pData->dwTypeID)) + { + hRes = CONVERT10_E_OLESTREAM_GET; + } + } + if(hRes == S_OK) + { + if(pData->dwTypeID != 0) + { + /* Get the length of the OleTypeName */ + dwSize = pOleStream->lpstbl->Get(pOleStream, (void *) &(pData->dwOleTypeNameLength), sizeof(pData->dwOleTypeNameLength)); + if(dwSize != sizeof(pData->dwOleTypeNameLength)) + { + hRes = CONVERT10_E_OLESTREAM_GET; + } + + if(hRes == S_OK) + { + if(pData->dwOleTypeNameLength > 0) + { + /* Get the OleTypeName */ + dwSize = pOleStream->lpstbl->Get(pOleStream, pData->strOleTypeName, pData->dwOleTypeNameLength); + if(dwSize != pData->dwOleTypeNameLength) + { + hRes = CONVERT10_E_OLESTREAM_GET; + } + } + } + if(bStream1) + { + dwSize = pOleStream->lpstbl->Get(pOleStream, (void *)&(pData->dwOleObjFileNameLength), sizeof(pData->dwOleObjFileNameLength)); + if(dwSize != sizeof(pData->dwOleObjFileNameLength)) + { + hRes = CONVERT10_E_OLESTREAM_GET; + } + if(hRes == S_OK) + { + if(pData->dwOleObjFileNameLength < 1) /* there is no file name exist */ + pData->dwOleObjFileNameLength = sizeof(pData->dwOleObjFileNameLength); + pData->pstrOleObjFileName = HeapAlloc(GetProcessHeap(), 0, pData->dwOleObjFileNameLength); + if(pData->pstrOleObjFileName) + { + dwSize = pOleStream->lpstbl->Get(pOleStream, pData->pstrOleObjFileName, pData->dwOleObjFileNameLength); + if(dwSize != pData->dwOleObjFileNameLength) + { + hRes = CONVERT10_E_OLESTREAM_GET; + } + } + else + hRes = CONVERT10_E_OLESTREAM_GET; + } + } + else + { + /* Get the Width of the Metafile */ + dwSize = pOleStream->lpstbl->Get(pOleStream, (void *)&(pData->dwMetaFileWidth), sizeof(pData->dwMetaFileWidth)); + if(dwSize != sizeof(pData->dwMetaFileWidth)) + { + hRes = CONVERT10_E_OLESTREAM_GET; + } + if(hRes == S_OK) + { + /* Get the Height of the Metafile */ + dwSize = pOleStream->lpstbl->Get(pOleStream, (void *)&(pData->dwMetaFileHeight), sizeof(pData->dwMetaFileHeight)); + if(dwSize != sizeof(pData->dwMetaFileHeight)) + { + hRes = CONVERT10_E_OLESTREAM_GET; + } + } + } + if(hRes == S_OK) + { + /* Get the Length of the Data */ + dwSize = pOleStream->lpstbl->Get(pOleStream, (void *)&(pData->dwDataLength), sizeof(pData->dwDataLength)); + if(dwSize != sizeof(pData->dwDataLength)) + { + hRes = CONVERT10_E_OLESTREAM_GET; + } + } + + if(hRes == S_OK) /* I don't know what this 8 byte information is. We have to figure out */ + { + if(!bStream1) /* if it is a second OLE stream data */ + { + pData->dwDataLength -= 8; + dwSize = pOleStream->lpstbl->Get(pOleStream, pData->strUnknown, sizeof(pData->strUnknown)); + if(dwSize != sizeof(pData->strUnknown)) + { + hRes = CONVERT10_E_OLESTREAM_GET; + } + } + } + if(hRes == S_OK) + { + if(pData->dwDataLength > 0) + { + pData->pData = HeapAlloc(GetProcessHeap(),0,pData->dwDataLength); + + /* Get Data (ex. IStorage, Metafile, or BMP) */ + if(pData->pData) + { + dwSize = pOleStream->lpstbl->Get(pOleStream, (void *)pData->pData, pData->dwDataLength); + if(dwSize != pData->dwDataLength) + { + hRes = CONVERT10_E_OLESTREAM_GET; + } + } + else + { + hRes = CONVERT10_E_OLESTREAM_GET; + } + } + } + } + } + return hRes; +} + +/************************************************************************* + * OLECONVERT_SaveOLE10 [Internal] + * + * Saves the OLE10 STREAM From memory + * + * PARAMS + * pData [I] Data Structure for the OLESTREAM Data + * pOleStream [I] The OLESTREAM to save + * + * RETURNS + * Success: S_OK + * Failure: CONVERT10_E_OLESTREAM_PUT for invalid Put + * + * NOTES + * This function is used by OleConvertIStorageToOLESTREAM only. + * + */ +static HRESULT OLECONVERT_SaveOLE10(OLECONVERT_OLESTREAM_DATA *pData, LPOLESTREAM pOleStream) +{ + DWORD dwSize; + HRESULT hRes = S_OK; + + + /* Set the OleID */ + dwSize = pOleStream->lpstbl->Put(pOleStream, (void *)&(pData->dwOleID), sizeof(pData->dwOleID)); + if(dwSize != sizeof(pData->dwOleID)) + { + hRes = CONVERT10_E_OLESTREAM_PUT; + } + + if(hRes == S_OK) + { + /* Set the TypeID */ + dwSize = pOleStream->lpstbl->Put(pOleStream, (void *)&(pData->dwTypeID), sizeof(pData->dwTypeID)); + if(dwSize != sizeof(pData->dwTypeID)) + { + hRes = CONVERT10_E_OLESTREAM_PUT; + } + } + + if(pData->dwOleID == OLESTREAM_ID && pData->dwTypeID != 0 && hRes == S_OK) + { + /* Set the Length of the OleTypeName */ + dwSize = pOleStream->lpstbl->Put(pOleStream, (void *)&(pData->dwOleTypeNameLength), sizeof(pData->dwOleTypeNameLength)); + if(dwSize != sizeof(pData->dwOleTypeNameLength)) + { + hRes = CONVERT10_E_OLESTREAM_PUT; + } + + if(hRes == S_OK) + { + if(pData->dwOleTypeNameLength > 0) + { + /* Set the OleTypeName */ + dwSize = pOleStream->lpstbl->Put(pOleStream, pData->strOleTypeName, pData->dwOleTypeNameLength); + if(dwSize != pData->dwOleTypeNameLength) + { + hRes = CONVERT10_E_OLESTREAM_PUT; + } + } + } + + if(hRes == S_OK) + { + /* Set the width of the Metafile */ + dwSize = pOleStream->lpstbl->Put(pOleStream, (void *)&(pData->dwMetaFileWidth), sizeof(pData->dwMetaFileWidth)); + if(dwSize != sizeof(pData->dwMetaFileWidth)) + { + hRes = CONVERT10_E_OLESTREAM_PUT; + } + } + + if(hRes == S_OK) + { + /* Set the height of the Metafile */ + dwSize = pOleStream->lpstbl->Put(pOleStream, (void *)&(pData->dwMetaFileHeight), sizeof(pData->dwMetaFileHeight)); + if(dwSize != sizeof(pData->dwMetaFileHeight)) + { + hRes = CONVERT10_E_OLESTREAM_PUT; + } + } + + if(hRes == S_OK) + { + /* Set the length of the Data */ + dwSize = pOleStream->lpstbl->Put(pOleStream, (void *)&(pData->dwDataLength), sizeof(pData->dwDataLength)); + if(dwSize != sizeof(pData->dwDataLength)) + { + hRes = CONVERT10_E_OLESTREAM_PUT; + } + } + + if(hRes == S_OK) + { + if(pData->dwDataLength > 0) + { + /* Set the Data (eg. IStorage, Metafile, Bitmap) */ + dwSize = pOleStream->lpstbl->Put(pOleStream, (void *) pData->pData, pData->dwDataLength); + if(dwSize != pData->dwDataLength) + { + hRes = CONVERT10_E_OLESTREAM_PUT; + } + } + } + } + return hRes; +} + +/************************************************************************* + * OLECONVERT_GetOLE20FromOLE10[Internal] + * + * This function copies OLE10 Data (the IStorage in the OLESTREAM) to disk, + * opens it, and copies the content to the dest IStorage for + * OleConvertOLESTREAMToIStorage + * + * + * PARAMS + * pDestStorage [I] The IStorage to copy the data to + * pBuffer [I] Buffer that contains the IStorage from the OLESTREAM + * nBufferLength [I] The size of the buffer + * + * RETURNS + * Nothing + * + * NOTES + * + * + */ +static void OLECONVERT_GetOLE20FromOLE10(LPSTORAGE pDestStorage, const BYTE *pBuffer, DWORD nBufferLength) +{ + HRESULT hRes; + HANDLE hFile; + IStorage *pTempStorage; + DWORD dwNumOfBytesWritten; + WCHAR wstrTempDir[MAX_PATH], wstrTempFile[MAX_PATH]; + + /* Create a temp File */ + GetTempPathW(MAX_PATH, wstrTempDir); + GetTempFileNameW(wstrTempDir, L"sis", 0, wstrTempFile); + hFile = CreateFileW(wstrTempFile, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0); + + if(hFile != INVALID_HANDLE_VALUE) + { + /* Write IStorage Data to File */ + WriteFile(hFile, pBuffer, nBufferLength, &dwNumOfBytesWritten, NULL); + CloseHandle(hFile); + + /* Open and copy temp storage to the Dest Storage */ + hRes = StgOpenStorage(wstrTempFile, NULL, STGM_READ, NULL, 0, &pTempStorage); + if(hRes == S_OK) + { + hRes = IStorage_CopyTo(pTempStorage, 0, NULL, NULL, pDestStorage); + IStorage_Release(pTempStorage); + } + DeleteFileW(wstrTempFile); + } +} + + +/************************************************************************* + * OLECONVERT_WriteOLE20ToBuffer [Internal] + * + * Saves the OLE10 STREAM From memory + * + * PARAMS + * pStorage [I] The Src IStorage to copy + * pData [I] The Dest Memory to write to. + * + * RETURNS + * The size in bytes allocated for pData + * + * NOTES + * Memory allocated for pData must be freed by the caller + * + * Used by OleConvertIStorageToOLESTREAM only. + * + */ +static DWORD OLECONVERT_WriteOLE20ToBuffer(LPSTORAGE pStorage, BYTE **pData) +{ + HANDLE hFile; + HRESULT hRes; + DWORD nDataLength = 0; + IStorage *pTempStorage; + WCHAR wstrTempDir[MAX_PATH], wstrTempFile[MAX_PATH]; + + *pData = NULL; + + /* Create temp Storage */ + GetTempPathW(MAX_PATH, wstrTempDir); + GetTempFileNameW(wstrTempDir, L"sis", 0, wstrTempFile); + hRes = StgCreateDocfile(wstrTempFile, STGM_CREATE | STGM_READWRITE | STGM_SHARE_EXCLUSIVE, 0, &pTempStorage); + + if(hRes == S_OK) + { + /* Copy Src Storage to the Temp Storage */ + IStorage_CopyTo(pStorage, 0, NULL, NULL, pTempStorage); + IStorage_Release(pTempStorage); + + /* Open Temp Storage as a file and copy to memory */ + hFile = CreateFileW(wstrTempFile, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0); + if(hFile != INVALID_HANDLE_VALUE) + { + nDataLength = GetFileSize(hFile, NULL); + *pData = HeapAlloc(GetProcessHeap(),0,nDataLength); + ReadFile(hFile, *pData, nDataLength, &nDataLength, 0); + CloseHandle(hFile); + } + DeleteFileW(wstrTempFile); + } + return nDataLength; +} + +/************************************************************************* + * OLECONVERT_CreateCompObjStream [Internal] + * + * Creates a "\001CompObj" is the destination IStorage if necessary. + * + * PARAMS + * pStorage [I] The dest IStorage to create the CompObj Stream + * if necessary. + * strOleTypeName [I] The ProgID + * + * RETURNS + * Success: S_OK + * Failure: REGDB_E_CLASSNOTREG if cannot reconstruct the stream + * + * NOTES + * This function is used by OleConvertOLESTREAMToIStorage only. + * + * The stream data is stored in the OLESTREAM and there should be + * no need to recreate the stream. If the stream is manually + * deleted it will attempt to create it by querying the registry. + * + * + */ +HRESULT OLECONVERT_CreateCompObjStream(LPSTORAGE pStorage, LPCSTR strOleTypeName) +{ + IStream *pStream; + HRESULT hStorageRes, hRes = S_OK; + OLECONVERT_ISTORAGE_COMPOBJ IStorageCompObj; + WCHAR bufferW[OLESTREAM_MAX_STR_LEN]; + + static const BYTE pCompObjUnknown1[] = {0x01, 0x00, 0xFE, 0xFF, 0x03, 0x0A, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF}; + static const BYTE pCompObjUnknown2[] = {0xF4, 0x39, 0xB2, 0x71}; + + /* Initialize the CompObj structure */ + memset(&IStorageCompObj, 0, sizeof(IStorageCompObj)); + memcpy(IStorageCompObj.byUnknown1, pCompObjUnknown1, sizeof(pCompObjUnknown1)); + memcpy(IStorageCompObj.byUnknown2, pCompObjUnknown2, sizeof(pCompObjUnknown2)); + + + /* Create a CompObj stream if it doesn't exist */ + hStorageRes = IStorage_CreateStream(pStorage, L"\1CompObj", + STGM_WRITE | STGM_SHARE_EXCLUSIVE, 0, 0, &pStream ); + if(hStorageRes == S_OK) + { + /* copy the OleTypeName to the compobj struct */ + IStorageCompObj.dwOleTypeNameLength = strlen(strOleTypeName)+1; + strcpy(IStorageCompObj.strOleTypeName, strOleTypeName); + + /* copy the OleTypeName to the compobj struct */ + /* Note: in the test made, these were Identical */ + IStorageCompObj.dwProgIDNameLength = strlen(strOleTypeName)+1; + strcpy(IStorageCompObj.strProgIDName, strOleTypeName); + + /* Get the CLSID */ + MultiByteToWideChar( CP_ACP, 0, IStorageCompObj.strProgIDName, -1, + bufferW, OLESTREAM_MAX_STR_LEN ); + hRes = CLSIDFromProgID(bufferW, &(IStorageCompObj.clsid)); + + if(hRes == S_OK) + { + HKEY hKey; + LONG hErr; + /* Get the CLSID Default Name from the Registry */ + hErr = open_classes_key(HKEY_CLASSES_ROOT, bufferW, MAXIMUM_ALLOWED, &hKey); + if(hErr == ERROR_SUCCESS) + { + char strTemp[OLESTREAM_MAX_STR_LEN]; + IStorageCompObj.dwCLSIDNameLength = OLESTREAM_MAX_STR_LEN; + hErr = RegQueryValueA(hKey, NULL, strTemp, (LONG*) &(IStorageCompObj.dwCLSIDNameLength)); + if(hErr == ERROR_SUCCESS) + { + strcpy(IStorageCompObj.strCLSIDName, strTemp); + } + RegCloseKey(hKey); + } + } + + /* Write CompObj Structure to stream */ + hRes = IStream_Write(pStream, IStorageCompObj.byUnknown1, sizeof(IStorageCompObj.byUnknown1), NULL); + + WriteClassStm(pStream,&(IStorageCompObj.clsid)); + + hRes = IStream_Write(pStream, &(IStorageCompObj.dwCLSIDNameLength), sizeof(IStorageCompObj.dwCLSIDNameLength), NULL); + if(IStorageCompObj.dwCLSIDNameLength > 0) + { + hRes = IStream_Write(pStream, IStorageCompObj.strCLSIDName, IStorageCompObj.dwCLSIDNameLength, NULL); + } + hRes = IStream_Write(pStream, &(IStorageCompObj.dwOleTypeNameLength) , sizeof(IStorageCompObj.dwOleTypeNameLength), NULL); + if(IStorageCompObj.dwOleTypeNameLength > 0) + { + hRes = IStream_Write(pStream, IStorageCompObj.strOleTypeName , IStorageCompObj.dwOleTypeNameLength, NULL); + } + hRes = IStream_Write(pStream, &(IStorageCompObj.dwProgIDNameLength) , sizeof(IStorageCompObj.dwProgIDNameLength), NULL); + if(IStorageCompObj.dwProgIDNameLength > 0) + { + hRes = IStream_Write(pStream, IStorageCompObj.strProgIDName , IStorageCompObj.dwProgIDNameLength, NULL); + } + hRes = IStream_Write(pStream, IStorageCompObj.byUnknown2 , sizeof(IStorageCompObj.byUnknown2), NULL); + IStream_Release(pStream); + } + return hRes; +} + + +/************************************************************************* + * OLECONVERT_CreateOlePresStream[Internal] + * + * Creates the "\002OlePres000" Stream with the Metafile data + * + * PARAMS + * pStorage [I] The dest IStorage to create \002OLEPres000 stream in. + * dwExtentX [I] Width of the Metafile + * dwExtentY [I] Height of the Metafile + * pData [I] Metafile data + * dwDataLength [I] Size of the Metafile data + * + * RETURNS + * Success: S_OK + * Failure: CONVERT10_E_OLESTREAM_PUT for invalid Put + * + * NOTES + * This function is used by OleConvertOLESTREAMToIStorage only. + * + */ +static void OLECONVERT_CreateOlePresStream(LPSTORAGE pStorage, DWORD dwExtentX, DWORD dwExtentY , BYTE *pData, DWORD dwDataLength) +{ + HRESULT hRes; + IStream *pStream; + static const BYTE pOlePresStreamHeader[] = + { + 0xFF, 0xFF, 0xFF, 0xFF, 0x03, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00 + }; + + static const BYTE pOlePresStreamHeaderEmpty[] = + { + 0x00, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00 + }; + + /* Create the OlePres000 Stream */ + hRes = IStorage_CreateStream(pStorage, L"\2OlePres000", + STGM_CREATE | STGM_WRITE | STGM_SHARE_EXCLUSIVE, 0, 0, &pStream ); + + if(hRes == S_OK) + { + DWORD nHeaderSize; + OLECONVERT_ISTORAGE_OLEPRES OlePres; + + memset(&OlePres, 0, sizeof(OlePres)); + /* Do we have any metafile data to save */ + if(dwDataLength > 0) + { + memcpy(OlePres.byUnknown1, pOlePresStreamHeader, sizeof(pOlePresStreamHeader)); + nHeaderSize = sizeof(pOlePresStreamHeader); + } + else + { + memcpy(OlePres.byUnknown1, pOlePresStreamHeaderEmpty, sizeof(pOlePresStreamHeaderEmpty)); + nHeaderSize = sizeof(pOlePresStreamHeaderEmpty); + } + /* Set width and height of the metafile */ + OlePres.dwExtentX = dwExtentX; + OlePres.dwExtentY = -dwExtentY; + + /* Set Data and Length */ + if(dwDataLength > sizeof(METAFILEPICT16)) + { + OlePres.dwSize = dwDataLength - sizeof(METAFILEPICT16); + OlePres.pData = &(pData[8]); + } + /* Save OlePres000 Data to Stream */ + hRes = IStream_Write(pStream, OlePres.byUnknown1, nHeaderSize, NULL); + hRes = IStream_Write(pStream, &(OlePres.dwExtentX), sizeof(OlePres.dwExtentX), NULL); + hRes = IStream_Write(pStream, &(OlePres.dwExtentY), sizeof(OlePres.dwExtentY), NULL); + hRes = IStream_Write(pStream, &(OlePres.dwSize), sizeof(OlePres.dwSize), NULL); + if(OlePres.dwSize > 0) + { + hRes = IStream_Write(pStream, OlePres.pData, OlePres.dwSize, NULL); + } + IStream_Release(pStream); + } +} + +/************************************************************************* + * OLECONVERT_CreateOle10NativeStream [Internal] + * + * Creates the "\001Ole10Native" Stream (should contain a BMP) + * + * PARAMS + * pStorage [I] Dest storage to create the stream in + * pData [I] Ole10 Native Data (ex. bmp) + * dwDataLength [I] Size of the Ole10 Native Data + * + * RETURNS + * Nothing + * + * NOTES + * This function is used by OleConvertOLESTREAMToIStorage only. + * + * Might need to verify the data and return appropriate error message + * + */ +static void OLECONVERT_CreateOle10NativeStream(LPSTORAGE pStorage, const BYTE *pData, DWORD dwDataLength) +{ + HRESULT hRes; + IStream *pStream; + + /* Create the Ole10Native Stream */ + hRes = IStorage_CreateStream(pStorage, L"\1Ole10Native", + STGM_CREATE | STGM_WRITE | STGM_SHARE_EXCLUSIVE, 0, 0, &pStream ); + + if(hRes == S_OK) + { + /* Write info to stream */ + hRes = IStream_Write(pStream, &dwDataLength, sizeof(dwDataLength), NULL); + hRes = IStream_Write(pStream, pData, dwDataLength, NULL); + IStream_Release(pStream); + } + +} + +/************************************************************************* + * OLECONVERT_GetOLE10ProgID [Internal] + * + * Finds the ProgID (or OleTypeID) from the IStorage + * + * PARAMS + * pStorage [I] The Src IStorage to get the ProgID + * strProgID [I] the ProgID string to get + * dwSize [I] the size of the string + * + * RETURNS + * Success: S_OK + * Failure: REGDB_E_CLASSNOTREG if cannot reconstruct the stream + * + * NOTES + * This function is used by OleConvertIStorageToOLESTREAM only. + * + * + */ +static HRESULT OLECONVERT_GetOLE10ProgID(LPSTORAGE pStorage, char *strProgID, DWORD *dwSize) +{ + HRESULT hRes; + IStream *pStream; + LARGE_INTEGER iSeekPos; + OLECONVERT_ISTORAGE_COMPOBJ CompObj; + + /* Open the CompObj Stream */ + hRes = IStorage_OpenStream(pStorage, L"\1CompObj", NULL, + STGM_READ | STGM_SHARE_EXCLUSIVE, 0, &pStream ); + if(hRes == S_OK) + { + + /*Get the OleType from the CompObj Stream */ + iSeekPos.LowPart = sizeof(CompObj.byUnknown1) + sizeof(CompObj.clsid); + iSeekPos.HighPart = 0; + + IStream_Seek(pStream, iSeekPos, STREAM_SEEK_SET, NULL); + IStream_Read(pStream, &CompObj.dwCLSIDNameLength, sizeof(CompObj.dwCLSIDNameLength), NULL); + iSeekPos.LowPart = CompObj.dwCLSIDNameLength; + IStream_Seek(pStream, iSeekPos, STREAM_SEEK_CUR , NULL); + IStream_Read(pStream, &CompObj.dwOleTypeNameLength, sizeof(CompObj.dwOleTypeNameLength), NULL); + iSeekPos.LowPart = CompObj.dwOleTypeNameLength; + IStream_Seek(pStream, iSeekPos, STREAM_SEEK_CUR , NULL); + + IStream_Read(pStream, dwSize, sizeof(*dwSize), NULL); + if(*dwSize > 0) + { + IStream_Read(pStream, strProgID, *dwSize, NULL); + } + IStream_Release(pStream); + } + else + { + STATSTG stat; + LPOLESTR wstrProgID; + + /* Get the OleType from the registry */ + REFCLSID clsid = &(stat.clsid); + IStorage_Stat(pStorage, &stat, STATFLAG_NONAME); + hRes = ProgIDFromCLSID(clsid, &wstrProgID); + if(hRes == S_OK) + { + *dwSize = WideCharToMultiByte(CP_ACP, 0, wstrProgID, -1, strProgID, *dwSize, NULL, FALSE); + CoTaskMemFree(wstrProgID); + } + + } + return hRes; +} + +/************************************************************************* + * OLECONVERT_GetOle10PresData [Internal] + * + * Converts IStorage "/001Ole10Native" stream to a OLE10 Stream + * + * PARAMS + * pStorage [I] Src IStroage + * pOleStream [I] Dest OleStream Mem Struct + * + * RETURNS + * Nothing + * + * NOTES + * This function is used by OleConvertIStorageToOLESTREAM only. + * + * Memory allocated for pData must be freed by the caller + * + * + */ +static void OLECONVERT_GetOle10PresData(LPSTORAGE pStorage, OLECONVERT_OLESTREAM_DATA *pOleStreamData) +{ + + HRESULT hRes; + IStream *pStream; + + /* Initialize Default data for OLESTREAM */ + pOleStreamData[0].dwOleID = OLESTREAM_ID; + pOleStreamData[0].dwTypeID = 2; + pOleStreamData[1].dwOleID = OLESTREAM_ID; + pOleStreamData[1].dwTypeID = 0; + pOleStreamData[0].dwMetaFileWidth = 0; + pOleStreamData[0].dwMetaFileHeight = 0; + pOleStreamData[0].pData = NULL; + pOleStreamData[1].pData = NULL; + + /* Open Ole10Native Stream */ + hRes = IStorage_OpenStream(pStorage, L"\1Ole10Native", NULL, + STGM_READ | STGM_SHARE_EXCLUSIVE, 0, &pStream ); + if(hRes == S_OK) + { + + /* Read Size and Data */ + IStream_Read(pStream, &(pOleStreamData->dwDataLength), sizeof(pOleStreamData->dwDataLength), NULL); + if(pOleStreamData->dwDataLength > 0) + { + pOleStreamData->pData = HeapAlloc(GetProcessHeap(),0,pOleStreamData->dwDataLength); + IStream_Read(pStream, pOleStreamData->pData, pOleStreamData->dwDataLength, NULL); + } + IStream_Release(pStream); + } + +} + + +/************************************************************************* + * OLECONVERT_GetOle20PresData[Internal] + * + * Converts IStorage "/002OlePres000" stream to a OLE10 Stream + * + * PARAMS + * pStorage [I] Src IStroage + * pOleStreamData [I] Dest OleStream Mem Struct + * + * RETURNS + * Nothing + * + * NOTES + * This function is used by OleConvertIStorageToOLESTREAM only. + * + * Memory allocated for pData must be freed by the caller + */ +static void OLECONVERT_GetOle20PresData(LPSTORAGE pStorage, OLECONVERT_OLESTREAM_DATA *pOleStreamData) +{ + HRESULT hRes; + IStream *pStream; + OLECONVERT_ISTORAGE_OLEPRES olePress; + + /* Initialize Default data for OLESTREAM */ + pOleStreamData[0].dwOleID = OLESTREAM_ID; + pOleStreamData[0].dwTypeID = 2; + pOleStreamData[0].dwMetaFileWidth = 0; + pOleStreamData[0].dwMetaFileHeight = 0; + pOleStreamData[0].dwDataLength = OLECONVERT_WriteOLE20ToBuffer(pStorage, &(pOleStreamData[0].pData)); + pOleStreamData[1].dwOleID = OLESTREAM_ID; + pOleStreamData[1].dwTypeID = 0; + pOleStreamData[1].dwOleTypeNameLength = 0; + pOleStreamData[1].strOleTypeName[0] = 0; + pOleStreamData[1].dwMetaFileWidth = 0; + pOleStreamData[1].dwMetaFileHeight = 0; + pOleStreamData[1].pData = NULL; + pOleStreamData[1].dwDataLength = 0; + + + /* Open OlePress000 stream */ + hRes = IStorage_OpenStream(pStorage, L"\2OlePres000", NULL, + STGM_READ | STGM_SHARE_EXCLUSIVE, 0, &pStream ); + if(hRes == S_OK) + { + LARGE_INTEGER iSeekPos; + METAFILEPICT16 MetaFilePict; + static const char strMetafilePictName[] = "METAFILEPICT"; + + /* Set the TypeID for a Metafile */ + pOleStreamData[1].dwTypeID = 5; + + /* Set the OleTypeName to Metafile */ + pOleStreamData[1].dwOleTypeNameLength = strlen(strMetafilePictName) +1; + strcpy(pOleStreamData[1].strOleTypeName, strMetafilePictName); + + iSeekPos.HighPart = 0; + iSeekPos.LowPart = sizeof(olePress.byUnknown1); + + /* Get Presentation Data */ + IStream_Seek(pStream, iSeekPos, STREAM_SEEK_SET, NULL); + IStream_Read(pStream, &(olePress.dwExtentX), sizeof(olePress.dwExtentX), NULL); + IStream_Read(pStream, &(olePress.dwExtentY), sizeof(olePress.dwExtentY), NULL); + IStream_Read(pStream, &(olePress.dwSize), sizeof(olePress.dwSize), NULL); + + /*Set width and Height */ + pOleStreamData[1].dwMetaFileWidth = olePress.dwExtentX; + pOleStreamData[1].dwMetaFileHeight = -olePress.dwExtentY; + if(olePress.dwSize > 0) + { + /* Set Length */ + pOleStreamData[1].dwDataLength = olePress.dwSize + sizeof(METAFILEPICT16); + + /* Set MetaFilePict struct */ + MetaFilePict.mm = 8; + MetaFilePict.xExt = olePress.dwExtentX; + MetaFilePict.yExt = olePress.dwExtentY; + MetaFilePict.hMF = 0; + + /* Get Metafile Data */ + pOleStreamData[1].pData = HeapAlloc(GetProcessHeap(),0,pOleStreamData[1].dwDataLength); + memcpy(pOleStreamData[1].pData, &MetaFilePict, sizeof(MetaFilePict)); + IStream_Read(pStream, &(pOleStreamData[1].pData[sizeof(MetaFilePict)]), pOleStreamData[1].dwDataLength-sizeof(METAFILEPICT16), NULL); + } + IStream_Release(pStream); + } +} + +/************************************************************************* + * OleConvertOLESTREAMToIStorage [OLE32.@] + * + * Read info on MSDN + * + * TODO + * DVTARGETDEVICE parameter is not handled + * Still unsure of some mem fields for OLE 10 Stream + * Still some unknowns for the IStorage: "\002OlePres000", "\001CompObj", + * and "\001OLE" streams + * + */ +HRESULT WINAPI OleConvertOLESTREAMToIStorage ( + LPOLESTREAM pOleStream, + LPSTORAGE pstg, + const DVTARGETDEVICE* ptd) +{ + int i; + HRESULT hRes=S_OK; + OLECONVERT_OLESTREAM_DATA pOleStreamData[2]; + + TRACE("%p %p %p\n", pOleStream, pstg, ptd); + + memset(pOleStreamData, 0, sizeof(pOleStreamData)); + + if(ptd != NULL) + { + FIXME("DVTARGETDEVICE is not NULL, unhandled parameter\n"); + } + + if(pstg == NULL || pOleStream == NULL) + { + hRes = E_INVALIDARG; + } + + if(hRes == S_OK) + { + /* Load the OLESTREAM to Memory */ + hRes = OLECONVERT_LoadOLE10(pOleStream, &pOleStreamData[0], TRUE); + } + + if(hRes == S_OK) + { + /* Load the OLESTREAM to Memory (part 2)*/ + hRes = OLECONVERT_LoadOLE10(pOleStream, &pOleStreamData[1], FALSE); + } + + if(hRes == S_OK) + { + + if(pOleStreamData[0].dwDataLength > sizeof(STORAGE_magic)) + { + /* Do we have the IStorage Data in the OLESTREAM */ + if(memcmp(pOleStreamData[0].pData, STORAGE_magic, sizeof(STORAGE_magic)) ==0) + { + OLECONVERT_GetOLE20FromOLE10(pstg, pOleStreamData[0].pData, pOleStreamData[0].dwDataLength); + OLECONVERT_CreateOlePresStream(pstg, pOleStreamData[1].dwMetaFileWidth, pOleStreamData[1].dwMetaFileHeight, pOleStreamData[1].pData, pOleStreamData[1].dwDataLength); + } + else + { + /* It must be an original OLE 1.0 source */ + OLECONVERT_CreateOle10NativeStream(pstg, pOleStreamData[0].pData, pOleStreamData[0].dwDataLength); + } + } + else + { + /* It must be an original OLE 1.0 source */ + OLECONVERT_CreateOle10NativeStream(pstg, pOleStreamData[0].pData, pOleStreamData[0].dwDataLength); + } + + /* Create CompObj Stream if necessary */ + hRes = OLECONVERT_CreateCompObjStream(pstg, pOleStreamData[0].strOleTypeName); + if(hRes == S_OK) + { + /*Create the Ole Stream if necessary */ + STORAGE_CreateOleStream(pstg, 0); + } + } + + + /* Free allocated memory */ + for(i=0; i < 2; i++) + { + HeapFree(GetProcessHeap(),0,pOleStreamData[i].pData); + HeapFree(GetProcessHeap(),0,pOleStreamData[i].pstrOleObjFileName); + pOleStreamData[i].pstrOleObjFileName = NULL; + } + return hRes; +} + +/************************************************************************* + * OleConvertIStorageToOLESTREAM [OLE32.@] + * + * Read info on MSDN + * + * Read info on MSDN + * + * TODO + * Still unsure of some mem fields for OLE 10 Stream + * Still some unknowns for the IStorage: "\002OlePres000", "\001CompObj", + * and "\001OLE" streams. + * + */ +HRESULT WINAPI OleConvertIStorageToOLESTREAM ( + LPSTORAGE pstg, + LPOLESTREAM pOleStream) +{ + int i; + HRESULT hRes = S_OK; + IStream *pStream; + OLECONVERT_OLESTREAM_DATA pOleStreamData[2]; + + TRACE("%p %p\n", pstg, pOleStream); + + memset(pOleStreamData, 0, sizeof(pOleStreamData)); + + if(pstg == NULL || pOleStream == NULL) + { + hRes = E_INVALIDARG; + } + if(hRes == S_OK) + { + /* Get the ProgID */ + pOleStreamData[0].dwOleTypeNameLength = OLESTREAM_MAX_STR_LEN; + hRes = OLECONVERT_GetOLE10ProgID(pstg, pOleStreamData[0].strOleTypeName, &(pOleStreamData[0].dwOleTypeNameLength)); + } + if(hRes == S_OK) + { + /* Was it originally Ole10 */ + hRes = IStorage_OpenStream(pstg, L"\1Ole10Native", 0, STGM_READ | STGM_SHARE_EXCLUSIVE, 0, &pStream); + if(hRes == S_OK) + { + IStream_Release(pStream); + /* Get Presentation Data for Ole10Native */ + OLECONVERT_GetOle10PresData(pstg, pOleStreamData); + } + else + { + /* Get Presentation Data (OLE20) */ + OLECONVERT_GetOle20PresData(pstg, pOleStreamData); + } + + /* Save OLESTREAM */ + hRes = OLECONVERT_SaveOLE10(&(pOleStreamData[0]), pOleStream); + if(hRes == S_OK) + { + hRes = OLECONVERT_SaveOLE10(&(pOleStreamData[1]), pOleStream); + } + + } + + /* Free allocated memory */ + for(i=0; i < 2; i++) + { + HeapFree(GetProcessHeap(),0,pOleStreamData[i].pData); + } + + return hRes; +} + +enum stream_1ole_flags { + OleStream_LinkedObject = 0x00000001, + OleStream_Convert = 0x00000004 +}; + +/************************************************************************* + * OleConvertIStorageToOLESTREAMEx [OLE32.@] + */ +HRESULT WINAPI OleConvertIStorageToOLESTREAMEx ( LPSTORAGE stg, CLIPFORMAT cf, LONG width, LONG height, + DWORD size, LPSTGMEDIUM medium, LPOLESTREAM olestream ) +{ + FIXME("%p, %x, %ld, %ld, %ld, %p, %p: stub\n", stg, cf, width, height, size, medium, olestream); + + return E_NOTIMPL; +} + +/*********************************************************************** + * GetConvertStg (OLE32.@) + */ +HRESULT WINAPI GetConvertStg(IStorage *stg) +{ + static const DWORD version_magic = 0x02000001; + DWORD header[2]; + IStream *stream; + HRESULT hr; + + TRACE("%p\n", stg); + + if (!stg) return E_INVALIDARG; + + hr = IStorage_OpenStream(stg, L"\1Ole", NULL, STGM_READ | STGM_SHARE_EXCLUSIVE, 0, &stream); + if (FAILED(hr)) return hr; + + hr = IStream_Read(stream, header, sizeof(header), NULL); + IStream_Release(stream); + if (FAILED(hr)) return hr; + + if (header[0] != version_magic) + { + ERR("got wrong version magic for 1Ole stream, %#lx.\n", header[0]); + return E_FAIL; + } + + return header[1] & OleStream_Convert ? S_OK : S_FALSE; +} + +/*********************************************************************** + * SetConvertStg (OLE32.@) + */ +HRESULT WINAPI SetConvertStg(IStorage *storage, BOOL convert) +{ + DWORD flags = convert ? OleStream_Convert : 0; + IStream *stream; + DWORD header[2]; + HRESULT hr; + + TRACE("(%p, %d)\n", storage, convert); + + hr = IStorage_OpenStream(storage, L"\1Ole", NULL, STGM_READWRITE | STGM_SHARE_EXCLUSIVE, 0, &stream); + if (FAILED(hr)) + { + if (hr != STG_E_FILENOTFOUND) + return hr; + + return STORAGE_CreateOleStream(storage, flags); + } + + hr = IStream_Read(stream, header, sizeof(header), NULL); + if (FAILED(hr)) + { + IStream_Release(stream); + return hr; + } + + /* update flag if differs */ + if ((header[1] ^ flags) & OleStream_Convert) + { + LARGE_INTEGER pos = {{0}}; + + if (header[1] & OleStream_Convert) + flags = header[1] & ~OleStream_Convert; + else + flags = header[1] | OleStream_Convert; + + pos.QuadPart = sizeof(DWORD); + hr = IStream_Seek(stream, pos, STREAM_SEEK_SET, NULL); + if (FAILED(hr)) + { + IStream_Release(stream); + return hr; + } + + hr = IStream_Write(stream, &flags, sizeof(flags), NULL); + } + + IStream_Release(stream); + return hr; +} diff --git a/dlls/coml2/storage32.h b/dlls/coml2/storage32.h new file mode 100644 index 00000000000..dd4b89aceec --- /dev/null +++ b/dlls/coml2/storage32.h @@ -0,0 +1,571 @@ +/* + * Compound Storage (32 bit version) + * + * Implemented using the documentation of the LAOLA project at + * URL:http://wwwwbs.cs.tu-berlin.de/~schwartz/pmh/index.html + * (Thanks to Martin Schwartz schwartz@cs.tu-berlin.de) + * + * This include file contains definitions of types and function + * prototypes that are used in the many files implementing the + * storage functionality + * + * Copyright 1998,1999 Francis Beaudet + * Copyright 1998,1999 Thuy Nguyen + * Copyright 2010 Vincent Povirk for CodeWeavers + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ +#ifndef __STORAGE32_H__ +#define __STORAGE32_H__ + +#include <stdarg.h> + +#include "windef.h" +#include "winbase.h" +#include "winnt.h" +#include "objbase.h" +#include "winreg.h" +#include "winternl.h" +#include "wine/list.h" + +/* + * Definitions for the file format offsets. + */ +static const ULONG OFFSET_MINORVERSION = 0x00000018; +static const ULONG OFFSET_MAJORVERSION = 0x0000001a; +static const ULONG OFFSET_BYTEORDERMARKER = 0x0000001c; +static const ULONG OFFSET_BIGBLOCKSIZEBITS = 0x0000001e; +static const ULONG OFFSET_SMALLBLOCKSIZEBITS = 0x00000020; +static const ULONG OFFSET_DIRSECTORCOUNT = 0x00000028; +static const ULONG OFFSET_BBDEPOTCOUNT = 0x0000002C; +static const ULONG OFFSET_ROOTSTARTBLOCK = 0x00000030; +static const ULONG OFFSET_TRANSACTIONSIG = 0x00000034; +static const ULONG OFFSET_SMALLBLOCKLIMIT = 0x00000038; +static const ULONG OFFSET_SBDEPOTSTART = 0x0000003C; +static const ULONG OFFSET_SBDEPOTCOUNT = 0x00000040; +static const ULONG OFFSET_EXTBBDEPOTSTART = 0x00000044; +static const ULONG OFFSET_EXTBBDEPOTCOUNT = 0x00000048; +static const ULONG OFFSET_BBDEPOTSTART = 0x0000004C; +static const ULONG OFFSET_PS_NAME = 0x00000000; +static const ULONG OFFSET_PS_NAMELENGTH = 0x00000040; +static const ULONG OFFSET_PS_STGTYPE = 0x00000042; +static const ULONG OFFSET_PS_LEFTCHILD = 0x00000044; +static const ULONG OFFSET_PS_RIGHTCHILD = 0x00000048; +static const ULONG OFFSET_PS_DIRROOT = 0x0000004C; +static const ULONG OFFSET_PS_GUID = 0x00000050; +static const ULONG OFFSET_PS_CTIMELOW = 0x00000064; +static const ULONG OFFSET_PS_CTIMEHIGH = 0x00000068; +static const ULONG OFFSET_PS_MTIMELOW = 0x0000006C; +static const ULONG OFFSET_PS_MTIMEHIGH = 0x00000070; +static const ULONG OFFSET_PS_STARTBLOCK = 0x00000074; +static const ULONG OFFSET_PS_SIZE = 0x00000078; +static const ULONG OFFSET_PS_SIZE_HIGH = 0x0000007C; +static const WORD DEF_BIG_BLOCK_SIZE_BITS = 0x0009; +static const WORD MIN_BIG_BLOCK_SIZE_BITS = 0x0009; +static const WORD MAX_BIG_BLOCK_SIZE_BITS = 0x000c; +static const WORD DEF_SMALL_BLOCK_SIZE_BITS = 0x0006; +static const WORD DEF_BIG_BLOCK_SIZE = 0x0200; +static const WORD DEF_SMALL_BLOCK_SIZE = 0x0040; +static const ULONG BLOCK_FIRST_SPECIAL = 0xFFFFFFFB; +static const ULONG BLOCK_EXTBBDEPOT = 0xFFFFFFFC; +static const ULONG BLOCK_SPECIAL = 0xFFFFFFFD; +static const ULONG BLOCK_END_OF_CHAIN = 0xFFFFFFFE; +static const ULONG BLOCK_UNUSED = 0xFFFFFFFF; +static const ULONG DIRENTRY_NULL = 0xFFFFFFFF; + +#define DIRENTRY_NAME_MAX_LEN 0x20 +#define DIRENTRY_NAME_BUFFER_LEN 0x40 + +#define RAW_DIRENTRY_SIZE 0x00000080 + +#define HEADER_SIZE 512 + +#define MIN_BIG_BLOCK_SIZE 0x200 +#define MAX_BIG_BLOCK_SIZE 0x1000 + +/* + * Type of child entry link + */ +#define DIRENTRY_RELATION_PREVIOUS 0 +#define DIRENTRY_RELATION_NEXT 1 +#define DIRENTRY_RELATION_DIR 2 + +/* + * type constant used in files for the root storage + */ +#define STGTY_ROOT 0x05 + +#define COUNT_BBDEPOTINHEADER 109 + +/* FIXME: This value is stored in the header, but we hard-code it to 0x1000. */ +#define LIMIT_TO_USE_SMALL_BLOCK 0x1000 + +#define STGM_ACCESS_MODE(stgm) ((stgm)&0x0000f) +#define STGM_SHARE_MODE(stgm) ((stgm)&0x000f0) +#define STGM_CREATE_MODE(stgm) ((stgm)&0x0f000) + +#define STGM_KNOWN_FLAGS (0xf0ff | \ + STGM_TRANSACTED | STGM_CONVERT | STGM_PRIORITY | STGM_NOSCRATCH | \ + STGM_NOSNAPSHOT | STGM_DIRECT_SWMR | STGM_DELETEONRELEASE | STGM_SIMPLE) + +/* + * Forward declarations of all the structures used by the storage + * module. + */ +typedef struct StorageBaseImpl StorageBaseImpl; +typedef struct StorageBaseImplVtbl StorageBaseImplVtbl; +typedef struct StorageImpl StorageImpl; +typedef struct BlockChainStream BlockChainStream; +typedef struct SmallBlockChainStream SmallBlockChainStream; +typedef struct IEnumSTATSTGImpl IEnumSTATSTGImpl; +typedef struct DirEntry DirEntry; +typedef struct StgStreamImpl StgStreamImpl; + +/* + * A reference to a directory entry in the file or a transacted cache. + */ +typedef ULONG DirRef; + +/* + * This utility structure is used to read/write the information in a directory + * entry. + */ +struct DirEntry +{ + WCHAR name[DIRENTRY_NAME_MAX_LEN]; + WORD sizeOfNameString; + BYTE stgType; + DirRef leftChild; + DirRef rightChild; + DirRef dirRootEntry; + GUID clsid; + FILETIME ctime; + FILETIME mtime; + ULONG startingBlock; + ULARGE_INTEGER size; +}; + +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, + SWMR_Writer, + SWMR_Reader +}; + +/**************************************************************************** + * StorageBaseImpl definitions. + * + * This structure defines the base information contained in all implementations + * of IStorage contained in this file storage implementation. + * + * In OOP terms, this is the base class for all the IStorage implementations + * contained in this file. + */ +struct StorageBaseImpl +{ + IStorage IStorage_iface; + IPropertySetStorage IPropertySetStorage_iface; /* interface for adding a properties stream */ + IDirectWriterLock IDirectWriterLock_iface; + LONG ref; + + /* + * Stream tracking list + */ + + struct list strmHead; + + /* + * Storage tracking list + */ + struct list storageHead; + + /* + * TRUE if this object has been invalidated + */ + BOOL reverted; + + /* + * Index of the directory entry of this storage + */ + DirRef storageDirEntry; + + /* + * virtual methods. + */ + const StorageBaseImplVtbl *baseVtbl; + + /* + * flags that this storage was opened or created with + */ + DWORD openFlags; + + /* + * State bits appear to only be preserved while running. No in the stream + */ + DWORD stateBits; + + BOOL create; /* Was the storage created or opened. + The behaviour of STGM_SIMPLE depends on this */ + /* + * If this storage was opened in transacted mode, the object that implements + * the transacted snapshot or cache. + */ + StorageBaseImpl *transactedChild; + enum swmr_mode lockingrole; +}; + +/* virtual methods for StorageBaseImpl objects */ +struct StorageBaseImplVtbl { + void (*Destroy)(StorageBaseImpl*); + void (*Invalidate)(StorageBaseImpl*); + HRESULT (*Flush)(StorageBaseImpl*); + HRESULT (*GetFilename)(StorageBaseImpl*,LPWSTR*); + HRESULT (*CreateDirEntry)(StorageBaseImpl*,const DirEntry*,DirRef*); + HRESULT (*WriteDirEntry)(StorageBaseImpl*,DirRef,const DirEntry*); + HRESULT (*ReadDirEntry)(StorageBaseImpl*,DirRef,DirEntry*); + HRESULT (*DestroyDirEntry)(StorageBaseImpl*,DirRef); + HRESULT (*StreamReadAt)(StorageBaseImpl*,DirRef,ULARGE_INTEGER,ULONG,void*,ULONG*); + HRESULT (*StreamWriteAt)(StorageBaseImpl*,DirRef,ULARGE_INTEGER,ULONG,const void*,ULONG*); + HRESULT (*StreamSetSize)(StorageBaseImpl*,DirRef,ULARGE_INTEGER); + HRESULT (*StreamLink)(StorageBaseImpl*,DirRef,DirRef); + HRESULT (*GetTransactionSig)(StorageBaseImpl*,ULONG*,BOOL); + HRESULT (*SetTransactionSig)(StorageBaseImpl*,ULONG); + HRESULT (*LockTransaction)(StorageBaseImpl*,BOOL); + HRESULT (*UnlockTransaction)(StorageBaseImpl*,BOOL); +}; + +static inline void StorageBaseImpl_Destroy(StorageBaseImpl *This) +{ + This->baseVtbl->Destroy(This); +} + +static inline void StorageBaseImpl_Invalidate(StorageBaseImpl *This) +{ + This->baseVtbl->Invalidate(This); +} + +static inline HRESULT StorageBaseImpl_Flush(StorageBaseImpl *This) +{ + return This->baseVtbl->Flush(This); +} + +static inline HRESULT StorageBaseImpl_GetFilename(StorageBaseImpl *This, LPWSTR *result) +{ + return This->baseVtbl->GetFilename(This, result); +} + +static inline HRESULT StorageBaseImpl_CreateDirEntry(StorageBaseImpl *This, + const DirEntry *newData, DirRef *index) +{ + return This->baseVtbl->CreateDirEntry(This, newData, index); +} + +static inline HRESULT StorageBaseImpl_WriteDirEntry(StorageBaseImpl *This, + DirRef index, const DirEntry *data) +{ + return This->baseVtbl->WriteDirEntry(This, index, data); +} + +static inline HRESULT StorageBaseImpl_ReadDirEntry(StorageBaseImpl *This, + DirRef index, DirEntry *data) +{ + return This->baseVtbl->ReadDirEntry(This, index, data); +} + +static inline HRESULT StorageBaseImpl_DestroyDirEntry(StorageBaseImpl *This, + DirRef index) +{ + return This->baseVtbl->DestroyDirEntry(This, index); +} + +/* Read up to size bytes from this directory entry's stream at the given offset. */ +static inline HRESULT StorageBaseImpl_StreamReadAt(StorageBaseImpl *This, + DirRef index, ULARGE_INTEGER offset, ULONG size, void *buffer, ULONG *bytesRead) +{ + return This->baseVtbl->StreamReadAt(This, index, offset, size, buffer, bytesRead); +} + +/* Write size bytes to this directory entry's stream at the given offset, + * growing the stream if necessary. */ +static inline HRESULT StorageBaseImpl_StreamWriteAt(StorageBaseImpl *This, + DirRef index, ULARGE_INTEGER offset, ULONG size, const void *buffer, ULONG *bytesWritten) +{ + return This->baseVtbl->StreamWriteAt(This, index, offset, size, buffer, bytesWritten); +} + +static inline HRESULT StorageBaseImpl_StreamSetSize(StorageBaseImpl *This, + DirRef index, ULARGE_INTEGER newsize) +{ + return This->baseVtbl->StreamSetSize(This, index, newsize); +} + +/* Make dst point to the same stream that src points to. Other stream operations + * will not work properly for entries that point to the same stream, so this + * must be a very temporary state, and only one entry pointing to a given stream + * may be reachable at any given time. */ +static inline HRESULT StorageBaseImpl_StreamLink(StorageBaseImpl *This, + DirRef dst, DirRef src) +{ + return This->baseVtbl->StreamLink(This, dst, src); +} + +static inline HRESULT StorageBaseImpl_GetTransactionSig(StorageBaseImpl *This, + ULONG* result, BOOL refresh) +{ + return This->baseVtbl->GetTransactionSig(This, result, refresh); +} + +static inline HRESULT StorageBaseImpl_SetTransactionSig(StorageBaseImpl *This, + ULONG value) +{ + return This->baseVtbl->SetTransactionSig(This, value); +} + +static inline HRESULT StorageBaseImpl_LockTransaction(StorageBaseImpl *This, BOOL write) +{ + return This->baseVtbl->LockTransaction(This, write); +} + +static inline HRESULT StorageBaseImpl_UnlockTransaction(StorageBaseImpl *This, BOOL write) +{ + return This->baseVtbl->UnlockTransaction(This, write); +} + +/**************************************************************************** + * StorageBaseImpl stream list handlers + */ + +void StorageBaseImpl_AddStream(StorageBaseImpl * stg, StgStreamImpl * strm); +void StorageBaseImpl_RemoveStream(StorageBaseImpl * stg, StgStreamImpl * strm); + +/* Number of BlockChainStream objects to cache in a StorageImpl */ +#define BLOCKCHAIN_CACHE_SIZE 4 + +/**************************************************************************** + * StorageImpl definitions. + * + * This implementation of the IStorage interface represents a root + * storage. Basically, a document file. + */ +struct StorageImpl +{ + struct StorageBaseImpl base; + + /* + * File header + */ + WORD bigBlockSizeBits; + WORD smallBlockSizeBits; + ULONG bigBlockSize; + ULONG smallBlockSize; + ULONG bigBlockDepotCount; + ULONG rootStartBlock; + ULONG smallBlockLimit; + ULONG smallBlockDepotStart; + ULONG extBigBlockDepotStart; + ULONG *extBigBlockDepotLocations; + ULONG extBigBlockDepotLocationsSize; + ULONG extBigBlockDepotCount; + ULONG bigBlockDepotStart[COUNT_BBDEPOTINHEADER]; + ULONG transactionSig; + + ULONG extBlockDepotCached[MAX_BIG_BLOCK_SIZE / 4]; + ULONG indexExtBlockDepotCached; + + ULONG blockDepotCached[MAX_BIG_BLOCK_SIZE / 4]; + ULONG indexBlockDepotCached; + ULONG prevFreeBlock; + + /* All small blocks before this one are known to be in use. */ + ULONG firstFreeSmallBlock; + + /* + * Abstraction of the big block chains for the chains of the header. + */ + BlockChainStream* rootBlockChain; + BlockChainStream* smallBlockDepotChain; + BlockChainStream* smallBlockRootChain; + + /* Cache of block chain streams objects for directory entries */ + BlockChainStream* blockChainCache[BLOCKCHAIN_CACHE_SIZE]; + UINT blockChainToEvict; + + ULONG locks_supported; + + ILockBytes* lockBytes; + + ULONG locked_bytes[8]; +}; + +/**************************************************************************** + * StgStreamImpl definitions. + * + * This class implements the IStream interface and represents a stream + * located inside a storage object. + */ +struct StgStreamImpl +{ + IStream IStream_iface; + LONG ref; + + /* + * We are an entry in the storage object's stream handler list + */ + struct list StrmListEntry; + + /* + * Storage that is the parent(owner) of the stream + */ + StorageBaseImpl* parentStorage; + + /* + * Access mode of this stream. + */ + DWORD grfMode; + + /* + * Index of the directory entry that owns (points to) this stream. + */ + DirRef dirEntry; + + /* + * This is the current position of the cursor in the stream + */ + ULARGE_INTEGER currentPosition; +}; + +static inline StgStreamImpl *impl_from_IStream( IStream *iface ) +{ + return CONTAINING_RECORD(iface, StgStreamImpl, IStream_iface); +} + +/* + * Method definition for the StgStreamImpl class. + */ +StgStreamImpl* StgStreamImpl_Construct( + StorageBaseImpl* parentStorage, + DWORD grfMode, + DirRef dirEntry); + + +/* Range lock constants. + * + * The storage format reserves the region from 0x7fffff00-0x7fffffff for + * locking and synchronization. Because it reserves the entire block containing + * that range, and the minimum block size is 512 bytes, 0x7ffffe00-0x7ffffeff + * also cannot be used for any other purpose. + * Unfortunately, the spec doesn't say which bytes + * within that range are used, and for what. These are guesses based on testing. + * In particular, ends of ranges may be wrong. + + 0x0 through 0x57: Unknown. Causes read-only exclusive opens to fail. + 0x58 through 0x6b: Priority mode. + 0x6c through 0x7f: No snapshot mode. + 0x80: Commit lock. + 0x81 through 0x91: Priority mode, again. Not sure why it uses two regions. + 0x92: Lock-checking lock. Held while opening so ranges can be tested without + causing spurious failures if others try to grab or test those ranges at the + same time. + 0x93 through 0xa6: Read mode. + 0xa7 through 0xba: Write mode. + 0xbb through 0xce: Deny read. + 0xcf through 0xe2: Deny write. + 0xe2 through 0xff: Unknown. Causes read-only exclusive opens to fail. +*/ + +#define RANGELOCK_UNK1_FIRST 0x7ffffe00 +#define RANGELOCK_UNK1_LAST 0x7fffff57 +#define RANGELOCK_PRIORITY1_FIRST 0x7fffff58 +#define RANGELOCK_PRIORITY1_LAST 0x7fffff6b +#define RANGELOCK_NOSNAPSHOT_FIRST 0x7fffff6c +#define RANGELOCK_NOSNAPSHOT_LAST 0x7fffff7f +#define RANGELOCK_COMMIT 0x7fffff80 +#define RANGELOCK_PRIORITY2_FIRST 0x7fffff81 +#define RANGELOCK_PRIORITY2_LAST 0x7fffff91 +#define RANGELOCK_CHECKLOCKS 0x7fffff92 +#define RANGELOCK_READ_FIRST 0x7fffff93 +#define RANGELOCK_READ_LAST 0x7fffffa6 +#define RANGELOCK_WRITE_FIRST 0x7fffffa7 +#define RANGELOCK_WRITE_LAST 0x7fffffba +#define RANGELOCK_DENY_READ_FIRST 0x7fffffbb +#define RANGELOCK_DENY_READ_LAST 0x7fffffce +#define RANGELOCK_DENY_WRITE_FIRST 0x7fffffcf +#define RANGELOCK_DENY_WRITE_LAST 0x7fffffe2 +#define RANGELOCK_UNK2_FIRST 0x7fffffe3 +#define RANGELOCK_UNK2_LAST 0x7fffffff +#define RANGELOCK_TRANSACTION_FIRST RANGELOCK_COMMIT +#define RANGELOCK_TRANSACTION_LAST RANGELOCK_CHECKLOCKS +#define RANGELOCK_FIRST RANGELOCK_UNK1_FIRST +#define RANGELOCK_LAST RANGELOCK_UNK2_LAST + +/* internal value for LockRegion/UnlockRegion */ +#define WINE_LOCK_READ 0x80000000 + + +/****************************************************************************** + * Endian conversion macros + */ +#ifdef WORDS_BIGENDIAN + +#ifndef htole32 +#define htole32(x) RtlUlongByteSwap(x) +#endif +#ifndef htole16 +#define htole16(x) RtlUshortByteSwap(x) +#endif +#define lendian32toh(x) RtlUlongByteSwap(x) +#define lendian16toh(x) RtlUshortByteSwap(x) + +#else + +#ifndef htole32 +#define htole32(x) (x) +#endif +#ifndef htole16 +#define htole16(x) (x) +#endif +#define lendian32toh(x) (x) +#define lendian16toh(x) (x) + +#endif + +/****************************************************************************** + * The StorageUtl_ functions are miscellaneous utility functions. Most of which + * are abstractions used to read values from file buffers without having to + * worry about bit order + */ +void StorageUtl_ReadWord(const BYTE* buffer, ULONG offset, WORD* value); +void StorageUtl_WriteWord(void *buffer, ULONG offset, WORD value); +void StorageUtl_ReadDWord(const BYTE* buffer, ULONG offset, DWORD* value); +void StorageUtl_WriteDWord(void *buffer, ULONG offset, DWORD value); +void StorageUtl_ReadULargeInteger(const BYTE* buffer, ULONG offset, + ULARGE_INTEGER* value); +void StorageUtl_WriteULargeInteger(void *buffer, ULONG offset, const ULARGE_INTEGER *value); +void StorageUtl_ReadGUID(const BYTE* buffer, ULONG offset, GUID* value); +void StorageUtl_WriteGUID(void *buffer, ULONG offset, const GUID* value); +void StorageUtl_CopyDirEntryToSTATSTG(StorageBaseImpl *storage,STATSTG* destination, + const DirEntry* source, int statFlags); + + +#endif /* __STORAGE32_H__ */