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