/*++ 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" // // Local constants // // // Local debug trace level // #define Dbg (0x20000000) // // YA definition of INLINE // #ifndef INLINE #define INLINE __inline #endif #define TAG_EXCLUSIVE_LOCK 'xeLF' #define TAG_FILE_LOCK 'lfLF' #define TAG_LOCK_INFO 'ilLF' #define TAG_LOCKTREE_NODE 'nlLF' #define TAG_SHARED_LOCK 'hsLF' #define TAG_WAITING_LOCK 'lwLF' // // Globals // // // This mutex synchronizes threads competing to initialize file lock structures. // FAST_MUTEX FsRtlCreateLockInfo; // // This spinlock resolves the race between teardown of a file's lock info and // cancellation of waiting locks for that file. We must always be able to save // a cancelled IRP for the cancelling thread. // KSPIN_LOCK FsRtlFileLockCancelCollideLock; SINGLE_LIST_ENTRY FsRtlFileLockCancelCollideList; // // Lookaside lists // // Here is a good place to note why this is still nonpaged. We need to be able // to cancel lock IRPs at DPC, and the ripple effects of this (esp. granting waiting // locks and synchronizing the waiting list) implies some unfortunate realities. // // This should be reinvestigated post NT 5.0. // NPAGED_LOOKASIDE_LIST FsRtlSharedLockLookasideList; NPAGED_LOOKASIDE_LIST FsRtlExclusiveLockLookasideList; NPAGED_LOOKASIDE_LIST FsRtlWaitingLockLookasideList; NPAGED_LOOKASIDE_LIST FsRtlLockTreeNodeLookasideList; NPAGED_LOOKASIDE_LIST FsRtlLockInfoLookasideList; PAGED_LOOKASIDE_LIST FsRtlFileLockLookasideList; // // Local structures // /*++ 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 average 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 (they actually occupy different pool block sizes). 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 bogus, and has been changed (NT 4.0). 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 _LOCKTREE_NODE { // // List of locks under this node // SINGLE_LIST_ENTRY Locks; // // Flag whether this node is holey as a result of a failed allocation // during a node split. During deletion of shared locks, we may // discover that the locks in the node no longer have total overlap // but cannot allocate resources to create the new nodes in the tree. // // Any insert into the region occupied by a holey node will finish by // trying to split a holey node up. Any split or access check in a // holey node must completely traverse the locks at the node. // BOOLEAN HoleyNode; // // Maximum byte offset affected by locks in this node. // 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 in this node. // 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. // SINGLE_LIST_ENTRY Link; // // The actual locked range // FILE_LOCK_INFO LockInfo; } 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. // RTL_SPLAY_LINKS Links; // // The actual locked range // FILE_LOCK_INFO LockInfo; } 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 // SINGLE_LIST_ENTRY Link; // // The optional procedure to call to complete a request. We require this // in the individual waiters so that we can heal the race between cancellation // and teardown of a lock structure. // PCOMPLETE_LOCK_IRP_ROUTINE CompleteLockIrpRoutine; // // 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, *PWAITING_LOCK; // // Each lock or waiting onto some lock queue. // typedef struct _LOCK_QUEUE { // // Sync 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 a non-paged pool // 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; // // Local Macros // // // The following macros sort out the allocation of internal structures. // INLINE PSH_LOCK FsRtlAllocateSharedLock ( VOID ) { return (PSH_LOCK) ExAllocateFromNPagedLookasideList( &FsRtlSharedLockLookasideList ); } INLINE PEX_LOCK FsRtlAllocateExclusiveLock ( VOID ) { return (PEX_LOCK) ExAllocateFromNPagedLookasideList( &FsRtlExclusiveLockLookasideList ); } INLINE PWAITING_LOCK FsRtlAllocateWaitingLock ( VOID ) { return (PWAITING_LOCK) ExAllocateFromNPagedLookasideList( &FsRtlWaitingLockLookasideList ); } INLINE PLOCKTREE_NODE FsRtlAllocateLockTreeNode ( VOID ) { return (PLOCKTREE_NODE) ExAllocateFromNPagedLookasideList( &FsRtlLockTreeNodeLookasideList ); } INLINE PLOCK_INFO FsRtlAllocateLockInfo ( VOID ) { return (PLOCK_INFO) ExAllocateFromNPagedLookasideList( &FsRtlLockInfoLookasideList ); } INLINE VOID FsRtlFreeSharedLock ( IN PSH_LOCK C ) { ExFreeToNPagedLookasideList( &FsRtlSharedLockLookasideList, (PVOID)C ); } INLINE VOID FsRtlFreeExclusiveLock ( IN PEX_LOCK C ) { ExFreeToNPagedLookasideList( &FsRtlExclusiveLockLookasideList, (PVOID)C ); } INLINE VOID FsRtlFreeWaitingLock ( IN PWAITING_LOCK C ) { ExFreeToNPagedLookasideList( &FsRtlWaitingLockLookasideList, (PVOID)C ); } INLINE VOID FsRtlFreeLockTreeNode ( IN PLOCKTREE_NODE C ) { ExFreeToNPagedLookasideList( &FsRtlLockTreeNodeLookasideList, (PVOID)C ); } INLINE VOID FsRtlFreeLockInfo ( IN PLOCK_INFO C ) { ExFreeToNPagedLookasideList( &FsRtlLockInfoLookasideList, (PVOID)C ); } #define FsRtlAcquireLockQueue(a,b) ExAcquireSpinLock(&(a)->QueueSpinLock, b) #define FsRtlReleaseLockQueue(a,b) ExReleaseSpinLock(&(a)->QueueSpinLock, b) #define FsRtlAcquireLockQueueAtDpc(a) ExAcquireSpinLockAtDpcLevel(&(a)->QueueSpinLock) #define FsRtlReleaseLockQueueFromDpc(a) ExReleaseSpinLockFromDpcLevel(&(a)->QueueSpinLock) #define FsRtlAcquireCancelCollide(a) ExAcquireSpinLock(&FsRtlFileLockCancelCollideLock, a) #define FsRtlReleaseCancelCollide(a) ExReleaseSpinLock(&FsRtlFileLockCancelCollideLock, a) #define FsRtlAcquireCancelCollideAtDpc(a) ExAcquireSpinLockAtDpcLevel(&FsRtlFileLockCancelCollideLock) #define FsRtlReleaseCancelCollideFromDpc(a) ExReleaseSpinLockFromDpcLevel(&FsRtlFileLockCancelCollideLock) // // Generic way to complete a lock IRP. We like to treat this as an overloaded // function so it can be used with LOCK_INFO, FILE_LOCK and WAITING_LOCK // structures, as appropriate using paged/nonpaged pool to discover the completion // routine. // #define FsRtlCompleteLockIrp( A, B, C, D, E, F ) \ FsRtlCompleteLockIrpReal( (A)->CompleteLockIrpRoutine, \ B, \ C, \ D, \ E, \ F ) INLINE VOID FsRtlCompleteLockIrpReal ( IN PCOMPLETE_LOCK_IRP_ROUTINE CompleteLockIrpRoutine, IN PVOID Context, IN PIRP Irp, IN NTSTATUS Status, IN PNTSTATUS NewStatus, IN PFILE_OBJECT FileObject ) { // // This fools the compiler into generating the Status only once // if it is calculated from an expression. // NTSTATUS LocalStatus = Status; if (CompleteLockIrpRoutine != NULL) { if (FileObject != NULL) { FileObject->LastLock = NULL; } Irp->IoStatus.Status = LocalStatus; *NewStatus = CompleteLockIrpRoutine( Context, Irp ); } else { FsRtlCompleteRequest( Irp, LocalStatus ); *NewStatus = LocalStatus; } } // // 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 ); PSH_LOCK FsRtlFindFirstOverlapInNode ( IN PLOCKTREE_NODE Node, IN PLARGE_INTEGER StartingByte, IN PLARGE_INTEGER EndingByte ); BOOLEAN FsRtlPrivateInsertLock ( IN PLOCK_INFO LockInfo, IN PFILE_OBJECT FileObject, IN PFILE_LOCK_INFO FileLockInfo ); BOOLEAN 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 ); 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 ); #ifdef ALLOC_PRAGMA #pragma alloc_text(INIT, FsRtlInitializeFileLocks) #endif VOID FsRtlInitializeFileLocks ( VOID ) /*++ Routine Description: Initializes the global portion of the filelock package. Arguments: None Return Value: None. --*/ { // // Build the lookaside lists for our internal structures. // ExInitializeNPagedLookasideList( &FsRtlSharedLockLookasideList, NULL, NULL, 0, sizeof(SH_LOCK), TAG_SHARED_LOCK, 16 ); ExInitializeNPagedLookasideList( &FsRtlExclusiveLockLookasideList, NULL, NULL, 0, sizeof(EX_LOCK), TAG_EXCLUSIVE_LOCK, 16 ); ExInitializeNPagedLookasideList( &FsRtlWaitingLockLookasideList, NULL, NULL, 0, sizeof(WAITING_LOCK), TAG_WAITING_LOCK, 16 ); ExInitializeNPagedLookasideList( &FsRtlLockTreeNodeLookasideList, NULL, NULL, 0, sizeof(LOCKTREE_NODE), TAG_LOCKTREE_NODE, 16 ); ExInitializeNPagedLookasideList( &FsRtlLockInfoLookasideList, NULL, NULL, 0, sizeof(LOCK_INFO), TAG_LOCK_INFO, 8 ); ExInitializePagedLookasideList( &FsRtlFileLockLookasideList, NULL, NULL, 0, sizeof(FILE_LOCK), TAG_FILE_LOCK, 8 ); // // Initialize the LockInfo creation mutex // ExInitializeFastMutex(&FsRtlCreateLockInfo); // // Initialize the cancel collision lock // KeInitializeSpinLock( &FsRtlFileLockCancelCollideLock ); FsRtlFileLockCancelCollideList.Next = NULL; } 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. // LockInfo = FsRtlAllocateLockInfo(); if (LockInfo == 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 vs. cancels and lock the queue. // FsRtlAcquireCancelCollide( &OldIrql ); FsRtlAcquireLockQueueAtDpc( &LockInfo->LockQueue ); // // 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. // // This will be incredibly rare, requiring a cancel to be pending in an async thread // while cleanup/close occurs in the owning filesystem, triggering teardown. // 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 so // we can know that we beat cancellation) before // we completing the irp. // FsRtlReleaseLockQueueFromDpc( &LockInfo->LockQueue ); IoAcquireCancelSpinLock( &Irp->CancelIrql ); IoSetCancelRoutine( Irp, NULL ); // // If it got cancelled, the cancel routine is now waiting on the other // side of the cancel collide for us to push it onto the collide list. // It'll get the IRP there as opposed to the shortly-to-be-axed lock // structure. // if (Irp->Cancel) { IoReleaseCancelSpinLock( Irp->CancelIrql ); PushEntryList( &FsRtlFileLockCancelCollideList, &WaitingLock->Link ); Irp = NULL; } else { IoReleaseCancelSpinLock( Irp->CancelIrql ); } // // If we got the ownership of the IRP, release the collide and complete // it, otherwise spin back around for more. // if (Irp) { FsRtlReleaseCancelCollide( OldIrql ); Irp->IoStatus.Information = 0; FsRtlCompleteLockIrp( LockInfo, WaitingLock->Context, Irp, STATUS_RANGE_NOT_LOCKED, &NewStatus, NULL ); FsRtlFreeWaitingLock( WaitingLock ); FsRtlAcquireCancelCollide( &OldIrql ); } FsRtlAcquireLockQueueAtDpc( &LockInfo->LockQueue ); } // // Release locks and free pool used to track the lock info on this file. // FsRtlReleaseLockQueueFromDpc( &LockInfo->LockQueue ); FsRtlReleaseCancelCollide( OldIrql ); FsRtlFreeLockInfo( LockInfo ); // // Unlink LockInfo from FileLock // FileLock->LockInformation = NULL; // // And return to our caller // DebugTrace(-1, Dbg, "FsRtlUninitializeFileLock -> VOID\n", 0 ); return; } PFILE_LOCK FsRtlAllocateFileLock ( IN PCOMPLETE_LOCK_IRP_ROUTINE CompleteLockIrpRoutine OPTIONAL, IN PUNLOCK_ROUTINE UnlockRoutine OPTIONAL ) { PFILE_LOCK FileLock; FileLock = ExAllocateFromPagedLookasideList( &FsRtlFileLockLookasideList ); if (FileLock != NULL) { FsRtlInitializeFileLock( FileLock, CompleteLockIrpRoutine, UnlockRoutine ); } return FileLock; } VOID FsRtlFreeFileLock ( IN PFILE_LOCK FileLock ) { FsRtlUninitializeFileLock( FileLock ); ExFreeToPagedLookasideList( &FsRtlFileLockLookasideList, FileLock ); } 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 StartingByte - supplies the first byte offset of the range to check EndingByte - 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; if (LastEdgeNode) *LastEdgeNode = NULL; if (GreaterThan) *GreaterThan = FALSE; 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 ); // // We may have to go right in the tree if this lock covers a range before the start of this // range we are looking for overlap on or this lock is [0, 0). This is important since a lock // on [0, 0) will look like the extent is from [0, ~0], which is the only case where the zero // length lock relation of End < Start does not hold. // if (Node->Extent < (ULONGLONG)StartingByte->QuadPart || (Lock->LockInfo.StartingByte.QuadPart == 0 && Lock->LockInfo.Length.QuadPart == 0)) { if ((ULONGLONG)Lock->LockInfo.EndingByte.QuadPart == (ULONGLONG)EndingByte->QuadPart && (ULONGLONG)Lock->LockInfo.StartingByte.QuadPart == (ULONGLONG)StartingByte->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)EndingByte->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)StartingByte->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 StartingByte - supplies the first byte offset of the range to check EndingByte - 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; if (LastEdgeNode) *LastEdgeNode = NULL; if (GreaterThan) *GreaterThan = FALSE; LastOverlapNode = NULL; SplayLinks = Tree; while (SplayLinks) { Lock = CONTAINING_RECORD( SplayLinks, EX_LOCK, Links ); // // We may have to go right in the tree if this lock covers a range before the start of this // range we are looking for overlap on or this lock is [0, 0). This is important since a lock // on [0, 0) will look like the extent is from [0, ~0], which is the only case where the zero // length lock relation of End < Start does not hold. // if ((ULONGLONG)Lock->LockInfo.EndingByte.QuadPart < (ULONGLONG)StartingByte->QuadPart || (Lock->LockInfo.StartingByte.QuadPart == 0 && Lock->LockInfo.Length.QuadPart == 0)) { if ((ULONGLONG)Lock->LockInfo.EndingByte.QuadPart == (ULONGLONG)EndingByte->QuadPart && (ULONGLONG)Lock->LockInfo.StartingByte.QuadPart == (ULONGLONG)StartingByte->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)EndingByte->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)StartingByte->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; } PSH_LOCK FsRtlFindFirstOverlapInNode ( IN PLOCKTREE_NODE Node, IN PLARGE_INTEGER StartingByte, IN PLARGE_INTEGER EndingByte ) /*++ Routine Description: This routine examines a shared lock node, usually a node which is known to be composed of several non-overlapping lock segments (holey), for true overlap with the indicated range. This is not handled in the normal overlap check (..FindFirstOverlappingSharedLock) since the needs for holey checks are rather different than the full node check. Arguments: Node - the lock tree node to be examined for overlap StartingByte - supplies the first byte offset of the range to check EndingByte - supplies the last byte offset of the range to check Return Value: PSH_LOCK - the first lock which overlaps with the specified range. --*/ { PSH_LOCK Lock; PSINGLE_LIST_ENTRY Link; for (Link = Node->Locks.Next; Link; Link = Link->Next) { Lock = CONTAINING_RECORD( Link, SH_LOCK, Link ); // // Logic is the same as above checkers. If the ending byte of the lock is less than the // starting byte of the range, OR we have the weird [0, 0) case, then the lock is almost // certainly less than the range. // if ((ULONGLONG)Lock->LockInfo.EndingByte.QuadPart < (ULONGLONG)StartingByte->QuadPart || (Lock->LockInfo.StartingByte.QuadPart == 0 && Lock->LockInfo.Length.QuadPart == 0)) { // // ... except if the lock and range are equivalent, in which case we have discovered // zero lock/range overlap. // if ((ULONGLONG)Lock->LockInfo.EndingByte.QuadPart == (ULONGLONG)EndingByte->QuadPart && (ULONGLONG)Lock->LockInfo.StartingByte.QuadPart == (ULONGLONG)StartingByte->QuadPart) { return Lock; } // // Look forward in the node. // continue; } // // No overlap at all if the lock begins at a higher byte than the last of the range. // We already covered zero length locks (where this is true, and overlap could still // occur). // if ((ULONGLONG)Lock->LockInfo.StartingByte.QuadPart > (ULONGLONG)EndingByte->QuadPart) { return NULL; } // // Regular overlap has occured. Return this lock. // return Lock; } // // If we invoke this check and wander off the end of the node without determining what is // going on, something is terribly wrong. // ASSERT( FALSE ); return NULL; } 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; PLOCKTREE_NODE Node; SplayLinks = FsRtlFindFirstOverlappingSharedNode( LockQueue->SharedLockTree, Starting, Ending, &BeginLinks, NULL); if (BeginLinks) { LockQueue->SharedLockTree = RtlSplay(BeginLinks); } // // If this node is holey, we'll have to walk the whole thing. // if (SplayLinks) { Node = CONTAINING_RECORD( SplayLinks, LOCKTREE_NODE, Links ); if (Node->HoleyNode) { return (BOOLEAN)(FsRtlFindFirstOverlapInNode( Node, Starting, Ending ) == NULL); } // // Overlapping non-holey node, so we do have shared lock conflict. // return FALSE; } // // No node overlaps. // return TRUE; } 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 // FsRtlAcquireLockQueue(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 // FsRtlAcquireLockQueue(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. The arguments that supply the initial conditions for the operation are optional if the node is known to be holey. 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: BOOLEAN - True if the split was successful, False otherwise. The node will be marked as Holey if the split could not occur. --*/ { PSH_LOCK Lock; PLOCKTREE_NODE NewNode; PSINGLE_LIST_ENTRY Link, *pLink, *NextpLink; LARGE_INTEGER MaxOffset, StartOffset, HaltOffset; BOOLEAN ExtentValid; BOOLEAN FailedHoleySplit = FALSE; // // There are two cases: the node is holey or not. If the node is holey, at some // point we failed to get resources to complete a split, so despite our caller's // good intentions we need to go over the entire node. // if (ParentNode->HoleyNode) { // // Just move the starting link back to the front. The maximum offset and // starting offset of the node will be initialized in the loop. We also turn // off the holey flag, which will be turned on again as appropriate. // pStartLink = &ParentNode->Locks.Next; ParentNode->HoleyNode = FALSE; HaltOffset.QuadPart = ParentNode->Extent; } else { HaltOffset = *LastShadowedByte; MaxOffset = *GlueOffset; StartOffset.QuadPart = 0; if (!ParentNode->Locks.Next || (ULONGLONG)HaltOffset.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)HaltOffset.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. This is only an issue for zero length // locks, so that we can figure out what is going on if we split a node and wind // up with some number of "overlapped" zero length locks at the front of the new // node. We must be able to notice this case, and not think that each needs to // be in a seperate node. // 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 this is not a zero length node starting at the beginning of this node, // break the node. The second half of the test keeps co-incident zero length locks // in the same node. (zero length lock ---> starting = ending + 1). // if ((ULONGLONG)Lock->LockInfo.StartingByte.QuadPart > (ULONGLONG)MaxOffset.QuadPart && !(Lock->LockInfo.Length.QuadPart == 0 && Lock->LockInfo.StartingByte.QuadPart == StartOffset.QuadPart)) { // // Break the node up here // NewNode = FsRtlAllocateLockTreeNode(); if (NewNode == NULL) { // // If we are out of resources, this node is now holey - we know that the locks at // this node do not completely cover the indicated range. Keep splitting for two // reasons: more resources may become avaliable, and we must keep updating the // node's extent if it is known to be invalid. // // // Now if this node was already holey it is not possible to state that, if we // manage to split if further as we keep walking, that the resulting "left" node // is not holey. See below. // if (ParentNode->HoleyNode) { FailedHoleySplit = TRUE; } ParentNode->HoleyNode = TRUE; } else { // // Initialize the node. // RtlInitializeSplayLinks(&NewNode->Links); NewNode->HoleyNode = FALSE; // // 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; // // The parent node can no longer be holey if we have not failed a split in this node. // if (!FailedHoleySplit) { ParentNode->HoleyNode = FALSE; } else { // // So reset the failure flag for the new node. // FailedHoleySplit = FALSE; } // // Move over to the new node. // ParentNode = NewNode; continue; } } if (ExtentValid && (ULONGLONG)Lock->LockInfo.StartingByte.QuadPart > (ULONGLONG)HaltOffset.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 ); FsRtlAcquireLockQueue( 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 ); FsRtlAcquireLockQueue( 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; BOOLEAN ViaFastCall; BOOLEAN ReleaseQueue = FALSE; PLOCK_INFO LockInfo; PLOCK_QUEUE LockQueue; KIRQL OldIrql; FILE_LOCK_INFO FileLockInfo; DebugTrace(+1, Dbg, "FsRtlPrivateLock, FileLock = %08lx\n", FileLock); // // If the irp is null then this is being called via the fast call method. // ViaFastCall = (BOOLEAN) !ARGUMENT_PRESENT( Irp ); 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 (!FsRtlPrivateInitializeFileLock (FileLock, ViaFastCall)) { 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. // FsRtlAcquireLockQueue(LockQueue, &OldIrql); ReleaseQueue = TRUE; try { // // 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 // WaitingLock = FsRtlAllocateWaitingLock(); // // Simply raise out if we can't allocate. // if (WaitingLock == NULL) { ExRaiseStatus( STATUS_INSUFFICIENT_RESOURCES ); } WaitingLock->Irp = Irp; WaitingLock->Context = Context; WaitingLock->CompleteLockIrpRoutine = LockInfo->CompleteLockIrpRoutine; 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_PTR)LockInfo; IoSetCancelRoutine( Irp, FsRtlPrivateCancelFileLockIrp ); if (Irp->Cancel) { // // Pull the cancel routine off of the IRP - if it is not // NULL, this means we won the race with IoCancelIrp and // will be responsible for cancelling the IRP synchronously. // If NULL, we lost and our cancel routine is already being // called for us. // // This must be done while holding the lock queue down since // this is how we synchronize with the cancel. // if (IoSetCancelRoutine( Irp, NULL )) { // // Irp's cancel routine was not called, do it ourselves. // Indicate to the cancel routine that he does not need // to release the cancel spinlock by passing a NULL DO. // // The queue will be dropped in order to complete the Irp. // We communicate the previous IRQL through the Irp itself. // Irp->CancelIrql = OldIrql; FsRtlPrivateCancelFileLockIrp( NULL, Irp ); ReleaseQueue = FALSE; } } Iosb->Status = STATUS_PENDING; try_return( Results = TRUE ); } else { try_return( Results = FALSE ); } } DebugTrace(0, Dbg, "We have access\n", 0); if (!FsRtlPrivateInsertLock( LockInfo, FileObject, &FileLockInfo )) { // // Resource exhaustion will cause us to fail here. Via the fast call, indicate // that it may be worthwhile to go around again via the Irp based path. If we // are already there, simply raise out. // if (ViaFastCall) { try_return( Results = FALSE ); } else { ExRaiseStatus( STATUS_INSUFFICIENT_RESOURCES ); } } else { Iosb->Status = STATUS_SUCCESS; } // // At long last, we're done. // Results = TRUE; try_exit: NOTHING; } finally { if (ReleaseQueue) { FsRtlReleaseLockQueue(LockQueue, OldIrql); } // // Complete the request provided we were given one and it is not a pending status // if (!AbnormalTermination() && 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 // BOOLEAN 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: BOOLEAN - True if the insert was successful, False if no resources were avaliable to complete the operation. --*/ { // // Now add the lock to the appropriate tree. // if (FileLockInfo->ExclusiveLock) { PEX_LOCK ExLock; ExLock = FsRtlAllocateExclusiveLock(); if (ExLock == NULL) { return FALSE; } ExLock->LockInfo = *FileLockInfo; FsRtlPrivateInsertExclusiveLock( &LockInfo->LockQueue, ExLock ); FileObject->LastLock = &ExLock->LockInfo; } else { PSH_LOCK ShLock; ShLock = FsRtlAllocateSharedLock(); if (ShLock == NULL) { return FALSE; } ShLock->LockInfo = *FileLockInfo; if (!FsRtlPrivateInsertSharedLock( &LockInfo->LockQueue, ShLock )) { return FALSE; } FileObject->LastLock = &ShLock->LockInfo; } // // 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; } return TRUE; } // // Internal Support Routine // BOOLEAN 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: BOOLEAN - True if the insert was successful, False if no resources were avaliable to complete the operation. --*/ { 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 // NextNode = FsRtlAllocateLockTreeNode(); // // If no resources are avaliable, simply fail now. // if (NextNode == NULL) { return FALSE; } RtlInitializeSplayLinks(&NextNode->Links); NextNode->HoleyNode = FALSE; 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 TRUE; } // // Now we examine the node to see if it is holey as a result of a resource-failed split. // If it is, we must complete the split before adding the new lock. // Node = CONTAINING_RECORD( OverlappedSplayLinks, LOCKTREE_NODE, Links ); // // Search down the overlapped node finding the position for the new lock // for (pLink = &Node->Locks; (Link = pLink->Next) != NULL; pLink = Link) { PSH_LOCK Lock; Lock = CONTAINING_RECORD( Link, SH_LOCK, Link ); // // We sort locks on this list first by starting byte, then by whether the length is zero or not. // This is important so that zero length locks appear prior to non-zero length locks, so that // they are split out of nodes into the tree in the correct order. // // if (NewLock->StartingByte <= Lock->StartingByte) ... // if (((ULONGLONG)NewLock->LockInfo.StartingByte.QuadPart < (ULONGLONG)Lock->LockInfo.StartingByte.QuadPart) || ((ULONGLONG)NewLock->LockInfo.StartingByte.QuadPart == (ULONGLONG)Lock->LockInfo.StartingByte.QuadPart && (NewLock->LockInfo.Length.QuadPart == 0 || Lock->LockInfo.Length.QuadPart != 0))) { 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. Note that // this isn't so much a walk as a repeated examination of our successor's // until one does not overlap (or we hit the end). // 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; } // // If we are intergrating a holey node into a non-holey node, try to split // the node first. It will be better to get this done with a smaller node // than a big, fully integrated one. Note that we are guaranteed that the // node will remain a candidate for integration since the first lock on the // node will still be there, and overlaps. // if (!Node->HoleyNode && NextNode->HoleyNode) { FsRtlSplitLocks( NextNode, NULL, NULL, NULL ); } // // 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) { // // If the node we just swallowed was (still!) holey, we perhaps made this // node holey too. The resolution of this is left to the lock split we will // perform after integration is complete. // // Note that if the extent of the node we are swallowing is interior // to the current node, we just covered whatever holes it contained. // if (NextNode->HoleyNode) { Node->HoleyNode = TRUE; } Node->Extent = NextNode->Extent; } // // Free the now empty node. // RtlDeleteNoSplay( OverlappedSplayLinks, &LockQueue->SharedLockTree ); FsRtlFreeLockTreeNode( NextNode ); } } // // Now, perhaps this node is still holey. For grins lets try one more time to split // this thing apart. // if (Node->HoleyNode) { FsRtlSplitLocks( Node, NULL, NULL, NULL ); } // // And return to our caller // return TRUE; } // // 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: None. --*/ { PSINGLE_LIST_ENTRY *pLink, Link; NTSTATUS NewStatus; BOOLEAN Result; 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. // // No matter what happens, this Irp must be completed now - even if we // are resource starved. User mode deadlock could be induced since there // may no longer be a pending unlock to cause a rescan of the waiting // list. // if (AccessGranted) { DebugTrace(0, Dbg, "FsRtlCheckWaitingLocks now has access\n", 0); // // Clear the cancel routine // IoAcquireCancelSpinLock( &Irp->CancelIrql ); IoSetCancelRoutine( Irp, NULL ); // // If the IRP got itself cancelled, it is cancelled and we won't grant it. // The canceller is waiting for the queue spinlock right now. // if (Irp->Cancel) { AccessGranted = FALSE; } IoReleaseCancelSpinLock( Irp->CancelIrql ); if (AccessGranted) { Result = 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, (Result? STATUS_SUCCESS : STATUS_INSUFFICIENT_RESOURCES), &NewStatus, FileLockInfo.FileObject ); if (Result && !NT_SUCCESS(NewStatus)) { // // Irp was not sucessfull, remove lock if it was added. // FsRtlPrivateRemoveLock ( LockInfo, &FileLockInfo, FALSE ); } // // Drop our private reference to the fileobject // ObDereferenceObject( FileLockInfo.FileObject ); // // Re-acquire queue lock // FsRtlAcquireLockQueue( LockQueue, &OldIrql ); // // Start scan over from begining // pLink = &LockQueue->WaitingLocks.Next; // // Free up pool // FsRtlFreeWaitingLock( WaitingLock ); continue; } } 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 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); // // If this node is holey, we'll have to walk the whole thing. // if (Node->HoleyNode) { ShLock = FsRtlFindFirstOverlapInNode( Node, &FileLockInfo->StartingByte, &FileLockInfo->EndingByte ); } else { ShLock = CONTAINING_RECORD(Node->Locks.Next, SH_LOCK, Link); } // // Look for overlap that we care about. Perhaps no overlap existed in the holey case. // if (ShLock && (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 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 GlueOffset, EndingDeletedByte; 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 // FsRtlAcquireLockQueue( 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; EndingDeletedByte.QuadPart = 0; GlueOffset.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)EndingDeletedByte.QuadPart) { // // Need to remember where the last offset affected by deleted locks is // EndingDeletedByte.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)GlueOffset.QuadPart) { // // Save the max offset until we have deleted our first node // GlueOffset.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, &EndingDeletedByte, &GlueOffset); } } 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 ); FsRtlAcquireLockQueue( 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, Links ); 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 ); FsRtlAcquireLockQueue( 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 // IoAcquireCancelSpinLock( &WaitingIrp->CancelIrql ); IoSetCancelRoutine( WaitingIrp, NULL ); // // If this IRP got itself cancelled, it is cancelled. // if (WaitingIrp->Cancel) { WaitingIrp = NULL; } IoReleaseCancelSpinLock( WaitingIrp->CancelIrql ); if (WaitingIrp) { WaitingIrp->IoStatus.Information = 0; // // We have a match and the IRP, 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_RANGE_NOT_LOCKED, &NewStatus, NULL ); // // Reaqcuire lock queue spinlock and start over // FsRtlAcquireLockQueue( 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 ); // // and return to our caller // DebugTrace(-1, Dbg, "FsRtlFastUnlockAll -> VOID\n", 0); return STATUS_SUCCESS; } 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; BOOLEAN CollideCheck = FALSE; 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); // // Iterate through the lock queue. // LockQueue = &LockInfo->LockQueue; // // Release the cancel spinlock and lock cancel collide if this is initiated by Io. // // We already have the lock queue if this is the race fixup from ourselves, // and the cancel Irql is in the Irp. // if (DeviceObject) { IoReleaseCancelSpinLock( Irp->CancelIrql ); FsRtlAcquireCancelCollide( &OldIrql ); // // Indicate we will check the collide list first, as the lockqueue itself // may be deallocated in a race with lock teardown. // CollideCheck = TRUE; pLink = &FsRtlFileLockCancelCollideList.Next; } else { OldIrql = Irp->CancelIrql; // // We will iterate only the locks off of this specific queue. // pLink = &LockQueue->WaitingLocks.Next; } while (TRUE) { // // Iterate through the waiting locks looking for the canceled one. // 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 (!CollideCheck && Link == LockQueue->WaitingLocksTail.Next) { LockQueue->WaitingLocksTail.Next = (PSINGLE_LIST_ENTRY) pLink; } Irp->IoStatus.Information = 0; // // Release the right lock and complete this waiter // if (CollideCheck) { FsRtlReleaseCancelCollide( OldIrql ); } else { FsRtlReleaseLockQueue( LockQueue, OldIrql ); } // // Complete this waiter. Note we pick the completion routine out // of the waiting lock structure so we can surf over the collided // cancel case. // FsRtlCompleteLockIrp( WaitingLock, WaitingLock->Context, Irp, STATUS_CANCELLED, &NewStatus, NULL ); // // Free up pool // FsRtlFreeWaitingLock( WaitingLock ); // // Our job is done! // return; } // // Flip over to the lock queue if we didn't find it on the collided list. // if (CollideCheck) { CollideCheck = FALSE; FsRtlAcquireLockQueueAtDpc( LockQueue ); FsRtlReleaseCancelCollideFromDpc( OldIrql ); pLink = &LockQueue->WaitingLocks.Next; continue; } break; } // // Release lock queue. This must actually not happen or we will have the // potential to have had the IRP we were looking for come back unaware // we wanted to cancel it. // ASSERT( FALSE ); FsRtlReleaseLockQueue(LockQueue, OldIrql); return; }