windows-nt/Source/XPSP1/NT/base/fs/ntfs/pnp.c

722 lines
20 KiB
C
Raw Permalink Normal View History

2020-09-26 03:20:57 -05:00
/*++
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
);
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;
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 (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 );
}
Status = NtfsCommonPnp( 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);
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
)
/*++
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!!!
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;
BOOLEAN DecrementCloseCount = FALSE;
#ifdef SYSCACHE_DEBUG
ULONG SystemHandleCount = 0;
#endif
ASSERT_IRP_CONTEXT( IrpContext );
ASSERT_IRP( *Irp );
ASSERT( FlagOn( IrpContext->TopLevelIrpContext->State, IRP_CONTEXT_STATE_OWNS_TOP_LEVEL ));
//
// 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 );
//
// Anyone who is flushing the volume or setting Vcb bits needs to get the
// vcb exclusively.
//
switch ( IrpSp->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 {
switch ( IrpSp->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;
DecrementCloseCount = TRUE;
NtfsReleaseVcb( IrpContext, Vcb );
CcWaitForCurrentLazyWriterActivity();
NtfsAcquireExclusiveVcb( IrpContext, Vcb, TRUE );
Vcb->CloseCount -= 1;
DecrementCloseCount = FALSE;
//
// 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", IrpSp->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 = 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 )) {
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 )) {
DebugTrace( 0, Dbg, ("IRP_MN_CANCEL_REMOVE_DEVICE --> Volume locked \n") );
NtfsUnlockVolumeInternal( IrpContext, Vcb );
}
break;
}
try_exit: NOTHING;
} finally {
if (DecrementCloseCount) {
if (!VcbAcquired) {
NtfsAcquireExclusiveVcb( IrpContext, Vcb, TRUE );
VcbAcquired = TRUE;
}
Vcb->CloseCount -= 1;
}
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;
}