blob: 0e1c2b9ae84fbf54cd0282c44b627dad8746f203 [file] [log] [blame]
/******************************************************************************
*
* 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 <assert.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include "winbase.h"
#include "winerror.h"
#include "wine/obj_storage.h"
#include "storage32.h"
/***********************************************************
* 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);
/******************************************************************************
* 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,
DWORD openFlags,
ULONG blocksize)
{
LPBIGBLOCKFILE This;
This = (LPBIGBLOCKFILE)HeapAlloc(GetProcessHeap(), 0, sizeof(BigBlockFile));
if (This == NULL)
return NULL;
This->hfile = hFile;
if (This->hfile == INVALID_HANDLE_VALUE)
{
HeapFree(GetProcessHeap(), 0, This);
return NULL;
}
This->flProtect = BIGBLOCKFILE_GetProtectMode(openFlags);
/* create the file mapping object
*/
This->hfilemap = CreateFileMappingA(This->hfile,
NULL,
This->flProtect,
0, 0,
NULL);
if (This->hfilemap == NULL)
{
CloseHandle(This->hfile);
HeapFree(GetProcessHeap(), 0, This);
return NULL;
}
/* initialize this
*/
This->filesize.LowPart = GetFileSize(This->hfile, NULL);
This->blocksize = blocksize;
/* create the mapped pages list
*/
This->maplisthead = HeapAlloc(GetProcessHeap(), 0, sizeof(MappedPage));
if (This->maplisthead == NULL)
{
CloseHandle(This->hfilemap);
CloseHandle(This->hfile);
HeapFree(GetProcessHeap(), 0, This);
return NULL;
}
This->maplisthead->next = NULL;
/* initialize the block list
*/
This->headblock = NULL;
return This;
}
/******************************************************************************
* BIGBLOCKFILE_Destructor
*
* Destructor. Clean up, free memory.
*/
void BIGBLOCKFILE_Destructor(
LPBIGBLOCKFILE This)
{
/* 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);
/* 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;
/*
* 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;
/*
* 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);
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 page_num, 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
*/
/* 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);
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_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;
}