xref: /reactos/ntoskrnl/cc/copy.c (revision 9592728f)
1 /*
2  * COPYRIGHT:       See COPYING in the top level directory
3  * PROJECT:         ReactOS kernel
4  * FILE:            ntoskrnl/cc/copy.c
5  * PURPOSE:         Implements cache managers copy interface
6  *
7  * PROGRAMMERS:     Some people?
8  *                  Pierre Schweitzer (pierre@reactos.org)
9  */
10 
11 /* INCLUDES ******************************************************************/
12 
13 #include <ntoskrnl.h>
14 #define NDEBUG
15 #include <debug.h>
16 
17 /* GLOBALS *******************************************************************/
18 
19 static PFN_NUMBER CcZeroPage = 0;
20 
21 #define MAX_ZERO_LENGTH    (256 * 1024)
22 
23 typedef enum _CC_COPY_OPERATION
24 {
25     CcOperationRead,
26     CcOperationWrite,
27     CcOperationZero
28 } CC_COPY_OPERATION;
29 
30 typedef enum _CC_CAN_WRITE_RETRY
31 {
32     FirstTry = 0,
33     RetryAllowRemote = 253,
34     RetryForceCheckPerFile = 254,
35     RetryMasterLocked = 255,
36 } CC_CAN_WRITE_RETRY;
37 
38 ULONG CcRosTraceLevel = 0;
39 ULONG CcFastMdlReadWait;
40 ULONG CcFastMdlReadNotPossible;
41 ULONG CcFastReadNotPossible;
42 ULONG CcFastReadWait;
43 ULONG CcFastReadNoWait;
44 ULONG CcFastReadResourceMiss;
45 
46 /* Counters:
47  * - Amount of pages flushed to the disk
48  * - Number of flush operations
49  */
50 ULONG CcDataPages = 0;
51 ULONG CcDataFlushes = 0;
52 
53 /* FUNCTIONS *****************************************************************/
54 
55 VOID
56 NTAPI
57 MiZeroPhysicalPage (
58     IN PFN_NUMBER PageFrameIndex
59 );
60 
61 VOID
62 NTAPI
63 CcInitCacheZeroPage (
64     VOID)
65 {
66     NTSTATUS Status;
67 
68     MI_SET_USAGE(MI_USAGE_CACHE);
69     //MI_SET_PROCESS2(PsGetCurrentProcess()->ImageFileName);
70     Status = MmRequestPageMemoryConsumer(MC_SYSTEM, TRUE, &CcZeroPage);
71     if (!NT_SUCCESS(Status))
72     {
73         DbgPrint("Can't allocate CcZeroPage.\n");
74         KeBugCheck(CACHE_MANAGER);
75     }
76     MiZeroPhysicalPage(CcZeroPage);
77 }
78 
79 NTSTATUS
80 NTAPI
81 CcReadVirtualAddress (
82     PROS_VACB Vacb)
83 {
84     ULONG Size;
85     PMDL Mdl;
86     NTSTATUS Status;
87     IO_STATUS_BLOCK IoStatus;
88     KEVENT Event;
89     ULARGE_INTEGER LargeSize;
90 
91     LargeSize.QuadPart = Vacb->SharedCacheMap->SectionSize.QuadPart - Vacb->FileOffset.QuadPart;
92     if (LargeSize.QuadPart > VACB_MAPPING_GRANULARITY)
93     {
94         LargeSize.QuadPart = VACB_MAPPING_GRANULARITY;
95     }
96     Size = LargeSize.LowPart;
97 
98     Size = ROUND_TO_PAGES(Size);
99     ASSERT(Size <= VACB_MAPPING_GRANULARITY);
100     ASSERT(Size > 0);
101 
102     Mdl = IoAllocateMdl(Vacb->BaseAddress, Size, FALSE, FALSE, NULL);
103     if (!Mdl)
104     {
105         return STATUS_INSUFFICIENT_RESOURCES;
106     }
107 
108     Status = STATUS_SUCCESS;
109     _SEH2_TRY
110     {
111         MmProbeAndLockPages(Mdl, KernelMode, IoWriteAccess);
112     }
113     _SEH2_EXCEPT (EXCEPTION_EXECUTE_HANDLER)
114     {
115         Status = _SEH2_GetExceptionCode();
116         DPRINT1("MmProbeAndLockPages failed with: %lx for %p (%p, %p)\n", Status, Mdl, Vacb, Vacb->BaseAddress);
117         KeBugCheck(CACHE_MANAGER);
118     } _SEH2_END;
119 
120     if (NT_SUCCESS(Status))
121     {
122         Mdl->MdlFlags |= MDL_IO_PAGE_READ;
123         KeInitializeEvent(&Event, NotificationEvent, FALSE);
124         Status = IoPageRead(Vacb->SharedCacheMap->FileObject, Mdl, &Vacb->FileOffset, &Event, &IoStatus);
125         if (Status == STATUS_PENDING)
126         {
127             KeWaitForSingleObject(&Event, Executive, KernelMode, FALSE, NULL);
128             Status = IoStatus.Status;
129         }
130 
131         MmUnlockPages(Mdl);
132     }
133 
134     IoFreeMdl(Mdl);
135 
136     if (!NT_SUCCESS(Status) && (Status != STATUS_END_OF_FILE))
137     {
138         DPRINT1("IoPageRead failed, Status %x\n", Status);
139         return Status;
140     }
141 
142     if (Size < VACB_MAPPING_GRANULARITY)
143     {
144         RtlZeroMemory((char*)Vacb->BaseAddress + Size,
145                       VACB_MAPPING_GRANULARITY - Size);
146     }
147 
148     return STATUS_SUCCESS;
149 }
150 
151 NTSTATUS
152 NTAPI
153 CcWriteVirtualAddress (
154     PROS_VACB Vacb)
155 {
156     ULONG Size;
157     PMDL Mdl;
158     NTSTATUS Status;
159     IO_STATUS_BLOCK IoStatus;
160     KEVENT Event;
161     ULARGE_INTEGER LargeSize;
162 
163     LargeSize.QuadPart = Vacb->SharedCacheMap->SectionSize.QuadPart - Vacb->FileOffset.QuadPart;
164     if (LargeSize.QuadPart > VACB_MAPPING_GRANULARITY)
165     {
166         LargeSize.QuadPart = VACB_MAPPING_GRANULARITY;
167     }
168     Size = LargeSize.LowPart;
169     //
170     // Nonpaged pool PDEs in ReactOS must actually be synchronized between the
171     // MmGlobalPageDirectory and the real system PDE directory. What a mess...
172     //
173     {
174         ULONG i = 0;
175         do
176         {
177             MmGetPfnForProcess(NULL, (PVOID)((ULONG_PTR)Vacb->BaseAddress + (i << PAGE_SHIFT)));
178         } while (++i < (Size >> PAGE_SHIFT));
179     }
180 
181     ASSERT(Size <= VACB_MAPPING_GRANULARITY);
182     ASSERT(Size > 0);
183 
184     Mdl = IoAllocateMdl(Vacb->BaseAddress, Size, FALSE, FALSE, NULL);
185     if (!Mdl)
186     {
187         return STATUS_INSUFFICIENT_RESOURCES;
188     }
189 
190     Status = STATUS_SUCCESS;
191     _SEH2_TRY
192     {
193         MmProbeAndLockPages(Mdl, KernelMode, IoReadAccess);
194     }
195     _SEH2_EXCEPT (EXCEPTION_EXECUTE_HANDLER)
196     {
197         Status = _SEH2_GetExceptionCode();
198         DPRINT1("MmProbeAndLockPages failed with: %lx for %p (%p, %p)\n", Status, Mdl, Vacb, Vacb->BaseAddress);
199         KeBugCheck(CACHE_MANAGER);
200     } _SEH2_END;
201 
202     if (NT_SUCCESS(Status))
203     {
204         KeInitializeEvent(&Event, NotificationEvent, FALSE);
205         Status = IoSynchronousPageWrite(Vacb->SharedCacheMap->FileObject, Mdl, &Vacb->FileOffset, &Event, &IoStatus);
206         if (Status == STATUS_PENDING)
207         {
208             KeWaitForSingleObject(&Event, Executive, KernelMode, FALSE, NULL);
209             Status = IoStatus.Status;
210         }
211 
212         MmUnlockPages(Mdl);
213     }
214     IoFreeMdl(Mdl);
215     if (!NT_SUCCESS(Status) && (Status != STATUS_END_OF_FILE))
216     {
217         DPRINT1("IoPageWrite failed, Status %x\n", Status);
218         return Status;
219     }
220 
221     return STATUS_SUCCESS;
222 }
223 
224 NTSTATUS
225 ReadWriteOrZero(
226     _Inout_ PVOID BaseAddress,
227     _Inout_opt_ PVOID Buffer,
228     _In_ ULONG Length,
229     _In_ CC_COPY_OPERATION Operation)
230 {
231     NTSTATUS Status = STATUS_SUCCESS;
232 
233     if (Operation == CcOperationZero)
234     {
235         /* Zero */
236         RtlZeroMemory(BaseAddress, Length);
237     }
238     else
239     {
240         _SEH2_TRY
241         {
242             if (Operation == CcOperationWrite)
243                 RtlCopyMemory(BaseAddress, Buffer, Length);
244             else
245                 RtlCopyMemory(Buffer, BaseAddress, Length);
246         }
247         _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
248         {
249             Status = _SEH2_GetExceptionCode();
250         }
251         _SEH2_END;
252     }
253     return Status;
254 }
255 
256 BOOLEAN
257 CcCopyData (
258     _In_ PFILE_OBJECT FileObject,
259     _In_ LONGLONG FileOffset,
260     _Inout_ PVOID Buffer,
261     _In_ LONGLONG Length,
262     _In_ CC_COPY_OPERATION Operation,
263     _In_ BOOLEAN Wait,
264     _Out_ PIO_STATUS_BLOCK IoStatus)
265 {
266     NTSTATUS Status;
267     LONGLONG CurrentOffset;
268     ULONG BytesCopied;
269     KIRQL OldIrql;
270     PROS_SHARED_CACHE_MAP SharedCacheMap;
271     PLIST_ENTRY ListEntry;
272     PROS_VACB Vacb;
273     ULONG PartialLength;
274     PVOID BaseAddress;
275     BOOLEAN Valid;
276     PPRIVATE_CACHE_MAP PrivateCacheMap;
277 
278     SharedCacheMap = FileObject->SectionObjectPointer->SharedCacheMap;
279     PrivateCacheMap = FileObject->PrivateCacheMap;
280     CurrentOffset = FileOffset;
281     BytesCopied = 0;
282 
283     if (!Wait)
284     {
285         /* test if the requested data is available */
286         KeAcquireSpinLock(&SharedCacheMap->CacheMapLock, &OldIrql);
287         /* FIXME: this loop doesn't take into account areas that don't have
288          * a VACB in the list yet */
289         ListEntry = SharedCacheMap->CacheMapVacbListHead.Flink;
290         while (ListEntry != &SharedCacheMap->CacheMapVacbListHead)
291         {
292             Vacb = CONTAINING_RECORD(ListEntry,
293                                      ROS_VACB,
294                                      CacheMapVacbListEntry);
295             ListEntry = ListEntry->Flink;
296             if (!Vacb->Valid &&
297                 DoRangesIntersect(Vacb->FileOffset.QuadPart,
298                                   VACB_MAPPING_GRANULARITY,
299                                   CurrentOffset, Length))
300             {
301                 KeReleaseSpinLock(&SharedCacheMap->CacheMapLock, OldIrql);
302                 /* data not available */
303                 return FALSE;
304             }
305             if (Vacb->FileOffset.QuadPart >= CurrentOffset + Length)
306                 break;
307         }
308         KeReleaseSpinLock(&SharedCacheMap->CacheMapLock, OldIrql);
309     }
310 
311     PartialLength = CurrentOffset % VACB_MAPPING_GRANULARITY;
312     if (PartialLength != 0)
313     {
314         PartialLength = min(Length, VACB_MAPPING_GRANULARITY - PartialLength);
315         Status = CcRosRequestVacb(SharedCacheMap,
316                                   ROUND_DOWN(CurrentOffset,
317                                              VACB_MAPPING_GRANULARITY),
318                                   &BaseAddress,
319                                   &Valid,
320                                   &Vacb);
321         if (!NT_SUCCESS(Status))
322             ExRaiseStatus(Status);
323         if (!Valid)
324         {
325             Status = CcReadVirtualAddress(Vacb);
326             if (!NT_SUCCESS(Status))
327             {
328                 CcRosReleaseVacb(SharedCacheMap, Vacb, FALSE, FALSE, FALSE);
329                 ExRaiseStatus(Status);
330             }
331         }
332         Status = ReadWriteOrZero((PUCHAR)BaseAddress + CurrentOffset % VACB_MAPPING_GRANULARITY,
333                                  Buffer,
334                                  PartialLength,
335                                  Operation);
336 
337         CcRosReleaseVacb(SharedCacheMap, Vacb, TRUE, Operation != CcOperationRead, FALSE);
338 
339         if (!NT_SUCCESS(Status))
340             ExRaiseStatus(STATUS_INVALID_USER_BUFFER);
341 
342         Length -= PartialLength;
343         CurrentOffset += PartialLength;
344         BytesCopied += PartialLength;
345 
346         if (Operation != CcOperationZero)
347             Buffer = (PVOID)((ULONG_PTR)Buffer + PartialLength);
348     }
349 
350     while (Length > 0)
351     {
352         ASSERT(CurrentOffset % VACB_MAPPING_GRANULARITY == 0);
353         PartialLength = min(VACB_MAPPING_GRANULARITY, Length);
354         Status = CcRosRequestVacb(SharedCacheMap,
355                                   CurrentOffset,
356                                   &BaseAddress,
357                                   &Valid,
358                                   &Vacb);
359         if (!NT_SUCCESS(Status))
360             ExRaiseStatus(Status);
361         if (!Valid &&
362             (Operation == CcOperationRead ||
363              PartialLength < VACB_MAPPING_GRANULARITY))
364         {
365             Status = CcReadVirtualAddress(Vacb);
366             if (!NT_SUCCESS(Status))
367             {
368                 CcRosReleaseVacb(SharedCacheMap, Vacb, FALSE, FALSE, FALSE);
369                 ExRaiseStatus(Status);
370             }
371         }
372         Status = ReadWriteOrZero(BaseAddress, Buffer, PartialLength, Operation);
373 
374         CcRosReleaseVacb(SharedCacheMap, Vacb, TRUE, Operation != CcOperationRead, FALSE);
375 
376         if (!NT_SUCCESS(Status))
377             ExRaiseStatus(STATUS_INVALID_USER_BUFFER);
378 
379         Length -= PartialLength;
380         CurrentOffset += PartialLength;
381         BytesCopied += PartialLength;
382 
383         if (Operation != CcOperationZero)
384             Buffer = (PVOID)((ULONG_PTR)Buffer + PartialLength);
385     }
386 
387     /* If that was a successful sync read operation, let's handle read ahead */
388     if (Operation == CcOperationRead && Length == 0 && Wait)
389     {
390         /* If file isn't random access and next read may get us cross VACB boundary,
391          * schedule next read
392          */
393         if (!BooleanFlagOn(FileObject->Flags, FO_RANDOM_ACCESS) &&
394             (CurrentOffset - 1) / VACB_MAPPING_GRANULARITY != (CurrentOffset + BytesCopied - 1) / VACB_MAPPING_GRANULARITY)
395         {
396             CcScheduleReadAhead(FileObject, (PLARGE_INTEGER)&FileOffset, BytesCopied);
397         }
398 
399         /* And update read history in private cache map */
400         PrivateCacheMap->FileOffset1.QuadPart = PrivateCacheMap->FileOffset2.QuadPart;
401         PrivateCacheMap->BeyondLastByte1.QuadPart = PrivateCacheMap->BeyondLastByte2.QuadPart;
402         PrivateCacheMap->FileOffset2.QuadPart = FileOffset;
403         PrivateCacheMap->BeyondLastByte2.QuadPart = FileOffset + BytesCopied;
404     }
405 
406     IoStatus->Status = STATUS_SUCCESS;
407     IoStatus->Information = BytesCopied;
408     return TRUE;
409 }
410 
411 VOID
412 CcPostDeferredWrites(VOID)
413 {
414     ULONG WrittenBytes;
415 
416     /* We'll try to write as much as we can */
417     WrittenBytes = 0;
418     while (TRUE)
419     {
420         KIRQL OldIrql;
421         PLIST_ENTRY ListEntry;
422         PDEFERRED_WRITE DeferredWrite;
423 
424         DeferredWrite = NULL;
425 
426         /* Lock our deferred writes list */
427         KeAcquireSpinLock(&CcDeferredWriteSpinLock, &OldIrql);
428         for (ListEntry = CcDeferredWrites.Flink;
429              ListEntry != &CcDeferredWrites;
430              ListEntry = ListEntry->Flink)
431         {
432             /* Extract an entry */
433             DeferredWrite = CONTAINING_RECORD(ListEntry, DEFERRED_WRITE, DeferredWriteLinks);
434 
435             /* Compute the modified bytes, based on what we already wrote */
436             WrittenBytes += DeferredWrite->BytesToWrite;
437             /* We overflowed, give up */
438             if (WrittenBytes < DeferredWrite->BytesToWrite)
439             {
440                 DeferredWrite = NULL;
441                 break;
442             }
443 
444             /* Check we can write */
445             if (CcCanIWrite(DeferredWrite->FileObject, WrittenBytes, FALSE, RetryForceCheckPerFile))
446             {
447                 /* We can, so remove it from the list and stop looking for entry */
448                 RemoveEntryList(&DeferredWrite->DeferredWriteLinks);
449                 break;
450             }
451 
452             /* If we don't accept modified pages, stop here */
453             if (!DeferredWrite->LimitModifiedPages)
454             {
455                 DeferredWrite = NULL;
456                 break;
457             }
458 
459             /* Reset count as nothing was written yet */
460             WrittenBytes -= DeferredWrite->BytesToWrite;
461             DeferredWrite = NULL;
462         }
463         KeReleaseSpinLock(&CcDeferredWriteSpinLock, OldIrql);
464 
465         /* Nothing to write found, give up */
466         if (DeferredWrite == NULL)
467         {
468             break;
469         }
470 
471         /* If we have an event, set it and quit */
472         if (DeferredWrite->Event)
473         {
474             KeSetEvent(DeferredWrite->Event, IO_NO_INCREMENT, FALSE);
475         }
476         /* Otherwise, call the write routine and free the context */
477         else
478         {
479             DeferredWrite->PostRoutine(DeferredWrite->Context1, DeferredWrite->Context2);
480             ExFreePoolWithTag(DeferredWrite, 'CcDw');
481         }
482     }
483 }
484 
485 VOID
486 CcPerformReadAhead(
487     IN PFILE_OBJECT FileObject)
488 {
489     NTSTATUS Status;
490     LONGLONG CurrentOffset;
491     KIRQL OldIrql;
492     PROS_SHARED_CACHE_MAP SharedCacheMap;
493     PROS_VACB Vacb;
494     ULONG PartialLength;
495     PVOID BaseAddress;
496     BOOLEAN Valid;
497     ULONG Length;
498     PPRIVATE_CACHE_MAP PrivateCacheMap;
499     BOOLEAN Locked;
500 
501     SharedCacheMap = FileObject->SectionObjectPointer->SharedCacheMap;
502 
503     /* Critical:
504      * PrivateCacheMap might disappear in-between if the handle
505      * to the file is closed (private is attached to the handle not to
506      * the file), so we need to lock the master lock while we deal with
507      * it. It won't disappear without attempting to lock such lock.
508      */
509     OldIrql = KeAcquireQueuedSpinLock(LockQueueMasterLock);
510     PrivateCacheMap = FileObject->PrivateCacheMap;
511     /* If the handle was closed since the read ahead was scheduled, just quit */
512     if (PrivateCacheMap == NULL)
513     {
514         KeReleaseQueuedSpinLock(LockQueueMasterLock, OldIrql);
515         ObDereferenceObject(FileObject);
516         return;
517     }
518     /* Otherwise, extract read offset and length and release private map */
519     else
520     {
521         KeAcquireSpinLockAtDpcLevel(&PrivateCacheMap->ReadAheadSpinLock);
522         CurrentOffset = PrivateCacheMap->ReadAheadOffset[1].QuadPart;
523         Length = PrivateCacheMap->ReadAheadLength[1];
524         KeReleaseSpinLockFromDpcLevel(&PrivateCacheMap->ReadAheadSpinLock);
525     }
526     KeReleaseQueuedSpinLock(LockQueueMasterLock, OldIrql);
527 
528     /* Time to go! */
529     DPRINT("Doing ReadAhead for %p\n", FileObject);
530     /* Lock the file, first */
531     if (!SharedCacheMap->Callbacks->AcquireForReadAhead(SharedCacheMap->LazyWriteContext, FALSE))
532     {
533         Locked = FALSE;
534         goto Clear;
535     }
536 
537     /* Remember it's locked */
538     Locked = TRUE;
539 
540     /* Don't read past the end of the file */
541     if (CurrentOffset >= SharedCacheMap->FileSize.QuadPart)
542     {
543         goto Clear;
544     }
545     if (CurrentOffset + Length > SharedCacheMap->FileSize.QuadPart)
546     {
547         Length = SharedCacheMap->FileSize.QuadPart - CurrentOffset;
548     }
549 
550     /* Next of the algorithm will lock like CcCopyData with the slight
551      * difference that we don't copy data back to an user-backed buffer
552      * We just bring data into Cc
553      */
554     PartialLength = CurrentOffset % VACB_MAPPING_GRANULARITY;
555     if (PartialLength != 0)
556     {
557         PartialLength = min(Length, VACB_MAPPING_GRANULARITY - PartialLength);
558         Status = CcRosRequestVacb(SharedCacheMap,
559                                   ROUND_DOWN(CurrentOffset,
560                                              VACB_MAPPING_GRANULARITY),
561                                   &BaseAddress,
562                                   &Valid,
563                                   &Vacb);
564         if (!NT_SUCCESS(Status))
565         {
566             DPRINT1("Failed to request VACB: %lx!\n", Status);
567             goto Clear;
568         }
569 
570         if (!Valid)
571         {
572             Status = CcReadVirtualAddress(Vacb);
573             if (!NT_SUCCESS(Status))
574             {
575                 CcRosReleaseVacb(SharedCacheMap, Vacb, FALSE, FALSE, FALSE);
576                 DPRINT1("Failed to read data: %lx!\n", Status);
577                 goto Clear;
578             }
579         }
580 
581         CcRosReleaseVacb(SharedCacheMap, Vacb, TRUE, FALSE, FALSE);
582 
583         Length -= PartialLength;
584         CurrentOffset += PartialLength;
585     }
586 
587     while (Length > 0)
588     {
589         ASSERT(CurrentOffset % VACB_MAPPING_GRANULARITY == 0);
590         PartialLength = min(VACB_MAPPING_GRANULARITY, Length);
591         Status = CcRosRequestVacb(SharedCacheMap,
592                                   CurrentOffset,
593                                   &BaseAddress,
594                                   &Valid,
595                                   &Vacb);
596         if (!NT_SUCCESS(Status))
597         {
598             DPRINT1("Failed to request VACB: %lx!\n", Status);
599             goto Clear;
600         }
601 
602         if (!Valid)
603         {
604             Status = CcReadVirtualAddress(Vacb);
605             if (!NT_SUCCESS(Status))
606             {
607                 CcRosReleaseVacb(SharedCacheMap, Vacb, FALSE, FALSE, FALSE);
608                 DPRINT1("Failed to read data: %lx!\n", Status);
609                 goto Clear;
610             }
611         }
612 
613         CcRosReleaseVacb(SharedCacheMap, Vacb, TRUE, FALSE, FALSE);
614 
615         Length -= PartialLength;
616         CurrentOffset += PartialLength;
617     }
618 
619 Clear:
620     /* See previous comment about private cache map */
621     OldIrql = KeAcquireQueuedSpinLock(LockQueueMasterLock);
622     PrivateCacheMap = FileObject->PrivateCacheMap;
623     if (PrivateCacheMap != NULL)
624     {
625         /* Mark read ahead as unactive */
626         KeAcquireSpinLockAtDpcLevel(&PrivateCacheMap->ReadAheadSpinLock);
627         InterlockedAnd((volatile long *)&PrivateCacheMap->UlongFlags, ~PRIVATE_CACHE_MAP_READ_AHEAD_ACTIVE);
628         KeReleaseSpinLockFromDpcLevel(&PrivateCacheMap->ReadAheadSpinLock);
629     }
630     KeReleaseQueuedSpinLock(LockQueueMasterLock, OldIrql);
631 
632     /* If file was locked, release it */
633     if (Locked)
634     {
635         SharedCacheMap->Callbacks->ReleaseFromReadAhead(SharedCacheMap->LazyWriteContext);
636     }
637 
638     /* And drop our extra reference (See: CcScheduleReadAhead) */
639     ObDereferenceObject(FileObject);
640 
641     return;
642 }
643 
644 /*
645  * @unimplemented
646  */
647 BOOLEAN
648 NTAPI
649 CcCanIWrite (
650     IN PFILE_OBJECT FileObject,
651     IN ULONG BytesToWrite,
652     IN BOOLEAN Wait,
653     IN BOOLEAN Retrying)
654 {
655     KIRQL OldIrql;
656     KEVENT WaitEvent;
657     ULONG Length, Pages;
658     BOOLEAN PerFileDefer;
659     DEFERRED_WRITE Context;
660     PFSRTL_COMMON_FCB_HEADER Fcb;
661     CC_CAN_WRITE_RETRY TryContext;
662     PROS_SHARED_CACHE_MAP SharedCacheMap;
663 
664     CCTRACE(CC_API_DEBUG, "FileObject=%p BytesToWrite=%lu Wait=%d Retrying=%d\n",
665         FileObject, BytesToWrite, Wait, Retrying);
666 
667     /* Write through is always OK */
668     if (BooleanFlagOn(FileObject->Flags, FO_WRITE_THROUGH))
669     {
670         return TRUE;
671     }
672 
673     TryContext = Retrying;
674     /* Allow remote file if not from posted */
675     if (IoIsFileOriginRemote(FileObject) && TryContext < RetryAllowRemote)
676     {
677         return TRUE;
678     }
679 
680     /* Don't exceed max tolerated size */
681     Length = MAX_ZERO_LENGTH;
682     if (BytesToWrite < MAX_ZERO_LENGTH)
683     {
684         Length = BytesToWrite;
685     }
686 
687     Pages = BYTES_TO_PAGES(Length);
688 
689     /* By default, assume limits per file won't be hit */
690     PerFileDefer = FALSE;
691     Fcb = FileObject->FsContext;
692     /* Do we have to check for limits per file? */
693     if (TryContext >= RetryForceCheckPerFile ||
694         BooleanFlagOn(Fcb->Flags, FSRTL_FLAG_LIMIT_MODIFIED_PAGES))
695     {
696         /* If master is not locked, lock it now */
697         if (TryContext != RetryMasterLocked)
698         {
699             OldIrql = KeAcquireQueuedSpinLock(LockQueueMasterLock);
700         }
701 
702         /* Let's not assume the file is cached... */
703         if (FileObject->SectionObjectPointer != NULL &&
704             FileObject->SectionObjectPointer->SharedCacheMap != NULL)
705         {
706             SharedCacheMap = FileObject->SectionObjectPointer->SharedCacheMap;
707             /* Do we have limits per file set? */
708             if (SharedCacheMap->DirtyPageThreshold != 0 &&
709                 SharedCacheMap->DirtyPages != 0)
710             {
711                 /* Yes, check whether they are blocking */
712                 if (Pages + SharedCacheMap->DirtyPages > SharedCacheMap->DirtyPageThreshold)
713                 {
714                     PerFileDefer = TRUE;
715                 }
716             }
717         }
718 
719         /* And don't forget to release master */
720         if (TryContext != RetryMasterLocked)
721         {
722             KeReleaseQueuedSpinLock(LockQueueMasterLock, OldIrql);
723         }
724     }
725 
726     /* So, now allow write if:
727      * - Not the first try or we have no throttling yet
728      * AND:
729      * - We don't exceed threshold!
730      * - We don't exceed what Mm can allow us to use
731      *   + If we're above top, that's fine
732      *   + If we're above bottom with limited modified pages, that's fine
733      *   + Otherwise, throttle!
734      */
735     if ((TryContext != FirstTry || IsListEmpty(&CcDeferredWrites)) &&
736         CcTotalDirtyPages + Pages < CcDirtyPageThreshold &&
737         (MmAvailablePages > MmThrottleTop ||
738          (MmModifiedPageListHead.Total < 1000 && MmAvailablePages > MmThrottleBottom)) &&
739         !PerFileDefer)
740     {
741         return TRUE;
742     }
743 
744     /* If we can wait, we'll start the wait loop for waiting till we can
745      * write for real
746      */
747     if (!Wait)
748     {
749         return FALSE;
750     }
751 
752     /* Otherwise, if there are no deferred writes yet, start the lazy writer */
753     if (IsListEmpty(&CcDeferredWrites))
754     {
755         KIRQL OldIrql;
756 
757         OldIrql = KeAcquireQueuedSpinLock(LockQueueMasterLock);
758         CcScheduleLazyWriteScan(TRUE);
759         KeReleaseQueuedSpinLock(LockQueueMasterLock, OldIrql);
760     }
761 
762     /* Initialize our wait event */
763     KeInitializeEvent(&WaitEvent, NotificationEvent, FALSE);
764 
765     /* And prepare a dummy context */
766     Context.NodeTypeCode = NODE_TYPE_DEFERRED_WRITE;
767     Context.NodeByteSize = sizeof(DEFERRED_WRITE);
768     Context.FileObject = FileObject;
769     Context.BytesToWrite = BytesToWrite;
770     Context.LimitModifiedPages = BooleanFlagOn(Fcb->Flags, FSRTL_FLAG_LIMIT_MODIFIED_PAGES);
771     Context.Event = &WaitEvent;
772 
773     /* And queue it */
774     if (Retrying)
775     {
776         /* To the top, if that's a retry */
777         ExInterlockedInsertHeadList(&CcDeferredWrites,
778                                     &Context.DeferredWriteLinks,
779                                     &CcDeferredWriteSpinLock);
780     }
781     else
782     {
783         /* To the bottom, if that's a first time */
784         ExInterlockedInsertTailList(&CcDeferredWrites,
785                                     &Context.DeferredWriteLinks,
786                                     &CcDeferredWriteSpinLock);
787     }
788 
789     DPRINT1("Actively deferring write for: %p\n", FileObject);
790     /* Now, we'll loop until our event is set. When it is set, it means that caller
791      * can immediately write, and has to
792      */
793     do
794     {
795         CcPostDeferredWrites();
796     } while (KeWaitForSingleObject(&WaitEvent, Executive, KernelMode, FALSE, &CcIdleDelay) != STATUS_SUCCESS);
797 
798     return TRUE;
799 }
800 
801 /*
802  * @implemented
803  */
804 BOOLEAN
805 NTAPI
806 CcCopyRead (
807     IN PFILE_OBJECT FileObject,
808     IN PLARGE_INTEGER FileOffset,
809     IN ULONG Length,
810     IN BOOLEAN Wait,
811     OUT PVOID Buffer,
812     OUT PIO_STATUS_BLOCK IoStatus)
813 {
814     CCTRACE(CC_API_DEBUG, "FileObject=%p FileOffset=%I64d Length=%lu Wait=%d\n",
815         FileObject, FileOffset->QuadPart, Length, Wait);
816 
817     DPRINT("CcCopyRead(FileObject 0x%p, FileOffset %I64x, "
818            "Length %lu, Wait %u, Buffer 0x%p, IoStatus 0x%p)\n",
819            FileObject, FileOffset->QuadPart, Length, Wait,
820            Buffer, IoStatus);
821 
822     return CcCopyData(FileObject,
823                       FileOffset->QuadPart,
824                       Buffer,
825                       Length,
826                       CcOperationRead,
827                       Wait,
828                       IoStatus);
829 }
830 
831 /*
832  * @implemented
833  */
834 BOOLEAN
835 NTAPI
836 CcCopyWrite (
837     IN PFILE_OBJECT FileObject,
838     IN PLARGE_INTEGER FileOffset,
839     IN ULONG Length,
840     IN BOOLEAN Wait,
841     IN PVOID Buffer)
842 {
843     IO_STATUS_BLOCK IoStatus;
844 
845     CCTRACE(CC_API_DEBUG, "FileObject=%p FileOffset=%I64d Length=%lu Wait=%d Buffer=%p\n",
846         FileObject, FileOffset->QuadPart, Length, Wait, Buffer);
847 
848     DPRINT("CcCopyWrite(FileObject 0x%p, FileOffset %I64x, "
849            "Length %lu, Wait %u, Buffer 0x%p)\n",
850            FileObject, FileOffset->QuadPart, Length, Wait, Buffer);
851 
852     return CcCopyData(FileObject,
853                       FileOffset->QuadPart,
854                       Buffer,
855                       Length,
856                       CcOperationWrite,
857                       Wait,
858                       &IoStatus);
859 }
860 
861 /*
862  * @implemented
863  */
864 VOID
865 NTAPI
866 CcDeferWrite (
867     IN PFILE_OBJECT FileObject,
868     IN PCC_POST_DEFERRED_WRITE PostRoutine,
869     IN PVOID Context1,
870     IN PVOID Context2,
871     IN ULONG BytesToWrite,
872     IN BOOLEAN Retrying)
873 {
874     KIRQL OldIrql;
875     PDEFERRED_WRITE Context;
876     PFSRTL_COMMON_FCB_HEADER Fcb;
877 
878     CCTRACE(CC_API_DEBUG, "FileObject=%p PostRoutine=%p Context1=%p Context2=%p BytesToWrite=%lu Retrying=%d\n",
879         FileObject, PostRoutine, Context1, Context2, BytesToWrite, Retrying);
880 
881     /* Try to allocate a context for queueing the write operation */
882     Context = ExAllocatePoolWithTag(NonPagedPool, sizeof(DEFERRED_WRITE), 'CcDw');
883     /* If it failed, immediately execute the operation! */
884     if (Context == NULL)
885     {
886         PostRoutine(Context1, Context2);
887         return;
888     }
889 
890     Fcb = FileObject->FsContext;
891 
892     /* Otherwise, initialize the context */
893     RtlZeroMemory(Context, sizeof(DEFERRED_WRITE));
894     Context->NodeTypeCode = NODE_TYPE_DEFERRED_WRITE;
895     Context->NodeByteSize = sizeof(DEFERRED_WRITE);
896     Context->FileObject = FileObject;
897     Context->PostRoutine = PostRoutine;
898     Context->Context1 = Context1;
899     Context->Context2 = Context2;
900     Context->BytesToWrite = BytesToWrite;
901     Context->LimitModifiedPages = BooleanFlagOn(Fcb->Flags, FSRTL_FLAG_LIMIT_MODIFIED_PAGES);
902 
903     /* And queue it */
904     if (Retrying)
905     {
906         /* To the top, if that's a retry */
907         ExInterlockedInsertHeadList(&CcDeferredWrites,
908                                     &Context->DeferredWriteLinks,
909                                     &CcDeferredWriteSpinLock);
910     }
911     else
912     {
913         /* To the bottom, if that's a first time */
914         ExInterlockedInsertTailList(&CcDeferredWrites,
915                                     &Context->DeferredWriteLinks,
916                                     &CcDeferredWriteSpinLock);
917     }
918 
919     /* Try to execute the posted writes */
920     CcPostDeferredWrites();
921 
922     /* Schedule a lazy writer run to handle deferred writes */
923     OldIrql = KeAcquireQueuedSpinLock(LockQueueMasterLock);
924     if (!LazyWriter.ScanActive)
925     {
926         CcScheduleLazyWriteScan(FALSE);
927     }
928     KeReleaseQueuedSpinLock(LockQueueMasterLock, OldIrql);
929 }
930 
931 /*
932  * @unimplemented
933  */
934 VOID
935 NTAPI
936 CcFastCopyRead (
937     IN PFILE_OBJECT FileObject,
938     IN ULONG FileOffset,
939     IN ULONG Length,
940     IN ULONG PageCount,
941     OUT PVOID Buffer,
942     OUT PIO_STATUS_BLOCK IoStatus)
943 {
944     LARGE_INTEGER LargeFileOffset;
945     BOOLEAN Success;
946 
947     CCTRACE(CC_API_DEBUG, "FileObject=%p FileOffset=%lu Length=%lu PageCount=%lu Buffer=%p\n",
948         FileObject, FileOffset, Length, PageCount, Buffer);
949 
950     DBG_UNREFERENCED_PARAMETER(PageCount);
951 
952     LargeFileOffset.QuadPart = FileOffset;
953     Success = CcCopyRead(FileObject,
954                          &LargeFileOffset,
955                          Length,
956                          TRUE,
957                          Buffer,
958                          IoStatus);
959     ASSERT(Success == TRUE);
960 }
961 
962 /*
963  * @unimplemented
964  */
965 VOID
966 NTAPI
967 CcFastCopyWrite (
968     IN PFILE_OBJECT FileObject,
969     IN ULONG FileOffset,
970     IN ULONG Length,
971     IN PVOID Buffer)
972 {
973     LARGE_INTEGER LargeFileOffset;
974     BOOLEAN Success;
975 
976     CCTRACE(CC_API_DEBUG, "FileObject=%p FileOffset=%lu Length=%lu Buffer=%p\n",
977         FileObject, FileOffset, Length, Buffer);
978 
979     LargeFileOffset.QuadPart = FileOffset;
980     Success = CcCopyWrite(FileObject,
981                           &LargeFileOffset,
982                           Length,
983                           TRUE,
984                           Buffer);
985     ASSERT(Success == TRUE);
986 }
987 
988 /*
989  * @implemented
990  */
991 BOOLEAN
992 NTAPI
993 CcZeroData (
994     IN PFILE_OBJECT FileObject,
995     IN PLARGE_INTEGER StartOffset,
996     IN PLARGE_INTEGER EndOffset,
997     IN BOOLEAN Wait)
998 {
999     NTSTATUS Status;
1000     LARGE_INTEGER WriteOffset;
1001     LONGLONG Length;
1002     ULONG CurrentLength;
1003     PMDL Mdl;
1004     ULONG i;
1005     IO_STATUS_BLOCK Iosb;
1006     KEVENT Event;
1007 
1008     CCTRACE(CC_API_DEBUG, "FileObject=%p StartOffset=%I64u EndOffset=%I64u Wait=%d\n",
1009         FileObject, StartOffset->QuadPart, EndOffset->QuadPart, Wait);
1010 
1011     DPRINT("CcZeroData(FileObject 0x%p, StartOffset %I64x, EndOffset %I64x, "
1012            "Wait %u)\n", FileObject, StartOffset->QuadPart, EndOffset->QuadPart,
1013            Wait);
1014 
1015     Length = EndOffset->QuadPart - StartOffset->QuadPart;
1016     WriteOffset.QuadPart = StartOffset->QuadPart;
1017 
1018     if (FileObject->SectionObjectPointer->SharedCacheMap == NULL)
1019     {
1020         /* File is not cached */
1021 
1022         Mdl = _alloca(MmSizeOfMdl(NULL, MAX_ZERO_LENGTH));
1023 
1024         while (Length > 0)
1025         {
1026             if (Length + WriteOffset.QuadPart % PAGE_SIZE > MAX_ZERO_LENGTH)
1027             {
1028                 CurrentLength = MAX_ZERO_LENGTH - WriteOffset.QuadPart % PAGE_SIZE;
1029             }
1030             else
1031             {
1032                 CurrentLength = Length;
1033             }
1034             MmInitializeMdl(Mdl, (PVOID)(ULONG_PTR)WriteOffset.QuadPart, CurrentLength);
1035             Mdl->MdlFlags |= (MDL_PAGES_LOCKED | MDL_IO_PAGE_READ);
1036             for (i = 0; i < ((Mdl->Size - sizeof(MDL)) / sizeof(ULONG)); i++)
1037             {
1038                 ((PPFN_NUMBER)(Mdl + 1))[i] = CcZeroPage;
1039             }
1040             KeInitializeEvent(&Event, NotificationEvent, FALSE);
1041             Status = IoSynchronousPageWrite(FileObject, Mdl, &WriteOffset, &Event, &Iosb);
1042             if (Status == STATUS_PENDING)
1043             {
1044                 KeWaitForSingleObject(&Event, Executive, KernelMode, FALSE, NULL);
1045                 Status = Iosb.Status;
1046             }
1047             if (Mdl->MdlFlags & MDL_MAPPED_TO_SYSTEM_VA)
1048             {
1049                 MmUnmapLockedPages(Mdl->MappedSystemVa, Mdl);
1050             }
1051             if (!NT_SUCCESS(Status))
1052             {
1053                 return FALSE;
1054             }
1055             WriteOffset.QuadPart += CurrentLength;
1056             Length -= CurrentLength;
1057         }
1058     }
1059     else
1060     {
1061         IO_STATUS_BLOCK IoStatus;
1062 
1063         return CcCopyData(FileObject,
1064                           WriteOffset.QuadPart,
1065                           NULL,
1066                           Length,
1067                           CcOperationZero,
1068                           Wait,
1069                           &IoStatus);
1070     }
1071 
1072     return TRUE;
1073 }
1074