/*++ 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) VOID NtfsContractQuotaToFileSize ( IN PIRP_CONTEXT IrpContext, IN PSCB Scb ); #ifdef ALLOC_PRAGMA #pragma alloc_text(PAGE, NtfsCommonCleanup) #pragma alloc_text(PAGE, NtfsFsdCleanup) #pragma alloc_text(PAGE, NtfsContractQuotaToFileSize) #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; 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 = NtfsSetTopLevelIrp( &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) { IrpContext = NtfsCreateIrpContext( Irp, CanFsdWait( Irp ) ); 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; } except(NtfsExceptionFilter( IrpContext, GetExceptionInformation() )) { // // We had some trouble trying to perform the requested // operation, so we'll abort the I/O request with // the error status that we get back from the // execption code // Status = NtfsProcessException( IrpContext, Irp, GetExceptionCode() ); } } while (Status == STATUS_CANT_WAIT || Status == STATUS_LOG_FILE_FULL); if (ThreadTopLevelContext == &TopLevelContext) { NtfsRestoreTopLevelIrp( ThreadTopLevelContext ); } FsRtlExitFileSystem(); // // And return to our caller // DebugTrace( -1, Dbg, ("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 AcquiredScb = FALSE; BOOLEAN CleanupAttrContext = FALSE; BOOLEAN UpdateDuplicateInfo = FALSE; BOOLEAN AddToDelayQueue = TRUE; USHORT TotalLinkAdj = 0; PLIST_ENTRY Links; NAME_PAIR NamePair; ASSERT_IRP_CONTEXT( IrpContext ); ASSERT_IRP( Irp ); 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 ); NtfsCompleteRequest( &IrpContext, &Irp, Status ); DebugTrace( -1, Dbg, ("NtfsCommonCleanup -> %08lx\n", Status) ); return Status; } // // Let's make sure we can wait. // if (!FlagOn(IrpContext->Flags, IRP_CONTEXT_FLAG_WAIT)) { Status = NtfsPostRequest( IrpContext, Irp ); DebugTrace( -1, Dbg, ("NtfsCommonCleanup -> %08lx\n", Status) ); return Status; } // // Remember if this is an open by file Id open. // OpenById = BooleanFlagOn( Ccb->Flags, CCB_FLAG_OPEN_BY_FILE_ID ); // // Acquire exclusive access to the Vcb and enqueue the irp if we didn't // get access // if (TypeOfOpen == UserVolumeOpen) { SetFlag( IrpContext->Flags, IRP_CONTEXT_FLAG_ACQUIRE_VCB_EX ); } if (FlagOn( IrpContext->Flags, IRP_CONTEXT_FLAG_ACQUIRE_VCB_EX )) { NtfsAcquireExclusiveVcb( IrpContext, Vcb, TRUE ); } else { NtfsAcquireSharedVcb( IrpContext, Vcb, TRUE ); } // // Use a try-finally to facilitate cleanup. // try { LcbForUpdate = LcbForCounts = Lcb = Ccb->Lcb; if (Lcb != NULL) { ParentScb = Lcb->Scb; if (ParentScb != NULL) { ParentFcb = ParentScb->Fcb; } } // // 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 ); } } AcquiredScb = TRUE; // // 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))) { 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 )) { FsRtlNotifyFullChangeDirectory( Vcb->NotifySync, &Vcb->DirNotifyList, FileObject->FsContext, NULL, FALSE, FALSE, 0, NULL, NULL, NULL ); } } // // Otherwise we are simply removing the attribute. // } else { SetFlag( Scb->ScbState, SCB_STATE_DELETE_ON_CLOSE ); } // // 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->CleanupCount == 1) && (Fcb->LinkCount == 0)) { 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->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 ((Scb->CleanupCount == 1) && FlagOn( Scb->ScbState, SCB_STATE_DELETE_ON_CLOSE )) { 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 ); } } // // Let's do a sanity check. // ASSERT( Fcb->CleanupCount != 0 ); ASSERT( Scb->CleanupCount != 0 ); // // If the cleanup count on the file will go to zero and there is // a large security descriptor and we haven't exceeded the security // creation count for this Fcb then dereference and possibly deallocate // the security descriptor for the Fcb. This is to prevent us from // holding onto pool while waiting for closes to come in. // if ((Fcb->CleanupCount == 1) && (Fcb->SharedSecurity != NULL) && (Fcb->CreateSecurityCount < FCB_CREATE_SECURITY_COUNT) && (GetSharedSecurityLength( Fcb->SharedSecurity ) > FCB_LARGE_ACL_SIZE)) { NtfsAcquireFcbSecurity( Fcb->Vcb ); NtfsDereferenceSharedSecurity( Fcb ); NtfsReleaseFcbSecurity( Fcb->Vcb ); } // // 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 ) && ((Vcb->FileObjectWithVcbLocked == FileObject) || ((ULONG)Vcb->FileObjectWithVcbLocked == ((ULONG)FileObject)+1))) { if ((ULONG)Vcb->FileObjectWithVcbLocked == ((ULONG)FileObject)+1) { NtfsPerformDismountOnVcb( IrpContext, Vcb, TRUE ); // // Purge the volume for the autocheck case. // } else if (FlagOn( FileObject->Flags, FO_FILE_MODIFIED )) { // // Drop the Scb for the volume Dasd around this call. // NtfsReleaseScb( IrpContext, Scb ); AcquiredScb = FALSE; NtfsFlushVolume( IrpContext, Vcb, FALSE, TRUE, TRUE, FALSE ); NtfsAcquireExclusiveScb( IrpContext, Scb ); AcquiredScb = TRUE; // // If this is not the boot partition then dismount the Vcb. // if ((Vcb->CleanupCount == 1) && ((Vcb->CloseCount - Vcb->SystemFileCloseCount) == 1)) { NtfsPerformDismountOnVcb( IrpContext, Vcb, TRUE ); } } ClearFlag( Vcb->VcbState, VCB_STATE_LOCKED | VCB_STATE_EXPLICIT_LOCK ); Vcb->FileObjectWithVcbLocked = NULL; #ifdef _CAIRO_ // // If the quota tracking has been requested and the quotas // need to be repaired then try to repair them now. // if (FlagOn( Vcb->QuotaFlags, QUOTA_FLAG_TRACKING_REQUESTED) && FlagOn( Vcb->QuotaFlags, QUOTA_FLAG_OUT_OF_DATE | QUOTA_FLAG_CORRUPT | QUOTA_FLAG_PENDING_DELETES)) { NtfsPostRepairQuotaIndex( IrpContext, Vcb ); } #endif // _CAIRO_ } break; 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 )) { FsRtlNotifyCleanup( Vcb->NotifySync, &Vcb->DirNotifyList, Ccb ); ClearFlag( Ccb->Flags, CCB_FLAG_DIR_NOTIFY ); InterlockedDecrement( &Vcb->NotifyCount ); } // // 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 (FlagOn( Vcb->VcbState, VCB_STATE_VOLUME_MOUNTED ) && (NodeType( Scb ) == NTFS_NTC_SCB_INDEX)) { if (DeleteFile) { 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; } } // // Now acquire the Parent Scb exclusive while still holding // the Vcb, to avoid deadlocks. The Parent Scb is required // since we will be deleting index entries in it. // if (ParentScb != NULL) { NtfsAcquireExclusiveScb( IrpContext, ParentScb ); AcquiredParentScb = TRUE; } try { NtfsDeleteFile( IrpContext, Fcb, ParentScb, NULL); TotalLinkAdj += 1; // // Remove all tunneling entries for this directory // FsRtlDeleteKeyFromTunnelCache(&Vcb->Tunnel, *(PULONGLONG)&Fcb->FileReference); if (ParentFcb != NULL) { NtfsUpdateFcb( ParentFcb ); } } except( (((Status = GetExceptionCode()) == STATUS_LOG_FILE_FULL) || (Status == STATUS_CANT_WAIT) || !FsRtlIsNtstatusExpected( Status )) ? EXCEPTION_CONTINUE_SEARCH : EXCEPTION_EXECUTE_HANDLER ) { NOTHING; } 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.Buffer != NULL) ? &Ccb->Lcb->Scb->ScbType.Index.NormalizedName : NULL), FILE_NOTIFY_CHANGE_DIR_NAME, FILE_ACTION_REMOVED, ParentFcb ); } SetFlag( Fcb->FcbState, FCB_STATE_FILE_DELETED ); // // 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. // NtfsRemovePrefix( 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( Scb->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 ); AddToDelayQueue = FALSE; } } else { AddToDelayQueue = FALSE; } // // 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. // NtfsAcquireFsrtlHeader( Scb ); if (AddToDelayQueue && !FlagOn( Scb->ScbState, SCB_STATE_DELAY_CLOSE ) && (NtfsData.DelayedCloseCount <= NtfsMaxDelayedCloseCount) && (Fcb->CloseCount == 1)) { SetFlag( Scb->ScbState, SCB_STATE_DELAY_CLOSE ); } else { ClearFlag( Scb->ScbState, SCB_STATE_DELAY_CLOSE ); } NtfsReleaseFsrtlHeader( Scb ); break; case UserFileOpen : #ifdef _CAIRO_ case UserPropertySetOpen : #endif // _CAIRO_ 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 )) { try { NtfsUpdateScbFromAttribute( IrpContext, Scb, NULL ); } except( (((Status = GetExceptionCode()) == STATUS_LOG_FILE_FULL) || (Status == STATUS_CANT_WAIT) || !FsRtlIsNtstatusExpected( Status )) ? EXCEPTION_CONTINUE_SEARCH : EXCEPTION_EXECUTE_HANDLER ) { NOTHING; } } NtfsSnapshotScb( IrpContext, Scb ); // // Coordinate the cleanup operation with the oplock state. // Cleanup operations can always cleanup immediately. // 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 (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 ); // // If the Fcb is in valid shape, we check on the cases where we delete // the file or attribute. // if (FlagOn( Vcb->VcbState, VCB_STATE_VOLUME_MOUNTED )) { // // 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 ); // // 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) { // // 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; } } } // Now acquire the Parent Scb exclusive while still holding // the Vcb, to avoid deadlocks. The Parent Scb is required // since we will be deleting index entries in it. // if (ParentScb != NULL) { NtfsAcquireExclusiveScb( IrpContext, ParentScb ); AcquiredParentScb = TRUE; } try { AddToDelayQueue = FALSE; NtfsDeleteFile( IrpContext, Fcb, ParentScb, &NamePair ); 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)) { FsRtlAddToTunnelCache( &Vcb->Tunnel, *(PULONGLONG)&ParentScb->Fcb->FileReference, &NamePair.Short, &NamePair.Long, BooleanFlagOn(Ccb->Lcb->FileNameAttr->Flags, FILE_NAME_DOS), sizeof(LONGLONG), &Fcb->Info.CreationTime); } if (ParentFcb != NULL) { NtfsUpdateFcb( ParentFcb ); } } except( (((Status = GetExceptionCode()) == STATUS_LOG_FILE_FULL) || (Status == STATUS_CANT_WAIT) || !FsRtlIsNtstatusExpected( Status )) ? EXCEPTION_CONTINUE_SEARCH : EXCEPTION_EXECUTE_HANDLER ) { NOTHING; } 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.Buffer != NULL) ? &Ccb->Lcb->Scb->ScbType.Index.NormalizedName : NULL), FILE_NOTIFY_CHANGE_FILE_NAME, FILE_ACTION_REMOVED, ParentFcb ); } SetFlag( Fcb->FcbState, FCB_STATE_FILE_DELETED ); // // 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. // NtfsRemovePrefix( 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( Scb->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. // if (RemoveLink) { NtfsAcquireExclusiveScb( IrpContext, ParentScb ); AcquiredParentScb = TRUE; try { AddToDelayQueue = FALSE; NtfsRemoveLink( IrpContext, Fcb, ParentScb, Lcb->ExactCaseLink.LinkName, &NamePair ); // // 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)) { FsRtlAddToTunnelCache( &Vcb->Tunnel, *(PULONGLONG)&ParentScb->Fcb->FileReference, &NamePair.Short, &NamePair.Long, BooleanFlagOn(Lcb->FileNameAttr->Flags, FILE_NAME_DOS), sizeof(LONGLONG), &Fcb->Info.CreationTime); } TotalLinkAdj += 1; NtfsUpdateFcb( ParentFcb ); } except( (((Status = GetExceptionCode()) == STATUS_LOG_FILE_FULL) || (Status == STATUS_CANT_WAIT) || !FsRtlIsNtstatusExpected( Status )) ? EXCEPTION_CONTINUE_SEARCH : EXCEPTION_EXECUTE_HANDLER ) { NOTHING; } 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.Buffer != NULL) ? &Ccb->Lcb->Scb->ScbType.Index.NormalizedName : NULL), FILE_NOTIFY_CHANGE_FILE_NAME, FILE_ACTION_REMOVED, ParentFcb ); } // // Remove all remaining prefixes on this link. // NtfsRemovePrefix( 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. // NtfsRemovePrefix( 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)) { // // We may also have to delete this attribute only. // if (DeleteStream) { ClearFlag( Scb->ScbState, SCB_STATE_DELETE_ON_CLOSE ); try { // // Delete the attribute only. // if (CleanupAttrContext) { NtfsCleanupAttributeContext( &AttrContext ); } NtfsInitializeAttributeContext( &AttrContext ); CleanupAttrContext = TRUE; NtfsLookupAttributeForScb( IrpContext, Scb, NULL, &AttrContext ); do { NtfsDeleteAttributeRecord( IrpContext, Fcb, TRUE, FALSE, &AttrContext ); } while (NtfsLookupNextAttributeForScb( IrpContext, Scb, &AttrContext )); } except( (((Status = GetExceptionCode()) == STATUS_LOG_FILE_FULL) || (Status == STATUS_CANT_WAIT) || !FsRtlIsNtstatusExpected( Status )) ? EXCEPTION_CONTINUE_SEARCH : EXCEPTION_EXECUTE_HANDLER ) { SetFlag( Scb->ScbState, SCB_STATE_DELETE_ON_CLOSE ); } // // 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; 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. // NtfsPrepareForUpdateDuplicate( IrpContext, Fcb, &LcbForUpdate, &ParentScb, TRUE ); ClearFlag( Scb->ScbState, SCB_STATE_CHECK_ATTRIBUTE_SIZE ); // // 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 ); } except( (((Status = GetExceptionCode()) == STATUS_LOG_FILE_FULL) || (Status == STATUS_CANT_WAIT) || !FsRtlIsNtstatusExpected( Status )) ? EXCEPTION_CONTINUE_SEARCH : EXCEPTION_EXECUTE_HANDLER ) { SetFlag( Scb->ScbState, SCB_STATE_CHECK_ATTRIBUTE_SIZE ); } // // 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( (((Status = GetExceptionCode()) == STATUS_LOG_FILE_FULL) || (Status == STATUS_CANT_WAIT) || !FsRtlIsNtstatusExpected( Status )) ? EXCEPTION_CONTINUE_SEARCH : EXCEPTION_EXECUTE_HANDLER ) { SetFlag( Scb->ScbState, SCB_STATE_CHECK_ATTRIBUTE_SIZE ); } // // 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 ); } #ifdef _CAIRO_ if (NtfsPerformQuotaOperation( Fcb )) { if ( FlagOn( Scb->ScbState, SCB_STATE_QUOTA_ENLARGED )) { ASSERT( NtfsIsTypeCodeSubjectToQuota( Scb->AttributeTypeCode )); ASSERT( FlagOn( Scb->ScbState, SCB_STATE_SUBJECT_TO_QUOTA )); // // Acquire the parent now so we enforce our locking // rules that the Mft Scb must be acquired after // the normal file resources. // NtfsPrepareForUpdateDuplicate( IrpContext, Fcb, &LcbForUpdate, &ParentScb, TRUE ); NtfsContractQuotaToFileSize( IrpContext, Scb ); } SetFlag( IrpContext->Flags, IRP_CONTEXT_FLAG_QUOTA_DISABLE ); } #endif // _CAIRO_ if (FlagOn( Scb->ScbState, SCB_STATE_TRUNCATE_ON_CLOSE )) { // // Acquire the parent now so we enforce our locking // rules that the Mft Scb must be acquired after // the normal file resources. // NtfsPrepareForUpdateDuplicate( IrpContext, Fcb, &LcbForUpdate, &ParentScb, TRUE ); ClearFlag( Scb->ScbState, SCB_STATE_TRUNCATE_ON_CLOSE ); // // 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; // // **** 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 ); // // If there are clusters to delete, we do so now. // if (EndingCluster != StartingCluster) { try { NtfsDeleteAllocation( IrpContext, FileObject, Scb, StartingCluster, MAXLONGLONG, TRUE, TRUE ); } except( (((Status = GetExceptionCode()) == STATUS_LOG_FILE_FULL) || (Status == STATUS_CANT_WAIT) || !FsRtlIsNtstatusExpected( Status )) ? EXCEPTION_CONTINUE_SEARCH : EXCEPTION_EXECUTE_HANDLER ) { SetFlag( Scb->ScbState, SCB_STATE_TRUNCATE_ON_CLOSE ); } } 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( &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( (((Status = GetExceptionCode()) == STATUS_LOG_FILE_FULL) || (Status == STATUS_CANT_WAIT) || !FsRtlIsNtstatusExpected( Status )) ? EXCEPTION_CONTINUE_SEARCH : EXCEPTION_EXECUTE_HANDLER ) { SetFlag( Scb->ScbState, SCB_STATE_TRUNCATE_ON_CLOSE ); } // // 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 (!FlagOn( FileObject->Flags, FO_NO_INTERMEDIATE_BUFFERING ) && (Scb->NonCachedCleanupCount != 0) && (Scb->CleanupCount == (Scb->NonCachedCleanupCount + 1)) && (Scb->CompressionUnit == 0) && (Scb->NonpagedScb->SegmentObject.DataSectionObject != NULL) && (Scb->NonpagedScb->SegmentObject.ImageSectionObject == NULL) && MmCanFileBeTruncated( &Scb->NonpagedScb->SegmentObject, NULL )) { // // Flush and purge the stream. // NtfsFlushAndPurgeScb( IrpContext, Scb, NULL ); // // Ignore any errors in this path. // IrpContext->ExceptionStatus = STATUS_SUCCESS; } if (AddToDelayQueue && !FlagOn( Scb->ScbState, SCB_STATE_DELAY_CLOSE ) && NtfsData.DelayedCloseCount <= NtfsMaxDelayedCloseCount && Fcb->CloseCount == 1) { SetFlag( Scb->ScbState, SCB_STATE_DELAY_CLOSE ); } else { ClearFlag( Scb->ScbState, SCB_STATE_DELAY_CLOSE ); } // // 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) { if (FlagOn( Fcb->FcbState, FCB_STATE_UPDATE_STD_INFO )) { Fcb->Info.LastAccessTime = Fcb->CurrentLastAccess; SetFlag( Fcb->InfoFlags, FCB_INFO_CHANGED_LAST_ACCESS ); } else if (!FlagOn( Fcb->FcbState, FCB_STATE_FILE_DELETED )) { NtfsCheckLastAccess( IrpContext, Fcb ); } } // // We check if we have to the standard information attribute. // We can only update attributes on mounted volumes. // if (FlagOn( Fcb->FcbState, FCB_STATE_UPDATE_STD_INFO ) && (Status == STATUS_SUCCESS) && !FlagOn( Scb->ScbState, SCB_STATE_VOLUME_DISMOUNTED )) { ASSERT( !FlagOn( Fcb->FcbState, FCB_STATE_FILE_DELETED )); ASSERT( TypeOfOpen != UserVolumeOpen ); try { NtfsUpdateStandardInformation( IrpContext, Fcb ); } except( (((Status = GetExceptionCode()) == STATUS_LOG_FILE_FULL) || (Status == STATUS_CANT_WAIT) || !FsRtlIsNtstatusExpected( Status )) ? EXCEPTION_CONTINUE_SEARCH : EXCEPTION_EXECUTE_HANDLER ) { NOTHING; } } // // Now update the duplicate information as well for volumes that are still mounted. // if (!FlagOn( Vcb->VcbState, VCB_STATE_VOLUME_MOUNTED )) { // // We shouldn't try to write the duplicate info to a dismounted volume. // UpdateDuplicateInfo = FALSE; } else if (FlagOn( Fcb->InfoFlags, FCB_INFO_DUPLICATE_FLAGS ) || ((LcbForUpdate != NULL) && FlagOn( LcbForUpdate->InfoFlags, FCB_INFO_DUPLICATE_FLAGS ))) { 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( (((Status = GetExceptionCode()) == STATUS_LOG_FILE_FULL) || (Status == STATUS_CANT_WAIT) || !FsRtlIsNtstatusExpected( Status )) ? EXCEPTION_CONTINUE_SEARCH : EXCEPTION_EXECUTE_HANDLER ) { 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) { ULONG FilterMatch; // // Check whether we need to report on file changes. // if ((Vcb->NotifyCount != 0) && (UpdateDuplicateInfo || FlagOn( Fcb->InfoFlags, FCB_INFO_MODIFIED_SECURITY ))) { // // 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) { 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.Buffer != NULL) ? &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.Buffer != NULL) ? &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( &AttrContext ); CleanupAttrContext = FALSE; } // // Now decrement the cleanup counts. // NtfsDecrementCleanupCounts( Scb, LcbForCounts, BooleanFlagOn( FileObject->Flags, FO_NO_INTERMEDIATE_BUFFERING )); // // 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 ); } // // Since this request has completed we can adjust the total link count // in the Fcb. // Fcb->TotalLinks -= TotalLinkAdj; #ifdef _CAIRO_ // // 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 ); } #endif // _CAIRO_ } finally { DebugUnwind( NtfsCommonCleanup ); ClearFlag( IrpContext->Flags, IRP_CONTEXT_FLAG_QUOTA_DISABLE ); // // Release any resources held. // NtfsReleaseVcb( IrpContext, Vcb ); // // 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 ); if (AcquiredScb) { NtfsReleaseScb( IrpContext, Scb ); } if (CleanupAttrContext) { NtfsCleanupAttributeContext( &AttrContext ); } if (NamePair.Long.Buffer != NamePair.LongBuffer) { NtfsFreePool(NamePair.Long.Buffer); } if (!AbnormalTermination()) { NtfsCompleteRequest( &IrpContext, &Irp, Status ); } // // And return to our caller // DebugTrace( -1, Dbg, ("NtfsCommonCleanup -> %08lx\n", Status) ); } return Status; } #ifdef _CAIRO_ VOID NtfsContractQuotaToFileSize ( IN PIRP_CONTEXT IrpContext, IN PSCB Scb ) /*++ Routine Description: This routine converts the quota charged for a stream from allocation size to file size. This should only be called for cleanup. Arguments: Scb - Supplies a pointer to the being changed. Return Value: None. --*/ { LONGLONG Delta; NTSTATUS Status; PAGED_CODE(); ASSERT( IrpContext->MajorFunction == IRP_MJ_CLEANUP ); ASSERT(!FlagOn( IrpContext->Flags, IRP_CONTEXT_FLAG_QUOTA_DISABLE )); try { ASSERT( NtfsIsTypeCodeSubjectToQuota( Scb->AttributeTypeCode )); ASSERT( FlagOn( Scb->ScbState, SCB_STATE_SUBJECT_TO_QUOTA )); if (FlagOn( Scb->ScbState, SCB_STATE_ATTRIBUTE_RESIDENT )) { Delta = (LONG) Scb->Header.FileSize.LowPart - NtfsResidentStreamQuota( Scb->Vcb ); } else { Delta = Scb->Header.FileSize.QuadPart - Scb->Header.AllocationSize.QuadPart; } if (Delta != 0) { NtfsUpdateFileQuota( IrpContext, Scb->Fcb, &Delta, TRUE, FALSE ); } ClearFlag( Scb->ScbState, SCB_STATE_QUOTA_ENLARGED ); } except( (((Status = GetExceptionCode()) == STATUS_LOG_FILE_FULL) || (Status == STATUS_CANT_WAIT) || !FsRtlIsNtstatusExpected( Status )) ? EXCEPTION_CONTINUE_SEARCH : EXCEPTION_EXECUTE_HANDLER ) { NOTHING; } } #endif // _CAIRO_