1bfe40e7a8da2f8d3f3d5574b1c06d8601568f01
[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 %I64x\n",
234 FileSizes->ValidDataLength.QuadPart);
235
236 InitializeListHead(&Map->AssociatedBcb);
237 InitializeListHead(&Map->PrivateCacheMaps);
238 InsertTailList(&CcpAllSharedCacheMaps, &Map->Entry);
239 DPRINT("New Map %p\n", Map);
240 }
241 /* We don't have a private cache map. Link it with the shared cache map
242 to serve as a held reference. When the list in the shared cache map
243 is empty, we know we can delete it. */
244 if (!PrivateCacheMap)
245 {
246 PrivateCacheMap = ExAllocatePool(NonPagedPool,
247 sizeof(*PrivateCacheMap));
248
249 FileObject->PrivateCacheMap = PrivateCacheMap;
250 PrivateCacheMap->FileObject = FileObject;
251 ObReferenceObject(PrivateCacheMap->FileObject);
252 }
253
254 PrivateCacheMap->Map = Map;
255 InsertTailList(&Map->PrivateCacheMaps, &PrivateCacheMap->ListEntry);
256
257 CcpUnlock();
258 }
259
260 /*
261
262 This function is used by NewCC's MM to determine whether any section objects
263 for a given file are not cache sections. If that's true, we're not allowed
264 to resize the file, although nothing actually prevents us from doing ;-)
265
266 */
267
268 ULONG
269 NTAPI
270 CcpCountCacheSections(IN PNOCC_CACHE_MAP Map)
271 {
272 PLIST_ENTRY Entry;
273 ULONG Count;
274
275 for (Count = 0, Entry = Map->AssociatedBcb.Flink;
276 Entry != &Map->AssociatedBcb;
277 Entry = Entry->Flink, Count++);
278
279 return Count;
280 }
281
282 BOOLEAN
283 NTAPI
284 CcUninitializeCacheMap(IN PFILE_OBJECT FileObject,
285 IN OPTIONAL PLARGE_INTEGER TruncateSize,
286 IN OPTIONAL PCACHE_UNINITIALIZE_EVENT UninitializeEvent)
287 {
288 BOOLEAN LastMap = FALSE;
289 PNOCC_CACHE_MAP Map = (PNOCC_CACHE_MAP)FileObject->SectionObjectPointer->SharedCacheMap;
290 PNOCC_PRIVATE_CACHE_MAP PrivateCacheMap = FileObject->PrivateCacheMap;
291
292 DPRINT("Uninitializing file object for %wZ SectionObjectPointer %x\n",
293 &FileObject->FileName,
294 FileObject->SectionObjectPointer);
295
296 ASSERT(UninitializeEvent == NULL);
297
298 /* It may not be strictly necessary to flush here, but we do just for
299 kicks. */
300 if (Map)
301 CcpFlushCache(Map, NULL, 0, NULL, FALSE);
302
303 CcpLock();
304 /* We have a private cache map, so we've been initialized and haven't been
305 * uninitialized. */
306 if (PrivateCacheMap)
307 {
308 ASSERT(!Map || Map == PrivateCacheMap->Map);
309 ASSERT(PrivateCacheMap->FileObject == FileObject);
310
311 RemoveEntryList(&PrivateCacheMap->ListEntry);
312 /* That was the last private cache map. It's time to delete all
313 cache stripes and all aspects of caching on the file. */
314 if (IsListEmpty(&PrivateCacheMap->Map->PrivateCacheMaps))
315 {
316 /* Get rid of all the cache stripes. */
317 while (!IsListEmpty(&PrivateCacheMap->Map->AssociatedBcb))
318 {
319 PNOCC_BCB Bcb = CONTAINING_RECORD(PrivateCacheMap->Map->AssociatedBcb.Flink,
320 NOCC_BCB,
321 ThisFileList);
322
323 DPRINT("Evicting cache stripe #%x\n", Bcb - CcCacheSections);
324 Bcb->RefCount = 1;
325 CcpDereferenceCache(Bcb - CcCacheSections, TRUE);
326 }
327 RemoveEntryList(&PrivateCacheMap->Map->Entry);
328 ExFreePool(PrivateCacheMap->Map);
329 FileObject->SectionObjectPointer->SharedCacheMap = NULL;
330 LastMap = TRUE;
331 }
332 ObDereferenceObject(PrivateCacheMap->FileObject);
333 FileObject->PrivateCacheMap = NULL;
334 ExFreePool(PrivateCacheMap);
335 }
336 CcpUnlock();
337
338 DPRINT("Uninit complete\n");
339
340 /* The return from CcUninitializeCacheMap means that 'caching was stopped'. */
341 return LastMap;
342 }
343
344 /*
345
346 CcSetFileSizes is used to tell the cache manager that the file changed
347 size. In our case, we use the internal Mm method MmExtendCacheSection
348 to notify Mm that our section potentially changed size, which may mean
349 truncating off data.
350
351 */
352 VOID
353 NTAPI
354 CcSetFileSizes(IN PFILE_OBJECT FileObject,
355 IN PCC_FILE_SIZES FileSizes)
356 {
357 PNOCC_CACHE_MAP Map = (PNOCC_CACHE_MAP)FileObject->SectionObjectPointer->SharedCacheMap;
358 PNOCC_BCB Bcb;
359
360 if (!Map) return;
361 Map->FileSizes = *FileSizes;
362 Bcb = Map->AssociatedBcb.Flink == &Map->AssociatedBcb ?
363 NULL : CONTAINING_RECORD(Map->AssociatedBcb.Flink, NOCC_BCB, ThisFileList);
364 if (!Bcb) return;
365 MmExtendCacheSection(Bcb->SectionObject, &FileSizes->FileSize, FALSE);
366 DPRINT("FileSizes->FileSize %x\n", FileSizes->FileSize.LowPart);
367 DPRINT("FileSizes->AllocationSize %x\n", FileSizes->AllocationSize.LowPart);
368 DPRINT("FileSizes->ValidDataLength %x\n", FileSizes->ValidDataLength.LowPart);
369 }
370
371 BOOLEAN
372 NTAPI
373 CcGetFileSizes(IN PFILE_OBJECT FileObject,
374 IN PCC_FILE_SIZES FileSizes)
375 {
376 PNOCC_CACHE_MAP Map = (PNOCC_CACHE_MAP)FileObject->SectionObjectPointer->SharedCacheMap;
377 if (!Map) return FALSE;
378 *FileSizes = Map->FileSizes;
379 return TRUE;
380 }
381
382 BOOLEAN
383 NTAPI
384 CcPurgeCacheSection(IN PSECTION_OBJECT_POINTERS SectionObjectPointer,
385 IN OPTIONAL PLARGE_INTEGER FileOffset,
386 IN ULONG Length,
387 IN BOOLEAN UninitializeCacheMaps)
388 {
389 PNOCC_CACHE_MAP Map = (PNOCC_CACHE_MAP)SectionObjectPointer->SharedCacheMap;
390 if (!Map) return TRUE;
391 CcpFlushCache(Map, NULL, 0, NULL, TRUE);
392 return TRUE;
393 }
394
395 VOID
396 NTAPI
397 CcSetDirtyPageThreshold(IN PFILE_OBJECT FileObject,
398 IN ULONG DirtyPageThreshold)
399 {
400 UNIMPLEMENTED_DBGBREAK();
401 }
402
403 /*
404
405 This could be implemented much more intelligently by mapping instances
406 of a CoW zero page into the affected regions. We just RtlZeroMemory
407 for now.
408
409 */
410 BOOLEAN
411 NTAPI
412 CcZeroData(IN PFILE_OBJECT FileObject,
413 IN PLARGE_INTEGER StartOffset,
414 IN PLARGE_INTEGER EndOffset,
415 IN BOOLEAN Wait)
416 {
417 PNOCC_BCB Bcb = NULL;
418 PLIST_ENTRY ListEntry = NULL;
419 LARGE_INTEGER LowerBound = *StartOffset;
420 LARGE_INTEGER UpperBound = *EndOffset;
421 LARGE_INTEGER Target, End;
422 PVOID PinnedBcb, PinnedBuffer;
423 PNOCC_CACHE_MAP Map = FileObject->SectionObjectPointer->SharedCacheMap;
424
425 DPRINT("S %I64x E %I64x\n",
426 StartOffset->QuadPart,
427 EndOffset->QuadPart);
428
429 if (!Map)
430 {
431 NTSTATUS Status;
432 IO_STATUS_BLOCK IOSB;
433 PCHAR ZeroBuf = ExAllocatePool(PagedPool, PAGE_SIZE);
434 ULONG ToWrite;
435
436 if (!ZeroBuf) RtlRaiseStatus(STATUS_INSUFFICIENT_RESOURCES);
437 DPRINT1("RtlZeroMemory(%x,%x)\n", ZeroBuf, PAGE_SIZE);
438 RtlZeroMemory(ZeroBuf, PAGE_SIZE);
439
440 Target.QuadPart = PAGE_ROUND_DOWN(LowerBound.QuadPart);
441 End.QuadPart = PAGE_ROUND_UP(UpperBound.QuadPart);
442
443 // Handle leading page
444 if (LowerBound.QuadPart != Target.QuadPart)
445 {
446 ToWrite = MIN(UpperBound.QuadPart - LowerBound.QuadPart,
447 (PAGE_SIZE - LowerBound.QuadPart) & (PAGE_SIZE - 1));
448
449 DPRINT("Zero last half %I64x %lx\n",
450 Target.QuadPart,
451 ToWrite);
452
453 Status = MiSimpleRead(FileObject,
454 &Target,
455 ZeroBuf,
456 PAGE_SIZE,
457 TRUE,
458 &IOSB);
459
460 if (!NT_SUCCESS(Status))
461 {
462 ExFreePool(ZeroBuf);
463 RtlRaiseStatus(Status);
464 }
465
466 DPRINT1("RtlZeroMemory(%p, %lx)\n",
467 ZeroBuf + LowerBound.QuadPart - Target.QuadPart,
468 ToWrite);
469
470 RtlZeroMemory(ZeroBuf + LowerBound.QuadPart - Target.QuadPart,
471 ToWrite);
472
473 Status = MiSimpleWrite(FileObject,
474 &Target,
475 ZeroBuf,
476 MIN(PAGE_SIZE,
477 UpperBound.QuadPart-Target.QuadPart),
478 &IOSB);
479
480 if (!NT_SUCCESS(Status))
481 {
482 ExFreePool(ZeroBuf);
483 RtlRaiseStatus(Status);
484 }
485 Target.QuadPart += PAGE_SIZE;
486 }
487
488 DPRINT1("RtlZeroMemory(%x,%x)\n", ZeroBuf, PAGE_SIZE);
489 RtlZeroMemory(ZeroBuf, PAGE_SIZE);
490
491 while (UpperBound.QuadPart - Target.QuadPart > PAGE_SIZE)
492 {
493 DPRINT("Zero full page %I64x\n",
494 Target.QuadPart);
495
496 Status = MiSimpleWrite(FileObject,
497 &Target,
498 ZeroBuf,
499 PAGE_SIZE,
500 &IOSB);
501
502 if (!NT_SUCCESS(Status))
503 {
504 ExFreePool(ZeroBuf);
505 RtlRaiseStatus(Status);
506 }
507 Target.QuadPart += PAGE_SIZE;
508 }
509
510 if (UpperBound.QuadPart > Target.QuadPart)
511 {
512 ToWrite = UpperBound.QuadPart - Target.QuadPart;
513 DPRINT("Zero first half %I64x %lx\n",
514 Target.QuadPart,
515 ToWrite);
516
517 Status = MiSimpleRead(FileObject,
518 &Target,
519 ZeroBuf,
520 PAGE_SIZE,
521 TRUE,
522 &IOSB);
523
524 if (!NT_SUCCESS(Status))
525 {
526 ExFreePool(ZeroBuf);
527 RtlRaiseStatus(Status);
528 }
529 DPRINT1("RtlZeroMemory(%x,%x)\n", ZeroBuf, ToWrite);
530 RtlZeroMemory(ZeroBuf, ToWrite);
531 Status = MiSimpleWrite(FileObject,
532 &Target,
533 ZeroBuf,
534 MIN(PAGE_SIZE,
535 UpperBound.QuadPart-Target.QuadPart),
536 &IOSB);
537 if (!NT_SUCCESS(Status))
538 {
539 ExFreePool(ZeroBuf);
540 RtlRaiseStatus(Status);
541 }
542 Target.QuadPart += PAGE_SIZE;
543 }
544
545 ExFreePool(ZeroBuf);
546 return TRUE;
547 }
548
549 CcpLock();
550 ListEntry = Map->AssociatedBcb.Flink;
551
552 while (ListEntry != &Map->AssociatedBcb)
553 {
554 Bcb = CONTAINING_RECORD(ListEntry, NOCC_BCB, ThisFileList);
555 CcpReferenceCache(Bcb - CcCacheSections);
556
557 if (Bcb->FileOffset.QuadPart + Bcb->Length >= LowerBound.QuadPart &&
558 Bcb->FileOffset.QuadPart < UpperBound.QuadPart)
559 {
560 DPRINT("Bcb #%x (@%I64x)\n",
561 Bcb - CcCacheSections,
562 Bcb->FileOffset.QuadPart);
563
564 Target.QuadPart = MAX(Bcb->FileOffset.QuadPart,
565 LowerBound.QuadPart);
566
567 End.QuadPart = MIN(Map->FileSizes.ValidDataLength.QuadPart,
568 UpperBound.QuadPart);
569
570 End.QuadPart = MIN(End.QuadPart,
571 Bcb->FileOffset.QuadPart + Bcb->Length);
572
573 CcpUnlock();
574
575 if (!CcPreparePinWrite(FileObject,
576 &Target,
577 End.QuadPart - Target.QuadPart,
578 TRUE,
579 Wait,
580 &PinnedBcb,
581 &PinnedBuffer))
582 {
583 return FALSE;
584 }
585
586 ASSERT(PinnedBcb == Bcb);
587
588 CcpLock();
589 ListEntry = ListEntry->Flink;
590 /* Return from pin state */
591 CcpUnpinData(PinnedBcb, TRUE);
592 }
593
594 CcpUnpinData(Bcb, TRUE);
595 }
596
597 CcpUnlock();
598
599 return TRUE;
600 }
601
602 PFILE_OBJECT
603 NTAPI
604 CcGetFileObjectFromSectionPtrs(IN PSECTION_OBJECT_POINTERS SectionObjectPointer)
605 {
606 PFILE_OBJECT Result = NULL;
607 PNOCC_CACHE_MAP Map = SectionObjectPointer->SharedCacheMap;
608 CcpLock();
609 if (!IsListEmpty(&Map->AssociatedBcb))
610 {
611 PNOCC_BCB Bcb = CONTAINING_RECORD(Map->AssociatedBcb.Flink,
612 NOCC_BCB,
613 ThisFileList);
614
615 Result = MmGetFileObjectForSection((PROS_SECTION_OBJECT)Bcb->SectionObject);
616 }
617 CcpUnlock();
618 return Result;
619 }
620
621 PFILE_OBJECT
622 NTAPI
623 CcGetFileObjectFromBcb(PVOID Bcb)
624 {
625 PNOCC_BCB RealBcb = (PNOCC_BCB)Bcb;
626 DPRINT("BCB #%x\n", RealBcb - CcCacheSections);
627 return MmGetFileObjectForSection((PROS_SECTION_OBJECT)RealBcb->SectionObject);
628 }
629
630 /* EOF */