1 /*
2  * PROJECT:         ReactOS kernel-mode tests
3  * LICENSE:         LGPLv2.1+ - See COPYING.LIB in the top level directory
4  * PURPOSE:         Test driver for CcCopyWrite function
5  * PROGRAMMER:      Pierre Schweitzer <pierre@reactos.org>
6  */
7 
8 #include <kmt_test.h>
9 
10 #define NDEBUG
11 #include <debug.h>
12 
13 typedef struct _TEST_FCB
14 {
15     FSRTL_ADVANCED_FCB_HEADER Header;
16     SECTION_OBJECT_POINTERS SectionObjectPointers;
17     FAST_MUTEX HeaderMutex;
18     ULONG WriteLength;
19 } TEST_FCB, *PTEST_FCB;
20 
21 static PFILE_OBJECT TestFileObject;
22 static PDEVICE_OBJECT TestDeviceObject;
23 static KMT_IRP_HANDLER TestIrpHandler;
24 static FAST_IO_DISPATCH TestFastIoDispatch;
25 static BOOLEAN InBehaviourTest;
26 
27 BOOLEAN ReadCalled;
28 LARGE_INTEGER ReadOffset;
29 ULONG ReadLength;
30 
31 static
32 BOOLEAN
33 NTAPI
34 FastIoRead(
35     _In_ PFILE_OBJECT FileObject,
36     _In_ PLARGE_INTEGER FileOffset,
37     _In_ ULONG Length,
38     _In_ BOOLEAN Wait,
39     _In_ ULONG LockKey,
40     _Out_ PVOID Buffer,
41     _Out_ PIO_STATUS_BLOCK IoStatus,
42     _In_ PDEVICE_OBJECT DeviceObject)
43 {
44     IoStatus->Status = STATUS_NOT_SUPPORTED;
45     return FALSE;
46 }
47 
48 static
49 BOOLEAN
50 NTAPI
51 FastIoWrite(
52     _In_ PFILE_OBJECT FileObject,
53     _In_ PLARGE_INTEGER FileOffset,
54     _In_ ULONG Length,
55     _In_ BOOLEAN Wait,
56     _In_ ULONG LockKey,
57     _Out_ PVOID Buffer,
58     _Out_ PIO_STATUS_BLOCK IoStatus,
59     _In_ PDEVICE_OBJECT DeviceObject)
60 {
61     IoStatus->Status = STATUS_NOT_SUPPORTED;
62     return FALSE;
63 }
64 
65 NTSTATUS
66 TestEntry(
67     _In_ PDRIVER_OBJECT DriverObject,
68     _In_ PCUNICODE_STRING RegistryPath,
69     _Out_ PCWSTR *DeviceName,
70     _Inout_ INT *Flags)
71 {
72     NTSTATUS Status = STATUS_SUCCESS;
73 
74     PAGED_CODE();
75 
76     UNREFERENCED_PARAMETER(RegistryPath);
77 
78     *DeviceName = L"CcCopyWrite";
79     *Flags = TESTENTRY_NO_EXCLUSIVE_DEVICE |
80              TESTENTRY_BUFFERED_IO_DEVICE |
81              TESTENTRY_NO_READONLY_DEVICE;
82 
83     KmtRegisterIrpHandler(IRP_MJ_CLEANUP, NULL, TestIrpHandler);
84     KmtRegisterIrpHandler(IRP_MJ_CREATE, NULL, TestIrpHandler);
85     KmtRegisterIrpHandler(IRP_MJ_READ, NULL, TestIrpHandler);
86     KmtRegisterIrpHandler(IRP_MJ_WRITE, NULL, TestIrpHandler);
87     KmtRegisterIrpHandler(IRP_MJ_FLUSH_BUFFERS, NULL, TestIrpHandler);
88 
89     TestFastIoDispatch.FastIoRead = FastIoRead;
90     TestFastIoDispatch.FastIoWrite = FastIoWrite;
91     DriverObject->FastIoDispatch = &TestFastIoDispatch;
92 
93 
94     return Status;
95 }
96 
97 VOID
98 TestUnload(
99     _In_ PDRIVER_OBJECT DriverObject)
100 {
101     PAGED_CODE();
102 }
103 
104 BOOLEAN
105 NTAPI
106 AcquireForLazyWrite(
107     _In_ PVOID Context,
108     _In_ BOOLEAN Wait)
109 {
110     return TRUE;
111 }
112 
113 VOID
114 NTAPI
115 ReleaseFromLazyWrite(
116     _In_ PVOID Context)
117 {
118     return;
119 }
120 
121 BOOLEAN
122 NTAPI
123 AcquireForReadAhead(
124     _In_ PVOID Context,
125     _In_ BOOLEAN Wait)
126 {
127     return TRUE;
128 }
129 
130 VOID
131 NTAPI
132 ReleaseFromReadAhead(
133     _In_ PVOID Context)
134 {
135     return;
136 }
137 
138 static CACHE_MANAGER_CALLBACKS Callbacks = {
139     AcquireForLazyWrite,
140     ReleaseFromLazyWrite,
141     AcquireForReadAhead,
142     ReleaseFromReadAhead,
143 };
144 
145 static
146 PVOID
147 MapAndLockUserBuffer(
148     _In_ _Out_ PIRP Irp,
149     _In_ ULONG BufferLength)
150 {
151     PMDL Mdl;
152 
153     if (Irp->MdlAddress == NULL)
154     {
155         Mdl = IoAllocateMdl(Irp->UserBuffer, BufferLength, FALSE, FALSE, Irp);
156         if (Mdl == NULL)
157         {
158             return NULL;
159         }
160 
161         _SEH2_TRY
162         {
163             MmProbeAndLockPages(Mdl, Irp->RequestorMode, IoWriteAccess);
164         }
165         _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
166         {
167             IoFreeMdl(Mdl);
168             Irp->MdlAddress = NULL;
169             _SEH2_YIELD(return NULL);
170         }
171         _SEH2_END;
172     }
173 
174     return MmGetSystemAddressForMdlSafe(Irp->MdlAddress, NormalPagePriority);
175 }
176 
177 static
178 void
179 reset_read(void)
180 {
181     ReadCalled = FALSE;
182     ReadOffset.QuadPart = MAXLONGLONG;
183     ReadLength = MAXULONG;
184 }
185 
186 #define ok_read_called(_Offset, _Length) do {                           \
187     ok(ReadCalled, "CcCopyWrite should have triggerred a read.\n");     \
188     ok_eq_longlong(ReadOffset.QuadPart, _Offset);                       \
189     ok_eq_ulong(ReadLength, _Length);                                   \
190 }while(0)
191 
192 #define ok_read_not_called() ok(!ReadCalled, "CcCopyWrite shouldn't have triggered a read.\n")
193 
194 static
195 VOID
196 Test_CcCopyWrite(PFILE_OBJECT FileObject)
197 {
198 
199     BOOLEAN Ret;
200     LARGE_INTEGER Offset;
201     CHAR Buffer[10];
202 
203     memset(Buffer, 0xAC, 10);
204 
205     /* Test bogus file object & file offset */
206     Ret = 'x';
207     KmtStartSeh()
208         Ret = CcCopyWrite(FileObject, NULL, 0, FALSE, NULL);
209     KmtEndSeh(STATUS_ACCESS_VIOLATION);
210     ok_eq_char(Ret, 'x');
211 
212     Ret = 'x';
213     Offset.QuadPart = 0;
214     KmtStartSeh()
215         Ret = CcCopyWrite(NULL, &Offset, 10, FALSE, Buffer);
216     KmtEndSeh(STATUS_ACCESS_VIOLATION);
217     ok_eq_char(Ret, 'x');
218 
219     /* What happens on invalid buffer */
220     Ret = 'x';
221     Offset.QuadPart = 0;
222     reset_read();
223     KmtStartSeh()
224         Ret = CcCopyWrite(FileObject, &Offset, 0, TRUE, NULL);
225     KmtEndSeh(STATUS_SUCCESS);
226     ok_bool_true(Ret, "CcCopyWrite(0, NULL) should succeed\n");
227     /* When there is nothing to write, there is no reason to read */
228     ok_read_not_called();
229 
230     Ret = 'x';
231     Offset.QuadPart = 0;
232     reset_read();
233     KmtStartSeh()
234         Ret = CcCopyWrite(FileObject, &Offset, 10, TRUE, NULL);
235     KmtEndSeh(STATUS_INVALID_USER_BUFFER);
236     ok_eq_char(Ret, 'x');
237     /* This raises an exception, but it actually triggered a read */
238     ok_read_called(0, PAGE_SIZE);
239 
240     /* So this one succeeds, as the page is now resident */
241     Ret = 'x';
242     Offset.QuadPart = 0;
243     reset_read();
244     KmtStartSeh()
245         Ret = CcCopyWrite(FileObject, &Offset, 10, FALSE, Buffer);
246     KmtEndSeh(STATUS_SUCCESS);
247     ok_bool_true(Ret, "CcCopyWrite should succeed\n");
248     /* But there was no read triggered, as the page is already resident. */
249     ok_read_not_called();
250 
251     /* But this one doesn't */
252     Ret = 'x';
253     Offset.QuadPart = PAGE_SIZE;
254     reset_read();
255     KmtStartSeh()
256         Ret = CcCopyWrite(FileObject, &Offset, 10, FALSE, Buffer);
257     KmtEndSeh(STATUS_SUCCESS);
258     ok_bool_false(Ret, "CcCopyWrite should fail\n");
259     /* But it triggered a read anyway. */
260     ok_read_called(PAGE_SIZE, PAGE_SIZE);
261 
262     /* Of course, waiting for it succeeds and triggers the read */
263     Ret = 'x';
264     Offset.QuadPart = PAGE_SIZE * 2;
265     reset_read();
266     KmtStartSeh()
267         Ret = CcCopyWrite(FileObject, &Offset, 10, TRUE, Buffer);
268     KmtEndSeh(STATUS_SUCCESS);
269     ok_bool_true(Ret, "CcCopyWrite should succeed\n");
270     ok_read_called(PAGE_SIZE * 2, PAGE_SIZE);
271 }
272 
273 static
274 NTSTATUS
275 TestIrpHandler(
276     _In_ PDEVICE_OBJECT DeviceObject,
277     _In_ PIRP Irp,
278     _In_ PIO_STACK_LOCATION IoStack)
279 {
280     LARGE_INTEGER Zero = RTL_CONSTANT_LARGE_INTEGER(0LL);
281     NTSTATUS Status;
282     PTEST_FCB Fcb;
283     CACHE_UNINITIALIZE_EVENT CacheUninitEvent;
284 
285     PAGED_CODE();
286 
287     DPRINT("IRP %x/%x\n", IoStack->MajorFunction, IoStack->MinorFunction);
288     ASSERT(IoStack->MajorFunction == IRP_MJ_CLEANUP ||
289            IoStack->MajorFunction == IRP_MJ_CREATE ||
290            IoStack->MajorFunction == IRP_MJ_READ ||
291            IoStack->MajorFunction == IRP_MJ_WRITE ||
292            IoStack->MajorFunction == IRP_MJ_FLUSH_BUFFERS);
293 
294     Status = STATUS_NOT_SUPPORTED;
295     Irp->IoStatus.Information = 0;
296 
297     if (IoStack->MajorFunction == IRP_MJ_CREATE)
298     {
299         ok_irql(PASSIVE_LEVEL);
300 
301         if (IoStack->FileObject->FileName.Length >= 2 * sizeof(WCHAR))
302         {
303             TestDeviceObject = DeviceObject;
304             TestFileObject = IoStack->FileObject;
305         }
306         Fcb = ExAllocatePoolWithTag(NonPagedPool, sizeof(*Fcb), 'FwrI');
307         RtlZeroMemory(Fcb, sizeof(*Fcb));
308         ExInitializeFastMutex(&Fcb->HeaderMutex);
309         FsRtlSetupAdvancedHeader(&Fcb->Header, &Fcb->HeaderMutex);
310         if (IoStack->FileObject->FileName.Length >= 2 * sizeof(WCHAR) &&
311             IoStack->FileObject->FileName.Buffer[1] == 'B')
312         {
313             Fcb->Header.AllocationSize.QuadPart = 1000000;
314             Fcb->Header.FileSize.QuadPart = 1000000;
315             Fcb->Header.ValidDataLength.QuadPart = 1000000;
316         }
317         else if (IoStack->FileObject->FileName.Length >= 2 * sizeof(WCHAR) &&
318                  IoStack->FileObject->FileName.Buffer[1] == 'S')
319         {
320             Fcb->Header.AllocationSize.QuadPart = 512;
321             Fcb->Header.FileSize.QuadPart = 512;
322             Fcb->Header.ValidDataLength.QuadPart = 512;
323         }
324         else if (IoStack->FileObject->FileName.Length >= 2 * sizeof(WCHAR) &&
325                  IoStack->FileObject->FileName.Buffer[1] == 'V')
326         {
327             Fcb->Header.AllocationSize.QuadPart = 62;
328             Fcb->Header.FileSize.QuadPart = 62;
329             Fcb->Header.ValidDataLength.QuadPart = 62;
330         }
331         else
332         {
333             Fcb->Header.AllocationSize.QuadPart = 1004;
334             Fcb->Header.FileSize.QuadPart = 1004;
335             Fcb->Header.ValidDataLength.QuadPart = 1004;
336         }
337         Fcb->Header.IsFastIoPossible = FastIoIsNotPossible;
338         IoStack->FileObject->FsContext = Fcb;
339         IoStack->FileObject->SectionObjectPointer = &Fcb->SectionObjectPointers;
340 
341         CcInitializeCacheMap(IoStack->FileObject,
342                              (PCC_FILE_SIZES)&Fcb->Header.AllocationSize,
343                              FALSE, &Callbacks, NULL);
344 
345         Irp->IoStatus.Information = FILE_OPENED;
346         Status = STATUS_SUCCESS;
347     }
348     else if (IoStack->MajorFunction == IRP_MJ_READ)
349     {
350         PMDL Mdl;
351         ULONG Length;
352         PVOID Buffer;
353         LARGE_INTEGER Offset;
354 
355         Offset = IoStack->Parameters.Read.ByteOffset;
356         Length = IoStack->Parameters.Read.Length;
357         Fcb = IoStack->FileObject->FsContext;
358 
359         ok_eq_pointer(DeviceObject, TestDeviceObject);
360         ok_eq_pointer(IoStack->FileObject, TestFileObject);
361 
362         ok(BooleanFlagOn(Irp->Flags, IRP_NOCACHE), "IRP not coming from Cc!\n");
363         ok_irql(APC_LEVEL);
364         ok((Offset.QuadPart == 0 || Offset.QuadPart == 4096 || Offset.QuadPart == 8192), "Unexpected offset: %I64i\n", Offset.QuadPart);
365         ok(Length % PAGE_SIZE == 0, "Length is not aligned: %I64i\n", Length);
366 
367         ReadCalled = TRUE;
368         ReadOffset = Offset;
369         ReadLength = Length;
370 
371         ok(Irp->AssociatedIrp.SystemBuffer == NULL, "A SystemBuffer was allocated!\n");
372         Buffer = MapAndLockUserBuffer(Irp, Length);
373         ok(Buffer != NULL, "Null pointer!\n");
374         RtlFillMemory(Buffer, Length, 0xBA);
375 
376         Status = STATUS_SUCCESS;
377 
378         Mdl = Irp->MdlAddress;
379         ok(Mdl != NULL, "Null pointer for MDL!\n");
380         ok((Mdl->MdlFlags & MDL_PAGES_LOCKED) != 0, "MDL not locked\n");
381         ok((Mdl->MdlFlags & MDL_SOURCE_IS_NONPAGED_POOL) == 0, "MDL from non paged\n");
382         ok((Mdl->MdlFlags & MDL_IO_PAGE_READ) != 0, "Non paging IO\n");
383         ok((Irp->Flags & IRP_PAGING_IO) != 0, "Non paging IO\n");
384 
385         Irp->IoStatus.Information = Length;
386         IoStack->FileObject->CurrentByteOffset.QuadPart = Offset.QuadPart + Length;
387     }
388     else if (IoStack->MajorFunction == IRP_MJ_WRITE)
389     {
390         PMDL Mdl;
391         ULONG Length;
392         PVOID Buffer;
393         LARGE_INTEGER Offset;
394         static const UNICODE_STRING BehaviourTestFileName = RTL_CONSTANT_STRING(L"\\BehaviourTestFile");
395 
396         Offset = IoStack->Parameters.Read.ByteOffset;
397         Length = IoStack->Parameters.Read.Length;
398         Fcb = IoStack->FileObject->FsContext;
399 
400         ok_bool_false(InBehaviourTest, "Shouldn't trigger write from CcCopyWrite\n");
401         /* Check special file name */
402         InBehaviourTest = RtlCompareUnicodeString(&IoStack->FileObject->FileName, &BehaviourTestFileName, TRUE) == 0;
403 
404         if (!FlagOn(Irp->Flags, IRP_NOCACHE))
405         {
406             BOOLEAN Ret;
407 
408             ok_irql(PASSIVE_LEVEL);
409 
410             if (InBehaviourTest)
411             {
412                 Test_CcCopyWrite(IoStack->FileObject);
413                 Status = Irp->IoStatus.Status = STATUS_SUCCESS;
414             }
415             else
416             {
417                 Buffer = Irp->AssociatedIrp.SystemBuffer;
418                 ok(Buffer != NULL, "Null pointer!\n");
419 
420                 Fcb->WriteLength = Length;
421 
422                 _SEH2_TRY
423                 {
424                     Ret = CcCopyWrite(IoStack->FileObject, &Offset, Length, TRUE, Buffer);
425                     ok_bool_true(Ret, "CcCopyWrite");
426                 }
427                 _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
428                 {
429                     Irp->IoStatus.Status = _SEH2_GetExceptionCode();
430                 }
431                 _SEH2_END;
432 
433                 Status = Irp->IoStatus.Status;
434             }
435         }
436         else
437         {
438             ok_irql(PASSIVE_LEVEL);
439 
440             if (InBehaviourTest)
441             {
442                 /* We wrote only to the 1st and 3rd page */
443                 ok((Offset.QuadPart == 0) || (Offset.QuadPart == PAGE_SIZE * 2), "Expected a non-cached write on 1st or 3rd page\n");
444                 /* We wrote that much with CcCopyWrite */
445                 ok_eq_ulong(Length, PAGE_SIZE);
446             }
447             else
448             {
449                 ok((Offset.QuadPart == 0 || Offset.QuadPart == 4096), "Unexpected offset: %I64i\n", Offset.QuadPart);
450                 ok_eq_ulong(Length, ROUND_TO_PAGES(Fcb->WriteLength));
451             }
452 
453             ok(Irp->AssociatedIrp.SystemBuffer == NULL, "A SystemBuffer was allocated!\n");
454             Buffer = MapAndLockUserBuffer(Irp, Length);
455             ok(Buffer != NULL, "Null pointer!\n");
456 
457             Status = STATUS_SUCCESS;
458 
459             Mdl = Irp->MdlAddress;
460             ok(Mdl != NULL, "Null pointer for MDL!\n");
461             ok((Mdl->MdlFlags & MDL_PAGES_LOCKED) != 0, "MDL not locked\n");
462             ok((Mdl->MdlFlags & MDL_SOURCE_IS_NONPAGED_POOL) == 0, "MDL from non paged\n");
463             ok((Mdl->MdlFlags & MDL_IO_PAGE_READ) == 0, "MDL for read paging IO\n");
464             ok((Irp->Flags & IRP_PAGING_IO) != 0, "Non paging IO\n");
465         }
466 
467         InBehaviourTest = FALSE;
468     }
469     else if (IoStack->MajorFunction == IRP_MJ_FLUSH_BUFFERS)
470     {
471         IO_STATUS_BLOCK IoStatus;
472 
473         Fcb = IoStack->FileObject->FsContext;
474         CcFlushCache(&Fcb->SectionObjectPointers, NULL, 0, &IoStatus);
475 
476         Status = STATUS_SUCCESS;
477     }
478     else if (IoStack->MajorFunction == IRP_MJ_CLEANUP)
479     {
480         ok_irql(PASSIVE_LEVEL);
481         KeInitializeEvent(&CacheUninitEvent.Event, NotificationEvent, FALSE);
482         CcUninitializeCacheMap(IoStack->FileObject, &Zero, &CacheUninitEvent);
483         KeWaitForSingleObject(&CacheUninitEvent.Event, Executive, KernelMode, FALSE, NULL);
484         Fcb = IoStack->FileObject->FsContext;
485         ExFreePoolWithTag(Fcb, 'FwrI');
486         IoStack->FileObject->FsContext = NULL;
487         Status = STATUS_SUCCESS;
488     }
489 
490     if (Status == STATUS_PENDING)
491     {
492         IoMarkIrpPending(Irp);
493         IoCompleteRequest(Irp, IO_NO_INCREMENT);
494         Status = STATUS_PENDING;
495     }
496     else
497     {
498         Irp->IoStatus.Status = Status;
499         IoCompleteRequest(Irp, IO_NO_INCREMENT);
500     }
501 
502     return Status;
503 }
504