Sync with trunk r58113.
[reactos.git] / ntoskrnl / cache / fssup.c
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 BOOLEAN
81 NTAPI
82 CcInitializeCacheManager(VOID)
83 {
84 int i;
85
86 DPRINT("Initialize\n");
87 for (i = 0; i < CACHE_NUM_SECTIONS; i++)
88 {
89 KeInitializeEvent(&CcCacheSections[i].ExclusiveWait,
90 SynchronizationEvent,
91 FALSE);
92
93 InitializeListHead(&CcCacheSections[i].ThisFileList);
94 }
95
96 InitializeListHead(&CcpAllSharedCacheMaps);
97
98 KeInitializeEvent(&CcDeleteEvent, SynchronizationEvent, FALSE);
99 KeInitializeEvent(&CcFinalizeEvent, SynchronizationEvent, FALSE);
100 KeInitializeEvent(&CcpLazyWriteEvent, SynchronizationEvent, FALSE);
101
102 CcCacheBitmap->Buffer = ((PULONG)&CcCacheBitmap[1]);
103 CcCacheBitmap->SizeOfBitMap = ROUND_UP(CACHE_NUM_SECTIONS, 32);
104 DPRINT1("Cache has %d entries\n", CcCacheBitmap->SizeOfBitMap);
105 ExInitializeFastMutex(&CcMutex);
106
107 return TRUE;
108 }
109
110 VOID
111 NTAPI
112 CcPfInitializePrefetcher(VOID)
113 {
114 /* Notify debugger */
115 DbgPrintEx(DPFLTR_PREFETCHER_ID,
116 DPFLTR_TRACE_LEVEL,
117 "CCPF: InitializePrefetecher()\n");
118
119 /* Setup the Prefetcher Data */
120 InitializeListHead(&CcPfGlobals.ActiveTraces);
121 InitializeListHead(&CcPfGlobals.CompletedTraces);
122 ExInitializeFastMutex(&CcPfGlobals.CompletedTracesLock);
123
124 /* FIXME: Setup the rest of the prefetecher */
125 }
126
127 BOOLEAN
128 NTAPI
129 CcpAcquireFileLock(PNOCC_CACHE_MAP Map)
130 {
131 DPRINT("Calling AcquireForLazyWrite: %x\n", Map->LazyContext);
132 return Map->Callbacks.AcquireForLazyWrite(Map->LazyContext, TRUE);
133 }
134
135 VOID
136 NTAPI
137 CcpReleaseFileLock(PNOCC_CACHE_MAP Map)
138 {
139 DPRINT("Releasing Lazy Write %x\n", Map->LazyContext);
140 Map->Callbacks.ReleaseFromLazyWrite(Map->LazyContext);
141 }
142
143 /*
144
145 Cc functions are required to treat alternate streams of a file as the same
146 for the purpose of caching, meaning that we must be able to find the shared
147 cache map associated with the ``real'' stream associated with a stream file
148 object, if one exists. We do that by identifying a private cache map in
149 our gamut that has the same volume, device and fscontext as the stream file
150 object we're holding. It's heavy but it does work. This can probably be
151 improved, although there doesn't seem to be any real association between
152 a stream file object and a sibling file object in the file object struct
153 itself.
154
155 */
156
157 /* Must have CcpLock() */
158 PFILE_OBJECT CcpFindOtherStreamFileObject(PFILE_OBJECT FileObject)
159 {
160 PLIST_ENTRY Entry, Private;
161 for (Entry = CcpAllSharedCacheMaps.Flink;
162 Entry != &CcpAllSharedCacheMaps;
163 Entry = Entry->Flink)
164 {
165 /* 'Identical' test for other stream file object */
166 PNOCC_CACHE_MAP Map = CONTAINING_RECORD(Entry, NOCC_CACHE_MAP, Entry);
167 for (Private = Map->PrivateCacheMaps.Flink;
168 Private != &Map->PrivateCacheMaps;
169 Private = Private->Flink)
170 {
171 PNOCC_PRIVATE_CACHE_MAP PrivateMap = CONTAINING_RECORD(Private,
172 NOCC_PRIVATE_CACHE_MAP,
173 ListEntry);
174
175 if (PrivateMap->FileObject->Flags & FO_STREAM_FILE &&
176 PrivateMap->FileObject->DeviceObject == FileObject->DeviceObject &&
177 PrivateMap->FileObject->Vpb == FileObject->Vpb &&
178 PrivateMap->FileObject->FsContext == FileObject->FsContext &&
179 PrivateMap->FileObject->FsContext2 == FileObject->FsContext2 &&
180 1)
181 {
182 return PrivateMap->FileObject;
183 }
184 }
185 }
186 return 0;
187 }
188
189 /* Thanks: http://windowsitpro.com/Windows/Articles/ArticleID/3864/pg/2/2.html */
190
191 VOID
192 NTAPI
193 CcInitializeCacheMap(IN PFILE_OBJECT FileObject,
194 IN PCC_FILE_SIZES FileSizes,
195 IN BOOLEAN PinAccess,
196 IN PCACHE_MANAGER_CALLBACKS Callbacks,
197 IN PVOID LazyWriteContext)
198 {
199 PNOCC_CACHE_MAP Map = FileObject->SectionObjectPointer->SharedCacheMap;
200 PNOCC_PRIVATE_CACHE_MAP PrivateCacheMap = FileObject->PrivateCacheMap;
201
202 CcpLock();
203 /* We don't have a shared cache map. First find out if we have a sibling
204 stream file object we can take it from. */
205 if (!Map && FileObject->Flags & FO_STREAM_FILE)
206 {
207 PFILE_OBJECT IdenticalStreamFileObject = CcpFindOtherStreamFileObject(FileObject);
208 if (IdenticalStreamFileObject)
209 Map = IdenticalStreamFileObject->SectionObjectPointer->SharedCacheMap;
210 if (Map)
211 {
212 DPRINT1("Linking SFO %x to previous SFO %x through cache map %x #\n",
213 FileObject,
214 IdenticalStreamFileObject,
215 Map);
216 }
217 }
218 /* We still don't have a shared cache map. We need to create one. */
219 if (!Map)
220 {
221 DPRINT("Initializing file object for (%p) %wZ\n",
222 FileObject,
223 &FileObject->FileName);
224
225 Map = ExAllocatePool(NonPagedPool, sizeof(NOCC_CACHE_MAP));
226 FileObject->SectionObjectPointer->SharedCacheMap = Map;
227 Map->FileSizes = *FileSizes;
228 Map->LazyContext = LazyWriteContext;
229 Map->ReadAheadGranularity = PAGE_SIZE;
230 RtlCopyMemory(&Map->Callbacks, Callbacks, sizeof(*Callbacks));
231
232 /* For now ... */
233 DPRINT("FileSizes->ValidDataLength %08x%08x\n",
234 FileSizes->ValidDataLength.HighPart,
235 FileSizes->ValidDataLength.LowPart);
236
237 InitializeListHead(&Map->AssociatedBcb);
238 InitializeListHead(&Map->PrivateCacheMaps);
239 InsertTailList(&CcpAllSharedCacheMaps, &Map->Entry);
240 DPRINT("New Map %x\n", Map);
241 }
242 /* We don't have a private cache map. Link it with the shared cache map
243 to serve as a held reference. When the list in the shared cache map
244 is empty, we know we can delete it. */
245 if (!PrivateCacheMap)
246 {
247 PrivateCacheMap = ExAllocatePool(NonPagedPool,
248 sizeof(*PrivateCacheMap));
249
250 FileObject->PrivateCacheMap = PrivateCacheMap;
251 PrivateCacheMap->FileObject = FileObject;
252 ObReferenceObject(PrivateCacheMap->FileObject);
253 }
254
255 PrivateCacheMap->Map = Map;
256 InsertTailList(&Map->PrivateCacheMaps, &PrivateCacheMap->ListEntry);
257
258 CcpUnlock();
259 }
260
261 /*
262
263 This function is used by NewCC's MM to determine whether any section objects
264 for a given file are not cache sections. If that's true, we're not allowed
265 to resize the file, although nothing actually prevents us from doing ;-)
266
267 */
268
269 ULONG
270 NTAPI
271 CcpCountCacheSections(IN PNOCC_CACHE_MAP Map)
272 {
273 PLIST_ENTRY Entry;
274 ULONG Count;
275
276 for (Count = 0, Entry = Map->AssociatedBcb.Flink;
277 Entry != &Map->AssociatedBcb;
278 Entry = Entry->Flink, Count++);
279
280 return Count;
281 }
282
283 BOOLEAN
284 NTAPI
285 CcUninitializeCacheMap(IN PFILE_OBJECT FileObject,
286 IN OPTIONAL PLARGE_INTEGER TruncateSize,
287 IN OPTIONAL PCACHE_UNINITIALIZE_EVENT UninitializeEvent)
288 {
289 BOOLEAN LastMap = FALSE;
290 PNOCC_CACHE_MAP Map = (PNOCC_CACHE_MAP)FileObject->SectionObjectPointer->SharedCacheMap;
291 PNOCC_PRIVATE_CACHE_MAP PrivateCacheMap = FileObject->PrivateCacheMap;
292
293 DPRINT("Uninitializing file object for %wZ SectionObjectPointer %x\n",
294 &FileObject->FileName,
295 FileObject->SectionObjectPointer);
296
297 ASSERT(UninitializeEvent == NULL);
298
299 /* It may not be strictly necessary to flush here, but we do just for
300 kicks. */
301 if (Map)
302 CcpFlushCache(Map, NULL, 0, NULL, FALSE);
303
304 CcpLock();
305 /* We have a private cache map, so we've been initialized and haven't been
306 * uninitialized. */
307 if (PrivateCacheMap)
308 {
309 ASSERT(!Map || Map == PrivateCacheMap->Map);
310 ASSERT(PrivateCacheMap->FileObject == FileObject);
311
312 RemoveEntryList(&PrivateCacheMap->ListEntry);
313 /* That was the last private cache map. It's time to delete all
314 cache stripes and all aspects of caching on the file. */
315 if (IsListEmpty(&PrivateCacheMap->Map->PrivateCacheMaps))
316 {
317 /* Get rid of all the cache stripes. */
318 while (!IsListEmpty(&PrivateCacheMap->Map->AssociatedBcb))
319 {
320 PNOCC_BCB Bcb = CONTAINING_RECORD(PrivateCacheMap->Map->AssociatedBcb.Flink,
321 NOCC_BCB,
322 ThisFileList);
323
324 DPRINT("Evicting cache stripe #%x\n", Bcb - CcCacheSections);
325 Bcb->RefCount = 1;
326 CcpDereferenceCache(Bcb - CcCacheSections, TRUE);
327 }
328 RemoveEntryList(&PrivateCacheMap->Map->Entry);
329 ExFreePool(PrivateCacheMap->Map);
330 FileObject->SectionObjectPointer->SharedCacheMap = NULL;
331 LastMap = TRUE;
332 }
333 ObDereferenceObject(PrivateCacheMap->FileObject);
334 FileObject->PrivateCacheMap = NULL;
335 ExFreePool(PrivateCacheMap);
336 }
337 CcpUnlock();
338
339 DPRINT("Uninit complete\n");
340
341 /* The return from CcUninitializeCacheMap means that 'caching was stopped'. */
342 return LastMap;
343 }
344
345 /*
346
347 CcSetFileSizes is used to tell the cache manager that the file changed
348 size. In our case, we use the internal Mm method MmExtendCacheSection
349 to notify Mm that our section potentially changed size, which may mean
350 truncating off data.
351
352 */
353 VOID
354 NTAPI
355 CcSetFileSizes(IN PFILE_OBJECT FileObject,
356 IN PCC_FILE_SIZES FileSizes)
357 {
358 PNOCC_CACHE_MAP Map = (PNOCC_CACHE_MAP)FileObject->SectionObjectPointer->SharedCacheMap;
359 PNOCC_BCB Bcb;
360
361 if (!Map) return;
362 Map->FileSizes = *FileSizes;
363 Bcb = Map->AssociatedBcb.Flink == &Map->AssociatedBcb ?
364 NULL : CONTAINING_RECORD(Map->AssociatedBcb.Flink, NOCC_BCB, ThisFileList);
365 if (!Bcb) return;
366 MmExtendCacheSection(Bcb->SectionObject, &FileSizes->FileSize, FALSE);
367 DPRINT("FileSizes->FileSize %x\n", FileSizes->FileSize.LowPart);
368 DPRINT("FileSizes->AllocationSize %x\n", FileSizes->AllocationSize.LowPart);
369 DPRINT("FileSizes->ValidDataLength %x\n", FileSizes->ValidDataLength.LowPart);
370 }
371
372 BOOLEAN
373 NTAPI
374 CcGetFileSizes(IN PFILE_OBJECT FileObject,
375 IN PCC_FILE_SIZES FileSizes)
376 {
377 PNOCC_CACHE_MAP Map = (PNOCC_CACHE_MAP)FileObject->SectionObjectPointer->SharedCacheMap;
378 if (!Map) return FALSE;
379 *FileSizes = Map->FileSizes;
380 return TRUE;
381 }
382
383 BOOLEAN
384 NTAPI
385 CcPurgeCacheSection(IN PSECTION_OBJECT_POINTERS SectionObjectPointer,
386 IN OPTIONAL PLARGE_INTEGER FileOffset,
387 IN ULONG Length,
388 IN BOOLEAN UninitializeCacheMaps)
389 {
390 PNOCC_CACHE_MAP Map = (PNOCC_CACHE_MAP)SectionObjectPointer->SharedCacheMap;
391 if (!Map) return TRUE;
392 CcpFlushCache(Map, NULL, 0, NULL, TRUE);
393 return TRUE;
394 }
395 \f
396 VOID
397 NTAPI
398 CcSetDirtyPageThreshold(IN PFILE_OBJECT FileObject,
399 IN ULONG DirtyPageThreshold)
400 {
401 UNIMPLEMENTED;
402 ASSERT(FALSE); // while (TRUE);
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 %08x%08x E %08x%08x\n",
428 StartOffset->u.HighPart,
429 StartOffset->u.LowPart,
430 EndOffset->u.HighPart,
431 EndOffset->u.LowPart);
432
433 if (!Map)
434 {
435 NTSTATUS Status;
436 IO_STATUS_BLOCK IOSB;
437 PCHAR ZeroBuf = ExAllocatePool(PagedPool, PAGE_SIZE);
438 ULONG ToWrite;
439
440 if (!ZeroBuf) RtlRaiseStatus(STATUS_INSUFFICIENT_RESOURCES);
441 DPRINT1("RtlZeroMemory(%x,%x)\n", ZeroBuf, PAGE_SIZE);
442 RtlZeroMemory(ZeroBuf, PAGE_SIZE);
443
444 Target.QuadPart = PAGE_ROUND_DOWN(LowerBound.QuadPart);
445 End.QuadPart = PAGE_ROUND_UP(UpperBound.QuadPart);
446
447 // Handle leading page
448 if (LowerBound.QuadPart != Target.QuadPart)
449 {
450 ToWrite = MIN(UpperBound.QuadPart - LowerBound.QuadPart,
451 (PAGE_SIZE - LowerBound.QuadPart) & (PAGE_SIZE - 1));
452
453 DPRINT("Zero last half %08x%08x %x\n",
454 Target.u.HighPart,
455 Target.u.LowPart,
456 ToWrite);
457
458 Status = MiSimpleRead(FileObject,
459 &Target,
460 ZeroBuf,
461 PAGE_SIZE,
462 TRUE,
463 &IOSB);
464
465 if (!NT_SUCCESS(Status))
466 {
467 ExFreePool(ZeroBuf);
468 RtlRaiseStatus(Status);
469 }
470
471 DPRINT1("RtlZeroMemory(%x,%x)\n",
472 ZeroBuf + LowerBound.QuadPart - Target.QuadPart,
473 ToWrite);
474
475 RtlZeroMemory(ZeroBuf + LowerBound.QuadPart - Target.QuadPart,
476 ToWrite);
477
478 Status = MiSimpleWrite(FileObject,
479 &Target,
480 ZeroBuf,
481 MIN(PAGE_SIZE,
482 UpperBound.QuadPart-Target.QuadPart),
483 &IOSB);
484
485 if (!NT_SUCCESS(Status))
486 {
487 ExFreePool(ZeroBuf);
488 RtlRaiseStatus(Status);
489 }
490 Target.QuadPart += PAGE_SIZE;
491 }
492
493 DPRINT1("RtlZeroMemory(%x,%x)\n", ZeroBuf, PAGE_SIZE);
494 RtlZeroMemory(ZeroBuf, PAGE_SIZE);
495
496 while (UpperBound.QuadPart - Target.QuadPart > PAGE_SIZE)
497 {
498 DPRINT("Zero full page %08x%08x\n",
499 Target.u.HighPart,
500 Target.u.LowPart);
501
502 Status = MiSimpleWrite(FileObject,
503 &Target,
504 ZeroBuf,
505 PAGE_SIZE,
506 &IOSB);
507
508 if (!NT_SUCCESS(Status))
509 {
510 ExFreePool(ZeroBuf);
511 RtlRaiseStatus(Status);
512 }
513 Target.QuadPart += PAGE_SIZE;
514 }
515
516 if (UpperBound.QuadPart > Target.QuadPart)
517 {
518 ToWrite = UpperBound.QuadPart - Target.QuadPart;
519 DPRINT("Zero first half %08x%08x %x\n",
520 Target.u.HighPart,
521 Target.u.LowPart,
522 ToWrite);
523
524 Status = MiSimpleRead(FileObject,
525 &Target,
526 ZeroBuf,
527 PAGE_SIZE,
528 TRUE,
529 &IOSB);
530
531 if (!NT_SUCCESS(Status))
532 {
533 ExFreePool(ZeroBuf);
534 RtlRaiseStatus(Status);
535 }
536 DPRINT1("RtlZeroMemory(%x,%x)\n", ZeroBuf, ToWrite);
537 RtlZeroMemory(ZeroBuf, ToWrite);
538 Status = MiSimpleWrite(FileObject,
539 &Target,
540 ZeroBuf,
541 MIN(PAGE_SIZE,
542 UpperBound.QuadPart-Target.QuadPart),
543 &IOSB);
544 if (!NT_SUCCESS(Status))
545 {
546 ExFreePool(ZeroBuf);
547 RtlRaiseStatus(Status);
548 }
549 Target.QuadPart += PAGE_SIZE;
550 }
551
552 ExFreePool(ZeroBuf);
553 return TRUE;
554 }
555
556 CcpLock();
557 ListEntry = Map->AssociatedBcb.Flink;
558
559 while (ListEntry != &Map->AssociatedBcb)
560 {
561 Bcb = CONTAINING_RECORD(ListEntry, NOCC_BCB, ThisFileList);
562 CcpReferenceCache(Bcb - CcCacheSections);
563
564 if (Bcb->FileOffset.QuadPart + Bcb->Length >= LowerBound.QuadPart &&
565 Bcb->FileOffset.QuadPart < UpperBound.QuadPart)
566 {
567 DPRINT("Bcb #%x (@%08x%08x)\n",
568 Bcb - CcCacheSections,
569 Bcb->FileOffset.u.HighPart,
570 Bcb->FileOffset.u.LowPart);
571
572 Target.QuadPart = MAX(Bcb->FileOffset.QuadPart,
573 LowerBound.QuadPart);
574
575 End.QuadPart = MIN(Map->FileSizes.ValidDataLength.QuadPart,
576 UpperBound.QuadPart);
577
578 End.QuadPart = MIN(End.QuadPart,
579 Bcb->FileOffset.QuadPart + Bcb->Length);
580
581 CcpUnlock();
582
583 if (!CcPreparePinWrite(FileObject,
584 &Target,
585 End.QuadPart - Target.QuadPart,
586 TRUE,
587 Wait,
588 &PinnedBcb,
589 &PinnedBuffer))
590 {
591 return FALSE;
592 }
593
594 ASSERT(PinnedBcb == Bcb);
595
596 CcpLock();
597 ListEntry = ListEntry->Flink;
598 /* Return from pin state */
599 CcpUnpinData(PinnedBcb, TRUE);
600 }
601
602 CcpUnpinData(Bcb, TRUE);
603 }
604
605 CcpUnlock();
606
607 return TRUE;
608 }
609
610 PFILE_OBJECT
611 NTAPI
612 CcGetFileObjectFromSectionPtrs(IN PSECTION_OBJECT_POINTERS SectionObjectPointer)
613 {
614 PFILE_OBJECT Result = NULL;
615 PNOCC_CACHE_MAP Map = SectionObjectPointer->SharedCacheMap;
616 CcpLock();
617 if (!IsListEmpty(&Map->AssociatedBcb))
618 {
619 PNOCC_BCB Bcb = CONTAINING_RECORD(Map->AssociatedBcb.Flink,
620 NOCC_BCB,
621 ThisFileList);
622
623 Result = MmGetFileObjectForSection((PROS_SECTION_OBJECT)Bcb->SectionObject);
624 }
625 CcpUnlock();
626 return Result;
627 }
628
629 PFILE_OBJECT
630 NTAPI
631 CcGetFileObjectFromBcb(PVOID Bcb)
632 {
633 PNOCC_BCB RealBcb = (PNOCC_BCB)Bcb;
634 DPRINT("BCB #%x\n", RealBcb - CcCacheSections);
635 return MmGetFileObjectForSection((PROS_SECTION_OBJECT)RealBcb->SectionObject);
636 }
637
638 /* EOF */