Rearrange winver detection code and cache the winver value we
[wine.git] / ole / stg_bigblockfile.c
blob564bc64c89bbb22a3196d8b66be5eff98cc976cf
1 /******************************************************************************
3 * BigBlockFile
5 * This is the implementation of a file that consists of blocks of
6 * a predetermined size.
7 * This class is used in the Compound File implementation of the
8 * IStorage and IStream interfaces. It provides the functionality
9 * to read and write any blocks in the file as well as setting and
10 * obtaining the size of the file.
11 * The blocks are indexed sequentially from the start of the file
12 * starting with -1.
14 * TODO:
15 * - Support for a transacted mode
17 * Copyright 1999 Thuy Nguyen
21 #include <assert.h>
22 #include <stdlib.h>
23 #include <stdio.h>
24 #include <string.h>
26 #include "winbase.h"
27 #include "winerror.h"
28 #include "wine/obj_storage.h"
29 #include "ole2.h"
31 #include "storage32.h"
33 #include "debug.h"
35 DEFAULT_DEBUG_CHANNEL(storage)
37 /***********************************************************
38 * Data structures used internally by the BigBlockFile
39 * class.
42 /***
43 * Itdentifies a single big block and the related
44 * information
46 struct BigBlock
48 BigBlock * next;
49 DWORD index;
50 DWORD access_mode;
51 LPVOID lpBlock;
54 /***
55 * This structure identifies the paged that are mapped
56 * from the file and their position in memory. It is
57 * also used to hold a reference count to those pages.
59 struct MappedPage
61 MappedPage * next;
62 DWORD number;
63 int ref;
64 LPVOID lpBytes;
67 #define PAGE_SIZE 131072
68 #define BLOCKS_PER_PAGE 256
70 #define NUMBER_OF_MAPPED_PAGES 100
72 /***********************************************************
73 * Prototypes for private methods
75 static void* BIGBLOCKFILE_GetMappedView(LPBIGBLOCKFILE This,
76 DWORD pagenum,
77 DWORD desired_access);
78 static void BIGBLOCKFILE_ReleaseMappedPage(LPBIGBLOCKFILE This,
79 DWORD pagenum,
80 DWORD access);
81 static void BIGBLOCKFILE_FreeAllMappedPages(LPBIGBLOCKFILE This);
82 static void* BIGBLOCKFILE_GetBigBlockPointer(LPBIGBLOCKFILE This,
83 ULONG index,
84 DWORD desired_access);
85 static BigBlock* BIGBLOCKFILE_GetBigBlockFromPointer(LPBIGBLOCKFILE This,
86 void* pBlock);
87 static void BIGBLOCKFILE_RemoveBlock(LPBIGBLOCKFILE This,
88 ULONG index);
89 static BigBlock* BIGBLOCKFILE_AddBigBlock(LPBIGBLOCKFILE This,
90 ULONG index);
91 static BigBlock* BIGBLOCKFILE_CreateBlock(ULONG index);
92 static DWORD BIGBLOCKFILE_GetProtectMode(DWORD openFlags);
93 static BOOL BIGBLOCKFILE_FileInit(LPBIGBLOCKFILE This, HANDLE hFile);
94 static BOOL BIGBLOCKFILE_MemInit(LPBIGBLOCKFILE This, ILockBytes* plkbyt);
95 static void BIGBLOCKFILE_RemoveAllBlocks(LPBIGBLOCKFILE This);
97 /******************************************************************************
98 * BIGBLOCKFILE_Construct
100 * Construct a big block file. Create the file mapping object.
101 * Create the read only mapped pages list, the writeable mapped page list
102 * and the blocks in use list.
104 BigBlockFile * BIGBLOCKFILE_Construct(
105 HANDLE hFile,
106 ILockBytes* pLkByt,
107 DWORD openFlags,
108 ULONG blocksize,
109 BOOL fileBased)
111 LPBIGBLOCKFILE This;
113 This = (LPBIGBLOCKFILE)HeapAlloc(GetProcessHeap(), 0, sizeof(BigBlockFile));
115 if (This == NULL)
116 return NULL;
118 This->fileBased = fileBased;
120 This->flProtect = BIGBLOCKFILE_GetProtectMode(openFlags);
122 This->blocksize = blocksize;
124 /* initialize the block list
126 This->headblock = NULL;
128 if (This->fileBased)
130 if (!BIGBLOCKFILE_FileInit(This, hFile))
132 HeapFree(GetProcessHeap(), 0, This);
133 return NULL;
136 else
138 if (!BIGBLOCKFILE_MemInit(This, pLkByt))
140 HeapFree(GetProcessHeap(), 0, This);
141 return NULL;
145 return This;
148 /******************************************************************************
149 * BIGBLOCKFILE_FileInit
151 * Initialize a big block object supported by a file.
153 static BOOL BIGBLOCKFILE_FileInit(LPBIGBLOCKFILE This, HANDLE hFile)
155 This->pLkbyt = NULL;
156 This->hbytearray = 0;
157 This->pbytearray = NULL;
159 This->hfile = hFile;
161 if (This->hfile == INVALID_HANDLE_VALUE)
162 return FALSE;
164 /* create the file mapping object
166 This->hfilemap = CreateFileMappingA(This->hfile,
167 NULL,
168 This->flProtect,
169 0, 0,
170 NULL);
172 if (This->hfilemap == NULL)
174 CloseHandle(This->hfile);
175 return FALSE;
178 This->filesize.LowPart = GetFileSize(This->hfile, NULL);
180 /* create the mapped pages list
182 This->maplisthead = HeapAlloc(GetProcessHeap(), 0, sizeof(MappedPage));
184 if (This->maplisthead == NULL)
186 CloseHandle(This->hfilemap);
187 CloseHandle(This->hfile);
188 return FALSE;
191 This->maplisthead->next = NULL;
193 return TRUE;
196 /******************************************************************************
197 * BIGBLOCKFILE_MemInit
199 * Initialize a big block object supported by an ILockBytes on HGLOABL.
201 static BOOL BIGBLOCKFILE_MemInit(LPBIGBLOCKFILE This, ILockBytes* plkbyt)
203 This->hfile = 0;
204 This->hfilemap = NULL;
205 This->maplisthead = NULL;
208 * Retrieve the handle to the byte array from the LockByte object.
210 if (GetHGlobalFromILockBytes(plkbyt, &(This->hbytearray)) != S_OK)
212 FIXME(storage, "May not be an ILockBytes on HGLOBAL\n");
213 return FALSE;
216 This->pLkbyt = plkbyt;
219 * Increment the reference count of the ILockByte object since
220 * we're keeping a reference to it.
222 ILockBytes_AddRef(This->pLkbyt);
224 This->filesize.LowPart = GlobalSize(This->hbytearray);
225 This->filesize.HighPart = 0;
227 This->pbytearray = GlobalLock(This->hbytearray);
229 return TRUE;
232 /******************************************************************************
233 * BIGBLOCKFILE_Destructor
235 * Destructor. Clean up, free memory.
237 void BIGBLOCKFILE_Destructor(
238 LPBIGBLOCKFILE This)
240 if (This->fileBased)
242 /* unmap all views and destroy the mapped page list
244 BIGBLOCKFILE_FreeAllMappedPages(This);
245 HeapFree(GetProcessHeap(), 0, This->maplisthead);
247 /* close all open handles
249 CloseHandle(This->hfilemap);
250 CloseHandle(This->hfile);
252 else
254 GlobalUnlock(This->hbytearray);
255 ILockBytes_Release(This->pLkbyt);
259 * Destroy the blocks list.
261 BIGBLOCKFILE_RemoveAllBlocks(This);
263 /* destroy this
265 HeapFree(GetProcessHeap(), 0, This);
268 /******************************************************************************
269 * BIGBLOCKFILE_GetROBigBlock
271 * Returns the specified block in read only mode.
272 * Will return NULL if the block doesn't exists.
274 void* BIGBLOCKFILE_GetROBigBlock(
275 LPBIGBLOCKFILE This,
276 ULONG index)
279 * block index starts at -1
280 * translate to zero based index
282 if (index == 0xffffffff)
283 index = 0;
284 else
285 index++;
288 * validate the block index
291 if ((This->blocksize * (index + 1)) >
292 (This->filesize.LowPart +
293 (This->blocksize - (This->filesize.LowPart % This->blocksize))))
294 return 0;
296 return BIGBLOCKFILE_GetBigBlockPointer(This, index, FILE_MAP_READ);
299 /******************************************************************************
300 * BIGBLOCKFILE_GetBigBlock
302 * Returns the specified block.
303 * Will grow the file if necessary.
305 void* BIGBLOCKFILE_GetBigBlock(LPBIGBLOCKFILE This, ULONG index)
308 * block index starts at -1
309 * translate to zero based index
311 if (index == 0xffffffff)
312 index = 0;
313 else
314 index++;
317 * make sure that the block physically exists
319 if ((This->blocksize * (index + 1)) > This->filesize.LowPart)
321 ULARGE_INTEGER newSize;
323 newSize.HighPart = 0;
324 newSize.LowPart = This->blocksize * (index + 1);
326 BIGBLOCKFILE_SetSize(This, newSize);
329 return BIGBLOCKFILE_GetBigBlockPointer(This, index, FILE_MAP_WRITE);
332 /******************************************************************************
333 * BIGBLOCKFILE_ReleaseBigBlock
335 * Releases the specified block.
337 void BIGBLOCKFILE_ReleaseBigBlock(LPBIGBLOCKFILE This, void *pBlock)
339 DWORD page_num;
340 BigBlock* theBigBlock;
342 if (pBlock == NULL)
343 return;
346 * get the block from the block list
348 theBigBlock = BIGBLOCKFILE_GetBigBlockFromPointer(This, pBlock);
350 if (theBigBlock == NULL)
351 return;
353 if (This->fileBased)
356 * find out which page this block is in
358 page_num = theBigBlock->index / BLOCKS_PER_PAGE;
361 * release this page
363 BIGBLOCKFILE_ReleaseMappedPage(This, page_num, theBigBlock->access_mode);
367 * remove block from list
369 BIGBLOCKFILE_RemoveBlock(This, theBigBlock->index);
372 /******************************************************************************
373 * BIGBLOCKFILE_SetSize
375 * Sets the size of the file.
378 void BIGBLOCKFILE_SetSize(LPBIGBLOCKFILE This, ULARGE_INTEGER newSize)
380 if (This->filesize.LowPart == newSize.LowPart)
381 return;
383 if (This->fileBased)
386 * unmap all views, must be done before call to SetEndFile
388 BIGBLOCKFILE_FreeAllMappedPages(This);
391 * close file-mapping object, must be done before call to SetEndFile
393 CloseHandle(This->hfilemap);
394 This->hfilemap = NULL;
397 * set the new end of file
399 SetFilePointer(This->hfile, newSize.LowPart, NULL, FILE_BEGIN);
400 SetEndOfFile(This->hfile);
403 * re-create the file mapping object
405 This->hfilemap = CreateFileMappingA(This->hfile,
406 NULL,
407 This->flProtect,
408 0, 0,
409 NULL);
411 else
413 GlobalUnlock(This->hbytearray);
416 * Resize the byte array object.
418 ILockBytes_SetSize(This->pLkbyt, newSize);
421 * Re-acquire the handle, it may have changed.
423 GetHGlobalFromILockBytes(This->pLkbyt, &This->hbytearray);
424 This->pbytearray = GlobalLock(This->hbytearray);
428 * empty the blocks list.
430 BIGBLOCKFILE_RemoveAllBlocks(This);
432 This->filesize.LowPart = newSize.LowPart;
433 This->filesize.HighPart = newSize.HighPart;
436 /******************************************************************************
437 * BIGBLOCKFILE_GetSize
439 * Returns the size of the file.
442 ULARGE_INTEGER BIGBLOCKFILE_GetSize(LPBIGBLOCKFILE This)
444 return This->filesize;
447 /******************************************************************************
448 * BIGBLOCKFILE_GetBigBlockPointer [PRIVATE]
450 * Returns a pointer to the specified block.
452 static void* BIGBLOCKFILE_GetBigBlockPointer(
453 LPBIGBLOCKFILE This,
454 ULONG index,
455 DWORD desired_access)
457 DWORD block_num;
458 void * pBytes;
459 BigBlock *aBigBlock;
461 /* get the big block from the list or add it to the list
463 aBigBlock = BIGBLOCKFILE_AddBigBlock(This, index);
465 if (aBigBlock == NULL)
466 return NULL;
468 /* we already have an address for this block
470 if (aBigBlock->lpBlock != NULL)
472 /* make sure the desired access matches what we already have
474 if (aBigBlock->access_mode == desired_access)
475 return aBigBlock->lpBlock;
476 else
477 return NULL;
481 * else aBigBlock->lpBigBlock == NULL, it's a new block
484 if (This->fileBased)
486 DWORD page_num;
488 /* find out which page this block is in
490 page_num = index / BLOCKS_PER_PAGE;
492 /* offset of the block in the page
494 block_num = index % BLOCKS_PER_PAGE;
496 /* get a pointer to the first byte in the page
498 pBytes = BIGBLOCKFILE_GetMappedView(This, page_num, desired_access);
500 else
502 pBytes = This->pbytearray;
503 block_num = index;
506 if (pBytes == NULL)
507 return NULL;
509 /* initialize block
511 aBigBlock->lpBlock = ((BYTE*)pBytes + (block_num*This->blocksize));
512 aBigBlock->access_mode = desired_access;
514 return aBigBlock->lpBlock;
517 /******************************************************************************
518 * BIGBLOCKFILE_CreateBlock [PRIVATE]
520 * Creates a node of the blocks list.
522 static BigBlock* BIGBLOCKFILE_CreateBlock(
523 ULONG index)
525 BigBlock *newBigBlock;
527 /* create new list node
529 newBigBlock = HeapAlloc(GetProcessHeap(), 0, sizeof(BigBlock));
531 if (newBigBlock == NULL)
532 return NULL;
534 /* initialize node
536 newBigBlock->index = index;
537 newBigBlock->lpBlock = NULL;
539 return newBigBlock;
542 /******************************************************************************
543 * BIGBLOCKFILE_AddBigBlock [PRIVATE]
545 * Returns the specified block from the blocks list.
546 * If the block is not found in the list, we will create it and add it to the
547 * list.
549 static BigBlock* BIGBLOCKFILE_AddBigBlock(
550 LPBIGBLOCKFILE This,
551 ULONG index)
553 BigBlock *current = This->headblock;
554 BigBlock *newBigBlock;
556 if (current == NULL) /* empty list */
558 newBigBlock = BIGBLOCKFILE_CreateBlock(index);
560 if (newBigBlock != NULL)
562 newBigBlock->next = NULL;
563 This->headblock = newBigBlock;
566 return newBigBlock;
568 else
571 * special handling for head of the list
574 if (current->index == index) /* it's already here */
575 return current;
576 else if (current->index > index) /* insertion at head of the list */
578 newBigBlock = BIGBLOCKFILE_CreateBlock(index);
580 if (newBigBlock != NULL)
582 newBigBlock->next = current;
583 This->headblock = newBigBlock;
586 return newBigBlock;
590 /* iterate through rest the list
592 while (current->next != NULL)
594 if (current->next->index == index) /* found it */
596 return current->next;
598 else if (current->next->index > index) /* it's not in the list */
600 newBigBlock = BIGBLOCKFILE_CreateBlock(index);
602 if (newBigBlock != NULL)
604 newBigBlock->next = current->next;
605 current->next = newBigBlock;
608 return newBigBlock;
610 else
611 current = current->next;
615 * insertion at end of the list
617 if (current->next == NULL)
619 newBigBlock = BIGBLOCKFILE_CreateBlock(index);
621 if (newBigBlock != NULL)
623 newBigBlock->next = NULL;
624 current->next = newBigBlock;
627 return newBigBlock;
630 return NULL;
633 /******************************************************************************
634 * BIGBLOCKFILE_RemoveAllBlocks [PRIVATE]
636 * Removes all blocks from the blocks list.
638 static void BIGBLOCKFILE_RemoveAllBlocks(
639 LPBIGBLOCKFILE This)
641 BigBlock *current;
643 while (This->headblock != NULL)
645 current = This->headblock;
646 This->headblock = current->next;
647 HeapFree(GetProcessHeap(), 0, current);
651 /******************************************************************************
652 * BIGBLOCKFILE_RemoveBlock [PRIVATE]
654 * Removes the specified block from the blocks list.
656 static void BIGBLOCKFILE_RemoveBlock(
657 LPBIGBLOCKFILE This,
658 ULONG index)
660 BigBlock *current = This->headblock;
663 * empty list
665 if (current == NULL)
666 return;
669 *special case: removing head of list
671 if (current->index == index)
674 * set new head free the old one
676 This->headblock = current->next;
677 HeapFree(GetProcessHeap(), 0, current);
679 return;
683 * iterate through rest of the list
685 while (current->next != NULL)
687 if (current->next->index == index) /* found it */
690 * unlink the block and free the block
692 current->next = current->next->next;
693 HeapFree(GetProcessHeap(), 0, current->next);
695 return;
697 else
699 /* next node
701 current = current->next;
706 /******************************************************************************
707 * BIGBLOCKFILE_GetBigBlockFromPointer [PRIVATE]
709 * Given a block pointer, this will return the corresponding block
710 * from the blocks list.
712 static BigBlock* BIGBLOCKFILE_GetBigBlockFromPointer(
713 LPBIGBLOCKFILE This,
714 void* pBlock)
716 BigBlock *current = This->headblock;
718 while (current != NULL)
720 if (current->lpBlock == pBlock)
722 break;
724 else
725 current = current->next;
728 return current;
731 /******************************************************************************
732 * BIGBLOCKFILE_GetMappedView [PRIVATE]
734 * Gets the page requested if it is already mapped.
735 * If it's not already mapped, this method will map it
737 static void * BIGBLOCKFILE_GetMappedView(
738 LPBIGBLOCKFILE This,
739 DWORD pagenum,
740 DWORD desired_access)
742 MappedPage* current = This->maplisthead;
743 ULONG count = 1;
744 BOOL found = FALSE;
746 assert(This->maplisthead != NULL);
749 * Search for the page in the list.
751 while ((found == FALSE) && (current->next != NULL))
753 if (current->next->number == pagenum)
755 found = TRUE;
758 * If it's not already at the head of the list
759 * move it there.
761 if (current != This->maplisthead)
763 MappedPage* temp = current->next;
765 current->next = current->next->next;
767 temp->next = This->maplisthead->next;
768 This->maplisthead->next = temp;
773 * The list is full and we haven't found it.
774 * Free the last element of the list because we'll add a new
775 * one at the head.
777 if ((found == FALSE) &&
778 (count >= NUMBER_OF_MAPPED_PAGES) &&
779 (current->next != NULL))
781 UnmapViewOfFile(current->next->lpBytes);
783 HeapFree(GetProcessHeap(), 0, current->next);
784 current->next = NULL;
787 if (current->next != NULL)
788 current = current->next;
790 count++;
794 * Add the page at the head of the list.
796 if (found == FALSE)
798 MappedPage* newMappedPage;
799 DWORD numBytesToMap;
800 DWORD hioffset = 0;
801 DWORD lowoffset = PAGE_SIZE * pagenum;
803 newMappedPage = HeapAlloc(GetProcessHeap(), 0, sizeof(MappedPage));
805 if (newMappedPage == NULL)
806 return NULL;
808 newMappedPage->number = pagenum;
809 newMappedPage->ref = 0;
811 newMappedPage->next = This->maplisthead->next;
812 This->maplisthead->next = newMappedPage;
814 if (((pagenum + 1) * PAGE_SIZE) > This->filesize.LowPart)
815 numBytesToMap = This->filesize.LowPart - (pagenum * PAGE_SIZE);
816 else
817 numBytesToMap = PAGE_SIZE;
819 if (This->flProtect == PAGE_READONLY)
820 desired_access = FILE_MAP_READ;
821 else
822 desired_access = FILE_MAP_WRITE;
824 newMappedPage->lpBytes = MapViewOfFile(This->hfilemap,
825 desired_access,
826 hioffset,
827 lowoffset,
828 numBytesToMap);
832 * The page we want should now be at the head of the list.
834 assert(This->maplisthead->next != NULL);
836 current = This->maplisthead->next;
837 current->ref++;
839 return current->lpBytes;
842 /******************************************************************************
843 * BIGBLOCKFILE_ReleaseMappedPage [PRIVATE]
845 * Decrements the reference count of the mapped page.
847 static void BIGBLOCKFILE_ReleaseMappedPage(
848 LPBIGBLOCKFILE This,
849 DWORD pagenum,
850 DWORD access)
852 MappedPage* previous = This->maplisthead;
853 MappedPage* current;
855 assert(This->maplisthead->next != NULL);
857 current = previous->next;
859 /* search for the page in the list
861 while (current != NULL)
863 if (current->number == pagenum)
865 /* decrement the reference count
867 current->ref--;
868 return;
870 else
872 previous = current;
873 current = current->next;
878 /******************************************************************************
879 * BIGBLOCKFILE_FreeAllMappedPages [PRIVATE]
881 * Unmap all currently mapped pages.
882 * Empty mapped pages list.
884 static void BIGBLOCKFILE_FreeAllMappedPages(
885 LPBIGBLOCKFILE This)
887 MappedPage * current = This->maplisthead->next;
889 while (current != NULL)
891 /* Unmap views.
893 UnmapViewOfFile(current->lpBytes);
895 /* Free the nodes.
897 This->maplisthead->next = current->next;
898 HeapFree(GetProcessHeap(), 0, current);
900 current = This->maplisthead->next;
904 /****************************************************************************
905 * BIGBLOCKFILE_GetProtectMode
907 * This function will return a protection mode flag for a file-mapping object
908 * from the open flags of a file.
910 static DWORD BIGBLOCKFILE_GetProtectMode(DWORD openFlags)
912 DWORD flProtect = PAGE_READONLY;
913 BOOL bSTGM_WRITE = ((openFlags & STGM_WRITE) == STGM_WRITE);
914 BOOL bSTGM_READWRITE = ((openFlags & STGM_READWRITE) == STGM_READWRITE);
915 BOOL bSTGM_READ = ! (bSTGM_WRITE || bSTGM_READWRITE);
917 if (bSTGM_READ)
918 flProtect = PAGE_READONLY;
920 if ((bSTGM_WRITE) || (bSTGM_READWRITE))
921 flProtect = PAGE_READWRITE;
923 return flProtect;