xref: /reactos/ntoskrnl/cache/fssup.c (revision 139a3d66)
1 /*
2  * COPYRIGHT:       See COPYING in the top level directory
3  * PROJECT:         ReactOS Kernel
4  * FILE:            ntoskrnl/cache/fssup.c
5  * PURPOSE:         Logging and configuration routines
6  * PROGRAMMERS:     Alex Ionescu (alex.ionescu@reactos.org)
7  *                  Art Yerkes
8  */
9 
10 /* INCLUDES *******************************************************************/
11 
12 #include <ntoskrnl.h>
13 #include "newcc.h"
14 #include "section/newmm.h"
15 #define NDEBUG
16 #include <debug.h>
17 
18 /* GLOBALS ********************************************************************/
19 
20 PFSN_PREFETCHER_GLOBALS CcPfGlobals;
21 extern LONG CcOutstandingDeletes;
22 extern KEVENT CcpLazyWriteEvent;
23 extern KEVENT CcFinalizeEvent;
24 extern VOID NTAPI CcpUnmapThread(PVOID Unused);
25 extern VOID NTAPI CcpLazyWriteThread(PVOID Unused);
26 HANDLE CcUnmapThreadHandle, CcLazyWriteThreadHandle;
27 CLIENT_ID CcUnmapThreadId, CcLazyWriteThreadId;
28 FAST_MUTEX GlobalPageOperation;
29 
30 /*
31 
32 A note about private cache maps.
33 
34 CcInitializeCacheMap and CcUninitializeCacheMap are not meant to be paired,
35 although they can work that way.
36 
37 The actual operation I've gleaned from reading both jan kratchovil's writing
38 and real filesystems is this:
39 
40 CcInitializeCacheMap means:
41 
42 Make the indicated FILE_OBJECT have a private cache map if it doesn't already
43 and make it have a shared cache map if it doesn't already.
44 
45 CcUninitializeCacheMap means:
46 
47 Take away the private cache map from this FILE_OBJECT.  If it's the last
48 private cache map corresponding to a specific shared cache map (the one that
49 was present in the FILE_OBJECT when it was created), then delete that too,
50 flusing all cached information.
51 
52 Using these simple semantics, filesystems can do all the things they actually
53 do:
54 
55 - Copy out the shared cache map pointer from a newly initialized file object
56 and store it in the fcb cache.
57 - Copy it back into any file object and call CcInitializeCacheMap to make
58 that file object be associated with the caching of all the other siblings.
59 - Call CcUninitializeCacheMap on a FILE_OBJECT many times, but have only the
60 first one count for each specific FILE_OBJECT.
61 - Have the actual last call to CcUninitializeCacheMap (that is, the one that
62 causes zero private cache maps to be associated with a shared cache map) to
63 delete the cache map and flush.
64 
65 So private cache map here is a light weight structure that just remembers
66 what shared cache map it associates with.
67 
68  */
69 typedef struct _NOCC_PRIVATE_CACHE_MAP
70 {
71     LIST_ENTRY ListEntry;
72     PFILE_OBJECT FileObject;
73     PNOCC_CACHE_MAP Map;
74 } NOCC_PRIVATE_CACHE_MAP, *PNOCC_PRIVATE_CACHE_MAP;
75 
76 LIST_ENTRY CcpAllSharedCacheMaps;
77 
78 /* FUNCTIONS ******************************************************************/
79 
80 INIT_FUNCTION
81 BOOLEAN
82 NTAPI
83 CcInitializeCacheManager(VOID)
84 {
85     int i;
86 
87     DPRINT("Initialize\n");
88     for (i = 0; i < CACHE_NUM_SECTIONS; i++)
89     {
90         KeInitializeEvent(&CcCacheSections[i].ExclusiveWait,
91                           SynchronizationEvent,
92                           FALSE);
93 
94         InitializeListHead(&CcCacheSections[i].ThisFileList);
95     }
96 
97     InitializeListHead(&CcpAllSharedCacheMaps);
98 
99     KeInitializeEvent(&CcDeleteEvent, SynchronizationEvent, FALSE);
100     KeInitializeEvent(&CcFinalizeEvent, SynchronizationEvent, FALSE);
101     KeInitializeEvent(&CcpLazyWriteEvent, SynchronizationEvent, FALSE);
102 
103     CcCacheBitmap->Buffer = ((PULONG)&CcCacheBitmap[1]);
104     CcCacheBitmap->SizeOfBitMap = ROUND_UP(CACHE_NUM_SECTIONS, 32);
105     DPRINT1("Cache has %d entries\n", CcCacheBitmap->SizeOfBitMap);
106     ExInitializeFastMutex(&CcMutex);
107 
108     return TRUE;
109 }
110 
111 INIT_FUNCTION
112 VOID
113 NTAPI
114 CcPfInitializePrefetcher(VOID)
115 {
116     /* Notify debugger */
117     DbgPrintEx(DPFLTR_PREFETCHER_ID,
118                DPFLTR_TRACE_LEVEL,
119                "CCPF: InitializePrefetecher()\n");
120 
121     /* Setup the Prefetcher Data */
122     InitializeListHead(&CcPfGlobals.ActiveTraces);
123     InitializeListHead(&CcPfGlobals.CompletedTraces);
124     ExInitializeFastMutex(&CcPfGlobals.CompletedTracesLock);
125 
126     /* FIXME: Setup the rest of the prefetecher */
127 }
128 
129 BOOLEAN
130 NTAPI
131 CcpAcquireFileLock(PNOCC_CACHE_MAP Map)
132 {
133     DPRINT("Calling AcquireForLazyWrite: %x\n", Map->LazyContext);
134     return Map->Callbacks.AcquireForLazyWrite(Map->LazyContext, TRUE);
135 }
136 
137 VOID
138 NTAPI
139 CcpReleaseFileLock(PNOCC_CACHE_MAP Map)
140 {
141     DPRINT("Releasing Lazy Write %x\n", Map->LazyContext);
142     Map->Callbacks.ReleaseFromLazyWrite(Map->LazyContext);
143 }
144 
145 /*
146 
147 Cc functions are required to treat alternate streams of a file as the same
148 for the purpose of caching, meaning that we must be able to find the shared
149 cache map associated with the ``real'' stream associated with a stream file
150 object, if one exists.  We do that by identifying a private cache map in
151 our gamut that has the same volume, device and fscontext as the stream file
152 object we're holding.  It's heavy but it does work.  This can probably be
153 improved, although there doesn't seem to be any real association between
154 a stream file object and a sibling file object in the file object struct
155 itself.
156 
157  */
158 
159 /* Must have CcpLock() */
160 PFILE_OBJECT CcpFindOtherStreamFileObject(PFILE_OBJECT FileObject)
161 {
162     PLIST_ENTRY Entry, Private;
163     for (Entry = CcpAllSharedCacheMaps.Flink;
164          Entry != &CcpAllSharedCacheMaps;
165          Entry = Entry->Flink)
166     {
167         /* 'Identical' test for other stream file object */
168         PNOCC_CACHE_MAP Map = CONTAINING_RECORD(Entry, NOCC_CACHE_MAP, Entry);
169         for (Private = Map->PrivateCacheMaps.Flink;
170              Private != &Map->PrivateCacheMaps;
171              Private = Private->Flink)
172         {
173             PNOCC_PRIVATE_CACHE_MAP PrivateMap = CONTAINING_RECORD(Private,
174                                                                    NOCC_PRIVATE_CACHE_MAP,
175                                                                    ListEntry);
176 
177             if (PrivateMap->FileObject->Flags & FO_STREAM_FILE &&
178                 PrivateMap->FileObject->DeviceObject == FileObject->DeviceObject &&
179                 PrivateMap->FileObject->Vpb == FileObject->Vpb &&
180                 PrivateMap->FileObject->FsContext == FileObject->FsContext &&
181                 PrivateMap->FileObject->FsContext2 == FileObject->FsContext2 &&
182                 1)
183             {
184                 return PrivateMap->FileObject;
185             }
186         }
187     }
188     return 0;
189 }
190 
191 /* Thanks: http://windowsitpro.com/Windows/Articles/ArticleID/3864/pg/2/2.html */
192 
193 VOID
194 NTAPI
195 CcInitializeCacheMap(IN PFILE_OBJECT FileObject,
196                      IN PCC_FILE_SIZES FileSizes,
197                      IN BOOLEAN PinAccess,
198                      IN PCACHE_MANAGER_CALLBACKS Callbacks,
199                      IN PVOID LazyWriteContext)
200 {
201     PNOCC_CACHE_MAP Map = FileObject->SectionObjectPointer->SharedCacheMap;
202     PNOCC_PRIVATE_CACHE_MAP PrivateCacheMap = FileObject->PrivateCacheMap;
203 
204     CcpLock();
205     /* We don't have a shared cache map.  First find out if we have a sibling
206        stream file object we can take it from. */
207     if (!Map && FileObject->Flags & FO_STREAM_FILE)
208     {
209         PFILE_OBJECT IdenticalStreamFileObject = CcpFindOtherStreamFileObject(FileObject);
210         if (IdenticalStreamFileObject)
211             Map = IdenticalStreamFileObject->SectionObjectPointer->SharedCacheMap;
212         if (Map)
213         {
214             DPRINT1("Linking SFO %x to previous SFO %x through cache map %x #\n",
215                     FileObject,
216                     IdenticalStreamFileObject,
217                     Map);
218         }
219     }
220     /* We still don't have a shared cache map.  We need to create one. */
221     if (!Map)
222     {
223         DPRINT("Initializing file object for (%p) %wZ\n",
224                FileObject,
225                &FileObject->FileName);
226 
227         Map = ExAllocatePool(NonPagedPool, sizeof(NOCC_CACHE_MAP));
228         FileObject->SectionObjectPointer->SharedCacheMap = Map;
229         Map->FileSizes = *FileSizes;
230         Map->LazyContext = LazyWriteContext;
231         Map->ReadAheadGranularity = PAGE_SIZE;
232         RtlCopyMemory(&Map->Callbacks, Callbacks, sizeof(*Callbacks));
233 
234         /* For now ... */
235         DPRINT("FileSizes->ValidDataLength %I64x\n",
236                FileSizes->ValidDataLength.QuadPart);
237 
238         InitializeListHead(&Map->AssociatedBcb);
239         InitializeListHead(&Map->PrivateCacheMaps);
240         InsertTailList(&CcpAllSharedCacheMaps, &Map->Entry);
241         DPRINT("New Map %p\n", Map);
242     }
243     /* We don't have a private cache map.  Link it with the shared cache map
244        to serve as a held reference. When the list in the shared cache map
245        is empty, we know we can delete it. */
246     if (!PrivateCacheMap)
247     {
248         PrivateCacheMap = ExAllocatePool(NonPagedPool,
249                                          sizeof(*PrivateCacheMap));
250 
251         FileObject->PrivateCacheMap = PrivateCacheMap;
252         PrivateCacheMap->FileObject = FileObject;
253         ObReferenceObject(PrivateCacheMap->FileObject);
254     }
255 
256     PrivateCacheMap->Map = Map;
257     InsertTailList(&Map->PrivateCacheMaps, &PrivateCacheMap->ListEntry);
258 
259     CcpUnlock();
260 }
261 
262 /*
263 
264 This function is used by NewCC's MM to determine whether any section objects
265 for a given file are not cache sections.  If that's true, we're not allowed
266 to resize the file, although nothing actually prevents us from doing ;-)
267 
268  */
269 
270 ULONG
271 NTAPI
272 CcpCountCacheSections(IN PNOCC_CACHE_MAP Map)
273 {
274     PLIST_ENTRY Entry;
275     ULONG Count;
276 
277     for (Count = 0, Entry = Map->AssociatedBcb.Flink;
278          Entry != &Map->AssociatedBcb;
279          Entry = Entry->Flink, Count++);
280 
281     return Count;
282 }
283 
284 BOOLEAN
285 NTAPI
286 CcUninitializeCacheMap(IN PFILE_OBJECT FileObject,
287                        IN OPTIONAL PLARGE_INTEGER TruncateSize,
288                        IN OPTIONAL PCACHE_UNINITIALIZE_EVENT UninitializeEvent)
289 {
290     BOOLEAN LastMap = FALSE;
291     PNOCC_CACHE_MAP Map = (PNOCC_CACHE_MAP)FileObject->SectionObjectPointer->SharedCacheMap;
292     PNOCC_PRIVATE_CACHE_MAP PrivateCacheMap = FileObject->PrivateCacheMap;
293 
294     DPRINT("Uninitializing file object for %wZ SectionObjectPointer %x\n",
295            &FileObject->FileName,
296            FileObject->SectionObjectPointer);
297 
298     ASSERT(UninitializeEvent == NULL);
299 
300     /* It may not be strictly necessary to flush here, but we do just for
301        kicks. */
302     if (Map)
303         CcpFlushCache(Map, NULL, 0, NULL, FALSE);
304 
305     CcpLock();
306     /* We have a private cache map, so we've been initialized and haven't been
307      * uninitialized. */
308     if (PrivateCacheMap)
309     {
310         ASSERT(!Map || Map == PrivateCacheMap->Map);
311         ASSERT(PrivateCacheMap->FileObject == FileObject);
312 
313         RemoveEntryList(&PrivateCacheMap->ListEntry);
314         /* That was the last private cache map.  It's time to delete all
315            cache stripes and all aspects of caching on the file. */
316         if (IsListEmpty(&PrivateCacheMap->Map->PrivateCacheMaps))
317         {
318             /* Get rid of all the cache stripes. */
319             while (!IsListEmpty(&PrivateCacheMap->Map->AssociatedBcb))
320             {
321                 PNOCC_BCB Bcb = CONTAINING_RECORD(PrivateCacheMap->Map->AssociatedBcb.Flink,
322                                                   NOCC_BCB,
323                                                   ThisFileList);
324 
325                 DPRINT("Evicting cache stripe #%x\n", Bcb - CcCacheSections);
326                 Bcb->RefCount = 1;
327                 CcpDereferenceCache(Bcb - CcCacheSections, TRUE);
328             }
329             RemoveEntryList(&PrivateCacheMap->Map->Entry);
330             ExFreePool(PrivateCacheMap->Map);
331             FileObject->SectionObjectPointer->SharedCacheMap = NULL;
332             LastMap = TRUE;
333         }
334         ObDereferenceObject(PrivateCacheMap->FileObject);
335         FileObject->PrivateCacheMap = NULL;
336         ExFreePool(PrivateCacheMap);
337     }
338     CcpUnlock();
339 
340     DPRINT("Uninit complete\n");
341 
342     /* The return from CcUninitializeCacheMap means that 'caching was stopped'. */
343     return LastMap;
344 }
345 
346 /*
347 
348 CcSetFileSizes is used to tell the cache manager that the file changed
349 size.  In our case, we use the internal Mm method MmExtendCacheSection
350 to notify Mm that our section potentially changed size, which may mean
351 truncating off data.
352 
353  */
354 VOID
355 NTAPI
356 CcSetFileSizes(IN PFILE_OBJECT FileObject,
357                IN PCC_FILE_SIZES FileSizes)
358 {
359     PNOCC_CACHE_MAP Map = (PNOCC_CACHE_MAP)FileObject->SectionObjectPointer->SharedCacheMap;
360     PNOCC_BCB Bcb;
361 
362     if (!Map) return;
363     Map->FileSizes = *FileSizes;
364     Bcb = Map->AssociatedBcb.Flink == &Map->AssociatedBcb ?
365         NULL : CONTAINING_RECORD(Map->AssociatedBcb.Flink, NOCC_BCB, ThisFileList);
366     if (!Bcb) return;
367     MmExtendCacheSection(Bcb->SectionObject, &FileSizes->FileSize, FALSE);
368     DPRINT("FileSizes->FileSize %x\n", FileSizes->FileSize.LowPart);
369     DPRINT("FileSizes->AllocationSize %x\n", FileSizes->AllocationSize.LowPart);
370     DPRINT("FileSizes->ValidDataLength %x\n", FileSizes->ValidDataLength.LowPart);
371 }
372 
373 BOOLEAN
374 NTAPI
375 CcGetFileSizes(IN PFILE_OBJECT FileObject,
376                IN PCC_FILE_SIZES FileSizes)
377 {
378     PNOCC_CACHE_MAP Map = (PNOCC_CACHE_MAP)FileObject->SectionObjectPointer->SharedCacheMap;
379     if (!Map) return FALSE;
380     *FileSizes = Map->FileSizes;
381     return TRUE;
382 }
383 
384 BOOLEAN
385 NTAPI
386 CcPurgeCacheSection(IN PSECTION_OBJECT_POINTERS SectionObjectPointer,
387                     IN OPTIONAL PLARGE_INTEGER FileOffset,
388                     IN ULONG Length,
389                     IN BOOLEAN UninitializeCacheMaps)
390 {
391     PNOCC_CACHE_MAP Map = (PNOCC_CACHE_MAP)SectionObjectPointer->SharedCacheMap;
392     if (!Map) return TRUE;
393     CcpFlushCache(Map, NULL, 0, NULL, TRUE);
394     return TRUE;
395 }
396 
397 VOID
398 NTAPI
399 CcSetDirtyPageThreshold(IN PFILE_OBJECT FileObject,
400                         IN ULONG DirtyPageThreshold)
401 {
402     UNIMPLEMENTED_DBGBREAK();
403 }
404 
405 /*
406 
407 This could be implemented much more intelligently by mapping instances
408 of a CoW zero page into the affected regions.  We just RtlZeroMemory
409 for now.
410 
411 */
412 BOOLEAN
413 NTAPI
414 CcZeroData(IN PFILE_OBJECT FileObject,
415            IN PLARGE_INTEGER StartOffset,
416            IN PLARGE_INTEGER EndOffset,
417            IN BOOLEAN Wait)
418 {
419     PNOCC_BCB Bcb = NULL;
420     PLIST_ENTRY ListEntry = NULL;
421     LARGE_INTEGER LowerBound = *StartOffset;
422     LARGE_INTEGER UpperBound = *EndOffset;
423     LARGE_INTEGER Target, End;
424     PVOID PinnedBcb, PinnedBuffer;
425     PNOCC_CACHE_MAP Map = FileObject->SectionObjectPointer->SharedCacheMap;
426 
427     DPRINT("S %I64x E %I64x\n",
428            StartOffset->QuadPart,
429            EndOffset->QuadPart);
430 
431     if (!Map)
432     {
433         NTSTATUS Status;
434         IO_STATUS_BLOCK IOSB;
435         PCHAR ZeroBuf = ExAllocatePool(PagedPool, PAGE_SIZE);
436         ULONG ToWrite;
437 
438         if (!ZeroBuf) RtlRaiseStatus(STATUS_INSUFFICIENT_RESOURCES);
439         DPRINT1("RtlZeroMemory(%x,%x)\n", ZeroBuf, PAGE_SIZE);
440         RtlZeroMemory(ZeroBuf, PAGE_SIZE);
441 
442         Target.QuadPart = PAGE_ROUND_DOWN(LowerBound.QuadPart);
443         End.QuadPart = PAGE_ROUND_UP(UpperBound.QuadPart);
444 
445         // Handle leading page
446         if (LowerBound.QuadPart != Target.QuadPart)
447         {
448             ToWrite = MIN(UpperBound.QuadPart - LowerBound.QuadPart,
449                           (PAGE_SIZE - LowerBound.QuadPart) & (PAGE_SIZE - 1));
450 
451             DPRINT("Zero last half %I64x %lx\n",
452                    Target.QuadPart,
453                    ToWrite);
454 
455             Status = MiSimpleRead(FileObject,
456                                   &Target,
457                                   ZeroBuf,
458                                   PAGE_SIZE,
459                                   TRUE,
460                                   &IOSB);
461 
462             if (!NT_SUCCESS(Status))
463             {
464                 ExFreePool(ZeroBuf);
465                 RtlRaiseStatus(Status);
466             }
467 
468             DPRINT1("RtlZeroMemory(%p, %lx)\n",
469                     ZeroBuf + LowerBound.QuadPart - Target.QuadPart,
470                     ToWrite);
471 
472             RtlZeroMemory(ZeroBuf + LowerBound.QuadPart - Target.QuadPart,
473                           ToWrite);
474 
475             Status = MiSimpleWrite(FileObject,
476                                    &Target,
477                                    ZeroBuf,
478                                    MIN(PAGE_SIZE,
479                                        UpperBound.QuadPart-Target.QuadPart),
480                                    &IOSB);
481 
482             if (!NT_SUCCESS(Status))
483             {
484                 ExFreePool(ZeroBuf);
485                 RtlRaiseStatus(Status);
486             }
487             Target.QuadPart += PAGE_SIZE;
488         }
489 
490         DPRINT1("RtlZeroMemory(%x,%x)\n", ZeroBuf, PAGE_SIZE);
491         RtlZeroMemory(ZeroBuf, PAGE_SIZE);
492 
493         while (UpperBound.QuadPart - Target.QuadPart > PAGE_SIZE)
494         {
495             DPRINT("Zero full page %I64x\n",
496                    Target.QuadPart);
497 
498             Status = MiSimpleWrite(FileObject,
499                                    &Target,
500                                    ZeroBuf,
501                                    PAGE_SIZE,
502                                    &IOSB);
503 
504             if (!NT_SUCCESS(Status))
505             {
506                 ExFreePool(ZeroBuf);
507                 RtlRaiseStatus(Status);
508             }
509             Target.QuadPart += PAGE_SIZE;
510         }
511 
512         if (UpperBound.QuadPart > Target.QuadPart)
513         {
514             ToWrite = UpperBound.QuadPart - Target.QuadPart;
515             DPRINT("Zero first half %I64x %lx\n",
516                    Target.QuadPart,
517                    ToWrite);
518 
519             Status = MiSimpleRead(FileObject,
520                                   &Target,
521                                   ZeroBuf,
522                                   PAGE_SIZE,
523                                   TRUE,
524                                   &IOSB);
525 
526             if (!NT_SUCCESS(Status))
527             {
528                 ExFreePool(ZeroBuf);
529                 RtlRaiseStatus(Status);
530             }
531             DPRINT1("RtlZeroMemory(%x,%x)\n", ZeroBuf, ToWrite);
532             RtlZeroMemory(ZeroBuf, ToWrite);
533             Status = MiSimpleWrite(FileObject,
534                                    &Target,
535                                    ZeroBuf,
536                                    MIN(PAGE_SIZE,
537                                        UpperBound.QuadPart-Target.QuadPart),
538                                    &IOSB);
539             if (!NT_SUCCESS(Status))
540             {
541                 ExFreePool(ZeroBuf);
542                 RtlRaiseStatus(Status);
543             }
544             Target.QuadPart += PAGE_SIZE;
545         }
546 
547         ExFreePool(ZeroBuf);
548         return TRUE;
549     }
550 
551     CcpLock();
552     ListEntry = Map->AssociatedBcb.Flink;
553 
554     while (ListEntry != &Map->AssociatedBcb)
555     {
556         Bcb = CONTAINING_RECORD(ListEntry, NOCC_BCB, ThisFileList);
557         CcpReferenceCache(Bcb - CcCacheSections);
558 
559         if (Bcb->FileOffset.QuadPart + Bcb->Length >= LowerBound.QuadPart &&
560             Bcb->FileOffset.QuadPart < UpperBound.QuadPart)
561         {
562             DPRINT("Bcb #%x (@%I64x)\n",
563                    Bcb - CcCacheSections,
564                    Bcb->FileOffset.QuadPart);
565 
566             Target.QuadPart = MAX(Bcb->FileOffset.QuadPart,
567                                   LowerBound.QuadPart);
568 
569             End.QuadPart = MIN(Map->FileSizes.ValidDataLength.QuadPart,
570                                UpperBound.QuadPart);
571 
572             End.QuadPart = MIN(End.QuadPart,
573                                Bcb->FileOffset.QuadPart + Bcb->Length);
574 
575             CcpUnlock();
576 
577             if (!CcPreparePinWrite(FileObject,
578                                    &Target,
579                                    End.QuadPart - Target.QuadPart,
580                                    TRUE,
581                                    Wait,
582                                    &PinnedBcb,
583                                    &PinnedBuffer))
584             {
585                 return FALSE;
586             }
587 
588             ASSERT(PinnedBcb == Bcb);
589 
590             CcpLock();
591             ListEntry = ListEntry->Flink;
592             /* Return from pin state */
593             CcpUnpinData(PinnedBcb, TRUE);
594         }
595 
596         CcpUnpinData(Bcb, TRUE);
597     }
598 
599     CcpUnlock();
600 
601     return TRUE;
602 }
603 
604 PFILE_OBJECT
605 NTAPI
606 CcGetFileObjectFromSectionPtrs(IN PSECTION_OBJECT_POINTERS SectionObjectPointer)
607 {
608     PFILE_OBJECT Result = NULL;
609     PNOCC_CACHE_MAP Map = SectionObjectPointer->SharedCacheMap;
610     CcpLock();
611     if (!IsListEmpty(&Map->AssociatedBcb))
612     {
613         PNOCC_BCB Bcb = CONTAINING_RECORD(Map->AssociatedBcb.Flink,
614                                           NOCC_BCB,
615                                           ThisFileList);
616 
617         Result = MmGetFileObjectForSection((PROS_SECTION_OBJECT)Bcb->SectionObject);
618     }
619     CcpUnlock();
620     return Result;
621 }
622 
623 PFILE_OBJECT
624 NTAPI
625 CcGetFileObjectFromBcb(PVOID Bcb)
626 {
627     PNOCC_BCB RealBcb = (PNOCC_BCB)Bcb;
628     DPRINT("BCB #%x\n", RealBcb - CcCacheSections);
629     return MmGetFileObjectForSection((PROS_SECTION_OBJECT)RealBcb->SectionObject);
630 }
631 
632 /* EOF */
633