1822 lines
58 KiB
C
1822 lines
58 KiB
C
/*++
|
||
|
||
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;
|
||
}
|
||
}
|
||
|
||
|