/*++ Copyright (c) 1989 Microsoft Corporation Module Name: FileLock.c Abstract: The file lock package provides a set of routines that allow the caller to handle byte range file lock requests. A variable of type FILE_LOCK is needed for every file with byte range locking. The package provides routines to set and clear locks, and to test for read or write access to a file with byte range locks. The main idea of the package is to have the file system initialize a FILE_LOCK variable for every data file as its opened, and then to simply call a file lock processing routine to handle all IRP's with a major function code of LOCK_CONTROL. The package is responsible for keeping track of locks and for completing the LOCK_CONTROL IRPS. When processing a read or write request the file system can then call two query routines to check for access. Most of the code for processing IRPS and checking for access use paged pool and can encounter a page fault, therefore the check routines cannot be called at DPC level. To help servers that do call the file system to do read/write operations at DPC level there is a additional routine that simply checks for the existence of a lock on a file and can be run at DPC level. Concurrent access to the FILE_LOCK variable must be controlled by the caller. The functions provided in this package are as follows: o FsRtlInitializeFileLock - Initialize a new FILE_LOCK structure. o FsRtlUninitializeFileLock - Uninitialize an existing FILE_LOCK structure. o FsRtlProcessFileLock - Process an IRP whose major function code is LOCK_CONTROL. o FsRtlCheckLockForReadAccess - Check for read access to a range of bytes in a file given an IRP. o FsRtlCheckLockForWriteAccess - Check for write access to a range of bytes in a file given an IRP. o FsRtlAreThereCurrentFileLocks - Check if there are any locks currently assigned to a file. o FsRtlGetNextFileLock - This procedure enumerates the current locks of a file lock variable. o FsRtlFastCheckLockForRead - Check for read access to a range of bytes in a file given separate parameters. o FsRtlFastCheckLockForWrite - Check for write access to a range of bytes in a file given separate parameters. o FsRtlFastLock - A fast non-Irp based way to get a lock o FsRtlFastUnlockSingle - A fast non-Irp based way to release a single lock o FsRtlFastUnlockAll - A fast non-Irp based way to release all locks held by a file object. o FsRtlFastUnlockAllByKey - A fast non-Irp based way to release all locks held by a file object that match a key. Authors: Gary Kimura [GaryKi] 24-Apr-1990 Dan Lovinger [DanLo] 22-Sep-1995 Revision History: --*/ #include "FsRtlP.h" #define FsRtlAllocateSharedLock( C ) { \ PKPRCB Prcb = KeGetCurrentPrcb(); \ *(C) = (PSH_LOCK) PopEntryList(&Prcb->FsRtlFreeSharedLockList); \ if (*(C) == NULL) { \ *(C) = FsRtlAllocatePool( NonPagedPool, sizeof(SH_LOCK) ); \ } \ } #define FsRtlAllocateExclusiveLock( C ) { \ PKPRCB Prcb = KeGetCurrentPrcb(); \ *(C) = (PEX_LOCK) PopEntryList(&Prcb->FsRtlFreeExclusiveLockList); \ if (*(C) == NULL) { \ *(C) = FsRtlAllocatePool( NonPagedPool, sizeof(EX_LOCK) ); \ } \ } #define FsRtlAllocateLockTreeNode( C ) { \ PKPRCB Prcb = KeGetCurrentPrcb(); \ *(C) = (PLOCKTREE_NODE) PopEntryList(&Prcb->FsRtlFreeLockTreeNodeList); \ if (*(C) == NULL) { \ *(C) = FsRtlAllocatePool( NonPagedPool, sizeof(LOCKTREE_NODE) ); \ } \ } #define FsRtlAllocateWaitingLock( C ) { \ PKPRCB Prcb = KeGetCurrentPrcb(); \ *(C) = (PWAITING_LOCK) PopEntryList(&Prcb->FsRtlFreeWaitingLockList); \ if (*(C) == NULL) { \ *(C) = FsRtlAllocatePool( NonPagedPool, sizeof(WAITING_LOCK) ); \ } \ } #define FsRtlFreeSharedLock( C ) { \ PKPRCB Prcb = KeGetCurrentPrcb(); \ PushEntryList(&Prcb->FsRtlFreeSharedLockList, &(C)->Link); \ } #define FsRtlFreeExclusiveLock( C ) { \ PKPRCB Prcb = KeGetCurrentPrcb(); \ PushEntryList(&Prcb->FsRtlFreeExclusiveLockList, &(C)->Link); \ } #define FsRtlFreeLockTreeNode( C ) { \ PKPRCB Prcb = KeGetCurrentPrcb(); \ PushEntryList(&Prcb->FsRtlFreeLockTreeNodeList, &(C)->Locks); \ } #define FsRtlFreeWaitingLock( C ) { \ PKPRCB Prcb = KeGetCurrentPrcb(); \ PushEntryList(&Prcb->FsRtlFreeWaitingLockList, &(C)->Link); \ } #define FsRtlAcquireLockQueue(a,b) \ ExAcquireSpinLock(&(a)->QueueSpinLock, b); #define FsRtlReacquireLockQueue(a,b,c) \ ExAcquireSpinLock(&(b)->QueueSpinLock, c); #define FsRtlReleaseLockQueue(a,b) \ ExReleaseSpinLock(&(a)->QueueSpinLock, b); #define FsRtlCompleteLockIrp(_FileLock, _Context, _Irp, _Status, _NewStatus, _FileObject) \ if (_FileLock->CompleteLockIrpRoutine != NULL) { \ if ((_FileObject) != NULL) { \ ((PFILE_OBJECT)(_FileObject))->LastLock = NULL; \ } \ _Irp->IoStatus.Status = _Status; \ *_NewStatus = _FileLock->CompleteLockIrpRoutine(_Context, _Irp); \ } else { \ FsRtlCompleteRequest( _Irp, _Status ); \ *_NewStatus = _Status; \ } // // Define USERTEST to get a version which compiles into a usermode test rig // #ifdef USERTEST #include #include #undef FsRtlAllocateSharedLock #undef FsRtlAllocateExclusiveLock #undef FsRtlAllocateLockTreeNode #undef FsRtlAllocateWaitingLock #undef FsRtlFreeSharedLock #undef FsRtlFreeExclusiveLock #undef FsRtlFreeLockTreeNode #undef FsRtlFreeWaitingLock #undef FsRtlAcquireLockQueue #undef FsRtlReacquireLockQueue #undef FsRtlReleaseLockQueue #undef FsRtlCompleteLockIrp #undef IoCompleteRequest #define FsRtlAllocateSharedLock( C ) *(C) = (PSH_LOCK)malloc(sizeof(SH_LOCK)) #define FsRtlAllocateExclusiveLock( C ) *(C) = (PEX_LOCK)malloc(sizeof(EX_LOCK)) #define FsRtlAllocateLockTreeNode( C ) *(C) = (PLOCKTREE_NODE)malloc(sizeof(LOCKTREE_NODE)) #define FsRtlAllocateWaitingLock( C ) *(C) = (PWAITING_LOCK)malloc(sizeof(WAITING_LOCK)) #define FsRtlFreeSharedLock( C ) free(C) #define FsRtlFreeExclusiveLock( C ) free(C) #define FsRtlFreeLockTreeNode( C ) free(C) #define FsRtlFreeWaitingLock( C ) free(C) #define FsRtlAcquireLockQueue(a,b) (*(b) = '\0') #define FsRtlReacquireLockQueue(a,b,c) (*(c) = '\0') #define FsRtlReleaseLockQueue(a,b) #define FsRtlCompleteLockIrp(_FileLock, _Context, _Irp, _Status, _NewStatus, _FileObject) \ { \ DbgBreakPoint(); \ *_NewStatus = STATUS_SUCCESS; \ } #define ExReleaseFastMutex(M) #define ExAcquireFastMutex(M) #define KeInitializeSpinLock(L) #define KfRaiseIrql(L) ('\0') #define KfLowerIrql(I) #define IoAcquireCancelSpinLock(I) #define IoReleaseCancelSpinLock(I) #define IoCompleteRequest(I, S) #endif FAST_MUTEX FsRtlCreateLockInfo; // // Local debug trace level // #define Dbg (0x20000000) #define FREE_LOCK_SIZE 16 /*++ Some of the decisions made regarding the internal datastructres may not be clear, so I should discuss the evolution of this design. The original file lock implementation was a single linked list, extended in the MP case to a set of linked lists which each held locks in page-aligned segments of the file. If locks spilled over these page-aligned segments the code fell back to the UP single linked list. There are clearly peformance implications with substantial usage of file locks, since these are mandatory locks. This implementation goes for O(lgn) search performance by using splay trees. In order to apply simple trees to this problem no node of the tree can overlap, so since shared locks can in fact overlap something must be done. The solution used here is to have a meta-structure contain all locks which do overlap and have the tree operations split and merge these nodes of (potentially) multiple locks. This is the LOCKTREE_NODE. It should be noted that the worst case add/delete lock times are still linear. Exclusive locks pose a problem because of an asymmetry in the semantics of applying locks to a file. If a process applies a shared lock to a section of a file, no application of an exclusive lock to bytes in that section can succeed. However, if a process applies an exclusive lock, that same process can get a shared lock as well. This behavior conflicts with the mergeable node since by applying locks in a given order we can get a node to have many shared locks and "rogue" exclusive locks which are hidden except to a linear search, which is what we're designing out. So exclusive locks must be seperated from the shared locks. This is the reason we have two lock trees. Since we have two lock trees, the worst case search is now O(lgm + lgn) for m exlcusive and n shared. Also, since no exclusive locks can ever overlap each other it is now unreasonable to have them use LOCKTREE_NODES - this would impose a memory penalty on code which was weighted toward exclusive locks. This means that the exclusive locks should be wired into the splay tree directly. So we need an RTL_SPLAY_LINKS, but this is 64 bits bigger than the SINGLE_LIST_ENTRY which shared locks need (to be threaded off of a LOCKTREE_NODE), which dictates seperate shared and exclusive lock structures to avoid penalizing code which was weighted toward shared locks by having that wasted 64 bits per lock. Hence EX_LOCK and SH_LOCK. Zero length locks are a bizzare creation, and there is some errata relating to them. It used to be the case that zero length locks would be granted without exception. This is flat out against the spec, and has been dropped. They are now subject to failure if they occupy a point interior to a lock of a type that can cause an access failure. A particular case that was previously allowed was a zero length exclusive lock interior to another exclusive lock. Zero length locks cannot conflict with zero length locks. This is the subject of some special code throughout the module. Note especially that zero length exclusive locks can overlap. Zero length locks also cannot conflict at the starting byte and ending byte of a range - they are points on the line. --*/ typedef struct { // // List of locks under this node // SINGLE_LIST_ENTRY Locks; // // Maximum byte offset affected by locks under Locks // Note: minimum offset is the starting offset of the // first lock at this node. // ULONGLONG Extent; // // Splay tree links to parent, lock groups strictly less than // and lock groups strictly greater than locks under Locks // RTL_SPLAY_LINKS Links; // // Last lock in the list (useful for node collapse under insert) // SINGLE_LIST_ENTRY Tail; } LOCKTREE_NODE, *PLOCKTREE_NODE; // // Define the threading wrappers for lock information // // // Each shared lock record corresponds to a current granted lock and is // maintained in a queue off of a LOCKTREE_NODE's Locks list. The list // of current locks is ordered according to the starting byte of the lock. // typedef struct _SH_LOCK { // // The link structures for the list of shared locks. // (must be first element - see FsRtlPrivateLimitFreeLockList) // SINGLE_LIST_ENTRY Link; // // The actual locked range // FILE_LOCK_INFO LockInfo; } SH_LOCK; typedef SH_LOCK *PSH_LOCK; // // Each exclusive lock record corresponds to a current granted lock and is // threaded into the exclusive lock tree. // typedef struct _EX_LOCK { // // The link structures for the list of current locks. // (must be first element - see FsRtlPrivateLimitFreeLockList) // union { // // Simple list reference for the freelist // SINGLE_LIST_ENTRY Link; // // The actual splay links when inserted // RTL_SPLAY_LINKS Links; }; // // The actual locked range // FILE_LOCK_INFO LockInfo; } EX_LOCK; typedef EX_LOCK *PEX_LOCK; // // Each Waiting lock record corresponds to a IRP that is waiting for a // lock to be granted and is maintained in a queue off of the FILE_LOCK's // WaitingLockQueue list. // typedef struct _WAITING_LOCK { // // The link structures for the list of waiting locks // (must be first element - see FsRtlPrivateLimitFreeLockList) // SINGLE_LIST_ENTRY Link; // // The context field to use when completing the irp via the alternate // routine // PVOID Context; // // A pointer to the IRP that is waiting for a lock // PIRP Irp; } WAITING_LOCK; typedef WAITING_LOCK *PWAITING_LOCK; // // Each lock or waiting onto some lock queue. // typedef struct _LOCK_QUEUE { // // SpinLock to guard queue access // KSPIN_LOCK QueueSpinLock; // // The items contain locktrees of the current granted // locks and a list of the waiting locks // PRTL_SPLAY_LINKS SharedLockTree; PRTL_SPLAY_LINKS ExclusiveLockTree; SINGLE_LIST_ENTRY WaitingLocks; SINGLE_LIST_ENTRY WaitingLocksTail; } LOCK_QUEUE, *PLOCK_QUEUE; // // Any file_lock which has had a lock applied gets non-paged pool // lock_info structure which tracks the current locks applied to // the file // typedef struct _LOCK_INFO { // // LowestLockOffset retains the offset of the lowest existing // lock. This facilitates a quick check to see if a read or // write can proceed without locking the lock database. This is // helpful for applications that use mirrored locks -- all locks // are higher than file data. // // If the lowest lock has an offset > 0xffffffff, LowestLockOffset // is set to 0xffffffff. // ULONG LowestLockOffset; // // The optional procedure to call to complete a request // PCOMPLETE_LOCK_IRP_ROUTINE CompleteLockIrpRoutine; // // The optional procedure to call when unlocking a byte range // PUNLOCK_ROUTINE UnlockRoutine; // // The locked ranges // LOCK_QUEUE LockQueue; } LOCK_INFO, *PLOCK_INFO; // // The following routines are private to this module // VOID FsRtlSplitLocks ( IN PLOCKTREE_NODE ParentNode, IN PSINGLE_LIST_ENTRY *pStartLink, IN PLARGE_INTEGER LastShadowedByte, IN PLARGE_INTEGER GlueOffset ); PRTL_SPLAY_LINKS FsRtlFindFirstOverlappingSharedNode ( IN PRTL_SPLAY_LINKS Tree, IN PLARGE_INTEGER StartingByte, IN PLARGE_INTEGER EndingByte, IN OUT PRTL_SPLAY_LINKS *LastEdgeNode, IN OUT PBOOLEAN GreaterThan ); PRTL_SPLAY_LINKS FsRtlFindFirstOverlappingExclusiveNode ( IN PRTL_SPLAY_LINKS Tree, IN PLARGE_INTEGER StartingByte, IN PLARGE_INTEGER EndingByte, IN OUT PRTL_SPLAY_LINKS *LastEdgeNode, IN OUT PBOOLEAN GreaterThan ); VOID FsRtlPrivateInsertLock ( IN PLOCK_INFO LockInfo, IN PFILE_OBJECT FileObject, IN PFILE_LOCK_INFO FileLockInfo ); VOID FsRtlPrivateInsertSharedLock ( IN PLOCK_QUEUE LockQueue, IN PSH_LOCK NewLock ); VOID FsRtlPrivateInsertExclusiveLock ( IN PLOCK_QUEUE LockQueue, IN PEX_LOCK NewLock ); VOID FsRtlPrivateCheckWaitingLocks ( IN PLOCK_INFO LockInfo, IN PLOCK_QUEUE LockQueue, IN KIRQL OldIrql ); VOID FsRtlPrivateCancelFileLockIrp ( IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp ); BOOLEAN FsRtlPrivateCheckForExclusiveLockAccess ( IN PLOCK_QUEUE LockInfo, IN PFILE_LOCK_INFO FileLockInfo ); BOOLEAN FsRtlPrivateCheckForSharedLockAccess ( IN PLOCK_QUEUE LockInfo, IN PFILE_LOCK_INFO FileLockInfo ); NTSTATUS FsRtlPrivateFastUnlockAll ( IN PFILE_LOCK FileLock, IN PFILE_OBJECT FileObject, IN PEPROCESS ProcessId, IN ULONG Key, IN BOOLEAN MatchKey, IN PVOID Context OPTIONAL ); BOOLEAN FsRtlPrivateInitializeFileLock ( IN PFILE_LOCK FileLock, IN BOOLEAN ViaFastCall ); VOID FsRtlPrivateRemoveLock ( IN PLOCK_INFO LockInfo, IN PFILE_LOCK_INFO, IN BOOLEAN CheckForWaiters ); VOID FsRtlPrivateLimitFreeLockList ( IN PSINGLE_LIST_ENTRY Link ); BOOLEAN FsRtlCheckNoSharedConflict ( IN PLOCK_QUEUE LockQueue, IN PLARGE_INTEGER Starting, IN PLARGE_INTEGER Ending ); BOOLEAN FsRtlCheckNoExclusiveConflict ( IN PLOCK_QUEUE LockQueue, IN PLARGE_INTEGER Starting, IN PLARGE_INTEGER Ending, IN ULONG Key, IN PFILE_OBJECT FileObject, IN PVOID ProcessId ); VOID FsRtlPrivateResetLowestLockOffset ( PLOCK_INFO LockInfo ); NTSTATUS FsRtlFastUnlockSingleShared ( IN PLOCK_INFO LockInfo, IN PFILE_OBJECT FileObject, IN LARGE_INTEGER UNALIGNED *FileOffset, IN PLARGE_INTEGER Length, IN PEPROCESS ProcessId, IN ULONG Key, IN PVOID Context OPTIONAL, IN BOOLEAN IgnoreUnlockRoutine, IN BOOLEAN CheckForWaiters ); NTSTATUS FsRtlFastUnlockSingleExclusive ( IN PLOCK_INFO LockInfo, IN PFILE_OBJECT FileObject, IN LARGE_INTEGER UNALIGNED *FileOffset, IN PLARGE_INTEGER Length, IN PEPROCESS ProcessId, IN ULONG Key, IN PVOID Context OPTIONAL, IN BOOLEAN IgnoreUnlockRoutine, IN BOOLEAN CheckForWaiters ); VOID FsRtlInitProcessorLockQueue ( VOID ) /*++ Routine Description: Initializes and pre-allocates some lock structures for this processor. Arguments: None Return Value: None. --*/ { #ifndef USERTEST PKPRCB Prcb; #if !defined(NT_UP) ULONG Count; PSH_LOCK ShLock; PEX_LOCK ExLock; PLOCKTREE_NODE Node; PWAITING_LOCK WaitingLock; #endif Prcb = KeGetCurrentPrcb(); Prcb->FsRtlFreeSharedLockList.Next = NULL; Prcb->FsRtlFreeExclusiveLockList.Next = NULL; Prcb->FsRtlFreeLockTreeNodeList.Next = NULL; Prcb->FsRtlFreeWaitingLockList.Next = NULL; #if !defined(NT_UP) for (Count = FREE_LOCK_SIZE/2; Count; Count--) { ShLock = FsRtlAllocatePool( NonPagedPool, sizeof(SH_LOCK) ); PushEntryList( &Prcb->FsRtlFreeSharedLockList, &ShLock->Link ); ExLock = FsRtlAllocatePool( NonPagedPool, sizeof(EX_LOCK) ); PushEntryList( &Prcb->FsRtlFreeExclusiveLockList, &ExLock->Link ); WaitingLock = FsRtlAllocatePool( NonPagedPool, sizeof(WAITING_LOCK) ); PushEntryList( &Prcb->FsRtlFreeWaitingLockList, &WaitingLock->Link ); // // The Locks field is overloaded for the freelist (still first in the // structure as per other requirements) // Node = FsRtlAllocatePool( NonPagedPool, sizeof(LOCKTREE_NODE) ); PushEntryList( &Prcb->FsRtlFreeLockTreeNodeList, &Node->Locks ); } #endif #endif } VOID FsRtlInitializeFileLock ( IN PFILE_LOCK FileLock, IN PCOMPLETE_LOCK_IRP_ROUTINE CompleteLockIrpRoutine OPTIONAL, IN PUNLOCK_ROUTINE UnlockRoutine OPTIONAL ) /*++ Routine Description: This routine initializes a new FILE_LOCK structure. The caller must supply the memory for the structure. This call must precede all other calls that utilize the FILE_LOCK variable. Arguments: FileLock - Supplies a pointer to the FILE_LOCK structure to initialize. CompleteLockIrpRoutine - Optionally supplies an alternate routine to call for completing IRPs. FsRtlProcessFileLock by default will call IoCompleteRequest to finish up an IRP; however if the caller want to process the completion itself then it needs to specify a completion routine here. This routine will then be called in place of IoCompleteRequest. UnlockRoutine - Optionally supplies a routine to call when removing a lock. Return Value: None. --*/ { DebugTrace(+1, Dbg, "FsRtlInitializeFileLock, FileLock = %08lx\n", FileLock); // // Clear non-paged pool pointer // FileLock->LockInformation = NULL; FileLock->CompleteLockIrpRoutine = CompleteLockIrpRoutine; FileLock->UnlockRoutine = UnlockRoutine; FileLock->FastIoIsQuestionable = FALSE; // // and return to our caller // DebugTrace(-1, Dbg, "FsRtlInitializeFileLock -> VOID\n", 0 ); return; } BOOLEAN FsRtlPrivateInitializeFileLock ( IN PFILE_LOCK FileLock, IN BOOLEAN ViaFastCall ) /*++ Routine Description: This routine initializes a new LOCK_INFO structure in non-paged pool for the FILE_LOCK. This routines only occurs once for a given FILE_LOCK and it only occurs if any locks are applied to that file. Arguments: FileLock - Supplies a pointer to the FILE_LOCK structure to initialize. ViaFastCall - Indicates if we are being invoked via a fast call or via the slow irp based method. Return Value: TRUE - If LockInfo structure was allocated and initialized --*/ { PLOCK_INFO LockInfo; BOOLEAN Results = FALSE; ExAcquireFastMutex(&FsRtlCreateLockInfo); try { if (FileLock->LockInformation != NULL) { // // Structure is already allocated, just return // try_return( Results = TRUE ); } // // Allocate pool for lock structures. If we fail then we will either return false or // raise based on if we know the caller has an try-except to handle a raise. // if ((LockInfo = ExAllocatePoolWithTag( NonPagedPool, sizeof(LOCK_INFO), 'trSF')) == NULL) { if (ViaFastCall) { try_return( Results = FALSE ); } else { ExRaiseStatus( STATUS_INSUFFICIENT_RESOURCES ); } } // // Allocate and initialize the waiting lock queue // spinlock, and initialize the queues // LockInfo->LowestLockOffset = 0xffffffff; KeInitializeSpinLock (&LockInfo->LockQueue.QueueSpinLock); LockInfo->LockQueue.SharedLockTree = NULL; LockInfo->LockQueue.ExclusiveLockTree = NULL; LockInfo->LockQueue.WaitingLocks.Next = NULL; LockInfo->LockQueue.WaitingLocksTail.Next = NULL; // // Copy Irp & Unlock routines from pagable FileLock structure // to non-pagable LockInfo structure // LockInfo->CompleteLockIrpRoutine = FileLock->CompleteLockIrpRoutine; LockInfo->UnlockRoutine = FileLock->UnlockRoutine; // // Clear continuation info for enum routine // FileLock->LastReturnedLockInfo.FileObject = NULL; FileLock->LastReturnedLock = NULL; // // Link LockInfo into FileLock // FileLock->LockInformation = (PVOID) LockInfo; Results = TRUE; try_exit: NOTHING; } finally { ExReleaseFastMutex(&FsRtlCreateLockInfo); } return Results; } VOID FsRtlUninitializeFileLock ( IN PFILE_LOCK FileLock ) /*++ Routine Description: This routine uninitializes a FILE_LOCK structure. After calling this routine the File lock must be reinitialized before being used again. This routine will free all files locks and completes any outstanding lock requests as a result of cleaning itself up. Arguments: FileLock - Supplies a pointer to the FILE_LOCK struture being decommissioned. Return Value: None. --*/ { PLOCK_INFO LockInfo; PSH_LOCK ShLock; PEX_LOCK ExLock; PSINGLE_LIST_ENTRY Link; PWAITING_LOCK WaitingLock; PLOCKTREE_NODE LockTreeNode; PIRP Irp; NTSTATUS NewStatus; KIRQL OldIrql; PKPRCB Prcb; DebugTrace(+1, Dbg, "FsRtlUninitializeFileLock, FileLock = %08lx\n", FileLock); if ((LockInfo = (PLOCK_INFO) FileLock->LockInformation) == NULL) { return ; } // // Lock the queue // FsRtlAcquireLockQueue(&LockInfo->LockQueue, &OldIrql); // // Free lock trees // while (LockInfo->LockQueue.SharedLockTree != NULL) { LockTreeNode = CONTAINING_RECORD(LockInfo->LockQueue.SharedLockTree, LOCKTREE_NODE, Links); // // Remove all locks associated with the root node // while (LockTreeNode->Locks.Next != NULL) { Link = PopEntryList (&LockTreeNode->Locks); ShLock = CONTAINING_RECORD( Link, SH_LOCK, Link ); FsRtlFreeSharedLock(ShLock); } // // Slice off the root node of the tree // RtlDeleteNoSplay(&LockTreeNode->Links, &LockInfo->LockQueue.SharedLockTree); FsRtlFreeLockTreeNode(LockTreeNode); } while (LockInfo->LockQueue.ExclusiveLockTree != NULL) { ExLock = CONTAINING_RECORD(LockInfo->LockQueue.ExclusiveLockTree, EX_LOCK, Links); RtlDeleteNoSplay(&ExLock->Links, &LockInfo->LockQueue.ExclusiveLockTree); FsRtlFreeExclusiveLock(ExLock); } // // Free WaitingLockQueue // while (LockInfo->LockQueue.WaitingLocks.Next != NULL) { Link = PopEntryList( &LockInfo->LockQueue.WaitingLocks ); WaitingLock = CONTAINING_RECORD( Link, WAITING_LOCK, Link ); Irp = WaitingLock->Irp; // // To complete an irp in the waiting queue we need to // void the cancel routine (protected by a spinlock) before // we can complete the irp // FsRtlReleaseLockQueue (&LockInfo->LockQueue, OldIrql); IoAcquireCancelSpinLock( &Irp->CancelIrql ); IoSetCancelRoutine( Irp, NULL ); IoReleaseCancelSpinLock( Irp->CancelIrql ); Irp->IoStatus.Information = 0; FsRtlCompleteLockIrp( LockInfo, WaitingLock->Context, Irp, STATUS_RANGE_NOT_LOCKED, &NewStatus, NULL ); FsRtlAcquireLockQueue(&LockInfo->LockQueue, &OldIrql); FsRtlFreeWaitingLock( WaitingLock ); } #ifndef USERTEST // // If lots of locks were freed verify, go check non-paged pool // usage on this processor // Prcb = KeGetCurrentPrcb(); FsRtlPrivateLimitFreeLockList (&Prcb->FsRtlFreeSharedLockList); FsRtlPrivateLimitFreeLockList (&Prcb->FsRtlFreeExclusiveLockList); FsRtlPrivateLimitFreeLockList (&Prcb->FsRtlFreeWaitingLockList); FsRtlPrivateLimitFreeLockList (&Prcb->FsRtlFreeLockTreeNodeList); #endif // // Free pool used to track the lock info on this file // FsRtlReleaseLockQueue (&LockInfo->LockQueue, OldIrql); ExFreePool (LockInfo); // // Unlink LockInfo from FileLock // FileLock->LockInformation = NULL; // // And return to our caller // DebugTrace(-1, Dbg, "FsRtlUninitializeFileLock -> VOID\n", 0 ); return; } NTSTATUS FsRtlProcessFileLock ( IN PFILE_LOCK FileLock, IN PIRP Irp, IN PVOID Context OPTIONAL ) /*++ Routine Description: This routine processes a file lock IRP it does either a lock request, or an unlock request. It also completes the IRP. Once called the user (i.e., File System) has relinquished control of the input IRP. If pool is not available to store the information this routine will raise a status value indicating insufficient resources. Arguments: FileLock - Supplies the File lock being modified/queried. Irp - Supplies the Irp being processed. Context - Optionally supplies a context to use when calling the user alternate IRP completion routine. Return Value: NTSTATUS - The return status for the operation. --*/ { PIO_STACK_LOCATION IrpSp; IO_STATUS_BLOCK Iosb; NTSTATUS Status; LARGE_INTEGER ByteOffset; DebugTrace(+1, Dbg, "FsRtlProcessFileLock, FileLock = %08lx\n", FileLock); Iosb.Information = 0; // // Get a pointer to the current Irp stack location and assert that // the major function code is for a lock operation // IrpSp = IoGetCurrentIrpStackLocation( Irp ); ASSERT( IrpSp->MajorFunction == IRP_MJ_LOCK_CONTROL ); // // Now process the different minor lock operations // switch (IrpSp->MinorFunction) { case IRP_MN_LOCK: ByteOffset = IrpSp->Parameters.LockControl.ByteOffset; (VOID) FsRtlPrivateLock( FileLock, IrpSp->FileObject, &ByteOffset, IrpSp->Parameters.LockControl.Length, IoGetRequestorProcess(Irp), IrpSp->Parameters.LockControl.Key, BooleanFlagOn(IrpSp->Flags, SL_FAIL_IMMEDIATELY), BooleanFlagOn(IrpSp->Flags, SL_EXCLUSIVE_LOCK), &Iosb, Irp, Context, FALSE ); break; case IRP_MN_UNLOCK_SINGLE: ByteOffset = IrpSp->Parameters.LockControl.ByteOffset; Iosb.Status = FsRtlFastUnlockSingle( FileLock, IrpSp->FileObject, &ByteOffset, IrpSp->Parameters.LockControl.Length, IoGetRequestorProcess(Irp), IrpSp->Parameters.LockControl.Key, Context, FALSE ); FsRtlCompleteLockIrp( FileLock, Context, Irp, Iosb.Status, &Status, NULL ); break; case IRP_MN_UNLOCK_ALL: Iosb.Status = FsRtlFastUnlockAll( FileLock, IrpSp->FileObject, IoGetRequestorProcess(Irp), Context ); FsRtlCompleteLockIrp( FileLock, Context, Irp, Iosb.Status, &Status, NULL ); break; case IRP_MN_UNLOCK_ALL_BY_KEY: Iosb.Status = FsRtlFastUnlockAllByKey( FileLock, IrpSp->FileObject, IoGetRequestorProcess(Irp), IrpSp->Parameters.LockControl.Key, Context ); FsRtlCompleteLockIrp( FileLock, Context, Irp, Iosb.Status, &Status, NULL ); 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. // DebugTrace(0, 1, "Invalid LockFile Minor Function Code %08lx\n", IrpSp->MinorFunction); FsRtlCompleteRequest( Irp, STATUS_INVALID_DEVICE_REQUEST ); Iosb.Status = STATUS_INVALID_DEVICE_REQUEST; break; } // // And return to our caller // DebugTrace(-1, Dbg, "FsRtlProcessFileLock -> %08lx\n", Iosb.Status); return Iosb.Status; } BOOLEAN FsRtlCheckLockForReadAccess ( IN PFILE_LOCK FileLock, IN PIRP Irp ) /*++ Routine Description: This routine checks to see if the caller has read access to the range indicated in the IRP due to file locks. This call does not complete the Irp it only uses it to get the lock information and read information. The IRP must be for a read operation. Arguments: FileLock - Supplies the File Lock to check. Irp - Supplies the Irp being processed. Return Value: BOOLEAN - TRUE if the indicated user/request has read access to the entire specified byte range, and FALSE otherwise --*/ { BOOLEAN Result; PIO_STACK_LOCATION IrpSp; PLOCK_INFO LockInfo; LARGE_INTEGER StartingByte; LARGE_INTEGER Length; ULONG Key; PFILE_OBJECT FileObject; PVOID ProcessId; LARGE_INTEGER BeyondLastByte; DebugTrace(+1, Dbg, "FsRtlCheckLockForReadAccess, FileLock = %08lx\n", FileLock); if ((LockInfo = (PLOCK_INFO) FileLock->LockInformation) == NULL) { DebugTrace(-1, Dbg, "FsRtlCheckLockForReadAccess (No current lock info) -> TRUE\n", 0); return TRUE; } // // Do a really fast test to see if there are any exclusive locks to start with // if (LockInfo->LockQueue.ExclusiveLockTree == NULL) { DebugTrace(-1, Dbg, "FsRtlCheckLockForReadAccess (No current locks) -> TRUE\n", 0); return TRUE; } // // Get the read offset and compare it to the lowest existing lock. // IrpSp = IoGetCurrentIrpStackLocation( Irp ); StartingByte = IrpSp->Parameters.Read.ByteOffset; (ULONGLONG)Length.QuadPart = (ULONGLONG)IrpSp->Parameters.Read.Length; (ULONGLONG)BeyondLastByte.QuadPart = (ULONGLONG)StartingByte.QuadPart + Length.LowPart; if ( (ULONGLONG)BeyondLastByte.QuadPart <= (ULONGLONG)LockInfo->LowestLockOffset ) { DebugTrace(-1, Dbg, "FsRtlCheckLockForReadAccess (Below lowest lock) -> TRUE\n", 0); return TRUE; } // // Get remaining parameters. // Key = IrpSp->Parameters.Read.Key; FileObject = IrpSp->FileObject; ProcessId = IoGetRequestorProcess( Irp ); // // Call our private work routine to do the real check // Result = FsRtlFastCheckLockForRead( FileLock, &StartingByte, &Length, Key, FileObject, ProcessId ); // // And return to our caller // DebugTrace(-1, Dbg, "FsRtlCheckLockForReadAccess -> %08lx\n", Result); return Result; } BOOLEAN FsRtlCheckLockForWriteAccess ( IN PFILE_LOCK FileLock, IN PIRP Irp ) /*++ Routine Description: This routine checks to see if the caller has write access to the indicated range due to file locks. This call does not complete the Irp it only uses it to get the lock information and write information. The IRP must be for a write operation. Arguments: FileLock - Supplies the File Lock to check. Irp - Supplies the Irp being processed. Return Value: BOOLEAN - TRUE if the indicated user/request has write access to the entire specified byte range, and FALSE otherwise --*/ { BOOLEAN Result; PIO_STACK_LOCATION IrpSp; PLOCK_INFO LockInfo; LARGE_INTEGER StartingByte; LARGE_INTEGER Length; ULONG Key; PFILE_OBJECT FileObject; PVOID ProcessId; LARGE_INTEGER BeyondLastByte; DebugTrace(+1, Dbg, "FsRtlCheckLockForWriteAccess, FileLock = %08lx\n", FileLock); if ((LockInfo = (PLOCK_INFO) FileLock->LockInformation) == NULL) { DebugTrace(-1, Dbg, "FsRtlCheckLockForWriteAccess (No current lock info) -> TRUE\n", 0); return TRUE; } // // Do a really fast test to see if there are any locks to start with // if (LockInfo->LockQueue.ExclusiveLockTree == NULL && LockInfo->LockQueue.SharedLockTree == NULL) { DebugTrace(-1, Dbg, "FsRtlCheckLockForWriteAccess (No current locks) -> TRUE\n", 0); return TRUE; } // // Get the write offset and compare it to the lowest existing lock. // IrpSp = IoGetCurrentIrpStackLocation( Irp ); StartingByte = IrpSp->Parameters.Write.ByteOffset; (ULONGLONG)Length.QuadPart = (ULONGLONG)IrpSp->Parameters.Write.Length; (ULONGLONG)BeyondLastByte.QuadPart = (ULONGLONG)StartingByte.QuadPart + Length.LowPart; if ( (ULONGLONG)BeyondLastByte.QuadPart <= (ULONGLONG)LockInfo->LowestLockOffset ) { DebugTrace(-1, Dbg, "FsRtlCheckLockForWriteAccess (Below lowest lock) -> TRUE\n", 0); return TRUE; } // // Get remaining parameters. // Key = IrpSp->Parameters.Write.Key; FileObject = IrpSp->FileObject; ProcessId = IoGetRequestorProcess( Irp ); // // Call our private work routine to do the real work // Result = FsRtlFastCheckLockForWrite( FileLock, &StartingByte, &Length, Key, FileObject, ProcessId ); // // And return to our caller // DebugTrace(-1, Dbg, "FsRtlCheckLockForWriteAccess -> %08lx\n", Result); return Result; } PRTL_SPLAY_LINKS FsRtlFindFirstOverlappingSharedNode ( IN PRTL_SPLAY_LINKS Tree, IN PLARGE_INTEGER StartingByte, IN PLARGE_INTEGER EndingByte, IN OUT PRTL_SPLAY_LINKS *LastEdgeNode, IN OUT PBOOLEAN GreaterThan ) /*++ Routine Description: This routine returns the first node in the shared lock tree which overlaps with the range given. No nodes given by RtlRealPredecessor() on the result overlap the range. Arguments: Tree - supplies the splay links of the root node of the shared tree to search Starting - supplies the first byte offset of the range to check Ending - supplies the last byte offset of the range to check LastEdgeNode - optional, will be set to the last node searched in the not including returned node (presumeably where a new node will be inserted if return is NULL). GreaterThan - optional, set according to whether LastEdgeNode is covering a range greater than the queried range. !GreaterThan == LessThan, since we would have returned this node in the "Equals" (overlap) case. Return Value: The splay links of the node, if such a node exists, NULL otherwise --*/ { PLOCKTREE_NODE Node, LastOverlapNode; PRTL_SPLAY_LINKS SplayLinks; PSH_LOCK Lock; LARGE_INTEGER Starting; LARGE_INTEGER Ending; if (LastEdgeNode) *LastEdgeNode = NULL; if (GreaterThan) *GreaterThan = FALSE; Starting = *StartingByte; Ending = *EndingByte; LastOverlapNode = NULL; SplayLinks = Tree; while (SplayLinks) { Node = CONTAINING_RECORD( SplayLinks, LOCKTREE_NODE, Links ); // // Pull up the first lock on the chain at this node to check // the starting byte offset of locks at this node // Lock = CONTAINING_RECORD( Node->Locks.Next, SH_LOCK, Link ); if (Node->Extent < (ULONGLONG)Starting.QuadPart) { if ((ULONGLONG)Lock->LockInfo.EndingByte.QuadPart == (ULONGLONG)Ending.QuadPart && (ULONGLONG)Lock->LockInfo.StartingByte.QuadPart == (ULONGLONG)Starting.QuadPart) { // // The extent of the node is less than the starting position of the // range we are checking and the first lock on this node is equal to // the range, which implies that the range and the lock are zero // length. // // This is a zero length lock node and we are searching for zero // length overlap. This makes multiple zero length shared locks // occupy the same node, which is a win, but makes application of // zero length exclusive locks check the length of the overlapping // lock to see if they really conflict. // break; } // // All locks at this node are strictly less than this // byterange, so go right in the tree. // if (LastEdgeNode) *LastEdgeNode = SplayLinks; if (GreaterThan) *GreaterThan = FALSE; SplayLinks = RtlRightChild(SplayLinks); continue; } if ((ULONGLONG)Lock->LockInfo.StartingByte.QuadPart <= (ULONGLONG)Ending.QuadPart) { // // We have an overlap, but we need to see if the byterange starts // before this node so that there is the guarantee that we start // the search at the correct point. There may be still be predecessor // nodes covering the byterange. // if ((ULONGLONG)Lock->LockInfo.StartingByte.QuadPart <= (ULONGLONG)Starting.QuadPart) { // // This node begins at a byte offset prior to the byterange we // are checking, so it must be the correct starting position. // break; } // // Drop a marker at this node so that we can come back if it turns out // that the left subtree does not cover the range of bytes before this // node in the byterange. // LastOverlapNode = Node; } // // It must now be the case that all locks at this node are strictly greater // than the byterange, or we have the candidate overlap case above, // so go left in the tree. // if (LastEdgeNode) *LastEdgeNode = SplayLinks; if (GreaterThan) *GreaterThan = TRUE; SplayLinks = RtlLeftChild(SplayLinks); } if (SplayLinks == NULL) { // // We hit the edge of the tree. If the LastOverlapNode is set, it means that // we had kept searching left in the tree for a node that covered the starting // byte of the byterange, but didn't find it. If it isn't set, we'll do the // right thing anyway since Node <- NULL. // Node = LastOverlapNode; } if (Node == NULL) { // // No overlapping node existed // return NULL; } // // Return the splay links of the first overlapping node // return &Node->Links; } PRTL_SPLAY_LINKS FsRtlFindFirstOverlappingExclusiveNode ( IN PRTL_SPLAY_LINKS Tree, IN PLARGE_INTEGER StartingByte, IN PLARGE_INTEGER EndingByte, IN OUT PRTL_SPLAY_LINKS *LastEdgeNode, IN OUT PBOOLEAN GreaterThan ) /*++ Routine Description: This routine returns the first node in the exclusive lock tree which overlaps with the range given. No nodes given by RtlRealPredecessor() on the result overlap the range. Arguments: Tree - supplies the splay links of the root node of the exclusive tree to search Starting - supplies the first byte offset of the range to check Ending - supplies the last byte offset of the range to check LastEdgeNode - optional, will be set to the last node searched not including returned node (presumeably where a new node will be inserted if return is NULL). GreaterThan - optional, set according to whether LastEdgeNode is covering a range greater than the queried range. !GreaterThan == LessThan, since we would have returned this node in the "Equals" (overlap) case. Return Value: The splay links of the node, if such a node exists, NULL otherwise --*/ { PRTL_SPLAY_LINKS SplayLinks; PEX_LOCK Lock, LastOverlapNode; LARGE_INTEGER Starting; LARGE_INTEGER Ending; if (LastEdgeNode) *LastEdgeNode = NULL; if (GreaterThan) *GreaterThan = FALSE; Starting = *StartingByte; Ending = *EndingByte; LastOverlapNode = NULL; SplayLinks = Tree; while (SplayLinks) { Lock = CONTAINING_RECORD( SplayLinks, EX_LOCK, Link ); if ((ULONGLONG)Lock->LockInfo.EndingByte.QuadPart < (ULONGLONG)Starting.QuadPart) { if ((ULONGLONG)Lock->LockInfo.EndingByte.QuadPart == (ULONGLONG)Ending.QuadPart && (ULONGLONG)Lock->LockInfo.StartingByte.QuadPart == (ULONGLONG)Starting.QuadPart) { // // The extent of the lock is less than the starting position of the // range we are checking and the lock is equal to the range, which // implies that the range and the lock are zero length. // // This is a zero length lock node and we are searching for zero // length overlap. Since the exclusive tree is one lock per node, // we are in the potential middle of a run of zero length locks in // the tree. Go left to find the first zero length lock. // // This is actually the same logic we'd use for equivalent locks, // but the only time that can happen in this tree is for zero length // locks. // LastOverlapNode = Lock; if (LastEdgeNode) *LastEdgeNode = SplayLinks; if (GreaterThan) *GreaterThan = FALSE; SplayLinks = RtlLeftChild(SplayLinks); continue; } // // This lock is strictly less than this byterange, so go // right in the tree. // if (LastEdgeNode) *LastEdgeNode = SplayLinks; if (GreaterThan) *GreaterThan = FALSE; SplayLinks = RtlRightChild(SplayLinks); continue; } if ((ULONGLONG)Lock->LockInfo.StartingByte.QuadPart <= (ULONGLONG)Ending.QuadPart) { // // We have an overlap, but we need to see if the byterange starts // before this node so that there is the guarantee that we start // the search at the correct point. There may be still be predecessor // nodes covering the byterange. // if ((ULONGLONG)Lock->LockInfo.StartingByte.QuadPart <= (ULONGLONG)Starting.QuadPart) { // // This node begins at a byte offset prior to the byterange we // are checking, so it must be the correct starting position. // break; } // // Drop a marker at this node so that we can come back if it turns out // that the left subtree does not cover the range of bytes before this // node in the byterange. // LastOverlapNode = Lock; } // // It must now be the case this lock is strictly greater than the byterange, // or we have the candidate overlap case above, so go left in the tree. // if (LastEdgeNode) *LastEdgeNode = SplayLinks; if (GreaterThan) *GreaterThan = TRUE; SplayLinks = RtlLeftChild(SplayLinks); } if (SplayLinks == NULL) { // // We hit the edge of the tree. If the LastOverlapNode is set, it means that // we had kept searching left in the tree for a node that covered the starting // byte of the byterange, but didn't find it. If it isn't set, we'll do the // right thing anyway since Node <- NULL. // Lock = LastOverlapNode; } if (Lock == NULL) { // // No overlapping lock existed // return NULL; } // // Return the splay links of the first overlapping lock // return &Lock->Links; } PFILE_LOCK_INFO FsRtlGetNextFileLock ( IN PFILE_LOCK FileLock, IN BOOLEAN Restart ) /*++ Routine Description: This routine enumerates the individual file locks denoted by the input file lock variable. It returns a pointer to the file lock information stored for each lock. The caller is responsible for synchronizing call to this procedure and for not altering any of the data returned by this procedure. If the caller does not synchronize the enumeration will not be reliably complete. The way a programmer will use this procedure to enumerate all of the locks is as follows: for (p = FsRtlGetNextFileLock( FileLock, TRUE ); p != NULL; p = FsRtlGetNextFileLock( FileLock, FALSE )) { // Process the lock information referenced by p } Order is *not* guaranteed. Arguments: FileLock - Supplies the File Lock to enumerate. The current enumeration state is stored in the file lock variable so if multiple threads are enumerating the lock at the same time the results will be unpredictable. Restart - Indicates if the enumeration is to start at the beginning of the file lock tree or if we are continuing from a previous call. Return Value: PFILE_LOCK_INFO - Either it returns a pointer to the next file lock record for the input file lock or it returns NULL if there are not more locks. --*/ { FILE_LOCK_INFO FileLockInfo; PVOID ContinuationPointer; PLOCK_INFO LockInfo; PLOCKTREE_NODE Node; PSINGLE_LIST_ENTRY Link; PRTL_SPLAY_LINKS SplayLinks, LastSplayLinks; PSH_LOCK ShLock; PEX_LOCK ExLock; BOOLEAN FoundReturnable, GreaterThan; KIRQL OldIrql; DebugTrace(+1, Dbg, "FsRtlGetNextFileLock, FileLock = %08lx\n", FileLock); if ((LockInfo = (PLOCK_INFO) FileLock->LockInformation) == NULL) { // // No lock information on this FileLock // return NULL; } FoundReturnable = FALSE; // // Before getting the spinlock, copy pagable info onto stack // FileLockInfo = FileLock->LastReturnedLockInfo; ContinuationPointer = FileLock->LastReturnedLock; FsRtlAcquireLockQueue (&LockInfo->LockQueue, &OldIrql); if (!Restart) { // // Given the last returned lock, find its current successor in the tree. // Previous implementations would reset the enumeration if the last returned // lock had been removed from the tree but I think we can be better in that // case since every other structure modifying event (add new locks, delete // other locks) would *not* have caused the reset. Possible minor performance // enhancement. // // // Find the node which could contain the last returned lock. We enumerate the // exclusive lock tree, then the shared lock tree. Find the one we're enumerating. // if (FileLockInfo.ExclusiveLock) { // // Continue enumeration in the exclusive lock tree // ExLock = NULL; SplayLinks = FsRtlFindFirstOverlappingExclusiveNode( LockInfo->LockQueue.ExclusiveLockTree, &FileLockInfo.StartingByte, &FileLockInfo.EndingByte, &LastSplayLinks, &GreaterThan ); if (SplayLinks == NULL) { // // No overlapping nodes were found, try to find successor // if (GreaterThan) { // // Last node looked at was greater than the lock so it is // the place to pick up the enumeration // SplayLinks = LastSplayLinks; } else { // // Last node looked at was less than the lock so grab its successor // if (LastSplayLinks) { SplayLinks = RtlRealSuccessor(LastSplayLinks); } } } else { // // Found an overlapping lock, see if it is the last returned // for (; SplayLinks; SplayLinks = RtlRealSuccessor(SplayLinks)) { ExLock = CONTAINING_RECORD( SplayLinks, EX_LOCK, Links ); if (ContinuationPointer == ExLock && (ULONGLONG)FileLockInfo.StartingByte.QuadPart == (ULONGLONG)ExLock->LockInfo.StartingByte.QuadPart && (ULONGLONG)FileLockInfo.Length.QuadPart == (ULONGLONG)ExLock->LockInfo.Length.QuadPart && FileLockInfo.Key == ExLock->LockInfo.Key && FileLockInfo.FileObject == ExLock->LockInfo.FileObject && FileLockInfo.ProcessId == ExLock->LockInfo.ProcessId) { // // Found last returned, dig up its successor // SplayLinks = RtlRealSuccessor(SplayLinks); // // Got the node cold, so we're done // break; } // // This lock overlapped and was not the last returned. In fact, since this lock would // have conflicted with the last returned we know it could not have been returned // before, so this should be returned to the caller. // // However, if it is a zero length lock we are looking for and a zero length lock we hit, // we are at the beginning of a run we need to inspect. If we cannot find the last lock // we returned, resume the enumeration at the beginning of the run. // if (ExLock->LockInfo.Length.QuadPart != 0 || FileLockInfo.Length.QuadPart != 0) { break; } // // Keep wandering down the run // } } // // Were we able to find a lock to return? // if (SplayLinks == NULL) { // // There aren't any more exclusive locks, fall over to the shared tree // SplayLinks = LockInfo->LockQueue.SharedLockTree; if (SplayLinks) { while (RtlLeftChild(SplayLinks)) { SplayLinks = RtlLeftChild(SplayLinks); } Node = CONTAINING_RECORD(SplayLinks, LOCKTREE_NODE, Links); ShLock = CONTAINING_RECORD(Node->Locks.Next, SH_LOCK, Link); FileLockInfo = ShLock->LockInfo; ContinuationPointer = ShLock; FoundReturnable = TRUE; } } else { // // This is the lock to return // ExLock = CONTAINING_RECORD( SplayLinks, EX_LOCK, Links ); FileLockInfo = ExLock->LockInfo; ContinuationPointer = ExLock; FoundReturnable = TRUE; } } else { // // Continue enumeration in the shared lock tree // Node = NULL; SplayLinks = FsRtlFindFirstOverlappingSharedNode( LockInfo->LockQueue.SharedLockTree, &FileLockInfo.StartingByte, &FileLockInfo.EndingByte, &LastSplayLinks, &GreaterThan ); if (SplayLinks == NULL) { // // No overlapping nodes were found // if (GreaterThan) { // // Last node looked at was greater than the lock so it is // the place to pick up the enumeration // if (LastSplayLinks) { SplayLinks = LastSplayLinks; Node = CONTAINING_RECORD( LastSplayLinks, LOCKTREE_NODE, Links ); } } else { // // Last node looked at was less than the lock so grab its successor // if (LastSplayLinks) { SplayLinks = RtlRealSuccessor(LastSplayLinks); if (SplayLinks) { Node = CONTAINING_RECORD( SplayLinks, LOCKTREE_NODE, Links ); } } } } else { // // Grab the node we found // Node = CONTAINING_RECORD( SplayLinks, LOCKTREE_NODE, Links ); } // // If we have a node to look at, it may still not contain the the last returned lock // if this isn't synchronized. // if (Node != NULL) { // // Walk down the locks at this node looking for the last returned lock // for (Link = Node->Locks.Next; Link; Link = Link->Next) { // // Get a pointer to the current lock record // ShLock = CONTAINING_RECORD( Link, SH_LOCK, Link ); // // See if it's a match // if (ContinuationPointer == ShLock && (ULONGLONG)FileLockInfo.StartingByte.QuadPart == (ULONGLONG)ShLock->LockInfo.StartingByte.QuadPart && (ULONGLONG)FileLockInfo.Length.QuadPart == (ULONGLONG)ShLock->LockInfo.Length.QuadPart && FileLockInfo.Key == ShLock->LockInfo.Key && FileLockInfo.FileObject == ShLock->LockInfo.FileObject && FileLockInfo.ProcessId == ShLock->LockInfo.ProcessId) { Link = Link->Next; break; } // // See if we passed by its slot // if ((ULONGLONG)FileLockInfo.StartingByte.QuadPart < (ULONGLONG)ShLock->LockInfo.StartingByte.QuadPart) { break; } } if (Link == NULL) { // // This node doesn't contain the successor, so move // up to the successor node in the tree and return the // first lock. If we're actually at the end of the tree // we just fall off the end correctly. // SplayLinks = RtlRealSuccessor(SplayLinks); if (SplayLinks) { Node = CONTAINING_RECORD( SplayLinks, LOCKTREE_NODE, Links ); Link = Node->Locks.Next; } } if (Link) { // // Found a Lock to return, copy it to the stack // ShLock = CONTAINING_RECORD( Link, SH_LOCK, Link ); FileLockInfo = ShLock->LockInfo; ContinuationPointer = ShLock; FoundReturnable = TRUE; } } } } else { // // Restarting the enumeration. Find leftmost node in the exclusive tree and hand back // the first lock, falling over to the shared if no exlcusive locks are applied // if (LockInfo->LockQueue.ExclusiveLockTree) { SplayLinks = LockInfo->LockQueue.ExclusiveLockTree; while (RtlLeftChild(SplayLinks) != NULL) { SplayLinks = RtlLeftChild(SplayLinks); } ExLock = CONTAINING_RECORD( SplayLinks, EX_LOCK, Links ); FileLockInfo = ExLock->LockInfo; ContinuationPointer = ExLock; FoundReturnable = TRUE; } else { if (LockInfo->LockQueue.SharedLockTree) { SplayLinks = LockInfo->LockQueue.SharedLockTree; while (RtlLeftChild(SplayLinks) != NULL) { SplayLinks = RtlLeftChild(SplayLinks); } Node = CONTAINING_RECORD( SplayLinks, LOCKTREE_NODE, Links ); ShLock = CONTAINING_RECORD( Node->Locks.Next, SH_LOCK, Link ); FileLockInfo = ShLock->LockInfo; ContinuationPointer = ShLock; FoundReturnable = TRUE; } } } // // Release all the lock queues // FsRtlReleaseLockQueue (&LockInfo->LockQueue, OldIrql); if (!FoundReturnable) { // // No returnable lock was found, end of list // return NULL; } // // Update current enum location information // FileLock->LastReturnedLockInfo = FileLockInfo; FileLock->LastReturnedLock = ContinuationPointer; // // Return lock record to caller // return &FileLock->LastReturnedLockInfo; } BOOLEAN FsRtlCheckNoSharedConflict ( IN PLOCK_QUEUE LockQueue, IN PLARGE_INTEGER Starting, IN PLARGE_INTEGER Ending ) /*++ Routine Description: This routine checks to see if there is overlap in the shared locks with the given range. It is intended for use in the write access check path so that a rebalance will occur. Arguments: FileLock - Supplies the File Lock to check StartingByte - Supplies the first byte (zero based) to check Length - Supplies the length, in bytes, to check Key - Supplies the key to use in the check FileObject - Supplies the file object to use in the check ProcessId - Supplies the Process Id to use in the check Return Value: BOOLEAN - TRUE if the indicated user/request doesn't conflict in entire specified byte range, and FALSE otherwise --*/ { PRTL_SPLAY_LINKS SplayLinks, BeginLinks; SplayLinks = FsRtlFindFirstOverlappingSharedNode( LockQueue->SharedLockTree, Starting, Ending, &BeginLinks, NULL); if (BeginLinks) { LockQueue->SharedLockTree = RtlSplay(BeginLinks); } return (SplayLinks == NULL); } BOOLEAN FsRtlCheckNoExclusiveConflict ( IN PLOCK_QUEUE LockQueue, IN PLARGE_INTEGER Starting, IN PLARGE_INTEGER Ending, IN ULONG Key, IN PFILE_OBJECT FileObject, IN PVOID ProcessId ) /*++ Routine Description: This routine checks to see if there is conflict in the exclusive locks with a given range and identifying tuple of key, fileobject and process. This is for part of the read access path. Arguments: FileLock - Supplies the File Lock to check StartingByte - Supplies the first byte (zero based) to check Length - Supplies the length, in bytes, to check Key - Supplies the key to use in the check FileObject - Supplies the file object to use in the check ProcessId - Supplies the Process Id to use in the check Return Value: BOOLEAN - TRUE if the indicated user/request doesn't conflict in entire specified byte range, and FALSE otherwise --*/ { PRTL_SPLAY_LINKS SplayLinks, BeginLinks; PEX_LOCK Lock; BOOLEAN Status = TRUE; // // Find the node to begin the search at and go // for (SplayLinks = FsRtlFindFirstOverlappingExclusiveNode( LockQueue->ExclusiveLockTree, Starting, Ending, &BeginLinks, NULL); SplayLinks; SplayLinks = RtlRealSuccessor(SplayLinks)) { Lock = CONTAINING_RECORD( SplayLinks, EX_LOCK, Links ); // // If the current lock is greater than the end of the range we're // looking for then the the user doesn't conflict // // if (Ending < Lock->StartingByte) ... // if ((ULONGLONG)Ending->QuadPart < (ULONGLONG)Lock->LockInfo.StartingByte.QuadPart) { DebugTrace(0, Dbg, "FsRtlCheckForExclusiveConflict, Ending < Lock->StartingByte\n", 0); break; } // // Check for any overlap with the request. The test for // overlap is that starting byte is less than or equal to the locks // ending byte, and the ending byte is greater than or equal to the // locks starting byte. We already tested for this latter case in // the preceding statement. // // if (Starting <= Lock->StartingByte + Lock->Length - 1) ... // if ((ULONGLONG)Starting->QuadPart <= (ULONGLONG)Lock->LockInfo.EndingByte.QuadPart) { // // This request overlaps the lock. We cannot grant the request // if the file object, process id, and key do not match. Otherwise // we'll continue looping looking at locks // if ((Lock->LockInfo.FileObject != FileObject) || (Lock->LockInfo.ProcessId != ProcessId) || (Lock->LockInfo.Key != Key)) { DebugTrace(0, Dbg, "FsRtlCheckForExclusiveConflict, Range locked already\n", 0); Status = FALSE; break; } } } if (BeginLinks) { LockQueue->ExclusiveLockTree = RtlSplay(BeginLinks); } // // We searched the entire range without a conflict so we'll note no conflict // return Status; } BOOLEAN FsRtlFastCheckLockForRead ( IN PFILE_LOCK FileLock, IN PLARGE_INTEGER StartingByte, IN PLARGE_INTEGER Length, IN ULONG Key, IN PFILE_OBJECT FileObject, IN PVOID ProcessId ) /*++ Routine Description: This routine checks to see if the caller has read access to the indicated range due to file locks. Arguments: FileLock - Supplies the File Lock to check StartingByte - Supplies the first byte (zero based) to check Length - Supplies the length, in bytes, to check Key - Supplies the to use in the check FileObject - Supplies the file object to use in the check ProcessId - Supplies the Process Id to use in the check Return Value: BOOLEAN - TRUE if the indicated user/request has read access to the entire specified byte range, and FALSE otherwise --*/ { LARGE_INTEGER Starting; LARGE_INTEGER Ending; PLOCK_INFO LockInfo; PLOCK_QUEUE LockQueue; KIRQL OldIrql; PFILE_LOCK_INFO LastLock; BOOLEAN Status; if ((LockInfo = (PLOCK_INFO) FileLock->LockInformation) == NULL) { // // No lock information on this FileLock // DebugTrace(0, Dbg, "FsRtlFastCheckLockForRead, No lock info\n", 0); return TRUE; } // // If there isn't an exclusive lock then we can immediately grant access // if (LockInfo->LockQueue.ExclusiveLockTree == NULL) { DebugTrace(0, Dbg, "FsRtlFastCheckLockForRead, No exlocks present\n", 0); return TRUE; } // // If length is zero then automatically give grant access // if ((ULONGLONG)Length->QuadPart == 0) { DebugTrace(0, Dbg, "FsRtlFastCheckLockForRead, Length == 0\n", 0); return TRUE; } // // Get our starting and ending byte position // Starting = *StartingByte; (ULONGLONG)Ending.QuadPart = (ULONGLONG)Starting.QuadPart + (ULONGLONG)Length->QuadPart - 1; // // Now check lock queue // LockQueue = &LockInfo->LockQueue; // // Grab the waiting lock queue spinlock to exclude anyone from messing // with the queue while we're using it // FsRtlReacquireLockQueue(LockInfo, LockQueue, &OldIrql); // // If the range ends below the lowest existing lock, this read is OK. // if ( ((ULONGLONG)Ending.QuadPart < (ULONGLONG)LockInfo->LowestLockOffset) ) { DebugTrace(0, Dbg, "FsRtlFastCheckLockForRead (below lowest lock)\n", 0); FsRtlReleaseLockQueue(LockQueue, OldIrql); return TRUE; } // // If the caller just locked this range, he can read it. // LastLock = (PFILE_LOCK_INFO)FileObject->LastLock; if ((LastLock != NULL) && ((ULONGLONG)Starting.QuadPart >= (ULONGLONG)LastLock->StartingByte.QuadPart) && ((ULONGLONG)Ending.QuadPart <= (ULONGLONG)LastLock->EndingByte.QuadPart) && (LastLock->Key == Key) && (LastLock->ProcessId == ProcessId)) { FsRtlReleaseLockQueue(LockQueue, OldIrql); return TRUE; } // // Check the exclusive locks for a conflict. It is impossible to have // a read conflict with any shared lock. // Status = FsRtlCheckNoExclusiveConflict(LockQueue, &Starting, &Ending, Key, FileObject, ProcessId); FsRtlReleaseLockQueue(LockQueue, OldIrql); return Status; } BOOLEAN FsRtlFastCheckLockForWrite ( IN PFILE_LOCK FileLock, IN PLARGE_INTEGER StartingByte, IN PLARGE_INTEGER Length, IN ULONG Key, IN PVOID FileObject, IN PVOID ProcessId ) /*++ Routine Description: This routine checks to see if the caller has write access to the indicated range due to file locks Arguments: FileLock - Supplies the File Lock to check StartingByte - Supplies the first byte (zero based) to check Length - Supplies the length, in bytes, to check Key - Supplies the to use in the check FileObject - Supplies the file object to use in the check ProcessId - Supplies the Process Id to use in the check Return Value: BOOLEAN - TRUE if the indicated user/request has write access to the entire specified byte range, and FALSE otherwise --*/ { LARGE_INTEGER Starting; LARGE_INTEGER Ending; PLOCK_INFO LockInfo; PLOCK_QUEUE LockQueue; KIRQL OldIrql; PFILE_LOCK_INFO LastLock; BOOLEAN Status; if ((LockInfo = (PLOCK_INFO) FileLock->LockInformation) == NULL) { // // No lock information on this FileLock // DebugTrace(0, Dbg, "FsRtlFastCheckLockForRead, No lock info\n", 0); return TRUE; } // // If there isn't a lock then we can immediately grant access // if (LockInfo->LockQueue.SharedLockTree == NULL && LockInfo->LockQueue.ExclusiveLockTree == NULL) { DebugTrace(0, Dbg, "FsRtlFastCheckLockForWrite, No locks present\n", 0); return TRUE; } // // If length is zero then automatically grant access // if ((ULONGLONG)Length->QuadPart == 0) { DebugTrace(0, Dbg, "FsRtlFastCheckLockForWrite, Length == 0\n", 0); return TRUE; } // // Get our starting and ending byte position // Starting = *StartingByte; (ULONGLONG)Ending.QuadPart = (ULONGLONG)Starting.QuadPart + (ULONGLONG)Length->QuadPart - 1; // // Now check lock queue // LockQueue = &LockInfo->LockQueue; // // Grab the waiting lock queue spinlock to exclude anyone from messing // with the queue while we're using it // FsRtlReacquireLockQueue(LockInfo, LockQueue, &OldIrql); // // If the range ends below the lowest existing lock, this write is OK. // if ( ((ULONGLONG)Ending.QuadPart < (ULONGLONG)LockInfo->LowestLockOffset) ) { DebugTrace(0, Dbg, "FsRtlFastCheckLockForWrite (below lowest lock)\n", 0); FsRtlReleaseLockQueue(LockQueue, OldIrql); return TRUE; } // // If the caller just locked this range exclusively, he can write it. // LastLock = (PFILE_LOCK_INFO)((PFILE_OBJECT)FileObject)->LastLock; if ((LastLock != NULL) && ((ULONGLONG)Starting.QuadPart >= (ULONGLONG)LastLock->StartingByte.QuadPart) && ((ULONGLONG)Ending.QuadPart <= (ULONGLONG)LastLock->EndingByte.QuadPart) && (LastLock->Key == Key) && (LastLock->ProcessId == ProcessId) && LastLock->ExclusiveLock) { FsRtlReleaseLockQueue(LockQueue, OldIrql); return TRUE; } // // Check the shared locks for overlap. Any overlap in the shared locks is fatal. // Status = FsRtlCheckNoSharedConflict(LockQueue, &Starting, &Ending); if (Status == TRUE) { // // No overlap in the shared locks, so check the exclusive locks for overlap. // Status = FsRtlCheckNoExclusiveConflict(LockQueue, &Starting, &Ending, Key, FileObject, ProcessId); } FsRtlReleaseLockQueue(LockQueue, OldIrql); return Status; } VOID FsRtlSplitLocks ( IN PLOCKTREE_NODE ParentNode, IN PSINGLE_LIST_ENTRY *pStartLink, IN PLARGE_INTEGER LastShadowedByte, IN PLARGE_INTEGER GlueOffset ) /*++ Routine Description: This routine examines and possibly splits off shared locks associated with a node into new nodes of the lock tree. Called from routines that have just deleted locks. Arguments: ParentNode- Supplies the node the locks are coming from pStartLink - Supplies the pointer to the link address of the start of the range of locks in the ParentNode's locklist that need to be checked LastShadowedByte - Supplies the last byte offset that needs to be checked GlueOffset - Supplies the maximum offset affected by locks prior to this point in the list Return Value: None --*/ { PSH_LOCK Lock; PLOCKTREE_NODE NewNode; PSINGLE_LIST_ENTRY Link, *pLink, *NextpLink; BOOLEAN ExtentValid; LARGE_INTEGER MaxOffset, StartOffset; MaxOffset = *GlueOffset; StartOffset.QuadPart = 0; if (!ParentNode->Locks.Next || (ULONGLONG)LastShadowedByte->QuadPart <= (ULONGLONG)MaxOffset.QuadPart) { // // The parent node is not there, doesn't have links associated, or the // last possible byte that is affected by the operation our caller made // is interior to the max extent of all locks still in this node - in // which case there is nothing that needs to be done. // return; } // // If the extent of the node is past the last byte affected by whatever // operations were done to this node, we can avoid the linear scan of // the list past that last affected byte since we already know the // extent of the entire list! If it is not (note that it would have to // be equal - by defintion) then we need to recalculate the extents of // all nodes we touch in this operation. // ExtentValid = (ParentNode->Extent > (ULONGLONG)LastShadowedByte->QuadPart); for (pLink = pStartLink; (Link = *pLink) != NULL; pLink = NextpLink) { NextpLink = &Link->Next; Lock = CONTAINING_RECORD( Link, SH_LOCK, Link ); if (ParentNode->Locks.Next == *pLink) { // // We're at the first lock in the node, and we know that we're going to leave // at least one lock here. Skip over that lock. We also know that the max // offset must be that locks's ending byte - make sure it is. Note that this // code is *exactly* the same as the update MaxOffset code at the bottom of // the loop. // MaxOffset.QuadPart = Lock->LockInfo.EndingByte.QuadPart; // // Set the starting offset of the node to assist in getting zero length locks right // StartOffset.QuadPart = Lock->LockInfo.StartingByte.QuadPart; // // If extents are invalid we also need to set it in case this turns out to // be the only lock at this node. // if (!ExtentValid) { ParentNode->Extent = (ULONGLONG)MaxOffset.QuadPart; } continue; } // // If the lock begins at a byte offset greater than the maximum offset seen to this // point, AND the starting offset of this node is not the same as this lock, break // the node. The second half of the test keeps overlapping zero length locks in the // same node. (zero length lock ---> starting = ending + 1) // if ((ULONGLONG)Lock->LockInfo.StartingByte.QuadPart > (ULONGLONG)MaxOffset.QuadPart && StartOffset.QuadPart != Lock->LockInfo.StartingByte.QuadPart) { // // Break the node up here // FsRtlAllocateLockTreeNode(&NewNode); RtlInitializeSplayLinks(&NewNode->Links); // // Find the spot in the tree to take the new node(s). If the current node has // a free right child, we use it, else find the successor node and use its // left child. One of these cases must be avaliable since we know there are // no nodes between this node and its successor. // if (RtlRightChild(&ParentNode->Links) == NULL) { RtlInsertAsRightChild(&ParentNode->Links, &NewNode->Links); } else { ASSERT(RtlLeftChild(RtlRealSuccessor(&ParentNode->Links)) == NULL); RtlInsertAsLeftChild(RtlRealSuccessor(&ParentNode->Links), &NewNode->Links); } // // Move the remaining locks over to the new node and fix up extents // NewNode->Locks.Next = *pLink; *pLink = NULL; NewNode->Tail.Next = ParentNode->Tail.Next; ParentNode->Tail.Next = CONTAINING_RECORD( pLink, SINGLE_LIST_ENTRY, Next ); // // This will cause us to fall into the first-lock clause above on the next pass // NextpLink = &NewNode->Locks.Next; // // The new node's extent is now copied from the parent. The old node's extent must be // the maximum offset we have seen to this point. // // Note that if ExtentValid is true, that must mean that the lock ending at that extent // is in the new node since if it was in the old node we wouldn't have been able to split. // NewNode->Extent = ParentNode->Extent; ParentNode->Extent = (ULONGLONG)MaxOffset.QuadPart; ParentNode = NewNode; continue; } if (ExtentValid && (ULONGLONG)Lock->LockInfo.StartingByte.QuadPart > (ULONGLONG)LastShadowedByte->QuadPart) { // // Our extents are good and this lock is past the shadow, so we can stop // return; } if ((ULONGLONG)MaxOffset.QuadPart < (ULONGLONG)Lock->LockInfo.EndingByte.QuadPart) { // // Update maximum offset // MaxOffset.QuadPart = Lock->LockInfo.EndingByte.QuadPart; if (!ExtentValid) { // // Extents are not good so we must update the extent // ParentNode->Extent = (ULONGLONG)MaxOffset.QuadPart; } } } // // Reached the end of the list, so update the extent (case of all subsequent locks // having been interior to GlueOffset) // ParentNode->Extent = (ULONGLONG)MaxOffset.QuadPart; return; } VOID FsRtlPrivateRemoveLock ( IN PLOCK_INFO LockInfo, IN PFILE_LOCK_INFO FileLockInfo, IN BOOLEAN CheckForWaiters ) /*++ Routine Description: General purpose cleanup routine. Finds the given lock structure and removes it from the file lock list. Differs from UnlockSingle only in that it disables the UnlockRoutine of the FileLock and optionalizes walking the waiting locks list. Arguments: FileLock - Supplies the file's lock structure supposedly containing a stale lock FileLockInfo - Supplies file lock data being freed CheckForWaiters - If true check for possible waiting locks, caused by freeing the locked range Return Value: None. --*/ { NTSTATUS Status; if (FileLockInfo->ExclusiveLock) { // // We must find it in the exclusive lock tree // Status = FsRtlFastUnlockSingleExclusive( LockInfo, FileLockInfo->FileObject, &FileLockInfo->StartingByte, &FileLockInfo->Length, FileLockInfo->ProcessId, FileLockInfo->Key, NULL, TRUE, CheckForWaiters ); ASSERT( Status == STATUS_SUCCESS); } else { // // We must find it in the shared lock tree // Status = FsRtlFastUnlockSingleShared( LockInfo, FileLockInfo->FileObject, &FileLockInfo->StartingByte, &FileLockInfo->Length, FileLockInfo->ProcessId, FileLockInfo->Key, NULL, TRUE, CheckForWaiters ); ASSERT( Status == STATUS_SUCCESS); } return; } NTSTATUS FsRtlFastUnlockSingle ( IN PFILE_LOCK FileLock, IN PFILE_OBJECT FileObject, IN LARGE_INTEGER UNALIGNED *FileOffset, IN PLARGE_INTEGER Length, IN PEPROCESS ProcessId, IN ULONG Key, IN PVOID Context OPTIONAL, IN BOOLEAN AlreadySynchronized ) /*++ Routine Description: This routine performs an Unlock Single operation on the current locks associated with the specified file lock. Only the lock with a matching file object, process id, key, and range is freed. Arguments: FileLock - Supplies the file lock being freed. FileObject - Supplies the file object holding the locks FileOffset - Supplies the offset to be unlocked Length - Supplies the length in bytes to be unlocked ProcessId - Supplies the process Id to use in this operation Key - Supplies the key to use in this operation Context - Optionally supplies context to use when completing Irps AlreadySynchronized - Indicates that the caller has already synchronized access to the file lock so the fields in the file lock and be updated without further locking, but not the queues. Return Value: NTSTATUS - The completion status for this operation --*/ { NTSTATUS Status; // // XXX AlreadySynchronized is obsolete. It was apparently added for the dead // XXX SoloLock code. // if (FileLock->LockInformation == NULL) { // // Fast exit - no locks are applied // return STATUS_RANGE_NOT_LOCKED; } Status = FsRtlFastUnlockSingleExclusive( FileLock->LockInformation, FileObject, FileOffset, Length, ProcessId, Key, Context, FALSE, TRUE ); if (Status == STATUS_SUCCESS) { // // Found and unlocked in the exclusive tree, so we're done // return Status; } Status = FsRtlFastUnlockSingleShared( FileLock->LockInformation, FileObject, FileOffset, Length, ProcessId, Key, Context, FALSE, TRUE ); return Status; } NTSTATUS FsRtlFastUnlockSingleShared ( IN PLOCK_INFO LockInfo, IN PFILE_OBJECT FileObject, IN LARGE_INTEGER UNALIGNED *FileOffset, IN PLARGE_INTEGER Length, IN PEPROCESS ProcessId, IN ULONG Key, IN PVOID Context OPTIONAL, IN BOOLEAN IgnoreUnlockRoutine, IN BOOLEAN CheckForWaiters ) /*++ Routine Description: This routine performs an Unlock Single operation on the current locks associated with the specified file lock. Only the lock with a matching file object, process id, key, and range is freed. Arguments: LockInfo - Supplies the lock data being operated on FileObject - Supplies the file object holding the locks FileOffset - Supplies the offset to be unlocked Length - Supplies the length in bytes to be unlocked ProcessId - Supplies the process Id to use in this operation Key - Supplies the key to use in this operation Context - Optionally supplies context to use when completing Irps IgnoreUnlockRoutine - inidicates that the filelock's unlock routine should not be called on lock removal (for removal of aborted locks) CheckForWaiters - If true check for possible waiting locks, caused by freeing the locked range Return Value: NTSTATUS - The completion status for this operation --*/ { PSINGLE_LIST_ENTRY *pLink, Link; KIRQL OldIrql; PLOCK_QUEUE LockQueue; PRTL_SPLAY_LINKS SplayLinks; LARGE_INTEGER EndingOffset, MaxOffset; PLOCKTREE_NODE Node; LARGE_INTEGER AlignedFileOffset; // // General case - search the outstanding lock queue for this lock // AlignedFileOffset = *FileOffset; LockQueue = &LockInfo->LockQueue; FsRtlAcquireLockQueue(LockQueue, &OldIrql); // // Check for the no locks currently held // if (LockQueue->SharedLockTree == NULL) { FsRtlReleaseLockQueue( LockQueue, OldIrql ); return STATUS_RANGE_NOT_LOCKED; } // // Find the overlapping node, if it exists, to search. Note that // we don't have to go through more than one node in the tree // since we are assuming this is an existing lock. // EndingOffset.QuadPart = (ULONGLONG)AlignedFileOffset.QuadPart + (ULONGLONG)Length->QuadPart - 1; SplayLinks = FsRtlFindFirstOverlappingSharedNode( LockQueue->SharedLockTree, &AlignedFileOffset, &EndingOffset, NULL, NULL ); if (SplayLinks == NULL) { // // No node in the tree overlaps this range, so we're done // FsRtlReleaseLockQueue(LockQueue, OldIrql); return STATUS_RANGE_NOT_LOCKED; } Node = CONTAINING_RECORD( SplayLinks, LOCKTREE_NODE, Links ); MaxOffset.QuadPart = 0; for (pLink = &Node->Locks.Next; (Link = *pLink) != NULL; pLink = &Link->Next) { PSH_LOCK Lock; Lock = CONTAINING_RECORD( Link, SH_LOCK, Link ); DebugTrace(0, Dbg, "Sh Top of Loop, Lock = %08lx\n", Lock ); if ((Lock->LockInfo.FileObject == FileObject) && (Lock->LockInfo.ProcessId == ProcessId) && (Lock->LockInfo.Key == Key) && ((ULONGLONG)Lock->LockInfo.StartingByte.QuadPart == (ULONGLONG)AlignedFileOffset.QuadPart) && ((ULONGLONG)Lock->LockInfo.Length.QuadPart == (ULONGLONG)Length->QuadPart)) { DebugTrace(0, Dbg, "Sh Found one to unlock\n", 0); // // We have an exact match so now is the time to delete this // lock. Remove the lock from the list, then call the // optional unlock routine, then delete the lock. // if (FileObject->LastLock == &Lock->LockInfo) { FileObject->LastLock = NULL; } if (*pLink == Node->Tail.Next) { // // Deleting the tail node of the list. Safe even if deleting the // first node since this implies we're also deleting the last node // in the node which means we'll delete the node ... // Node->Tail.Next = CONTAINING_RECORD( pLink, SINGLE_LIST_ENTRY, Next ); } // // Snip the deleted lock // *pLink = Link->Next; if (pLink == &Node->Locks.Next) { // // Deleted first lock in node // if (Node->Locks.Next == NULL) { // // Just deleted last lock on this node, so free it // LockQueue->SharedLockTree = RtlDelete(SplayLinks); FsRtlFreeLockTreeNode(Node); Node = NULL; } if (LockInfo->LowestLockOffset != 0xffffffff && LockInfo->LowestLockOffset == Lock->LockInfo.StartingByte.LowPart) { // // This was the lowest lock in the trees, reset the lowest lock offset // FsRtlPrivateResetLowestLockOffset(LockInfo); } } // // Now the fun begins. It may be the case that the lock just snipped from // the chain was gluing locks at this node together, so we need to // inspect the chain. // if (Node) { FsRtlSplitLocks(Node, pLink, &Lock->LockInfo.EndingByte, &MaxOffset); } if (!IgnoreUnlockRoutine && LockInfo->UnlockRoutine != NULL) { FsRtlReleaseLockQueue( LockQueue, OldIrql ); LockInfo->UnlockRoutine( Context, &Lock->LockInfo ); FsRtlReacquireLockQueue( LockInfo, LockQueue, &OldIrql ); } FsRtlFreeSharedLock( Lock ); // // See if there are additional waiting locks that we can // now release. // if (CheckForWaiters && LockQueue->WaitingLocks.Next) { FsRtlPrivateCheckWaitingLocks( LockInfo, LockQueue, OldIrql ); } FsRtlReleaseLockQueue( LockQueue, OldIrql ); return STATUS_SUCCESS; } if ((ULONGLONG)Lock->LockInfo.StartingByte.QuadPart > (ULONGLONG)AlignedFileOffset.QuadPart) { // // The current lock begins at a byte offset greater than the range we are seeking // to unlock. This range must therefore not be locked. // break; } if ((ULONGLONG)MaxOffset.QuadPart < (ULONGLONG)Lock->LockInfo.EndingByte.QuadPart) { // // Maintain the maximum offset affected by locks up to this point. // MaxOffset.QuadPart = Lock->LockInfo.EndingByte.QuadPart; } } // // Lock was not found, return to our caller // FsRtlReleaseLockQueue(LockQueue, OldIrql); return STATUS_RANGE_NOT_LOCKED; } NTSTATUS FsRtlFastUnlockSingleExclusive ( IN PLOCK_INFO LockInfo, IN PFILE_OBJECT FileObject, IN LARGE_INTEGER UNALIGNED *FileOffset, IN PLARGE_INTEGER Length, IN PEPROCESS ProcessId, IN ULONG Key, IN PVOID Context OPTIONAL, IN BOOLEAN IgnoreUnlockRoutine, IN BOOLEAN CheckForWaiters ) /*++ Routine Description: This routine performs an Unlock Single operation on the exclusive locks associated with the specified lock data. Only the lock with a matching file object, process id, key, and range is freed. Arguments: LockInfo - Supplies the lock data being operated on FileObject - Supplies the file object holding the locks FileOffset - Supplies the offset to be unlocked Length - Supplies the length in bytes to be unlocked ProcessId - Supplies the process Id to use in this operation Key - Supplies the key to use in this operation Context - Optionally supplies context to use when completing Irps IgnoreUnlockRoutine - inidicates that the filelock's unlock routine should not be called on lock removal (for removal of aborted locks) CheckForWaiters - If true check for possible waiting locks, caused by freeing the locked range Return Value: NTSTATUS - The completion status for this operation --*/ { KIRQL OldIrql; PLOCK_QUEUE LockQueue; PRTL_SPLAY_LINKS SplayLinks; LARGE_INTEGER EndingOffset; PEX_LOCK Lock; LARGE_INTEGER AlignedFileOffset; // // General case - search the outstanding lock queue for this lock // AlignedFileOffset = *FileOffset; LockQueue = &LockInfo->LockQueue; FsRtlAcquireLockQueue(LockQueue, &OldIrql); // // Check for the no locks currently held // if (LockQueue->ExclusiveLockTree == NULL) { FsRtlReleaseLockQueue( LockQueue, OldIrql ); return STATUS_RANGE_NOT_LOCKED; } // // Find the overlapping lock, if it exists. Note that this is usually // the only lock we need to check since we are assuming this is an // existing lock. However, if the lock is a zero length lock we will // have a run of locks to check. // EndingOffset.QuadPart = (ULONGLONG)AlignedFileOffset.QuadPart + (ULONGLONG)Length->QuadPart - 1; for (SplayLinks = FsRtlFindFirstOverlappingExclusiveNode( LockQueue->ExclusiveLockTree, &AlignedFileOffset, &EndingOffset, NULL, NULL ); SplayLinks; SplayLinks = RtlRealSuccessor(SplayLinks)) { Lock = CONTAINING_RECORD( SplayLinks, EX_LOCK, Links ); if ((Lock->LockInfo.FileObject == FileObject) && (Lock->LockInfo.ProcessId == ProcessId) && (Lock->LockInfo.Key == Key) && ((ULONGLONG)Lock->LockInfo.StartingByte.QuadPart == (ULONGLONG)AlignedFileOffset.QuadPart) && ((ULONGLONG)Lock->LockInfo.Length.QuadPart == (ULONGLONG)Length->QuadPart)) { DebugTrace(0, Dbg, "Ex Found one to unlock\n", 0); // // We have an exact match so now is the time to delete this // lock. Remove the lock from the list, then call the // optional unlock routine, then delete the lock. // if (FileObject->LastLock == &Lock->LockInfo) { FileObject->LastLock = NULL; } // // Snip the deleted lock // LockQueue->ExclusiveLockTree = RtlDelete(&Lock->Links); if (LockInfo->LowestLockOffset != 0xffffffff && LockInfo->LowestLockOffset == Lock->LockInfo.StartingByte.LowPart) { // // This was the lowest lock in the tree, so reset the lowest lock // offset // FsRtlPrivateResetLowestLockOffset(LockInfo); } if (!IgnoreUnlockRoutine && LockInfo->UnlockRoutine != NULL) { FsRtlReleaseLockQueue( LockQueue, OldIrql ); LockInfo->UnlockRoutine( Context, &Lock->LockInfo ); FsRtlReacquireLockQueue( LockInfo, LockQueue, &OldIrql ); } FsRtlFreeExclusiveLock( Lock ); // // See if there are additional waiting locks that we can // now release. // if (CheckForWaiters && LockQueue->WaitingLocks.Next) { FsRtlPrivateCheckWaitingLocks( LockInfo, LockQueue, OldIrql ); } FsRtlReleaseLockQueue( LockQueue, OldIrql ); return STATUS_SUCCESS; } if ((ULONGLONG)Lock->LockInfo.StartingByte.QuadPart > (ULONGLONG)AlignedFileOffset.QuadPart) { // // The current lock begins at a byte offset greater than the range we are seeking // to unlock. This range must therefore not be locked. // break; } } // // Lock was not found, return to our caller // FsRtlReleaseLockQueue(LockQueue, OldIrql); return STATUS_RANGE_NOT_LOCKED; } NTSTATUS FsRtlFastUnlockAll ( IN PFILE_LOCK FileLock, IN PFILE_OBJECT FileObject, IN PEPROCESS ProcessId, IN PVOID Context OPTIONAL ) /*++ Routine Description: This routine performs an Unlock all operation on the current locks associated with the specified file lock. Only those locks with a matching file object and process id are freed. Arguments: FileLock - Supplies the file lock being freed. FileObject - Supplies the file object associated with the file lock ProcessId - Supplies the Process Id assoicated with the locks to be freed Context - Supplies an optional context to use when completing waiting lock irps. Return Value: None --*/ { return FsRtlPrivateFastUnlockAll( FileLock, FileObject, ProcessId, 0, FALSE, // No Key Context ); } NTSTATUS FsRtlFastUnlockAllByKey ( IN PFILE_LOCK FileLock, IN PFILE_OBJECT FileObject, IN PEPROCESS ProcessId, IN ULONG Key, IN PVOID Context OPTIONAL ) /*++ Routine Description: This routine performs an Unlock All by Key operation on the current locks associated with the specified file lock. Only those locks with a matching file object, process id, and key are freed. The input Irp is completed by this procedure Arguments: FileLock - Supplies the file lock being freed. FileObject - Supplies the file object associated with the file lock ProcessId - Supplies the Process Id assoicated with the locks to be freed Key - Supplies the Key to use in this operation Context - Supplies an optional context to use when completing waiting lock irps. Return Value: NTSTATUS - The return status for the operation. --*/ { return FsRtlPrivateFastUnlockAll( FileLock, FileObject, ProcessId, Key, TRUE, Context ); } // // Local Support Routine // BOOLEAN FsRtlPrivateLock ( IN PFILE_LOCK FileLock, IN PFILE_OBJECT FileObject, IN PLARGE_INTEGER FileOffset, IN PLARGE_INTEGER Length, IN PEPROCESS ProcessId, IN ULONG Key, IN BOOLEAN FailImmediately, IN BOOLEAN ExclusiveLock, OUT PIO_STATUS_BLOCK Iosb, IN PIRP Irp OPTIONAL, IN PVOID Context, IN BOOLEAN AlreadySynchronized ) /*++ Routine Description: This routine preforms a lock operation request. This handles both the fast get lock and the Irp based get lock. If the Irp is supplied then this routine will either complete the Irp or enqueue it as a waiting lock request. Arguments: FileLock - Supplies the File Lock to work against FileObject - Supplies the file object used in this operation FileOffset - Supplies the file offset used in this operation Length - Supplies the length used in this operation ProcessId - Supplies the process ID used in this operation Key - Supplies the key used in this operation FailImmediately - Indicates if the request should fail immediately if the lock cannot be granted. ExclusiveLock - Indicates if this is a request for an exclusive or shared lock Iosb - Receives the Status if this operation is successful Context - Supplies the context with which to complete Irp with AlreadySynchronized - Indicates that the caller has already synchronized access to the file lock so the fields in the file lock and be updated without further locking, but not the queues. Return Value: BOOLEAN - TRUE if this operation completed and FALSE otherwise. --*/ { BOOLEAN Results; BOOLEAN AccessGranted; PLOCK_INFO LockInfo; PLOCK_QUEUE LockQueue; KIRQL OldIrql; FILE_LOCK_INFO FileLockInfo; DebugTrace(+1, Dbg, "FsRtlPrivateLock, FileLock = %08lx\n", FileLock); if ((LockInfo = (PLOCK_INFO) FileLock->LockInformation) == NULL) { DebugTrace(+2, Dbg, "FsRtlPrivateLock, New LockInfo required\n", 0); // // No lock information on this FileLock, create the structure. If the irp // is null then this is being called via the fast call method. // // if (!FsRtlPrivateInitializeFileLock (FileLock, (BOOLEAN)(Irp == NULL))) { return FALSE; } // // Set flag so file locks will be checked on the fast io // code paths // FileLock->FastIoIsQuestionable = TRUE; // // Pickup allocated lockinfo structure // LockInfo = (PLOCK_INFO) FileLock->LockInformation; } // // Assume success and build LockData structure prior to acquiring // the lock queue spinlock. (mp perf enhancement) // FileLockInfo.StartingByte = *FileOffset; FileLockInfo.Length = *Length; (ULONGLONG)FileLockInfo.EndingByte.QuadPart = (ULONGLONG)FileLockInfo.StartingByte.QuadPart + (ULONGLONG)FileLockInfo.Length.QuadPart - 1; FileLockInfo.Key = Key; FileLockInfo.FileObject = FileObject; FileLockInfo.ProcessId = ProcessId; FileLockInfo.ExclusiveLock = ExclusiveLock; LockQueue = &LockInfo->LockQueue; // // Now we need to actually run through our current lock queue so if // we didn't already grab the spinlock then grab it now // FsRtlAcquireLockQueue(LockQueue, &OldIrql); try { //ASSERTMSG("LockCount/CurrentLockQueue disagree ", LockInfo->CurrentLockQueue.Next != NULL)); // // Case on whether we're trying to take out an exclusive lock or // a shared lock. And in both cases try to get appropriate access. // if (ExclusiveLock) { DebugTrace(0, Dbg, "Check for write access\n", 0); AccessGranted = FsRtlPrivateCheckForExclusiveLockAccess( LockQueue, &FileLockInfo ); } else { DebugTrace(0, Dbg, "Check for read access\n", 0); AccessGranted = FsRtlPrivateCheckForSharedLockAccess( LockQueue, &FileLockInfo ); } // // Now AccessGranted tells us whether we can really get the access // for the range we want // if (!AccessGranted) { DebugTrace(0, Dbg, "We do not have access\n", 0); // // We cannot read/write to the range, so we cannot take out // the lock. Now if the user wanted to fail immediately then // we'll complete the Irp, otherwise we'll enqueue this Irp // to the waiting lock queue // if (FailImmediately) { // // Set our status and return, the finally clause will // complete the request // DebugTrace(0, Dbg, "And we fail immediately\n", 0); Iosb->Status = STATUS_LOCK_NOT_GRANTED; try_return( Results = TRUE ); } else if (ARGUMENT_PRESENT(Irp)) { PWAITING_LOCK WaitingLock; DebugTrace(0, Dbg, "And we enqueue the Irp for later\n", 0); // // Allocate a new waiting record, set it to point to the // waiting Irp, and insert it in the tail of the waiting // locks queue // FsRtlAllocateWaitingLock( &WaitingLock ); WaitingLock->Irp = Irp; WaitingLock->Context = Context; IoMarkIrpPending( Irp ); // // Add WaitingLock WaitingLockQueue // WaitingLock->Link.Next = NULL; if (LockQueue->WaitingLocks.Next == NULL) { // // Create new list // LockQueue->WaitingLocks.Next = &WaitingLock->Link; LockQueue->WaitingLocksTail.Next = &WaitingLock->Link; } else { // // Add waiter to tail of list // LockQueue->WaitingLocksTail.Next->Next = &WaitingLock->Link; LockQueue->WaitingLocksTail.Next = &WaitingLock->Link; } // // Setup IRP in case it's canceled - then set the // IRP's cancel routine // Irp->IoStatus.Information = (ULONG)LockInfo; IoSetCancelRoutine( Irp, FsRtlPrivateCancelFileLockIrp ); if (Irp->Cancel) { // // Irp is in the canceled state; however we set the // cancel routine without using IoAcquireCancleSpinLock. // We need to synchronize with the Io system and recheck // the irp to see if the cancel routine needs invoked // FsRtlReleaseLockQueue(LockQueue, OldIrql); IoAcquireCancelSpinLock( &Irp->CancelIrql ); if (Irp->CancelRoutine == FsRtlPrivateCancelFileLockIrp) { // // Irp's cancel routine was not called, clear // it in the irp and do it now // // IoCancelSpinLock is freed in the cancel routine // IoSetCancelRoutine( Irp, NULL ); FsRtlPrivateCancelFileLockIrp( NULL, Irp ); } else { // // The irp's cancel routine has/is being invoked // by the Io subsystem. // IoReleaseCancelSpinLock( Irp->CancelIrql ); } FsRtlReacquireLockQueue(LockInfo, LockQueue, &OldIrql); } Iosb->Status = STATUS_PENDING; try_return( Results = TRUE ); } else { try_return( Results = FALSE ); } } DebugTrace(0, Dbg, "We have access\n", 0); FsRtlPrivateInsertLock( LockInfo, FileObject, &FileLockInfo ); // // Get ready to ready to our caller // Iosb->Status = STATUS_SUCCESS; Results = TRUE; try_exit: NOTHING; } finally { FsRtlReleaseLockQueue(LockQueue, OldIrql); // // Complete the request provided we were given one and it is not a pending status // if (ARGUMENT_PRESENT(Irp) && (Iosb->Status != STATUS_PENDING)) { NTSTATUS NewStatus; // // We must reference the fileobject for the case that the IRP completion // fails and we need to lift the lock. Although the only reason we have // to touch the fileobject in the remove case is to unset the LastLock field, // we have no way of knowing if we will race with a reference count drop // and lose. // ObReferenceObject( FileObject ); // // Complete the request, if the don't get back success then // we need to possibly remove the lock that we just // inserted. // FsRtlCompleteLockIrp( LockInfo, Context, Irp, Iosb->Status, &NewStatus, FileObject ); if (!NT_SUCCESS(NewStatus) && NT_SUCCESS(Iosb->Status) ) { // // Irp failed, remove the lock which was added // FsRtlPrivateRemoveLock ( LockInfo, &FileLockInfo, TRUE ); } // // Lift our private reference to the fileobject. This may induce deletion. // ObDereferenceObject( FileObject ); Iosb->Status = NewStatus; } DebugTrace(-1, Dbg, "FsRtlPrivateLock -> %08lx\n", Results); } // // and return to our caller // return Results; } // // Internal Support Routine // VOID FsRtlPrivateInsertLock ( IN PLOCK_INFO LockInfo, IN PFILE_OBJECT FileObject, IN PFILE_LOCK_INFO FileLockInfo ) /*++ Routine Description: This routine fills in a new lock record of the appropriate type and inserts it into the lock information. Arguments: LockInfo - Supplies the lock being modified FileObject - The associated file object to update hints in FileLockInfo - Supplies the new lock data to add to the lock queue Return Value: None. --*/ { // // Fix up the lowest lock offset if need be // if ((ULONGLONG)FileLockInfo->StartingByte.QuadPart < (ULONGLONG)LockInfo->LowestLockOffset) { ASSERT( FileLockInfo->StartingByte.HighPart == 0 ); LockInfo->LowestLockOffset = FileLockInfo->StartingByte.LowPart; } if (FileLockInfo->ExclusiveLock) { PEX_LOCK ExLock; FsRtlAllocateExclusiveLock( &ExLock ); ExLock->LockInfo = *FileLockInfo; FsRtlPrivateInsertExclusiveLock( &LockInfo->LockQueue, ExLock ); FileObject->LastLock = &ExLock->LockInfo; } else { PSH_LOCK ShLock; FsRtlAllocateSharedLock( &ShLock ); ShLock->LockInfo = *FileLockInfo; FsRtlPrivateInsertSharedLock( &LockInfo->LockQueue, ShLock ); FileObject->LastLock = &ShLock->LockInfo; } return; } // // Internal Support Routine // VOID FsRtlPrivateInsertSharedLock ( IN PLOCK_QUEUE LockQueue, IN PSH_LOCK NewLock ) /*++ Routine Description: This routine adds a new shared lock record to the File lock's current lock queue. Locks are inserted into nodes ordered by their starting byte. Arguments: LockQueue - Supplies the lock queue being modified NewLock - Supplies the new shared lock to add to the lock queue Return Value: None. --*/ { PSINGLE_LIST_ENTRY pLink, Link; PRTL_SPLAY_LINKS OverlappedSplayLinks, ParentSplayLinks; PLOCKTREE_NODE Node, NextNode; PSH_LOCK NextLock; BOOLEAN GreaterThan; OverlappedSplayLinks = FsRtlFindFirstOverlappingSharedNode( LockQueue->SharedLockTree, &NewLock->LockInfo.StartingByte, &NewLock->LockInfo.EndingByte, &ParentSplayLinks, &GreaterThan ); if (OverlappedSplayLinks == NULL) { // // Simple insert case, build a new node // FsRtlAllocateLockTreeNode(&NextNode); RtlInitializeSplayLinks(&NextNode->Links); NextNode->Locks.Next = NextNode->Tail.Next = &NewLock->Link; NextNode->Extent = (ULONGLONG)NewLock->LockInfo.EndingByte.QuadPart; NewLock->Link.Next = NULL; if (ParentSplayLinks) { // // We have a real parent node in the tree // if (GreaterThan) { ASSERT(RtlLeftChild(ParentSplayLinks) == NULL); RtlInsertAsLeftChild(ParentSplayLinks, &NextNode->Links); } else { ASSERT(RtlRightChild(ParentSplayLinks) == NULL); RtlInsertAsRightChild(ParentSplayLinks, &NextNode->Links); } // // Splay all new nodes in the tree // LockQueue->SharedLockTree = RtlSplay(&NextNode->Links); } else { // // First node in the tree // LockQueue->SharedLockTree = &NextNode->Links; } return; } // // Search down the overlapped node finding the position for the new lock // Node = CONTAINING_RECORD( OverlappedSplayLinks, LOCKTREE_NODE, Links ); for (pLink = &Node->Locks; (Link = pLink->Next) != NULL; pLink = Link) { PSH_LOCK Lock; Lock = CONTAINING_RECORD( Link, SH_LOCK, Link ); // // If the new lock can go right before the lock already in the // list then we break out of the loop // // if (NewLock->StartingByte <= Lock->StartingByte) ... // if ((ULONGLONG)NewLock->LockInfo.StartingByte.QuadPart <= (ULONGLONG)Lock->LockInfo.StartingByte.QuadPart) { break; } } // // At this point pLink points to the record that comes right after // the new lock that we're inserting so we can simply push the // newlock into the entrylist // DebugTrace(0, Dbg, "InsertSharedLock, Insert Before = %08lx\n", Link); if (pLink->Next == NULL) { // // Adding onto the tail of the list // Node->Tail.Next = &NewLock->Link; } NewLock->Link.Next = pLink->Next; pLink->Next = &NewLock->Link; // // And splay the node we inserted into // LockQueue->SharedLockTree = RtlSplay(OverlappedSplayLinks); if ((ULONGLONG)NewLock->LockInfo.EndingByte.QuadPart > Node->Extent) { // // The new lock extends the range of this node, so fix up the extent // Node->Extent = NewLock->LockInfo.EndingByte.QuadPart; // // Walk across the remainder of the tree integrating newly overlapping // nodes into the node we just inserted the new lock into // ParentSplayLinks = OverlappedSplayLinks; for (OverlappedSplayLinks = RtlRealSuccessor(ParentSplayLinks); OverlappedSplayLinks; OverlappedSplayLinks = RtlRealSuccessor(ParentSplayLinks)) { NextNode = CONTAINING_RECORD( OverlappedSplayLinks, LOCKTREE_NODE, Links ); NextLock = CONTAINING_RECORD( NextNode->Locks.Next, SH_LOCK, Link ); if ((ULONGLONG)NextLock->LockInfo.StartingByte.QuadPart > Node->Extent) { // // This node is not overlapped, so stop // break; } // // Integrate the locks in this node into our list // Node->Tail.Next->Next = NextNode->Locks.Next; Node->Tail.Next = NextNode->Tail.Next; if (NextNode->Extent > Node->Extent) { Node->Extent = NextNode->Extent; } // // Free the now empty node. // RtlDeleteNoSplay(OverlappedSplayLinks, &LockQueue->SharedLockTree); FsRtlFreeLockTreeNode(NextNode); } } // // And return to our caller // return; } // // Internal Support Routine // VOID FsRtlPrivateInsertExclusiveLock ( IN PLOCK_QUEUE LockQueue, IN PEX_LOCK NewLock ) /*++ Routine Description: This routine adds a new exclusive lock record to the File lock's current lock queue. Arguments: LockQueue - Supplies the lock queue being modified NewLock - Supplies the new exclusive lock to add to the lock queue Return Value: None. --*/ { PRTL_SPLAY_LINKS OverlappedSplayLinks, ParentSplayLinks; BOOLEAN GreaterThan; OverlappedSplayLinks = FsRtlFindFirstOverlappingExclusiveNode( LockQueue->ExclusiveLockTree, &NewLock->LockInfo.StartingByte, &NewLock->LockInfo.EndingByte, &ParentSplayLinks, &GreaterThan ); // // This is the exclusive tree. Nothing can overlap (caller is supposed to insure this) unless // the lock is a zero length lock, in which case we just insert it - still. // ASSERT(!OverlappedSplayLinks || NewLock->LockInfo.Length.QuadPart == 0); // // Simple insert ... // RtlInitializeSplayLinks(&NewLock->Links); if (OverlappedSplayLinks) { // // With zero length locks we have OverlappedSplayLinks at the starting point // of a run of zero length locks, so we have to e flexible about where the new // node is inserted. // if (RtlRightChild(OverlappedSplayLinks)) { // // Right slot taken. We can use the left slot or go to the sucessor's left slot // if (RtlLeftChild(OverlappedSplayLinks)) { ASSERT(RtlLeftChild(RtlRealSuccessor(OverlappedSplayLinks)) == NULL); RtlInsertAsLeftChild(RtlRealSuccessor(OverlappedSplayLinks), &NewLock->Links); } else { RtlInsertAsLeftChild(OverlappedSplayLinks, &NewLock->Links); } } else { RtlInsertAsRightChild(OverlappedSplayLinks, &NewLock->Links); } } else if (ParentSplayLinks) { // // We have a real parent node in the tree, and must be at a leaf since // there was no overlap // if (GreaterThan) { ASSERT(RtlLeftChild(ParentSplayLinks) == NULL); RtlInsertAsLeftChild(ParentSplayLinks, &NewLock->Links); } else { ASSERT(RtlRightChild(ParentSplayLinks) == NULL); RtlInsertAsRightChild(ParentSplayLinks, &NewLock->Links); } } else { // // First node in the tree // LockQueue->ExclusiveLockTree = &NewLock->Links; } // // And return to our caller // return; } // // Internal Support Routine // VOID FsRtlPrivateCheckWaitingLocks ( IN PLOCK_INFO LockInfo, IN PLOCK_QUEUE LockQueue, IN KIRQL OldIrql ) /*++ Routine Description: This routine checks to see if any of the current waiting locks are now be satisfied, and if so it completes their IRPs. Arguments: LockInfo - LockInfo which LockQueue is member of LockQueue - Supplies queue which needs to be checked OldIrql - Irql to restore when LockQueue is released Return Value: PLOCK_QUEUE - Normally returns the input LockQueue, but if we switch from multiple queues to a single queue while this routine is running, may return a different queue. --*/ { PSINGLE_LIST_ENTRY *pLink, Link; NTSTATUS NewStatus; pLink = &LockQueue->WaitingLocks.Next; while ((Link = *pLink) != NULL) { PWAITING_LOCK WaitingLock; PIRP Irp; PIO_STACK_LOCATION IrpSp; BOOLEAN AccessGranted; FILE_LOCK_INFO FileLockInfo; // // Get a pointer to the waiting lock record // WaitingLock = CONTAINING_RECORD( Link, WAITING_LOCK, Link ); DebugTrace(0, Dbg, "FsRtlCheckWaitingLocks, Loop top, WaitingLock = %08lx\n", WaitingLock); // // Get a local copy of the necessary fields we'll need to use // Irp = WaitingLock->Irp; IrpSp = IoGetCurrentIrpStackLocation( Irp ); FileLockInfo.StartingByte = IrpSp->Parameters.LockControl.ByteOffset; FileLockInfo.Length = *IrpSp->Parameters.LockControl.Length; (ULONGLONG)FileLockInfo.EndingByte.QuadPart = (ULONGLONG)FileLockInfo.StartingByte.QuadPart + (ULONGLONG)FileLockInfo.Length.QuadPart - 1; FileLockInfo.FileObject = IrpSp->FileObject; FileLockInfo.ProcessId = IoGetRequestorProcess( Irp ); FileLockInfo.Key = IrpSp->Parameters.LockControl.Key; FileLockInfo.ExclusiveLock = BooleanFlagOn(IrpSp->Flags, SL_EXCLUSIVE_LOCK); // // Now case on whether we're trying to take out an exclusive lock or // a shared lock. And in both cases try to get the appropriate access // For the exclusive case we send in a NULL file object and process // id, this will ensure that the lookup does not give us write // access through an exclusive lock. // if (FileLockInfo.ExclusiveLock) { DebugTrace(0, Dbg, "FsRtlCheckWaitingLocks do we have write access?\n", 0); AccessGranted = FsRtlPrivateCheckForExclusiveLockAccess( LockQueue, &FileLockInfo ); } else { DebugTrace(0, Dbg, "FsRtlCheckWaitingLocks do we have read access?\n", 0); AccessGranted = FsRtlPrivateCheckForSharedLockAccess( LockQueue, &FileLockInfo ); } // // Now AccessGranted tells us whether we can really get the // access for the range we want // if (AccessGranted) { DebugTrace(0, Dbg, "FsRtlCheckWaitingLocks now has access\n", 0); // // Clear the cancel routine // IoSetCancelRoutine( Irp, NULL ); FsRtlPrivateInsertLock( LockInfo, IrpSp->FileObject, &FileLockInfo ); // // Now we need to remove this granted waiter and complete // it's irp. // *pLink = Link->Next; if (Link == LockQueue->WaitingLocksTail.Next) { LockQueue->WaitingLocksTail.Next = (PSINGLE_LIST_ENTRY) pLink; } // // Release LockQueue and complete this waiter // FsRtlReleaseLockQueue(LockQueue, OldIrql); // // Reference the fileobject over the completion attempt so we can have a // chance to cleanup safely if we fail // ObReferenceObject( FileLockInfo.FileObject ); // // Now we can complete the IRP, if we don't get back success // from the completion routine then we remove the lock we just // inserted. // FsRtlCompleteLockIrp( LockInfo, WaitingLock->Context, Irp, STATUS_SUCCESS, &NewStatus, FileLockInfo.FileObject ); if (!NT_SUCCESS(NewStatus)) { // // Irp was not sucessfull, remove lock // FsRtlPrivateRemoveLock ( LockInfo, &FileLockInfo, FALSE ); } // // Drop our private reference to the fileobject // ObDereferenceObject( FileLockInfo.FileObject ); // // Re-acquire queue lock // FsRtlReacquireLockQueue(LockInfo, LockQueue, &OldIrql); // // Start scan over from begining // pLink = &LockQueue->WaitingLocks.Next; // // Free up pool // FsRtlFreeWaitingLock( WaitingLock ); } else { DebugTrace( 0, Dbg, "FsRtlCheckWaitingLocks still no access\n", 0); // // Move to next lock // pLink = &Link->Next; } } // // And return to our caller // return; } BOOLEAN FsRtlPrivateCheckForExclusiveLockAccess ( IN PLOCK_QUEUE LockQueue, IN PFILE_LOCK_INFO FileLockInfo ) /*++ Routine Description: This routine checks to see if the caller can get an exclusive lock on the indicated range due to file locks in the passed in lock queue. Assumes Lock queue SpinLock is held by caller Arguments: LockQueue - Queue which needs to be checked for collision FileLockInfo - Lock which is being checked Return Value: BOOLEAN - TRUE if the indicated user can place the exclusive lock over the entire specified byte range, and FALSE otherwise --*/ { PRTL_SPLAY_LINKS SplayLinks, LastSplayLinks = NULL; PLOCKTREE_NODE Node; PSH_LOCK ShLock; PEX_LOCK ExLock; if (LockQueue->SharedLockTree && (SplayLinks = FsRtlFindFirstOverlappingSharedNode( LockQueue->SharedLockTree, &FileLockInfo->StartingByte, &FileLockInfo->EndingByte, &LastSplayLinks, NULL))) { Node = CONTAINING_RECORD(SplayLinks, LOCKTREE_NODE, Links); ShLock = CONTAINING_RECORD(Node->Locks.Next, SH_LOCK, Link); if (FileLockInfo->Length.QuadPart || ShLock->LockInfo.Length.QuadPart) { // // If we are checking a nonzero extent and overlapped, it is fatal. If we // are checking a zero extent and overlapped a nonzero extent, it is fatal. // return FALSE; } } if (LastSplayLinks) { LockQueue->SharedLockTree = RtlSplay(LastSplayLinks); LastSplayLinks = NULL; } if (LockQueue->ExclusiveLockTree && (SplayLinks = FsRtlFindFirstOverlappingExclusiveNode( LockQueue->ExclusiveLockTree, &FileLockInfo->StartingByte, &FileLockInfo->EndingByte, &LastSplayLinks, NULL))) { ExLock = CONTAINING_RECORD(SplayLinks, EX_LOCK, Links); if (FileLockInfo->Length.QuadPart || ExLock->LockInfo.Length.QuadPart) { // // If we are checking a nonzero extent and overlapped, it is fatal. If we // are checking a zero extent and overlapped a nonzero extent, it is fatal. // return FALSE; } } if (LastSplayLinks) { LockQueue->ExclusiveLockTree = RtlSplay(LastSplayLinks); } // // We searched the entire range without a conflict so we can grant // the exclusive lock // return TRUE; } BOOLEAN FsRtlPrivateCheckForSharedLockAccess ( IN PLOCK_QUEUE LockQueue, IN PFILE_LOCK_INFO FileLockInfo ) /*++ Routine Description: This routine checks to see if the caller can get a shared lock on the indicated range due to file locks in the passed in lock queue. Assumes Lock queue SpinLock is held by caller Arguments: LockQueue - Queue which needs to be checked for collision FileLockInfo - Lock which is being checked Arguments: Return Value: BOOLEAN - TRUE if the indicated user can place the shared lock over entire specified byte range, and FALSE otherwise --*/ { PEX_LOCK Lock; PRTL_SPLAY_LINKS SplayLinks, LastSplayLinks; BOOLEAN Status = TRUE; // // If there are no exclusive locks, this is quick ... // if (LockQueue->ExclusiveLockTree == NULL) { return TRUE; } // // No lock in the shared lock tree can prevent access, so just search the exclusive // tree for conflict. // for (SplayLinks = FsRtlFindFirstOverlappingExclusiveNode( LockQueue->ExclusiveLockTree, &FileLockInfo->StartingByte, &FileLockInfo->EndingByte, &LastSplayLinks, NULL); SplayLinks; SplayLinks = RtlRealSuccessor(SplayLinks)) { Lock = CONTAINING_RECORD( SplayLinks, EX_LOCK, Links ); if ((ULONGLONG)Lock->LockInfo.StartingByte.QuadPart > (ULONGLONG)FileLockInfo->EndingByte.QuadPart) { // // This node is covering a range greater than the range we care about, // so we're done // break; } // // We may not be able to grant the request if the fileobject, processid, // and key do not match. // if ((Lock->LockInfo.FileObject != FileLockInfo->FileObject) || (Lock->LockInfo.ProcessId != FileLockInfo->ProcessId) || (Lock->LockInfo.Key != FileLockInfo->Key)) { // // We have a mismatch between caller and owner. It is ok not to conflict // if the caller and owner will have/have zero length locks (zero length // locks cannot conflict). // if (FileLockInfo->Length.QuadPart || Lock->LockInfo.Length.QuadPart) { Status = FALSE; break; } } } if (LastSplayLinks) { LockQueue->ExclusiveLockTree = RtlSplay(LastSplayLinks); } // // We searched the entire range without a conflict so we can grant // the shared lock // return Status; } VOID FsRtlPrivateResetLowestLockOffset ( PLOCK_INFO LockInfo ) /*++ Routine Description: This routine resets the lowest lock offset hint in a LOCK_INFO to the lowest lock offset currently held by a lock inside of the LOCK_INFO. Arguments: LockInfo - the lock data to operate on Return Value: None --*/ { PEX_LOCK ExLock = NULL; PSH_LOCK ShLock = NULL; PFILE_LOCK_INFO LowestLockInfo = NULL; PRTL_SPLAY_LINKS SplayLinks; PLOCKTREE_NODE Node; // // Fix up the lowest lock offset if we have non-empty trees and there was // a lock in the low 32 bit region // if (LockInfo->LowestLockOffset != 0xffffffff && (LockInfo->LockQueue.SharedLockTree != NULL || LockInfo->LockQueue.ExclusiveLockTree != NULL)) { // // Grab the lowest nodes in the trees // if (LockInfo->LockQueue.SharedLockTree) { SplayLinks = LockInfo->LockQueue.SharedLockTree; while (RtlLeftChild(SplayLinks) != NULL) { SplayLinks = RtlLeftChild(SplayLinks); } Node = CONTAINING_RECORD( SplayLinks, LOCKTREE_NODE, Links ); ShLock = CONTAINING_RECORD( Node->Locks.Next, SH_LOCK, Link ); } if (LockInfo->LockQueue.ExclusiveLockTree) { SplayLinks = LockInfo->LockQueue.ExclusiveLockTree; while (RtlLeftChild(SplayLinks) != NULL) { SplayLinks = RtlLeftChild(SplayLinks); } ExLock = CONTAINING_RECORD( SplayLinks, EX_LOCK, Links ); } // // Figure out which of the lowest locks is actually lowest. We know that one of the lock // trees at least has a lock, so if we have don't have exclusive locks then we do know // we have shared locks ... // if (ExLock && (!ShLock || (ULONGLONG)ExLock->LockInfo.StartingByte.QuadPart < (ULONGLONG)ShLock->LockInfo.StartingByte.QuadPart)) { LowestLockInfo = &ExLock->LockInfo; } else { LowestLockInfo = &ShLock->LockInfo; } if (LowestLockInfo->StartingByte.HighPart == 0) { LockInfo->LowestLockOffset = LowestLockInfo->StartingByte.LowPart; } else { LockInfo->LowestLockOffset = 0xffffffff; } } else { // // If there are no locks, set the lock offset high // LockInfo->LowestLockOffset = 0xffffffff; } } NTSTATUS FsRtlPrivateFastUnlockAll ( IN PFILE_LOCK FileLock, IN PFILE_OBJECT FileObject, IN PEPROCESS ProcessId, IN ULONG Key, IN BOOLEAN MatchKey, IN PVOID Context OPTIONAL ) /*++ Routine Description: This routine performs an Unlock all operation on the current locks associated with the specified file lock. Only those locks with a matching file object and process id are freed. Additionally, it is possible to free only those locks which also match a given key. Arguments: FileLock - Supplies the file lock being freed. FileObject - Supplies the file object associated with the file lock ProcessId - Supplies the Process Id assoicated with the locks to be freed Key - Supplies the Key to use in this operation MatchKey - Whether or not the Key must also match for lock to be freed. Context - Supplies an optional context to use when completing waiting lock irps. Return Value: None --*/ { PLOCK_INFO LockInfo; PLOCK_QUEUE LockQueue; PSINGLE_LIST_ENTRY *pLink, *SavepLink, Link; NTSTATUS NewStatus; KIRQL OldIrql; LARGE_INTEGER MaxOffset, SaveEndingByte; BOOLEAN UnlockRoutine; PSH_LOCK ShLock; PEX_LOCK ExLock; PRTL_SPLAY_LINKS SplayLinks, SuccessorLinks; PLOCKTREE_NODE Node; DebugTrace(+1, Dbg, "FsRtlPrivateFastUnlockAll, FileLock = %08lx\n", FileLock); if ((LockInfo = FileLock->LockInformation) == NULL) { // // No lock information on this FileLock // DebugTrace(+1, Dbg, "FsRtlPrivateFastUnlockAll, No LockInfo\n", FileLock); return STATUS_RANGE_NOT_LOCKED; } FileObject->LastLock = NULL; LockQueue = &LockInfo->LockQueue; // // Grab the waiting lock queue spinlock to exclude anyone from messing // with the queue while we're using it // FsRtlReacquireLockQueue(LockInfo, LockQueue, &OldIrql); if (LockQueue->SharedLockTree == NULL && LockQueue->ExclusiveLockTree == NULL) { // // No locks on this FileLock // DebugTrace(+1, Dbg, "FsRtlPrivateFastUnlockAll, No LockTrees\n", FileLock); FsRtlReleaseLockQueue(LockQueue, OldIrql); return STATUS_RANGE_NOT_LOCKED; } // // Remove all matching locks in the shared lock tree // if (LockQueue->SharedLockTree != NULL) { // // Grab the lowest node in the tree // SplayLinks = LockQueue->SharedLockTree; while (RtlLeftChild(SplayLinks) != NULL) { SplayLinks = RtlLeftChild(SplayLinks); } // // Walk all nodes in the tree // UnlockRoutine = FALSE; for (; SplayLinks; SplayLinks = SuccessorLinks) { Node = CONTAINING_RECORD(SplayLinks, LOCKTREE_NODE, Links ); // // Save the next node because we may split this node apart in the process // of deleting locks. It would be a waste of time to traverse those split // nodes. The only case in which we will not have traversed the entire list // before doing the split will be if there is an unlock routine attached // to this FileLock in which case we will be restarting the entire scan // anyway. // SuccessorLinks = RtlRealSuccessor(SplayLinks); // // Search down the current lock queue looking for a match on // the file object and process id // SavepLink = NULL; SaveEndingByte.QuadPart = 0; pLink = &Node->Locks.Next; while ((Link = *pLink) != NULL) { ShLock = CONTAINING_RECORD( Link, SH_LOCK, Link ); DebugTrace(0, Dbg, "Top of ShLock Loop, Lock = %08lx\n", ShLock ); if ((ShLock->LockInfo.FileObject == FileObject) && (ShLock->LockInfo.ProcessId == ProcessId) && (!MatchKey || ShLock->LockInfo.Key == Key)) { DebugTrace(0, Dbg, "Found one to unlock\n", 0); // // We have a match so now is the time to delete this lock. // Save the neccesary information to do the split node check. // Remove the lock from the list, then call the // optional unlock routine, then delete the lock. // if (SavepLink == NULL) { // // Need to remember where the first lock was deleted // SavepLink = pLink; } if ((ULONGLONG)ShLock->LockInfo.EndingByte.QuadPart > (ULONGLONG)SaveEndingByte.QuadPart) { // // Need to remember where the last offset affected by deleted locks is // SaveEndingByte.QuadPart = ShLock->LockInfo.EndingByte.QuadPart; } if (*pLink == Node->Tail.Next) { // // Deleting the tail node of the list. Safe even if deleting the // first node since this implies we're also deleting the last node // in the node which means we'll delete the node ... // Node->Tail.Next = CONTAINING_RECORD( pLink, SINGLE_LIST_ENTRY, Next ); } *pLink = Link->Next; if (LockInfo->UnlockRoutine != NULL) { // // Signal a lock that needs to have a special unlock routine // called on it. This is complex to deal with since we'll have // to release the queue, call it, and reacquire - meaning we // also have to restart. But we still need to reorder the node // first ... // UnlockRoutine = TRUE; break; } FsRtlFreeSharedLock( ShLock ); } else { // // Move to next lock // pLink = &Link->Next; } if (SavepLink == NULL && (ULONGLONG)ShLock->LockInfo.EndingByte.QuadPart > (ULONGLONG)MaxOffset.QuadPart) { // // Save the max offset until we have deleted our first node // MaxOffset.QuadPart = ShLock->LockInfo.EndingByte.QuadPart; } } if (SavepLink) { // // Locks were actually deleted here, so we have to check the state of the node // if (Node->Locks.Next == NULL) { // // We have just deleted everything at this node // LockQueue->SharedLockTree = RtlDelete(SplayLinks); FsRtlFreeLockTreeNode(Node); } else { // // Now that we have deleted all matching locks in this node, we do the // check on the node to split out any now non-overlapping locks. Conceptually, // we have deleted just one big lock that starts at the starting byte of the // first deleted lock and extends to the last byte of the last deleted lock. // FsRtlSplitLocks(Node, SavepLink, &SaveEndingByte, &MaxOffset); } } if (UnlockRoutine) { // // We dropped out of the node scan because we had a lock that needs extra // processing during unlock. Do it. // FsRtlReleaseLockQueue(LockQueue, OldIrql); LockInfo->UnlockRoutine(Context, &ShLock->LockInfo); FsRtlReacquireLockQueue(LockInfo, LockQueue, &OldIrql); FsRtlFreeSharedLock(ShLock); UnlockRoutine = FALSE; // // We have to restart the scan, because the list may have changed while // we were in the unlock routine. Careful, because the tree may be empty. // if (SuccessorLinks = LockQueue->SharedLockTree) { while (RtlLeftChild(SuccessorLinks) != NULL) { SuccessorLinks = RtlLeftChild(SuccessorLinks); } } } } } // // Remove all matching locks in the exclusive lock tree // if (LockQueue->ExclusiveLockTree != NULL) { SplayLinks = LockQueue->ExclusiveLockTree; while (RtlLeftChild(SplayLinks) != NULL) { SplayLinks = RtlLeftChild(SplayLinks); } // // Walk all nodes in the tree // UnlockRoutine = FALSE; for (; SplayLinks; SplayLinks = SuccessorLinks ) { SuccessorLinks = RtlRealSuccessor(SplayLinks); ExLock = CONTAINING_RECORD( SplayLinks, EX_LOCK, Link ); DebugTrace(0, Dbg, "Top of ExLock Loop, Lock = %08lx\n", ExLock ); if ((ExLock->LockInfo.FileObject == FileObject) && (ExLock->LockInfo.ProcessId == ProcessId) && (!MatchKey || ExLock->LockInfo.Key == Key)) { LockQueue->ExclusiveLockTree = RtlDelete(&ExLock->Links); if (LockInfo->UnlockRoutine != NULL) { // // We're dropping out of the node scan because we have a lock // that needs extra processing during unlock. Do it. // FsRtlReleaseLockQueue(LockQueue, OldIrql); LockInfo->UnlockRoutine(Context, &ExLock->LockInfo); FsRtlReacquireLockQueue(LockInfo, LockQueue, &OldIrql); // // We have to restart the scan, because the list may have changed while // we were in the unlock routine. Careful, because the tree may be empty. // if (SuccessorLinks = LockQueue->ExclusiveLockTree) { while (RtlLeftChild(SuccessorLinks) != NULL) { SuccessorLinks = RtlLeftChild(SuccessorLinks); } } } FsRtlFreeExclusiveLock(ExLock); } } } // // Search down the waiting lock queue looking for a match on the // file object and process id. // pLink = &LockQueue->WaitingLocks.Next; while ((Link = *pLink) != NULL) { PWAITING_LOCK WaitingLock; PIRP WaitingIrp; PIO_STACK_LOCATION WaitingIrpSp; WaitingLock = CONTAINING_RECORD( Link, WAITING_LOCK, Link ); DebugTrace(0, Dbg, "Top of Waiting Loop, WaitingLock = %08lx\n", WaitingLock); // // Get a copy of the necessary fields we'll need to use // WaitingIrp = WaitingLock->Irp; WaitingIrpSp = IoGetCurrentIrpStackLocation( WaitingIrp ); if ((FileObject == WaitingIrpSp->FileObject) && (ProcessId == IoGetRequestorProcess( WaitingIrp )) && (!MatchKey || Key == WaitingIrpSp->Parameters.LockControl.Key)) { DebugTrace(0, Dbg, "Found a waiting lock to abort\n", 0); // // We now void the cancel routine in the irp // IoSetCancelRoutine( WaitingIrp, NULL ); WaitingIrp->IoStatus.Information = 0; // // We have a match so now is the time to delete this waiter // But we must not mess up our link iteration variable. We // do this by simply starting the iteration over again, // after we delete ourselves. We also will deallocate the // lock after we delete it. // *pLink = Link->Next; if (Link == LockQueue->WaitingLocksTail.Next) { LockQueue->WaitingLocksTail.Next = (PSINGLE_LIST_ENTRY) pLink; } FsRtlReleaseLockQueue(LockQueue, OldIrql); // // And complete this lock request Irp // FsRtlCompleteLockIrp( LockInfo, WaitingLock->Context, WaitingIrp, STATUS_SUCCESS, &NewStatus, NULL ); // // Reaqcuire lock queue spinlock and start over // FsRtlReacquireLockQueue(LockInfo, LockQueue, &OldIrql); // // Start over // pLink = &LockQueue->WaitingLocks.Next; // // Put memory onto free list // FsRtlFreeWaitingLock( WaitingLock ); continue; } // // Move to next lock // pLink = &Link->Next; } // // At this point we've gone through unlocking everything. So // now try and release any waiting locks. // FsRtlPrivateCheckWaitingLocks( LockInfo, LockQueue, OldIrql ); // // We deleted a (possible) bunch of locks, go repair the lowest lock offset // FsRtlPrivateResetLowestLockOffset( LockInfo ); FsRtlReleaseLockQueue( LockQueue, OldIrql ); // // Check free lock/node lists to be within reason // #ifndef USERTEST KeRaiseIrql (DISPATCH_LEVEL, &OldIrql); FsRtlPrivateLimitFreeLockList (&KeGetCurrentPrcb()->FsRtlFreeSharedLockList); FsRtlPrivateLimitFreeLockList (&KeGetCurrentPrcb()->FsRtlFreeExclusiveLockList); FsRtlPrivateLimitFreeLockList (&KeGetCurrentPrcb()->FsRtlFreeWaitingLockList); FsRtlPrivateLimitFreeLockList (&KeGetCurrentPrcb()->FsRtlFreeLockTreeNodeList); KeLowerIrql (OldIrql); #endif // // and return to our caller // DebugTrace(-1, Dbg, "FsRtlFastUnlockAll -> VOID\n", 0); return STATUS_SUCCESS; } VOID FsRtlPrivateLimitFreeLockList ( IN PSINGLE_LIST_ENTRY Link ) /*++ Routine Description: Scans list and free exceesive elments back to pool. Note: this function assumes that the Link field is the first element in the allocated structures. Arguments: Link - List to check length on Return Value: None --*/ { PSINGLE_LIST_ENTRY FreeLink; ULONG Count; // // Leave some entries on the free list // for (Count=FREE_LOCK_SIZE; Count && Link; Count--, Link = Link->Next) ; // // If not end of list, then free the remaining entires // if (Link) { // // Chop free list, and get list of Links to free // FreeLink = Link->Next; Link->Next = NULL; // // Free all remaining links // while (FreeLink) { Link = FreeLink->Next; ExFreePool (FreeLink); FreeLink = Link; } } } VOID FsRtlPrivateCancelFileLockIrp ( IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp ) /*++ Routine Description: This routine implements the cancel function for an irp saved in a waiting lock queue Arguments: DeviceObject - Ignored Irp - Supplies the Irp being cancelled. A pointer to the FileLock structure for the lock is stored in the information field of the irp's iosb. Return Value: none. --*/ { PSINGLE_LIST_ENTRY *pLink, Link; PLOCK_INFO LockInfo; PLOCK_QUEUE LockQueue; KIRQL OldIrql; NTSTATUS NewStatus; UNREFERENCED_PARAMETER( DeviceObject ); // // The information field is used to store a pointer to the file lock // containing the irp // LockInfo = (PLOCK_INFO) (Irp->IoStatus.Information); // // Release the cancel spinlock // IoReleaseCancelSpinLock( Irp->CancelIrql ); // // Iterate through all lock queues. Note on a UP build there is // only one lock queue. // LockQueue = &LockInfo->LockQueue; // // Iterate through all of the waiting locks looking for a canceled one // Lock the waiting queue // FsRtlReacquireLockQueue(LockInfo, LockQueue, &OldIrql); pLink = &LockQueue->WaitingLocks.Next; while ((Link = *pLink) != NULL) { PWAITING_LOCK WaitingLock; // // Get a pointer to the waiting lock record // WaitingLock = CONTAINING_RECORD( Link, WAITING_LOCK, Link ); DebugTrace(0, Dbg, "FsRtlPrivateCancelFileLockIrp, Loop top, WaitingLock = %08lx\n", WaitingLock); if( WaitingLock->Irp != Irp ) { pLink = &Link->Next; continue; } // // We've found it -- remove it from the list // *pLink = Link->Next; if (Link == LockQueue->WaitingLocksTail.Next) { LockQueue->WaitingLocksTail.Next = (PSINGLE_LIST_ENTRY) pLink; } Irp->IoStatus.Information = 0; // // Release LockQueue and complete this waiter // FsRtlReleaseLockQueue(LockQueue, OldIrql); // // Complete this waiter // FsRtlCompleteLockIrp( LockInfo, WaitingLock->Context, Irp, STATUS_CANCELLED, &NewStatus, NULL ); // // Free up pool // FsRtlFreeWaitingLock( WaitingLock ); // // Our job is done! // return; } // // Release lock queue // FsRtlReleaseLockQueue(LockQueue, OldIrql); return; } #ifdef USERTEST void DumpTree( FILE_LOCK *FileLock, ULONG DumpType) { PLOCK_INFO LockInfo; PRTL_SPLAY_LINKS SplayLinks, Ptr; PLOCKTREE_NODE Node; PSINGLE_LIST_ENTRY Link; ULONG Indent = 1, i; printf("++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n"); LockInfo = FileLock->LockInformation; if (LockInfo == NULL) { printf("No LockInfo\n"); goto end; } printf("LowestLockOffset = %08x\n", LockInfo->LowestLockOffset); SplayLinks = LockInfo->LockQueue.SharedLockTree; if (SplayLinks == NULL) { printf("Shared LockTree is empty\n"); goto excl; } while (RtlLeftChild(SplayLinks) != NULL) { SplayLinks = RtlLeftChild(SplayLinks); Indent++; } while (SplayLinks) { PSH_LOCK Lock; Node = CONTAINING_RECORD( SplayLinks, LOCKTREE_NODE, Links ); for (i = 1; i < Indent; i++) { printf(" "); } printf("Node 0x%08x Extent = %08x%08x\n", Node, (ULONG)((Node->Extent >> 32) & 0xffffffff), (ULONG)(Node->Extent & 0xffffffff)); for (Link = Node->Locks.Next; Link; Link = Link->Next) { Lock = CONTAINING_RECORD( Link, SH_LOCK, Link ); for (i = 1; i < Indent; i++) { printf(" "); } printf("Start = %08x%08x Len = %08x%08x End = %08x%08x\n", Lock->LockInfo.StartingByte.HighPart, Lock->LockInfo.StartingByte.LowPart, Lock->LockInfo.Length.HighPart, Lock->LockInfo.Length.LowPart, Lock->LockInfo.EndingByte.HighPart, Lock->LockInfo.EndingByte.LowPart); } printf("\n"); Ptr = SplayLinks; /* first check to see if there is a right subtree to the input link if there is then the real successor is the left most node in the right subtree. That is find and return P in the following diagram Links \ . . . / P \ */ if ((Ptr = RtlRightChild(SplayLinks)) != NULL) { Indent++; while (RtlLeftChild(Ptr) != NULL) { Indent++; Ptr = RtlLeftChild(Ptr); } SplayLinks = Ptr; } else { /* we do not have a right child so check to see if have a parent and if so find the first ancestor that we are a left decendent of. That is find and return P in the following diagram P / . . . Links */ Ptr = SplayLinks; while (RtlIsRightChild(Ptr)) { Indent--; Ptr = RtlParent(Ptr); } if (!RtlIsLeftChild(Ptr)) { // // we do not have a real successor so we simply return // NULL // SplayLinks = NULL; } else { Indent--; SplayLinks = RtlParent(Ptr); } } } // // .. and the exclusive side // excl: SplayLinks = LockInfo->LockQueue.ExclusiveLockTree; Indent = 1; if (SplayLinks == NULL) { printf("Exclusive LockTree is empty\n"); goto end; } while (RtlLeftChild(SplayLinks) != NULL) { SplayLinks = RtlLeftChild(SplayLinks); Indent++; } while (SplayLinks) { PEX_LOCK Lock; Lock = CONTAINING_RECORD( SplayLinks, EX_LOCK, Links ); for (i = 1; i < Indent; i++) { printf(" "); } printf("Start = %08x%08x Len = %08x%08x End = %08x%08x\n", Lock->LockInfo.StartingByte.HighPart, Lock->LockInfo.StartingByte.LowPart, Lock->LockInfo.Length.HighPart, Lock->LockInfo.Length.LowPart, Lock->LockInfo.EndingByte.HighPart, Lock->LockInfo.EndingByte.LowPart); Ptr = SplayLinks; /* first check to see if there is a right subtree to the input link if there is then the real successor is the left most node in the right subtree. That is find and return P in the following diagram Links \ . . . / P \ */ if ((Ptr = RtlRightChild(SplayLinks)) != NULL) { Indent++; while (RtlLeftChild(Ptr) != NULL) { Indent++; Ptr = RtlLeftChild(Ptr); } SplayLinks = Ptr; } else { /* we do not have a right child so check to see if have a parent and if so find the first ancestor that we are a left decendent of. That is find and return P in the following diagram P / . . . Links */ Ptr = SplayLinks; while (RtlIsRightChild(Ptr)) { Indent--; Ptr = RtlParent(Ptr); } if (!RtlIsLeftChild(Ptr)) { // // we do not have a real successor so we simply return // NULL // SplayLinks = NULL; } else { Indent--; SplayLinks = RtlParent(Ptr); } } } end: printf("------------------------------------------------------------------\n"); } #endif