Leaked source code of windows server 2003
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.
 
 
 
 
 
 

2703 lines
87 KiB

/*++
Copyright (c) 1991 Microsoft Corporation
Module Name:
Flush.c
Abstract:
This module implements the flush buffers routine for Ntfs called by the
dispatch driver.
Author:
Tom Miller [TomM] 18-Jan-1992
Revision History:
--*/
#include "NtfsProc.h"
//
// The Bug check file id for this module
//
#define BugCheckFileId (NTFS_BUG_CHECK_FLUSH)
//
// The local debug trace level
//
#define Dbg (DEBUG_TRACE_FLUSH)
//
// Macro to attempt to flush a stream from an Scb.
//
#define FlushScb(IRPC,SCB,IOS) { \
(IOS)->Status = NtfsFlushUserStream((IRPC),(SCB),NULL,0); \
NtfsNormalizeAndCleanupTransaction( IRPC, \
&(IOS)->Status, \
TRUE, \
STATUS_UNEXPECTED_IO_ERROR ); \
if (FlagOn((SCB)->ScbState, SCB_STATE_FILE_SIZE_LOADED)) { \
NtfsWriteFileSizes( (IRPC), \
(SCB), \
&(SCB)->Header.ValidDataLength.QuadPart, \
TRUE, \
TRUE, \
TRUE ); \
} \
}
//
// Local procedure prototypes
//
NTSTATUS
NtfsFlushCompletionRoutine(
IN PDEVICE_OBJECT DeviceObject,
IN PIRP Irp,
IN PVOID Contxt
);
NTSTATUS
NtfsFlushFcbFileRecords (
IN PIRP_CONTEXT IrpContext,
IN PFCB Fcb
);
LONG
NtfsFlushVolumeExceptionFilter (
IN PIRP_CONTEXT IrpContext,
IN PEXCEPTION_POINTERS ExceptionPointer,
IN NTSTATUS ExceptionCode
);
#ifdef ALLOC_PRAGMA
#pragma alloc_text(PAGE, NtfsCommonFlushBuffers)
#pragma alloc_text(PAGE, NtfsFlushAndPurgeFcb)
#pragma alloc_text(PAGE, NtfsFlushAndPurgeScb)
#pragma alloc_text(PAGE, NtfsFlushFcbFileRecords)
#pragma alloc_text(PAGE, NtfsFlushLsnStreams)
#pragma alloc_text(PAGE, NtfsFlushVolume)
#pragma alloc_text(PAGE, NtfsFsdFlushBuffers)
#pragma alloc_text(PAGE, NtfsFlushUserStream)
#endif
NTSTATUS
NtfsFsdFlushBuffers (
IN PVOLUME_DEVICE_OBJECT VolumeDeviceObject,
IN PIRP Irp
)
/*++
Routine Description:
This routine implements the FSD part of flush buffers.
Arguments:
VolumeDeviceObject - Supplies the volume device object where the
file exists
Irp - Supplies the Irp being processed
Return Value:
NTSTATUS - The FSD status for the IRP
--*/
{
TOP_LEVEL_CONTEXT TopLevelContext;
PTOP_LEVEL_CONTEXT ThreadTopLevelContext;
NTSTATUS Status = STATUS_SUCCESS;
PIRP_CONTEXT IrpContext = NULL;
ASSERT_IRP( Irp );
UNREFERENCED_PARAMETER( VolumeDeviceObject );
PAGED_CODE();
DebugTrace( +1, Dbg, ("NtfsFsdFlushBuffers\n") );
//
// Call the common flush buffer routine
//
FsRtlEnterFileSystem();
ThreadTopLevelContext = NtfsInitializeTopLevelIrp( &TopLevelContext, FALSE, FALSE );
do {
try {
//
// We are either initiating this request or retrying it.
//
if (IrpContext == NULL) {
//
// Allocate and initialize the Irp.
//
NtfsInitializeIrpContext( Irp, CanFsdWait( Irp ), &IrpContext );
//
// Initialize the thread top level structure, if needed.
//
NtfsUpdateIrpContextWithTopLevel( IrpContext, ThreadTopLevelContext );
} else if (Status == STATUS_LOG_FILE_FULL) {
NtfsCheckpointForLogFileFull( IrpContext );
}
Status = NtfsCommonFlushBuffers( IrpContext, Irp );
break;
} except(NtfsExceptionFilter( IrpContext, GetExceptionInformation() )) {
//
// We had some trouble trying to perform the requested
// operation, so we'll abort the I/O request with
// the error status that we get back from the
// execption code
//
Status = NtfsProcessException( IrpContext, Irp, GetExceptionCode() );
}
} while (Status == STATUS_CANT_WAIT ||
Status == STATUS_LOG_FILE_FULL);
ASSERT( IoGetTopLevelIrp() != (PIRP) &TopLevelContext );
FsRtlExitFileSystem();
//
// And return to our caller
//
DebugTrace( -1, Dbg, ("NtfsFsdFlushBuffers -> %08lx\n", Status) );
return Status;
}
NTSTATUS
NtfsCommonFlushBuffers (
IN PIRP_CONTEXT IrpContext,
IN PIRP Irp
)
/*++
Routine Description:
This is the common routine for flush buffers called by both the fsd and fsp
threads.
Arguments:
Irp - Supplies the Irp to process
Return Value:
NTSTATUS - The return status for the operation
--*/
{
NTSTATUS Status;
PIO_STACK_LOCATION IrpSp;
PFILE_OBJECT FileObject;
TYPE_OF_OPEN TypeOfOpen;
PVCB Vcb;
PFCB Fcb;
PSCB Scb;
PCCB Ccb;
PLCB Lcb = NULL;
PSCB ParentScb = NULL;
BOOLEAN VcbAcquired = FALSE;
BOOLEAN ScbAcquired = FALSE;
BOOLEAN ParentScbAcquired = FALSE;
ASSERT_IRP_CONTEXT( IrpContext );
ASSERT_IRP( Irp );
ASSERT( FlagOn( IrpContext->TopLevelIrpContext->State, IRP_CONTEXT_STATE_OWNS_TOP_LEVEL ));
PAGED_CODE();
IrpSp = IoGetCurrentIrpStackLocation( Irp );
DebugTrace( +1, Dbg, ("NtfsCommonFlushBuffers\n") );
DebugTrace( 0, Dbg, ("Irp = %08lx\n", Irp) );
DebugTrace( 0, Dbg, ("->FileObject = %08lx\n", IrpSp->FileObject) );
//
// Extract and decode the file object
//
FileObject = IrpSp->FileObject;
TypeOfOpen = NtfsDecodeFileObject( IrpContext, FileObject, &Vcb, &Fcb, &Scb, &Ccb, TRUE );
//
// abort immediately for non files
//
if (UnopenedFileObject == TypeOfOpen) {
NtfsCompleteRequest( IrpContext, Irp, STATUS_INVALID_PARAMETER );
return STATUS_INVALID_PARAMETER;
}
//
// Nuthin-doing if the volume is mounted read only.
//
if (NtfsIsVolumeReadOnly( Vcb )) {
Status = STATUS_MEDIA_WRITE_PROTECTED;
NtfsCompleteRequest( IrpContext, Irp, Status );
DebugTrace( -1, Dbg, ("NtfsCommonFlushBuffers -> %08lx\n", Status) );
return Status;
}
Status = STATUS_SUCCESS;
try {
//
// Case on the type of open that we are trying to flush
//
switch (TypeOfOpen) {
case UserFileOpen:
DebugTrace( 0, Dbg, ("Flush User File Open\n") );
//
// Acquire the Vcb so we can update the duplicate information as well.
//
NtfsAcquireSharedVcb( IrpContext, Vcb, TRUE );
VcbAcquired = TRUE;
//
// While we have the Vcb, let's make sure it's still mounted.
//
if (!FlagOn( Vcb->VcbState, VCB_STATE_VOLUME_MOUNTED )) {
try_return( Status = STATUS_VOLUME_DISMOUNTED );
}
//
// Make sure the data gets out to disk.
//
NtfsAcquireExclusivePagingIo( IrpContext, Fcb );
//
// Acquire exclusive access to the Scb and enqueue the irp
// if we didn't get access
//
NtfsAcquireExclusiveScb( IrpContext, Scb );
ScbAcquired = TRUE;
//
// Flush the stream and verify there were no errors.
//
FlushScb( IrpContext, Scb, &Irp->IoStatus );
//
// Now commit what we've done so far.
//
NtfsCheckpointCurrentTransaction( IrpContext );
//
// Update the time stamps and file sizes in the Fcb based on
// the state of the File Object.
//
NtfsUpdateScbFromFileObject( IrpContext, FileObject, Scb, TRUE );
//
// If we are to update standard information then do so now.
//
if (FlagOn( Fcb->FcbState, FCB_STATE_UPDATE_STD_INFO )) {
NtfsUpdateStandardInformation( IrpContext, Fcb );
}
//
// If this is the system hive there is more work to do. We want to flush
// all of the file records for this file as well as for the parent index
// stream. We also want to flush the parent index stream. Acquire the
// parent stream exclusively now so that the update duplicate call won't
// acquire it shared first.
//
if (FlagOn( Ccb->Flags, CCB_FLAG_SYSTEM_HIVE )) {
//
// Start by acquiring all of the necessary files to avoid deadlocks.
//
if (Ccb->Lcb != NULL) {
ParentScb = Ccb->Lcb->Scb;
if (ParentScb != NULL) {
NtfsAcquireExclusiveScb( IrpContext, ParentScb );
ParentScbAcquired = TRUE;
}
}
}
//
// Update the duplicate information if there are updates to apply.
//
if (FlagOn( Fcb->InfoFlags, FCB_INFO_DUPLICATE_FLAGS )) {
Lcb = Ccb->Lcb;
NtfsPrepareForUpdateDuplicate( IrpContext, Fcb, &Lcb, &ParentScb, TRUE );
NtfsUpdateDuplicateInfo( IrpContext, Fcb, Lcb, ParentScb );
NtfsUpdateLcbDuplicateInfo( Fcb, Lcb );
if (ParentScbAcquired) {
NtfsReleaseScb( IrpContext, ParentScb );
ParentScbAcquired = FALSE;
}
}
//
// Now flush the file records for this stream.
//
if (FlagOn( Ccb->Flags, CCB_FLAG_SYSTEM_HIVE )) {
//
// Flush the file records for this file.
//
Status = NtfsFlushFcbFileRecords( IrpContext, Scb->Fcb );
//
// Now flush the parent index stream.
//
if (NT_SUCCESS(Status) && (ParentScb != NULL)) {
CcFlushCache( &ParentScb->NonpagedScb->SegmentObject, NULL, 0, &Irp->IoStatus );
Status = Irp->IoStatus.Status;
//
// Finish by flushing the file records for the parent out
// to disk.
//
if (NT_SUCCESS( Status )) {
Status = NtfsFlushFcbFileRecords( IrpContext, ParentScb->Fcb );
}
}
}
//
// If our status is still success then flush the log file and
// report any changes.
//
if (NT_SUCCESS( Status )) {
ULONG FilterMatch;
LfsFlushToLsn( Vcb->LogHandle, LiMax );
//
// We only want to do this DirNotify if we updated duplicate
// info and set the ParentScb.
//
if (!FlagOn( Ccb->Flags, CCB_FLAG_OPEN_BY_FILE_ID ) &&
(Vcb->NotifyCount != 0) &&
FlagOn( Fcb->InfoFlags, FCB_INFO_DUPLICATE_FLAGS )) {
FilterMatch = NtfsBuildDirNotifyFilter( IrpContext, Fcb->InfoFlags );
if (FilterMatch != 0) {
NtfsReportDirNotify( IrpContext,
Fcb->Vcb,
&Ccb->FullFileName,
Ccb->LastFileNameOffset,
NULL,
((FlagOn( Ccb->Flags, CCB_FLAG_PARENT_HAS_DOS_COMPONENT ) &&
(Ccb->Lcb != NULL) &&
(Ccb->Lcb->Scb->ScbType.Index.NormalizedName.Length != 0)) ?
&Ccb->Lcb->Scb->ScbType.Index.NormalizedName :
NULL),
FilterMatch,
FILE_ACTION_MODIFIED,
ParentScb->Fcb );
}
}
ClearFlag( Fcb->InfoFlags,
FCB_INFO_NOTIFY_FLAGS | FCB_INFO_DUPLICATE_FLAGS );
}
break;
case UserViewIndexOpen:
case UserDirectoryOpen:
//
// If the user had opened the root directory then we'll
// oblige by flushing the volume.
//
if (NodeType(Scb) != NTFS_NTC_SCB_ROOT_INDEX) {
DebugTrace( 0, Dbg, ("Flush a directory does nothing\n") );
break;
}
case UserVolumeOpen:
DebugTrace( 0, Dbg, ("Flush User Volume Open\n") );
NtfsAcquireExclusiveVcb( IrpContext, Vcb, TRUE );
VcbAcquired = TRUE;
//
// While we have the Vcb, let's make sure it's still mounted.
//
if (!FlagOn( Vcb->VcbState, VCB_STATE_VOLUME_MOUNTED )) {
try_return( Status = STATUS_VOLUME_DISMOUNTED );
}
NtfsFlushVolume( IrpContext,
Vcb,
TRUE,
FALSE,
TRUE,
FALSE );
//
// Make sure all of the data written in the flush gets to disk.
//
LfsFlushToLsn( Vcb->LogHandle, LiMax );
break;
case StreamFileOpen:
//
// Nothing to do here.
//
break;
default:
//
// Nothing to do if we have our driver object.
//
break;
}
//
// Abort transaction on error by raising.
//
NtfsCleanupTransaction( IrpContext, Status, FALSE );
try_exit: NOTHING;
} finally {
DebugUnwind( NtfsCommonFlushBuffers );
//
// Release any resources which were acquired.
//
if (ScbAcquired) {
NtfsReleaseScb( IrpContext, Scb );
}
if (ParentScbAcquired) {
NtfsReleaseScb( IrpContext, ParentScb );
}
if (VcbAcquired) {
NtfsReleaseVcb( IrpContext, Vcb );
}
//
// If this is a normal termination then pass the request on
// to the target device object.
//
if (!AbnormalTermination()) {
NTSTATUS DriverStatus;
PIO_STACK_LOCATION NextIrpSp;
//
// Free the IrpContext now before calling the lower driver. Do this
// now in case this fails so that we won't complete the Irp in our
// exception routine after passing it to the lower driver.
//
NtfsCompleteRequest( IrpContext, NULL, STATUS_SUCCESS );
ASSERT( Vcb != NULL );
//
// Get the next stack location, and copy over the stack location
//
NextIrpSp = IoGetNextIrpStackLocation( Irp );
*NextIrpSp = *IrpSp;
//
// Set up the completion routine
//
IoSetCompletionRoutine( Irp,
NtfsFlushCompletionRoutine,
NULL,
TRUE,
TRUE,
TRUE );
//
// Send the request.
//
DriverStatus = IoCallDriver(Vcb->TargetDeviceObject, Irp);
Status = (DriverStatus == STATUS_INVALID_DEVICE_REQUEST) ?
Status : DriverStatus;
}
DebugTrace( -1, Dbg, ("NtfsCommonFlushBuffers -> %08lx\n", Status) );
}
return Status;
}
NTSTATUS
NtfsFlushVolume (
IN PIRP_CONTEXT IrpContext,
IN PVCB Vcb,
IN BOOLEAN FlushCache,
IN BOOLEAN PurgeFromCache,
IN BOOLEAN ReleaseAllFiles,
IN BOOLEAN MarkFilesForDismount
)
/*++
Routine Description:
This routine non-recursively flushes a volume. This routine will always do
as much of the operation as possible. It will continue until getting a logfile
full. If any of the streams can't be flushed because of corruption then we
will try to flush the others. We will mark the volume dirty in this case.
We will pass the error code back to the caller because they often need to
proceed as best as possible (i.e. shutdown).
Arguments:
Vcb - Supplies the volume to flush
FlushCache - Supplies TRUE if the caller wants to flush the data in the
cache to disk.
PurgeFromCache - Supplies TRUE if the caller wants the data purged from
the Cache (such as for autocheck!)
ReleaseAllFiles - Indicates that our caller would like to release all Fcb's
after TeardownStructures. This will prevent a deadlock when acquiring
paging io resource after a main resource which is held from a previous
teardown.
Return Value:
STATUS_SUCCESS or else the first error status.
--*/
{
NTSTATUS Status = STATUS_SUCCESS;
PFCB Fcb;
PFCB NextFcb;
PSCB Scb;
PSCB NextScb;
IO_STATUS_BLOCK IoStatus;
ULONG Pass;
BOOLEAN UserDataFile;
BOOLEAN RemovedFcb = FALSE;
BOOLEAN DecrementScbCleanup = FALSE;
BOOLEAN DecrementNextFcbClose = FALSE;
BOOLEAN DecrementNextScbCleanup = FALSE;
BOOLEAN AcquiredFcb = FALSE;
BOOLEAN PagingIoAcquired = FALSE;
BOOLEAN ReleaseFiles = FALSE;
LOGICAL MediaRemoved = FALSE;
LONG ReleaseVcbCount = 0;
PAGED_CODE();
DebugTrace( +1, Dbg, ("NtfsFlushVolume, Vcb = %08lx\n", Vcb) );
//
// This operation must be able to wait.
//
if (!FlagOn( IrpContext->State, IRP_CONTEXT_STATE_WAIT )) {
NtfsRaiseStatus( IrpContext, STATUS_CANT_WAIT, NULL, NULL );
}
//
// Make sure there is nothing on the delayed close queue.
//
NtfsFspClose( Vcb );
//
// Acquire the Vcb exclusive. The Raise condition cannot happen.
//
NtfsAcquireExclusiveVcb( IrpContext, Vcb, TRUE );
ReleaseVcbCount += 1;
try {
//
// We won't do any flushes, but we still have to
// do the dismount/teardown processing.
//
if ((IrpContext->MajorFunction == IRP_MJ_PNP) &&
(IrpContext->MinorFunction == IRP_MN_SURPRISE_REMOVAL)) {
MediaRemoved = TRUE;
}
//
// Don't bother flushing read only volumes
//
if (NtfsIsVolumeReadOnly( Vcb )) {
FlushCache = FALSE;
}
//
// Set the PURGE_IN_PROGRESS flag if this is a purge operation.
//
if (PurgeFromCache) {
SetFlag( Vcb->VcbState, VCB_STATE_VOL_PURGE_IN_PROGRESS);
}
//
// Start by flushing the log file to assure Write-Ahead-Logging.
//
if (!MediaRemoved) {
LfsFlushToLsn( Vcb->LogHandle, LiMax );
}
//
// There will be two passes through the Fcb's for the volume. On the
// first pass we just want to flush/purge the user data streams. On
// the second pass we want to flush the other streams. We hold off on
// several of the system files until after these two passes since they
// may be modified during the flush phases.
//
Pass = 0;
do {
PVOID RestartKey;
//
// Loop through all of the Fcb's in the Fcb table.
//
RestartKey = NULL;
NtfsAcquireFcbTable( IrpContext, Vcb );
NextFcb = Fcb = NtfsGetNextFcbTableEntry( Vcb, &RestartKey );
NtfsReleaseFcbTable( IrpContext, Vcb );
if (NextFcb != NULL) {
InterlockedIncrement( &NextFcb->CloseCount );
DecrementNextFcbClose = TRUE;
}
while (Fcb != NULL) {
//
// Acquire Paging I/O first, since we may be deleting or truncating.
// Testing for the PagingIoResource is not really safe without
// holding the main resource, so we correct for that below.
//
if (Fcb->PagingIoResource != NULL) {
NtfsAcquireExclusivePagingIo( IrpContext, Fcb );
PagingIoAcquired = TRUE;
}
//
// Let's acquire this Scb exclusively.
//
NtfsAcquireExclusiveFcb( IrpContext, Fcb, NULL, ACQUIRE_NO_DELETE_CHECK );
AcquiredFcb = TRUE;
//
// We depend on the state of the RemovedFcb flag to tell us that
// we can trust the 'Acquired' booleans above.
//
ASSERT( !RemovedFcb );
//
// If we now do not see a paging I/O resource we are golden,
// othewise we can absolutely release and acquire the resources
// safely in the right order, since a resource in the Fcb is
// not going to go away.
//
if (!PagingIoAcquired && (Fcb->PagingIoResource != NULL)) {
NtfsReleaseFcb( IrpContext, Fcb );
NtfsAcquireExclusivePagingIo( IrpContext, Fcb );
PagingIoAcquired = TRUE;
NtfsAcquireExclusiveFcb( IrpContext, Fcb, NULL, ACQUIRE_NO_DELETE_CHECK );
}
//
// If this is not one of the special system files then perform
// the flush and purge as requested. Go ahead and test file numbers
// instead of walking through the Scbs in the Vcb just in case they
// have been deleted.
//
if (NtfsSegmentNumber( &Fcb->FileReference ) != MASTER_FILE_TABLE_NUMBER &&
NtfsSegmentNumber( &Fcb->FileReference ) != LOG_FILE_NUMBER &&
NtfsSegmentNumber( &Fcb->FileReference ) != VOLUME_DASD_NUMBER &&
NtfsSegmentNumber( &Fcb->FileReference ) != BIT_MAP_FILE_NUMBER &&
NtfsSegmentNumber( &Fcb->FileReference ) != BOOT_FILE_NUMBER &&
NtfsSegmentNumber( &Fcb->FileReference ) != BAD_CLUSTER_FILE_NUMBER &&
!FlagOn( Fcb->FcbState, FCB_STATE_USN_JOURNAL )) {
//
// We will walk through all of the Scb's for this Fcb. In
// the first pass we will only deal with user data streams.
// In the second pass we will do the others.
//
Scb = NULL;
while (TRUE) {
Scb = NtfsGetNextChildScb( Fcb, Scb );
if (Scb == NULL) { break; }
//
// Reference the Scb to keep it from going away.
//
InterlockedIncrement( &Scb->CleanupCount );
DecrementScbCleanup = TRUE;
//
// Check whether this is a user data file.
//
UserDataFile = FALSE;
if ((NodeType( Scb ) == NTFS_NTC_SCB_DATA) &&
(Scb->AttributeTypeCode == $DATA)) {
UserDataFile = TRUE;
}
//
// Process this Scb in the correct loop.
//
if ((Pass == 0) == (UserDataFile)) {
//
// Initialize the state of the Io to SUCCESS.
//
IoStatus.Status = STATUS_SUCCESS;
//
// Don't put this Scb on the delayed close queue.
//
ClearFlag( Scb->ScbState, SCB_STATE_DELAY_CLOSE );
//
// Flush this stream if it is not already deleted.
// Also don't flush resident streams for system attributes.
//
if (FlushCache &&
!FlagOn( Scb->ScbState, SCB_STATE_ATTRIBUTE_DELETED ) &&
(!FlagOn( Scb->ScbState, SCB_STATE_ATTRIBUTE_RESIDENT) ||
(Scb->AttributeTypeCode == $DATA))) {
//
// Enclose the flushes with try-except, so that we can
// react to log file full, and in any case keep on truckin.
//
try {
FlushScb( IrpContext, Scb, &IoStatus );
NtfsCheckpointCurrentTransaction( IrpContext );
//
// We will handle all errors except LOG_FILE_FULL and fatal
// bugcheck errors here. In the corruption case we will
// want to mark the volume dirty and continue.
//
} except( NtfsFlushVolumeExceptionFilter( IrpContext,
GetExceptionInformation(),
(IoStatus.Status = GetExceptionCode()) )) {
//
// To make sure that we can access all of our streams correctly,
// we first restore all of the higher sizes before aborting the
// transaction. Then we restore all of the lower sizes after
// the abort, so that all Scbs are finally restored.
//
NtfsRestoreScbSnapshots( IrpContext, TRUE );
NtfsAbortTransaction( IrpContext, IrpContext->Vcb, NULL );
NtfsRestoreScbSnapshots( IrpContext, FALSE );
//
// Clear the top-level exception status so we won't raise
// later.
//
NtfsMinimumExceptionProcessing( IrpContext );
IrpContext->ExceptionStatus = STATUS_SUCCESS;
//
// Remember the first error.
//
if (Status == STATUS_SUCCESS) {
Status = IoStatus.Status;
}
//
// If the current status is either DISK_CORRUPT or FILE_CORRUPT then
// mark the volume dirty. We clear the IoStatus to allow
// a corrupt file to be purged. Otherwise it will never
// leave memory.
//
if ((IoStatus.Status == STATUS_DISK_CORRUPT_ERROR) ||
(IoStatus.Status == STATUS_FILE_CORRUPT_ERROR)) {
NtfsMarkVolumeDirty( IrpContext, Vcb );
IoStatus.Status = STATUS_SUCCESS;
}
}
}
//
// Proceed with the purge if there are no failures. We will
// purge if the flush revealed a corrupt file though.
//
if (PurgeFromCache
&& IoStatus.Status == STATUS_SUCCESS) {
BOOLEAN DataSectionExists;
BOOLEAN ImageSectionExists;
DataSectionExists = (BOOLEAN)(Scb->NonpagedScb->SegmentObject.DataSectionObject != NULL);
ImageSectionExists = (BOOLEAN)(Scb->NonpagedScb->SegmentObject.ImageSectionObject != NULL);
//
// Since purging the data section can cause the image
// section to go away, we will flush the image section first.
//
if (ImageSectionExists) {
(VOID)MmFlushImageSection( &Scb->NonpagedScb->SegmentObject, MmFlushForWrite );
}
if (DataSectionExists &&
!CcPurgeCacheSection( &Scb->NonpagedScb->SegmentObject,
NULL,
0,
FALSE ) &&
(Status == STATUS_SUCCESS)) {
Status = STATUS_UNABLE_TO_DELETE_SECTION;
}
}
if (MarkFilesForDismount) {
//
// Set the dismounted flag for this stream so we
// know we have to fail reads & writes to it.
//
SetFlag( Scb->ScbState, SCB_STATE_VOLUME_DISMOUNTED );
// Also mark the Scb as not allowing fast io --
// this ensures that the file system will get a
// chance to see all reads & writes to this stream.
//
NtfsAcquireFsrtlHeader( Scb );
Scb->Header.IsFastIoPossible = FastIoIsNotPossible;
NtfsReleaseFsrtlHeader( Scb );
}
}
//
// Move to the next Scb.
//
InterlockedDecrement( &Scb->CleanupCount );
DecrementScbCleanup = FALSE;
}
}
//
// If the current Fcb has a USN journal entry and we are forcing a dismount
// then generate the close record.
//
if (MarkFilesForDismount &&
(IoStatus.Status == STATUS_SUCCESS) &&
(NextFcb->FcbUsnRecord != NULL) &&
(NextFcb->FcbUsnRecord->UsnRecord.Reason != 0) &&
(!NtfsIsVolumeReadOnly( Vcb ))) {
//
// Try to post the change but don't fail on an error like DISK_FULL.
//
try {
//
// Now try to actually post the change.
//
NtfsPostUsnChange( IrpContext, Fcb, USN_REASON_CLOSE );
//
// Now write the journal, checkpoint the transaction, and free the UsnJournal to
// reduce contention. We force the write now, because the Fcb may get deleted
// before we normally would write the changes when the transaction commits.
//
NtfsWriteUsnJournalChanges( IrpContext );
NtfsCheckpointCurrentTransaction( IrpContext );
} except( NtfsFlushVolumeExceptionFilter( IrpContext,
GetExceptionInformation(),
(IoStatus.Status = GetExceptionCode()) )) {
NtfsMinimumExceptionProcessing( IrpContext );
IoStatus.Status = STATUS_SUCCESS;
if (IrpContext->TransactionId != 0) {
//
// We couldn't write the commit record, we clean up as
// best we can.
//
NtfsCleanupFailedTransaction( IrpContext );
}
}
}
//
// Remove our reference to the current Fcb.
//
InterlockedDecrement( &NextFcb->CloseCount );
DecrementNextFcbClose = FALSE;
//
// Get the next Fcb and reference it so it won't go away.
//
NtfsAcquireFcbTable( IrpContext, Vcb );
NextFcb = NtfsGetNextFcbTableEntry( Vcb, &RestartKey );
NtfsReleaseFcbTable( IrpContext, Vcb );
if (NextFcb != NULL) {
InterlockedIncrement( &NextFcb->CloseCount );
DecrementNextFcbClose = TRUE;
}
//
// Flushing the volume can cause new file objects to be allocated.
// If we are in the second pass and the Fcb is for a user file
// or directory then try to perform a teardown on this.
//
if ((Pass == 1) &&
!FlagOn(Fcb->FcbState, FCB_STATE_SYSTEM_FILE)) {
ASSERT( IrpContext->TransactionId == 0 );
//
// We can actually get failures in this routine if we need to log standard info.
//
try {
NtfsTeardownStructures( IrpContext,
Fcb,
NULL,
FALSE,
0,
&RemovedFcb );
//
// TeardownStructures can create a transaction. Commit
// it if present.
//
if (IrpContext->TransactionId != 0) {
NtfsCheckpointCurrentTransaction( IrpContext );
}
} except( NtfsFlushVolumeExceptionFilter( IrpContext,
GetExceptionInformation(),
GetExceptionCode() )) {
NtfsMinimumExceptionProcessing( IrpContext );
if (IrpContext->TransactionId != 0) {
//
// We couldn't write the commit record, we clean up as
// best we can.
//
NtfsCleanupFailedTransaction( IrpContext );
}
}
}
//
// If the Fcb is still around then free any of the the other
// resources we have acquired.
//
if (!RemovedFcb) {
//
// Free the snapshots for the current Fcb. This will keep us
// from having a snapshot for all open attributes in the
// system.
//
NtfsFreeSnapshotsForFcb( IrpContext, Fcb );
if (PagingIoAcquired) {
ASSERT( IrpContext->TransactionId == 0 );
NtfsReleasePagingIo( IrpContext, Fcb );
}
if (AcquiredFcb) {
NtfsReleaseFcb( IrpContext, Fcb );
}
}
//
// If our caller wants to insure that all files are released
// between flushes then walk through the exclusive Fcb list
// and free everything.
//
if (ReleaseAllFiles) {
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 );
}
PagingIoAcquired = FALSE;
AcquiredFcb = FALSE;
//
// Always set this back to FALSE to indicate that we can trust the
// 'Acquired' flags above.
//
RemovedFcb = FALSE;
//
// Now move to the next Fcb.
//
Fcb = NextFcb;
}
} while (++Pass < 2);
//
// The root directory is the only fcb with a mixture of user
// streams that should be torn down now and system streams that
// can't be torn down now.
// When we tried to teardown the whole Fcb, we might have run
// into the index root attribute and stopped our teardown, in
// which case we may leave a close count on the Vcb which will
// keep autochk from being able to lock the volume. We need to
// make sure the root directory indeed exists, and this isn't
// the call to flush the volume during mount when we haven't yet
// opened the root directory.
//
if (Vcb->RootIndexScb != NULL) {
Fcb = Vcb->RootIndexScb->Fcb;
//
// Get the first Scb for the root directory Fcb.
//
Scb = NtfsGetNextChildScb( Fcb, NULL );
while (Scb != NULL) {
NextScb = NtfsGetNextChildScb( Fcb, Scb );
if (NextScb != NULL) {
InterlockedIncrement( &NextScb->CleanupCount );
DecrementNextScbCleanup = TRUE;
}
//
// We can actually get failures in this routine if we need to log standard info.
//
try {
if (NtfsIsTypeCodeUserData( Scb->AttributeTypeCode )) {
//
// Notice that we don't bother passing RemovedFcb, since
// the root directory Fcb isn't going to go away.
//
NtfsTeardownStructures( IrpContext,
Scb,
NULL,
FALSE,
0,
NULL );
}
//
// TeardownStructures can create a transaction. Commit
// it if present.
//
if (IrpContext->TransactionId != 0) {
NtfsCheckpointCurrentTransaction( IrpContext );
}
} except( NtfsFlushVolumeExceptionFilter( IrpContext,
GetExceptionInformation(),
GetExceptionCode() )) {
NtfsMinimumExceptionProcessing( IrpContext );
if (IrpContext->TransactionId != 0) {
//
// We couldn't write the commit record, we clean up as
// best we can.
//
NtfsCleanupFailedTransaction( IrpContext );
}
}
//
// Decrement the cleanup count of the next Scb if we incremented it.
//
if (DecrementNextScbCleanup) {
InterlockedDecrement( &NextScb->CleanupCount );
DecrementNextScbCleanup = FALSE;
}
//
// Move to the next Scb.
//
Scb = NextScb;
}
}
//
// Make sure that all of the delayed or async closes for this Vcb are gone.
//
if (PurgeFromCache) {
NtfsFspClose( Vcb );
}
//
// If we are to mark the files for dismount then do the Volume Dasd file now.
//
if (MarkFilesForDismount) {
NtfsAcquireExclusiveFcb( IrpContext, Vcb->VolumeDasdScb->Fcb, NULL, ACQUIRE_NO_DELETE_CHECK );
SetFlag( Vcb->VolumeDasdScb->ScbState, SCB_STATE_VOLUME_DISMOUNTED );
NtfsReleaseFcb( IrpContext, Vcb->VolumeDasdScb->Fcb );
}
//
// Now we want to flush/purge the streams for volume bitmap and then the Usn
// journal and Scb.
//
{
PFCB SystemFcbs[3];
PSCB ThisScb;
//
// Store the volume bitmap, usn journal and Mft into the array.
//
RtlZeroMemory( SystemFcbs, sizeof( SystemFcbs ));
if (Vcb->BitmapScb != NULL) {
SystemFcbs[0] = Vcb->BitmapScb->Fcb;
}
if (Vcb->UsnJournal != NULL) {
SystemFcbs[1] = Vcb->UsnJournal->Fcb;
}
if (Vcb->MftScb != NULL) {
SystemFcbs[2] = Vcb->MftScb->Fcb;
}
Pass = 0;
do {
Fcb = SystemFcbs[Pass];
if (Fcb != NULL) {
//
// Purge the Mft cache if we are at the Mft.
//
if (Pass == 2) {
//
// If we are operating on the MFT, make sure we don't have any
// cached maps lying around...
//
NtfsPurgeFileRecordCache( IrpContext );
//
// If we are purging the MFT then acquire all files to
// avoid a purge deadlock. If someone create an MFT mapping
// between the flush and purge then the purge can spin
// indefinitely in CC.
//
if (PurgeFromCache && !ReleaseFiles) {
NtfsAcquireAllFiles( IrpContext, Vcb, TRUE, FALSE, FALSE );
ReleaseFiles = TRUE;
//
// NtfsAcquireAllFiles acquired the Vcb one more time.
//
ReleaseVcbCount += 1;
}
//
// For the other Fcb's we still need to synchronize the flush and
// purge so acquire and drop the Fcb.
//
} else {
NextFcb = Fcb;
InterlockedIncrement( &NextFcb->CloseCount );
DecrementNextFcbClose = TRUE;
NtfsAcquireExclusiveFcb( IrpContext, Fcb, NULL, ACQUIRE_NO_DELETE_CHECK );
AcquiredFcb = TRUE;
}
//
// Go through each Scb for each of these Fcb's.
//
ThisScb = NtfsGetNextChildScb( Fcb, NULL );
while (ThisScb != NULL) {
Scb = NtfsGetNextChildScb( Fcb, ThisScb );
//
// Initialize the state of the Io to SUCCESS.
//
IoStatus.Status = STATUS_SUCCESS;
//
// Reference the next Scb to keep it from going away if
// we purge the current one.
//
if (Scb != NULL) {
InterlockedIncrement( &Scb->CleanupCount );
DecrementScbCleanup = TRUE;
}
if (FlushCache) {
//
// Flush the stream. No need to update file sizes because these
// are all logged streams.
//
CcFlushCache( &ThisScb->NonpagedScb->SegmentObject, NULL, 0, &IoStatus );
if (!NT_SUCCESS( IoStatus.Status )) {
Status = IoStatus.Status;
}
//
// Use a try-except to commit the current transaction.
//
try {
NtfsCleanupTransaction( IrpContext, IoStatus.Status, TRUE );
NtfsCheckpointCurrentTransaction( IrpContext );
//
// We will handle all errors except LOG_FILE_FULL and fatal
// bugcheck errors here. In the corruption case we will
// want to mark the volume dirty and continue.
//
} except( NtfsFlushVolumeExceptionFilter( IrpContext,
GetExceptionInformation(),
(IoStatus.Status = GetExceptionCode()) )) {
//
// To make sure that we can access all of our streams correctly,
// we first restore all of the higher sizes before aborting the
// transaction. Then we restore all of the lower sizes after
// the abort, so that all Scbs are finally restored.
//
NtfsRestoreScbSnapshots( IrpContext, TRUE );
NtfsAbortTransaction( IrpContext, IrpContext->Vcb, NULL );
NtfsRestoreScbSnapshots( IrpContext, FALSE );
//
// Clear the top-level exception status so we won't raise
// later.
//
NtfsMinimumExceptionProcessing( IrpContext );
IrpContext->ExceptionStatus = STATUS_SUCCESS;
//
// Remember the first error.
//
if (Status == STATUS_SUCCESS) {
Status = IoStatus.Status;
}
//
// If the current status is either DISK_CORRUPT or FILE_CORRUPT then
// mark the volume dirty. We clear the IoStatus to allow
// a corrupt file to be purged. Otherwise it will never
// leave memory.
//
if ((IoStatus.Status == STATUS_DISK_CORRUPT_ERROR) ||
(IoStatus.Status == STATUS_FILE_CORRUPT_ERROR)) {
NtfsMarkVolumeDirty( IrpContext, Vcb );
IoStatus.Status = STATUS_SUCCESS;
}
}
}
//
// Purge this stream if there have been no errors.
//
if (PurgeFromCache
&& IoStatus.Status == STATUS_SUCCESS) {
if (!CcPurgeCacheSection( &ThisScb->NonpagedScb->SegmentObject,
NULL,
0,
FALSE ) &&
(Status == STATUS_SUCCESS)) {
Status = STATUS_UNABLE_TO_DELETE_SECTION;
}
}
//
// Remove any reference we have to the next Scb and move
// forward to the next Scb.
//
if (DecrementScbCleanup) {
InterlockedDecrement( &Scb->CleanupCount );
DecrementScbCleanup = FALSE;
}
ThisScb = Scb;
}
//
// Purge the Mft cache if we are at the Mft. Do this before and
// after dealing with the Mft.
//
if (Pass == 2) {
//
// If we are operating on the MFT, make sure we don't have any
// cached maps lying around...
//
NtfsPurgeFileRecordCache( IrpContext );
//
// If we are purging the MFT then acquire all files to
// avoid a purge deadlock. If someone create an MFT mapping
// between the flush and purge then the purge can spin
// indefinitely in CC.
//
if (PurgeFromCache && !ReleaseFiles) {
NtfsAcquireAllFiles( IrpContext, Vcb, TRUE, FALSE, FALSE );
ReleaseFiles = TRUE;
//
// NtfsAcquireAllFiles acquired the Vcb one more time.
//
ReleaseVcbCount += 1;
}
//
// Release the volume bitmap and Usn journal.
//
} else {
InterlockedDecrement( &NextFcb->CloseCount );
DecrementNextFcbClose = FALSE;
NtfsReleaseFcb( IrpContext, Fcb );
AcquiredFcb = FALSE;
}
}
Pass += 1;
} while (Pass != 3);
//
// Also flag as dismounted the usnjournal and volume bitmap.
//
if (MarkFilesForDismount) {
if (Vcb->BitmapScb != NULL) {
NtfsAcquireExclusiveFcb( IrpContext, Vcb->BitmapScb->Fcb, NULL, ACQUIRE_NO_DELETE_CHECK );
SetFlag( Vcb->BitmapScb->ScbState, SCB_STATE_VOLUME_DISMOUNTED );
NtfsReleaseFcb( IrpContext, Vcb->BitmapScb->Fcb );
}
if (Vcb->UsnJournal != NULL) {
NtfsAcquireExclusiveFcb( IrpContext, Vcb->UsnJournal->Fcb, NULL, ACQUIRE_NO_DELETE_CHECK );
SetFlag( Vcb->UsnJournal->ScbState, SCB_STATE_VOLUME_DISMOUNTED );
NtfsReleaseFcb( IrpContext, Vcb->UsnJournal->Fcb );
}
}
}
} finally {
//
// If this is a purge then clear the purge flag.
//
if (PurgeFromCache) {
ClearFlag( Vcb->VcbState, VCB_STATE_VOL_PURGE_IN_PROGRESS );
}
//
// Restore any counts we may have incremented to reference
// in-memory structures.
//
if (DecrementScbCleanup) {
InterlockedDecrement( &Scb->CleanupCount );
}
if (DecrementNextFcbClose) {
InterlockedDecrement( &NextFcb->CloseCount );
}
if (DecrementNextScbCleanup) {
InterlockedDecrement( &NextScb->CleanupCount );
}
//
// We would've released our resources if we had
// successfully removed the fcb.
//
if (!RemovedFcb) {
if (PagingIoAcquired) {
NtfsReleasePagingIo( IrpContext, Fcb );
}
if (AcquiredFcb) {
NtfsReleaseFcb( IrpContext, Fcb );
}
}
if (ReleaseFiles) {
//
// NtfsReleaseAllFiles is going to release the Vcb. We'd
// better have it acquired at least once.
//
ASSERT( ReleaseVcbCount >= 1 );
NtfsReleaseAllFiles( IrpContext, Vcb, FALSE );
ReleaseVcbCount -= 1;
}
//
// Release the Vcb now. We'd better have the Vcb acquired at least once.
//
ASSERTMSG( "Ignore this assert, 96773 is truly fixed",
(ReleaseVcbCount >= 1) );
if (ReleaseVcbCount >= 1) {
NtfsReleaseVcb( IrpContext, Vcb );
}
}
DebugTrace( -1, Dbg, ("NtfsFlushVolume -> %08lx\n", Status) );
return Status;
}
NTSTATUS
NtfsFlushLsnStreams (
IN PIRP_CONTEXT IrpContext,
IN PVCB Vcb,
IN BOOLEAN ForceRemove,
IN BOOLEAN Partial
)
/*++
Routine Description:
This routine non-recursively flushes all of the Lsn streams in the open
attribute table and removes them from the table. We assume the vcb
has been pre-acquired shared and the open attribute table is preacquired
Arguments:
Vcb - Supplies the volume to flush
ForceRemove - If true remove the open attribute even if the flush fails
OpenAttributeTableAcquired - Set to final state of table on exit - it should be true
to start off with and we may drop and reacquire it in between
Partial - if true only flush a few streams
Return Value:
STATUS_SUCCESS or else the most recent error status
--*/
{
NTSTATUS Status = STATUS_SUCCESS;
IO_STATUS_BLOCK IoStatus;
ULONG Pass = 1;
ULONG AttrIndex;
ULONG AttributesToFlush;
POPEN_ATTRIBUTE_ENTRY AttributeEntry;
PSCB Scb;
PFCB Fcb;
BOOLEAN AcquiredPaging = FALSE;
BOOLEAN RemovedFcb;
BOOLEAN FcbTableAcquired = FALSE;
BOOLEAN ScbValid;
#if DBG || defined( NTFS_FREE_ASSERT )
BOOLEAN EmptyList = IsListEmpty( &IrpContext->ExclusiveFcbList );
#endif
PAGED_CODE();
DebugTrace( +1, Dbg, ("NtfsFlushLsnStreams, Vcb = %08lx\n", Vcb) );
ASSERT( NtfsIsSharedVcb( Vcb ) );
ASSERT( ExIsResourceAcquiredExclusive( &Vcb->OpenAttributeTable.Resource ) );
try {
//
// This operation must be able to wait.
//
if (!FlagOn( IrpContext->State, IRP_CONTEXT_STATE_WAIT )) {
NtfsRaiseStatus( IrpContext, STATUS_CANT_WAIT, NULL, NULL );
}
//
// Start by flushing the log file to push as much of it out as possible in 1 chunk
//
LfsFlushToLsn( Vcb->LogHandle, LiMax );
//
// preacquire the fcbtable mutex to prevent any fcb's from being deleted
//
NtfsReleaseRestartTable( &Vcb->OpenAttributeTable );
NtfsAcquireFcbTable( IrpContext, Vcb );
NtfsAcquireExclusiveRestartTable( &Vcb->OpenAttributeTable, TRUE );
if (Partial) {
AttributesToFlush = Vcb->OpenAttributeTable.Table->NumberAllocated / 2;
Pass = 2;
} else {
Pass = 1;
}
//
// Scan the table in 2 passes - the first for user files the second for metadata
// during each pass files and remove them from the table. We are guaranteed
// that all transactions are done at this point so the bitmap will not
// be reopened during an abort for example
//
// Partial flushes start in pass 2 so they flush the mft first
//
for (; Pass <= 2; Pass++) {
//
// Loop through to flush all of the streams in the open attribute table.
//
AttributeEntry = NtfsGetFirstRestartTable( &Vcb->OpenAttributeTable );
while ((AttributeEntry != NULL) && (!Partial || AttributesToFlush > 0)) {
AttrIndex = GetIndexFromRestartEntry( &Vcb->OpenAttributeTable, AttributeEntry );
Scb = AttributeEntry->OatData->Overlay.Scb;
if (Scb != NULL) {
ScbValid = TRUE;
Fcb = Scb->Fcb;
//
// Skip system files during pass 1
//
if ((Pass == 1) && FlagOn( Scb->Fcb->FcbState, FCB_STATE_SYSTEM_FILE )) {
AttributeEntry = NtfsGetNextRestartTable( &Vcb->OpenAttributeTable, AttributeEntry );
continue;
}
//
// The mft is neither going to be torn down nor is it sync'ed on the main resource
// during a flush
//
if (Scb != Vcb->MftScb) {
//
// Reference the fcb to keep something around - we have the fcbtable mutex
// which is preventing any teardowns
//
Fcb->ReferenceCount += 1;
//
// If there is an scb to acquire - we must drop the open attr table and fcbtable mutex
// first since they are end resources
//
NtfsReleaseRestartTable( &Vcb->OpenAttributeTable );
AttributeEntry = NULL;
NtfsReleaseFcbTable( IrpContext, Vcb );
//
// Acquire main exclusive for flushing synchronization (this is metadata) and
// so that we can change the oat info in the non paged scb (see AcquireSharedScbForTransaction
// for what we need to lock out). Note we need to acquire the file even
// if its deleted.
//
NtfsAcquireExclusiveFcb( IrpContext, Fcb, NULL, ACQUIRE_NO_DELETE_CHECK | ACQUIRE_HOLD_BITMAP );
//
// We've acquired the fcb so release our ref count
//
NtfsAcquireFcbTable( IrpContext, Vcb );
Fcb->ReferenceCount -= 1;
NtfsReleaseFcbTable( IrpContext, Vcb );
//
// Is the scb still relevant? The open attribute table will only be
// decreasing in size due to the drain pending state so check if the
// scb is still in it now that we're synchronized
//
NtfsAcquireExclusiveRestartTable( &Vcb->OpenAttributeTable, TRUE );
AttributeEntry = GetRestartEntryFromIndex( &Vcb->OpenAttributeTable, AttrIndex );
if ((AttributeEntry->AllocatedOrNextFree != RESTART_ENTRY_ALLOCATED) ||
(AttributeEntry->OatData->Overlay.Scb != Scb)) {
//
// If we're not partially draining the table then a new entry should
// not appear in it
//
ASSERT( Partial || (AttributeEntry->AllocatedOrNextFree != RESTART_ENTRY_ALLOCATED) ||
(AttributeEntry->OatData->Overlay.Scb == NULL) );
ScbValid = FALSE;
}
} else {
NtfsReleaseFcbTable( IrpContext, Vcb );
}
IoStatus.Status = STATUS_SUCCESS;
//
// Skip flushing the Mft mirror and any deleted streams. If the header
// is uninitialized for this stream then it means that the
// attribute doesn't exist (INDEX_ALLOCATION where the create failed)
// or the attribute is now resident. Streams with paging resources
// (except for the volume bitmap) are skipped to prevent a possible
// deadlock (normally only seen in the hot fix path -- that's the
// only time an ordinary user stream ends up in the open attribute
// table) when this routine acquires the main resource without
// holding the paging resource. The easiest way to avoid this is
// to skip such files, since it's user data, not logged metadata,
// that's going to be flushed anyway, and flushing user data
// doesn't help a checkpoint at all.
//
if (ScbValid) {
if ((Scb != Vcb->Mft2Scb) &&
!FlagOn( Scb->ScbState, SCB_STATE_ATTRIBUTE_DELETED ) &&
FlagOn( Scb->ScbState, SCB_STATE_HEADER_INITIALIZED ) &&
((Scb == Vcb->BitmapScb) || (Scb->Header.PagingIoResource == NULL))) {
//
// The current attribute entry scb should match what we originally found
//
ASSERT( AttributeEntry->OatData->Overlay.Scb == Scb );
//
// Drop the table during the flush
//
NtfsReleaseRestartTable( &Vcb->OpenAttributeTable );
//
// Now flush the stream. We don't worry about file sizes because
// any logged stream should have the file size already in the log.
//
CcFlushCache( &Scb->NonpagedScb->SegmentObject, NULL, 0, &IoStatus );
if (!NT_SUCCESS( IoStatus.Status )) {
Status = IoStatus.Status;
}
NtfsAcquireExclusiveRestartTable( &Vcb->OpenAttributeTable, TRUE );
}
//
// Remove the open attribute entry for this scb from the table and clean it up
// If the flush succeeded or we're forcing removal
//
if (!Partial && (NT_SUCCESS( IoStatus.Status ) || ForceRemove)) {
ASSERT( Scb->NonpagedScb->OpenAttributeTableIndex == AttrIndex );
//
// We now need the mft exclusive (everyone else already has it) to
// zero out the oat info in the nonpaged scb
//
if (Scb == Vcb->MftScb) {
NtfsReleaseRestartTable( &Vcb->OpenAttributeTable );
NtfsAcquireExclusiveScb( IrpContext, Scb );
NtfsAcquireExclusiveRestartTable( &Vcb->OpenAttributeTable, TRUE );
}
AttributeEntry = GetRestartEntryFromIndex( &Vcb->OpenAttributeTable, AttrIndex );
if (AttributeEntry->AllocatedOrNextFree == RESTART_ENTRY_ALLOCATED) {
NtfsFreeAttributeEntry( Vcb, AttributeEntry );
}
if (Scb == Vcb->MftScb) {
NtfsReleaseScb( IrpContext, Scb );
}
}
}
NtfsReleaseRestartTable( &Vcb->OpenAttributeTable );
if (Scb != Vcb->MftScb) {
RemovedFcb = FALSE;
NtfsTeardownStructures( IrpContext,
Fcb,
NULL,
FALSE,
0,
&RemovedFcb );
if (!RemovedFcb) {
NtfsReleaseFcb( IrpContext, Fcb );
}
}
#if DBG || defined( NTFS_FREE_ASSERT )
//
// We shouldn't own anything more at this point than what we started with
//
ASSERT( !EmptyList || IsListEmpty( &IrpContext->ExclusiveFcbList ) );
#endif
NtfsAcquireFcbTable( IrpContext, Vcb );
NtfsAcquireExclusiveRestartTable( &Vcb->OpenAttributeTable, TRUE );
} else if (!Partial) {
//
// There was no scb so just clean this entry up. Note we still own the table
// unlike above where we had to drop it
//
NtfsFreeAttributeEntry( Vcb, AttributeEntry );
}
if (Partial) {
//
// Find our place in the table since we're not removing any entries
// starting with our old index
//
AttributeEntry = GetRestartEntryFromIndex( &Vcb->OpenAttributeTable, AttrIndex );
if (AttributeEntry) {
AttributeEntry = NtfsGetNextRestartTable( &Vcb->OpenAttributeTable, AttributeEntry );
}
AttributesToFlush -= 1;
} else {
AttributeEntry = NtfsGetFirstRestartTable( &Vcb->OpenAttributeTable );
}
}
}
} finally {
//
// We should still have 1 and only 1 owner count on the table
//
ASSERT( ExIsResourceAcquiredSharedLite( &Vcb->OpenAttributeTable.Resource ) == 1 );
}
//
// At this point there should be no entries left in the table unless its a partial flush
//
ASSERT( !NT_SUCCESS( Status ) || (Vcb->OpenAttributeTable.Table->NumberAllocated == 0) || Partial);
NtfsReleaseFcbTable( IrpContext, Vcb );
DebugTrace( -1, Dbg, ("NtfsFlushLsnStreams2 -> %08lx\n", Status) );
return Status;
UNREFERENCED_PARAMETER( IrpContext );
}
VOID
NtfsFlushAndPurgeFcb (
IN PIRP_CONTEXT IrpContext,
IN PFCB Fcb
)
/*++
Routine Description:
This routine will flush and purge all of the open streams for an
Fcb. It is indended to prepare this Fcb such that a teardown will
remove this Fcb for the tree. The caller has guaranteed that the
Fcb can't go away.
Arguments:
Fcb - Supplies the Fcb to flush
Return Value:
None. The caller calls teardown structures and checks the result.
--*/
{
IO_STATUS_BLOCK IoStatus;
BOOLEAN DecrementNextScbCleanup = FALSE;
PSCB Scb;
PSCB NextScb;
PAGED_CODE();
//
// Use a try-finally to facilitate cleanup.
//
try {
//
// Get the first Scb for the Fcb.
//
Scb = NtfsGetNextChildScb( Fcb, NULL );
while (Scb != NULL) {
BOOLEAN DataSectionExists;
BOOLEAN ImageSectionExists;
NextScb = NtfsGetNextChildScb( Fcb, Scb );
//
// Save the attribute list for last so we don't purge it
// and then bring it back for another attribute.
//
if ((Scb->AttributeTypeCode == $ATTRIBUTE_LIST) &&
(NextScb != NULL)) {
RemoveEntryList( &Scb->FcbLinks );
InsertTailList( &Fcb->ScbQueue, &Scb->FcbLinks );
Scb = NextScb;
continue;
}
if (!FlagOn( Scb->ScbState, SCB_STATE_ATTRIBUTE_DELETED )) {
FlushScb( IrpContext, Scb, &IoStatus );
}
//
// The call to purge below may generate a close call.
// We increment the cleanup count of the next Scb to prevent
// it from going away in a TearDownStructures as part of that
// close.
//
DataSectionExists = (BOOLEAN)(Scb->NonpagedScb->SegmentObject.DataSectionObject != NULL);
ImageSectionExists = (BOOLEAN)(Scb->NonpagedScb->SegmentObject.ImageSectionObject != NULL);
if (NextScb != NULL) {
InterlockedIncrement( &NextScb->CleanupCount );
DecrementNextScbCleanup = TRUE;
}
if (ImageSectionExists) {
(VOID)MmFlushImageSection( &Scb->NonpagedScb->SegmentObject, MmFlushForWrite );
}
if (DataSectionExists) {
CcPurgeCacheSection( &Scb->NonpagedScb->SegmentObject,
NULL,
0,
FALSE );
}
//
// Decrement the cleanup count of the next Scb if we incremented
// it.
//
if (DecrementNextScbCleanup) {
InterlockedDecrement( &NextScb->CleanupCount );
DecrementNextScbCleanup = FALSE;
}
//
// Move to the next Scb.
//
Scb = NextScb;
}
} finally {
//
// Restore any counts we may have incremented to reference
// in-memory structures.
//
if (DecrementNextScbCleanup) {
InterlockedDecrement( &NextScb->CleanupCount );
}
}
return;
}
VOID
NtfsFlushAndPurgeScb (
IN PIRP_CONTEXT IrpContext,
IN PSCB Scb,
IN PSCB ParentScb OPTIONAL
)
/*++
Routine Description:
This routine is called to flush and purge a stream. It is used
when there are now only non-cached handles on a file and there is
a data section. Flushing and purging the data section will mean that
the user non-cached io won't have to block for the cache coherency calls.
We want to remove all of the Fcb's from the exclusive list so that the
lower level flush will be its own transaction. We don't want to drop
any of the resources however so we acquire the Scb's above explicitly
and then empty the exclusive list. In all cases we will reacquire the
Scb's before raising out of this routine.
Because this routines causes the write out of all data to disk its critical
that it also updates the filesizes on disk. If NtfsWriteFileSizes raises logfile full
the caller (create, cleanup etc.) must recall this routine or update the filesizes
itself. To help with doing this we set the irpcontext state that we attemted a
flushandpurge. Also note because we purged the section on a retry it may no longer
be there so a test on (SectionObjectPointer->DataSection != NULL) will miss retrying
Arguments:
Scb - Scb for the stream to flush and purge. The reference count on this
stream will prevent it from going away.
ParentScb - If specified then this is the parent for the stream being flushed.
Return Value:
None.
--*/
{
IO_STATUS_BLOCK Iosb;
BOOLEAN PurgeResult;
PAGED_CODE();
//
// Only actually flush and purge if there is a data section
//
if (Scb->NonpagedScb->SegmentObject.DataSectionObject) {
//
// Commit the current transaction.
//
NtfsCheckpointCurrentTransaction( IrpContext );
//
// Acquire the Scb explicitly. We don't bother to do the same for the
// parent SCB here; we'll just acquire it on our way out.
//
NtfsAcquireResourceExclusive( IrpContext, Scb, TRUE );
//
// Walk through and release all of the Fcb's in the Fcb list.
//
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 );
//
// Use a try-finally to reacquire the Scbs.
//
try {
//
// Perform the flush, raise on error.
//
#ifdef COMPRESS_ON_WIRE
if (Scb->Header.FileObjectC != NULL) {
PCOMPRESSION_SYNC CompressionSync = NULL;
//
// Use a try-finally to clean up the compression sync.
//
try {
Iosb.Status = NtfsSynchronizeUncompressedIo( Scb,
NULL,
0,
TRUE,
&CompressionSync );
} finally {
NtfsReleaseCompressionSync( CompressionSync );
}
NtfsNormalizeAndCleanupTransaction( IrpContext, &Iosb.Status, TRUE, STATUS_UNEXPECTED_IO_ERROR );
}
#endif
//
// After doing the work of the flush we must update the ondisk sizes either
// here or in close if we fail logfile full
//
NtfsPurgeFileRecordCache( IrpContext );
SetFlag( Scb->ScbState, SCB_STATE_WRITE_FILESIZE_ON_CLOSE );
CcFlushCache( &Scb->NonpagedScb->SegmentObject, NULL, 0, &Iosb );
#ifdef SYSCACHE_DEBUG
if (ScbIsBeingLogged( Scb )) {
ASSERT( Scb->Fcb->PagingIoResource != NULL );
ASSERT( NtfsIsExclusiveScbPagingIo( Scb ) );
FsRtlLogSyscacheEvent( Scb, SCE_CC_FLUSH, 0, 0, 0, Iosb.Status );
}
#endif
NtfsNormalizeAndCleanupTransaction( IrpContext, &Iosb.Status, TRUE, STATUS_UNEXPECTED_IO_ERROR );
//
// If no error, then purge the section
//
PurgeResult = CcPurgeCacheSection( &Scb->NonpagedScb->SegmentObject, NULL, 0, FALSE );
#ifdef SYSCACHE_DEBUG
if (ScbIsBeingLogged( Scb )) {
ASSERT( (Scb->Fcb->PagingIoResource != NULL) && NtfsIsExclusiveScbPagingIo( Scb ) );
FsRtlLogSyscacheEvent( Scb, SCE_CC_FLUSH_AND_PURGE, 0, 0, (DWORD_PTR)(Scb->NonpagedScb->SegmentObject.SharedCacheMap), PurgeResult );
}
#endif
} finally {
//
// Reacquire the Scb and it's parent..
//
NtfsAcquireExclusiveScb( IrpContext, Scb );
NtfsReleaseResource( IrpContext, Scb );
if (ARGUMENT_PRESENT( ParentScb )) {
NtfsAcquireExclusiveScb( IrpContext, ParentScb );
}
}
} // endif DataSection existed
//
// Write the file sizes to the attribute. Commit the transaction since the
// file sizes must get to disk.
//
ASSERT( FlagOn( Scb->ScbState, SCB_STATE_HEADER_INITIALIZED ) );
NtfsWriteFileSizes( IrpContext, Scb, &Scb->Header.ValidDataLength.QuadPart, TRUE, TRUE, FALSE );
NtfsCheckpointCurrentTransaction( IrpContext );
ClearFlag( Scb->ScbState, SCB_STATE_WRITE_FILESIZE_ON_CLOSE );
return;
}
//
// Local support routine
//
NTSTATUS
NtfsFlushCompletionRoutine(
IN PDEVICE_OBJECT DeviceObject,
IN PIRP Irp,
IN PVOID Contxt
)
{
UNREFERENCED_PARAMETER( DeviceObject );
UNREFERENCED_PARAMETER( Contxt );
//
// Add the hack-o-ramma to fix formats.
//
if ( Irp->PendingReturned ) {
IoMarkIrpPending( Irp );
}
//
// If the Irp got STATUS_INVALID_DEVICE_REQUEST, normalize it
// to STATUS_SUCCESS.
//
if (Irp->IoStatus.Status == STATUS_INVALID_DEVICE_REQUEST) {
Irp->IoStatus.Status = STATUS_SUCCESS;
}
return STATUS_SUCCESS;
}
//
// Local support routine
//
NTSTATUS
NtfsFlushFcbFileRecords (
IN PIRP_CONTEXT IrpContext,
IN PFCB Fcb
)
/*++
Routine Description:
This routine is called to flush the file records for a given file. It is
intended to flush the critical file records for the system hives.
Arguments:
Fcb - This is the Fcb to flush.
Return Value:
NTSTATUS - The status returned from the flush operation.
--*/
{
IO_STATUS_BLOCK IoStatus;
BOOLEAN MoreToGo;
LONGLONG LastFileOffset = MAXLONGLONG;
ATTRIBUTE_ENUMERATION_CONTEXT AttrContext;
PAGED_CODE();
NtfsInitializeAttributeContext( &AttrContext );
IoStatus.Status = STATUS_SUCCESS;
//
// Use a try-finally to cleanup the context.
//
try {
//
// Find the first. It should be there.
//
MoreToGo = NtfsLookupAttribute( IrpContext,
Fcb,
&Fcb->FileReference,
&AttrContext );
if (!MoreToGo) {
NtfsRaiseStatus( IrpContext, STATUS_FILE_CORRUPT_ERROR, NULL, Fcb );
}
while (MoreToGo) {
if (AttrContext.FoundAttribute.MftFileOffset != LastFileOffset) {
LastFileOffset = AttrContext.FoundAttribute.MftFileOffset;
CcFlushCache( &Fcb->Vcb->MftScb->NonpagedScb->SegmentObject,
(PLARGE_INTEGER) &LastFileOffset,
Fcb->Vcb->BytesPerFileRecordSegment,
&IoStatus );
if (!NT_SUCCESS( IoStatus.Status )) {
IoStatus.Status = FsRtlNormalizeNtstatus( IoStatus.Status,
STATUS_UNEXPECTED_IO_ERROR );
break;
}
}
MoreToGo = NtfsLookupNextAttribute( IrpContext,
Fcb,
&AttrContext );
}
} finally {
DebugUnwind( NtfsFlushFcbFileRecords );
NtfsCleanupAttributeContext( IrpContext, &AttrContext );
}
return IoStatus.Status;
}
NTSTATUS
NtfsFlushUserStream (
IN PIRP_CONTEXT IrpContext,
IN PSCB Scb,
IN PLONGLONG FileOffset OPTIONAL,
IN ULONG Length
)
/*++
Routine Description:
This routine flushes a user stream as a top-level action. To do so
it checkpoints the current transaction first and frees all of the
caller's snapshots. After doing the flush, it snapshots the input
Scb again, just in case the caller plans to do any more work on that
stream. If the caller needs to modify any other streams (presumably
metadata), it must know to snapshot them itself after calling this
routine.
Arguments:
Scb - Stream to flush
FileOffset - FileOffset at which the flush is to start, or NULL for
entire stream.
Length - Number of bytes to flush. Ignored if FileOffset not specified.
Return Value:
Status of the flush
--*/
{
IO_STATUS_BLOCK IoStatus;
BOOLEAN ScbAcquired = FALSE;
PAGED_CODE();
//
// Checkpoint the current transaction and free all of its snapshots,
// in order to treat the flush as a top-level action with his own
// snapshots, etc.
//
NtfsCheckpointCurrentTransaction( IrpContext );
NtfsFreeSnapshotsForFcb( IrpContext, NULL );
//
// Set the wait flag in the IrpContext so we don't hit a case where the
// reacquire below fails because we can't wait. If our caller was asynchronous
// and we get this far we will continue synchronously.
//
SetFlag( IrpContext->State, IRP_CONTEXT_STATE_WAIT );
//
// We must free the Scb now before calling through MM to prevent
// collided page deadlocks.
//
//
// We are about to flush the stream. The Scb may be acquired exclusive
// and, thus, is linked onto the IrpContext or onto one higher
// up in the IoCallDriver stack. We are about to make a
// call back into Ntfs which may acquire the Scb exclusive, but
// NOT put it onto the nested IrpContext exclusive queue which prevents
// the nested completion from freeing the Scb.
//
// This is only a problem for Scb's without a paging resource.
//
// We acquire the Scb via ExAcquireResourceExclusiveLite, sidestepping
// Ntfs bookkeeping, and release it via NtfsReleaseScb.
//
ScbAcquired = NtfsIsExclusiveScb( Scb );
if (ScbAcquired) {
if (Scb->Header.PagingIoResource == NULL) {
NtfsAcquireResourceExclusive( IrpContext, Scb, TRUE );
}
NtfsReleaseScb( IrpContext, Scb );
}
#ifdef COMPRESS_ON_WIRE
if (Scb->Header.FileObjectC != NULL) {
PCOMPRESSION_SYNC CompressionSync = NULL;
//
// Use a try-finally to clean up the compression sync.
//
try {
NtfsSynchronizeUncompressedIo( Scb,
NULL,
0,
TRUE,
&CompressionSync );
} finally {
NtfsReleaseCompressionSync( CompressionSync );
}
}
#endif
//
// Clear the file record cache before doing the flush. Otherwise FlushVolume may hold this
// file and be purging the Mft at the same time this thread has a Vacb in the Mft and is
// trying to reacquire the file in the recursive IO thread.
//
NtfsPurgeFileRecordCache( IrpContext );
//
// Now do the flush he wanted as a top-level action
//
CcFlushCache( &Scb->NonpagedScb->SegmentObject, (PLARGE_INTEGER)FileOffset, Length, &IoStatus );
//
// Now reacquire for the caller.
//
if (ScbAcquired) {
NtfsAcquireExclusiveScb( IrpContext, Scb );
if (Scb->Header.PagingIoResource == NULL) {
NtfsReleaseResource( IrpContext, Scb );
}
}
return IoStatus.Status;
}
//
// Local support routine
//
LONG
NtfsFlushVolumeExceptionFilter (
IN PIRP_CONTEXT IrpContext,
IN PEXCEPTION_POINTERS ExceptionPointer,
IN NTSTATUS ExceptionCode
)
{
//
// Swallow any errors except LOG_FILE_FULL, CANT_WAIT and anything else not expected.
//
if ((ExceptionCode == STATUS_LOG_FILE_FULL) ||
(ExceptionCode == STATUS_CANT_WAIT) ||
!FsRtlIsNtstatusExpected( ExceptionCode )) {
return EXCEPTION_CONTINUE_SEARCH;
} else {
return EXCEPTION_EXECUTE_HANDLER;
}
UNREFERENCED_PARAMETER( IrpContext );
UNREFERENCED_PARAMETER( ExceptionPointer );
}