Windows-Server-2003/base/fs/ntfs/cleanup.c

2906 lines
106 KiB
C
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*++
Copyright (c) 1991 Microsoft Corporation
Module Name:
Cleanup.c
Abstract:
This module implements the File Cleanup routine for Ntfs called by the
dispatch driver.
Author:
Your Name [Email] dd-Mon-Year
Revision History:
--*/
#include "NtfsProc.h"
//
// The Bug check file id for this module
//
#define BugCheckFileId (NTFS_BUG_CHECK_CLEANUP)
//
// The local debug trace level
//
#define Dbg (DEBUG_TRACE_CLEANUP)
#ifdef BRIANDBG
ULONG NtfsCleanupDiskFull = 0;
ULONG NtfsCleanupNoPool = 0;
#endif
#ifdef BRIANDBG
LONG
NtfsFsdCleanupExceptionFilter (
IN PIRP_CONTEXT IrpContext OPTIONAL,
IN PEXCEPTION_POINTERS ExceptionPointer
);
#endif
#ifdef ALLOC_PRAGMA
#pragma alloc_text(PAGE, NtfsCommonCleanup)
#pragma alloc_text(PAGE, NtfsFsdCleanup)
#pragma alloc_text(PAGE, NtfsTrimNormalizedNames)
#endif
NTSTATUS
NtfsFsdCleanup (
IN PVOLUME_DEVICE_OBJECT VolumeDeviceObject,
IN PIRP Irp
)
/*++
Routine Description:
This routine implements the FSD part of Cleanup.
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;
IRP_CONTEXT LocalIrpContext;
ULONG LogFileFullCount = 0;
ASSERT_IRP( Irp );
PAGED_CODE();
//
// If we were called with our file system device object instead of a
// volume device object, just complete this request with STATUS_SUCCESS
//
if (VolumeDeviceObject->DeviceObject.Size == (USHORT)sizeof(DEVICE_OBJECT)) {
Irp->IoStatus.Status = STATUS_SUCCESS;
Irp->IoStatus.Information = FILE_OPENED;
IoCompleteRequest( Irp, IO_DISK_INCREMENT );
return STATUS_SUCCESS;
}
DebugTrace( +1, Dbg, ("NtfsFsdCleanup\n") );
//
// Call the common Cleanup routine
//
FsRtlEnterFileSystem();
ThreadTopLevelContext = NtfsInitializeTopLevelIrp( &TopLevelContext, FALSE, FALSE );
//
// Do the following in a loop to catch the log file full and cant wait
// calls.
//
do {
try {
//
// We are either initiating this request or retrying it.
//
if (IrpContext == NULL) {
//
// Allocate and initialize the Irp. Always use the local IrpContext
// for cleanup. It is never posted.
//
IrpContext = &LocalIrpContext;
NtfsInitializeIrpContext( Irp, TRUE, &IrpContext );
//
// Initialize the thread top level structure, if needed.
//
NtfsUpdateIrpContextWithTopLevel( IrpContext, ThreadTopLevelContext );
} else if (Status == STATUS_LOG_FILE_FULL) {
NtfsCheckpointForLogFileFull( IrpContext );
if (++LogFileFullCount >= 2) {
SetFlag( IrpContext->Flags, IRP_CONTEXT_FLAG_EXCESS_LOG_FULL );
}
}
Status = NtfsCommonCleanup( IrpContext, Irp );
break;
#ifdef BRIANDBG
} except(NtfsFsdCleanupExceptionFilter( IrpContext, GetExceptionInformation() )) {
#else
} except(NtfsExceptionFilter( IrpContext, GetExceptionInformation() )) {
#endif
//
// 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
// exception 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, ("NtfsFsdCleanup -> %08lx\n", Status) );
return Status;
}
NTSTATUS
NtfsCommonCleanup (
IN PIRP_CONTEXT IrpContext,
IN PIRP Irp
)
/*++
Routine Description:
This is the common routine for Cleanup 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;
PLCB LcbForUpdate;
PLCB LcbForCounts;
PSCB ParentScb = NULL;
PFCB ParentFcb = NULL;
PLCB ThisLcb;
PSCB ThisScb;
ATTRIBUTE_ENUMERATION_CONTEXT AttrContext;
PLONGLONG TruncateSize = NULL;
LONGLONG LocalTruncateSize;
BOOLEAN DeleteFile = FALSE;
BOOLEAN DeleteStream = FALSE;
BOOLEAN OpenById;
BOOLEAN RemoveLink = FALSE;
BOOLEAN AcquiredParentScb = FALSE;
BOOLEAN VolumeMounted = TRUE;
LOGICAL VolumeMountedReadOnly;
BOOLEAN AcquiredObjectID = FALSE;
BOOLEAN CleanupAttrContext = FALSE;
BOOLEAN UpdateDuplicateInfo = FALSE;
BOOLEAN AddToDelayQueue = TRUE;
BOOLEAN UnlockedVolume = FALSE;
BOOLEAN DeleteFromFcbTable = FALSE;
USHORT TotalLinkAdj = 0;
PLIST_ENTRY Links;
NAME_PAIR NamePair;
NTFS_TUNNELED_DATA TunneledData;
BOOLEAN DecrementScb = FALSE;
PSCB ImageScb;
#if (defined(DBG) || defined(NTFS_FREE_ASSERTS))
BOOLEAN DecrementedCleanupCount = FALSE;
#endif
PSCB CurrentParentScb;
BOOLEAN AcquiredCheckpoint = FALSE;
ASSERT_IRP_CONTEXT( IrpContext );
ASSERT_IRP( Irp );
ASSERT( FlagOn( IrpContext->TopLevelIrpContext->State, IRP_CONTEXT_STATE_OWNS_TOP_LEVEL ));
PAGED_CODE();
NtfsInitializeNamePair( &NamePair );
//
// Get the current Irp stack location
//
IrpSp = IoGetCurrentIrpStackLocation( Irp );
DebugTrace( +1, Dbg, ("NtfsCommonCleanup\n") );
DebugTrace( 0, Dbg, ("IrpContext = %08lx\n", IrpContext) );
DebugTrace( 0, Dbg, ("Irp = %08lx\n", Irp) );
//
// Extract and decode the file object
//
FileObject = IrpSp->FileObject;
TypeOfOpen = NtfsDecodeFileObject( IrpContext, FileObject, &Vcb, &Fcb, &Scb, &Ccb, FALSE );
Status = STATUS_SUCCESS;
#ifdef BENL_DBG
SetFlag( FileObject->Flags, 0x1000000 );
#endif
//
// Special case the unopened file object and stream files.
//
if ((TypeOfOpen == UnopenedFileObject) ||
(TypeOfOpen == StreamFileOpen)) {
//
// Just set the FO_CLEANUP_COMPLETE flag, and get out
//
SetFlag( FileObject->Flags, FO_CLEANUP_COMPLETE );
//
// Theoretically we should never hit this case. It means an app
// tried to close a handle he didn't open (call NtClose with a handle
// value that happens to be in the handle table). It is safe to
// simply return SUCCESS in this case.
//
// Trigger an assert so we can find the bad app though.
//
ASSERT( TypeOfOpen != StreamFileOpen );
DebugTrace( -1, Dbg, ("NtfsCommonCleanup -> %08lx\n", Status) );
NtfsCompleteRequest( IrpContext, Irp, Status );
return Status;
}
//
// Remember if this is an open by file Id open.
//
OpenById = BooleanFlagOn( Ccb->Flags, CCB_FLAG_OPEN_BY_FILE_ID );
//
// Remember the source info flags in the Ccb.
//
IrpContext->SourceInfo = Ccb->UsnSourceInfo;
//
// Acquire exclusive access to the Vcb and enqueue the irp if we didn't
// get access
//
if (TypeOfOpen == UserVolumeOpen) {
if (FlagOn( IrpContext->State, IRP_CONTEXT_STATE_DASD_UNLOCK )) {
//
// Start by locking out all other checkpoint
// operations.
//
NtfsAcquireCheckpointSynchronization( IrpContext, Vcb );
AcquiredCheckpoint = TRUE;
}
ASSERTMSG( "Acquire could fail.\n", FlagOn( IrpContext->State, IRP_CONTEXT_STATE_WAIT ) );
NtfsAcquireExclusiveVcb( IrpContext, Vcb, FALSE );
} else {
//
// We will never have the checkpoint here so we can raise if dismounted
//
ASSERT( !AcquiredCheckpoint );
NtfsAcquireSharedVcb( IrpContext, Vcb, TRUE );
}
//
// Remember if the volume has been dismounted. No point in makeing any disk
// changes in that case.
//
if (!FlagOn( Vcb->VcbState, VCB_STATE_VOLUME_MOUNTED )) {
VolumeMounted = FALSE;
}
VolumeMountedReadOnly = (LOGICAL)NtfsIsVolumeReadOnly( Vcb );
//
// Use a try-finally to facilitate cleanup.
//
try {
//
// 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 );
NtfsAcquireExclusiveScb( IrpContext, Scb );
} else {
NtfsAcquireExclusiveScb( IrpContext, Scb );
//
// 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 (Fcb->PagingIoResource != NULL) {
NtfsReleaseScb( IrpContext, Scb );
NtfsAcquireExclusivePagingIo( IrpContext, Fcb );
NtfsAcquireExclusiveScb( IrpContext, Scb );
}
}
LcbForUpdate = LcbForCounts = Lcb = Ccb->Lcb;
if (Lcb != NULL) {
ParentScb = Lcb->Scb;
if (ParentScb != NULL) {
ParentFcb = ParentScb->Fcb;
}
}
if (VolumeMounted && !VolumeMountedReadOnly) {
//
// Update the Lcb/Scb to reflect the case where this opener had
// specified delete on close.
//
if (FlagOn( Ccb->Flags, CCB_FLAG_DELETE_ON_CLOSE )) {
if (FlagOn( Ccb->Flags, CCB_FLAG_OPEN_AS_FILE )) {
BOOLEAN LastLink;
BOOLEAN NonEmptyIndex;
//
// It is ok to get rid of this guy. All we need to do is
// mark this Lcb for delete and decrement the link count
// in the Fcb. If this is a primary link, then we
// indicate that the primary link has been deleted.
//
if (!LcbLinkIsDeleted( Lcb ) &&
(!IsDirectory( &Fcb->Info ) ||
NtfsIsLinkDeleteable( IrpContext, Fcb, &NonEmptyIndex, &LastLink))) {
//
// Walk through all of the Scb's for this stream and
// make sure there are no active image sections.
//
ImageScb = NULL;
while ((ImageScb = NtfsGetNextChildScb( Fcb, ImageScb )) != NULL) {
InterlockedIncrement( &ImageScb->CloseCount );
DecrementScb = TRUE;
if (NtfsIsTypeCodeUserData( ImageScb->AttributeTypeCode ) &&
!FlagOn( ImageScb->ScbState, SCB_STATE_ATTRIBUTE_DELETED ) &&
(ImageScb->NonpagedScb->SegmentObject.ImageSectionObject != NULL)) {
if (!MmFlushImageSection( &ImageScb->NonpagedScb->SegmentObject,
MmFlushForDelete )) {
InterlockedDecrement( &ImageScb->CloseCount );
DecrementScb = FALSE;
break;
}
}
InterlockedDecrement( &ImageScb->CloseCount );
DecrementScb = FALSE;
}
if (ImageScb == NULL) {
if (FlagOn( Lcb->FileNameAttr->Flags, FILE_NAME_DOS | FILE_NAME_NTFS )) {
SetFlag( Fcb->FcbState, FCB_STATE_PRIMARY_LINK_DELETED );
}
Fcb->LinkCount -= 1;
SetFlag( Lcb->LcbState, LCB_STATE_DELETE_ON_CLOSE );
//
// Call into the notify package to close any handles on
// a directory being deleted.
//
if (IsDirectory( &Fcb->Info )) {
FsRtlNotifyFilterChangeDirectory( Vcb->NotifySync,
&Vcb->DirNotifyList,
FileObject->FsContext,
NULL,
FALSE,
FALSE,
0,
NULL,
NULL,
NULL,
NULL );
}
}
}
//
// Otherwise we are simply removing the attribute.
//
} else {
ImageScb = Scb;
InterlockedIncrement( &ImageScb->CloseCount );
DecrementScb = TRUE;
if ((ImageScb->NonpagedScb->SegmentObject.ImageSectionObject == NULL) ||
MmFlushImageSection( &ImageScb->NonpagedScb->SegmentObject,
MmFlushForDelete )) {
SetFlag( Scb->ScbState, SCB_STATE_DELETE_ON_CLOSE );
}
InterlockedDecrement( &ImageScb->CloseCount );
DecrementScb = FALSE;
}
//
// Clear the flag so we will ignore it in the log file full case.
//
ClearFlag( Ccb->Flags, CCB_FLAG_DELETE_ON_CLOSE );
}
//
// If we are going to try and delete something, anything, knock the file
// size and valid data down to zero. Then update the snapshot
// so that the sizes will be zero even if the operation fails.
//
// If we're deleting the file, go through all of the Scb's.
//
if ((Fcb->LinkCount == 0) &&
(Fcb->CleanupCount == 1)) {
DeleteFile = TRUE;
NtfsFreeSnapshotsForFcb( IrpContext, Scb->Fcb );
for (Links = Fcb->ScbQueue.Flink;
Links != &Fcb->ScbQueue;
Links = Links->Flink) {
ThisScb = CONTAINING_RECORD( Links, SCB, FcbLinks );
//
// Set the Scb sizes to zero except for the attribute list.
//
if ((ThisScb->AttributeTypeCode != $ATTRIBUTE_LIST) &&
(ThisScb->AttributeTypeCode != $REPARSE_POINT)) {
//
// If the file is non-resident we will need a file object
// when we delete the allocation. Create it now
// so that CC will know the stream is shrinking to zero
// and will purge the data.
//
if ((ThisScb->FileObject == NULL) &&
(ThisScb->AttributeTypeCode == $DATA) &&
(ThisScb->NonpagedScb->SegmentObject.DataSectionObject != NULL) &&
!FlagOn( ThisScb->ScbState, SCB_STATE_ATTRIBUTE_RESIDENT )) {
//
// Use a try-finally here in case we get an
// INSUFFICIENT_RESOURCES. We still need to
// proceed with the delete.
//
try {
NtfsCreateInternalAttributeStream( IrpContext,
ThisScb,
TRUE,
&NtfsInternalUseFile[COMMONCLEANUP_FILE_NUMBER] );
} except( NtfsCleanupExceptionFilter( IrpContext, GetExceptionInformation(), &Status )) {
if (Status == STATUS_INSUFFICIENT_RESOURCES) {
Status = STATUS_SUCCESS;
}
}
}
ThisScb->Header.FileSize =
ThisScb->Header.ValidDataLength = Li0;
}
if (FlagOn( ThisScb->ScbState, SCB_STATE_FILE_SIZE_LOADED )) {
NtfsSnapshotScb( IrpContext, ThisScb );
}
}
//
// Otherwise we may only be deleting this stream.
//
} else if (FlagOn( Scb->ScbState, SCB_STATE_DELETE_ON_CLOSE ) &&
(Scb->CleanupCount == 1)) {
try {
//
// We may have expanded the quota here and currently own some
// quota resource. We want to release them now so we don't
// deadlock with resources acquired later.
//
NtfsCheckpointCurrentTransaction( IrpContext );
if (IrpContext->SharedScb != NULL) {
NtfsReleaseSharedResources( IrpContext );
}
DeleteStream = TRUE;
Scb->Header.FileSize =
Scb->Header.ValidDataLength = Li0;
NtfsFreeSnapshotsForFcb( IrpContext, Scb->Fcb );
if (FlagOn( Scb->ScbState, SCB_STATE_FILE_SIZE_LOADED )) {
NtfsSnapshotScb( IrpContext, Scb );
}
} except( NtfsCleanupExceptionFilter( IrpContext, GetExceptionInformation(), &Status )) {
NOTHING;
}
}
}
//
// Let's do a sanity check.
//
ASSERT( Fcb->CleanupCount != 0 );
ASSERT( Scb->CleanupCount != 0 );
//
// Case on the type of open that we are trying to cleanup.
//
switch (TypeOfOpen) {
case UserVolumeOpen :
DebugTrace( 0, Dbg, ("Cleanup on user volume\n") );
//
// First set the FO_CLEANUP_COMPLETE flag.
//
SetFlag( FileObject->Flags, FO_CLEANUP_COMPLETE );
//
// For a volume open, we check if this open locked the volume.
// All the other work is done in common code below.
//
if (FlagOn( Vcb->VcbState, VCB_STATE_LOCKED ) &&
(((ULONG_PTR)Vcb->FileObjectWithVcbLocked & ~1) == (ULONG_PTR)FileObject)) {
//
// Note its unlock attempt and retry so we can serialize with checkpoints
//
if (!FlagOn( IrpContext->State, IRP_CONTEXT_STATE_DASD_UNLOCK )) {
SetFlag( IrpContext->State, IRP_CONTEXT_STATE_DASD_UNLOCK );
NtfsRaiseStatus( IrpContext, STATUS_CANT_WAIT, NULL, NULL );
}
if ((ULONG_PTR)Vcb->FileObjectWithVcbLocked == ((ULONG_PTR)FileObject)+1) {
NtfsPerformDismountOnVcb( IrpContext, Vcb, TRUE, NULL );
//
// Purge the volume for the autocheck case.
//
} else if (FlagOn( FileObject->Flags, FO_FILE_MODIFIED )) {
if (VolumeMounted && !VolumeMountedReadOnly) {
//
// Drop the Scb for the volume Dasd around this call.
//
NtfsReleaseScb( IrpContext, Scb );
try {
NtfsFlushVolume( IrpContext, Vcb, FALSE, TRUE, TRUE, FALSE );
} except( NtfsCleanupExceptionFilter( IrpContext, GetExceptionInformation(), &Status )) {
NOTHING;
}
NtfsAcquireExclusiveScb( IrpContext, Scb );
}
//
// If this is not the boot partition then dismount the Vcb.
//
#ifdef SYSCACHE_DEBUG
if ((((Vcb->SyscacheScb) && (Vcb->CleanupCount == 2)) || (Vcb->CleanupCount == 1)) &&
#else
if ((Vcb->CleanupCount == 1) &&
#endif
((Vcb->CloseCount - Vcb->SystemFileCloseCount) == 1)) {
try {
NtfsPerformDismountOnVcb( IrpContext, Vcb, TRUE, NULL );
} except( NtfsCleanupExceptionFilter( IrpContext, GetExceptionInformation(), &Status )) {
NOTHING;
}
}
}
ClearFlag( Vcb->VcbState, VCB_STATE_LOCKED | VCB_STATE_EXPLICIT_LOCK );
Vcb->FileObjectWithVcbLocked = NULL;
UnlockedVolume = TRUE;
//
// If the quota tracking has been requested and the quotas
// need to be repaired then try to repair them now. Also restart
// the Usn journal deletion if it was underway.
//
if (VolumeMounted && !VolumeMountedReadOnly) {
if ((Status == STATUS_SUCCESS) &&
(Vcb->DeleteUsnData.FinalStatus == STATUS_VOLUME_DISMOUNTED)) {
Vcb->DeleteUsnData.FinalStatus = STATUS_SUCCESS;
try {
NtfsPostSpecial( IrpContext, Vcb, NtfsDeleteUsnSpecial, &Vcb->DeleteUsnData );
} except( NtfsCleanupExceptionFilter( IrpContext, GetExceptionInformation(), &Status )) {
NOTHING;
}
}
if ((Status == STATUS_SUCCESS) &&
FlagOn( Vcb->QuotaFlags, QUOTA_FLAG_TRACKING_REQUESTED) &&
FlagOn( Vcb->QuotaFlags, QUOTA_FLAG_OUT_OF_DATE |
QUOTA_FLAG_CORRUPT |
QUOTA_FLAG_PENDING_DELETES)) {
try {
NtfsPostRepairQuotaIndex( IrpContext, Vcb );
} except( NtfsCleanupExceptionFilter( IrpContext, GetExceptionInformation(), &Status )) {
NOTHING;
}
}
}
}
break;
case UserViewIndexOpen :
case UserDirectoryOpen :
DebugTrace( 0, Dbg, ("Cleanup on user directory/file\n") );
NtfsSnapshotScb( IrpContext, Scb );
//
// Capture any changes to the time stamps for this file.
//
NtfsUpdateScbFromFileObject( IrpContext, FileObject, Scb, TRUE );
//
// Now set the FO_CLEANUP_COMPLETE flag.
//
SetFlag( FileObject->Flags, FO_CLEANUP_COMPLETE );
//
// To perform cleanup on a directory, we first complete any
// Irps watching from this directory. If we are deleting the
// file then we remove all prefix entries for all the Lcb's going
// into this directory and delete the file. We then report to
// dir notify that this file is going away.
//
//
// Complete any Notify Irps on this file handle.
//
if (FlagOn( Ccb->Flags, CCB_FLAG_DIR_NOTIFY )) {
//
// Both the notify count and notify list are separate for view
// indices versus file name indices (directories).
//
if (TypeOfOpen == UserViewIndexOpen) {
FsRtlNotifyCleanup( Vcb->NotifySync, &Vcb->ViewIndexNotifyList, Ccb );
ClearFlag( Ccb->Flags, CCB_FLAG_DIR_NOTIFY );
InterlockedDecrement( &Vcb->ViewIndexNotifyCount );
} else {
FsRtlNotifyCleanup( Vcb->NotifySync, &Vcb->DirNotifyList, Ccb );
ClearFlag( Ccb->Flags, CCB_FLAG_DIR_NOTIFY );
InterlockedDecrement( &Vcb->NotifyCount );
}
}
//
// Try to remove normalized name if its long and if no handles active (only this 1 left)
// and no lcbs are active - all notifies farther down in function
// use parent Scb's normalized name. If we don't remove it here
// this always goes away during a close
//
if ((Scb->ScbType.Index.NormalizedName.MaximumLength > LONGNAME_THRESHOLD) &&
(Fcb->CleanupCount == 1)) {
//
// Cleanup the current scb node and then trim its parents
//
NtfsDeleteNormalizedName( Scb );
if (Lcb != NULL) {
CurrentParentScb = Lcb->Scb;
} else {
CurrentParentScb = NULL;
}
if ((CurrentParentScb != NULL) &&
(CurrentParentScb->ScbType.Index.NormalizedName.MaximumLength > LONGNAME_THRESHOLD)) {
NtfsTrimNormalizedNames( IrpContext, Fcb, CurrentParentScb );
}
}
//
// When cleaning up a user directory, we always remove the
// share access and modify the file counts. If the Fcb
// has been marked as delete on close and this is the last
// open file handle, we remove the file from the Mft and
// remove it from it's parent index entry.
//
if (VolumeMounted &&
(!VolumeMountedReadOnly) &&
((SafeNodeType( Scb ) == NTFS_NTC_SCB_INDEX))) {
if (DeleteFile) {
BOOLEAN AcquiredFcbTable = FALSE;
ASSERT( (Lcb == NULL) ||
(LcbLinkIsDeleted( Lcb ) && Lcb->CleanupCount == 1 ));
//
// If we don't have an Lcb and there is one on the Fcb then
// let's use it.
//
if ((Lcb == NULL) && !IsListEmpty( &Fcb->LcbQueue )) {
Lcb = CONTAINING_RECORD( Fcb->LcbQueue.Flink,
LCB,
FcbLinks );
ParentScb = Lcb->Scb;
if (ParentScb != NULL) {
ParentFcb = ParentScb->Fcb;
}
}
try {
//
// In a very rare case the handle on a file may be an
// OpenByFileID handle. In that case we need to find
// the parent for the remaining link on the file.
//
if (ParentScb == NULL) {
PFILE_NAME FileNameAttr;
UNICODE_STRING FileName;
NtfsInitializeAttributeContext( &AttrContext );
CleanupAttrContext = TRUE;
if (!NtfsLookupAttributeByCode( IrpContext,
Fcb,
&Fcb->FileReference,
$FILE_NAME,
&AttrContext )) {
NtfsRaiseStatus( IrpContext, STATUS_FILE_CORRUPT_ERROR, NULL, Fcb );
}
//
// Now we need an Fcb and then an Scb for the directory.
//
FileNameAttr = (PFILE_NAME) NtfsAttributeValue( NtfsFoundAttribute( &AttrContext ));
NtfsAcquireFcbTable( IrpContext, Vcb );
AcquiredFcbTable = TRUE;
ParentFcb = NtfsCreateFcb( IrpContext,
Vcb,
FileNameAttr->ParentDirectory,
FALSE,
TRUE,
NULL );
ParentFcb->ReferenceCount += 1;
if (!NtfsAcquireExclusiveFcb( IrpContext, ParentFcb, NULL, ACQUIRE_NO_DELETE_CHECK | ACQUIRE_DONT_WAIT )) {
NtfsReleaseFcbTable( IrpContext, Vcb );
NtfsAcquireExclusiveFcb( IrpContext, ParentFcb, NULL, ACQUIRE_NO_DELETE_CHECK );
NtfsAcquireFcbTable( IrpContext, Vcb );
}
ParentFcb->ReferenceCount -= 1;
NtfsReleaseFcbTable( IrpContext, Vcb );
AcquiredFcbTable = FALSE;
ParentScb = NtfsCreateScb( IrpContext,
ParentFcb,
$INDEX_ALLOCATION,
&NtfsFileNameIndex,
FALSE,
NULL );
if (FlagOn(Scb->ScbState, SCB_STATE_FILE_SIZE_LOADED)) {
NtfsSnapshotScb( IrpContext, Scb );
}
//
// Make sure this new Scb is connected to our Fcb so teardown
// will happen.
//
FileName.Buffer = &FileNameAttr->FileName[0];
FileName.MaximumLength =
FileName.Length = FileNameAttr->FileNameLength * sizeof( WCHAR );
NtfsCreateLcb( IrpContext,
ParentScb,
Fcb,
FileName,
FileNameAttr->Flags,
NULL );
AcquiredParentScb = TRUE;
}
NtfsDeleteFile( IrpContext, Fcb, ParentScb, &AcquiredParentScb, NULL, NULL );
TotalLinkAdj += 1;
//
// Remove all tunneling entries for this directory
//
FsRtlDeleteKeyFromTunnelCache( &Vcb->Tunnel,
*(PULONGLONG) &Fcb->FileReference );
} except( NtfsCleanupExceptionFilter( IrpContext, GetExceptionInformation(), &Status )) {
NOTHING;
}
if (AcquiredFcbTable) {
NtfsReleaseFcbTable( IrpContext, Vcb );
}
if (STATUS_SUCCESS == Status) {
if (!OpenById && (Vcb->NotifyCount != 0)) {
NtfsReportDirNotify( IrpContext,
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),
FILE_NOTIFY_CHANGE_DIR_NAME,
FILE_ACTION_REMOVED,
ParentFcb );
}
DeleteFromFcbTable = TRUE;
//
// We certainly don't need to any on disk update for this
// file now.
//
ClearFlag( Ccb->Flags,
CCB_FLAG_USER_SET_LAST_MOD_TIME |
CCB_FLAG_USER_SET_LAST_CHANGE_TIME |
CCB_FLAG_USER_SET_LAST_ACCESS_TIME );
}
} else {
//
// Determine if we should put this on the delayed close list.
// The following must be true.
//
// - This is not the root directory
// - This directory is not about to be deleted
// - This is the last handle and last file object for this
// directory.
// - There are no other file objects on this file.
// - We are not currently reducing the delayed close queue.
//
if ((Fcb->CloseCount == 1) &&
(NtfsData.DelayedCloseCount <= NtfsMaxDelayedCloseCount)) {
NtfsAcquireFsrtlHeader( Scb );
SetFlag( Scb->ScbState, SCB_STATE_DELAY_CLOSE );
NtfsReleaseFsrtlHeader( Scb );
}
}
}
break;
case UserFileOpen :
DebugTrace( 0, Dbg, ("Cleanup on user file\n") );
//
// If the Scb is uninitialized, we read it from the disk.
//
if (!FlagOn( Scb->ScbState, SCB_STATE_HEADER_INITIALIZED ) &&
VolumeMounted &&
!VolumeMountedReadOnly) {
try {
NtfsUpdateScbFromAttribute( IrpContext, Scb, NULL );
} except( NtfsCleanupExceptionFilter( IrpContext, GetExceptionInformation(), &Status )) {
NOTHING;
}
}
NtfsSnapshotScb( IrpContext, Scb );
//
// Coordinate the cleanup operation with the oplock state.
// Cleanup operations can always cleanup any oplocks immediately.
//
if (SafeNodeType( Scb ) == NTFS_NTC_SCB_DATA) {
FsRtlCheckOplock( &Scb->ScbType.Data.Oplock,
Irp,
IrpContext,
NULL,
NULL );
}
//
// In this case, we have to unlock all the outstanding file
// locks, update the time stamps for the file and sizes for
// this attribute, and set the archive bit if necessary.
//
if ((SafeNodeType( Scb ) == NTFS_NTC_SCB_DATA) &&
(Scb->ScbType.Data.FileLock != NULL)) {
(VOID) FsRtlFastUnlockAll( Scb->ScbType.Data.FileLock,
FileObject,
IoGetRequestorProcess( Irp ),
NULL );
}
//
// Update the FastIoField.
//
NtfsAcquireFsrtlHeader( Scb );
Scb->Header.IsFastIoPossible = NtfsIsFastIoPossible( Scb );
NtfsReleaseFsrtlHeader( Scb );
//
// Trim normalized names of parent dirs if they are over the threshold
//
ASSERT( IrpContext->TransactionId == 0 );
if (Fcb->CleanupCount == 1) {
if (Lcb != NULL) {
CurrentParentScb = Lcb->Scb;
} else {
CurrentParentScb = NULL;
}
if ((CurrentParentScb != NULL) &&
(CurrentParentScb->ScbType.Index.NormalizedName.MaximumLength > LONGNAME_THRESHOLD)) {
NtfsTrimNormalizedNames( IrpContext, Fcb, CurrentParentScb );
}
} // endif cleanupcnt == 1
//
// If the Fcb is in valid shape, we check on the cases where we delete
// the file or attribute.
//
if (VolumeMounted && !VolumeMountedReadOnly) {
//
// Capture any changes to the time stamps for this file.
//
NtfsUpdateScbFromFileObject( IrpContext, FileObject, Scb, TRUE );
//
// Now set the FO_CLEANUP_COMPLETE flag.
//
SetFlag( FileObject->Flags, FO_CLEANUP_COMPLETE );
if (Status == STATUS_SUCCESS) {
//
// We are checking here for special actions we take when
// we have the last user handle on a link and the link has
// been marked for delete. We could either be removing the
// file or removing a link.
//
if ((Lcb == NULL) || (LcbLinkIsDeleted( Lcb ) && (Lcb->CleanupCount == 1))) {
if (DeleteFile) {
BOOLEAN AcquiredFcbTable = FALSE;
//
// If we don't have an Lcb and the Fcb has some entries then
// grab one of these to do the update.
//
if (Lcb == NULL) {
for (Links = Fcb->LcbQueue.Flink;
Links != &Fcb->LcbQueue;
Links = Links->Flink) {
ThisLcb = CONTAINING_RECORD( Fcb->LcbQueue.Flink,
LCB,
FcbLinks );
if (!FlagOn( ThisLcb->LcbState, LCB_STATE_LINK_IS_GONE )) {
Lcb = ThisLcb;
ParentScb = Lcb->Scb;
if (ParentScb != NULL) {
ParentFcb = ParentScb->Fcb;
}
break;
}
}
}
try {
//
// In a very rare case the handle on a file may be an
// OpenByFileID handle. In that case we need to find
// the parent for the remaining link on the file.
//
if (ParentScb == NULL) {
PFILE_NAME FileNameAttr;
UNICODE_STRING FileName;
NtfsInitializeAttributeContext( &AttrContext );
CleanupAttrContext = TRUE;
if (!NtfsLookupAttributeByCode( IrpContext,
Fcb,
&Fcb->FileReference,
$FILE_NAME,
&AttrContext )) {
NtfsRaiseStatus( IrpContext, STATUS_FILE_CORRUPT_ERROR, NULL, Fcb );
}
//
// Now we need an Fcb and then an Scb for the directory.
//
FileNameAttr = (PFILE_NAME) NtfsAttributeValue( NtfsFoundAttribute( &AttrContext ));
NtfsAcquireFcbTable( IrpContext, Vcb );
AcquiredFcbTable = TRUE;
ParentFcb = NtfsCreateFcb( IrpContext,
Vcb,
FileNameAttr->ParentDirectory,
FALSE,
TRUE,
NULL );
ParentFcb->ReferenceCount += 1;
if (!NtfsAcquireExclusiveFcb( IrpContext, ParentFcb, NULL, ACQUIRE_NO_DELETE_CHECK | ACQUIRE_DONT_WAIT )) {
NtfsReleaseFcbTable( IrpContext, Vcb );
NtfsAcquireExclusiveFcb( IrpContext, ParentFcb, NULL, ACQUIRE_NO_DELETE_CHECK );
NtfsAcquireFcbTable( IrpContext, Vcb );
}
ParentFcb->ReferenceCount -= 1;
NtfsReleaseFcbTable( IrpContext, Vcb );
AcquiredFcbTable = FALSE;
ParentScb = NtfsCreateScb( IrpContext,
ParentFcb,
$INDEX_ALLOCATION,
&NtfsFileNameIndex,
FALSE,
NULL );
if (FlagOn(Scb->ScbState, SCB_STATE_FILE_SIZE_LOADED)) {
NtfsSnapshotScb( IrpContext, Scb );
}
//
// Make sure this new Scb is connected to our Fcb so teardown
// will happen.
//
FileName.Buffer = &FileNameAttr->FileName[0];
FileName.MaximumLength =
FileName.Length = FileNameAttr->FileNameLength * sizeof( WCHAR );
NtfsCreateLcb( IrpContext,
ParentScb,
Fcb,
FileName,
FileNameAttr->Flags,
NULL );
AcquiredParentScb = TRUE;
}
AddToDelayQueue = FALSE;
TunneledData.HasObjectId = FALSE;
NtfsDeleteFile( IrpContext, Fcb, ParentScb, &AcquiredParentScb, &NamePair, &TunneledData );
TotalLinkAdj += 1;
//
// Stash property information in the tunnel if the object was
// opened by name, has a parent directory caller was treating it
// as a non-POSIX object and we had an good, active link
//
if (!OpenById &&
ParentScb &&
Ccb->Lcb &&
!FlagOn(FileObject->Flags, FO_OPENED_CASE_SENSITIVE)) {
NtfsGetTunneledData( IrpContext,
Fcb,
&TunneledData );
FsRtlAddToTunnelCache( &Vcb->Tunnel,
*(PULONGLONG)&ParentScb->Fcb->FileReference,
&NamePair.Short,
&NamePair.Long,
BooleanFlagOn(Ccb->Lcb->FileNameAttr->Flags, FILE_NAME_DOS),
sizeof(NTFS_TUNNELED_DATA),
&TunneledData);
}
} except( NtfsCleanupExceptionFilter( IrpContext, GetExceptionInformation(), &Status )) {
NOTHING;
}
if (AcquiredFcbTable) {
NtfsReleaseFcbTable( IrpContext, Vcb );
}
if (Status == STATUS_SUCCESS) {
if ((Vcb->NotifyCount != 0) &&
!OpenById) {
NtfsReportDirNotify( IrpContext,
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),
FILE_NOTIFY_CHANGE_FILE_NAME,
FILE_ACTION_REMOVED,
ParentFcb );
}
DeleteFromFcbTable = TRUE;
//
// We certainly don't need to any on disk update for this
// file now.
//
ClearFlag( Ccb->Flags,
CCB_FLAG_USER_SET_LAST_MOD_TIME |
CCB_FLAG_USER_SET_LAST_CHANGE_TIME |
CCB_FLAG_USER_SET_LAST_ACCESS_TIME );
//
// We will truncate the attribute to size 0.
//
TruncateSize = (PLONGLONG)&Li0;
}
//
// Now we want to check for the last user's handle on a
// link (or the last handle on a Ntfs/8.3 pair). In this
// case we want to remove the links from the disk.
//
} else if (Lcb != NULL) {
ThisLcb = NULL;
RemoveLink = TRUE;
if (FlagOn( Lcb->FileNameAttr->Flags, FILE_NAME_DOS | FILE_NAME_NTFS ) &&
(Lcb->FileNameAttr->Flags != (FILE_NAME_NTFS | FILE_NAME_DOS))) {
//
// Walk through all the links looking for a link
// with a flag set which is not the same as the
// link we already have.
//
for (Links = Fcb->LcbQueue.Flink;
Links != &Fcb->LcbQueue;
Links = Links->Flink) {
ThisLcb = CONTAINING_RECORD( Links, LCB, FcbLinks );
//
// If this has a flag set and is not the Lcb
// for this cleanup, then we check if there
// are no Ccb's left for this.
//
if (FlagOn( ThisLcb->FileNameAttr->Flags, FILE_NAME_DOS | FILE_NAME_NTFS )
&&
(ThisLcb != Lcb)) {
if (ThisLcb->CleanupCount != 0) {
RemoveLink = FALSE;
}
break;
}
ThisLcb = NULL;
}
}
//
// If we are to remove the link, we do so now. This removes
// the filename attributes and the entries in the parent
// indexes for this link. In addition, we mark the links
// as having been removed and decrement the number of links
// left on the file. We don't remove the link if this the
// last link on the file. This means that someone has the
// file open by FileID. We don't want to remove the last
// link until all the handles are closed.
//
if (RemoveLink && (Fcb->TotalLinks > 1)) {
NtfsAcquireExclusiveScb( IrpContext, ParentScb );
AcquiredParentScb = TRUE;
//
// We might end up with deallocating or even allocating an MFT
// record in the process of deleting this entry. So, it's wise to preacquire
// QuotaControl resource, lest we deadlock with the MftScb resource.
// Unfortunately, it's not easy to figure out whether any of those will
// actually happen at this point.
//
// We must have acquired all the other resources except $ObjId
// at this point. QuotaControl must get acquired after ObjId Scb.
//
if (NtfsPerformQuotaOperation( Fcb )) {
NtfsAcquireSharedScb( IrpContext, Vcb->ObjectIdTableScb );
AcquiredObjectID = TRUE;
NtfsAcquireQuotaControl( IrpContext, Fcb->QuotaControl );
}
try {
AddToDelayQueue = FALSE;
TunneledData.HasObjectId = FALSE;
//
// Post the link change.
//
NtfsPostUsnChange( IrpContext, Fcb, USN_REASON_HARD_LINK_CHANGE );
NtfsRemoveLink( IrpContext,
Fcb,
ParentScb,
Lcb->ExactCaseLink.LinkName,
&NamePair,
&TunneledData );
//
// It's possible that this is the link that was used for the
// Usn name. Make sure we look up the Usn name on the
// next operation.
//
ClearFlag( Fcb->FcbState, FCB_STATE_VALID_USN_NAME );
//
// Stash property information in the tunnel if caller opened the
// object by name and was treating it as a non-POSIX object
//
if (!OpenById && !FlagOn(FileObject->Flags, FO_OPENED_CASE_SENSITIVE)) {
NtfsGetTunneledData( IrpContext,
Fcb,
&TunneledData );
FsRtlAddToTunnelCache( &Vcb->Tunnel,
*(PULONGLONG)&ParentScb->Fcb->FileReference,
&NamePair.Short,
&NamePair.Long,
BooleanFlagOn(Lcb->FileNameAttr->Flags, FILE_NAME_DOS),
sizeof(NTFS_TUNNELED_DATA),
&TunneledData);
}
TotalLinkAdj += 1;
NtfsUpdateFcb( ParentFcb,
(FCB_INFO_CHANGED_LAST_CHANGE |
FCB_INFO_CHANGED_LAST_MOD |
FCB_INFO_UPDATE_LAST_ACCESS) );
} except( NtfsCleanupExceptionFilter( IrpContext, GetExceptionInformation(), &Status )) {
NOTHING;
}
if ((Vcb->NotifyCount != 0) &&
!OpenById &&
(Status == STATUS_SUCCESS)) {
NtfsReportDirNotify( IrpContext,
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),
FILE_NOTIFY_CHANGE_FILE_NAME,
FILE_ACTION_REMOVED,
ParentFcb );
}
LcbForUpdate = NULL;
//
// Update the time stamps for removing the link. Clear the
// FO_CLEANUP_COMPLETE flag around this call so the time
// stamp change is not nooped.
//
SetFlag( Ccb->Flags, CCB_FLAG_UPDATE_LAST_CHANGE );
ClearFlag( FileObject->Flags, FO_CLEANUP_COMPLETE );
NtfsUpdateScbFromFileObject( IrpContext, FileObject, Scb, TRUE );
SetFlag( FileObject->Flags, FO_CLEANUP_COMPLETE );
} else {
RemoveLink = FALSE;
}
}
}
//
// If the file/attribute is not going away, we update the
// attribute size now rather than waiting for the Lazy
// Writer to catch up. If the cleanup count isn't 1 then
// defer the following actions.
//
if ((Scb->CleanupCount == 1) &&
(Fcb->LinkCount != 0) &&
(Status == STATUS_SUCCESS)) {
//
// We may also have to delete this attribute only.
//
if (DeleteStream) {
//
// If this stream is subject to quota then we need to acquire the
// parent now. Other we will acquire quota control during the
// delete operation and then try to acquire the parent to
// perform the update duplicate info.
//
if (NtfsPerformQuotaOperation( Fcb ) && (!RemoveLink) && (!FlagOn( Fcb->FcbState, FCB_STATE_SYSTEM_FILE ))) {
NtfsPrepareForUpdateDuplicate( IrpContext,
Fcb,
&LcbForUpdate,
&ParentScb,
TRUE );
}
//
// We might be retrying the delete stream operation due to a log file
// full. If the attribute type code is $UNUSED then the stream has
// already been deleted.
//
if (Scb->AttributeTypeCode != $UNUSED) {
try {
//
// Delete the attribute only.
//
if (CleanupAttrContext) {
NtfsCleanupAttributeContext( IrpContext, &AttrContext );
}
NtfsInitializeAttributeContext( &AttrContext );
CleanupAttrContext = TRUE;
NtfsLookupAttributeForScb( IrpContext, Scb, NULL, &AttrContext );
do {
NtfsDeleteAttributeRecord( IrpContext,
Fcb,
(DELETE_LOG_OPERATION |
DELETE_RELEASE_FILE_RECORD |
DELETE_RELEASE_ALLOCATION),
&AttrContext );
} while (NtfsLookupNextAttributeForScb( IrpContext, Scb, &AttrContext ));
} except( NtfsCleanupExceptionFilter( IrpContext, GetExceptionInformation(), &Status )) {
NOTHING;
}
NtfsPostUsnChange( IrpContext, Fcb, USN_REASON_STREAM_CHANGE );
}
//
// Now that we're done deleting the attribute, we need to checkpoint
// so that the Mft resource can be released. First we need to set
// the appropriate IrpContext flag to indicate whether we really need
// to release the Mft.
//
if ((Vcb->MftScb != NULL) &&
(Vcb->MftScb->Fcb->ExclusiveFcbLinks.Flink != NULL) &&
NtfsIsExclusiveScb( Vcb->MftScb )) {
SetFlag( IrpContext->Flags, IRP_CONTEXT_FLAG_RELEASE_MFT );
}
try {
NtfsCheckpointCurrentTransaction( IrpContext );
} except( NtfsCleanupExceptionFilter( IrpContext, GetExceptionInformation(), &Status )) {
NOTHING;
}
//
// Set the Scb flag to indicate that the attribute is
// gone. Setting $UNUSED means we won't roll this back
//
Scb->ValidDataToDisk =
Scb->Header.AllocationSize.QuadPart =
Scb->Header.FileSize.QuadPart =
Scb->Header.ValidDataLength.QuadPart = 0;
Scb->AttributeTypeCode = $UNUSED;
SetFlag( Scb->ScbState, SCB_STATE_ATTRIBUTE_DELETED );
SetFlag( Scb->ScbState, SCB_STATE_NOTIFY_REMOVE_STREAM );
ClearFlag( Scb->ScbState,
SCB_STATE_NOTIFY_RESIZE_STREAM |
SCB_STATE_NOTIFY_MODIFY_STREAM |
SCB_STATE_NOTIFY_ADD_STREAM );
//
// Update the time stamps for removing the link. Clear the
// FO_CLEANUP_COMPLETE flag around this call so the time
// stamp change is not nooped.
//
SetFlag( Ccb->Flags,
CCB_FLAG_UPDATE_LAST_CHANGE | CCB_FLAG_SET_ARCHIVE );
ClearFlag( FileObject->Flags, FO_CLEANUP_COMPLETE );
NtfsUpdateScbFromFileObject( IrpContext, FileObject, Scb, TRUE );
SetFlag( FileObject->Flags, FO_CLEANUP_COMPLETE );
TruncateSize = (PLONGLONG)&Li0;
//
// Check if we're to modify the allocation size or file size.
//
} else {
if (FlagOn( Scb->ScbState, SCB_STATE_CHECK_ATTRIBUTE_SIZE )) {
//
// Acquire the parent now so we enforce our locking
// rules that the Mft Scb must be acquired after
// the normal file resources.
//
if (!RemoveLink && !FlagOn( Fcb->FcbState, FCB_STATE_SYSTEM_FILE )) {
NtfsPrepareForUpdateDuplicate( IrpContext,
Fcb,
&LcbForUpdate,
&ParentScb,
TRUE );
}
//
// For the non-resident streams we will write the file
// size to disk.
//
if (!FlagOn( Scb->ScbState, SCB_STATE_ATTRIBUTE_RESIDENT )) {
//
// Setting AdvanceOnly to FALSE guarantees we will not
// incorrectly advance the valid data size.
//
try {
NtfsWriteFileSizes( IrpContext,
Scb,
&Scb->Header.ValidDataLength.QuadPart,
FALSE,
TRUE,
TRUE );
} except( NtfsCleanupExceptionFilter( IrpContext, GetExceptionInformation(), &Status )) {
NOTHING;
}
//
// For resident streams we will write the correct size to
// the resident attribute.
//
} else {
//
// We need to lookup the attribute and change
// the attribute value. We can point to
// the attribute itself as the changing
// value.
//
NtfsInitializeAttributeContext( &AttrContext );
CleanupAttrContext = TRUE;
try {
NtfsLookupAttributeForScb( IrpContext, Scb, NULL, &AttrContext );
NtfsChangeAttributeValue( IrpContext,
Fcb,
Scb->Header.FileSize.LowPart,
NULL,
0,
TRUE,
TRUE,
FALSE,
FALSE,
&AttrContext );
} except( NtfsCleanupExceptionFilter( IrpContext, GetExceptionInformation(), &Status )) {
NOTHING;
}
//
// Verify the allocation size is now correct.
//
if (QuadAlign( Scb->Header.FileSize.LowPart ) != Scb->Header.AllocationSize.LowPart) {
Scb->Header.AllocationSize.LowPart = QuadAlign(Scb->Header.FileSize.LowPart);
}
}
//
// Update the size change to the Fcb.
//
NtfsUpdateScbFromFileObject( IrpContext, FileObject, Scb, TRUE );
}
if (Status == STATUS_SUCCESS) {
if (FlagOn( Scb->ScbState, SCB_STATE_TRUNCATE_ON_CLOSE ) &&
(Status == STATUS_SUCCESS)) {
//
// Acquire the parent now so we enforce our locking
// rules that the Mft Scb must be acquired after
// the normal file resources.
//
if (!RemoveLink && !FlagOn( Fcb->FcbState, FCB_STATE_SYSTEM_FILE)) {
NtfsPrepareForUpdateDuplicate( IrpContext,
Fcb,
&LcbForUpdate,
&ParentScb,
TRUE );
}
//
// We have two cases:
//
// Resident: We are looking for the case where the
// valid data length is less than the file size.
// In this case we shrink the attribute.
//
// NonResident: We are looking for unused clusters
// past the end of the file.
//
// We skip the following if we had any previous errors.
//
if (!FlagOn( Scb->ScbState, SCB_STATE_ATTRIBUTE_RESIDENT )) {
//
// We don't need to truncate if the file size is 0.
//
if (Scb->Header.AllocationSize.QuadPart != 0) {
VCN StartingCluster;
VCN EndingCluster;
LONGLONG AllocationSize;
//
// **** Do we need to give up the Vcb for this
// call.
//
StartingCluster = LlClustersFromBytes( Vcb, Scb->Header.FileSize.QuadPart );
EndingCluster = LlClustersFromBytes( Vcb, Scb->Header.AllocationSize.QuadPart );
AllocationSize = Scb->Header.AllocationSize.QuadPart;
//
// If there are clusters to delete, we do so now.
//
if (EndingCluster != StartingCluster) {
try {
NtfsDeleteAllocation( IrpContext,
FileObject,
Scb,
StartingCluster,
MAXLONGLONG,
TRUE,
TRUE );
} except( NtfsCleanupExceptionFilter( IrpContext, GetExceptionInformation(), &Status )) {
NOTHING;
}
}
LocalTruncateSize = Scb->Header.FileSize.QuadPart;
TruncateSize = &LocalTruncateSize;
}
//
// This is the resident case.
//
} else {
//
// Check if the file size length is less than
// the allocated size.
//
if (QuadAlign( Scb->Header.FileSize.LowPart ) < Scb->Header.AllocationSize.LowPart) {
//
// We need to lookup the attribute and change
// the attribute value. We can point to
// the attribute itself as the changing
// value.
//
if (CleanupAttrContext) {
NtfsCleanupAttributeContext( IrpContext, &AttrContext );
}
NtfsInitializeAttributeContext( &AttrContext );
CleanupAttrContext = TRUE;
try {
NtfsLookupAttributeForScb( IrpContext, Scb, NULL, &AttrContext );
NtfsChangeAttributeValue( IrpContext,
Fcb,
Scb->Header.FileSize.LowPart,
NULL,
0,
TRUE,
TRUE,
FALSE,
FALSE,
&AttrContext );
} except( NtfsCleanupExceptionFilter( IrpContext, GetExceptionInformation(), &Status )) {
NOTHING;
}
//
// Remember the smaller allocation size
//
Scb->Header.AllocationSize.LowPart = QuadAlign( Scb->Header.FileSize.LowPart );
Scb->TotalAllocated = Scb->Header.AllocationSize.QuadPart;
}
}
NtfsUpdateScbFromFileObject( IrpContext, FileObject, Scb, TRUE );
}
}
}
}
//
// If this was the last cached open, and there are open
// non-cached handles, attempt a flush and purge operation
// to avoid cache coherency overhead from these non-cached
// handles later. We ignore any I/O errors from the flush
// except for CANT_WAIT and LOG_FILE_FULL.
//
// Skip for the remove link case because we need to hold onto the parent
// and this is optional
//
if ((Scb->NonCachedCleanupCount != 0) &&
(Scb->CleanupCount == (Scb->NonCachedCleanupCount + 1)) &&
(Scb->CompressionUnit == 0) &&
(Scb->NonpagedScb->SegmentObject.ImageSectionObject == NULL) &&
!FlagOn( FileObject->Flags, FO_NO_INTERMEDIATE_BUFFERING ) &&
(Status == STATUS_SUCCESS) &&
MmCanFileBeTruncated( &Scb->NonpagedScb->SegmentObject, NULL ) &&
!FlagOn( Scb->Fcb->FcbState, FCB_STATE_SYSTEM_FILE ) &&
!RemoveLink) {
//
// FlushAndPurge may release the parent. Go ahead and explicitly
// release it. Otherwise we may overrelease it later before uninitializing
// the cache map. At the same time release the quota control if necc. via
// release shared resources
//
try {
NtfsCheckpointCurrentTransaction( IrpContext );
} except( NtfsCleanupExceptionFilter( IrpContext, GetExceptionInformation(), &Status )) {
NOTHING;
}
if (AcquiredParentScb) {
NtfsReleaseScb( IrpContext, ParentScb );
AcquiredParentScb = FALSE;
}
if (IrpContext->SharedScbSize > 0) {
NtfsReleaseSharedResources( IrpContext );
}
//
// Flush and purge the stream.
//
try {
NtfsFlushAndPurgeScb( IrpContext,
Scb,
NULL );
} except( NtfsCleanupExceptionFilter( IrpContext, GetExceptionInformation(), &Status )) {
NOTHING;
}
//
// Ignore any errors in this path.
//
IrpContext->ExceptionStatus = STATUS_SUCCESS;
}
if ((Fcb->CloseCount == 1) &&
AddToDelayQueue &&
(NtfsData.DelayedCloseCount <= NtfsMaxDelayedCloseCount) &&
(Status == STATUS_SUCCESS) &&
!FlagOn( Fcb->FcbState, FCB_STATE_PAGING_FILE )) {
NtfsAcquireFsrtlHeader( Scb );
SetFlag( Scb->ScbState, SCB_STATE_DELAY_CLOSE );
NtfsReleaseFsrtlHeader( Scb );
}
}
//
// If the Fcb is bad, we will truncate the cache to size zero.
//
} else {
//
// Now set the FO_CLEANUP_COMPLETE flag.
//
SetFlag( FileObject->Flags, FO_CLEANUP_COMPLETE );
TruncateSize = (PLONGLONG)&Li0;
}
break;
default :
NtfsBugCheck( TypeOfOpen, 0, 0 );
}
//
// If any of the Fcb Info flags are set we call the routine
// to update the duplicated information in the parent directories.
// We need to check here in case none of the flags are set but
// we want to update last access time.
//
if (Fcb->Info.LastAccessTime != Fcb->CurrentLastAccess) {
ASSERT( TypeOfOpen != UserVolumeOpen );
if (FlagOn( Fcb->FcbState, FCB_STATE_UPDATE_STD_INFO )) {
ASSERT( !FlagOn( Fcb->FcbState, FCB_STATE_FILE_DELETED ));
Fcb->Info.LastAccessTime = Fcb->CurrentLastAccess;
SetFlag( Fcb->InfoFlags, FCB_INFO_CHANGED_LAST_ACCESS );
} else if (!FlagOn( Fcb->FcbState, FCB_STATE_FILE_DELETED )) {
if (NtfsCheckLastAccess( IrpContext, Fcb )) {
SetFlag( Fcb->FcbState, FCB_STATE_UPDATE_STD_INFO );
SetFlag( Fcb->InfoFlags, FCB_INFO_CHANGED_LAST_ACCESS );
}
}
}
if (VolumeMounted && !VolumeMountedReadOnly) {
//
// We check if we have the standard information attribute.
// We can only update attributes on mounted volumes.
//
if (FlagOn( Fcb->FcbState, FCB_STATE_UPDATE_STD_INFO ) &&
(Status == STATUS_SUCCESS)) {
ASSERT( !FlagOn( Fcb->FcbState, FCB_STATE_FILE_DELETED ));
ASSERT( TypeOfOpen != UserVolumeOpen );
try {
NtfsUpdateStandardInformation( IrpContext, Fcb );
} except( NtfsCleanupExceptionFilter( IrpContext, GetExceptionInformation(), &Status )) {
NOTHING;
}
}
//
// Now update the duplicate information as well for volumes that are still mounted.
//
if ((FlagOn( Fcb->InfoFlags, FCB_INFO_DUPLICATE_FLAGS ) ||
((LcbForUpdate != NULL) &&
FlagOn( LcbForUpdate->InfoFlags, FCB_INFO_DUPLICATE_FLAGS ))) &&
(Status == STATUS_SUCCESS) &&
!RemoveLink &&
(!FlagOn( Fcb->FcbState, FCB_STATE_SYSTEM_FILE))) {
ASSERT( TypeOfOpen != UserVolumeOpen );
ASSERT( !FlagOn( Fcb->FcbState, FCB_STATE_FILE_DELETED ));
NtfsPrepareForUpdateDuplicate( IrpContext, Fcb, &LcbForUpdate, &ParentScb, TRUE );
//
// Now update the duplicate info.
//
try {
NtfsUpdateDuplicateInfo( IrpContext, Fcb, LcbForUpdate, ParentScb );
} except( NtfsCleanupExceptionFilter( IrpContext, GetExceptionInformation(), &Status )) {
NOTHING;
}
UpdateDuplicateInfo = TRUE;
}
//
// If we have modified the Info structure or security, we report this
// to the dir-notify package (except for OpenById cases).
//
if (!OpenById && (Status == STATUS_SUCCESS)) {
ULONG FilterMatch;
//
// Check whether we need to report on file changes.
//
if ((Vcb->NotifyCount != 0) &&
(UpdateDuplicateInfo || FlagOn( Fcb->InfoFlags, FCB_INFO_MODIFIED_SECURITY ))) {
ASSERT( TypeOfOpen != UserVolumeOpen );
//
// We map the Fcb info flags into the dir notify flags.
//
FilterMatch = NtfsBuildDirNotifyFilter( IrpContext,
(Fcb->InfoFlags |
(LcbForUpdate ? LcbForUpdate->InfoFlags : 0) ));
//
// If the filter match is non-zero, that means we also need to do a
// dir notify call.
//
if (FilterMatch != 0) {
ASSERT( TypeOfOpen != UserVolumeOpen );
ASSERT( !FlagOn( Fcb->FcbState, FCB_STATE_FILE_DELETED ));
NtfsReportDirNotify( IrpContext,
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,
ParentFcb );
}
}
ClearFlag( Fcb->InfoFlags, FCB_INFO_MODIFIED_SECURITY );
//
// If this is a named stream with changes then report them as well.
//
if ((Scb->AttributeName.Length != 0) &&
NtfsIsTypeCodeUserData( Scb->AttributeTypeCode )) {
if ((Vcb->NotifyCount != 0) &&
FlagOn( Scb->ScbState,
SCB_STATE_NOTIFY_REMOVE_STREAM |
SCB_STATE_NOTIFY_RESIZE_STREAM |
SCB_STATE_NOTIFY_MODIFY_STREAM )) {
ULONG Action;
FilterMatch = 0;
//
// Start by checking for a delete.
//
if (FlagOn( Scb->ScbState, SCB_STATE_NOTIFY_REMOVE_STREAM )) {
FilterMatch = FILE_NOTIFY_CHANGE_STREAM_NAME;
Action = FILE_ACTION_REMOVED_STREAM;
} else {
//
// Check if the file size changed.
//
if (FlagOn( Scb->ScbState, SCB_STATE_NOTIFY_RESIZE_STREAM )) {
FilterMatch = FILE_NOTIFY_CHANGE_STREAM_SIZE;
}
//
// Now check if the stream data was modified.
//
if (FlagOn( Scb->ScbState, SCB_STATE_NOTIFY_MODIFY_STREAM )) {
SetFlag( FilterMatch, FILE_NOTIFY_CHANGE_STREAM_WRITE );
}
Action = FILE_ACTION_MODIFIED_STREAM;
}
NtfsReportDirNotify( IrpContext,
Vcb,
&Ccb->FullFileName,
Ccb->LastFileNameOffset,
&Scb->AttributeName,
((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,
Action,
ParentFcb );
}
ClearFlag( Scb->ScbState,
SCB_STATE_NOTIFY_ADD_STREAM |
SCB_STATE_NOTIFY_REMOVE_STREAM |
SCB_STATE_NOTIFY_RESIZE_STREAM |
SCB_STATE_NOTIFY_MODIFY_STREAM );
}
}
if (UpdateDuplicateInfo) {
NtfsUpdateLcbDuplicateInfo( Fcb, LcbForUpdate );
Fcb->InfoFlags = 0;
}
}
//
// Always clear the update standard information flag.
//
ClearFlag( Fcb->FcbState, FCB_STATE_UPDATE_STD_INFO );
//
// Let's give up the parent Fcb if we have acquired it. This will
// prevent deadlocks in any uninitialize code below.
//
if (AcquiredParentScb) {
NtfsReleaseScb( IrpContext, ParentScb );
AcquiredParentScb = FALSE;
}
//
// Uninitialize the cache map if this file has been cached or we are
// trying to delete.
//
if ((FileObject->PrivateCacheMap != NULL) || (TruncateSize != NULL)) {
CcUninitializeCacheMap( FileObject, (PLARGE_INTEGER)TruncateSize, NULL );
}
//
// Check that the non-cached handle count is consistent.
//
ASSERT( !FlagOn( FileObject->Flags, FO_NO_INTERMEDIATE_BUFFERING ) ||
(Scb->NonCachedCleanupCount != 0 ));
if (CleanupAttrContext) {
NtfsCleanupAttributeContext( IrpContext, &AttrContext );
CleanupAttrContext = FALSE;
}
//
// On final cleanup, post the close to the Usn Journal, if other changes have been
// posted.
//
if ((Fcb->CleanupCount == 1) &&
(Fcb->FcbUsnRecord != NULL) &&
((Fcb->FcbUsnRecord->UsnRecord.Reason != 0) ||
((IrpContext->Usn.CurrentUsnFcb != NULL) && (IrpContext->Usn.NewReasons != 0)))) {
PSCB TempScb;
//
// Leave if there are any streams with user-mapped files.
//
TempScb = (PSCB)CONTAINING_RECORD( Fcb->ScbQueue.Flink,
SCB,
FcbLinks );
while (&TempScb->FcbLinks != &Fcb->ScbQueue) {
if (FlagOn(TempScb->Header.Flags, FSRTL_FLAG_USER_MAPPED_FILE)) {
goto NoPost;
}
TempScb = (PSCB)CONTAINING_RECORD( TempScb->FcbLinks.Flink,
SCB,
FcbLinks );
}
//
// Now try to actually post the change.
//
NtfsPostUsnChange( IrpContext, Fcb, USN_REASON_CLOSE );
//
// Escape here if we are not posting the close due to a user-mapped file.
//
NoPost: NOTHING;
}
//
// Now, if anything at all is posted to the Usn Journal, we must write it now
// so that we do not get a log file full later.
//
ASSERT( IrpContext->Usn.NextUsnFcb == NULL );
if ((IrpContext->Usn.CurrentUsnFcb != NULL) &&
(Status == STATUS_SUCCESS)) {
//
// Now write the journal, checkpoint the transaction, and free the UsnJournal to
// reduce contention.
//
try {
NtfsWriteUsnJournalChanges( IrpContext );
NtfsCheckpointCurrentTransaction( IrpContext );
} except( NtfsCleanupExceptionFilter( IrpContext, GetExceptionInformation(), &Status )) {
NOTHING;
}
}
//
// Make sure the TRUNCATE and ATTRIBUTE_SIZE flags in the Scb are cleared.
//
if (FlagOn( Scb->ScbState,
SCB_STATE_CHECK_ATTRIBUTE_SIZE | SCB_STATE_TRUNCATE_ON_CLOSE ) &&
(Scb->CleanupCount == 1)) {
NtfsAcquireFsrtlHeader( Scb );
ClearFlag( Scb->ScbState,
SCB_STATE_CHECK_ATTRIBUTE_SIZE | SCB_STATE_TRUNCATE_ON_CLOSE );
NtfsReleaseFsrtlHeader( Scb );
}
//
// Now decrement the cleanup counts.
//
// NOTE - DO NOT ADD CODE AFTER THIS POINT THAT CAN CAUSE A RETRY.
//
NtfsDecrementCleanupCounts( Scb,
LcbForCounts,
BooleanFlagOn( FileObject->Flags, FO_NO_INTERMEDIATE_BUFFERING ));
#if (defined(DBG) || defined(NTFS_FREE_ASSERTS))
DecrementedCleanupCount = TRUE;
#endif
//
// Fixup the in memory lcb if we removed a link - this will not roll back
// so it must done at a point where we won't abort
//
if (RemoveLink) {
//
// Remove all remaining prefixes on this link.
//
ASSERT( NtfsIsExclusiveScb( Lcb->Scb ) );
NtfsRemovePrefix( Lcb );
//
// Remove any hash table entries for this Lcb.
//
NtfsRemoveHashEntriesForLcb( Lcb );
//
// Mark the links as being removed.
//
SetFlag( Lcb->LcbState, LCB_STATE_LINK_IS_GONE );
//
// We depend on ThisLcb being unused between the removelink
// section and here. It should point to the other part of a pair
// of 8.3 links if there is one
//
#if DBG || defined( NTFS_FREE_ASSERTS )
{
PLIST_ENTRY Links;
PLCB LcbPair = NULL;
if (FlagOn( Lcb->FileNameAttr->Flags, FILE_NAME_DOS | FILE_NAME_NTFS )) {
for (Links = Fcb->LcbQueue.Flink;
Links != &Fcb->LcbQueue;
Links = Links->Flink) {
LcbPair = CONTAINING_RECORD( Links, LCB, FcbLinks );
//
// If this has a flag set and is not the Lcb
// for this cleanup, then we check if there
// are no Ccb's left for this.
//
if (FlagOn( LcbPair->FileNameAttr->Flags, FILE_NAME_DOS | FILE_NAME_NTFS )
&&
(LcbPair != Lcb)) {
break;
}
LcbPair = NULL;
}
}
ASSERT( LcbPair == ThisLcb );
}
#endif
if (ThisLcb != NULL) {
//
// Remove all remaining prefixes on this link.
//
ASSERT( NtfsIsExclusiveScb( ThisLcb->Scb ) );
NtfsRemovePrefix( ThisLcb );
//
// Remove any hash table entries for this Lcb.
//
NtfsRemoveHashEntriesForLcb( ThisLcb );
SetFlag( ThisLcb->LcbState, LCB_STATE_LINK_IS_GONE );
ThisLcb->InfoFlags = 0;
}
//
// Since the link is gone we don't want to update the
// duplicate information for this link.
//
Lcb->InfoFlags = 0;
}
//
// We remove the share access from the Scb.
//
IoRemoveShareAccess( FileObject, &Scb->ShareAccess );
//
// Modify the delete counts in the Fcb.
//
if (FlagOn( Ccb->Flags, CCB_FLAG_DELETE_FILE )) {
Fcb->FcbDeleteFile -= 1;
ClearFlag( Ccb->Flags, CCB_FLAG_DELETE_FILE );
}
if (FlagOn( Ccb->Flags, CCB_FLAG_DENY_DELETE )) {
Fcb->FcbDenyDelete -= 1;
ClearFlag( Ccb->Flags, CCB_FLAG_DENY_DELETE );
}
if (FlagOn( Ccb->Flags, CCB_FLAG_DENY_DEFRAG)) {
ClearFlag( Ccb->Flags, CCB_FLAG_DENY_DEFRAG );
ClearFlag( Scb->ScbPersist, SCB_PERSIST_DENY_DEFRAG );
}
//
// Since this request has completed we can adjust the total link count
// in the Fcb.
//
Fcb->TotalLinks -= TotalLinkAdj;
//
// Release the quota control block. This does not have to be done
// here however, it allows us to free up the quota control block
// before the fcb is removed from the table. This keeps the assert
// about quota table empty from triggering in
// NtfsClearAndVerifyQuotaIndex.
//
if (NtfsPerformQuotaOperation(Fcb) &&
FlagOn( Fcb->FcbState, FCB_STATE_FILE_DELETED )) {
NtfsDereferenceQuotaControlBlock( Vcb,
&Fcb->QuotaControl );
}
//
// If we hit some failure in modifying the disk then make sure to roll back
// all of the changes.
//
if (Status != STATUS_SUCCESS) {
NtfsRaiseStatus( IrpContext, STATUS_SUCCESS, NULL, NULL );
}
} finally {
DebugUnwind( NtfsCommonCleanup );
//
// Remove this fcb from the Fcb table if neccessary. We delay this for
// synchronization with usn delete worker. By finishing the delete now we're
// guarranteed our usn work occured safely
//
if (DeleteFromFcbTable && FlagOn( Fcb->FcbState, FCB_STATE_IN_FCB_TABLE )) {
NtfsAcquireFcbTable( IrpContext, Vcb );
NtfsDeleteFcbTableEntry( Fcb->Vcb, Fcb->FileReference );
NtfsReleaseFcbTable( IrpContext, Vcb );
ClearFlag( Fcb->FcbState, FCB_STATE_IN_FCB_TABLE );
}
//
// We clear the file object pointer in the Ccb.
// This prevents us from trying to access this in a
// rename operation.
//
SetFlag( Ccb->Flags, CCB_FLAG_CLEANUP );
//
// Release any resources held.
//
if (AcquiredObjectID) {
NtfsReleaseScb( IrpContext, Vcb->ObjectIdTableScb );
}
NtfsReleaseVcb( IrpContext, Vcb );
if (AcquiredCheckpoint) {
NtfsReleaseCheckpointSynchronization( IrpContext, Vcb );
AcquiredCheckpoint = FALSE;
}
if (CleanupAttrContext) {
NtfsCleanupAttributeContext( IrpContext, &AttrContext );
}
if (NamePair.Long.Buffer != NamePair.LongBuffer) {
NtfsFreePool(NamePair.Long.Buffer);
}
if (DecrementScb) {
InterlockedDecrement( &ImageScb->CloseCount );
}
//
// If we just cleaned up a volume handle and in so doing unlocked the
// volume, notify anyone who might be interested. We can't do this
// until we've released all our resources, since there's no telling
// what services might do when they get notified, and we don't want
// to introduce potential deadlocks.
//
if (UnlockedVolume) {
ASSERT( !AbnormalTermination() );
ASSERT( IrpContext->TransactionId == 0 );
NtfsReleaseScb( IrpContext, Scb );
FsRtlNotifyVolumeEvent( FileObject, FSRTL_VOLUME_UNLOCK );
}
ASSERT( !AbnormalTermination() ||
!DeleteFile ||
FlagOn( Fcb->FcbState, FCB_STATE_IN_FCB_TABLE ) ||
(IrpContext->TransactionId == 0) );
//
// After a file is deleted and committed no more usn reasons should be left
// whether or not the journal is active
//
ASSERT( AbnormalTermination() ||
!FlagOn( Fcb->FcbState, FCB_STATE_FILE_DELETED ) ||
(Fcb->FcbUsnRecord == NULL) ||
(Fcb->FcbUsnRecord->UsnRecord.Reason == 0) );
//
// And return to our caller
//
DebugTrace( -1, Dbg, ("NtfsCommonCleanup -> %08lx\n", Status) );
}
#if (defined(DBG) || defined(NTFS_FREE_ASSERTS))
ASSERT( DecrementedCleanupCount );
#endif
NtfsCompleteRequest( IrpContext, Irp, Status );
return Status;
}
VOID
NtfsTrimNormalizedNames (
IN PIRP_CONTEXT IrpContext,
IN PFCB Fcb,
IN PSCB ParentScb
)
/*++
Routine Description:
Walk up the FCB/SCB chain removing all normalized names longer than
the threshold size in inactive directories
Try to remove parent dir normalized name if its long and if no handles active (only this 1 left)
and no lcbs are active - all notifies farther down in function (NtfsCommonCleanup)
use parent Scb's normalized name. If we don't remove it here
this always goes away during a close
Arguments:
IrpContext --
Fcb -- The fcb of the starting node should already be acquired
ParentScb - The scb of the parent of the current node
Return Value:
none
--*/
{
BOOLEAN DirectoryActive = FALSE;
PFCB CurrentFcb;
PSCB CurrentParentScb;
PLCB ChildLcb;
PLCB ParentLcb;
PLIST_ENTRY Links;
PAGED_CODE()
//
// We may be occuring during a transaction so be careful to not acquire resources
// for the life of the transaction while traversing the tree
//
CurrentFcb = Fcb;
CurrentParentScb = ParentScb;
NtfsAcquireResourceExclusive( IrpContext, CurrentParentScb, TRUE );
while ((CurrentParentScb->ScbType.Index.NormalizedName.MaximumLength > LONGNAME_THRESHOLD) &&
(CurrentParentScb->CleanupCount == 0)) {
ASSERT( (CurrentParentScb->Header.NodeTypeCode == NTFS_NTC_SCB_INDEX) ||
(CurrentParentScb->Header.NodeTypeCode == NTFS_NTC_SCB_ROOT_INDEX) );
//
// Determine that directory has no active links or handles
//
for (Links = CurrentParentScb->ScbType.Index.LcbQueue.Flink;
Links != &(CurrentParentScb->ScbType.Index.LcbQueue);
Links = Links->Flink) {
ChildLcb = CONTAINING_RECORD( Links, LCB, ScbLinks );
//
// We know the Fcb we were called with has a single cleanup count left.
//
if ((ChildLcb->CleanupCount != 0) &&
(ChildLcb->Fcb != CurrentFcb)) {
DirectoryActive = TRUE;
break;
}
}
if (!DirectoryActive) {
//
// Now acquire and free name in SCB
//
NtfsDeleteNormalizedName( CurrentParentScb );
//
// Move up to next level
//
if (CurrentFcb != Fcb) {
NtfsReleaseResource( IrpContext, CurrentFcb );
}
CurrentFcb = CurrentParentScb->Fcb;
if (CurrentFcb->CleanupCount != 0) {
//
// Setting equal to FCB just means don't release it when we exit below
//
CurrentFcb = Fcb;
break;
}
if (!(IsListEmpty( &(CurrentFcb->LcbQueue) ))) {
ParentLcb = CONTAINING_RECORD( CurrentFcb->LcbQueue.Flink,
LCB,
FcbLinks );
CurrentParentScb = ParentLcb->Scb;
if (CurrentParentScb != NULL) {
NtfsAcquireResourceExclusive( IrpContext, CurrentParentScb, TRUE );
} else {
break;
}
} else {
CurrentParentScb = NULL;
break;
}
} else {
break;
} // endif directory active
} // endwhile longname in currentparentscb
//
// Release last node if it isn't the starting one
//
if (CurrentFcb != Fcb) {
ASSERT( CurrentFcb != NULL );
NtfsReleaseResource( IrpContext, CurrentFcb );
}
if (CurrentParentScb != NULL) {
NtfsReleaseResource( IrpContext, CurrentParentScb );
}
return;
} // NtfsTrimNormalizedNames
//
// Local support routine
//
LONG
NtfsCleanupExceptionFilter (
IN PIRP_CONTEXT IrpContext,
IN PEXCEPTION_POINTERS ExceptionPointer,
OUT PNTSTATUS Status
)
/*++
Routine Description:
Exception filter for errors during cleanup. We want to raise if this is
a retryable condition or fatal error, plow on as best we can if not.
Arguments:
IrpContext - IrpContext
ExceptionPointer - Pointer to the exception context.
Status - Address to store the error status.
Return Value:
Exception status - EXCEPTION_CONTINUE_SEARCH if we want to raise to another handler,
EXCEPTION_EXECUTE_HANDLER if we plan to proceed on.
--*/
{
*Status = ExceptionPointer->ExceptionRecord->ExceptionCode;
NtfsCorruptionBreakPointTest( IrpContext, *Status );
if ((*Status == STATUS_LOG_FILE_FULL) ||
(*Status == STATUS_CANT_WAIT) ||
!FsRtlIsNtstatusExpected( *Status )) {
return EXCEPTION_CONTINUE_SEARCH;
}
NtfsMinimumExceptionProcessing( IrpContext );
#ifdef BRIANDBG
#ifndef LFS_CLUSTER_CHECK
//
// Some errors are acceptable in this path.
//
if (*Status == STATUS_DISK_FULL) {
NtfsCleanupDiskFull += 1;
} else if (*Status == STATUS_INSUFFICIENT_RESOURCES) {
NtfsCleanupNoPool += 1;
} else {
//
// Cluster systems can hit inpage errors here because of DEVICE_OFFLINE
// for log I/O.
//
ASSERT( FALSE );
}
#endif
#endif
return EXCEPTION_EXECUTE_HANDLER;
}
#ifdef BRIANDBG
LONG
NtfsFsdCleanupExceptionFilter (
IN PIRP_CONTEXT IrpContext OPTIONAL,
IN PEXCEPTION_POINTERS ExceptionPointer
)
{
NTSTATUS ExceptionCode = ExceptionPointer->ExceptionRecord->ExceptionCode;
ASSERT( NT_SUCCESS( ExceptionCode ) ||
(ExceptionCode == STATUS_CANT_WAIT) ||
(ExceptionCode == STATUS_LOG_FILE_FULL) );
return NtfsExceptionFilter( IrpContext, ExceptionPointer );
}
#endif