/****************************************************************************** * * 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 * */ #include #include #include #include #include "winbase.h" #include "winerror.h" #include "wine/obj_storage.h" #include "ole2.h" #include "storage32.h" #include "debug.h" DEFAULT_DEBUG_CHANNEL(storage) /*********************************************************** * Data structures used internally by the BigBlockFile * class. */ /*** * Itdentifies a single big block and the related * information */ struct BigBlock { BigBlock * next; DWORD index; DWORD access_mode; LPVOID lpBlock; }; /*** * 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. */ struct MappedPage { MappedPage * next; DWORD number; int ref; LPVOID lpBytes; }; #define PAGE_SIZE 131072 #define BLOCKS_PER_PAGE 256 #define NUMBER_OF_MAPPED_PAGES 100 /*********************************************************** * Prototypes for private methods */ static void* BIGBLOCKFILE_GetMappedView(LPBIGBLOCKFILE This, DWORD pagenum, DWORD desired_access); static void BIGBLOCKFILE_ReleaseMappedPage(LPBIGBLOCKFILE This, DWORD pagenum, DWORD access); static void BIGBLOCKFILE_FreeAllMappedPages(LPBIGBLOCKFILE This); static void* BIGBLOCKFILE_GetBigBlockPointer(LPBIGBLOCKFILE This, ULONG index, DWORD desired_access); static BigBlock* BIGBLOCKFILE_GetBigBlockFromPointer(LPBIGBLOCKFILE This, void* pBlock); static void BIGBLOCKFILE_RemoveBlock(LPBIGBLOCKFILE This, ULONG index); static BigBlock* BIGBLOCKFILE_AddBigBlock(LPBIGBLOCKFILE This, ULONG index); static BigBlock* BIGBLOCKFILE_CreateBlock(ULONG index); static DWORD BIGBLOCKFILE_GetProtectMode(DWORD openFlags); static BOOL BIGBLOCKFILE_FileInit(LPBIGBLOCKFILE This, HANDLE hFile); static BOOL BIGBLOCKFILE_MemInit(LPBIGBLOCKFILE This, ILockBytes* plkbyt); static void BIGBLOCKFILE_RemoveAllBlocks(LPBIGBLOCKFILE This); /****************************************************************************** * BIGBLOCKFILE_Construct * * Construct a big block file. Create the file mapping object. * Create the read only mapped pages list, the writeable 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; /* initialize the block list */ This->headblock = NULL; 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; /* create the file mapping object */ This->hfilemap = CreateFileMappingA(This->hfile, NULL, This->flProtect, 0, 0, NULL); if (This->hfilemap == NULL) { CloseHandle(This->hfile); return FALSE; } This->filesize.LowPart = GetFileSize(This->hfile, NULL); /* create the mapped pages list */ This->maplisthead = HeapAlloc(GetProcessHeap(), 0, sizeof(MappedPage)); if (This->maplisthead == NULL) { CloseHandle(This->hfilemap); CloseHandle(This->hfile); return FALSE; } This->maplisthead->next = NULL; 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 = NULL; This->maplisthead = NULL; /* * Retrieve the handle to the byte array from the LockByte object. */ if (GetHGlobalFromILockBytes(plkbyt, &(This->hbytearray)) != S_OK) { FIXME(storage, "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.LowPart = GlobalSize(This->hbytearray); This->filesize.HighPart = 0; This->pbytearray = GlobalLock(This->hbytearray); return TRUE; } /****************************************************************************** * BIGBLOCKFILE_Destructor * * Destructor. Clean up, free memory. */ void BIGBLOCKFILE_Destructor( LPBIGBLOCKFILE This) { if (This->fileBased) { /* unmap all views and destroy the mapped page list */ BIGBLOCKFILE_FreeAllMappedPages(This); HeapFree(GetProcessHeap(), 0, This->maplisthead); /* close all open handles */ CloseHandle(This->hfilemap); CloseHandle(This->hfile); } else { GlobalUnlock(This->hbytearray); ILockBytes_Release(This->pLkbyt); } /* * Destroy the blocks list. */ BIGBLOCKFILE_RemoveAllBlocks(This); /* 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)) > (This->filesize.LowPart + (This->blocksize - (This->filesize.LowPart % This->blocksize)))) return 0; 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.LowPart) { ULARGE_INTEGER newSize; newSize.HighPart = 0; newSize.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) { DWORD page_num; BigBlock* theBigBlock; if (pBlock == NULL) return; /* * get the block from the block list */ theBigBlock = BIGBLOCKFILE_GetBigBlockFromPointer(This, pBlock); if (theBigBlock == NULL) return; if (This->fileBased) { /* * find out which page this block is in */ page_num = theBigBlock->index / BLOCKS_PER_PAGE; /* * release this page */ BIGBLOCKFILE_ReleaseMappedPage(This, page_num, theBigBlock->access_mode); } /* * remove block from list */ BIGBLOCKFILE_RemoveBlock(This, theBigBlock->index); } /****************************************************************************** * BIGBLOCKFILE_SetSize * * Sets the size of the file. * */ void BIGBLOCKFILE_SetSize(LPBIGBLOCKFILE This, ULARGE_INTEGER newSize) { if (This->filesize.LowPart == newSize.LowPart) return; if (This->fileBased) { /* * unmap all views, must be done before call to SetEndFile */ BIGBLOCKFILE_FreeAllMappedPages(This); /* * close file-mapping object, must be done before call to SetEndFile */ CloseHandle(This->hfilemap); This->hfilemap = NULL; /* * set the new end of file */ SetFilePointer(This->hfile, newSize.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); } /* * empty the blocks list. */ BIGBLOCKFILE_RemoveAllBlocks(This); This->filesize.LowPart = newSize.LowPart; This->filesize.HighPart = newSize.HighPart; } /****************************************************************************** * BIGBLOCKFILE_GetSize * * Returns the size of the file. * */ ULARGE_INTEGER BIGBLOCKFILE_GetSize(LPBIGBLOCKFILE This) { return This->filesize; } /****************************************************************************** * BIGBLOCKFILE_GetBigBlockPointer [PRIVATE] * * Returns a pointer to the specified block. */ static void* BIGBLOCKFILE_GetBigBlockPointer( LPBIGBLOCKFILE This, ULONG index, DWORD desired_access) { DWORD block_num; void * pBytes; BigBlock *aBigBlock; /* get the big block from the list or add it to the list */ aBigBlock = BIGBLOCKFILE_AddBigBlock(This, index); if (aBigBlock == NULL) return NULL; /* we already have an address for this block */ if (aBigBlock->lpBlock != NULL) { /* make sure the desired access matches what we already have */ if (aBigBlock->access_mode == desired_access) return aBigBlock->lpBlock; else return NULL; } /* * else aBigBlock->lpBigBlock == NULL, it's a new block */ if (This->fileBased) { DWORD page_num; /* find out which page this block is in */ page_num = index / BLOCKS_PER_PAGE; /* offset of the block in the page */ block_num = index % BLOCKS_PER_PAGE; /* get a pointer to the first byte in the page */ pBytes = BIGBLOCKFILE_GetMappedView(This, page_num, desired_access); } else { pBytes = This->pbytearray; block_num = index; } if (pBytes == NULL) return NULL; /* initialize block */ aBigBlock->lpBlock = ((BYTE*)pBytes + (block_num*This->blocksize)); aBigBlock->access_mode = desired_access; return aBigBlock->lpBlock; } /****************************************************************************** * BIGBLOCKFILE_CreateBlock [PRIVATE] * * Creates a node of the blocks list. */ static BigBlock* BIGBLOCKFILE_CreateBlock( ULONG index) { BigBlock *newBigBlock; /* create new list node */ newBigBlock = HeapAlloc(GetProcessHeap(), 0, sizeof(BigBlock)); if (newBigBlock == NULL) return NULL; /* initialize node */ newBigBlock->index = index; newBigBlock->lpBlock = NULL; return newBigBlock; } /****************************************************************************** * BIGBLOCKFILE_AddBigBlock [PRIVATE] * * Returns the specified block from the blocks list. * If the block is not found in the list, we will create it and add it to the * list. */ static BigBlock* BIGBLOCKFILE_AddBigBlock( LPBIGBLOCKFILE This, ULONG index) { BigBlock *current = This->headblock; BigBlock *newBigBlock; if (current == NULL) /* empty list */ { newBigBlock = BIGBLOCKFILE_CreateBlock(index); if (newBigBlock != NULL) { newBigBlock->next = NULL; This->headblock = newBigBlock; } return newBigBlock; } else { /* * special handling for head of the list */ if (current->index == index) /* it's already here */ return current; else if (current->index > index) /* insertion at head of the list */ { newBigBlock = BIGBLOCKFILE_CreateBlock(index); if (newBigBlock != NULL) { newBigBlock->next = current; This->headblock = newBigBlock; } return newBigBlock; } } /* iterate through rest the list */ while (current->next != NULL) { if (current->next->index == index) /* found it */ { return current->next; } else if (current->next->index > index) /* it's not in the list */ { newBigBlock = BIGBLOCKFILE_CreateBlock(index); if (newBigBlock != NULL) { newBigBlock->next = current->next; current->next = newBigBlock; } return newBigBlock; } else current = current->next; } /* * insertion at end of the list */ if (current->next == NULL) { newBigBlock = BIGBLOCKFILE_CreateBlock(index); if (newBigBlock != NULL) { newBigBlock->next = NULL; current->next = newBigBlock; } return newBigBlock; } return NULL; } /****************************************************************************** * BIGBLOCKFILE_RemoveAllBlocks [PRIVATE] * * Removes all blocks from the blocks list. */ static void BIGBLOCKFILE_RemoveAllBlocks( LPBIGBLOCKFILE This) { BigBlock *current; while (This->headblock != NULL) { current = This->headblock; This->headblock = current->next; HeapFree(GetProcessHeap(), 0, current); } } /****************************************************************************** * BIGBLOCKFILE_RemoveBlock [PRIVATE] * * Removes the specified block from the blocks list. */ static void BIGBLOCKFILE_RemoveBlock( LPBIGBLOCKFILE This, ULONG index) { BigBlock *current = This->headblock; /* * empty list */ if (current == NULL) return; /* *special case: removing head of list */ if (current->index == index) { /* * set new head free the old one */ This->headblock = current->next; HeapFree(GetProcessHeap(), 0, current); return; } /* * iterate through rest of the list */ while (current->next != NULL) { if (current->next->index == index) /* found it */ { /* * unlink the block and free the block */ current->next = current->next->next; HeapFree(GetProcessHeap(), 0, current->next); return; } else { /* next node */ current = current->next; } } } /****************************************************************************** * BIGBLOCKFILE_GetBigBlockFromPointer [PRIVATE] * * Given a block pointer, this will return the corresponding block * from the blocks list. */ static BigBlock* BIGBLOCKFILE_GetBigBlockFromPointer( LPBIGBLOCKFILE This, void* pBlock) { BigBlock *current = This->headblock; while (current != NULL) { if (current->lpBlock == pBlock) { break; } else current = current->next; } return current; } /****************************************************************************** * 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 pagenum, DWORD desired_access) { MappedPage* current = This->maplisthead; ULONG count = 1; BOOL found = FALSE; assert(This->maplisthead != NULL); /* * Search for the page in the list. */ while ((found == FALSE) && (current->next != NULL)) { if (current->next->number == pagenum) { found = TRUE; /* * If it's not already at the head of the list * move it there. */ if (current != This->maplisthead) { MappedPage* temp = current->next; current->next = current->next->next; temp->next = This->maplisthead->next; This->maplisthead->next = temp; } } /* * The list is full and we haven't found it. * Free the last element of the list because we'll add a new * one at the head. */ if ((found == FALSE) && (count >= NUMBER_OF_MAPPED_PAGES) && (current->next != NULL)) { UnmapViewOfFile(current->next->lpBytes); HeapFree(GetProcessHeap(), 0, current->next); current->next = NULL; } if (current->next != NULL) current = current->next; count++; } /* * Add the page at the head of the list. */ if (found == FALSE) { MappedPage* newMappedPage; DWORD numBytesToMap; DWORD hioffset = 0; DWORD lowoffset = PAGE_SIZE * pagenum; newMappedPage = HeapAlloc(GetProcessHeap(), 0, sizeof(MappedPage)); if (newMappedPage == NULL) return NULL; newMappedPage->number = pagenum; newMappedPage->ref = 0; newMappedPage->next = This->maplisthead->next; This->maplisthead->next = newMappedPage; if (((pagenum + 1) * PAGE_SIZE) > This->filesize.LowPart) numBytesToMap = This->filesize.LowPart - (pagenum * PAGE_SIZE); else numBytesToMap = PAGE_SIZE; if (This->flProtect == PAGE_READONLY) desired_access = FILE_MAP_READ; else desired_access = FILE_MAP_WRITE; newMappedPage->lpBytes = MapViewOfFile(This->hfilemap, desired_access, hioffset, lowoffset, numBytesToMap); } /* * The page we want should now be at the head of the list. */ assert(This->maplisthead->next != NULL); current = This->maplisthead->next; current->ref++; return current->lpBytes; } /****************************************************************************** * BIGBLOCKFILE_ReleaseMappedPage [PRIVATE] * * Decrements the reference count of the mapped page. */ static void BIGBLOCKFILE_ReleaseMappedPage( LPBIGBLOCKFILE This, DWORD pagenum, DWORD access) { MappedPage* previous = This->maplisthead; MappedPage* current; assert(This->maplisthead->next != NULL); current = previous->next; /* search for the page in the list */ while (current != NULL) { if (current->number == pagenum) { /* decrement the reference count */ current->ref--; return; } else { previous = current; current = current->next; } } } /****************************************************************************** * BIGBLOCKFILE_FreeAllMappedPages [PRIVATE] * * Unmap all currently mapped pages. * Empty mapped pages list. */ static void BIGBLOCKFILE_FreeAllMappedPages( LPBIGBLOCKFILE This) { MappedPage * current = This->maplisthead->next; while (current != NULL) { /* Unmap views. */ UnmapViewOfFile(current->lpBytes); /* Free the nodes. */ This->maplisthead->next = current->next; HeapFree(GetProcessHeap(), 0, current); current = This->maplisthead->next; } } /**************************************************************************** * 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) { DWORD flProtect = PAGE_READONLY; BOOL bSTGM_WRITE = ((openFlags & STGM_WRITE) == STGM_WRITE); BOOL bSTGM_READWRITE = ((openFlags & STGM_READWRITE) == STGM_READWRITE); BOOL bSTGM_READ = ! (bSTGM_WRITE || bSTGM_READWRITE); if (bSTGM_READ) flProtect = PAGE_READONLY; if ((bSTGM_WRITE) || (bSTGM_READWRITE)) flProtect = PAGE_READWRITE; return flProtect; }