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.
3460 lines
113 KiB
3460 lines
113 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 PERESOURCE ResourceToRelease OPTIONAL,
|
|
IN PBCB Bcb,
|
|
IN LOCK_OPERATION Operation,
|
|
IN ULONG IsCompressed
|
|
);
|
|
|
|
VOID
|
|
NtfsSetMdlBcbOwners (
|
|
IN PMDL MdlChain
|
|
);
|
|
|
|
VOID
|
|
NtfsCleanupCompressedMdlChain (
|
|
IN PMDL MdlChain,
|
|
IN ULONG Error
|
|
);
|
|
|
|
#ifdef NTFS_RWC_DEBUG
|
|
|
|
PRWC_HISTORY_ENTRY
|
|
NtfsGetHistoryEntry (
|
|
IN PSCB Scb
|
|
);
|
|
|
|
#ifdef ALLOC_PRAGMA
|
|
#pragma alloc_text(PAGE, NtfsGetHistoryEntry)
|
|
#endif
|
|
|
|
#define CACHE_NTC_BCB (0x2FD)
|
|
#define CACHE_NTC_OBCB (0x2FA)
|
|
|
|
typedef struct _OBCB {
|
|
|
|
//
|
|
// Type and size of this record
|
|
//
|
|
|
|
CSHORT NodeTypeCode;
|
|
CSHORT NodeByteSize;
|
|
|
|
//
|
|
// Byte FileOffset and and length of entire buffer
|
|
//
|
|
|
|
ULONG ByteLength;
|
|
LARGE_INTEGER FileOffset;
|
|
|
|
//
|
|
// Vector of Bcb pointers.
|
|
//
|
|
|
|
PPUBLIC_BCB Bcbs[ANYSIZE_ARRAY];
|
|
|
|
} OBCB;
|
|
typedef OBCB *POBCB;
|
|
|
|
PRWC_HISTORY_ENTRY
|
|
NtfsGetHistoryEntry (
|
|
IN PSCB Scb
|
|
)
|
|
{
|
|
ULONG NextIndex;
|
|
|
|
PAGED_CODE();
|
|
|
|
//
|
|
// Store and entry in the history buffer.
|
|
//
|
|
|
|
if (Scb->ScbType.Data.HistoryBuffer == NULL) {
|
|
|
|
PVOID NewBuffer;
|
|
|
|
NewBuffer = NtfsAllocatePool( PagedPool,
|
|
sizeof( RWC_HISTORY_ENTRY ) * MAX_RWC_HISTORY_INDEX );
|
|
|
|
RtlZeroMemory( NewBuffer, sizeof( RWC_HISTORY_ENTRY ) * MAX_RWC_HISTORY_INDEX );
|
|
NtfsAcquireFsrtlHeader( Scb );
|
|
|
|
if (Scb->ScbType.Data.HistoryBuffer == NULL) {
|
|
|
|
Scb->ScbType.Data.HistoryBuffer = NewBuffer;
|
|
|
|
} else {
|
|
|
|
NtfsFreePool( NewBuffer );
|
|
}
|
|
|
|
NtfsReleaseFsrtlHeader( Scb );
|
|
}
|
|
|
|
NextIndex = InterlockedIncrement( &Scb->ScbType.Data.RwcIndex );
|
|
if (NextIndex >= MAX_RWC_HISTORY_INDEX) {
|
|
|
|
NextIndex = 0;
|
|
InterlockedExchange( &Scb->ScbType.Data.RwcIndex, 0);
|
|
}
|
|
|
|
return Scb->ScbType.Data.HistoryBuffer + NextIndex;
|
|
}
|
|
#endif
|
|
|
|
#ifdef ALLOC_PRAGMA
|
|
#pragma alloc_text(PAGE, NtfsCopyReadC)
|
|
#pragma alloc_text(PAGE, NtfsCompressedCopyRead)
|
|
#pragma alloc_text(PAGE, NtfsCopyWriteC)
|
|
#pragma alloc_text(PAGE, NtfsCompressedCopyWrite)
|
|
#pragma alloc_text(PAGE, NtfsAddToCompressedMdlChain)
|
|
#pragma alloc_text(PAGE, NtfsSetMdlBcbOwners)
|
|
#pragma alloc_text(PAGE, NtfsSynchronizeCompressedIo)
|
|
#pragma alloc_text(PAGE, NtfsSynchronizeUncompressedIo)
|
|
#pragma alloc_text(PAGE, NtfsAcquireCompressionSync)
|
|
#pragma alloc_text(PAGE, NtfsReleaseCompressionSync)
|
|
#endif
|
|
|
|
#ifdef NTFS_RWCMP_TRACE
|
|
ULONG NtfsCompressionTrace = 0;
|
|
#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.
|
|
|
|
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.
|
|
|
|
DeviceObject - Standard Fast I/O Device object input.
|
|
|
|
Return Value:
|
|
|
|
FALSE - if the data was not delivered for any reason
|
|
|
|
TRUE - if the data is being delivered
|
|
|
|
--*/
|
|
|
|
{
|
|
PNTFS_ADVANCED_FCB_HEADER Header;
|
|
LONGLONG LocalOffset;
|
|
PFAST_IO_DISPATCH FastIoDispatch;
|
|
FILE_COMPRESSION_INFORMATION CompressionInformation;
|
|
ULONG CompressionUnitSize, ChunkSize;
|
|
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));
|
|
|
|
//
|
|
// Get out immediately if COW is not supported.
|
|
//
|
|
|
|
if (!NtfsEnableCompressedIO) { return FALSE; }
|
|
|
|
//
|
|
// 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 = (PNTFS_ADVANCED_FCB_HEADER)FileObject->FsContext;
|
|
|
|
#ifdef NTFS_RWCMP_TRACE
|
|
if (NtfsCompressionTrace && IsSyscache(Header)) {
|
|
DbgPrint("NtfsCopyReadC: FO = %08lx, Len = %08lx\n", FileOffset->LowPart, Length );
|
|
}
|
|
#endif
|
|
|
|
//
|
|
// 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 = ExAcquireResourceSharedLite( 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 );
|
|
|
|
//
|
|
// Set the Flag if we are in fact beyond ValidDataLength.
|
|
//
|
|
|
|
if (DoingIoAtEof) {
|
|
SetFlag( Header->Flags, FSRTL_FLAG_EOF_ADVANCE_ACTIVE );
|
|
|
|
#if (DBG || defined( NTFS_FREE_ASSERTS ))
|
|
((PSCB) Header)->IoAtEofThread = (PERESOURCE_THREAD) ExGetCurrentResourceThread();
|
|
|
|
} else {
|
|
|
|
ASSERT( ((PSCB) Header)->IoAtEofThread != (PERESOURCE_THREAD) ExGetCurrentResourceThread() );
|
|
#endif
|
|
}
|
|
}
|
|
|
|
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) {
|
|
|
|
//
|
|
// 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
|
|
//
|
|
|
|
IoSetTopLevelIrp( (PIRP) FSRTL_FAST_IO_TOP_LEVEL_IRP );
|
|
|
|
if (NT_SUCCESS(IoStatus->Status)) {
|
|
|
|
//
|
|
// Don't do the sychronize flush if we currently own Eof. The recursive
|
|
// flush may try to reacquire.
|
|
//
|
|
|
|
if (DoingIoAtEof &&
|
|
(((PSCB)Header)->NonpagedScb->SegmentObject.DataSectionObject != NULL)) {
|
|
|
|
IoStatus->Status = STATUS_FILE_LOCK_CONFLICT;
|
|
|
|
} else {
|
|
|
|
IoStatus->Status = NtfsCompressedCopyRead( FileObject,
|
|
FileOffset,
|
|
Length,
|
|
Buffer,
|
|
MdlChain,
|
|
CompressedDataInfo,
|
|
CompressedDataInfoLength,
|
|
DeviceObject,
|
|
Header,
|
|
CompressionUnitSize,
|
|
ChunkSize );
|
|
}
|
|
}
|
|
|
|
Status = (BOOLEAN)NT_SUCCESS(IoStatus->Status);
|
|
|
|
|
|
IoSetTopLevelIrp( NULL );
|
|
|
|
Done: NOTHING;
|
|
|
|
if (DoingIoAtEof) {
|
|
ExAcquireFastMutex( Header->FastMutex );
|
|
NtfsFinishIoAtEof( Header );
|
|
ExReleaseFastMutex( Header->FastMutex );
|
|
}
|
|
|
|
//
|
|
// For the Mdl case, we must keep the resource unless
|
|
// we are past the end of the file or had nothing to write.
|
|
//
|
|
|
|
if ((MdlChain == NULL) || !Status || (*MdlChain == NULL)) {
|
|
ExReleaseResourceLite( Header->PagingIoResource );
|
|
}
|
|
|
|
FsRtlExitFileSystem();
|
|
}
|
|
|
|
#ifdef NTFS_RWCMP_TRACE
|
|
if (NtfsCompressionTrace && IsSyscache(Header)) {
|
|
DbgPrint("Return Status = %08lx\n", Status);
|
|
}
|
|
#endif
|
|
|
|
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 PNTFS_ADVANCED_FCB_HEADER Header,
|
|
IN ULONG CompressionUnitSize,
|
|
IN ULONG ChunkSize
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This is a common routine for doing compressed copy or Mdl reads in
|
|
the compressed stream. It is called both by the FastIo entry for
|
|
this function, as well as by read.c if a compressed read Irp is received.
|
|
The caller must be correctly synchronized for the stream.
|
|
|
|
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.
|
|
|
|
CompressedDataInfo - Returns compressed data info with compressed chunk
|
|
sizes
|
|
|
|
CompressedDataInfoLength - Supplies the size of the info buffer in bytes.
|
|
|
|
DeviceObject - Standard Fast I/O Device object input.
|
|
|
|
Header - Pointer to FsRtl header for file (also is our Scb)
|
|
|
|
CompressionUnitSize - Size of Compression Unit in bytes.
|
|
|
|
ChunkSize - ChunkSize in bytes.
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS for operation. If STATUS_NOT_MAPPED_USER_DATA, then the caller
|
|
should map the normal uncompressed data stream and call back.
|
|
|
|
--*/
|
|
|
|
{
|
|
PFILE_OBJECT LocalFileObject;
|
|
PULONG NextReturnChunkSize;
|
|
PUCHAR CompressedBuffer, EndOfCompressedBuffer, ChunkBuffer, StartOfCompressionUnit;
|
|
LONGLONG LocalOffset;
|
|
ULONG CuCompressedSize;
|
|
PVOID MdlBuffer;
|
|
ULONG MdlLength;
|
|
ULONG PinFlags;
|
|
BOOLEAN IsCompressed;
|
|
BOOLEAN LastCompressionUnit;
|
|
NTSTATUS Status = STATUS_SUCCESS;
|
|
PCOMPRESSION_SYNC CompressionSync = NULL;
|
|
PBCB Bcb = NULL;
|
|
PBCB UncompressedBcb = NULL;
|
|
|
|
ULONG ClusterSize = ((PSCB)Header)->Vcb->BytesPerCluster;
|
|
|
|
#ifdef NTFS_RWC_DEBUG
|
|
PRWC_HISTORY_ENTRY ReadHistoryBuffer;
|
|
#endif
|
|
|
|
UNREFERENCED_PARAMETER( CompressedDataInfoLength );
|
|
UNREFERENCED_PARAMETER( DeviceObject );
|
|
|
|
ASSERT(CompressedDataInfoLength >= (sizeof(COMPRESSED_DATA_INFO) +
|
|
(((Length >> CompressedDataInfo->ChunkShift) - 1) *
|
|
sizeof(ULONG))));
|
|
ASSERT((FileOffset->QuadPart & (ChunkSize - 1)) == 0);
|
|
ASSERT((((FileOffset->QuadPart + Length) & (ChunkSize - 1)) == 0) ||
|
|
((FileOffset->QuadPart + Length) == Header->FileSize.QuadPart));
|
|
ASSERT((MdlChain == NULL) || (*MdlChain == NULL));
|
|
|
|
//
|
|
// if we start after vdl we will never pin the compressed buffer
|
|
//
|
|
|
|
ASSERT( FileOffset->QuadPart < Header->ValidDataLength.QuadPart );
|
|
|
|
//
|
|
// Return an error if the file is not compressed.
|
|
//
|
|
|
|
if (((PSCB)Header)->CompressionUnit == 0) {
|
|
return STATUS_UNSUPPORTED_COMPRESSION;
|
|
}
|
|
|
|
#ifdef NTFS_RWCMP_TRACE
|
|
if (NtfsCompressionTrace && IsSyscache(Header)) {
|
|
DbgPrint(" CompressedCopyRead: FO = %08lx, Len = %08lx\n", FileOffset->LowPart, Length );
|
|
}
|
|
#endif
|
|
|
|
#ifdef NTFS_RWC_DEBUG
|
|
if ((FileOffset->QuadPart < NtfsRWCHighThreshold) &&
|
|
(FileOffset->QuadPart + Length > NtfsRWCLowThreshold)) {
|
|
|
|
PRWC_HISTORY_ENTRY NextBuffer;
|
|
|
|
ReadHistoryBuffer =
|
|
NextBuffer = NtfsGetHistoryEntry( (PSCB) Header );
|
|
|
|
NextBuffer->Operation = StartOfRead;
|
|
NextBuffer->Information = Header->ValidDataLength.LowPart;
|
|
NextBuffer->FileOffset = (ULONG) FileOffset->QuadPart;
|
|
NextBuffer->Length = (ULONG) Length;
|
|
}
|
|
#endif
|
|
|
|
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);
|
|
|
|
NextReturnChunkSize = &CompressedDataInfo->CompressedChunkSizes[0];
|
|
|
|
//
|
|
// Loop through desired compression units
|
|
//
|
|
|
|
while (TRUE) {
|
|
|
|
//
|
|
// Free any Bcb from previous loop.
|
|
//
|
|
|
|
if (Bcb != NULL) {
|
|
|
|
ASSERT( (UncompressedBcb == NULL) ||
|
|
(UncompressedBcb == Bcb ) );
|
|
|
|
CcUnpinData( Bcb );
|
|
UncompressedBcb = Bcb = NULL;
|
|
|
|
} else if (UncompressedBcb != NULL) {
|
|
|
|
CcUnpinData( UncompressedBcb );
|
|
UncompressedBcb = NULL;
|
|
}
|
|
|
|
//
|
|
// If there is an uncompressed stream, then we have to synchronize with that.
|
|
//
|
|
|
|
if (((PSCB)Header)->NonpagedScb->SegmentObject.DataSectionObject != NULL) {
|
|
|
|
Status = NtfsSynchronizeCompressedIo( (PSCB)Header,
|
|
&LocalOffset,
|
|
Length,
|
|
FALSE,
|
|
&CompressionSync );
|
|
|
|
if (!NT_SUCCESS(Status)) {
|
|
ASSERT( Status == STATUS_USER_MAPPED_FILE );
|
|
leave;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Loop to get the correct data pinned.
|
|
//
|
|
// The synchronize call above has guaranteed that no data can written through
|
|
// the uncompressed section (barring the loop back below), and it has also flushed
|
|
// any dirty data that may have already been in the uncompressed section. Here we
|
|
// are basically trying to figure out how much data we should pin and then get it
|
|
// pinned.
|
|
//
|
|
// We use the following steps:
|
|
//
|
|
// 1. Query the current compressed size (derived from the allocation state).
|
|
// If the size is neither 0-allocated nor fully allocated, then we will
|
|
// simply pin the data in the compressed section - this is the normal case.
|
|
// 2. However, the first time we see one of these special sizes, we do not
|
|
// know if could be the case that there is dirty data sitting in the compressed
|
|
// cache. Therefore, we set up to pin just one page with PIN_IF_BCB. This
|
|
// will only pin something if there is aleady a Bcb for it.
|
|
// 3. Now we determine if we think the data is compressed or not, counting the
|
|
// special cases from the previous point as compressed. This determines
|
|
// which section to read from.
|
|
// 4. Now, if we think there is/may be data to pin, we call Cc. If he comes
|
|
// back with no data (only possible if we set PIN_IF_BCB), then we know we
|
|
// can loop back to the top and trust the allocation state on disk now.
|
|
// (That is because we flushed the uncompressed stream and found no Bcb in
|
|
// the compressed stream.) On the second time through we should correctly
|
|
// handle the 0-allocated or fully-allocated cases. (The careful reader
|
|
// will note that if there is no uncompressed section, then indeed writers
|
|
// to the compressed section could be going on in parallel with this read,
|
|
// and we could handle the 0- or fully-allocated case while there is
|
|
// new compressed data in the cache. However on the second loop we know
|
|
// there really was all 0's in the file at one point which it is correct
|
|
// to return, and it is always correct to go to the uncompressed cache
|
|
// if we still see fully-allocated. More importantly, we have an
|
|
// unsynchronized reader and writer, and the reader's result is therefore
|
|
// nondeterministic anyway.
|
|
//
|
|
|
|
PinFlags = PIN_WAIT;
|
|
|
|
do {
|
|
|
|
//
|
|
// If we are beyond ValidDataLength, then the CompressedSize is 0!
|
|
//
|
|
|
|
if (LocalOffset >= Header->ValidDataLength.QuadPart) {
|
|
|
|
CuCompressedSize = 0;
|
|
ClearFlag( PinFlags, PIN_IF_BCB );
|
|
|
|
//
|
|
// Otherwise query the compressed size.
|
|
//
|
|
|
|
} else {
|
|
|
|
NtfsFastIoQueryCompressedSize( FileObject,
|
|
(PLARGE_INTEGER)&LocalOffset,
|
|
&CuCompressedSize );
|
|
|
|
//
|
|
// If it looks uncompressed, we are probably trying to read data
|
|
// that has not been written out yet. Also if the space is not yet
|
|
// allocated, then we also need to try to hit the data in the compressed
|
|
// cache.
|
|
//
|
|
|
|
if (((CuCompressedSize == CompressionUnitSize) || (CuCompressedSize == 0)) &&
|
|
!FlagOn(PinFlags, PIN_IF_BCB)) {
|
|
|
|
CuCompressedSize = 0x1000;
|
|
SetFlag( PinFlags, PIN_IF_BCB );
|
|
|
|
//
|
|
// Make sure we really read the data if this is the second time through.
|
|
//
|
|
|
|
} else {
|
|
|
|
//
|
|
// If the range is dirty and there is no Bcb in the compressed stream
|
|
// then always go to the uncompressed stream.
|
|
//
|
|
|
|
if (FlagOn( PinFlags, PIN_IF_BCB ) &&
|
|
(CuCompressedSize != CompressionUnitSize)) {
|
|
|
|
LONGLONG ClusterCount = 1 << ((PSCB) Header)->CompressionUnitShift;
|
|
|
|
if (NtfsCheckForReservedClusters( (PSCB) Header,
|
|
LlClustersFromBytesTruncate( ((PSCB) Header)->Vcb, LocalOffset ),
|
|
&ClusterCount )) {
|
|
|
|
CuCompressedSize = CompressionUnitSize;
|
|
}
|
|
}
|
|
|
|
ClearFlag( PinFlags, PIN_IF_BCB );
|
|
}
|
|
}
|
|
|
|
ASSERT( CuCompressedSize <= CompressionUnitSize );
|
|
IsCompressed = (BOOLEAN)((CuCompressedSize != CompressionUnitSize) &&
|
|
(CompressedDataInfo->CompressionFormatAndEngine != 0));
|
|
|
|
//
|
|
// Figure out which FileObject to use.
|
|
//
|
|
|
|
LocalFileObject = Header->FileObjectC;
|
|
if (!IsCompressed) {
|
|
LocalFileObject = ((PSCB)Header)->FileObject;
|
|
if (LocalFileObject == NULL) {
|
|
Status = STATUS_NOT_MAPPED_DATA;
|
|
goto Done;
|
|
}
|
|
}
|
|
|
|
//
|
|
// If the compression unit is not (yet) allocated, then there is
|
|
// no need to synchronize - we will return 0-lengths for chunk sizes.
|
|
//
|
|
|
|
if (CuCompressedSize != 0) {
|
|
|
|
//
|
|
// Map the compression unit in the compressed or uncompressed
|
|
// stream.
|
|
//
|
|
|
|
CcPinRead( LocalFileObject,
|
|
(PLARGE_INTEGER)&LocalOffset,
|
|
CuCompressedSize,
|
|
PinFlags,
|
|
&Bcb,
|
|
&CompressedBuffer );
|
|
|
|
//
|
|
// If there is no Bcb it means we were assuming the data was in
|
|
// the compressed buffer and only wanted to wait if it was
|
|
// present. Well it isn't there so force ourselved to go
|
|
// back and look in the uncompressed section.
|
|
//
|
|
|
|
if (Bcb == NULL) {
|
|
|
|
ASSERT( FlagOn( PinFlags, PIN_IF_BCB ));
|
|
continue;
|
|
}
|
|
|
|
//
|
|
// 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) {
|
|
|
|
//
|
|
// Now, we know the data where we are about to read is compressed,
|
|
// but we cannot really tell for sure how big it is since there may
|
|
// be dirty data in the cache.
|
|
//
|
|
// We will say the size is (CompressionUnitSize - ClusterSize)
|
|
// which is the largest possible compressed size, and we will normally
|
|
// just hit on the existing dirty Bcb and/or resident pages anyway.
|
|
// (If we do not, then we will just fault those pages in one at a
|
|
// time anyway. This looks bad having to do this twice, but it
|
|
// is only until the dirty data finally gets flushed out.) This also
|
|
// means we may walk off the range we pinned in a read-only mode, but
|
|
// that should be benign.
|
|
//
|
|
// Of course in the main line case, we figured out exactly how much
|
|
// data to read in and we did so when we pinned it above.
|
|
//
|
|
|
|
CuCompressedSize = CompressionUnitSize - ClusterSize;
|
|
|
|
//
|
|
// Otherwise remember to release this Bcb.
|
|
//
|
|
|
|
} else {
|
|
|
|
UncompressedBcb = Bcb;
|
|
}
|
|
}
|
|
|
|
} while ((Bcb == NULL) && (CuCompressedSize != 0));
|
|
|
|
//
|
|
// Now that we are synchronized with the buffer, see if someone snuck
|
|
// in behind us and created the noncached stream since we last checked
|
|
// for that stream. If so we have to loop back to synchronize with the
|
|
// compressed stream again.
|
|
//
|
|
|
|
if ((CompressionSync == NULL) &&
|
|
(((PSCB)Header)->NonpagedScb->SegmentObject.DataSectionObject != NULL)) {
|
|
|
|
continue;
|
|
}
|
|
|
|
EndOfCompressedBuffer = Add2Ptr( CompressedBuffer, CuCompressedSize );
|
|
StartOfCompressionUnit = CompressedBuffer;
|
|
|
|
//
|
|
// Remember if we may go past the end of the file.
|
|
//
|
|
|
|
LastCompressionUnit = FALSE;
|
|
|
|
if (LocalOffset + CuCompressedSize > Header->FileSize.QuadPart) {
|
|
|
|
LastCompressionUnit = TRUE;
|
|
}
|
|
|
|
//
|
|
// Now loop through desired chunks
|
|
//
|
|
|
|
MdlLength = 0;
|
|
|
|
do {
|
|
|
|
//
|
|
// Assume current chunk does not compress, else get current
|
|
// chunk size.
|
|
//
|
|
|
|
if (IsCompressed) {
|
|
|
|
if (CuCompressedSize != 0) {
|
|
|
|
PUCHAR PrevCompressedBuffer;
|
|
|
|
//
|
|
// We have to do a careful check to see if the return value is
|
|
// greater than or equal to the chunk size AND the data is in fact
|
|
// compressed. We don't have anyway to pass this data back to
|
|
// the server so he can interpret it correctly.
|
|
//
|
|
|
|
PrevCompressedBuffer = CompressedBuffer;
|
|
|
|
Status = RtlDescribeChunk( CompressedDataInfo->CompressionFormatAndEngine,
|
|
&CompressedBuffer,
|
|
EndOfCompressedBuffer,
|
|
&ChunkBuffer,
|
|
NextReturnChunkSize );
|
|
|
|
if (!NT_SUCCESS(Status) && (Status != STATUS_NO_MORE_ENTRIES)) {
|
|
ExRaiseStatus(Status);
|
|
}
|
|
|
|
//
|
|
// If the size is greater or equal to the chunk size AND the data is compressed
|
|
// then force this to the uncompressed path. Note that the Rtl package has
|
|
// been changed so that this case shouldn't happen on new disks but it is
|
|
// possible that it could exist on exiting disks.
|
|
//
|
|
|
|
if ((*NextReturnChunkSize >= ChunkSize) &&
|
|
(PrevCompressedBuffer == ChunkBuffer)) {
|
|
|
|
//
|
|
// Raise an error code that causes the server to reissue in
|
|
// the uncompressed path.
|
|
//
|
|
|
|
ExRaiseStatus( STATUS_UNSUPPORTED_COMPRESSION );
|
|
}
|
|
|
|
//
|
|
// Another unusual case is where the compressed data extends past the containing
|
|
// file size. We don't have anyway to prevent the next page from being zeroed.
|
|
// Ask the server to go the uncompressed path.
|
|
//
|
|
|
|
if (LastCompressionUnit) {
|
|
|
|
LONGLONG EndOfPage;
|
|
|
|
EndOfPage = LocalOffset + PtrOffset( StartOfCompressionUnit, CompressedBuffer ) + PAGE_SIZE - 1;
|
|
((PLARGE_INTEGER) &EndOfPage)->LowPart &= ~(PAGE_SIZE - 1);
|
|
|
|
if (EndOfPage > Header->FileSize.QuadPart) {
|
|
|
|
//
|
|
// Raise an error code that causes the server to reissue in
|
|
// the uncompressed path.
|
|
//
|
|
|
|
ExRaiseStatus( STATUS_UNSUPPORTED_COMPRESSION );
|
|
}
|
|
}
|
|
|
|
ASSERT( *NextReturnChunkSize <= ChunkSize );
|
|
|
|
//
|
|
// If the entire CompressionUnit is empty, do this.
|
|
//
|
|
|
|
} else {
|
|
*NextReturnChunkSize = 0;
|
|
#ifdef NTFS_RWC_DEBUG
|
|
if ((LocalOffset < NtfsRWCHighThreshold) &&
|
|
(LocalOffset + CompressionUnitSize > NtfsRWCLowThreshold)) {
|
|
|
|
PRWC_HISTORY_ENTRY NextBuffer;
|
|
|
|
NextBuffer = NtfsGetHistoryEntry( (PSCB) Header );
|
|
|
|
NextBuffer->Operation = ReadZeroes;
|
|
NextBuffer->Information = 0;
|
|
NextBuffer->FileOffset = (ULONG) LocalOffset;
|
|
NextBuffer->Length = 0;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
//
|
|
// If the file is not compressed, we have to fill in
|
|
// the appropriate chunk size and buffer, and advance
|
|
// CompressedBuffer.
|
|
//
|
|
|
|
} else {
|
|
#ifdef NTFS_RWC_DEBUG
|
|
if ((LocalOffset < NtfsRWCHighThreshold) &&
|
|
(LocalOffset + ChunkSize > NtfsRWCLowThreshold)) {
|
|
|
|
PRWC_HISTORY_ENTRY NextBuffer;
|
|
|
|
NextBuffer = NtfsGetHistoryEntry( (PSCB) Header );
|
|
|
|
NextBuffer->Operation = ReadUncompressed;
|
|
NextBuffer->Information = (LocalFileObject == ((PSCB)Header)->FileObject);
|
|
NextBuffer->FileOffset = (ULONG) LocalOffset;
|
|
NextBuffer->Length = 0;
|
|
}
|
|
#endif
|
|
*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 or is all zeros, we have to
|
|
// emit what we had (which captures the Bcb pointer),
|
|
// and start a new Mdl buffer.
|
|
//
|
|
|
|
if ((*NextReturnChunkSize == ChunkSize) || (*NextReturnChunkSize == 0)) {
|
|
|
|
NtfsAddToCompressedMdlChain( MdlChain,
|
|
MdlBuffer,
|
|
MdlLength,
|
|
Header->PagingIoResource,
|
|
Bcb,
|
|
IoReadAccess,
|
|
IsCompressed );
|
|
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,
|
|
Header->PagingIoResource,
|
|
Bcb,
|
|
IoReadAccess,
|
|
IsCompressed );
|
|
Bcb = NULL;
|
|
MdlLength = 0;
|
|
}
|
|
}
|
|
|
|
Done:
|
|
|
|
FileObject->Flags |= FO_FILE_FAST_IO_READ;
|
|
|
|
if (NT_SUCCESS(Status) && (MdlLength != 0)) {
|
|
NtfsAddToCompressedMdlChain( MdlChain,
|
|
MdlBuffer,
|
|
MdlLength,
|
|
Header->PagingIoResource,
|
|
Bcb,
|
|
IoReadAccess,
|
|
IsCompressed );
|
|
Bcb = NULL;
|
|
}
|
|
|
|
} except( FsRtlIsNtstatusExpected(Status = GetExceptionCode())
|
|
? EXCEPTION_EXECUTE_HANDLER
|
|
: EXCEPTION_CONTINUE_SEARCH ) {
|
|
|
|
NOTHING;
|
|
}
|
|
|
|
//
|
|
// Unpin any Bcbs we still have.
|
|
//
|
|
|
|
if (Bcb != NULL) {
|
|
CcUnpinData( Bcb );
|
|
|
|
} else if (UncompressedBcb != NULL) {
|
|
CcUnpinData( UncompressedBcb );
|
|
}
|
|
|
|
if (CompressionSync != NULL) {
|
|
NtfsReleaseCompressionSync( CompressionSync );
|
|
}
|
|
|
|
//
|
|
// Perform Mdl-specific processing.
|
|
//
|
|
|
|
if (MdlChain != NULL) {
|
|
|
|
//
|
|
// On error, cleanup any MdlChain we built up
|
|
//
|
|
|
|
if (!NT_SUCCESS(Status)) {
|
|
|
|
NtfsCleanupCompressedMdlChain( *MdlChain, TRUE );
|
|
*MdlChain = NULL;
|
|
|
|
//
|
|
// Change owner Id for the Scb and Bcbs we are holding.
|
|
//
|
|
|
|
} else if (*MdlChain != NULL) {
|
|
|
|
NtfsSetMdlBcbOwners( *MdlChain );
|
|
ExSetResourceOwnerPointer( Header->PagingIoResource, (PVOID)((PCHAR)*MdlChain + 3) );
|
|
}
|
|
}
|
|
|
|
#ifdef NTFS_RWCMP_TRACE
|
|
if (NtfsCompressionTrace && IsSyscache(Header)) {
|
|
|
|
ULONG ci;
|
|
|
|
if (NT_SUCCESS(Status)) {
|
|
DbgPrint(" Chunks:");
|
|
for (ci = 0; ci < CompressedDataInfo->NumberOfChunks; ci++) {
|
|
DbgPrint(" %lx", CompressedDataInfo->CompressedChunkSizes[ci]);
|
|
}
|
|
DbgPrint("\n");
|
|
}
|
|
DbgPrint(" Return Status = %08lx\n", Status);
|
|
}
|
|
#endif
|
|
|
|
#ifdef NTFS_RWC_DEBUG
|
|
if ((Status == STATUS_SUCCESS) &&
|
|
(FileOffset->QuadPart < NtfsRWCHighThreshold) &&
|
|
(FileOffset->QuadPart + Length > NtfsRWCLowThreshold)) {
|
|
|
|
PRWC_HISTORY_ENTRY NextBuffer;
|
|
|
|
NextBuffer = NtfsGetHistoryEntry( (PSCB) Header );
|
|
|
|
NextBuffer->Operation = EndOfRead;
|
|
NextBuffer->Information = (ULONG) ReadHistoryBuffer;
|
|
NextBuffer->FileOffset = 0;
|
|
NextBuffer->Length = 0;
|
|
|
|
if (ReadHistoryBuffer != NULL) {
|
|
SetFlag( ReadHistoryBuffer->Operation, 0x80000000 );
|
|
}
|
|
}
|
|
#endif
|
|
|
|
return Status;
|
|
}
|
|
|
|
|
|
BOOLEAN
|
|
NtfsMdlReadCompleteCompressed (
|
|
IN struct _FILE_OBJECT *FileObject,
|
|
IN PMDL MdlChain,
|
|
IN struct _DEVICE_OBJECT *DeviceObject
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine frees resources and the Mdl Chain after a compressed read.
|
|
|
|
Arguments:
|
|
|
|
FileObject - pointer to the file object for the request.
|
|
|
|
MdlChain - as returned from compressed copy read.
|
|
|
|
DeviceObject - As required for a fast I/O routine.
|
|
|
|
Return Value:
|
|
|
|
TRUE - if fast path succeeded
|
|
|
|
FALSE - if an Irp is required
|
|
|
|
--*/
|
|
|
|
{
|
|
PERESOURCE ResourceToRelease;
|
|
|
|
if (MdlChain != NULL) {
|
|
|
|
ResourceToRelease = *(PERESOURCE *)Add2Ptr( MdlChain, MdlChain->Size + sizeof( PBCB ));
|
|
}
|
|
|
|
NtfsCleanupCompressedMdlChain( MdlChain, FALSE );
|
|
|
|
//
|
|
// If the server tried to read past the end of the file in the
|
|
// fast path then he calls us with NULL for the MDL. We already
|
|
// released the thread in that case.
|
|
//
|
|
|
|
if (MdlChain != NULL) {
|
|
|
|
ExReleaseResourceForThread( ResourceToRelease, (ERESOURCE_THREAD)((PCHAR)MdlChain + 3) );
|
|
}
|
|
|
|
return TRUE;
|
|
|
|
UNREFERENCED_PARAMETER( DeviceObject );
|
|
UNREFERENCED_PARAMETER( FileObject );
|
|
}
|
|
|
|
|
|
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.
|
|
|
|
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
|
|
|
|
--*/
|
|
|
|
{
|
|
PNTFS_ADVANCED_FCB_HEADER Header;
|
|
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 out immediately if COW is not supported.
|
|
//
|
|
|
|
if (!NtfsEnableCompressedIO) { return FALSE; }
|
|
|
|
//
|
|
// Get a real pointer to the common fcb header
|
|
//
|
|
|
|
Header = (PNTFS_ADVANCED_FCB_HEADER)FileObject->FsContext;
|
|
|
|
#ifdef NTFS_RWCMP_TRACE
|
|
if (NtfsCompressionTrace && IsSyscache(Header)) {
|
|
DbgPrint("NtfsCopyWriteC: FO = %08lx, Len = %08lx\n", FileOffset->LowPart, Length );
|
|
}
|
|
#endif
|
|
|
|
//
|
|
// 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;
|
|
|
|
//
|
|
// 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;
|
|
|
|
ExAcquireResourceExclusiveLite( Header->PagingIoResource, TRUE );
|
|
CompressionUnitSize = ((PSCB) Header)->CompressionUnit;
|
|
|
|
LocalOffset = FileOffset->QuadPart & ~(LONGLONG)(CompressionUnitSize - 1);
|
|
|
|
FlushLength = (Length + (ULONG)(FileOffset->QuadPart - LocalOffset) + CompressionUnitSize - 1) &
|
|
~(CompressionUnitSize - 1);
|
|
|
|
CcFlushCache( FileObject->SectionObjectPointer,
|
|
(PLARGE_INTEGER)&LocalOffset,
|
|
FlushLength,
|
|
NULL );
|
|
CcPurgeCacheSection( FileObject->SectionObjectPointer,
|
|
(PLARGE_INTEGER)&LocalOffset,
|
|
FlushLength,
|
|
FALSE );
|
|
ExReleaseResourceLite( Header->PagingIoResource );
|
|
}
|
|
|
|
NewFileSize.QuadPart = FileOffset->QuadPart + Length;
|
|
|
|
//
|
|
// Prevent truncates by acquiring paging I/O
|
|
//
|
|
|
|
ExAcquireResourceSharedLite( Header->PagingIoResource, TRUE );
|
|
|
|
//
|
|
// Get the compression information for this file and return those fields.
|
|
//
|
|
|
|
NtfsFastIoQueryCompressionInfo( FileObject, &CompressionInformation, IoStatus );
|
|
CompressionUnitSize = ((PSCB) Header)->CompressionUnit;
|
|
|
|
//
|
|
// See if the engine matches, so we can pass that on to the
|
|
// compressed write routine.
|
|
//
|
|
|
|
EngineMatches =
|
|
((CompressedDataInfo->CompressionFormatAndEngine == CompressionInformation.CompressionFormat) &&
|
|
(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 );
|
|
|
|
//
|
|
// Set the Flag if we are changing FileSize or ValidDataLength,
|
|
// and save current values.
|
|
//
|
|
|
|
if (DoingIoAtEof) {
|
|
|
|
SetFlag( Header->Flags, FSRTL_FLAG_EOF_ADVANCE_ACTIVE );
|
|
#if (DBG || defined( NTFS_FREE_ASSERTS ))
|
|
((PSCB) Header)->IoAtEofThread = (PERESOURCE_THREAD) ExGetCurrentResourceThread();
|
|
#endif
|
|
|
|
//
|
|
// 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;
|
|
}
|
|
}
|
|
|
|
#if (DBG || defined( NTFS_FREE_ASSERTS ))
|
|
} else {
|
|
|
|
ASSERT( ((PSCB) Header)->IoAtEofThread != (PERESOURCE_THREAD) ExGetCurrentResourceThread() );
|
|
#endif
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Update both caches with EOF.
|
|
//
|
|
|
|
if (DoingIoAtEof) {
|
|
NtfsSetBothCacheSizes( FileObject,
|
|
(PCC_FILE_SIZES)&Header->AllocationSize,
|
|
(PSCB)Header );
|
|
}
|
|
|
|
//
|
|
// 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
|
|
//
|
|
|
|
IoSetTopLevelIrp( (PIRP) FSRTL_FAST_IO_TOP_LEVEL_IRP );
|
|
|
|
ASSERT(CompressedDataInfoLength >= (sizeof(COMPRESSED_DATA_INFO) +
|
|
(((Length >> CompressedDataInfo->ChunkShift) - 1) *
|
|
sizeof(ULONG))));
|
|
|
|
if (NT_SUCCESS(IoStatus->Status)) {
|
|
|
|
if (DoingIoAtEof &&
|
|
(((PSCB)Header)->NonpagedScb->SegmentObject.DataSectionObject != NULL)) {
|
|
|
|
IoStatus->Status = STATUS_FILE_LOCK_CONFLICT;
|
|
|
|
} else {
|
|
|
|
IoStatus->Status = NtfsCompressedCopyWrite( FileObject,
|
|
FileOffset,
|
|
Length,
|
|
Buffer,
|
|
MdlChain,
|
|
CompressedDataInfo,
|
|
DeviceObject,
|
|
Header,
|
|
CompressionUnitSize,
|
|
ChunkSize,
|
|
EngineMatches );
|
|
}
|
|
}
|
|
|
|
IoSetTopLevelIrp( NULL );
|
|
|
|
Status = (BOOLEAN)NT_SUCCESS(IoStatus->Status);
|
|
|
|
//
|
|
// 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) {
|
|
|
|
CC_FILE_SIZES CcFileSizes;
|
|
|
|
ExAcquireFastMutex( Header->FastMutex );
|
|
FileObject->Flags |= FO_FILE_SIZE_CHANGED;
|
|
Header->ValidDataLength = NewFileSize;
|
|
CcFileSizes = *(PCC_FILE_SIZES)&Header->AllocationSize;
|
|
NtfsVerifySizes( Header );
|
|
NtfsFinishIoAtEof( Header );
|
|
|
|
//
|
|
// Update the normal cache with ValidDataLength.
|
|
//
|
|
|
|
if (((PSCB)Header)->FileObject != NULL) {
|
|
CcSetFileSizes( ((PSCB)Header)->FileObject, &CcFileSizes );
|
|
}
|
|
ExReleaseFastMutex( Header->FastMutex );
|
|
}
|
|
|
|
goto Done1;
|
|
}
|
|
|
|
ErrOut: NOTHING;
|
|
|
|
Status = FALSE;
|
|
if (DoingIoAtEof) {
|
|
ExAcquireFastMutex( Header->FastMutex );
|
|
if (CcIsFileCached(FileObject)) {
|
|
*CcGetFileSizePointer(FileObject) = OldFileSize;
|
|
}
|
|
if (Header->FileObjectC != NULL) {
|
|
*CcGetFileSizePointer(Header->FileObjectC) = OldFileSize;
|
|
}
|
|
Header->FileSize = OldFileSize;
|
|
NtfsFinishIoAtEof( Header );
|
|
ExReleaseFastMutex( Header->FastMutex );
|
|
}
|
|
|
|
Done1: NOTHING;
|
|
|
|
//
|
|
// For the Mdl case, we must keep the resource.
|
|
//
|
|
|
|
if ((MdlChain == NULL) || !Status || (*MdlChain == NULL)) {
|
|
ExReleaseResourceLite( Header->PagingIoResource );
|
|
}
|
|
|
|
FsRtlExitFileSystem();
|
|
}
|
|
|
|
} else {
|
|
|
|
//
|
|
// We could not do the I/O now.
|
|
//
|
|
|
|
Status = FALSE;
|
|
}
|
|
|
|
|
|
#ifdef NTFS_RWCMP_TRACE
|
|
if (NtfsCompressionTrace && IsSyscache(Header)) {
|
|
DbgPrint("Return Status = %08lx\n", Status);
|
|
}
|
|
#endif
|
|
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 PNTFS_ADVANCED_FCB_HEADER Header,
|
|
IN ULONG CompressionUnitSize,
|
|
IN ULONG ChunkSize,
|
|
IN ULONG EngineMatches
|
|
)
|
|
|
|
/*++
|
|
|
|
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.
|
|
|
|
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.
|
|
|
|
CompressedDataInfo - Returns compressed data info with compressed chunk
|
|
sizes
|
|
|
|
DeviceObject - Standard Fast I/O Device object input.
|
|
|
|
Header - Pointer to FsRtl header for file (also is our Scb)
|
|
|
|
CompressionUnitSize - Size of Compression Unit in bytes.
|
|
|
|
ChunkSize - ChunkSize in bytes.
|
|
|
|
EngineMatches - TRUE if the caller has determined that the compressed
|
|
data format matches the compression engine for the file.
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS for operation. If STATUS_NOT_MAPPED_USER_DATA, then the caller
|
|
should map the normal uncompressed data stream and call back.
|
|
|
|
--*/
|
|
|
|
{
|
|
NTSTATUS Status = STATUS_SUCCESS;
|
|
|
|
PUCHAR StartOfPin;
|
|
ULONG SizeToPin;
|
|
|
|
LONGLONG LocalOffset;
|
|
PULONG NextChunkSize, TempChunkSize;
|
|
PUCHAR ChunkBuffer;
|
|
PUCHAR CacheBuffer;
|
|
PUCHAR EndOfCacheBuffer;
|
|
|
|
ULONG SavedLength;
|
|
PUCHAR SavedBuffer;
|
|
|
|
ULONG ChunkOfZeros;
|
|
ULONG UncompressedChunkHeader;
|
|
|
|
ULONG ChunkSizes[17];
|
|
ULONG i, ChunksSeen;
|
|
|
|
ULONG TempUlong;
|
|
|
|
PVOID MdlBuffer;
|
|
ULONG MdlLength = 0;
|
|
|
|
ULONG ClusterSize = ((PSCB)Header)->Vcb->BytesPerCluster;
|
|
|
|
PBCB Bcb = NULL;
|
|
PBCB TempBcb = NULL;
|
|
PCOMPRESSION_SYNC CompressionSync = NULL;
|
|
|
|
BOOLEAN FullOverwrite = FALSE;
|
|
BOOLEAN IsCompressed;
|
|
|
|
ASSERT((FileOffset->QuadPart & (ChunkSize - 1)) == 0);
|
|
ASSERT((((FileOffset->QuadPart + Length) & (ChunkSize - 1)) == 0) ||
|
|
((FileOffset->QuadPart + Length) == Header->FileSize.QuadPart));
|
|
ASSERT((MdlChain == NULL) || (*MdlChain == NULL));
|
|
|
|
//
|
|
// Return an error if the file is not compressed.
|
|
//
|
|
|
|
if (!EngineMatches || ((PSCB)Header)->CompressionUnit == 0) {
|
|
return STATUS_UNSUPPORTED_COMPRESSION;
|
|
}
|
|
|
|
#ifdef NTFS_RWCMP_TRACE
|
|
if (NtfsCompressionTrace && IsSyscache(Header)) {
|
|
|
|
ULONG ci;
|
|
|
|
DbgPrint(" CompressedWrite: FO = %08lx, Len = %08lx\n", FileOffset->LowPart, Length );
|
|
DbgPrint(" Chunks:");
|
|
for (ci = 0; ci < CompressedDataInfo->NumberOfChunks; ci++) {
|
|
DbgPrint(" %lx", CompressedDataInfo->CompressedChunkSizes[ci]);
|
|
}
|
|
DbgPrint("\n");
|
|
}
|
|
#endif
|
|
|
|
#ifdef NTFS_RWC_DEBUG
|
|
if ((FileOffset->QuadPart < NtfsRWCHighThreshold) &&
|
|
(FileOffset->QuadPart + Length > NtfsRWCLowThreshold)) {
|
|
|
|
PRWC_HISTORY_ENTRY NextBuffer;
|
|
|
|
NextBuffer = NtfsGetHistoryEntry( (PSCB) Header );
|
|
|
|
NextBuffer->Operation = StartOfWrite;
|
|
NextBuffer->Information = CompressedDataInfo->NumberOfChunks;
|
|
NextBuffer->FileOffset = (ULONG) FileOffset->QuadPart;
|
|
NextBuffer->Length = (ULONG) Length;
|
|
}
|
|
#endif
|
|
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);
|
|
|
|
NextChunkSize = &CompressedDataInfo->CompressedChunkSizes[0];
|
|
|
|
//
|
|
// Get the overhead for zero chunks and uncompressed chunks.
|
|
//
|
|
// **** temporary solution awaits Rtl routine.
|
|
//
|
|
|
|
ASSERT(CompressedDataInfo->CompressionFormatAndEngine == COMPRESSION_FORMAT_LZNT1);
|
|
ChunkOfZeros = 6;
|
|
UncompressedChunkHeader = 2;
|
|
// Status = RtlGetSpecialChunkSizes( CompressedDataInfo->CompressionFormatAndEngine,
|
|
// &ChunkOfZeros,
|
|
// &UncompressedChunkHeader );
|
|
//
|
|
// ASSERT(NT_SUCCESS(Status));
|
|
//
|
|
|
|
//
|
|
// Loop through desired compression units
|
|
//
|
|
|
|
while (TRUE) {
|
|
|
|
//
|
|
// Free any Bcb from previous pass
|
|
//
|
|
|
|
if (Bcb != NULL) {
|
|
CcUnpinData( Bcb );
|
|
Bcb = NULL;
|
|
}
|
|
|
|
//
|
|
// If there is an uncompressed stream, then we have to synchronize with that.
|
|
//
|
|
|
|
if (((PSCB)Header)->NonpagedScb->SegmentObject.DataSectionObject != NULL) {
|
|
Status = NtfsSynchronizeCompressedIo( (PSCB)Header,
|
|
&LocalOffset,
|
|
Length,
|
|
TRUE,
|
|
&CompressionSync );
|
|
|
|
if (!NT_SUCCESS(Status)) {
|
|
ASSERT( Status == STATUS_USER_MAPPED_FILE );
|
|
leave;
|
|
}
|
|
}
|
|
|
|
//
|
|
// 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 we are not at the start of a compression unit, calculate the
|
|
// index of the chunk we will be working on, and reduce SavedLength
|
|
// accordingly.
|
|
//
|
|
|
|
i = 0;
|
|
if (LocalOffset < FileOffset->QuadPart) {
|
|
i = (ULONG)(FileOffset->QuadPart - LocalOffset);
|
|
SavedLength -= i;
|
|
i >>= CompressedDataInfo->ChunkShift;
|
|
}
|
|
|
|
//
|
|
// Loop to calculate sum of chunk sizes being written, handling both empty
|
|
// and uncompressed chunk cases. We will remember the nonzero size of each
|
|
// chunk being written so we can merge this info with the sizes of any chunks
|
|
// not being overwritten below.
|
|
// Reserve space for a chunk of zeroes for each chunk ahead of the first one
|
|
// being written.
|
|
//
|
|
|
|
SizeToPin = ChunkOfZeros * i;
|
|
TempUlong = SavedLength >> CompressedDataInfo->ChunkShift;
|
|
TempChunkSize = NextChunkSize;
|
|
RtlZeroMemory( ChunkSizes, sizeof( ChunkSizes ));
|
|
|
|
while (TempUlong--) {
|
|
|
|
ChunkSizes[i] = *TempChunkSize;
|
|
if (*TempChunkSize == 0) {
|
|
ChunkSizes[i] += ChunkOfZeros;
|
|
ASSERT(ChunkOfZeros != 0);
|
|
} else if (*TempChunkSize == ChunkSize) {
|
|
ChunkSizes[i] += UncompressedChunkHeader;
|
|
}
|
|
SizeToPin += ChunkSizes[i];
|
|
TempChunkSize++;
|
|
i += 1;
|
|
}
|
|
|
|
//
|
|
// If this is not a full overwrite, get the current compression unit
|
|
// size and make sure we pin at least that much. Don't bother to check
|
|
// the allocation if this range of the file has not been written yet.
|
|
//
|
|
|
|
if (!FullOverwrite && (LocalOffset < ((PSCB)Header)->ValidDataToDisk)) {
|
|
|
|
NtfsFastIoQueryCompressedSize( FileObject,
|
|
(PLARGE_INTEGER)&LocalOffset,
|
|
&TempUlong );
|
|
|
|
ASSERT( TempUlong <= CompressionUnitSize );
|
|
|
|
if (TempUlong > SizeToPin) {
|
|
SizeToPin = TempUlong;
|
|
}
|
|
}
|
|
|
|
//
|
|
// At this point we are ready to overwrite data in the compression
|
|
// unit. See if the data is really compressed.
|
|
//
|
|
// If it looks like we are beyond ValidDataToDisk, then assume it is compressed
|
|
// for now, and we will see for sure later when we get the data pinned. This
|
|
// is actually an unsafe test that will occassionally send us down the "wrong"
|
|
// path. However, it is always safe to take the uncompressed path, and if we
|
|
// think the data is compressed, we always check again below.
|
|
//
|
|
|
|
IsCompressed = (BOOLEAN)(((SizeToPin <= (CompressionUnitSize - ClusterSize)) ||
|
|
(LocalOffset >= ((PSCB)Header)->ValidDataToDisk)) &&
|
|
EngineMatches);
|
|
|
|
//
|
|
// Possibly neither the new nor old data for this CompressionUnit is
|
|
// nonzero, so we must pin something so that we can cause any old allocation
|
|
// to get deleted. This code relies on any compression algorithm being
|
|
// able to express an entire compression unit of 0's in one page or less.
|
|
//
|
|
|
|
if (SizeToPin == 0) {
|
|
SizeToPin = PAGE_SIZE;
|
|
|
|
} else {
|
|
|
|
//
|
|
// Add a ulong for the null terminator.
|
|
//
|
|
|
|
SizeToPin += sizeof( ULONG );
|
|
}
|
|
|
|
Status = STATUS_SUCCESS;
|
|
|
|
//
|
|
// Round the pin size to a page boundary. Then we can tell when we need to pin a larger range.
|
|
//
|
|
|
|
SizeToPin = (SizeToPin + PAGE_SIZE - 1) & ~(PAGE_SIZE - 1);
|
|
|
|
//
|
|
// 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,
|
|
FALSE,
|
|
PIN_WAIT | PIN_EXCLUSIVE,
|
|
&Bcb,
|
|
&CacheBuffer );
|
|
|
|
//
|
|
// Now that we are synchronized with the buffer, see if someone snuck
|
|
// in behind us and created the noncached stream since we last checked
|
|
// for that stream. If so we have to go back and get correctly synchronized.
|
|
//
|
|
|
|
if ((CompressionSync == NULL) &&
|
|
(((PSCB)Header)->NonpagedScb->SegmentObject.DataSectionObject != NULL)) {
|
|
|
|
continue;
|
|
}
|
|
|
|
//
|
|
// 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;
|
|
|
|
#ifdef NTFS_RWC_DEBUG
|
|
if ((LocalOffset < NtfsRWCHighThreshold) &&
|
|
(LocalOffset + SizeToPin > NtfsRWCLowThreshold)) {
|
|
|
|
PRWC_HISTORY_ENTRY NextBuffer;
|
|
|
|
//
|
|
// Check for the case where we don't have a full Bcb.
|
|
//
|
|
|
|
if (SafeNodeType( Bcb ) == CACHE_NTC_OBCB) {
|
|
|
|
PPUBLIC_BCB NextBcb;
|
|
|
|
NextBcb = ((POBCB) Bcb)->Bcbs[0];
|
|
|
|
NextBuffer = NtfsGetHistoryEntry( (PSCB) Header );
|
|
|
|
NextBuffer->Operation = PartialBcb;
|
|
NextBuffer->Information = 0;
|
|
NextBuffer->FileOffset = (ULONG) NextBcb->MappedFileOffset.QuadPart;
|
|
NextBuffer->Length = NextBcb->MappedLength;
|
|
|
|
ASSERT( NextBuffer->Length <= SizeToPin );
|
|
|
|
} else {
|
|
|
|
PPUBLIC_BCB NextBcb;
|
|
ASSERT( SafeNodeType( Bcb ) == CACHE_NTC_BCB );
|
|
|
|
NextBcb = (PPUBLIC_BCB) Bcb;
|
|
|
|
ASSERT( LocalOffset + SizeToPin <= NextBcb->MappedFileOffset.QuadPart + NextBcb->MappedLength );
|
|
}
|
|
|
|
NextBuffer = NtfsGetHistoryEntry( (PSCB) Header );
|
|
|
|
NextBuffer->Operation = FullOverwrite;
|
|
NextBuffer->Information = 0;
|
|
NextBuffer->FileOffset = (ULONG) LocalOffset;
|
|
NextBuffer->Length = (ULONG) SizeToPin;
|
|
|
|
}
|
|
#endif
|
|
|
|
} else {
|
|
|
|
//
|
|
// Read the data from the compressed stream that we will combine
|
|
// with the chunks being written.
|
|
//
|
|
|
|
CcPinRead( Header->FileObjectC,
|
|
(PLARGE_INTEGER)&LocalOffset,
|
|
SizeToPin,
|
|
PIN_WAIT | PIN_EXCLUSIVE,
|
|
&Bcb,
|
|
&CacheBuffer );
|
|
|
|
//
|
|
// Now that we are synchronized with the buffer, see if someone snuck
|
|
// in behind us and created the noncached stream since we last checked
|
|
// for that stream. If so we have to go back and get correctly synchronized.
|
|
//
|
|
|
|
if ((CompressionSync == NULL) &&
|
|
(((PSCB)Header)->NonpagedScb->SegmentObject.DataSectionObject != NULL)) {
|
|
|
|
continue;
|
|
}
|
|
|
|
//
|
|
// Now that the data is pinned (we are synchronized with the
|
|
// CompressionUnit), we need to recalculate how much should be
|
|
// pinned. We do this by summing up all the sizes of the chunks
|
|
// that are being written with the sizes of the existing chunks
|
|
// that will remain.
|
|
//
|
|
|
|
StartOfPin = CacheBuffer;
|
|
EndOfCacheBuffer = Add2Ptr( CacheBuffer, CompressionUnitSize - ClusterSize );
|
|
|
|
i = 0;
|
|
|
|
//
|
|
// Loop through to find all the existing chunks, and remember their
|
|
// sizes if they are not being overwritten. (Remember if we overwrite
|
|
// with a chunk of all zeros, it takes nonzero bytes to do it!)
|
|
//
|
|
// This loop completes the formation of an array of chunksizes. The
|
|
// start of the array is guaranteed to be nonzero, and it terminates
|
|
// with a chunk size of 0. Note if fewer chunks are filled in than
|
|
// exist in the compression unit, that is ok - we do not need to write
|
|
// trailing chunks of 0's.
|
|
//
|
|
|
|
ChunksSeen = FALSE;
|
|
while (i < 16) {
|
|
|
|
Status = RtlDescribeChunk( CompressedDataInfo->CompressionFormatAndEngine,
|
|
&StartOfPin,
|
|
EndOfCacheBuffer,
|
|
&ChunkBuffer,
|
|
&TempUlong );
|
|
|
|
//
|
|
// If there are no more entries, see if we are done, else treat
|
|
// it as a chunk of 0's.
|
|
//
|
|
|
|
if (!NT_SUCCESS(Status)) {
|
|
|
|
ASSERT(Status == STATUS_NO_MORE_ENTRIES);
|
|
|
|
if (ChunksSeen) {
|
|
break;
|
|
}
|
|
|
|
TempUlong = ChunkOfZeros;
|
|
|
|
//
|
|
// Make sure we enter the length for a chunk of zeroes.
|
|
//
|
|
|
|
} else if (TempUlong == 0) {
|
|
|
|
TempUlong = ChunkOfZeros;
|
|
}
|
|
|
|
if (ChunkSizes[i] == 0) {
|
|
ChunkSizes[i] = TempUlong;
|
|
} else {
|
|
ChunksSeen = TRUE;
|
|
}
|
|
|
|
i += 1;
|
|
}
|
|
|
|
//
|
|
// Now sum up the sizes of the chunks we will write.
|
|
//
|
|
|
|
i = 0;
|
|
TempUlong = 0;
|
|
while (ChunkSizes[i] != 0) {
|
|
TempUlong += ChunkSizes[i];
|
|
i += 1;
|
|
}
|
|
|
|
//
|
|
// If the existing data is larger, pin that range.
|
|
//
|
|
|
|
if (TempUlong < PtrOffset(CacheBuffer, StartOfPin)) {
|
|
TempUlong = PtrOffset(CacheBuffer, StartOfPin);
|
|
}
|
|
|
|
IsCompressed = (TempUlong <= (CompressionUnitSize - ClusterSize));
|
|
|
|
//
|
|
// We now know if we will really end up with compressed data, so
|
|
// get out now stop processing if the data is not compressed.
|
|
//
|
|
|
|
if (IsCompressed) {
|
|
|
|
TempUlong += sizeof(ULONG);
|
|
|
|
//
|
|
// Now we have to repin if we actually need more space.
|
|
//
|
|
|
|
if (TempUlong > SizeToPin) {
|
|
|
|
SizeToPin = (TempUlong + PAGE_SIZE - 1) & ~(PAGE_SIZE - 1);
|
|
|
|
TempBcb = Bcb;
|
|
Bcb = NULL;
|
|
|
|
//
|
|
// Read the data from the compressed stream that we will combine
|
|
// with the chunks being written.
|
|
//
|
|
|
|
CcPinRead( Header->FileObjectC,
|
|
(PLARGE_INTEGER)&LocalOffset,
|
|
SizeToPin,
|
|
PIN_WAIT | PIN_EXCLUSIVE,
|
|
&Bcb,
|
|
&CacheBuffer );
|
|
|
|
CcUnpinData( TempBcb );
|
|
TempBcb = NULL;
|
|
}
|
|
|
|
ASSERT( TempUlong <= CompressionUnitSize );
|
|
|
|
//
|
|
// Really make the data dirty by physically modifying a byte
|
|
// in each page.
|
|
//
|
|
|
|
TempUlong = 0;
|
|
|
|
while (TempUlong < SizeToPin) {
|
|
|
|
volatile PULONG NextBuffer;
|
|
|
|
NextBuffer = Add2Ptr( CacheBuffer, TempUlong );
|
|
|
|
*NextBuffer = *NextBuffer;
|
|
TempUlong += PAGE_SIZE;
|
|
}
|
|
|
|
#ifdef NTFS_RWC_DEBUG
|
|
if ((LocalOffset < NtfsRWCHighThreshold) &&
|
|
(LocalOffset + SizeToPin > NtfsRWCLowThreshold)) {
|
|
|
|
PRWC_HISTORY_ENTRY NextBuffer;
|
|
|
|
NextBuffer = NtfsGetHistoryEntry( (PSCB) Header );
|
|
|
|
NextBuffer->Operation = SetDirty;
|
|
NextBuffer->Information = 0;
|
|
NextBuffer->FileOffset = (ULONG) LocalOffset;
|
|
NextBuffer->Length = (ULONG) SizeToPin;
|
|
}
|
|
#endif
|
|
|
|
CcSetDirtyPinnedData( Bcb, NULL );
|
|
}
|
|
}
|
|
|
|
EndOfCacheBuffer = Add2Ptr( CacheBuffer, CompressionUnitSize - ClusterSize );
|
|
|
|
//
|
|
// 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 or is all 0's, we have to
|
|
// emit what we had (which captures the Bcb pointer),
|
|
// and start a new Mdl buffer.
|
|
//
|
|
|
|
if ((*TempChunkSize == ChunkSize) || (*TempChunkSize == 0)) {
|
|
|
|
NtfsAddToCompressedMdlChain( MdlChain,
|
|
MdlBuffer,
|
|
MdlLength,
|
|
Header->PagingIoResource,
|
|
Bcb,
|
|
IoWriteAccess,
|
|
TRUE );
|
|
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;
|
|
|
|
//
|
|
// 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
|
|
// chunk in order to skip over it.
|
|
//
|
|
|
|
Status = RtlDescribeChunk( CompressedDataInfo->CompressionFormatAndEngine,
|
|
&CacheBuffer,
|
|
EndOfCacheBuffer,
|
|
&ChunkBuffer,
|
|
&TempUlong );
|
|
|
|
//
|
|
// 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;
|
|
}
|
|
}
|
|
|
|
//
|
|
// 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,
|
|
Header->PagingIoResource,
|
|
Bcb,
|
|
IoWriteAccess,
|
|
TRUE );
|
|
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 (((PSCB)Header)->FileObject == 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.
|
|
//
|
|
|
|
SizeToPin = (((Length < CompressionUnitSize) ? Length : CompressionUnitSize) + PAGE_SIZE - 1) & ~(PAGE_SIZE - 1);
|
|
|
|
CcPinRead( ((PSCB)Header)->FileObject,
|
|
(PLARGE_INTEGER)&LocalOffset,
|
|
SizeToPin,
|
|
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);
|
|
}
|
|
|
|
//
|
|
// Now we can finally advance our pointer into the chunk sizes.
|
|
//
|
|
|
|
NextChunkSize = TempChunkSize;
|
|
}
|
|
|
|
Done: NOTHING;
|
|
|
|
if ((MdlLength != 0) && NT_SUCCESS(Status)) {
|
|
NtfsAddToCompressedMdlChain( MdlChain,
|
|
MdlBuffer,
|
|
MdlLength,
|
|
Header->PagingIoResource,
|
|
Bcb,
|
|
IoWriteAccess,
|
|
TRUE );
|
|
Bcb = NULL;
|
|
}
|
|
|
|
} except( FsRtlIsNtstatusExpected((Status = GetExceptionCode()))
|
|
? EXCEPTION_EXECUTE_HANDLER
|
|
: EXCEPTION_CONTINUE_SEARCH ) {
|
|
|
|
NOTHING;
|
|
}
|
|
|
|
//
|
|
// Unpin the Bcbs we still have.
|
|
//
|
|
|
|
if (TempBcb != NULL) {
|
|
CcUnpinData( TempBcb );
|
|
}
|
|
if (Bcb != NULL) {
|
|
CcUnpinData( Bcb );
|
|
}
|
|
if (CompressionSync != NULL) {
|
|
NtfsReleaseCompressionSync( CompressionSync );
|
|
}
|
|
|
|
//
|
|
// Perform Mdl-specific processing.
|
|
//
|
|
|
|
if (MdlChain != NULL) {
|
|
|
|
//
|
|
// On error, cleanup any MdlChain we built up
|
|
//
|
|
|
|
if (!NT_SUCCESS(Status)) {
|
|
|
|
NtfsCleanupCompressedMdlChain( *MdlChain, TRUE );
|
|
*MdlChain = NULL;
|
|
|
|
//
|
|
// Change owner Id for the Scb and Bcbs we are holding.
|
|
//
|
|
|
|
} else if (*MdlChain != NULL) {
|
|
|
|
NtfsSetMdlBcbOwners( *MdlChain );
|
|
ExSetResourceOwnerPointer( Header->PagingIoResource, (PVOID)((PCHAR)*MdlChain + 3) );
|
|
}
|
|
}
|
|
|
|
#ifdef NTFS_RWCMP_TRACE
|
|
if (NtfsCompressionTrace && IsSyscache(Header)) {
|
|
DbgPrint(" Return Status = %08lx\n", Status);
|
|
}
|
|
#endif
|
|
return Status;
|
|
|
|
UNREFERENCED_PARAMETER( DeviceObject );
|
|
}
|
|
|
|
|
|
BOOLEAN
|
|
NtfsMdlWriteCompleteCompressed (
|
|
IN struct _FILE_OBJECT *FileObject,
|
|
IN PLARGE_INTEGER FileOffset,
|
|
IN PMDL MdlChain,
|
|
IN struct _DEVICE_OBJECT *DeviceObject
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine frees resources and the Mdl Chain after a compressed write.
|
|
|
|
Arguments:
|
|
|
|
FileObject - pointer to the file object for the request.
|
|
|
|
MdlChain - as returned from compressed write.
|
|
|
|
DeviceObject - As required for a fast I/O routine.
|
|
|
|
Return Value:
|
|
|
|
TRUE - if fast path succeeded
|
|
|
|
FALSE - if an Irp is required
|
|
|
|
--*/
|
|
|
|
{
|
|
PERESOURCE ResourceToRelease;
|
|
|
|
if (MdlChain != NULL) {
|
|
|
|
ResourceToRelease = *(PERESOURCE *)Add2Ptr( MdlChain, MdlChain->Size + sizeof( PBCB ));
|
|
|
|
NtfsCleanupCompressedMdlChain( MdlChain, FALSE );
|
|
|
|
//
|
|
// Release the held resource.
|
|
//
|
|
|
|
ExReleaseResourceForThread( ResourceToRelease, (ERESOURCE_THREAD)((PCHAR)MdlChain + 3) );
|
|
}
|
|
return TRUE;
|
|
|
|
UNREFERENCED_PARAMETER( FileObject );
|
|
UNREFERENCED_PARAMETER( DeviceObject );
|
|
UNREFERENCED_PARAMETER( FileOffset );
|
|
}
|
|
|
|
|
|
VOID
|
|
NtfsAddToCompressedMdlChain (
|
|
IN OUT PMDL *MdlChain,
|
|
IN PVOID MdlBuffer,
|
|
IN ULONG MdlLength,
|
|
IN PERESOURCE ResourceToRelease OPTIONAL,
|
|
IN PBCB Bcb,
|
|
IN LOCK_OPERATION Operation,
|
|
IN ULONG IsCompressed
|
|
)
|
|
|
|
/*++
|
|
|
|
|
|
Routine Description:
|
|
|
|
This routine creates and Mdl for the described buffer and adds it to
|
|
the chain.
|
|
|
|
Arguments:
|
|
|
|
MdlChain - MdlChain pointer to append the first/new Mdl to.
|
|
|
|
MdlBuffer - Buffer address for this Mdl.
|
|
|
|
MdlLength - Length of buffer in bytes.
|
|
|
|
ResourceToRelease - Indicates which resource to release, only specified for compressed IO.
|
|
|
|
Bcb - Bcb to remember with this Mdl, to be freed when Mdl completed
|
|
|
|
Operation - IoReadAccess or IoWriteAccess
|
|
|
|
IsCompressed - Supplies TRUE if the Bcb is in the compressed stream
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
|
|
{
|
|
PMDL Mdl, MdlTemp;
|
|
|
|
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 + (2 * 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 -= 2 * sizeof(ULONG);
|
|
Mdl->ByteCount -= 2 * PAGE_SIZE;
|
|
|
|
//
|
|
// 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.
|
|
//
|
|
|
|
if (Mdl->ByteCount != 0) {
|
|
MmProbeAndLockPages( Mdl, KernelMode, Operation );
|
|
}
|
|
|
|
//
|
|
// Only store the Bcb if this is the compressed stream.
|
|
//
|
|
|
|
if (!IsCompressed && (Bcb != NULL)) {
|
|
Bcb = NULL;
|
|
}
|
|
*(PBCB *)Add2Ptr( Mdl, Mdl->Size ) = Bcb;
|
|
*(PERESOURCE *)Add2Ptr( Mdl, Mdl->Size + sizeof( PBCB )) = ResourceToRelease;
|
|
|
|
//
|
|
// 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
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine may be called to set all of the Bcb resource owners in an Mdl
|
|
to be equal to the address of the first element in the MdlChain, so that they
|
|
can be freed in the context of a different thread.
|
|
|
|
Arguments:
|
|
|
|
MdlChain - Supplies the MdlChain to process
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
|
|
{
|
|
PBCB Bcb;
|
|
|
|
while (MdlChain != NULL) {
|
|
|
|
//
|
|
// Unpin the Bcb we saved away, and restore the Mdl counts
|
|
// we altered.
|
|
//
|
|
|
|
Bcb = *(PBCB *)Add2Ptr(MdlChain, MdlChain->Size);
|
|
if (Bcb != NULL) {
|
|
CcSetBcbOwnerPointer( Bcb, (PVOID)((PCHAR)MdlChain + 3) );
|
|
}
|
|
|
|
MdlChain = MdlChain->Next;
|
|
}
|
|
}
|
|
|
|
VOID
|
|
NtfsCleanupCompressedMdlChain (
|
|
IN PMDL MdlChain,
|
|
IN ULONG Error
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine is called to free all of the resources associated with a
|
|
compressed Mdl chain. It may be called for an error in the processing
|
|
of a request or when a request completes.
|
|
|
|
Arguments:
|
|
|
|
MdlChain - Supplies the address of the first element in the chain to clean up.
|
|
|
|
Error - Supplies TRUE on error (resources are still owned by current thread) or
|
|
FALSE on a normal completion (resources owned by MdlChain).
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
|
|
{
|
|
PMDL MdlTemp;
|
|
PBCB Bcb;
|
|
|
|
while (MdlChain != NULL) {
|
|
|
|
//
|
|
// Save a pointer to the next guy in the chain.
|
|
//
|
|
|
|
MdlTemp = MdlChain->Next;
|
|
|
|
//
|
|
// Unlock the pages.
|
|
//
|
|
|
|
if (MdlChain->ByteCount != 0) {
|
|
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 += 2 * sizeof(ULONG);
|
|
MdlChain->ByteCount += 2 * PAGE_SIZE;
|
|
|
|
IoFreeMdl( MdlChain );
|
|
|
|
MdlChain = MdlTemp;
|
|
}
|
|
}
|
|
|
|
|
|
NTSTATUS
|
|
NtfsSynchronizeUncompressedIo (
|
|
IN PSCB Scb,
|
|
IN PLONGLONG FileOffset OPTIONAL,
|
|
IN ULONG Length,
|
|
IN ULONG WriteAccess,
|
|
IN OUT PCOMPRESSION_SYNC *CompressionSync
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine attempts to synchronize with the compressed data cache,
|
|
for an I/O in the uncompressed cache. The view in the compressed cache
|
|
is locked shared or exclusive without reading. Then the compressed cache
|
|
is flushed and purged as appropriate.
|
|
|
|
We will allocate a COMPRESSION_SYNC structure to serialize each cache
|
|
manager view and use that for the locking granularity.
|
|
|
|
Arguments:
|
|
|
|
Scb - Supplies the Scb for the stream.
|
|
|
|
FileOffset - Byte offset in file for desired data. NULL if we are to
|
|
flush and purge the entire file.
|
|
|
|
Length - Length of desired data in bytes.
|
|
|
|
WriteAccess - Supplies TRUE if the caller plans to do a write, or FALSE
|
|
for a read.
|
|
|
|
CompressionSync - Synchronization object to serialize access to the view.
|
|
The caller's routine is responsible for releasing this.
|
|
|
|
Return Value:
|
|
|
|
Status of the flush operation, or STATUS_UNSUCCESSFUL for a WriteAccess
|
|
where the purge failed.
|
|
|
|
--*/
|
|
|
|
{
|
|
ULONG Change = 0;
|
|
IO_STATUS_BLOCK IoStatus;
|
|
PSECTION_OBJECT_POINTERS SectionObjectPointers = &Scb->NonpagedScb->SegmentObjectC;
|
|
LONGLONG LocalFileOffset;
|
|
PLONGLONG LocalOffsetPtr;
|
|
|
|
if (ARGUMENT_PRESENT( FileOffset )) {
|
|
|
|
LocalFileOffset = *FileOffset & ~(VACB_MAPPING_GRANULARITY - 1);
|
|
LocalOffsetPtr = &LocalFileOffset;
|
|
ASSERT( ((*FileOffset & (VACB_MAPPING_GRANULARITY - 1)) + Length) <= VACB_MAPPING_GRANULARITY );
|
|
|
|
} else {
|
|
|
|
LocalFileOffset = 0;
|
|
LocalOffsetPtr = NULL;
|
|
Length = 0;
|
|
}
|
|
|
|
IoStatus.Status = STATUS_SUCCESS;
|
|
if ((*CompressionSync == NULL) || ((*CompressionSync)->FileOffset != LocalFileOffset)) {
|
|
|
|
if (*CompressionSync != NULL) {
|
|
|
|
NtfsReleaseCompressionSync( *CompressionSync );
|
|
*CompressionSync = NULL;
|
|
}
|
|
|
|
*CompressionSync = NtfsAcquireCompressionSync( LocalFileOffset, Scb, WriteAccess );
|
|
|
|
//
|
|
// Always flush the remainder of the Vacb. This is to prevent a problem if MM reads additional
|
|
// pages into section because of the page fault clustering.
|
|
//
|
|
|
|
if (ARGUMENT_PRESENT( FileOffset )) {
|
|
|
|
LocalFileOffset = *FileOffset & ~((ULONG_PTR)Scb->CompressionUnit - 1);
|
|
Length = VACB_MAPPING_GRANULARITY - (((ULONG) LocalFileOffset) & (VACB_MAPPING_GRANULARITY - 1));
|
|
}
|
|
|
|
//
|
|
// We must always flush the other cache.
|
|
//
|
|
|
|
CcFlushCache( SectionObjectPointers,
|
|
(PLARGE_INTEGER) LocalOffsetPtr,
|
|
Length,
|
|
&IoStatus );
|
|
|
|
#ifdef NTFS_RWCMP_TRACE
|
|
if (NtfsCompressionTrace && IsSyscache(Scb)) {
|
|
DbgPrint(" CcFlushCache: FO = %08lx, Len = %08lx, IoStatus = %08lx, Scb = %08lx\n",
|
|
(ULONG)LocalFileOffset,
|
|
Length,
|
|
IoStatus.Status,
|
|
Scb );
|
|
}
|
|
#endif
|
|
|
|
//
|
|
// On writes, we purge the other cache after a successful flush.
|
|
//
|
|
|
|
if (WriteAccess && NT_SUCCESS(IoStatus.Status)) {
|
|
|
|
if (!CcPurgeCacheSection( SectionObjectPointers,
|
|
(PLARGE_INTEGER) LocalOffsetPtr,
|
|
Length,
|
|
FALSE )) {
|
|
|
|
return STATUS_UNSUCCESSFUL;
|
|
}
|
|
}
|
|
}
|
|
|
|
return IoStatus.Status;
|
|
}
|
|
|
|
|
|
NTSTATUS
|
|
NtfsSynchronizeCompressedIo (
|
|
IN PSCB Scb,
|
|
IN PLONGLONG FileOffset,
|
|
IN ULONG Length,
|
|
IN ULONG WriteAccess,
|
|
IN OUT PCOMPRESSION_SYNC *CompressionSync
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine attempts to synchronize with the uncompressed data cache,
|
|
for an I/O in the compressed cache. The range in the compressed cache
|
|
is assumed to already be locked by the caller. Then the uncompressed cache
|
|
is flushed and purged as appropriate.
|
|
|
|
We will allocate a COMPRESSION_SYNC structure to serialize each cache
|
|
manager view and use that for the locking granularity.
|
|
|
|
Arguments:
|
|
|
|
Scb - Supplies the Scb for the stream.
|
|
|
|
FileOffset - Byte offset in file for desired data.
|
|
|
|
Length - Length of desired data in bytes.
|
|
|
|
WriteAccess - Supplies TRUE if the caller plans to do a write, or FALSE
|
|
for a read.
|
|
|
|
CompressionSync - Synchronization object to serialize access to the view.
|
|
The caller's routine is responsible for releasing this.
|
|
|
|
Return Value:
|
|
|
|
Status of the flush operation, or STATUS_USER_MAPPED_FILE for a WriteAccess
|
|
where the purge failed. (This is the only expected case where a purge would
|
|
fail.
|
|
|
|
--*/
|
|
|
|
{
|
|
IO_STATUS_BLOCK IoStatus;
|
|
PSECTION_OBJECT_POINTERS SectionObjectPointers = &Scb->NonpagedScb->SegmentObject;
|
|
LONGLONG LocalFileOffset = *FileOffset & ~(VACB_MAPPING_GRANULARITY - 1);
|
|
|
|
IoStatus.Status = STATUS_SUCCESS;
|
|
if ((*CompressionSync == NULL) || ((*CompressionSync)->FileOffset != LocalFileOffset)) {
|
|
|
|
//
|
|
// Release any previous view and Lock the current view.
|
|
//
|
|
|
|
if (*CompressionSync != NULL) {
|
|
|
|
NtfsReleaseCompressionSync( *CompressionSync );
|
|
*CompressionSync = NULL;
|
|
}
|
|
|
|
*CompressionSync = NtfsAcquireCompressionSync( LocalFileOffset, Scb, WriteAccess );
|
|
|
|
//
|
|
// Now that we are synchronized on a view, test for a write to a user-mapped file.
|
|
// In case we keep hitting this path, this is better than waiting for a purge to
|
|
// fail.
|
|
//
|
|
|
|
if (WriteAccess &&
|
|
(FlagOn( Scb->Header.Flags, FSRTL_FLAG_USER_MAPPED_FILE ) ||
|
|
FlagOn( Scb->ScbState, SCB_STATE_REALLOCATE_ON_WRITE ))) {
|
|
return STATUS_USER_MAPPED_FILE;
|
|
}
|
|
|
|
//
|
|
// Always flush the remainder of the Vacb. This is to prevent a problem if MM reads additional
|
|
// pages into section because of the page fault clustering.
|
|
//
|
|
|
|
LocalFileOffset = *FileOffset & ~((ULONG_PTR)Scb->CompressionUnit - 1);
|
|
Length = VACB_MAPPING_GRANULARITY - (((ULONG) LocalFileOffset) & (VACB_MAPPING_GRANULARITY - 1));
|
|
|
|
//
|
|
// We must always flush the other cache.
|
|
//
|
|
|
|
CcFlushCache( SectionObjectPointers,
|
|
(PLARGE_INTEGER)&LocalFileOffset,
|
|
Length,
|
|
&IoStatus );
|
|
|
|
//
|
|
// On writes, we purge the other cache after a successful flush.
|
|
//
|
|
|
|
if (WriteAccess && NT_SUCCESS(IoStatus.Status)) {
|
|
|
|
if (!CcPurgeCacheSection( SectionObjectPointers,
|
|
(PLARGE_INTEGER)&LocalFileOffset,
|
|
Length,
|
|
FALSE )) {
|
|
|
|
return STATUS_USER_MAPPED_FILE;
|
|
}
|
|
}
|
|
}
|
|
|
|
return IoStatus.Status;
|
|
}
|
|
|
|
|
|
PCOMPRESSION_SYNC
|
|
NtfsAcquireCompressionSync (
|
|
IN LONGLONG FileOffset,
|
|
IN PSCB Scb,
|
|
IN ULONG WriteAccess
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine is called to lock a range of a stream to serialize the compressed and
|
|
uncompressed IO.
|
|
|
|
Arguments:
|
|
|
|
FileOffset - File offset to lock. This will be rounded to a cache view boundary.
|
|
|
|
Scb - Supplies the Scb for the stream.
|
|
|
|
WriteAccess - Indicates if the user wants write access. We will acquire the range
|
|
exclusively in that case.
|
|
|
|
Return Value:
|
|
|
|
PCOMPRESSION_SYNC - A pointer to the synchronization object for the range. This routine may
|
|
raise, typically if the structure can't be allocated.
|
|
|
|
--*/
|
|
|
|
{
|
|
PCOMPRESSION_SYNC CompressionSync = NULL;
|
|
PCOMPRESSION_SYNC NewCompressionSync;
|
|
BOOLEAN FoundSync = FALSE;
|
|
|
|
PAGED_CODE();
|
|
|
|
//
|
|
// Round the file offset down to a view boundary.
|
|
//
|
|
|
|
((PLARGE_INTEGER) &FileOffset)->LowPart &= ~(VACB_MAPPING_GRANULARITY - 1);
|
|
|
|
//
|
|
// Acquire the mutex for the stream. Then walk and look for a matching resource.
|
|
//
|
|
|
|
NtfsAcquireFsrtlHeader( Scb );
|
|
|
|
CompressionSync = (PCOMPRESSION_SYNC) Scb->ScbType.Data.CompressionSyncList.Flink;
|
|
|
|
while (CompressionSync != (PCOMPRESSION_SYNC) &Scb->ScbType.Data.CompressionSyncList) {
|
|
|
|
//
|
|
// Continue if we haven't found our entry.
|
|
//
|
|
|
|
if (CompressionSync->FileOffset < FileOffset) {
|
|
|
|
//
|
|
// Go to the next entry.
|
|
//
|
|
|
|
CompressionSync = (PCOMPRESSION_SYNC) CompressionSync->CompressionLinks.Flink;
|
|
continue;
|
|
}
|
|
|
|
if (CompressionSync->FileOffset == FileOffset) {
|
|
|
|
FoundSync = TRUE;
|
|
}
|
|
|
|
//
|
|
// Exit in any case.
|
|
//
|
|
|
|
break;
|
|
}
|
|
|
|
//
|
|
// If we didn't find the entry then attempt to allocate a new one.
|
|
//
|
|
|
|
if (!FoundSync) {
|
|
|
|
NewCompressionSync = (PCOMPRESSION_SYNC) ExAllocateFromNPagedLookasideList( &NtfsCompressSyncLookasideList );
|
|
|
|
//
|
|
// Release the mutex and raise an error if we couldn't allocate.
|
|
//
|
|
|
|
if (NewCompressionSync == NULL) {
|
|
|
|
NtfsReleaseFsrtlHeader( Scb );
|
|
ExRaiseStatus( STATUS_INSUFFICIENT_RESOURCES );
|
|
}
|
|
|
|
//
|
|
// We have the new entry and know where it belongs in the list. Do the final initialization
|
|
// and add it to the list.
|
|
//
|
|
|
|
NewCompressionSync->FileOffset = FileOffset;
|
|
NewCompressionSync->Scb = Scb;
|
|
|
|
//
|
|
// Add it just ahead of the entry we stopped at.
|
|
//
|
|
|
|
InsertTailList( &CompressionSync->CompressionLinks, &NewCompressionSync->CompressionLinks );
|
|
CompressionSync = NewCompressionSync;
|
|
}
|
|
|
|
//
|
|
// We know have the structure. Reference it so it can't go away. Then drop the
|
|
// mutex and wait for it.
|
|
//
|
|
|
|
CompressionSync->ReferenceCount += 1;
|
|
|
|
NtfsReleaseFsrtlHeader( Scb );
|
|
|
|
if (WriteAccess) {
|
|
|
|
ExAcquireResourceExclusiveLite( &CompressionSync->Resource, TRUE );
|
|
|
|
} else {
|
|
|
|
ExAcquireResourceSharedLite( &CompressionSync->Resource, TRUE );
|
|
}
|
|
|
|
return CompressionSync;
|
|
}
|
|
|
|
|
|
VOID
|
|
NtfsReleaseCompressionSync (
|
|
IN PCOMPRESSION_SYNC CompressionSync
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine is called to release a range in a stream which was locked serial compressed and
|
|
uncompressed IO.
|
|
|
|
Arguments:
|
|
|
|
CompressionSync - Pointer to the synchronization object.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
|
|
{
|
|
PSCB Scb = CompressionSync->Scb;
|
|
PAGED_CODE();
|
|
|
|
//
|
|
// Release the resource and then acquire the mutext for the stream. If we are the last
|
|
// reference then free the structure.
|
|
//
|
|
|
|
ExReleaseResourceLite( &CompressionSync->Resource );
|
|
|
|
NtfsAcquireFsrtlHeader( Scb );
|
|
|
|
CompressionSync->ReferenceCount -= 1;
|
|
if (CompressionSync->ReferenceCount == 0) {
|
|
|
|
RemoveEntryList( &CompressionSync->CompressionLinks );
|
|
ExFreeToNPagedLookasideList( &NtfsCompressSyncLookasideList, CompressionSync );
|
|
}
|
|
|
|
NtfsReleaseFsrtlHeader( Scb );
|
|
return;
|
|
}
|
|
|