mirror of https://github.com/lianthony/NT4.0
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1821 lines
58 KiB
1821 lines
58 KiB
/*++
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
|