mirror of https://github.com/lianthony/NT4.0
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1871 lines
54 KiB
1871 lines
54 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 ); \
|
|
} \
|
|
}
|
|
|
|
//
|
|
// Local procedure prototypes
|
|
//
|
|
|
|
NTSTATUS
|
|
NtfsFlushCompletionRoutine(
|
|
IN PDEVICE_OBJECT DeviceObject,
|
|
IN PIRP Irp,
|
|
IN PVOID Contxt
|
|
);
|
|
|
|
NTSTATUS
|
|
NtfsFlushFcbFileRecords (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN PFCB Fcb
|
|
);
|
|
|
|
#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 = NtfsSetTopLevelIrp( &TopLevelContext, FALSE, FALSE );
|
|
|
|
do {
|
|
|
|
try {
|
|
|
|
//
|
|
// We are either initiating this request or retrying it.
|
|
//
|
|
|
|
if (IrpContext == NULL) {
|
|
|
|
IrpContext = NtfsCreateIrpContext( Irp, CanFsdWait( Irp ) );
|
|
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);
|
|
|
|
if (ThreadTopLevelContext == &TopLevelContext) {
|
|
NtfsRestoreTopLevelIrp( ThreadTopLevelContext );
|
|
}
|
|
|
|
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;
|
|
|
|
PLIST_ENTRY Links;
|
|
PFCB NextFcb;
|
|
ULONG Count;
|
|
|
|
BOOLEAN VcbAcquired = FALSE;
|
|
BOOLEAN ScbAcquired = FALSE;
|
|
BOOLEAN ParentScbAcquired = FALSE;
|
|
|
|
ASSERT_IRP_CONTEXT( IrpContext );
|
|
ASSERT_IRP( Irp );
|
|
|
|
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 );
|
|
|
|
Status = STATUS_SUCCESS;
|
|
|
|
try {
|
|
|
|
//
|
|
// Case on the type of open that we are trying to flush
|
|
//
|
|
|
|
switch (TypeOfOpen) {
|
|
|
|
case UserFileOpen:
|
|
#ifdef _CAIRO_
|
|
case UserPropertySetOpen:
|
|
#endif // _CAIRO_
|
|
|
|
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;
|
|
|
|
//
|
|
// 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, LfsQueryLastLsn( Vcb->LogHandle ));
|
|
|
|
//
|
|
// 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.Buffer != NULL) ?
|
|
&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 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;
|
|
|
|
NtfsFlushVolume( IrpContext,
|
|
Vcb,
|
|
TRUE,
|
|
FALSE,
|
|
TRUE,
|
|
FALSE );
|
|
|
|
//
|
|
// Make sure all of the data written in the flush gets to disk.
|
|
//
|
|
|
|
LfsFlushToLsn( Vcb->LogHandle, LfsQueryLastLsn( Vcb->LogHandle ));
|
|
break;
|
|
|
|
case StreamFileOpen:
|
|
|
|
//
|
|
// Nothing to do here.
|
|
//
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
NtfsBugCheck( TypeOfOpen, 0, 0 );
|
|
}
|
|
|
|
//
|
|
// Abort transaction on error by raising.
|
|
//
|
|
|
|
NtfsCleanupTransaction( IrpContext, Status, FALSE );
|
|
|
|
} 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 );
|
|
|
|
//
|
|
// 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;
|
|
IO_STATUS_BLOCK IoStatus;
|
|
|
|
ULONG Pass;
|
|
|
|
BOOLEAN UserDataFile;
|
|
BOOLEAN RemovedFcb;
|
|
BOOLEAN DecrementScbCleanup = FALSE;
|
|
BOOLEAN DecrementNextFcbClose = FALSE;
|
|
|
|
BOOLEAN AcquiredFcb = FALSE;
|
|
BOOLEAN PagingIoAcquired = FALSE;
|
|
BOOLEAN ReleaseFiles = FALSE;
|
|
|
|
PAGED_CODE();
|
|
|
|
DebugTrace( +1, Dbg, ("NtfsFlushVolume, Vcb = %08lx\n", Vcb) );
|
|
|
|
//
|
|
// This operation must be able to wait.
|
|
//
|
|
|
|
if (!FlagOn( IrpContext->Flags, IRP_CONTEXT_FLAG_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 );
|
|
|
|
try {
|
|
|
|
//
|
|
// 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.
|
|
//
|
|
|
|
LfsFlushToLsn( Vcb->LogHandle, LfsQueryLastLsn( Vcb->LogHandle ) );
|
|
|
|
//
|
|
// 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, TRUE, FALSE );
|
|
AcquiredFcb = TRUE;
|
|
|
|
//
|
|
// 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, TRUE, FALSE );
|
|
}
|
|
|
|
//
|
|
// 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 ) != BAD_CLUSTER_FILE_NUMBER) {
|
|
|
|
//
|
|
// 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( (((IoStatus.Status = GetExceptionCode()) == STATUS_LOG_FILE_FULL) ||
|
|
!FsRtlIsNtstatusExpected( IoStatus.Status ))
|
|
? EXCEPTION_CONTINUE_SEARCH
|
|
: EXCEPTION_EXECUTE_HANDLER ) {
|
|
|
|
//
|
|
// 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.
|
|
//
|
|
|
|
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
|
|
&& IoStatus.Status == STATUS_SUCCESS) {
|
|
|
|
//
|
|
// 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.
|
|
//
|
|
|
|
ExAcquireFastMutex( Scb->Header.FastMutex );
|
|
Scb->Header.IsFastIoPossible = FastIoIsNotPossible;
|
|
ExReleaseFastMutex( Scb->Header.FastMutex );
|
|
}
|
|
}
|
|
|
|
//
|
|
// Move to the next Scb.
|
|
//
|
|
|
|
InterlockedDecrement( &Scb->CleanupCount );
|
|
DecrementScbCleanup = FALSE;
|
|
}
|
|
}
|
|
|
|
//
|
|
// 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.
|
|
//
|
|
|
|
RemovedFcb = FALSE;
|
|
if ((Pass == 1) &&
|
|
(NtfsSegmentNumber( &Fcb->FileReference ) >= FIRST_USER_FILE_NUMBER)) {
|
|
|
|
ASSERT( IrpContext->TransactionId == 0 );
|
|
|
|
NtfsTeardownStructures( IrpContext,
|
|
Fcb,
|
|
NULL,
|
|
FALSE,
|
|
FALSE,
|
|
&RemovedFcb );
|
|
|
|
//
|
|
// TeardownStructures can create a transaction. Commit
|
|
// it if present.
|
|
//
|
|
|
|
if (IrpContext->TransactionId != 0) {
|
|
|
|
NtfsCheckpointCurrentTransaction( 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 ));
|
|
}
|
|
}
|
|
|
|
PagingIoAcquired = FALSE;
|
|
AcquiredFcb = FALSE;
|
|
|
|
//
|
|
// Now move to the next Fcb.
|
|
//
|
|
|
|
Fcb = NextFcb;
|
|
}
|
|
|
|
} while (++Pass < 2);
|
|
|
|
//
|
|
// Make sure that all of the delayed or async closes for this Vcb are gone.
|
|
//
|
|
|
|
if (PurgeFromCache) {
|
|
|
|
NtfsFspClose( Vcb );
|
|
}
|
|
|
|
//
|
|
// Now we want to flush/purge the streams for volume bitmap and then the Mft.
|
|
//
|
|
|
|
Pass = 0;
|
|
|
|
if (Vcb->BitmapScb != NULL) {
|
|
|
|
Fcb = Vcb->BitmapScb->Fcb;
|
|
|
|
} else {
|
|
|
|
Fcb = NULL;
|
|
}
|
|
|
|
do {
|
|
|
|
PSCB ThisScb;
|
|
|
|
if (Fcb != NULL) {
|
|
|
|
//
|
|
// 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 Bitmap or Mft
|
|
//
|
|
|
|
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( (((IoStatus.Status = GetExceptionCode()) == STATUS_LOG_FILE_FULL) ||
|
|
!FsRtlIsNtstatusExpected( IoStatus.Status ))
|
|
? EXCEPTION_CONTINUE_SEARCH
|
|
: EXCEPTION_EXECUTE_HANDLER ) {
|
|
|
|
//
|
|
// 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.
|
|
//
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
if (Vcb->MftScb != NULL) {
|
|
|
|
Fcb = Vcb->MftScb->Fcb;
|
|
|
|
//
|
|
// 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 );
|
|
ReleaseFiles = TRUE;
|
|
}
|
|
|
|
} else {
|
|
|
|
Fcb = NULL;
|
|
}
|
|
|
|
} while (++Pass < 2);
|
|
|
|
} finally {
|
|
|
|
//
|
|
// If this is a purge then clear the purge flag and set flag to force
|
|
// rescan of volume bitmap.
|
|
//
|
|
|
|
if (PurgeFromCache) {
|
|
|
|
ClearFlag( Vcb->VcbState, VCB_STATE_VOL_PURGE_IN_PROGRESS );
|
|
SetFlag( Vcb->VcbState, VCB_STATE_RELOAD_FREE_CLUSTERS );
|
|
}
|
|
|
|
//
|
|
// Restore any counts we may have incremented to reference
|
|
// in-memory structures.
|
|
//
|
|
|
|
if (DecrementScbCleanup) {
|
|
|
|
InterlockedDecrement( &Scb->CleanupCount );
|
|
}
|
|
|
|
if (DecrementNextFcbClose) {
|
|
|
|
InterlockedDecrement( &NextFcb->CloseCount );
|
|
}
|
|
|
|
if (PagingIoAcquired) {
|
|
NtfsReleasePagingIo( IrpContext, Fcb );
|
|
}
|
|
|
|
if (AcquiredFcb) {
|
|
NtfsReleaseFcb( IrpContext, Fcb );
|
|
}
|
|
|
|
if (ReleaseFiles) {
|
|
|
|
NtfsReleaseAllFiles( IrpContext, Vcb, FALSE );
|
|
}
|
|
|
|
//
|
|
// Release the Vcb now.
|
|
//
|
|
|
|
NtfsReleaseVcb( IrpContext, Vcb );
|
|
}
|
|
|
|
DebugTrace( -1, Dbg, ("NtfsFlushVolume -> %08lx\n", Status) );
|
|
|
|
return Status;
|
|
}
|
|
|
|
|
|
NTSTATUS
|
|
NtfsFlushLsnStreams (
|
|
IN PVCB Vcb
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine non-recursively flushes all of the Lsn streams in the open
|
|
attribute table. It assumes that the files have all been acquired
|
|
exclusive prior to this call. It also assumes our caller will provide the
|
|
synchronization for the open attribute table.
|
|
|
|
Arguments:
|
|
|
|
Vcb - Supplies the volume to flush
|
|
|
|
Return Value:
|
|
|
|
STATUS_SUCCESS or else the most recent error status
|
|
|
|
--*/
|
|
|
|
{
|
|
NTSTATUS Status = STATUS_SUCCESS;
|
|
IO_STATUS_BLOCK IoStatus;
|
|
|
|
POPEN_ATTRIBUTE_ENTRY AttributeEntry;
|
|
PSCB Scb;
|
|
|
|
PAGED_CODE();
|
|
|
|
DebugTrace( +1, Dbg, ("NtfsFlushLsnStreams, Vcb = %08lx\n", Vcb) );
|
|
|
|
//
|
|
// Start by flushing the log file to assure Write-Ahead-Logging.
|
|
//
|
|
|
|
LfsFlushToLsn( Vcb->LogHandle, LfsQueryLastLsn( Vcb->LogHandle ) );
|
|
|
|
//
|
|
// Loop through to flush all of the streams in the open attribute table.
|
|
// We skip the Mft and mirror so they get flushed last.
|
|
//
|
|
|
|
AttributeEntry = NtfsGetFirstRestartTable( &Vcb->OpenAttributeTable );
|
|
|
|
while (AttributeEntry != NULL) {
|
|
|
|
Scb = AttributeEntry->Overlay.Scb;
|
|
|
|
//
|
|
// Skip the Mft, its 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.
|
|
//
|
|
|
|
if (Scb != NULL
|
|
&& Scb != Vcb->MftScb
|
|
&& Scb != Vcb->Mft2Scb
|
|
&& Scb != Vcb->BadClusterFileScb
|
|
&& !FlagOn( Scb->ScbState, SCB_STATE_ATTRIBUTE_DELETED )
|
|
&& FlagOn( Scb->ScbState, SCB_STATE_HEADER_INITIALIZED )) {
|
|
|
|
IoStatus.Status = STATUS_SUCCESS;
|
|
|
|
//
|
|
// 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 )) {
|
|
|
|
ASSERTMSG( "Failed to flush stream for clean checkpoint\n", FALSE );
|
|
Status = IoStatus.Status;
|
|
}
|
|
|
|
}
|
|
|
|
AttributeEntry = NtfsGetNextRestartTable( &Vcb->OpenAttributeTable,
|
|
AttributeEntry );
|
|
}
|
|
|
|
//
|
|
// Now we do the Mft. Flushing the Mft will automatically update the mirror.
|
|
//
|
|
|
|
if (Vcb->MftScb != NULL) {
|
|
|
|
IoStatus.Status = STATUS_SUCCESS;
|
|
|
|
CcFlushCache( &Vcb->MftScb->NonpagedScb->SegmentObject, NULL, 0, &IoStatus );
|
|
|
|
if (!NT_SUCCESS( IoStatus.Status )) {
|
|
|
|
ASSERTMSG( "Failed to flush Mft stream for clean checkpoint\n", FALSE );
|
|
Status = IoStatus.Status;
|
|
}
|
|
}
|
|
|
|
DebugTrace( -1, Dbg, ("NtfsFlushLsnStreams -> %08lx\n", Status) );
|
|
|
|
return Status;
|
|
}
|
|
|
|
|
|
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 AttributeListScb;
|
|
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.
|
|
|
|
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;
|
|
|
|
PAGED_CODE();
|
|
|
|
//
|
|
// Commit the current transaction.
|
|
//
|
|
|
|
NtfsCheckpointCurrentTransaction( IrpContext );
|
|
|
|
//
|
|
// Acquire the Scb and ParentScb explicitly.
|
|
//
|
|
|
|
ExAcquireResourceExclusive( Scb->Header.Resource, TRUE );
|
|
|
|
if (ARGUMENT_PRESENT( ParentScb )) {
|
|
|
|
ExAcquireResourceExclusive( ParentScb->Header.Resource, 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 ));
|
|
}
|
|
|
|
//
|
|
// Use a try-finally to reacquire the Scbs.
|
|
//
|
|
|
|
try {
|
|
|
|
//
|
|
// Perform the flush, raise on error.
|
|
//
|
|
|
|
CcFlushCache( &Scb->NonpagedScb->SegmentObject, NULL, 0, &Iosb );
|
|
NtfsNormalizeAndCleanupTransaction( IrpContext, &Iosb.Status, TRUE, STATUS_UNEXPECTED_IO_ERROR );
|
|
|
|
//
|
|
// If no error then purge the section.
|
|
//
|
|
|
|
CcPurgeCacheSection( &Scb->NonpagedScb->SegmentObject, NULL, 0, FALSE );
|
|
|
|
} finally {
|
|
|
|
//
|
|
// Reacquire the Scbs.
|
|
//
|
|
|
|
NtfsAcquireExclusiveScb( IrpContext, Scb );
|
|
ExReleaseResource( Scb->Header.Resource );
|
|
|
|
if (ARGUMENT_PRESENT( ParentScb )) {
|
|
|
|
NtfsAcquireExclusiveScb( IrpContext, ParentScb );
|
|
ExReleaseResource( ParentScb->Header.Resource );
|
|
}
|
|
}
|
|
|
|
//
|
|
// Write the file sizes to the attribute. Commit the transaction since the
|
|
// file sizes must get to disk.
|
|
//
|
|
|
|
NtfsWriteFileSizes( IrpContext, Scb, &Scb->Header.ValidDataLength.QuadPart, TRUE, TRUE );
|
|
NtfsCheckpointCurrentTransaction( IrpContext );
|
|
|
|
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( &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->Flags, IRP_CONTEXT_FLAG_WAIT );
|
|
|
|
//
|
|
// We must free the Scb now before calling through MM to prevent
|
|
// collided page deadlocks.
|
|
//
|
|
|
|
if (Scb->Header.PagingIoResource != NULL) {
|
|
|
|
ScbAcquired = ExIsResourceAcquiredExclusive( Scb->Header.Resource );
|
|
if (ScbAcquired) {
|
|
NtfsReleaseScb( IrpContext, Scb );
|
|
// ASSERT( !ExIsResourceAcquiredExclusive(Scb->Header.Resource) );
|
|
}
|
|
}
|
|
|
|
//
|
|
// 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 );
|
|
}
|
|
|
|
return IoStatus.Status;
|
|
}
|