Windows NT 4.0 source code leak
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.
 
 
 
 
 
 

3610 lines
98 KiB

/*++
Copyright (c) 1990 Microsoft Corporation
Module Name:
lock.c
Abstract:
This module implements the NtLockFile and NtUnlockFile API for NT.
Author:
Larry Osterman (larryo) 23-Nov-1990
Revision History:
23-Nov-1990 larryo
Created
--*/
#define INCLUDE_SMB_LOCK
#define INCLUDE_SMB_READ_WRITE
#include "precomp.h"
#pragma hdrstop
KSPIN_LOCK
RdrLockHeadSpinLock = {0};
typedef struct _LOCKCONTEXT {
TRANCEIVE_HEADER Header; // Standard XCeive header.
WORK_QUEUE_ITEM WorkHeader; // Work header if request fails.
PSMB_BUFFER SmbBuffer; // SMB buffer used for send of lock
PIRP Irp; // IRP to complete when finished.
PICB Icb; // ICB of file (unlocked when done).
PMPX_ENTRY Mte; // MPX table entry for lock request.
PLCB Lcb; // LockControlBlock for Lock&Read
PMDL LcbMdl; // MDL for LCB
PSMB_BUFFER ReceiveSmb; // Smb Buffer for receive of Lock&Read.
PIRP ReceiveIrp; // I/O request packet for receive.
BOOLEAN FailImmediately;// TRUE if request is to fail immediatly
BOOLEAN LockFailed; // True if lock failed
BOOLEAN ReadFailed; // True if &X operation failed on Locking&X
BOOLEAN CoreLock; // True if this was a core lock req.
#if RDRDBG_LOCK
BOOLEAN FailLockAndRead;
#endif
} LOCKCONTEXT, *PLOCKCONTEXT;
typedef struct _UNLOCKCONTEXT {
TRANCEIVE_HEADER Header; // Standard XCeive header.
WORK_QUEUE_ITEM WorkHeader; // Work header for queuing to FSP.
PMPX_ENTRY Mte; // MPX table entry for lock request.
PSMB_BUFFER SmbBuffer; // SMB buffer for send.
BOOLEAN WaitForCompletion; // True if &X operation failed on Locking&X
BOOLEAN ThreadReferenced; // True if &X operation failed on Locking&X
PICB Icb; // ICB of file being unlocked
PLCB Lcb; // Lock Control Block for Write&Unlock
PMDL LcbMdl; // MDL for LCB
PFILE_OBJECT FileObject; // File Object performin W&U.
PETHREAD RequestorsThread; // Thread initiating Write&Unlock
ERESOURCE_THREAD RequestorsRThread; // Tread performing the lock.
} UNLOCKCONTEXT, *PUNLOCKCONTEXT;
DBGSTATIC
STANDARD_CALLBACK_HEADER(
LockOperationCallback
);
DBGSTATIC
STANDARD_CALLBACK_HEADER(
UnLockOperationCallback
);
DBGSTATIC
NTSTATUS
CompleteFailedLockIrp (
IN PDEVICE_OBJECT DeviceObject,
IN PIRP Irp,
IN PVOID Ctx
);
DBGSTATIC
VOID
CompleteLockOperation(
IN PVOID Ctx
);
DBGSTATIC
VOID
FailLockOperation (
IN PVOID Context
);
DBGSTATIC
NTSTATUS
LockAndReadComplete (
IN PDEVICE_OBJECT DeviceObject,
IN PIRP Irp,
IN PVOID Ctx
);
DBGSTATIC
NTSTATUS
CompleteUnlockAllIrp (
IN PDEVICE_OBJECT DeviceObject,
IN PIRP Irp,
IN PVOID Ctx
);
#ifdef ALLOC_PRAGMA
#pragma alloc_text(PAGE, RdrFsdLockOperation)
#pragma alloc_text(PAGE, RdrFspLockOperation)
#pragma alloc_text(PAGE, RdrFscLockOperation)
#pragma alloc_text(PAGE, RdrLockOperationCompletion)
#pragma alloc_text(PAGE, RdrUnlockOperation)
#pragma alloc_text(PAGE, RdrUnlockAll)
#pragma alloc_text(PAGE, RdrUnlockFileLocks)
#pragma alloc_text(PAGE, RdrLockRange)
#pragma alloc_text(PAGE, RdrUnlockRange)
#pragma alloc_text(PAGE, CompleteLockOperation)
#pragma alloc_text(PAGE, FailLockOperation)
#pragma alloc_text(PAGE, RdrTruncateLockHeadForFcb)
#pragma alloc_text(PAGE, RdrTruncateLockHeadForIcb)
#pragma alloc_text(PAGE, RdrInitializeAndXBehind)
#pragma alloc_text(PAGE3FILE, CompleteUnlockAllIrp)
#pragma alloc_text(PAGE3FILE, LockOperationCallback)
#pragma alloc_text(PAGE3FILE, LockAndReadComplete)
#pragma alloc_text(PAGE3FILE, CompleteFailedLockIrp)
#pragma alloc_text(PAGE3FILE, UnLockOperationCallback)
#pragma alloc_text(PAGE3FILE, RdrUninitializeLockHead)
#pragma alloc_text(PAGE3FILE, RdrFindLcb)
#pragma alloc_text(PAGE3FILE, RdrAllocateLcb)
#pragma alloc_text(PAGE3FILE, RdrInsertLock)
#pragma alloc_text(PAGE3FILE, RdrRemoveLock)
#pragma alloc_text(PAGE3FILE, RdrFreeLcb)
#pragma alloc_text(PAGE3FILE, RdrStartAndXBehindOperation)
#pragma alloc_text(PAGE3FILE, RdrEndAndXBehindOperation)
#pragma alloc_text(PAGE3FILE, RdrWaitForAndXBehindOperation)
#endif
NTSTATUS
RdrFsdLockOperation (
IN PFS_DEVICE_OBJECT DeviceObject,
IN PIRP Irp
)
/*++
Routine Description:
This routine implements the FSD version of the NtLockFile and NtUnlockFile
API.
Arguments:
IN PFS_DEVICE_OBJECT DeviceObject, - Supplies the device object for this
request
IN PIRP Irp - Supplies the IRP that describes the request
Return Value:
NTSTATUS - Status of operation
--*/
{
BOOLEAN Wait;
PIO_STACK_LOCATION IrpSp;
NTSTATUS Status;
PAGED_CODE();
dprintf(DPRT_FILELOCK|DPRT_DISPATCH, ("RdrFsdLockOperation\n"));
FsRtlEnterFileSystem();
//
// Decide if we can block for I/O
//
Wait = CanFsdWait( Irp );
//
// Get a pointer to the current Irp stack location
//
IrpSp = IoGetCurrentIrpStackLocation( Irp );
try {
//
// We know this is a file lock control operation, so we'll case on the
// minor function, and call the appropriate common routine which
// will either complete the operation or send the Irp off to the FSP
// if necessary. This is the only place where we abort an invalid
// minor function for a lock control operation
//
switch (IrpSp->MinorFunction) {
case IRP_MN_LOCK:
case IRP_MN_UNLOCK_SINGLE:
case IRP_MN_UNLOCK_ALL:
case IRP_MN_UNLOCK_ALL_BY_KEY:
Status = RdrFscLockOperation( Wait, DeviceObject, Irp );
break;
default:
//
// For all other minor function codes we say they're invalid
// and complete the request. Note that the IRP has not been
// marked pending so this error will be returned directly to
// the caller.
//
dprintf( DPRT_FILELOCK, ("Invalid LockFile Minor Function Code %08lx\n", IrpSp->MinorFunction));
RdrCompleteRequest( Irp, STATUS_INVALID_DEVICE_REQUEST );
Status = STATUS_INVALID_DEVICE_REQUEST;
break;
}
} except(EXCEPTION_EXECUTE_HANDLER) {
Status = GetExceptionCode();
}
dprintf(DPRT_FILELOCK, ("RdrFsdLockOperation -> %X\n", Status));
FsRtlExitFileSystem();
return Status;
}
NTSTATUS
RdrFspLockOperation (
IN PFS_DEVICE_OBJECT DeviceObject,
IN PIRP Irp
)
/*++
Routine Description:
This routine implements the FSD version of the NtLockFile and NtUnlockFile
API.
Arguments:
IN PFS_DEVICE_OBJECT DeviceObject, - Supplies the device object for this
request
IN PIRP Irp - Supplies the IRP that describes the request
Return Value:
NTSTATUS - Status of operation
--*/
{
PAGED_CODE();
dprintf(DPRT_FILELOCK, ("RdrFspLockOperation\n"));
//
// Call the common lock routine. The Fsp is always allows to block
//
return RdrFscLockOperation( TRUE, DeviceObject, Irp );
}
NTSTATUS
RdrFscLockOperation (
IN BOOLEAN Wait,
IN PFS_DEVICE_OBJECT DeviceObject,
IN PIRP Irp
)
/*++
Routine Description:
This routine implements the FSD version of the NtLockFile and NtUnlockFile
API.
Arguments:
IN PFS_DEVICE_OBJECT DeviceObject, - Supplies the device object for this
request
IN PIRP Irp - Supplies the IRP that describes the request
Return Value:
NTSTATUS - Status of operation
--*/
{
NTSTATUS Status;
PIO_STACK_LOCATION IrpSp;
#if 0 && RDRDBG_LOG
PVOID fileObject;
ULONG offset;
ULONG length;
ULONG key;
CCHAR operation;
#endif
PFCB Fcb;
PAGED_CODE();
//
// Get the current stack location
//
IrpSp = IoGetCurrentIrpStackLocation( Irp );
Fcb = FCB_OF(IrpSp);
dprintf(DPRT_FILELOCK, ("RdrFscLockOperation. Wait: %lx, Irp:%08lx, FileObject: %08lx\n", Wait, Irp, IrpSp->FileObject));
//
// Acquire exclusive access to the Fcb and enqueue the Irp if we didn't
// get access
//
//
// Note that we keep an exclusive FCB lock across the lock operation,
// this is to make sure that read or write operations don't come
// in while we are trying to perform the lock request.
//
if (!RdrAcquireFcbLock(Fcb, ExclusiveLock, Wait )) {
dprintf(DPRT_FILELOCK, ("Could not acquire FCB lock\n"));
RdrFsdPostToFsp ( DeviceObject, Irp );
dprintf(DPRT_FILELOCK, ("Returning STATUS_PENDING\n"));
return STATUS_PENDING;
}
Status = RdrIsOperationValid(ICB_OF(IrpSp), IRP_MJ_LOCK_CONTROL, IrpSp->FileObject);
if (!NT_SUCCESS(Status)) {
RdrReleaseFcbLock( Fcb);
RdrCompleteRequest(Irp, Status);
return Status;
}
try {
//
// Now call the fsrtl routine do actually process the file lock
//
#if 0 && RDRDBG_LOG
fileObject = IrpSp->FileObject;
offset = IrpSp->Parameters.LockControl.ByteOffset.LowPart;
length = IrpSp->Parameters.LockControl.Length->LowPart;
key = IrpSp->Parameters.LockControl.Key;
operation = IrpSp->MinorFunction;
switch (operation) {
case IRP_MN_LOCK:
//RdrLog(( "lock", &Fcb->FileName, 4, IoGetRequestorProcess(Irp), fileObject, offset, length ));
break;
case IRP_MN_UNLOCK_SINGLE:
//RdrLog(( "unlock", &Fcb->FileName, 4, IoGetRequestorProcess(Irp), fileObject, offset, length ));
break;
case IRP_MN_UNLOCK_ALL:
//RdrLog(( "unlckall", &Fcb->FileName, 2, IoGetRequestorProcess(Irp), fileObject ));
break;
case IRP_MN_UNLOCK_ALL_BY_KEY:
//RdrLog(( "unlckkey", &Fcb->FileName, 3, IoGetRequestorProcess(Irp), fileObject, key ));
break;
}
#endif
Status = FsRtlProcessFileLock( &Fcb->FileLock, Irp, ICB_OF(IrpSp));
#if 0 && RDRDBG_LOG
if ( (Status == STATUS_FILE_LOCK_CONFLICT) && (operation == IRP_MN_LOCK) ) {
//RdrLog(( "lockCONF", &Fcb->FileName, 4, IoGetRequestorProcess(Irp), fileObject, offset, length ));
}
#endif
// try_exit: NOTHING;
} finally {
RdrReleaseFcbLock( Fcb );
}
dprintf(DPRT_FILELOCK, ("Returning status %X\n", Status));
return Status;
}
NTSTATUS
RdrLockOperationCompletion (
IN PVOID Context,
IN PIRP Irp
)
/*++
Routine Description:
This routine is called after the FSRTL lock package has determined that
a lock operation can successfully proceed. It will remote the specified
lock to the remote server (if possible). If the remoted lock (or unlock)
operation completes successfully, we return success, otherwise, we complete
the IRP with the appropriate error, and return that error to the caller.
Arguments:
IN PVOID Context - Provides the context supplied to FsRtlProcessFileLock.
In this case, it is the ICB of the file to lock.
IN PIRP Irp - Supplies an IRP describing the lock operation.
Return Value:
NTSTATUS - Final status of operation. If the redirector had to pass the
request to the remote server, this will return STATUS_PENDING.
--*/
{
PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp);
PICB Icb = ICB_OF(IrpSp);
NTSTATUS Status = Irp->IoStatus.Status;
PAGED_CODE();
dprintf(DPRT_FILELOCK|DPRT_DISPATCH, ("RdrLockOperationCompletion. Irp:%lx, IrpSp: %lx, Cnt: %lx\n", Irp, IoGetCurrentIrpStackLocation(Irp), Irp->CurrentLocation));
// DbgBreakPoint();
if ((Context != NULL) &&
(!RdrCanFileBeBuffered(Icb) || (Icb->u.f.OplockLevel == SMB_OPLOCK_LEVEL_II)) &&
NT_SUCCESS(Status)) {
switch (IrpSp->MinorFunction) {
case IRP_MN_LOCK:
Status = RdrLockRange(Irp,
Icb,
IrpSp->Parameters.LockControl.ByteOffset,
*IrpSp->Parameters.LockControl.Length,
IrpSp->Parameters.LockControl.Key,
(BOOLEAN )((IrpSp->Flags & SL_FAIL_IMMEDIATELY) != 0),
(BOOLEAN )((IrpSp->Flags & SL_EXCLUSIVE_LOCK) != 0));
break;
case IRP_MN_UNLOCK_SINGLE:
case IRP_MN_UNLOCK_ALL:
case IRP_MN_UNLOCK_ALL_BY_KEY:
dprintf(DPRT_FILELOCK, ("Complete unlock IRP %lx (ignored)\n", Irp));
break;
default:
dprintf(DPRT_FILELOCK, ("Unknown lock operation %lx\n", IrpSp->MinorFunction));
break;
}
} else {
//
// Lanman 1.0 servers don't support either specifying a timeout
// or shared lock ranges. Since the file may be oplocked on the
// server, we cannot allow the user to request a lock operation
// that we cannot transmit on the oplock break, so we have to
// fail the lock request
//
// Note that we only execute this code if we would have otherwise
// granted the lock.
//
// In addition, the LANMAN 2.0 pinball server doesn't support shared
// locks either, so we cannot allow shared locks to lanman 2.0
// servers.... Sigh...
//
if (( RdrCanFileBeBuffered(Icb) || ( Context == NULL ))
&&
NT_SUCCESS(Status)
&&
(IrpSp->MinorFunction == IRP_MN_LOCK)
&&
(Icb->NonPagedFcb->Flags & FCB_OPLOCKED)) {
if (!FlagOn(Icb->Fcb->Connection->Server->Capabilities, DF_LANMAN21)
&&
(!FlagOn(IrpSp->Flags, SL_EXCLUSIVE_LOCK) ||
!FlagOn(IrpSp->Flags, SL_FAIL_IMMEDIATELY))) {
Status = STATUS_NOT_SUPPORTED;
} else if (!FlagOn(Icb->Fcb->Connection->Server->Capabilities, DF_NT_SMBS)
&&
((IrpSp->Parameters.LockControl.Length->HighPart != 0) ||
(IrpSp->Parameters.LockControl.ByteOffset.HighPart != 0))) {
Status = STATUS_NOT_SUPPORTED;
}
}
}
if (Status != STATUS_PENDING) {
dprintf(DPRT_FILELOCK, ("Completing lock IRP %lx with status %X\n", Irp, Status));
//
// The operation succeeded locally, now we must make sure that
// this lock operation will be valid remotely as well.
//
RdrCompleteRequest(Irp, Status);
}
return Status;
}
VOID
RdrUnlockOperation (
IN PVOID Context,
IN PFILE_LOCK_INFO LockInfo
)
/*++
Routine Description:
This routine is called after the FSRTL lock package has determined that
a locked region is to be unlocked. It will remove the specified
lock to the remote server (if possible). If the remoted unlock
operation completes successfully, we return success.
Arguments:
IN PVOID Context - Provides the context supplied to FsRtlProcessFileLock.
In this case, it is the ICB of the file to lock.
IN PFILE_LOCK_INFO LockInfo - Describes the region being unlock
Return Value:
NTSTATUS - Status of unlock operation (it can't really fail, though).
--*/
{
PICB Icb = Context;
NTSTATUS Status;
BOOLEAN WaitForCompletion;
PAGED_CODE();
//
// If there is a NULL context, this means that this routine was called
// on behalf of a failed lock request, so we return immediately.
//
//
// In addition, it's possible that this handle is no longer invalid (if
// the file was closed by a NetUseDel, for example), in which case we
// should just ignore the unlock request.
//
if ((Context == NULL) ||
(RdrCanFileBeBuffered(Icb) && (Icb->u.f.OplockLevel != SMB_OPLOCK_LEVEL_II)) ||
!(Icb->Flags & ICB_HASHANDLE)) {
return;
}
dprintf(DPRT_FILELOCK, ("RdrUnlockOperation %lx,%lx Len:%lx,%lx Key:%lx\n", LockInfo->StartingByte.HighPart, LockInfo->StartingByte.LowPart, LockInfo->Length.HighPart, LockInfo->Length.LowPart, LockInfo->Key));
ASSERT(Icb->Signature == STRUCTURE_SIGNATURE_ICB);
ASSERT(Icb == LockInfo->FileObject->FsContext2);
//
// If the redirector heuristics indicate that
// we should not use unlock behind, then set up to wait for the
// unlock to complete.
//
WaitForCompletion = !RdrData.UseUnlockBehind;
// DbgBreakPoint();
Status = RdrUnlockRange (NULL,
LockInfo->FileObject,
Icb,
LockInfo->StartingByte,
LockInfo->Length,
LockInfo->Key,
WaitForCompletion);
// ASSERT (NT_SUCCESS(Status));
}
NTSTATUS
RdrUnlockAll (
IN PFS_DEVICE_OBJECT DeviceObject,
IN PFILE_OBJECT FileObject
)
/*++
Routine Description:
This routine is called to unlock all regions outstanding on a file.
Arguments:
IN PFILE_OBJECT FileObject - An instance of the file
Return Value:
NTSTATUS - Status of unlock operation (it can't really fail, though).
--*/
{
NTSTATUS Status;
PICB Icb = FileObject->FsContext2;
PIRP UnlockAllIrp;
PIO_STACK_LOCATION UnlockAllIrpSp;
PAGED_CODE();
//
// Allocate and initialize the I/O Request Packet (IRP)
// for this operation.
//
UnlockAllIrp = ALLOCATE_IRP( FileObject, &DeviceObject->DeviceObject, 4, NULL );
if (UnlockAllIrp == NULL) {
return(STATUS_INSUFFICIENT_RESOURCES);
}
dprintf(DPRT_FILELOCK, ("Build Unlock All Irp, Irp: %lx\n", UnlockAllIrp));
//
// Fill in the service independent parameters in the
// IRP.
//
UnlockAllIrp->UserEvent = NULL;
UnlockAllIrp->UserIosb = &UnlockAllIrp->IoStatus;
UnlockAllIrp->Flags = IRP_SYNCHRONOUS_API;
UnlockAllIrp->Overlay.AsynchronousParameters.UserApcRoutine = (PIO_APC_ROUTINE) NULL;
//
// Get a pointer to the stack location for the first driver. This will
// be used to pass the original function codes and parameters. No
// function-specific parameters are required for this operation.
//
UnlockAllIrpSp = IoGetNextIrpStackLocation( UnlockAllIrp );
UnlockAllIrpSp->MajorFunction = IRP_MJ_LOCK_CONTROL;
UnlockAllIrpSp->MinorFunction = IRP_MN_UNLOCK_ALL;
UnlockAllIrpSp->FileObject = FileObject;
IoSetCompletionRoutine(UnlockAllIrp, CompleteUnlockAllIrp, NULL, TRUE, TRUE, TRUE);
//
// Invoke the driver at its appropriate dispatch entry with the IRP.
//
Status = IoCallDriver( (PDEVICE_OBJECT )DeviceObject, UnlockAllIrp );
// ASSERT(NT_SUCCESS(Status));
return Status;
}
DBGSTATIC
NTSTATUS
CompleteUnlockAllIrp (
IN PDEVICE_OBJECT DeviceObject,
IN PIRP Irp,
IN PVOID Ctx
)
/*++
CompleteUnlockAllIrp - Final completion for user request.
Routine Description:
This routine is called on final completion of the TDI_Receive
request from the transport. If the request completed successfully,
this routine will complete the request with no error, if the receive
completed with an error, it will flag the error and complete the
request.
Arguments:
DeviceObject - Device structure that the request completed on.
Irp - The Irp that completed.
Context - Context information for completion.
Return Value:
Return value to be returned from receive indication routine.
--*/
{
DeviceObject, Ctx;
DISCARDABLE_CODE(RdrFileDiscardableSection);
dprintf(DPRT_FILELOCK, ("CompleteUnlockAllOperation, Irp: %lx\n", Irp));
// ASSERT (NT_SUCCESS(Irp->IoStatus.Status));
FREE_IRP( Irp, 5, NULL );
return STATUS_MORE_PROCESSING_REQUIRED;
}
NTSTATUS
RdrUnlockFileLocks (
IN PFCB Fcb,
IN PUNICODE_STRING DeviceName OPTIONAL
)
/*++
Routine Description:
This routine is called to unlock all of the locks outstanding on an FCB
to a remote server.
Arguments:
IN PFCB Fcb - Supplies the file whose locks to unlock.
Return Value:
NTSTATUS - Status of dump operation.
--*/
{
NTSTATUS Status;
PFILE_LOCK_INFO FileLock;
PAGED_CODE();
for (FileLock = FsRtlGetNextFileLock(&Fcb->FileLock, TRUE)
;
FileLock != NULL
;
FileLock = FsRtlGetNextFileLock(&Fcb->FileLock, FALSE)) {
PICB Icb = (PICB )FileLock->FileObject->FsContext2;
if (!ARGUMENT_PRESENT(DeviceName) ||
RtlEqualUnicodeString(DeviceName, &Icb->DeviceName, TRUE)) {
Status = RdrUnlockRange(NULL,
FileLock->FileObject,
Icb,
FileLock->StartingByte,
FileLock->Length,
FileLock->Key,
(BOOLEAN)!RdrData.UseUnlockBehind);
if (!NT_SUCCESS(Status)) {
return Status;
}
}
}
return STATUS_SUCCESS;
}
//
//
// RdrLockRange
//
//
NTSTATUS
RdrLockRange (
IN PIRP Irp,
IN PICB Icb,
IN LARGE_INTEGER StartingByte,
IN LARGE_INTEGER Length,
IN ULONG Key,
IN BOOLEAN FailImmediately,
IN BOOLEAN ExclusiveLock
)
/*++
Routine Description:
This routine will perform a lock operation over the network to a remote
server.
Arguments:
IN PIRP Irp - Supplies an I/O Request Packet to use.
IN PICB Icb - ICB representing file to be locked.
IN LARGE_INTEGER StartingByte - The starting offset of the lock operation
IN LARGE_INTEGER Length - The number of bytes to lock.
IN ULONG Key - Supplies an additional 32 bit key for the lock operation
IN BOOLEAN FailImmediately - TRUE if the lock is to immediately fail.
IN BOOLEAN ExclusiveLock - TRUE if the lock is exclusive.
Return Value:
NTSTATUS - Final status of the lock operation.
Note:
In order to avoid tying up a redir thread for this lock operation, this
routine performs the lock operation asynchronously. If the request
has been successfully transmitted to the server, then the this routine
will return STATUS_PENDING. The callback routine will complete the
lock IRP with the appropriate status.
After the lock has completed, we queue a request up to a generic worker
thread to free up the resources used in the lock request.
--*/
{
PSMB_BUFFER SmbBuffer = NULL;
PSMB_HEADER Smb;
NTSTATUS Status;
PLOCKCONTEXT Context = NULL;
PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp);
PLCB Lcb = NULL;
PSERVERLISTENTRY Server = Icb->Fcb->Connection->Server;
BOOLEAN UseLockAndRead;
PAGED_CODE();
ASSERT (Icb->Signature == STRUCTURE_SIGNATURE_ICB);
ASSERT (Irp != NULL);
dprintf(DPRT_FILELOCK,
("RdrLockRange %lx%lx Len:%lx%lx Key:%lx\n",
IrpSp->Parameters.LockControl.ByteOffset.HighPart,
IrpSp->Parameters.LockControl.ByteOffset.LowPart,
IrpSp->Parameters.LockControl.Length->LowPart,
IrpSp->Parameters.LockControl.Length->HighPart,
IrpSp->Parameters.LockControl.Key));
try {
if ((SmbBuffer = RdrAllocateSMBBuffer()) == NULL) {
try_return(Status = STATUS_INSUFFICIENT_RESOURCES);
}
Context = ALLOCATE_POOL(NonPagedPool, sizeof(LOCKCONTEXT), POOL_LOCKCTX);
if (Context == NULL) {
try_return(Status = STATUS_INSUFFICIENT_RESOURCES);
}
//
// Initialize the kernel event in the header to the Not-Signalled state.
//
KeInitializeEvent(&Context->Header.KernelEvent, NotificationEvent, 0);
//
// Fill in the context header to allow us to complete the lock operation.
//
Context->Header.Type = CONTEXT_LOCK;
Context->Irp = Irp;
Context->Icb = Icb;
Context->Lcb = NULL;
Context->Mte = NULL;
Context->SmbBuffer = SmbBuffer;
ExInitializeWorkItem(&Context->WorkHeader, FailLockOperation, Context);
Context->FailImmediately = FailImmediately;
Context->LockFailed = FALSE;
Context->ReadFailed = FALSE;
Context->CoreLock = FALSE; // Assume this is not a core lock.
Context->ReceiveSmb = NULL;
Context->LcbMdl = NULL;
//
// We cannot allow zero length lock operations to non NT servers, they
// can't handle them.
//
if ((Length.LowPart == 0 && Length.HighPart == 0) &&
((Server->Capabilities & DF_NT_SMBS) == 0)) {
try_return(Status = STATUS_NOT_SUPPORTED);
}
//
// We set up for performing a Lock&Read operation, if it is reasonable
// to do so.
//
UseLockAndRead = RdrData.UseLockAndReadWriteAndUnlock;
//
// Please note: We check the FCB filesize here to see if the lock
// range requested is past the nominal end of file. We can get
// away with this even if the file is not owned exclusively because
// Lock&Read is a performance optimization.
//
// Basically, this means that we won't try to lock&read if we think
// that the lock request is past the end of file.
//
if (UseLockAndRead &&
Server->Capabilities & DF_LOCKREAD &&
Length.HighPart == 0 &&
Length.LowPart != 0 &&
(StartingByte.QuadPart < Icb->Fcb->Header.FileSize.QuadPart) &&
(Length.LowPart < (Icb->Fcb->Connection->Server->BufferSize - (sizeof(SMB_HEADER)+FIELD_OFFSET(REQ_READ, Buffer[0])))) &&
(StartingByte.LowPart < 0x7fffffff) &&
ExclusiveLock && FailImmediately) {
Lcb = RdrAllocateLcb (&Icb->u.f.LockHead, StartingByte, Length.LowPart, Key);
}
if (Lcb != NULL) {
#if RDRDBG_LOCK
if ((StartingByte.QuadPart == 29) && (Length.QuadPart == 968)) {
Context->FailLockAndRead = TRUE;
} else
Context->FailLockAndRead = FALSE;
#endif
//
// If we are going to try a Lock&Read, we need to pre-allocate
// a number of things related to the request.
//
Context->Lcb = Lcb;
Context->ReceiveSmb = RdrAllocateSMBBuffer();
if (Context->ReceiveSmb == NULL) {
RdrFreeLcb(&Icb->u.f.LockHead, Lcb);
} else {
BOOLEAN BufferLocked = TRUE;
Context->LcbMdl = IoAllocateMdl(Lcb->Buffer,
Lcb->Length, FALSE, FALSE, NULL);
if (Context->LcbMdl == NULL) {
RdrFreeSMBBuffer(Context->ReceiveSmb);
Context->ReceiveSmb = NULL;
Context->LcbMdl = NULL;
Context->Lcb = NULL;
RdrFreeLcb(&Icb->u.f.LockHead, Lcb);
Lcb = NULL;
} else {
try {
MmProbeAndLockPages(Context->LcbMdl, KernelMode, IoWriteAccess);
} except(EXCEPTION_EXECUTE_HANDLER) {
InternalError(("ProbeAndLock of LCB buffer failed"));
IoFreeMdl(Context->LcbMdl);
RdrFreeSMBBuffer(Context->ReceiveSmb);
Context->ReceiveSmb = NULL;
Context->LcbMdl = NULL;
Context->Lcb = NULL;
RdrFreeLcb(&Icb->u.f.LockHead, Lcb);
Lcb = NULL;
}
}
if (Context->Lcb) {
Context->ReceiveSmb->Mdl->ByteCount =
sizeof(SMB_HEADER) + FIELD_OFFSET(RESP_READ, Buffer[0]);
Context->ReceiveSmb->Mdl->Next = Context->LcbMdl;
Context->ReceiveIrp = ALLOCATE_IRP(
Server->ConnectionContext->ConnectionObject,
NULL,
5,
Context
);
if (Context->ReceiveIrp == NULL) {
try_return(Status = STATUS_INSUFFICIENT_RESOURCES);
}
RdrBuildReceive(Context->ReceiveIrp, Server,
LockAndReadComplete, Context,
Context->ReceiveSmb->Mdl, RdrMdlLength(Context->ReceiveSmb->Mdl));
//
// See the comment in NETTRANS.C to see why we do this...
//
IoSetNextIrpStackLocation( Context->ReceiveIrp);
}
}
}
Smb = (PSMB_HEADER )SmbBuffer->Buffer;
//
// If we want to try a Lock&Read, build a Lock&Read SMB.
//
if (Lcb) {
PREQ_READ LockAndReadRequest;
ASSERT ( Length.LowPart <= 0xffff );
//
// This server supports Lock&Read/Write&Unlock, the request
// is for an positive offset into the file, and the request
// will fit in the servers negotiated buffer size.
// We want to try this one as a Lock&Read SMB.
//
Context->Lcb = Lcb;
LockAndReadRequest = (PREQ_READ)(Smb+1);
Smb->Command = SMB_COM_LOCK_AND_READ;
LockAndReadRequest->WordCount = 5;
SmbPutUshort(&LockAndReadRequest->Fid, Icb->FileId);
SmbPutUshort(&LockAndReadRequest->Count, (USHORT )(Length.LowPart & 0xffff));
SmbPutUlong(&LockAndReadRequest->Offset, StartingByte.LowPart);
SmbPutUshort(&LockAndReadRequest->Remaining, 0);
SmbPutUshort(&LockAndReadRequest->ByteCount, 0);
SmbBuffer->Mdl->ByteCount = sizeof(SMB_HEADER)+
FIELD_OFFSET(REQ_READ, Buffer[0]);
} else if (Server->Capabilities & DF_LANMAN20) {
PREQ_LOCKING_ANDX LockRequest;
PLOCKING_ANDX_RANGE LockRange;
PNTLOCKING_ANDX_RANGE NtLockRange;
USHORT LockType = 0;
//
// Lanman 2.0 pinball servers don't support shared locks (even
// though Lanman 2.0 FAT servers support them). As a result,
// we cannot support shared locks to Lanman servers.
//
if (!ExclusiveLock &&
((Server->Capabilities & DF_LANMAN21) == 0)) {
try_return(Status = STATUS_NOT_SUPPORTED);
}
LockRequest = (PREQ_LOCKING_ANDX)(Smb+1);
Smb->Command = SMB_COM_LOCKING_ANDX;
LockRequest->WordCount = 8;
LockRequest->AndXCommand = SMB_COM_NO_ANDX_COMMAND;
LockRequest->AndXReserved = 0;
SmbPutUshort(&LockRequest->AndXOffset, 0);
SmbPutUshort(&LockRequest->Fid, Icb->FileId);
if (ExclusiveLock) {
LockType = 0;
} else {
LockType = LOCKING_ANDX_SHARED_LOCK;
}
if (Server->Capabilities & DF_NT_SMBS) {
LockType |= LOCKING_ANDX_LARGE_FILES;
} else {
if (StartingByte.HighPart != 0) {
try_return(Status = STATUS_INVALID_PARAMETER);
}
}
SmbPutUshort(&LockRequest->LockType, LockType);
if (FailImmediately) {
SmbPutUlong(&LockRequest->Timeout, 0);
} else {
SmbPutUlong(&LockRequest->Timeout, 0xffffffff);
}
SmbPutUshort(&LockRequest->NumberOfUnlocks, 0);
SmbPutUshort(&LockRequest->NumberOfLocks, 1);
if (Server->Capabilities & DF_NT_SMBS) {
SmbPutUshort(&LockRequest->ByteCount, sizeof(NTLOCKING_ANDX_RANGE));
NtLockRange = (PNTLOCKING_ANDX_RANGE )LockRequest->Buffer;
//
// Fill in the lock range in the SMB.
//
SmbPutUshort(&NtLockRange->Pid, RDR_PROCESS_ID);
SmbPutUlong(&NtLockRange->OffsetLow, StartingByte.LowPart);
SmbPutUlong(&NtLockRange->OffsetHigh, StartingByte.HighPart);
SmbPutUlong(&NtLockRange->LengthLow, Length.LowPart);
SmbPutUlong(&NtLockRange->LengthHigh, Length.HighPart);
SmbBuffer->Mdl->ByteCount = sizeof(SMB_HEADER)+
FIELD_OFFSET(REQ_LOCKING_ANDX, Buffer[0])+
sizeof(NTLOCKING_ANDX_RANGE);
} else {
SmbPutUshort(&LockRequest->ByteCount, sizeof(LOCKING_ANDX_RANGE));
LockRange = (PLOCKING_ANDX_RANGE )LockRequest->Buffer;
//
// Fill in the lock range in the SMB.
//
SmbPutUshort(&LockRange->Pid, RDR_PROCESS_ID);
SmbPutUlong(&LockRange->Offset, StartingByte.LowPart);
SmbPutUlong(&LockRange->Length, Length.LowPart);
SmbBuffer->Mdl->ByteCount = sizeof(SMB_HEADER)+
FIELD_OFFSET(REQ_LOCKING_ANDX, Buffer[0])+
sizeof(LOCKING_ANDX_RANGE);
}
} else {
PREQ_LOCK_BYTE_RANGE LockRequest;
//
// Neither LM 1.0 or MS-NET support non exclusive locks, so
// if the user didn't request one, report an error.
//
if (!ExclusiveLock) {
try_return(Status = STATUS_NOT_SUPPORTED);
}
//
// OS/2 interprets offsets into a file as signed quantities. This
// means that we cannot attempt a Lock&Read at a negative offset
// into the file, because the request will fail with an
// ERROR_NEGATIVE_OFFSET error.
//
Context->CoreLock = TRUE;
LockRequest = (PREQ_LOCK_BYTE_RANGE)(Smb+1);
Smb->Command = SMB_COM_LOCK_BYTE_RANGE;
SmbPutUshort(&LockRequest->WordCount, 5);
SmbPutUshort(&LockRequest->Fid, Icb->FileId);
SmbPutUlong(&LockRequest->Count, Length.LowPart);
SmbPutUlong(&LockRequest->Offset, StartingByte.LowPart);
SmbPutUshort(&LockRequest->ByteCount, 0);
SmbBuffer->Mdl->ByteCount = sizeof(SMB_HEADER)+
FIELD_OFFSET(REQ_LOCK_BYTE_RANGE, Buffer[0]);
}
//
// Before we return pending to the I/O subsystem, we have to
// mark the Irp as being pending. If we do not, the I/O
// subsystem will not necessarily complete the IRP
// properly.
//
IoMarkIrpPending(Irp);
if (Context->Lcb == NULL) {
Context->Header.TransferSize = SmbBuffer->Mdl->ByteCount +
sizeof(RESP_LOCKING_ANDX);
} else {
Context->Header.TransferSize = SmbBuffer->Mdl->ByteCount +
sizeof(RESP_LOCKING_ANDX) +
Length.LowPart; // Reading the data
}
//
// Believe it or not, there is a REALLY good reason for
// passing NULL as the Irp into RdrNetTranceiveNoWait here.
//
// The problem involves how stack locations are managed, and
// the order that network operations complete with an indication
// based transport that supports piggybacked ack's.
//
// If the IRP containing the lock request is used for
// RdrNetTranceiveNoWait, the subsequent stack location in the IRP
// will be used for a TDI_SEND request. It is possible (in fact
// likely) that the TDI_SEND request will not finish before
// the indication has come in for the lock request. If we then
// complete the lock IRP from inside the indication routine,
// we will complete the TDI_SEND, not the lock! Then, when
// the TDI_SEND completes, it will complete the users lock
// IRP with STATUS_MORE_PROCESSING_REQUIRED which will
// short circuit the I/O completion mechanism totally!
//
Status = RdrNetTranceiveNoWait(NT_NORMAL | NT_NORECONNECT, NULL,
Icb->Fcb->Connection,
SmbBuffer->Mdl,
Context,
LockOperationCallback,
Icb->Se,
&Context->Mte);
if (!NT_SUCCESS(Status)) {
IrpSp->Control &= ~SL_PENDING_RETURNED;
try_return(Status);
} else {
try_return(Status = STATUS_PENDING);
}
try_exit:NOTHING;
} finally {
if (Status != STATUS_PENDING) {
if (Context != NULL) {
FREE_POOL(Context);
}
if (SmbBuffer != NULL) {
RdrFreeSMBBuffer(SmbBuffer);
}
}
}
dprintf(DPRT_FILELOCK, ("RdrLockRange. Returning %X\n", Status));
return Status;
}
DBGSTATIC
STANDARD_CALLBACK_HEADER (
LockOperationCallback
)
/*++
Routine Description:
This routine is the callback routine for the processing of a lock related
SMB.
Arguments:
IN PSMB_HEADER Smb - SMB response from server.
IN PMPX_ENTRY MpxEntry - MPX table entry for request.
IN PVOID Context - Context from caller.
IN BOOLEAN ErrorIndicator - TRUE if error indication
IN NTSTATUS NetworkErrorCode OPTIONAL - Network error if error indication.
IN OUT PIRP *Irp - IRP from TDI
Return Value:
NTSTATUS - STATUS_PENDING if we are to complete the request
--*/
{
NTSTATUS Status;
NTSTATUS SmbStatus;
BOOLEAN CompleteIrp = TRUE;
PLOCKCONTEXT Context = Ctx;
USHORT LockLength;
PRESP_LOCKING_ANDX LockingAndXResponse = (PRESP_LOCKING_ANDX )(Smb+1);
PRESP_LOCK_BYTE_RANGE LockResponse = (PRESP_LOCK_BYTE_RANGE )(Smb+1);
PRESP_READ LockAndReadResponse = (PRESP_READ )(Smb+1);
UNREFERENCED_PARAMETER(MpxEntry);
UNREFERENCED_PARAMETER(Irp);
UNREFERENCED_PARAMETER(Server);
DISCARDABLE_CODE(RdrFileDiscardableSection);
ASSERT(Context->Header.Type == CONTEXT_LOCK);
dprintf(DPRT_FILELOCK, ("LockOperationCallback"));
Context->Header.ErrorType = NoError; // Assume no error at first.
Context->Header.ErrorCode = STATUS_SUCCESS;
//
// If we are called because the VC dropped, indicate it in the response
//
try {
if (ErrorIndicator) {
//
// If there was some kind of network error, then the
// lock request failed.
//
Context->Header.ErrorType = NetError;
Context->Header.ErrorCode = RdrMapNetworkError(NetworkErrorCode);
try_return(Status = STATUS_SUCCESS);
} else if (!NT_SUCCESS(SmbStatus = RdrMapSmbError(Smb, Server))) {
if (SmbStatus == STATUS_INVALID_HANDLE) {
RdrInvalidateFileId(Context->Icb->NonPagedFcb, Context->Icb->FileId);
}
//
// The only legal errors for Lock&Read protocol are
// STATUS_LOCK_NOT_GRANTED (on NT),
// LOCK_VIOLATION and SHARING_VIOLATION. If we get any other
// errors, we assume they are associated with the read operation
// and that the lock operation has succeded, while the read
// operation failed.
//
// DbgBreakPoint();
if ((Smb->Command == SMB_COM_LOCKING_ANDX) ||
(Smb->Command == SMB_COM_LOCK_BYTE_RANGE) ||
((Smb->Command == SMB_COM_LOCK_AND_READ) &&
( (SmbStatus == STATUS_FILE_LOCK_CONFLICT) ||
(SmbStatus == STATUS_LOCK_NOT_GRANTED) ||
(SmbStatus == STATUS_SHARING_VIOLATION)
)
)
) {
if ((Smb->Command == SMB_COM_LOCK_BYTE_RANGE) &&
(SmbStatus == STATUS_UNEXPECTED_NETWORK_ERROR )) {
// Xenix server returns a bad status for conflict
SmbStatus = STATUS_LOCK_NOT_GRANTED;
}
//
// If the lock request fails, we simply post the request
// to a worker thread and let it complete the request
// with whatever error is appropriate.
//
// We need to pass some additional information to the
// worker routine to indicate exactly what operation failed
// before we queue it to the worker thread.
//
Context->Header.ErrorType = SMBError;
Context->Header.ErrorCode = SmbStatus;
//
// The lock failed, indicate that to the completion routine.
//
Context->LockFailed = TRUE;
ExInitializeWorkItem(&Context->WorkHeader, FailLockOperation, Context);
ExQueueWorkItem(&Context->WorkHeader, DelayedWorkQueue);
CompleteIrp = FALSE;
try_return(Status = STATUS_SUCCESS);
} else {
ASSERT(Smb->Command == SMB_COM_LOCK_AND_READ);
//
// The read portion of a Lock&Read SMB failed.
//
// Delete the LCB associated with the request and
// complete the operation successfully.
//
Context->ReadFailed = TRUE;
CompleteIrp = FALSE;
ExInitializeWorkItem(&Context->WorkHeader, FailLockOperation, Context);
ExQueueWorkItem(&Context->WorkHeader, DelayedWorkQueue);
//
// Return success, the lock request succeeded.
//
try_return(Status = STATUS_SUCCESS);
}
}
//
// This lock operation succeeded.
//
// If this was a lock&read request, queue up a receive to handle
// the read data and continue.
//
Status = STATUS_SUCCESS;
switch (Smb->Command) {
case SMB_COM_LOCKING_ANDX:
if (LockingAndXResponse->AndXCommand != SMB_COM_NO_ANDX_COMMAND) {
//
// This was a Locking&Read request.
//
// Queue up a receive request to receive the locked data.
//
//
InternalError(("Lock&Read using Locking&X not yet implemented"));
}
//
// It wasn't a locking&read, so just complete the lock request.
//
break;
case SMB_COM_LOCK_AND_READ:
//
// This was a Lock&Read request.
//
// Queue up a receive request to receive the locked data.
//
// We will complete the lock request before the data is transfered
// to get a bit more overlap in the request.
//
//
// Only cache the lock request if the entire range was read in.
//
if (((LockLength = SmbGetUshort(&LockAndReadResponse->Count)) != 0) &&
(LockLength == (USHORT )(Context->Lcb->Length & 0xffff))) {
RdrStartReceiveForMpxEntry (MpxEntry, Context->ReceiveIrp);
*Irp = Context->ReceiveIrp;
CompleteIrp = FALSE;
*SmbLength = 0;
try_return(Status = STATUS_MORE_PROCESSING_REQUIRED);
} else {
//
// The Lock succeeded, but the read failed. This means
// that we should post a request to the FSP indicating that
// the read failed.
//
// In this case, however, the lock succeeded, so we should
// post the read immediately.
//
Context->ReadFailed = TRUE;
CompleteIrp = FALSE;
ExQueueWorkItem(&Context->WorkHeader, DelayedWorkQueue);
try_return(Status = STATUS_SUCCESS);
}
break;
case SMB_COM_LOCK_BYTE_RANGE:
//
// If this was an ordinary lock operation, we are all done, so
// we can return right away.
//
break;
default:
InternalError(("Unknown SMB request %x passed to RdrCompleteLock\n", Smb->Command));
RdrStatistics.NetworkErrors += 1;
RdrWriteErrorLogEntry(
NULL,
IO_ERR_LAYERED_FAILURE,
EVENT_RDR_INVALID_LOCK_REPLY,
STATUS_SUCCESS,
Smb,
sizeof(SMB_HEADER)
);
break;
}
try_exit:NOTHING;
} finally {
if (CompleteIrp) {
dprintf(DPRT_FILELOCK, ("Completing lock IRP\n"));
RdrCompleteRequest(Context->Irp, Context->Header.ErrorCode);
ExInitializeWorkItem(&Context->WorkHeader, CompleteLockOperation, Context);
//
// We have to queue this to an executive worker thread (as opposed
// to a redirector worker thread) because there can only be
// one redirector worker thread processing a request at a time. If
// the redirector worker thread gets stuck doing some form of
// scavenger operation (like PurgeDormantCachedFile), it could
// starve out the write completion. This in turn could cause
// us to pool lots of these write completions, and eventually
// to exceed the maximum # of requests to the server (it actually
// happened once).
//
//
// It is safe to use executive worker threads for this operation,
// since CompleteLockOperation won't interact (and thus starve)
// the cache manager.
//
ExQueueWorkItem(&Context->WorkHeader, DelayedWorkQueue);
}
//
// You cannot touch the context block at DPC level from this point
// on.
//
KeSetEvent(&Context->Header.KernelEvent, IO_NETWORK_INCREMENT, FALSE);
}
if (Status == STATUS_MORE_PROCESSING_REQUIRED) {
if ((*Irp) != Context->ReceiveIrp) {
KeBugCheck(0x0001);
}
if ((*Irp)->Type != IO_TYPE_IRP) {
KeBugCheck(0x0002);
}
}
return Status;
}
DBGSTATIC
NTSTATUS
LockAndReadComplete (
IN PDEVICE_OBJECT DeviceObject,
IN PIRP Irp,
IN PVOID Ctx
)
/*++
NetTranceiveComplete - Final completion for user request.
Routine Description:
This routine is called on final completion of the TDI_Receive
request from the transport. If the request completed successfully,
this routine will complete the request with no error, if the receive
completed with an error, it will flag the error and complete the
request.
Arguments:
DeviceObject - Device structure that the request completed on.
Irp - The Irp that completed.
Context - Context information for completion.
Return Value:
Return value to be returned from receive indication routine.
--*/
{
PLOCKCONTEXT Context = Ctx;
NTSTATUS Status;
DeviceObject;
DISCARDABLE_CODE(RdrFileDiscardableSection);
dprintf(DPRT_FILELOCK, ("LockAndReadComplete: %lx\n", Context));
ASSERT(Context->Header.Type == CONTEXT_LOCK);
RdrCompleteReceiveForMpxEntry (Context->Header.MpxTableEntry, Irp);
if (NT_SUCCESS(Irp->IoStatus.Status)
#if RDRDBG_LOCK
&&
!Context->FailLockAndRead
#endif
) {
ExInterlockedAddLargeStatistic(
&RdrStatistics.BytesReceived,
Irp->IoStatus.Information );
//
// Stick the LCB into the Icb's lock chain - it's valid now.
//
RdrInsertLock (&Context->Icb->u.f.LockHead, Context->Lcb);
//
// It's now safe to complete the lock IRP since we have finished
// linking in the read ahead portion of the data. If we attempted to
// do a Lock&Read and read in the data, we know the lock succeeded.
//
dprintf(DPRT_FILELOCK, ("Complete lock Irp %lx with STATUS_SUCCESS\n", Context->Irp));
RdrCompleteRequest(Context->Irp, STATUS_SUCCESS);
//
// We have to queue this to an executive worker thread (as opposed
// to a redirector worker thread) because there can only be
// one redirector worker thread processing a request at a time. If
// the redirector worker thread gets stuck doing some form of
// scavenger operation (like PurgeDormantCachedFile), it could
// starve out the write completion. This in turn could cause
// us to pool lots of these write completions, and eventually
// to exceed the maximum # of requests to the server (it actually
// happened once).
//
// It is safe to use executive worker threads for this operation,
// since CompleteLockOperation won't interact (and thus starve)
// the cache manager.
//
ExInitializeWorkItem(&Context->WorkHeader, CompleteLockOperation, Context);
ExQueueWorkItem (&Context->WorkHeader, DelayedWorkQueue);
SMBTRACE_RDR( Irp->MdlAddress );
} else {
//
// The receive failed, so queue up a failure indication to the
// FSP to free up the resources used on the Lock&Read.
//
Context->ReadFailed = TRUE;
RdrStatistics.FailedCompletionOperations += 1;
ExInitializeWorkItem(&Context->WorkHeader, FailLockOperation, Context);
ExQueueWorkItem (&Context->WorkHeader, DelayedWorkQueue);
}
//
// You cannot touch the context block at DPC level from this point
// on.
//
KeSetEvent(&Context->Header.KernelEvent, IO_NETWORK_INCREMENT, FALSE);
//
// Short circuit I/O completion (See NETTRANS.C).
//
Status = STATUS_MORE_PROCESSING_REQUIRED;
dprintf(DPRT_FILELOCK, ("Returning: %X\n", Status));
return Status;
}
DBGSTATIC
VOID
FailLockOperation (
IN PVOID Ctx
)
/*++
Routine Description:
This routine is called when a lock operation fails.
Arguments:
IN PVOID Context - Supplies the context for the operation.
Return Value:
None.
--*/
{
PLOCKCONTEXT Context = Ctx;
PIRP Irp = Context->Irp;
PIO_STACK_LOCATION LockIrpSp = IoGetCurrentIrpStackLocation(Irp);
PAGED_CODE();
dprintf(DPRT_FILELOCK, ("FailLockOperation"));
ASSERT (Context->Header.Type == CONTEXT_LOCK);
//
// If this lock request failed because of an SMB error, and
// the request was not to be failed immediatly, we want to re-issue
// the lock request and return.
//
if (Context->Header.ErrorType == SMBError &&
Context->CoreLock &&
!Context->FailImmediately) {
// NOTE: We Need to use pseudo polling here BIGTIME!
PICB Icb = Context->Icb;
FREE_POOL(Context->SmbBuffer);
FREE_POOL(Context);
RdrLockRange(Irp,
Icb,
LockIrpSp->Parameters.LockControl.ByteOffset,
*LockIrpSp->Parameters.LockControl.Length,
LockIrpSp->Parameters.LockControl.Key,
TRUE, FALSE);
return;
}
if (Context->ReadFailed) {
//
// The lock succeeded, but the read failed. CompleteLockOperation
// will clean up the LCB.
//
dprintf(DPRT_FILELOCK, ("LockAndRead, read operation failed\n"));
ASSERT(Context->Lcb);
RdrCompleteRequest(Irp, STATUS_SUCCESS);
} else {
//
// The lock failed, so the read part (if any), is moot. If there
// was trailing read, CompleteLockOperation will clean it up.
//
PIO_STACK_LOCATION IrpSp;
NTSTATUS Status;
LARGE_INTEGER LockRange;
KEVENT CompletionEvent;
// DbgBreakPoint();
dprintf(DPRT_FILELOCK, ("Lock request %lx failed. Unwinding, Sp: %lx, Cnt: %lx\n", Irp, IoGetCurrentIrpStackLocation(Irp), Irp->CurrentLocation));
KeInitializeEvent(&CompletionEvent, NotificationEvent, FALSE);
//
// If the lock failed we want to fail the lock operation.
//
// Before we fail the lock operation, we want to unwind the
// read operation.
//
IrpSp = IoGetNextIrpStackLocation( Irp );
//
// The unlock IRP stack location should be just about identical
// to the lock IRP stack location except for the function code,
// so initialize it as such.
//
*IrpSp = *LockIrpSp;
IrpSp->Parameters.LockControl.Length = &LockRange;
LockRange = *LockIrpSp->Parameters.LockControl.Length;
IrpSp->MinorFunction = IRP_MN_UNLOCK_SINGLE;
IoSetCompletionRoutine(Irp, CompleteFailedLockIrp, &CompletionEvent, TRUE, TRUE, TRUE);
//
// Set the next IRP stack location as the current IRP stack location.
//
// This simulates a call to IoCallDriver.
//
IoSetNextIrpStackLocation(Irp);
//
// We want to have an exclusive lock on the file for this
// operation to protect ourselves from other lock requests.
//
RdrAcquireFcbLock(Context->Icb->Fcb, ExclusiveLock, TRUE);
dprintf(DPRT_FILELOCK, ("Failing lock IRP %lx with IrpSp = %lx, Cnt = %lx\n", Irp, IoGetCurrentIrpStackLocation(Irp), Irp->CurrentLocation));
Status = FsRtlProcessFileLock(&Context->Icb->Fcb->FileLock, Irp, NULL );
if (Status == STATUS_PENDING) {
//
// Wait for the unlock to complete if PENDING was returned.
//
// We must do this because the LockRange is kept on the stack.
//
KeWaitForSingleObject(&CompletionEvent, Executive, KernelMode, FALSE, NULL);
}
RdrReleaseFcbLock(Context->Icb->Fcb);
//
// The lock request failed, complete the lock IRP with the
// appropriate error.
//
dprintf(DPRT_FILELOCK, ("Completing IRP %lx (IrpSp = %lx, Cnt = %lx), Status %X\n", Irp, IoGetCurrentIrpStackLocation(Irp), Irp->CurrentLocation, Context->Header.ErrorCode));
if (!NT_SUCCESS(Status)) {
RdrCompleteRequest(Irp, Status);
} else {
RdrCompleteRequest(Irp, Context->Header.ErrorCode);
}
}
CompleteLockOperation(Context);
return;
}
DBGSTATIC
VOID
CompleteLockOperation(
IN PVOID Ctx
)
/*++
Routine Description:
This routine is called to free up the resources used for a lock operation.
It is called after the Lock IRP has completed to free up the MPX table
entry used to send lock SMB.
This routine does double duty to complete both lock and unlock operations.
Arguments:
IN PVOID Ctx - Supplies the context describing the operation.
Return Value:
None.
--*/
{
PLOCKCONTEXT LContext = Ctx;
PUNLOCKCONTEXT UContext = Ctx;
PMPX_ENTRY Mte;
PICB Icb;
PSMB_BUFFER SmbBuffer;
PAGED_CODE();
dprintf(DPRT_FILELOCK, ("CompleteLockOperation"));
ASSERT (LContext->Header.Type == CONTEXT_LOCK ||
UContext->Header.Type == CONTEXT_UNLOCK);
if (LContext->Header.Type == CONTEXT_LOCK) {
Mte = LContext->Mte;
SmbBuffer = LContext->SmbBuffer;
Icb = LContext->Icb;
} else {
ASSERT(UContext->Header.Type == CONTEXT_UNLOCK);
Mte = UContext->Mte;
SmbBuffer = UContext->SmbBuffer;
Icb = UContext->Icb;
}
//
// Wait until both the send (and receive) for this MPX request completes.
//
RdrWaitTranceive(Mte);
//
// Now that the lock operation has completed, free up the MPX
// table entry and return.
//
RdrEndTranceive(Mte);
//
// Free up the SMB buffer used for the send request.
//
RdrFreeSMBBuffer(SmbBuffer);
if (LContext->Header.Type == CONTEXT_LOCK) {
if (LContext->Lcb) {
//
// This was a Lock&Read request, free up the resources associated
// with the lock&read.
//
//
// Unlock the pages from the processes working set.
//
MmUnlockPages(LContext->LcbMdl);
//
// Deallocate the MDL allocated for the read request.
//
IoFreeMdl(LContext->LcbMdl);
FREE_IRP( LContext->ReceiveIrp, 6, LContext );
RdrFreeSMBBuffer(LContext->ReceiveSmb);
//
// If either the lock failed or the trailing read failed,
// we need to free up the LCB here.
//
if ( LContext->LockFailed || LContext->ReadFailed ) {
RdrFreeLcb(&Icb->u.f.LockHead, LContext->Lcb);
}
}
FREE_POOL(LContext);
} else {
ASSERT(UContext->Header.Type == CONTEXT_UNLOCK);
if (UContext->Lcb) {
//
// This was a write&unlock operation that completed. Free
// up the stuff associated with the write&unlock.
//
MmUnlockPages(UContext->LcbMdl);
IoFreeMdl(UContext->LcbMdl);
RdrFreeLcb(&Icb->u.f.LockHead, UContext->Lcb);
if (UContext->FileObject != NULL) {
ObDereferenceObject(UContext->FileObject);
UContext->FileObject = NULL;
}
if (UContext->RequestorsRThread != 0) {
RdrReleaseFcbLockForThread(Icb->Fcb, UContext->RequestorsRThread);
UContext->RequestorsRThread = 0;
}
if (UContext->ThreadReferenced) {
ObDereferenceObject(UContext->RequestorsThread);
UContext->ThreadReferenced = FALSE;
}
}
if (!UContext->WaitForCompletion) {
RdrEndAndXBehindOperation(&Icb->u.f.AndXBehind);
}
FREE_POOL(UContext);
}
}
DBGSTATIC
NTSTATUS
CompleteFailedLockIrp (
IN PDEVICE_OBJECT DeviceObject,
IN PIRP Irp,
IN PVOID Ctx
)
/*++
CompleteFailedLockIrp - Final completion for pseudo unlock IRP
Routine Description:
This routine is called after the FSRTL lock package completes an unlock
request that was generated for a failed lock request. We build a dummy
unlock request in the next IRP stack location from the lock request.
When the unlock is completed, this routine is called. We simply return
STATUS_MORE_PROCESSING_REQUIRED to short circuit I/O completion on the
IRP.
Arguments:
DeviceObject - Device structure that the request completed on.
Irp - The Irp that completed.
Context - Context information for completion.
Return Value:
Return value to be returned from receive indication routine.
--*/
{
PKEVENT Event = Ctx;
DeviceObject, Ctx;
DISCARDABLE_CODE(RdrFileDiscardableSection);
dprintf(DPRT_FILELOCK, ("CompletFailedLockIrp. Irp:%lx, IrpSp: %lx, Cnt: %lx\n", Irp, IoGetCurrentIrpStackLocation(Irp), Irp->CurrentLocation));
ASSERT (NT_SUCCESS(Irp->IoStatus.Status));
//
// Set the event to the signalled state to allow the task time code to
// proceed.
//
KeSetEvent(Event, IO_NETWORK_INCREMENT, FALSE);
return STATUS_MORE_PROCESSING_REQUIRED;
}
//
//
// RdrUnlockRange
//
//
NTSTATUS
RdrUnlockRange (
IN PIRP Irp OPTIONAL,
IN PFILE_OBJECT FileObject OPTIONAL,
IN PICB Icb,
IN LARGE_INTEGER StartingByte,
IN LARGE_INTEGER Length,
IN ULONG Key,
IN BOOLEAN WaitForCompletion
)
/*++
Routine Description:
This routine will unlock the described range over the network to a remote
server.
Arguments:
IN PIRP Irp OPTIONAL - Supplies an I/O Request Packet to use.
IN PICB Icb - ICB representing file to be unlocked.
IN LARGE_INTEGER StartingByte - The starting offset of the unlock operation
IN LARGE_INTEGER Length - The number of bytes to unlock.
IN ULONG Key - Supplies an additional 32 bit key for the unlock operation
IN BOOLEAN WaitForCompletion - TRUE if the redirector should wait for the
unlock (or write&unlock) to complete.
Return Value:
NTSTATUS - Final status of the lock operation.
--*/
{
PSMB_BUFFER SmbBuffer;
PSMB_HEADER Smb;
NTSTATUS Status;
PUNLOCKCONTEXT Context = NULL;
ULONG ServerCapabilities = Icb->Fcb->Connection->Server->Capabilities;
BOOLEAN RequestSubmitted = FALSE;
BOOLEAN AndXBehindStarted = FALSE;
PAGED_CODE();
dprintf(DPRT_FILELOCK, ("RdrUnlockRange. %lx,%lx to %lx,%lx Key %lx\n",StartingByte.HighPart, StartingByte.LowPart, Length.HighPart, Length.LowPart, Key));
ASSERT (Icb->Signature == STRUCTURE_SIGNATURE_ICB);
try {
if ((SmbBuffer = RdrAllocateSMBBuffer()) == NULL) {
try_return(Status = STATUS_INSUFFICIENT_RESOURCES);
}
Smb = (PSMB_HEADER )SmbBuffer->Buffer;
Context = ALLOCATE_POOL(NonPagedPool, sizeof(UNLOCKCONTEXT), POOL_UNLOCKCTX);
if (Context == NULL) {
try_return(Status = STATUS_INSUFFICIENT_RESOURCES);
}
//
// Initialize the kernel event in the header to the Not-Signalled state.
//
KeInitializeEvent(&Context->Header.KernelEvent, NotificationEvent, 0);
//
// Fill in the context header to allow us to complete the lock operation.
//
Context->Header.Type = CONTEXT_UNLOCK;
Context->Mte = NULL;
Context->Icb = Icb;
Context->SmbBuffer = SmbBuffer;
Context->WaitForCompletion = WaitForCompletion;
Context->ThreadReferenced = FALSE;
Context->RequestorsThread = NULL;
Context->RequestorsRThread = 0;
Context->FileObject = NULL;
if (Length.HighPart == 0) {
Context->Lcb = RdrFindLcb(&Icb->u.f.LockHead, StartingByte, Length.LowPart, Key);
if (Context->Lcb != NULL) {
//
// Guarantee that there is an exact match between this LCB
// and the range locked in the file. This can happen
// if we lock bytes 0-50, and then later lock bytes 20-25
// and unlock bytes 20-25. We don't want to do a write&unlock
// in that case.
//
if ((Context->Lcb->ByteOffset.QuadPart != StartingByte.QuadPart)
||
(Context->Lcb->Length != Length.LowPart)
||
(Context->Lcb->Key != Key)) {
//
// There wasn't an exact match, so ignore this LCB.
//
Context->Lcb = NULL;
}
}
} else {
Context->Lcb = NULL;
}
if (Context->Lcb) {
ASSERT (RdrData.UseLockAndReadWriteAndUnlock);
//
// Reference the current thread (since we got this
// threads resource pointer.
//
Context->RequestorsThread = PsGetCurrentThread();
ObReferenceObject(Context->RequestorsThread);
Context->ThreadReferenced = TRUE;
if (FileObject != NULL) {
ObReferenceObject(FileObject);
Context->FileObject = FileObject;
} else {
ASSERT(WaitForCompletion);
Context->FileObject = NULL;
}
//
// Acquire an exclusive lock to the FCB to prevent anyone from
// messing with the contents of the file until this unlock
// request completes. This is to protect the LCB structures
// associated with the lock from a write request.
//
if (!WaitForCompletion) {
RdrAcquireFcbLock(Icb->Fcb, ExclusiveLock, TRUE);
Context->RequestorsRThread = ExGetCurrentResourceThread();
} else {
Context->RequestorsRThread = 0;
}
RdrRemoveLock(&Icb->u.f.LockHead, Context->Lcb);
if (Context->Lcb->Flags & LCB_DIRTY) {
Context->LcbMdl = IoAllocateMdl(Context->Lcb->Buffer, Context->Lcb->Length, FALSE, FALSE, NULL);
if (Context->LcbMdl == NULL) {
Status = STATUS_INSUFFICIENT_RESOURCES;
//
// Tell the user that he just lost data.
//
if (Irp != NULL) {
#if MAGIC_BULLET
if ( RdrEnableMagic ) {
RdrSendMagicBullet(NULL);
DbgPrint( "RDR: About to raise unlock behind hard error for IRP %x\n", Irp );
DbgBreakPoint();
}
#endif
IoRaiseInformationalHardError(Status, NULL, Irp->Tail.Overlay.Thread);
}
RdrWriteErrorLogEntry(
Icb->Fcb->Connection->Server,
IO_ERR_LAYERED_FAILURE,
EVENT_RDR_FAILED_UNLOCK,
Status,
NULL,
0
);
RdrFreeLcb(&Icb->u.f.LockHead, Context->Lcb);
Context->Lcb = NULL;
Context->LcbMdl = NULL;
RdrReleaseFcbLock(Icb->Fcb);
if (Context->ThreadReferenced) {
ObDereferenceObject(Context->RequestorsThread);
Context->ThreadReferenced = FALSE;
}
if (FileObject != NULL) {
ObDereferenceObject(FileObject);
Context->FileObject = NULL;
}
} else {
try {
MmProbeAndLockPages(Context->LcbMdl, KernelMode, IoReadAccess);
} except(EXCEPTION_EXECUTE_HANDLER) {
Status = GetExceptionCode();
//
// Tell the user that he just lost data.
//
if (Irp != NULL) {
#if MAGIC_BULLET
if ( RdrEnableMagic ) {
RdrSendMagicBullet(NULL);
DbgPrint( "RDR: About to raise unlock behind hard error for IRP %x\n", Irp );
DbgBreakPoint();
}
#endif
IoRaiseInformationalHardError(Status, NULL, Irp->Tail.Overlay.Thread);
}
RdrWriteErrorLogEntry(
Icb->Fcb->Connection->Server,
IO_ERR_LAYERED_FAILURE,
EVENT_RDR_FAILED_UNLOCK,
Status,
NULL,
0
);
RdrFreeLcb(&Icb->u.f.LockHead, Context->Lcb);
Context->Lcb = NULL;
IoFreeMdl(Context->LcbMdl);
Context->LcbMdl = NULL;
RdrReleaseFcbLock(Icb->Fcb);
if (Context->ThreadReferenced) {
ObDereferenceObject(Context->RequestorsThread);
Context->ThreadReferenced = FALSE;
}
if (FileObject != NULL) {
ObDereferenceObject(FileObject);
Context->FileObject = NULL;
}
}
}
} else {
RdrReleaseFcbLock(Icb->Fcb);
RdrFreeLcb(&Icb->u.f.LockHead, Context->Lcb);
if (Context->ThreadReferenced) {
ObDereferenceObject(Context->RequestorsThread);
Context->ThreadReferenced = FALSE;
}
if (FileObject != NULL) {
ObDereferenceObject(FileObject);
FileObject = NULL;
Context->FileObject = NULL;
}
Context->Lcb = NULL;
}
ASSERT (RdrFindLcb(&Icb->u.f.LockHead, StartingByte, Length.LowPart, Key) == NULL);
}
if (Context->Lcb) {
PREQ_WRITE WriteAndUnlockRequest;
ASSERT(ServerCapabilities & DF_LOCKREAD);
ASSERT(Length.LowPart <= 0xffff);
// DbgBreakPoint();
dprintf(DPRT_FILELOCK, ("WriteAndUnlock request\n"));
WriteAndUnlockRequest = (PREQ_WRITE)(Smb+1);
Smb->Command = SMB_COM_WRITE_AND_UNLOCK;
SmbPutUshort(&WriteAndUnlockRequest->WordCount, 5);
SmbPutUshort(&WriteAndUnlockRequest->Fid, Icb->FileId);
SmbPutUshort(&WriteAndUnlockRequest->Count, (USHORT )(Length.LowPart & 0xffff));
SmbPutUlong(&WriteAndUnlockRequest->Offset, StartingByte.LowPart);
SmbPutUshort(&WriteAndUnlockRequest->ByteCount, (USHORT)((Length.LowPart&0xffff)+3));
WriteAndUnlockRequest->BufferFormat = SMB_FORMAT_DATA;
SmbPutUshort(&WriteAndUnlockRequest->DataLength, (USHORT)(Length.LowPart&0xffff));
SmbBuffer->Mdl->ByteCount = sizeof(SMB_HEADER)+
FIELD_OFFSET(REQ_WRITE, Buffer[0]);
SmbBuffer->Mdl->Next = Context->LcbMdl;
} else if (ServerCapabilities & DF_LANMAN20) {
//
// Note that even though the SMB protocol indicates that you can
// use LOCKING&X to a LANMAN 1.0 server, the LANMAN 1.0 server
// doesn't actually support using LOCKING&X for anything but
// break oplock responses, and in addition, the LANMAN 1.0
// server doesn't support shared locks (and timeouts).
//
PREQ_LOCKING_ANDX LockRequest;
PLOCKING_ANDX_RANGE LockRange;
PNTLOCKING_ANDX_RANGE NtLockRange;
USHORT LockType = 0;
dprintf(DPRT_FILELOCK, ("LockingAndX unlock\n"));
LockRequest = (PREQ_LOCKING_ANDX)(Smb+1);
Smb->Command = SMB_COM_LOCKING_ANDX;
LockRequest->WordCount = 8;
LockRequest->AndXCommand = SMB_COM_NO_ANDX_COMMAND;
SmbPutUshort(&LockRequest->AndXOffset, 0);
SmbPutUshort(&LockRequest->Fid, Icb->FileId);
if (ServerCapabilities & DF_NT_SMBS) {
LockType |= LOCKING_ANDX_LARGE_FILES;
} else {
if (StartingByte.HighPart != 0) {
try_return(Status = STATUS_INVALID_PARAMETER);
}
}
SmbPutUshort(&LockRequest->LockType, LockType);
SmbPutUlong(&LockRequest->Timeout, 0);
SmbPutUshort(&LockRequest->NumberOfUnlocks, 1);
SmbPutUshort(&LockRequest->NumberOfLocks, 0);
if (ServerCapabilities & DF_NT_SMBS) {
SmbPutUshort(&LockRequest->ByteCount, sizeof(NTLOCKING_ANDX_RANGE));
NtLockRange = (PNTLOCKING_ANDX_RANGE )LockRequest->Buffer;
//
// Fill in the lock range in the SMB.
//
SmbPutUshort(&NtLockRange->Pid, RDR_PROCESS_ID);
SmbPutUlong(&NtLockRange->OffsetLow, StartingByte.LowPart);
SmbPutUlong(&NtLockRange->OffsetHigh, StartingByte.HighPart);
SmbPutUlong(&NtLockRange->LengthLow, Length.LowPart);
SmbPutUlong(&NtLockRange->LengthHigh, Length.HighPart);
SmbBuffer->Mdl->ByteCount = sizeof(SMB_HEADER)+
FIELD_OFFSET(REQ_LOCKING_ANDX, Buffer[0])+
sizeof(NTLOCKING_ANDX_RANGE);
} else {
SmbPutUshort(&LockRequest->ByteCount, sizeof(LOCKING_ANDX_RANGE));
LockRange = (PLOCKING_ANDX_RANGE )LockRequest->Buffer;
//
// Fill in the lock range in the SMB.
//
SmbPutUshort(&LockRange->Pid, RDR_PROCESS_ID);
SmbPutUlong(&LockRange->Offset, StartingByte.LowPart);
SmbPutUlong(&LockRange->Length, Length.LowPart);
SmbBuffer->Mdl->ByteCount = sizeof(SMB_HEADER)+
FIELD_OFFSET(REQ_LOCKING_ANDX, Buffer[0])+
sizeof(LOCKING_ANDX_RANGE);
}
} else {
PREQ_UNLOCK_BYTE_RANGE UnlockRequest;
UnlockRequest = (PREQ_UNLOCK_BYTE_RANGE)(Smb+1);
dprintf(DPRT_FILELOCK, ("CoreUnlock request\n"));
Smb->Command = SMB_COM_UNLOCK_BYTE_RANGE;
SmbPutUshort(&UnlockRequest->WordCount, 5);
SmbPutUshort(&UnlockRequest->Fid, Icb->FileId);
SmbPutUlong(&UnlockRequest->Count, Length.LowPart);
SmbPutUlong(&UnlockRequest->Offset, StartingByte.LowPart);
SmbPutUshort(&UnlockRequest->ByteCount, 0);
SmbBuffer->Mdl->ByteCount = sizeof(SMB_HEADER)+
FIELD_OFFSET(REQ_UNLOCK_BYTE_RANGE, Buffer[0]);
}
if (Context->Lcb == NULL) {
Context->Header.TransferSize = SmbBuffer->Mdl->ByteCount +
sizeof(RESP_LOCKING_ANDX);
} else {
Context->Header.TransferSize = SmbBuffer->Mdl->ByteCount +
sizeof(RESP_LOCKING_ANDX) +
Length.LowPart; // Reading the data
}
if (!WaitForCompletion) {
RdrStartAndXBehindOperation(&Icb->u.f.AndXBehind);
AndXBehindStarted = TRUE;
}
Status = RdrNetTranceiveNoWait(NT_NORMAL | NT_NORECONNECT, Irp,
Icb->Fcb->Connection,
SmbBuffer->Mdl,
Context,
UnLockOperationCallback,
Icb->Se,
&Context->Mte);
if (NT_SUCCESS(Status)) {
//
// If we made it to the wire with this request, flag that
// we successfully did so.
//
RequestSubmitted = TRUE;
}
try_return(Status);
try_exit:NOTHING;
} finally {
if (RequestSubmitted && WaitForCompletion) {
CompleteLockOperation(Context);
Status = STATUS_SUCCESS;
} else if (!RequestSubmitted) {
ASSERT (!NT_SUCCESS(Status));
if (Context != NULL) {
if (Context->Lcb != NULL) {
if (Context->RequestorsRThread != 0) {
RdrReleaseFcbLockForThread(Icb->Fcb, Context->RequestorsRThread);
Context->RequestorsRThread = 0;
}
if (Context->LcbMdl != NULL) {
MmUnlockPages(Context->LcbMdl);
IoFreeMdl(Context->LcbMdl);
}
RdrFreeLcb(&Icb->u.f.LockHead, Context->Lcb);
}
if (Context->ThreadReferenced) {
ObDereferenceObject(Context->RequestorsThread);
}
if (Context->FileObject != NULL) {
ObDereferenceObject(Context->FileObject);
}
}
if (AndXBehindStarted) {
RdrEndAndXBehindOperation(&Icb->u.f.AndXBehind);
}
//
// If the request failed, we won't call the completion routine,
// so the SMB buffer won't get freed.
//
if (Context != NULL) {
FREE_POOL(Context);
}
if (SmbBuffer != NULL) {
RdrFreeSMBBuffer(SmbBuffer);
}
} else {
ASSERT (NT_SUCCESS(Status));
ASSERT (!WaitForCompletion );
}
}
dprintf(DPRT_FILELOCK, ("RdrUnlockRange. Returning %X\n", Status));
return Status;
}
DBGSTATIC
STANDARD_CALLBACK_HEADER (
UnLockOperationCallback
)
/*++
Routine Description:
This routine is the callback routine for the processing of an unlock
related SMB (Unlock, Write&Unlock, Write&Locking&X).
Arguments:
IN PSMB_HEADER Smb - SMB response from server.
IN PMPX_ENTRY MpxEntry - MPX table entry for request.
IN PVOID Context - Context from caller.
IN BOOLEAN ErrorIndicator - TRUE if error indication
IN NTSTATUS NetworkErrorCode OPTIONAL - Network error if error indication.
IN OUT PIRP *Irp - IRP from TDI
Return Value:
NTSTATUS - STATUS_PENDING if we are to complete the request
--*/
{
PUNLOCKCONTEXT Context = Ctx;
NTSTATUS Status;
UNREFERENCED_PARAMETER(MpxEntry);
UNREFERENCED_PARAMETER(Irp);
UNREFERENCED_PARAMETER(SmbLength);
UNREFERENCED_PARAMETER(Server);
ASSERT(Context->Header.Type == CONTEXT_UNLOCK);
DISCARDABLE_CODE(RdrFileDiscardableSection);
dprintf(DPRT_FILELOCK, ("UnlockOperationCallback\n"));
Context->Header.ErrorType = NoError; // Assume no error at first.
//
// If we are called because the VC dropped, indicate it in the response
//
if (ErrorIndicator) {
Context->Header.ErrorType = NetError;
Context->Header.ErrorCode = RdrMapNetworkError(NetworkErrorCode);
goto ReturnStatus;
}
if (!NT_SUCCESS(Status = RdrMapSmbError(Smb, Server))) {
RdrWriteErrorLogEntry(
Server,
IO_ERR_LAYERED_FAILURE,
EVENT_RDR_FAILED_UNLOCK,
Status,
Smb,
(USHORT)*SmbLength
);
Context->Header.ErrorType = SMBError;
Context->Header.ErrorCode = Status;
}
if (Status == STATUS_INVALID_HANDLE) {
RdrInvalidateFileId(Context->Icb->NonPagedFcb, Context->Icb->FileId);
}
//
// In the case of a lock operation completing, we don't particularly
// care about the response, so just set the kernel event (if appropriate)
// and return.
//
ReturnStatus:
//
// If this was an async operation, indicate it is now done.
//
if (!Context->WaitForCompletion) {
ExInitializeWorkItem (&Context->WorkHeader, CompleteLockOperation, Context);
ExQueueWorkItem (&Context->WorkHeader, DelayedWorkQueue);
}
//
// You cannot touch the context block at DPC level from this point
// on.
//
KeSetEvent(&Context->Header.KernelEvent, IO_NETWORK_INCREMENT, FALSE);
return STATUS_SUCCESS;
}
PLCB
RdrFindLcb (
IN PLOCKHEAD LockHead,
IN LARGE_INTEGER ByteOffset,
IN ULONG Length,
IN ULONG Key
)
/*++
Routine Description:
This routine will find an LCB for the file described by ICB that matches
the locked region described by ByteOffset, Length and Key.
Arguments:
IN PLOCKHEAD LockHead - Supplies a pointer to the lock structure head.
IN LARGE_INTEGER ByteOffset - Supplies the offset into the file to find.
IN ULONG Length - Supplies the size of the locked region
IN ULONG Key - NT Key used as an additional match for the region.
Return Value:
PLCB - LCB if found, or NULL if no matching LCB could be found.
Note:
The LCB returned by this routine will not necessarily match the input
range exactly. It is used to find the lock that "covers" a region being
read from.
--*/
{
PLIST_ENTRY LcbEntry;
PLCB Lcb;
KIRQL OldIrql;
// PAGED_CODE();
dprintf(DPRT_FILELOCK, ("RdrFindLcb %lx%lx %lx %lx\n", ByteOffset.HighPart, ByteOffset.LowPart, Length, Key));
//
// Acquire the resource, and block until it is available.
//
ACQUIRE_SPIN_LOCK(&RdrLockHeadSpinLock, &OldIrql);
try {
for (LcbEntry = LockHead->LockList.Flink ;
LcbEntry != &LockHead->LockList ;
LcbEntry = LcbEntry->Flink) {
Lcb = CONTAINING_RECORD(LcbEntry, LCB, NextLCB);
//
// The LCB covers the requested region if the requested
// byte offset is greater than the start of the LCB, and
// the last byte in the LCB (byte offset+Length) is less
// than the last byte in the region.
//
//
// ByteOffset > Lcb->ByteOffset &&
// Lcb->ByteOffset+Lcb->Length > ByteOffset+Length
//
dprintf(DPRT_FILELOCK, ("Check LCB %lx. %lx,%lx %lx %lx\n", Lcb, Lcb->ByteOffset.HighPart, Lcb->ByteOffset.LowPart, Lcb->Length, Lcb->Key));
if (Lcb->Key == Key &&
(ByteOffset.QuadPart >= Lcb->ByteOffset.QuadPart) &&
(Lcb->ByteOffset.QuadPart + Lcb->Length) >=
(ByteOffset.QuadPart + Length)) {
try_return(Lcb);
}
}
try_return(Lcb = NULL);
try_exit:NOTHING;
} finally {
RELEASE_SPIN_LOCK(&RdrLockHeadSpinLock, OldIrql);
}
dprintf(DPRT_FILELOCK, ("RdrFindLcb returning %lx\n", Lcb));
return Lcb;
}
PLCB
RdrAllocateLcb (
IN PLOCKHEAD LockHead,
IN LARGE_INTEGER ByteOffset,
IN ULONG Length,
IN ULONG Key
)
/*++
Routine Description:
This routine will allocate an LCB (and lock buffer) for the requested
range of the file. If an existing LCB exists covering this range, it
will return an error.
Arguments:
IN PLOCKHEAD LockHead - Describes the file to apply the lock to.
IN LARGE_INTEGER ByteOffset - Supplies the offset in the file to lock
IN ULONG Length - Supplies the length of the region to lock.
IN ULONG Key - Supplies a key for the lock.
Return Value:
PLCB - LCB allocated if sufficient quota exists to allocate the lock.
Note:
Since these structures are only used for read ahead data, we do NOT charge
quota for them.
--*/
{
PLCB Lcb = NULL, ReturnValue = NULL;
KIRQL OldIrql;
DISCARDABLE_CODE(RdrFileDiscardableSection);
dprintf(DPRT_FILELOCK, ("RdrAllocateLcb(%lx%lx, %lx, %lx)", ByteOffset.HighPart, ByteOffset.LowPart, Length, Key));
try {
ASSERT(Length != 0);
if (RdrFindLcb(LockHead, ByteOffset, Length, Key) == NULL) {
//
// Charge "quota" for this LCB. If there is sufficient "quota"
// available, allocate the LCB structures, otherwise, fail the
// request.
//
ACQUIRE_SPIN_LOCK(&RdrLockHeadSpinLock, &OldIrql);
if (LockHead->QuotaAvailable >= Length) {
LockHead->QuotaAvailable -= Length;
RELEASE_SPIN_LOCK(&RdrLockHeadSpinLock, OldIrql);
} else {
RELEASE_SPIN_LOCK(&RdrLockHeadSpinLock, OldIrql);
try_return(ReturnValue = NULL);
}
//
// Allocate pool for the LCB structure.
//
Lcb = ALLOCATE_POOL(NonPagedPool, sizeof(LCB), POOL_LCB);
if (Lcb == NULL) {
try_return(ReturnValue = NULL);
}
//
// Now allocate pool for the LCB's buffer.
//
Lcb->Buffer = ALLOCATE_POOL(PagedPoolCacheAligned, Length, POOL_LCBBUFFER);
if (Lcb->Buffer == NULL) {
try_return(ReturnValue = NULL);
}
Lcb->Signature = STRUCTURE_SIGNATURE_LCB;
Lcb->ByteOffset = ByteOffset;
Lcb->Length = Length;
Lcb->Key = Key;
Lcb->Flags = 0;
try_return(ReturnValue = Lcb);
} else {
//
// It is legal to find an LCB that covers an existing locked
// region, if a user locks bytes 0-50 of the file, and then
// locks bytes 15-25 of the same file, the lock will be allowed,
// and we will find an LCB covering the range. In this case,
// we want to return NULL, since we don't want to attempt to
// do a Lock&Read on the file (we would get cache consistancy
// problems otherwise.
//
InternalError(("Allocating an LCB where one already exists!!\n"));
try_return(ReturnValue = NULL);
}
try_exit:NOTHING;
} finally {
if (ReturnValue == NULL) {
if (Lcb != NULL) {
if (Lcb->Buffer != NULL) {
FREE_POOL(Lcb->Buffer);
}
FREE_POOL(Lcb);
}
}
}
dprintf(DPRT_FILELOCK, ("RdrAllocateLcb, return %lx", ReturnValue));
return ReturnValue;
}
VOID
RdrInsertLock (
IN PLOCKHEAD LockHead,
IN PLCB Lcb
)
/*++
Routine Description:
This routine is called when a region has been successfully locked. It will
insert the lock structure described in the list of outstanding LCB's for the
supplied lockhead.
Arguments:
PLOCKHEAD LockHead - Supplies the lock head for the lock request.
PLCB Lcb - Supplies the LCB to free.
Return Value:
None.
--*/
{
KIRQL OldIrql;
DISCARDABLE_CODE(RdrFileDiscardableSection);
dprintf(DPRT_FILELOCK, ("RdrInsertLock %lx\n", Lcb));
ACQUIRE_SPIN_LOCK(&RdrLockHeadSpinLock, &OldIrql);
InsertHeadList(&LockHead->LockList, &Lcb->NextLCB);
RELEASE_SPIN_LOCK(&RdrLockHeadSpinLock, OldIrql);
}
VOID
RdrRemoveLock (
IN PLOCKHEAD LockHead,
IN PLCB Lcb
)
/*++
Routine Description:
This routine is called when a region has been successfully locked. It will
insert the lock structure described in the list of outstanding LCB's for the
supplied lockhead.
Arguments:
PLOCKHEAD LockHead - Supplies the lock head for the lock request.
PLCB Lcb - Supplies the LCB to free.
Return Value:
None.
--*/
{
KIRQL OldIrql;
DISCARDABLE_CODE(RdrFileDiscardableSection);
dprintf(DPRT_FILELOCK, ("RdrRemoveLock %lx\n", Lcb));
ACQUIRE_SPIN_LOCK(&RdrLockHeadSpinLock, &OldIrql);
RemoveEntryList(&Lcb->NextLCB);
RELEASE_SPIN_LOCK(&RdrLockHeadSpinLock, OldIrql);
}
VOID
RdrFreeLcb (
PLOCKHEAD LockHead,
PLCB Lcb
)
/*++
Routine Description:
This routine will free up the pool associated with an LCB. It is called
both when the lock request failed, and when the region is being unlocked.
Arguments:
PLOCKHEAD LockHead - Supplies the lock head for the lock request.
PLCB Lcb - Supplies the LCB to free.
Return Value:
None.
--*/
{
KIRQL OldIrql;
DISCARDABLE_CODE(RdrFileDiscardableSection);
dprintf(DPRT_FILELOCK, ("RdrFreeLcb %lx\n", Lcb));
//
// Return the "quota" for this LCB. This allows us to put other locks
// on the file.
//
ACQUIRE_SPIN_LOCK(&RdrLockHeadSpinLock, &OldIrql);
LockHead->QuotaAvailable += Lcb->Length;
RELEASE_SPIN_LOCK(&RdrLockHeadSpinLock, OldIrql);
FREE_POOL(Lcb->Buffer);
FREE_POOL(Lcb);
}
VOID
RdrUninitializeLockHead (
IN PLOCKHEAD LockHead
)
/*++
Routine Description:
This routine initializes a redirector lock head.
Arguments:
IN PLOCKHEAD LockHead - Supplies the lock head to initialize
Return Value:
None.
--*/
{
PLIST_ENTRY LcbEntry, NextEntry;
PLCB Lcb;
KIRQL OldIrql;
DISCARDABLE_CODE(RdrFileDiscardableSection);
ASSERT (LockHead->Signature == STRUCTURE_SIGNATURE_LOCKHEAD);
dprintf(DPRT_FILELOCK, ("RdrUninitializeLockHead %lx\n", LockHead));
//
// It is possible that there may be Lcb's left on the chain of
// locks if a VC goes down at the wrong time.
//
ACQUIRE_SPIN_LOCK(&RdrLockHeadSpinLock, &OldIrql);
for (LcbEntry = LockHead->LockList.Flink;
LcbEntry != &LockHead->LockList ;
LcbEntry = NextEntry) {
Lcb = CONTAINING_RECORD(LcbEntry, LCB, NextLCB);
RemoveEntryList(&Lcb->NextLCB);
LockHead->QuotaAvailable += Lcb->Length;
//
// Release the spin lock protecting the list while
// we free the buffer and lcb structures.
//
RELEASE_SPIN_LOCK(&RdrLockHeadSpinLock, OldIrql);
FREE_POOL(Lcb->Buffer);
ACQUIRE_SPIN_LOCK(&RdrLockHeadSpinLock, &OldIrql);
NextEntry = Lcb->NextLCB.Flink;
FREE_POOL(Lcb);
}
RELEASE_SPIN_LOCK(&RdrLockHeadSpinLock, OldIrql);
}
VOID
RdrTruncateLockHeadForFcb (
IN PFCB Fcb
)
/*++
Routine Description:
This routine discards or writes lock range readahead/writebehind
data when a file is truncated.
Arguments:
IN PICB Fcb - Supplies the FCB being truncated.
Return Value:
None.
--*/
{
PLIST_ENTRY IcbEntry;
PICB Icb;
PAGED_CODE();
ASSERT (ExIsResourceAcquiredExclusive(Fcb->Header.Resource));
dprintf(DPRT_FILELOCK, ("RdrTruncateLockHeadForFcb %lx\n", Fcb));
if ( Fcb->NonPagedFcb->Type == DiskFile ) {
for (IcbEntry = Fcb->InstanceChain.Flink ;
IcbEntry != &Fcb->InstanceChain ;
IcbEntry = IcbEntry->Flink) {
Icb = CONTAINING_RECORD(IcbEntry, ICB, InstanceNext);
if ( (Icb->Type == DiskFile) &&
(Icb->Flags & ICB_OPENED) ) {
RdrTruncateLockHeadForIcb( Icb );
}
}
}
dprintf(DPRT_FILELOCK, ("RdrTruncateLockHeadForFcb %lx done\n", Fcb));
return;
}
VOID
RdrTruncateLockHeadForIcb (
IN PICB Icb
)
/*++
Routine Description:
This routine discards or writes lock range readahead/writebehind
data when a file is truncated.
Arguments:
IN PICB Icb - Supplies the ICB being truncated.
Return Value:
None.
--*/
{
PLOCKHEAD LockHead = &Icb->u.f.LockHead;
PLIST_ENTRY LcbEntry;
PLCB Lcb;
LARGE_INTEGER FileSize = Icb->Fcb->Header.FileSize;
LARGE_INTEGER BufferEnd;
PAGED_CODE();
dprintf(DPRT_FILELOCK, ("RdrTruncateLockHeadForIcb %lx %lx\n", Icb, LockHead));
ASSERT (Icb->Type == DiskFile);
ASSERT (LockHead->Signature == STRUCTURE_SIGNATURE_LOCKHEAD);
ASSERT (ExIsResourceAcquiredExclusive(Icb->Fcb->Header.Resource));
LcbEntry = LockHead->LockList.Flink;
while ( LcbEntry != &LockHead->LockList ) {
Lcb = CONTAINING_RECORD(LcbEntry, LCB, NextLCB);
LcbEntry = LcbEntry->Flink;
//
// If this buffer starts beyond the new end of file, remove it
// from the list and discard it.
//
if ( Lcb->ByteOffset.QuadPart > FileSize.QuadPart ) {
RemoveEntryList( &Lcb->NextLCB );
dprintf(DPRT_FILELOCK, ("RdrTruncateLockHeadForIcb: LCB %lx discarded\n", Lcb));
LockHead->QuotaAvailable += Lcb->Length;
FREE_POOL(Lcb->Buffer);
FREE_POOL(Lcb);
} else {
//
// If this buffer ends beyond the new end of file, then write
// it or discard it.
//
BufferEnd.QuadPart = Lcb->ByteOffset.QuadPart + Lcb->Length;
if ( BufferEnd.QuadPart > FileSize.QuadPart ) {
RemoveEntryList( &Lcb->NextLCB );
LockHead->QuotaAvailable += Lcb->Length;
if (!FlagOn(Lcb->Flags, LCB_DIRTY)) {
//
// The data in the LCB is not dirty. Discard the LCB.
//
dprintf(DPRT_FILELOCK, ("RdrTruncateLockHeadForIcb: LCB %lx discarded\n", Lcb));
} else {
NTSTATUS Status;
BOOLEAN AllDataWritten;
ULONG AmountActuallyWritten;
//
// The data in the LCB is dirty. Write it and discard
// the LCB.
//
dprintf(DPRT_FILELOCK, ("RdrTruncateLockHeadForIcb: LCB %lx truncated\n", Lcb));
Lcb->Length =(ULONG) (FileSize.QuadPart - Lcb->ByteOffset.QuadPart);
Status = RdrWriteRange(
NULL,
Icb->u.f.FileObject,
NULL,
Lcb->Buffer,
Lcb->Length,
Lcb->ByteOffset,
TRUE,
NULL,
NULL,
&AllDataWritten,
&AmountActuallyWritten
);
if ( !NT_SUCCESS(Status) || !AllDataWritten ) {
ULONG DataBuffer[2];
DataBuffer[0] = Lcb->Length;
DataBuffer[1] = AmountActuallyWritten;
RdrWriteErrorLogEntry(Icb->Fcb->Connection->Server,
IO_ERR_LAYERED_FAILURE,
EVENT_RDR_WRITE_BEHIND_FLUSH_FAILED,
Status,
&DataBuffer,
sizeof(DataBuffer)
);
}
}
FREE_POOL(Lcb->Buffer);
FREE_POOL(Lcb);
}
}
}
dprintf(DPRT_FILELOCK, ("RdrTruncateLockHeadForIcb %lx %lx done\n", Icb, LockHead));
return;
}
VOID
RdrInitializeAndXBehind(
IN PAND_X_BEHIND AndXBehind
)
/*++
Routine Description:
This routine initializes and AND_X_BEHIND structure.
The redirectors AND_X_BEHIND structure is used for write behind, unlock
behind, and other related asynchronous operations. To use it, you
first initialize an AND_X_BEHIND structure calling RdrInitializeAndXBehind.
Whenever you initiate a "behind" operation, you call
RdrStartAndXBehindOperation, and when the operation completes, you call
RdrEndAndXBehindOperation.
If you have something that must be synchronized with a "behind"
operation (for example, you cannot perform a read or write until an unlock
behind operation completes), call RdrWaitForAndXBehindOperation.
Arguments:
IN PAND_X_BEHIND AndXBehind - Supplies the structure to initialize
Return Value:
None.
--*/
{
PAGED_CODE();
dprintf(DPRT_FILELOCK, ("InitializeAndXBehind: %lx\n", AndXBehind));
KeInitializeSpinLock(&AndXBehind->BehindOperationLock);
AndXBehind->NumberOfBehindOperations = 0;
KeInitializeEvent(&AndXBehind->BehindOperationCompleted, NotificationEvent, TRUE);
}
VOID
RdrStartAndXBehindOperation(
IN PAND_X_BEHIND AndXBehind
)
/*++
Routine Description:
This routine is called when a "Behind" operation is started.
It will lock the AndXBehind structure, make sure the completion event
is set to the NOT_SIGNALLED state, and incrememnt the number of operations
waiting on the request.
Arguments:
IN PAND_X_BEHIND AndXBehind - Supplies the structure to initialize
Return Value:
None.
--*/
{
KIRQL OldIrql;
DISCARDABLE_CODE(RdrFileDiscardableSection);
//
// Acquire the lock protecting the AndXBehind structure.
//
ACQUIRE_SPIN_LOCK(&AndXBehind->BehindOperationLock, &OldIrql);
dprintf(DPRT_FILELOCK, ("StartAndXBehind: %lx. Count now %lx\n", AndXBehind, AndXBehind->NumberOfBehindOperations+1));
if (AndXBehind->NumberOfBehindOperations == 0) {
//
// Make sure that any calls to RdrWaitForAndXBehindOperation will wait
// until the data is available.
//
KeClearEvent(&AndXBehind->BehindOperationCompleted);
}
//
// Remember that another behind operation is active.
AndXBehind->NumberOfBehindOperations += 1;
RELEASE_SPIN_LOCK(&AndXBehind->BehindOperationLock, OldIrql);
}
VOID
RdrEndAndXBehindOperation(
IN PAND_X_BEHIND AndXBehind
)
/*++
Routine Description:
This routine is called when a "Behind" operation is completed.
It will lock the AndXBehind structure, decrement the number of AndXBehind
operations, and if there are no longer any outstanding, will set the event
to the SIGNALLED state.
Arguments:
IN PAND_X_BEHIND AndXBehind - Supplies the structure to initialize
Return Value:
None.
--*/
{
KIRQL OldIrql;
DISCARDABLE_CODE(RdrFileDiscardableSection);
//
// Acquire the lock protecting the AndXBehind structure.
//
ACQUIRE_SPIN_LOCK(&AndXBehind->BehindOperationLock, &OldIrql);
dprintf(DPRT_FILELOCK, ("EndAndXBehind: %lx. Count now %lx\n", AndXBehind, AndXBehind->NumberOfBehindOperations-1));
ASSERT (AndXBehind->NumberOfBehindOperations > 0);
//
// Remember that another behind operation is active.
//
AndXBehind->NumberOfBehindOperations -= 1;
if (AndXBehind->NumberOfBehindOperations == 0) {
//
// Wake anyone waiting on the last AndX behind operation to complete.
//
KeSetEvent(&AndXBehind->BehindOperationCompleted, 0, FALSE);
}
RELEASE_SPIN_LOCK(&AndXBehind->BehindOperationLock, OldIrql);
}
VOID
RdrWaitForAndXBehindOperation(
IN PAND_X_BEHIND AndXBehind
)
/*++
Routine Description:
This routine is called to wait until all AndXBehind operations are
completed
Arguments:
IN PAND_X_BEHIND AndXBehind - Supplies the structure to initialize
Return Value:
None.
--*/
{
KIRQL OldIrql;
DISCARDABLE_CODE(RdrFileDiscardableSection);
dprintf(DPRT_FILELOCK, ("WaitForAndXBehind: %lx. Count now %lx\n", AndXBehind, AndXBehind->NumberOfBehindOperations));
//
// Early out if there are no andx behind operations pending.
//
if (AndXBehind->NumberOfBehindOperations == 0) {
return;
}
//
// Wait until the last AndX Behind operation completes.
//
KeWaitForSingleObject(&AndXBehind->BehindOperationCompleted,
Executive,
KernelMode,
FALSE,
NULL);
//
// Acquire and release the lock protecting the AndXBehind structure.
// This gives RdrEndAndXBehindOperation a chance to finish its work
// before our caller can delete the AndXBehind structure.
//
ACQUIRE_SPIN_LOCK(&AndXBehind->BehindOperationLock, &OldIrql);
RELEASE_SPIN_LOCK(&AndXBehind->BehindOperationLock, OldIrql);
}