/****************************************************************************** * * BigBlockFile * * This is the implementation of a file that consists of blocks of * a predetermined size. * This class is used in the Compound File implementation of the * IStorage and IStream interfaces. It provides the functionality * to read and write any blocks in the file as well as setting and * obtaining the size of the file. * The blocks are indexed sequentially from the start of the file * starting with -1. * * TODO: * - Support for a transacted mode * * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include #include #include #include #include #include #define NONAMELESSUNION #define NONAMELESSSTRUCT #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); /*********************************************************** * Data structures used internally by the BigBlockFile * class. */ /* We map in PAGE_SIZE-sized chunks. Must be a multiple of 4096. */ #define PAGE_SIZE 131072 #define BLOCKS_PER_PAGE (PAGE_SIZE / BIG_BLOCK_SIZE) /* We keep a list of recently-discarded pages. This controls the * size of that list. */ #define MAX_VICTIM_PAGES 16 /* This structure provides one bit for each block in a page. * Use BIGBLOCKFILE_{Test,Set,Clear}Bit to manipulate it. */ typedef struct { unsigned int bits[BLOCKS_PER_PAGE / (CHAR_BIT * sizeof(unsigned int))]; } BlockBits; /*** * This structure identifies the paged that are mapped * from the file and their position in memory. It is * also used to hold a reference count to those pages. * * page_index identifies which PAGE_SIZE chunk from the * file this mapping represents. (The mappings are always * PAGE_SIZE-aligned.) */ struct MappedPage { MappedPage *next; MappedPage *prev; DWORD page_index; LPVOID lpBytes; LONG refcnt; BlockBits readable_blocks; BlockBits writable_blocks; }; /*********************************************************** * Prototypes for private methods */ static void* BIGBLOCKFILE_GetMappedView(LPBIGBLOCKFILE This, DWORD page_index); static void BIGBLOCKFILE_ReleaseMappedPage(LPBIGBLOCKFILE This, MappedPage *page); static void BIGBLOCKFILE_FreeAllMappedPages(LPBIGBLOCKFILE This); static void BIGBLOCKFILE_UnmapAllMappedPages(LPBIGBLOCKFILE This); static void BIGBLOCKFILE_RemapAllMappedPages(LPBIGBLOCKFILE This); static void* BIGBLOCKFILE_GetBigBlockPointer(LPBIGBLOCKFILE This, ULONG index, DWORD desired_access); static MappedPage* BIGBLOCKFILE_GetPageFromPointer(LPBIGBLOCKFILE This, void* pBlock); static MappedPage* BIGBLOCKFILE_CreatePage(LPBIGBLOCKFILE This, ULONG page_index); static DWORD BIGBLOCKFILE_GetProtectMode(DWORD openFlags); static BOOL BIGBLOCKFILE_FileInit(LPBIGBLOCKFILE This, HANDLE hFile); static BOOL BIGBLOCKFILE_MemInit(LPBIGBLOCKFILE This, ILockBytes* plkbyt); /* Note that this evaluates a and b multiple times, so don't * pass expressions with side effects. */ #define ROUND_UP(a, b) ((((a) + (b) - 1)/(b))*(b)) /*********************************************************** * Blockbits functions. */ static inline BOOL BIGBLOCKFILE_TestBit(const BlockBits *bb, unsigned int index) { unsigned int array_index = index / (CHAR_BIT * sizeof(unsigned int)); unsigned int bit_index = index % (CHAR_BIT * sizeof(unsigned int)); return bb->bits[array_index] & (1 << bit_index); } static inline void BIGBLOCKFILE_SetBit(BlockBits *bb, unsigned int index) { unsigned int array_index = index / (CHAR_BIT * sizeof(unsigned int)); unsigned int bit_index = index % (CHAR_BIT * sizeof(unsigned int)); bb->bits[array_index] |= (1 << bit_index); } static inline void BIGBLOCKFILE_ClearBit(BlockBits *bb, unsigned int index) { unsigned int array_index = index / (CHAR_BIT * sizeof(unsigned int)); unsigned int bit_index = index % (CHAR_BIT * sizeof(unsigned int)); bb->bits[array_index] &= ~(1 << bit_index); } static inline void BIGBLOCKFILE_Zero(BlockBits *bb) { memset(bb->bits, 0, sizeof(bb->bits)); } /****************************************************************************** * BIGBLOCKFILE_Construct * * Construct a big block file. Create the file mapping object. * Create the read only mapped pages list, the writable mapped page list * and the blocks in use list. */ BigBlockFile * BIGBLOCKFILE_Construct( HANDLE hFile, ILockBytes* pLkByt, DWORD openFlags, ULONG blocksize, BOOL fileBased) { LPBIGBLOCKFILE This; This = (LPBIGBLOCKFILE)HeapAlloc(GetProcessHeap(), 0, sizeof(BigBlockFile)); if (This == NULL) return NULL; This->fileBased = fileBased; This->flProtect = BIGBLOCKFILE_GetProtectMode(openFlags); This->blocksize = blocksize; This->maplist = NULL; This->victimhead = NULL; This->victimtail = NULL; This->num_victim_pages = 0; if (This->fileBased) { if (!BIGBLOCKFILE_FileInit(This, hFile)) { HeapFree(GetProcessHeap(), 0, This); return NULL; } } else { if (!BIGBLOCKFILE_MemInit(This, pLkByt)) { HeapFree(GetProcessHeap(), 0, This); return NULL; } } return This; } /****************************************************************************** * BIGBLOCKFILE_FileInit * * Initialize a big block object supported by a file. */ static BOOL BIGBLOCKFILE_FileInit(LPBIGBLOCKFILE This, HANDLE hFile) { This->pLkbyt = NULL; This->hbytearray = 0; This->pbytearray = NULL; This->hfile = hFile; if (This->hfile == INVALID_HANDLE_VALUE) return FALSE; This->filesize.u.LowPart = GetFileSize(This->hfile, &This->filesize.u.HighPart); if( This->filesize.u.LowPart || This->filesize.u.HighPart ) { /* create the file mapping object */ This->hfilemap = CreateFileMappingA(This->hfile, NULL, This->flProtect, 0, 0, NULL); if (!This->hfilemap) { CloseHandle(This->hfile); return FALSE; } } else This->hfilemap = NULL; This->maplist = NULL; TRACE("file len %lu\n", This->filesize.u.LowPart); return TRUE; } /****************************************************************************** * BIGBLOCKFILE_MemInit * * Initialize a big block object supported by an ILockBytes on HGLOABL. */ static BOOL BIGBLOCKFILE_MemInit(LPBIGBLOCKFILE This, ILockBytes* plkbyt) { This->hfile = 0; This->hfilemap = 0; /* * Retrieve the handle to the byte array from the LockByte object. */ if (GetHGlobalFromILockBytes(plkbyt, &(This->hbytearray)) != S_OK) { FIXME("May not be an ILockBytes on HGLOBAL\n"); return FALSE; } This->pLkbyt = plkbyt; /* * Increment the reference count of the ILockByte object since * we're keeping a reference to it. */ ILockBytes_AddRef(This->pLkbyt); This->filesize.u.LowPart = GlobalSize(This->hbytearray); This->filesize.u.HighPart = 0; This->pbytearray = GlobalLock(This->hbytearray); TRACE("mem on %p len %lu\n", This->pbytearray, This->filesize.u.LowPart); return TRUE; } /****************************************************************************** * BIGBLOCKFILE_Destructor * * Destructor. Clean up, free memory. */ void BIGBLOCKFILE_Destructor( LPBIGBLOCKFILE This) { BIGBLOCKFILE_FreeAllMappedPages(This); if (This->fileBased) { CloseHandle(This->hfilemap); CloseHandle(This->hfile); } else { GlobalUnlock(This->hbytearray); ILockBytes_Release(This->pLkbyt); } /* destroy this */ HeapFree(GetProcessHeap(), 0, This); } /****************************************************************************** * BIGBLOCKFILE_GetROBigBlock * * Returns the specified block in read only mode. * Will return NULL if the block doesn't exists. */ void* BIGBLOCKFILE_GetROBigBlock( LPBIGBLOCKFILE This, ULONG index) { /* * block index starts at -1 * translate to zero based index */ if (index == 0xffffffff) index = 0; else index++; /* * validate the block index * */ if (This->blocksize * (index + 1) > ROUND_UP(This->filesize.u.LowPart, This->blocksize)) { TRACE("out of range %lu vs %lu\n", This->blocksize * (index + 1), This->filesize.u.LowPart); return NULL; } return BIGBLOCKFILE_GetBigBlockPointer(This, index, FILE_MAP_READ); } /****************************************************************************** * BIGBLOCKFILE_GetBigBlock * * Returns the specified block. * Will grow the file if necessary. */ void* BIGBLOCKFILE_GetBigBlock(LPBIGBLOCKFILE This, ULONG index) { /* * block index starts at -1 * translate to zero based index */ if (index == 0xffffffff) index = 0; else index++; /* * make sure that the block physically exists */ if ((This->blocksize * (index + 1)) > This->filesize.u.LowPart) { ULARGE_INTEGER newSize; newSize.u.HighPart = 0; newSize.u.LowPart = This->blocksize * (index + 1); BIGBLOCKFILE_SetSize(This, newSize); } return BIGBLOCKFILE_GetBigBlockPointer(This, index, FILE_MAP_WRITE); } /****************************************************************************** * BIGBLOCKFILE_ReleaseBigBlock * * Releases the specified block. */ void BIGBLOCKFILE_ReleaseBigBlock(LPBIGBLOCKFILE This, void *pBlock) { MappedPage *page; if (pBlock == NULL) return; page = BIGBLOCKFILE_GetPageFromPointer(This, pBlock); if (page == NULL) return; BIGBLOCKFILE_ReleaseMappedPage(This, page); } /****************************************************************************** * BIGBLOCKFILE_SetSize * * Sets the size of the file. * */ void BIGBLOCKFILE_SetSize(LPBIGBLOCKFILE This, ULARGE_INTEGER newSize) { if (This->filesize.u.LowPart == newSize.u.LowPart) return; TRACE("from %lu to %lu\n", This->filesize.u.LowPart, newSize.u.LowPart); /* * unmap all views, must be done before call to SetEndFile */ BIGBLOCKFILE_UnmapAllMappedPages(This); if (This->fileBased) { char buf[10]; /* * close file-mapping object, must be done before call to SetEndFile */ if( This->hfilemap ) CloseHandle(This->hfilemap); This->hfilemap = 0; /* * BEGIN HACK * This fixes a bug when saving through smbfs. * smbmount a Windows shared directory, save a structured storage file * to that dir: crash. * * The problem is that the SetFilePointer-SetEndOfFile combo below * doesn't always succeed. The file is not grown. It seems like the * operation is cached. By doing the WriteFile, the file is actually * grown on disk. * This hack is only needed when saving to smbfs. */ memset(buf, '0', 10); SetFilePointer(This->hfile, newSize.u.LowPart, NULL, FILE_BEGIN); WriteFile(This->hfile, buf, 10, NULL, NULL); /* * END HACK */ /* * set the new end of file */ SetFilePointer(This->hfile, newSize.u.LowPart, NULL, FILE_BEGIN); SetEndOfFile(This->hfile); /* * re-create the file mapping object */ This->hfilemap = CreateFileMappingA(This->hfile, NULL, This->flProtect, 0, 0, NULL); } else { GlobalUnlock(This->hbytearray); /* * Resize the byte array object. */ ILockBytes_SetSize(This->pLkbyt, newSize); /* * Re-acquire the handle, it may have changed. */ GetHGlobalFromILockBytes(This->pLkbyt, &This->hbytearray); This->pbytearray = GlobalLock(This->hbytearray); } This->filesize.u.LowPart = newSize.u.LowPart; This->filesize.u.HighPart = newSize.u.HighPart; BIGBLOCKFILE_RemapAllMappedPages(This); } /****************************************************************************** * BIGBLOCKFILE_GetSize * * Returns the size of the file. * */ ULARGE_INTEGER BIGBLOCKFILE_GetSize(LPBIGBLOCKFILE This) { return This->filesize; } /****************************************************************************** * BIGBLOCKFILE_AccessCheck [PRIVATE] * * block_index is the index within the page. */ static BOOL BIGBLOCKFILE_AccessCheck(MappedPage *page, ULONG block_index, DWORD desired_access) { assert(block_index < BLOCKS_PER_PAGE); if (desired_access == FILE_MAP_READ) { if (BIGBLOCKFILE_TestBit(&page->writable_blocks, block_index)) return FALSE; BIGBLOCKFILE_SetBit(&page->readable_blocks, block_index); } else { assert(desired_access == FILE_MAP_WRITE); if (BIGBLOCKFILE_TestBit(&page->readable_blocks, block_index)) return FALSE; BIGBLOCKFILE_SetBit(&page->writable_blocks, block_index); } return TRUE; } /****************************************************************************** * BIGBLOCKFILE_GetBigBlockPointer [PRIVATE] * * Returns a pointer to the specified block. */ static void* BIGBLOCKFILE_GetBigBlockPointer( LPBIGBLOCKFILE This, ULONG block_index, DWORD desired_access) { DWORD page_index = block_index / BLOCKS_PER_PAGE; DWORD block_on_page = block_index % BLOCKS_PER_PAGE; MappedPage *page = BIGBLOCKFILE_GetMappedView(This, page_index); if (!page || !page->lpBytes) return NULL; if (!BIGBLOCKFILE_AccessCheck(page, block_on_page, desired_access)) { BIGBLOCKFILE_ReleaseMappedPage(This, page); return NULL; } return (LPBYTE)page->lpBytes + (block_on_page * This->blocksize); } /****************************************************************************** * BIGBLOCKFILE_GetMappedPageFromPointer [PRIVATE] * * pBlock is a pointer to a block on a page. * The page has to be on the in-use list. (As oppsed to the victim list.) * * Does not increment the usage count. */ static MappedPage *BIGBLOCKFILE_GetPageFromPointer(LPBIGBLOCKFILE This, void *pBlock) { MappedPage *page; for (page = This->maplist; page != NULL; page = page->next) { if ((LPBYTE)pBlock >= (LPBYTE)page->lpBytes && (LPBYTE)pBlock <= (LPBYTE)page->lpBytes + PAGE_SIZE) break; } return page; } /****************************************************************************** * BIGBLOCKFILE_FindPageInList [PRIVATE] * */ static MappedPage *BIGBLOCKFILE_FindPageInList(MappedPage *head, ULONG page_index) { for (; head != NULL; head = head->next) { if (head->page_index == page_index) { InterlockedIncrement(&head->refcnt); break; } } return head; } static void BIGBLOCKFILE_UnlinkPage(MappedPage *page) { if (page->next) page->next->prev = page->prev; if (page->prev) page->prev->next = page->next; } static void BIGBLOCKFILE_LinkHeadPage(MappedPage **head, MappedPage *page) { if (*head) (*head)->prev = page; page->next = *head; page->prev = NULL; *head = page; } /****************************************************************************** * BIGBLOCKFILE_GetMappedView [PRIVATE] * * Gets the page requested if it is already mapped. * If it's not already mapped, this method will map it */ static void * BIGBLOCKFILE_GetMappedView( LPBIGBLOCKFILE This, DWORD page_index) { MappedPage *page; page = BIGBLOCKFILE_FindPageInList(This->maplist, page_index); if (!page) { page = BIGBLOCKFILE_FindPageInList(This->victimhead, page_index); if (page) { This->num_victim_pages--; BIGBLOCKFILE_Zero(&page->readable_blocks); BIGBLOCKFILE_Zero(&page->writable_blocks); } } if (page) { /* If the page is not already at the head of the list, move * it there. (Also moves pages from victim to main list.) */ if (This->maplist != page) { if (This->victimhead == page) This->victimhead = page->next; if (This->victimtail == page) This->victimtail = page->prev; BIGBLOCKFILE_UnlinkPage(page); BIGBLOCKFILE_LinkHeadPage(&This->maplist, page); } return page; } page = BIGBLOCKFILE_CreatePage(This, page_index); if (!page) return NULL; BIGBLOCKFILE_LinkHeadPage(&This->maplist, page); return page; } static BOOL BIGBLOCKFILE_MapPage(LPBIGBLOCKFILE This, MappedPage *page) { DWORD lowoffset = PAGE_SIZE * page->page_index; if (This->fileBased) { DWORD numBytesToMap; DWORD desired_access; if( !This->hfilemap ) return FALSE; if (lowoffset + PAGE_SIZE > This->filesize.u.LowPart) numBytesToMap = This->filesize.u.LowPart - lowoffset; else numBytesToMap = PAGE_SIZE; if (This->flProtect == PAGE_READONLY) desired_access = FILE_MAP_READ; else desired_access = FILE_MAP_WRITE; page->lpBytes = MapViewOfFile(This->hfilemap, desired_access, 0, lowoffset, numBytesToMap); } else { page->lpBytes = (LPBYTE)This->pbytearray + lowoffset; } TRACE("mapped page %lu to %p\n", page->page_index, page->lpBytes); return page->lpBytes != NULL; } static MappedPage *BIGBLOCKFILE_CreatePage(LPBIGBLOCKFILE This, ULONG page_index) { MappedPage *page; page = HeapAlloc(GetProcessHeap(), 0, sizeof(MappedPage)); if (page == NULL) return NULL; page->page_index = page_index; page->refcnt = 1; page->next = NULL; page->prev = NULL; BIGBLOCKFILE_MapPage(This, page); BIGBLOCKFILE_Zero(&page->readable_blocks); BIGBLOCKFILE_Zero(&page->writable_blocks); return page; } static void BIGBLOCKFILE_UnmapPage(LPBIGBLOCKFILE This, MappedPage *page) { TRACE("%ld at %p\n", page->page_index, page->lpBytes); if (page->refcnt > 0) ERR("unmapping inuse page %p\n", page->lpBytes); if (This->fileBased && page->lpBytes) UnmapViewOfFile(page->lpBytes); page->lpBytes = NULL; } static void BIGBLOCKFILE_DeletePage(LPBIGBLOCKFILE This, MappedPage *page) { BIGBLOCKFILE_UnmapPage(This, page); HeapFree(GetProcessHeap(), 0, page); } /****************************************************************************** * BIGBLOCKFILE_ReleaseMappedPage [PRIVATE] * * Decrements the reference count of the mapped page. */ static void BIGBLOCKFILE_ReleaseMappedPage( LPBIGBLOCKFILE This, MappedPage *page) { assert(This != NULL); assert(page != NULL); /* If the page is no longer refenced, move it to the victim list. * If the victim list is too long, kick somebody off. */ if (!InterlockedDecrement(&page->refcnt)) { if (This->maplist == page) This->maplist = page->next; BIGBLOCKFILE_UnlinkPage(page); if (MAX_VICTIM_PAGES > 0) { if (This->num_victim_pages >= MAX_VICTIM_PAGES) { MappedPage *victim = This->victimtail; if (victim) { This->victimtail = victim->prev; if (This->victimhead == victim) This->victimhead = victim->next; BIGBLOCKFILE_UnlinkPage(victim); BIGBLOCKFILE_DeletePage(This, victim); } } else This->num_victim_pages++; BIGBLOCKFILE_LinkHeadPage(&This->victimhead, page); if (This->victimtail == NULL) This->victimtail = page; } else BIGBLOCKFILE_DeletePage(This, page); } } static void BIGBLOCKFILE_DeleteList(LPBIGBLOCKFILE This, MappedPage *list) { while (list != NULL) { MappedPage *next = list->next; BIGBLOCKFILE_DeletePage(This, list); list = next; } } /****************************************************************************** * BIGBLOCKFILE_FreeAllMappedPages [PRIVATE] * * Unmap all currently mapped pages. * Empty mapped pages list. */ static void BIGBLOCKFILE_FreeAllMappedPages( LPBIGBLOCKFILE This) { BIGBLOCKFILE_DeleteList(This, This->maplist); BIGBLOCKFILE_DeleteList(This, This->victimhead); This->maplist = NULL; This->victimhead = NULL; This->victimtail = NULL; This->num_victim_pages = 0; } static void BIGBLOCKFILE_UnmapList(LPBIGBLOCKFILE This, MappedPage *list) { for (; list != NULL; list = list->next) { BIGBLOCKFILE_UnmapPage(This, list); } } static void BIGBLOCKFILE_UnmapAllMappedPages(LPBIGBLOCKFILE This) { BIGBLOCKFILE_UnmapList(This, This->maplist); BIGBLOCKFILE_UnmapList(This, This->victimhead); } static void BIGBLOCKFILE_RemapList(LPBIGBLOCKFILE This, MappedPage *list) { while (list != NULL) { MappedPage *next = list->next; if (list->page_index * PAGE_SIZE > This->filesize.u.LowPart) { TRACE("discarding %lu\n", list->page_index); /* page is entirely outside of the file, delete it */ BIGBLOCKFILE_UnlinkPage(list); BIGBLOCKFILE_DeletePage(This, list); } else { /* otherwise, remap it */ BIGBLOCKFILE_MapPage(This, list); } list = next; } } static void BIGBLOCKFILE_RemapAllMappedPages(LPBIGBLOCKFILE This) { BIGBLOCKFILE_RemapList(This, This->maplist); BIGBLOCKFILE_RemapList(This, This->victimhead); } /**************************************************************************** * BIGBLOCKFILE_GetProtectMode * * This function will return a protection mode flag for a file-mapping object * from the open flags of a file. */ static DWORD BIGBLOCKFILE_GetProtectMode(DWORD openFlags) { if (openFlags & (STGM_WRITE | STGM_READWRITE)) return PAGE_READWRITE; else return PAGE_READONLY; }