/*++ Copyright (c) 1991 Microsoft Corporation Module Name: Pnp.c Abstract: This module implements the Pnp routines for Ntfs called by the dispatch driver. Author: Gary Kimura [GaryKi] 29-Aug-1991 Revision History: --*/ #include "NtfsProc.h" // // The Bug check file id for this module // #define BugCheckFileId (NTFS_BUG_CHECK_PNP) // // The local debug trace level // #define Dbg (DEBUG_TRACE_PNP) // // Local procedure prototypes // NTSTATUS NtfsCommonPnp ( IN PIRP_CONTEXT IrpContext, IN PIRP *Irp, IN OUT PBOOLEAN CallerDecrementCloseCount ); NTSTATUS NtfsPnpCompletionRoutine ( IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp, IN PNTFS_COMPLETION_CONTEXT CompletionContext ); VOID NtfsPerformSurpriseRemoval( IN PIRP_CONTEXT IrpContext, IN PVCB Vcb ); #ifdef ALLOC_PRAGMA #pragma alloc_text(PAGE, NtfsCommonPnp) #pragma alloc_text(PAGE, NtfsFsdPnp) #pragma alloc_text(PAGE, NtfsPerformSurpriseRemoval) #endif NTSTATUS NtfsFsdPnp ( IN PVOLUME_DEVICE_OBJECT VolumeDeviceObject, IN PIRP Irp ) /*++ Routine Description: This routine implements the FSD entry point for plug and play (Pnp). 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 --*/ { NTSTATUS Status = STATUS_SUCCESS; TOP_LEVEL_CONTEXT TopLevelContext; PTOP_LEVEL_CONTEXT ThreadTopLevelContext; PIRP_CONTEXT IrpContext = NULL; BOOLEAN DecrementCloseCount = FALSE; ASSERT_IRP( Irp ); UNREFERENCED_PARAMETER( VolumeDeviceObject ); #ifdef NTFSPNPDBG if (NtfsDebugTraceLevel != 0) SetFlag( NtfsDebugTraceLevel, DEBUG_TRACE_PNP ); #endif DebugTrace( +1, Dbg, ("NtfsFsdPnp\n") ); // // Call the common Pnp routine // FsRtlEnterFileSystem(); switch( IoGetCurrentIrpStackLocation( Irp )->MinorFunction ) { case IRP_MN_QUERY_REMOVE_DEVICE: case IRP_MN_REMOVE_DEVICE: case IRP_MN_CANCEL_REMOVE_DEVICE: case IRP_MN_SURPRISE_REMOVAL: ThreadTopLevelContext = NtfsInitializeTopLevelIrp( &TopLevelContext, FALSE, FALSE ); break; default: ThreadTopLevelContext = NtfsInitializeTopLevelIrp( &TopLevelContext, TRUE, TRUE ); break; } do { try { // // We are either initiating this request or retrying it. // if (NT_SUCCESS( Status ) && (IrpContext == NULL)) { // // Allocate and initialize the Irp. // NtfsInitializeIrpContext( Irp, TRUE, &IrpContext ); // // Initialize the thread top level structure, if needed. // NtfsUpdateIrpContextWithTopLevel( IrpContext, ThreadTopLevelContext ); } else if (Status == STATUS_LOG_FILE_FULL) { NtfsCheckpointForLogFileFull( IrpContext ); } else if (Status != STATUS_CANT_WAIT) { // // As long as Status is not STATUS_CANT_WAIT or STATUS_LOG_FILE_FULL, // we want to exit the loop. // if (DecrementCloseCount) { NtfsAcquireExclusiveVcb( IrpContext, IrpContext->Vcb, TRUE ); IrpContext->Vcb->CloseCount -= 1; NtfsReleaseVcbCheckDelete( IrpContext, IrpContext->Vcb, IrpContext->MajorFunction, NULL ); ClearFlag( IrpContext->State, IRP_CONTEXT_STATE_PERSISTENT ); NtfsCompleteRequest( IrpContext, NULL, Status ); } break; } Status = NtfsCommonPnp( IrpContext, &Irp, &DecrementCloseCount ); } 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 (TRUE); ASSERT( IoGetTopLevelIrp() != (PIRP) &TopLevelContext ); FsRtlExitFileSystem(); // // And return to our caller // DebugTrace( -1, Dbg, ("NtfsFsdPnp -> %08lx\n", Status) ); return Status; } NTSTATUS NtfsCommonPnp ( IN PIRP_CONTEXT IrpContext, IN PIRP *Irp, IN OUT PBOOLEAN CallerDecrementCloseCount ) /*++ Routine Description: This is the common routine for PnP called by the fsd thread. Arguments: Irp - Supplies the Irp to process. WARNING! THIS IRP HAS NO FILE OBJECT IN OUR IRP STACK LOCATION!!! CallerDecrementCloseCount - Returns TRUE if the caller needs to decrement the Vcb CloseCount. Return Value: NTSTATUS - The return status for the operation --*/ { NTSTATUS Status; NTSTATUS FlushStatus; PIO_STACK_LOCATION IrpSp; NTFS_COMPLETION_CONTEXT CompletionContext; PVOLUME_DEVICE_OBJECT OurDeviceObject; PVCB Vcb; BOOLEAN VcbAcquired = FALSE; BOOLEAN CheckpointAcquired = FALSE; #ifdef SYSCACHE_DEBUG ULONG SystemHandleCount = 0; #endif ASSERT_IRP_CONTEXT( IrpContext ); ASSERT( FlagOn( IrpContext->TopLevelIrpContext->State, IRP_CONTEXT_STATE_OWNS_TOP_LEVEL )); if (*Irp != NULL) { ASSERT_IRP( *Irp ); // // Get the current Irp stack location. // IrpSp = IoGetCurrentIrpStackLocation( *Irp ); // // Find our Vcb. This is tricky since we have no file object in the Irp. // OurDeviceObject = (PVOLUME_DEVICE_OBJECT) IrpSp->DeviceObject; // // Make sure this device object really is big enough to be a volume device // object. If it isn't, we need to get out before we try to reference some // field that takes us past the end of an ordinary device object. Then we // check if it is actually one of ours, just to be perfectly paranoid. // if (OurDeviceObject->DeviceObject.Size != sizeof(VOLUME_DEVICE_OBJECT) || NodeType(&OurDeviceObject->Vcb) != NTFS_NTC_VCB) { NtfsCompleteRequest( IrpContext, *Irp, STATUS_INVALID_PARAMETER ); return STATUS_INVALID_PARAMETER; } Vcb = &OurDeviceObject->Vcb; KeInitializeEvent( &CompletionContext.Event, NotificationEvent, FALSE ); } else { Vcb = IrpContext->Vcb; } // // Anyone who is flushing the volume or setting Vcb bits needs to get the // vcb exclusively. // switch ( IrpContext->MinorFunction ) { case IRP_MN_QUERY_REMOVE_DEVICE: case IRP_MN_SURPRISE_REMOVAL: // // Lock volume / dismount synchs with checkpoint - we need to do this first before // acquiring the vcb to preserve locking order since we're going to do a lock in // the query remove case and a dismount in the surprise removal // NtfsAcquireCheckpointSynchronization( IrpContext, Vcb ); CheckpointAcquired = TRUE; // fall through case IRP_MN_REMOVE_DEVICE: case IRP_MN_CANCEL_REMOVE_DEVICE: NtfsAcquireExclusiveVcb( IrpContext, Vcb, TRUE ); VcbAcquired = TRUE; break; } try { if (*Irp != NULL) { switch ( IrpContext->MinorFunction ) { case IRP_MN_QUERY_REMOVE_DEVICE: DebugTrace( 0, Dbg, ("IRP_MN_QUERY_REMOVE_DEVICE\n") ); if (!FlagOn( Vcb->VcbState, VCB_STATE_VOLUME_MOUNTED )) { Status = STATUS_VOLUME_DISMOUNTED; break; } // // If we already know we don't want to dismount this volume, don't bother // flushing now. If there's a nonzero cleanup count, flushing won't get // the close count down to zero, so we might as well get out now. // #ifdef SYSCACHE_DEBUG if (Vcb->SyscacheScb != NULL) { SystemHandleCount = Vcb->SyscacheScb->CleanupCount; } if ((Vcb->CleanupCount > SystemHandleCount) || #else if ((Vcb->CleanupCount > 0) || #endif FlagOn(Vcb->VcbState, VCB_STATE_DISALLOW_DISMOUNT)) { DebugTrace( 0, Dbg, ("IRP_MN_QUERY_REMOVE_DEVICE --> cleanup count still %x \n", Vcb->CleanupCount) ); // // We don't want the device to get removed or stopped if this volume has files // open. We'll fail this query, and we won't bother calling the driver(s) below us. // Status = STATUS_UNSUCCESSFUL; } else { // // We might dismount this volume soon, so let's try to flush and purge // everything we can right now. // FlushStatus = NtfsFlushVolume( IrpContext, Vcb, TRUE, TRUE, TRUE, FALSE ); // // We need to make sure the cache manager is done with any lazy writes // that might be keeping the close count up. Since Cc might need to // close some streams, we need to release the vcb. We'd hate to have // the Vcb go away, so we'll bias the close count temporarily. // Vcb->CloseCount += 1; NtfsReleaseVcb( IrpContext, Vcb ); CcWaitForCurrentLazyWriterActivity(); ASSERT( FlagOn( IrpContext->State, IRP_CONTEXT_STATE_WAIT ) ); NtfsAcquireExclusiveVcb( IrpContext, Vcb, TRUE ); Vcb->CloseCount -= 1; // // Since we dropped the Vcb, we need to redo any tests we've done. // if (!FlagOn( Vcb->VcbState, VCB_STATE_VOLUME_MOUNTED )) { Status = STATUS_VOLUME_DISMOUNTED; break; } #ifdef SYSCACHE_DEBUG if (Vcb->SyscacheScb != NULL) { SystemHandleCount = Vcb->SyscacheScb->CleanupCount; } if ((Vcb->CleanupCount > SystemHandleCount) || #else if ((Vcb->CleanupCount > 0) || #endif FlagOn(Vcb->VcbState, VCB_STATE_DISALLOW_DISMOUNT)) { Status = STATUS_UNSUCCESSFUL; break; } if ((Vcb->CloseCount - (Vcb->SystemFileCloseCount + Vcb->QueuedCloseCount)) > 0) { DebugTrace( 0, Dbg, ("IRP_MN_QUERY_REMOVE_DEVICE --> %x user files still open \n", (Vcb->CloseCount - Vcb->SystemFileCloseCount)) ); // // We don't want the device to get removed or stopped if this volume has files // open. We'll fail this query, and we won't bother calling the driver(s) below us. // Status = STATUS_UNSUCCESSFUL; } else { // // We've already done all we can to clear up any open files, so there's // no point in retrying if this lock volume fails. We'll just tell // NtfsLockVolumeInternal we're already retrying. // ULONG Retrying = 1; DebugTrace( 0, Dbg, ("IRP_MN_QUERY_REMOVE_DEVICE --> No user files, Locking volume \n") ); Status = NtfsLockVolumeInternal( IrpContext, Vcb, ((PFILE_OBJECT) 1), &Retrying ); // // Remember not to send any irps to the target device now. // if (NT_SUCCESS( Status )) { ASSERT_EXCLUSIVE_RESOURCE( &Vcb->Resource ); SetFlag( Vcb->VcbState, VCB_STATE_TARGET_DEVICE_STOPPED ); } } } break; case IRP_MN_REMOVE_DEVICE: DebugTrace( 0, Dbg, ("IRP_MN_REMOVE_DEVICE\n") ); // // If remove_device is preceded by query_remove, we treat this just // like a cancel_remove and unlock the volume and pass the irp to // the driver(s) below the filesystem. // if (FlagOn( Vcb->VcbState, VCB_STATE_EXPLICIT_LOCK )) { DebugTrace( 0, Dbg, ("IRP_MN_REMOVE_DEVICE --> Volume locked \n") ); Status = NtfsUnlockVolumeInternal( IrpContext, Vcb ); } else { // // The only other possibility is for remove_device to be prededed // by surprise_remove, in which case we treat this as a failed verify. // // **** TODO **** ADD CODE TO TREAT THIS LIKE A FAILED VERIFY DebugTrace( 0, Dbg, ("IRP_MN_REMOVE_DEVICE --> Volume _not_ locked \n") ); Status = STATUS_SUCCESS; } break; case IRP_MN_SURPRISE_REMOVAL: DebugTrace( 0, Dbg, ("IRP_MN_SURPRISE_REMOVAL\n") ); // // For surprise removal, we call the driver(s) below us first, then do // our processing. Let us also remember that we can't send any more // IRPs to the target device. // SetFlag( Vcb->VcbState, VCB_STATE_TARGET_DEVICE_STOPPED ); Status = STATUS_SUCCESS; break; case IRP_MN_CANCEL_REMOVE_DEVICE: Status = STATUS_SUCCESS; break; default: DebugTrace( 0, Dbg, ("Some other PnP IRP_MN_ %x\n", IrpContext->MinorFunction) ); Status = STATUS_SUCCESS; break; } // // We only pass this irp down if we didn't have some reason to fail it ourselves. // We want to keep the IrpContext around for our own cleanup. // if (!NT_SUCCESS( Status )) { NtfsCompleteRequest( NULL, *Irp, Status ); try_return( NOTHING ); } // // Get the next stack location, and copy over the stack location // IoCopyCurrentIrpStackLocationToNext( *Irp ); // // Set up the completion routine // CompletionContext.IrpContext = IrpContext; IoSetCompletionRoutine( *Irp, NtfsPnpCompletionRoutine, &CompletionContext, TRUE, TRUE, TRUE ); // // Send the request to the driver(s) below us. - We don't own it anymore // so null it out // Status = IoCallDriver( Vcb->TargetDeviceObject, *Irp ); *Irp = IrpContext->OriginatingIrp = NULL; // // Wait for the driver to definitely complete // if (Status == STATUS_PENDING) { KeWaitForSingleObject( &CompletionContext.Event, Executive, KernelMode, FALSE, NULL ); KeClearEvent( &CompletionContext.Event ); } } // // Post processing - these are items that need to be done after the lower // storage stack has processed the request. // switch (IrpContext->MinorFunction) { case IRP_MN_SURPRISE_REMOVAL: // // Start the tear-down process irrespective of the status // the driver below us sent back. There's no turning back here. // if (FlagOn( Vcb->VcbState, VCB_STATE_VOLUME_MOUNTED )) { if (!*CallerDecrementCloseCount) { SetFlag( IrpContext->State, IRP_CONTEXT_STATE_PERSISTENT ); Vcb->CloseCount += 1; *CallerDecrementCloseCount = TRUE; } NtfsPerformSurpriseRemoval( IrpContext, Vcb ); } break; case IRP_MN_CANCEL_REMOVE_DEVICE: // // Since we cancelled and have told the driver we can now safely unlock // the volume and send ioctls to the drive (unlock media) // ClearFlag( Vcb->VcbState, VCB_STATE_TARGET_DEVICE_STOPPED ); if (FlagOn( Vcb->VcbState, VCB_STATE_EXPLICIT_LOCK )) { if (!*CallerDecrementCloseCount) { SetFlag( IrpContext->State, IRP_CONTEXT_STATE_PERSISTENT ); Vcb->CloseCount += 1; *CallerDecrementCloseCount = TRUE; } DebugTrace( 0, Dbg, ("IRP_MN_CANCEL_REMOVE_DEVICE --> Volume locked \n") ); NtfsUnlockVolumeInternal( IrpContext, Vcb ); } break; } try_exit: NOTHING; } finally { if (VcbAcquired) { // // All 4 paths query / remove / surprise remove / cancel remove // come through here. For the 3 except query we want the vcb to go away // if possible. In the query remove path - dismount won't be complete // even if the close count is 0 (since the dismount is incomplete) // so this will only release // NtfsReleaseVcbCheckDelete( IrpContext, Vcb, IrpContext->MajorFunction, NULL ); } if (CheckpointAcquired) { NtfsReleaseCheckpointSynchronization( IrpContext, Vcb ); } } // // Cleanup our IrpContext; The underlying driver completed the Irp. // DebugTrace( -1, Dbg, ("NtfsCommonPnp -> %08lx\n", Status ) ); NtfsCompleteRequest( IrpContext, NULL, Status ); return Status; } // // Local support routine // NTSTATUS NtfsPnpCompletionRoutine ( IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp, IN PNTFS_COMPLETION_CONTEXT CompletionContext ) { PIO_STACK_LOCATION IrpSp; PIRP_CONTEXT IrpContext; PVOLUME_DEVICE_OBJECT OurDeviceObject; PVCB Vcb; BOOLEAN VcbAcquired = FALSE; ASSERT_IRP( Irp ); IrpContext = CompletionContext->IrpContext; ASSERT_IRP_CONTEXT( IrpContext ); // // Get the current Irp stack location. // IrpSp = IoGetCurrentIrpStackLocation( Irp ); // // Find our Vcb. This is tricky since we have no file object in the Irp. // OurDeviceObject = (PVOLUME_DEVICE_OBJECT) DeviceObject; // // Make sure this device object really is big enough to be a volume device // object. If it isn't, we need to get out before we try to reference some // field that takes us past the end of an ordinary device object. Then we // check if it is actually one of ours, just to be perfectly paranoid. // if (OurDeviceObject->DeviceObject.Size != sizeof(VOLUME_DEVICE_OBJECT) || NodeType(&OurDeviceObject->Vcb) != NTFS_NTC_VCB) { return STATUS_INVALID_PARAMETER; } Vcb = &OurDeviceObject->Vcb; KeSetEvent( &CompletionContext->Event, 0, FALSE ); // // Propagate the Irp pending state. // if (Irp->PendingReturned) { IoMarkIrpPending( Irp ); } return STATUS_SUCCESS; } // // Local utility routine // VOID NtfsPerformSurpriseRemoval ( IN PIRP_CONTEXT IrpContext, IN PVCB Vcb ) /*++ Performs further processing on SURPRISE_REMOVAL notifications. --*/ { ASSERT(ExIsResourceAcquiredExclusiveLite( &Vcb->Resource )); // // Flush and purge and mark all files as dismounted. // Since there may be outstanding handles, we could still see any // operation (read, write, set info, etc.) happen for files on the // volume after surprise_remove. Since all the files will be marked // for dismount, we will fail these operations gracefully. All // operations besides cleanup & close on the volume will fail from // this time on. // if (!FlagOn( Vcb->VcbState, VCB_STATE_DISALLOW_DISMOUNT )) { (VOID)NtfsFlushVolume( IrpContext, Vcb, FALSE, TRUE, TRUE, TRUE ); NtfsPerformDismountOnVcb( IrpContext, Vcb, TRUE, NULL ); } return; }