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