NT4/private/ntos/cntfs/rwcmpsup.c
2020-09-30 17:12:29 +02:00

1822 lines
58 KiB
C
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*++
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;
}
}