|
|
/*++
Copyright (c) 1991 Microsoft Corporation
Module Name:
CacheSup.c
Abstract:
This module implements the cache management routines for Ntfs
Author:
Your Name [Email] dd-Mon-Year
Revision History:
--*/
#include "NtfsProc.h"
//
// The Bug check file id for this module
//
#define BugCheckFileId (NTFS_BUG_CHECK_CACHESUP)
#define MAX_ZERO_THRESHOLD (0x00400000)
//
// Local debug trace level
//
#define Dbg (DEBUG_TRACE_CACHESUP)
#ifdef ALLOC_PRAGMA
#pragma alloc_text(PAGE, NtfsCompleteMdl)
#pragma alloc_text(PAGE, NtfsCreateInternalStreamCommon)
#pragma alloc_text(PAGE, NtfsDeleteInternalAttributeStream)
#pragma alloc_text(PAGE, NtfsMapStream)
#pragma alloc_text(PAGE, NtfsPinMappedData)
#pragma alloc_text(PAGE, NtfsPinStream)
#pragma alloc_text(PAGE, NtfsPreparePinWriteStream)
#pragma alloc_text(PAGE, NtfsZeroData)
#endif
VOID NtfsCreateInternalStreamCommon ( IN PIRP_CONTEXT IrpContext, IN PSCB Scb, IN BOOLEAN UpdateScb, IN BOOLEAN CompressedStream, IN UNICODE_STRING const *StreamName )
/*++
Routine Description:
This routine is called to prepare a stream file associated with a particular attribute of a file. On return, the Scb for the attribute will have an associated stream file object. On return, this stream file will have been initialized through the cache manager.
TEMPCODE The following assumptions have been made or if open issue, still unresolved.
- Assume. The call to create Scb will initialize the Mcb for the non-resident case.
- Assume. When this file is created I increment the open count but not the unclean count for this Scb. When we are done with the stream file, we should uninitialize it and dereference it. We also set the file object pointer to NULL. Close will then do the correct thing.
- Assume. Since this call is likely to be followed shortly by either a read or write, the cache map is initialized here.
Arguments:
Scb - Supplies the address to store the Scb for this attribute and stream file. This will exist on return from this function.
UpdateScb - Indicates if the caller wants to update the Scb from the attribute.
CompressedStream - Supplies TRUE if caller wishes to create the compressed stream.
StreamName - Internal stream name or NULL is there isn't one available. This is a constant value so we don't have to allocate any pool.
Return Value:
None.
--*/
{ PVCB Vcb = Scb->Vcb;
CC_FILE_SIZES CcFileSizes; PFILE_OBJECT CallersFileObject; PFILE_OBJECT *FileObjectPtr = &Scb->FileObject; PFILE_OBJECT UnwindStreamFile = NULL;
BOOLEAN UnwindInitializeCacheMap = FALSE; BOOLEAN DecrementScbCleanup = FALSE;
BOOLEAN AcquiredMutex = FALSE;
ASSERT_IRP_CONTEXT( IrpContext );
PAGED_CODE();
DebugTrace( +1, Dbg, ("NtfsCreateInternalAttributeStream\n") ); DebugTrace( 0, Dbg, ("Scb -> %08lx\n", Scb) );
//
// Change FileObjectPtr if he wants the compressed stream
//
#ifdef COMPRESS_ON_WIRE
if (CompressedStream) { FileObjectPtr = &Scb->Header.FileObjectC; } #endif
//
// If there is no file object, we create one and initialize
// it.
//
if (*FileObjectPtr == NULL) {
//
// Only acquire the mutex if we don't have the file exclusive.
//
if (!NtfsIsExclusiveScb( Scb )) {
KeWaitForSingleObject( &StreamFileCreationMutex, Executive, KernelMode, FALSE, NULL ); AcquiredMutex = TRUE; }
try {
//
// Someone could have gotten there first.
//
if (*FileObjectPtr == NULL) {
UnwindStreamFile = IoCreateStreamFileObjectLite( NULL, Scb->Vcb->Vpb->RealDevice);
if (ARGUMENT_PRESENT( StreamName )) { UnwindStreamFile->FileName.MaximumLength = StreamName->MaximumLength; UnwindStreamFile->FileName.Length = StreamName->Length; UnwindStreamFile->FileName.Buffer = StreamName->Buffer; }
//
// Propagate any flags from the caller's FileObject to our
// stream file that the Cache Manager may look at, so we do not
// miss hints like sequential only or temporary.
//
if (!FlagOn(Scb->ScbState, SCB_STATE_MODIFIED_NO_WRITE) && (IrpContext->OriginatingIrp != NULL) && (CallersFileObject = IoGetCurrentIrpStackLocation(IrpContext->OriginatingIrp)->FileObject)) {
SetFlag( UnwindStreamFile->Flags, CallersFileObject->Flags & NTFS_FO_PROPAGATE_TO_STREAM ); }
UnwindStreamFile->SectionObjectPointer = &Scb->NonpagedScb->SegmentObject;
//
// For a compressed stream, we have to use separate section
// object pointers.
//
#ifdef COMPRESS_ON_WIRE
if (CompressedStream) { UnwindStreamFile->SectionObjectPointer = &Scb->NonpagedScb->SegmentObjectC;
} #endif
//
// If we have created the stream file, we set it to type
// 'StreamFileOpen'
//
NtfsSetFileObject( UnwindStreamFile, StreamFileOpen, Scb, NULL );
if (FlagOn( Scb->ScbState, SCB_STATE_TEMPORARY )) {
SetFlag( UnwindStreamFile->Flags, FO_TEMPORARY_FILE ); }
//
// Initialize the fields of the file object.
//
UnwindStreamFile->ReadAccess = TRUE; UnwindStreamFile->WriteAccess = TRUE; UnwindStreamFile->DeleteAccess = TRUE;
//
// Increment the open count and set the section
// object pointers. We don't set the unclean count as the
// cleanup call has already occurred.
//
NtfsIncrementCloseCounts( Scb, TRUE, FALSE );
//
// Increment the cleanup count in this Scb to prevent the
// Scb from going away if the cache call fails.
//
InterlockedIncrement( &Scb->CleanupCount ); DecrementScbCleanup = TRUE;
//
// If the Scb header has not been initialized, we will do so now.
//
if (UpdateScb && !FlagOn( Scb->ScbState, SCB_STATE_HEADER_INITIALIZED )) {
NtfsUpdateScbFromAttribute( IrpContext, Scb, NULL ); }
//
// If this is a compressed stream and the file is not already
// marked as MODIFIED_NO_WRITE then do it now. Use the
// Extended flag field in the Fsrtl header for this. Since this
// is the only place we make this call with FsContext2 == NULL,
// it does not matter how we leave the FsRtl header flag.!
//
NtfsAcquireFsrtlHeader( Scb ); ClearFlag(Scb->Header.Flags2, FSRTL_FLAG2_DO_MODIFIED_WRITE); if (!FlagOn( Scb->ScbState, SCB_STATE_MODIFIED_NO_WRITE ) && !FlagOn( Scb->Header.Flags2, FSRTL_FLAG2_DO_MODIFIED_WRITE ) && !CompressedStream) {
SetFlag(Scb->Header.Flags2, FSRTL_FLAG2_DO_MODIFIED_WRITE); } NtfsReleaseFsrtlHeader( Scb );
//
// Check if we need to initialize the cache map for the stream file.
// The size of the section to map will be the current allocation
// for the stream file.
//
if (UnwindStreamFile->PrivateCacheMap == NULL) {
BOOLEAN PinAccess;
CcFileSizes = *(PCC_FILE_SIZES)&Scb->Header.AllocationSize;
//
// If this is a stream with Usa protection, we want to tell
// the Cache Manager we do not need to get any valid data
// callbacks. We do this by having xxMax sitting in
// ValidDataLength for the call, but we have to restore the
// correct value afterwards.
//
// We also do this for all of the stream files created during
// restart. This has the effect of telling Mm to always
// fault the page in from disk. Don't generate a zero page if
// push up the file size during restart.
//
if (FlagOn( Scb->ScbState, SCB_STATE_MODIFIED_NO_WRITE )) {
CcFileSizes.ValidDataLength.QuadPart = MAXLONGLONG; }
PinAccess = (BOOLEAN) (Scb->AttributeTypeCode != $DATA || FlagOn(Scb->Fcb->FcbState, FCB_STATE_PAGING_FILE | FCB_STATE_SYSTEM_FILE) || FlagOn( Scb->Vcb->VcbState, VCB_STATE_RESTART_IN_PROGRESS ) || CompressedStream);
//
// Bias this for the Usn journal.
//
if (FlagOn( Scb->ScbPersist, SCB_PERSIST_USN_JOURNAL )) {
CcFileSizes.AllocationSize.QuadPart -= Vcb->UsnCacheBias; CcFileSizes.FileSize.QuadPart -= Vcb->UsnCacheBias; }
CcInitializeCacheMap( UnwindStreamFile, &CcFileSizes, PinAccess, &NtfsData.CacheManagerCallbacks, (PCHAR)Scb + CompressedStream );
UnwindInitializeCacheMap = TRUE; }
//
// Now call Cc to set the log handle for the file.
//
if (FlagOn( Scb->ScbState, SCB_STATE_MODIFIED_NO_WRITE ) && (Scb != Vcb->LogFileScb)) {
CcSetLogHandleForFile( UnwindStreamFile, Vcb->LogHandle, &LfsFlushToLsn ); }
//
// It is now safe to store the stream file in the Scb. We wait
// until now because we don't want an unsafe tester to use the
// file object until the cache is initialized.
//
*FileObjectPtr = UnwindStreamFile; }
} finally {
DebugUnwind( NtfsCreateInternalAttributeStream );
//
// Undo our work if an error occurred.
//
if (AbnormalTermination()) {
//
// Uninitialize the cache file if we initialized it.
//
if (UnwindInitializeCacheMap) {
CcUninitializeCacheMap( UnwindStreamFile, NULL, NULL ); }
//
// Dereference the stream file if we created it.
//
if (UnwindStreamFile != NULL) {
//
// Clear the internal file name constant
//
NtfsClearInternalFilename( UnwindStreamFile );
ObDereferenceObject( UnwindStreamFile ); } }
//
// Restore the Scb cleanup count.
//
if (DecrementScbCleanup) {
InterlockedDecrement( &Scb->CleanupCount ); }
if (AcquiredMutex) {
KeReleaseMutant( &StreamFileCreationMutex, IO_NO_INCREMENT, FALSE, FALSE ); }
DebugTrace( -1, Dbg, ("NtfsCreateInternalAttributeStream -> VOID\n") ); } }
return; }
BOOLEAN NtfsDeleteInternalAttributeStream ( IN PSCB Scb, IN ULONG ForceClose, IN ULONG CompressedStreamOnly )
/*++
Routine Description:
This routine is the inverse of NtfsCreateInternalAttributeStream. It uninitializes the cache map and dereferences the stream file object. It is coded defensively, in case the stream file object does not exist or the cache map has not been initialized.
Arguments:
Scb - Supplies the Scb for which the stream file is to be deleted.
ForceClose - Indicates if we to immediately close everything down or if we are willing to let Mm slowly migrate things out.
CompressedStreamOnly - Indicates if we only want to delete the compressed stream.
Return Value:
BOOLEAN - TRUE if we dereference a file object, FALSE otherwise.
--*/
{ PFILE_OBJECT FileObject; #ifdef COMPRESS_ON_WIRE
PFILE_OBJECT FileObjectC; #endif
BOOLEAN Dereferenced = FALSE;
PAGED_CODE();
//
// We normally already have the paging Io resource. If we do
// not, then it is typically some cleanup path of create or
// whatever. This code assumes that if we cannot get the paging
// Io resource, then there is other activity still going on,
// and it is ok to not delete the stream! For example, it could
// be the lazy writer, who definitely needs the stream.
//
if ( #ifdef COMPRESS_ON_WIRE
((Scb->FileObject != NULL) || (Scb->Header.FileObjectC != NULL)) && #else
(Scb->FileObject != NULL) && #endif
((Scb->Header.PagingIoResource == NULL) || ExAcquireResourceExclusiveLite( Scb->Header.PagingIoResource, FALSE ))) {
KeWaitForSingleObject( &StreamFileCreationMutex, Executive, KernelMode, FALSE, NULL );
//
// Capture both file objects and clear the fields so no one else
// can access them.
//
if (CompressedStreamOnly) {
FileObject = NULL;
} else {
FileObject = Scb->FileObject; Scb->FileObject = NULL;
//
// Clear the internal file name constant
//
NtfsClearInternalFilename( FileObject ); }
#ifdef COMPRESS_ON_WIRE
FileObjectC = Scb->Header.FileObjectC; Scb->Header.FileObjectC = NULL; #endif
KeReleaseMutant( &StreamFileCreationMutex, IO_NO_INCREMENT, FALSE, FALSE );
if (Scb->Header.PagingIoResource != NULL) { ExReleaseResourceLite( Scb->Header.PagingIoResource ); }
//
// Now dereference each file object.
//
if (FileObject != NULL) {
//
// We shouldn't be deleting the internal stream objects of the MFT & co, unless
// we are in the dismounting, restarting or mounting path.
//
ASSERT( (((PSCB) FileObject->FsContext)->Header.NodeTypeCode != NTFS_NTC_SCB_MFT) || FlagOn( Scb->Vcb->VcbState, VCB_STATE_RESTART_IN_PROGRESS ) || FlagOn( Scb->Vcb->VcbState, VCB_STATE_PERFORMED_DISMOUNT ) || !FlagOn( Scb->Vcb->VcbState, VCB_STATE_VOLUME_MOUNTED ) || Scb->Vcb->RootIndexScb == NULL );
if (FileObject->PrivateCacheMap != NULL) {
CcUninitializeCacheMap( FileObject, (ForceClose ? &Li0 : NULL), NULL ); }
ObDereferenceObject( FileObject ); Dereferenced = TRUE; }
#ifdef COMPRESS_ON_WIRE
if (FileObjectC != NULL) {
if (FileObjectC->PrivateCacheMap != NULL) {
CcUninitializeCacheMap( FileObjectC, (ForceClose ? &Li0 : NULL), NULL ); }
//
// For the compressed stream, deallocate the additional
// section object pointers.
//
ObDereferenceObject( FileObjectC ); Dereferenced = TRUE; } #endif
}
return Dereferenced; }
VOID NtfsMapStream ( IN PIRP_CONTEXT IrpContext, IN PSCB Scb, IN LONGLONG FileOffset, IN ULONG Length, OUT PVOID *Bcb, OUT PVOID *Buffer )
/*++
Routine Description:
This routine is called to map a range of bytes within the stream file for an Scb. The allowed range to map is bounded by the allocation size for the Scb. This operation is only valid on a non-resident Scb.
TEMPCODE - The following need to be resolved for this routine.
- Can the caller specify either an empty range or an invalid range. In that case we need to able to return the actual length of the mapped range.
Arguments:
Scb - This is the Scb for the operation.
FileOffset - This is the offset within the Scb where the data is to be pinned.
Length - This is the number of bytes to pin.
Bcb - Returns a pointer to the Bcb for this range of bytes.
Buffer - Returns a pointer to the range of bytes. We can fault them in by touching them, but they aren't guaranteed to stay unless we pin them via the Bcb.
Return Value:
None.
--*/
{ ASSERT_IRP_CONTEXT( IrpContext ); ASSERT_SCB( Scb ); ASSERT( Length != 0 );
PAGED_CODE();
DebugTrace( +1, Dbg, ("NtfsMapStream\n") ); DebugTrace( 0, Dbg, ("Scb = %08lx\n", Scb) ); DebugTrace( 0, Dbg, ("FileOffset = %016I64x\n", FileOffset) ); DebugTrace( 0, Dbg, ("Length = %08lx\n", Length) );
//
// The file object should already exist in the Scb.
//
ASSERT( Scb->FileObject != NULL );
//
// If we are trying to go beyond the end of the allocation, assume
// we have some corruption.
//
if ((FileOffset + Length) > Scb->Header.AllocationSize.QuadPart) {
NtfsRaiseStatus( IrpContext, STATUS_FILE_CORRUPT_ERROR, NULL, Scb->Fcb ); }
//
// Call the cache manager to map the data. This call may raise, but
// will never return an error (including CANT_WAIT).
//
if (!CcMapData( Scb->FileObject, (PLARGE_INTEGER)&FileOffset, Length, TRUE, Bcb, Buffer )) {
NtfsRaiseStatus( IrpContext, STATUS_CANT_WAIT, NULL, NULL ); } #ifdef MAPCOUNT_DBG
IrpContext->MapCount++; #endif
DebugTrace( 0, Dbg, ("Buffer -> %08lx\n", *Buffer) ); DebugTrace( -1, Dbg, ("NtfsMapStream -> VOID\n") );
return; }
VOID NtfsPinMappedData ( IN PIRP_CONTEXT IrpContext, IN PSCB Scb, IN LONGLONG FileOffset, IN ULONG Length, IN OUT PVOID *Bcb )
/*++
Routine Description:
This routine is called to pin a previously mapped range of bytes within the stream file for an Scb, for the purpose of subsequently modifying this byte range. The allowed range to map is bounded by the allocation size for the Scb. This operation is only valid on a non-resident Scb.
The data is guaranteed to stay at the same virtual address as previously returned from NtfsMapStream.
TEMPCODE - The following need to be resolved for this routine.
- Can the caller specify either an empty range or an invalid range. In that case we need to able to return the actual length of the mapped range.
Arguments:
Scb - This is the Scb for the operation.
FileOffset - This is the offset within the Scb where the data is to be pinned.
Length - This is the number of bytes to pin.
Bcb - Returns a pointer to the Bcb for this range of bytes.
Return Value:
None.
--*/
{ ASSERT_IRP_CONTEXT( IrpContext ); ASSERT_SCB( Scb ); ASSERT( Length != 0 );
PAGED_CODE();
DebugTrace( +1, Dbg, ("NtfsPinMappedData\n") ); DebugTrace( 0, Dbg, ("Scb = %08lx\n", Scb) ); DebugTrace( 0, Dbg, ("FileOffset = %016I64x\n", FileOffset) ); DebugTrace( 0, Dbg, ("Length = %08lx\n", Length) );
//
// The file object should already exist in the Scb.
//
ASSERT( Scb->FileObject != NULL );
//
// If we are trying to go beyond the end of the allocation, assume
// we have some corruption.
//
if ((FileOffset + Length) > Scb->Header.AllocationSize.QuadPart) {
NtfsRaiseStatus( IrpContext, STATUS_FILE_CORRUPT_ERROR, NULL, Scb->Fcb ); }
//
// Call the cache manager to map the data. This call may raise, but
// will never return an error (including CANT_WAIT).
//
if (!CcPinMappedData( Scb->FileObject, (PLARGE_INTEGER)&FileOffset, Length, FlagOn( IrpContext->State, IRP_CONTEXT_STATE_WAIT ), Bcb )) {
NtfsRaiseStatus( IrpContext, STATUS_CANT_WAIT, NULL, NULL ); }
DebugTrace( -1, Dbg, ("NtfsMapStream -> VOID\n") );
return; }
VOID NtfsPinStream ( IN PIRP_CONTEXT IrpContext, IN PSCB Scb, IN LONGLONG FileOffset, IN ULONG Length, OUT PVOID *Bcb, OUT PVOID *Buffer )
/*++
Routine Description:
This routine is called to pin a range of bytes within the stream file for an Scb. The allowed range to pin is bounded by the allocation size for the Scb. This operation is only valid on a non-resident Scb.
TEMPCODE - The following need to be resolved for this routine.
- Can the caller specify either an empty range or an invalid range. In that case we need to able to return the actual length of the pinned range.
Arguments:
Scb - This is the Scb for the operation.
FileOffset - This is the offset within the Scb where the data is to be pinned.
Length - This is the number of bytes to pin.
Bcb - Returns a pointer to the Bcb for this range of bytes.
Buffer - Returns a pointer to the range of bytes pinned in memory.
Return Value:
None.
--*/
{ NTSTATUS OldStatus = IrpContext->ExceptionStatus;
ASSERT_IRP_CONTEXT( IrpContext ); ASSERT_SCB( Scb ); ASSERT( Length != 0 );
PAGED_CODE();
DebugTrace( +1, Dbg, ("NtfsPinStream\n") ); DebugTrace( 0, Dbg, ("Scb = %08lx\n", Scb) ); DebugTrace( 0, Dbg, ("FileOffset = %016I64x\n", FileOffset) ); DebugTrace( 0, Dbg, ("Length = %08lx\n", Length) );
//
// The file object should already exist in the Scb.
//
ASSERT( Scb->FileObject != NULL );
//
// If we are trying to go beyond the end of the allocation, assume
// we have some corruption.
//
if ((FileOffset + Length) > Scb->Header.AllocationSize.QuadPart) {
NtfsRaiseStatus( IrpContext, STATUS_FILE_CORRUPT_ERROR, NULL, Scb->Fcb ); }
//
// Call the cache manager to map the data. This call may raise, or
// will return FALSE if waiting is required.
//
if (FlagOn( Scb->ScbPersist, SCB_PERSIST_USN_JOURNAL )) {
FileOffset -= Scb->Vcb->UsnCacheBias; }
if (!CcPinRead( Scb->FileObject, (PLARGE_INTEGER)&FileOffset, Length, FlagOn( IrpContext->State, IRP_CONTEXT_STATE_WAIT ), Bcb, Buffer )) {
ASSERT( !FlagOn( IrpContext->State, IRP_CONTEXT_STATE_WAIT ));
//
// Could not pin the data without waiting (cache miss).
//
NtfsRaiseStatus( IrpContext, STATUS_CANT_WAIT, NULL, NULL ); }
//
// We don't want to propagate wether or not we hit eof. Its assumed the code pinning is
// already filesize synchronized
//
if (IrpContext->ExceptionStatus == STATUS_END_OF_FILE) { IrpContext->ExceptionStatus = OldStatus; }
#ifdef MAPCOUNT_DBG
IrpContext->MapCount++; #endif
DebugTrace( 0, Dbg, ("Bcb -> %08lx\n", *Bcb) ); DebugTrace( 0, Dbg, ("Buffer -> %08lx\n", *Buffer) ); DebugTrace( -1, Dbg, ("NtfsMapStream -> VOID\n") );
return; }
VOID NtfsPreparePinWriteStream ( IN PIRP_CONTEXT IrpContext, IN PSCB Scb, IN LONGLONG FileOffset, IN ULONG Length, IN BOOLEAN Zero, OUT PVOID *Bcb, OUT PVOID *Buffer )
/*++
Routine Description:
Arguments:
Return Value:
--*/
{ ASSERT_IRP_CONTEXT( IrpContext ); ASSERT_SCB( Scb );
PAGED_CODE();
DebugTrace( +1, Dbg, ("NtfsPreparePinWriteStream\n") ); DebugTrace( 0, Dbg, ("Scb = %08lx\n", Scb) ); DebugTrace( 0, Dbg, ("FileOffset = %016I64x\n", FileOffset) ); DebugTrace( 0, Dbg, ("Length = %08lx\n", Length) );
//
// The file object should already exist in the Scb.
//
ASSERT( Scb->FileObject != NULL );
//
// If we are trying to go beyond the end of the allocation, assume
// we have some corruption.
//
if ((FileOffset + Length) > Scb->Header.AllocationSize.QuadPart) {
NtfsRaiseStatus( IrpContext, STATUS_FILE_CORRUPT_ERROR, NULL, Scb->Fcb ); }
//
// Call the cache manager to do it. This call may raise, or
// will return FALSE if waiting is required.
//
if (!CcPreparePinWrite( Scb->FileObject, (PLARGE_INTEGER)&FileOffset, Length, Zero, FlagOn( IrpContext->State, IRP_CONTEXT_STATE_WAIT ), Bcb, Buffer )) {
ASSERT( !FlagOn( IrpContext->State, IRP_CONTEXT_STATE_WAIT ));
//
// Could not pin the data without waiting (cache miss).
//
NtfsRaiseStatus( IrpContext, STATUS_CANT_WAIT, NULL, NULL ); } #ifdef MAPCOUNT_DBG
IrpContext->MapCount++; #endif
DebugTrace( 0, Dbg, ("Bcb -> %08lx\n", *Bcb) ); DebugTrace( 0, Dbg, ("Buffer -> %08lx\n", *Buffer) ); DebugTrace( -1, Dbg, ("NtfsPreparePinWriteStream -> VOID\n") );
return; }
NTSTATUS NtfsCompleteMdl ( IN PIRP_CONTEXT IrpContext, IN PIRP Irp )
/*++
Routine Description:
This routine performs the function of completing Mdl read and write requests. It should be called only from NtfsFsdRead and NtfsFsdWrite.
Arguments:
Irp - Supplies the originating Irp.
Return Value:
NTSTATUS - Will always be STATUS_PENDING or STATUS_SUCCESS.
--*/
{ PFILE_OBJECT FileObject; PIO_STACK_LOCATION IrpSp; PNTFS_ADVANCED_FCB_HEADER Header;
ASSERT( FlagOn( IrpContext->TopLevelIrpContext->State, IRP_CONTEXT_STATE_OWNS_TOP_LEVEL )); PAGED_CODE();
DebugTrace( +1, Dbg, ("NtfsCompleteMdl\n") ); DebugTrace( 0, Dbg, ("IrpContext = %08lx\n", IrpContext) ); DebugTrace( 0, Dbg, ("Irp = %08lx\n", Irp) );
//
// Do completion processing.
//
FileObject = IoGetCurrentIrpStackLocation( Irp )->FileObject;
switch( IrpContext->MajorFunction ) {
case IRP_MJ_READ:
CcMdlReadComplete( FileObject, Irp->MdlAddress ); break;
case IRP_MJ_WRITE:
try {
PSCB Scb; VBO StartingVbo; LONGLONG ByteCount; LONGLONG ByteRange; BOOLEAN DoingIoAtEof = FALSE;
ASSERT( FlagOn( IrpContext->State, IRP_CONTEXT_STATE_WAIT ));
IrpSp = IoGetCurrentIrpStackLocation( Irp ); Scb = (PSCB)(IrpSp->FileObject->FsContext); Header = &(Scb->Header);
//
// Now synchronize with the FsRtl Header and Scb.
//
if (Header->PagingIoResource != NULL) {
StartingVbo = IrpSp->Parameters.Write.ByteOffset.QuadPart; ByteCount = (LONGLONG) IrpSp->Parameters.Write.Length; ByteRange = StartingVbo + ByteCount + PAGE_SIZE - 1; ClearFlag( ((ULONG) ByteRange), PAGE_SIZE - 1 );
ExAcquireResourceSharedLite( Header->PagingIoResource, TRUE ); NtfsAcquireFsrtlHeader( Scb );
//
// Now see if this is at EOF.
// Recursive flush will generate IO which ends on page boundary
// which is why we rounded the range
//
if (ByteRange > Header->ValidDataLength.QuadPart) {
//
// Mark that we are writing to EOF. If someone else is currently
// writing to EOF, wait for them.
//
ASSERT( ByteRange - StartingVbo < MAXULONG );
DoingIoAtEof = !FlagOn( Header->Flags, FSRTL_FLAG_EOF_ADVANCE_ACTIVE ) || NtfsWaitForIoAtEof( Header, (PLARGE_INTEGER)&StartingVbo, (ULONG)(ByteRange - StartingVbo) );
if (DoingIoAtEof) {
SetFlag( Header->Flags, FSRTL_FLAG_EOF_ADVANCE_ACTIVE );
#if (DBG || defined( NTFS_FREE_ASSERTS ))
((PSCB) Header)->IoAtEofThread = (PERESOURCE_THREAD) ExGetCurrentResourceThread(); #endif
//
// Store this in the IrpContext until commit or post.
//
IrpContext->CleanupStructure = Scb; } }
NtfsReleaseFsrtlHeader( Scb ); }
CcMdlWriteComplete( FileObject, &IrpSp->Parameters.Write.ByteOffset, Irp->MdlAddress );
} finally {
if (Header->PagingIoResource != NULL) {
ExReleaseResourceLite( Header->PagingIoResource ); } }
break;
default:
DebugTrace( DEBUG_TRACE_ERROR, 0, ("Illegal Mdl Complete.\n") );
ASSERTMSG("Illegal Mdl Complete, About to bugcheck ", FALSE); NtfsBugCheck( IrpContext->MajorFunction, 0, 0 ); }
//
// Mdl is now deallocated.
//
Irp->MdlAddress = NULL;
//
// Ignore errors. CC has already cleaned up his structures.
//
IrpContext->ExceptionStatus = STATUS_SUCCESS; NtfsMinimumExceptionProcessing( IrpContext );
//
// Complete the request and exit right away.
//
NtfsCompleteRequest( IrpContext, Irp, STATUS_SUCCESS );
DebugTrace( -1, Dbg, ("NtfsCompleteMdl -> STATUS_SUCCESS\n") );
return STATUS_SUCCESS; }
BOOLEAN NtfsZeroData ( IN PIRP_CONTEXT IrpContext, IN PSCB Scb, IN PFILE_OBJECT FileObject, IN LONGLONG StartingZero, IN LONGLONG ByteCount, IN OUT PLONGLONG CommittedFileSize OPTIONAL )
/*++
Routine Description:
This routine is called to zero a range of a file in order to advance valid data length.
Arguments:
Scb - Scb for the stream to zero.
FileObject - FileObject for the stream.
StartingZero - Offset to begin the zero operation.
ByteCount - Length of range to zero.
CommittedFileSize - If we write the file sizes and commit the transaction then we want to let our caller know what point to roll back file size on a subsequent failure. On entry it has the size our caller wants to roll back the file size to. On exit it has the new size to roll back to which takes into account any updates to the file size which have been logged.
Return Value:
BOOLEAN - TRUE if the entire range was zeroed, FALSE if the request is broken up or the cache manager would block.
--*/
{ LONGLONG Temp;
#ifdef COMPRESS_ON_WIRE
IO_STATUS_BLOCK IoStatus; #endif
ULONG SectorSize;
BOOLEAN Finished; BOOLEAN CompleteZero = TRUE; BOOLEAN ScbAcquired = FALSE;
PVCB Vcb = Scb->Vcb;
LONGLONG ZeroStart; LONGLONG BeyondZeroEnd; ULONG CompressionUnit = Scb->CompressionUnit;
BOOLEAN Wait;
PAGED_CODE();
Wait = (BOOLEAN) FlagOn( IrpContext->State, IRP_CONTEXT_STATE_WAIT );
//
// We don't expect to ever be explicitly zeroing system files
//
ASSERT( !FlagOn( Scb->Fcb->FcbState, FCB_STATE_SYSTEM_FILE ) );
SectorSize = Vcb->BytesPerSector;
//
// We may be able to simplify the zero operation (sparse file or when writing
// compressed) by deallocating large ranges of the file. Otherwise we have to
// generate zeroes for the entire range. If that is the case we want to split
// this operation up.
//
if ((ByteCount > MAX_ZERO_THRESHOLD) && !FlagOn( Scb->ScbState, SCB_STATE_WRITE_COMPRESSED ) && !FlagOn( Scb->AttributeFlags, ATTRIBUTE_FLAG_SPARSE )) {
ByteCount = MAX_ZERO_THRESHOLD; CompleteZero = FALSE; }
ZeroStart = BlockAlign( StartingZero, (LONG)SectorSize ); BeyondZeroEnd = BlockAlign( StartingZero + ByteCount, (LONG)SectorSize );
ASSERT( BeyondZeroEnd >= (StartingZero + ByteCount) );
//
// Directly zero from startingzero to zerostart on disk for vanilla nonresident
// files. Compressed files always write out compression units worth of data
// which would cover this range. Resident files are always changed in
// NtfsChangeAttributeValue which also also zeroes any gaps
//
if ((CompressionUnit == 0) && (ZeroStart != StartingZero) && (!FlagOn( Scb->ScbState, SCB_STATE_ATTRIBUTE_RESIDENT ))) {
//
// Writing directly to disk is always safe - we can go through the
// cachemap if the file is cached and its not mapped. If its mapped
// there may be data between vdl and fs we don't know about yet
// We also must go non cached if we're not the top level request to avoid
// a recursive flush if mm is initiating the initial write via the deref seg thread or
// if an initial cache coherency flush caused this
//
BOOLEAN CachedWrite = NtfsIsTopLevelRequest( IrpContext ) && (FileObject->PrivateCacheMap != NULL) && !FlagOn( Scb->Header.Flags, FSRTL_FLAG_USER_MAPPED_FILE );
NtfsZeroEndOfSector( IrpContext, IrpContext->OriginatingIrp, Scb, StartingZero, CachedWrite );
#ifdef SYSCACHE_DEBUG
if (ScbIsBeingLogged( Scb )) { FsRtlLogSyscacheEvent( Scb, SCE_ZERO_HEAD_SECTOR, CachedWrite, StartingZero, ZeroStart, Scb->Header.ValidDataLength.QuadPart ); } #endif
}
//
// We must flush the first compression unit in case it is partially populated
// in the compressed stream.
//
#ifdef COMPRESS_ON_WIRE
if ((Scb->NonpagedScb->SegmentObjectC.DataSectionObject != NULL) && ((StartingZero & (CompressionUnit - 1)) != 0)) {
StartingZero = BlockAlignTruncate( StartingZero, (LONG)CompressionUnit ); CcFlushCache( &Scb->NonpagedScb->SegmentObjectC, (PLARGE_INTEGER)&StartingZero, CompressionUnit, &IoStatus );
if (!NT_SUCCESS(IoStatus.Status)) { NtfsNormalizeAndRaiseStatus( IrpContext, IoStatus.Status, STATUS_UNEXPECTED_IO_ERROR ); } } #endif
//
// If this is a sparse or compressed file and we are zeroing a lot, then let's
// just delete the space instead of writing tons of zeros and deleting
// the space in the noncached path! If we are currently decompressing
// a compressed file we can't take this path.
//
if ((FlagOn( Scb->ScbState, SCB_STATE_WRITE_COMPRESSED ) || FlagOn( Scb->AttributeFlags, ATTRIBUTE_FLAG_SPARSE )) && (ByteCount > (Scb->CompressionUnit * 2))) {
//
// Find the end of the first compression unit being zeroed.
//
Temp = BlockAlign( ZeroStart, (LONG)CompressionUnit );
//
// Zero the first compression unit.
//
if ((ULONG)Temp != (ULONG)ZeroStart) {
Finished = CcZeroData( FileObject, (PLARGE_INTEGER)&ZeroStart, (PLARGE_INTEGER)&Temp, Wait );
#ifdef SYSCACHE_DEBUG
if (ScbIsBeingLogged( Scb )) { FsRtlLogSyscacheEvent( Scb, SCE_ZERO_HEAD_COMPRESSED, 0, ZeroStart, Temp, Finished ); } #endif
if (!Finished) {return FALSE;}
ZeroStart = Temp; }
//
// Now delete all of the compression units in between.
//
//
// Calculate the start of the last compression unit in bytes.
//
Temp = BeyondZeroEnd; (ULONG)Temp &= ~(CompressionUnit - 1);
//
// If the caller has not already started a transaction (like write.c),
// then let's just do the delete as an atomic action.
//
if (!NtfsIsExclusiveScb( Scb )) {
NtfsAcquireExclusiveScb( IrpContext, Scb ); ScbAcquired = TRUE;
if (ARGUMENT_PRESENT( CommittedFileSize )) {
NtfsMungeScbSnapshot( IrpContext, Scb, *CommittedFileSize ); } }
try {
//
// Delete the space.
//
NtfsDeleteAllocation( IrpContext, FileObject, Scb, LlClustersFromBytes( Vcb, ZeroStart ), LlClustersFromBytesTruncate( Vcb, Temp ) - 1, TRUE, TRUE );
//
// If we didn't raise then update the Scb values for compressed files.
//
if (FlagOn( Scb->AttributeFlags, ATTRIBUTE_FLAG_COMPRESSION_MASK )) { Scb->ValidDataToDisk = Temp; }
//
// If we succeed, commit the atomic action. Release all of the exclusive
// resources if our user explicitly acquired the Fcb here.
//
if (ScbAcquired) { NtfsCheckpointCurrentTransaction( IrpContext );
if (ARGUMENT_PRESENT( CommittedFileSize )) {
ASSERT( Scb->ScbSnapshot != NULL ); *CommittedFileSize = Scb->ScbSnapshot->FileSize; }
while (!IsListEmpty( &IrpContext->ExclusiveFcbList )) {
NtfsReleaseFcb( IrpContext, (PFCB)CONTAINING_RECORD( IrpContext->ExclusiveFcbList.Flink, FCB, ExclusiveFcbLinks )); }
ClearFlag( IrpContext->Flags, IRP_CONTEXT_FLAG_RELEASE_USN_JRNL | IRP_CONTEXT_FLAG_RELEASE_MFT );
ScbAcquired = FALSE; }
if (FlagOn( Scb->ScbState, SCB_STATE_UNNAMED_DATA )) {
Scb->Fcb->Info.AllocatedLength = Scb->TotalAllocated; SetFlag( Scb->Fcb->InfoFlags, FCB_INFO_CHANGED_ALLOC_SIZE ); }
} finally {
if (ScbAcquired) { NtfsReleaseScb( IrpContext, Scb ); } }
//
// Zero the beginning of the last compression unit.
//
if ((ULONG)Temp != (ULONG)BeyondZeroEnd) {
Finished = CcZeroData( FileObject, (PLARGE_INTEGER)&Temp, (PLARGE_INTEGER)&BeyondZeroEnd, Wait );
#ifdef SYSCACHE_DEBUG
if (ScbIsBeingLogged( Scb )) { FsRtlLogSyscacheEvent( Scb, SCE_ZERO_TAIL_COMPRESSED, 0, Temp, BeyondZeroEnd, Finished ); } #endif
if (!Finished) {return FALSE;}
BeyondZeroEnd = Temp; }
return TRUE; }
//
// If we were called to just zero part of a sector we are in trouble.
//
if (ZeroStart == BeyondZeroEnd) {
return TRUE; }
Finished = CcZeroData( FileObject, (PLARGE_INTEGER)&ZeroStart, (PLARGE_INTEGER)&BeyondZeroEnd, Wait );
//
// If we are breaking this request up then commit the current
// transaction (including updating the valid data length in
// in the Scb) and return FALSE.
//
if (Finished && !CompleteZero) {
//
// Synchronize the valid data length change using the mutex.
//
ExAcquireFastMutex( Scb->Header.FastMutex ); Scb->Header.ValidDataLength.QuadPart = BeyondZeroEnd;
//
// Move the rollback point up to include the range of zeroed
// data.
//
if (ARGUMENT_PRESENT( CommittedFileSize )) {
if (BeyondZeroEnd > *CommittedFileSize) {
*CommittedFileSize = BeyondZeroEnd; } }
ASSERT( Scb->Header.ValidDataLength.QuadPart <= Scb->Header.FileSize.QuadPart );
ExReleaseFastMutex( Scb->Header.FastMutex ); NtfsCheckpointCurrentTransaction( IrpContext ); return FALSE; }
return Finished; }
|