1 /******************************************************************************
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
15 * - Support for a transacted mode
17 * Copyright 1999 Thuy Nguyen
19 * This library is free software; you can redistribute it and/or
20 * modify it under the terms of the GNU Lesser General Public
21 * License as published by the Free Software Foundation; either
22 * version 2.1 of the License, or (at your option) any later version.
24 * This library is distributed in the hope that it will be useful,
25 * but WITHOUT ANY WARRANTY; without even the implied warranty of
26 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
27 * Lesser General Public License for more details.
29 * You should have received a copy of the GNU Lesser General Public
30 * License along with this library; if not, write to the Free Software
31 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
42 #define NONAMELESSUNION
43 #define NONAMELESSSTRUCT
52 #include "storage32.h"
54 #include "wine/debug.h"
56 WINE_DEFAULT_DEBUG_CHANNEL(storage
);
58 /***********************************************************
59 * Data structures used internally by the BigBlockFile
63 /* We map in PAGE_SIZE-sized chunks. Must be a multiple of 4096. */
64 #define PAGE_SIZE 131072
66 #define BLOCKS_PER_PAGE (PAGE_SIZE / BIG_BLOCK_SIZE)
68 /* We keep a list of recently-discarded pages. This controls the
69 * size of that list. */
70 #define MAX_VICTIM_PAGES 16
72 /* This structure provides one bit for each block in a page.
73 * Use BIGBLOCKFILE_{Test,Set,Clear}Bit to manipulate it. */
76 unsigned int bits
[BLOCKS_PER_PAGE
/ (CHAR_BIT
* sizeof(unsigned int))];
80 * This structure identifies the paged that are mapped
81 * from the file and their position in memory. It is
82 * also used to hold a reference count to those pages.
84 * page_index identifies which PAGE_SIZE chunk from the
85 * file this mapping represents. (The mappings are always
89 typedef struct MappedPage MappedPage
;
100 BlockBits readable_blocks
;
101 BlockBits writable_blocks
;
107 ULARGE_INTEGER filesize
;
113 MappedPage
*victimhead
, *victimtail
;
114 ULONG num_victim_pages
;
118 /***********************************************************
119 * Prototypes for private methods
122 /* Note that this evaluates a and b multiple times, so don't
123 * pass expressions with side effects. */
124 #define ROUND_UP(a, b) ((((a) + (b) - 1)/(b))*(b))
126 /***********************************************************
127 * Blockbits functions.
129 static inline BOOL
BIGBLOCKFILE_TestBit(const BlockBits
*bb
,
132 unsigned int array_index
= index
/ (CHAR_BIT
* sizeof(unsigned int));
133 unsigned int bit_index
= index
% (CHAR_BIT
* sizeof(unsigned int));
135 return bb
->bits
[array_index
] & (1 << bit_index
);
138 static inline void BIGBLOCKFILE_SetBit(BlockBits
*bb
, unsigned int index
)
140 unsigned int array_index
= index
/ (CHAR_BIT
* sizeof(unsigned int));
141 unsigned int bit_index
= index
% (CHAR_BIT
* sizeof(unsigned int));
143 bb
->bits
[array_index
] |= (1 << bit_index
);
146 static inline void BIGBLOCKFILE_ClearBit(BlockBits
*bb
, unsigned int index
)
148 unsigned int array_index
= index
/ (CHAR_BIT
* sizeof(unsigned int));
149 unsigned int bit_index
= index
% (CHAR_BIT
* sizeof(unsigned int));
151 bb
->bits
[array_index
] &= ~(1 << bit_index
);
154 static inline void BIGBLOCKFILE_Zero(BlockBits
*bb
)
156 memset(bb
->bits
, 0, sizeof(bb
->bits
));
159 /******************************************************************************
160 * BIGBLOCKFILE_FileInit
162 * Initialize a big block object supported by a file.
164 static BOOL
BIGBLOCKFILE_FileInit(LPBIGBLOCKFILE This
, HANDLE hFile
)
169 if (This
->hfile
== INVALID_HANDLE_VALUE
)
172 This
->filesize
.u
.LowPart
= GetFileSize(This
->hfile
,
173 &This
->filesize
.u
.HighPart
);
175 if( This
->filesize
.u
.LowPart
|| This
->filesize
.u
.HighPart
)
177 /* create the file mapping object
179 This
->hfilemap
= CreateFileMappingA(This
->hfile
,
187 CloseHandle(This
->hfile
);
192 This
->hfilemap
= NULL
;
194 This
->maplist
= NULL
;
196 TRACE("file len %u\n", This
->filesize
.u
.LowPart
);
201 /******************************************************************************
202 * BIGBLOCKFILE_LockBytesInit
204 * Initialize a big block object supported by an ILockBytes.
206 static BOOL
BIGBLOCKFILE_LockBytesInit(LPBIGBLOCKFILE This
, ILockBytes
* plkbyt
)
210 This
->pLkbyt
= plkbyt
;
211 ILockBytes_AddRef(This
->pLkbyt
);
213 /* We'll get the size directly with ILockBytes_Stat */
214 This
->filesize
.QuadPart
= 0;
216 TRACE("ILockBytes %p\n", This
->pLkbyt
);
220 /******************************************************************************
221 * BIGBLOCKFILE_FindPageInList [PRIVATE]
224 static MappedPage
*BIGBLOCKFILE_FindPageInList(MappedPage
*head
,
227 for (; head
!= NULL
; head
= head
->next
)
229 if (head
->page_index
== page_index
)
231 InterlockedIncrement(&head
->refcnt
);
240 static void BIGBLOCKFILE_UnlinkPage(MappedPage
*page
)
242 if (page
->next
) page
->next
->prev
= page
->prev
;
243 if (page
->prev
) page
->prev
->next
= page
->next
;
246 static void BIGBLOCKFILE_LinkHeadPage(MappedPage
**head
, MappedPage
*page
)
248 if (*head
) (*head
)->prev
= page
;
254 static BOOL
BIGBLOCKFILE_MapPage(BigBlockFile
*This
, MappedPage
*page
)
256 DWORD lowoffset
= PAGE_SIZE
* page
->page_index
;
258 DWORD desired_access
;
260 assert(This
->fileBased
);
262 if( !This
->hfilemap
)
265 if (lowoffset
+ PAGE_SIZE
> This
->filesize
.u
.LowPart
)
266 numBytesToMap
= This
->filesize
.u
.LowPart
- lowoffset
;
268 numBytesToMap
= PAGE_SIZE
;
270 if (This
->flProtect
== PAGE_READONLY
)
271 desired_access
= FILE_MAP_READ
;
273 desired_access
= FILE_MAP_WRITE
;
275 page
->lpBytes
= MapViewOfFile(This
->hfilemap
, desired_access
, 0,
276 lowoffset
, numBytesToMap
);
277 page
->mapped_bytes
= numBytesToMap
;
279 TRACE("mapped page %u to %p\n", page
->page_index
, page
->lpBytes
);
281 return page
->lpBytes
!= NULL
;
285 static MappedPage
*BIGBLOCKFILE_CreatePage(BigBlockFile
*This
, ULONG page_index
)
289 page
= HeapAlloc(GetProcessHeap(), 0, sizeof(MappedPage
));
293 page
->page_index
= page_index
;
299 if (!BIGBLOCKFILE_MapPage(This
, page
))
301 HeapFree(GetProcessHeap(),0,page
);
305 BIGBLOCKFILE_Zero(&page
->readable_blocks
);
306 BIGBLOCKFILE_Zero(&page
->writable_blocks
);
312 /******************************************************************************
313 * BIGBLOCKFILE_GetMappedView [PRIVATE]
315 * Gets the page requested if it is already mapped.
316 * If it's not already mapped, this method will map it
318 static void * BIGBLOCKFILE_GetMappedView(
324 page
= BIGBLOCKFILE_FindPageInList(This
->maplist
, page_index
);
327 page
= BIGBLOCKFILE_FindPageInList(This
->victimhead
, page_index
);
330 This
->num_victim_pages
--;
332 BIGBLOCKFILE_Zero(&page
->readable_blocks
);
333 BIGBLOCKFILE_Zero(&page
->writable_blocks
);
339 /* If the page is not already at the head of the list, move
340 * it there. (Also moves pages from victim to main list.) */
341 if (This
->maplist
!= page
)
343 if (This
->victimhead
== page
) This
->victimhead
= page
->next
;
344 if (This
->victimtail
== page
) This
->victimtail
= page
->prev
;
346 BIGBLOCKFILE_UnlinkPage(page
);
348 BIGBLOCKFILE_LinkHeadPage(&This
->maplist
, page
);
354 page
= BIGBLOCKFILE_CreatePage(This
, page_index
);
355 if (!page
) return NULL
;
357 BIGBLOCKFILE_LinkHeadPage(&This
->maplist
, page
);
362 static void BIGBLOCKFILE_UnmapPage(LPBIGBLOCKFILE This
, MappedPage
*page
)
364 TRACE("%d at %p\n", page
->page_index
, page
->lpBytes
);
366 assert(This
->fileBased
);
368 if (page
->refcnt
> 0)
369 ERR("unmapping inuse page %p\n", page
->lpBytes
);
372 UnmapViewOfFile(page
->lpBytes
);
374 page
->lpBytes
= NULL
;
377 static void BIGBLOCKFILE_DeletePage(LPBIGBLOCKFILE This
, MappedPage
*page
)
379 BIGBLOCKFILE_UnmapPage(This
, page
);
381 HeapFree(GetProcessHeap(), 0, page
);
384 /******************************************************************************
385 * BIGBLOCKFILE_ReleaseMappedPage [PRIVATE]
387 * Decrements the reference count of the mapped page.
389 static void BIGBLOCKFILE_ReleaseMappedPage(
393 assert(This
!= NULL
);
394 assert(page
!= NULL
);
396 /* If the page is no longer refenced, move it to the victim list.
397 * If the victim list is too long, kick somebody off. */
398 if (!InterlockedDecrement(&page
->refcnt
))
400 if (This
->maplist
== page
) This
->maplist
= page
->next
;
402 BIGBLOCKFILE_UnlinkPage(page
);
404 if (MAX_VICTIM_PAGES
> 0)
406 if (This
->num_victim_pages
>= MAX_VICTIM_PAGES
)
408 MappedPage
*victim
= This
->victimtail
;
411 This
->victimtail
= victim
->prev
;
412 if (This
->victimhead
== victim
)
413 This
->victimhead
= victim
->next
;
415 BIGBLOCKFILE_UnlinkPage(victim
);
416 BIGBLOCKFILE_DeletePage(This
, victim
);
419 else This
->num_victim_pages
++;
421 BIGBLOCKFILE_LinkHeadPage(&This
->victimhead
, page
);
422 if (This
->victimtail
== NULL
) This
->victimtail
= page
;
425 BIGBLOCKFILE_DeletePage(This
, page
);
429 static void BIGBLOCKFILE_DeleteList(LPBIGBLOCKFILE This
, MappedPage
*list
)
433 MappedPage
*next
= list
->next
;
435 BIGBLOCKFILE_DeletePage(This
, list
);
441 /******************************************************************************
442 * BIGBLOCKFILE_FreeAllMappedPages [PRIVATE]
444 * Unmap all currently mapped pages.
445 * Empty mapped pages list.
447 static void BIGBLOCKFILE_FreeAllMappedPages(
450 BIGBLOCKFILE_DeleteList(This
, This
->maplist
);
451 BIGBLOCKFILE_DeleteList(This
, This
->victimhead
);
453 This
->maplist
= NULL
;
454 This
->victimhead
= NULL
;
455 This
->victimtail
= NULL
;
456 This
->num_victim_pages
= 0;
459 static void BIGBLOCKFILE_UnmapList(LPBIGBLOCKFILE This
, MappedPage
*list
)
461 for (; list
!= NULL
; list
= list
->next
)
463 BIGBLOCKFILE_UnmapPage(This
, list
);
467 static void BIGBLOCKFILE_UnmapAllMappedPages(LPBIGBLOCKFILE This
)
469 BIGBLOCKFILE_UnmapList(This
, This
->maplist
);
470 BIGBLOCKFILE_UnmapList(This
, This
->victimhead
);
473 static void BIGBLOCKFILE_RemapList(LPBIGBLOCKFILE This
, MappedPage
*list
)
477 MappedPage
*next
= list
->next
;
479 if (list
->page_index
* PAGE_SIZE
> This
->filesize
.u
.LowPart
)
481 TRACE("discarding %u\n", list
->page_index
);
483 /* page is entirely outside of the file, delete it */
484 BIGBLOCKFILE_UnlinkPage(list
);
485 BIGBLOCKFILE_DeletePage(This
, list
);
489 /* otherwise, remap it */
490 BIGBLOCKFILE_MapPage(This
, list
);
497 static void BIGBLOCKFILE_RemapAllMappedPages(LPBIGBLOCKFILE This
)
499 BIGBLOCKFILE_RemapList(This
, This
->maplist
);
500 BIGBLOCKFILE_RemapList(This
, This
->victimhead
);
503 /****************************************************************************
504 * BIGBLOCKFILE_GetProtectMode
506 * This function will return a protection mode flag for a file-mapping object
507 * from the open flags of a file.
509 static DWORD
BIGBLOCKFILE_GetProtectMode(DWORD openFlags
)
511 switch(STGM_ACCESS_MODE(openFlags
))
515 return PAGE_READWRITE
;
517 return PAGE_READONLY
;
521 /* ILockByte Interfaces */
523 /******************************************************************************
524 * This method is part of the ILockBytes interface.
526 * It reads a block of information from the byte array at the specified
529 * See the documentation of ILockBytes for more info.
531 static HRESULT
ImplBIGBLOCKFILE_ReadAt(
532 BigBlockFile
* const This
,
533 ULARGE_INTEGER ulOffset
, /* [in] */
534 void* pv
, /* [length_is][size_is][out] */
536 ULONG
* pcbRead
) /* [out] */
538 ULONG first_page
= ulOffset
.u
.LowPart
/ PAGE_SIZE
;
539 ULONG offset_in_page
= ulOffset
.u
.LowPart
% PAGE_SIZE
;
540 ULONG bytes_left
= cb
;
541 ULONG page_index
= first_page
;
542 ULONG bytes_from_page
;
543 LPVOID writePtr
= pv
;
547 TRACE("(%p)-> %i %p %i %p\n",This
, ulOffset
.u
.LowPart
, pv
, cb
, pcbRead
);
549 /* verify a sane environment */
550 if (!This
) return E_FAIL
;
552 if (offset_in_page
+ bytes_left
> PAGE_SIZE
)
553 bytes_from_page
= PAGE_SIZE
- offset_in_page
;
555 bytes_from_page
= bytes_left
;
564 MappedPage
*page
= BIGBLOCKFILE_GetMappedView(This
, page_index
);
566 if (!page
|| !page
->lpBytes
)
568 rc
= STG_E_READFAULT
;
572 TRACE("page %i, offset %u, bytes_from_page %u, bytes_left %u\n",
573 page
->page_index
, offset_in_page
, bytes_from_page
, bytes_left
);
575 if (page
->mapped_bytes
< bytes_from_page
)
578 bytes_from_page
= page
->mapped_bytes
;
581 readPtr
= (BYTE
*)page
->lpBytes
+ offset_in_page
;
582 memcpy(writePtr
,readPtr
,bytes_from_page
);
583 BIGBLOCKFILE_ReleaseMappedPage(This
, page
);
586 *pcbRead
+= bytes_from_page
;
587 bytes_left
-= bytes_from_page
;
589 if (bytes_left
&& !eof
)
591 writePtr
= (LPBYTE
)writePtr
+ bytes_from_page
;
594 if (bytes_left
> PAGE_SIZE
)
595 bytes_from_page
= PAGE_SIZE
;
597 bytes_from_page
= bytes_left
;
601 rc
= STG_E_READFAULT
;
610 /******************************************************************************
611 * This method is part of the ILockBytes interface.
613 * It writes the specified bytes at the specified offset.
614 * position. If the file is too small, it will be resized.
616 * See the documentation of ILockBytes for more info.
618 static HRESULT
ImplBIGBLOCKFILE_WriteAt(
619 BigBlockFile
* const This
,
620 ULARGE_INTEGER ulOffset
, /* [in] */
621 const void* pv
, /* [size_is][in] */
623 ULONG
* pcbWritten
) /* [out] */
625 ULONG size_needed
= ulOffset
.u
.LowPart
+ cb
;
626 ULONG first_page
= ulOffset
.u
.LowPart
/ PAGE_SIZE
;
627 ULONG offset_in_page
= ulOffset
.u
.LowPart
% PAGE_SIZE
;
628 ULONG bytes_left
= cb
;
629 ULONG page_index
= first_page
;
631 LPCVOID readPtr
= pv
;
635 TRACE("(%p)-> %i %p %i %p\n",This
, ulOffset
.u
.LowPart
, pv
, cb
, pcbWritten
);
637 /* verify a sane environment */
638 if (!This
) return E_FAIL
;
640 if (This
->flProtect
!= PAGE_READWRITE
)
641 return STG_E_ACCESSDENIED
;
643 if (size_needed
> This
->filesize
.u
.LowPart
)
645 ULARGE_INTEGER newSize
;
646 newSize
.u
.HighPart
= 0;
647 newSize
.u
.LowPart
= size_needed
;
648 BIGBLOCKFILE_SetSize(This
, newSize
);
651 if (offset_in_page
+ bytes_left
> PAGE_SIZE
)
652 bytes_to_page
= PAGE_SIZE
- offset_in_page
;
654 bytes_to_page
= bytes_left
;
662 MappedPage
*page
= BIGBLOCKFILE_GetMappedView(This
, page_index
);
664 TRACE("page %i, offset %u, bytes_to_page %u, bytes_left %u\n",
665 page
? page
->page_index
: 0, offset_in_page
, bytes_to_page
, bytes_left
);
669 ERR("Unable to get a page to write. This should never happen\n");
674 if (page
->mapped_bytes
< bytes_to_page
)
676 ERR("Not enough bytes mapped to the page. This should never happen\n");
681 writePtr
= (BYTE
*)page
->lpBytes
+ offset_in_page
;
682 memcpy(writePtr
,readPtr
,bytes_to_page
);
683 BIGBLOCKFILE_ReleaseMappedPage(This
, page
);
686 *pcbWritten
+= bytes_to_page
;
687 bytes_left
-= bytes_to_page
;
691 readPtr
= (const BYTE
*)readPtr
+ bytes_to_page
;
694 if (bytes_left
> PAGE_SIZE
)
695 bytes_to_page
= PAGE_SIZE
;
697 bytes_to_page
= bytes_left
;
704 /******************************************************************************
705 * BIGBLOCKFILE_Construct
707 * Construct a big block file. Create the file mapping object.
708 * Create the read only mapped pages list, the writable mapped page list
709 * and the blocks in use list.
711 BigBlockFile
*BIGBLOCKFILE_Construct(HANDLE hFile
, ILockBytes
* pLkByt
, DWORD openFlags
,
712 ULONG blocksize
, BOOL fileBased
)
716 This
= HeapAlloc(GetProcessHeap(), 0, sizeof(BigBlockFile
));
721 This
->fileBased
= fileBased
;
722 This
->flProtect
= BIGBLOCKFILE_GetProtectMode(openFlags
);
723 This
->blocksize
= blocksize
;
725 This
->maplist
= NULL
;
726 This
->victimhead
= NULL
;
727 This
->victimtail
= NULL
;
728 This
->num_victim_pages
= 0;
732 if (!BIGBLOCKFILE_FileInit(This
, hFile
))
734 HeapFree(GetProcessHeap(), 0, This
);
740 if (!BIGBLOCKFILE_LockBytesInit(This
, pLkByt
))
742 HeapFree(GetProcessHeap(), 0, This
);
750 /******************************************************************************
751 * BIGBLOCKFILE_Destructor
753 * Destructor. Clean up, free memory.
755 void BIGBLOCKFILE_Destructor(BigBlockFile
*This
)
757 BIGBLOCKFILE_FreeAllMappedPages(This
);
761 CloseHandle(This
->hfilemap
);
762 CloseHandle(This
->hfile
);
766 ILockBytes_Release(This
->pLkbyt
);
769 HeapFree(GetProcessHeap(), 0, This
);
772 /******************************************************************************
773 * BIGBLOCKFILE_ReadAt
775 HRESULT
BIGBLOCKFILE_ReadAt(BigBlockFile
*This
, ULARGE_INTEGER offset
,
776 void* buffer
, ULONG size
, ULONG
* bytesRead
)
779 return ImplBIGBLOCKFILE_ReadAt(This
,offset
,buffer
,size
,bytesRead
);
781 return ILockBytes_ReadAt(This
->pLkbyt
,offset
,buffer
,size
,bytesRead
);
784 /******************************************************************************
785 * BIGBLOCKFILE_WriteAt
787 HRESULT
BIGBLOCKFILE_WriteAt(BigBlockFile
*This
, ULARGE_INTEGER offset
,
788 const void* buffer
, ULONG size
, ULONG
* bytesRead
)
791 return ImplBIGBLOCKFILE_WriteAt(This
,offset
,buffer
,size
,bytesRead
);
793 return ILockBytes_WriteAt(This
->pLkbyt
,offset
,buffer
,size
,bytesRead
);
796 /******************************************************************************
797 * BIGBLOCKFILE_SetSize
799 * Sets the size of the file.
802 HRESULT
BIGBLOCKFILE_SetSize(BigBlockFile
*This
, ULARGE_INTEGER newSize
)
805 LARGE_INTEGER newpos
;
807 if (!This
->fileBased
)
808 return ILockBytes_SetSize(This
->pLkbyt
, newSize
);
810 if (This
->filesize
.u
.LowPart
== newSize
.u
.LowPart
)
813 TRACE("from %u to %u\n", This
->filesize
.u
.LowPart
, newSize
.u
.LowPart
);
816 * Unmap all views, must be done before call to SetEndFile.
818 * Just ditch the victim list because there is no guarantee we will need them
819 * and it is not worth the performance hit to unmap and remap them all.
821 BIGBLOCKFILE_DeleteList(This
, This
->victimhead
);
822 This
->victimhead
= NULL
;
823 This
->victimtail
= NULL
;
824 This
->num_victim_pages
= 0;
826 BIGBLOCKFILE_UnmapAllMappedPages(This
);
828 newpos
.QuadPart
= newSize
.QuadPart
;
829 if (SetFilePointerEx(This
->hfile
, newpos
, NULL
, FILE_BEGIN
))
831 if( This
->hfilemap
) CloseHandle(This
->hfilemap
);
833 SetEndOfFile(This
->hfile
);
835 /* re-create the file mapping object */
836 This
->hfilemap
= CreateFileMappingA(This
->hfile
, NULL
, This
->flProtect
,
840 This
->filesize
= newSize
;
841 BIGBLOCKFILE_RemapAllMappedPages(This
);
845 /******************************************************************************
846 * BIGBLOCKFILE_GetSize
848 * Gets the size of the file.
851 static HRESULT
BIGBLOCKFILE_GetSize(BigBlockFile
*This
, ULARGE_INTEGER
*size
)
855 *size
= This
->filesize
;
859 hr
= ILockBytes_Stat(This
->pLkbyt
, &stat
, STATFLAG_NONAME
);
860 if(SUCCEEDED(hr
)) *size
= stat
.cbSize
;
865 /******************************************************************************
866 * BIGBLOCKFILE_EnsureExists
868 * Grows the file if necessary to make sure the block is valid.
870 HRESULT
BIGBLOCKFILE_EnsureExists(BigBlockFile
*This
, ULONG index
)
875 /* Block index starts at -1 translate to zero based index */
876 if (index
== 0xffffffff)
881 hr
= BIGBLOCKFILE_GetSize(This
, &size
);
882 if(FAILED(hr
)) return hr
;
884 /* make sure that the block physically exists */
885 if ((This
->blocksize
* (index
+ 1)) > size
.QuadPart
)
887 ULARGE_INTEGER newSize
;
889 newSize
.QuadPart
= This
->blocksize
* (index
+ 1);
890 hr
= BIGBLOCKFILE_SetSize(This
, newSize
);