ole32: Rework storage with blockfiles to properly use ILockBytes interfaces.
[wine] / dlls / ole32 / stg_bigblockfile.c
1 /******************************************************************************
2  *
3  * BigBlockFile
4  *
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.
13  *
14  * TODO:
15  * - Support for a transacted mode
16  *
17  * Copyright 1999 Thuy Nguyen
18  *
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.
23  *
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.
28  *
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
32  */
33
34 #include <assert.h>
35 #include <stdlib.h>
36 #include <stdarg.h>
37 #include <stdio.h>
38 #include <string.h>
39 #include <limits.h>
40
41 #define COBJMACROS
42 #define NONAMELESSUNION
43 #define NONAMELESSSTRUCT
44
45 #include "windef.h"
46 #include "winbase.h"
47 #include "winuser.h"
48 #include "winerror.h"
49 #include "objbase.h"
50 #include "ole2.h"
51
52 #include "storage32.h"
53
54 #include "wine/debug.h"
55
56 WINE_DEFAULT_DEBUG_CHANNEL(storage);
57
58 /***********************************************************
59  * Data structures used internally by the BigBlockFile
60  * class.
61  */
62
63 /* We map in PAGE_SIZE-sized chunks. Must be a multiple of 4096. */
64 #define PAGE_SIZE       131072
65
66 #define BLOCKS_PER_PAGE (PAGE_SIZE / BIG_BLOCK_SIZE)
67
68 /* We keep a list of recently-discarded pages. This controls the
69  * size of that list. */
70 #define MAX_VICTIM_PAGES 16
71
72 /* This structure provides one bit for each block in a page.
73  * Use BIGBLOCKFILE_{Test,Set,Clear}Bit to manipulate it. */
74 typedef struct
75 {
76     unsigned int bits[BLOCKS_PER_PAGE / (CHAR_BIT * sizeof(unsigned int))];
77 } BlockBits;
78
79 /***
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.
83  *
84  * page_index identifies which PAGE_SIZE chunk from the
85  * file this mapping represents. (The mappings are always
86  * PAGE_SIZE-aligned.)
87  */
88 struct MappedPage
89 {
90     MappedPage *next;
91     MappedPage *prev;
92
93     DWORD  page_index;
94     DWORD  mapped_bytes;
95     LPVOID lpBytes;
96     LONG   refcnt;
97
98     BlockBits readable_blocks;
99     BlockBits writable_blocks;
100 };
101
102 /***********************************************************
103  * Prototypes for private methods
104  */
105 static void*     BIGBLOCKFILE_GetMappedView(LPBIGBLOCKFILE This,
106                                             DWORD          page_index);
107 static void      BIGBLOCKFILE_ReleaseMappedPage(LPBIGBLOCKFILE This,
108                                                 MappedPage *page);
109 static void      BIGBLOCKFILE_FreeAllMappedPages(LPBIGBLOCKFILE This);
110 static void      BIGBLOCKFILE_UnmapAllMappedPages(LPBIGBLOCKFILE This);
111 static void      BIGBLOCKFILE_RemapAllMappedPages(LPBIGBLOCKFILE This);
112 static MappedPage* BIGBLOCKFILE_CreatePage(LPBIGBLOCKFILE This,
113                                            ULONG page_index);
114 static DWORD     BIGBLOCKFILE_GetProtectMode(DWORD openFlags);
115 static BOOL      BIGBLOCKFILE_FileInit(LPBIGBLOCKFILE This, HANDLE hFile);
116 static BOOL      BIGBLOCKFILE_MemInit(LPBIGBLOCKFILE This, ILockBytes* plkbyt);
117
118 /* Note that this evaluates a and b multiple times, so don't
119  * pass expressions with side effects. */
120 #define ROUND_UP(a, b) ((((a) + (b) - 1)/(b))*(b))
121
122 /***********************************************************
123  * Blockbits functions.
124  */
125 static inline BOOL BIGBLOCKFILE_TestBit(const BlockBits *bb,
126                                         unsigned int index)
127 {
128     unsigned int array_index = index / (CHAR_BIT * sizeof(unsigned int));
129     unsigned int bit_index = index % (CHAR_BIT * sizeof(unsigned int));
130
131     return bb->bits[array_index] & (1 << bit_index);
132 }
133
134 static inline void BIGBLOCKFILE_SetBit(BlockBits *bb, unsigned int index)
135 {
136     unsigned int array_index = index / (CHAR_BIT * sizeof(unsigned int));
137     unsigned int bit_index = index % (CHAR_BIT * sizeof(unsigned int));
138
139     bb->bits[array_index] |= (1 << bit_index);
140 }
141
142 static inline void BIGBLOCKFILE_ClearBit(BlockBits *bb, unsigned int index)
143 {
144     unsigned int array_index = index / (CHAR_BIT * sizeof(unsigned int));
145     unsigned int bit_index = index % (CHAR_BIT * sizeof(unsigned int));
146
147     bb->bits[array_index] &= ~(1 << bit_index);
148 }
149
150 static inline void BIGBLOCKFILE_Zero(BlockBits *bb)
151 {
152     memset(bb->bits, 0, sizeof(bb->bits));
153 }
154
155 /******************************************************************************
156  *      BIGBLOCKFILE_Construct
157  *
158  * Construct a big block file. Create the file mapping object.
159  * Create the read only mapped pages list, the writable mapped page list
160  * and the blocks in use list.
161  */
162 BigBlockFile * BIGBLOCKFILE_Construct(
163   HANDLE   hFile,
164   ILockBytes* pLkByt,
165   DWORD    openFlags,
166   ULONG    blocksize,
167   BOOL     fileBased)
168 {
169   LPBIGBLOCKFILE This;
170
171   This = HeapAlloc(GetProcessHeap(), 0, sizeof(BigBlockFile));
172
173   if (This == NULL)
174     return NULL;
175
176   This->fileBased = fileBased;
177
178   This->flProtect = BIGBLOCKFILE_GetProtectMode(openFlags);
179
180   This->blocksize = blocksize;
181
182   This->maplist = NULL;
183   This->victimhead = NULL;
184   This->victimtail = NULL;
185   This->num_victim_pages = 0;
186
187   if (This->fileBased)
188   {
189     if (!BIGBLOCKFILE_FileInit(This, hFile))
190     {
191       HeapFree(GetProcessHeap(), 0, This);
192       return NULL;
193     }
194   }
195   else
196   {
197     if (!BIGBLOCKFILE_MemInit(This, pLkByt))
198     {
199       HeapFree(GetProcessHeap(), 0, This);
200       return NULL;
201     }
202   }
203
204   return This;
205 }
206
207 /******************************************************************************
208  *      BIGBLOCKFILE_FileInit
209  *
210  * Initialize a big block object supported by a file.
211  */
212 static BOOL BIGBLOCKFILE_FileInit(LPBIGBLOCKFILE This, HANDLE hFile)
213 {
214   This->pLkbyt     = NULL;
215   This->hbytearray = 0;
216   This->pbytearray = NULL;
217
218   This->hfile = hFile;
219
220   if (This->hfile == INVALID_HANDLE_VALUE)
221     return FALSE;
222
223   This->filesize.u.LowPart = GetFileSize(This->hfile,
224                                          &This->filesize.u.HighPart);
225
226   if( This->filesize.u.LowPart || This->filesize.u.HighPart )
227   {
228     /* create the file mapping object
229      */
230     This->hfilemap = CreateFileMappingA(This->hfile,
231                                         NULL,
232                                         This->flProtect,
233                                         0, 0,
234                                         NULL);
235
236     if (!This->hfilemap)
237     {
238       CloseHandle(This->hfile);
239       return FALSE;
240     }
241   }
242   else
243     This->hfilemap = NULL;
244
245   This->maplist = NULL;
246
247   TRACE("file len %u\n", This->filesize.u.LowPart);
248
249   return TRUE;
250 }
251
252 /******************************************************************************
253  *      BIGBLOCKFILE_MemInit
254  *
255  * Initialize a big block object supported by an ILockBytes on HGLOABL.
256  */
257 static BOOL BIGBLOCKFILE_MemInit(LPBIGBLOCKFILE This, ILockBytes* plkbyt)
258 {
259   This->hfile       = 0;
260   This->hfilemap    = 0;
261
262   /*
263    * Retrieve the handle to the byte array from the LockByte object.
264    */
265   if (GetHGlobalFromILockBytes(plkbyt, &(This->hbytearray)) != S_OK)
266   {
267     FIXME("May not be an ILockBytes on HGLOBAL\n");
268     return FALSE;
269   }
270
271   This->pLkbyt = plkbyt;
272
273   /*
274    * Increment the reference count of the ILockByte object since
275    * we're keeping a reference to it.
276    */
277   ILockBytes_AddRef(This->pLkbyt);
278
279   This->filesize.u.LowPart = GlobalSize(This->hbytearray);
280   This->filesize.u.HighPart = 0;
281
282   This->pbytearray = GlobalLock(This->hbytearray);
283
284   TRACE("mem on %p len %u\n", This->pbytearray, This->filesize.u.LowPart);
285
286   return TRUE;
287 }
288
289 /******************************************************************************
290  *      BIGBLOCKFILE_Destructor
291  *
292  * Destructor. Clean up, free memory.
293  */
294 void BIGBLOCKFILE_Destructor(
295   LPBIGBLOCKFILE This)
296 {
297   BIGBLOCKFILE_FreeAllMappedPages(This);
298
299   if (This->fileBased)
300   {
301     CloseHandle(This->hfilemap);
302     CloseHandle(This->hfile);
303   }
304   else
305   {
306     GlobalUnlock(This->hbytearray);
307     ILockBytes_Release(This->pLkbyt);
308   }
309
310   /* destroy this
311    */
312   HeapFree(GetProcessHeap(), 0, This);
313 }
314
315 /******************************************************************************
316  *      BIGBLOCKFILE_EnsureExists
317  *
318  * Grows the file if necessary to make sure the block is valid.
319  */
320 void BIGBLOCKFILE_EnsureExists(LPBIGBLOCKFILE This, ULONG index)
321 {
322   /*
323    * block index starts at -1
324    * translate to zero based index
325    */
326   if (index == 0xffffffff)
327     index = 0;
328   else
329     index++;
330
331   /*
332    * make sure that the block physically exists
333    */
334   if ((This->blocksize * (index + 1)) > This->filesize.u.LowPart)
335   {
336     ULARGE_INTEGER newSize;
337
338     newSize.u.HighPart = 0;
339     newSize.u.LowPart = This->blocksize * (index + 1);
340
341     BIGBLOCKFILE_SetSize(This, newSize);
342   }
343 }
344
345 /******************************************************************************
346  *      BIGBLOCKFILE_SetSize
347  *
348  * Sets the size of the file.
349  *
350  */
351 void BIGBLOCKFILE_SetSize(LPBIGBLOCKFILE This, ULARGE_INTEGER newSize)
352 {
353   if (This->filesize.u.LowPart == newSize.u.LowPart)
354     return;
355
356   TRACE("from %u to %u\n", This->filesize.u.LowPart, newSize.u.LowPart);
357   /*
358    * unmap all views, must be done before call to SetEndFile
359    */
360   BIGBLOCKFILE_UnmapAllMappedPages(This);
361
362   if (This->fileBased)
363   {
364     LARGE_INTEGER newpos;
365
366     newpos.QuadPart = newSize.QuadPart;
367     if (SetFilePointerEx(This->hfile, newpos, NULL, FILE_BEGIN))
368     {
369         if( This->hfilemap ) CloseHandle(This->hfilemap);
370
371         SetEndOfFile(This->hfile);
372
373         /*
374          * re-create the file mapping object
375          */
376         This->hfilemap = CreateFileMappingA(This->hfile,
377                                             NULL,
378                                             This->flProtect,
379                                             0, 0,
380                                             NULL);
381     }
382   }
383   else
384   {
385     GlobalUnlock(This->hbytearray);
386
387     /*
388      * Resize the byte array object.
389      */
390     ILockBytes_SetSize(This->pLkbyt, newSize);
391
392     /*
393      * Re-acquire the handle, it may have changed.
394      */
395     GetHGlobalFromILockBytes(This->pLkbyt, &This->hbytearray);
396     This->pbytearray = GlobalLock(This->hbytearray);
397   }
398
399   This->filesize.u.LowPart = newSize.u.LowPart;
400   This->filesize.u.HighPart = newSize.u.HighPart;
401
402   BIGBLOCKFILE_RemapAllMappedPages(This);
403 }
404
405 /******************************************************************************
406  *      BIGBLOCKFILE_GetSize
407  *
408  * Returns the size of the file.
409  *
410  */
411 ULARGE_INTEGER BIGBLOCKFILE_GetSize(LPBIGBLOCKFILE This)
412 {
413   return This->filesize;
414 }
415
416 /******************************************************************************
417  *      BIGBLOCKFILE_FindPageInList      [PRIVATE]
418  *
419  */
420 static MappedPage *BIGBLOCKFILE_FindPageInList(MappedPage *head,
421                                                ULONG page_index)
422 {
423     for (; head != NULL; head = head->next)
424     {
425         if (head->page_index == page_index)
426         {
427             InterlockedIncrement(&head->refcnt);
428             break;
429         }
430     }
431
432     return head;
433
434 }
435
436 static void BIGBLOCKFILE_UnlinkPage(MappedPage *page)
437 {
438     if (page->next) page->next->prev = page->prev;
439     if (page->prev) page->prev->next = page->next;
440 }
441
442 static void BIGBLOCKFILE_LinkHeadPage(MappedPage **head, MappedPage *page)
443 {
444     if (*head) (*head)->prev = page;
445     page->next = *head;
446     page->prev = NULL;
447     *head = page;
448 }
449
450 /******************************************************************************
451  *      BIGBLOCKFILE_GetMappedView      [PRIVATE]
452  *
453  * Gets the page requested if it is already mapped.
454  * If it's not already mapped, this method will map it
455  */
456 static void * BIGBLOCKFILE_GetMappedView(
457   LPBIGBLOCKFILE This,
458   DWORD          page_index)
459 {
460     MappedPage *page;
461
462     page = BIGBLOCKFILE_FindPageInList(This->maplist, page_index);
463     if (!page)
464     {
465         page = BIGBLOCKFILE_FindPageInList(This->victimhead, page_index);
466         if (page)
467         {
468             This->num_victim_pages--;
469
470             BIGBLOCKFILE_Zero(&page->readable_blocks);
471             BIGBLOCKFILE_Zero(&page->writable_blocks);
472         }
473     }
474
475     if (page)
476     {
477         /* If the page is not already at the head of the list, move
478          * it there. (Also moves pages from victim to main list.) */
479         if (This->maplist != page)
480         {
481             if (This->victimhead == page) This->victimhead = page->next;
482             if (This->victimtail == page) This->victimtail = page->prev;
483
484             BIGBLOCKFILE_UnlinkPage(page);
485
486             BIGBLOCKFILE_LinkHeadPage(&This->maplist, page);
487         }
488
489         return page;
490     }
491
492     page = BIGBLOCKFILE_CreatePage(This, page_index);
493     if (!page) return NULL;
494
495     BIGBLOCKFILE_LinkHeadPage(&This->maplist, page);
496
497     return page;
498 }
499
500 static BOOL BIGBLOCKFILE_MapPage(LPBIGBLOCKFILE This, MappedPage *page)
501 {
502     DWORD lowoffset = PAGE_SIZE * page->page_index;
503
504     if (This->fileBased)
505     {
506         DWORD numBytesToMap;
507         DWORD desired_access;
508
509         if( !This->hfilemap )
510             return FALSE;
511
512         if (lowoffset + PAGE_SIZE > This->filesize.u.LowPart)
513             numBytesToMap = This->filesize.u.LowPart - lowoffset;
514         else
515             numBytesToMap = PAGE_SIZE;
516
517         if (This->flProtect == PAGE_READONLY)
518             desired_access = FILE_MAP_READ;
519         else
520             desired_access = FILE_MAP_WRITE;
521
522         page->lpBytes = MapViewOfFile(This->hfilemap, desired_access, 0,
523                                       lowoffset, numBytesToMap);
524         page->mapped_bytes = numBytesToMap;
525     }
526     else
527     {
528         page->lpBytes = (LPBYTE)This->pbytearray + lowoffset;
529         page->mapped_bytes = PAGE_SIZE;
530     }
531
532     TRACE("mapped page %u to %p\n", page->page_index, page->lpBytes);
533
534     return page->lpBytes != NULL;
535 }
536
537 static MappedPage *BIGBLOCKFILE_CreatePage(LPBIGBLOCKFILE This,
538                                            ULONG page_index)
539 {
540     MappedPage *page;
541
542     page = HeapAlloc(GetProcessHeap(), 0, sizeof(MappedPage));
543     if (page == NULL)
544       return NULL;
545
546     page->page_index = page_index;
547     page->refcnt = 1;
548
549     page->next = NULL;
550     page->prev = NULL;
551
552     if (!BIGBLOCKFILE_MapPage(This, page))
553     {
554         HeapFree(GetProcessHeap(),0,page);
555         return NULL;
556     }
557
558     BIGBLOCKFILE_Zero(&page->readable_blocks);
559     BIGBLOCKFILE_Zero(&page->writable_blocks);
560
561     return page;
562 }
563
564 static void BIGBLOCKFILE_UnmapPage(LPBIGBLOCKFILE This, MappedPage *page)
565 {
566     TRACE("%d at %p\n", page->page_index, page->lpBytes);
567     if (page->refcnt > 0)
568         ERR("unmapping inuse page %p\n", page->lpBytes);
569
570     if (This->fileBased && page->lpBytes)
571         UnmapViewOfFile(page->lpBytes);
572
573     page->lpBytes = NULL;
574 }
575
576 static void BIGBLOCKFILE_DeletePage(LPBIGBLOCKFILE This, MappedPage *page)
577 {
578     BIGBLOCKFILE_UnmapPage(This, page);
579
580     HeapFree(GetProcessHeap(), 0, page);
581 }
582
583 /******************************************************************************
584  *      BIGBLOCKFILE_ReleaseMappedPage      [PRIVATE]
585  *
586  * Decrements the reference count of the mapped page.
587  */
588 static void BIGBLOCKFILE_ReleaseMappedPage(
589   LPBIGBLOCKFILE This,
590   MappedPage    *page)
591 {
592     assert(This != NULL);
593     assert(page != NULL);
594
595     /* If the page is no longer refenced, move it to the victim list.
596      * If the victim list is too long, kick somebody off. */
597     if (!InterlockedDecrement(&page->refcnt))
598     {
599         if (This->maplist == page) This->maplist = page->next;
600
601         BIGBLOCKFILE_UnlinkPage(page);
602
603         if (MAX_VICTIM_PAGES > 0)
604         {
605             if (This->num_victim_pages >= MAX_VICTIM_PAGES)
606             {
607                 MappedPage *victim = This->victimtail;
608                 if (victim)
609                 {
610                     This->victimtail = victim->prev;
611                     if (This->victimhead == victim)
612                         This->victimhead = victim->next;
613
614                     BIGBLOCKFILE_UnlinkPage(victim);
615                     BIGBLOCKFILE_DeletePage(This, victim);
616                 }
617             }
618             else This->num_victim_pages++;
619
620             BIGBLOCKFILE_LinkHeadPage(&This->victimhead, page);
621             if (This->victimtail == NULL) This->victimtail = page;
622         }
623         else
624             BIGBLOCKFILE_DeletePage(This, page);
625     }
626 }
627
628 static void BIGBLOCKFILE_DeleteList(LPBIGBLOCKFILE This, MappedPage *list)
629 {
630     while (list != NULL)
631     {
632         MappedPage *next = list->next;
633
634         BIGBLOCKFILE_DeletePage(This, list);
635
636         list = next;
637     }
638 }
639
640 /******************************************************************************
641  *      BIGBLOCKFILE_FreeAllMappedPages     [PRIVATE]
642  *
643  * Unmap all currently mapped pages.
644  * Empty mapped pages list.
645  */
646 static void BIGBLOCKFILE_FreeAllMappedPages(
647   LPBIGBLOCKFILE This)
648 {
649     BIGBLOCKFILE_DeleteList(This, This->maplist);
650     BIGBLOCKFILE_DeleteList(This, This->victimhead);
651
652     This->maplist = NULL;
653     This->victimhead = NULL;
654     This->victimtail = NULL;
655     This->num_victim_pages = 0;
656 }
657
658 static void BIGBLOCKFILE_UnmapList(LPBIGBLOCKFILE This, MappedPage *list)
659 {
660     for (; list != NULL; list = list->next)
661     {
662         BIGBLOCKFILE_UnmapPage(This, list);
663     }
664 }
665
666 static void BIGBLOCKFILE_UnmapAllMappedPages(LPBIGBLOCKFILE This)
667 {
668     BIGBLOCKFILE_UnmapList(This, This->maplist);
669     BIGBLOCKFILE_UnmapList(This, This->victimhead);
670 }
671
672 static void BIGBLOCKFILE_RemapList(LPBIGBLOCKFILE This, MappedPage *list)
673 {
674     while (list != NULL)
675     {
676         MappedPage *next = list->next;
677
678         if (list->page_index * PAGE_SIZE > This->filesize.u.LowPart)
679         {
680             TRACE("discarding %u\n", list->page_index);
681
682             /* page is entirely outside of the file, delete it */
683             BIGBLOCKFILE_UnlinkPage(list);
684             BIGBLOCKFILE_DeletePage(This, list);
685         }
686         else
687         {
688             /* otherwise, remap it */
689             BIGBLOCKFILE_MapPage(This, list);
690         }
691
692         list = next;
693     }
694 }
695
696 static void BIGBLOCKFILE_RemapAllMappedPages(LPBIGBLOCKFILE This)
697 {
698     BIGBLOCKFILE_RemapList(This, This->maplist);
699     BIGBLOCKFILE_RemapList(This, This->victimhead);
700 }
701
702 /****************************************************************************
703  *      BIGBLOCKFILE_GetProtectMode
704  *
705  * This function will return a protection mode flag for a file-mapping object
706  * from the open flags of a file.
707  */
708 static DWORD BIGBLOCKFILE_GetProtectMode(DWORD openFlags)
709 {
710     switch(STGM_ACCESS_MODE(openFlags))
711     {
712     case STGM_WRITE:
713     case STGM_READWRITE:
714         return PAGE_READWRITE;
715     }
716     return PAGE_READONLY;
717 }
718
719
720 /* ILockByte Interfaces */
721
722 /******************************************************************************
723  * This method is part of the ILockBytes interface.
724  *
725  * It reads a block of information from the byte array at the specified
726  * offset.
727  *
728  * See the documentation of ILockBytes for more info.
729  */
730 static HRESULT WINAPI ImplBIGBLOCKFILE_ReadAt(
731       BigBlockFile* const This,
732       ULARGE_INTEGER ulOffset,  /* [in] */
733       void*          pv,        /* [length_is][size_is][out] */
734       ULONG          cb,        /* [in] */
735       ULONG*         pcbRead)   /* [out] */
736 {
737     ULONG first_page = ulOffset.u.LowPart / PAGE_SIZE;
738     ULONG offset_in_page = ulOffset.u.LowPart % PAGE_SIZE;
739     ULONG bytes_left = cb;
740     ULONG page_index = first_page;
741     ULONG bytes_from_page;
742     LPVOID writePtr = pv;
743
744     HRESULT rc = S_OK;
745
746     TRACE("(%p)-> %i %p %i %p\n",This, ulOffset.u.LowPart, pv, cb, pcbRead);
747
748     /* verify a sane enviroment */
749     if (!This) return E_FAIL;
750
751     if (offset_in_page + bytes_left > PAGE_SIZE)
752         bytes_from_page = PAGE_SIZE - offset_in_page;
753     else
754         bytes_from_page = bytes_left;
755
756     if (pcbRead)
757         *pcbRead = 0;
758
759     while (bytes_left)
760     {
761         LPBYTE readPtr;
762         BOOL eof = FALSE;
763         MappedPage *page = BIGBLOCKFILE_GetMappedView(This, page_index);
764
765         if (!page || !page->lpBytes)
766         {
767             rc = STG_E_READFAULT;
768             break;
769         }
770
771         TRACE("page %i,  offset %u, bytes_from_page %u, bytes_left %u\n",
772             page->page_index, offset_in_page, bytes_from_page, bytes_left);
773
774         if (page->mapped_bytes < bytes_from_page)
775         {
776             eof = TRUE;
777             bytes_from_page = page->mapped_bytes;
778         }
779
780         readPtr = (BYTE*)page->lpBytes + offset_in_page;
781         memcpy(writePtr,readPtr,bytes_from_page);
782         BIGBLOCKFILE_ReleaseMappedPage(This, page);
783
784         if (pcbRead)
785             *pcbRead += bytes_from_page;
786         bytes_left -= bytes_from_page;
787
788         if (bytes_left && !eof)
789         {
790             writePtr = (LPBYTE)writePtr + bytes_from_page;
791             page_index ++;
792             offset_in_page = 0;
793             if (bytes_left > PAGE_SIZE)
794                 bytes_from_page = PAGE_SIZE;
795             else
796                 bytes_from_page = bytes_left;
797         }
798         if (eof)
799         {
800             rc = STG_E_READFAULT;
801             break;
802         }
803     }
804
805     TRACE("finished\n");
806     return rc;
807 }
808
809 /******************************************************************************
810  * This method is part of the ILockBytes interface.
811  *
812  * It writes the specified bytes at the specified offset.
813  * position. If the file is too small, it will be resized.
814  *
815  * See the documentation of ILockBytes for more info.
816  */
817 static HRESULT WINAPI ImplBIGBLOCKFILE_WriteAt(
818       BigBlockFile* const This,
819       ULARGE_INTEGER ulOffset,    /* [in] */
820       const void*    pv,          /* [size_is][in] */
821       ULONG          cb,          /* [in] */
822       ULONG*         pcbWritten)  /* [out] */
823 {
824     ULONG size_needed = ulOffset.u.LowPart + cb;
825     ULONG first_page = ulOffset.u.LowPart / PAGE_SIZE;
826     ULONG offset_in_page = ulOffset.u.LowPart % PAGE_SIZE;
827     ULONG bytes_left = cb;
828     ULONG page_index = first_page;
829     ULONG bytes_to_page;
830     LPCVOID readPtr = pv;
831
832     HRESULT rc = S_OK;
833
834     TRACE("(%p)-> %i %p %i %p\n",This, ulOffset.u.LowPart, pv, cb, pcbWritten);
835
836     /* verify a sane enviroment */
837     if (!This) return E_FAIL;
838
839     if (This->flProtect != PAGE_READWRITE)
840         return STG_E_ACCESSDENIED;
841
842     if (size_needed > This->filesize.u.LowPart)
843     {
844         ULARGE_INTEGER newSize;
845         newSize.u.HighPart = 0;
846         newSize.u.LowPart = size_needed;
847         BIGBLOCKFILE_SetSize(This, newSize);
848     }
849
850     if (offset_in_page + bytes_left > PAGE_SIZE)
851         bytes_to_page = PAGE_SIZE - offset_in_page;
852     else
853         bytes_to_page = bytes_left;
854
855     if (pcbWritten)
856         *pcbWritten = 0;
857
858     while (bytes_left)
859     {
860         LPBYTE writePtr;
861         MappedPage *page = BIGBLOCKFILE_GetMappedView(This, page_index);
862
863         TRACE("page %i,  offset %u, bytes_to_page %u, bytes_left %u\n",
864             page->page_index, offset_in_page, bytes_to_page, bytes_left);
865
866         if (page->mapped_bytes < bytes_to_page)
867         {
868             ERR("Not enough bytes mapped to the page. This should never happen\n");
869             rc = E_FAIL;
870             break;
871         }
872
873         if (!page)
874         {
875             ERR("Unable to get a page to write. This should never happen\n");
876             rc = E_FAIL;
877             break;
878         }
879
880         writePtr = (BYTE*)page->lpBytes + offset_in_page;
881         memcpy(writePtr,readPtr,bytes_to_page);
882         BIGBLOCKFILE_ReleaseMappedPage(This, page);
883
884         if (pcbWritten)
885             *pcbWritten += bytes_to_page;
886         bytes_left -= bytes_to_page;
887
888         if (bytes_left)
889         {
890             readPtr = (LPBYTE)readPtr + bytes_to_page;
891             page_index ++;
892             offset_in_page = 0;
893             if (bytes_left > PAGE_SIZE)
894                 bytes_to_page = PAGE_SIZE;
895             else
896                 bytes_to_page = bytes_left;
897         }
898     }
899
900     return rc;
901 }
902
903
904 HRESULT     BIGBLOCKFILE_ReadAt(LPBIGBLOCKFILE This, ULARGE_INTEGER offset,
905                 void* buffer, ULONG size, ULONG* bytesRead)
906 {
907     if (This->fileBased)
908         return ImplBIGBLOCKFILE_ReadAt(This,offset,buffer,size,bytesRead);
909     else
910         return ILockBytes_ReadAt(This->pLkbyt,offset,buffer,size,bytesRead);
911 }
912
913 HRESULT    BIGBLOCKFILE_WriteAt(LPBIGBLOCKFILE This, ULARGE_INTEGER offset,
914                 void* buffer, const ULONG size, ULONG* bytesRead)
915 {
916     if (This->fileBased)
917         return ImplBIGBLOCKFILE_WriteAt(This,offset,buffer,size,bytesRead);
918     else
919         return ILockBytes_WriteAt(This->pLkbyt,offset,buffer,size,bytesRead);
920 }