Source code of Windows XP (NT5)
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

2256 lines
69 KiB

  1. /*++
  2. Copyright (c) 1990 Microsoft Corporation
  3. Module Name:
  4. copysup.c
  5. Abstract:
  6. This module implements the copy support routines for the Cache subsystem.
  7. Author:
  8. Tom Miller [TomM] 4-May-1990
  9. Revision History:
  10. --*/
  11. #include "cc.h"
  12. //
  13. // Define our debug constant
  14. //
  15. #define me 0x00000004
  16. #ifdef ALLOC_PRAGMA
  17. #pragma alloc_text(PAGE,CcCopyRead)
  18. #pragma alloc_text(PAGE,CcFastCopyRead)
  19. #endif
  20. BOOLEAN
  21. CcCopyRead (
  22. IN PFILE_OBJECT FileObject,
  23. IN PLARGE_INTEGER FileOffset,
  24. IN ULONG Length,
  25. IN BOOLEAN Wait,
  26. OUT PVOID Buffer,
  27. OUT PIO_STATUS_BLOCK IoStatus
  28. )
  29. /*++
  30. Routine Description:
  31. This routine attempts to copy the specified file data from the cache
  32. into the output buffer, and deliver the correct I/O status. It is *not*
  33. safe to call this routine from Dpc level.
  34. If the caller does not want to block (such as for disk I/O), then
  35. Wait should be supplied as FALSE. If Wait was supplied as FALSE and
  36. it is currently impossible to supply all of the requested data without
  37. blocking, then this routine will return FALSE. However, if the
  38. data is immediately accessible in the cache and no blocking is
  39. required, this routine copies the data and returns TRUE.
  40. If the caller supplies Wait as TRUE, then this routine is guaranteed
  41. to copy the data and return TRUE. If the data is immediately
  42. accessible in the cache, then no blocking will occur. Otherwise,
  43. the the data transfer from the file into the cache will be initiated,
  44. and the caller will be blocked until the data can be returned.
  45. File system Fsd's should typically supply Wait = TRUE if they are
  46. processing a synchronous I/O requests, or Wait = FALSE if they are
  47. processing an asynchronous request.
  48. File system or Server Fsp threads should supply Wait = TRUE.
  49. Arguments:
  50. FileObject - Pointer to the file object for a file which was
  51. opened with NO_INTERMEDIATE_BUFFERING clear, i.e., for
  52. which CcInitializeCacheMap was called by the file system.
  53. FileOffset - Byte offset in file for desired data.
  54. Length - Length of desired data in bytes.
  55. Wait - FALSE if caller may not block, TRUE otherwise (see description
  56. above)
  57. Buffer - Pointer to output buffer to which data should be copied.
  58. IoStatus - Pointer to standard I/O status block to receive the status
  59. for the transfer. (STATUS_SUCCESS guaranteed for cache
  60. hits, otherwise the actual I/O status is returned.)
  61. Note that even if FALSE is returned, the IoStatus.Information
  62. field will return the count of any bytes successfully
  63. transferred before a blocking condition occured. The caller
  64. may either choose to ignore this information, or resume
  65. the copy later accounting for bytes transferred.
  66. Return Value:
  67. FALSE - if Wait was supplied as FALSE and the data was not delivered
  68. TRUE - if the data is being delivered
  69. --*/
  70. {
  71. PSHARED_CACHE_MAP SharedCacheMap;
  72. PPRIVATE_CACHE_MAP PrivateCacheMap;
  73. PVOID CacheBuffer;
  74. LARGE_INTEGER FOffset;
  75. PVACB Vacb;
  76. PBCB Bcb;
  77. PVACB ActiveVacb;
  78. ULONG ActivePage;
  79. ULONG PageIsDirty;
  80. ULONG SavedState;
  81. ULONG PagesToGo;
  82. ULONG MoveLength;
  83. ULONG LengthToGo;
  84. NTSTATUS Status;
  85. ULONG OriginalLength = Length;
  86. PETHREAD Thread = PsGetCurrentThread();
  87. ULONG GotAMiss = 0;
  88. DebugTrace(+1, me, "CcCopyRead\n", 0 );
  89. MmSavePageFaultReadAhead( Thread, &SavedState );
  90. //
  91. // Get pointer to shared and private cache maps
  92. //
  93. SharedCacheMap = FileObject->SectionObjectPointer->SharedCacheMap;
  94. PrivateCacheMap = FileObject->PrivateCacheMap;
  95. //
  96. // Check for read past file size, the caller must filter this case out.
  97. //
  98. ASSERT( ( FileOffset->QuadPart + (LONGLONG)Length) <= SharedCacheMap->FileSize.QuadPart );
  99. //
  100. // If read ahead is enabled, then do the read ahead here so it
  101. // overlaps with the copy (otherwise we will do it below).
  102. // Note that we are assuming that we will not get ahead of our
  103. // current transfer - if read ahead is working it should either
  104. // already be in memory or else underway.
  105. //
  106. if (PrivateCacheMap->Flags.ReadAheadEnabled && (PrivateCacheMap->ReadAheadLength[1] == 0)) {
  107. CcScheduleReadAhead( FileObject, FileOffset, Length );
  108. }
  109. FOffset = *FileOffset;
  110. //
  111. // Increment performance counters
  112. //
  113. if (Wait) {
  114. HOT_STATISTIC(CcCopyReadWait) += 1;
  115. //
  116. // This is not an exact solution, but when IoPageRead gets a miss,
  117. // it cannot tell whether it was CcCopyRead or CcMdlRead, but since
  118. // the miss should occur very soon, by loading the pointer here
  119. // probably the right counter will get incremented, and in any case,
  120. // we hope the errrors average out!
  121. //
  122. CcMissCounter = &CcCopyReadWaitMiss;
  123. } else {
  124. HOT_STATISTIC(CcCopyReadNoWait) += 1;
  125. }
  126. //
  127. // See if we have an active Vacb, that we can just copy to.
  128. //
  129. GetActiveVacb( SharedCacheMap, OldIrql, ActiveVacb, ActivePage, PageIsDirty );
  130. if (ActiveVacb != NULL) {
  131. if ((ULONG)(FOffset.QuadPart >> VACB_OFFSET_SHIFT) == (ActivePage >> (VACB_OFFSET_SHIFT - PAGE_SHIFT))) {
  132. ULONG LengthToCopy = VACB_MAPPING_GRANULARITY - (FOffset.LowPart & (VACB_MAPPING_GRANULARITY - 1));
  133. if (SharedCacheMap->NeedToZero != NULL) {
  134. CcFreeActiveVacb( SharedCacheMap, NULL, 0, FALSE );
  135. }
  136. //
  137. // Get the starting point in the view.
  138. //
  139. CacheBuffer = (PVOID)((PCHAR)ActiveVacb->BaseAddress +
  140. (FOffset.LowPart & (VACB_MAPPING_GRANULARITY - 1)));
  141. //
  142. // Reduce LengthToCopy if it is greater than our caller's length.
  143. //
  144. if (LengthToCopy > Length) {
  145. LengthToCopy = Length;
  146. }
  147. //
  148. // Like the logic for the normal case below, we want to spin around
  149. // making sure Mm only reads the pages we will need.
  150. //
  151. PagesToGo = ADDRESS_AND_SIZE_TO_SPAN_PAGES( CacheBuffer,
  152. LengthToCopy ) - 1;
  153. //
  154. // Copy the data to the user buffer.
  155. //
  156. try {
  157. if (PagesToGo != 0) {
  158. LengthToGo = LengthToCopy;
  159. while (LengthToGo != 0) {
  160. MoveLength = (ULONG)((PCHAR)(ROUND_TO_PAGES(((PCHAR)CacheBuffer + 1))) -
  161. (PCHAR)CacheBuffer);
  162. if (MoveLength > LengthToGo) {
  163. MoveLength = LengthToGo;
  164. }
  165. //
  166. // Here's hoping that it is cheaper to call Mm to see if
  167. // the page is valid. If not let Mm know how many pages
  168. // we are after before doing the move.
  169. //
  170. MmSetPageFaultReadAhead( Thread, PagesToGo );
  171. GotAMiss |= !MmCheckCachedPageState( CacheBuffer, FALSE );
  172. RtlCopyBytes( Buffer, CacheBuffer, MoveLength );
  173. PagesToGo -= 1;
  174. LengthToGo -= MoveLength;
  175. Buffer = (PCHAR)Buffer + MoveLength;
  176. CacheBuffer = (PCHAR)CacheBuffer + MoveLength;
  177. }
  178. //
  179. // Handle the read here that stays on a single page.
  180. //
  181. } else {
  182. //
  183. // Here's hoping that it is cheaper to call Mm to see if
  184. // the page is valid. If not let Mm know how many pages
  185. // we are after before doing the move.
  186. //
  187. MmSetPageFaultReadAhead( Thread, 0 );
  188. GotAMiss |= !MmCheckCachedPageState( CacheBuffer, FALSE );
  189. RtlCopyBytes( Buffer, CacheBuffer, LengthToCopy );
  190. Buffer = (PCHAR)Buffer + LengthToCopy;
  191. }
  192. } except( CcCopyReadExceptionFilter( GetExceptionInformation(),
  193. &Status ) ) {
  194. MmResetPageFaultReadAhead( Thread, SavedState );
  195. SetActiveVacb( SharedCacheMap, OldIrql, ActiveVacb, ActivePage, PageIsDirty );
  196. //
  197. // If we got an access violation, then the user buffer went
  198. // away. Otherwise we must have gotten an I/O error trying
  199. // to bring the data in.
  200. //
  201. if (Status == STATUS_ACCESS_VIOLATION) {
  202. ExRaiseStatus( STATUS_INVALID_USER_BUFFER );
  203. }
  204. else {
  205. ExRaiseStatus( FsRtlNormalizeNtstatus( Status,
  206. STATUS_UNEXPECTED_IO_ERROR ));
  207. }
  208. }
  209. //
  210. // Now adjust FOffset and Length by what we copied.
  211. //
  212. FOffset.QuadPart = FOffset.QuadPart + (LONGLONG)LengthToCopy;
  213. Length -= LengthToCopy;
  214. }
  215. //
  216. // If that was all the data, then remember the Vacb
  217. //
  218. if (Length == 0) {
  219. SetActiveVacb( SharedCacheMap, OldIrql, ActiveVacb, ActivePage, PageIsDirty );
  220. //
  221. // Otherwise we must free it because we will map other vacbs below.
  222. //
  223. } else {
  224. CcFreeActiveVacb( SharedCacheMap, ActiveVacb, ActivePage, PageIsDirty );
  225. }
  226. }
  227. //
  228. // Not all of the transfer will come back at once, so we have to loop
  229. // until the entire transfer is complete.
  230. //
  231. while (Length != 0) {
  232. ULONG ReceivedLength;
  233. LARGE_INTEGER BeyondLastByte;
  234. //
  235. // Call local routine to Map or Access the file data, then move the data,
  236. // then call another local routine to free the data. If we cannot map
  237. // the data because of a Wait condition, return FALSE.
  238. //
  239. // Note that this call may result in an exception, however, if it
  240. // does no Bcb is returned and this routine has absolutely no
  241. // cleanup to perform. Therefore, we do not have a try-finally
  242. // and we allow the possibility that we will simply be unwound
  243. // without notice.
  244. //
  245. if (Wait) {
  246. CacheBuffer = CcGetVirtualAddress( SharedCacheMap,
  247. FOffset,
  248. &Vacb,
  249. &ReceivedLength );
  250. BeyondLastByte.QuadPart = FOffset.QuadPart + (LONGLONG)ReceivedLength;
  251. } else if (!CcPinFileData( FileObject,
  252. &FOffset,
  253. Length,
  254. TRUE,
  255. FALSE,
  256. FALSE,
  257. &Bcb,
  258. &CacheBuffer,
  259. &BeyondLastByte )) {
  260. DebugTrace(-1, me, "CcCopyRead -> FALSE\n", 0 );
  261. HOT_STATISTIC(CcCopyReadNoWaitMiss) += 1;
  262. //
  263. // Enable ReadAhead if we missed.
  264. //
  265. CC_SET_PRIVATE_CACHE_MAP (PrivateCacheMap, PRIVATE_CACHE_MAP_READ_AHEAD_ENABLED);
  266. return FALSE;
  267. } else {
  268. //
  269. // Calculate how much data is described by Bcb starting at our desired
  270. // file offset.
  271. //
  272. ReceivedLength = (ULONG)(BeyondLastByte.QuadPart - FOffset.QuadPart);
  273. }
  274. //
  275. // If we got more than we need, make sure to only transfer
  276. // the right amount.
  277. //
  278. if (ReceivedLength > Length) {
  279. ReceivedLength = Length;
  280. }
  281. //
  282. // It is possible for the user buffer to become no longer accessible
  283. // since it was last checked by the I/O system. If we fail to access
  284. // the buffer we must raise a status that the caller's exception
  285. // filter considers as "expected". Also we unmap the Bcb here, since
  286. // we otherwise would have no other reason to put a try-finally around
  287. // this loop.
  288. //
  289. try {
  290. PagesToGo = ADDRESS_AND_SIZE_TO_SPAN_PAGES( CacheBuffer,
  291. ReceivedLength ) - 1;
  292. //
  293. // We know exactly how much we want to read here, and we do not
  294. // want to read any more in case the caller is doing random access.
  295. // Our read ahead logic takes care of detecting sequential reads,
  296. // and tends to do large asynchronous read aheads. So far we have
  297. // only mapped the data and we have not forced any in. What we
  298. // do now is get into a loop where we copy a page at a time and
  299. // just prior to each move, we tell MM how many additional pages
  300. // we would like to have read in, in the event that we take a
  301. // fault. With this strategy, for cache hits we never make a single
  302. // expensive call to MM to guarantee that the data is in, yet if we
  303. // do take a fault, we are guaranteed to only take one fault because
  304. // we will read all of the data in for the rest of the transfer.
  305. //
  306. // We test first for the multiple page case, to keep the small
  307. // reads faster.
  308. //
  309. if (PagesToGo != 0) {
  310. LengthToGo = ReceivedLength;
  311. while (LengthToGo != 0) {
  312. MoveLength = (ULONG)((PCHAR)(ROUND_TO_PAGES(((PCHAR)CacheBuffer + 1))) -
  313. (PCHAR)CacheBuffer);
  314. if (MoveLength > LengthToGo) {
  315. MoveLength = LengthToGo;
  316. }
  317. //
  318. // Here's hoping that it is cheaper to call Mm to see if
  319. // the page is valid. If not let Mm know how many pages
  320. // we are after before doing the move.
  321. //
  322. MmSetPageFaultReadAhead( Thread, PagesToGo );
  323. GotAMiss |= !MmCheckCachedPageState( CacheBuffer, FALSE );
  324. RtlCopyBytes( Buffer, CacheBuffer, MoveLength );
  325. PagesToGo -= 1;
  326. LengthToGo -= MoveLength;
  327. Buffer = (PCHAR)Buffer + MoveLength;
  328. CacheBuffer = (PCHAR)CacheBuffer + MoveLength;
  329. }
  330. //
  331. // Handle the read here that stays on a single page.
  332. //
  333. } else {
  334. //
  335. // Here's hoping that it is cheaper to call Mm to see if
  336. // the page is valid. If not let Mm know how many pages
  337. // we are after before doing the move.
  338. //
  339. MmSetPageFaultReadAhead( Thread, 0 );
  340. GotAMiss |= !MmCheckCachedPageState( CacheBuffer, FALSE );
  341. RtlCopyBytes( Buffer, CacheBuffer, ReceivedLength );
  342. Buffer = (PCHAR)Buffer + ReceivedLength;
  343. }
  344. }
  345. except( CcCopyReadExceptionFilter( GetExceptionInformation(),
  346. &Status ) ) {
  347. CcMissCounter = &CcThrowAway;
  348. //
  349. // If we get an exception, then we have to renable page fault
  350. // clustering and unmap on the way out.
  351. //
  352. MmResetPageFaultReadAhead( Thread, SavedState );
  353. if (Wait) {
  354. CcFreeVirtualAddress( Vacb );
  355. } else {
  356. CcUnpinFileData( Bcb, TRUE, UNPIN );
  357. }
  358. //
  359. // If we got an access violation, then the user buffer went
  360. // away. Otherwise we must have gotten an I/O error trying
  361. // to bring the data in.
  362. //
  363. if (Status == STATUS_ACCESS_VIOLATION) {
  364. ExRaiseStatus( STATUS_INVALID_USER_BUFFER );
  365. }
  366. else {
  367. ExRaiseStatus( FsRtlNormalizeNtstatus( Status,
  368. STATUS_UNEXPECTED_IO_ERROR ));
  369. }
  370. }
  371. //
  372. // Update number of bytes transferred.
  373. //
  374. Length -= ReceivedLength;
  375. //
  376. // Unmap the data now, and calculate length left to transfer.
  377. //
  378. if (Wait) {
  379. //
  380. // If there is more to go, just free this vacb.
  381. //
  382. if (Length != 0) {
  383. CcFreeVirtualAddress( Vacb );
  384. //
  385. // Otherwise save it for the next time through.
  386. //
  387. } else {
  388. SetActiveVacb( SharedCacheMap, OldIrql, Vacb, (ULONG)(FOffset.QuadPart >> PAGE_SHIFT), 0 );
  389. break;
  390. }
  391. } else {
  392. CcUnpinFileData( Bcb, TRUE, UNPIN );
  393. }
  394. //
  395. // Assume we did not get all the data we wanted, and set FOffset
  396. // to the end of the returned data.
  397. //
  398. FOffset = BeyondLastByte;
  399. }
  400. MmResetPageFaultReadAhead( Thread, SavedState );
  401. CcMissCounter = &CcThrowAway;
  402. //
  403. // Now enable read ahead if it looks like we got any misses, and do
  404. // the first one.
  405. //
  406. if (GotAMiss &&
  407. !FlagOn( FileObject->Flags, FO_RANDOM_ACCESS ) &&
  408. !PrivateCacheMap->Flags.ReadAheadEnabled) {
  409. CC_SET_PRIVATE_CACHE_MAP (PrivateCacheMap, PRIVATE_CACHE_MAP_READ_AHEAD_ENABLED);
  410. CcScheduleReadAhead( FileObject, FileOffset, OriginalLength );
  411. }
  412. //
  413. // Now that we have described our desired read ahead, let's
  414. // shift the read history down.
  415. //
  416. PrivateCacheMap->FileOffset1 = PrivateCacheMap->FileOffset2;
  417. PrivateCacheMap->BeyondLastByte1 = PrivateCacheMap->BeyondLastByte2;
  418. PrivateCacheMap->FileOffset2 = *FileOffset;
  419. PrivateCacheMap->BeyondLastByte2.QuadPart =
  420. FileOffset->QuadPart + (LONGLONG)OriginalLength;
  421. IoStatus->Status = STATUS_SUCCESS;
  422. IoStatus->Information = OriginalLength;
  423. DebugTrace(-1, me, "CcCopyRead -> TRUE\n", 0 );
  424. return TRUE;
  425. }
  426. VOID
  427. CcFastCopyRead (
  428. IN PFILE_OBJECT FileObject,
  429. IN ULONG FileOffset,
  430. IN ULONG Length,
  431. IN ULONG PageCount,
  432. OUT PVOID Buffer,
  433. OUT PIO_STATUS_BLOCK IoStatus
  434. )
  435. /*++
  436. Routine Description:
  437. This routine attempts to copy the specified file data from the cache
  438. into the output buffer, and deliver the correct I/O status.
  439. This is a faster version of CcCopyRead which only supports 32-bit file
  440. offsets and synchronicity (Wait = TRUE).
  441. Arguments:
  442. FileObject - Pointer to the file object for a file which was
  443. opened with NO_INTERMEDIATE_BUFFERING clear, i.e., for
  444. which CcInitializeCacheMap was called by the file system.
  445. FileOffset - Byte offset in file for desired data.
  446. Length - Length of desired data in bytes.
  447. PageCount - Number of pages spanned by the read.
  448. Buffer - Pointer to output buffer to which data should be copied.
  449. IoStatus - Pointer to standard I/O status block to receive the status
  450. for the transfer. (STATUS_SUCCESS guaranteed for cache
  451. hits, otherwise the actual I/O status is returned.)
  452. Note that even if FALSE is returned, the IoStatus.Information
  453. field will return the count of any bytes successfully
  454. transferred before a blocking condition occured. The caller
  455. may either choose to ignore this information, or resume
  456. the copy later accounting for bytes transferred.
  457. Return Value:
  458. None
  459. --*/
  460. {
  461. PSHARED_CACHE_MAP SharedCacheMap;
  462. PPRIVATE_CACHE_MAP PrivateCacheMap;
  463. PVOID CacheBuffer;
  464. LARGE_INTEGER FOffset;
  465. PVACB Vacb;
  466. PVACB ActiveVacb;
  467. ULONG ActivePage;
  468. ULONG PageIsDirty;
  469. ULONG SavedState;
  470. ULONG PagesToGo;
  471. ULONG MoveLength;
  472. ULONG LengthToGo;
  473. NTSTATUS Status;
  474. LARGE_INTEGER OriginalOffset;
  475. ULONG OriginalLength = Length;
  476. PETHREAD Thread = PsGetCurrentThread();
  477. ULONG GotAMiss = 0;
  478. UNREFERENCED_PARAMETER (PageCount);
  479. DebugTrace(+1, me, "CcFastCopyRead\n", 0 );
  480. MmSavePageFaultReadAhead( Thread, &SavedState );
  481. //
  482. // Get pointer to shared and private cache maps
  483. //
  484. SharedCacheMap = FileObject->SectionObjectPointer->SharedCacheMap;
  485. PrivateCacheMap = FileObject->PrivateCacheMap;
  486. //
  487. // Check for read past file size, the caller must filter this case out.
  488. //
  489. ASSERT( (FileOffset + Length) <= SharedCacheMap->FileSize.LowPart );
  490. //
  491. // If read ahead is enabled, then do the read ahead here so it
  492. // overlaps with the copy (otherwise we will do it below).
  493. // Note that we are assuming that we will not get ahead of our
  494. // current transfer - if read ahead is working it should either
  495. // already be in memory or else underway.
  496. //
  497. OriginalOffset.LowPart = FileOffset;
  498. OriginalOffset.HighPart = 0;
  499. if (PrivateCacheMap->Flags.ReadAheadEnabled && (PrivateCacheMap->ReadAheadLength[1] == 0)) {
  500. CcScheduleReadAhead( FileObject, &OriginalOffset, Length );
  501. }
  502. //
  503. // This is not an exact solution, but when IoPageRead gets a miss,
  504. // it cannot tell whether it was CcCopyRead or CcMdlRead, but since
  505. // the miss should occur very soon, by loading the pointer here
  506. // probably the right counter will get incremented, and in any case,
  507. // we hope the errrors average out!
  508. //
  509. CcMissCounter = &CcCopyReadWaitMiss;
  510. //
  511. // Increment performance counters
  512. //
  513. HOT_STATISTIC(CcCopyReadWait) += 1;
  514. //
  515. // See if we have an active Vacb, that we can just copy to.
  516. //
  517. GetActiveVacb( SharedCacheMap, OldIrql, ActiveVacb, ActivePage, PageIsDirty );
  518. if (ActiveVacb != NULL) {
  519. if ((FileOffset >> VACB_OFFSET_SHIFT) == (ActivePage >> (VACB_OFFSET_SHIFT - PAGE_SHIFT))) {
  520. ULONG LengthToCopy = VACB_MAPPING_GRANULARITY - (FileOffset & (VACB_MAPPING_GRANULARITY - 1));
  521. if (SharedCacheMap->NeedToZero != NULL) {
  522. CcFreeActiveVacb( SharedCacheMap, NULL, 0, FALSE );
  523. }
  524. //
  525. // Get the starting point in the view.
  526. //
  527. CacheBuffer = (PVOID)((PCHAR)ActiveVacb->BaseAddress +
  528. (FileOffset & (VACB_MAPPING_GRANULARITY - 1)));
  529. //
  530. // Reduce LengthToCopy if it is greater than our caller's length.
  531. //
  532. if (LengthToCopy > Length) {
  533. LengthToCopy = Length;
  534. }
  535. //
  536. // Like the logic for the normal case below, we want to spin around
  537. // making sure Mm only reads the pages we will need.
  538. //
  539. PagesToGo = ADDRESS_AND_SIZE_TO_SPAN_PAGES( CacheBuffer,
  540. LengthToCopy ) - 1;
  541. //
  542. // Copy the data to the user buffer.
  543. //
  544. try {
  545. if (PagesToGo != 0) {
  546. LengthToGo = LengthToCopy;
  547. while (LengthToGo != 0) {
  548. MoveLength = (ULONG)((PCHAR)(ROUND_TO_PAGES(((PCHAR)CacheBuffer + 1))) -
  549. (PCHAR)CacheBuffer);
  550. if (MoveLength > LengthToGo) {
  551. MoveLength = LengthToGo;
  552. }
  553. //
  554. // Here's hoping that it is cheaper to call Mm to see if
  555. // the page is valid. If not let Mm know how many pages
  556. // we are after before doing the move.
  557. //
  558. MmSetPageFaultReadAhead( Thread, PagesToGo );
  559. GotAMiss |= !MmCheckCachedPageState( CacheBuffer, FALSE );
  560. RtlCopyBytes( Buffer, CacheBuffer, MoveLength );
  561. PagesToGo -= 1;
  562. LengthToGo -= MoveLength;
  563. Buffer = (PCHAR)Buffer + MoveLength;
  564. CacheBuffer = (PCHAR)CacheBuffer + MoveLength;
  565. }
  566. //
  567. // Handle the read here that stays on a single page.
  568. //
  569. } else {
  570. //
  571. // Here's hoping that it is cheaper to call Mm to see if
  572. // the page is valid. If not let Mm know how many pages
  573. // we are after before doing the move.
  574. //
  575. MmSetPageFaultReadAhead( Thread, 0 );
  576. GotAMiss |= !MmCheckCachedPageState( CacheBuffer, FALSE );
  577. RtlCopyBytes( Buffer, CacheBuffer, LengthToCopy );
  578. Buffer = (PCHAR)Buffer + LengthToCopy;
  579. }
  580. } except( CcCopyReadExceptionFilter( GetExceptionInformation(),
  581. &Status ) ) {
  582. MmResetPageFaultReadAhead( Thread, SavedState );
  583. SetActiveVacb( SharedCacheMap, OldIrql, ActiveVacb, ActivePage, PageIsDirty );
  584. //
  585. // If we got an access violation, then the user buffer went
  586. // away. Otherwise we must have gotten an I/O error trying
  587. // to bring the data in.
  588. //
  589. if (Status == STATUS_ACCESS_VIOLATION) {
  590. ExRaiseStatus( STATUS_INVALID_USER_BUFFER );
  591. }
  592. else {
  593. ExRaiseStatus( FsRtlNormalizeNtstatus( Status,
  594. STATUS_UNEXPECTED_IO_ERROR ));
  595. }
  596. }
  597. //
  598. // Now adjust FileOffset and Length by what we copied.
  599. //
  600. FileOffset += LengthToCopy;
  601. Length -= LengthToCopy;
  602. }
  603. //
  604. // If that was all the data, then remember the Vacb
  605. //
  606. if (Length == 0) {
  607. SetActiveVacb( SharedCacheMap, OldIrql, ActiveVacb, ActivePage, PageIsDirty );
  608. //
  609. // Otherwise we must free it because we will map other vacbs below.
  610. //
  611. } else {
  612. CcFreeActiveVacb( SharedCacheMap, ActiveVacb, ActivePage, PageIsDirty );
  613. }
  614. }
  615. //
  616. // Not all of the transfer will come back at once, so we have to loop
  617. // until the entire transfer is complete.
  618. //
  619. FOffset.HighPart = 0;
  620. FOffset.LowPart = FileOffset;
  621. while (Length != 0) {
  622. ULONG ReceivedLength;
  623. ULONG BeyondLastByte;
  624. //
  625. // Call local routine to Map or Access the file data, then move the data,
  626. // then call another local routine to free the data. If we cannot map
  627. // the data because of a Wait condition, return FALSE.
  628. //
  629. // Note that this call may result in an exception, however, if it
  630. // does no Bcb is returned and this routine has absolutely no
  631. // cleanup to perform. Therefore, we do not have a try-finally
  632. // and we allow the possibility that we will simply be unwound
  633. // without notice.
  634. //
  635. CacheBuffer = CcGetVirtualAddress( SharedCacheMap,
  636. FOffset,
  637. &Vacb,
  638. &ReceivedLength );
  639. BeyondLastByte = FOffset.LowPart + ReceivedLength;
  640. //
  641. // If we got more than we need, make sure to only transfer
  642. // the right amount.
  643. //
  644. if (ReceivedLength > Length) {
  645. ReceivedLength = Length;
  646. }
  647. //
  648. // It is possible for the user buffer to become no longer accessible
  649. // since it was last checked by the I/O system. If we fail to access
  650. // the buffer we must raise a status that the caller's exception
  651. // filter considers as "expected". Also we unmap the Bcb here, since
  652. // we otherwise would have no other reason to put a try-finally around
  653. // this loop.
  654. //
  655. try {
  656. PagesToGo = ADDRESS_AND_SIZE_TO_SPAN_PAGES( CacheBuffer,
  657. ReceivedLength ) - 1;
  658. //
  659. // We know exactly how much we want to read here, and we do not
  660. // want to read any more in case the caller is doing random access.
  661. // Our read ahead logic takes care of detecting sequential reads,
  662. // and tends to do large asynchronous read aheads. So far we have
  663. // only mapped the data and we have not forced any in. What we
  664. // do now is get into a loop where we copy a page at a time and
  665. // just prior to each move, we tell MM how many additional pages
  666. // we would like to have read in, in the event that we take a
  667. // fault. With this strategy, for cache hits we never make a single
  668. // expensive call to MM to guarantee that the data is in, yet if we
  669. // do take a fault, we are guaranteed to only take one fault because
  670. // we will read all of the data in for the rest of the transfer.
  671. //
  672. // We test first for the multiple page case, to keep the small
  673. // reads faster.
  674. //
  675. if (PagesToGo != 0) {
  676. LengthToGo = ReceivedLength;
  677. while (LengthToGo != 0) {
  678. MoveLength = (ULONG)((PCHAR)(ROUND_TO_PAGES(((PCHAR)CacheBuffer + 1))) -
  679. (PCHAR)CacheBuffer);
  680. if (MoveLength > LengthToGo) {
  681. MoveLength = LengthToGo;
  682. }
  683. //
  684. // Here's hoping that it is cheaper to call Mm to see if
  685. // the page is valid. If not let Mm know how many pages
  686. // we are after before doing the move.
  687. //
  688. MmSetPageFaultReadAhead( Thread, PagesToGo );
  689. GotAMiss |= !MmCheckCachedPageState( CacheBuffer, FALSE );
  690. RtlCopyBytes( Buffer, CacheBuffer, MoveLength );
  691. PagesToGo -= 1;
  692. LengthToGo -= MoveLength;
  693. Buffer = (PCHAR)Buffer + MoveLength;
  694. CacheBuffer = (PCHAR)CacheBuffer + MoveLength;
  695. }
  696. //
  697. // Handle the read here that stays on a single page.
  698. //
  699. } else {
  700. //
  701. // Here's hoping that it is cheaper to call Mm to see if
  702. // the page is valid. If not let Mm know how many pages
  703. // we are after before doing the move.
  704. //
  705. MmSetPageFaultReadAhead( Thread, 0 );
  706. GotAMiss |= !MmCheckCachedPageState( CacheBuffer, FALSE );
  707. RtlCopyBytes( Buffer, CacheBuffer, ReceivedLength );
  708. Buffer = (PCHAR)Buffer + ReceivedLength;
  709. }
  710. }
  711. except( CcCopyReadExceptionFilter( GetExceptionInformation(),
  712. &Status ) ) {
  713. CcMissCounter = &CcThrowAway;
  714. //
  715. // If we get an exception, then we have to renable page fault
  716. // clustering and unmap on the way out.
  717. //
  718. MmResetPageFaultReadAhead( Thread, SavedState );
  719. CcFreeVirtualAddress( Vacb );
  720. //
  721. // If we got an access violation, then the user buffer went
  722. // away. Otherwise we must have gotten an I/O error trying
  723. // to bring the data in.
  724. //
  725. if (Status == STATUS_ACCESS_VIOLATION) {
  726. ExRaiseStatus( STATUS_INVALID_USER_BUFFER );
  727. }
  728. else {
  729. ExRaiseStatus( FsRtlNormalizeNtstatus( Status,
  730. STATUS_UNEXPECTED_IO_ERROR ));
  731. }
  732. }
  733. //
  734. // Update number of bytes transferred.
  735. //
  736. Length -= ReceivedLength;
  737. //
  738. // Unmap the data now, and calculate length left to transfer.
  739. //
  740. if (Length != 0) {
  741. //
  742. // If there is more to go, just free this vacb.
  743. //
  744. CcFreeVirtualAddress( Vacb );
  745. } else {
  746. //
  747. // Otherwise save it for the next time through.
  748. //
  749. SetActiveVacb( SharedCacheMap, OldIrql, Vacb, (FOffset.LowPart >> PAGE_SHIFT), 0 );
  750. break;
  751. }
  752. //
  753. // Assume we did not get all the data we wanted, and set FOffset
  754. // to the end of the returned data.
  755. //
  756. FOffset.LowPart = BeyondLastByte;
  757. }
  758. MmResetPageFaultReadAhead( Thread, SavedState );
  759. CcMissCounter = &CcThrowAway;
  760. //
  761. // Now enable read ahead if it looks like we got any misses, and do
  762. // the first one.
  763. //
  764. if (GotAMiss &&
  765. !FlagOn( FileObject->Flags, FO_RANDOM_ACCESS ) &&
  766. !PrivateCacheMap->Flags.ReadAheadEnabled) {
  767. CC_SET_PRIVATE_CACHE_MAP (PrivateCacheMap, PRIVATE_CACHE_MAP_READ_AHEAD_ENABLED);
  768. CcScheduleReadAhead( FileObject, &OriginalOffset, OriginalLength );
  769. }
  770. //
  771. // Now that we have described our desired read ahead, let's
  772. // shift the read history down.
  773. //
  774. PrivateCacheMap->FileOffset1.LowPart = PrivateCacheMap->FileOffset2.LowPart;
  775. PrivateCacheMap->BeyondLastByte1.LowPart = PrivateCacheMap->BeyondLastByte2.LowPart;
  776. PrivateCacheMap->FileOffset2.LowPart = OriginalOffset.LowPart;
  777. PrivateCacheMap->BeyondLastByte2.LowPart = OriginalOffset.LowPart + OriginalLength;
  778. IoStatus->Status = STATUS_SUCCESS;
  779. IoStatus->Information = OriginalLength;
  780. DebugTrace(-1, me, "CcFastCopyRead -> VOID\n", 0 );
  781. }
  782. BOOLEAN
  783. CcCopyWrite (
  784. IN PFILE_OBJECT FileObject,
  785. IN PLARGE_INTEGER FileOffset,
  786. IN ULONG Length,
  787. IN BOOLEAN Wait,
  788. IN PVOID Buffer
  789. )
  790. /*++
  791. Routine Description:
  792. This routine attempts to copy the specified file data from the specified
  793. buffer into the Cache, and deliver the correct I/O status. It is *not*
  794. safe to call this routine from Dpc level.
  795. If the caller does not want to block (such as for disk I/O), then
  796. Wait should be supplied as FALSE. If Wait was supplied as FALSE and
  797. it is currently impossible to receive all of the requested data without
  798. blocking, then this routine will return FALSE. However, if the
  799. correct space is immediately accessible in the cache and no blocking is
  800. required, this routine copies the data and returns TRUE.
  801. If the caller supplies Wait as TRUE, then this routine is guaranteed
  802. to copy the data and return TRUE. If the correct space is immediately
  803. accessible in the cache, then no blocking will occur. Otherwise,
  804. the necessary work will be initiated to read and/or free cache data,
  805. and the caller will be blocked until the data can be received.
  806. File system Fsd's should typically supply Wait = TRUE if they are
  807. processing a synchronous I/O requests, or Wait = FALSE if they are
  808. processing an asynchronous request.
  809. File system or Server Fsp threads should supply Wait = TRUE.
  810. Arguments:
  811. FileObject - Pointer to the file object for a file which was
  812. opened with NO_INTERMEDIATE_BUFFERING clear, i.e., for
  813. which CcInitializeCacheMap was called by the file system.
  814. FileOffset - Byte offset in file to receive the data.
  815. Length - Length of data in bytes.
  816. Wait - FALSE if caller may not block, TRUE otherwise (see description
  817. above)
  818. Buffer - Pointer to input buffer from which data should be copied.
  819. Return Value:
  820. FALSE - if Wait was supplied as FALSE and the data was not copied.
  821. TRUE - if the data has been copied.
  822. Raises:
  823. STATUS_INSUFFICIENT_RESOURCES - If a pool allocation failure occurs.
  824. This can only occur if Wait was specified as TRUE. (If Wait is
  825. specified as FALSE, and an allocation failure occurs, this
  826. routine simply returns FALSE.)
  827. --*/
  828. {
  829. PSHARED_CACHE_MAP SharedCacheMap;
  830. PFSRTL_ADVANCED_FCB_HEADER FcbHeader;
  831. PVACB ActiveVacb;
  832. ULONG ActivePage;
  833. PVOID ActiveAddress;
  834. ULONG PageIsDirty;
  835. KIRQL OldIrql;
  836. NTSTATUS Status;
  837. PVOID CacheBuffer;
  838. LARGE_INTEGER FOffset;
  839. PBCB Bcb;
  840. ULONG ZeroFlags;
  841. LARGE_INTEGER Temp;
  842. DebugTrace(+1, me, "CcCopyWrite\n", 0 );
  843. //
  844. // If the caller specified Wait == FALSE, but the FileObject is WriteThrough,
  845. // then we need to just get out.
  846. //
  847. if ((FileObject->Flags & FO_WRITE_THROUGH) && !Wait) {
  848. DebugTrace(-1, me, "CcCopyWrite->FALSE (WriteThrough && !Wait)\n", 0 );
  849. return FALSE;
  850. }
  851. //
  852. // Get pointer to shared cache map
  853. //
  854. SharedCacheMap = FileObject->SectionObjectPointer->SharedCacheMap;
  855. FOffset = *FileOffset;
  856. //
  857. // See if we have an active Vacb, that we can just copy to.
  858. //
  859. GetActiveVacb( SharedCacheMap, OldIrql, ActiveVacb, ActivePage, PageIsDirty );
  860. if (ActiveVacb != NULL) {
  861. //
  862. // See if the request starts in the ActivePage. WriteThrough requests must
  863. // go the longer route through CcMapAndCopy, where WriteThrough flushes are
  864. // implemented.
  865. //
  866. if (((ULONG)(FOffset.QuadPart >> PAGE_SHIFT) == ActivePage) && (Length != 0) &&
  867. !FlagOn( FileObject->Flags, FO_WRITE_THROUGH )) {
  868. ULONG LengthToCopy = PAGE_SIZE - (FOffset.LowPart & (PAGE_SIZE - 1));
  869. //
  870. // Reduce LengthToCopy if it is greater than our caller's length.
  871. //
  872. if (LengthToCopy > Length) {
  873. LengthToCopy = Length;
  874. }
  875. //
  876. // Copy the data to the user buffer.
  877. //
  878. try {
  879. //
  880. // If we are copying to a page that is locked down, then
  881. // we have to do it under our spinlock, and update the
  882. // NeedToZero field.
  883. //
  884. OldIrql = 0xFF;
  885. CacheBuffer = (PVOID)((PCHAR)ActiveVacb->BaseAddress +
  886. (FOffset.LowPart & (VACB_MAPPING_GRANULARITY - 1)));
  887. if (SharedCacheMap->NeedToZero != NULL) {
  888. //
  889. // The FastLock may not write our "flag".
  890. //
  891. OldIrql = 0;
  892. ExAcquireFastLock( &SharedCacheMap->ActiveVacbSpinLock, &OldIrql );
  893. //
  894. // Note that the NeedToZero could be cleared, since we
  895. // tested it without the spinlock.
  896. //
  897. ActiveAddress = SharedCacheMap->NeedToZero;
  898. if ((ActiveAddress != NULL) &&
  899. (ActiveVacb == SharedCacheMap->NeedToZeroVacb) &&
  900. (((PCHAR)CacheBuffer + LengthToCopy) > (PCHAR)ActiveAddress)) {
  901. //
  902. // If we are skipping some bytes in the page, then we need
  903. // to zero them.
  904. //
  905. if ((PCHAR)CacheBuffer > (PCHAR)ActiveAddress) {
  906. RtlZeroMemory( ActiveAddress, (PCHAR)CacheBuffer - (PCHAR)ActiveAddress );
  907. }
  908. SharedCacheMap->NeedToZero = (PVOID)((PCHAR)CacheBuffer + LengthToCopy);
  909. }
  910. ExReleaseFastLock( &SharedCacheMap->ActiveVacbSpinLock, OldIrql );
  911. }
  912. RtlCopyBytes( CacheBuffer, Buffer, LengthToCopy );
  913. } except( CcCopyReadExceptionFilter( GetExceptionInformation(),
  914. &Status ) ) {
  915. //
  916. // If we failed to overwrite the uninitialized data,
  917. // zero it now (we cannot safely restore NeedToZero).
  918. //
  919. if (OldIrql != 0xFF) {
  920. RtlZeroBytes( CacheBuffer, LengthToCopy );
  921. }
  922. SetActiveVacb( SharedCacheMap, OldIrql, ActiveVacb, ActivePage, ACTIVE_PAGE_IS_DIRTY );
  923. //
  924. // If we got an access violation, then the user buffer went
  925. // away. Otherwise we must have gotten an I/O error trying
  926. // to bring the data in.
  927. //
  928. if (Status == STATUS_ACCESS_VIOLATION) {
  929. ExRaiseStatus( STATUS_INVALID_USER_BUFFER );
  930. }
  931. else {
  932. ExRaiseStatus( FsRtlNormalizeNtstatus( Status,
  933. STATUS_UNEXPECTED_IO_ERROR ));
  934. }
  935. }
  936. //
  937. // Now adjust FOffset and Length by what we copied.
  938. //
  939. Buffer = (PVOID)((PCHAR)Buffer + LengthToCopy);
  940. FOffset.QuadPart = FOffset.QuadPart + (LONGLONG)LengthToCopy;
  941. Length -= LengthToCopy;
  942. //
  943. // If that was all the data, then get outski...
  944. //
  945. if (Length == 0) {
  946. SetActiveVacb( SharedCacheMap, OldIrql, ActiveVacb, ActivePage, ACTIVE_PAGE_IS_DIRTY );
  947. return TRUE;
  948. }
  949. //
  950. // Remember that the page is dirty now.
  951. //
  952. PageIsDirty |= ACTIVE_PAGE_IS_DIRTY;
  953. }
  954. CcFreeActiveVacb( SharedCacheMap, ActiveVacb, ActivePage, PageIsDirty );
  955. //
  956. // Else someone else could have the active page, and may want to zero
  957. // the range we plan to write!
  958. //
  959. } else if (SharedCacheMap->NeedToZero != NULL) {
  960. CcFreeActiveVacb( SharedCacheMap, NULL, 0, FALSE );
  961. }
  962. //
  963. // At this point we can calculate the ZeroFlags.
  964. //
  965. //
  966. // We can always zero middle pages, if any.
  967. //
  968. ZeroFlags = ZERO_MIDDLE_PAGES;
  969. if (((FOffset.LowPart & (PAGE_SIZE - 1)) == 0) &&
  970. (Length >= PAGE_SIZE)) {
  971. ZeroFlags |= ZERO_FIRST_PAGE;
  972. }
  973. if (((FOffset.LowPart + Length) & (PAGE_SIZE - 1)) == 0) {
  974. ZeroFlags |= ZERO_LAST_PAGE;
  975. }
  976. Temp = FOffset;
  977. Temp.LowPart &= ~(PAGE_SIZE -1);
  978. //
  979. // If there is an advanced header, then we can acquire the FastMutex to
  980. // make capturing ValidDataLength atomic. Currently our other file systems
  981. // are either RO or do not really support 64-bits.
  982. //
  983. FcbHeader = (PFSRTL_ADVANCED_FCB_HEADER)FileObject->FsContext;
  984. if (FlagOn(FcbHeader->Flags, FSRTL_FLAG_ADVANCED_HEADER)) {
  985. ExAcquireFastMutex( FcbHeader->FastMutex );
  986. Temp.QuadPart = ((PFSRTL_COMMON_FCB_HEADER)FileObject->FsContext)->ValidDataLength.QuadPart -
  987. Temp.QuadPart;
  988. ExReleaseFastMutex( FcbHeader->FastMutex );
  989. } else {
  990. Temp.QuadPart = ((PFSRTL_COMMON_FCB_HEADER)FileObject->FsContext)->ValidDataLength.QuadPart -
  991. Temp.QuadPart;
  992. }
  993. if (Temp.QuadPart <= 0) {
  994. ZeroFlags |= ZERO_FIRST_PAGE | ZERO_MIDDLE_PAGES | ZERO_LAST_PAGE;
  995. } else if ((Temp.HighPart == 0) && (Temp.LowPart <= PAGE_SIZE)) {
  996. ZeroFlags |= ZERO_MIDDLE_PAGES | ZERO_LAST_PAGE;
  997. }
  998. //
  999. // Call a routine to map and copy the data in Mm and get out.
  1000. //
  1001. if (Wait) {
  1002. CcMapAndCopy( SharedCacheMap,
  1003. Buffer,
  1004. &FOffset,
  1005. Length,
  1006. ZeroFlags,
  1007. FileObject );
  1008. return TRUE;
  1009. }
  1010. //
  1011. // The rest of this routine is the Wait == FALSE case.
  1012. //
  1013. // Not all of the transfer will come back at once, so we have to loop
  1014. // until the entire transfer is complete.
  1015. //
  1016. while (Length != 0) {
  1017. ULONG ReceivedLength;
  1018. LARGE_INTEGER BeyondLastByte;
  1019. if (!CcPinFileData( FileObject,
  1020. &FOffset,
  1021. Length,
  1022. FALSE,
  1023. TRUE,
  1024. FALSE,
  1025. &Bcb,
  1026. &CacheBuffer,
  1027. &BeyondLastByte )) {
  1028. DebugTrace(-1, me, "CcCopyWrite -> FALSE\n", 0 );
  1029. return FALSE;
  1030. } else {
  1031. //
  1032. // Calculate how much data is described by Bcb starting at our desired
  1033. // file offset.
  1034. //
  1035. ReceivedLength = (ULONG)(BeyondLastByte.QuadPart - FOffset.QuadPart);
  1036. //
  1037. // If we got more than we need, make sure to only transfer
  1038. // the right amount.
  1039. //
  1040. if (ReceivedLength > Length) {
  1041. ReceivedLength = Length;
  1042. }
  1043. }
  1044. //
  1045. // It is possible for the user buffer to become no longer accessible
  1046. // since it was last checked by the I/O system. If we fail to access
  1047. // the buffer we must raise a status that the caller's exception
  1048. // filter considers as "expected". Also we unmap the Bcb here, since
  1049. // we otherwise would have no other reason to put a try-finally around
  1050. // this loop.
  1051. //
  1052. try {
  1053. RtlCopyBytes( CacheBuffer, Buffer, ReceivedLength );
  1054. CcSetDirtyPinnedData( Bcb, NULL );
  1055. CcUnpinFileData( Bcb, FALSE, UNPIN );
  1056. }
  1057. except( CcCopyReadExceptionFilter( GetExceptionInformation(),
  1058. &Status ) ) {
  1059. CcUnpinFileData( Bcb, TRUE, UNPIN );
  1060. //
  1061. // If we got an access violation, then the user buffer went
  1062. // away. Otherwise we must have gotten an I/O error trying
  1063. // to bring the data in.
  1064. //
  1065. if (Status == STATUS_ACCESS_VIOLATION) {
  1066. ExRaiseStatus( STATUS_INVALID_USER_BUFFER );
  1067. }
  1068. else {
  1069. ExRaiseStatus(FsRtlNormalizeNtstatus( Status, STATUS_UNEXPECTED_IO_ERROR ));
  1070. }
  1071. }
  1072. //
  1073. // Assume we did not get all the data we wanted, and set FOffset
  1074. // to the end of the returned data and adjust the Buffer and Length.
  1075. //
  1076. FOffset = BeyondLastByte;
  1077. Buffer = (PCHAR)Buffer + ReceivedLength;
  1078. Length -= ReceivedLength;
  1079. }
  1080. DebugTrace(-1, me, "CcCopyWrite -> TRUE\n", 0 );
  1081. return TRUE;
  1082. }
  1083. VOID
  1084. CcFastCopyWrite (
  1085. IN PFILE_OBJECT FileObject,
  1086. IN ULONG FileOffset,
  1087. IN ULONG Length,
  1088. IN PVOID Buffer
  1089. )
  1090. /*++
  1091. Routine Description:
  1092. This routine attempts to copy the specified file data from the specified
  1093. buffer into the Cache, and deliver the correct I/O status.
  1094. This is a faster version of CcCopyWrite which only supports 32-bit file
  1095. offsets and synchronicity (Wait = TRUE) and no Write Through.
  1096. Arguments:
  1097. FileObject - Pointer to the file object for a file which was
  1098. opened with NO_INTERMEDIATE_BUFFERING clear, i.e., for
  1099. which CcInitializeCacheMap was called by the file system.
  1100. FileOffset - Byte offset in file to receive the data.
  1101. Length - Length of data in bytes.
  1102. Buffer - Pointer to input buffer from which data should be copied.
  1103. Return Value:
  1104. None
  1105. Raises:
  1106. STATUS_INSUFFICIENT_RESOURCES - If a pool allocation failure occurs.
  1107. This can only occur if Wait was specified as TRUE. (If Wait is
  1108. specified as FALSE, and an allocation failure occurs, this
  1109. routine simply returns FALSE.)
  1110. --*/
  1111. {
  1112. PSHARED_CACHE_MAP SharedCacheMap;
  1113. PVOID CacheBuffer;
  1114. PVACB ActiveVacb;
  1115. ULONG ActivePage;
  1116. PVOID ActiveAddress;
  1117. ULONG PageIsDirty;
  1118. KIRQL OldIrql;
  1119. NTSTATUS Status;
  1120. ULONG ZeroFlags;
  1121. ULONG ValidDataLength;
  1122. LARGE_INTEGER FOffset;
  1123. DebugTrace(+1, me, "CcFastCopyWrite\n", 0 );
  1124. //
  1125. // Get pointer to shared cache map and a copy of valid data length
  1126. //
  1127. SharedCacheMap = FileObject->SectionObjectPointer->SharedCacheMap;
  1128. //
  1129. // See if we have an active Vacb, that we can just copy to.
  1130. //
  1131. GetActiveVacb( SharedCacheMap, OldIrql, ActiveVacb, ActivePage, PageIsDirty );
  1132. if (ActiveVacb != NULL) {
  1133. //
  1134. // See if the request starts in the ActivePage. WriteThrough requests must
  1135. // go the longer route through CcMapAndCopy, where WriteThrough flushes are
  1136. // implemented.
  1137. //
  1138. if (((FileOffset >> PAGE_SHIFT) == ActivePage) && (Length != 0) &&
  1139. !FlagOn( FileObject->Flags, FO_WRITE_THROUGH )) {
  1140. ULONG LengthToCopy = PAGE_SIZE - (FileOffset & (PAGE_SIZE - 1));
  1141. //
  1142. // Reduce LengthToCopy if it is greater than our caller's length.
  1143. //
  1144. if (LengthToCopy > Length) {
  1145. LengthToCopy = Length;
  1146. }
  1147. //
  1148. // Copy the data to the user buffer.
  1149. //
  1150. try {
  1151. //
  1152. // If we are copying to a page that is locked down, then
  1153. // we have to do it under our spinlock, and update the
  1154. // NeedToZero field.
  1155. //
  1156. OldIrql = 0xFF;
  1157. CacheBuffer = (PVOID)((PCHAR)ActiveVacb->BaseAddress +
  1158. (FileOffset & (VACB_MAPPING_GRANULARITY - 1)));
  1159. if (SharedCacheMap->NeedToZero != NULL) {
  1160. //
  1161. // The FastLock may not write our "flag".
  1162. //
  1163. OldIrql = 0;
  1164. ExAcquireFastLock( &SharedCacheMap->ActiveVacbSpinLock, &OldIrql );
  1165. //
  1166. // Note that the NeedToZero could be cleared, since we
  1167. // tested it without the spinlock.
  1168. //
  1169. ActiveAddress = SharedCacheMap->NeedToZero;
  1170. if ((ActiveAddress != NULL) &&
  1171. (ActiveVacb == SharedCacheMap->NeedToZeroVacb) &&
  1172. (((PCHAR)CacheBuffer + LengthToCopy) > (PCHAR)ActiveAddress)) {
  1173. //
  1174. // If we are skipping some bytes in the page, then we need
  1175. // to zero them.
  1176. //
  1177. if ((PCHAR)CacheBuffer > (PCHAR)ActiveAddress) {
  1178. RtlZeroMemory( ActiveAddress, (PCHAR)CacheBuffer - (PCHAR)ActiveAddress );
  1179. }
  1180. SharedCacheMap->NeedToZero = (PVOID)((PCHAR)CacheBuffer + LengthToCopy);
  1181. }
  1182. ExReleaseFastLock( &SharedCacheMap->ActiveVacbSpinLock, OldIrql );
  1183. }
  1184. RtlCopyBytes( CacheBuffer, Buffer, LengthToCopy );
  1185. } except( CcCopyReadExceptionFilter( GetExceptionInformation(),
  1186. &Status ) ) {
  1187. //
  1188. // If we failed to overwrite the uninitialized data,
  1189. // zero it now (we cannot safely restore NeedToZero).
  1190. //
  1191. if (OldIrql != 0xFF) {
  1192. RtlZeroBytes( CacheBuffer, LengthToCopy );
  1193. }
  1194. SetActiveVacb( SharedCacheMap, OldIrql, ActiveVacb, ActivePage, ACTIVE_PAGE_IS_DIRTY );
  1195. //
  1196. // If we got an access violation, then the user buffer went
  1197. // away. Otherwise we must have gotten an I/O error trying
  1198. // to bring the data in.
  1199. //
  1200. if (Status == STATUS_ACCESS_VIOLATION) {
  1201. ExRaiseStatus( STATUS_INVALID_USER_BUFFER );
  1202. }
  1203. else {
  1204. ExRaiseStatus( FsRtlNormalizeNtstatus( Status,
  1205. STATUS_UNEXPECTED_IO_ERROR ));
  1206. }
  1207. }
  1208. //
  1209. // Now adjust FileOffset and Length by what we copied.
  1210. //
  1211. Buffer = (PVOID)((PCHAR)Buffer + LengthToCopy);
  1212. FileOffset += LengthToCopy;
  1213. Length -= LengthToCopy;
  1214. //
  1215. // If that was all the data, then get outski...
  1216. //
  1217. if (Length == 0) {
  1218. SetActiveVacb( SharedCacheMap, OldIrql, ActiveVacb, ActivePage, ACTIVE_PAGE_IS_DIRTY );
  1219. return;
  1220. }
  1221. //
  1222. // Remember that the page is dirty now.
  1223. //
  1224. PageIsDirty |= ACTIVE_PAGE_IS_DIRTY;
  1225. }
  1226. CcFreeActiveVacb( SharedCacheMap, ActiveVacb, ActivePage, PageIsDirty );
  1227. //
  1228. // Else someone else could have the active page, and may want to zero
  1229. // the range we plan to write!
  1230. //
  1231. } else if (SharedCacheMap->NeedToZero != NULL) {
  1232. CcFreeActiveVacb( SharedCacheMap, NULL, 0, FALSE );
  1233. }
  1234. //
  1235. // Set up for call to CcMapAndCopy
  1236. //
  1237. FOffset.LowPart = FileOffset;
  1238. FOffset.HighPart = 0;
  1239. ValidDataLength = ((PFSRTL_COMMON_FCB_HEADER)FileObject->FsContext)->ValidDataLength.LowPart;
  1240. ASSERT((ValidDataLength == MAXULONG) ||
  1241. (((PFSRTL_COMMON_FCB_HEADER)FileObject->FsContext)->ValidDataLength.HighPart == 0));
  1242. //
  1243. // At this point we can calculate the ReadOnly flag for
  1244. // the purposes of whether to use the Bcb resource, and
  1245. // we can calculate the ZeroFlags.
  1246. //
  1247. //
  1248. // We can always zero middle pages, if any.
  1249. //
  1250. ZeroFlags = ZERO_MIDDLE_PAGES;
  1251. if (((FileOffset & (PAGE_SIZE - 1)) == 0) &&
  1252. (Length >= PAGE_SIZE)) {
  1253. ZeroFlags |= ZERO_FIRST_PAGE;
  1254. }
  1255. if (((FileOffset + Length) & (PAGE_SIZE - 1)) == 0) {
  1256. ZeroFlags |= ZERO_LAST_PAGE;
  1257. }
  1258. if ((FileOffset & ~(PAGE_SIZE - 1)) >= ValidDataLength) {
  1259. ZeroFlags |= ZERO_FIRST_PAGE | ZERO_MIDDLE_PAGES | ZERO_LAST_PAGE;
  1260. } else if (((FileOffset & ~(PAGE_SIZE - 1)) + PAGE_SIZE) >= ValidDataLength) {
  1261. ZeroFlags |= ZERO_MIDDLE_PAGES | ZERO_LAST_PAGE;
  1262. }
  1263. //
  1264. // Call a routine to map and copy the data in Mm and get out.
  1265. //
  1266. CcMapAndCopy( SharedCacheMap,
  1267. Buffer,
  1268. &FOffset,
  1269. Length,
  1270. ZeroFlags,
  1271. FileObject );
  1272. DebugTrace(-1, me, "CcFastCopyWrite -> VOID\n", 0 );
  1273. }
  1274. LONG
  1275. CcCopyReadExceptionFilter(
  1276. IN PEXCEPTION_POINTERS ExceptionPointer,
  1277. IN PNTSTATUS ExceptionCode
  1278. )
  1279. /*++
  1280. Routine Description:
  1281. This routine serves as a exception filter and has the special job of
  1282. extracting the "real" I/O error when Mm raises STATUS_IN_PAGE_ERROR
  1283. beneath us.
  1284. Arguments:
  1285. ExceptionPointer - A pointer to the exception record that contains
  1286. the real Io Status.
  1287. ExceptionCode - A pointer to an NTSTATUS that is to receive the real
  1288. status.
  1289. Return Value:
  1290. EXCEPTION_EXECUTE_HANDLER
  1291. --*/
  1292. {
  1293. *ExceptionCode = ExceptionPointer->ExceptionRecord->ExceptionCode;
  1294. if ( (*ExceptionCode == STATUS_IN_PAGE_ERROR) &&
  1295. (ExceptionPointer->ExceptionRecord->NumberParameters >= 3) ) {
  1296. *ExceptionCode = (NTSTATUS) ExceptionPointer->ExceptionRecord->ExceptionInformation[2];
  1297. }
  1298. ASSERT( !NT_SUCCESS(*ExceptionCode) );
  1299. return EXCEPTION_EXECUTE_HANDLER;
  1300. }
  1301. BOOLEAN
  1302. CcCanIWrite (
  1303. IN PFILE_OBJECT FileObject,
  1304. IN ULONG BytesToWrite,
  1305. IN BOOLEAN Wait,
  1306. IN UCHAR Retrying
  1307. )
  1308. /*++
  1309. Routine Description:
  1310. This routine tests whether it is ok to do a write to the cache
  1311. or not, according to the Thresholds of dirty bytes and available
  1312. pages. The first time this routine is called for a request (Retrying
  1313. FALSE), we automatically make the new request queue if there are other
  1314. requests in the queue.
  1315. Note that the ListEmpty test is important to prevent small requests from sneaking
  1316. in and starving large requests.
  1317. Arguments:
  1318. FileObject - for the file to be written
  1319. BytesToWrite - Number of bytes caller wishes to write to the Cache.
  1320. Wait - TRUE if the caller owns no resources, and can block inside this routine
  1321. until it is ok to write.
  1322. Retrying - Specified as FALSE when the request is first received, and
  1323. otherwise specified as TRUE if this write has already entered
  1324. the queue. Special non-zero value of MAXUCHAR indicates that
  1325. we were called within the cache manager with a MasterSpinLock held,
  1326. so do not attempt to acquire it here. MAXUCHAR - 1 means we
  1327. were called within the Cache Manager with some other spinlock
  1328. held. MAXUCHAR - 2 means we want to enforce throttling, even if
  1329. the file object is flagged as being of remote origin. For either
  1330. of the first two special values, we do not touch the FsRtl header.
  1331. Return Value:
  1332. TRUE if it is ok to write.
  1333. FALSE if the caller should defer the write via a call to CcDeferWrite.
  1334. --*/
  1335. {
  1336. PSHARED_CACHE_MAP SharedCacheMap;
  1337. KEVENT Event;
  1338. KIRQL OldIrql;
  1339. ULONG PagesToWrite;
  1340. BOOLEAN ExceededPerFileThreshold;
  1341. DEFERRED_WRITE DeferredWrite;
  1342. PSECTION_OBJECT_POINTERS SectionObjectPointers;
  1343. //
  1344. // If this file is writethrough or of remote origin, exempt it from throttling
  1345. // and let it write. We do this under the assumption that it has been throttled
  1346. // at the remote location and we do not want to block it here. If we were called
  1347. // with Retrying set to MAXUCHAR - 2, enforce the throttle regardless of the
  1348. // file object origin (see above).
  1349. //
  1350. if (BooleanFlagOn( FileObject->Flags, FO_WRITE_THROUGH) ||
  1351. (IoIsFileOriginRemote(FileObject) && (Retrying != MAXUCHAR - 2))) {
  1352. return TRUE;
  1353. }
  1354. //
  1355. // Do a special test here for file objects that keep track of dirty
  1356. // pages on a per-file basis. This is used mainly for slow links.
  1357. //
  1358. ExceededPerFileThreshold = FALSE;
  1359. PagesToWrite = ((BytesToWrite < WRITE_CHARGE_THRESHOLD ?
  1360. BytesToWrite : WRITE_CHARGE_THRESHOLD) + (PAGE_SIZE - 1)) / PAGE_SIZE;
  1361. //
  1362. // Don't dereference the FsContext field if we were called while holding
  1363. // a spinlock.
  1364. //
  1365. if ((Retrying >= MAXUCHAR - 1) ||
  1366. FlagOn(((PFSRTL_COMMON_FCB_HEADER)(FileObject->FsContext))->Flags,
  1367. FSRTL_FLAG_LIMIT_MODIFIED_PAGES)) {
  1368. if (Retrying != MAXUCHAR) {
  1369. CcAcquireMasterLock( &OldIrql );
  1370. }
  1371. if (((SectionObjectPointers = FileObject->SectionObjectPointer) != NULL) &&
  1372. ((SharedCacheMap = SectionObjectPointers->SharedCacheMap) != NULL) &&
  1373. (SharedCacheMap->DirtyPageThreshold != 0) &&
  1374. (SharedCacheMap->DirtyPages != 0) &&
  1375. ((PagesToWrite + SharedCacheMap->DirtyPages) >
  1376. SharedCacheMap->DirtyPageThreshold)) {
  1377. ExceededPerFileThreshold = TRUE;
  1378. }
  1379. if (Retrying != MAXUCHAR) {
  1380. CcReleaseMasterLock( OldIrql );
  1381. }
  1382. }
  1383. //
  1384. // See if it is ok to do the write right now
  1385. //
  1386. if ((Retrying || IsListEmpty(&CcDeferredWrites))
  1387. &&
  1388. (CcTotalDirtyPages + PagesToWrite < CcDirtyPageThreshold)
  1389. &&
  1390. MmEnoughMemoryForWrite()
  1391. &&
  1392. !ExceededPerFileThreshold) {
  1393. return TRUE;
  1394. }
  1395. //
  1396. // Otherwise, if our caller is synchronous, we will just wait here.
  1397. //
  1398. if (Wait) {
  1399. if (IsListEmpty(&CcDeferredWrites) ) {
  1400. //
  1401. // Get a write scan to occur NOW
  1402. //
  1403. CcAcquireMasterLock( &OldIrql );
  1404. CcScheduleLazyWriteScan( TRUE );
  1405. CcReleaseMasterLock( OldIrql );
  1406. }
  1407. KeInitializeEvent( &Event, NotificationEvent, FALSE );
  1408. //
  1409. // Fill in the block. Note that we can access the Fsrtl Common Header
  1410. // even if it's paged because Wait will be FALSE if called from
  1411. // within the cache.
  1412. //
  1413. DeferredWrite.NodeTypeCode = CACHE_NTC_DEFERRED_WRITE;
  1414. DeferredWrite.NodeByteSize = sizeof(DEFERRED_WRITE);
  1415. DeferredWrite.FileObject = FileObject;
  1416. DeferredWrite.BytesToWrite = BytesToWrite;
  1417. DeferredWrite.Event = &Event;
  1418. DeferredWrite.LimitModifiedPages = BooleanFlagOn(((PFSRTL_COMMON_FCB_HEADER)(FileObject->FsContext))->Flags,
  1419. FSRTL_FLAG_LIMIT_MODIFIED_PAGES);
  1420. //
  1421. // Now insert at the appropriate end of the list
  1422. //
  1423. if (Retrying) {
  1424. ExInterlockedInsertHeadList( &CcDeferredWrites,
  1425. &DeferredWrite.DeferredWriteLinks,
  1426. &CcDeferredWriteSpinLock );
  1427. } else {
  1428. ExInterlockedInsertTailList( &CcDeferredWrites,
  1429. &DeferredWrite.DeferredWriteLinks,
  1430. &CcDeferredWriteSpinLock );
  1431. }
  1432. while (TRUE) {
  1433. //
  1434. // Now since we really didn't synchronize anything but the insertion,
  1435. // we call the post routine to make sure that in some wierd case we
  1436. // do not leave anyone hanging with no dirty bytes for the Lazy Writer.
  1437. //
  1438. CcPostDeferredWrites();
  1439. //
  1440. // Finally wait until the event is signalled and we can write
  1441. // and return to tell the guy he can write.
  1442. //
  1443. if (KeWaitForSingleObject( &Event,
  1444. Executive,
  1445. KernelMode,
  1446. FALSE,
  1447. &CcIdleDelay ) == STATUS_SUCCESS) {
  1448. return TRUE;
  1449. }
  1450. }
  1451. } else {
  1452. return FALSE;
  1453. }
  1454. }
  1455. VOID
  1456. CcDeferWrite (
  1457. IN PFILE_OBJECT FileObject,
  1458. IN PCC_POST_DEFERRED_WRITE PostRoutine,
  1459. IN PVOID Context1,
  1460. IN PVOID Context2,
  1461. IN ULONG BytesToWrite,
  1462. IN BOOLEAN Retrying
  1463. )
  1464. /*++
  1465. Routine Description:
  1466. This routine may be called to have the Cache Manager defer posting
  1467. of a write until the Lazy Writer makes some progress writing, or
  1468. there are more available pages. A file system would normally call
  1469. this routine after receiving FALSE from CcCanIWrite, and preparing
  1470. the request to be posted.
  1471. Arguments:
  1472. FileObject - for the file to be written
  1473. PostRoutine - Address of the PostRoutine that the Cache Manager can
  1474. call to post the request when conditions are right. Note
  1475. that it is possible that this routine will be called
  1476. immediately from this routine.
  1477. Context1 - First context parameter for the post routine.
  1478. Context2 - Secont parameter for the post routine.
  1479. BytesToWrite - Number of bytes that the request is trying to write
  1480. to the cache.
  1481. Retrying - Supplied as FALSE if the request is being posted for the
  1482. first time, TRUE otherwise.
  1483. Return Value:
  1484. None
  1485. --*/
  1486. {
  1487. PDEFERRED_WRITE DeferredWrite;
  1488. KIRQL OldIrql;
  1489. //
  1490. // Attempt to allocate a deferred write block, and if we do not get
  1491. // one, just post it immediately rather than gobbling up must succeed
  1492. // pool.
  1493. //
  1494. DeferredWrite = ExAllocatePoolWithTag( NonPagedPool, sizeof(DEFERRED_WRITE), 'wDcC' );
  1495. if (DeferredWrite == NULL) {
  1496. (*PostRoutine)( Context1, Context2 );
  1497. return;
  1498. }
  1499. //
  1500. // Fill in the block.
  1501. //
  1502. DeferredWrite->NodeTypeCode = CACHE_NTC_DEFERRED_WRITE;
  1503. DeferredWrite->NodeByteSize = sizeof(DEFERRED_WRITE);
  1504. DeferredWrite->FileObject = FileObject;
  1505. DeferredWrite->BytesToWrite = BytesToWrite;
  1506. DeferredWrite->Event = NULL;
  1507. DeferredWrite->PostRoutine = PostRoutine;
  1508. DeferredWrite->Context1 = Context1;
  1509. DeferredWrite->Context2 = Context2;
  1510. DeferredWrite->LimitModifiedPages = BooleanFlagOn(((PFSRTL_COMMON_FCB_HEADER)(FileObject->FsContext))->Flags,
  1511. FSRTL_FLAG_LIMIT_MODIFIED_PAGES);
  1512. //
  1513. // Now insert at the appropriate end of the list
  1514. //
  1515. if (Retrying) {
  1516. ExInterlockedInsertHeadList( &CcDeferredWrites,
  1517. &DeferredWrite->DeferredWriteLinks,
  1518. &CcDeferredWriteSpinLock );
  1519. } else {
  1520. ExInterlockedInsertTailList( &CcDeferredWrites,
  1521. &DeferredWrite->DeferredWriteLinks,
  1522. &CcDeferredWriteSpinLock );
  1523. }
  1524. //
  1525. // Now since we really didn't synchronize anything but the insertion,
  1526. // we call the post routine to make sure that in some wierd case we
  1527. // do not leave anyone hanging with no dirty bytes for the Lazy Writer.
  1528. //
  1529. CcPostDeferredWrites();
  1530. //
  1531. // Schedule the lazy writer in case the reason we're blocking
  1532. // is that we're waiting for Mm (or some other external flag)
  1533. // to lower and let this write happen. He will be the one to
  1534. // keep coming back and checking if this can proceed, even if
  1535. // there are no cache manager pages to write.
  1536. //
  1537. CcAcquireMasterLock( &OldIrql);
  1538. if (!LazyWriter.ScanActive) {
  1539. CcScheduleLazyWriteScan( FALSE );
  1540. }
  1541. CcReleaseMasterLock( OldIrql);
  1542. }
  1543. VOID
  1544. CcPostDeferredWrites (
  1545. )
  1546. /*++
  1547. Routine Description:
  1548. This routine may be called to see if any deferred writes should be posted
  1549. now, and to post them. It should be called any time the status of the
  1550. queue may have changed, such as when a new entry has been added, or the
  1551. Lazy Writer has finished writing out buffers and set them clean.
  1552. Arguments:
  1553. None
  1554. Return Value:
  1555. None
  1556. --*/
  1557. {
  1558. PDEFERRED_WRITE DeferredWrite;
  1559. ULONG TotalBytesLetLoose = 0;
  1560. KIRQL OldIrql;
  1561. do {
  1562. //
  1563. // Initially clear the deferred write structure pointer
  1564. // and syncrhronize.
  1565. //
  1566. DeferredWrite = NULL;
  1567. ExAcquireSpinLock( &CcDeferredWriteSpinLock, &OldIrql );
  1568. //
  1569. // If the list is empty we are done.
  1570. //
  1571. if (!IsListEmpty(&CcDeferredWrites)) {
  1572. PLIST_ENTRY Entry;
  1573. Entry = CcDeferredWrites.Flink;
  1574. while (Entry != &CcDeferredWrites) {
  1575. DeferredWrite = CONTAINING_RECORD( Entry,
  1576. DEFERRED_WRITE,
  1577. DeferredWriteLinks );
  1578. //
  1579. // Check for a paranoid case here that TotalBytesLetLoose
  1580. // wraps. We stop processing the list at this time.
  1581. //
  1582. TotalBytesLetLoose += DeferredWrite->BytesToWrite;
  1583. if (TotalBytesLetLoose < DeferredWrite->BytesToWrite) {
  1584. DeferredWrite = NULL;
  1585. break;
  1586. }
  1587. //
  1588. // If it is now ok to post this write, remove him from
  1589. // the list.
  1590. //
  1591. if (CcCanIWrite( DeferredWrite->FileObject,
  1592. TotalBytesLetLoose,
  1593. FALSE,
  1594. MAXUCHAR - 1 )) {
  1595. RemoveEntryList( &DeferredWrite->DeferredWriteLinks );
  1596. break;
  1597. //
  1598. // Otherwise, it is time to stop processing the list, so
  1599. // we clear the pointer again unless we throttled this item
  1600. // because of a private dirty page limit.
  1601. //
  1602. } else {
  1603. //
  1604. // If this was a private throttle, skip over it and
  1605. // remove its byte count from the running total.
  1606. //
  1607. if (DeferredWrite->LimitModifiedPages) {
  1608. Entry = Entry->Flink;
  1609. TotalBytesLetLoose -= DeferredWrite->BytesToWrite;
  1610. DeferredWrite = NULL;
  1611. continue;
  1612. } else {
  1613. DeferredWrite = NULL;
  1614. break;
  1615. }
  1616. }
  1617. }
  1618. }
  1619. ExReleaseSpinLock( &CcDeferredWriteSpinLock, OldIrql );
  1620. //
  1621. // If we got something, set the event or call the post routine
  1622. // and deallocate the structure.
  1623. //
  1624. if (DeferredWrite != NULL) {
  1625. if (DeferredWrite->Event != NULL) {
  1626. KeSetEvent( DeferredWrite->Event, 0, FALSE );
  1627. } else {
  1628. (*DeferredWrite->PostRoutine)( DeferredWrite->Context1,
  1629. DeferredWrite->Context2 );
  1630. ExFreePool( DeferredWrite );
  1631. }
  1632. }
  1633. //
  1634. // Loop until we find no more work to do.
  1635. //
  1636. } while (DeferredWrite != NULL);
  1637. }