mirror of https://github.com/tongzx/nt5src
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.
3049 lines
113 KiB
3049 lines
113 KiB
/*++
|
|
|
|
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;
|
|
|
|
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;
|
|
|
|
ULONG FcbStateClearFlags = 0;
|
|
|
|
BOOLEAN DecrementScb = FALSE;
|
|
PSCB ImageScb;
|
|
|
|
#ifdef BRIANDBG
|
|
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;
|
|
|
|
//
|
|
// Special case the unopened file object and stream files.
|
|
//
|
|
|
|
if ((TypeOfOpen == UnopenedFileObject) ||
|
|
(TypeOfOpen == StreamFileOpen)) {
|
|
|
|
//
|
|
// Just set the FO_CLEANUP_COMPLETE flag, and get outsky...
|
|
//
|
|
|
|
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 );
|
|
|
|
//
|
|
// Commit the delete - this has the nice effect of writing out all usn journal records for the
|
|
// delete after this we can safely remove the in memory structures (esp. the fcbtable)
|
|
// and not worry about retrying the request due to a logfilefull
|
|
//
|
|
|
|
NtfsCheckpointCurrentTransaction( IrpContext );
|
|
|
|
TotalLinkAdj += 1;
|
|
|
|
//
|
|
// Remove all tunneling entries for this directory
|
|
//
|
|
|
|
FsRtlDeleteKeyFromTunnelCache( &Vcb->Tunnel,
|
|
*(PULONGLONG) &Fcb->FileReference );
|
|
|
|
if (ParentFcb != NULL) {
|
|
|
|
NtfsUpdateFcb( ParentFcb,
|
|
(FCB_INFO_CHANGED_LAST_CHANGE |
|
|
FCB_INFO_CHANGED_LAST_MOD |
|
|
FCB_INFO_UPDATE_LAST_ACCESS) );
|
|
}
|
|
|
|
} 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 );
|
|
}
|
|
|
|
SetFlag( Fcb->FcbState, FCB_STATE_FILE_DELETED );
|
|
SetFlag( FcbStateClearFlags, FCB_STATE_FILE_DELETED );
|
|
DeleteFromFcbTable = TRUE;
|
|
|
|
//
|
|
// We need to mark all of the links on the file as gone.
|
|
// If there is a parent Scb then it will be the parent
|
|
// for all of the links.
|
|
//
|
|
|
|
for (Links = Fcb->LcbQueue.Flink;
|
|
Links != &Fcb->LcbQueue;
|
|
Links = Links->Flink) {
|
|
|
|
ThisLcb = CONTAINING_RECORD( Links, LCB, FcbLinks );
|
|
|
|
//
|
|
// Remove all remaining prefixes on this link.
|
|
// Make sure the resource is acquired.
|
|
//
|
|
|
|
if (!AcquiredParentScb) {
|
|
|
|
NtfsAcquireExclusiveScb( IrpContext, ParentScb );
|
|
AcquiredParentScb = TRUE;
|
|
}
|
|
|
|
NtfsRemovePrefix( ThisLcb );
|
|
|
|
//
|
|
// Remove any hash table entries for this Lcb.
|
|
//
|
|
|
|
NtfsRemoveHashEntriesForLcb( ThisLcb );
|
|
|
|
SetFlag( ThisLcb->LcbState, LCB_STATE_LINK_IS_GONE );
|
|
|
|
//
|
|
// We don't need to report any changes on this link.
|
|
//
|
|
|
|
ThisLcb->InfoFlags = 0;
|
|
}
|
|
|
|
//
|
|
// We need to mark all of the Scbs as gone.
|
|
//
|
|
|
|
for (Links = Fcb->ScbQueue.Flink;
|
|
Links != &Fcb->ScbQueue;
|
|
Links = Links->Flink) {
|
|
|
|
ThisScb = CONTAINING_RECORD( Links, SCB, FcbLinks );
|
|
|
|
ClearFlag( ThisScb->ScbState,
|
|
SCB_STATE_NOTIFY_ADD_STREAM |
|
|
SCB_STATE_NOTIFY_REMOVE_STREAM |
|
|
SCB_STATE_NOTIFY_RESIZE_STREAM |
|
|
SCB_STATE_NOTIFY_MODIFY_STREAM );
|
|
|
|
if (!FlagOn( ThisScb->ScbState, SCB_STATE_ATTRIBUTE_DELETED )) {
|
|
|
|
NtfsSnapshotScb( IrpContext, ThisScb );
|
|
|
|
ThisScb->ValidDataToDisk =
|
|
ThisScb->Header.AllocationSize.QuadPart =
|
|
ThisScb->Header.FileSize.QuadPart =
|
|
ThisScb->Header.ValidDataLength.QuadPart = 0;
|
|
|
|
SetFlag( ThisScb->ScbState, SCB_STATE_ATTRIBUTE_DELETED );
|
|
}
|
|
}
|
|
|
|
//
|
|
// We certainly don't need to any on disk update for this
|
|
// file now.
|
|
//
|
|
|
|
Fcb->InfoFlags = 0;
|
|
ClearFlag( Fcb->FcbState, FCB_STATE_UPDATE_STD_INFO );
|
|
|
|
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 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 );
|
|
|
|
//
|
|
// Commit the delete - this has the nice effect of writing out all usn journal records for the
|
|
// delete after this we can safely remove the in memory structures (esp. the fcbtable)
|
|
// and not worry about retrying the request due to a logfilefull
|
|
//
|
|
|
|
NtfsCheckpointCurrentTransaction( IrpContext );
|
|
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);
|
|
}
|
|
|
|
if (ParentFcb != NULL) {
|
|
|
|
NtfsUpdateFcb( ParentFcb,
|
|
(FCB_INFO_CHANGED_LAST_CHANGE |
|
|
FCB_INFO_CHANGED_LAST_MOD |
|
|
FCB_INFO_UPDATE_LAST_ACCESS) );
|
|
}
|
|
|
|
} 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 );
|
|
}
|
|
|
|
SetFlag( Fcb->FcbState, FCB_STATE_FILE_DELETED );
|
|
SetFlag( FcbStateClearFlags, FCB_STATE_FILE_DELETED );
|
|
DeleteFromFcbTable = TRUE;
|
|
|
|
//
|
|
// We need to mark all of the links on the file as gone.
|
|
//
|
|
|
|
for (Links = Fcb->LcbQueue.Flink;
|
|
Links != &Fcb->LcbQueue;
|
|
Links = Links->Flink) {
|
|
|
|
ThisLcb = CONTAINING_RECORD( Links, LCB, FcbLinks );
|
|
|
|
if (ThisLcb->Scb == ParentScb) {
|
|
|
|
//
|
|
// Remove all remaining prefixes on this link.
|
|
// Make sure the resource is acquired.
|
|
//
|
|
|
|
if (!AcquiredParentScb) {
|
|
|
|
NtfsAcquireExclusiveScb( IrpContext, ParentScb );
|
|
AcquiredParentScb = TRUE;
|
|
}
|
|
|
|
NtfsRemovePrefix( ThisLcb );
|
|
|
|
//
|
|
// Remove any hash table entries for this Lcb.
|
|
//
|
|
|
|
NtfsRemoveHashEntriesForLcb( ThisLcb );
|
|
|
|
SetFlag( ThisLcb->LcbState, LCB_STATE_LINK_IS_GONE );
|
|
|
|
//
|
|
// We don't need to report any changes on this link.
|
|
//
|
|
|
|
ThisLcb->InfoFlags = 0;
|
|
}
|
|
}
|
|
|
|
//
|
|
// We need to mark all of the Scbs as gone.
|
|
//
|
|
|
|
for (Links = Fcb->ScbQueue.Flink;
|
|
Links != &Fcb->ScbQueue;
|
|
Links = Links->Flink) {
|
|
|
|
ThisScb = CONTAINING_RECORD( Links, SCB, FcbLinks );
|
|
|
|
ClearFlag( ThisScb->ScbState,
|
|
SCB_STATE_NOTIFY_ADD_STREAM |
|
|
SCB_STATE_NOTIFY_REMOVE_STREAM |
|
|
SCB_STATE_NOTIFY_RESIZE_STREAM |
|
|
SCB_STATE_NOTIFY_MODIFY_STREAM );
|
|
|
|
if (!FlagOn( ThisScb->ScbState, SCB_STATE_ATTRIBUTE_DELETED )) {
|
|
|
|
NtfsSnapshotScb( IrpContext, ThisScb );
|
|
|
|
ThisScb->ValidDataToDisk =
|
|
ThisScb->Header.AllocationSize.QuadPart =
|
|
ThisScb->Header.FileSize.QuadPart =
|
|
ThisScb->Header.ValidDataLength.QuadPart = 0;
|
|
|
|
SetFlag( ThisScb->ScbState, SCB_STATE_ATTRIBUTE_DELETED );
|
|
}
|
|
}
|
|
|
|
//
|
|
// We certainly don't need to any on disk update for this
|
|
// file now.
|
|
//
|
|
|
|
Fcb->InfoFlags = 0;
|
|
ClearFlag( Fcb->FcbState, FCB_STATE_UPDATE_STD_INFO );
|
|
|
|
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 );
|
|
}
|
|
|
|
//
|
|
// 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 );
|
|
|
|
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;
|
|
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 );
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// 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 ) && (LcbForUpdate != NULL) && (!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.
|
|
//
|
|
|
|
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 (!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 (!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.
|
|
//
|
|
|
|
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 )) {
|
|
|
|
//
|
|
// 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 );
|
|
FcbStateClearFlags = 0;
|
|
|
|
} 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) &&
|
|
(!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 );
|
|
FcbStateClearFlags = 0;
|
|
|
|
} 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 ));
|
|
|
|
#ifdef BRIANDBG
|
|
DecrementedCleanupCount = TRUE;
|
|
#endif
|
|
//
|
|
// 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 );
|
|
}
|
|
|
|
FcbStateClearFlags = 0;
|
|
|
|
} finally {
|
|
|
|
DebugUnwind( NtfsCommonCleanup );
|
|
|
|
//
|
|
// Clear any FcbState flags we want to unwind.
|
|
//
|
|
|
|
ClearFlag( Fcb->FcbState, FcbStateClearFlags );
|
|
|
|
//
|
|
// 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) {
|
|
|
|
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) );
|
|
}
|
|
|
|
#ifdef BRIANDBG
|
|
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;
|
|
|
|
//
|
|
// For now break if we catch corruption errors on both free and checked
|
|
// TODO: Remove this before we ship
|
|
//
|
|
|
|
if (NtfsBreakOnCorrupt &&
|
|
((*Status == STATUS_FILE_CORRUPT_ERROR) ||
|
|
(*Status == STATUS_DISK_CORRUPT_ERROR))) {
|
|
|
|
if (*KdDebuggerEnabled) {
|
|
DbgPrint("*******************************************\n");
|
|
DbgPrint("NTFS detected corruption on your volume\n");
|
|
DbgPrint("IrpContext=0x%08x, VCB=0x%08x\n",IrpContext,IrpContext->Vcb);
|
|
DbgPrint("Send email to NTFSDEV\n");
|
|
DbgPrint("*******************************************\n");
|
|
DbgBreakPoint();
|
|
}
|
|
}
|
|
|
|
// ASSERT( *Status != STATUS_FILE_CORRUPT_ERROR );
|
|
|
|
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
|