You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
5959 lines
171 KiB
5959 lines
171 KiB
/*++
|
|
|
|
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;
|
|
|
|
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;
|
|
Length.QuadPart = (ULONGLONG)IrpSp->Parameters.Read.Length;
|
|
|
|
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;
|
|
Length.QuadPart = (ULONGLONG)IrpSp->Parameters.Write.Length;
|
|
|
|
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 = NULL, 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 = NULL, 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;
|
|
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;
|
|
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 = {0}, StartOffset = {0}, HaltOffset;
|
|
|
|
LOGICAL ExtentValid;
|
|
LOGICAL 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 SoloLock code.
|
|
//
|
|
|
|
UNREFERENCED_PARAMETER (AlreadySynchronized);
|
|
|
|
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 = FALSE;
|
|
BOOLEAN AccessGranted;
|
|
BOOLEAN ViaFastCall;
|
|
BOOLEAN ReleaseQueue;
|
|
|
|
PLOCK_INFO LockInfo;
|
|
PLOCK_QUEUE LockQueue;
|
|
KIRQL OldIrql;
|
|
FILE_LOCK_INFO FileLockInfo;
|
|
|
|
UNREFERENCED_PARAMETER (AlreadySynchronized);
|
|
|
|
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;
|
|
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.
|
|
//
|
|
|
|
ReleaseQueue = TRUE;
|
|
FsRtlAcquireLockQueue(LockQueue, &OldIrql);
|
|
|
|
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;
|
|
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 = NULL;
|
|
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;
|
|
KIRQL CancelIrql;
|
|
|
|
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.
|
|
//
|
|
|
|
CancelIrql = WaitingIrp->CancelIrql;
|
|
|
|
if (WaitingIrp->Cancel) {
|
|
|
|
WaitingIrp = NULL;
|
|
}
|
|
|
|
IoReleaseCancelSpinLock( 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;
|
|
}
|
|
|