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