/*++ Copyright (c) 1991 Microsoft Corporation Module Name: RwCmpSup.c Abstract: This module implements the fast I/O routines for read/write compressed. Author: Tom Miller [TomM] 14-Jul-1991 Revision History: --*/ #include "NtfsProc.h" VOID NtfsAddToCompressedMdlChain ( IN OUT PMDL *MdlChain, IN PVOID MdlBuffer, IN ULONG MdlLength, IN PBCB Bcb, IN LOCK_OPERATION Operation ); VOID NtfsSetMdlBcbOwners ( IN PMDL MdlChain ); VOID NtfsCleanupCompressedMdlChain ( IN OUT PMDL *MdlChain, IN ULONG Error ); #ifdef ALLOC_PRAGMA #pragma alloc_text(PAGE, NtfsCopyReadC) #pragma alloc_text(PAGE, NtfsCompressedCopyRead) #pragma alloc_text(PAGE, NtfsMdlReadCompleteCompressed) #pragma alloc_text(PAGE, NtfsCopyWriteC) #pragma alloc_text(PAGE, NtfsCompressedCopyWrite) #pragma alloc_text(PAGE, NtfsMdlWriteCompleteCompressed) #pragma alloc_text(PAGE, NtfsAddToCompressedMdlChain) #pragma alloc_text(PAGE, NtfsSetMdlBcbOwners) #pragma alloc_text(PAGE, NtfsCleanupCompressedMdlChain) #endif BOOLEAN NtfsCopyReadC ( IN PFILE_OBJECT FileObject, IN PLARGE_INTEGER FileOffset, IN ULONG Length, IN ULONG LockKey, OUT PVOID Buffer, OUT PMDL *MdlChain, OUT PIO_STATUS_BLOCK IoStatus, OUT PCOMPRESSED_DATA_INFO CompressedDataInfo, IN ULONG CompressedDataInfoLength, IN PDEVICE_OBJECT DeviceObject ) /*++ Routine Description: This routine does a fast cached read bypassing the usual file system entry routine (i.e., without the Irp). It is used to do a copy read of a cached file object. For a complete description of the arguments see CcCopyRead. Arguments: FileObject - Pointer to the file object being read. FileOffset - Byte offset in file for desired data. Length - Length of desired data in bytes. Buffer - Pointer to output buffer to which data should be copied. MdlChain - Pointer to an MdlChain pointer to receive an Mdl to describe the data in the cache. IoStatus - Pointer to standard I/O status block to receive the status for the transfer. CompressedDataInfo - Returns compressed data info with compressed chunk sizes CompressedDataInfoLength - Supplies the size of the info buffer in bytes. Return Value: FALSE - if the data was not delivered for any reason TRUE - if the data is being delivered --*/ { PFSRTL_ADVANCED_FCB_HEADER Header; LONGLONG LocalOffset; PFAST_IO_DISPATCH FastIoDispatch; EOF_WAIT_BLOCK EofWaitBlock; FILE_COMPRESSION_INFORMATION CompressionInformation; ULONG CompressionUnitSize, ChunkSize, CuCompressedSize; BOOLEAN Status = TRUE; BOOLEAN DoingIoAtEof = FALSE; PAGED_CODE(); // // You cannot have both a buffer to copy into and an MdlChain. // ASSERT((Buffer == NULL) || (MdlChain == NULL)); // // Assume success. // IoStatus->Status = STATUS_SUCCESS; IoStatus->Information = Length; CompressedDataInfo->NumberOfChunks = 0; // // Special case a read of zero length // if (Length != 0) { // // Get a real pointer to the common fcb header // Header = (PFSRTL_ADVANCED_FCB_HEADER)FileObject->FsContext; // // Enter the file system // FsRtlEnterFileSystem(); // // Make our best guess on whether we need the file exclusive // or shared. Note that we do not check FileOffset->HighPart // until below. // Status = ExAcquireResourceShared( Header->PagingIoResource, TRUE ); // // Now that the File is acquired shared, we can safely test if it // is really cached and if we can do fast i/o and if not, then // release the fcb and return. // if ((Header->FileObjectC == NULL) || (Header->FileObjectC->PrivateCacheMap == NULL) || (Header->IsFastIoPossible == FastIoIsNotPossible)) { Status = FALSE; goto Done; } // // Get the address of the driver object's Fast I/O dispatch structure. // FastIoDispatch = DeviceObject->DriverObject->FastIoDispatch; // // Get the compression information for this file and return those fields. // NtfsFastIoQueryCompressionInfo( FileObject, &CompressionInformation, IoStatus ); CompressedDataInfo->CompressionFormatAndEngine = CompressionInformation.CompressionFormat; CompressedDataInfo->CompressionUnitShift = CompressionInformation.CompressionUnitShift; CompressionUnitSize = 1 << CompressionInformation.CompressionUnitShift; CompressedDataInfo->ChunkShift = CompressionInformation.ChunkShift; CompressedDataInfo->ClusterShift = CompressionInformation.ClusterShift; CompressedDataInfo->Reserved = 0; ChunkSize = 1 << CompressionInformation.ChunkShift; // // If we either got an error in the call above, or the file size is less than // one chunk, then return an error. (Could be an Ntfs resident attribute.) if (!NT_SUCCESS(IoStatus->Status) || (Header->FileSize.QuadPart < ChunkSize)) { Status = FALSE; goto Done; } ASSERT((FileOffset->LowPart & (ChunkSize - 1)) == 0); // // If there is a normal cache section, flush that first, flushing integral // compression units so we don't write them twice. // if (FileObject->SectionObjectPointer->SharedCacheMap != NULL) { LocalOffset = FileOffset->QuadPart & ~(LONGLONG)(CompressionUnitSize - 1); CcFlushCache( FileObject->SectionObjectPointer, (PLARGE_INTEGER)&LocalOffset, (Length + (ULONG)(FileOffset->QuadPart - LocalOffset) + ChunkSize - 1) & ~(ChunkSize - 1), NULL ); } // // Now synchronize with the FsRtl Header // ExAcquireFastMutex( Header->FastMutex ); // // Now see if we are reading beyond ValidDataLength. We have to // do it now so that our reads are not nooped. // LocalOffset = FileOffset->QuadPart + (LONGLONG)Length; if (LocalOffset > Header->ValidDataLength.QuadPart) { // // We must serialize with anyone else doing I/O at beyond // ValidDataLength, and then remember if we need to declare // when we are done. // DoingIoAtEof = !FlagOn( Header->Flags, FSRTL_FLAG_EOF_ADVANCE_ACTIVE ) || NtfsWaitForIoAtEof( Header, FileOffset, Length, &EofWaitBlock ); // // Set the Flag if we are in fact beyond ValidDataLength. // if (DoingIoAtEof) { SetFlag( Header->Flags, FSRTL_FLAG_EOF_ADVANCE_ACTIVE ); } } ExReleaseFastMutex( Header->FastMutex ); // // Check if fast I/O is questionable and if so then go ask the // file system the answer // if (Header->IsFastIoPossible == FastIoIsQuestionable) { ASSERT(!KeIsExecutingDpc()); // // All file systems that set "Is Questionable" had better support // fast I/O // ASSERT(FastIoDispatch != NULL); ASSERT(FastIoDispatch->FastIoCheckIfPossible != NULL); // // Call the file system to check for fast I/O. If the answer is // anything other than GoForIt then we cannot take the fast I/O // path. // if (!FastIoDispatch->FastIoCheckIfPossible( FileObject, FileOffset, Length, TRUE, LockKey, TRUE, // read operation IoStatus, DeviceObject )) { // // Fast I/O is not possible so release the Fcb and return. // Status = FALSE; goto Done; } } // // Check for read past file size. // IoStatus->Information = Length; if ( LocalOffset > Header->FileSize.QuadPart ) { if ( FileOffset->QuadPart >= Header->FileSize.QuadPart ) { IoStatus->Status = STATUS_END_OF_FILE; IoStatus->Information = 0; goto Done; } IoStatus->Information = Length = (ULONG)( Header->FileSize.QuadPart - FileOffset->QuadPart ); } // // We can do fast i/o so call the cc routine to do the work and then // release the fcb when we've done. If for whatever reason the // copy read fails, then return FALSE to our caller. // // Also mark this as the top level "Irp" so that lower file system // levels will not attempt a pop-up // PsGetCurrentThread()->TopLevelIrp = FSRTL_FAST_IO_TOP_LEVEL_IRP; IoStatus->Status = NtfsCompressedCopyRead( FileObject, FileOffset, Length, Buffer, MdlChain, CompressedDataInfo, CompressedDataInfoLength, DeviceObject, Header, CompressionUnitSize, ChunkSize ); Status = (BOOLEAN)NT_SUCCESS(IoStatus->Status); PsGetCurrentThread()->TopLevelIrp = 0; Done: NOTHING; if (DoingIoAtEof) { ExAcquireFastMutex( Header->FastMutex ); NtfsFinishIoAtEof( Header ); ExReleaseFastMutex( Header->FastMutex ); } // // For the Mdl case, we must keep the resource. // if ((MdlChain == NULL) || !Status) { ExReleaseResource( Header->PagingIoResource ); } FsRtlExitFileSystem(); } return Status; } NTSTATUS NtfsCompressedCopyRead ( IN PFILE_OBJECT FileObject, IN PLARGE_INTEGER FileOffset, IN ULONG Length, OUT PVOID Buffer, OUT PMDL *MdlChain, OUT PCOMPRESSED_DATA_INFO CompressedDataInfo, IN ULONG CompressedDataInfoLength, IN PDEVICE_OBJECT DeviceObject, IN PFSRTL_ADVANCED_FCB_HEADER Header, IN ULONG CompressionUnitSize, IN ULONG ChunkSize ) { PFILE_OBJECT LocalFileObject; PULONG NextReturnChunkSize; PUCHAR CompressedBuffer, EndOfCompressedBuffer, ChunkBuffer; LONGLONG LocalOffset; ULONG CuCompressedSize; PVOID MdlBuffer; ULONG MdlLength; BOOLEAN IsCompressed; NTSTATUS Status = STATUS_SUCCESS; PBCB Bcb = NULL; UNREFERENCED_PARAMETER( CompressedDataInfoLength ); UNREFERENCED_PARAMETER( DeviceObject ); try { // // Get ready to loop through all of the compression units. // LocalOffset = FileOffset->QuadPart & ~(LONGLONG)(CompressionUnitSize - 1); Length = (Length + (ULONG)(FileOffset->QuadPart - LocalOffset) + ChunkSize - 1) & ~(ChunkSize - 1); ASSERT(CompressedDataInfoLength >= (sizeof(COMPRESSED_DATA_INFO) + (((Length >> CompressedDataInfo->ChunkShift) - 1) * sizeof(ULONG)))); NextReturnChunkSize = &CompressedDataInfo->CompressedChunkSizes[0]; // // Loop through desired compression units // while (TRUE) { NtfsFastIoQueryCompressedSize( FileObject, (PLARGE_INTEGER)&LocalOffset, &CuCompressedSize ); ASSERT( CuCompressedSize <= CompressionUnitSize ); IsCompressed = (BOOLEAN)((CuCompressedSize != CompressionUnitSize) && (CompressedDataInfo->CompressionFormatAndEngine != 0)); // // Figure out which FileObject to use. // LocalFileObject = Header->FileObjectC; if (!IsCompressed) { if (FileObject->PrivateCacheMap == NULL) { Status = STATUS_NOT_MAPPED_DATA; goto Done; } LocalFileObject = FileObject; } // // If the CompressionUnit is not allocated, we still have to // pin a page to synchronize on this buffer. We reload the // correct size below. // if (CuCompressedSize == 0) { CuCompressedSize = PAGE_SIZE; } // // Map the compression unit in the compressed or uncompressed // stream. // CcPinRead( LocalFileObject, (PLARGE_INTEGER)&LocalOffset, CuCompressedSize, TRUE, &Bcb, &CompressedBuffer ); // // Now that the data is pinned (we are synchronized with the // CompressionUnit), we have to get the size again since it could // have changed. // if (IsCompressed) { NtfsFastIoQueryCompressedSize( FileObject, (PLARGE_INTEGER)&LocalOffset, &CuCompressedSize ); // // In the extremely unlikely event that the compression state changed // before we got the buffer pinned, just raise to get this request // retried. // if (CuCompressedSize == CompressionUnitSize) { ExRaiseStatus( STATUS_CANT_WAIT ); } } ASSERT( CuCompressedSize <= CompressionUnitSize ); IsCompressed = (BOOLEAN)((CuCompressedSize != CompressionUnitSize) && (CompressedDataInfo->CompressionFormatAndEngine != 0)); EndOfCompressedBuffer = Add2Ptr( CompressedBuffer, CuCompressedSize ); // // Now loop through desired chunks // MdlLength = 0; do { // // Assume current chunk does not compress, else get current // chunk size. // if (IsCompressed) { Status = RtlDescribeChunk( CompressedDataInfo->CompressionFormatAndEngine, &CompressedBuffer, EndOfCompressedBuffer, &ChunkBuffer, NextReturnChunkSize ); if (!NT_SUCCESS(Status) && (Status != STATUS_NO_MORE_ENTRIES)) { ExRaiseStatus(Status); } // // If the file is not compressed, we have to fill in // the appropriate chunk size and buffer, and advance // CompressedBuffer. // } else { *NextReturnChunkSize = ChunkSize; ChunkBuffer = CompressedBuffer; CompressedBuffer = Add2Ptr( CompressedBuffer, ChunkSize ); } Status = STATUS_SUCCESS; // // We may not have reached the first chunk yet. // if (LocalOffset >= FileOffset->QuadPart) { if (MdlChain != NULL) { // // If we have not started remembering an Mdl buffer, // then do so now. // if (MdlLength == 0) { MdlBuffer = ChunkBuffer; // // Otherwise we just have to increase the length // and check for an uncompressed chunk, because that // forces us to emit the previous Mdl since we do // not transmit the chunk header in this case. // } else { // // In the rare case that we hit an individual chunk // that did not compress, we have to emit what we // had (which captures the Bcb pointer), and start // a new Mdl buffer. // if (*NextReturnChunkSize == ChunkSize) { NtfsAddToCompressedMdlChain( MdlChain, MdlBuffer, MdlLength, Bcb, IoReadAccess ); Bcb = NULL; MdlBuffer = ChunkBuffer; MdlLength = 0; } } MdlLength += *NextReturnChunkSize; // // Else copy next chunk (compressed or not). // } else { // // Copy next chunk (compressed or not). // RtlCopyBytes( Buffer, ChunkBuffer, (IsCompressed || (Length >= *NextReturnChunkSize)) ? *NextReturnChunkSize : Length ); // // Advance output buffer by bytes copied. // Buffer = (PCHAR)Buffer + *NextReturnChunkSize; } NextReturnChunkSize += 1; CompressedDataInfo->NumberOfChunks += 1; } // // Reduce length by chunk copied, and check if we are done. // if (Length > ChunkSize) { Length -= ChunkSize; } else { goto Done; } LocalOffset += ChunkSize; } while ((LocalOffset & (CompressionUnitSize - 1)) != 0); // // If this is an Mdl call, then it is time to add to the MdlChain // before moving to the next compression unit. // if (MdlLength != 0) { NtfsAddToCompressedMdlChain( MdlChain, MdlBuffer, MdlLength, Bcb, IoReadAccess ); MdlLength = 0; // // Otherwise if there is still a Bcb, unpin it // } else if (Bcb != NULL) { CcUnpinData(Bcb); } Bcb = NULL; } Done: FileObject->Flags |= FO_FILE_FAST_IO_READ; if ((MdlLength != 0) && NT_SUCCESS(Status)) { NtfsAddToCompressedMdlChain( MdlChain, MdlBuffer, MdlLength, Bcb, IoReadAccess ); Bcb = NULL; } } except( FsRtlIsNtstatusExpected(Status = GetExceptionCode()) ? EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH ) { NOTHING; } // // Unpin the Bcb if we still have it. // if (Bcb != NULL) { CcUnpinData(Bcb); } // // On error, cleanup any MdlChain we built up // if (!NT_SUCCESS(Status) && (MdlChain != NULL)) { NtfsCleanupCompressedMdlChain( MdlChain, TRUE ); // // Change owner Id for the Scb and Bcbs we are holding. // } else { NtfsSetMdlBcbOwners( *MdlChain ); ExSetResourceOwnerPointer( Header->PagingIoResource, (PVOID)((PCHAR)*MdlChain + 3) ); } return Status; } BOOLEAN NtfsMdlReadCompleteCompressed ( IN struct _FILE_OBJECT *FileObject, IN PMDL MdlChain, IN struct _DEVICE_OBJECT *DeviceObject ) { PFSRTL_ADVANCED_FCB_HEADER Header; UNREFERENCED_PARAMETER( DeviceObject ); NtfsCleanupCompressedMdlChain( &MdlChain, FALSE ); // // Get a real pointer to the common fcb header, and release with // the Id we used. // Header = (PFSRTL_ADVANCED_FCB_HEADER)FileObject->FsContext; ExReleaseResourceForThread( Header->PagingIoResource, (ERESOURCE_THREAD)((PCHAR)MdlChain + 3) ); return TRUE; } BOOLEAN NtfsCopyWriteC ( IN PFILE_OBJECT FileObject, IN PLARGE_INTEGER FileOffset, IN ULONG Length, IN ULONG LockKey, IN PVOID Buffer, OUT PMDL *MdlChain, OUT PIO_STATUS_BLOCK IoStatus, IN PCOMPRESSED_DATA_INFO CompressedDataInfo, IN ULONG CompressedDataInfoLength, IN PDEVICE_OBJECT DeviceObject ) /*++ Routine Description: This routine does a fast cached write bypassing the usual file system entry routine (i.e., without the Irp). It is used to do a copy write of a cached file object. For a complete description of the arguments see CcCopyWrite. Arguments: FileObject - Pointer to the file object being write. FileOffset - Byte offset in file for desired data. Length - Length of desired data in bytes. Buffer - Pointer to output buffer to which data should be copied. MdlChain - Pointer to an MdlChain pointer to receive an Mdl to describe where the data may be written in the cache. IoStatus - Pointer to standard I/O status block to receive the status for the transfer. CompressedDataInfo - Returns compressed data info with compressed chunk sizes CompressedDataInfoLength - Supplies the size of the info buffer in bytes. Return Value: FALSE - if there is an error. TRUE - if the data is being delivered --*/ { PFSRTL_ADVANCED_FCB_HEADER Header; EOF_WAIT_BLOCK EofWaitBlock; FILE_COMPRESSION_INFORMATION CompressionInformation; ULONG CompressionUnitSize, ChunkSize; ULONG EngineMatches; LARGE_INTEGER NewFileSize; LARGE_INTEGER OldFileSize; LONGLONG LocalOffset; PFAST_IO_DISPATCH FastIoDispatch = DeviceObject->DriverObject->FastIoDispatch; ULONG DoingIoAtEof = FALSE; BOOLEAN Status = TRUE; UNREFERENCED_PARAMETER( CompressedDataInfoLength ); PAGED_CODE(); // // You cannot have both a buffer to copy into and an MdlChain. // ASSERT((Buffer == NULL) || (MdlChain == NULL)); // // Get a real pointer to the common fcb header // Header = (PFSRTL_ADVANCED_FCB_HEADER)FileObject->FsContext; // // See if it is ok to handle this in the fast path. // if (CcCanIWrite( FileObject, Length, TRUE, FALSE ) && !FlagOn(FileObject->Flags, FO_WRITE_THROUGH) && CcCopyWriteWontFlush(FileObject, FileOffset, Length)) { // // Assume our transfer will work // IoStatus->Status = STATUS_SUCCESS; IoStatus->Information = Length; CompressedDataInfo->NumberOfChunks = 0; // // Special case the zero byte length // if (Length != 0) { // // Enter the file system // FsRtlEnterFileSystem(); // // Calculate the compression unit and chunk sizes. // CompressionUnitSize = 1 << CompressedDataInfo->CompressionUnitShift; ChunkSize = 1 << CompressedDataInfo->ChunkShift; // // If there is a normal cache section, flush that first, flushing integral // compression units so we don't write them twice. // // if (FileObject->SectionObjectPointer->SharedCacheMap != NULL) { ULONG FlushLength = (Length + (ULONG)(FileOffset->QuadPart - LocalOffset) + CompressionUnitSize - 1) & ~(CompressionUnitSize - 1); LocalOffset = FileOffset->QuadPart & ~(LONGLONG)(CompressionUnitSize - 1); ExAcquireResourceExclusive( Header->PagingIoResource, TRUE ); CcFlushCache( FileObject->SectionObjectPointer, (PLARGE_INTEGER)&LocalOffset, FlushLength, NULL ); CcPurgeCacheSection( FileObject->SectionObjectPointer, (PLARGE_INTEGER)&LocalOffset, FlushLength, FALSE ); ExReleaseResource( Header->PagingIoResource ); } NewFileSize.QuadPart = FileOffset->QuadPart + Length; // // Prevent truncates by acquiring paging I/O // ExAcquireResourceShared( Header->PagingIoResource, TRUE ); // // Get the compression information for this file and return those fields. // NtfsFastIoQueryCompressionInfo( FileObject, &CompressionInformation, IoStatus ); // // See if the engine matches, so we can pass that on to the // compressed write routine. // EngineMatches = ((CompressedDataInfo->CompressionFormatAndEngine == CompressionInformation.CompressionFormat) && (CompressedDataInfo->CompressionUnitShift == CompressionInformation.CompressionUnitShift) && (CompressedDataInfo->ChunkShift == CompressionInformation.ChunkShift)); // // If we either got an error in the call above, or the file size is less than // one chunk, then return an error. (Could be an Ntfs resident attribute.) // if (!NT_SUCCESS(IoStatus->Status) || (Header->FileSize.QuadPart < ChunkSize)) { goto ErrOut; } // // Now synchronize with the FsRtl Header // ExAcquireFastMutex( Header->FastMutex ); // // Now see if we will change FileSize. We have to do it now // so that our reads are not nooped. Note we do not allow // FileOffset to be WRITE_TO_EOF. // ASSERT((FileOffset->LowPart & (ChunkSize - 1)) == 0); if (NewFileSize.QuadPart > Header->ValidDataLength.QuadPart) { // // We can change FileSize and ValidDataLength if either, no one // else is now, or we are still extending after waiting. // DoingIoAtEof = !FlagOn( Header->Flags, FSRTL_FLAG_EOF_ADVANCE_ACTIVE ) || NtfsWaitForIoAtEof( Header, FileOffset, Length, &EofWaitBlock ); // // Set the Flag if we are changing FileSize or ValidDataLength, // and save current values. // if (DoingIoAtEof) { SetFlag( Header->Flags, FSRTL_FLAG_EOF_ADVANCE_ACTIVE ); // // Now calculate the new FileSize and see if we wrapped the // 32-bit boundary. // NewFileSize.QuadPart = FileOffset->QuadPart + Length; // // Update Filesize now so that we do not truncate reads. // OldFileSize.QuadPart = Header->FileSize.QuadPart; if (NewFileSize.QuadPart > Header->FileSize.QuadPart) { // // If we are beyond AllocationSize, go to ErrOut // if (NewFileSize.QuadPart > Header->AllocationSize.QuadPart) { ExReleaseFastMutex( Header->FastMutex ); goto ErrOut; } else { Header->FileSize.QuadPart = NewFileSize.QuadPart; } } } } ExReleaseFastMutex( Header->FastMutex ); // // Now that the File is acquired shared, we can safely test if it // is really cached and if we can do fast i/o and if not, then // release the fcb and return. // // Note, we do not want to call CcZeroData here, // but rather defer zeroing to the file system, due to // the need for exclusive resource acquisition. Therefore // we get out if we are beyond ValidDataLength. // if ((Header->FileObjectC == NULL) || (Header->FileObjectC->PrivateCacheMap == NULL) || (Header->IsFastIoPossible == FastIoIsNotPossible) || (FileOffset->QuadPart > Header->ValidDataLength.QuadPart)) { goto ErrOut; } // // Check if fast I/O is questionable and if so then go ask // the file system the answer // if (Header->IsFastIoPossible == FastIoIsQuestionable) { FastIoDispatch = DeviceObject->DriverObject->FastIoDispatch; // // All file system then set "Is Questionable" had better // support fast I/O // ASSERT(FastIoDispatch != NULL); ASSERT(FastIoDispatch->FastIoCheckIfPossible != NULL); // // Call the file system to check for fast I/O. If the // answer is anything other than GoForIt then we cannot // take the fast I/O path. // if (!FastIoDispatch->FastIoCheckIfPossible( FileObject, FileOffset, Length, TRUE, LockKey, FALSE, // write operation IoStatus, DeviceObject )) { // // Fast I/O is not possible so cleanup and return. // goto ErrOut; } } // // We can do fast i/o so call the cc routine to do the work // and then release the fcb when we've done. If for whatever // reason the copy write fails, then return FALSE to our // caller. // // Also mark this as the top level "Irp" so that lower file // system levels will not attempt a pop-up // PsGetCurrentThread()->TopLevelIrp = FSRTL_FAST_IO_TOP_LEVEL_IRP; ASSERT(CompressedDataInfoLength >= (sizeof(COMPRESSED_DATA_INFO) + (((Length >> CompressedDataInfo->ChunkShift) - 1) * sizeof(ULONG)))); Status = (BOOLEAN)NT_SUCCESS(NtfsCompressedCopyWrite( FileObject, FileOffset, Length, Buffer, MdlChain, CompressedDataInfo, DeviceObject, Header, CompressionUnitSize, ChunkSize, EngineMatches )); PsGetCurrentThread()->TopLevelIrp = 0; // // If we succeeded, see if we have to update FileSize ValidDataLength. // if (Status) { // // Set this handle as having modified the file // FileObject->Flags |= FO_FILE_MODIFIED; if (DoingIoAtEof) { // // Make sure Cc knows the current FileSize, as set above, // (we may not have changed it). // CcGetFileSizePointer(FileObject)->QuadPart = Header->FileSize.QuadPart; ExAcquireFastMutex( Header->FastMutex ); FileObject->Flags |= FO_FILE_SIZE_CHANGED; Header->ValidDataLength = NewFileSize; NtfsFinishIoAtEof( Header ); ExReleaseFastMutex( Header->FastMutex ); } goto Done1; } ErrOut: NOTHING; Status = FALSE; if (DoingIoAtEof) { ExAcquireFastMutex( Header->FastMutex ); Header->FileSize = OldFileSize; NtfsFinishIoAtEof( Header ); ExReleaseFastMutex( Header->FastMutex ); } Done1: NOTHING; // // For the Mdl case, we must keep the resource. // if ((MdlChain == NULL) || !Status) { ExReleaseResource( Header->PagingIoResource ); } FsRtlExitFileSystem(); } } else { // // We could not do the I/O now. // Status = FALSE; } return Status; } NTSTATUS NtfsCompressedCopyWrite ( IN PFILE_OBJECT FileObject, IN PLARGE_INTEGER FileOffset, IN ULONG Length, IN PVOID Buffer, OUT PMDL *MdlChain, IN PCOMPRESSED_DATA_INFO CompressedDataInfo, IN PDEVICE_OBJECT DeviceObject, IN PFSRTL_ADVANCED_FCB_HEADER Header, IN ULONG CompressionUnitSize, IN ULONG ChunkSize, IN ULONG EngineMatches ) { LONGLONG LocalOffset; ULONG CuCompressedSize, SizeToPin; PULONG NextChunkSize, TempChunkSize; PUCHAR CacheBuffer, EndOfCacheBuffer, ChunkBuffer, SavedBuffer; ULONG SavedLength; ULONG ClusterSize; PVOID MdlBuffer; ULONG MdlLength; BOOLEAN IsCompressed; NTSTATUS Status = STATUS_SUCCESS; PBCB Bcb = NULL; BOOLEAN FullOverwrite = FALSE; UNREFERENCED_PARAMETER( DeviceObject ); try { // // Get ready to loop through all of the compression units. // LocalOffset = FileOffset->QuadPart & ~(LONGLONG)(CompressionUnitSize - 1); Length = (Length + (ULONG)(FileOffset->QuadPart - LocalOffset) + ChunkSize - 1) & ~(ChunkSize - 1); ClusterSize = 1 << CompressedDataInfo->ClusterShift; NextChunkSize = &CompressedDataInfo->CompressedChunkSizes[0]; // // Loop through desired compression units // while (TRUE) { // // Determine whether or not this is a full overwrite of a // compression unit. // FullOverwrite = (LocalOffset >= Header->ValidDataLength.QuadPart) || ((LocalOffset >= FileOffset->QuadPart) && (Length >= CompressionUnitSize)); // // Calculate how much of current compression unit is being // written, uncompressed. // SavedLength = Length; if (SavedLength >= CompressionUnitSize) { SavedLength = CompressionUnitSize; } if (LocalOffset < FileOffset->QuadPart) { SavedLength -= (ULONG)(FileOffset->QuadPart - LocalOffset); } // // Loop to calculate sum of chunk sizes being written. // SizeToPin = 0; for (TempChunkSize = NextChunkSize; TempChunkSize < (NextChunkSize + (SavedLength >> CompressedDataInfo->ChunkShift)); TempChunkSize++ ) { SizeToPin += *TempChunkSize; } // // If this is not a full overwrite, get the current compression unit // size and make sure we pin at least that much. // if (!FullOverwrite) { NtfsFastIoQueryCompressedSize( FileObject, (PLARGE_INTEGER)&LocalOffset, &CuCompressedSize ); ASSERT( CuCompressedSize <= CompressionUnitSize ); if (CuCompressedSize > SizeToPin) { SizeToPin = CuCompressedSize; } } // // Possibly neither the new nor old data for this CompressionUnit is // nonzero. // if (SizeToPin != 0) { // // At this point we are ready to overwrite data in the compression // unit. See if the data is really compressed. // IsCompressed = (BOOLEAN)(((FullOverwrite && (SizeToPin <= (CompressionUnitSize - ClusterSize))) || (CuCompressedSize != CompressionUnitSize)) && EngineMatches); Status = STATUS_SUCCESS; // // Save current length in case we have to restart our work in // the uncompressed stream. // TempChunkSize = NextChunkSize; SavedLength = Length; SavedBuffer = Buffer; if (IsCompressed) { // // Map the compression unit in the compressed stream. // if (FullOverwrite) { // // If we are overwriting the entire compression unit, then // call CcPreparePinWrite so that empty pages may be used // instead of reading the file. Also force the byte count // to integral pages, so no one thinks we need to read the // last page. // CcPreparePinWrite( Header->FileObjectC, (PLARGE_INTEGER)&LocalOffset, (SizeToPin + PAGE_SIZE - 1) & ~(PAGE_SIZE - 1), FALSE, 3, // Wait + acquire resource exclusive! &Bcb, &CacheBuffer ); // // If it is a full overwrite, we need to initialize an empty // buffer. **** This is not completely correct, we otherwise // need a routine to initialize an empty compressed data buffer. // *(PULONG)CacheBuffer = 0; } else { CcPinRead( Header->FileObjectC, (PLARGE_INTEGER)&LocalOffset, SizeToPin, 3, // Wait + acquire resource exclusive! &Bcb, &CacheBuffer ); CcSetDirtyPinnedData( Bcb, NULL ); // // Now that the data is pinned (we are synchronized with the // CompressionUnit), we have to get the size again since it could // have changed. // NtfsFastIoQueryCompressedSize( FileObject, (PLARGE_INTEGER)&LocalOffset, &CuCompressedSize ); IsCompressed = (CuCompressedSize != CompressionUnitSize); ASSERT( CuCompressedSize <= CompressionUnitSize ); } EndOfCacheBuffer = Add2Ptr( CacheBuffer, CompressionUnitSize - ClusterSize ); MdlLength = 0; // // Now loop through desired chunks (if it is still compressed) // if (IsCompressed) do { // // We may not have reached the first chunk yet. // if (LocalOffset >= FileOffset->QuadPart) { // // Reserve space for the current chunk. // Status = RtlReserveChunk( CompressedDataInfo->CompressionFormatAndEngine, &CacheBuffer, EndOfCacheBuffer, &ChunkBuffer, *TempChunkSize ); if (!NT_SUCCESS(Status)) { break; } // // If the caller wants an MdlChain, then handle the Mdl // processing here. // if (MdlChain != NULL) { // // If we have not started remembering an Mdl buffer, // then do so now. // if (MdlLength == 0) { MdlBuffer = ChunkBuffer; // // Otherwise we just have to increase the length // and check for an uncompressed chunk, because that // forces us to emit the previous Mdl since we do // not transmit the chunk header in this case. // } else { // // In the rare case that we hit an individual chunk // that did not compress, we have to emit what we // had (which captures the Bcb pointer), and start // a new Mdl buffer. // if (*TempChunkSize == ChunkSize) { NtfsAddToCompressedMdlChain( MdlChain, MdlBuffer, MdlLength, Bcb, IoWriteAccess ); Bcb = NULL; MdlBuffer = ChunkBuffer; MdlLength = 0; } } MdlLength += *TempChunkSize; // // Else copy next chunk (compressed or not). // } else { RtlCopyBytes( ChunkBuffer, Buffer, *TempChunkSize ); // // Advance input buffer by bytes copied. // Buffer = (PCHAR)Buffer + *TempChunkSize; } TempChunkSize += 1; // // Reduce length by chunk copied, and check if we are done. // if (Length > ChunkSize) { Length -= ChunkSize; } else { goto Done; } // // If we are skipping over a nonexistant chunk, then we have // to reserve a chunk of zeros. // } else { // // If we have not reached our chunk, then describe the current // chunke in order to skip over it. // Status = RtlDescribeChunk( CompressedDataInfo->CompressionFormatAndEngine, &CacheBuffer, EndOfCacheBuffer, &ChunkBuffer, TempChunkSize ); // // If there is not current chunk, we must insert a chunk of zeros. // if (Status == STATUS_NO_MORE_ENTRIES) { Status = RtlReserveChunk( CompressedDataInfo->CompressionFormatAndEngine, &CacheBuffer, EndOfCacheBuffer, &ChunkBuffer, 0 ); if (!NT_SUCCESS(Status)) { ASSERT(NT_SUCCESS(Status)); break; } // // Get out if we got some other kind of unexpected error. // } else if (!NT_SUCCESS(Status)) { ASSERT(NT_SUCCESS(Status)); break; } } LocalOffset += ChunkSize; } while ((LocalOffset & (CompressionUnitSize - 1)) != 0); // // If this is an Mdl call, then it is time to add to the MdlChain // before moving to the next view. // if (MdlLength != 0) { NtfsAddToCompressedMdlChain( MdlChain, MdlBuffer, MdlLength, Bcb, IoWriteAccess ); Bcb = NULL; MdlLength = 0; } } // // Uncompressed loop. // if (!IsCompressed || !NT_SUCCESS(Status)) { // // If we get here for an Mdl request, just tell him to send // it uncompressed! // if (MdlChain != NULL) { if (NT_SUCCESS(Status)) { Status = STATUS_BUFFER_OVERFLOW; } goto Done; // // If we are going to write the uncompressed stream, // we have to make sure it is there. // } else if (FileObject->PrivateCacheMap == NULL) { Status = STATUS_NOT_MAPPED_DATA; goto Done; } // // Restore sizes and pointers to the beginning of the // current compression unit, and we will handle the // data uncompressed. // LocalOffset -= SavedLength - Length; Length = SavedLength; Buffer = SavedBuffer; TempChunkSize = NextChunkSize; // // We may have a Bcb from the above loop to unpin. // Then we must flush and purge the compressed // stream before proceding. // if (Bcb != NULL) { CcUnpinData(Bcb); Bcb = NULL; } // // We must first flush and purge the compressed stream // since we will be writing into the uncompressed stream. // The flush is actually only necessary if we are not doing // a full overwrite anyway. // if (!FullOverwrite) { CcFlushCache( Header->FileObjectC->SectionObjectPointer, (PLARGE_INTEGER)&LocalOffset, CompressionUnitSize, NULL ); } CcPurgeCacheSection( Header->FileObjectC->SectionObjectPointer, (PLARGE_INTEGER)&LocalOffset, CompressionUnitSize, FALSE ); // // If LocalOffset was rounded down to a compression // unit boundary (must have failed in the first // compression unit), then start from the actual // starting FileOffset. // if (LocalOffset < FileOffset->QuadPart) { Length -= (ULONG)(FileOffset->QuadPart - LocalOffset); LocalOffset = FileOffset->QuadPart; } // // Map the compression unit in the uncompressed // stream. // CcPinRead( FileObject, (PLARGE_INTEGER)&LocalOffset, (Length < CompressionUnitSize) ? Length : CompressionUnitSize, TRUE, &Bcb, &CacheBuffer ); CcSetDirtyPinnedData( Bcb, NULL ); // // Now loop through desired chunks // do { // // If this chunk is compressed, then decompress it // into the cache. // if (*TempChunkSize != ChunkSize) { Status = RtlDecompressBuffer( CompressedDataInfo->CompressionFormatAndEngine, CacheBuffer, ChunkSize, Buffer, *TempChunkSize, &SavedLength ); // // See if the data is ok. // if (!NT_SUCCESS(Status)) { ASSERT(NT_SUCCESS(Status)); goto Done; } // // Zero to the end of the chunk if it was not all there. // if (SavedLength != ChunkSize) { RtlZeroMemory( Add2Ptr(CacheBuffer, SavedLength), ChunkSize - SavedLength ); } } else { // // Copy next chunk (it's not compressed). // RtlCopyBytes( CacheBuffer, Buffer, ChunkSize ); } // // Advance input buffer by bytes copied. // Buffer = (PCHAR)Buffer + *TempChunkSize; CacheBuffer = (PCHAR)CacheBuffer + ChunkSize; TempChunkSize += 1; // // Reduce length by chunk copied, and check if we are done. // if (Length > ChunkSize) { Length -= ChunkSize; } else { goto Done; } LocalOffset += ChunkSize; } while ((LocalOffset & (CompressionUnitSize - 1)) != 0); CcUnpinData(Bcb); Bcb = NULL; } } } Done: NOTHING; if ((MdlLength != 0) && NT_SUCCESS(Status)) { NtfsAddToCompressedMdlChain( MdlChain, MdlBuffer, MdlLength, Bcb, IoWriteAccess ); Bcb = NULL; } } except( FsRtlIsNtstatusExpected((Status = GetExceptionCode())) ? EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH ) { NOTHING; } // // Unpin the Bcb if we still have it. // if (Bcb != NULL) { CcUnpinData(Bcb); } // // On error, cleanup any MdlChain we built up // if (!NT_SUCCESS(Status) && (MdlChain != NULL)) { NtfsCleanupCompressedMdlChain( MdlChain, TRUE ); // // Change owner Id for the Scb and Bcbs we are holding. // } else { NtfsSetMdlBcbOwners( *MdlChain ); ExSetResourceOwnerPointer( Header->PagingIoResource, (PVOID)((PCHAR)*MdlChain + 3) ); } return Status; } BOOLEAN NtfsMdlWriteCompleteCompressed ( IN struct _FILE_OBJECT *FileObject, IN PLARGE_INTEGER FileOffset, IN PMDL MdlChain, IN struct _DEVICE_OBJECT *DeviceObject ) { PFSRTL_ADVANCED_FCB_HEADER Header; UNREFERENCED_PARAMETER( DeviceObject ); UNREFERENCED_PARAMETER( FileOffset ); NtfsCleanupCompressedMdlChain( &MdlChain, FALSE ); // // Get a real pointer to the common fcb header // Header = (PFSRTL_ADVANCED_FCB_HEADER)FileObject->FsContext; ExReleaseResourceForThread( Header->PagingIoResource, (ERESOURCE_THREAD)((PCHAR)MdlChain + 3) ); return TRUE; } VOID NtfsAddToCompressedMdlChain ( IN OUT PMDL *MdlChain, IN PVOID MdlBuffer, IN ULONG MdlLength, IN PBCB Bcb, IN LOCK_OPERATION Operation ) { PMDL Mdl, MdlTemp; ULONG SavedState; ASSERT(sizeof(ULONG) == sizeof(PBCB)); // // Now attempt to allocate an Mdl to describe the mapped data. // We "lie" about the length of the buffer by one page, in order // to get an extra field to store a pointer to the Bcb in. // Mdl = IoAllocateMdl( MdlBuffer, (MdlLength + PAGE_SIZE), FALSE, FALSE, NULL ); if (Mdl == NULL) { ExRaiseStatus( STATUS_INSUFFICIENT_RESOURCES ); } // // Now subtract out the space we reserved for our Bcb pointer // and then store it. // Mdl->Size -= sizeof(ULONG); Mdl->ByteCount -= PAGE_SIZE; *(PBCB *)Add2Ptr(Mdl, Mdl->Size) = Bcb; // // Note that this probe should never fail, because we can // trust the address returned from CcPinFileData. Therefore, // if we succeed in allocating the Mdl above, we should // manage to elude any expected exceptions through the end // of this loop. // MmDisablePageFaultClustering(&SavedState); MmProbeAndLockPages( Mdl, KernelMode, Operation ); MmEnablePageFaultClustering(SavedState); // // Now link the Mdl into the caller's chain // if ( *MdlChain == NULL ) { *MdlChain = Mdl; } else { MdlTemp = CONTAINING_RECORD( *MdlChain, MDL, Next ); while (MdlTemp->Next != NULL) { MdlTemp = MdlTemp->Next; } MdlTemp->Next = Mdl; } } VOID NtfsSetMdlBcbOwners ( IN PMDL MdlChain ) { PBCB Bcb; while (MdlChain != NULL) { // // Unpin the Bcb we saved away, and restore the Mdl counts // we altered. // Bcb = *(PBCB *)Add2Ptr(MdlChain, MdlChain->Size); CcSetBcbOwnerPointer( Bcb, (PVOID)((PCHAR)MdlChain + 3) ); MdlChain = MdlChain->Next; } } VOID NtfsCleanupCompressedMdlChain ( IN OUT PMDL *MdlChain, IN ULONG Error ) { PMDL MdlTemp; PBCB Bcb; while (*MdlChain != NULL) { // // Save a pointer to the next guy in the chain. // MdlTemp = (*MdlChain)->Next; // // Unlock the pages. // MmUnlockPages( *MdlChain ); // // Unpin the Bcb we saved away, and restore the Mdl counts // we altered. // Bcb = *(PBCB *)Add2Ptr((*MdlChain), (*MdlChain)->Size); if (Bcb != NULL) { if (Error) { CcUnpinData( Bcb ); } else { CcUnpinDataForThread( Bcb, (ERESOURCE_THREAD)((PCHAR)*MdlChain + 3) ); } } (*MdlChain)->Size += sizeof(ULONG); (*MdlChain)->ByteCount += PAGE_SIZE; IoFreeMdl( *MdlChain ); *MdlChain = MdlTemp; } }