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 /* We keep a list of recently-discarded pages. This controls the
67 * size of that list. */
68 #define MAX_VICTIM_PAGES 16
71 * This structure identifies the paged that are mapped
72 * from the file and their position in memory. It is
73 * also used to hold a reference count to those pages.
75 * page_index identifies which PAGE_SIZE chunk from the
76 * file this mapping represents. (The mappings are always
80 typedef struct MappedPage MappedPage
;
95 ULARGE_INTEGER filesize
;
100 MappedPage
*victimhead
, *victimtail
;
101 ULONG num_victim_pages
;
105 /***********************************************************
106 * Prototypes for private methods
109 /* Note that this evaluates a and b multiple times, so don't
110 * pass expressions with side effects. */
111 #define ROUND_UP(a, b) ((((a) + (b) - 1)/(b))*(b))
113 /******************************************************************************
114 * BIGBLOCKFILE_FileInit
116 * Initialize a big block object supported by a file.
118 static BOOL
BIGBLOCKFILE_FileInit(LPBIGBLOCKFILE This
, HANDLE hFile
)
123 if (This
->hfile
== INVALID_HANDLE_VALUE
)
126 This
->filesize
.u
.LowPart
= GetFileSize(This
->hfile
,
127 &This
->filesize
.u
.HighPart
);
129 if( This
->filesize
.u
.LowPart
|| This
->filesize
.u
.HighPart
)
131 /* create the file mapping object
133 This
->hfilemap
= CreateFileMappingA(This
->hfile
,
141 CloseHandle(This
->hfile
);
146 This
->hfilemap
= NULL
;
148 This
->maplist
= NULL
;
150 TRACE("file len %u\n", This
->filesize
.u
.LowPart
);
155 /******************************************************************************
156 * BIGBLOCKFILE_LockBytesInit
158 * Initialize a big block object supported by an ILockBytes.
160 static BOOL
BIGBLOCKFILE_LockBytesInit(LPBIGBLOCKFILE This
, ILockBytes
* plkbyt
)
164 This
->pLkbyt
= plkbyt
;
165 ILockBytes_AddRef(This
->pLkbyt
);
167 /* We'll get the size directly with ILockBytes_Stat */
168 This
->filesize
.QuadPart
= 0;
170 TRACE("ILockBytes %p\n", This
->pLkbyt
);
174 /******************************************************************************
175 * BIGBLOCKFILE_FindPageInList [PRIVATE]
178 static MappedPage
*BIGBLOCKFILE_FindPageInList(MappedPage
*head
,
181 for (; head
!= NULL
; head
= head
->next
)
183 if (head
->page_index
== page_index
)
185 InterlockedIncrement(&head
->refcnt
);
194 static void BIGBLOCKFILE_UnlinkPage(MappedPage
*page
)
196 if (page
->next
) page
->next
->prev
= page
->prev
;
197 if (page
->prev
) page
->prev
->next
= page
->next
;
200 static void BIGBLOCKFILE_LinkHeadPage(MappedPage
**head
, MappedPage
*page
)
202 if (*head
) (*head
)->prev
= page
;
208 static BOOL
BIGBLOCKFILE_MapPage(BigBlockFile
*This
, MappedPage
*page
)
210 DWORD lowoffset
= PAGE_SIZE
* page
->page_index
;
212 DWORD desired_access
;
214 assert(This
->fileBased
);
216 if( !This
->hfilemap
)
219 if (lowoffset
+ PAGE_SIZE
> This
->filesize
.u
.LowPart
)
220 numBytesToMap
= This
->filesize
.u
.LowPart
- lowoffset
;
222 numBytesToMap
= PAGE_SIZE
;
224 if (This
->flProtect
== PAGE_READONLY
)
225 desired_access
= FILE_MAP_READ
;
227 desired_access
= FILE_MAP_WRITE
;
229 page
->lpBytes
= MapViewOfFile(This
->hfilemap
, desired_access
, 0,
230 lowoffset
, numBytesToMap
);
231 page
->mapped_bytes
= numBytesToMap
;
233 TRACE("mapped page %u to %p\n", page
->page_index
, page
->lpBytes
);
235 return page
->lpBytes
!= NULL
;
239 static MappedPage
*BIGBLOCKFILE_CreatePage(BigBlockFile
*This
, ULONG page_index
)
243 page
= HeapAlloc(GetProcessHeap(), 0, sizeof(MappedPage
));
247 page
->page_index
= page_index
;
253 if (!BIGBLOCKFILE_MapPage(This
, page
))
255 HeapFree(GetProcessHeap(),0,page
);
263 /******************************************************************************
264 * BIGBLOCKFILE_GetMappedView [PRIVATE]
266 * Gets the page requested if it is already mapped.
267 * If it's not already mapped, this method will map it
269 static void * BIGBLOCKFILE_GetMappedView(
275 page
= BIGBLOCKFILE_FindPageInList(This
->maplist
, page_index
);
278 page
= BIGBLOCKFILE_FindPageInList(This
->victimhead
, page_index
);
281 This
->num_victim_pages
--;
287 /* If the page is not already at the head of the list, move
288 * it there. (Also moves pages from victim to main list.) */
289 if (This
->maplist
!= page
)
291 if (This
->victimhead
== page
) This
->victimhead
= page
->next
;
292 if (This
->victimtail
== page
) This
->victimtail
= page
->prev
;
294 BIGBLOCKFILE_UnlinkPage(page
);
296 BIGBLOCKFILE_LinkHeadPage(&This
->maplist
, page
);
302 page
= BIGBLOCKFILE_CreatePage(This
, page_index
);
303 if (!page
) return NULL
;
305 BIGBLOCKFILE_LinkHeadPage(&This
->maplist
, page
);
310 static void BIGBLOCKFILE_UnmapPage(LPBIGBLOCKFILE This
, MappedPage
*page
)
312 TRACE("%d at %p\n", page
->page_index
, page
->lpBytes
);
314 assert(This
->fileBased
);
316 if (page
->refcnt
> 0)
317 ERR("unmapping inuse page %p\n", page
->lpBytes
);
320 UnmapViewOfFile(page
->lpBytes
);
322 page
->lpBytes
= NULL
;
325 static void BIGBLOCKFILE_DeletePage(LPBIGBLOCKFILE This
, MappedPage
*page
)
327 BIGBLOCKFILE_UnmapPage(This
, page
);
329 HeapFree(GetProcessHeap(), 0, page
);
332 /******************************************************************************
333 * BIGBLOCKFILE_ReleaseMappedPage [PRIVATE]
335 * Decrements the reference count of the mapped page.
337 static void BIGBLOCKFILE_ReleaseMappedPage(
341 assert(This
!= NULL
);
342 assert(page
!= NULL
);
344 /* If the page is no longer refenced, move it to the victim list.
345 * If the victim list is too long, kick somebody off. */
346 if (!InterlockedDecrement(&page
->refcnt
))
348 if (This
->maplist
== page
) This
->maplist
= page
->next
;
350 BIGBLOCKFILE_UnlinkPage(page
);
352 if (MAX_VICTIM_PAGES
> 0)
354 if (This
->num_victim_pages
>= MAX_VICTIM_PAGES
)
356 MappedPage
*victim
= This
->victimtail
;
359 This
->victimtail
= victim
->prev
;
360 if (This
->victimhead
== victim
)
361 This
->victimhead
= victim
->next
;
363 BIGBLOCKFILE_UnlinkPage(victim
);
364 BIGBLOCKFILE_DeletePage(This
, victim
);
367 else This
->num_victim_pages
++;
369 BIGBLOCKFILE_LinkHeadPage(&This
->victimhead
, page
);
370 if (This
->victimtail
== NULL
) This
->victimtail
= page
;
373 BIGBLOCKFILE_DeletePage(This
, page
);
377 static void BIGBLOCKFILE_DeleteList(LPBIGBLOCKFILE This
, MappedPage
*list
)
381 MappedPage
*next
= list
->next
;
383 BIGBLOCKFILE_DeletePage(This
, list
);
389 /******************************************************************************
390 * BIGBLOCKFILE_FreeAllMappedPages [PRIVATE]
392 * Unmap all currently mapped pages.
393 * Empty mapped pages list.
395 static void BIGBLOCKFILE_FreeAllMappedPages(
398 BIGBLOCKFILE_DeleteList(This
, This
->maplist
);
399 BIGBLOCKFILE_DeleteList(This
, This
->victimhead
);
401 This
->maplist
= NULL
;
402 This
->victimhead
= NULL
;
403 This
->victimtail
= NULL
;
404 This
->num_victim_pages
= 0;
407 static void BIGBLOCKFILE_UnmapList(LPBIGBLOCKFILE This
, MappedPage
*list
)
409 for (; list
!= NULL
; list
= list
->next
)
411 BIGBLOCKFILE_UnmapPage(This
, list
);
415 static void BIGBLOCKFILE_UnmapAllMappedPages(LPBIGBLOCKFILE This
)
417 BIGBLOCKFILE_UnmapList(This
, This
->maplist
);
418 BIGBLOCKFILE_UnmapList(This
, This
->victimhead
);
421 static void BIGBLOCKFILE_RemapList(LPBIGBLOCKFILE This
, MappedPage
*list
)
425 MappedPage
*next
= list
->next
;
427 if (list
->page_index
* PAGE_SIZE
> This
->filesize
.u
.LowPart
)
429 TRACE("discarding %u\n", list
->page_index
);
431 /* page is entirely outside of the file, delete it */
432 BIGBLOCKFILE_UnlinkPage(list
);
433 BIGBLOCKFILE_DeletePage(This
, list
);
437 /* otherwise, remap it */
438 BIGBLOCKFILE_MapPage(This
, list
);
445 static void BIGBLOCKFILE_RemapAllMappedPages(LPBIGBLOCKFILE This
)
447 BIGBLOCKFILE_RemapList(This
, This
->maplist
);
448 BIGBLOCKFILE_RemapList(This
, This
->victimhead
);
451 /****************************************************************************
452 * BIGBLOCKFILE_GetProtectMode
454 * This function will return a protection mode flag for a file-mapping object
455 * from the open flags of a file.
457 static DWORD
BIGBLOCKFILE_GetProtectMode(DWORD openFlags
)
459 switch(STGM_ACCESS_MODE(openFlags
))
463 return PAGE_READWRITE
;
465 return PAGE_READONLY
;
469 /* ILockByte Interfaces */
471 /******************************************************************************
472 * This method is part of the ILockBytes interface.
474 * It reads a block of information from the byte array at the specified
477 * See the documentation of ILockBytes for more info.
479 static HRESULT
ImplBIGBLOCKFILE_ReadAt(
480 BigBlockFile
* const This
,
481 ULARGE_INTEGER ulOffset
, /* [in] */
482 void* pv
, /* [length_is][size_is][out] */
484 ULONG
* pcbRead
) /* [out] */
486 ULONG first_page
= ulOffset
.u
.LowPart
/ PAGE_SIZE
;
487 ULONG offset_in_page
= ulOffset
.u
.LowPart
% PAGE_SIZE
;
488 ULONG bytes_left
= cb
;
489 ULONG page_index
= first_page
;
490 ULONG bytes_from_page
;
491 LPVOID writePtr
= pv
;
495 TRACE("(%p)-> %i %p %i %p\n",This
, ulOffset
.u
.LowPart
, pv
, cb
, pcbRead
);
497 /* verify a sane environment */
498 if (!This
) return E_FAIL
;
500 if (offset_in_page
+ bytes_left
> PAGE_SIZE
)
501 bytes_from_page
= PAGE_SIZE
- offset_in_page
;
503 bytes_from_page
= bytes_left
;
512 MappedPage
*page
= BIGBLOCKFILE_GetMappedView(This
, page_index
);
514 if (!page
|| !page
->lpBytes
)
516 rc
= STG_E_READFAULT
;
520 TRACE("page %i, offset %u, bytes_from_page %u, bytes_left %u\n",
521 page
->page_index
, offset_in_page
, bytes_from_page
, bytes_left
);
523 if (page
->mapped_bytes
< bytes_from_page
)
526 bytes_from_page
= page
->mapped_bytes
;
529 readPtr
= (BYTE
*)page
->lpBytes
+ offset_in_page
;
530 memcpy(writePtr
,readPtr
,bytes_from_page
);
531 BIGBLOCKFILE_ReleaseMappedPage(This
, page
);
534 *pcbRead
+= bytes_from_page
;
535 bytes_left
-= bytes_from_page
;
537 if (bytes_left
&& !eof
)
539 writePtr
= (LPBYTE
)writePtr
+ bytes_from_page
;
542 if (bytes_left
> PAGE_SIZE
)
543 bytes_from_page
= PAGE_SIZE
;
545 bytes_from_page
= bytes_left
;
549 rc
= STG_E_READFAULT
;
558 /******************************************************************************
559 * This method is part of the ILockBytes interface.
561 * It writes the specified bytes at the specified offset.
562 * position. If the file is too small, it will be resized.
564 * See the documentation of ILockBytes for more info.
566 static HRESULT
ImplBIGBLOCKFILE_WriteAt(
567 BigBlockFile
* const This
,
568 ULARGE_INTEGER ulOffset
, /* [in] */
569 const void* pv
, /* [size_is][in] */
571 ULONG
* pcbWritten
) /* [out] */
573 ULONG size_needed
= ulOffset
.u
.LowPart
+ cb
;
574 ULONG first_page
= ulOffset
.u
.LowPart
/ PAGE_SIZE
;
575 ULONG offset_in_page
= ulOffset
.u
.LowPart
% PAGE_SIZE
;
576 ULONG bytes_left
= cb
;
577 ULONG page_index
= first_page
;
579 LPCVOID readPtr
= pv
;
583 TRACE("(%p)-> %i %p %i %p\n",This
, ulOffset
.u
.LowPart
, pv
, cb
, pcbWritten
);
585 /* verify a sane environment */
586 if (!This
) return E_FAIL
;
588 if (This
->flProtect
!= PAGE_READWRITE
)
589 return STG_E_ACCESSDENIED
;
591 if (size_needed
> This
->filesize
.u
.LowPart
)
593 ULARGE_INTEGER newSize
;
594 newSize
.u
.HighPart
= 0;
595 newSize
.u
.LowPart
= size_needed
;
596 BIGBLOCKFILE_SetSize(This
, newSize
);
599 if (offset_in_page
+ bytes_left
> PAGE_SIZE
)
600 bytes_to_page
= PAGE_SIZE
- offset_in_page
;
602 bytes_to_page
= bytes_left
;
610 MappedPage
*page
= BIGBLOCKFILE_GetMappedView(This
, page_index
);
612 TRACE("page %i, offset %u, bytes_to_page %u, bytes_left %u\n",
613 page
? page
->page_index
: 0, offset_in_page
, bytes_to_page
, bytes_left
);
617 ERR("Unable to get a page to write. This should never happen\n");
622 if (page
->mapped_bytes
< bytes_to_page
)
624 ERR("Not enough bytes mapped to the page. This should never happen\n");
629 writePtr
= (BYTE
*)page
->lpBytes
+ offset_in_page
;
630 memcpy(writePtr
,readPtr
,bytes_to_page
);
631 BIGBLOCKFILE_ReleaseMappedPage(This
, page
);
634 *pcbWritten
+= bytes_to_page
;
635 bytes_left
-= bytes_to_page
;
639 readPtr
= (const BYTE
*)readPtr
+ bytes_to_page
;
642 if (bytes_left
> PAGE_SIZE
)
643 bytes_to_page
= PAGE_SIZE
;
645 bytes_to_page
= bytes_left
;
652 /******************************************************************************
653 * BIGBLOCKFILE_Construct
655 * Construct a big block file. Create the file mapping object.
656 * Create the read only mapped pages list, the writable mapped page list
657 * and the blocks in use list.
659 BigBlockFile
*BIGBLOCKFILE_Construct(HANDLE hFile
, ILockBytes
* pLkByt
, DWORD openFlags
,
664 This
= HeapAlloc(GetProcessHeap(), 0, sizeof(BigBlockFile
));
669 This
->fileBased
= fileBased
;
670 This
->flProtect
= BIGBLOCKFILE_GetProtectMode(openFlags
);
672 This
->maplist
= NULL
;
673 This
->victimhead
= NULL
;
674 This
->victimtail
= NULL
;
675 This
->num_victim_pages
= 0;
679 if (!BIGBLOCKFILE_FileInit(This
, hFile
))
681 HeapFree(GetProcessHeap(), 0, This
);
687 if (!BIGBLOCKFILE_LockBytesInit(This
, pLkByt
))
689 HeapFree(GetProcessHeap(), 0, This
);
697 /******************************************************************************
698 * BIGBLOCKFILE_Destructor
700 * Destructor. Clean up, free memory.
702 void BIGBLOCKFILE_Destructor(BigBlockFile
*This
)
704 BIGBLOCKFILE_FreeAllMappedPages(This
);
708 CloseHandle(This
->hfilemap
);
709 CloseHandle(This
->hfile
);
713 ILockBytes_Release(This
->pLkbyt
);
716 HeapFree(GetProcessHeap(), 0, This
);
719 /******************************************************************************
720 * BIGBLOCKFILE_ReadAt
722 HRESULT
BIGBLOCKFILE_ReadAt(BigBlockFile
*This
, ULARGE_INTEGER offset
,
723 void* buffer
, ULONG size
, ULONG
* bytesRead
)
726 return ImplBIGBLOCKFILE_ReadAt(This
,offset
,buffer
,size
,bytesRead
);
728 return ILockBytes_ReadAt(This
->pLkbyt
,offset
,buffer
,size
,bytesRead
);
731 /******************************************************************************
732 * BIGBLOCKFILE_WriteAt
734 HRESULT
BIGBLOCKFILE_WriteAt(BigBlockFile
*This
, ULARGE_INTEGER offset
,
735 const void* buffer
, ULONG size
, ULONG
* bytesRead
)
738 return ImplBIGBLOCKFILE_WriteAt(This
,offset
,buffer
,size
,bytesRead
);
740 return ILockBytes_WriteAt(This
->pLkbyt
,offset
,buffer
,size
,bytesRead
);
743 /******************************************************************************
744 * BIGBLOCKFILE_SetSize
746 * Sets the size of the file.
749 HRESULT
BIGBLOCKFILE_SetSize(BigBlockFile
*This
, ULARGE_INTEGER newSize
)
752 LARGE_INTEGER newpos
;
754 if (!This
->fileBased
)
755 return ILockBytes_SetSize(This
->pLkbyt
, newSize
);
757 if (This
->filesize
.u
.LowPart
== newSize
.u
.LowPart
)
760 TRACE("from %u to %u\n", This
->filesize
.u
.LowPart
, newSize
.u
.LowPart
);
763 * Unmap all views, must be done before call to SetEndFile.
765 * Just ditch the victim list because there is no guarantee we will need them
766 * and it is not worth the performance hit to unmap and remap them all.
768 BIGBLOCKFILE_DeleteList(This
, This
->victimhead
);
769 This
->victimhead
= NULL
;
770 This
->victimtail
= NULL
;
771 This
->num_victim_pages
= 0;
773 BIGBLOCKFILE_UnmapAllMappedPages(This
);
775 newpos
.QuadPart
= newSize
.QuadPart
;
776 if (SetFilePointerEx(This
->hfile
, newpos
, NULL
, FILE_BEGIN
))
778 if( This
->hfilemap
) CloseHandle(This
->hfilemap
);
780 SetEndOfFile(This
->hfile
);
782 /* re-create the file mapping object */
783 This
->hfilemap
= CreateFileMappingA(This
->hfile
, NULL
, This
->flProtect
,
787 This
->filesize
= newSize
;
788 BIGBLOCKFILE_RemapAllMappedPages(This
);
792 /******************************************************************************
793 * BIGBLOCKFILE_GetSize
795 * Gets the size of the file.
798 static HRESULT
BIGBLOCKFILE_GetSize(BigBlockFile
*This
, ULARGE_INTEGER
*size
)
802 *size
= This
->filesize
;
806 hr
= ILockBytes_Stat(This
->pLkbyt
, &stat
, STATFLAG_NONAME
);
807 if(SUCCEEDED(hr
)) *size
= stat
.cbSize
;
812 /******************************************************************************
813 * BIGBLOCKFILE_Expand
815 * Grows the file to the specified size if necessary.
817 HRESULT
BIGBLOCKFILE_Expand(BigBlockFile
*This
, ULARGE_INTEGER newSize
)
822 hr
= BIGBLOCKFILE_GetSize(This
, &size
);
823 if(FAILED(hr
)) return hr
;
825 if (newSize
.QuadPart
> size
.QuadPart
)
826 hr
= BIGBLOCKFILE_SetSize(This
, newSize
);