* Sync up to trunk head (r60691).
[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
396 VOID
397 NTAPI
398 CcSetDirtyPageThreshold(IN PFILE_OBJECT FileObject,
399 IN ULONG DirtyPageThreshold)
400 {
401 UNIMPLEMENTED_DBGBREAK();
402 }
403
404 /*
405
406 This could be implemented much more intelligently by mapping instances
407 of a CoW zero page into the affected regions. We just RtlZeroMemory
408 for now.
409
410 */
411 BOOLEAN
412 NTAPI
413 CcZeroData(IN PFILE_OBJECT FileObject,
414 IN PLARGE_INTEGER StartOffset,
415 IN PLARGE_INTEGER EndOffset,
416 IN BOOLEAN Wait)
417 {
418 PNOCC_BCB Bcb = NULL;
419 PLIST_ENTRY ListEntry = NULL;
420 LARGE_INTEGER LowerBound = *StartOffset;
421 LARGE_INTEGER UpperBound = *EndOffset;
422 LARGE_INTEGER Target, End;
423 PVOID PinnedBcb, PinnedBuffer;
424 PNOCC_CACHE_MAP Map = FileObject->SectionObjectPointer->SharedCacheMap;
425
426 DPRINT("S %08x%08x E %08x%08x\n",
427 StartOffset->u.HighPart,
428 StartOffset->u.LowPart,
429 EndOffset->u.HighPart,
430 EndOffset->u.LowPart);
431
432 if (!Map)
433 {
434 NTSTATUS Status;
435 IO_STATUS_BLOCK IOSB;
436 PCHAR ZeroBuf = ExAllocatePool(PagedPool, PAGE_SIZE);
437 ULONG ToWrite;
438
439 if (!ZeroBuf) RtlRaiseStatus(STATUS_INSUFFICIENT_RESOURCES);
440 DPRINT1("RtlZeroMemory(%x,%x)\n", ZeroBuf, PAGE_SIZE);
441 RtlZeroMemory(ZeroBuf, PAGE_SIZE);
442
443 Target.QuadPart = PAGE_ROUND_DOWN(LowerBound.QuadPart);
444 End.QuadPart = PAGE_ROUND_UP(UpperBound.QuadPart);
445
446 // Handle leading page
447 if (LowerBound.QuadPart != Target.QuadPart)
448 {
449 ToWrite = MIN(UpperBound.QuadPart - LowerBound.QuadPart,
450 (PAGE_SIZE - LowerBound.QuadPart) & (PAGE_SIZE - 1));
451
452 DPRINT("Zero last half %08x%08x %x\n",
453 Target.u.HighPart,
454 Target.u.LowPart,
455 ToWrite);
456
457 Status = MiSimpleRead(FileObject,
458 &Target,
459 ZeroBuf,
460 PAGE_SIZE,
461 TRUE,
462 &IOSB);
463
464 if (!NT_SUCCESS(Status))
465 {
466 ExFreePool(ZeroBuf);
467 RtlRaiseStatus(Status);
468 }
469
470 DPRINT1("RtlZeroMemory(%x,%x)\n",
471 ZeroBuf + LowerBound.QuadPart - Target.QuadPart,
472 ToWrite);
473
474 RtlZeroMemory(ZeroBuf + LowerBound.QuadPart - Target.QuadPart,
475 ToWrite);
476
477 Status = MiSimpleWrite(FileObject,
478 &Target,
479 ZeroBuf,
480 MIN(PAGE_SIZE,
481 UpperBound.QuadPart-Target.QuadPart),
482 &IOSB);
483
484 if (!NT_SUCCESS(Status))
485 {
486 ExFreePool(ZeroBuf);
487 RtlRaiseStatus(Status);
488 }
489 Target.QuadPart += PAGE_SIZE;
490 }
491
492 DPRINT1("RtlZeroMemory(%x,%x)\n", ZeroBuf, PAGE_SIZE);
493 RtlZeroMemory(ZeroBuf, PAGE_SIZE);
494
495 while (UpperBound.QuadPart - Target.QuadPart > PAGE_SIZE)
496 {
497 DPRINT("Zero full page %08x%08x\n",
498 Target.u.HighPart,
499 Target.u.LowPart);
500
501 Status = MiSimpleWrite(FileObject,
502 &Target,
503 ZeroBuf,
504 PAGE_SIZE,
505 &IOSB);
506
507 if (!NT_SUCCESS(Status))
508 {
509 ExFreePool(ZeroBuf);
510 RtlRaiseStatus(Status);
511 }
512 Target.QuadPart += PAGE_SIZE;
513 }
514
515 if (UpperBound.QuadPart > Target.QuadPart)
516 {
517 ToWrite = UpperBound.QuadPart - Target.QuadPart;
518 DPRINT("Zero first half %08x%08x %x\n",
519 Target.u.HighPart,
520 Target.u.LowPart,
521 ToWrite);
522
523 Status = MiSimpleRead(FileObject,
524 &Target,
525 ZeroBuf,
526 PAGE_SIZE,
527 TRUE,
528 &IOSB);
529
530 if (!NT_SUCCESS(Status))
531 {
532 ExFreePool(ZeroBuf);
533 RtlRaiseStatus(Status);
534 }
535 DPRINT1("RtlZeroMemory(%x,%x)\n", ZeroBuf, ToWrite);
536 RtlZeroMemory(ZeroBuf, ToWrite);
537 Status = MiSimpleWrite(FileObject,
538 &Target,
539 ZeroBuf,
540 MIN(PAGE_SIZE,
541 UpperBound.QuadPart-Target.QuadPart),
542 &IOSB);
543 if (!NT_SUCCESS(Status))
544 {
545 ExFreePool(ZeroBuf);
546 RtlRaiseStatus(Status);
547 }
548 Target.QuadPart += PAGE_SIZE;
549 }
550
551 ExFreePool(ZeroBuf);
552 return TRUE;
553 }
554
555 CcpLock();
556 ListEntry = Map->AssociatedBcb.Flink;
557
558 while (ListEntry != &Map->AssociatedBcb)
559 {
560 Bcb = CONTAINING_RECORD(ListEntry, NOCC_BCB, ThisFileList);
561 CcpReferenceCache(Bcb - CcCacheSections);
562
563 if (Bcb->FileOffset.QuadPart + Bcb->Length >= LowerBound.QuadPart &&
564 Bcb->FileOffset.QuadPart < UpperBound.QuadPart)
565 {
566 DPRINT("Bcb #%x (@%08x%08x)\n",
567 Bcb - CcCacheSections,
568 Bcb->FileOffset.u.HighPart,
569 Bcb->FileOffset.u.LowPart);
570
571 Target.QuadPart = MAX(Bcb->FileOffset.QuadPart,
572 LowerBound.QuadPart);
573
574 End.QuadPart = MIN(Map->FileSizes.ValidDataLength.QuadPart,
575 UpperBound.QuadPart);
576
577 End.QuadPart = MIN(End.QuadPart,
578 Bcb->FileOffset.QuadPart + Bcb->Length);
579
580 CcpUnlock();
581
582 if (!CcPreparePinWrite(FileObject,
583 &Target,
584 End.QuadPart - Target.QuadPart,
585 TRUE,
586 Wait,
587 &PinnedBcb,
588 &PinnedBuffer))
589 {
590 return FALSE;
591 }
592
593 ASSERT(PinnedBcb == Bcb);
594
595 CcpLock();
596 ListEntry = ListEntry->Flink;
597 /* Return from pin state */
598 CcpUnpinData(PinnedBcb, TRUE);
599 }
600
601 CcpUnpinData(Bcb, TRUE);
602 }
603
604 CcpUnlock();
605
606 return TRUE;
607 }
608
609 PFILE_OBJECT
610 NTAPI
611 CcGetFileObjectFromSectionPtrs(IN PSECTION_OBJECT_POINTERS SectionObjectPointer)
612 {
613 PFILE_OBJECT Result = NULL;
614 PNOCC_CACHE_MAP Map = SectionObjectPointer->SharedCacheMap;
615 CcpLock();
616 if (!IsListEmpty(&Map->AssociatedBcb))
617 {
618 PNOCC_BCB Bcb = CONTAINING_RECORD(Map->AssociatedBcb.Flink,
619 NOCC_BCB,
620 ThisFileList);
621
622 Result = MmGetFileObjectForSection((PROS_SECTION_OBJECT)Bcb->SectionObject);
623 }
624 CcpUnlock();
625 return Result;
626 }
627
628 PFILE_OBJECT
629 NTAPI
630 CcGetFileObjectFromBcb(PVOID Bcb)
631 {
632 PNOCC_BCB RealBcb = (PNOCC_BCB)Bcb;
633 DPRINT("BCB #%x\n", RealBcb - CcCacheSections);
634 return MmGetFileObjectForSection((PROS_SECTION_OBJECT)RealBcb->SectionObject);
635 }
636
637 /* EOF */